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

simpl server #82

Merged
merged 28 commits into from
Mar 16, 2016
Merged

simpl server #82

merged 28 commits into from
Mar 16, 2016

Conversation

stavxyz
Copy link
Contributor

@stavxyz stavxyz commented Sep 1, 2015

This change installs a simpl command into your environment during package installation using the console_scripts entry point. The first available subcommand is server. You can now use simpl server to quickly run your app.

The interface is similar to that of bottle's "command line interface".

The simpl/server boilerplate and interface can be used a few different ways:

1. Point simpl server to your routes

Example:

# test_app.py

import bottle

@bottle.get('/')
def hello():
    return 'Hello World!'

Run the command

simpl server -a test_app -p 8888
Bottle v0.12.8 server starting up (using XTornadoServer())...
Listening on http://127.0.0.1:8888/
Hit Ctrl-C to quit.

test it...

you@localhost $ http get localhost:8888
HTTP/1.1 200 OK
Content-Length: 12
Content-Type: text/html; charset=UTF-8
Server: TornadoServer/4.2

Hello World!

Of course, this might not be how you choose to continue as your app matures

2. Attach simpl's server subcommand to your parser

Admittedly, this technique is a little strange, and the code must be in the exact order as written below. But it works.

import argparse

from simpl import server


parser = argparse.ArgumentParser()
server_parser = server.attach_parser(parser.add_subparsers())
server_parser.set_defaults(func=server.run)
args = awesome_parser.parse_args()

conf = config.init(options=server.OPTIONS)
conf.parse()
args._func(conf)

3. Just use the server.OPTIONS, and run bottle however you want.

import bottle
from simpl import server

conf = server.CONFIG
conf.parse()

b = bottle.Bottle()
# add middleware, routes to Bottle instance
conf['app'] = b
server.run(conf)

--help

you@localhost$ simpl server --help
usage: simpl server [-h] [--ini PATH] [--app APP] [--host HOST] [--port PORT]
                    [--server SERVER] [--debug-server] [--quiet-server]
                    [--no-reloader] [--interval INTERVAL]
                    [--adapter-options [ADAPTER_OPTIONS [ADAPTER_OPTIONS ...]]]

optional arguments:
  -h, --help            show this help message and exit

initialization (metaconfig) arguments:
  evaluated first and can be used to source an entire config

  --ini PATH            Source some or all of the options from this ini file.

Server Options:
  --app APP, -a APP     WSGI application to load by name.
                        Ex: package.module gets the module
                            package.module:name gets the variable 'name'
                            package.module.func() calls func() and gets the result
  --host HOST           Server address to bind to. (default: 127.0.0.1)
  --port PORT, -p PORT  Server port to bind to. (default: 8080)
  --server SERVER, -s SERVER
                        Server adapter to use. To see more, run:
                        `python -c "import bottle;print(bottle.server_names.keys())"`
                         (default: xtornado)
  --debug-server        Run bottle server with debug=True which is useful
                        for development or troubleshooting. Warning: This
                        may expose raw tracebacks and unmodified error
                        messages in responses! Note: this is not an option
                        to configure DEBUG level logging. (default: False)
  --quiet-server        Suppress bottle's output to stdout and stderr,
                        e.g. "Bottle v0.12.8 server starting up..." and
                        others. (default: False)
  --no-reloader         Disable bottle auto-reloading server, which
                        automatically restarts the server when file
                        changes are detected. Note: some server adapters,
                        such as eventlet, do not support auto-reloading. (default: False)
  --interval INTERVAL, -i INTERVAL
                        Auto-reloader interval in seconds (default: 1)
  --adapter-options [ADAPTER_OPTIONS [ADAPTER_OPTIONS ...]], -o [ADAPTER_OPTIONS [ADAPTER_OPTIONS ...]]
                        Key-value pairs separated by '=' to be passed to
                        the underlying server adapter, e.g. XEventletServer,
                        and are mapped to the adapter's self.options
                        instance attribute. Example usage:
                          simpl server -s xeventlet -o keyfile=~/mykey ciphers=GOST94

@stavxyz stavxyz force-pushed the simpl-server branch 2 times, most recently from 966f732 to e773f61 Compare September 1, 2015 19:32
@stavxyz stavxyz changed the title simpl server WIP: simpl server Sep 1, 2015
@stavxyz stavxyz force-pushed the simpl-server branch 6 times, most recently from a0e31e3 to c4c48b9 Compare September 2, 2015 20:07
@stavxyz stavxyz changed the title WIP: simpl server simpl server Sep 2, 2015
@stavxyz stavxyz force-pushed the simpl-server branch 3 times, most recently from 26d8158 to dbb4931 Compare September 2, 2015 20:23
@stavxyz stavxyz mentioned this pull request Sep 2, 2015
3 tasks
@ryandub
Copy link
Contributor

ryandub commented Sep 2, 2015

+1 from me!

@ziadsawalha
Copy link
Contributor

Nice! A couple of notes:

  • could you add docs to the README?
  • Is it possible call it from a project's entry point instead of using the simpl commandline and entry point? I.e. right now you show how to use the simpl command-line separately and pass it any entry point (could be anything, not even in the project that is using simpl). But for many of our projects we use simpl.config and have many options that we control from inside the project (like customizing the program name) and we have project-specific entry points. For those projects, we might want to append a set of server options and then expose the start command from the project itself. For example:
"""Project Awesome."""

from simpl import config
from simpl import log
from simpl import server

SIMPL_OPTIONS = log.OPTIONS + server.OPTIONS
OPTIONS = MY_OPTIONS + SIMPL_OPTIONS

if __name__ == "__main__":
    conf = config.init(options=OPTIONS)
    log.configure(conf)
    conf.parse()
    if conf.command == "server":
        server.run(name="awesome", conf)
    else:
        do_something_else()

So the command would be similar to what you have in the PR description, but would be the project entry point, no simpl's:

$ awesome server --help
usage: awesome server [-h] [--ini PATH] [--app APP] [--host HOST] [--port PORT]
                    [--server SERVER] [--debug-server] [--quiet-server]
                    [--no-reloader] [--interval INTERVAL]
                    [--adapter-options [ADAPTER_OPTIONS [ADAPTER_OPTIONS ...]]]

optional arguments:
  -h, --help            show this help message and exit

initialization (metaconfig) arguments:
  evaluated first and can be used to source an entire config

  --ini PATH            Source some or all of the options from this ini file.

Server Options:
  --app APP, -a APP     WSGI application to load by name.
                        Ex: package.module gets the module
                            package.module:name gets the variable 'name'
                            package.module.func() calls func() and gets the result
  --host HOST           Server address to bind to. (default: 127.0.0.1)
  --port PORT, -p PORT  Server port to bind to. (default: 8080)
  --server SERVER, -s SERVER
                        Server adapter to use. To see more, run:
                        `python -c "import bottle;print(bottle.server_names.keys())"`
                         (default: xtornado)
  --debug-server        Run bottle server with debug=True which is useful
                        for development or troubleshooting. Warning: This
                        may expose raw tracebacks and unmodified error
                        messages in responses! Note: this is not an option
                        to configure DEBUG level logging. (default: False)
  --quiet-server        Suppress bottle's output to stdout and stderr,
                        e.g. "Bottle v0.12.8 server starting up..." and
                        others. (default: False)
  --no-reloader         Disable bottle auto-reloading server, which
                        automatically restarts the server when file
                        changes are detected. Note: some server adapters,
                        such as eventlet, do not support auto-reloading. (default: False)
  --interval INTERVAL, -i INTERVAL
                        Auto-reloader interval in seconds (default: 1)
  --adapter-options [ADAPTER_OPTIONS [ADAPTER_OPTIONS ...]], -o [ADAPTER_OPTIONS [ADAPTER_OPTIONS ...]]
                        Key-value pairs separated by '=' to be passed to
                        the underlying server adapter, e.g. XEventletServer,
                        and are mapped to the adapter's self.options
                        instance attribute. Example usage:
                          simpl server -s xeventlet -o keyfile=~/mykey ciphers=GOST94

If that is possible, could you show an example of you recommend it be done? If not, can we explore making that possible?

@stavxyz
Copy link
Contributor Author

stavxyz commented Sep 3, 2015

@ziadsawalha Thanks for the comments.

Thinking/coding out loud...

"""Project Awesome."""

from simpl import config
from simpl import log
from simpl import server
from simpl.utils import cli as cli_utils

SIMPL_OPTIONS = log.OPTIONS + server.OPTIONS
OPTIONS = MY_OPTIONS + SIMPL_OPTIONS

# Where MY_OPTIONS have some server options
# with group='Server Options'

# Also, project awesome defines the `awesome`
# console_scripts entry_point which points to
# this file's main()

def main():

    awesome_parser = cli_utils.HelpfulParser(prog='awesome')
    server_parser = server.attach_parser(awesome_parser.add_subparsers())
    server_parser.set_defaults(_func=server.run)
    args = awesome_parser.parse_args()

    conf = config.init(options=OPTIONS)
    conf.parse()
    args._func(conf)


if __name__ == "__main__":

    main()

Maybe we could improve on this?

@ziadsawalha
Copy link
Contributor

Where MY_OPTIONS have some server options with group='Server Options

Is that needed if we provide server.OPTIONS in simpl? See SIMPL_OPTIONS = log.OPTIONS + server.OPTIONS

@stavxyz
Copy link
Contributor Author

stavxyz commented Sep 3, 2015

@ziadsawalha it's not needed, but it would allow your app's options to be grouped with the server options provided by simpl in the help/usage output.

@stavxyz stavxyz force-pushed the simpl-server branch 2 times, most recently from 0a46beb to 4333d7f Compare September 3, 2015 23:36
group='Server Options',
),
config.Option(
'--debug-server',
Copy link
Contributor

Choose a reason for hiding this comment

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

I noticed that you changed this from --debug\-d to --debug-server. Why not just have it enabled when --debug\-d is supplied? I'm not sure I see the need/benefit for --debug\-d and --debug-server being separate.

--debug\-d already generates gobs of data (as might be expected from a debug mode), so having it output traces in bottle also seems acceptable. Note: for the record, --verbose is how to log heavily but not dump gobs of data out.

I do recognize the challenge of addressing the duplicate --debug\-d option in server.OPTIONS and log.OPTIONS if we did that. IMO, we should try to solve that and keep only one -d flag.

@stavxyz
Copy link
Contributor Author

stavxyz commented Sep 4, 2015

@ziadsawalha I am not sure how to handle the conflicting options. I believe --debug and --debug-server should mean different things, so I am not sure about merging them. And if I did merge them, where would the option live? In server.py or log.py ? What would the description be? Or in a common options module?

I think --debug-server WRT the bottle server should (read: will eventually) mean that the traceback will be returned in the json payload if FormatExceptionMiddleware catches it. See #83

@ziadsawalha
Copy link
Contributor

To resolve the conflict, --debug\-d could move into config.py and become a built-in option in config.py. If putting simpl in debug mode starts to mean more than just logging, then it would no longer be appropriate for that option to be in log.py.

I believe --debug and --debug-server should mean different things

So would we have --debug-db, --debug-config, etc... for each module?

@ziadsawalha
Copy link
Contributor

Thanks for documenting "Attach simpl's server subcommand to your parser". Could you add that markdown to the code in the PR as well? Either in the server module docstrings or a readme? It should end up being the canonical "how to use this" text.

def test_simpl_server(self):
argv = ['server', '--quiet-server', '--port', str(get_free_port())]
proc = multiprocessing.Process(
target=simpl_cli.main, kwargs={'argv': argv})
Copy link
Contributor

Choose a reason for hiding this comment

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

I struggled with this in https://github.com/checkmate/simpl/blob/master/tests/test_server.py#L109. I think this might not get correctly reflected in in test coverage.

This allows simpl.config user to override the use
of the default argparse.ArgumentParser
If options is not explicitly supplied to build_parser(),
use self._options and some additional logic for suppressing
defaults with a value of None.
--debug was an existing option in log.OPTIONS, so
the debug flag for the server was changed to
--debug-server.

--quiet was an existing option in log.OPTIONS, so
the quiet flag for the server was changed to
--quiet-server
Using the utils.cli.kwargs argument type, you
can pass a string like

"hello=world new=toys for=sure"
Example:

  simpl server -s xeventlet -o keyfile=~/mykey ciphers=GOST94
In addition to configuring log format and log level, the use
of --debug will also set the bottle server to debug mode, which
will return tracebacks in the response body if an unexpected error
occurs.

If --quiet is used, in addition to configuring a minimal log format
and WARNING log level, the routes and other startup text is not printed
to stdout.
...unless quiet mode is on.

This also changes the startup process to convert a
string (e.g. mypackage.mywsgiapp) into
a wsgi app identical to the method used by bottle.
Refactors server.py to add the build_app() function
which can be used to get a wsgi-compliant app to
be ran by something other than simpl or bottle.
In order for the pipes not to block, this needs
to be run with eventlet or gevent monkey patching.
@stavxyz stavxyz force-pushed the simpl-server branch 2 times, most recently from 6cb58e4 to 245521a Compare March 16, 2016 18:12
stavxyz added a commit that referenced this pull request Mar 16, 2016
@stavxyz stavxyz merged commit 03975cf into rackerlabs:master Mar 16, 2016
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.

None yet

3 participants