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
1=== modified file 'client.py'
2--- client.py 2013-04-04 13:13:34 +0000
3+++ client.py 2013-04-19 15:36:27 +0000
4@@ -24,6 +24,7 @@
5 import os
6
7 from utah.url import url_argument
8+from utah.client.battery import battery
9 from utah.client.runner import Runner
10 from utah.client.state_agent import StateAgentYAML
11 from utah.client.result import Result, ResultYAML, ResultJSON
12@@ -73,6 +74,10 @@
13 help='append to output')
14 parser.add_argument('-d', '--debug', action='store_true',
15 help='Print debugging output')
16+ parser.add_argument('-b', '--battery',
17+ default=battery.implementation,
18+ choices=battery.implementations(),
19+ help='Choose which battery implementation to use.')
20
21 return parser
22
23@@ -87,6 +92,10 @@
24 format='%(asctime)s - %(levelname)s - %(message)s')
25
26
27+def setup_battery(battery_name):
28+ battery.implementation = battery_name
29+
30+
31 def main():
32 """Interpret command line arguments and run tests."""
33 # TODO: write <2.7 optparse version and set based on version of python
34@@ -102,6 +111,7 @@
35 sys.exit(ReturnCodes.INVALID_USER)
36
37 setup_logging(args.debug)
38+ setup_battery(args.battery)
39
40 logging.debug(args)
41 logging.info(platform.uname())
42
43=== modified file 'utah/client/battery.py'
44--- utah/client/battery.py 2013-04-04 13:03:51 +0000
45+++ utah/client/battery.py 2013-04-19 15:36:27 +0000
46@@ -20,10 +20,17 @@
47 import re
48 import logging
49
50-
51-class _Battery(object):
52-
53- """Battery information gathering."""
54+from utah.client.exceptions import UTAHClientError
55+
56+
57+class _SWBatteryImpl(object):
58+
59+ """Gathers battery information via the kernel's "power_supply" class.
60+
61+ .. seealso::
62+ https://www.kernel.org/doc/Documentation/power/power_supply_class.txt
63+
64+ """
65
66 POWER_SUPPLY_DIR = '/sys/class/power_supply'
67 BATTERY_DIR_REGEX = re.compile(r'bat.*', re.IGNORECASE)
68@@ -32,6 +39,11 @@
69 def __init__(self):
70 self.filenames = None
71
72+ @staticmethod
73+ def get_name():
74+ """return the implementation name displayed to the user."""
75+ return 'software'
76+
77 def get_data(self):
78 """Get data from the battery information files.
79
80@@ -103,5 +115,34 @@
81 .format(', '.join(filenames)))
82 return filenames
83
84-# Use singleton object to avoid multiple unneeded initializations
85+
86+class _Battery(object):
87+ def __init__(self):
88+ self._impl = _SWBatteryImpl()
89+ self._impls = {}
90+
91+ for name, cls in globals().iteritems():
92+ if name.endswith("BatteryImpl"):
93+ self._impls[cls.get_name()] = cls
94+
95+ def implementations(self):
96+ """return the battery implementation names."""
97+ return self._impls.keys()
98+
99+ def get_data(self):
100+ """return the battery data from the implementation."""
101+ if not self._impl:
102+ raise UTAHClientError('No battery implementation set')
103+ return self._impl.get_data()
104+
105+ def _get_impl(self):
106+ if not self._impl:
107+ return None
108+ return self._impl.get_name()
109+
110+ def _set_impl(self, name):
111+ self._impl = self._impls[name]()
112+
113+ implementation = property(_get_impl, _set_impl)
114+
115 battery = _Battery()
116
117=== added file 'utah/client/tests/test_battery.py'
118--- utah/client/tests/test_battery.py 1970-01-01 00:00:00 +0000
119+++ utah/client/tests/test_battery.py 2013-04-19 15:36:27 +0000
120@@ -0,0 +1,54 @@
121+# Ubuntu Testing Automation Harness
122+# Copyright 2012 Canonical Ltd.
123+
124+# This program is free software: you can redistribute it and/or modify it
125+# under the terms of the GNU General Public License version 3, as published
126+# by the Free Software Foundation.
127+
128+# This program is distributed in the hope that it will be useful, but
129+# WITHOUT ANY WARRANTY; without even the implied warranties of
130+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
131+# PURPOSE. See the GNU General Public License for more details.
132+
133+# You should have received a copy of the GNU General Public License along
134+# with this program. If not, see <http://www.gnu.org/licenses/>.
135+
136+"""Test our battery interface."""
137+
138+
139+import unittest
140+
141+from utah.client.battery import battery
142+
143+
144+class TestBattery(unittest.TestCase):
145+
146+ """Basic testing of the battery interface."""
147+
148+ def test_listing(self):
149+ """Ensure we properly discover the implementations."""
150+ self.assertTrue('software' in battery.implementations())
151+
152+ def test_default(self):
153+ """Make sure battery initialization is initialzed correctly."""
154+ self.assertIsNotNone(battery.get_data())
155+
156+ def test_non_default(self):
157+ """Perform check to see if non-default battery works.
158+
159+ :returns: nothing - the inner function is confusing pep257
160+
161+ """
162+ class dummy(object):
163+ @staticmethod
164+ def get_name():
165+ return 'dummy'
166+
167+ def get_data(self):
168+ return {'foo': 'bar'}
169+
170+ battery._impls[dummy.get_name()] = dummy
171+ self.assertTrue('dummy' in battery.implementations())
172+
173+ battery.implementation = dummy.get_name()
174+ self.assertEquals(battery.get_data()['foo'], 'bar')

Subscribers

People subscribed via source and target branches

to all changes: