diff --git a/CHANGELOG.md b/CHANGELOG.md index 2cf1a4c..8911c6d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +# Version 0.7.0 + +* Adds introspection tools for channel management objects: + * `get_channelmethods` + * `get_channelproperties` +* Changes logger names for artifacts and downgrades some messages from INFO level to DEBUG + * This change in logger names could break deployed logging configurations, and for this + reason, we incremented the minor version; otherwise this would be a micro bump. + # Version 0.6.0 * Adds the `channelmethod` decorator as a callable-oriented version of `channelproperty` (Issue #41) diff --git a/flowz/artifacts.py b/flowz/artifacts.py index 43f41d0..6ce7d56 100644 --- a/flowz/artifacts.py +++ b/flowz/artifacts.py @@ -14,7 +14,7 @@ class AbstractArtifact(object): An object that wraps the details for asynchronous access to an artifact. """ - logger = logging.getLogger('Artifact') + logger = logging.getLogger(__name__) name = None __exists__ = False @@ -117,7 +117,7 @@ def __start_get__(self): except: self.logger.exception("%s getter failure." % str(self)) raise - self.logger.info("%s retrieved." % str(self)) + self.logger.debug("%s retrieved." % str(self)) raise gen.Return(result) @@ -140,9 +140,9 @@ def __init__(self, deriver, *sources, **kw): @gen.coroutine def __start_get__(self): - self.logger.info("%s waiting on sources." % str(self)) + self.logger.debug("%s waiting on sources." % str(self)) sources = yield [maybe_artifact(source) for source in self.sources] - self.logger.info("%s running deriver." % str(self)) + self.logger.debug("%s running deriver." % str(self)) yield gen.moment try: result = self.deriver(*sources) @@ -150,7 +150,7 @@ def __start_get__(self): self.logger.exception("%s deriver failure." % str(self)) raise self.__exists__ = True - self.logger.info("%s ready." % str(self)) + self.logger.debug("%s ready." % str(self)) self.sources = None self.deriver = None raise gen.Return(result) @@ -169,12 +169,12 @@ def __init__(self, executor, deriver, *sources, **kw): @param sources: zero or more sources that can be synchronous or asychronous """ super(ThreadedDerivedArtifact, self).__init__(deriver, *sources, **kw) - self.logger.info("%s created (%s)." % (str(self), self.name)) + self.logger.debug("%s created (%s)." % (str(self), self.name)) self.executor = executor @concurrent.run_on_executor def __derive__(self, *sources): - self.logger.info("%s running deriver on executor." % str(self)) + self.logger.debug("%s running deriver on executor." % str(self)) try: return self.deriver(*sources) except: @@ -183,11 +183,11 @@ def __derive__(self, *sources): @gen.coroutine def __start_get__(self): - self.logger.info("%s waiting on sources." % str(self)) + self.logger.debug("%s waiting on sources." % str(self)) sources = yield [maybe_artifact(source) for source in self.sources] result = yield self.__derive__(*sources) self.__exists__ = True - self.logger.info("%s ready." % str(self)) + self.logger.debug("%s ready." % str(self)) self.sources = None self.deriver = None raise gen.Return(result) diff --git a/flowz/channels/management.py b/flowz/channels/management.py index 5f9b299..a78710d 100644 --- a/flowz/channels/management.py +++ b/flowz/channels/management.py @@ -1,4 +1,5 @@ import functools +import inspect class AbstractChannelAccessor(object): """ @@ -157,6 +158,7 @@ def wrapped(self): except KeyError: manager.add_builder(name, lambda: fn(self)) return manager[name] + wrapped.is_channelmethod = True return wrapped def _channelproperty(manager_name, fn): @@ -350,3 +352,30 @@ def pairs(self): return _channelmethod('channel_manager', fn_or_name) return lambda fn: _channelmethod(fn_or_name, fn) + +def get_channelmethods(obj): + """ + Returns a sorted list of the names of all channelmethods defined for an object. + """ + channelmethods = list() + # getmembers() returns a list of tuples already sorted by name + for name, method in inspect.getmembers(obj, inspect.ismethod): + # To be a channelmethod, the method must have an attribute called `is_channelmethod`, + # *and* it must be bound to the object, since it is common for channelmethods from one + # channel manager to be passed into the constructor of a subsequent channel manager! + if hasattr(method, 'is_channelmethod') and (method.__self__ == obj): + channelmethods.append(name) + return tuple(channelmethods) + + +def get_channelproperties(obj): + """ + Returns a sorted list of the names of all channelproperties defined for an object. + """ + channelproperties = list() + # getmembers() returns a list of tuples already sorted by name + for name, property in inspect.getmembers(obj.__class__, inspect.isdatadescriptor): + if hasattr(property, 'fget'): + if hasattr(property.fget, 'is_channelmethod'): + channelproperties.append(name) + return tuple(channelproperties) diff --git a/flowz/test/manager_test.py b/flowz/test/manager_test.py index 5508c80..a78aff8 100644 --- a/flowz/test/manager_test.py +++ b/flowz/test/manager_test.py @@ -299,3 +299,44 @@ def final(self): return self.build('final', self.child_a(), self.child_b()) +class IgnoreMe(object): + @management.channelmethod + def method_to_ignore(self): + pass + + +class OneOfBoth(object): + def __init__(self): + super(OneOfBoth, self).__init__() + # This tests to make sure that have an attribute that is a channelmethod passed + # from *another* object is not seen as a channelmethod for this object + self.should_ignore = IgnoreMe().method_to_ignore + + @management.channelproperty + def prop(self): + pass + + @management.channelmethod + def method(self): + pass + + +def test_get_channelmethods(): + # This test not integrated into classes above because of monkey business creating get_* methods + expected = ChannelBuildHelperCases.HELPER_NAMES + tools.assert_equals(management.get_channelmethods(TestChannelMethod()), expected) + tools.assert_equals(management.get_channelmethods(TestChannelMethodAlternateName()), expected) + tools.assert_equals(management.get_channelmethods(TestChannelProperty()), tuple()) + tools.assert_equals(management.get_channelmethods(TestChannelPropertyAlternateName()), tuple()) + + tools.assert_equals(management.get_channelmethods(OneOfBoth()), ('method',)) + + +def test_get_channelproperties(): + expected = ChannelBuildHelperCases.HELPER_NAMES + tools.assert_equals(management.get_channelproperties(TestChannelMethod()), tuple()) + tools.assert_equals(management.get_channelproperties(TestChannelMethodAlternateName()), tuple()) + tools.assert_equals(management.get_channelproperties(TestChannelProperty()), expected) + tools.assert_equals(management.get_channelproperties(TestChannelPropertyAlternateName()), expected) + + tools.assert_equals(management.get_channelproperties(OneOfBoth()), ('prop',)) diff --git a/setup.py b/setup.py index 2d5277b..a8e1ac5 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setup( name = 'flowz', - version = '0.6.0', + version = '0.7.0', description = 'Async I/O - oriented dependency programming framework', url = 'https://github.com/ethanrowe/flowz', author = 'Ethan Rowe',