@@ -122,7 +122,79 @@ def __str__(self):
122122 __repr__ = __str__
123123
124124
125- class Stream (object ):
125+ class APIRegisterMixin (object ):
126+
127+ @classmethod
128+ def register_api (cls , modifier = identity , attribute_name = None ):
129+ """ Add callable to Stream API
130+
131+ This allows you to register a new method onto this class. You can use
132+ it as a decorator.::
133+
134+ >>> @Stream.register_api()
135+ ... class foo(Stream):
136+ ... ...
137+
138+ >>> Stream().foo(...) # this works now
139+
140+ It attaches the callable as a normal attribute to the class object. In
141+ doing so it respects inheritance (all subclasses of Stream will also
142+ get the foo attribute).
143+
144+ By default callables are assumed to be instance methods. If you like
145+ you can include modifiers to apply before attaching to the class as in
146+ the following case where we construct a ``staticmethod``.
147+
148+ >>> @Stream.register_api(staticmethod)
149+ ... class foo(Stream):
150+ ... ...
151+
152+ >>> Stream.foo(...) # Foo operates as a static method
153+
154+ You can also provide an optional ``attribute_name`` argument to control
155+ the name of the attribute your callable will be attached as.
156+
157+ >>> @Stream.register_api(attribute_name="bar")
158+ ... class foo(Stream):
159+ ... ...
160+
161+ >> Stream().bar(...) # foo was actually attached as bar
162+ """
163+ def _ (func ):
164+ @functools .wraps (func )
165+ def wrapped (* args , ** kwargs ):
166+ return func (* args , ** kwargs )
167+ name = attribute_name if attribute_name else func .__name__
168+ setattr (cls , name , modifier (wrapped ))
169+ return func
170+ return _
171+
172+ @classmethod
173+ def register_plugin_entry_point (cls , entry_point , modifier = identity ):
174+ if hasattr (cls , entry_point .name ):
175+ raise ValueError (
176+ f"Can't add { entry_point .name } from { entry_point .module_name } "
177+ f"to { cls .__name__ } : duplicate method name."
178+ )
179+
180+ def stub (* args , ** kwargs ):
181+ """ Entrypoints-based streamz plugin. Will be loaded on first call. """
182+ node = entry_point .load ()
183+ if not issubclass (node , Stream ):
184+ raise TypeError (
185+ f"Error loading { entry_point .name } "
186+ f"from module { entry_point .module_name } : "
187+ f"{ node .__class__ .__name__ } must be a subclass of Stream"
188+ )
189+ if getattr (cls , entry_point .name ).__name__ == "stub" :
190+ cls .register_api (
191+ modifier = modifier , attribute_name = entry_point .name
192+ )(node )
193+ return node (* args , ** kwargs )
194+ cls .register_api (modifier = modifier , attribute_name = entry_point .name )(stub )
195+
196+
197+ class Stream (APIRegisterMixin ):
126198 """ A Stream is an infinite sequence of data.
127199
128200 Streams subscribe to each other passing and transforming data between them.
@@ -179,6 +251,8 @@ def __init__(self, upstream=None, upstreams=None, stream_name=None,
179251 loop = None , asynchronous = None , ensure_io_loop = False ):
180252 self .name = stream_name
181253 self .downstreams = OrderedWeakrefSet ()
254+ self .current_value = None
255+ self .current_metadata = None
182256 if upstreams is not None :
183257 self .upstreams = list (upstreams )
184258 elif upstream is not None :
@@ -267,80 +341,16 @@ def _remove_upstream(self, upstream):
267341 classes which handle stream specific buffers/caches"""
268342 self .upstreams .remove (upstream )
269343
270- @classmethod
271- def register_api (cls , modifier = identity , attribute_name = None ):
272- """ Add callable to Stream API
273-
274- This allows you to register a new method onto this class. You can use
275- it as a decorator.::
276-
277- >>> @Stream.register_api()
278- ... class foo(Stream):
279- ... ...
280-
281- >>> Stream().foo(...) # this works now
282-
283- It attaches the callable as a normal attribute to the class object. In
284- doing so it respects inheritance (all subclasses of Stream will also
285- get the foo attribute).
286-
287- By default callables are assumed to be instance methods. If you like
288- you can include modifiers to apply before attaching to the class as in
289- the following case where we construct a ``staticmethod``.
290-
291- >>> @Stream.register_api(staticmethod)
292- ... class foo(Stream):
293- ... ...
294-
295- >>> Stream.foo(...) # Foo operates as a static method
296-
297- You can also provide an optional ``attribute_name`` argument to control
298- the name of the attribute your callable will be attached as.
299-
300- >>> @Stream.register_api(attribute_name="bar")
301- ... class foo(Stream):
302- ... ...
303-
304- >> Stream().bar(...) # foo was actually attached as bar
305- """
306- def _ (func ):
307- @functools .wraps (func )
308- def wrapped (* args , ** kwargs ):
309- return func (* args , ** kwargs )
310- name = attribute_name if attribute_name else func .__name__
311- setattr (cls , name , modifier (wrapped ))
312- return func
313- return _
314-
315- @classmethod
316- def register_plugin_entry_point (cls , entry_point , modifier = identity ):
317- if hasattr (cls , entry_point .name ):
318- raise ValueError (
319- f"Can't add { entry_point .name } from { entry_point .module_name } "
320- f"to { cls .__name__ } : duplicate method name."
321- )
322-
323- def stub (* args , ** kwargs ):
324- """ Entrypoints-based streamz plugin. Will be loaded on first call. """
325- node = entry_point .load ()
326- if not issubclass (node , Stream ):
327- raise TypeError (
328- f"Error loading { entry_point .name } "
329- f"from module { entry_point .module_name } : "
330- f"{ node .__class__ .__name__ } must be a subclass of Stream"
331- )
332- if getattr (cls , entry_point .name ).__name__ == "stub" :
333- cls .register_api (
334- modifier = modifier , attribute_name = entry_point .name
335- )(node )
336- return node (* args , ** kwargs )
337- cls .register_api (modifier = modifier , attribute_name = entry_point .name )(stub )
338-
339344 def start (self ):
340345 """ Start any upstream sources """
341346 for upstream in self .upstreams :
342347 upstream .start ()
343348
349+ def stop (self ):
350+ """ Stop upstream sources """
351+ for upstream in self .upstreams :
352+ upstream .stop ()
353+
344354 def __str__ (self ):
345355 s_list = []
346356 if self .name :
@@ -430,6 +440,8 @@ def _emit(self, x, metadata=None):
430440 A reference counter used to check when data is done
431441
432442 """
443+ self .current_value = x
444+ self .current_metadata = metadata
433445 if metadata :
434446 self ._retain_refs (metadata , len (self .downstreams ))
435447 else :
@@ -548,13 +560,6 @@ def remove(self, predicate):
548560 """ Only pass through elements for which the predicate returns False """
549561 return self .filter (lambda x : not predicate (x ))
550562
551- def stop (self ):
552- """Call on any stream node to halt all upstream sources"""
553- prev , s = self .upstream , self
554- while s :
555- prev , s = s , s .upstream
556- prev .stopped = True
557-
558563 @property
559564 def scan (self ):
560565 return self .accumulate
0 commit comments