In world of Java you have JMX to address most of it... but what can we use in Python? While working under Scheduler [1], I found that the easiest way to implement "Python MX" is to use werkzeug [2].
So, what do we need to get it working? First of all, we need HTML template (here, we arranged output in two columns):
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?xml version="1.0" encoding="UTF-8"?> | |
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" | |
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> | |
<html xmlns="http://www.w3.org/1999/xhtml"> | |
<body> | |
<table style="width: 100%"> <tbody> | |
{%- for row in details.entries %} | |
<tr> | |
<td>{{ row[0] }}</td> | |
<td>{{ row[1] }}</td> | |
</tr> | |
{%- endfor %} | |
</tbody> </table> | |
</body> | |
</html> |
Next, we need to expose URL for the template:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from werkzeug.utils import cached_property, redirect | |
from werkzeug.wrappers import Response | |
from utils import render_template, expose, jinja_env | |
@expose('/') | |
@expose('/example_details/') | |
def example_details(request): | |
details = ExampleDetails(jinja_env.globals['mbean']) | |
return render_template('template_example.html', details=details) | |
def not_found(request): | |
return render_template('not_found.html') | |
# And implement details themselves: | |
class ExampleDetails(object): | |
def __init__(self, mbean): | |
self.mbean = mbean | |
@cached_property | |
def entries(self): | |
return [[self.mbean.property_1, self.mbean.property_2]] |
Now, all we left to do is - start the MX and set the Bean:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class MBeanExample(object): | |
def __init__(self): | |
self.property_1 = 'this is' | |
self.property_2 = 'example' | |
def start_mx(self): | |
""" import MX module (which has back-reference import to self) and start it """ | |
from mx.mx import MX | |
self.mx = MX(self) | |
self.mx.start_mx_thread() | |
if __name__ == '__main__': | |
source = MBeanExample() | |
source.start_mx() |
Where MX is:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from threading import Thread | |
from werkzeug.wrappers import Request | |
from werkzeug.wsgi import ClosingIterator, SharedDataMiddleware | |
from werkzeug.exceptions import HTTPException, NotFound | |
from werkzeug.serving import run_simple | |
from utils import STATIC_PATH, local, local_manager, url_map, jinja_env | |
import views | |
class MX(object): | |
def __init__(self, mbean): | |
local.application = self | |
self.mbean = mbean | |
jinja_env.globals['mbean'] = mbean | |
self.dispatch = SharedDataMiddleware(self.dispatch, { | |
'/static': STATIC_PATH | |
}) | |
def dispatch(self, environ, start_response): | |
local.application = self | |
request = Request(environ) | |
local.url_adapter = adapter = url_map.bind_to_environ(environ) | |
try: | |
endpoint, values = adapter.match() | |
handler = getattr(views, endpoint) | |
response = handler(request, **values) | |
except NotFound as e: | |
response = views.not_found(request) | |
response.status_code = 404 | |
except HTTPException as e: | |
response = e | |
return ClosingIterator(response(environ, start_response), | |
[local_manager.cleanup]) | |
def __call__(self, environ, start_response): | |
return self.dispatch(environ, start_response) | |
def start_mx_thread(self, hostname = None, port = None): | |
"""Spawns a new HTTP server, residing on defined hostname and port | |
:param hostname: the default hostname the server should listen on. | |
:param port: the default port of the server. | |
""" | |
hostname = '0.0.0.0' | |
port = 5000 | |
reloader = False # use_reloader: the default setting for the reloader. | |
debugger= False # | |
evalex=True # use_evalex: the default setting for the evalex flag of the debugger. | |
threaded=False # threaded: the default threading setting. | |
processes=1 # processes: the default number of processes to start. | |
reloader_interval = 1 | |
static_files=None # static_files: optional dict of static files. | |
extra_files=None # extra_files: optional list of extra files to track for reloading. | |
ssl_context=None # ssl_context: optional SSL context for running server in HTTPS mode. | |
mx_thread = Thread(target=run_simple(hostname = hostname, | |
port = port, | |
application = self, | |
use_debugger = debugger, | |
use_evalex = evalex, | |
extra_files = extra_files, | |
use_reloader = reloader, | |
reloader_interval = reloader_interval, | |
threaded = threaded, | |
processes = processes, | |
static_files=static_files, | |
ssl_context=ssl_context)) | |
mx_thread.daemon = True | |
mx_thread.start() |
And utils.py looks like:
As soon as coding is complete navigate in your browser to "localhost:5000":
And with some efforts it can turn to:
All things considered - Python MX is not a trivial hack, however not a paramount either. With great tools like Werkzeug our life is much easier.
For real-world example, please refer to Scheduler MX folder [3].
[1] Scheduler
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from werkzeug.utils import cached_property, redirect | |
from werkzeug.wrappers import Response | |
from utils import render_template, expose, jinja_env | |
@expose('/') | |
@expose('/example_details/') | |
def example_details(request): | |
details = ExampleDetails(jinja_env.globals['mbean']) | |
return render_template('template_example.html', details=details) | |
def not_found(request): | |
return render_template('not_found.html') | |
# And implement details themselves: | |
class ExampleDetails(object): | |
def __init__(self, mbean): | |
self.mbean = mbean | |
@cached_property | |
def entries(self): | |
return [[self.mbean.property_1, self.mbean.property_2]] |
As soon as coding is complete navigate in your browser to "localhost:5000":
And with some efforts it can turn to:
All things considered - Python MX is not a trivial hack, however not a paramount either. With great tools like Werkzeug our life is much easier.
For real-world example, please refer to Scheduler MX folder [3].
[1] Scheduler
[2] Werkzeug