├── .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 |
--------------------------------------------------------------------------------