[lxc-devel] [pylxd/master] Add PYLXD_WARNINGS env variable to be able to supress warnings
ajkavanagh on Github
lxc-bot at linuxcontainers.org
Fri May 3 14:12:43 UTC 2019
A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 858 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20190503/28a76bb3/attachment.bin>
-------------- next part --------------
From 6ba85dc5ca1db19cef7daaab08938888c008c77b Mon Sep 17 00:00:00 2001
From: Alex Kavanagh <alex.kavanagh at canonical.com>
Date: Fri, 3 May 2019 15:10:16 +0100
Subject: [PATCH] Add PYLXD_WARNINGS env variable to be able to supress
warnings
If the LXD server that pylxd is connected to supports attributes on
objects that pylxd doesn't yet support, then a warning is issued using
the `warnings` module. This can fill logs with annoying warnings. So
this patch adds the ability to set an env variable PYLXD_WARNINGS to
'none' to suppress all the warnings, or to 'always' to get the existing
behaviour. The new behavior is to issue a warning once for each
instance of an attribute that isn't known for each object.
Closes: #301
Signed-off-by: Alex Kavanagh <alex.kavanagh at canonical.com>
---
doc/source/usage.rst | 16 ++++++++++++++
pylxd/models/_model.py | 21 ++++++++++++++++++
pylxd/models/operation.py | 22 ++++++++++++++++++-
pylxd/tests/models/test_model.py | 33 ++++++++++++++++++++++++++++
pylxd/tests/models/test_operation.py | 31 ++++++++++++++++++++++++++
tox.ini | 1 +
6 files changed, 123 insertions(+), 1 deletion(-)
diff --git a/doc/source/usage.rst b/doc/source/usage.rst
index 109071d4..1d2b938b 100644
--- a/doc/source/usage.rst
+++ b/doc/source/usage.rst
@@ -101,3 +101,19 @@ Some changes to LXD will return immediately, but actually occur in the
background after the http response returns. All operations that happen
this way will also take an optional `wait` parameter that, when `True`,
will not return until the operation is completed.
+
+UserWarning: Attempted to set unknown attribute "x" on instance of "y"
+----------------------------------------------------------------------
+
+The LXD server changes frequently, particularly if it is snap installed. In
+this case it is possible that the LXD server may send back objects with
+attributes that this version of pylxd is not aware of, and in that situation,
+the pylxd library issues the warning above.
+
+The default behaviour is that *one* warning is issued for each unknown
+attribute on *each* object class that it unknown. Further warnings are then
+surpressed. The environment variable ``PYLXD_WARNINGS`` can be set to control
+the warnings further:
+
+ - if set to ``none`` then *all* warnings are surpressed all the time.
+ - if set to ``always`` then warnings are always issued for each instance returned from the server.
diff --git a/pylxd/models/_model.py b/pylxd/models/_model.py
index 83fd0673..62887446 100644
--- a/pylxd/models/_model.py
+++ b/pylxd/models/_model.py
@@ -11,6 +11,7 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
+import os
import warnings
import six
@@ -83,6 +84,11 @@ def __new__(cls, name, bases, attrs):
return super(ModelType, cls).__new__(cls, name, bases, attrs)
+# Global used to record which warnings have been issues already for unknown
+# attributes.
+_seen_attribute_warnings = set()
+
+
@six.add_metaclass(ModelType)
class Model(object):
"""A Base LXD object model.
@@ -98,6 +104,13 @@ class Model(object):
un-initialized attributes are read. When attributes are modified,
the instance is marked as dirty. `save` will save the changes
to the server.
+
+ If the LXD server sends attributes that this version of pylxd is unaware of
+ then a warning is printed. By default the warning is issued ONCE and then
+ supressed for every subsequent attempted setting. The warnings can be
+ completely suppressed by setting the environment variable PYLXD_WARNINGS to
+ 'none', or always displayed by setting the PYLXD_WARNINGS variable to
+ 'always'.
"""
NotFound = exceptions.NotFound
__slots__ = ['client', '__dirty__']
@@ -110,6 +123,14 @@ def __init__(self, client, **kwargs):
try:
setattr(self, key, val)
except AttributeError:
+ global _seen_attribute_warnings
+ env = os.environ.get('PYLXD_WARNINGS', '').lower()
+ item = "{}.{}".format(self.__class__.__name__, key)
+ if env != 'always' and item in _seen_attribute_warnings:
+ continue
+ _seen_attribute_warnings.add(item)
+ if env == 'none':
+ continue
warnings.warn(
'Attempted to set unknown attribute "{}" '
'on instance of "{}"'.format(
diff --git a/pylxd/models/operation.py b/pylxd/models/operation.py
index a36df32c..ac094f50 100644
--- a/pylxd/models/operation.py
+++ b/pylxd/models/operation.py
@@ -19,8 +19,21 @@
from six.moves.urllib import parse
+# Global used to record which warnings have been issues already for unknown
+# attributes.
+_seen_attribute_warnings = set()
+
+
class Operation(object):
- """A LXD operation."""
+ """An LXD operation.
+
+ If the LXD server sends attributes that this version of pylxd is unaware of
+ then a warning is printed. By default the warning is issued ONCE and then
+ supressed for every subsequent attempted setting. The warnings can be
+ completely suppressed by setting the environment variable PYLXD_WARNINGS to
+ 'none', or always displayed by setting the PYLXD_WARNINGS variable to
+ 'always'.
+ """
__slots__ = [
'_client',
@@ -53,6 +66,13 @@ def __init__(self, **kwargs):
except AttributeError:
# ignore attributes we don't know about -- prevent breakage
# in the future if new attributes are added.
+ global _seen_attribute_warnings
+ env = os.environ.get('PYLXD_WARNINGS', '').lower()
+ if env != 'always' and key in _seen_attribute_warnings:
+ continue
+ _seen_attribute_warnings.add(key)
+ if env == 'none':
+ continue
warnings.warn(
'Attempted to set unknown attribute "{}" '
'on instance of "{}"'
diff --git a/pylxd/tests/models/test_model.py b/pylxd/tests/models/test_model.py
index 9aa1a4f3..244df0d3 100644
--- a/pylxd/tests/models/test_model.py
+++ b/pylxd/tests/models/test_model.py
@@ -11,6 +11,8 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
+import mock
+
from pylxd.models import _model as model
from pylxd.tests import testing
@@ -76,6 +78,37 @@ def test_init(self):
self.assertEqual(self.client, item.client)
self.assertEqual('an-item', item.name)
+ @mock.patch.dict('os.environ', {'PYLXD_WARNINGS': ''})
+ @mock.patch('warnings.warn')
+ def test_init_warnings_once(self, mock_warn):
+ with mock.patch('pylxd.models._model._seen_attribute_warnings',
+ new=set()):
+ Item(self.client, unknown='some_value')
+ mock_warn.assert_called_once_with(mock.ANY)
+ Item(self.client, unknown='some_value_as_well')
+ mock_warn.assert_called_once_with(mock.ANY)
+ Item(self.client, unknown2="some_2nd_value")
+ self.assertEqual(len(mock_warn.call_args_list), 2)
+
+ @mock.patch.dict('os.environ', {'PYLXD_WARNINGS': 'none'})
+ @mock.patch('warnings.warn')
+ def test_init_warnings_none(self, mock_warn):
+ with mock.patch('pylxd.models._model._seen_attribute_warnings',
+ new=set()):
+ Item(self.client, unknown='some_value')
+ mock_warn.assert_not_called()
+
+ @mock.patch.dict('os.environ', {'PYLXD_WARNINGS': 'always'})
+ @mock.patch('warnings.warn')
+ def test_init_warnings_always(self, mock_warn):
+ with mock.patch('pylxd.models._model._seen_attribute_warnings',
+ new=set()):
+ Item(self.client, unknown='some_value')
+ mock_warn.assert_called_once_with(mock.ANY)
+ Item(self.client, unknown='some_value_as_well')
+ self.assertEqual(len(mock_warn.call_args_list), 2)
+
+ @mock.patch.dict('os.environ', {'PYLXD_WARNINGS': 'none'})
def test_init_unknown_attribute(self):
"""Unknown attributes aren't set."""
item = Item(self.client, name='an-item', nonexistent='SRSLY')
diff --git a/pylxd/tests/models/test_operation.py b/pylxd/tests/models/test_operation.py
index 1e70e869..83790b06 100644
--- a/pylxd/tests/models/test_operation.py
+++ b/pylxd/tests/models/test_operation.py
@@ -13,6 +13,7 @@
# under the License.
import json
+import mock
from pylxd import exceptions, models
from pylxd.tests import testing
@@ -21,6 +22,36 @@
class TestOperation(testing.PyLXDTestCase):
"""Tests for pylxd.models.Operation."""
+ @mock.patch.dict('os.environ', {'PYLXD_WARNINGS': ''})
+ @mock.patch('warnings.warn')
+ def test_init_warnings_once(self, mock_warn):
+ with mock.patch('pylxd.models.operation._seen_attribute_warnings',
+ new=set()):
+ models.Operation(unknown='some_value')
+ mock_warn.assert_called_once_with(mock.ANY)
+ models.Operation(unknown='some_value_as_well')
+ mock_warn.assert_called_once_with(mock.ANY)
+ models.Operation(unknown2="some_2nd_value")
+ self.assertEqual(len(mock_warn.call_args_list), 2)
+
+ @mock.patch.dict('os.environ', {'PYLXD_WARNINGS': 'none'})
+ @mock.patch('warnings.warn')
+ def test_init_warnings_none(self, mock_warn):
+ with mock.patch('pylxd.models.operation._seen_attribute_warnings',
+ new=set()):
+ models.Operation(unknown='some_value')
+ mock_warn.assert_not_called()
+
+ @mock.patch.dict('os.environ', {'PYLXD_WARNINGS': 'always'})
+ @mock.patch('warnings.warn')
+ def test_init_warnings_always(self, mock_warn):
+ with mock.patch('pylxd.models.operation._seen_attribute_warnings',
+ new=set()):
+ models.Operation(unknown='some_value')
+ mock_warn.assert_called_once_with(mock.ANY)
+ models.Operation(unknown='some_value_as_well')
+ self.assertEqual(len(mock_warn.call_args_list), 2)
+
def test_get(self):
"""Return an operation."""
name = 'operation-abc'
diff --git a/tox.ini b/tox.ini
index 7f460636..3dbff000 100644
--- a/tox.ini
+++ b/tox.ini
@@ -8,6 +8,7 @@ usedevelop = True
install_command = pip install -U {opts} {packages}
setenv =
VIRTUAL_ENV={envdir}
+ PYLXD_WARNINGS=none
deps = -r{toxinidir}/requirements.txt
-r{toxinidir}/test-requirements.txt
commands = nosetests --with-coverage --cover-package=pylxd pylxd
More information about the lxc-devel
mailing list