Skip to content

Commit

Permalink
Avoid cyclic imports
Browse files Browse the repository at this point in the history
  • Loading branch information
marmarek committed Mar 1, 2017
1 parent 8f92250 commit c298edd
Show file tree
Hide file tree
Showing 5 changed files with 163 additions and 136 deletions.
1 change: 0 additions & 1 deletion ci/pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ ignore=tests
# abstract-class-little-used: see http://www.logilab.org/ticket/111138
disable=
bad-continuation,
cyclic-import,
fixme,
locally-disabled,
missing-docstring
Expand Down
134 changes: 3 additions & 131 deletions qubesmgmt/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,140 +19,12 @@
# with this program; if not, see <http://www.gnu.org/licenses/>.


import ast
import os

import qubesmgmt.exc

DEFAULT = object()


class PropertyHolder(object):
'''A base class for object having properties retrievable using mgmt API.
Warning: each (non-private) local attribute needs to be defined at class
level, even if initialized in __init__; otherwise will be treated as
property retrievable using mgmt call.
'''
#: a place for appropriate Qubes() object (QubesLocal or QubesRemote),
# use None for self
app = None

def __init__(self, app, method_prefix, method_dest):
#: appropriate Qubes() object (QubesLocal or QubesRemote), use None
# for self
self.app = app
self._method_prefix = method_prefix
self._method_dest = method_dest
self._properties = None
self._properties_help = None

def qubesd_call(self, dest, method, arg=None, payload=None):
'''
Call into qubesd using appropriate mechanism. This method should be
defined by a subclass.
:param dest: Destination VM name
:param method: Full API method name ('mgmt...')
:param arg: Method argument (if any)
:param payload: Payload send to the method
:return: Data returned by qubesd (string)
'''
# have the actual implementation at Qubes() instance
if self.app:
return self.app.qubesd_call(dest, method, arg, payload)
raise NotImplementedError

@staticmethod
def _parse_qubesd_response(response_data):
if response_data[0:2] == b'\x30\x00':
return response_data[2:]
elif response_data[0:2] == b'\x32\x00':
(_, exc_type, _traceback, format_string, args) = \
response_data.split(b'\x00', 4)
# drop last field because of terminating '\x00'
args = [arg.decode() for arg in args.split(b'\x00')[:-1]]
format_string = format_string.decode('utf-8')
exc_type = exc_type.decode('ascii')
exc_class = getattr(qubesmgmt.exc, exc_type, 'QubesException')
# TODO: handle traceback if given
raise exc_class(format_string, *args)
else:
raise qubesmgmt.exc.QubesException('Invalid response format')


def property_list(self):
if self._properties is None:
properties_str = self.qubesd_call(
self._method_dest,
self._method_prefix + 'List',
None,
None)
self._properties = properties_str.decode('ascii').splitlines()
# TODO: make it somehow immutable
return self._properties

def property_is_default(self, item):
if item.startswith('_'):
raise AttributeError(item)
property_str = self.qubesd_call(
self._method_dest,
self._method_prefix + 'Get',
item,
None)
(default, _value) = property_str.split(b' ', 1)
assert default.startswith(b'default=')
is_default_str = default.split(b'=')[1]
is_default = ast.literal_eval(is_default_str.decode('ascii'))
assert isinstance(is_default, bool)
return is_default

def __getattr__(self, item):
if item.startswith('_'):
raise AttributeError(item)
property_str = self.qubesd_call(
self._method_dest,
self._method_prefix + 'Get',
item,
None)
(_default, value) = property_str.split(b' ', 1)
value = value.decode()
if value[0] == '\'':
return ast.literal_eval('u' + value)
else:
return ast.literal_eval(value)

def __setattr__(self, key, value):
if key.startswith('_') or key in dir(self):
return super(PropertyHolder, self).__setattr__(key, value)
if value is DEFAULT:
self.qubesd_call(
self._method_dest,
self._method_prefix + 'Reset',
key,
None)
else:
if isinstance(value, qubesmgmt.vm.QubesVM):
# pylint: disable=protected-access
value = value._name
self.qubesd_call(
self._method_dest,
self._method_prefix + 'Set',
key,
str(value).encode('utf-8'))

def __delattr__(self, name):
if name.startswith('_') or name in dir(self):
return super(PropertyHolder, self).__delattr__(name)
self.qubesd_call(
self._method_dest,
self._method_prefix + 'Reset',
name
)

# pylint: disable=wrong-import-position
import qubesmgmt.base
import qubesmgmt.app

DEFAULT = qubesmgmt.base.DEFAULT

if os.path.exists(qubesmgmt.app.QUBESD_SOCK):
Qubes = qubesmgmt.app.QubesLocal
else:
Expand Down
4 changes: 2 additions & 2 deletions qubesmgmt/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

import subprocess

import qubesmgmt
import qubesmgmt.base
import qubesmgmt.vm
import qubesmgmt.exc

Expand Down Expand Up @@ -71,7 +71,7 @@ def keys(self):
return self._vm_list.keys()


class QubesBase(qubesmgmt.PropertyHolder):
class QubesBase(qubesmgmt.base.PropertyHolder):
'''Main Qubes application'''

#: domains (VMs) collection
Expand Down
156 changes: 156 additions & 0 deletions qubesmgmt/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
# -*- encoding: utf8 -*-
#
# The Qubes OS Project, http://www.qubes-os.org
#
# Copyright (C) 2017 Marek Marczykowski-Górecki
# <[email protected]>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License along
# with this program; if not, see <http://www.gnu.org/licenses/>.
import ast
import qubesmgmt.exc

DEFAULT = object()

class PropertyHolder(object):
'''A base class for object having properties retrievable using mgmt API.
Warning: each (non-private) local attribute needs to be defined at class
level, even if initialized in __init__; otherwise will be treated as
property retrievable using mgmt call.
'''
#: a place for appropriate Qubes() object (QubesLocal or QubesRemote),
# use None for self
app = None

def __init__(self, app, method_prefix, method_dest):
#: appropriate Qubes() object (QubesLocal or QubesRemote), use None
# for self
self.app = app
self._method_prefix = method_prefix
self._method_dest = method_dest
self._properties = None
self._properties_help = None

def qubesd_call(self, dest, method, arg=None, payload=None):
'''
Call into qubesd using appropriate mechanism. This method should be
defined by a subclass.
:param dest: Destination VM name
:param method: Full API method name ('mgmt...')
:param arg: Method argument (if any)
:param payload: Payload send to the method
:return: Data returned by qubesd (string)
'''
# have the actual implementation at Qubes() instance
if self.app:
return self.app.qubesd_call(dest, method, arg, payload)
raise NotImplementedError

@staticmethod
def _parse_qubesd_response(response_data):
if response_data[0:2] == b'\x30\x00':
return response_data[2:]
elif response_data[0:2] == b'\x32\x00':
(_, exc_type, _traceback, format_string, args) = \
response_data.split(b'\x00', 4)
# drop last field because of terminating '\x00'
args = [arg.decode() for arg in args.split(b'\x00')[:-1]]
format_string = format_string.decode('utf-8')
exc_type = exc_type.decode('ascii')
exc_class = getattr(qubesmgmt.exc, exc_type, 'QubesException')
# TODO: handle traceback if given
raise exc_class(format_string, *args)
else:
raise qubesmgmt.exc.QubesException('Invalid response format')

def property_list(self):
'''
List available properties (their names).
:return: list of strings
'''
if self._properties is None:
properties_str = self.qubesd_call(
self._method_dest,
self._method_prefix + 'List',
None,
None)
self._properties = properties_str.decode('ascii').splitlines()
# TODO: make it somehow immutable
return self._properties

def property_is_default(self, item):
'''
Check if given property have default value
:param str item: name of property
:return: bool
'''
if item.startswith('_'):
raise AttributeError(item)
property_str = self.qubesd_call(
self._method_dest,
self._method_prefix + 'Get',
item,
None)
(default, _value) = property_str.split(b' ', 1)
assert default.startswith(b'default=')
is_default_str = default.split(b'=')[1]
is_default = ast.literal_eval(is_default_str.decode('ascii'))
assert isinstance(is_default, bool)
return is_default

def __getattr__(self, item):
if item.startswith('_'):
raise AttributeError(item)
property_str = self.qubesd_call(
self._method_dest,
self._method_prefix + 'Get',
item,
None)
(_default, value) = property_str.split(b' ', 1)
value = value.decode()
if value[0] == '\'':
return ast.literal_eval('u' + value)
else:
return ast.literal_eval(value)

def __setattr__(self, key, value):
if key.startswith('_') or key in dir(self):
return super(PropertyHolder, self).__setattr__(key, value)
if value is qubesmgmt.DEFAULT:
self.qubesd_call(
self._method_dest,
self._method_prefix + 'Reset',
key,
None)
else:
if isinstance(value, qubesmgmt.vm.QubesVM):
# pylint: disable=protected-access
value = value._name
self.qubesd_call(
self._method_dest,
self._method_prefix + 'Set',
key,
str(value).encode('utf-8'))

def __delattr__(self, name):
if name.startswith('_') or name in dir(self):
return super(PropertyHolder, self).__delattr__(name)
self.qubesd_call(
self._method_dest,
self._method_prefix + 'Reset',
name
)
4 changes: 2 additions & 2 deletions qubesmgmt/vm/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@
# You should have received a copy of the GNU General Public License along
# with this program; if not, see <http://www.gnu.org/licenses/>.

import qubesmgmt
import qubesmgmt.base


class QubesVM(qubesmgmt.PropertyHolder):
class QubesVM(qubesmgmt.base.PropertyHolder):
def __init__(self, app, name, vm_class):
self._name = name
self._class = vm_class
Expand Down

0 comments on commit c298edd

Please sign in to comment.