Skip to content

Commit

Permalink
Merge pull request #53 from jasongrout/metadata
Browse files Browse the repository at this point in the history
Add .tag method for adding metadata

deprecate metadata as `**kwargs` in constructor
  • Loading branch information
minrk committed Jul 26, 2015
2 parents 3d88fb1 + b822836 commit c661f4b
Show file tree
Hide file tree
Showing 8 changed files with 289 additions and 100 deletions.
16 changes: 8 additions & 8 deletions examples/myapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,24 +42,24 @@ class Foo(Configurable):
"""

i = Int(0, config=True, help="The integer i.")
j = Int(1, config=True, help="The integer j.")
name = Unicode(u'Brian', config=True, help="First name.")
i = Int(0, help="The integer i.").tag(config=True)
j = Int(1, help="The integer j.").tag(config=True)
name = Unicode(u'Brian', help="First name.").tag(config=True)


class Bar(Configurable):

enabled = Bool(True, config=True, help="Enable bar.")
enabled = Bool(True, help="Enable bar.").tag(config=True)


class MyApp(Application):

name = Unicode(u'myapp')
running = Bool(False, config=True,
help="Is the app running?")
running = Bool(False,
help="Is the app running?").tag(config=True)
classes = List([Bar, Foo])
config_file = Unicode(u'', config=True,
help="Load this config file")
config_file = Unicode(u'',
help="Load this config file").tag(config=True)

aliases = Dict(dict(i='Foo.i',j='Foo.j',name='Foo.name', running='MyApp.running',
enabled='Bar.enabled', log_level='MyApp.log_level'))
Expand Down
11 changes: 5 additions & 6 deletions traitlets/config/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,8 +152,7 @@ def _classes_inc_parents(self):
# The log level for the application
log_level = Enum((0,10,20,30,40,50,'DEBUG','INFO','WARN','ERROR','CRITICAL'),
default_value=logging.WARN,
config=True,
help="Set the log level by value or name.")
help="Set the log level by value or name.").tag(config=True)
def _log_level_changed(self, name, old, new):
"""Adjust the log level when log_level is set."""
if isinstance(new, string_types):
Expand All @@ -163,15 +162,15 @@ def _log_level_changed(self, name, old, new):

_log_formatter_cls = LevelFormatter

log_datefmt = Unicode("%Y-%m-%d %H:%M:%S", config=True,
log_datefmt = Unicode("%Y-%m-%d %H:%M:%S",
help="The date format used by logging formatters for %(asctime)s"
)
).tag(config=True)
def _log_datefmt_changed(self, name, old, new):
self._log_format_changed('log_format', self.log_format, self.log_format)

log_format = Unicode("[%(name)s]%(highlevel)s %(message)s", config=True,
log_format = Unicode("[%(name)s]%(highlevel)s %(message)s",
help="The Logging format template",
)
).tag(config=True)
def _log_format_changed(self, name, old, new):
"""Change the log formatter when log_format is set."""
_log_handler = self.log.handlers[0]
Expand Down
8 changes: 4 additions & 4 deletions traitlets/config/configurable.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ def _load_config(self, cfg, section_names=None, traits=None):
setattr(self, name, deepcopy(config_value))

def _config_changed(self, name, old, new):
"""Update all the class traits having ``config=True`` as metadata.
"""Update all the class traits having ``config=True`` in metadata.
For any class trait with a ``config`` metadata attribute that is
``True``, we update the trait with the value of the corresponding
Expand Down Expand Up @@ -220,7 +220,7 @@ def class_get_trait_help(cls, trait, inst=None):
# include Enum choices
lines.append(indent('Choices: %r' % (trait.values,)))

help = trait.get_metadata('help')
help = trait.metadata.get('help', None)
if help is not None:
help = '\n'.join(wrap_paragraphs(help, 76))
lines.append(indent(help, 4))
Expand Down Expand Up @@ -256,7 +256,7 @@ def c(s):
lines.append('')

for name, trait in iteritems(cls.class_own_traits(config=True)):
help = trait.get_metadata('help') or ''
help = trait.metadata.get('help', '')
lines.append(c(help))
lines.append('# c.%s.%s = %r'%(cls.__name__, name, trait.default_value))
lines.append('')
Expand Down Expand Up @@ -297,7 +297,7 @@ def class_config_rst_doc(cls):
lines.append(' Default: ``%s``' % dvr)
lines.append('')

help = trait.get_metadata('help')
help = trait.metadata.get('help', None)
if help is not None:
lines.append(indent(dedent(help), 4))
else:
Expand Down
16 changes: 7 additions & 9 deletions traitlets/config/tests/test_application.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,25 +30,23 @@

class Foo(Configurable):

i = Integer(0, config=True, help="The integer i.")
j = Integer(1, config=True, help="The integer j.")
name = Unicode(u'Brian', config=True, help="First name.")
i = Integer(0, help="The integer i.").tag(config=True)
j = Integer(1, help="The integer j.").tag(config=True)
name = Unicode(u'Brian', help="First name.").tag(config=True)


class Bar(Configurable):

b = Integer(0, config=True, help="The integer b.")
enabled = Bool(True, config=True, help="Enable bar.")
b = Integer(0, help="The integer b.").tag(config=True)
enabled = Bool(True, help="Enable bar.").tag(config=True)


class MyApp(Application):

name = Unicode(u'myapp')
running = Bool(False, config=True,
help="Is the app running?")
running = Bool(False, help="Is the app running?").tag(config=True)
classes = List([Bar, Foo])
config_file = Unicode(u'', config=True,
help="Load this config file")
config_file = Unicode(u'', help="Load this config file").tag(config=True)

aliases = Dict({
'i' : 'Foo.i',
Expand Down
46 changes: 36 additions & 10 deletions traitlets/config/tests/test_configurable.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,11 @@
from traitlets.config.loader import Config
from ipython_genutils.py3compat import PY3

from ...tests._warnings import expected_warnings

class MyConfigurable(Configurable):
a = Integer(1, config=True, help="The integer a.")
b = Float(1.0, config=True, help="The integer b.")
a = Integer(1, help="The integer a.").tag(config=True)
b = Float(1.0, help="The integer b.").tag(config=True)
c = Unicode('no config')


Expand Down Expand Up @@ -49,13 +50,13 @@ class MyConfigurable(Configurable):
mc_help_inst = mc_help_inst.replace(u"<Integer>", u"<Int>")

class Foo(Configurable):
a = Integer(0, config=True, help="The integer a.")
b = Unicode('nope', config=True)
a = Integer(0, help="The integer a.").tag(config=True)
b = Unicode('nope').tag(config=True)


class Bar(Foo):
b = Unicode('gotit', config=False, help="The string b.")
c = Float(config=True, help="The string c.")
b = Unicode('gotit', help="The string b.").tag(config=False)
c = Float(help="The string c.").tag(config=True)


class TestConfigurable(TestCase):
Expand Down Expand Up @@ -263,15 +264,15 @@ def test_multi_parent_priority(self):
self.assertEqual(myc.b, parent.config.MyParent2.MyParent.MyConfigurable.b)

class Containers(Configurable):
lis = List(config=True)
lis = List().tag(config=True)
def _lis_default(self):
return [-1]

s = Set(config=True)
s = Set().tag(config=True)
def _s_default(self):
return {'a'}

d = Dict(config=True)
d = Dict().tag(config=True)
def _d_default(self):
return {'a' : 'b'}

Expand Down Expand Up @@ -368,7 +369,7 @@ class SomeSingleton(SingletonConfigurable):
pass

class DefaultConfigurable(Configurable):
a = Integer(config=True)
a = Integer().tag(config=True)
def _config_default(self):
if SomeSingleton.initialized():
return SomeSingleton.instance().config
Expand All @@ -386,3 +387,28 @@ def _config_default(self):
self.assertIs(d2.config, single.config)
self.assertEqual(d2.a, 5)

def test_config_default_deprecated(self):
"""Make sure configurables work even with the deprecations in traitlets"""
class SomeSingleton(SingletonConfigurable):
pass

with expected_warnings(['Metadata should be set using the \.tag\(\) method']):
class DefaultConfigurable(Configurable):
a = Integer(config=True)
def _config_default(self):
if SomeSingleton.initialized():
return SomeSingleton.instance().config
return Config()

c = Config()
c.DefaultConfigurable.a = 5

d1 = DefaultConfigurable()
self.assertEqual(d1.a, 0)

single = SomeSingleton.instance(config=c)

d2 = DefaultConfigurable()
self.assertIs(d2.config, single.config)
self.assertEqual(d2.a, 5)

107 changes: 107 additions & 0 deletions traitlets/tests/_warnings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# From scikit-image: https://github.com/scikit-image/scikit-image/blob/c2f8c4ab123ebe5f7b827bc495625a32bb225c10/skimage/_shared/_warnings.py
# Licensed under modified BSD license

__all__ = ['all_warnings', 'expected_warnings']

from contextlib import contextmanager
import sys
import warnings
import inspect
import re


@contextmanager
def all_warnings():
"""
Context for use in testing to ensure that all warnings are raised.
Examples
--------
>>> import warnings
>>> def foo():
... warnings.warn(RuntimeWarning("bar"))
We raise the warning once, while the warning filter is set to "once".
Hereafter, the warning is invisible, even with custom filters:
>>> with warnings.catch_warnings():
... warnings.simplefilter('once')
... foo()
We can now run ``foo()`` without a warning being raised:
>>> from numpy.testing import assert_warns
>>> foo()
To catch the warning, we call in the help of ``all_warnings``:
>>> with all_warnings():
... assert_warns(RuntimeWarning, foo)
"""

# Whenever a warning is triggered, Python adds a __warningregistry__
# member to the *calling* module. The exercize here is to find
# and eradicate all those breadcrumbs that were left lying around.
#
# We proceed by first searching all parent calling frames and explicitly
# clearing their warning registries (necessary for the doctests above to
# pass). Then, we search for all submodules of skimage and clear theirs
# as well (necessary for the skimage test suite to pass).

frame = inspect.currentframe()
if frame:
for f in inspect.getouterframes(frame):
f[0].f_locals['__warningregistry__'] = {}
del frame

for mod_name, mod in list(sys.modules.items()):
if 'six.moves' in mod_name:
continue
try:
mod.__warningregistry__.clear()
except AttributeError:
pass

with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
yield w


@contextmanager
def expected_warnings(matching):
"""Context for use in testing to catch known warnings matching regexes
Parameters
----------
matching : list of strings or compiled regexes
Regexes for the desired warning to catch
Examples
--------
>>> from skimage import data, img_as_ubyte, img_as_float
>>> with expected_warnings(['precision loss']):
... d = img_as_ubyte(img_as_float(data.coins()))
Notes
-----
Uses `all_warnings` to ensure all warnings are raised.
Upon exiting, it checks the recorded warnings for the desired matching
pattern(s).
Raises a ValueError if any match was not found or an unexpected
warning was raised.
Allows for three types of behaviors: "and", "or", and "optional" matches.
This is done to accomodate different build enviroments or loop conditions
that may produce different warnings. The behaviors can be combined.
If you pass multiple patterns, you get an orderless "and", where all of the
warnings must be raised.
If you use the "|" operator in a pattern, you can catch one of several warnings.
Finally, you can use "|\A\Z" in a pattern to signify it as optional.
"""
with all_warnings() as w:
# enter context
yield w
# exited user context, check the recorded warnings
remaining = [m for m in matching if not '\A\Z' in m.split('|')]
for warn in w:
found = False
for match in matching:
if re.search(match, str(warn.message)) is not None:
found = True
if match in remaining:
remaining.remove(match)
if not found:
raise ValueError('Unexpected warning: %s' % str(warn.message))
if len(remaining) > 0:
msg = 'No warning raised matching:\n%s' % '\n'.join(remaining)
raise ValueError(msg)

0 comments on commit c661f4b

Please sign in to comment.