Merge lp:~doanac/utah/pluggable-battery into lp:utah

Proposed by Andy Doan
Status: Merged
Approved by: Javier Collado
Approved revision: 866
Merged at revision: 868
Proposed branch: lp:~doanac/utah/pluggable-battery
Merge into: lp:utah
Diff against target: 174 lines (+110/-5)
3 files modified
client.py (+10/-0)
utah/client/battery.py (+46/-5)
utah/client/tests/test_battery.py (+54/-0)
To merge this branch: bzr merge lp:~doanac/utah/pluggable-battery
Reviewer Review Type Date Requested Status
Javier Collado (community) Approve
Andy Doan (community) Approve
Review via email: mp+159033@code.launchpad.net

Description of the change

kind of went on a detour today. the "puts" system is going to be deployed in the lab pretty soon and will cause us to need to support multiple battery implementations in the UTAH client.

I'm not a huge fan of this approach, but we aren't using pkg_resources anywhere else in our code, so thought this might suffice.

To post a comment you must log in.
Revision history for this message
Max Brustkern (nuclearbob) wrote :

This looks reasonable to me, but I don't have a good way to test it.

Revision history for this message
Andy Doan (doanac) wrote :

I added a temp class class to the file like:

class _dummyBattery(object):
    @staticmethod
    def get_name():
        return 'dummy'

    def get_data(self):
        return {'foo': 'bar'}

Revision history for this message
Javier Collado (javier.collado) wrote :

The code looks fine, but let me make a few comments:

- _batteries
I think the client script shouldn't take care of caching this value. What about
exposing this data in the battery module directly? The get_implementations
function could be removed as well:

utah/client/battery.py
implementations = {}
for name, cls in globals().iteritems():
    if name.endswith("Battery"):
        implementations[cls.get_name()] = cls

utah/client.py
battery.implementations

- %(default)s in battery argument help string
%(default)s could be used in `batt_help` to let the user know from the command
line what is the default battery implementation.

- keys() iteration
Given that in a dictionary the iteration happens on its keys by default, I
prefer to avoid using the keys() method for that.

- utah.client.battery._SWBattery.get_name static method
Is this something that will need to be calculated on the fly for other
implementations? If not, then a class attribute is just fine.

- utah.client.battery.get
Using this function to get the battery implementation every time, makes the interface
more verbose:
battery.get().get_data()

What about having another class, something like `EmptyBattery` that implements
`get_data` in a way that raises the exception for the error not to having
selected any implementation?

class EmptyBattery:
    def get_data(self):
        raise UTAHException('No battery implementation selected')

battery = EmptyBattery()

This way, the interface can still be the same as in the previous version:
from utah.client.battery import battery
battery.get_data()

and the code that checks for the implementation is not executed repeatedly.

- RuntimeError
To be consistent with the rest of the code, this should be a UTAHException.

Revision history for this message
Andy Doan (doanac) wrote :

nice suggestions. I think I basically took your idea, but not quite exactly. I kept the "get_name" static method, because I wasn't sure if it would need to be computed or not in the future.

review: Needs Resubmitting
Revision history for this message
Javier Collado (javier.collado) wrote :

The diff is now empty. It looks like lp:~doanac/utah/pluggable-battery was
overwritten with dev.

Revision history for this message
Andy Doan (doanac) wrote :

oops - should be updated now

review: Needs Resubmitting
Revision history for this message
Javier Collado (javier.collado) wrote :

That's strange, the diff is still empty.

I've taken a look at the branch directly and the changes look better now. In
particular, I like to still have the easy interface like: battery.get_data()

Anyway, still there are a couple of things I don't like much:

- set_implementation

A set method looks and kind of implies that the user of the code know how it
works under the hood.

What do you think about having a property called 'implementation' whose getter
method returns the name of the current implementation and the setter sets the
right implementation based only on the name.

This way, the client code just needs something like:
battery.implementation = <implementation_name>

and also, the dictionary of class don't really need to exposed, just the
implementation names so that the name passed through the command line can be
validated.

- setup_battery

This function raises ArgumentTypeError if the passed implementation name isn't
available. This should be done by `argparse` directly using a `choices` list
(i.e. a list of implementation names) in the call to `add_argument`.

lp:~doanac/utah/pluggable-battery updated
864. By Andy Doan

review comments from javier

some good ideas by javier

Revision history for this message
Andy Doan (doanac) wrote :

thanks for the great feedback. revno 864 cleans up stuff from your last comment.

review: Needs Resubmitting
lp:~doanac/utah/pluggable-battery updated
865. By Andy Doan

remove unused import

Revision history for this message
Javier Collado (javier.collado) wrote :

Thanks for the effort on the branch. I think the changes are pretty good shape
now.

Running static analysis tools I found a few issues:

- flake8 output
./utah/client/tests/test_battery.py:21:1: F401 'UTAHClientError' imported but unused
./utah/client/battery.py:25:1: E302 expected 2 blank lines, found 1

- pep257 output
battery.py:27:4: PEP257 Class docstring should have 1 blank line around them.
battery.py:41:4: PEP257 Exported definitions should have docstrings.
battery.py:125:4: PEP257 Exported definitions should have docstrings.
battery.py:128:4: PEP257 Exported definitions should have docstrings.
test_battery.py:30:8: PEP257 First line should be in imperative mood ('Do', not 'Does').
test_battery.py:38:8: PEP257 Return value type should be mentioned.

Finally, regarding the property code, it's fine the way it is now, but I wanted
to mention that I like the decorator syntax in the documentation just in case
you want to consider it:

class C(object):
    def __init__(self):
        self._x = None

    @property
    def x(self):
        """I'm the 'x' property."""
        return self._x

    @x.setter
    def x(self, value):
        self._x = value

lp:~doanac/utah/pluggable-battery updated
866. By Andy Doan

pep257/pyflake warnings

Revision history for this message
Andy Doan (doanac) wrote :

should be cleaned up now.

review: Approve
Revision history for this message
Javier Collado (javier.collado) wrote :

Looks good now. Thanks.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'client.py'
--- client.py 2013-04-04 13:13:34 +0000
+++ client.py 2013-04-19 15:36:27 +0000
@@ -24,6 +24,7 @@
24import os24import os
2525
26from utah.url import url_argument26from utah.url import url_argument
27from utah.client.battery import battery
27from utah.client.runner import Runner28from utah.client.runner import Runner
28from utah.client.state_agent import StateAgentYAML29from utah.client.state_agent import StateAgentYAML
29from utah.client.result import Result, ResultYAML, ResultJSON30from utah.client.result import Result, ResultYAML, ResultJSON
@@ -73,6 +74,10 @@
73 help='append to output')74 help='append to output')
74 parser.add_argument('-d', '--debug', action='store_true',75 parser.add_argument('-d', '--debug', action='store_true',
75 help='Print debugging output')76 help='Print debugging output')
77 parser.add_argument('-b', '--battery',
78 default=battery.implementation,
79 choices=battery.implementations(),
80 help='Choose which battery implementation to use.')
7681
77 return parser82 return parser
7883
@@ -87,6 +92,10 @@
87 format='%(asctime)s - %(levelname)s - %(message)s')92 format='%(asctime)s - %(levelname)s - %(message)s')
8893
8994
95def setup_battery(battery_name):
96 battery.implementation = battery_name
97
98
90def main():99def main():
91 """Interpret command line arguments and run tests."""100 """Interpret command line arguments and run tests."""
92 # TODO: write <2.7 optparse version and set based on version of python101 # TODO: write <2.7 optparse version and set based on version of python
@@ -102,6 +111,7 @@
102 sys.exit(ReturnCodes.INVALID_USER)111 sys.exit(ReturnCodes.INVALID_USER)
103112
104 setup_logging(args.debug)113 setup_logging(args.debug)
114 setup_battery(args.battery)
105115
106 logging.debug(args)116 logging.debug(args)
107 logging.info(platform.uname())117 logging.info(platform.uname())
108118
=== modified file 'utah/client/battery.py'
--- utah/client/battery.py 2013-04-04 13:03:51 +0000
+++ utah/client/battery.py 2013-04-19 15:36:27 +0000
@@ -20,10 +20,17 @@
20import re20import re
21import logging21import logging
2222
2323from utah.client.exceptions import UTAHClientError
24class _Battery(object):24
2525
26 """Battery information gathering."""26class _SWBatteryImpl(object):
27
28 """Gathers battery information via the kernel's "power_supply" class.
29
30 .. seealso::
31 https://www.kernel.org/doc/Documentation/power/power_supply_class.txt
32
33 """
2734
28 POWER_SUPPLY_DIR = '/sys/class/power_supply'35 POWER_SUPPLY_DIR = '/sys/class/power_supply'
29 BATTERY_DIR_REGEX = re.compile(r'bat.*', re.IGNORECASE)36 BATTERY_DIR_REGEX = re.compile(r'bat.*', re.IGNORECASE)
@@ -32,6 +39,11 @@
32 def __init__(self):39 def __init__(self):
33 self.filenames = None40 self.filenames = None
3441
42 @staticmethod
43 def get_name():
44 """return the implementation name displayed to the user."""
45 return 'software'
46
35 def get_data(self):47 def get_data(self):
36 """Get data from the battery information files.48 """Get data from the battery information files.
3749
@@ -103,5 +115,34 @@
103 .format(', '.join(filenames)))115 .format(', '.join(filenames)))
104 return filenames116 return filenames
105117
106# Use singleton object to avoid multiple unneeded initializations118
119class _Battery(object):
120 def __init__(self):
121 self._impl = _SWBatteryImpl()
122 self._impls = {}
123
124 for name, cls in globals().iteritems():
125 if name.endswith("BatteryImpl"):
126 self._impls[cls.get_name()] = cls
127
128 def implementations(self):
129 """return the battery implementation names."""
130 return self._impls.keys()
131
132 def get_data(self):
133 """return the battery data from the implementation."""
134 if not self._impl:
135 raise UTAHClientError('No battery implementation set')
136 return self._impl.get_data()
137
138 def _get_impl(self):
139 if not self._impl:
140 return None
141 return self._impl.get_name()
142
143 def _set_impl(self, name):
144 self._impl = self._impls[name]()
145
146 implementation = property(_get_impl, _set_impl)
147
107battery = _Battery()148battery = _Battery()
108149
=== added file 'utah/client/tests/test_battery.py'
--- utah/client/tests/test_battery.py 1970-01-01 00:00:00 +0000
+++ utah/client/tests/test_battery.py 2013-04-19 15:36:27 +0000
@@ -0,0 +1,54 @@
1# Ubuntu Testing Automation Harness
2# Copyright 2012 Canonical Ltd.
3
4# This program is free software: you can redistribute it and/or modify it
5# under the terms of the GNU General Public License version 3, as published
6# by the Free Software Foundation.
7
8# This program is distributed in the hope that it will be useful, but
9# WITHOUT ANY WARRANTY; without even the implied warranties of
10# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11# PURPOSE. See the GNU General Public License for more details.
12
13# You should have received a copy of the GNU General Public License along
14# with this program. If not, see <http://www.gnu.org/licenses/>.
15
16"""Test our battery interface."""
17
18
19import unittest
20
21from utah.client.battery import battery
22
23
24class TestBattery(unittest.TestCase):
25
26 """Basic testing of the battery interface."""
27
28 def test_listing(self):
29 """Ensure we properly discover the implementations."""
30 self.assertTrue('software' in battery.implementations())
31
32 def test_default(self):
33 """Make sure battery initialization is initialzed correctly."""
34 self.assertIsNotNone(battery.get_data())
35
36 def test_non_default(self):
37 """Perform check to see if non-default battery works.
38
39 :returns: nothing - the inner function is confusing pep257
40
41 """
42 class dummy(object):
43 @staticmethod
44 def get_name():
45 return 'dummy'
46
47 def get_data(self):
48 return {'foo': 'bar'}
49
50 battery._impls[dummy.get_name()] = dummy
51 self.assertTrue('dummy' in battery.implementations())
52
53 battery.implementation = dummy.get_name()
54 self.assertEquals(battery.get_data()['foo'], 'bar')

Subscribers

People subscribed via source and target branches

to all changes: