Skip to content

Commit

Permalink
Basic steps to allow multiple configurations in one module.
Browse files Browse the repository at this point in the history
  • Loading branch information
idlesign committed Oct 11, 2017
1 parent 7ee7360 commit d837c79
Show file tree
Hide file tree
Showing 10 changed files with 112 additions and 85 deletions.
58 changes: 36 additions & 22 deletions README.rst
Expand Up @@ -51,19 +51,11 @@ By that time you already know that **uwsgiconf** is just another configuration m
.. _Why: http://uwsgi-docs.readthedocs.io/en/latest/FAQ.html#why-do-you-support-multiple-methods-of-configuration


Usage Strategies
----------------
Overview
--------

Two main strategies to use **uwsgiconf**:

1. **Static:** create configuration ``.py`` and compile it when you like into classic uWSGI ``.ini`` using provided methods.
2. **Dynamic:** create configuration .py, and give it directly to uWSGI with ``exec`` directive.

**uwsgiconf** CLI has tools to streamline both of these.


A taste of it
-------------
Static configuration
~~~~~~~~~~~~~~~~~~~~

Let's make ``uwsgicfg.py``. There we configure it using nice ``PythonSection`` preset to run our web app.

Expand All @@ -80,29 +72,51 @@ Let's make ``uwsgicfg.py``. There we configure it using nice ``PythonSection`` p
# Make app available on http://127.0.0.1:8000
PythonSection.networking.sockets.http('127.0.0.1:8000'),
).as_configuration()
)
configuration.print_ini()
1. Now if you want to generate ``myconf.ini`` file and use it for uWSGI you can do it with:
1. Now if you want to generate ``myconf.ini`` file and use it for uWSGI manually you can do it with:

.. code-block:: bash
$ python uwsgicfg.py > myconf.ini
; or just
$ uwsgiconf compile > myconf.ini
$ uwsgi myconf.ini
2. Or for dynamic usage of .py:
2. Or use ``uwsgiconf`` to automatically spawn uWSGI processes for configurations defined in your module:

.. code-block:: bash
$ uwsgi --ini "exec://python uwsgicfg.py"
; or just
$ uwsgiconf run
**Note:** ``uwsgiconf`` CLI requires ``click`` package available.


Runtime configuration
~~~~~~~~~~~~~~~~~~~~~

**uwsgiconf** comes with ``runtime`` package which is similar to **uwsgidecorators** but offers different abstractions.

These abstractions will also use a stub ``uwsgi`` module when if the real one is not available.

A couple of examples:

.. code-block:: python
from uwsgiconf.runtime.locking import lock
from uwsgiconf.runtime.scheduling import register_timer_rb
@register_timer_rb(10, repeat=2)
def repeat_twice():
"""This function will be called twice with 10 seconds interval
(by default in in first available mule) using red-black tree based timer.
"""
with lock():
# Code under this context manager will be locked using default (0) uWSGI lock.
do()
Documentation
-------------

Expand Down
16 changes: 8 additions & 8 deletions demos/onefile.py
Expand Up @@ -16,7 +16,7 @@ def application(env, start_response):

from functools import partial
import random
from uwsgiconf import uwsgi # This will be available under uWSGI.
from uwsgiconf.runtime.environ import uwsgi_env

start_response('200 OK', [('Content-Type','text/html')])

Expand All @@ -25,8 +25,8 @@ def application(env, start_response):

'<div>Some random number for you: %s</div>' % random.randint(1, 99999),

'<div>uWSGI version: %s</div>' % uwsgi.version.decode('utf8'),
'<div>uWSGI request ID: %s</div>' % uwsgi.request_id(),
'<div>uWSGI version: %s</div>' % uwsgi_env.get_version(),
'<div>uWSGI request ID: %s</div>' % uwsgi_env.request.id,
]

return map(partial(bytes, encoding='utf8'), data)
Expand All @@ -39,7 +39,7 @@ def configure():

FILE = os.path.abspath(__file__)

PythonSection(
section = PythonSection(
# Automatically reload uWSGI if this file is changed.
touch_reload=FILE,

Expand All @@ -53,9 +53,9 @@ def configure():
).networking.register_socket(
PythonSection.networking.sockets.http('127.0.0.1:8000'),

).as_configuration().print_ini()
)

return section

if __name__ == '__main__':
# It seems we're asked for a configuration file.
configure()

configuration = configure()
4 changes: 2 additions & 2 deletions docs/source/hints.rst
@@ -1,5 +1,5 @@
Tips and hints
==============
FAQ
===


How to get through
Expand Down
4 changes: 2 additions & 2 deletions docs/source/index_api_static.rst
@@ -1,5 +1,5 @@
Configuration [Dynamic]
=======================
Configuration [Static]
======================

.. toctree::
:maxdepth: 3
Expand Down
38 changes: 9 additions & 29 deletions docs/source/quickstart.rst
@@ -1,30 +1,6 @@
Quickstart
==========

Usage Strategies
----------------

Two main strategies to use **uwsgiconf**:

1. **Static:** create configuration .py and compile it on demand into classic uWSGI .ini using provided methods.

.. code-block:: bash
$ python uwsgicfg.py > myconf.ini
; or just
$ uwsgiconf compile > myconf.ini
$ uwsgi myconf.ini
2. **Dynamic:** create configuration .py, and give it directly to uWSGI with ``exec`` directive.

.. code-block:: bash
$ uwsgi --ini "exec://python uwsgicfg.py"
; or just
$ uwsgiconf run
Using a preset to run Python web application
--------------------------------------------

Expand Down Expand Up @@ -56,12 +32,18 @@ Let's make ``uwsgicfg.py``. There we configure it using nice ``PythonSection`` p
# Make app available at http://127.0.0.1:8000
PythonSection.networking.sockets.http('127.0.0.1:8000'),
).as_configuration()
)
configuration.print_ini()
Now we are ready to use this configuration:

Now we are ready to use this configuration dynamically (see ``Strategies`` paragraph above).
.. code-block:: bash
$ uwsgiconf compile > myconf.ini
$ uwsgi myconf.ini
; or instead just
$ uwsgiconf run
Configuration with multiple sections
Expand Down Expand Up @@ -108,5 +90,3 @@ Let's configure uWSGI to use Emperor Broodlord mode as described here_.
master_process.set_idle_params(timeout=30, exit=True)
])
configuration.print_ini()
8 changes: 5 additions & 3 deletions tests/confs/dummy.py
Expand Up @@ -8,9 +8,11 @@
not_conf2 = [1, 2]

configuration = [

Configuration([
Section('conf1_1'),
Section(),
Section('conf1_2').env('A', 'B')
]),
Section('conf2_1'),
], alias='uwsgicgf_test1'),

Section().env('D', 'E'),
]
9 changes: 5 additions & 4 deletions tests/test_utils.py
Expand Up @@ -88,20 +88,21 @@ def test_conf_module_compile():

out = '\n'.join(out)

assert 'conf1_1' in out
assert 'conf1_2' in out
assert 'conf2_1' in out
assert '[uwsgi]' in out
assert '[conf1_2]' in out
assert 'D=E' in out


def test_conf_module_run(monkeypatch):
executed = []
monkeypatch.setattr(os, 'execvp', lambda *args: executed.append(True))
monkeypatch.setattr(os, 'spawnvp', lambda *args: executed.append(True))

fpath = os.path.join(os.path.dirname(__file__), 'confs', 'dummy.py')

module = ConfModule(fpath)
module.spawn_uwsgi()

assert len(executed) == 2
assert all(executed)


Expand Down
8 changes: 6 additions & 2 deletions uwsgiconf/cli.py
Expand Up @@ -34,13 +34,17 @@ def base():

@base.command()
@arg_conf
def run(conf):
@click.option('--only', help='Configuration alias from module to run uWSGI with.')
def run(conf, only):
"""Runs uWSGI passing to it using the default or another `uwsgiconf` configuration module.
"""
with errorprint():
config = ConfModule(conf)
config.spawn_uwsgi()
spawned = config.spawn_uwsgi(only)

for alias, pid in spawned:
click.secho("Spawned uWSGI for '%s' configuration. PID %s" % (alias, pid), fg='green')


@base.command()
Expand Down
2 changes: 2 additions & 0 deletions uwsgiconf/runtime/environ.py
Expand Up @@ -9,6 +9,8 @@ class _Environment(object):

# todo slots

request = None # type: _Request

hostname = uwsgi.hostname # type: str
"""Current host name."""

Expand Down
50 changes: 37 additions & 13 deletions uwsgiconf/utils.py
Expand Up @@ -15,6 +15,10 @@
from .exceptions import ConfigurationError


if False: # pragma: nocover
from uwsgiconf.config import Configuration


PY3 = sys.version_info[0] == 3

if PY3: # pragma: nocover
Expand Down Expand Up @@ -74,13 +78,28 @@ def __init__(self, fpath):
self.fpath = fpath
self._confs = None

def spawn_uwsgi(self):
"""Spawns uWSGI process wich will use current configuration module.
def spawn_uwsgi(self, only=None):
"""Spawns uWSGI process(es) which will use configuration(s) from the module.
Returns list of tuples:
(configuration_alias, uwsgi_process_id)
.. note:: uWSGI process will replace ``uwsgiconf`` process.
:param str|unicode only: Configuration alias to run from the module.
If not set uWSGI will be spawned for every configuration found in the module.
:rtype: list
"""
UwsgiRunner().spawn(self.fpath)
spawned = []

for config in self.configurations: # type: Configuration
alias = config.alias

if only is None or alias == only:
filepath = config.tofile()
pid = UwsgiRunner().spawn(filepath)
spawned.append((alias, pid))

return spawned

@property
def configurations(self):
Expand All @@ -100,14 +119,14 @@ def configurations(self):

if attr is None:
raise ConfigurationError(
"'configuration' attribute not found [ %s ]" % fpath)
"'configuration' attribute not found. File: %s" % fpath)

if callable(attr):
attr = attr()

if not attr:
raise ConfigurationError(
"'configuration' attribute is empty [ %s ]" % fpath)
"'configuration' attribute is empty. File: %s" % fpath)

if not isinstance(attr, (list, tuple)):
attr = [attr]
Expand All @@ -125,7 +144,7 @@ def configurations(self):

if not confs:
raise ConfigurationError(
"'configuration' attribute must hold either 'Section' or 'Configuration' objects [ %s ]" % fpath)
"'configuration' attribute must hold either 'Section' or 'Configuration' objects. File: %s" % fpath)

self._confs = confs

Expand Down Expand Up @@ -280,15 +299,20 @@ def prepare_env(cls):

return os.path.basename(python_binary)

def spawn(self, module_path):
"""Spawns uWSGI using the given configuration module.
.. note:: uWSGI process will replace ``uwsgiconf`` process.
def spawn(self, filepath):
"""Spawns uWSGI using the given configuration file (python module or .ini file).
:param str|unicode module_path:
:param str|unicode filepath:
"""
os.execvp('uwsgi', ['uwsgi', '--ini', 'exec://%s %s' % (self.binary_python, module_path)])
if filepath.endswith('.ini'):
target = filepath

else:
# todo Use this to get more.
target = 'exec://%s %s' % (self.binary_python, filepath)

return os.spawnvp(os.P_NOWAIT, 'uwsgi', ['uwsgi', '--ini', target])


def parse_command_plugins_output(out):
Expand Down

0 comments on commit d837c79

Please sign in to comment.