Merge lp:~stub/charm-helpers/py3 into lp:charm-helpers

Proposed by Stuart Bishop
Status: Merged
Merged at revision: 371
Proposed branch: lp:~stub/charm-helpers/py3
Merge into: lp:charm-helpers
Diff against target: 213 lines (+68/-18)
7 files modified
charmhelpers/contrib/peerstorage/__init__.py (+2/-0)
charmhelpers/core/hookenv.py (+6/-0)
charmhelpers/core/host.py (+1/-1)
charmhelpers/fetch/__init__.py (+1/-1)
tests/contrib/peerstorage/test_peerstorage.py (+8/-0)
tests/core/test_hookenv.py (+37/-1)
tests/core/test_templating.py (+13/-15)
To merge this branch: bzr merge lp:~stub/charm-helpers/py3
Reviewer Review Type Date Requested Status
Jorge Niedbalski (community) Needs Fixing
charmers Pending
Review via email: mp+244668@code.launchpad.net

Description of the change

A small grabbag of fixes I have needed putting together a new charm.

The main fix is host.write() for Python3, where Python3 is picky about byte strings vs. text strings. Since we use write for all sorts of files, it needs to accept byte strings. Py2 users are unaffected.

Includes fixes for Bug #1402192 , as well as https://code.launchpad.net/~stub/charm-helpers/improve-peerstorage/+merge/243519 (if that hasn't landed already).

To post a comment you must log in.
lp:~stub/charm-helpers/py3 updated
214. By Stuart Bishop

Merge trunk

Revision history for this message
Jorge Niedbalski (niedbalski) wrote :

LGTM, minor observations.

review: Needs Fixing
lp:~stub/charm-helpers/py3 updated
215. By Stuart Bishop

Tidy a test

Revision history for this message
Stuart Bishop (stub) wrote :

I discussed the .get() method with Jorge, and why the current approach is necessary.

I have tidied the test I made whitespace changes to further per request.

Revision history for this message
Jay R. Wren (evarlast) wrote :

The behavior of the in operator is not consistent.

In an attempt to workaround the get bug, one may use in to prevent a KeyError

    if 'foo' not in config:
        return

but this returns even if 'foo' is in _prev_dict.

a workaround is

    if 'foo' not in config.keys():
        return

but expected behavior is that the .keys() call is not necessary.

lp:~stub/charm-helpers/py3 updated
216. By Stuart Bishop

Merge trunk

217. By Stuart Bishop

Add test demonstrating the behavior of the in operator

Revision history for this message
Stuart Bishop (stub) wrote :

@evarlast - I've added a test for the behaviour of the in operator, and it seems to be fine.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'charmhelpers/contrib/peerstorage/__init__.py'
2--- charmhelpers/contrib/peerstorage/__init__.py 2015-01-22 06:06:03 +0000
3+++ charmhelpers/contrib/peerstorage/__init__.py 2015-04-29 13:06:12 +0000
4@@ -73,6 +73,8 @@
5 exc_list = exc_list if exc_list else []
6 peerdb_settings = peer_retrieve('-', relation_name=relation_name)
7 matched = {}
8+ if peerdb_settings is None:
9+ return matched
10 for k, v in peerdb_settings.items():
11 full_prefix = prefix + delimiter
12 if k.startswith(full_prefix):
13
14=== modified file 'charmhelpers/core/hookenv.py'
15--- charmhelpers/core/hookenv.py 2015-04-07 16:12:51 +0000
16+++ charmhelpers/core/hookenv.py 2015-04-29 13:06:12 +0000
17@@ -250,6 +250,12 @@
18 except KeyError:
19 return (self._prev_dict or {})[key]
20
21+ def get(self, key, default=None):
22+ try:
23+ return self[key]
24+ except KeyError:
25+ return default
26+
27 def keys(self):
28 prev_keys = []
29 if self._prev_dict is not None:
30
31=== modified file 'charmhelpers/core/host.py'
32--- charmhelpers/core/host.py 2015-03-12 10:56:57 +0000
33+++ charmhelpers/core/host.py 2015-04-29 13:06:12 +0000
34@@ -90,7 +90,7 @@
35 ['service', service_name, 'status'],
36 stderr=subprocess.STDOUT).decode('UTF-8')
37 except subprocess.CalledProcessError as e:
38- return 'unrecognized service' not in e.output
39+ return b'unrecognized service' not in e.output
40 else:
41 return True
42
43
44=== modified file 'charmhelpers/fetch/__init__.py'
45--- charmhelpers/fetch/__init__.py 2015-01-22 06:11:15 +0000
46+++ charmhelpers/fetch/__init__.py 2015-04-29 13:06:12 +0000
47@@ -158,7 +158,7 @@
48
49 def apt_cache(in_memory=True):
50 """Build and return an apt cache"""
51- import apt_pkg
52+ from apt import apt_pkg
53 apt_pkg.init()
54 if in_memory:
55 apt_pkg.config.set("Dir::Cache::pkgcache", "")
56
57=== modified file 'tests/contrib/peerstorage/test_peerstorage.py'
58--- tests/contrib/peerstorage/test_peerstorage.py 2014-08-19 09:38:07 +0000
59+++ tests/contrib/peerstorage/test_peerstorage.py 2015-04-29 13:06:12 +0000
60@@ -146,6 +146,14 @@
61 self.assertEquals(peerstorage.peer_retrieve_by_prefix(rel_id), settings)
62
63 @patch.object(peerstorage, 'peer_retrieve')
64+ def test_peer_retrieve_by_prefix_empty_relation(self, peer_retrieve):
65+ # If relation-get returns None, peer_retrieve_by_prefix returns
66+ # an empty dictionary.
67+ peer_retrieve.return_value = None
68+ rel_id = 'db:2'
69+ self.assertEquals(peerstorage.peer_retrieve_by_prefix(rel_id), {})
70+
71+ @patch.object(peerstorage, 'peer_retrieve')
72 def test_peer_retrieve_by_prefix_exc_list(self, peer_retrieve):
73 rel_id = 'db:2'
74 settings = {
75
76=== modified file 'tests/core/test_hookenv.py'
77--- tests/core/test_hookenv.py 2015-03-12 10:31:22 +0000
78+++ tests/core/test_hookenv.py 2015-04-29 13:06:12 +0000
79@@ -3,7 +3,7 @@
80 from subprocess import CalledProcessError
81 import shutil
82 import tempfile
83-from mock import patch, call, mock_open
84+from mock import patch, call, mock_open, sentinel
85 from mock import MagicMock
86 from testtools import TestCase
87 import yaml
88@@ -128,11 +128,47 @@
89 self.assertEqual(c['foo'], 'bar')
90 self.assertEqual(c['baz'], 'bam')
91
92+ def test_get(self):
93+ c = hookenv.Config(dict(foo='bar'))
94+ c.save()
95+ c = hookenv.Config(dict(baz='bam'))
96+
97+ self.assertIsNone(c.get('missing'))
98+ self.assertIs(c.get('missing', sentinel.missing), sentinel.missing)
99+ self.assertEqual(c.get('foo'), 'bar')
100+ self.assertEqual(c.get('baz'), 'bam')
101+
102 def test_keys(self):
103 c = hookenv.Config(dict(foo='bar'))
104 c["baz"] = "bar"
105 self.assertEqual(sorted([six.u("foo"), "baz"]), sorted(c.keys()))
106
107+ def test_in(self):
108+ # Test behavior of the in operator.
109+
110+ # Items that exist in the dict exist. Items that don't don't.
111+ c = hookenv.Config(dict(foo='one'))
112+ self.assertTrue('foo' in c)
113+ self.assertTrue('bar' not in c)
114+ c.save()
115+ self.assertTrue('foo' in c)
116+ self.assertTrue('bar' not in c)
117+
118+ # Adding items works as expected.
119+ c['foo'] = 'two'
120+ c['bar'] = 'two'
121+ self.assertTrue('foo' in c)
122+ self.assertTrue('bar' in c)
123+ c.save()
124+ self.assertTrue('foo' in c)
125+ self.assertTrue('bar' in c)
126+
127+ # Removing items works as expected.
128+ del c['foo']
129+ self.assertTrue('foo' not in c)
130+ c.save()
131+ self.assertTrue('foo' not in c)
132+
133
134 class SerializableTest(TestCase):
135 def test_serializes_object_to_json(self):
136
137=== modified file 'tests/core/test_templating.py'
138--- tests/core/test_templating.py 2015-02-03 21:52:36 +0000
139+++ tests/core/test_templating.py 2015-04-29 13:06:12 +0000
140@@ -1,9 +1,9 @@
141-import os
142 import pkg_resources
143 import shutil
144 import tempfile
145 import unittest
146 import jinja2
147+import os.path
148 import pwd
149 import grp
150
151@@ -17,7 +17,8 @@
152 class TestTemplating(unittest.TestCase):
153 def setUp(self):
154 self.charm_dir = pkg_resources.resource_filename(__name__, '')
155- self._charm_dir_patch = mock.patch.object(templating.hookenv, 'charm_dir')
156+ self._charm_dir_patch = mock.patch.object(templating.hookenv,
157+ 'charm_dir')
158 self._charm_dir_mock = self._charm_dir_patch.start()
159 self._charm_dir_mock.side_effect = lambda: self.charm_dir
160
161@@ -28,9 +29,8 @@
162 @mock.patch.object(templating.host, 'mkdir')
163 @mock.patch.object(templating.host, 'log')
164 def test_render(self, log, mkdir, fchown):
165- _, fn1 = tempfile.mkstemp()
166- _, fn2 = tempfile.mkstemp()
167- try:
168+ with tempfile.NamedTemporaryFile() as fn1, \
169+ tempfile.NamedTemporaryFile() as fn2:
170 context = {
171 'nats': {
172 'port': '1234',
173@@ -41,23 +41,19 @@
174 },
175 'nginx_port': 80,
176 }
177- templating.render('fake_cc.yml', fn1,
178+ templating.render('fake_cc.yml', fn1.name,
179 context, templates_dir=TEMPLATES_DIR)
180- contents = open(fn1).read()
181+ contents = open(fn1.name).read()
182 self.assertRegexpMatches(contents, 'port: 1234')
183 self.assertRegexpMatches(contents, 'host: example.com')
184 self.assertRegexpMatches(contents, 'domain: api.foo.com')
185
186- templating.render('test.conf', fn2, context,
187+ templating.render('test.conf', fn2.name, context,
188 templates_dir=TEMPLATES_DIR)
189- contents = open(fn2).read()
190+ contents = open(fn2.name).read()
191 self.assertRegexpMatches(contents, 'listen 80')
192 self.assertEqual(fchown.call_count, 2)
193 self.assertEqual(mkdir.call_count, 2)
194- finally:
195- for fn in (fn1, fn2):
196- if os.path.exists(fn):
197- os.remove(fn)
198
199 @mock.patch.object(templating.host.os, 'fchown')
200 @mock.patch.object(templating.host, 'log')
201@@ -80,8 +76,10 @@
202 @mock.patch.object(templating, 'hookenv')
203 @mock.patch('jinja2.Environment')
204 def test_load_error(self, Env, hookenv):
205- Env().get_template.side_effect = jinja2.exceptions.TemplateNotFound('fake_cc.yml')
206+ Env().get_template.side_effect = jinja2.exceptions.TemplateNotFound(
207+ 'fake_cc.yml')
208 self.assertRaises(
209 jinja2.exceptions.TemplateNotFound, templating.render,
210 'fake.src', 'fake.tgt', {}, templates_dir='tmpl')
211- hookenv.log.assert_called_once_with('Could not load template fake.src from tmpl.', level=hookenv.ERROR)
212+ hookenv.log.assert_called_once_with(
213+ 'Could not load template fake.src from tmpl.', level=hookenv.ERROR)

Subscribers

People subscribed via source and target branches