├── .gitignore ├── .project ├── .pydevproject ├── .settings ├── org.eclipse.core.resources.prefs ├── org.python.pydev.analysis.yaml └── org.python.pydev.yaml ├── README.md ├── _pyvmmonitor_core_tests ├── __init__.py ├── _py3_only.py ├── test_bounds.py ├── test_callback.py ├── test_commands.py ├── test_disposable.py ├── test_interface.py ├── test_iterables.py ├── test_kill.py ├── test_list_utils.py ├── test_log_utils.py ├── test_math_utils.py ├── test_memoize.py ├── test_nodes_tree.py ├── test_orderedset.py ├── test_plugins.py ├── test_props.py ├── test_system_mutex.py ├── test_weaklist.py └── test_weakmethod.py └── pyvmmonitor_core ├── __init__.py ├── appdirs.py ├── bunch.py ├── callback.py ├── capture.py ├── commands_manager.py ├── compat.py ├── disposable.py ├── dotpath.py ├── exec_external.py ├── html.py ├── interface.py ├── iterables.py ├── lazy_loading.py ├── list_utils.py ├── log_utils.py ├── lru.py ├── magic_vars.py ├── math_utils.py ├── memoization.py ├── mock.py ├── nodes_tree.py ├── null.py ├── ordered_set.py ├── path.py ├── plugins.py ├── props.py ├── system_mutex.py ├── thread_utils.py ├── time_utils.py ├── weak_utils.py └── weakmethod.py /.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 | .installed.cfg 23 | *.egg 24 | 25 | # PyInstaller 26 | # Usually these files are written by a python script from a template 27 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 28 | *.manifest 29 | *.spec 30 | 31 | # Installer logs 32 | pip-log.txt 33 | pip-delete-this-directory.txt 34 | 35 | # Unit test / coverage reports 36 | htmlcov/ 37 | .tox/ 38 | .coverage 39 | .cache 40 | nosetests.xml 41 | coverage.xml 42 | .pytest_cache 43 | 44 | # Translations 45 | *.mo 46 | *.pot 47 | 48 | # Django stuff: 49 | *.log 50 | 51 | # Sphinx documentation 52 | docs/_build/ 53 | 54 | # PyBuilder 55 | target/ 56 | 57 | .pytest_cache -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | pyvmmonitor-core 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 | 6 | /${PROJECT_DIR_NAME} 7 | 8 | 9 | 10 | python interpreter 11 | 12 | Default 13 | 14 | 3.0 15 | 16 | 17 | -------------------------------------------------------------------------------- /.settings/org.eclipse.core.resources.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | encoding//.settings/org.python.pydev.analysis.yaml=UTF-8 3 | encoding//.settings/org.python.pydev.yaml=UTF-8 4 | -------------------------------------------------------------------------------- /.settings/org.python.pydev.analysis.yaml: -------------------------------------------------------------------------------- 1 | SEVERITY_UNUSED_IMPORT: '1' 2 | NAMES_TO_CONSIDER_GLOBALS: _,tr 3 | PEP8_IGNORE_WARNINGS: --max-line-length=100 4 | SEVERITY_UNRESOLVED_IMPORT: '2' 5 | SEVERITY_NO_EFFECT_STMT: '1' 6 | SEVERITY_REIMPORT: '1' 7 | SEVERITY_PEP8: '1' 8 | SEVERITY_DUPLICATED_SIGNATURE: '2' 9 | DO_CODE_ANALYSIS: true 10 | SEVERITY_UNUSED_WILD_IMPORT: '1' 11 | SEVERITY_UNUSED_PARAMETER: '0' 12 | SEVERITY_UNUSED_VARIABLE: '1' 13 | SEVERITY_NO_SELF: '2' 14 | PEP8_USE_SYSTEM: true 15 | SEVERITY_INDENTATION_PROBLEM: '1' 16 | NAMES_TO_IGNORE_UNUSED_IMPORT: __init__, *QT 17 | SEVERITY_UNDEFINED_IMPORT_VARIABLE: '2' 18 | SEVERITY_ASSIGNMENT_TO_BUILT_IN_SYMBOL: '1' 19 | NAMES_TO_IGNORE_UNUSED_VARIABLE: dummy, _, unused 20 | SEVERITY_UNDEFINED_VARIABLE: '2' 21 | WHEN_ANALYZE: '2' 22 | -------------------------------------------------------------------------------- /.settings/org.python.pydev.yaml: -------------------------------------------------------------------------------- 1 | ADD_NEW_LINE_AT_END_OF_FILE: true 2 | AUTOPEP8_PARAMETERS: -a -a --max-line-length=100 --ignore=E309 3 | AUTO_ADD_SELF: true 4 | AUTO_BRACES: true 5 | AUTO_COLON: true 6 | AUTO_DEDENT_ELSE: true 7 | AUTO_FORMAT_ONLY_WORKSPACE_FILES: true 8 | AUTO_INDENT_AFTER_PAR_WIDTH: 1 9 | AUTO_INDENT_TO_PAR_LEVEL: false 10 | AUTO_LINK: false 11 | AUTO_LITERALS: true 12 | AUTO_PAR: true 13 | AUTO_WRITE_IMPORT_STR: true 14 | BLANK_LINES_BEFORE_INNER: 1 15 | BLANK_LINES_BEFORE_TOP_LEVEL: 2 16 | BLANK_LINES_INNER: 1 17 | BLANK_LINES_TOP_LEVEL: 2 18 | BREAK_IMPORTS_MODE: PARENTHESIS 19 | DATE_FIELD_FORMAT: yyyy-MM-dd 20 | DATE_FIELD_NAME: __updated__ 21 | DELETE_UNUSED_IMPORTS: true 22 | ENABLE_DATE_FIELD_ACTION: false 23 | FORMAT_BEFORE_SAVING: true 24 | FORMAT_ONLY_CHANGED_LINES: false 25 | FORMAT_WITH_AUTOPEP8: false 26 | FROM_IMPORTS_FIRST: false 27 | GROUP_IMPORTS: true 28 | IMPORT_ENGINE: IMPORT_ENGINE_ISORT 29 | MANAGE_BLANK_LINES: true 30 | MULTILINE_IMPORTS: true 31 | PEP8_IMPORTS: true 32 | SAVE_ACTIONS_ONLY_ON_WORKSPACE_FILES: true 33 | SMART_INDENT_PAR: true 34 | SMART_LINE_MOVE: false 35 | SORT_IMPORTS_ON_SAVE: true 36 | SORT_NAMES_GROUPED: false 37 | SPACES_BEFORE_COMMENT: '2' 38 | SPACES_IN_START_COMMENT: '1' 39 | TRIM_EMPTY_LINES: true 40 | TRIM_MULTILINE_LITERALS: true 41 | USE_ASSIGN_WITH_PACES_INSIDER_PARENTESIS: false 42 | USE_OPERATORS_WITH_SPACE: true 43 | USE_SPACE_AFTER_COMMA: true 44 | USE_SPACE_FOR_PARENTESIS: false 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This project contains the Open Source bits of the core utilities used in PyVmMonitor (http://www.pyvmmonitor.com/) 2 | 3 | The licenses used are: 4 | 5 | - PSF License (null) 6 | - MIT (appdirs) 7 | - LGPL (other modules) 8 | -------------------------------------------------------------------------------- /_pyvmmonitor_core_tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fabioz/pyvmmonitor-core/b067267af6c4a9eb8eeb4fc91c439bf99fcc4d6e/_pyvmmonitor_core_tests/__init__.py -------------------------------------------------------------------------------- /_pyvmmonitor_core_tests/_py3_only.py: -------------------------------------------------------------------------------- 1 | class ISomething(object): 2 | 3 | def m1(self, a, *, b=10): 4 | pass 5 | 6 | 7 | class SomethingImpl(object): 8 | 9 | def m1(self, a, *, b=10): 10 | pass 11 | 12 | 13 | class SomethingImplWrong(object): 14 | 15 | def m1(self, a, *, b=5): 16 | pass 17 | -------------------------------------------------------------------------------- /_pyvmmonitor_core_tests/test_bounds.py: -------------------------------------------------------------------------------- 1 | def test_bounds(): 2 | from pyvmmonitor_core.math_utils import Bounds 3 | bounds = Bounds() 4 | assert not bounds.is_valid() 5 | bounds.add_point((10, 10)) 6 | 7 | assert bounds.is_valid() 8 | assert bounds.width == 0 9 | assert bounds.height == 0 10 | 11 | bounds.add_point((0, 0)) 12 | assert bounds.nodes == ((0, 0), (0, 10), (10, 10), (10, 0)) 13 | assert bounds.width == 10 14 | assert bounds.height == 10 15 | 16 | assert bounds.center == (5, 5) 17 | 18 | x, y, w, h = bounds 19 | assert (x, y, w, h) == (0, 0, 10, 10) 20 | -------------------------------------------------------------------------------- /_pyvmmonitor_core_tests/test_callback.py: -------------------------------------------------------------------------------- 1 | from pyvmmonitor_core.callback import Callback 2 | from pyvmmonitor_core.weak_utils import get_weakref 3 | 4 | 5 | def test_callback(): 6 | c = Callback() 7 | 8 | class F(object): 9 | def __init__(self): 10 | self.called = None 11 | 12 | def __call__(self, b): 13 | self.called = b 14 | 15 | f = F() 16 | c.register(f.__call__) 17 | assert len(c) == 1 18 | 19 | c(1) 20 | assert f.called == 1 21 | f = get_weakref(f) 22 | assert f() is None 23 | c(1) 24 | assert len(c) == 0 25 | 26 | 27 | def test_callback2(): 28 | c = Callback() 29 | 30 | class F(object): 31 | def __init__(self): 32 | self.called = None 33 | 34 | def __call__(self, b): 35 | self.called = b 36 | 37 | f = F() 38 | c.register(f) 39 | assert len(c) == 1 40 | 41 | c(1) 42 | assert f.called == 1 43 | f = get_weakref(f) 44 | assert f() is None 45 | c(1) 46 | assert len(c) == 0 47 | 48 | 49 | def test_callback3(): 50 | c = Callback() 51 | 52 | called = [] 53 | 54 | # When we pass a function we'll create a strong reference! 55 | def strong_ref_to_method(b): 56 | called.append(b) 57 | 58 | c.register(strong_ref_to_method) 59 | assert len(c) == 1 60 | 61 | c(1) 62 | del strong_ref_to_method 63 | c(1) 64 | assert len(c) == 1 65 | -------------------------------------------------------------------------------- /_pyvmmonitor_core_tests/test_commands.py: -------------------------------------------------------------------------------- 1 | from pyvmmonitor_core.commands_manager import ICommandsManager 2 | 3 | 4 | def test_commands_params(): 5 | from pyvmmonitor_core.commands_manager import create_default_commands_manager 6 | 7 | copied = [] 8 | 9 | def dummy_copy(param): 10 | copied.append(param) 11 | 12 | commands_manager = create_default_commands_manager() 13 | commands_manager.register_command('copy', 'Copy') 14 | commands_manager.activate('copy', param=2) 15 | assert copied == [] # Default handler does nothing 16 | 17 | commands_manager.set_command_handler('copy', dummy_copy) 18 | commands_manager.activate('copy', param=2) 19 | assert copied == [2] 20 | 21 | 22 | def test_scope_activate(): 23 | from pyvmmonitor_core.commands_manager import create_default_commands_manager 24 | from pyvmmonitor_core.commands_manager import ICommandsManager 25 | 26 | copied = [] 27 | 28 | def dummy_bar(): 29 | copied.append('bar') 30 | 31 | def dummy_foo(): 32 | copied.append('foo') 33 | 34 | commands_manager = create_default_commands_manager() 35 | commands_manager.register_command('copy', 'Copy') 36 | commands_manager.register_scope('scope1') 37 | commands_manager.set_command_handler('copy', dummy_bar) 38 | commands_manager.set_command_handler('copy', dummy_foo, scope='scope1') 39 | 40 | commands_manager.activate('copy') 41 | assert copied == ['bar'] 42 | 43 | commands_manager.activate('copy', 'scope1') 44 | assert copied == ['bar', 'foo'] 45 | 46 | commands_manager.activate_scope('scope1') 47 | 48 | commands_manager.activate('copy') 49 | assert copied == ['bar', 'foo', 'foo'] 50 | 51 | commands_manager.activate('copy', ICommandsManager.DEFAULT_SCOPE) 52 | assert copied == ['bar', 'foo', 'foo', 'bar'] 53 | 54 | 55 | def test_commands_remove_handler(): 56 | from pyvmmonitor_core.commands_manager import create_default_commands_manager 57 | 58 | copied = [] 59 | 60 | def dummy_copy(): 61 | copied.append(1) 62 | 63 | def dummy_foo(): 64 | print 65 | 66 | commands_manager = create_default_commands_manager() 67 | commands_manager.register_command('copy', 'Copy') 68 | commands_manager.activate('copy') 69 | assert copied == [] 70 | 71 | commands_manager.register_scope('copy_scope') 72 | commands_manager.set_command_handler('copy', dummy_copy, scope='copy_scope') 73 | commands_manager.activate('copy') 74 | assert copied == [] 75 | 76 | commands_manager.activate_scope('copy_scope') 77 | assert commands_manager.list_active_scopes() == [ICommandsManager.DEFAULT_SCOPE, 'copy_scope'] 78 | commands_manager.activate('copy') 79 | assert copied == [1] 80 | 81 | # Don't remove because it's not the current one. 82 | commands_manager.remove_command_handler('copy', dummy_foo, scope='copy_scope') 83 | commands_manager.activate('copy') 84 | assert copied == [1, 1] 85 | 86 | # Now, actually remove it. 87 | commands_manager.remove_command_handler('copy', dummy_copy, scope='copy_scope') 88 | commands_manager.activate('copy') 89 | assert copied == [1, 1] 90 | 91 | 92 | def test_commands(): 93 | from pyvmmonitor_core.commands_manager import CommandUndefinedEror 94 | from pyvmmonitor_core.commands_manager import create_default_commands_manager 95 | import pytest 96 | 97 | copied = [] 98 | 99 | def dummy_copy(): 100 | copied.append(1) 101 | 102 | def dummy_copy_on_dock_focused(): 103 | copied.append(2) 104 | 105 | commands_manager = create_default_commands_manager() 106 | commands_manager.register_command('copy', 'Copy') 107 | commands_manager.activate('copy') 108 | assert copied == [] 109 | 110 | commands_manager.set_command_handler('copy', dummy_copy) 111 | 112 | commands_manager.activate('copy') 113 | assert copied == [1] 114 | 115 | # Allow to override commands in a given scope 116 | commands_manager.register_scope('dock_focused') 117 | commands_manager.set_command_handler('copy', dummy_copy_on_dock_focused, scope='dock_focused') 118 | commands_manager.activate('copy') 119 | assert copied == [1, 1] 120 | 121 | commands_manager.activate_scope('dock_focused') 122 | assert commands_manager.list_active_scopes() == [ICommandsManager.DEFAULT_SCOPE, 'dock_focused'] 123 | commands_manager.activate('copy') 124 | assert copied == [1, 1, 2] 125 | commands_manager.deactivate_scope('dock_focused') 126 | 127 | assert commands_manager.list_active_scopes() == [ICommandsManager.DEFAULT_SCOPE] 128 | 129 | commands_manager.activate('copy') 130 | assert copied == [1, 1, 2, 1] 131 | 132 | with pytest.raises(RuntimeError): 133 | commands_manager.deactivate_scope('dock_focused') 134 | 135 | with pytest.raises(ValueError): 136 | commands_manager.activate_scope('invalid') 137 | 138 | with pytest.raises(ValueError): 139 | commands_manager.set_command_handler('copy', dummy_copy_on_dock_focused, scope='invalid') 140 | 141 | with pytest.raises(CommandUndefinedEror): 142 | commands_manager.activate('invalid') 143 | 144 | with pytest.raises(RuntimeError): 145 | commands_manager.register_command('copy', 'Foo') 146 | 147 | assert commands_manager.list_command_ids() == ['copy'] 148 | -------------------------------------------------------------------------------- /_pyvmmonitor_core_tests/test_disposable.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | def test_disposable(): 4 | from pyvmmonitor_core import overrides 5 | from pyvmmonitor_core.disposable import Disposable 6 | 7 | disposed = [] 8 | 9 | class MyDisposable(Disposable): 10 | 11 | @overrides(Disposable._on_dispose) 12 | def _on_dispose(self): 13 | disposed.append(1) 14 | 15 | d = MyDisposable() 16 | d.dispose() 17 | assert disposed == [1] 18 | -------------------------------------------------------------------------------- /_pyvmmonitor_core_tests/test_interface.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | def test_interfaces(): 5 | from pyvmmonitor_core import interface 6 | from pyvmmonitor_core.interface import BadImplementationError 7 | 8 | class ISomething(object): 9 | 10 | def m1(self): 11 | pass 12 | 13 | @interface.check_implements(ISomething) 14 | class SomethingImpl(object): 15 | 16 | def m1(self): 17 | pass 18 | 19 | s = SomethingImpl() 20 | assert interface.is_implementation(s, ISomething) 21 | assert interface.is_implementation(SomethingImpl, ISomething) 22 | 23 | with pytest.raises(BadImplementationError): 24 | 25 | @interface.check_implements(ISomething) 26 | class NoImpl(object): 27 | pass 28 | 29 | with pytest.raises(BadImplementationError): 30 | 31 | @interface.check_implements(ISomething) 32 | class NoImplDerivingFromInterface(ISomething): 33 | pass 34 | 35 | 36 | def test_interfaces_match_params(): 37 | from pyvmmonitor_core import interface 38 | from pyvmmonitor_core.interface import BadImplementationError 39 | 40 | class ISomething(object): 41 | 42 | def m1(self, a): 43 | pass 44 | 45 | @interface.check_implements(ISomething) 46 | class SomethingImpl(object): 47 | 48 | def m1(self, a): 49 | pass 50 | 51 | s = SomethingImpl() 52 | assert interface.is_implementation(s, ISomething) 53 | assert interface.is_implementation(SomethingImpl, ISomething) 54 | 55 | with pytest.raises(BadImplementationError): 56 | 57 | @interface.check_implements(ISomething) 58 | class NoImpl(object): 59 | 60 | def m1(self, bad_param): 61 | pass 62 | 63 | 64 | def test_interfaces_full_argspec_required(): 65 | import sys 66 | if sys.version_info[0] <= 2: 67 | return 68 | 69 | from _pyvmmonitor_core_tests import _py3_only 70 | from pyvmmonitor_core import interface 71 | from pyvmmonitor_core.interface import BadImplementationError 72 | ISomething = _py3_only.ISomething 73 | SomethingImplWrong = _py3_only.SomethingImplWrong 74 | SomethingImpl = _py3_only.SomethingImpl 75 | 76 | with pytest.raises(BadImplementationError): 77 | 78 | @interface.check_implements(ISomething) 79 | class SomethingImpl(object): 80 | 81 | def m1(self, a): 82 | pass 83 | 84 | with pytest.raises(BadImplementationError): 85 | interface.assert_implements(SomethingImplWrong, ISomething) 86 | 87 | interface.assert_implements(SomethingImpl, ISomething) 88 | 89 | 90 | def test_interface_properties(): 91 | 92 | from pyvmmonitor_core.interface import BadImplementationError 93 | 94 | class IFoo(object): 95 | 96 | @property 97 | def foo(self): 98 | pass 99 | 100 | @foo.setter 101 | def foo(self, foo): 102 | pass 103 | 104 | def check(): 105 | from pyvmmonitor_core.interface import check_implements 106 | 107 | @check_implements(IFoo) 108 | class Foo(object): 109 | pass 110 | 111 | def check2(): 112 | from pyvmmonitor_core.interface import check_implements 113 | 114 | @check_implements(IFoo) 115 | class Foo(object): 116 | 117 | @property 118 | def foo(self): 119 | pass 120 | 121 | def check3(): 122 | from pyvmmonitor_core.interface import check_implements 123 | 124 | @check_implements(IFoo) 125 | class Foo(object): 126 | 127 | @property 128 | def foo(self): 129 | pass 130 | 131 | @foo.setter 132 | def foo(self, foo): 133 | pass 134 | 135 | with pytest.raises(BadImplementationError): 136 | check() 137 | 138 | with pytest.raises(BadImplementationError): 139 | check2() 140 | 141 | check3() 142 | -------------------------------------------------------------------------------- /_pyvmmonitor_core_tests/test_iterables.py: -------------------------------------------------------------------------------- 1 | from pyvmmonitor_core import iterables 2 | 3 | 4 | def test_iter_curr_and_next(): 5 | lst = [1, 2, 3] 6 | assert list(iterables.iter_curr_and_next_closed_cycle(lst)) == [ 7 | (1, 2), (2, 3), (3, 1)] 8 | -------------------------------------------------------------------------------- /_pyvmmonitor_core_tests/test_kill.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import sys 3 | from pyvmmonitor_core.exec_external import kill_process 4 | 5 | 6 | def test_kill_process(): 7 | p = subprocess.Popen([sys.executable, '-c', 'import time;time.sleep(5)']) 8 | kill_process(p.pid, kill_children=True) 9 | -------------------------------------------------------------------------------- /_pyvmmonitor_core_tests/test_list_utils.py: -------------------------------------------------------------------------------- 1 | def test_remove_last_occurrence(): 2 | from pyvmmonitor_core.list_utils import remove_last_occurrence 3 | lst = [1, 2, 3, 3, 2] 4 | 5 | assert remove_last_occurrence(lst, 3) 6 | assert lst == [1, 2, 3, 2] 7 | 8 | assert remove_last_occurrence(lst, 3) 9 | assert lst == [1, 2, 2] 10 | 11 | assert not remove_last_occurrence(lst, 3) 12 | assert lst == [1, 2, 2] 13 | -------------------------------------------------------------------------------- /_pyvmmonitor_core_tests/test_log_utils.py: -------------------------------------------------------------------------------- 1 | from pyvmmonitor_core import log_utils 2 | from pyvmmonitor_core.compat import unicode 3 | 4 | 5 | def test_log_utils(tmpdir): 6 | import os 7 | logger = log_utils.get_logger('test_log_utils') 8 | log_filename = os.path.join(unicode(tmpdir.ensure_dir()), 'test_log_utils.log') 9 | 10 | os.environ['TEST_LOG_ENV_VAR'] = 'DEBUG' 11 | log_utils.config_rotating_file_handler_from_env_var( 12 | 'TEST_LOG_ENV_VAR', log_filename, logger_name='test_log_utils') 13 | 14 | logger.warning('warn') 15 | logger.info('info') 16 | logger.debug('debug') 17 | try: 18 | raise RuntimeError('ThisException') 19 | except Exception: 20 | logger.exception('SomeException') 21 | 22 | # We have something as: 23 | # 2017-05-18 10:35:02,033 - test_log_utils - WARNING : warn 24 | # 2017-05-18 10:35:02,033 - test_log_utils - INFO : info 25 | # 2017-05-18 10:35:02,035 - test_log_utils - DEBUG : debug 26 | # 2017-05-18 10:35:02,035 - test_log_utils - ERROR : SomeException 27 | # Traceback (most recent call last): 28 | # File "X:\pyvmmonitor-core\_pyvmmonitor_core_tests\test_log_utils.py", line 17, in test_log_utils 29 | # raise RuntimeError('ThisException') 30 | # RuntimeError: ThisException 31 | 32 | # Let's compare the file endings. 33 | expected = '''- test_log_utils - WARNING : warn 34 | - test_log_utils - INFO : info 35 | - test_log_utils - DEBUG : debug 36 | - test_log_utils - ERROR : SomeException 37 | Traceback (most recent call last): 38 | in test_log_utils 39 | raise RuntimeError('ThisException') 40 | RuntimeError: ThisException 41 | ''' 42 | 43 | with open(log_filename, 'r') as stream: 44 | lines = stream.read().splitlines() 45 | assert len(lines) == 8 46 | for expected_line, found_line in zip(expected.splitlines(), lines): 47 | assert found_line.endswith(expected_line) 48 | -------------------------------------------------------------------------------- /_pyvmmonitor_core_tests/test_math_utils.py: -------------------------------------------------------------------------------- 1 | from pyvmmonitor_core.math_utils import almost_equal, is_clockwise 2 | 3 | 4 | def test_compute_distance(): 5 | from pyvmmonitor_core.math_utils import calculate_distance 6 | assert almost_equal(calculate_distance((10, 10), (0, 0)), 14.142135623730951) 7 | 8 | 9 | def test_is_clockwise(): 10 | assert is_clockwise([(0, 0), (10, 0), (10, 10)]) 11 | assert not is_clockwise([(0, 0), (10, 0), (10, -10)]) 12 | 13 | 14 | def test_is_point_close_to_line(): 15 | from pyvmmonitor_core.math_utils import is_point_close_to_line 16 | assert is_point_close_to_line((0, 0), [(0, 0), (0, 1)]) 17 | assert is_point_close_to_line((0, 0.5), [(0, 0), (0, 1)]) 18 | assert is_point_close_to_line((0.1, 0.5), [(0, 0), (0, 1)]) 19 | 20 | assert not is_point_close_to_line((0, 1.5), [(0, 0), (0, 1)]) 21 | assert not is_point_close_to_line((0.5, 1.0), [(0, 0), (0, 1)]) 22 | -------------------------------------------------------------------------------- /_pyvmmonitor_core_tests/test_memoize.py: -------------------------------------------------------------------------------- 1 | def test_memoize(): 2 | 3 | import itertools 4 | count = itertools.count(0) 5 | from pyvmmonitor_core.memoization import memoize 6 | 7 | @memoize 8 | def func(param1, param2): 9 | return [next(count)] 10 | 11 | assert func(1, 2) == [0] 12 | assert func(1, 2) == [0] 13 | assert func(1, 2) == [0] 14 | assert func(1, 4) == [1] 15 | -------------------------------------------------------------------------------- /_pyvmmonitor_core_tests/test_nodes_tree.py: -------------------------------------------------------------------------------- 1 | import io 2 | 3 | from pyvmmonitor_core.nodes_tree import NodesTree, Node 4 | 5 | 6 | def test_nodes_tree(): 7 | nodes_tree = NodesTree() 8 | node = nodes_tree.add_child(Node(1)) 9 | node2 = node.add_child(Node(2)) 10 | node2.add_child(Node(3)) 11 | nodes_tree.add_child(Node(4)) 12 | 13 | stream = io.StringIO() 14 | stream.write(u'\n') 15 | nodes_tree.print_rep(stream=stream) 16 | assert stream.getvalue() == u''' 17 | int: 1 18 | int: 2 19 | int: 3 20 | int: 4 21 | '''.replace('\r\n', '\n').replace('\r', '\n') 22 | 23 | assert len(nodes_tree) == 4 -------------------------------------------------------------------------------- /_pyvmmonitor_core_tests/test_orderedset.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from pyvmmonitor_core.ordered_set import OrderedSet 4 | from pyvmmonitor_core.weak_utils import WeakList 5 | 6 | 7 | class Stub(object): 8 | 9 | created = WeakList() 10 | 11 | def __init__(self, data): 12 | self.data = data 13 | self.created.append(self) 14 | 15 | def __hash__(self, *args, **kwargs): 16 | return self.data 17 | 18 | def __eq__(self, o): 19 | if isinstance(o, Stub): 20 | return self.data == o.data 21 | 22 | return False 23 | 24 | def __ne__(self, o): 25 | return not self == o 26 | 27 | def __repr__(self): 28 | return str(self.data) 29 | 30 | __str__ = __repr__ 31 | 32 | 33 | def test_ordered_set(): 34 | 35 | s = OrderedSet([Stub(1), Stub(2)]) 36 | 37 | s.add(Stub(3)) 38 | assert list(s) == [Stub(1), Stub(2), Stub(3)] 39 | s.discard(Stub(2)) 40 | assert list(s) == [Stub(1), Stub(3)] 41 | assert list(reversed(s)) == [Stub(3), Stub(1)] 42 | assert s.index(Stub(3)) == 1 43 | repr(s) 44 | str(s) 45 | assert s.item_at(1) == Stub(3) 46 | 47 | assert len(s) == 2 48 | 49 | assert s == OrderedSet([Stub(1), Stub(3)]) 50 | s.clear() 51 | assert len(Stub.created) == 0, 'Stub objects not garbage-collected!' 52 | 53 | s = OrderedSet([Stub(1), Stub(3)]) 54 | s.add(Stub(1)) 55 | assert list(s) == [Stub(1), Stub(3)] 56 | 57 | s.add(Stub(4)) 58 | assert list(s) == [Stub(1), Stub(3), Stub(4)] 59 | 60 | with pytest.raises(KeyError): 61 | OrderedSet().popitem(last=False) 62 | with pytest.raises(KeyError): 63 | OrderedSet().popitem(last=True) 64 | 65 | s.popitem(last=False) 66 | assert list(s) == [Stub(3), Stub(4)] 67 | s.popitem(last=True) 68 | assert list(s) == [Stub(3)] 69 | 70 | s.insert_before(Stub(3), Stub(4)) 71 | assert list(s) == [Stub(4), Stub(3)] 72 | assert Stub(4) in s 73 | 74 | s.insert_before(Stub(4), Stub(5)) 75 | assert list(s) == [Stub(5), Stub(4), Stub(3)] 76 | 77 | s.insert_before(Stub(3), Stub(9)) 78 | assert list(s) == [Stub(5), Stub(4), Stub(9), Stub(3)] 79 | 80 | s.insert_before(Stub(5), Stub(8)) 81 | expected = [Stub(8), Stub(5), Stub(4), Stub(9), Stub(3)] 82 | assert list(s) == expected 83 | for e in expected: 84 | assert e in s 85 | 86 | assert list(reversed(s)) == list(reversed(expected)) 87 | s.discard(Stub(8)) 88 | assert list(s) == [Stub(5), Stub(4), Stub(9), Stub(3)] 89 | 90 | del e 91 | del expected 92 | 93 | s.move_to_end(Stub(4)) 94 | assert list(s) == [Stub(5), Stub(9), Stub(3), Stub(4)] 95 | 96 | s.move_to_beginning(Stub(4)) 97 | assert list(s) == [Stub(4), Stub(5), Stub(9), Stub(3)] 98 | 99 | s.move_to_previous(Stub(5)) 100 | assert list(s) == [Stub(5), Stub(4), Stub(9), Stub(3)] 101 | 102 | s.move_to_previous(Stub(5)) 103 | assert list(s) == [Stub(5), Stub(4), Stub(9), Stub(3)] 104 | 105 | s.move_to_next(Stub(5)) 106 | assert list(s) == [Stub(4), Stub(5), Stub(9), Stub(3)] 107 | 108 | s.move_to_next(Stub(3)) 109 | assert list(s) == [Stub(4), Stub(5), Stub(9), Stub(3)] 110 | 111 | assert s.get_previous(Stub(4)) is None 112 | assert s.get_previous(Stub(3)) == Stub(9) 113 | 114 | assert s.get_next(Stub(4)) == Stub(5) 115 | assert s.get_next(Stub(3)) is None 116 | 117 | assert s.item_at(-1) == Stub(3) 118 | assert s.item_at(-2) == Stub(9) 119 | 120 | s.clear() 121 | assert len(Stub.created) == 0, 'Stub objects not garbage-collected!' 122 | 123 | 124 | def test_ordered_set2(): 125 | 126 | s = OrderedSet() 127 | s.add(Stub(1)) 128 | assert list(s) == [Stub(1)] 129 | 130 | s.add(Stub(2)) 131 | assert list(s) == [Stub(1), Stub(2)] 132 | 133 | s.add(Stub(3)) 134 | assert list(s) == [Stub(1), Stub(2), Stub(3)] 135 | assert list(reversed(s)) == [Stub(3), Stub(2), Stub(1)] 136 | 137 | s.discard(Stub(1)) 138 | assert list(s) == [Stub(2), Stub(3)] 139 | 140 | s.move_to_next(Stub(2)) 141 | assert list(s) == [Stub(3), Stub(2)] 142 | 143 | s.move_to_previous(Stub(2)) 144 | assert list(s) == [Stub(2), Stub(3)] 145 | 146 | s.discard(Stub(3)) 147 | assert list(s) == [Stub(2)] 148 | 149 | s.discard(Stub(2)) 150 | assert list(s) == [] 151 | 152 | s.add(Stub(1)) 153 | s.insert_after(Stub(1), Stub(2)) 154 | assert list(s) == [Stub(1), Stub(2)] 155 | 156 | s.insert_after(Stub(1), Stub(3)) 157 | assert list(s) == [Stub(1), Stub(3), Stub(2)] 158 | 159 | s.pop() 160 | assert list(s) == [Stub(1), Stub(3)] 161 | 162 | s.pop() 163 | assert list(s) == [Stub(1)] 164 | 165 | s.pop() 166 | assert list(s) == [] 167 | 168 | assert len(Stub.created) == 0, 'Stub objects not garbage-collected!' 169 | -------------------------------------------------------------------------------- /_pyvmmonitor_core_tests/test_plugins.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from pyvmmonitor_core.callback import Callback 4 | from pyvmmonitor_core.plugins import (InstanceAlreadyRegisteredError, 5 | NotRegisteredError, PluginManager) 6 | 7 | 8 | class EPFoo(object): 9 | 10 | def __init__(self): 11 | self.foo = False 12 | 13 | def Foo(self): 14 | pass 15 | 16 | 17 | class EPBar(object): 18 | 19 | def __init__(self): 20 | pass 21 | 22 | def Bar(self): 23 | pass 24 | 25 | 26 | class FooImpl(EPFoo): 27 | 28 | def __init__(self): 29 | self.exited = Callback() 30 | 31 | def Foo(self): 32 | self.foo = True 33 | 34 | def plugins_exit(self): 35 | self.exited(self) 36 | 37 | 38 | class AnotherFooImpl(EPFoo): 39 | pass 40 | 41 | 42 | def test_plugins(): 43 | 44 | pm = PluginManager() 45 | pm.register(EPFoo, '_pyvmmonitor_core_tests.test_plugins.FooImpl', keep_instance=True) 46 | with pytest.raises(InstanceAlreadyRegisteredError): 47 | pm.register( 48 | EPFoo, 49 | '_pyvmmonitor_core_tests.test_plugins.AnotherFooImpl', 50 | keep_instance=True) 51 | 52 | foo = pm.get_instance(EPFoo) 53 | assert pm.get_instance(EPFoo) is foo 54 | assert pm[EPFoo] is foo 55 | assert pm['EPFoo'] is foo 56 | # It's only registered in a way where the instance is kept 57 | assert not pm.get_implementations(EPFoo) 58 | 59 | assert not pm.get_implementations(EPBar) 60 | with pytest.raises(NotRegisteredError): 61 | pm.get_instance(EPBar) 62 | 63 | pm.register( 64 | EPFoo, 65 | '_pyvmmonitor_core_tests.test_plugins.AnotherFooImpl', 66 | context='context2', 67 | keep_instance=True) 68 | 69 | assert len(list(pm.iter_existing_instances(EPFoo))) == 1 70 | assert isinstance(pm.get_instance(EPFoo, context='context2'), AnotherFooImpl) 71 | 72 | assert len(list(pm.iter_existing_instances(EPFoo))) == 2 73 | assert set(pm.iter_existing_instances(EPFoo)) == set( 74 | [pm.get_instance(EPFoo, context='context2'), pm.get_instance(EPFoo)]) 75 | 76 | # Request using a string. 77 | assert len(list(pm.iter_existing_instances('EPFoo'))) == 2 78 | assert set(pm.iter_existing_instances('EPFoo')) == set( 79 | [pm.get_instance(EPFoo, context='context2'), pm.get_instance('EPFoo')]) 80 | 81 | 82 | def test_plugins_exit(): 83 | pm = PluginManager() 84 | pm.register(EPFoo, '_pyvmmonitor_core_tests.test_plugins.FooImpl', keep_instance=True) 85 | f1 = pm.get_instance(EPFoo) 86 | f2 = pm.get_instance(EPFoo, 'bar') 87 | exited = [] 88 | 89 | def on_exit(s): 90 | exited.append(s) 91 | 92 | f1.exited.register(on_exit) 93 | f2.exited.register(on_exit) 94 | pm.exit() 95 | assert set(exited) == set([f1, f2]) 96 | 97 | 98 | def test_inject(): 99 | pm = PluginManager() 100 | pm.register(EPFoo, '_pyvmmonitor_core_tests.test_plugins.FooImpl', keep_instance=True) 101 | 102 | from pyvmmonitor_core.plugins import inject 103 | 104 | @inject(foo=EPFoo) 105 | def m1(foo, pm): 106 | return foo 107 | 108 | assert m1(pm=pm) == pm.get_instance(EPFoo) 109 | 110 | 111 | def test_inject_class(): 112 | pm = PluginManager() 113 | pm.register(EPFoo, '_pyvmmonitor_core_tests.test_plugins.FooImpl', keep_instance=True) 114 | pm.register(EPBar, '_pyvmmonitor_core_tests.test_plugins.FooImpl', keep_instance=False) 115 | pm.register(EPBar, '_pyvmmonitor_core_tests.test_plugins.AnotherFooImpl', keep_instance=False) 116 | 117 | from pyvmmonitor_core.plugins import inject 118 | 119 | @inject(foo=EPFoo, foo2=[EPBar]) 120 | def m1(foo, foo2, pm): 121 | return foo, foo2 122 | 123 | assert m1(pm=pm)[0] == pm.get_instance(EPFoo) 124 | assert len(m1(pm=pm)[1]) == 2 125 | -------------------------------------------------------------------------------- /_pyvmmonitor_core_tests/test_props.py: -------------------------------------------------------------------------------- 1 | from pyvmmonitor_core import overrides 2 | from pyvmmonitor_core.props import PropsCustomProperty, PropsObject 3 | 4 | 5 | def test_props(): 6 | 7 | class MyProps(PropsObject): 8 | PropsObject.declare_props(a=10) 9 | 10 | p = MyProps() 11 | assert p.a == 10 12 | 13 | notifications = [] 14 | 15 | def on_modified(obj, attrs): 16 | notifications.append((obj, attrs)) 17 | 18 | p.register_modified(on_modified) 19 | 20 | p.a = 20 21 | 22 | assert notifications == [(p, {'a': (20, 10)})] 23 | p.a = 20 24 | assert notifications == [(p, {'a': (20, 10)})] 25 | 26 | assert p.create_memento() == {'a': 20} 27 | p.set_memento({'a': 30}) 28 | assert p.a == 30 29 | 30 | 31 | def test_custom_props_convert(): 32 | 33 | class CustomProp(PropsCustomProperty): 34 | 35 | @overrides(PropsCustomProperty.convert) 36 | def convert(self, obj, val): 37 | if val.__class__ == list: 38 | val = tuple(val) 39 | return val 40 | 41 | class MyProps(PropsObject): 42 | PropsObject.declare_props(a=CustomProp((10,))) 43 | 44 | p = MyProps() 45 | assert p.a == (10,) 46 | p.a = [20] 47 | assert p.a == (20,) 48 | 49 | 50 | def test_props_as_dict(): 51 | 52 | class MyProps(PropsObject): 53 | PropsObject.declare_props(a=10, b=20) 54 | 55 | class MyProps2(MyProps): 56 | PropsObject.declare_props(c=30) 57 | 58 | props = MyProps2() 59 | 60 | assert props.get_all_props_names() == frozenset(('a', 'b', 'c')) 61 | props.a = 0 62 | assert props.get_props_as_dict() == {'a': 0, 'b': 20, 'c': 30} 63 | assert props.__all_props_cache_info__['hit'] == 1 64 | 65 | assert MyProps2().get_all_props_names() == frozenset(('a', 'b', 'c')) 66 | assert props.__all_props_cache_info__['hit'] == 2 67 | 68 | 69 | def test_props_single_notification(): 70 | 71 | from pyvmmonitor_core.props import delayed_notifications 72 | 73 | class MyProps(PropsObject): 74 | PropsObject.declare_props(a=10, b=20, c=30) 75 | 76 | props = MyProps() 77 | notifications = [] 78 | 79 | def on_modified(obj, attrs): 80 | notifications.append((obj, attrs)) 81 | 82 | props.register_modified(on_modified) 83 | with delayed_notifications(props): 84 | props.a = 44 85 | props.b = 55 86 | props.a = 22 87 | props.c = 55 88 | props.c = 30 89 | 90 | assert notifications == [(props, {'a': (22, 10), 'b': (55, 20)})] 91 | -------------------------------------------------------------------------------- /_pyvmmonitor_core_tests/test_system_mutex.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from pyvmmonitor_core.system_mutex import SystemMutex 4 | from pyvmmonitor_core.weak_utils import get_weakref 5 | 6 | 7 | def test_system_mutex(): 8 | mutex_name = 'pyvmmonitor 11111__15' 9 | 10 | system_mutex = SystemMutex(mutex_name) 11 | assert system_mutex.get_mutex_aquired() 12 | 13 | mutex2 = SystemMutex(mutex_name) 14 | assert not mutex2.get_mutex_aquired() 15 | del mutex2 16 | 17 | system_mutex.release_mutex() 18 | 19 | mutex3 = SystemMutex(mutex_name) 20 | assert mutex3.get_mutex_aquired() 21 | mutex3 = get_weakref(mutex3) # Garbage-collected 22 | 23 | # Calling release more times should not be an error 24 | system_mutex.release_mutex() 25 | 26 | mutex4 = SystemMutex(mutex_name) 27 | assert mutex4.get_mutex_aquired() 28 | 29 | with pytest.raises(AssertionError): 30 | SystemMutex('mutex/') # Invalid name 31 | -------------------------------------------------------------------------------- /_pyvmmonitor_core_tests/test_weaklist.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | class _Dummy(object): 4 | pass 5 | 6 | 7 | def test_weaklist(): 8 | from pyvmmonitor_core.weak_utils import WeakList 9 | l = WeakList() 10 | d1 = _Dummy() 11 | l.append(d1) 12 | assert len(list(l)) == 1 13 | l.clear() 14 | assert len(list(l)) == 0 15 | l.append(d1) 16 | assert len(list(l)) == 1 17 | d1 = None 18 | assert len(list(l)) == 0 19 | 20 | d1 = _Dummy() 21 | d2 = _Dummy() 22 | l.append(d1) 23 | l.append(d2) 24 | assert len(list(l)) == 2 25 | l.remove(d1) 26 | assert len(list(l)) == 1 27 | l.remove(d1) 28 | assert len(list(l)) == 1 29 | assert len(l) == 1 30 | 31 | 32 | def test_weak_ordered_set(): 33 | from pyvmmonitor_core.weak_utils import WeakOrderedSet 34 | s = WeakOrderedSet() 35 | d1 = _Dummy() 36 | s.add(d1) 37 | assert len(list(s)) == 1 38 | s.clear() 39 | assert len(list(s)) == 0 40 | s.add(d1) 41 | assert len(list(s)) == 1 42 | d1 = None 43 | assert len(list(s)) == 0 44 | 45 | d1 = _Dummy() 46 | d2 = _Dummy() 47 | s.add(d1) 48 | s.add(d2) 49 | assert len(list(s)) == 2 50 | s.remove(d1) 51 | assert len(list(s)) == 1 52 | s.discard(d1) 53 | assert len(list(s)) == 1 54 | assert len(s) == 1 55 | -------------------------------------------------------------------------------- /_pyvmmonitor_core_tests/test_weakmethod.py: -------------------------------------------------------------------------------- 1 | def test_weakmethod(): 2 | from pyvmmonitor_core.weakmethod import WeakMethodProxy 3 | 4 | class Object(object): 5 | def foo(self): 6 | return 1 7 | 8 | obj = Object() 9 | proxy = WeakMethodProxy(obj.foo) 10 | assert proxy() == 1 11 | del obj 12 | assert proxy() is None 13 | -------------------------------------------------------------------------------- /pyvmmonitor_core/__init__.py: -------------------------------------------------------------------------------- 1 | # License: LGPL 2 | # 3 | # Copyright: Brainwy Software 4 | import os 5 | import sys 6 | from functools import wraps 7 | 8 | __file__ = os.path.abspath(__file__) 9 | 10 | 11 | def get_public_api_location(): 12 | basedir = os.path.dirname(os.path.dirname(__file__)) 13 | public_api = os.path.join(basedir, 'public_api') 14 | if os.path.exists(public_api): 15 | # In dev mode we don't really use the public_api dir, we use 16 | # the module dir directly. 17 | return public_api 18 | return basedir 19 | 20 | 21 | def overrides(method_overridden): 22 | ''' 23 | This is just an 'annotation' method to say that some method is overridden. 24 | 25 | It also checks that the method name is the same. 26 | ''' 27 | 28 | def wrapper(func): 29 | if func.__name__ != method_overridden.__name__: 30 | msg = "Wrong @overrides: %r expected, but overwriting %r." 31 | msg = msg % (func.__name__, method_overridden.__name__) 32 | raise AssertionError(msg) 33 | 34 | if func.__doc__ is None: 35 | # inherit docs if it's not there already. 36 | func.__doc__ = method_overridden.__doc__ 37 | 38 | return func 39 | 40 | return wrapper 41 | 42 | 43 | def implements(method_implemented): 44 | ''' 45 | This is just an 'annotation' method to say that some method is implemented. 46 | 47 | It also checks that the method name is the same. 48 | ''' 49 | 50 | def wrapper(func): 51 | if hasattr(method_implemented, '__name__'): 52 | if func.__name__ != method_implemented.__name__: 53 | msg = "Wrong @implements: %r expected, but implementing %r." 54 | msg = msg % (func.__name__, method_implemented.__name__) 55 | raise AssertionError(msg) 56 | 57 | if func.__doc__ is None: 58 | # inherit docs if it's not there already. 59 | func.__doc__ = method_implemented.__doc__ 60 | 61 | return func 62 | 63 | return wrapper 64 | 65 | 66 | def is_frozen(): 67 | return getattr(sys, 'frozen', False) 68 | 69 | 70 | _is_development = not is_frozen() 71 | 72 | 73 | def is_development(): 74 | ''' 75 | Note: although it's initialized to be the opposite of the frozen, it has a different semantic. 76 | 77 | I.e.: frozen means we're executing from a zip with a different layout and is_development on the 78 | other way should be used to do additional checks and asserts (and we could set the development 79 | mode to True even when frozen). 80 | ''' 81 | return _is_development 82 | 83 | 84 | def abstract(func): 85 | ''' 86 | Marks some method as abstract (meaning it has to be overridden in a subclass). 87 | ''' 88 | 89 | @wraps(func) 90 | def wrapper(self, *args, **kwargs): 91 | msg = 'Method %r must be implemented in class %r.' % (func.__name__, self.__class__) 92 | raise NotImplementedError(msg) 93 | 94 | return wrapper 95 | 96 | 97 | if __name__ == '__main__': 98 | 99 | class A(object): 100 | 101 | def m1(self): 102 | pass 103 | 104 | class B(A): 105 | 106 | @overrides(A.m1) 107 | def m1(self): 108 | pass 109 | 110 | @abstract 111 | def m2(self): 112 | pass 113 | 114 | assert B.m1.__name__ == 'm1' 115 | -------------------------------------------------------------------------------- /pyvmmonitor_core/appdirs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Copyright (c) 2005-2010 ActiveState Software Inc. 3 | # License: License :: OSI Approved :: MIT License 4 | # https://pypi.python.org/pypi/appdirs/1.2.0 5 | 6 | """Utilities for determining application-specific dirs. 7 | 8 | See for details and usage. 9 | """ 10 | # Dev Notes: 11 | # - MSDN on where to store app data files: 12 | # http://support.microsoft.com/default.aspx?scid=kb;en-us;310294#XSLTH3194121123120121120120 13 | # - Mac OS X: http://developer.apple.com/documentation/MacOSX/Conceptual/BPFileSystem/index.html 14 | # - XDG spec for Un*x: http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html 15 | 16 | __version_info__ = (1, 2, 0) 17 | __version__ = '.'.join(map(str, __version_info__)) 18 | 19 | import os 20 | import sys 21 | 22 | PY3 = sys.version_info[0] == 3 23 | 24 | if PY3: 25 | unicode = str 26 | 27 | 28 | class AppDirsError(Exception): 29 | pass 30 | 31 | 32 | def user_data_dir(appname, appauthor=None, version=None, roaming=False): 33 | r"""Return full path to the user-specific data dir for this application. 34 | 35 | "appname" is the name of application. 36 | "appauthor" (only required and used on Windows) is the name of the 37 | appauthor or distributing body for this application. Typically 38 | it is the owning company name. 39 | "version" is an optional version path element to append to the 40 | path. You might want to use this if you want multiple versions 41 | of your app to be able to run independently. If used, this 42 | would typically be ".". 43 | "roaming" (boolean, default False) can be set True to use the Windows 44 | roaming appdata directory. That means that for users on a Windows 45 | network setup for roaming profiles, this user data will be 46 | sync'd on login. See 47 | 48 | for a discussion of issues. 49 | 50 | Typical user data directories are: 51 | Mac OS X: ~/Library/Application Support/ 52 | Unix: ~/.config/ # or in $XDG_CONFIG_HOME if defined 53 | Win XP (not roaming): C:\Documents and Settings\\Application Data\\ 54 | Win XP (roaming): C:\Documents and Settings\\Local Settings\Application Data\\ 55 | Win 7 (not roaming): C:\Users\\AppData\Local\\ 56 | Win 7 (roaming): C:\Users\\AppData\Roaming\\ 57 | 58 | For Unix, we follow the XDG spec and support $XDG_CONFIG_HOME. We don't 59 | use $XDG_DATA_HOME as that data dir is mostly used at the time of 60 | installation, instead of the application adding data during runtime. 61 | Also, in practice, Linux apps tend to store their data in 62 | "~/.config/" instead of "~/.local/share/". 63 | """ 64 | if appauthor is None: 65 | raise AppDirsError("must specify 'appauthor'") 66 | if sys.platform.startswith("win"): 67 | const = roaming and "CSIDL_APPDATA" or "CSIDL_LOCAL_APPDATA" 68 | path = os.path.join(_get_win_folder(const), appauthor, appname) 69 | elif sys.platform == 'darwin': 70 | path = os.path.join( 71 | os.path.expanduser('~/Library/Application Support/'), 72 | appauthor, appname) 73 | else: 74 | path = os.path.join( 75 | os.getenv('XDG_CONFIG_HOME', os.path.expanduser("~/.config")), 76 | appauthor, appname.lower()) 77 | if version: 78 | path = os.path.join(path, version) 79 | return path 80 | 81 | 82 | def site_data_dir(appname, appauthor=None, version=None): 83 | r"""Return full path to the user-shared data dir for this application. 84 | 85 | "appname" is the name of application. 86 | "appauthor" (only required and used on Windows) is the name of the 87 | appauthor or distributing body for this application. Typically 88 | it is the owning company name. 89 | "version" is an optional version path element to append to the 90 | path. You might want to use this if you want multiple versions 91 | of your app to be able to run independently. If used, this 92 | would typically be ".". 93 | 94 | Typical user data directories are: 95 | Mac OS X: /Library/Application Support/ 96 | Unix: /etc/xdg/ 97 | Win XP: C:\Documents and Settings\All Users\Application Data\\ 98 | Vista: (Fail! "C:\ProgramData" is a hidden *system* directory on Vista.) 99 | Win 7: C:\ProgramData\\ # Hidden, but writeable on Win 7. 100 | 101 | For Unix, this is using the $XDG_CONFIG_DIRS[0] default. 102 | 103 | WARNING: Do not use this on Windows. See the Vista-Fail note above for why. 104 | """ 105 | if sys.platform.startswith("win"): 106 | if appauthor is None: 107 | raise AppDirsError("must specify 'appauthor' on Windows") 108 | path = os.path.join(_get_win_folder("CSIDL_COMMON_APPDATA"), 109 | appauthor, appname) 110 | elif sys.platform == 'darwin': 111 | path = os.path.join( 112 | os.path.expanduser('/Library/Application Support'), 113 | appname) 114 | else: 115 | # XDG default for $XDG_CONFIG_DIRS[0]. Perhaps should actually 116 | # *use* that envvar, if defined. 117 | path = "/etc/xdg/" + appname.lower() 118 | if version: 119 | path = os.path.join(path, version) 120 | return path 121 | 122 | 123 | def user_cache_dir(appname, appauthor=None, version=None, opinion=True): 124 | r"""Return full path to the user-specific cache dir for this application. 125 | 126 | "appname" is the name of application. 127 | "appauthor" (only required and used on Windows) is the name of the 128 | appauthor or distributing body for this application. Typically 129 | it is the owning company name. 130 | "version" is an optional version path element to append to the 131 | path. You might want to use this if you want multiple versions 132 | of your app to be able to run independently. If used, this 133 | would typically be ".". 134 | "opinion" (boolean) can be False to disable the appending of 135 | "Cache" to the base app data dir for Windows. See 136 | discussion below. 137 | 138 | Typical user cache directories are: 139 | Mac OS X: ~/Library/Caches/ 140 | Unix: ~/.cache/ (XDG default) 141 | Win XP: C:\Documents and Settings\\Local Settings\Application Data\\\Cache 142 | Vista: C:\Users\\AppData\Local\\\Cache 143 | 144 | On Windows the only suggestion in the MSDN docs is that local settings go in 145 | the `CSIDL_LOCAL_APPDATA` directory. This is identical to the non-roaming 146 | app data dir (the default returned by `user_data_dir` above). Apps typically 147 | put cache data somewhere *under* the given dir here. Some examples: 148 | ...\Mozilla\Firefox\Profiles\\Cache 149 | ...\Acme\SuperApp\Cache\1.0 150 | OPINION: This function appends "Cache" to the `CSIDL_LOCAL_APPDATA` value. 151 | This can be disabled with the `opinion=False` option. 152 | """ 153 | if sys.platform.startswith("win"): 154 | if appauthor is None: 155 | raise AppDirsError("must specify 'appauthor' on Windows") 156 | path = os.path.join(_get_win_folder("CSIDL_LOCAL_APPDATA"), 157 | appauthor, appname) 158 | if opinion: 159 | path = os.path.join(path, "Cache") 160 | elif sys.platform == 'darwin': 161 | path = os.path.join( 162 | os.path.expanduser('~/Library/Caches'), 163 | appname) 164 | else: 165 | path = os.path.join( 166 | os.getenv('XDG_CACHE_HOME', os.path.expanduser('~/.cache')), 167 | appname.lower()) 168 | if version: 169 | path = os.path.join(path, version) 170 | return path 171 | 172 | 173 | def user_log_dir(appname, appauthor=None, version=None, opinion=True): 174 | r"""Return full path to the user-specific log dir for this application. 175 | 176 | "appname" is the name of application. 177 | "appauthor" (only required and used on Windows) is the name of the 178 | appauthor or distributing body for this application. Typically 179 | it is the owning company name. 180 | "version" is an optional version path element to append to the 181 | path. You might want to use this if you want multiple versions 182 | of your app to be able to run independently. If used, this 183 | would typically be ".". 184 | "opinion" (boolean) can be False to disable the appending of 185 | "Logs" to the base app data dir for Windows, and "log" to the 186 | base cache dir for Unix. See discussion below. 187 | 188 | Typical user cache directories are: 189 | Mac OS X: ~/Library/Logs/ 190 | Unix: ~/.cache//log # or under $XDG_CACHE_HOME if defined 191 | Win XP: C:\Documents and Settings\\Local Settings\Application Data\\\Logs 192 | Vista: C:\Users\\AppData\Local\\\Logs 193 | 194 | On Windows the only suggestion in the MSDN docs is that local settings 195 | go in the `CSIDL_LOCAL_APPDATA` directory. (Note: I'm interested in 196 | examples of what some windows apps use for a logs dir.) 197 | 198 | OPINION: This function appends "Logs" to the `CSIDL_LOCAL_APPDATA` 199 | value for Windows and appends "log" to the user cache dir for Unix. 200 | This can be disabled with the `opinion=False` option. 201 | """ 202 | if sys.platform == "darwin": 203 | path = os.path.join( 204 | os.path.expanduser('~/Library/Logs'), 205 | appname) 206 | elif sys.platform == "win32": 207 | path = user_data_dir(appname, appauthor, version) 208 | version = False 209 | if opinion: 210 | path = os.path.join(path, "Logs") 211 | else: 212 | path = user_cache_dir(appname, appauthor, version) 213 | version = False 214 | if opinion: 215 | path = os.path.join(path, "log") 216 | if version: 217 | path = os.path.join(path, version) 218 | return path 219 | 220 | 221 | class AppDirs(object): 222 | """Convenience wrapper for getting application dirs.""" 223 | 224 | def __init__(self, appname, appauthor, version=None, roaming=False): 225 | self.appname = appname 226 | self.appauthor = appauthor 227 | self.version = version 228 | self.roaming = roaming 229 | 230 | @property 231 | def user_data_dir(self): 232 | return user_data_dir(self.appname, self.appauthor, 233 | version=self.version, roaming=self.roaming) 234 | 235 | @property 236 | def site_data_dir(self): 237 | return site_data_dir(self.appname, self.appauthor, 238 | version=self.version) 239 | 240 | @property 241 | def user_cache_dir(self): 242 | return user_cache_dir(self.appname, self.appauthor, 243 | version=self.version) 244 | 245 | @property 246 | def user_log_dir(self): 247 | return user_log_dir(self.appname, self.appauthor, 248 | version=self.version) 249 | 250 | #---- internal support stuff 251 | 252 | 253 | def _get_win_folder_from_registry(csidl_name): 254 | """This is a fallback technique at best. I'm not sure if using the 255 | registry for this guarantees us the correct answer for all CSIDL_* 256 | names. 257 | """ 258 | import _winreg 259 | 260 | shell_folder_name = { 261 | "CSIDL_APPDATA": "AppData", 262 | "CSIDL_COMMON_APPDATA": "Common AppData", 263 | "CSIDL_LOCAL_APPDATA": "Local AppData", 264 | }[csidl_name] 265 | 266 | key = _winreg.OpenKey(_winreg.HKEY_CURRENT_USER, 267 | r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders") 268 | dir, type = _winreg.QueryValueEx(key, shell_folder_name) 269 | return dir 270 | 271 | 272 | def _get_win_folder_with_pywin32(csidl_name): 273 | from win32com.shell import shellcon, shell 274 | dir = shell.SHGetFolderPath(0, getattr(shellcon, csidl_name), 0, 0) 275 | # Try to make this a unicode path because SHGetFolderPath does 276 | # not return unicode strings when there is unicode data in the 277 | # path. 278 | try: 279 | dir = unicode(dir) 280 | 281 | # Downgrade to short path name if have highbit chars. See 282 | # . 283 | has_high_char = False 284 | for c in dir: 285 | if ord(c) > 255: 286 | has_high_char = True 287 | break 288 | if has_high_char: 289 | try: 290 | import win32api 291 | dir = win32api.GetShortPathName(dir) 292 | except ImportError: 293 | pass 294 | except UnicodeError: 295 | pass 296 | return dir 297 | 298 | 299 | def _get_win_folder_with_ctypes(csidl_name): 300 | import ctypes 301 | 302 | csidl_const = { 303 | "CSIDL_APPDATA": 26, 304 | "CSIDL_COMMON_APPDATA": 35, 305 | "CSIDL_LOCAL_APPDATA": 28, 306 | }[csidl_name] 307 | 308 | buf = ctypes.create_unicode_buffer(1024) 309 | ctypes.windll.shell32.SHGetFolderPathW(None, csidl_const, None, 0, buf) 310 | 311 | # Downgrade to short path name if have highbit chars. See 312 | # . 313 | has_high_char = False 314 | for c in buf: 315 | if ord(c) > 255: 316 | has_high_char = True 317 | break 318 | if has_high_char: 319 | buf2 = ctypes.create_unicode_buffer(1024) 320 | if ctypes.windll.kernel32.GetShortPathNameW(buf.value, buf2, 1024): 321 | buf = buf2 322 | 323 | return buf.value 324 | 325 | 326 | if sys.platform == "win32": 327 | try: 328 | import win32com.shell 329 | _get_win_folder = _get_win_folder_with_pywin32 330 | except ImportError: 331 | try: 332 | import ctypes 333 | _get_win_folder = _get_win_folder_with_ctypes 334 | except ImportError: 335 | _get_win_folder = _get_win_folder_from_registry 336 | 337 | #---- self test code 338 | 339 | if __name__ == "__main__": 340 | appname = "MyApp" 341 | appauthor = "MyCompany" 342 | 343 | props = ("user_data_dir", "site_data_dir", "user_cache_dir", 344 | "user_log_dir") 345 | 346 | print("-- app dirs (without optional 'version')") 347 | dirs = AppDirs(appname, appauthor, version="1.0") 348 | for prop in props: 349 | print("%s: %s" % (prop, getattr(dirs, prop))) 350 | 351 | print("\n-- app dirs (with optional 'version')") 352 | dirs = AppDirs(appname, appauthor) 353 | for prop in props: 354 | print("%s: %s" % (prop, getattr(dirs, prop))) 355 | -------------------------------------------------------------------------------- /pyvmmonitor_core/bunch.py: -------------------------------------------------------------------------------- 1 | ''' 2 | To use a Bunch do: 3 | 4 | bunch = Bunch(a=10, b=20) 5 | print(bunch.a) 6 | ''' 7 | 8 | 9 | class Bunch(object): 10 | 11 | def __init__(self, **kwargs): 12 | self.__dict__.update(kwargs) 13 | -------------------------------------------------------------------------------- /pyvmmonitor_core/callback.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Copyright: ESSS - Engineering Simulation and Scientific Software Ltda 3 | License: LGPL 4 | 5 | Based on: https://github.com/ESSS/ben10/blob/master/source/python/ben10/foundation/callback.py 6 | 7 | To use a callback do: 8 | 9 | class MyObject(object): 10 | def receive_notification(self, arg): 11 | print('Receive notification: %s' % (arg,)) 12 | 13 | my_object = MyObject() 14 | callback = Callback() 15 | 16 | # Note: only weak-references are kept (unless it's an unbound method), so, this object is 17 | # still available for garbage-collection. 18 | callback.register(my_object.receive_notification) 19 | 20 | ... 21 | 22 | callback(arg=10) 23 | 24 | callback.unregister(my_object.receive_notification) 25 | 26 | ''' 27 | import sys 28 | import types 29 | import weakref 30 | from collections import OrderedDict as odict 31 | 32 | from pyvmmonitor_core import compat 33 | from pyvmmonitor_core.thread_utils import is_in_main_thread 34 | 35 | try: 36 | import new 37 | except ImportError: 38 | import types as new 39 | 40 | 41 | class Callback(object): 42 | ''' 43 | Object that provides a way for others to connect in it and later call it to call 44 | those connected. 45 | 46 | .. note:: This implementation is improved in that it works directly accessing functions based 47 | on a key in an ordered dict, so, register, unregister and contains are much faster than the 48 | old callback. 49 | 50 | .. note:: it only stores weakrefs to objects connected 51 | 52 | .. note:: __slots__ added, so, it cannot have weakrefs to it (but as it stores weakrefs 53 | internally, that shouldn't be a problem). If weakrefs are really needed, 54 | __weakref__ should be added to the slots. 55 | ''' 56 | __call_out_of_main_thread__ = True 57 | 58 | __slots__ = [ 59 | '_callbacks', 60 | '__weakref__' # We need this to be able to add weak references to callback objects. 61 | ] 62 | 63 | def __init__(self): 64 | self._callbacks = odict() 65 | 66 | if compat.PY2: 67 | def _get_key(self, func): 68 | ''' 69 | :param object func: 70 | The function for which we want the key. 71 | 72 | :rtype: object 73 | :returns: 74 | Returns the key to be used to access the object. 75 | 76 | .. note:: The key is guaranteed to be unique among the living objects, but if the object 77 | is garbage collected, a new function may end up having the same key. 78 | ''' 79 | try: 80 | if func.im_self is not None: 81 | # bound method 82 | return (id(func.im_self), id(func.im_func), id(func.im_class)) 83 | else: 84 | return (id(func.im_func), id(func.im_class)) 85 | 86 | except AttributeError: 87 | return id(func) 88 | else: 89 | def _get_key(self, func): 90 | ''' 91 | :param object func: 92 | The function for which we want the key. 93 | 94 | :rtype: object 95 | :returns: 96 | Returns the key to be used to access the object. 97 | 98 | .. note:: The key is guaranteed to be unique among the living objects, but if the object 99 | is garbage collected, a new function may end up having the same key. 100 | ''' 101 | try: 102 | if func.__self__ is not None: 103 | # bound method 104 | return (id(func.__self__), id(func.__func__), id(func.__class__)) 105 | else: 106 | return (id(func.__func__), id(func.__class__)) 107 | 108 | except AttributeError: 109 | # Instance or function 110 | return id(func) 111 | 112 | if compat.PY2: 113 | def _get_info(self, func): 114 | ''' 115 | :rtype: tuple(func_obj, func_func, func_class) 116 | :returns: 117 | Returns a tuple with the information needed to call a method later on (close to the 118 | WeakMethodRef, but a bit more specialized -- and faster for this context). 119 | ''' 120 | try: 121 | if func.im_self is not None: 122 | # bound method 123 | return (weakref.ref(func.im_self), func.im_func, func.im_class) 124 | else: 125 | # unbound method 126 | return (None, func.im_func, func.im_class) 127 | except AttributeError: 128 | if not isinstance(func, types.FunctionType): 129 | # Deal with an instance 130 | return (weakref.ref(func), None, None) 131 | else: 132 | # Not a method -- a callable: create a strong reference 133 | # Why you may ask? Well, the main reason is that this use-case is usually for 134 | # closures, so, it may be hard to find a place to add the instance -- and if 135 | # it's a top level, the function will be alive until the end of times anyway. 136 | # 137 | # Anyways, this is probably a case that should only be used with care as 138 | # unregistering must be explicit and things in the function scope will be 139 | # kept alive! 140 | return (None, func, None) 141 | else: 142 | def _get_info(self, func): 143 | ''' 144 | :rtype: tuple(func_obj, func_func, func_class) 145 | :returns: 146 | Returns a tuple with the information needed to call a method later on (close to the 147 | WeakMethodRef, but a bit more specialized -- and faster for this context). 148 | ''' 149 | try: 150 | if func.__self__ is not None: 151 | # bound method 152 | return (weakref.ref(func.__self__), func.__func__, func.__class__) 153 | else: 154 | # unbound method 155 | return (None, func.__func__, func.__class__) 156 | except AttributeError: 157 | if not isinstance(func, types.FunctionType): 158 | # Deal with an instance 159 | return (weakref.ref(func), None, None) 160 | else: 161 | # Not a method -- a callable: create a strong reference 162 | # Why you may ask? Well, the main reason is that this use-case is usually for 163 | # closures, so, it may be hard to find a place to add the instance -- and if 164 | # it's a top level, the function will be alive until the end of times anyway. 165 | # 166 | # Anyways, this is probably a case that should only be used with care as 167 | # unregistering must be explicit and things in the function scope will be 168 | # kept alive! 169 | return (None, func, None) 170 | 171 | def __call__(self, *args, **kwargs): # @DontTrace 172 | ''' 173 | Calls every registered function with the given args and kwargs. 174 | ''' 175 | callbacks = self._callbacks 176 | if not callbacks: 177 | return 178 | 179 | # Note: There's a copy of this code in the _calculate_to_call method below. It's a copy 180 | # because we don't want to had a function call overhead here. 181 | to_call = [] 182 | 183 | for key, info in compat.items(callbacks): # iterate in a copy 184 | 185 | func_obj = info[0] 186 | if func_obj is not None: 187 | # Ok, we have a self. 188 | func_obj = func_obj() 189 | if func_obj is None: 190 | # self is dead 191 | del callbacks[key] 192 | else: 193 | func_func = info[1] 194 | if func_func is None: 195 | to_call.append(func_obj) 196 | else: 197 | if compat.PY2: 198 | to_call.append(new.instancemethod(func_func, func_obj, info[2])) 199 | else: 200 | to_call.append(new.MethodType(func_func, func_obj)) 201 | 202 | else: 203 | func_func = info[1] 204 | 205 | # No self: either classmethod or just callable 206 | to_call.append(func_func) 207 | 208 | # let's keep the 'if' outside of the iteration... 209 | if not is_in_main_thread(): 210 | for func in to_call: 211 | if not getattr(func, '__call_out_of_main_thread__', False): 212 | raise AssertionError( 213 | 'Call: %s out of the main thread (and it is not marked as @not_main_thread_callback)!' % 214 | (func,)) 215 | 216 | for func in to_call: 217 | try: 218 | func(*args, **kwargs) 219 | except Exception: # Show it but don't propagate. 220 | sys.excepthook(*sys.exc_info()) 221 | 222 | def register(self, func): 223 | ''' 224 | Registers a function in the callback. 225 | 226 | :param object func: 227 | Method or function that will be called later. 228 | ''' 229 | key = self._get_key(func) 230 | callbacks = self._callbacks 231 | 232 | callbacks.pop(key, None) # remove if it exists 233 | callbacks[key] = self._get_info(func) 234 | 235 | def unregister(self, func): 236 | ''' 237 | unregister a function previously registered with register. 238 | 239 | :param object func: 240 | The function to be unregistered. 241 | ''' 242 | key = self._get_key(func) 243 | 244 | try: 245 | # As there can only be 1 instance with the same id alive, it should be OK just 246 | # deleting it directly (because if there was a dead reference pointing to it it will 247 | # be already dead anyways) 248 | del self._callbacks[key] 249 | except (KeyError, AttributeError): 250 | # Even when unregistering some function that isn't registered we shouldn't trigger an 251 | # exception, just do nothing 252 | pass 253 | 254 | def unregister_all(self): 255 | ''' 256 | Unregisters all functions 257 | ''' 258 | self._callbacks.clear() 259 | 260 | def __len__(self): 261 | return len(self._callbacks) 262 | 263 | 264 | def not_main_thread_callback(func): 265 | func.__call_out_of_main_thread__ = True 266 | return func 267 | -------------------------------------------------------------------------------- /pyvmmonitor_core/capture.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Helper to capture streams (stdout, stderr). 3 | 4 | Sample uses: 5 | 6 | with capture('stderr') as stderr: 7 | ... 8 | 9 | print(stderr.getvalue()) 10 | 11 | 12 | with capture(('stderr', 'stdout')) as streams: 13 | ... 14 | 15 | print(streams.getvalue()) 16 | 17 | License: LGPL 18 | 19 | Copyright: Brainwy Software 20 | ''' 21 | from __future__ import with_statement 22 | 23 | import sys 24 | 25 | PY2 = sys.version_info[0] < 3 26 | PY3 = not PY2 27 | 28 | 29 | def capture(streams=('stdout', 'stderr'), keep_original=False): 30 | if not isinstance(streams, (list, tuple)): 31 | streams = (streams,) 32 | return _RedirectionScope(keep_original, streams) 33 | 34 | 35 | # ================================================================================================== 36 | # Private API 37 | # ================================================================================================== 38 | class _StreamRedirector: 39 | 40 | def __init__(self, *args): 41 | self._streams = args 42 | 43 | def write(self, s): 44 | for r in self._streams: 45 | try: 46 | r.write(s) 47 | except Exception: 48 | pass 49 | 50 | def isatty(self): 51 | return False 52 | 53 | def flush(self): 54 | for r in self._streams: 55 | r.flush() 56 | 57 | def __getattr__(self, name): 58 | for r in self._streams: 59 | if hasattr(r, name): 60 | return r.__getattribute__(name) 61 | raise AttributeError(name) 62 | 63 | 64 | class _DummyStream: 65 | 66 | def __init__(self): 67 | self.buflist = [] 68 | import os 69 | self.encoding = os.environ.get('PYTHONIOENCODING', 'utf-8') 70 | 71 | def getvalue(self): 72 | return ''.join(self.buflist) 73 | 74 | def write(self, s): 75 | if not PY3: 76 | if isinstance(s, unicode): 77 | s = s.encode(self.encoding) 78 | self.buflist.append(s) 79 | 80 | def isatty(self): 81 | return False 82 | 83 | def flush(self): 84 | pass 85 | 86 | 87 | class _RedirectionScope(object): 88 | 89 | def __init__(self, keep_original, streams): 90 | self.keep_original = keep_original 91 | self.streams = streams 92 | self.original_streams = [] 93 | 94 | def __enter__(self, *args, **kwargs): 95 | stream = _DummyStream() 96 | 97 | for std in self.streams: 98 | original = getattr(sys, std) 99 | self.original_streams.append(original) 100 | 101 | if self.keep_original: 102 | setattr(sys, std, _StreamRedirector(original, stream)) 103 | else: 104 | setattr(sys, std, stream) 105 | return stream 106 | 107 | def __exit__(self, *args, **kwargs): 108 | for std, original in zip(self.streams, self.original_streams): 109 | setattr(sys, std, original) 110 | -------------------------------------------------------------------------------- /pyvmmonitor_core/commands_manager.py: -------------------------------------------------------------------------------- 1 | ''' 2 | The commands manager is an API to be used to create commands, bind commands to handlers and 3 | activate them. 4 | 5 | It's also possible to bind handlers to a given scope so that they're only active when a scope 6 | is active. 7 | 8 | # The basic usage is: 9 | 10 | commands_manager.register_command('copy', 'Copy') 11 | commands_manager.set_command_handler('copy', copy_to_clipboard) 12 | commands_manager.activate('copy') # activates the copy action 13 | 14 | # Then, if there was a special copy on some context, 15 | # one would need to register the scope/handler: 16 | 17 | commands_manager.register_scope('custom_scope') 18 | commands_manager.set_command_handler('copy', copy_to_clipboard, 'custom_scope') 19 | 20 | # And then active/deactivate such a context when needed: 21 | 22 | commands_manager.activate_scope('custom_scope') 23 | commands_manager.activate('copy') 24 | commands_manager.deactivate_scope('custom_scope') 25 | ''' 26 | 27 | from collections import namedtuple 28 | 29 | from pyvmmonitor_core import implements, interface 30 | 31 | 32 | class ICommandsManager(object): 33 | 34 | DEFAULT_SCOPE = None 35 | CURRENT_SCOPE = [] # Sentinel: any mutable (check with 'is') 36 | 37 | def register_command(self, command_id, command_name, icon=None, status_tip=None): 38 | ''' 39 | Registers a command and makes it available to be activated (if no handler is available 40 | after being registered, nothing is done if it's activated). 41 | 42 | :param str command_id: 43 | :param str command_name: 44 | :param object icon: 45 | May be the actual icon or a way to identify it (at core it doesn't make 46 | a difference, it just stores the value to be consumed later on). 47 | :param str status_tip: 48 | A tip for the command (if not given, a default one may be given based on the 49 | command_name). 50 | ''' 51 | 52 | def get_command_info(self, command_id): 53 | ''' 54 | :param str command_id: 55 | The command id for which we want the info. 56 | 57 | :return: a namedtuple with command_id, command_name, icon, status_tip 58 | ''' 59 | 60 | def set_command_handler(self, command_id, command_handler, scope=DEFAULT_SCOPE): 61 | ''' 62 | Sets a handler to the given command id (optionally with a different scope). 63 | 64 | The command_handler must be a callable -- it may accept arguments (which then will need to 65 | be passed in #activate). 66 | 67 | It's possible to pass None to set no command handler in the context (also see 68 | remove_command_handler to remove a registered command handler -- in case it's registered 69 | and then removed). 70 | ''' 71 | 72 | def remove_command_handler(self, command_id, command_handler, scope=DEFAULT_SCOPE): 73 | ''' 74 | Removes a registered handler if it's the current handler at a given scope (does nothing 75 | if it's not the current handler). 76 | ''' 77 | 78 | def activate(self, command_id, __scope__=CURRENT_SCOPE, **kwargs): 79 | ''' 80 | Activates a given command. 81 | 82 | kwargs are passed on to the handler of the command. Note that only arguments which are 83 | simple python objects should be passed. 84 | 85 | Namely: int/long/float/complex/str/bytes/bool/tuple/list/set (this restriction is enforced 86 | so that clients can be sure that they can easily replicate a command invocation). 87 | ''' 88 | 89 | def register_scope(self, scope): 90 | ''' 91 | :param str scope: 92 | The scope which can have a different set of handlers for the existing actions. 93 | ''' 94 | 95 | def activate_scope(self, scope): 96 | ''' 97 | Activates a given scope so that the commands registered in such a scope have precedence 98 | over the commands in the default scope (or previously activated scopes). 99 | ''' 100 | 101 | def deactivate_scope(self, scope): 102 | ''' 103 | Deactivates a previously activated scope. 104 | ''' 105 | 106 | def list_command_ids(self): 107 | ''' 108 | Returns the available command ids. 109 | ''' 110 | 111 | def list_active_scopes(self): 112 | ''' 113 | Returns the current scope activation list. 114 | 115 | :rtype: list(str) 116 | ''' 117 | 118 | 119 | def create_default_commands_manager(): 120 | ''' 121 | Creates a default implementation for ICommandsManager. 122 | ''' 123 | return _DefaultCommandsManager() 124 | 125 | # --- Private API from now on --- 126 | 127 | 128 | def _default_noop_handler(**kwargs): 129 | pass 130 | 131 | 132 | class CommandUndefinedEror(Exception): 133 | pass 134 | 135 | 136 | _CommandInfo = namedtuple('_CommandInfo', ('command_id', 'command_name', 'icon', 'status_tip')) 137 | 138 | 139 | @interface.check_implements(ICommandsManager) 140 | class _DefaultCommandsManager(object): 141 | ''' 142 | Users should base on ICommandsManager (create_default_commands_manager can be used to create 143 | a default implementation, this class is not exposed and can be removed -- use aggregation 144 | to compose a new class if needed). 145 | 146 | @see: create_default_commands_manager() 147 | ''' 148 | 149 | def __init__(self): 150 | self._command_id_to_scopes = {} 151 | self._command_id_to_info = {} 152 | self._activated_scopes = [ICommandsManager.DEFAULT_SCOPE] 153 | self._valid_scopes = {ICommandsManager.DEFAULT_SCOPE} 154 | 155 | @implements(ICommandsManager.list_command_ids) 156 | def list_command_ids(self): 157 | from pyvmmonitor_core import compat 158 | return compat.keys(self._command_id_to_info) 159 | 160 | @implements(ICommandsManager.list_active_scopes) 161 | def list_active_scopes(self): 162 | return self._activated_scopes[:] 163 | 164 | @implements(ICommandsManager.register_scope) 165 | def register_scope(self, scope): 166 | self._valid_scopes.add(scope) 167 | 168 | @implements(ICommandsManager.activate_scope) 169 | def activate_scope(self, scope): 170 | if scope not in self._valid_scopes: 171 | raise ValueError('The passed scope (%s) was not registered.' % (scope,)) 172 | 173 | self._activated_scopes.append(scope) 174 | 175 | if len(self._activated_scopes) > 20: 176 | import sys 177 | sys.stderr.write( 178 | 'It seems there is some issue in scopes not being deactivated!\nActivated scopes: %s' % # @IgnorePep8 179 | (self._activated_scopes,)) 180 | 181 | @implements(ICommandsManager.deactivate_scope) 182 | def deactivate_scope(self, scope): 183 | from pyvmmonitor_core.list_utils import remove_last_occurrence 184 | 185 | if scope == ICommandsManager.DEFAULT_SCOPE: 186 | raise AssertionError('Default scope cannot be deactivated.') 187 | 188 | if not remove_last_occurrence(self._activated_scopes, scope): 189 | raise RuntimeError( 190 | 'Unable to deactivate scope not activated: %s. Active scopes: %s' % 191 | (scope, self._activated_scopes)) 192 | 193 | @implements(ICommandsManager.register_command) 194 | def register_command(self, command_id, command_name, icon=None, status_tip=None): 195 | if command_id in self._command_id_to_info: 196 | raise RuntimeError('Command: %s already registered' % (command_id,)) 197 | 198 | self._command_id_to_info[command_id] = _CommandInfo( 199 | command_id, command_name, icon, status_tip) 200 | 201 | self._command_id_to_scopes[command_id] = { 202 | ICommandsManager.DEFAULT_SCOPE: _default_noop_handler 203 | } 204 | 205 | @implements(ICommandsManager.get_command_info) 206 | def get_command_info(self, command_id): 207 | try: 208 | return self._command_id_to_info[command_id] 209 | except KeyError: 210 | raise CommandUndefinedEror('Command with id: %s is not defined.' % (command_id,)) 211 | 212 | @implements(ICommandsManager.set_command_handler) 213 | def set_command_handler(self, command_id, command_handler, 214 | scope=ICommandsManager.DEFAULT_SCOPE): 215 | if scope not in self._valid_scopes: 216 | raise ValueError('The passed scope (%s) was not registered.' % (scope,)) 217 | 218 | try: 219 | scopes = self._command_id_to_scopes[command_id] 220 | except KeyError: 221 | raise CommandUndefinedEror('Command with id: %s is not defined.' % (command_id,)) 222 | else: 223 | prev_command_handler = scopes.get(scope, _default_noop_handler) 224 | scopes[scope] = command_handler 225 | return prev_command_handler 226 | 227 | @implements(ICommandsManager.remove_command_handler) 228 | def remove_command_handler(self, command_id, command_handler, 229 | scope=ICommandsManager.DEFAULT_SCOPE): 230 | if scope not in self._valid_scopes: 231 | raise ValueError('The passed scope (%s) was not registered.' % (scope,)) 232 | 233 | try: 234 | scopes = self._command_id_to_scopes[command_id] 235 | except KeyError: 236 | raise CommandUndefinedEror('Command with id: %s is not defined.' % (command_id,)) 237 | else: 238 | prev_command_handler = scopes.get(scope, _default_noop_handler) 239 | if prev_command_handler is command_handler: 240 | scopes[scope] = None 241 | return True 242 | 243 | @implements(ICommandsManager.activate) 244 | def activate(self, command_id, __scope__=ICommandsManager.CURRENT_SCOPE, **kwargs): 245 | try: 246 | scopes = self._command_id_to_scopes[command_id] 247 | except KeyError: 248 | raise CommandUndefinedEror('Command with id: %s is not defined.' % (command_id,)) 249 | else: 250 | if __scope__ is ICommandsManager.CURRENT_SCOPE: 251 | for active_scope in reversed(self._activated_scopes): 252 | handler = scopes.get(active_scope) 253 | if handler is not None: 254 | handler(**kwargs) 255 | break 256 | else: 257 | # Use the passed scope. 258 | handler = scopes.get(__scope__) 259 | if handler is not None: 260 | handler(**kwargs) 261 | -------------------------------------------------------------------------------- /pyvmmonitor_core/compat.py: -------------------------------------------------------------------------------- 1 | ''' 2 | License: LGPL 3 | 4 | Copyright: Brainwy Software 5 | 6 | Helpers to deal with python2/3. 7 | ''' 8 | 9 | import sys 10 | 11 | PY2 = sys.version_info[0] < 3 12 | PY3 = not PY2 13 | 14 | 15 | if PY3: 16 | unicode = str 17 | bytes = bytes 18 | xrange = range 19 | izip = zip 20 | from io import StringIO 21 | 22 | def iterkeys(d): 23 | return d.keys() 24 | 25 | def itervalues(d): 26 | return d.values() 27 | 28 | def iteritems(d): 29 | return d.items() 30 | 31 | def values(d): 32 | return list(d.values()) 33 | 34 | def keys(d): 35 | return list(d.keys()) 36 | 37 | def items(d): 38 | return list(d.items()) 39 | 40 | def as_bytes(b): 41 | if b.__class__ == str: 42 | return b.encode('utf-8') 43 | return b 44 | 45 | def as_bytesf(b): 46 | if b.__class__ == str: 47 | return b.encode(sys.getfilesystemencoding()) 48 | return b 49 | 50 | def as_unicode(b): 51 | if b.__class__ != str: 52 | return b.decode('utf-8', 'replace') 53 | return b 54 | 55 | def as_unicodef(b): # unicode filesystem 56 | if b.__class__ != str: 57 | return b.decode(sys.getfilesystemencoding(), 'replace') 58 | return b 59 | 60 | def next(it): 61 | return it.__next__() 62 | 63 | 64 | else: 65 | unicode = unicode 66 | bytes = str 67 | xrange = xrange 68 | import itertools 69 | izip = itertools.izip 70 | 71 | from StringIO import StringIO 72 | 73 | def as_bytes(b): 74 | if b.__class__ == unicode: 75 | return b.encode('utf-8') 76 | return b 77 | 78 | def as_bytesf(b): 79 | if b.__class__ == unicode: 80 | return b.encode(sys.getfilesystemencoding()) 81 | return b 82 | 83 | def as_unicode(b): 84 | if b.__class__ != unicode: 85 | return b.decode('utf-8', 'replace') 86 | return b 87 | 88 | def as_unicodef(b): # unicode filesystem 89 | if b.__class__ != unicode: 90 | return b.decode(sys.getfilesystemencoding(), 'replace') 91 | return b 92 | 93 | def iterkeys(d): 94 | return d.iterkeys() 95 | 96 | def itervalues(d): 97 | return d.itervalues() 98 | 99 | def iteritems(d): 100 | return d.iteritems() 101 | 102 | def values(d): 103 | return d.values() 104 | 105 | def keys(d): 106 | return d.keys() 107 | 108 | def items(d): 109 | return d.items() 110 | 111 | def next(it): 112 | return it.next() 113 | -------------------------------------------------------------------------------- /pyvmmonitor_core/disposable.py: -------------------------------------------------------------------------------- 1 | ''' 2 | To use: 3 | 4 | class MyClassWithResources(Disposable): 5 | 6 | def _on_dispose(self): 7 | ... # dispose of resources 8 | 9 | 10 | obj = MyClassWithResources() 11 | ... 12 | obj.dispose() 13 | 14 | If the object isn't properly disposed, a print will be given at interpreter shutdown. 15 | ''' 16 | from __future__ import print_function 17 | 18 | # This version isn't always correct (when collected during interpreter shutdown it may fail to print). 19 | # class Disposable(object): 20 | # ''' 21 | # Class which warns if it's not properly disposed before object is deleted (which could 22 | # be during interpreter shutdown). 23 | # ''' 24 | # 25 | # def __init__(self): 26 | # import weakref 27 | # self._dispose_info = dispose_info = {'disposed': False} 28 | # 29 | # s = str(self) 30 | # 31 | # def on_dispose(ref): 32 | # if not dispose_info['disposed']: 33 | # import sys 34 | # import traceback 35 | # try: 36 | # frame = sys._getframe() 37 | # lst = traceback.format_list(traceback.extract_stack(frame)[-10:]) 38 | # finally: 39 | # # We can have leaks if we don't collect the frame here. 40 | # frame = None 41 | # msg = ''.join(lst + [ 42 | # '%s: not properly disposed before being collected.\n' % (s,) 43 | # ]) 44 | # sys.stderr.write(msg) 45 | # 46 | # self._ref = weakref.ref(self, on_dispose) 47 | # 48 | # def dispose(self): 49 | # if not self.is_disposed(): 50 | # try: 51 | # self._on_dispose() 52 | # finally: 53 | # self._dispose_info['disposed'] = True 54 | # 55 | # def is_disposed(self): 56 | # return self._dispose_info['disposed'] 57 | # 58 | # def _on_dispose(self): 59 | # pass 60 | 61 | 62 | class _Warn(object): 63 | 64 | def __init__(self, msg): 65 | self.msg = msg 66 | 67 | def __call__(self): 68 | import sys 69 | sys.stderr.write(self.msg) 70 | 71 | 72 | class Disposable(object): 73 | ''' 74 | Class which warns if it's not properly disposed before object is deleted (which could 75 | be during interpreter shutdown). 76 | ''' 77 | 78 | def __init__(self): 79 | self._disposed = False 80 | 81 | try: 82 | from pyvmmonitor_core import is_development 83 | if is_development(): 84 | import sys 85 | import traceback 86 | frame = sys._getframe() 87 | lst = traceback.format_list(traceback.extract_stack(frame)) 88 | else: 89 | lst = [] 90 | finally: 91 | # We can have leaks if we don't collect the frame here. 92 | frame = None 93 | msg = '%s: not properly disposed before being collected (see allocation point above).\n' % ( 94 | self,) 95 | msg = ''.join(lst + [msg]) 96 | 97 | self._msg_callback = _Warn(msg) 98 | import atexit 99 | if hasattr(atexit, 'unregister'): 100 | atexit.register(self._msg_callback) 101 | 102 | def dispose(self): 103 | import atexit 104 | if hasattr(atexit, 'unregister'): 105 | atexit.unregister(self._msg_callback) 106 | try: 107 | self._on_dispose() 108 | finally: 109 | self._disposed = True 110 | 111 | def is_disposed(self): 112 | return self._disposed 113 | 114 | def _on_dispose(self): 115 | pass 116 | -------------------------------------------------------------------------------- /pyvmmonitor_core/dotpath.py: -------------------------------------------------------------------------------- 1 | ''' 2 | License: LGPL 3 | 4 | Copyright: Brainwy Software 5 | 6 | Helpers for dealing with names with dots in it (like os.path deals with slashes). 7 | ''' 8 | 9 | 10 | def parent(path): 11 | ''' 12 | parent('a.b.c.d') => 'a.b.c' 13 | parent('foo') => '' 14 | ''' 15 | index = path.rfind('.') 16 | if index > -1: 17 | return path[:index] 18 | else: 19 | return '' 20 | 21 | 22 | def name(path): 23 | ''' 24 | name('a.b.c.d') => 'd' 25 | name('foo') => 'foo' 26 | ''' 27 | index = path.rfind('.') 28 | if index > -1: 29 | return path[index + 1:] 30 | else: 31 | return path 32 | -------------------------------------------------------------------------------- /pyvmmonitor_core/exec_external.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Helpers for dealing with launching some external utility and keeping track of its output/killing it 3 | later on. 4 | 5 | see: #ExecExternal docs 6 | 7 | License: LGPL 8 | 9 | Copyright: Brainwy Software 10 | ''' 11 | import subprocess 12 | import sys 13 | import threading 14 | 15 | import psutil 16 | 17 | from pyvmmonitor_core.log_utils import get_logger 18 | 19 | try: 20 | import StringIO 21 | except Exception: 22 | import io as StringIO 23 | 24 | PY2 = sys.version_info[0] < 3 25 | 26 | logger = get_logger(__name__) 27 | 28 | 29 | def kill_process(pid, kill_children=True): 30 | try: 31 | process = psutil.Process(pid) 32 | except psutil.NoSuchProcess: 33 | logger.info( 34 | 'Process with pid %s not available to kill (probably died in the meanwhile).', 35 | (pid,)) 36 | return 37 | 38 | if kill_children: 39 | for child in _get_children(process): 40 | try: 41 | child.kill() 42 | except psutil.NoSuchProcess: 43 | logger.info( 44 | 'Child process with pid %s not available to kill ' 45 | '(probably died in the meanwhile).', (child.pid,)) 46 | try: 47 | process.kill() 48 | except psutil.NoSuchProcess: 49 | logger.info( 50 | 'Process with pid %s not available to kill (probably died in the meanwhile).', 51 | (pid,)) 52 | 53 | 54 | def _get_children(process): 55 | try: 56 | if hasattr(process, 'children'): 57 | return process.children(recursive=True) 58 | return process.get_children(recursive=True) 59 | except psutil.NoSuchProcess: 60 | logger.info( 61 | 'Process with pid %s not available ' 62 | '(probably died in the meanwhile).', (process.pid,)) 63 | return [] 64 | 65 | 66 | class ExecExternal(object): 67 | ''' 68 | execute_external = ExecExternal([my, cmd]) 69 | 70 | # I.e.: call will be in a busy loop getting output, so, we have to 71 | # do it in a thread. 72 | threading.Thread(target=execute_external.call).start() 73 | 74 | while not execute_external.finished: 75 | output = execute_external.get_output() 76 | if output: 77 | ... show to user? 78 | 79 | if cancel_it(): 80 | execute_external.cancel() 81 | 82 | sleep(0.1) 83 | 84 | output = execute_external.get_output() 85 | if output: 86 | # Do one additional loop as it may have finished but the last output 87 | # was not gotten. 88 | ''' 89 | 90 | def __init__(self, args, cwd=None, env=None): 91 | self.args = args 92 | self.cwd = cwd 93 | self.env = env 94 | self.pid = None 95 | self._read_contents = [] 96 | self._process = None 97 | self._finished_event = threading.Event() 98 | self._lock = threading.Lock() 99 | 100 | finished = property(lambda self: self._finished_event.is_set()) 101 | 102 | @property 103 | def stdin(self): 104 | return self._process.stdin 105 | 106 | def call(self, **popen_kwargs): 107 | try: 108 | startupinfo = None 109 | if sys.platform == 'win32': 110 | # We don't want to show the shell on windows! 111 | startupinfo = subprocess.STARTUPINFO() 112 | startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW 113 | startupinfo.wShowWindow = subprocess.SW_HIDE 114 | startupinfo = startupinfo 115 | 116 | if PY2: 117 | args = [] 118 | for arg in self.args: 119 | if isinstance(arg, unicode): 120 | arg = arg.encode(sys.getfilesystemencoding()) 121 | args.append(arg) 122 | 123 | else: 124 | args = self.args 125 | 126 | kwargs = dict( 127 | args=args, 128 | bufsize=1, 129 | cwd=self.cwd, 130 | env=self.env, 131 | universal_newlines=True, 132 | stdout=subprocess.PIPE, 133 | stderr=subprocess.STDOUT, 134 | startupinfo=startupinfo, 135 | ) 136 | kwargs.update(popen_kwargs) 137 | process = self._process = subprocess.Popen(**kwargs) 138 | self.pid = process.pid 139 | 140 | try: 141 | for line in iter(process.stdout.readline, ''): 142 | with self._lock: 143 | self._read_contents.append(line) 144 | 145 | process.wait() 146 | finally: 147 | self._finished_event.set() 148 | self._process = None 149 | except Exception: 150 | f = StringIO.StringIO() 151 | import traceback 152 | traceback.print_exc(file=f) 153 | with self._lock: 154 | self._read_contents.append(f.getvalue()) 155 | raise 156 | 157 | def get_output(self): 158 | with self._lock: 159 | current_read = self._read_contents 160 | self._read_contents = [] 161 | return ''.join(current_read) 162 | 163 | def cancel(self): 164 | if self._finished_event.is_set(): 165 | return 166 | process = self._process 167 | 168 | if process is not None: 169 | kill_process(process.pid) 170 | 171 | self._finished_event.set() 172 | self._process = None 173 | -------------------------------------------------------------------------------- /pyvmmonitor_core/html.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Helper for escaping html which works with either bytes or unicode. 3 | 4 | License: LGPL 5 | 6 | Copyright: Brainwy Software 7 | ''' 8 | 9 | import sys 10 | 11 | PY2 = sys.version_info[0] < 3 12 | PY3 = not PY2 13 | 14 | if PY3: 15 | 16 | def escape_html(s, quote=None): 17 | if isinstance(s, bytes): 18 | s = s.replace(b"&", b"&") # First! 19 | s = s.replace(b"<", b"<") 20 | s = s.replace(b">", b">") 21 | s = s.replace(b'"', b""") 22 | else: 23 | if not isinstance(s, str): 24 | s = str(s) 25 | 26 | s = s.replace("&", "&") # First! 27 | s = s.replace("<", "<") 28 | s = s.replace(">", ">") 29 | s = s.replace('"', """) 30 | return s 31 | 32 | else: 33 | 34 | def escape_html(s, quote=None): 35 | if isinstance(s, str): 36 | s = s.replace(b"&", b"&") # First! 37 | s = s.replace(b"<", b"<") 38 | s = s.replace(b">", b">") 39 | s = s.replace(b'"', b""") 40 | 41 | else: 42 | if not isinstance(s, unicode): 43 | s = unicode(s) 44 | 45 | s = s.replace(u"&", u"&") # First! 46 | s = s.replace(u"<", u"<") 47 | s = s.replace(u">", u">") 48 | s = s.replace(u'"', u""") 49 | 50 | return s 51 | -------------------------------------------------------------------------------- /pyvmmonitor_core/interface.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Module with basic functionality for declaring/verifying interfaces on Python. 3 | 4 | It's recommended that interfaces start with 'I' (although it's not enforced). 5 | 6 | Any object can be used as an interface (which implies all the methods in the class 7 | are virtual and should be implemented by an implementor of such interface). 8 | 9 | Example: 10 | 11 | class ISomething(object): 12 | def do_something(self): 13 | """ 14 | Do something here 15 | """ 16 | 17 | @interface.check_implements(ISomething) 18 | class SomethingImpl(object): 19 | 20 | # @implements is optional, for documentation purposes (will inherit docstring). 21 | @implements(ISomething.do_something) 22 | def do_something(self): 23 | pass 24 | 25 | Alternatively, it's possible to check if some object implements a given interface with: 26 | 27 | interface.is_implementation(obj, ISomething) 28 | 29 | or assert it: 30 | 31 | interface.assert_implements(obj, ISomething) 32 | 33 | Note: results are cached for faster access afterwards and the cache-key is the tuple(cls, 34 | interface), so, classes won't be garbage-collected after a check (usually classes don't die and this 35 | isn't a problem, but it may be the source of leaks in applications which create classes and expect 36 | them to die when no longer referenced). 37 | 38 | License: LGPL 39 | 40 | Copyright: Brainwy Software 41 | ''' 42 | 43 | import inspect 44 | import sys 45 | from collections import namedtuple 46 | 47 | from pyvmmonitor_core import compat 48 | from pyvmmonitor_core.memoization import memoize 49 | 50 | 51 | class BadImplementationError(Exception): 52 | pass 53 | 54 | 55 | _obj_methods = frozenset(dir(object)) 56 | _PY2 = sys.version_info[0] == 2 57 | 58 | 59 | @memoize 60 | def _get_methods_and_properties(interface_class): 61 | obj_methods = _obj_methods 62 | ret = [] 63 | for method_name in dir(interface_class): 64 | if method_name not in obj_methods: 65 | m = getattr(interface_class, method_name) 66 | if (inspect.isfunction(m) or inspect.ismethod(m) or 67 | m.__class__ == property or m.__class__.__name__ in ('cython_function_or_method',)): 68 | ret.append(method_name) 69 | return frozenset(ret) 70 | 71 | 72 | ImplDetails = namedtuple('ImplDetails', 'is_impl msg'.split()) 73 | _cache = {} 74 | 75 | if _PY2: 76 | from new import classobj # @UnresolvedImport 77 | 78 | def _is_class(obj): 79 | return isinstance(obj, (type, classobj)) 80 | 81 | else: 82 | 83 | def _is_class(obj): 84 | return isinstance(obj, type) 85 | 86 | 87 | def _impl_details(cls_or_obj, interface_class): 88 | if _is_class(cls_or_obj): 89 | cls = cls_or_obj 90 | else: 91 | cls = cls_or_obj.__class__ 92 | 93 | key = (cls, interface_class) 94 | ret = _cache.get(key) 95 | if ret is not None: 96 | return ret 97 | 98 | cls_methods_and_properties = _get_methods_and_properties(cls) 99 | for method_or_prop_name in _get_methods_and_properties(interface_class): 100 | if method_or_prop_name not in cls_methods_and_properties: 101 | found = False 102 | else: 103 | method_in_cls = getattr(cls, method_or_prop_name) 104 | method_in_interface = getattr(interface_class, method_or_prop_name) 105 | if method_in_cls == method_in_interface: 106 | found = False # Interface subclass but doesn't override method_or_prop_name. 107 | else: 108 | found = True 109 | 110 | if not found: 111 | impl_details = _cache[key] = ImplDetails( 112 | False, '%s.%s not available.\n\nExpected: %s\nto implement: %s\nFrom: %s' % 113 | (cls.__name__, method_or_prop_name, cls, method_or_prop_name, interface_class)) 114 | 115 | return impl_details 116 | else: 117 | if method_in_interface.__class__ == property: 118 | if method_in_cls.__class__ != property: 119 | msg = ('Expected %s to be a property in %s' % ( 120 | cls.__name__, method_or_prop_name)) 121 | impl_details = _cache[key] = ImplDetails(False, msg) 122 | return impl_details 123 | 124 | if method_in_interface.fdel is not None: 125 | if method_in_cls.fdel is None: 126 | msg = ('Expected fdel in %s.%s property.' % ( 127 | cls.__name__, method_or_prop_name)) 128 | impl_details = _cache[key] = ImplDetails(False, msg) 129 | return impl_details 130 | 131 | if method_in_interface.fset is not None: 132 | if method_in_cls.fset is None: 133 | msg = ('Expected fset in %s.%s property.' % ( 134 | cls.__name__, method_or_prop_name)) 135 | impl_details = _cache[key] = ImplDetails(False, msg) 136 | return impl_details 137 | 138 | # No need to check fget (should always be there). 139 | continue 140 | 141 | # Let's see if parameters match 142 | if compat.PY2: 143 | cls_args, cls_varargs, cls_varkw, cls_defaults = inspect.getargspec(method_in_cls) 144 | cls_kwonlyargs = None 145 | cls_kwonlydefaults = None 146 | else: 147 | cls_args, cls_varargs, cls_varkw, cls_defaults, cls_kwonlyargs, \ 148 | cls_kwonlydefaults, _ = inspect.getfullargspec(method_in_cls) 149 | 150 | if cls_varargs is not None and cls_varkw is not None: 151 | if not cls_args or cls_args == ['self'] or cls_args == ['cls']: 152 | # (*args, **kwargs), (self, *args, **kwargs) and (cls, *args, **kwargs) 153 | # always match 154 | continue 155 | 156 | if compat.PY2: 157 | interf_args, interf_varargs, interf_varkw, interf_defaults = inspect.getargspec( 158 | method_in_interface) 159 | interf_kwonlyargs = None 160 | interf_kwonlydefaults = None 161 | else: 162 | interf_args, interf_varargs, interf_varkw, interf_defaults, interf_kwonlyargs, \ 163 | interf_kwonlydefaults, _interf_annotations = inspect.getfullargspec( 164 | method_in_interface) 165 | 166 | # Now, let's see if parameters actually match the interface parameters. 167 | if interf_varargs is not None and cls_varargs is None or \ 168 | interf_varkw is not None and cls_varkw is None or \ 169 | interf_args != cls_args or \ 170 | interf_defaults != cls_defaults or \ 171 | interf_kwonlyargs != cls_kwonlyargs or \ 172 | interf_kwonlydefaults != cls_kwonlydefaults: 173 | 174 | if _PY2: 175 | interface_signature = inspect.formatargspec( 176 | interf_args, interf_varargs, interf_varkw, interf_defaults) 177 | 178 | class_signature = inspect.formatargspec( 179 | cls_args, cls_varargs, cls_varkw, cls_defaults) 180 | else: 181 | interface_signature = inspect.formatargspec( 182 | interf_args, interf_varargs, interf_varkw, interf_defaults, 183 | interf_kwonlyargs, interf_kwonlydefaults) 184 | 185 | class_signature = inspect.formatargspec( 186 | cls_args, cls_varargs, cls_varkw, cls_defaults, cls_kwonlyargs, 187 | cls_kwonlydefaults) 188 | 189 | msg = ("\nMethod params in %s.%s:\n" 190 | " %s\ndon't match params in %s.%s\n %s") 191 | msg = msg % (cls.__name__, method_or_prop_name, class_signature, 192 | interface_class.__name__, method_or_prop_name, interface_signature) 193 | 194 | impl_details = _cache[key] = ImplDetails(False, msg) 195 | 196 | return impl_details 197 | 198 | impl_details = _cache[key] = ImplDetails(True, None) 199 | return impl_details 200 | 201 | 202 | def assert_implements(cls_or_obj, interface_class): 203 | ''' 204 | :raise BadImplementationError: 205 | If the given object doesn't implement the passed interface. 206 | ''' 207 | details = _impl_details(cls_or_obj, interface_class) 208 | if details.msg: 209 | raise BadImplementationError(details.msg) 210 | 211 | 212 | def is_implementation(cls_or_obj, interface_class): 213 | ''' 214 | :return bool: 215 | True if the given object implements the passed interface (and False otherwise). 216 | ''' 217 | details = _impl_details(cls_or_obj, interface_class) 218 | return details.is_impl 219 | 220 | 221 | def check_implements(*interface_classes): 222 | ''' 223 | To be used as decorator: 224 | 225 | class ISomething(object): 226 | def m1(self): 227 | pass 228 | 229 | @interface.check_implements(ISomething) 230 | class SomethingImpl(object): 231 | pass 232 | 233 | ''' 234 | 235 | def func(cls): 236 | for interface_class in interface_classes: 237 | details = _impl_details(cls, interface_class) 238 | if details.msg: 239 | raise BadImplementationError(details.msg) 240 | return cls 241 | 242 | return func 243 | -------------------------------------------------------------------------------- /pyvmmonitor_core/iterables.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Helpers for dealing with iterables. 3 | 4 | License: LGPL 5 | 6 | Copyright: Brainwy Software 7 | ''' 8 | 9 | 10 | def remove_duplicates(iterable, ret_type=list): 11 | ''' 12 | Removes duplicate keeping order (and creating a new instance of the returned type). 13 | ''' 14 | seen = set() 15 | seen_add = seen.add 16 | return ret_type(x for x in iterable if not (x in seen or seen_add(x))) 17 | 18 | 19 | def count_iterable(iterable): 20 | ''' 21 | Count the number of items in an iterable (note that this will exhaust it in the process and 22 | it'll be unusable afterwards). 23 | ''' 24 | i = 0 25 | for _x in iterable: 26 | i += 1 27 | return i 28 | 29 | 30 | def iter_curr_and_next_closed_cycle(lst): 31 | ''' 32 | Provides an iterator which will give items with the curr and next value (repeating the 33 | first when the end is reached). 34 | 35 | i.e.: if lst == [1, 2, 3], will iterate with [(1, 2), (2, 3), (3, 1)] 36 | ''' 37 | if lst.__class__ == tuple: 38 | return zip(lst, lst[1:] + (lst[0],)) 39 | else: 40 | return zip(lst, lst[1:] + [lst[0]]) 41 | -------------------------------------------------------------------------------- /pyvmmonitor_core/lazy_loading.py: -------------------------------------------------------------------------------- 1 | # License: LGPL 2 | # 3 | # Copyright: Brainwy Software 4 | 5 | 6 | def load_token(import_class_path): 7 | initial = import_class_path 8 | try: 9 | i = import_class_path.rindex('.') 10 | modname = import_class_path[:i] 11 | import_class_path = import_class_path[i + 1:] 12 | 13 | ret = __import__(modname) 14 | for part in modname.split('.')[1:]: 15 | ret = getattr(ret, part) 16 | 17 | ret = getattr(ret, import_class_path) 18 | except (ImportError, AttributeError): 19 | raise ImportError('Unable to import: %s' % (initial,)) 20 | return ret 21 | 22 | 23 | class LazyCallable(object): 24 | 25 | def __init__(self, import_path): 26 | self.import_path = import_path 27 | self._loaded = None 28 | 29 | def __call__(self, *args, **kwargs): 30 | if self._loaded is None: 31 | self._loaded = load_token(self.import_path) 32 | return self._loaded(*args, **kwargs) 33 | -------------------------------------------------------------------------------- /pyvmmonitor_core/list_utils.py: -------------------------------------------------------------------------------- 1 | # License: LGPL 2 | # 3 | # Copyright: Brainwy Software 4 | 5 | 6 | def remove_last_occurrence(lst, element): 7 | ''' 8 | Removes the last occurrence of a given element in a list (modifies list in-place). 9 | 10 | :return bool: 11 | True if the element was found and False otherwise. 12 | ''' 13 | for i, s in enumerate(reversed(lst)): 14 | if s == element: 15 | del lst[len(lst) - 1 - i] 16 | return True 17 | return False 18 | -------------------------------------------------------------------------------- /pyvmmonitor_core/log_utils.py: -------------------------------------------------------------------------------- 1 | import logging.handlers 2 | from contextlib import contextmanager 3 | 4 | # Same thing as logging.getLogger, but with a pep-8 alias. 5 | # 6 | # Usually used as: 7 | # 8 | # logger = get_logger(__name__) 9 | # 10 | # ... 11 | # 12 | # logger.warn('Something happened.') 13 | get_logger = logging.getLogger 14 | 15 | 16 | def config_rotating_file_handler_from_env_var(env_var, log_filename, logger_name=''): 17 | ''' 18 | Configures a rotating file handler based on the contents of some environment var which 19 | can have a value of the logging preceded by 'ONLY_STDOUT:' to skip putting contents in the 20 | log file. 21 | 22 | :param env_var: 23 | The name of the environment variable to check. Valid values for it are: 24 | 'CRITICAL', 'ERROR', 'WARN', 'WARNING', 'INFO', 'DEBUG' 25 | 26 | it may also be preceded by: 27 | 'ONLY_STDOUT:', in which case the file won't be put in log_filename and will only be 28 | logged to stdout/stderr streams with a StreamHandler. 29 | 30 | :param log_filename: 31 | The place where the log filename should be set. 32 | 33 | :param logger_name: 34 | The name of the logger to which the handler should be added. 35 | ''' 36 | import os 37 | logging_level = os.environ.get(env_var, 'WARN').upper() 38 | 39 | only_stdout = False 40 | if logging_level.startswith('ONLY_STDOUT:'): 41 | only_stdout = True 42 | logging_level = logging_level[len('ONLY_STDOUT:'):] 43 | 44 | valid_levels = set(['CRITICAL', 'ERROR', 'WARN', 'WARNING', 'INFO', 'DEBUG']) 45 | msg = '' 46 | if logging_level not in valid_levels: 47 | msg = 'Invalid logging level on env var: %s: %s' % (env_var, logging_level) 48 | logging_level = 'WARN' 49 | 50 | logger = get_logger(logger_name) 51 | logger.setLevel(logging_level) 52 | 53 | if only_stdout: 54 | handler = logging.StreamHandler() 55 | else: 56 | handler = logging.handlers.RotatingFileHandler( 57 | log_filename, maxBytes=1024 * 1024, backupCount=5, encoding='utf-8') 58 | 59 | formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s : %(message)s') 60 | handler.setFormatter(formatter) 61 | 62 | logger.addHandler(handler) 63 | 64 | if msg: 65 | get_logger(__name__).warn(msg) 66 | 67 | 68 | @contextmanager 69 | def logger_level(logger, level): 70 | initial = logger.level 71 | logger.level = level 72 | try: 73 | yield 74 | finally: 75 | logger.level = initial 76 | -------------------------------------------------------------------------------- /pyvmmonitor_core/lru.py: -------------------------------------------------------------------------------- 1 | # License: LGPL 2 | # 3 | # Copyright: Brainwy Software 4 | 5 | import itertools 6 | import operator 7 | import sys 8 | 9 | _PY2 = sys.version_info[0] < 3 10 | _IS_PY3 = not _PY2 11 | 12 | 13 | class LRUCache(object): 14 | 15 | ''' 16 | This LRU cache should be reasonable for short collections, as it does a sort on the items if the 17 | collection would become too big (so, it is very fast for getting and setting but when its size 18 | would become higher than the max size it does one sort based on the internal time to decide 19 | which items should be removed -- which should be Ok if the resize_to isn't too close to the 20 | max_size so that it becomes an operation that doesn't happen all the time). 21 | ''' 22 | 23 | def __init__(self, max_size=100, resize_to=70): 24 | ''' 25 | :param int max_size: 26 | This is the maximum size of the cache. When some item is added and the cache would become 27 | bigger than this, it's resized to the value passed on resize_to. 28 | 29 | :param int resize_to: 30 | When a resize operation happens, this is the size of the final cache. 31 | ''' 32 | assert resize_to < max_size 33 | self.max_size = max_size 34 | self.resize_to = resize_to 35 | self._counter = 0 36 | self._dict = {} 37 | if _IS_PY3: 38 | self._next_time = itertools.count(0).__next__ 39 | else: 40 | self._next_time = itertools.count(0).next 41 | 42 | def __getitem__(self, key): 43 | item = self._dict[key] 44 | item[2] = self._next_time() 45 | return item[1] 46 | 47 | def __len__(self): 48 | return len(self._dict) 49 | 50 | def __setitem__(self, key, value): 51 | item = self._dict.get(key) 52 | if item is None: 53 | if len(self._dict) + 1 > self.max_size: 54 | self._resize_to() 55 | 56 | item = [key, value, self._next_time()] 57 | self._dict[key] = item 58 | else: 59 | item[1] = value 60 | item[2] = self._next_time() 61 | 62 | def __delitem__(self, key): 63 | del self._dict[key] 64 | 65 | def get(self, key, default=None): 66 | try: 67 | return self[key] 68 | except KeyError: 69 | return default 70 | 71 | def clear(self): 72 | self._dict.clear() 73 | 74 | if _IS_PY3: 75 | def values(self): 76 | return [i[1] for i in self._dict.values()] 77 | 78 | def keys(self): 79 | return [x[0] for x in self._dict.values()] 80 | 81 | def _resize_to(self): 82 | ordered = sorted(self._dict.values(), key=operator.itemgetter(2))[:self.resize_to] 83 | for i in ordered: 84 | del self._dict[i[0]] 85 | 86 | def iteritems(self, access_time=False): 87 | ''' 88 | :param bool access_time: 89 | If True sorts the returned items by the internal access time. 90 | ''' 91 | if access_time: 92 | for x in sorted(self._dict.values(), key=operator.itemgetter(2)): 93 | yield x[0], x[1] 94 | else: 95 | for x in self._dict.items(): 96 | yield x[0], x[1] 97 | 98 | else: 99 | def values(self): 100 | return [i[1] for i in self._dict.itervalues()] 101 | 102 | def keys(self): 103 | return [x[0] for x in self._dict.itervalues()] 104 | 105 | def _resize_to(self): 106 | ordered = sorted(self._dict.itervalues(), key=operator.itemgetter(2))[:self.resize_to] 107 | for i in ordered: 108 | del self._dict[i[0]] 109 | 110 | def iteritems(self, access_time=False): 111 | ''' 112 | :param bool access_time: 113 | If True sorts the returned items by the internal access time. 114 | ''' 115 | if access_time: 116 | for x in sorted(self._dict.itervalues(), key=operator.itemgetter(2)): 117 | yield x[0], x[1] 118 | else: 119 | for x in self._dict.iteritems(): 120 | yield x[0], x[1] 121 | -------------------------------------------------------------------------------- /pyvmmonitor_core/magic_vars.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Helpers for debugging: 3 | 4 | To use: 5 | 6 | set_magic_var() 7 | 8 | ... 9 | 10 | Somewhere else in the code: 11 | 12 | if is_magic_var_set(): 13 | import pydevd;pydevd.settrace() 14 | 15 | License: LGPL 16 | Copyright: Brainwy Software 17 | ''' 18 | 19 | _magic_vars = {} 20 | 21 | 22 | def set_magic_var(var_name='global_var', value=1): 23 | _magic_vars[var_name] = value 24 | 25 | 26 | def inc_magic_var(var_name='global_var'): 27 | _magic_vars[var_name] = _magic_vars.get(var_name, 0) + 1 28 | 29 | 30 | def is_magic_var_set(var_name='global_var'): 31 | return _magic_vars.get(var_name) is not None 32 | 33 | 34 | def get_magic_var(var_name='global_var'): 35 | return _magic_vars.get(var_name) 36 | -------------------------------------------------------------------------------- /pyvmmonitor_core/math_utils.py: -------------------------------------------------------------------------------- 1 | # License: LGPL 2 | # 3 | # Copyright: Brainwy Software 4 | 5 | # Numeric Constants 6 | from math import atan2, cos, pow, sin, sqrt 7 | 8 | from pyvmmonitor_core import compat 9 | 10 | MAX_INT32 = int(pow(2, 31) - 1) 11 | MIN_INT32 = -(MAX_INT32 + 1) 12 | 13 | MAX_UNSIGNED_INT32 = int(pow(2, 32) - 1) 14 | 15 | MAX_INT64 = int(pow(2, 63) - 1) 16 | MIN_INT64 = -(MAX_INT32 + 1) 17 | 18 | MAX_UNSIGNED_INT64 = int(pow(2, 64) - 1) 19 | 20 | MIN_FLOAT = -1e308 21 | MAX_FLOAT = 1e308 22 | 23 | PLUS_INFINITY = float('inf') 24 | MINUS_INFINITY = float('-inf') 25 | 26 | NAN = float('nan') 27 | 28 | 29 | def calc_angle_in_radians(p0, p1): 30 | x1, y1 = p0 31 | x2, y2 = p1 32 | theta = atan2(y2 - y1, x2 - x1) 33 | return theta 34 | 35 | 36 | def is_clockwise(points): 37 | ''' 38 | Note: considers 0,0 to be at the top-left corner, with values 39 | increasing to the bottom/right (at qt uses in the graphics view). 40 | ''' 41 | from . import iterables 42 | v = 0 43 | for p1, p2 in iterables.iter_curr_and_next_closed_cycle(points): 44 | v += ((p2[0] - p1[0]) * (p1[1] + p2[1])) 45 | return v < 0 46 | 47 | 48 | def create_point(base_point, angle_in_radians, distance): 49 | ''' 50 | Creates a new point from an angle and a distance from a base point. 51 | ''' 52 | x, y = base_point 53 | x += distance * cos(angle_in_radians) 54 | y += distance * sin(angle_in_radians) 55 | return x, y 56 | 57 | 58 | def calculate_distance(p0, p1): 59 | return sqrt((p1[0] - p0[0]) ** 2 + (p1[1] - p0[1]) ** 2) 60 | 61 | 62 | def nearest_point(pt, p0, p1): 63 | import numpy 64 | vec_p0_p1 = p1 - p0 65 | vec_len = numpy.linalg.norm(vec_p0_p1) 66 | 67 | vec_unit = vec_p0_p1 / vec_len 68 | 69 | vec_p0_pt = pt - p0 70 | project_vec_p0_pt_in_vec_unit = numpy.dot(vec_p0_pt, vec_unit) 71 | vec_projection = vec_unit * project_vec_p0_pt_in_vec_unit 72 | point_projected_on_line = p0 + vec_projection 73 | 74 | if project_vec_p0_pt_in_vec_unit > vec_len: # clip to segment 75 | return p1 76 | if project_vec_p0_pt_in_vec_unit < 0: 77 | return p0 78 | 79 | return point_projected_on_line 80 | 81 | 82 | def is_point_close_to_line(point, line, delta=0.2): 83 | import numpy 84 | line = numpy.array(line) 85 | for i in range(len(line) - 1): 86 | nearest = nearest_point(point, line[i], line[i + 1]) 87 | if calculate_distance(point, nearest) <= delta: 88 | return True 89 | 90 | return False 91 | 92 | 93 | def almost_equal(v1, v2, delta=1e-7): 94 | if v1.__class__ in (tuple, list): 95 | return all(almost_equal(x1, x2, delta) for (x1, x2) in compat.izip(v1, v2)) 96 | return abs(v1 - v2) < delta 97 | 98 | 99 | class Bounds(object): 100 | 101 | x1 = MAX_FLOAT 102 | y1 = MAX_FLOAT 103 | x2 = MIN_FLOAT 104 | y2 = MIN_FLOAT 105 | _immutable = False 106 | 107 | def __init__(self, x1=None, y1=None, x2=None, y2=None): 108 | if x1 is not None: 109 | self.x1 = x1 110 | if x2 is not None: 111 | self.x2 = x2 112 | if y1 is not None: 113 | self.y1 = y1 114 | if y2 is not None: 115 | self.y2 = y2 116 | 117 | def add_point(self, point): 118 | if self._immutable: 119 | raise AssertionError('Bounds currently immutable.') 120 | 121 | x, y = point 122 | if x < self.x1: 123 | self.x1 = x 124 | if y < self.y1: 125 | self.y1 = y 126 | 127 | if x > self.x2: 128 | self.x2 = x 129 | if y > self.y2: 130 | self.y2 = y 131 | 132 | def add_points(self, points): 133 | for p in points: 134 | self.add_point(p) 135 | 136 | @property 137 | def x(self): 138 | return self.x1 139 | 140 | @property 141 | def y(self): 142 | return self.y1 143 | 144 | @property 145 | def width(self): 146 | return self.x2 - self.x1 147 | 148 | @property 149 | def height(self): 150 | return self.y2 - self.y1 151 | 152 | def is_valid(self): 153 | return self.x1 <= self.x2 and self.y1 <= self.y2 154 | 155 | @property 156 | def center(self): 157 | return ((self.x1 + self.x2) / 2., (self.y1 + self.y2) / 2.) 158 | 159 | @property 160 | def nodes(self): 161 | ''' 162 | Returns nodes in the order: 163 | 164 | 0 ------------ 3 165 | | | 166 | | | 167 | 1 ------------ 2 168 | 169 | :rtype: tuple(tuple(float,float),tuple(float,float),tuple(float,float),tuple(float,float)) 170 | ''' 171 | return ((self.x1, self.y1), (self.x1, self.y2), (self.x2, self.y2), (self.x2, self.y1)) 172 | 173 | def __iter__(self): 174 | # x, y, w, h 175 | yield self.x1 176 | yield self.y1 177 | yield self.x2 - self.x1 178 | yield self.y2 - self.y1 179 | 180 | def copy(self): 181 | return Bounds(self.x1, self.y1, self.x2, self.y2) 182 | 183 | def enlarge(self, increment): 184 | if self._immutable: 185 | raise AssertionError('Bounds currently immutable.') 186 | 187 | if self.is_valid(): 188 | self.x1 -= increment 189 | self.x2 += increment 190 | 191 | self.y1 -= increment 192 | self.y2 += increment 193 | 194 | def enlarged(self, increment): 195 | cp = self.copy() 196 | cp.enlarge(increment) 197 | return cp 198 | 199 | def scale(self, scale): 200 | if self._immutable: 201 | raise AssertionError('Bounds currently immutable.') 202 | 203 | if self.is_valid(): 204 | diff_x = abs(self.width * scale) / 2. 205 | diff_y = abs(self.height * scale) / 2. 206 | 207 | self.x1 -= diff_x 208 | self.x2 += diff_x 209 | 210 | self.y1 -= diff_y 211 | self.y2 += diff_y 212 | 213 | def scaled(self, scale): 214 | cp = self.copy() 215 | cp.scale(scale) 216 | return cp 217 | 218 | def __str__(self): 219 | return 'Bounds(x1=%s, y1=%s, x2=%s, y2=%s)' % (self.x1, self.y1, self.x2, self.y2) 220 | 221 | __repr__ = __str__ 222 | 223 | def __eq__(self, o): 224 | if isinstance(o, Bounds): 225 | return list(self) == list(o) 226 | 227 | return False 228 | 229 | def __ne__(self, o): 230 | return not self == o 231 | 232 | def make_immutable(self): 233 | self._immutable = True 234 | 235 | 236 | def radians_to_0_360_degrees(angle): 237 | import math 238 | degrees = math.degrees(angle) 239 | while degrees < 0: 240 | degrees += 360 241 | while degrees > 360: 242 | degrees -= 360 243 | return degrees 244 | 245 | 246 | def equilateral_triangle_height(triangle_side): 247 | return sqrt(3) / 2 * triangle_side 248 | -------------------------------------------------------------------------------- /pyvmmonitor_core/memoization.py: -------------------------------------------------------------------------------- 1 | def memoize(function): 2 | ''' 3 | Use as: 4 | 5 | @memoize 6 | def method(v1): 7 | pass 8 | 9 | Note that memoizes everything passed (things put in it will never be collected). 10 | ''' 11 | from functools import wraps 12 | 13 | memo = {} 14 | 15 | @wraps(function) 16 | def wrapper(*args): 17 | if args in memo: 18 | return memo[args] 19 | else: 20 | ret = function(*args) 21 | memo[args] = ret 22 | return ret 23 | 24 | return wrapper 25 | -------------------------------------------------------------------------------- /pyvmmonitor_core/mock.py: -------------------------------------------------------------------------------- 1 | # License: LGPL 2 | # 3 | # Copyright: Brainwy Software 4 | from pyvmmonitor_core import compat 5 | 6 | ''' 7 | A simple mock... 8 | ''' 9 | 10 | 11 | class Mock(object): 12 | ''' 13 | with Mock(foo, a=10, b=20): 14 | ... 15 | 16 | or 17 | 18 | mock = Mock(foo, a=10, b=20) 19 | ... 20 | mock.__exit__() 21 | 22 | ''' 23 | 24 | def __init__(self, obj, **kw): 25 | '''Create mock object 26 | obj - Object to be mocked 27 | kw - Mocked attributes 28 | ''' 29 | self.obj = obj 30 | self._restore = [] 31 | for attr, val in compat.iteritems(kw): 32 | self._restore.append((attr, getattr(obj, attr))) 33 | setattr(self.obj, attr, val) 34 | 35 | def __enter__(self): 36 | return self 37 | 38 | def __exit__(self, *args, **kwargs): 39 | for attr, val in self._restore: 40 | setattr(self.obj, attr, val) 41 | self.obj = None 42 | self._restore = [] 43 | -------------------------------------------------------------------------------- /pyvmmonitor_core/nodes_tree.py: -------------------------------------------------------------------------------- 1 | ''' 2 | To use: 3 | 4 | nodes_tree = NodesTree() 5 | node = nodes_tree.add_child(Node(1)) 6 | node2 = node.add_child(Node(2)) 7 | 8 | for child in nodes_tree.children: 9 | ... 10 | 11 | License: LGPL 12 | 13 | Copyright: Brainwy Software 14 | ''' 15 | 16 | import sys 17 | 18 | 19 | class Node(object): 20 | 21 | def __init__(self, data): 22 | self.data = data 23 | self.children = [] 24 | 25 | def add_child(self, node): 26 | assert isinstance(node, Node) 27 | self.children.append(node) 28 | return node 29 | 30 | def __hash__(self, *args, **kwargs): 31 | raise AssertionError('unhashable') 32 | 33 | def __len__(self): 34 | count = len(self.children) 35 | for c in self.children: 36 | count += len(c) 37 | return count 38 | 39 | def print_rep(self, node=None, level=0, stream=None): 40 | if stream is None: 41 | stream = sys.stderr 42 | 43 | if node is None: 44 | node = self 45 | 46 | pre = ' ' * (2 * level) 47 | for child in node.children: 48 | stream.write(u'%s%s: %s\n' % (pre, child.data.__class__.__name__, child.data)) 49 | self.print_rep(child, level + 1, stream) 50 | 51 | def as_str(self): 52 | from io import StringIO 53 | s = StringIO() 54 | self.print_rep(stream=s) 55 | return s.getvalue() 56 | 57 | 58 | class NodesTree(Node): # Special node with no associated data 59 | 60 | def __init__(self): 61 | Node.__init__(self, None) 62 | -------------------------------------------------------------------------------- /pyvmmonitor_core/null.py: -------------------------------------------------------------------------------- 1 | # License: PSF License 2 | # Copyright: Dinu C. Gherman 3 | 4 | 5 | class Null: 6 | """ 7 | Gotten from: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/68205 8 | """ 9 | 10 | def __init__(self, *args, **kwargs): 11 | return None 12 | 13 | def __call__(self, *args, **kwargs): 14 | return self 15 | 16 | def __getattr__(self, mname): 17 | if len(mname) > 4 and mname[:2] == '__' and mname[-2:] == '__': 18 | # Don't pretend to implement special method names. 19 | raise AttributeError(mname) 20 | return self 21 | 22 | def __setattr__(self, name, value): 23 | return self 24 | 25 | def __delattr__(self, name): 26 | return self 27 | 28 | def __repr__(self): 29 | return "" 30 | 31 | def __str__(self): 32 | return "Null" 33 | 34 | def __len__(self): 35 | return 0 36 | 37 | def __getitem__(self): 38 | return self 39 | 40 | def __setitem__(self, *args, **kwargs): 41 | pass 42 | 43 | def write(self, *args, **kwargs): 44 | pass 45 | 46 | def __nonzero__(self): 47 | return 0 48 | 49 | __bool__ = __nonzero__ 50 | 51 | def __iter__(self): 52 | return iter(()) 53 | 54 | def __enter__(self, *args, **kwargs): 55 | pass 56 | 57 | def __exit__(self, *args, **kwargs): 58 | pass 59 | 60 | 61 | NULL = Null() # Constant instance 62 | -------------------------------------------------------------------------------- /pyvmmonitor_core/ordered_set.py: -------------------------------------------------------------------------------- 1 | ''' 2 | A feature-rich ordered set (which allows reordering internal items). 3 | 4 | License: MIT 5 | 6 | Copyright: Brainwy Software 7 | ''' 8 | 9 | try: 10 | from collections.abc import MutableSet 11 | except ImportError: 12 | from collections import MutableSet 13 | from weakref import ref 14 | 15 | 16 | class _Node(object): 17 | __slots__ = ['prev', 'next', 'el', '__weakref__'] 18 | 19 | 20 | class OrderedSet(MutableSet): 21 | ''' 22 | Some notes: 23 | 24 | self._end.next is actually the first element of the internal double linked list 25 | self._end.prev is the last element 26 | 27 | Almost all operations should be fast O(1), except "index, item_at", which are O(n): 28 | ''' 29 | 30 | def __init__(self, initial=()): 31 | end = self._end = _Node() 32 | 33 | root_ref = self._root_ref = ref(end) 34 | end.prev = root_ref 35 | end.next = root_ref 36 | self._dict = {} 37 | 38 | for a in initial: 39 | self.add(a) 40 | 41 | def add(self, el): 42 | if el not in self._dict: 43 | node = _Node() 44 | node.el = el 45 | 46 | root = self._end 47 | curr_ref = root.prev 48 | curr = curr_ref() 49 | node.prev = curr_ref 50 | node.next = self._root_ref 51 | self._dict[el] = node 52 | curr.next = root.prev = ref(node) 53 | 54 | def update(self, *args): 55 | for s in args: 56 | for e in s: 57 | self.add(e) 58 | 59 | def index(self, elem): 60 | # Note: this is a slow operation! 61 | for i, el in enumerate(self): 62 | if el == elem: 63 | return i 64 | return -1 65 | 66 | def __contains__(self, x): 67 | return x in self._dict 68 | 69 | def __iter__(self): 70 | root = self._end 71 | node = root.next() 72 | 73 | while node is not root: 74 | yield node.el 75 | node = node.next() 76 | 77 | def __reversed__(self): 78 | root = self._end 79 | curr = root.prev() 80 | while curr is not root: 81 | yield curr.el 82 | curr = curr.prev() 83 | 84 | def discard(self, el): 85 | entry = self._dict.pop(el, None) 86 | if entry is not None: 87 | # Set a ref with a ref is ok. 88 | entry.prev().next = entry.next 89 | entry.next().prev = entry.prev 90 | 91 | def item_at(self, i): 92 | if i < 0: 93 | it = reversed(self) 94 | i = abs(i) - 1 95 | else: 96 | it = self 97 | 98 | # Note: this is a slow operation (unless it's something as i = 0 or i = -1) 99 | for k, el in enumerate(it): 100 | if i == k: 101 | return el 102 | raise IndexError(i) 103 | 104 | def __len__(self): 105 | return len(self._dict) 106 | 107 | def __repr__(self): 108 | return 'OrderedSet([%s])' % (', '.join(map(repr, iter(self)))) 109 | 110 | def __str__(self): 111 | return '{%s}' % (', '.join(map(repr, iter(self)))) 112 | 113 | def popitem(self, last=True): 114 | ret = self.pop(last) 115 | # Kept for backward compatibility (was returning popitem from odict mapping key=key). 116 | # Should probably be deprecated going forward. 117 | return ret, ret 118 | 119 | def pop(self, last=True): 120 | if last: 121 | node = self._end.prev() 122 | else: 123 | node = self._end.next() 124 | 125 | if node is self._end: 126 | raise KeyError('empty') 127 | ret = node.el 128 | self.discard(ret) 129 | return ret 130 | 131 | def insert_before(self, el_before, el): 132 | ''' 133 | Insert el before el_before 134 | ''' 135 | assert el not in self._dict 136 | assert el_before in self._dict 137 | 138 | new_link = _Node() 139 | new_link.el = el 140 | self._dict[el] = new_link 141 | 142 | add_before_link = self._dict[el_before] 143 | 144 | new_link.prev = add_before_link.prev 145 | new_link.next = ref(add_before_link) 146 | new_link_ref = ref(new_link) 147 | add_before_link.prev = new_link_ref 148 | new_link.prev().next = new_link_ref 149 | 150 | def insert_after(self, el_after, el): 151 | ''' 152 | Insert el after el_after 153 | ''' 154 | assert el not in self._dict 155 | assert el_after in self._dict 156 | 157 | new_link = _Node() 158 | new_link.el = el 159 | self._dict[el] = new_link 160 | 161 | add_after_link = self._dict[el_after] 162 | 163 | new_link.next = add_after_link.next 164 | new_link.prev = ref(add_after_link) 165 | new_link_ref = ref(new_link) 166 | add_after_link.next = new_link_ref 167 | new_link.next().prev = new_link_ref 168 | 169 | def move_to_beginning(self, el): 170 | self.discard(el) 171 | if not self._dict: 172 | self.add(el) 173 | else: 174 | root = self._end 175 | node = root.next() 176 | self.insert_before(node.el, el) 177 | 178 | def move_to_end(self, el): 179 | self.discard(el) 180 | self.add(el) 181 | 182 | def move_to_previous(self, el): 183 | node = self._dict[el] 184 | if len(self._dict) > 1 and self._end.next().el != el: 185 | before_key = node.prev().el 186 | self.discard(el) 187 | self.insert_before(before_key, el) 188 | 189 | def move_to_next(self, el): 190 | node = self._dict[el] 191 | if len(self._dict) > 1 and self._end.prev().el != el: 192 | after_key = node.next().el 193 | self.discard(el) 194 | self.insert_after(after_key, el) 195 | 196 | def get_previous(self, el): 197 | node = self._dict[el] 198 | prev = node.prev() 199 | if prev is self._end: 200 | return None 201 | return prev.el 202 | 203 | def get_next(self, el): 204 | node = self._dict[el] 205 | next_node = node.next() 206 | if next_node is self._end: 207 | return None 208 | return next_node.el 209 | -------------------------------------------------------------------------------- /pyvmmonitor_core/path.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Helpers for os.path which work with bytes and unicode on Python 2/3 considering either '/' or '\\'. 3 | 4 | License: LGPL 5 | 6 | Copyright: Brainwy Software 7 | ''' 8 | 9 | import sys 10 | 11 | PY2 = sys.version_info[0] < 3 12 | PY3 = not PY2 13 | 14 | if PY3: 15 | def all_basename(name): 16 | # Instead of using name = os.path.basename(name) 17 | # use the approach below which is the same on all platforms... 18 | if isinstance(name, str): 19 | i = name.rfind('/') + 1 20 | name = name[i:] 21 | i = name.rfind('\\') + 1 22 | name = name[i:] 23 | return name 24 | else: 25 | # unicode 26 | i = name.rfind(b'/') + 1 27 | name = name[i:] 28 | i = name.rfind(b'\\') + 1 29 | name = name[i:] 30 | return name 31 | 32 | else: 33 | def all_basename(name): 34 | # Instead of using name = os.path.basename(name) 35 | # use the approach below which is the same on all platforms... 36 | 37 | if isinstance(name, unicode): 38 | i = name.rfind(u'/') + 1 39 | name = name[i:] 40 | i = name.rfind(u'\\') + 1 41 | name = name[i:] 42 | return name 43 | else: 44 | i = name.rfind(b'/') + 1 45 | name = name[i:] 46 | i = name.rfind(b'\\') + 1 47 | name = name[i:] 48 | return name 49 | -------------------------------------------------------------------------------- /pyvmmonitor_core/plugins.py: -------------------------------------------------------------------------------- 1 | # License: LGPL 2 | # 3 | # Copyright: Brainwy Software 4 | 5 | ''' 6 | Defines a PluginManager (which doesn't really have plugins, only a registry of extension points 7 | and implementations for such extension points). 8 | 9 | To use, create the extension points you want (any class starting with 'EP') and register 10 | implementations for those. 11 | 12 | I.e.: 13 | 14 | pm = PluginManager() 15 | pm.register(EPFoo, '_pyvmmonitor_core_tests.test_plugins.FooImpl', keep_instance=True) 16 | pm.register(EPBar, '_pyvmmonitor_core_tests.test_plugins.BarImpl', keep_instance=False) 17 | 18 | Then, later, to use it it's possible to ask for instances through the PluginManager API: 19 | 20 | foo_instances = pm.get_implementations(EPFoo) # Each time this is called, new 21 | # foo_instances will be created 22 | bar_instance = pm.get_instance(EPBar) # Each time this is called, the same bar_instance is returned. 23 | 24 | Alternatively, it's possible to use a decorator to use a dependency injection pattern -- i.e.: 25 | don't call me, I'll call you ;) 26 | 27 | @inject(foo_instance=EPFoo, bar_instances=[EPBar]) 28 | def m1(foo_instance, bar_instances, pm): 29 | for bar in bar_instances: 30 | ... 31 | 32 | foo_instance.foo 33 | 34 | ''' 35 | 36 | import functools 37 | import sys 38 | 39 | from pyvmmonitor_core import compat 40 | from pyvmmonitor_core.callback import Callback 41 | from pyvmmonitor_core.lazy_loading import load_token 42 | from pyvmmonitor_core.weak_utils import get_weakref 43 | 44 | if sys.version_info[0] >= 3: 45 | string_types = (str,) 46 | else: 47 | string_types = (unicode, str) 48 | 49 | load_class = load_token # Alias for backward compatibility 50 | 51 | 52 | class NotInstanceError(RuntimeError): 53 | pass 54 | 55 | 56 | class NotRegisteredError(RuntimeError): 57 | pass 58 | 59 | 60 | class InstanceAlreadyRegisteredError(RuntimeError): 61 | pass 62 | 63 | 64 | class IPluginsExit(object): 65 | 66 | def plugins_exit(self): 67 | pass 68 | 69 | 70 | class PluginManager(object): 71 | 72 | ''' 73 | This is a manager of plugins (which we refer to extension points and implementations). 74 | 75 | Mostly, we have a number of EPs (Extension Points) and implementations may be registered 76 | for those extension points. 77 | 78 | The PluginManager is able to provide implementations (through #get_implementations) which are 79 | not kept on being tracked and a special concept which keeps an instance alive for an extension 80 | (through #get_instance). 81 | 82 | Every instance registered will have: 83 | 84 | - a 'pm' attribute set to this PluginManager (which is a weak reference to the plugin manager). 85 | 86 | - a 'plugins_exit' method called if it defines it when the PluginManager is about to exit (if 87 | it defines one). 88 | ''' 89 | 90 | def __init__(self): 91 | self._ep_to_impls = {} 92 | self._ep_to_instance_impls = {} 93 | self._ep_to_context_to_instance = {} 94 | self._name_to_ep = {} 95 | self.exited = False 96 | self.on_about_to_exit = Callback() 97 | 98 | def get_implementations(self, ep): 99 | assert not self.exited 100 | if ep.__class__ in string_types: 101 | ep = self._name_to_ep[ep] 102 | 103 | impls = self._ep_to_impls.get(ep, []) 104 | ret = [] 105 | for impl, kwargs in impls: 106 | class_ = load_class(impl) 107 | instance = class_(**kwargs) 108 | instance.pm = get_weakref(self) 109 | ret.append(instance) 110 | 111 | return ret 112 | 113 | def register(self, ep, impl, kwargs={}, context=None, keep_instance=False): 114 | ''' 115 | 116 | :param ep: 117 | :param str impl: 118 | This is the full path to the class implementation. 119 | 120 | :param kwargs: 121 | :param context: 122 | If keep_instance is True, it's possible to register it for a given 123 | context. 124 | 125 | :param keep_instance: 126 | If True, it'll be only available through pm.get_instance and the 127 | instance will be kept for further calls. 128 | If False, it'll only be available through get_implementations. 129 | ''' 130 | assert not self.exited 131 | if ep.__class__ in string_types: 132 | raise ValueError('Expected the actual EP class to be passed.') 133 | self._name_to_ep[ep.__name__] = ep 134 | 135 | if keep_instance: 136 | register_at = self._ep_to_instance_impls 137 | impls = register_at.get((ep, context)) 138 | if impls is None: 139 | impls = register_at[(ep, context)] = [] 140 | else: 141 | raise InstanceAlreadyRegisteredError( 142 | 'Unable to override when instance is kept and an implementation ' 143 | 'is already registered.') 144 | else: 145 | register_at = self._ep_to_impls 146 | impls = register_at.get(ep) 147 | if impls is None: 148 | impls = register_at[ep] = [] 149 | 150 | impls.append((impl, kwargs)) 151 | 152 | def set_instance(self, ep, instance, context=None): 153 | if ep.__class__ in string_types: 154 | raise ValueError('Expected the actual EP class to be passed.') 155 | self._name_to_ep[ep.__name__] = ep 156 | 157 | instance.pm = get_weakref(self) 158 | instances = self._ep_to_context_to_instance.get(ep) 159 | if instances is None: 160 | instances = self._ep_to_context_to_instance[ep] = {} 161 | instances[context] = instance 162 | 163 | def iter_existing_instances(self, ep): 164 | if ep.__class__ in string_types: 165 | ep = self._name_to_ep[ep] 166 | 167 | return compat.itervalues(self._ep_to_context_to_instance[ep]) 168 | 169 | def has_instance(self, ep, context=None): 170 | if ep.__class__ in string_types: 171 | ep = self._name_to_ep.get(ep) 172 | if ep is None: 173 | return False 174 | 175 | try: 176 | self.get_instance(ep, context) 177 | return True 178 | except NotRegisteredError: 179 | return False 180 | 181 | def get_instance(self, ep, context=None): 182 | ''' 183 | Creates an instance in this plugin manager: Meaning that whenever a new EP is asked in 184 | the same context it'll receive the same instance created previously (and it'll be 185 | kept alive in the plugin manager). 186 | 187 | Also, the instance will have its 'pm' attribute set to be this plugin manager. 188 | ''' 189 | if self.exited: 190 | raise AssertionError('PluginManager already exited') 191 | 192 | if ep.__class__ in string_types: 193 | ep = self._name_to_ep[ep] 194 | try: 195 | return self._ep_to_context_to_instance[ep][context] 196 | except KeyError: 197 | try: 198 | impls = self._ep_to_instance_impls[(ep, context)] 199 | except KeyError: 200 | found = False 201 | if context is not None: 202 | found = True 203 | try: 204 | impls = self._ep_to_instance_impls[(ep, None)] 205 | except KeyError: 206 | found = False 207 | if not found: 208 | if ep in self._ep_to_impls: 209 | # Registered but not a kept instance. 210 | raise NotInstanceError() 211 | else: 212 | # Not registered at all. 213 | raise NotRegisteredError() 214 | assert len(impls) == 1 215 | impl, kwargs = impls[0] 216 | class_ = load_class(impl) 217 | 218 | instances = self._ep_to_context_to_instance.get(ep) 219 | if instances is None: 220 | instances = self._ep_to_context_to_instance[ep] = {} 221 | 222 | ret = instances[context] = class_(**kwargs) 223 | ret.pm = get_weakref(self) 224 | return ret 225 | 226 | __getitem__ = get_instance 227 | 228 | def exit(self): 229 | try: 230 | self.on_about_to_exit() 231 | for ctx in compat.values(self._ep_to_context_to_instance): 232 | for instance in compat.values(ctx): 233 | if hasattr(instance, 'plugins_exit'): 234 | try: 235 | instance.plugins_exit() 236 | except Exception: 237 | import traceback 238 | traceback.print_exc() 239 | finally: 240 | self.exited = True 241 | self._ep_to_context_to_instance.clear() 242 | self._ep_to_impls.clear() 243 | 244 | 245 | def inject(**inject_kwargs): 246 | 247 | def decorator(func): 248 | 249 | @functools.wraps(func) 250 | def inject_dec(*args, **kwargs): 251 | pm = kwargs.get('pm') 252 | if pm is None: 253 | raise AssertionError( 254 | 'pm argument with PluginManager not passed (required for @inject).') 255 | 256 | for key, val in compat.iteritems(inject_kwargs): 257 | if key not in kwargs: 258 | if val.__class__ is list: 259 | kwargs[key] = pm.get_implementations(val[0]) 260 | else: 261 | kwargs[key] = pm.get_instance(val) 262 | return func(*args, **kwargs) 263 | 264 | return inject_dec 265 | 266 | return decorator 267 | -------------------------------------------------------------------------------- /pyvmmonitor_core/props.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from contextlib import contextmanager 3 | 4 | from pyvmmonitor_core import compat 5 | from pyvmmonitor_core.callback import Callback 6 | 7 | try: 8 | # Optional dependency: if greenlets are available check that properties aren't changed 9 | # when out of the main coroutine (the idea being that properties belong to main objects 10 | # in coroutines -- if this is a problem this can be relaxed later on). 11 | 12 | # It can also be locally disabled with: 13 | # from pyvmmonitor_core import props 14 | # with props.disable_in_coroutine_assert(): 15 | # ... 16 | import greenlet 17 | 18 | _disable_not_in_coroutine_assert = 0 19 | 20 | @contextmanager 21 | def disable_in_coroutine_assert(): 22 | global _disable_not_in_coroutine_assert 23 | _disable_not_in_coroutine_assert += 1 24 | try: 25 | yield 26 | finally: 27 | _disable_not_in_coroutine_assert -= 1 28 | 29 | def _assert_not_in_coroutine(): 30 | if _disable_not_in_coroutine_assert: 31 | return 32 | assert greenlet.getcurrent().parent is None, 'Did not expect to be in a coroutine' 33 | 34 | except ImportError: 35 | 36 | # Add no-op version. 37 | def _assert_not_in_coroutine(): 38 | pass 39 | 40 | @contextmanager 41 | def disable_in_coroutine_assert(): 42 | yield 43 | 44 | 45 | def _make_property(key, default): 46 | 47 | def get_val(self): 48 | return self._props.get(key, default) 49 | 50 | def set_val(self, val): 51 | # Note: coroutines are meant for pure functions, so, they shouldn't change anything. 52 | _assert_not_in_coroutine() 53 | prev = self._props.get(key, default) 54 | self._props[key] = val 55 | if prev != val: 56 | self._on_modified_callback(self, {key: (val, prev)}) 57 | 58 | return property(get_val, set_val) 59 | 60 | 61 | class PropsCustomProperty(object): 62 | 63 | def __init__(self, default): 64 | self.default = default 65 | 66 | def convert(self, obj, val): 67 | ''' 68 | Subclasses may override to convert the value which is being set. 69 | ''' 70 | return val 71 | 72 | def _make_property(outer_self, key): # @NoSelf 73 | 74 | default = outer_self.default 75 | 76 | def get_val(self): 77 | return self._props.get(key, default) 78 | 79 | def set_val(self, val): 80 | # Note: coroutines are meant for pure functions, so, they shouldn't 81 | # change anything. 82 | _assert_not_in_coroutine() 83 | val = outer_self.convert(self, val) 84 | 85 | # Note: we're choosing to copy/paste the global "_make_property" to avoid 86 | # paying a function call. 87 | prev = self._props.get(key, default) 88 | self._props[key] = val 89 | if prev != val: 90 | self._on_modified_callback(self, {key: (val, prev)}) 91 | 92 | return property(get_val, set_val) 93 | 94 | 95 | class _ModifiedCallbackKeeper(object): 96 | 97 | def __init__(self): 98 | self._new_val_old_vals = {} 99 | 100 | def __call__(self, obj, attrs): 101 | for key, (new_val, old_val) in compat.iteritems(attrs): 102 | prev_notification = self._new_val_old_vals.get(key) 103 | if prev_notification is not None: 104 | old_val = prev_notification[1] 105 | 106 | if new_val == old_val: 107 | self._new_val_old_vals.pop(key) 108 | else: 109 | self._new_val_old_vals[key] = (new_val, old_val) 110 | 111 | def notify(self, obj, original_callback): 112 | original_callback(obj, self._new_val_old_vals) 113 | 114 | 115 | class PropsObject(object): 116 | ''' 117 | To use: 118 | 119 | class Point(PropsObject): 120 | 121 | PropsObject.declare_props(x=0, y=0) 122 | 123 | PropsObject.add_slot('_internal_attr') 124 | 125 | point = Point() 126 | 127 | def on_modified(obj, attrs): 128 | if 'x' in attrs: 129 | new_val, old_val = attrs['x'] 130 | print('new x', new_val, 'old_x', old_val) 131 | 132 | point.register_modified(on_modified) 133 | ''' 134 | 135 | __slots__ = ['_props', '_on_modified_callback', '__weakref__', '_original_on_modified_callback'] 136 | 137 | @classmethod 138 | def declare_props(cls, **kwargs): 139 | frame = sys._getframe().f_back 140 | namespace = frame.f_locals 141 | 142 | props_namespace = namespace.get('__props__') 143 | if props_namespace is None: 144 | props_namespace = namespace['__props__'] = [] 145 | 146 | for key, val in compat.iteritems(kwargs): 147 | if isinstance(val, PropsCustomProperty): 148 | namespace[key] = val._make_property(key) 149 | else: 150 | namespace[key] = _make_property(key, val) 151 | props_namespace.append(key) 152 | 153 | if '__slots__' not in namespace: 154 | namespace['__slots__'] = [] 155 | 156 | @classmethod 157 | def add_slot(cls, slot): 158 | frame = sys._getframe().f_back 159 | namespace = frame.f_locals 160 | namespace['__slots__'].append(slot) 161 | 162 | def __init__(self, **kwargs): 163 | self._props = {} 164 | self._original_on_modified_callback = self._on_modified_callback = Callback() 165 | for key, val in compat.iteritems(kwargs): 166 | setattr(self, key, val) 167 | 168 | def register_modified(self, on_modified): 169 | self._original_on_modified_callback.register(on_modified) 170 | 171 | def unregister_modified(self, on_modified): 172 | self._original_on_modified_callback.unregister(on_modified) 173 | 174 | def create_memento(self): 175 | ''' 176 | Note that the memento only includes the properties which were changed. To get all properties 177 | use get_props_as_dict. 178 | ''' 179 | return dict((attr, getattr(self, attr)) for attr in self._props) 180 | 181 | def set_memento(self, memento): 182 | for key, val in compat.iteritems(memento): 183 | setattr(self, key, val) 184 | 185 | @classmethod 186 | def get_all_props_names(cls): 187 | all_props = getattr(cls, '__all_props__', None) 188 | if all_props is None: 189 | import inspect 190 | all_prop_names = set() 191 | all_prop_names.update(cls.__props__) 192 | for base_class in inspect.getmro(cls): 193 | # Can't recursively call get_all_props_names() as depending on the hierarchy it 194 | # wouldn't work. 195 | all_prop_names.update(getattr(base_class, '__props__', [])) 196 | 197 | all_props = frozenset(all_prop_names) 198 | cls.__all_props__ = all_props 199 | cls.__all_props_cache_info__ = {'hit': 0} 200 | else: 201 | cls.__all_props_cache_info__['hit'] += 1 202 | 203 | return all_props 204 | 205 | def get_props_as_dict(self): 206 | ret = {} 207 | for prop in self.get_all_props_names(): 208 | ret[prop] = getattr(self, prop) 209 | return ret 210 | 211 | @classmethod 212 | def delegate_to_props(cls, *props): 213 | frame = sys._getframe().f_back 214 | namespace = frame.f_locals 215 | 216 | for prop in props: 217 | namespace[prop] = _make_delegator_property(prop) 218 | 219 | 220 | @contextmanager 221 | def delayed_notifications(props_obj): 222 | original = props_obj._on_modified_callback 223 | modified_callback_keeper = _ModifiedCallbackKeeper() 224 | props_obj._on_modified_callback = modified_callback_keeper 225 | try: 226 | yield 227 | finally: 228 | props_obj._on_modified_callback = original 229 | modified_callback_keeper.notify(props_obj, original) 230 | 231 | 232 | def _make_delegator_property(key): 233 | 234 | def get_val(self): 235 | return getattr(self._props, key) 236 | 237 | def set_val(self, val): 238 | return setattr(self._props, key, val) 239 | 240 | return property(get_val, set_val) 241 | -------------------------------------------------------------------------------- /pyvmmonitor_core/system_mutex.py: -------------------------------------------------------------------------------- 1 | ''' 2 | To use, create a SystemMutex, check if it was acquired (get_mutex_aquired()) and if acquired the 3 | mutex is kept until the instance is collected or release_mutex is called. 4 | 5 | I.e.: 6 | 7 | mutex = SystemMutex('my_unique_name') 8 | if mutex.get_mutex_aquired(): 9 | print('acquired') 10 | else: 11 | print('not acquired') 12 | 13 | License: LGPL 14 | 15 | Copyright: Brainwy Software 16 | ''' 17 | 18 | import re 19 | import sys 20 | import tempfile 21 | import traceback 22 | import weakref 23 | 24 | from pyvmmonitor_core.null import NULL 25 | 26 | 27 | def check_valid_mutex_name(mutex_name): 28 | # To be windows/linux compatible we can't use non-valid filesystem names 29 | # (as on linux it's a file-based lock). 30 | 31 | regexp = re.compile(r'[\*\?"<>|/\\:]') 32 | result = regexp.findall(mutex_name) 33 | if result is not None and len(result) > 0: 34 | raise AssertionError('Mutex name is invalid: %s' % (mutex_name,)) 35 | 36 | 37 | if sys.platform == 'win32': 38 | 39 | import os 40 | 41 | class SystemMutex(object): 42 | 43 | def __init__(self, mutex_name): 44 | check_valid_mutex_name(mutex_name) 45 | filename = os.path.join(tempfile.gettempdir(), mutex_name) 46 | try: 47 | os.unlink(filename) 48 | except Exception: 49 | pass 50 | try: 51 | handle = os.open(filename, os.O_CREAT | os.O_EXCL | os.O_RDWR) 52 | try: 53 | try: 54 | pid = str(os.getpid()) 55 | except Exception: 56 | pid = 'unable to get pid' 57 | os.write(handle, pid) 58 | except Exception: 59 | pass # Ignore this as it's pretty much optional 60 | except Exception: 61 | self._release_mutex = NULL 62 | self._acquired = False 63 | else: 64 | def release_mutex(*args, **kwargs): 65 | # Note: can't use self here! 66 | if not getattr(release_mutex, 'called', False): 67 | release_mutex.called = True 68 | try: 69 | os.close(handle) 70 | except Exception: 71 | traceback.print_exc() 72 | try: 73 | # Removing is optional as we'll try to remove on startup anyways (but 74 | # let's do it to keep the filesystem cleaner). 75 | os.unlink(filename) 76 | except Exception: 77 | pass 78 | 79 | # Don't use __del__: this approach doesn't have as many pitfalls. 80 | self._ref = weakref.ref(self, release_mutex) 81 | 82 | self._release_mutex = release_mutex 83 | self._acquired = True 84 | 85 | def get_mutex_aquired(self): 86 | return self._acquired 87 | 88 | def release_mutex(self): 89 | self._release_mutex() 90 | 91 | 92 | # Below we have a better implementation, but it relies on win32api which we can't be sure 93 | # the client will have available in the Python version installed at the client, so, we're 94 | # using a file-based implementation which should work in any implementation. 95 | # 96 | # from win32api import CloseHandle, GetLastError 97 | # from win32event import CreateMutex 98 | # from winerror import ERROR_ALREADY_EXISTS 99 | # 100 | # class SystemMutex(object): 101 | # 102 | # def __init__(self, mutex_name): 103 | # check_valid_mutex_name(mutex_name) 104 | # mutex = self.mutex = CreateMutex(None, False, mutex_name) 105 | # self._acquired = GetLastError() != ERROR_ALREADY_EXISTS 106 | # 107 | # if self._acquired: 108 | # 109 | # def release_mutex(*args, **kwargs): 110 | # Note: can't use self here! 111 | # if not getattr(release_mutex, 'called', False): 112 | # release_mutex.called = True 113 | # try: 114 | # CloseHandle(mutex) 115 | # except: 116 | # traceback.print_exc() 117 | # 118 | # Don't use __del__: this approach doesn't have as many pitfalls. 119 | # self._ref = weakref.ref(self, release_mutex) 120 | # self._release_mutex = release_mutex 121 | # else: 122 | # self._release_mutex = NULL 123 | # CloseHandle(mutex) 124 | # 125 | # def get_mutex_aquired(self): 126 | # return self._acquired 127 | # 128 | # def release_mutex(self): 129 | # self._release_mutex() 130 | 131 | else: # Linux 132 | import os 133 | import fcntl 134 | 135 | class SystemMutex(object): 136 | 137 | def __init__(self, mutex_name): 138 | check_valid_mutex_name(mutex_name) 139 | filename = os.path.join(tempfile.gettempdir(), mutex_name) 140 | try: 141 | handle = open(filename, 'w') 142 | fcntl.flock(handle, fcntl.LOCK_EX | fcntl.LOCK_NB) 143 | except Exception: 144 | self._release_mutex = NULL 145 | self._acquired = False 146 | try: 147 | handle.close() 148 | except Exception: 149 | pass 150 | else: 151 | def release_mutex(*args, **kwargs): 152 | # Note: can't use self here! 153 | if not getattr(release_mutex, 'called', False): 154 | release_mutex.called = True 155 | try: 156 | fcntl.flock(handle, fcntl.LOCK_UN) 157 | except Exception: 158 | traceback.print_exc() 159 | try: 160 | handle.close() 161 | except Exception: 162 | traceback.print_exc() 163 | try: 164 | # Removing is pretty much optional (but let's do it to keep the 165 | # filesystem cleaner). 166 | os.unlink(filename) 167 | except Exception: 168 | pass 169 | 170 | # Don't use __del__: this approach doesn't have as many pitfalls. 171 | self._ref = weakref.ref(self, release_mutex) 172 | 173 | self._release_mutex = release_mutex 174 | self._acquired = True 175 | 176 | def get_mutex_aquired(self): 177 | return self._acquired 178 | 179 | def release_mutex(self): 180 | self._release_mutex() 181 | -------------------------------------------------------------------------------- /pyvmmonitor_core/thread_utils.py: -------------------------------------------------------------------------------- 1 | ''' 2 | License: LGPL 3 | 4 | Copyright: Brainwy Software 5 | ''' 6 | 7 | 8 | def is_in_main_thread(): 9 | import threading 10 | return threading.current_thread().name == 'MainThread' 11 | -------------------------------------------------------------------------------- /pyvmmonitor_core/time_utils.py: -------------------------------------------------------------------------------- 1 | def time_method(func): 2 | ''' 3 | Helper decorator to time a function. 4 | 5 | To use: 6 | 7 | @time_method 8 | def func(): 9 | ... 10 | 11 | ''' 12 | import time 13 | 14 | try: 15 | func_name = func.func_name 16 | except Exception: 17 | func_name = func.__name__ 18 | 19 | def wrap(*args, **kwargs): 20 | time1 = time.time() 21 | ret = func(*args, **kwargs) 22 | print('%s function took %0.3f s' % (func_name, (time.time() - time1))) 23 | return ret 24 | return wrap 25 | -------------------------------------------------------------------------------- /pyvmmonitor_core/weak_utils.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Helpers for dealing with weak-references (with special treatment of bound methods). 3 | 4 | 5 | License: LGPL 6 | 7 | Copyright: Brainwy Software 8 | ''' 9 | 10 | import inspect 11 | import weakref 12 | 13 | from pyvmmonitor_core.ordered_set import OrderedSet 14 | from pyvmmonitor_core.weakmethod import WeakMethod 15 | 16 | 17 | def _NONE_LAMDA(): return None # @IgnorePep8 18 | 19 | 20 | def get_weakref(obj): 21 | if obj is None: 22 | return _NONE_LAMDA 23 | 24 | if isinstance(obj, weakref.ref): 25 | return obj 26 | 27 | if inspect.ismethod(obj): 28 | return WeakMethod(obj) 29 | 30 | return weakref.ref(obj) 31 | 32 | 33 | def assert_weakref_none(ref): 34 | r = ref() 35 | if r is None: 36 | return 37 | r = None 38 | 39 | import sys 40 | 41 | if hasattr(sys, 'exc_clear'): 42 | sys.exc_clear() 43 | r = ref() 44 | if r is None: 45 | return 46 | 47 | r = ref() 48 | assert r is not None 49 | 50 | 51 | class WeakList(object): 52 | 53 | def __init__(self, lst=None): 54 | self._items = [] 55 | if lst is not None: 56 | for obj in lst: 57 | self.append(obj) 58 | 59 | def append(self, obj): 60 | # Add backwards to ease on iteration removal (on our __iter__ we'll go the other way 61 | # around so that the client sees it in the proper order). 62 | self._items.insert(0, weakref.ref(obj)) 63 | 64 | def remove(self, obj): 65 | i = len(self._items) - 1 66 | while i >= 0: 67 | ref = self._items[i] 68 | d = ref() 69 | if d is None or d is obj: 70 | del self._items[i] 71 | i -= 1 72 | 73 | def __iter__(self): 74 | i = len(self._items) - 1 75 | while i >= 0: 76 | ref = self._items[i] 77 | d = ref() 78 | if d is None: 79 | del self._items[i] 80 | else: 81 | yield d 82 | i -= 1 83 | 84 | def iter_refs(self): 85 | return iter(reversed(self._items)) 86 | 87 | def clear(self): 88 | del self._items[:] 89 | 90 | def __len__(self): 91 | i = 0 92 | for _k in self: 93 | i += 1 94 | return i 95 | 96 | 97 | class WeakSet(object): 98 | 99 | def __init__(self): 100 | self._items = set() 101 | 102 | def add(self, item): 103 | self._items.add(get_weakref(item)) 104 | 105 | def remove(self, item): 106 | self._items.remove(get_weakref(item)) 107 | 108 | def discard(self, item): 109 | try: 110 | self.remove(item) 111 | except KeyError: 112 | pass 113 | 114 | def clear(self): 115 | self._items.clear() 116 | 117 | def __iter__(self): 118 | for ref in self._items.copy(): 119 | d = ref() 120 | if d is None: 121 | self._items.remove(ref) 122 | else: 123 | yield d 124 | 125 | def __len__(self): 126 | i = 0 127 | for _k in self: 128 | i += 1 129 | return i 130 | 131 | 132 | class WeakOrderedSet(object): 133 | 134 | def __init__(self): 135 | self._items = OrderedSet() 136 | 137 | def add(self, item): 138 | self._items.add(get_weakref(item)) 139 | 140 | def remove(self, item): 141 | self._items.remove(get_weakref(item)) 142 | 143 | def discard(self, item): 144 | self._items.discard(get_weakref(item)) 145 | 146 | def clear(self): 147 | self._items.clear() 148 | 149 | def __iter__(self): 150 | for ref in list(self._items): 151 | d = ref() 152 | if d is None: 153 | self._items.discard(ref) 154 | else: 155 | yield d 156 | 157 | def __len__(self): 158 | i = 0 159 | for _k in self: 160 | i += 1 161 | return i 162 | 163 | 164 | class NamedWeakMethod(object): 165 | 166 | def __init__(self, obj, method_name): 167 | self.obj = get_weakref(obj) 168 | self.method_name = method_name 169 | 170 | def __call__(self): 171 | obj = self.obj() 172 | if obj is None: 173 | return 174 | getattr(obj, self.method_name)() 175 | 176 | def __hash__(self, *args, **kwargs): 177 | return hash(self.method_name) 178 | 179 | def __eq__(self, o): 180 | if isinstance(o, NamedWeakMethod): 181 | return self.method_name == o.method_name and self.obj() == o.obj() 182 | 183 | return False 184 | 185 | def __ne__(self, o): 186 | return not self == o 187 | -------------------------------------------------------------------------------- /pyvmmonitor_core/weakmethod.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Copyright: ESSS - Engineering Simulation and Scientific Software Ltda 3 | License: LGPL 4 | 5 | From: https://github.com/ESSS/ben10/blob/master/source/python/ben10/foundation/weak_ref.py 6 | 7 | Weak reference to bound-methods. This allows the client to hold a bound method 8 | while allowing GC to work. 9 | 10 | Based on recipe from Python Cookbook, page 191. Differs by only working on 11 | boundmethods and returning a true boundmethod in the __call__() function. 12 | ''' 13 | import weakref 14 | 15 | from pyvmmonitor_core import compat 16 | 17 | try: 18 | import new 19 | except ImportError: 20 | import types as new 21 | 22 | try: 23 | ReferenceError = weakref.ReferenceError 24 | except AttributeError: 25 | ReferenceError = ReferenceError 26 | 27 | 28 | class WeakMethod(object): 29 | ''' 30 | Keeps a reference to an object but doesn't prevent that object from being garbage collected 31 | (unless it's a function) 32 | ''' 33 | 34 | __slots__ = [ 35 | '_obj', 36 | '_func', 37 | '_class', 38 | '_hash', 39 | '__weakref__' # We need this to be able to add weak references. 40 | ] 41 | 42 | if compat.PY2: 43 | def __init__(self, method): 44 | try: 45 | if method.im_self is not None: 46 | # bound method 47 | self._obj = weakref.ref(method.im_self) 48 | else: 49 | # unbound method 50 | self._obj = None 51 | self._func = method.im_func 52 | self._class = method.im_class 53 | except AttributeError: 54 | # For functions leave strong references. 55 | self._obj = None 56 | self._func = method 57 | self._class = None 58 | else: # Py3 onwards 59 | def __init__(self, method): 60 | try: 61 | if method.__self__ is not None: 62 | # bound method 63 | self._obj = weakref.ref(method.__self__) 64 | else: 65 | # unbound method 66 | self._obj = None 67 | self._func = method.__func__ 68 | self._class = method.__class__ 69 | except AttributeError: 70 | # For functions leave strong references. 71 | self._obj = None 72 | self._func = method 73 | self._class = None 74 | 75 | def __call__(self): 76 | ''' 77 | Return a new bound-method like the original, or the original function if refers just to 78 | a function or unbound method. 79 | 80 | @return: 81 | None if the original object doesn't exist anymore. 82 | ''' 83 | if self.is_dead(): 84 | return None 85 | if self._obj is not None: 86 | if compat.PY2: 87 | # we have an instance: return a bound method 88 | return new.instancemethod(self._func, self._obj(), self._class) 89 | else: 90 | return new.MethodType(self._func, self._obj()) 91 | else: 92 | # we don't have an instance: return just the function 93 | return self._func 94 | 95 | def is_dead(self): 96 | ''' 97 | Returns True if the referenced callable was a bound method and 98 | the instance no longer exists. Otherwise, return False. 99 | ''' 100 | return self._obj is not None and self._obj() is None 101 | 102 | def __eq__(self, other): 103 | try: 104 | return isinstance(self, type(other)) and self() == other() 105 | except Exception: 106 | return False 107 | 108 | def __ne__(self, other): 109 | return not self == other 110 | 111 | def __hash__(self): 112 | if not hasattr(self, '_hash'): 113 | # The hash should be immutable (must be calculated once and never changed -- otherwise 114 | # we won't be able to get it when the object dies) 115 | self._hash = hash(WeakMethod.__call__(self)) 116 | 117 | return self._hash 118 | 119 | def __repr__(self): 120 | func_name = getattr(self._func, '__name__', str(self._func)) 121 | if self._obj is not None: 122 | obj = self._obj() 123 | if obj is None: 124 | obj_str = '' 125 | else: 126 | obj_str = '%X' % id(obj) 127 | msg = '' 128 | return msg % (self._class.__name__, func_name, obj_str) 129 | else: 130 | return '' % func_name 131 | 132 | 133 | class WeakMethodProxy(WeakMethod): 134 | ''' 135 | Like ref, but calling it will cause the referent method to be called with the same 136 | arguments. If the referent's object no longer lives, ReferenceError is raised. 137 | ''' 138 | 139 | def __init__(self, method, throw_error_if_called_when_dead=False): 140 | WeakMethod.__init__(self, method) 141 | self._throw_error_if_called_when_dead = throw_error_if_called_when_dead 142 | 143 | def __call__(self, *args, **kwargs): 144 | func = WeakMethod.__call__(self) 145 | if func is None: 146 | if self._throw_error_if_called_when_dead: 147 | raise ReferenceError('Object is dead. Was of class: %s' % (self._class,)) 148 | else: 149 | return func(*args, **kwargs) 150 | 151 | def __hash__(self): 152 | # If __eq__ is redefined, __hash__ also needs to be redefined. 153 | return WeakMethod.__hash__(self) 154 | 155 | def __eq__(self, other): 156 | try: 157 | func1 = WeakMethod.__call__(self) 158 | func2 = WeakMethod.__call__(other) 159 | return isinstance(self, type(other)) and func1 == func2 160 | except Exception: 161 | return False 162 | --------------------------------------------------------------------------------