├── java ├── testjvm │ ├── .gitignore │ ├── .classpath │ ├── .project │ ├── pom.xml │ └── src │ │ └── main │ │ └── java │ │ └── org │ │ └── jolokia │ │ └── jmx4py │ │ └── testjvm │ │ └── App.java └── jolokia.properties ├── requirements.txt ├── docs ├── _static │ ├── jmx4py-logo-48.png │ ├── jmx4py-logo-64.png │ └── jmx4py-logo.svg ├── index.rst └── conf.py ├── test-requirements.txt ├── debian └── changelog ├── MANIFEST.in ├── dev-requirements.txt ├── .project ├── .pydevproject ├── src ├── tests │ ├── logging.cfg │ ├── test_package.py │ ├── markers.py │ ├── test_util_network.py │ ├── conftest.py │ ├── test_cli.py │ └── test_jolokia_client.py └── jmx4py │ ├── util │ ├── __init__.py │ ├── auxiliary.py │ └── network.py │ ├── jolokia │ ├── __init__.py │ ├── errors.py │ ├── connection.py │ └── client.py │ ├── __init__.py │ ├── __main__.py │ └── _compat.py ├── .travis.yml ├── project.d ├── README.md ├── cookiecutter.json ├── classifiers.txt ├── skeleton_module.py ├── coverage.cfg ├── skeleton_testmodule.py └── pylint.cfg ├── tox.ini ├── setup.cfg ├── .gitignore ├── tasks.py ├── .env ├── README.md ├── CONTRIBUTING.md ├── _pavement_py ├── setup.py └── LICENSE /java/testjvm/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # 2 | # Install requirements 3 | # 4 | 5 | click>=3.3,<4 6 | colorama 7 | -------------------------------------------------------------------------------- /docs/_static/jmx4py-logo-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhermann/jmx4py/HEAD/docs/_static/jmx4py-logo-48.png -------------------------------------------------------------------------------- /docs/_static/jmx4py-logo-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhermann/jmx4py/HEAD/docs/_static/jmx4py-logo-64.png -------------------------------------------------------------------------------- /test-requirements.txt: -------------------------------------------------------------------------------- 1 | # 2 | # Test requirements 3 | # 4 | 5 | pytest==2.6.4 6 | pytest-spec==0.2.24 7 | pytest-cov==1.8.1 8 | sh==1.11 9 | -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | jmx4py (0.1) lucid; urgency=low 2 | 3 | * Initial release. 4 | 5 | -- Juergen Hermann Thu, 22 Sep 2011 17:46:24 +0200 6 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | # 2 | # Source Distribution Manifest 3 | # 4 | # More at https://docs.python.org/2/distutils/sourcedist.html 5 | 6 | include README.md LICENSE requirements.txt *-requirements.txt 7 | recursive-include project.d *.py *.md *.txt *.cfg 8 | recursive-include debian changelog 9 | -------------------------------------------------------------------------------- /dev-requirements.txt: -------------------------------------------------------------------------------- 1 | # 2 | # Development requirements 3 | # 4 | 5 | invoke==0.9.0 6 | #rituals==0.3.0 7 | -e git+https://github.com/jhermann/rituals.git#egg=rituals 8 | 9 | pylint==1.4.1 10 | bpython==0.13.1 11 | yolk3k==0.8.6 12 | 13 | tox==1.8.1 14 | twine==1.5.0 15 | 16 | Sphinx == 1.1.3 17 | docutils >= 0.11 18 | 19 | -r test-requirements.txt 20 | -r requirements.txt 21 | -e . 22 | -------------------------------------------------------------------------------- /java/testjvm/.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | jmx4py 4 | 5 | 6 | 7 | 8 | 9 | org.python.pydev.PyDevBuilder 10 | 11 | 12 | 13 | 14 | 15 | org.python.pydev.pythonNature 16 | 17 | 18 | -------------------------------------------------------------------------------- /.pydevproject: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | venv 6 | python 2.6 7 | 8 | /jmx4py/src 9 | 10 | 11 | -------------------------------------------------------------------------------- /java/testjvm/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | jmx4py-testjvm 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | 15 | org.eclipse.jdt.core.javanature 16 | 17 | 18 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. jmx4py documentation master file, created by 2 | sphinx-quickstart on Thu Nov 3 18:52:57 2011. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to jmx4py's documentation! 7 | ================================== 8 | 9 | Contents: 10 | 11 | .. toctree:: 12 | :maxdepth: 2 13 | 14 | 15 | 16 | Indices and tables 17 | ================== 18 | 19 | * :ref:`genindex` 20 | * :ref:`modindex` 21 | * :ref:`search` 22 | -------------------------------------------------------------------------------- /src/tests/logging.cfg: -------------------------------------------------------------------------------- 1 | # Logging configuration for unit tests 2 | # 3 | # For details see http://docs.python.org/library/logging.html#configuring-logging 4 | # 5 | 6 | [loggers] 7 | keys = root 8 | 9 | [handlers] 10 | keys = tests 11 | 12 | [formatters] 13 | keys = tests 14 | 15 | [logger_root] 16 | level = DEBUG 17 | handlers = tests 18 | 19 | [handler_tests] 20 | level = NOTSET 21 | class = StreamHandler 22 | args = (sys.stderr,) 23 | formatter = tests 24 | 25 | [formatter_tests] 26 | datefmt = %H:%M:%S 27 | format = %(asctime)s,%(msecs)03d %(levelname)-8s [%(name)s] %(message)s 28 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # 2 | # Travis Project Descriptor 3 | # 4 | # See http://docs.travis-ci.com/user/build-configuration/ 5 | # 6 | 7 | # build matrix 8 | language: python 9 | python: 10 | - "2.7" 11 | - "3.4" 12 | # - "pypy" 13 | #matrix: 14 | # # Do not allow failures for Python 3 when you create "universal" wheels (see 'setup.cfg') 15 | # allow_failures: 16 | # - python: "3.4" 17 | 18 | # command to install dependencies 19 | install: 20 | - "pip install -r dev-requirements.txt" 21 | - "python setup.py develop -U" 22 | 23 | # command to run tests 24 | script: invoke ci 25 | -------------------------------------------------------------------------------- /project.d/README.md: -------------------------------------------------------------------------------- 1 | # Project Resources 2 | 3 | This directory exists to keep the project base directory clean and 4 | holds any files that do not *have to* live in the project root. 5 | It contains the folowing files: 6 | 7 | Filename | Description 8 | :---- | :---- 9 | ``classifiers.txt`` | The Trove classifiers for this project (loaded into ``setup.py``). 10 | ``pylint.cfg`` | Configuration for Pylint during ``invoke check``. 11 | ``skeleton_module.py`` | A skeleton file to quickly create new package modules. 12 | ``skeleton_testmodule.py`` | A skeleton file to quickly create new test modules. 13 | -------------------------------------------------------------------------------- /project.d/cookiecutter.json: -------------------------------------------------------------------------------- 1 | { 2 | "email": "jh@web.de", 3 | "features": "travis flake8 cli", 4 | "full_name": "J\u00fcrgen Hermann", 5 | "github_url": "https://github.com/jhermann/jmx4py", 6 | "github_username": "jhermann", 7 | "keywords": "hosted.by.github python java jmx jolokia http rest json", 8 | "license": "Apache 2.0", 9 | "pkg_name": "jmx4py", 10 | "project_name": "Python Client for the Jolokia JMX Agent", 11 | "repo_name": "jmx4py", 12 | "short_description": "A Python Client for the Jolokia JMX Agent", 13 | "url": "https://github.com/jhermann/jmx4py", 14 | "version": "0.1.0", 15 | "year": "2011" 16 | } 17 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | # Tox configuration, for details see 2 | # 3 | # http://tox.testrun.org/ 4 | # 5 | # $ . .env "--yes" "--develop" 6 | # $ tox 7 | 8 | [tox] 9 | envlist = py27, py34 10 | , flake8 11 | 12 | [testenv] 13 | deps = 14 | -r./test-requirements.txt 15 | -r./requirements.txt 16 | commands = 17 | py.test -c {toxinidir}/setup.cfg --color=yes --cov=jmx4py \ 18 | --cov-config=project.d/coverage.cfg --cov-report=term --cov-report=html --cov-report=xml \ 19 | {posargs} 20 | 21 | 22 | 23 | [testenv:flake8] 24 | deps = 25 | flake8==2.3.0 26 | pep8==1.6.2 27 | 28 | ; for now just informational 29 | commands = 30 | flake8 --count --statistics --exit-zero src/jmx4py 31 | 32 | -------------------------------------------------------------------------------- /project.d/classifiers.txt: -------------------------------------------------------------------------------- 1 | # Details at http://pypi.python.org/pypi?:action=list_classifiers 2 | Development Status :: 3 - Alpha 3 | #Development Status :: 4 - Beta 4 | #Development Status :: 5 - Production/Stable 5 | Intended Audience :: Developers 6 | Intended Audience :: Information Technology 7 | Intended Audience :: System Administrators 8 | License :: OSI Approved :: Apache Software License 9 | Operating System :: OS Independent 10 | Programming Language :: Python :: 2 11 | Programming Language :: Python :: 2.7 12 | Programming Language :: Python :: 3 13 | Programming Language :: Python :: 3.4 14 | Topic :: Software Development :: Libraries :: Python Modules 15 | Topic :: System :: Monitoring 16 | Topic :: Utilities 17 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | # 2 | # Configuration for setuptools 3 | # 4 | 5 | [egg_info] 6 | tag_build = dev 7 | tag_date = true 8 | 9 | [sdist] 10 | formats = zip 11 | 12 | [bdist_wheel] 13 | # If you set this to 1, make sure you have a proper Travis CI build matrix, 14 | # and that your Trove classifiers state you support Python 2 and 3 15 | universal = 1 16 | 17 | [upload] 18 | show_response = 1 19 | 20 | 21 | [pytest] 22 | norecursedirs = .* *.egg *.egg-info bin dist include lib local share static docs 23 | python_files = src/tests/test_*.py 24 | addopts = --spec 25 | 26 | ; by default, exclude tests needing a live JVM 27 | ## TODO: attr = !jvm 28 | 29 | markers = 30 | cli: command line interface integration tests. 31 | integration: integration tests. 32 | jvm: tests that require spinning up a JVM 33 | online: tests that need an Internet connection. 34 | 35 | 36 | [flake8] 37 | #ignore = E226,… 38 | max-line-length = 132 39 | -------------------------------------------------------------------------------- /project.d/skeleton_module.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # pylint: disable=bad-continuation 3 | """ Short description. 4 | """ 5 | # Copyright © 2011 Jürgen Hermann 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | from __future__ import absolute_import, unicode_literals, print_function 19 | 20 | import os 21 | import sys 22 | 23 | # from . import … 24 | 25 | 26 | __all__ = [] 27 | -------------------------------------------------------------------------------- /project.d/coverage.cfg: -------------------------------------------------------------------------------- 1 | # 2 | # Coveragerc configuration, for details see 3 | # 4 | # http://nedbatchelder.com/code/coverage/config.html 5 | # 6 | 7 | [run] 8 | branch = True 9 | ; data_file = build/coverage.db 10 | 11 | omit = 12 | */_compat.py 13 | */tests/*.py 14 | 15 | 16 | [report] 17 | ignore_errors = True 18 | 19 | # Regex 'find' matches for lines to exclude from consideration 20 | exclude_lines = 21 | # Have to re-enable the standard pragma 22 | pragma: no cover 23 | 24 | # Don't complain about missing debug-only code 25 | def __repr__ 26 | if self\.debug 27 | 28 | # Don't complain if tests don't hit defensive assertion code 29 | raise AssertionError 30 | raise NotImplementedError 31 | 32 | # Don't complain if non-runnable code isn't run 33 | if 0: 34 | if False: 35 | if __name__ == .__main__.: 36 | 37 | 38 | [xml] 39 | output = build/coverage.xml 40 | 41 | 42 | [html] 43 | directory = build/coverage_html_report 44 | -------------------------------------------------------------------------------- /src/jmx4py/util/__init__.py: -------------------------------------------------------------------------------- 1 | """ Helper package. 2 | """ 3 | # Copyright 2011 Juergen Hermann 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | # We do this once here, all other imports reference util.json 18 | try: 19 | import json 20 | except ImportError: 21 | # Not using "as" here because that confuses Eclipse etc. on Python 2.6+ 22 | import simplejson # pylint: disable=F0401 23 | json = simplejson # pylint: disable=C0103 24 | -------------------------------------------------------------------------------- /project.d/skeleton_testmodule.py: -------------------------------------------------------------------------------- 1 | # *- coding: utf-8 -*- 2 | # pylint: disable=wildcard-import, missing-docstring, no-self-use, bad-continuation 3 | """ Test «some_module». 4 | """ 5 | # Copyright © 2011 Jürgen Hermann 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | from __future__ import absolute_import, unicode_literals, print_function 19 | 20 | from jmx4py import some_module 21 | 22 | 23 | def test_fails(): 24 | assert False, "This test needs to get some sensible logic" 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | lib/ 17 | lib64/ 18 | parts/ 19 | sdist/ 20 | var/ 21 | *.egg-info/ 22 | src/*.egg-info/ 23 | .installed.cfg 24 | *.egg 25 | 26 | # virtualenv 27 | bin/ 28 | include/ 29 | lib/ 30 | share/ 31 | local/ 32 | pyvenv.cfg 33 | .venv/ 34 | 35 | # PyInstaller 36 | # Usually these files are written by a python script from a template 37 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 38 | *.manifest 39 | *.spec 40 | 41 | # Installer logs 42 | pip-log.txt 43 | pip-delete-this-directory.txt 44 | pip-selfcheck.json 45 | 46 | # Unit test / coverage reports 47 | htmlcov/ 48 | .tox/ 49 | .coverage 50 | .cache 51 | nosetests.xml 52 | coverage.xml 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | 61 | # Sphinx documentation 62 | docs/_build/ 63 | 64 | # PyBuilder 65 | target/ 66 | 67 | # Jekyll 68 | _site/ 69 | -------------------------------------------------------------------------------- /java/jolokia.properties: -------------------------------------------------------------------------------- 1 | # Jolokia JVM Agent configuration 2 | # -*- coding: iso-8859-1 -*- 3 | 4 | # Context under which the agent is deployed 5 | agentContext: /jolokia 6 | 7 | # Host address to which the HTTP server should bind to 8 | host: localhost 9 | 10 | # Port the HTTP server should listen to 11 | port: 8089 12 | 13 | # Protocol to use [http | https] 14 | # For the SSL stack there are various additional configuration options. 15 | protocol: http 16 | 17 | # Size of request backlog before requests get discarded 18 | backlog: 10 19 | 20 | # Threading model of the HTTP server 21 | # fixed - Thread pool with a fixed number of threads (see also threadNr) 22 | # cached - Cached thred pool which creates threads on demand 23 | # single - A single thread only 24 | executor: fixed 25 | 26 | # Number of threads to be used when the fixed execution model is chosen 27 | threadNr: 5 28 | 29 | # Path to the SSL keystore to use (https only) 30 | keystore: 31 | 32 | # Keystore password (https only) 33 | keystorePassword: changeit 34 | 35 | # Whether client certificates should be used for authentication (https only) [true | false] 36 | useSslClientAuthentication: false 37 | -------------------------------------------------------------------------------- /src/tests/test_package.py: -------------------------------------------------------------------------------- 1 | # *- coding: utf-8 -*- 2 | # pylint: disable=wildcard-import, missing-docstring, no-self-use, bad-continuation 3 | """ Test the package metadata. 4 | """ 5 | # Copyright © 2011 Jürgen Hermann 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | from __future__ import absolute_import, unicode_literals, print_function 19 | 20 | from jmx4py import __version__ as version 21 | 22 | 23 | def test_semver(): 24 | """Test a proper semantic version is used.""" 25 | # TODO Test rules according to PEP440 – Version Identification and Dependency Specification 26 | assert len(version.split('.')) == 3, "Semantic version M.m.µ OK" 27 | assert all(i.isdigit for i in version.split('.')), "Semantic version parts are numeric" 28 | -------------------------------------------------------------------------------- /src/tests/markers.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # pylint: disable=invalid-name 3 | """ py.test markers. 4 | 5 | For details needed to understand these tests, refer to: 6 | https://pytest.org/ 7 | http://pythontesting.net/start-here/ 8 | """ 9 | # Copyright © 2011 Jürgen Hermann 10 | # 11 | # Licensed under the Apache License, Version 2.0 (the "License"); 12 | # you may not use this file except in compliance with the License. 13 | # You may obtain a copy of the License at 14 | # 15 | # http://www.apache.org/licenses/LICENSE-2.0 16 | # 17 | # Unless required by applicable law or agreed to in writing, software 18 | # distributed under the License is distributed on an "AS IS" BASIS, 19 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 20 | # See the License for the specific language governing permissions and 21 | # limitations under the License. 22 | from __future__ import absolute_import, unicode_literals, print_function 23 | 24 | import pytest 25 | 26 | 27 | # See also setup.cfg » [pytest] » markers 28 | cli = pytest.mark.cli 29 | integration = pytest.mark.integration 30 | jvm = pytest.mark.jvm 31 | online = pytest.mark.online 32 | 33 | 34 | # Export all markers 35 | __all__ = [_k 36 | for _k, _v in globals().items() 37 | if _v.__class__ is cli.__class__] 38 | -------------------------------------------------------------------------------- /src/jmx4py/jolokia/__init__.py: -------------------------------------------------------------------------------- 1 | """ Jolokia client API for Python. 2 | """ 3 | # Copyright 2011 Juergen Hermann 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | import socket 18 | import urllib2 19 | import httplib 20 | 21 | from jmx4py.jolokia.errors import JmxException, JmxResponseError 22 | from jmx4py.jolokia.client import JmxClient, quote, unquote 23 | from jmx4py.jolokia.connection import JmxConnection, JmxHttpConnection 24 | 25 | JmxConnection.register("http", JmxHttpConnection) 26 | JmxConnection.register("https", JmxHttpConnection) 27 | 28 | ERRORS = ( 29 | socket.error, 30 | urllib2.URLError, 31 | httplib.HTTPException, 32 | JmxException, 33 | ) 34 | 35 | __all__ = [ 36 | "ERRORS", "JmxException", "JmxResponseError", 37 | "quote", "unquote", 38 | "JmxClient", "JmxConnection", "JmxHttpConnection", 39 | ] 40 | -------------------------------------------------------------------------------- /src/tests/test_util_network.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # pylint: disable=missing-docstring 3 | """ Network utility tests. 4 | """ 5 | # Copyright 2011 Juergen Hermann 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | from __future__ import absolute_import, unicode_literals, print_function 19 | 20 | import logging 21 | import unittest 22 | import urllib2 23 | 24 | from jmx4py.util import network 25 | 26 | log = logging.getLogger(__name__) 27 | 28 | 29 | class SplitURLCredentialsTest(unittest.TestCase): 30 | 31 | def test_simple_url(self): 32 | url = "http://example.com/" 33 | self.assertEqual(network.split_url_credentials(url), (url, None, None)) 34 | 35 | 36 | def test_user_pwd_url(self): 37 | url = "http://foo:bar@example.com/baz" 38 | self.assertEqual(network.split_url_credentials(url), ("http://example.com/baz", "foo", "bar")) 39 | 40 | 41 | def test_bad_url(self): 42 | url = "http://foo@example.com/baz" 43 | self.failUnlessRaises(urllib2.URLError, network.split_url_credentials, url) 44 | -------------------------------------------------------------------------------- /src/jmx4py/util/auxiliary.py: -------------------------------------------------------------------------------- 1 | """ Generic helper functions. 2 | 3 | @author: jhe 4 | """ 5 | # Copyright 2011 Juergen Hermann 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | 19 | def digest_repr(obj): 20 | """ Return a short digest of a possibly deeply nested (JSON) object. 21 | """ 22 | if isinstance(obj, (tuple, list, set, dict)): 23 | def nesting(collection): 24 | "Helper" 25 | if isinstance(collection, dict): 26 | collection = collection.values() 27 | if isinstance(collection, (tuple, list, set)): 28 | return 1 + max(nesting(i) for i in collection) 29 | else: 30 | return 1 31 | 32 | depth = nesting(obj) 33 | contents = list(obj) 34 | if len(contents) > 10: 35 | contents[3:-2] = "..." 36 | return "<%s of maxdepth %d and len %d holding %s>" % ( 37 | type(obj).__name__, depth, len(obj), ', '.join(repr(i) for i in contents)) 38 | else: 39 | return repr(obj) # Normal repr() for scalar or other non-collection objects 40 | -------------------------------------------------------------------------------- /java/testjvm/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | org.jolokia 6 | jmx4py-testjvm 7 | 1-SNAPSHOT 8 | jar 9 | 10 | jmx4py-testjvm 11 | https://github.com/jhermann/jmx4py 12 | 13 | 14 | UTF-8 15 | org.jolokia.jmx4py.testjvm.App 16 | 17 | 18 | 19 | 20 | 21 | 22 | maven-jar-plugin 23 | 24 | 25 | 26 | ${exec.mainClass} 27 | org.jolokia.jmx4py.testjvm 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | junit 38 | junit 39 | 3.8.1 40 | test 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /src/jmx4py/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # pylint: disable=bad-whitespace 3 | # flake8: noqa 4 | """ 5 | JMX for Python – A Python Client for the Jolokia JMX Agent. 6 | 7 | Jolokia is a JMX-HTTP bridge giving an alternative to JSR-160 connectors. 8 | It is an agent based approach with support for many platforms. In addition 9 | to basic JMX operations it enhances JMX remoting with unique features like 10 | bulk requests or fine grained security policies. 11 | 12 | jmx4py offers a client API similar to the existing Jolokia clients for Perl 13 | (jmx4perl), Java and Javascript. Additionally, it'll build upon the basic 14 | API and offer further features related to monitoring and controlling JVMs 15 | via JMX using Python. 16 | 17 | 18 | Copyright © 2011 Jürgen Hermann 19 | 20 | Licensed under the Apache License, Version 2.0 (the "License"); 21 | you may not use this file except in compliance with the License. 22 | You may obtain a copy of the License at 23 | 24 | http://www.apache.org/licenses/LICENSE-2.0 25 | 26 | Unless required by applicable law or agreed to in writing, software 27 | distributed under the License is distributed on an "AS IS" BASIS, 28 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 29 | See the License for the specific language governing permissions and 30 | limitations under the License. 31 | """ 32 | from __future__ import absolute_import, unicode_literals, print_function 33 | 34 | __url__ = "https://github.com/jhermann/jmx4py" 35 | __version__ = "0.1.0" 36 | __license__ = "Apache 2.0" 37 | __author__ = "Jürgen Hermann" 38 | __author_email__ = "jh@web.de" 39 | __keywords__ = "hosted.by.github python java jmx jolokia http rest json" 40 | 41 | __all__ = [] 42 | -------------------------------------------------------------------------------- /src/jmx4py/jolokia/errors.py: -------------------------------------------------------------------------------- 1 | """ JMX Exceptions. 2 | 3 | @author: jhe 4 | """ 5 | # Copyright 2011 Juergen Hermann 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | 19 | class JmxException(Exception): 20 | """ JMX exception base class. 21 | """ 22 | 23 | 24 | class JmxResponseError(JmxException): 25 | """ Indicates request results containing an error status. 26 | """ 27 | 28 | def __init__(self, response, request=None): 29 | """ Extract major fields from an error response and map it to 30 | common Python conventions. 31 | """ 32 | super(JmxResponseError, self).__init__(response, request) 33 | 34 | # "code" and "reason" are more common in Python (httplib) 35 | self.code = self.status = response.get("status", -1) 36 | self.reason = self.error = response.get("error", "") 37 | self.request = request or {} 38 | self.response = response 39 | 40 | 41 | def __str__(self): 42 | """ Generate concise description of an error. 43 | """ 44 | return "status %d for %s operation: %s" % ( 45 | self.code, self.request.get("type", "UNKNOWN"), self.reason, 46 | ) 47 | 48 | 49 | def __repr__(self): 50 | """ Generate concise representation of an error. 51 | """ 52 | return "<%s %s>" % (self.__class__.__name__, self) 53 | -------------------------------------------------------------------------------- /src/jmx4py/__main__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # pylint: disable=bad-continuation 3 | """ Command line interface. 4 | """ 5 | # Copyright © 2011 Jürgen Hermann 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | from __future__ import absolute_import, unicode_literals, print_function 19 | 20 | import sys 21 | 22 | import click 23 | 24 | 25 | __app_name__ = 'jmx4py' 26 | CONTEXT_SETTINGS = dict( 27 | help_option_names=['-h', '--help'], 28 | ) 29 | 30 | 31 | @click.group(context_settings=CONTEXT_SETTINGS) 32 | @click.version_option( 33 | message='%(prog)s %(version)s [Python {}]'.format(' '.join(sys.version.split())), 34 | ) 35 | @click.option('-q', '--quiet', is_flag=True, default=False, help='Be quiet (show only errors).') 36 | @click.option('-v', '--verbose', is_flag=True, default=False, help='Create extra verbose output.') 37 | @click.pass_context 38 | def cli(ctx, version=False, quiet=False, verbose=False): # pylint: disable=unused-argument 39 | """'jmx4py' command line tool.""" 40 | appdir = click.get_app_dir(__app_name__) # noqa 41 | # click.secho('appdir = {0}'.format(appdir), fg='yellow') 42 | 43 | 44 | @cli.command(name='help') 45 | def help_command(): 46 | """Print some helpful message.""" 47 | click.echo('Helpful message.') 48 | 49 | 50 | if __name__ == "__main__": # imported via "python -m"? 51 | __package__ = 'jmx4py' # pylint: disable=redefined-builtin 52 | cli() # pylint: disable=no-value-for-parameter 53 | -------------------------------------------------------------------------------- /src/tests/conftest.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # pylint: disable=too-few-public-methods 3 | """ py.test dynamic configuration. 4 | 5 | For details needed to understand these tests, refer to: 6 | https://pytest.org/ 7 | http://pythontesting.net/start-here/ 8 | """ 9 | # Copyright © 2011 Jürgen Hermann 10 | # 11 | # Licensed under the Apache License, Version 2.0 (the "License"); 12 | # you may not use this file except in compliance with the License. 13 | # You may obtain a copy of the License at 14 | # 15 | # http://www.apache.org/licenses/LICENSE-2.0 16 | # 17 | # Unless required by applicable law or agreed to in writing, software 18 | # distributed under the License is distributed on an "AS IS" BASIS, 19 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 20 | # See the License for the specific language governing permissions and 21 | # limitations under the License. 22 | from __future__ import absolute_import, unicode_literals, print_function 23 | 24 | import logging 25 | import unittest 26 | 27 | import pytest 28 | 29 | from jmx4py.jolokia import client, connection 30 | 31 | 32 | # Globally available fixtures 33 | @pytest.fixture(scope='session') 34 | def logger(): 35 | """Test logger instance as a fixture.""" 36 | logging.basicConfig(level=logging.DEBUG) 37 | return logging.getLogger('tests') 38 | 39 | 40 | class JmxMockedConnection(connection.JmxConnection): 41 | """ JMX Proxy Connection Mock. 42 | """ 43 | 44 | def __init__(self, url): 45 | """ Create a proxy connection. 46 | """ 47 | super(JmxMockedConnection, self).__init__(url) 48 | 49 | 50 | def open(self): 51 | """ Open the connection. 52 | """ 53 | 54 | 55 | def close(self): 56 | """ Close the connection and release associated resources. 57 | """ 58 | 59 | 60 | def _do_send(self, data): 61 | """ Perform a single request and return the deserialized response. 62 | """ 63 | 64 | connection.JmxConnection.register("mock", JmxMockedConnection) 65 | 66 | 67 | # @attr("jvm") 68 | class JvmTestCase(unittest.TestCase): 69 | """ Test base class that provides an already prepared client. 70 | """ 71 | 72 | def setUp(self): 73 | self.proxy = client.JmxClient(("localhost", 8089)) 74 | -------------------------------------------------------------------------------- /tasks.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # pylint: disable=wildcard-import, unused-wildcard-import, bad-continuation 3 | """ Project automation for Invoke. 4 | """ 5 | from __future__ import absolute_import, unicode_literals 6 | 7 | import os 8 | import shutil 9 | import tempfile 10 | 11 | from invoke import run, task 12 | from rituals.invoke_tasks import * # pylint: disable=redefined-builtin 13 | 14 | 15 | @task(name='fresh-cookies', 16 | help={ 17 | 'mold': "git URL or directory to use for the refresh", 18 | }, 19 | ) 20 | def fresh_cookies(mold=''): 21 | """Refresh the project from the original cookiecutter template.""" 22 | mold = mold or "https://github.com/Springerle/py-generic-project.git" # TODO: URL from config 23 | tmpdir = os.path.join(tempfile.gettempdir(), "moar-jmx4py") 24 | 25 | if os.path.isdir('.git'): 26 | # TODO: Ensure there are no local unstashed changes 27 | pass 28 | 29 | # Make a copy of the new mold version 30 | if os.path.isdir(tmpdir): 31 | shutil.rmtree(tmpdir) 32 | if os.path.exists(mold): 33 | shutil.copytree(mold, tmpdir, ignore=shutil.ignore_patterns( 34 | ".git", ".svn", "*~", 35 | )) 36 | else: 37 | run("git clone {} {}".format(mold, tmpdir), echo=True) 38 | 39 | # Copy recorded "cookiecutter.json" into mold 40 | shutil.copy2("project.d/cookiecutter.json", tmpdir) 41 | 42 | with pushd('..'): 43 | run("cookiecutter --no-input {}".format(tmpdir), echo=True) 44 | if os.path.exists('.git'): 45 | run("git status", echo=True) 46 | 47 | 48 | @task(help={ 49 | 'verbose': "Make 'tox' more talkative", 50 | 'env-list': "Override list of environments to use (e.g. 'py27,py34')", 51 | 'opts': "Extra flags for tox", 52 | }) 53 | def tox(verbose=False, env_list='', opts=''): 54 | """Perform multi-environment tests.""" 55 | snakepits = ['/opt/pyenv/bin'] # TODO: config value 56 | cmd = [] 57 | 58 | snakepits = [i for i in snakepits if os.path.isdir(i)] 59 | if snakepits: 60 | cmd += ['PATH="{}:$PATH"'.format(os.pathsep.join(snakepits),)] 61 | 62 | cmd += ['tox'] 63 | if verbose: 64 | cmd += ['-v'] 65 | if env_list: 66 | cmd += ['-e', env_list] 67 | cmd += opts 68 | cmd += ['2>&1'] 69 | run(' '.join(cmd), echo=True) 70 | 71 | 72 | @task(help={ 73 | 'pty': "Whether to run commands under a pseudo-tty", 74 | }) # pylint: disable=invalid-name 75 | def ci(pty=True): 76 | """Perform continuous integration tasks.""" 77 | opts = [''] 78 | 79 | # 'tox' makes no sense in Travis 80 | if os.environ.get('TRAVIS', '').lower() == 'true': 81 | opts += ['test'] 82 | else: 83 | opts += ['tox'] 84 | 85 | run("invoke clean --all build --docs check --reports{} 2>&1".format(' '.join(opts)), echo=True, pty=pty) 86 | -------------------------------------------------------------------------------- /src/tests/test_cli.py: -------------------------------------------------------------------------------- 1 | 2 | # *- coding: utf-8 -*- 3 | # pylint: disable=wildcard-import, unused-wildcard-import, missing-docstring 4 | # pylint: disable=redefined-outer-name, no-self-use, bad-continuation 5 | """ Test '__main__' CLI stub. 6 | 7 | See http://click.pocoo.org/3/testing/ 8 | """ 9 | # Copyright © 2011 Jürgen Hermann 10 | # 11 | # Licensed under the Apache License, Version 2.0 (the "License"); 12 | # you may not use this file except in compliance with the License. 13 | # You may obtain a copy of the License at 14 | # 15 | # http://www.apache.org/licenses/LICENSE-2.0 16 | # 17 | # Unless required by applicable law or agreed to in writing, software 18 | # distributed under the License is distributed on an "AS IS" BASIS, 19 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 20 | # See the License for the specific language governing permissions and 21 | # limitations under the License. 22 | from __future__ import absolute_import, unicode_literals, print_function 23 | 24 | import sys 25 | 26 | import sh 27 | import pytest 28 | from click.testing import CliRunner 29 | 30 | from markers import * 31 | from jmx4py import __version__ as version 32 | from jmx4py import __main__ as main 33 | 34 | 35 | UsageError = sh.ErrorReturnCode_2 # pylint: disable=no-member 36 | 37 | 38 | @pytest.fixture 39 | def cmd(): 40 | """Command fixture.""" 41 | return sh.Command(main.__app_name__) 42 | 43 | 44 | @cli 45 | @integration 46 | def test_cli_help(cmd): 47 | result = cmd('--help') 48 | lines = result.stdout.decode('ascii').splitlines() 49 | 50 | assert main.__app_name__ in lines[0].split(), "Command name is reported" 51 | 52 | 53 | @cli 54 | @integration 55 | def test_cli_version(cmd): 56 | result = cmd('--version') 57 | stdout = result.stdout.decode('ascii') 58 | reported_version = stdout.split()[1] 59 | py_version = sys.version.split()[0] 60 | 61 | assert version in stdout, "Version string contains version" 62 | assert reported_version[:len(version)] == version, "Version is 2nd field" 63 | assert py_version in stdout, "Python version is reported" 64 | 65 | 66 | @cli 67 | @integration 68 | def test_cli_invalid_option(cmd): 69 | with pytest.raises(UsageError): 70 | cmd('--this-is-certainly-not-a-supported-option') 71 | 72 | 73 | @cli 74 | @integration 75 | def test_cli_invalid_sub_command(cmd): 76 | with pytest.raises(UsageError): 77 | cmd.sub_command_that_does_not_exist() 78 | 79 | 80 | @cli 81 | def test_cmd_missing(): 82 | runner = CliRunner() 83 | result = runner.invoke(main.cli) 84 | 85 | assert result.exit_code == 0 86 | 87 | 88 | @cli 89 | def test_cmd_help(): 90 | runner = CliRunner() 91 | result = runner.invoke(main.help_command) 92 | word1 = result.output.split()[0] 93 | 94 | assert result.exit_code == 0 95 | assert word1 == 'Helpful', "Helpful message is printed" 96 | 97 | -------------------------------------------------------------------------------- /java/testjvm/src/main/java/org/jolokia/jmx4py/testjvm/App.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2011 Juergen Hermann 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package org.jolokia.jmx4py.testjvm; 18 | 19 | import java.io.File; 20 | import java.io.FileNotFoundException; 21 | import java.io.FileOutputStream; 22 | import java.io.IOException; 23 | import java.io.OutputStreamWriter; 24 | import java.io.UnsupportedEncodingException; 25 | import java.lang.management.ManagementFactory; 26 | 27 | 28 | /** 29 | * Application to start a test JVM and keep it open until stopped. 30 | * 31 | */ 32 | public class App { 33 | private static String jvm_id = "N/A"; 34 | 35 | public static void main(String[] args) { 36 | if (args.length == 0) { 37 | fail("Provide the path to a guard file!"); 38 | } 39 | jvm_id = ManagementFactory.getRuntimeMXBean().getName(); 40 | 41 | File guard = new File(args[0]); 42 | createGuard(guard); 43 | guard.deleteOnExit(); 44 | 45 | System.out.println("Waiting for guard file '" + guard + "' of JVM " + jvm_id + " to disappear..."); 46 | while (guard.exists()) { 47 | try { 48 | Thread.sleep(1000L); 49 | } catch (InterruptedException e) { 50 | Thread.currentThread().interrupt(); 51 | } 52 | } 53 | 54 | System.out.println("Guard file gone, test JVM " + jvm_id + " exiting..."); 55 | } 56 | 57 | 58 | /** 59 | * @param guard 60 | */ 61 | private static void createGuard(File guard) { 62 | OutputStreamWriter out = null; 63 | try { 64 | out = new OutputStreamWriter(new FileOutputStream(guard), "UTF-8"); 65 | } catch (UnsupportedEncodingException e) { 66 | fail("Punt! JVM is broken!"); 67 | } catch (FileNotFoundException e) { 68 | fail("Bad guard file path: " + guard); 69 | } 70 | try { 71 | try { 72 | out.write(jvm_id + "\n"); 73 | } catch (IOException e) { 74 | fail("Cannot write to guard file: " + guard); 75 | } 76 | } finally { 77 | try { 78 | out.close(); 79 | } catch (IOException e) { 80 | fail("Cannot write to guard file: " + guard); 81 | } 82 | } 83 | } 84 | 85 | 86 | /** 87 | * @param msg 88 | */ 89 | private static void fail(String msg) { 90 | System.out.println(msg); 91 | System.exit(1); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | # autoenv script (https://github.com/kennethreitz/autoenv) 2 | _venv_xtrace=$(set +o | grep xtrace) 3 | set +x 4 | _venv_name="$(basename $(pwd))" 5 | 6 | # command line flags, mostly for CI server usage 7 | _venv_create=false 8 | _venv_develop=false 9 | _venv_pip_log='egrep "Found|Collecting|Installing.collected|Searching.for|Installed|Finished"' 10 | while test "${1:0:1}" = "-"; do 11 | case "$1" in 12 | --yes) _venv_create=true ;; # create missing venv without prompting 13 | --develop) _venv_develop=true ;; # always call "develop -U" on activation 14 | --verbose | -v) _venv_pip_log=cat ;; # don't filter install logs on terminal 15 | *) echo "WARNING: Ignored unknown option '$1'" ;; 16 | esac 17 | shift 18 | done 19 | 20 | # Outside the tree of the .env script? 21 | if pwd | egrep '^'$(basename "$1")'($|/)' >/dev/null; then 22 | : echo Outside "[$0 $1]" 23 | 24 | # Inside the tree of the .env script, but have another local '.env'? 25 | elif test \! -f .env -o "$1" != "$(pwd)/.env"; then 26 | : echo Inside "[$0 $1]" 27 | 28 | # Only try virtualenv creation outside of template dirs; the egrep pattern is escaped for hiding it from Jinja2 29 | elif pwd | egrep -v '/{''{''.*''}''}(/|$)' >/dev/null || $_venv_create; then 30 | test -f ".env" && _venv_ask=true || _venv_ask=false 31 | for _venv_base in .venv venv . ..; do 32 | if test -f "$_venv_base/$_venv_name/bin/activate"; then 33 | deactivate 2>/dev/null || : 34 | . "$_venv_base/$_venv_name/bin/activate" 35 | if test -f setup.py; then 36 | $_venv_develop && python setup.py -q develop -U || : 37 | python setup.py --name --version --url | tr \\n \\0 \ 38 | | xargs -0 printf "*** Activated %s %s @ %s\\n" || : 39 | else 40 | echo "*** Activated $_venv_base/$_venv_name" 41 | fi 42 | _venv_ask=false 43 | break 44 | fi 45 | done 46 | 47 | if $_venv_ask && test \! -d .venv; then 48 | $_venv_create || { read -p "No virtualenv found, shall I create one for you? [Y/n] " -n 1 -r || REPLY='n'; echo; } 49 | if $_venv_create || [[ $REPLY =~ ^[Yy]?$ ]]; then 50 | /usr/bin/virtualenv ".venv/$_venv_name" 51 | . ".venv/$_venv_name/bin/activate" 52 | pip --log ".venv/pip-install.log" install -UI pip 2>&1 | $_venv_pip_log 53 | pip --log ".venv/pip-install.log" install -UI "setuptools>=12.1" "wheel>=0.24.0" 2>&1 | $_venv_pip_log 54 | pip --log ".venv/pip-install.log" install -r dev-requirements.txt 2>&1 | $_venv_pip_log 55 | if test -f setup.py; then 56 | python setup.py develop -U 2>&1 | $_venv_pip_log 57 | echo 58 | python setup.py --name --version --author --author-email --license --description --url \ 59 | | tr \\n \\0 | xargs -0 printf "%s %s by %s <%s> [%s]\\n%s\\n%s\\n" || : 60 | else 61 | echo; echo "*** No 'setup.py' found, all done." 62 | fi 63 | else 64 | mkdir -p .venv # prevent constant prompting 65 | fi 66 | fi 67 | fi 68 | 69 | unset _venv_name _venv_ask _venv_create _venv_develop _venv_pip_log _venv_base 70 | $_venv_xtrace # restore xtrace state 71 | unset _venv_xtrace 72 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jmx4py 2 | 3 | ![Logo](https://raw.github.com/jhermann/jmx4py/master/docs/_static/jmx4py-logo-64.png) 4 | 5 | A Python Client for the Jolokia JMX Agent 6 | 7 |  [![Travis CI](https://api.travis-ci.org/jhermann/jmx4py.svg)](https://travis-ci.org/jhermann/jmx4py) 8 |  [![GitHub Issues](https://img.shields.io/github/issues/jhermann/jmx4py.svg)](https://github.com/jhermann/jmx4py/issues) 9 |  [![License](https://img.shields.io/pypi/l/jmx4py.svg)](https://github.com/jhermann/jmx4py/blob/master/LICENSE) 10 |  [![Development Status](https://pypip.in/status/jmx4py/badge.svg)](https://pypi.python.org/pypi/jmx4py/) 11 |  [![Latest Version](https://img.shields.io/pypi/v/jmx4py.svg)](https://pypi.python.org/pypi/jmx4py/) 12 |  [![Download format](https://pypip.in/format/jmx4py/badge.svg)](https://pypi.python.org/pypi/jmx4py/) 13 |  [![Downloads](https://img.shields.io/pypi/dw/jmx4py.svg)](https://pypi.python.org/pypi/jmx4py/) 14 | 15 | 16 | ## Overview 17 | 18 | Jolokia is a JMX-HTTP bridge giving an alternative to JSR-160 connectors. 19 | It is an agent based approach with support for many platforms. In addition 20 | to basic JMX operations it enhances JMX remoting with unique features like 21 | bulk requests or fine grained security policies. 22 | 23 | jmx4py offers a client API similar to the existing Jolokia clients for Perl 24 | (jmx4perl), Java and Javascript. Additionally, it'll build upon the basic 25 | API and offer further features related to monitoring and controlling JVMs 26 | via JMX using Python. 27 | 28 | 29 | ## Setup 30 | 31 | To create a working directory for this project, 32 | follow these steps on a POSIX system: 33 | 34 | ```sh 35 | git clone "https://github.com/jhermann/jmx4py.git" 36 | cd "jmx4py" 37 | . .env --yes --develop 38 | invoke build --docs test check 39 | ``` 40 | 41 | The ``.env`` script creates a virtualenv and 42 | installs the necessary tools into it. 43 | See the script for details. 44 | 45 | Now you can explore the API by simply issuing the following command: 46 | 47 | invoke explore 48 | 49 | For that to succeed, you must also have a working Java + Maven environment, 50 | since a small test application is built and then started in the background, 51 | so you can work against a live JVM. 52 | 53 | See [CONTRIBUTING](https://github.com/jhermann/jmx4py/blob/master/CONTRIBUTING.md) for more. 54 | 55 | 56 | ## Installation 57 | 58 | **TODO**: pip install / dh-virtualenv 59 | 60 | 61 | ## Usage 62 | 63 | jmx4py offers the following command line tools... **TODO** 64 | 65 | For using jmx4py from Python, consult the API documentation available at **TODO** 66 | 67 | 68 | ## Known Limitations & Issues 69 | 70 | * The API is subject to change for 0.x, until enough practical experience is gained. 71 | * Only Jolokia 1.0 and up (Protocol v6) is supported. 72 | * GET requests aren't supported. 73 | * Bulk requests aren't supported. 74 | * Python 2.7 is used for development, and 2.7 and 3.4 are tested in continuous integration. 75 | 76 | 77 | ## References 78 | 79 | **Project** 80 | 81 | * [jmx4py @ PyPI](http://pypi.python.org/pypi/jmx4py/) 82 | * [jmx4py @ Open HUB](https://www.openhub.net/p/jmx4py) 83 | * [Jolokia](http://www.jolokia.org/) 84 | 85 | **Tools** 86 | 87 | * [Cookiecutter](http://cookiecutter.readthedocs.io/en/latest/) 88 | * [PyInvoke](http://www.pyinvoke.org/) 89 | * [pytest](http://pytest.org/latest/contents.html) 90 | * [tox](https://tox.readthedocs.io/en/latest/) 91 | * [Pylint](http://docs.pylint.org/) 92 | * [twine](https://github.com/pypa/twine#twine) 93 | * [bpython](http://docs.bpython-interpreter.org/) 94 | * [yolk3k](https://github.com/myint/yolk#yolk) 95 | 96 | **Packages** 97 | 98 | * [Rituals](https://jhermann.github.io/rituals) 99 | * [Click](http://click.pocoo.org/) 100 | 101 | 102 | ## Acknowledgements 103 | 104 | … 105 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribution Guidelines 2 | 3 | ## Overview 4 | 5 | Contributing to this project is easy, and reporting an issue or adding to the documentation 6 | also improves things for every user. You don't need to be a developer to contribute. 7 | 8 | 9 | ### Reporting issues 10 | 11 | Please use the *GitHub issue tracker*, and describe your problem so that it can be easily 12 | reproduced. Providing relevant version information on the project itself and your environment helps with that. 13 | 14 | 15 | ### Improving documentation 16 | 17 | The easiest way to provide examples or related documentation that helps other users 18 | is the *GitHub wiki*. 19 | 20 | If you are comfortable with the Sphinx documentation tool, you can also prepare a 21 | pull request with changes to the core documentation. 22 | GitHub's built-in text editor makes this especially easy, when you choose the 23 | _“Create a new branch for this commit and start a pull request”_ option on saving. 24 | Small fixes for typos and the like are a matter of minutes when using that tool. 25 | 26 | 27 | ### Code contributions 28 | 29 | Here's a quick guide to improve the code: 30 | 31 | 1. Fork the repo, and clone the fork to your machine. 32 | 1. Add your improvements, the technical details are further below. 33 | 1. Run the tests and make sure they're passing (`invoke test`). 34 | 1. Check for violations of code conventions (`invoke check`). 35 | 1. Make sure the documentation builds without errors (`invoke build --docs`). 36 | 1. Push to your fork and submit a [pull request](https://help.github.com/articles/using-pull-requests/). 37 | 38 | Please be patient while waiting for a review. Life & work tend to interfere. 39 | 40 | 41 | ## Details on contributing code 42 | 43 | This project is written in [Python](http://www.python.org/), 44 | and the documentation is generated using [Sphinx](https://pypi.python.org/pypi/Sphinx). 45 | [setuptools](https://packaging.python.org/en/latest/projects.html#setuptools) 46 | and [Invoke](http://www.pyinvoke.org/) are used to build and manage the project. 47 | Tests are written and executed using [pytest](http://pytest.org/) and 48 | [tox](https://testrun.org/tox/ ). 49 | 50 | 51 | ### Set up a working development environment 52 | 53 | To set up a working directory from your own fork, 54 | follow [these steps](https://github.com/jhermann/jmx4py#contributing), 55 | but replace the repository `https` URLs with SSH ones that point to your fork. 56 | 57 | For that to work on Debian type systems, you need the 58 | `git`, `python`, and `python-virtualenv` 59 | packages installed. Other distributions are similar. 60 | 61 | 62 | ### Add your changes to a feature branch 63 | 64 | For any cohesive set of changes, create a *new* branch based on the current upstream `master`, 65 | with a name reflecting the essence of your improvement. 66 | 67 | ```sh 68 | git branch "name-for-my-fixes" origin/master 69 | git checkout "name-for-my-fixes" 70 | … make changes… 71 | invoke ci # check output for broken tests, or PEP8 violations and the like 72 | … commit changes… 73 | git push origin "name-for-my-fixes" 74 | ``` 75 | 76 | Please don't create large lumps of unrelated changes in a single pull request. 77 | Also take extra care to avoid spurious changes, like mass whitespace diffs. 78 | All Python sources use spaces to indent, not TABs. 79 | 80 | 81 | ### Make sure your changes work 82 | 83 | Some things that will increase the chance that your pull request is accepted: 84 | 85 | * Follow style conventions you see used in the source already (and read [PEP8](http://www.python.org/dev/peps/pep-0008/)). 86 | * Include tests that fail *without* your code, and pass *with* it. Only minor refactoring and documentation changes require no new tests. If you are adding functionality or fixing a bug, please also add a test for it! 87 | * Update any documentation or examples impacted by your change. 88 | * Styling conventions and code quality are checked with `invoke check`, tests are run using `invoke test`, and the docs can be built locally using `invoke build --docs`. 89 | 90 | Following these hints also expedites the whole procedure, since it avoids unnecessary feedback cycles. 91 | -------------------------------------------------------------------------------- /src/jmx4py/util/network.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # pylint: disable=bad-continuation 3 | """ Networking. 4 | """ 5 | # Copyright 2011 Juergen Hermann 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | from __future__ import absolute_import, unicode_literals, print_function 19 | 20 | import urllib2 21 | import urlparse 22 | 23 | 24 | class RetryLimitingHTTPPasswordMgrWithDefaultRealm( # pylint: disable=too-few-public-methods 25 | urllib2.HTTPPasswordMgrWithDefaultRealm): 26 | """ Fixes http://bugs.python.org/issue8797 for certain Python versions provided by Linux packaging 27 | and still running in the wild. 28 | """ 29 | retries = 0 30 | 31 | def find_user_password(self, realm, authuri): 32 | """ Limit number of queries per request. 33 | 34 | Note that retries needs to be reset in the calling code. 35 | """ 36 | # allow sending the username:password 5 times before failing! 37 | if self.retries > 5: 38 | from httplib import HTTPMessage 39 | from StringIO import StringIO 40 | raise urllib2.HTTPError(authuri, 401, "basic auth failed for realm %r" % realm, 41 | HTTPMessage(StringIO("")), None) 42 | 43 | self.retries += 1 44 | return urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password(self, realm, authuri) 45 | 46 | 47 | def split_url_credentials(url): 48 | """ Split username and password from an URL and return tuple 49 | (plain_url, username, password). 50 | """ 51 | scheme, netloc, path, query, fragment = urlparse.urlsplit(url) 52 | username, password = None, None 53 | 54 | if '@' in netloc: 55 | try: 56 | credentials, netloc = netloc.split('@', 1) 57 | username, password = credentials.split(':', 1) 58 | except (TypeError, ValueError), exc: 59 | raise urllib2.URLError("Malformed URL credentials in %r (%s)" % (url, exc)) 60 | 61 | url = urlparse.urlunsplit((scheme, netloc, path, query, fragment)) 62 | 63 | return url, username, password 64 | 65 | 66 | def urlopen(req, username=None, password=None): 67 | """ C{urlopen()} wrapper supporting Basic Auth credentials. 68 | 69 | @param req: to be opened. 70 | @param username: Optional default credentials. 71 | @param password: Optional default credentials. 72 | @return: Handle for C{url}. 73 | """ 74 | # Get URL from request object, else treat it as a string 75 | try: 76 | urlstr = req.get_full_url() 77 | except AttributeError: 78 | urlstr = req 79 | 80 | # Try to get credentials from the URL 81 | credentials = None 82 | _, url_username, url_password = split_url_credentials(urlstr) 83 | if url_username: 84 | username, password = url_username, url_password 85 | 86 | if username or password: 87 | # Register opener 88 | credentials = (username, password) 89 | if credentials not in urlopen.opener: 90 | urlopen.pwd_mgr[credentials] = RetryLimitingHTTPPasswordMgrWithDefaultRealm() 91 | urlopen.handler[credentials] = urllib2.HTTPBasicAuthHandler(urlopen.pwd_mgr[credentials]) 92 | urlopen.opener[credentials] = urllib2.build_opener( 93 | urlopen.handler[credentials], 94 | urllib2.HTTPHandler(debuglevel=int(urlopen.debug)), 95 | urllib2.HTTPSHandler(debuglevel=int(urlopen.debug)), 96 | ) 97 | 98 | # Add credentials entry for Python >= 2.4, and reset retry counter 99 | urlopen.pwd_mgr[credentials].add_password(None, urlstr, username, password) 100 | urlopen.pwd_mgr[credentials].retries = 0 101 | urlopen.handler[credentials].retried = 0 # fix Python 2.6.6 bug 102 | elif credentials not in urlopen.opener: 103 | # Opener for public URLs 104 | urlopen.opener[credentials] = urllib2.build_opener( 105 | urllib2.HTTPHandler(debuglevel=int(urlopen.debug)), 106 | urllib2.HTTPSHandler(debuglevel=int(urlopen.debug)), 107 | ) 108 | 109 | # Open URL and return handle 110 | return urlopen.opener[credentials].open(req) 111 | 112 | urlopen.opener = {} 113 | urlopen.handler = {} 114 | urlopen.pwd_mgr = {} 115 | urlopen.debug = False 116 | -------------------------------------------------------------------------------- /src/jmx4py/jolokia/connection.py: -------------------------------------------------------------------------------- 1 | """ JMX Connections. 2 | 3 | @author: jhe 4 | """ 5 | # Copyright 2011 Juergen Hermann 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | 19 | import urllib2 20 | 21 | from jmx4py.util import network, json 22 | 23 | 24 | class JmxConnection(object): 25 | """ JMX Proxy Connection base class. 26 | """ 27 | 28 | # Registry of URL schemes for different connection types 29 | registry = {} 30 | 31 | 32 | @classmethod 33 | def register(cls, url_scheme, factory): 34 | """ Register a connection factory for the given URL scheme. 35 | """ 36 | cls.registry[url_scheme.lower()] = factory 37 | 38 | 39 | @classmethod 40 | def from_url(cls, url): 41 | """ Create a connection from the given URL using the scheme registry. 42 | """ 43 | try: 44 | # Support the common socket pair for HTTP connections to the default context 45 | host, port = url 46 | 47 | try: 48 | port = int(port) 49 | except (TypeError, ValueError), exc: 50 | raise urllib2.URLError("Bad port in (host, port) pair %r (%s)" % (url, exc)) 51 | 52 | return JmxHttpConnection("http://%s:%d/jolokia/" % (host, port)) 53 | except (TypeError, ValueError): 54 | url_scheme = url.split(':', 1)[0].lower() 55 | if url_scheme not in cls.registry: 56 | raise urllib2.URLError("Unsupported URl scheme '%s' in '%s'" % (url_scheme, url)) 57 | 58 | return cls.registry[url_scheme](url) 59 | 60 | 61 | def __init__(self, url): 62 | """ Create a proxy connection. 63 | """ 64 | self.url = url 65 | self.calls = 0 66 | self.errors = 0 67 | 68 | 69 | def open(self): 70 | """ Open the connection. 71 | """ 72 | raise NotImplementedError() 73 | 74 | 75 | def close(self): 76 | """ Close the connection and release associated resources. 77 | """ 78 | raise NotImplementedError() 79 | 80 | 81 | def _do_send(self, data): 82 | """ Template method performing the connection-specific data transfer. 83 | """ 84 | raise NotImplementedError() 85 | 86 | 87 | def send(self, data): 88 | """ Perform a single request and return the deserialized response. 89 | """ 90 | self.calls += 1 91 | try: 92 | # TODO: Add latency statistics? 93 | resp = self._do_send(data) 94 | except: 95 | self.errors += 1 96 | raise 97 | else: 98 | if resp.get("status") != 200: 99 | self.errors += 1 100 | return resp 101 | 102 | 103 | class JmxHttpConnection(JmxConnection): 104 | """ JMX Proxy Connection via HTTP. 105 | """ 106 | 107 | def __init__(self, url): 108 | """ Create a proxy connection. 109 | """ 110 | super(JmxHttpConnection, self).__init__(url) 111 | self.url = self.url.rstrip('/') + '/' 112 | self._open = False 113 | 114 | 115 | def open(self): 116 | """ Open the connection. 117 | """ 118 | # Currently, we have no connection pooling, so this is basically a NOP 119 | self._open = True 120 | return self 121 | 122 | 123 | def close(self): 124 | """ Close the connection and release associated resources. 125 | """ 126 | self._open = False 127 | 128 | 129 | def _do_send(self, data): 130 | """ Perform a single request and return the deserialized response. 131 | """ 132 | headers = { 133 | "User-Agent": "jmx4py 0.1", # TODO: add automatic version detection 134 | } 135 | req_body = json.dumps(data) # TODO: using data automatically select POST as method 136 | req = urllib2.Request(self.url, data=req_body, headers=headers, unverifiable=True) 137 | 138 | handle = network.urlopen(req) # TODO: , username, password) 139 | try: 140 | # TODO: wire debugging 141 | # if debug: 142 | # log.trace("Reponse headers for %r:\n %s" % ( 143 | # url, "\n ".join(i.strip() for i in handle.info().headers) 144 | # )) 145 | result = json.loads(handle.read()) 146 | return result 147 | finally: 148 | handle.close() 149 | -------------------------------------------------------------------------------- /src/tests/test_jolokia_client.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # pylint: disable=missing-docstring, wildcard-import, unused-wildcard-import 3 | # pylint: disable=too-few-public-methods, bad-continuation 4 | """ Jolokia client proxy tests. 5 | """ 6 | # Copyright 2011 Juergen Hermann 7 | # 8 | # Licensed under the Apache License, Version 2.0 (the "License"); 9 | # you may not use this file except in compliance with the License. 10 | # You may obtain a copy of the License at 11 | # 12 | # http://www.apache.org/licenses/LICENSE-2.0 13 | # 14 | # Unless required by applicable law or agreed to in writing, software 15 | # distributed under the License is distributed on an "AS IS" BASIS, 16 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | # See the License for the specific language governing permissions and 18 | # limitations under the License. 19 | from __future__ import absolute_import, unicode_literals, print_function 20 | 21 | import logging 22 | import unittest 23 | import urllib2 24 | 25 | from conftest import JmxMockedConnection, JvmTestCase 26 | from jmx4py.jolokia.connection import JmxHttpConnection 27 | from jmx4py.jolokia.client import * #@UnusedWildImport 28 | 29 | log = logging.getLogger(__name__) 30 | 31 | 32 | class JmxEscapingTest(unittest.TestCase): 33 | 34 | # Unescaped and escaped test data 35 | DATA = ( 36 | (None, None), 37 | ("", ""), 38 | ("a", "a"), 39 | ("!", "!!"), 40 | ("a/b", "a!/b"), 41 | ("a/b/c", "a!/b!/c"), 42 | ("a!b/c", "a!!b!/c"), 43 | ) 44 | 45 | def test_quote(self): 46 | for text, quoted in self.DATA: 47 | self.assertEqual(quote(text), quoted) 48 | 49 | 50 | def test_unquote(self): 51 | for text, quoted in self.DATA: 52 | self.assertEqual(text, unquote(quoted)) 53 | 54 | 55 | def test_unquote_extra(self): 56 | self.assertEqual("ab!/z", unquote("!a!b!!!/!z")) 57 | 58 | 59 | def test_unquote_trail(self): 60 | self.failUnlessRaises(ValueError, unquote, "!") 61 | self.failUnlessRaises(ValueError, unquote, "!!!") 62 | 63 | 64 | class JmxClientTest(unittest.TestCase): 65 | 66 | def test_client_connection(self): 67 | proxy = JmxClient("mock:") 68 | self.failUnless(isinstance(proxy.connection, JmxMockedConnection)) 69 | 70 | 71 | def test_host_port(self): 72 | proxy = JmxClient(("localhost", "8080")) 73 | self.failUnless(isinstance(proxy.connection, JmxHttpConnection)) 74 | self.failUnlessEqual(proxy.connection.url, "http://localhost:8080/jolokia/") 75 | 76 | 77 | def test_bad_scheme(self): 78 | self.failUnlessRaises(urllib2.URLError, JmxClient, "foobar:") 79 | 80 | 81 | def test_bad_port(self): 82 | self.failUnlessRaises(urllib2.URLError, JmxClient, ("localhost", "x")) 83 | 84 | 85 | class JmxClientBasicsTest(JvmTestCase): 86 | 87 | def test_repr(self): 88 | self.failUnless("localhost" in repr(self.proxy)) 89 | 90 | 91 | def test_bad_type(self): 92 | self.failUnlessRaises(JmxResponseError, self.proxy._execute, type="foo bar baz") 93 | 94 | self.failUnlessEqual(self.proxy.connection.calls, 1) 95 | self.failUnlessEqual(self.proxy.connection.errors, 1) 96 | 97 | 98 | class JmxClientReadTest(JvmTestCase): 99 | 100 | def test_read(self): 101 | resp = self.proxy.read("java.lang:type=Memory") 102 | self.failUnless(all(i in resp.value for i in ["HeapMemoryUsage", "NonHeapMemoryUsage"])) 103 | 104 | 105 | def test_read_with_path(self): 106 | resp = self.proxy.read("java.lang:type=Memory", "HeapMemoryUsage", "used") 107 | self.failUnless(isinstance(resp.value, int)) 108 | 109 | 110 | def test_multi_read(self): 111 | resp = self.proxy.read("java.lang:type=Memory", ["HeapMemoryUsage", "NonHeapMemoryUsage"]) 112 | self.failUnlessEqual(set(resp.value.keys()), set(["HeapMemoryUsage", "NonHeapMemoryUsage"])) 113 | 114 | 115 | def test_multi_read_with_path(self): 116 | self.failUnlessRaises(JmxResponseError, self.proxy.read, 117 | "java.lang:type=Memory", ["HeapMemoryUsage", "NonHeapMemoryUsage"], "used") 118 | 119 | 120 | class JmxClientWriteTest(JvmTestCase): 121 | 122 | def test_write(self): 123 | pass 124 | #TODO: resp = self.proxy.write("java.lang:type=...", ...) 125 | 126 | 127 | class JmxClientInvokeTest(JvmTestCase): 128 | 129 | def test_invoke(self): 130 | pass # TODO: write test 131 | 132 | 133 | class JmxClientSearchTest(JvmTestCase): 134 | 135 | def test_search(self): 136 | pass # TODO: write test 137 | 138 | 139 | class JmxClientVersionTest(JvmTestCase): 140 | 141 | def test_version(self): 142 | version = self.proxy.version() 143 | 144 | self.failUnlessEqual(self.proxy.connection.calls, 1) 145 | self.failUnlessEqual(self.proxy.connection.errors, 0) 146 | 147 | self.failUnlessEqual(version["status"], 200) 148 | self.failUnless(isinstance(version["timestamp"], int)) 149 | self.failUnlessEqual(version["request"]["type"], "version") 150 | self.failUnless(version.protocol.startswith("6.")) 151 | -------------------------------------------------------------------------------- /src/jmx4py/_compat.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # pylint: disable=invalid-name, unused-import, missing-docstring, exec-used 3 | # pylint: disable=unused-argument, too-few-public-methods, redefined-builtin 4 | # pylint: disable=no-name-in-module, no-member, undefined-variable 5 | # pylint: disable=import-error 6 | # flake8: noqa 7 | """ 8 | jmx4py._compat 9 | 10 | Some py2/py3 compatibility support based on a stripped down 11 | version of six so there is no dependency on a specific version 12 | of it. 13 | 14 | Copied from Jinja2 (2015-03-22, #9bf5fcb). See also 15 | 16 | http://lucumr.pocoo.org/2013/5/21/porting-to-python-3-redux/ 17 | 18 | :copyright: Copyright (c) 2013 by the Jinja team, see their AUTHORS. 19 | :license: BSD, see the module's source for details. 20 | """ 21 | # Some rights reserved. 22 | # 23 | # Redistribution and use in source and binary forms, with or without 24 | # modification, are permitted provided that the following conditions are 25 | # met: 26 | # 27 | # * Redistributions of source code must retain the above copyright 28 | # notice, this list of conditions and the following disclaimer. 29 | # 30 | # * Redistributions in binary form must reproduce the above 31 | # copyright notice, this list of conditions and the following 32 | # disclaimer in the documentation and/or other materials provided 33 | # with the distribution. 34 | # 35 | # * The names of the contributors may not be used to endorse or 36 | # promote products derived from this software without specific 37 | # prior written permission. 38 | # 39 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 40 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 41 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 42 | # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 43 | # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 44 | # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 45 | # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 46 | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 47 | # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 48 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 49 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 50 | 51 | import sys 52 | 53 | PY2 = sys.version_info[0] == 2 54 | PYPY = hasattr(sys, 'pypy_translation_info') 55 | _identity = lambda x: x 56 | 57 | 58 | if not PY2: 59 | unichr = chr 60 | range_type = range 61 | text_type = str 62 | string_types = (str,) 63 | integer_types = (int,) 64 | 65 | iterkeys = lambda d: iter(d.keys()) 66 | itervalues = lambda d: iter(d.values()) 67 | iteritems = lambda d: iter(d.items()) 68 | 69 | import pickle 70 | from io import BytesIO, StringIO 71 | NativeStringIO = StringIO 72 | 73 | def reraise(tp, value, tb=None): 74 | if value.__traceback__ is not tb: 75 | raise value.with_traceback(tb) 76 | raise value 77 | 78 | ifilter = filter 79 | imap = map 80 | izip = zip 81 | intern = sys.intern 82 | 83 | implements_iterator = _identity 84 | implements_to_string = _identity 85 | encode_filename = _identity 86 | get_next = lambda x: x.__next__ 87 | 88 | else: 89 | unichr = unichr 90 | text_type = unicode 91 | range_type = xrange 92 | string_types = (str, unicode) 93 | integer_types = (int, long) 94 | 95 | iterkeys = lambda d: d.iterkeys() 96 | itervalues = lambda d: d.itervalues() 97 | iteritems = lambda d: d.iteritems() 98 | 99 | import cPickle as pickle 100 | from cStringIO import StringIO as BytesIO, StringIO 101 | NativeStringIO = BytesIO 102 | 103 | exec('def reraise(tp, value, tb=None):\n raise tp, value, tb') 104 | 105 | from itertools import imap, izip, ifilter 106 | intern = intern 107 | 108 | def implements_iterator(cls): 109 | cls.next = cls.__next__ 110 | del cls.__next__ 111 | return cls 112 | 113 | def implements_to_string(cls): 114 | cls.__unicode__ = cls.__str__ 115 | cls.__str__ = lambda x: x.__unicode__().encode('utf-8') 116 | return cls 117 | 118 | get_next = lambda x: x.next 119 | 120 | def encode_filename(filename): 121 | if isinstance(filename, unicode): 122 | return filename.encode('utf-8') 123 | return filename 124 | 125 | 126 | def with_metaclass(meta, *bases): 127 | # This requires a bit of explanation: the basic idea is to make a 128 | # dummy metaclass for one level of class instanciation that replaces 129 | # itself with the actual metaclass. Because of internal type checks 130 | # we also need to make sure that we downgrade the custom metaclass 131 | # for one level to something closer to type (that's why __call__ and 132 | # __init__ comes back from type etc.). 133 | # 134 | # This has the advantage over six.with_metaclass in that it does not 135 | # introduce dummy classes into the final MRO. 136 | class metaclass(meta): 137 | __call__ = type.__call__ 138 | __init__ = type.__init__ 139 | def __new__(cls, name, this_bases, d): 140 | if this_bases is None: 141 | return type.__new__(cls, name, (), d) 142 | return meta(name, bases, d) 143 | return metaclass('temporary_class', None, {}) 144 | 145 | 146 | try: 147 | from urllib.parse import quote_from_bytes as url_quote 148 | except ImportError: 149 | from urllib import quote as url_quote 150 | -------------------------------------------------------------------------------- /_pavement_py: -------------------------------------------------------------------------------- 1 | # TODO: Copy the remainder to tasks.py 2 | """ jmx4py - A Python Client for the Jolokia JMX Agent. 3 | """ 4 | 5 | # You can override this with a local proxy URL, if available 6 | JOLOKIA_REPO_URL = os.environ.get("JOLOKIA_REPO_URL", "http://labs.consol.de/maven/repository") 7 | jolokia_version = "1.0.0" 8 | 9 | project = dict( 10 | data_files = [ 11 | ("EGG-INFO", [ 12 | "README", "LICENSE", "debian/changelog", 13 | ]), 14 | ], 15 | ) 16 | 17 | # 18 | # Helpers 19 | # 20 | def fail(msg): 21 | "Print error message and exit" 22 | error("BUILD ERROR: " + msg) 23 | sys.exit(1) 24 | 25 | 26 | def copy_url(url, dest): 27 | "Helper to copy an URL" 28 | import urllib2, shutil 29 | 30 | info("GET %s => %s" % (url, dest)) 31 | with closing(urllib2.urlopen(url)) as url_handle: 32 | with closing(open(dest, "wb")) as dest_handle: 33 | shutil.copyfileobj(url_handle, dest_handle) 34 | 35 | 36 | def pylint(opts=""): 37 | "Call pylint" 38 | sh("./bin/pylint %s --rcfile pylint.rc --import-graph=%s %s" % ( 39 | opts, path("build/imports.png").abspath(), project["name"]), 40 | ignore_error=True) # TODO: should check for return code ERROR bits and fail on errors 41 | 42 | 43 | def stop_all_jvms(): 44 | "Stop all running test JVMs." 45 | running_agents = [i.split(None, 1) 46 | for i in sh("jps -m", capture=True, ignore_error=True).splitlines() 47 | if "jmx4py-testjvm" in i 48 | ] 49 | for pid, cmd in running_agents: 50 | pid = int(pid, 10) 51 | info("Stopping '%s' (PID=%d)" % (cmd, pid)) 52 | os.kill(pid, signal.SIGTERM) 53 | for _ in range(25): 54 | time.sleep(0.1) 55 | try: 56 | os.kill(pid, signal.SIGTERM) 57 | except EnvironmentError: 58 | break 59 | else: 60 | info("WARNING: JVM '%s' (PID=%d) could not be stopped" % (cmd, pid)) 61 | 62 | 63 | def run_with_jvm(closure, *args, **kw): 64 | "Run the provided callable with a live JVM running in the background" 65 | if not sh("which mvn", capture=True, ignore_error=True): 66 | fail("Maven build tool not installed / available on your path!") 67 | 68 | with pushd("java/testjvm") as base_dir: 69 | # Build test app if not there 70 | jars = path("target").glob("jmx4py-*.jar") 71 | if not jars: 72 | sh("mvn package") 73 | jars = path("target").glob("jmx4py-*.jar") 74 | if not jars: 75 | fail("Maven build failed to produce an artifact!") 76 | 77 | # Get agent if not there 78 | if not path("target/jolokia-jvm-agent.jar").exists(): 79 | copy_url( 80 | "%s/org/jolokia/jolokia-jvm/%s/jolokia-jvm-%s-agent.jar" % (JOLOKIA_REPO_URL, jolokia_version, jolokia_version), 81 | "target/jolokia-jvm-agent.jar") 82 | 83 | # Start test JVM in background 84 | stop_all_jvms() 85 | jolokia_props_path = path(base_dir) / "java" / "jolokia.properties" 86 | jolokia_props = dict((key, val.strip()) for key, val in ( 87 | line.split(':', 1) for line in jolokia_props_path.lines() if ':' in line)) 88 | guard_file = path("/tmp/jmx4py-test-guard-%d" % os.getuid()) 89 | sh("java -javaagent:target/jolokia-jvm-agent.jar=config=%s -jar %s %s &" % ( 90 | jolokia_props_path, jars[0].abspath(), guard_file)) 91 | for _ in range(50): 92 | if guard_file.exists(): 93 | print "JVM name:", guard_file.text().strip() 94 | break 95 | time.sleep(.1) 96 | else: 97 | fail("JVM start failed") 98 | 99 | # Now run the given closure 100 | try: 101 | with pushd(base_dir): 102 | closure(*args, **kw) 103 | except KeyboardInterrupt: 104 | fail("Aborted by CTRL-C") 105 | finally: 106 | # Stop test JVM 107 | guard_file.remove() 108 | time.sleep(.25) 109 | stop_all_jvms() 110 | 111 | 112 | # 113 | # Tasks 114 | # 115 | @task 116 | @needs(["clean"]) 117 | def clean_dist(): 118 | "Clean up build and dist files." 119 | path("dist").rmtree() 120 | 121 | 122 | @task 123 | @needs(["clean_dist"]) 124 | def clean_all(): 125 | "Clean up everything." 126 | # clean up local eggs (setup requirements) 127 | for name in path(".").dirs("*.egg"): 128 | path(name).rmtree() 129 | for name in path(".").files("*.egg"): 130 | path(name).remove() 131 | 132 | # get rid of virtualenv environment 133 | bindir = path("bin") 134 | if not bindir.islink(): 135 | for exe in bindir.files(): 136 | path(exe).remove() 137 | for dirname in ("lib", "include", "Scripts"): 138 | path(dirname).rmtree() 139 | 140 | 141 | @task 142 | def jvmtests(): 143 | "Run integration tests against a live JVM" 144 | run_with_jvm(sh, "nosetests -a jvm") # run all tests! 145 | 146 | 147 | @task 148 | def explore(): 149 | "Run interactive interpreter against a live JVM" 150 | init = path(tempfile.mkstemp(".py", "jmx4py-")[1]) 151 | init.write_lines([ 152 | "from jmx4py import jolokia", 153 | "jp = jolokia.JmxClient(('localhost', 8089))", 154 | "print('Use the following object that was created for you to test the API:\\n')", 155 | "print('jp = %r\\n' % jp)", 156 | ]) 157 | run_with_jvm(sh, "bpython -i %s" % init) 158 | init.remove() 159 | 160 | 161 | @task 162 | @needs('generate_setup', 'minilib', 'distutils.command.sdist') 163 | def sdist(): 164 | "Package a source distribution" 165 | 166 | 167 | @task 168 | def docs(): 169 | "Build documentation" 170 | call_task("paver.doctools.html") 171 | 172 | 173 | @task 174 | def lint(): 175 | "Automatic source code check" 176 | pylint("-rn") 177 | 178 | 179 | @task 180 | def tests(): 181 | "Execute unit tests" 182 | # The nosetests task does dirty things to the process environment, spawn a new process 183 | sh("nosetests") 184 | 185 | 186 | @task 187 | def integration(): 188 | "Run all tasks adequate for continuous integration" 189 | call_task("build") 190 | call_task("jvmtests") 191 | pylint(">build/lint.log -ry -f parseable") 192 | call_task("docs") 193 | call_task("sdist") 194 | call_task("bdist_egg") 195 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # pylint: disable=bad-whitespace, attribute-defined-outside-init, invalid-name 4 | """ 5 | Python Client for the Jolokia JMX Agent – A Python Client for the Jolokia JMX Agent. 6 | 7 | This setuptools script follows the DRY principle and tries to 8 | minimize repetition of project metadata by loading it from other 9 | places (like the package's `__init__.py`). Incidently, this makes 10 | the script almost identical between different projects. 11 | 12 | It is also importable (by using the usual `if __name__ == '__main__'` 13 | idiom), and exposes the project's setup data in a `project` dict. 14 | This allows other tools to exploit the data assembling code contained 15 | in here, and again supports the DRY principle. The `rituals` package 16 | uses that to provide Invoke tasks that work for any project, based on 17 | its project metadata. 18 | 19 | Copyright © 2011 Jürgen Hermann 20 | 21 | Licensed under the Apache License, Version 2.0 (the "License"); 22 | you may not use this file except in compliance with the License. 23 | You may obtain a copy of the License at 24 | 25 | http://www.apache.org/licenses/LICENSE-2.0 26 | 27 | Unless required by applicable law or agreed to in writing, software 28 | distributed under the License is distributed on an "AS IS" BASIS, 29 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 30 | See the License for the specific language governing permissions and 31 | limitations under the License. 32 | """ 33 | 34 | # Project data (the rest is parsed from __init__.py and other project files) 35 | name = 'jmx4py' 36 | package_name = 'jmx4py' 37 | 38 | # ~~~ BEGIN springerle/py-generic-project ~~~ 39 | # Stdlib imports 40 | import os 41 | import re 42 | import sys 43 | import textwrap 44 | from codecs import open # pylint: disable=redefined-builtin 45 | from collections import defaultdict 46 | 47 | # Import setuptools 48 | try: 49 | from setuptools import setup, find_packages 50 | from setuptools.command.test import test as TestCommand 51 | except ImportError as exc: 52 | raise RuntimeError("Cannot install '{0}', setuptools is missing ({1})".format(name, exc)) 53 | 54 | # Helpers 55 | project_root = os.path.abspath(os.path.dirname(__file__)) 56 | 57 | def srcfile(*args): 58 | "Helper for path building." 59 | return os.path.join(*((project_root,) + args)) 60 | 61 | class PyTest(TestCommand): 62 | """pytest integration into setuptool's `test` command.""" 63 | user_options = [('pytest-args=', 'a', "Arguments to pass to py.test")] 64 | 65 | def initialize_options(self): 66 | TestCommand.initialize_options(self) 67 | self.pytest_args = [] 68 | 69 | def finalize_options(self): 70 | TestCommand.finalize_options(self) 71 | self.test_args = [] 72 | self.test_suite = True 73 | 74 | def run_tests(self): 75 | # import locally, cause outside the eggs aren't loaded 76 | import pytest 77 | errno = pytest.main(self.pytest_args) 78 | if errno: 79 | sys.exit(errno) 80 | 81 | def _build_metadata(): # pylint: disable=too-many-locals, too-many-branches 82 | "Return project's metadata as a dict." 83 | # Handle metadata in package source 84 | expected_keys = ('url', 'version', 'license', 'author', 'author_email', 'long_description', 'keywords') 85 | metadata = {} 86 | with open(srcfile('src', package_name, '__init__.py'), encoding='utf-8') as handle: 87 | pkg_init = handle.read() 88 | # Get default long description from docstring 89 | metadata['long_description'] = re.search(r'^"""(.+?)^"""$', pkg_init, re.DOTALL|re.MULTILINE).group(1) 90 | for line in pkg_init.splitlines(): 91 | match = re.match(r"""^__({0})__ += (?P['"])(.+?)(?P=q)$""".format('|'.join(expected_keys)), line) 92 | if match: 93 | metadata[match.group(1)] = match.group(3) 94 | 95 | if not all(i in metadata for i in expected_keys): 96 | raise RuntimeError("Missing or bad metadata in '{0}' package".format(name)) 97 | 98 | text = metadata['long_description'].strip() 99 | if text: 100 | metadata['description'], text = text.split('.', 1) 101 | metadata['description'] = ' '.join(metadata['description'].split()).strip() + '.' # normalize whitespace 102 | metadata['long_description'] = textwrap.dedent(text).strip() 103 | metadata['keywords'] = metadata['keywords'].replace(',', ' ').strip().split() 104 | 105 | # Load requirements files 106 | requirements_files = dict( 107 | install = 'requirements.txt', 108 | setup = 'setup-requirements.txt', 109 | test = 'test-requirements.txt', 110 | ) 111 | requires = {} 112 | for key, filename in requirements_files.items(): 113 | requires[key] = [] 114 | if os.path.exists(srcfile(filename)): 115 | with open(srcfile(filename), encoding='utf-8') as handle: 116 | for line in handle: 117 | line = line.strip() 118 | if line and not line.startswith('#'): 119 | if line.startswith('-e'): 120 | line = line.split()[1].split('#egg=')[1] 121 | requires[key].append(line) 122 | if not any('pytest' == re.split('[\t ,<=>]', i.lower())[0] for i in requires['test']): 123 | requires['test'].append('pytest') # add missing requirement 124 | 125 | # CLI entry points 126 | console_scripts = [] 127 | for path, dirs, files in os.walk(srcfile('src', package_name)): 128 | dirs = [i for i in dirs if not i.startswith('.')] 129 | if '__main__.py' in files: 130 | path = path[len(srcfile('src') + os.sep):] 131 | appname = path.split(os.sep)[-1] 132 | with open(srcfile('src', path, '__main__.py'), encoding='utf-8') as handle: 133 | for line in handle.readlines(): 134 | match = re.match(r"""^__app_name__ += (?P['"])(.+?)(?P=q)$""", line) 135 | if match: 136 | appname = match.group(2) 137 | console_scripts.append('{0} = {1}.__main__:cli'.format(appname, path.replace(os.sep, '.'))) 138 | 139 | # Add some common files to EGG-INFO 140 | candidate_files = [ 141 | 'LICENSE', 'NOTICE', 142 | 'README', 'README.md', 'README.rst', 'README.txt', 143 | 'CHANGES', 'CHANGELOG', 'debian/changelog', 144 | ] 145 | data_files = defaultdict(list) 146 | for filename in candidate_files: 147 | if os.path.exists(srcfile(filename)): 148 | data_files['EGG-INFO'].append(filename) 149 | 150 | # Complete project metadata 151 | classifiers = [] 152 | for classifiers_txt in ('classifiers.txt', 'project.d/classifiers.txt'): 153 | classifiers_txt = srcfile(classifiers_txt) 154 | if os.path.exists(classifiers_txt): 155 | with open(classifiers_txt, encoding='utf-8') as handle: 156 | classifiers = [i.strip() for i in handle if i.strip() and not i.startswith('#')] 157 | break 158 | 159 | metadata.update(dict( 160 | name = name, 161 | package_dir = {'': 'src'}, 162 | packages = find_packages(srcfile('src'), exclude=['tests']), 163 | data_files = data_files.items(), 164 | zip_safe = False, 165 | include_package_data = True, 166 | install_requires = requires['install'], 167 | setup_requires = requires['setup'], 168 | tests_require = requires['test'], 169 | classifiers = classifiers, 170 | cmdclass = dict( 171 | test = PyTest, 172 | ), 173 | entry_points = dict( 174 | console_scripts = console_scripts, 175 | ), 176 | )) 177 | return metadata 178 | 179 | # Ensure "setup.py" is importable by other tools, to access the project's metadata 180 | project = _build_metadata() 181 | __all__ = ['project', 'project_root', 'package_name', 'srcfile'] 182 | if __name__ == '__main__': 183 | setup(**project) 184 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # jmx4py documentation build configuration file, created by 4 | # sphinx-quickstart on Thu Nov 3 18:52:57 2011. 5 | # 6 | # This file is execfile()d with the current directory set to its containing dir. 7 | # 8 | # Note that not all possible configuration values are present in this 9 | # autogenerated file. 10 | # 11 | # All configuration values have a default; values that are commented out 12 | # serve to show the default. 13 | 14 | import sys, os 15 | 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | #sys.path.insert(0, os.path.abspath('.')) 20 | 21 | # -- General configuration ----------------------------------------------------- 22 | 23 | # If your documentation needs a minimal Sphinx version, state it here. 24 | #needs_sphinx = '1.0' 25 | 26 | # Add any Sphinx extension module names here, as strings. They can be extensions 27 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 28 | extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.ifconfig', 'sphinx.ext.viewcode'] 29 | 30 | # Add any paths that contain templates here, relative to this directory. 31 | templates_path = ['_templates'] 32 | 33 | # The suffix of source filenames. 34 | source_suffix = '.rst' 35 | 36 | # The encoding of source files. 37 | #source_encoding = 'utf-8-sig' 38 | 39 | # The master toctree document. 40 | master_doc = 'index' 41 | 42 | # General information about the project. 43 | project = u'jmx4py' 44 | copyright = u'2011, Jürgen Hermann' 45 | 46 | # The version info for the project you're documenting, acts as replacement for 47 | # |version| and |release|, also used in various other places throughout the 48 | # built documents. 49 | # 50 | # The short X.Y version. 51 | version = '1.0' 52 | # The full version, including alpha/beta/rc tags. 53 | release = '1.0' 54 | 55 | # The language for content autogenerated by Sphinx. Refer to documentation 56 | # for a list of supported languages. 57 | #language = None 58 | 59 | # There are two options for replacing |today|: either, you set today to some 60 | # non-false value, then it is used: 61 | #today = '' 62 | # Else, today_fmt is used as the format for a strftime call. 63 | #today_fmt = '%B %d, %Y' 64 | 65 | # List of patterns, relative to source directory, that match files and 66 | # directories to ignore when looking for source files. 67 | exclude_patterns = [] 68 | 69 | # The reST default role (used for this markup: `text`) to use for all documents. 70 | #default_role = None 71 | 72 | # If true, '()' will be appended to :func: etc. cross-reference text. 73 | #add_function_parentheses = True 74 | 75 | # If true, the current module name will be prepended to all description 76 | # unit titles (such as .. function::). 77 | #add_module_names = True 78 | 79 | # If true, sectionauthor and moduleauthor directives will be shown in the 80 | # output. They are ignored by default. 81 | #show_authors = False 82 | 83 | # The name of the Pygments (syntax highlighting) style to use. 84 | pygments_style = 'sphinx' 85 | 86 | # A list of ignored prefixes for module index sorting. 87 | #modindex_common_prefix = [] 88 | 89 | 90 | # -- Options for HTML output --------------------------------------------------- 91 | 92 | # The theme to use for HTML and HTML Help pages. See the documentation for 93 | # a list of builtin themes. 94 | html_theme = 'default' 95 | 96 | # Theme options are theme-specific and customize the look and feel of a theme 97 | # further. For a list of options available for each theme, see the 98 | # documentation. 99 | #html_theme_options = {} 100 | 101 | # Add any paths that contain custom themes here, relative to this directory. 102 | #html_theme_path = [] 103 | 104 | # The name for this set of Sphinx documents. If None, it defaults to 105 | # " v documentation". 106 | #html_title = None 107 | 108 | # A shorter title for the navigation bar. Default is the same as html_title. 109 | #html_short_title = None 110 | 111 | # The name of an image file (relative to this directory) to place at the top 112 | # of the sidebar. 113 | #html_logo = None 114 | 115 | # The name of an image file (within the static path) to use as favicon of the 116 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 117 | # pixels large. 118 | #html_favicon = None 119 | 120 | # Add any paths that contain custom static files (such as style sheets) here, 121 | # relative to this directory. They are copied after the builtin static files, 122 | # so a file named "default.css" will overwrite the builtin "default.css". 123 | html_static_path = ['_static'] 124 | 125 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 126 | # using the given strftime format. 127 | #html_last_updated_fmt = '%b %d, %Y' 128 | 129 | # If true, SmartyPants will be used to convert quotes and dashes to 130 | # typographically correct entities. 131 | #html_use_smartypants = True 132 | 133 | # Custom sidebar templates, maps document names to template names. 134 | #html_sidebars = {} 135 | 136 | # Additional templates that should be rendered to pages, maps page names to 137 | # template names. 138 | #html_additional_pages = {} 139 | 140 | # If false, no module index is generated. 141 | #html_domain_indices = True 142 | 143 | # If false, no index is generated. 144 | #html_use_index = True 145 | 146 | # If true, the index is split into individual pages for each letter. 147 | #html_split_index = False 148 | 149 | # If true, links to the reST sources are added to the pages. 150 | #html_show_sourcelink = True 151 | 152 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 153 | #html_show_sphinx = True 154 | 155 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 156 | #html_show_copyright = True 157 | 158 | # If true, an OpenSearch description file will be output, and all pages will 159 | # contain a tag referring to it. The value of this option must be the 160 | # base URL from which the finished HTML is served. 161 | #html_use_opensearch = '' 162 | 163 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 164 | #html_file_suffix = None 165 | 166 | # Output file base name for HTML help builder. 167 | htmlhelp_basename = 'jmx4pydoc' 168 | 169 | 170 | # -- Options for LaTeX output -------------------------------------------------- 171 | 172 | latex_elements = { 173 | # The paper size ('letterpaper' or 'a4paper'). 174 | #'papersize': 'letterpaper', 175 | 176 | # The font size ('10pt', '11pt' or '12pt'). 177 | #'pointsize': '10pt', 178 | 179 | # Additional stuff for the LaTeX preamble. 180 | #'preamble': '', 181 | } 182 | 183 | # Grouping the document tree into LaTeX files. List of tuples 184 | # (source start file, target name, title, author, documentclass [howto/manual]). 185 | latex_documents = [ 186 | ('index', 'jmx4py.tex', u'jmx4py Documentation', 187 | u'Jürgen Hermann', 'manual'), 188 | ] 189 | 190 | # The name of an image file (relative to this directory) to place at the top of 191 | # the title page. 192 | #latex_logo = None 193 | 194 | # For "manual" documents, if this is true, then toplevel headings are parts, 195 | # not chapters. 196 | #latex_use_parts = False 197 | 198 | # If true, show page references after internal links. 199 | #latex_show_pagerefs = False 200 | 201 | # If true, show URL addresses after external links. 202 | #latex_show_urls = False 203 | 204 | # Documents to append as an appendix to all manuals. 205 | #latex_appendices = [] 206 | 207 | # If false, no module index is generated. 208 | #latex_domain_indices = True 209 | 210 | 211 | # -- Options for manual page output -------------------------------------------- 212 | 213 | # One entry per manual page. List of tuples 214 | # (source start file, name, description, authors, manual section). 215 | man_pages = [ 216 | ('index', 'jmx4py', u'jmx4py Documentation', 217 | [u'Jürgen Hermann'], 1) 218 | ] 219 | 220 | # If true, show URL addresses after external links. 221 | #man_show_urls = False 222 | 223 | 224 | # -- Options for Texinfo output ------------------------------------------------ 225 | 226 | # Grouping the document tree into Texinfo files. List of tuples 227 | # (source start file, target name, title, author, 228 | # dir menu entry, description, category) 229 | texinfo_documents = [ 230 | ('index', 'jmx4py', u'jmx4py Documentation', 231 | u'Juergen Hermann', 'jmx4py', 'One line description of project.', 232 | 'Miscellaneous'), 233 | ] 234 | 235 | # Documents to append as an appendix to all manuals. 236 | #texinfo_appendices = [] 237 | 238 | # If false, no module index is generated. 239 | #texinfo_domain_indices = True 240 | 241 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 242 | #texinfo_show_urls = 'footnote' 243 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2011 Juergen Hermann 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /src/jmx4py/jolokia/client.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # pylint: disable=bad-whitespace, too-few-public-methods 3 | """ JMX Client Proxy. 4 | 5 | See http://www.jolokia.org/reference/html/protocol.html for a 6 | detailed description of the Jolokia protocol and different ways 7 | to query for information. The client API methods only describe 8 | the major points, and specifics of the Python interface. 9 | """ 10 | # Copyright 2011 Juergen Hermann 11 | # 12 | # Licensed under the Apache License, Version 2.0 (the "License"); 13 | # you may not use this file except in compliance with the License. 14 | # You may obtain a copy of the License at 15 | # 16 | # http://www.apache.org/licenses/LICENSE-2.0 17 | # 18 | # Unless required by applicable law or agreed to in writing, software 19 | # distributed under the License is distributed on an "AS IS" BASIS, 20 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 21 | # See the License for the specific language governing permissions and 22 | # limitations under the License. 23 | from __future__ import absolute_import, unicode_literals, print_function 24 | 25 | import re 26 | import datetime 27 | from contextlib import closing 28 | 29 | from jmx4py.util import json, auxiliary 30 | from jmx4py.jolokia.errors import JmxResponseError 31 | from jmx4py.jolokia.connection import JmxConnection 32 | 33 | _QUOTE_RE = re.compile(r"([!/])") 34 | _UNQUOTE_RE = re.compile(r"!(.)") 35 | 36 | 37 | def quote(path): 38 | """ Escape a path according to Jolokia v6 rules. 39 | """ 40 | return _QUOTE_RE.sub(r"!\1", path) if path else path 41 | 42 | 43 | 44 | def unquote(path): 45 | """ Un-escape a path according to Jolokia v6 rules. 46 | """ 47 | if not path: 48 | return path 49 | if path.endswith('!') and (len(path) - len(path.rstrip('!'))) % 2: 50 | raise ValueError("Invalid trailing escape in %r" % path) 51 | return _UNQUOTE_RE.sub(r"\1", path) 52 | 53 | 54 | #class JmxRequest(object): 55 | # """ JMX Proxy Request. 56 | # """ 57 | # 58 | # def __init__(self, **kw): 59 | # """ Populate request from given keyword arguments. 60 | # """ 61 | 62 | 63 | class JmxResponse(dict): 64 | """ JMX Proxy Response. Wraps responses into a fancy accessor. 65 | """ 66 | 67 | def __str__(self): 68 | """ Return fully nested representation of JMX response (JSON dump). 69 | """ 70 | return json.dumps(self, indent=2) 71 | 72 | 73 | def __repr__(self): 74 | """ Return short representation of JMX response. 75 | 76 | >>> jp.read("java.lang:type=Memory", "HeapMemoryUsage", path="used") 77 | 78 | """ 79 | return "<%s type=%s status=%d timestamp=%s value=%s>" % ( 80 | self.__class__.__name__, self["request"]["type"], self["status"], 81 | datetime.datetime.fromtimestamp(self["timestamp"]).isoformat(), 82 | auxiliary.digest_repr(self["value"]), 83 | ) 84 | 85 | 86 | def __getitem__(self, key): 87 | try: 88 | return super(JmxResponse, self).__getitem__(key) 89 | except KeyError: 90 | if '.' in key: 91 | namespace = self["value"] 92 | for part in key.split('.'): 93 | namespace = namespace.get(part, None) 94 | if not namespace: 95 | break 96 | return namespace 97 | elif key in self["value"]: 98 | return self["value"][key] 99 | raise 100 | 101 | 102 | def __getattr__(self, name): 103 | try: 104 | # TODO: Check if there's such a wrapper already out there! 105 | # TODO: Need to handle nested access (response.foo.bar) 106 | return self[name] 107 | except KeyError: 108 | return getattr(super(JmxResponse, self), name) 109 | 110 | 111 | 112 | class JmxClientConfig(object): 113 | """ JMX Client Proxy Configuration. 114 | 115 | TODO: atually pass these on to the connection! 116 | user User name when authentication is used. 117 | If not set, no authentication is used. If set, password must be set, too 118 | password Password used for authentication. Only used when user is set. 119 | 120 | TODO: more parameters? 121 | timeout The timeout in seconds for network operations. 122 | contentCharset Defines the charset to be used per default for encoding content body. ISO-8859-1 123 | expectContinue Activates Expect: 100-Continue handshake for the entity enclosing methods. 124 | The purpose of the Expect: 100-Continue handshake to allow a client that is sending 125 | a request message with a request body to determine if the origin server is willing 126 | to accept the request (based on the request headers) before the client sends the 127 | request body. The use of the Expect: 100-continue handshake can result in noticable 128 | peformance improvement for entity enclosing requests that require the target server's 129 | authentication. true 130 | """ 131 | 132 | def __init__(self, url, **kw): 133 | """ Store configuration as given in keyword parameters, mixing in default values. 134 | """ 135 | self.url = url 136 | self.user = kw.get("user", None) 137 | self.password = kw.get("password", None) 138 | self.method = kw.get("method", "POST") 139 | #self. = kw.get("", None) 140 | 141 | 142 | class JmxClient(object): 143 | """ JMX Client Proxy. 144 | """ 145 | # TODO: Historical values - http://www.jolokia.org/reference/html/protocol.html#history 146 | # TODO: Support bulk requests - http://www.jolokia.org/reference/html/protocol.html#post-request 147 | 148 | def __init__(self, url, **kw): 149 | """ Open a proxy connection to a Jolokia agent. 150 | """ 151 | self.cfg = JmxClientConfig(url, **kw) 152 | self.connection = JmxConnection.from_url(self.cfg.url) 153 | 154 | 155 | def __repr__(self): 156 | """ Return client proxy identification. 157 | """ 158 | return "%s(%r)" % (self.__class__.__name__, self.connection.url) 159 | 160 | 161 | def _execute(self, **kw): 162 | """ Execute a request as defined by the given keyword arguments. 163 | 164 | TODO: Do we need this? 165 | MAX_DEPTH Maximum traversal depth for serialization of complex objects. 166 | Use this with a "list" request to restrict the depth of the returned meta data tree. 167 | MAX_COLLECTION_SIZE Maximum size of collections returned during serialization. 168 | If larger, a collection is truncated to this size. 169 | MAX_OBJECTS Maximum number of objects returned in the response's value. 170 | IGNORE_ERRORS Option for ignoring errors during JMX operations and JSON serialization. 171 | This works only for certain operations like pattern reads and should be either true or false. 172 | """ 173 | req = kw # pass on unchanged keyword params, for now 174 | 175 | with closing(self.connection.open()) as handle: 176 | resp = JmxResponse(handle.send(req)) 177 | if resp.status != 200: 178 | raise JmxResponseError(resp, req) 179 | return resp 180 | 181 | 182 | def read(self, mbean, attribute=None, path=None, **kw): 183 | """ A read request gets one or more attributes from one or more 184 | MBeans within a single request. 185 | 186 | Various call variants can be used to specify one or more 187 | attributes along with the JMX ObjectName (which can be a pattern). A 188 | path can be set as property for specifying an inner path, too. 189 | 190 | A read request for multiple attributes on the same MBean is initiated 191 | by giving a list of attributes to the request. If no attribute is 192 | provided, then all attributes are fetched. The MBean name can be 193 | given as a pattern in which case the attributes are read on all 194 | matching MBeans. If a MBean pattern and multiple attributes are 195 | requestes, then only the value of attributes which matches both 196 | are returned, the others are ignored. Paths cannot be used with 197 | multi value reads, though. 198 | """ 199 | req = dict(type = "read", mbean = mbean) 200 | if attribute: 201 | req["attribute"] = attribute 202 | if path: 203 | req["path"] = quote(path) 204 | if kw: 205 | req.update(kw) 206 | resp = self._execute(**req) 207 | return resp 208 | 209 | 210 | def write(self): 211 | """ TODO: Implement write() 212 | J4pWriteRequest and J4pWriteResponse 213 | 214 | A J4pWriteRequest is used to set the value of an MBean 215 | attribute. Beside the mandatory object and attribute name the 216 | value must be give in the constructor as well. Optionally a path 217 | can be provided, too. Only certain types for the given value can 218 | be serialized properly for calling the Jolokia agent as described 219 | in Section 6.4.2, "Request parameter serialization". 220 | 221 | The old value is returned as J4pWriteResponse's value. 222 | J4pExecRequest and J4pExecResponse 223 | 224 | """ 225 | resp = self._execute(type = "write") 226 | return resp 227 | 228 | 229 | def invoke(self): 230 | """ TODO: Implement invoke() 231 | J4pExecRequests are used for executing operation on MBeans. 232 | The constructor takes as mandatory arguments the MBean's object 233 | name, the operation name and any arguments required by the 234 | operation. Only certain types for the given arguments can be 235 | serialized properly for calling the Jolokia agent as described in 236 | Section 6.4.2, "Request parameter serialization". 237 | 238 | The returned J4pExecResponse contains the return value of the 239 | operation called. 240 | J4pSearchRequest and J4pSearchResponse 241 | 242 | """ 243 | resp = self._execute(type = "invoke") 244 | return resp 245 | 246 | 247 | def search(self): 248 | """ TODO: Implement search() 249 | A J4pSearchRequest contains a valid single MBean object name 250 | pattern which is used for searching MBeans. 251 | 252 | The J4pSearchResponse holds a list of found object names. 253 | J4pListRequest and J4pListResponse 254 | 255 | For obtaining meta data on MBeans a J4pListRequest should be 256 | used. It can be used with a inner path to obtain only a subtree 257 | of the response, otherwise the whole tree as described in Section 258 | 6.2.5.3, "List response" is returned. With the query parameter 259 | maxDepth can be used to restrict the depth of returned tree. 260 | 261 | The single value of a J4pListResponse is a tree (or subtree) 262 | as a JSON object, which has the format described in Section 263 | 6.2.5.3, "List response". 264 | J4pVersionRequest 265 | 266 | """ 267 | resp = self._execute(type = "search") 268 | return resp 269 | 270 | 271 | def version(self): 272 | """ Request the Jolokia agent's version information. 273 | See JmxResponse for ways to access the result. 274 | 275 | >>> import jmx4py.jolokia 276 | >>> jp = jmx4py.jolokia.JmxClient(("localhost", 8089)) 277 | >>> jp 278 | JmxClient('http://localhost:8089/jolokia/') 279 | >>> jp.version().protocol_info[:1] 280 | (6,) 281 | >>> jp.version().agent_info[:2] 282 | (1, 0) 283 | """ 284 | resp = self._execute(type = "version") 285 | resp["value"]["protocol_info"] = tuple(int(i) for i in resp.protocol.split('.')) 286 | resp["value"]["agent_info"] = tuple(int(i) for i in resp.agent.split('.')) 287 | return resp 288 | -------------------------------------------------------------------------------- /project.d/pylint.cfg: -------------------------------------------------------------------------------- 1 | [MASTER] 2 | 3 | # Specify a configuration file. 4 | #rcfile= 5 | 6 | # Python code to execute, usually for sys.path manipulation such as 7 | # pygtk.require(). 8 | #init-hook= 9 | 10 | # Profiled execution. 11 | profile=no 12 | 13 | # Add files or directories to the blacklist. They should be base names, not 14 | # paths. 15 | ignore=CVS 16 | 17 | # Pickle collected data for later comparisons. 18 | persistent=yes 19 | 20 | # List of plugins (as comma separated values of python modules names) to load, 21 | # usually to register additional checkers. 22 | load-plugins= 23 | 24 | # Use multiple processes to speed up Pylint. 25 | jobs=1 26 | 27 | # Allow loading of arbitrary C extensions. Extensions are imported into the 28 | # active Python interpreter and may run arbitrary code. 29 | unsafe-load-any-extension=no 30 | 31 | # A comma-separated list of package or module names from where C extensions may 32 | # be loaded. Extensions are loading into the active Python interpreter and may 33 | # run arbitrary code 34 | extension-pkg-whitelist= 35 | 36 | 37 | [MESSAGES CONTROL] 38 | 39 | # Only show warnings with the listed confidence levels. Leave empty to show 40 | # all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED 41 | confidence= 42 | 43 | # Enable the message, report, category or checker with the given id(s). You can 44 | # either give multiple identifier separated by comma (,) or put this option 45 | # multiple time. See also the "--disable" option for examples. 46 | #enable= 47 | 48 | # Disable the message, report, category or checker with the given id(s). You 49 | # can either give multiple identifiers separated by comma (,) or put this 50 | # option multiple times (only on the command line, not in the configuration 51 | # file where it should appear only once).You can also use "--disable=all" to 52 | # disable everything first and then reenable specific checks. For example, if 53 | # you want to run only the similarities checker, you can use "--disable=all 54 | # --enable=similarities". If you want to run only the classes checker, but have 55 | # no Warning level messages displayed, use"--disable=all --enable=classes 56 | # --disable=W" 57 | #disable=E1608,W1627,E1601,E1603,E1602,E1605,E1604,E1607,E1606,W1621,W1620,W1623,W1622,W1625,W1624,W1609,W1626,W1607,W1606,W1605,W1604,W1603,W1602,W1601,I0021,I0020,W1618,W1619,W1630,W1631,W1610,W1611,W1612,W1613,W1614,W1615,W1616,W1617,W1632,W1633,W0704,W1628,W1629,W1608 58 | disable=locally-disabled, star-args 59 | 60 | [REPORTS] 61 | 62 | # Set the output format. Available formats are text, parseable, colorized, msvs 63 | # (visual studio) and html. You can also give a reporter class, eg 64 | # mypackage.mymodule.MyReporterClass. 65 | output-format=text 66 | 67 | # Put messages in a separate file for each module / package specified on the 68 | # command line instead of printing them on stdout. Reports (if any) will be 69 | # written in a file name "pylint_global.[txt|html]". 70 | files-output=no 71 | 72 | # Tells whether to display a full report or only the messages 73 | reports=no 74 | 75 | # Python expression which should return a note less than 10 (10 is the highest 76 | # note). You have access to the variables errors warning, statement which 77 | # respectively contain the number of errors / warnings messages and the total 78 | # number of statements analyzed. This is used by the global evaluation report 79 | # (RP0004). 80 | evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) 81 | 82 | # Add a comment according to your evaluation note. This is used by the global 83 | # evaluation report (RP0004). 84 | comment=no 85 | 86 | # Template used to display messages. This is a python new-style format string 87 | # used to format the message information. See doc for all details 88 | #msg-template= 89 | 90 | 91 | [TYPECHECK] 92 | 93 | # Tells whether missing members accessed in mixin class should be ignored. A 94 | # mixin class is detected if its name ends with "mixin" (case insensitive). 95 | ignore-mixin-members=yes 96 | 97 | # List of module names for which member attributes should not be checked 98 | # (useful for modules/projects where namespaces are manipulated during runtime 99 | # and thus existing member attributes cannot be deduced by static analysis 100 | ignored-modules= 101 | 102 | # List of classes names for which member attributes should not be checked 103 | # (useful for classes with attributes dynamically set). 104 | ignored-classes=SQLObject 105 | 106 | # When zope mode is activated, add a predefined set of Zope acquired attributes 107 | # to generated-members. 108 | zope=no 109 | 110 | # List of members which are set dynamically and missed by pylint inference 111 | # system, and so shouldn't trigger E0201 when accessed. Python regular 112 | # expressions are accepted. 113 | generated-members=REQUEST,acl_users,aq_parent 114 | 115 | 116 | [LOGGING] 117 | 118 | # Logging modules to check that the string format arguments are in logging 119 | # function parameter format 120 | logging-modules=logging 121 | 122 | 123 | [SIMILARITIES] 124 | 125 | # Minimum lines number of a similarity. 126 | min-similarity-lines=4 127 | 128 | # Ignore comments when computing similarities. 129 | ignore-comments=yes 130 | 131 | # Ignore docstrings when computing similarities. 132 | ignore-docstrings=yes 133 | 134 | # Ignore imports when computing similarities. 135 | ignore-imports=no 136 | 137 | 138 | [BASIC] 139 | 140 | # Required attributes for module, separated by a comma 141 | required-attributes= 142 | 143 | # List of builtins function names that should not be used, separated by a comma 144 | bad-functions=map,filter,input,apply 145 | 146 | # Good variable names which should always be accepted, separated by a comma 147 | good-names=i,j,k,ex,Run,_,kw,v 148 | 149 | # Bad variable names which should always be refused, separated by a comma 150 | bad-names=foo,bar,baz,toto,tutu,tata 151 | 152 | # Colon-delimited sets of names that determine each other's naming style when 153 | # the name regexes allow several styles. 154 | name-group= 155 | 156 | # Include a hint for the correct naming format with invalid-name 157 | include-naming-hint=no 158 | 159 | # Regular expression matching correct function names 160 | function-rgx=[a-z_][a-z0-9_]{2,30}$ 161 | 162 | # Naming hint for function names 163 | function-name-hint=[a-z_][a-z0-9_]{2,30}$ 164 | 165 | # Regular expression matching correct variable names 166 | variable-rgx=[a-z_][a-z0-9_]{2,30}$ 167 | 168 | # Naming hint for variable names 169 | variable-name-hint=[a-z_][a-z0-9_]{2,30}$ 170 | 171 | # Regular expression matching correct constant names 172 | const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))|log|app|manager$ 173 | 174 | # Naming hint for constant names 175 | const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$ 176 | 177 | # Regular expression matching correct attribute names 178 | attr-rgx=[a-z_][a-z0-9_]{2,30}$ 179 | 180 | # Naming hint for attribute names 181 | attr-name-hint=[a-z_][a-z0-9_]{2,30}$ 182 | 183 | # Regular expression matching correct argument names 184 | argument-rgx=[a-z_][a-z0-9_]{2,30}$ 185 | 186 | # Naming hint for argument names 187 | argument-name-hint=[a-z_][a-z0-9_]{2,30}$ 188 | 189 | # Regular expression matching correct class attribute names 190 | class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ 191 | 192 | # Naming hint for class attribute names 193 | class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ 194 | 195 | # Regular expression matching correct inline iteration names 196 | inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ 197 | 198 | # Naming hint for inline iteration names 199 | inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$ 200 | 201 | # Regular expression matching correct class names 202 | class-rgx=[A-Z_][a-zA-Z0-9]+$ 203 | 204 | # Naming hint for class names 205 | class-name-hint=[A-Z_][a-zA-Z0-9]+$ 206 | 207 | # Regular expression matching correct module names 208 | module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ 209 | 210 | # Naming hint for module names 211 | module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ 212 | 213 | # Regular expression matching correct method names 214 | method-rgx=[a-z_][a-z0-9_]{2,30}$ 215 | 216 | # Naming hint for method names 217 | method-name-hint=[a-z_][a-z0-9_]{2,30}$ 218 | 219 | # Regular expression which should only match function or class names that do 220 | # not require a docstring. 221 | no-docstring-rgx=__.*__ 222 | 223 | # Minimum line length for functions/classes that require docstrings, shorter 224 | # ones are exempt. 225 | docstring-min-length=-1 226 | 227 | 228 | [FORMAT] 229 | 230 | # Maximum number of characters on a single line. 231 | max-line-length=132 232 | 233 | # Regexp for a line that is allowed to be longer than the limit. 234 | ignore-long-lines=^\s*(# )??$ 235 | 236 | # Allow the body of an if to be on the same line as the test if there is no 237 | # else. 238 | single-line-if-stmt=no 239 | 240 | # List of optional constructs for which whitespace checking is disabled 241 | no-space-check=trailing-comma,dict-separator 242 | 243 | # Maximum number of lines in a module 244 | max-module-lines=1500 245 | 246 | # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 247 | # tab). 248 | indent-string=' ' 249 | 250 | # Number of spaces of indent required inside a hanging or continued line. 251 | indent-after-paren=4 252 | 253 | # Expected format of line ending, e.g. empty (any line ending), LF or CRLF. 254 | expected-line-ending-format= 255 | 256 | 257 | [SPELLING] 258 | 259 | # Spelling dictionary name. Available dictionaries: none. To make it working 260 | # install python-enchant package. 261 | spelling-dict= 262 | 263 | # List of comma separated words that should not be checked. 264 | spelling-ignore-words= 265 | 266 | # A path to a file that contains private dictionary; one word per line. 267 | spelling-private-dict-file= 268 | 269 | # Tells whether to store unknown words to indicated private dictionary in 270 | # --spelling-private-dict-file option instead of raising a message. 271 | spelling-store-unknown-words=no 272 | 273 | 274 | [VARIABLES] 275 | 276 | # Tells whether we should check for unused import in __init__ files. 277 | init-import=no 278 | 279 | # A regular expression matching the name of dummy variables (i.e. expectedly 280 | # not used). 281 | dummy-variables-rgx=_$|dummy 282 | 283 | # List of additional names supposed to be defined in builtins. Remember that 284 | # you should avoid to define new builtins when possible. 285 | additional-builtins= 286 | 287 | # List of strings which can identify a callback function by name. A callback 288 | # name must start or end with one of those strings. 289 | callbacks=cb_,_cb 290 | 291 | 292 | [MISCELLANEOUS] 293 | 294 | # List of note tags to take in consideration, separated by a comma. 295 | notes=FIXME,XXX,TODO 296 | 297 | 298 | [DESIGN] 299 | 300 | # Maximum number of arguments for function / method 301 | max-args=8 302 | 303 | # Argument names that match this expression will be ignored. Default to name 304 | # with leading underscore 305 | ignored-argument-names=_.* 306 | 307 | # Maximum number of locals for function / method body 308 | max-locals=15 309 | 310 | # Maximum number of return / yield for function / method body 311 | max-returns=6 312 | 313 | # Maximum number of branch for function / method body 314 | max-branches=12 315 | 316 | # Maximum number of statements in function / method body 317 | max-statements=50 318 | 319 | # Maximum number of parents for a class (see R0901). 320 | max-parents=7 321 | 322 | # Maximum number of attributes for a class (see R0902). 323 | max-attributes=7 324 | 325 | # Minimum number of public methods for a class (see R0903). 326 | min-public-methods=2 327 | 328 | # Maximum number of public methods for a class (see R0904). 329 | max-public-methods=20 330 | 331 | 332 | [CLASSES] 333 | 334 | # List of interface methods to ignore, separated by a comma. This is used for 335 | # instance to not check methods defines in Zope's Interface base class. 336 | ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by 337 | 338 | # List of method names used to declare (i.e. assign) instance attributes. 339 | defining-attr-methods=__init__,__new__,setUp,init 340 | 341 | # List of valid names for the first argument in a class method. 342 | valid-classmethod-first-arg=cls 343 | 344 | # List of valid names for the first argument in a metaclass class method. 345 | valid-metaclass-classmethod-first-arg=mcs 346 | 347 | # List of member names, which should be excluded from the protected access 348 | # warning. 349 | exclude-protected=_asdict,_fields,_replace,_source,_make 350 | 351 | 352 | [IMPORTS] 353 | 354 | # Deprecated modules which should not be used, separated by a comma 355 | deprecated-modules=regsub,TERMIOS,Bastion,rexec,string 356 | 357 | # Create a graph of every (i.e. internal and external) dependencies in the 358 | # given file (report RP0402 must not be disabled) 359 | import-graph= 360 | 361 | # Create a graph of external dependencies in the given file (report RP0402 must 362 | # not be disabled) 363 | ext-import-graph= 364 | 365 | # Create a graph of internal dependencies in the given file (report RP0402 must 366 | # not be disabled) 367 | int-import-graph= 368 | 369 | 370 | [EXCEPTIONS] 371 | 372 | # Exceptions that will emit a warning when being caught. Defaults to 373 | # "Exception" 374 | overgeneral-exceptions=Exception 375 | -------------------------------------------------------------------------------- /docs/_static/jmx4py-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 24 | 26 | 30 | 34 | 35 | 45 | 55 | 65 | 75 | 86 | 88 | 92 | 96 | 97 | 99 | 103 | 107 | 108 | 110 | 114 | 118 | 119 | 121 | 125 | 129 | 130 | 139 | 149 | 155 | 159 | 165 | 171 | 172 | 178 | 183 | 188 | 192 | 193 | 202 | 207 | 212 | 216 | 222 | 226 | 227 | 233 | 237 | 243 | 249 | 250 | 256 | 261 | 266 | 270 | 271 | 281 | 282 | 292 | 294 | 298 | 302 | 303 | 312 | 322 | 327 | 333 | 337 | 338 | 344 | 348 | 349 | 354 | 362 | 367 | 368 | 378 | 387 | 397 | 406 | 416 | 425 | 426 | 446 | 448 | 449 | 451 | image/svg+xml 452 | 454 | 455 | 456 | 457 | 458 | 463 | 466 | 469 | 475 | 481 | 487 | 493 | 499 | 505 | 511 | 517 | 523 | 529 | 535 | 541 | 547 | 555 | 556 | 559 | 564 | 569 | 570 | 575 | 580 | 583 | 588 | 593 | 594 | 595 | 596 | 597 | --------------------------------------------------------------------------------