22from glob import glob
33import os
44import time
5- import tornado .ioloop
65from tornado import gen
76
87from .core import Stream , convert_interval , RefCounter
98
109
11- def PeriodicCallback (callback , callback_time , asynchronous = False , ** kwargs ):
12- source = Stream (asynchronous = asynchronous )
13-
14- def _ ():
15- result = callback ()
16- source ._emit (result )
17-
18- pc = tornado .ioloop .PeriodicCallback (_ , callback_time , ** kwargs )
19- pc .start ()
20- pc .start ()
21- return source
22-
23-
2410def sink_to_file (filename , upstream , mode = 'w' , prefix = '' , suffix = '\n ' , flush = False ):
2511 file = open (filename , mode = mode )
2612
@@ -34,6 +20,17 @@ def write(text):
3420
3521
3622class Source (Stream ):
23+ """Start node for a set of Streams
24+
25+ Source nodes emit data into other nodes. They typically get this data
26+ by polling external sources, and are necessarily run by an event loop.
27+
28+ Parameters
29+ ----------
30+ start: bool
31+ Whether to call the run method immediately. If False, nothing
32+ will happen until ``source.start()`` is called.
33+ """
3734 _graphviz_shape = 'doubleoctagon'
3835
3936 def __init__ (self , start = False , ** kwargs ):
@@ -49,15 +46,68 @@ def stop(self):
4946 self .stopped = True
5047
5148 def start (self ):
52- """start polling"""
53- self .stopped = False
54- self .started = True
55- self .loop .add_callback (self .run )
49+ """start polling
50+
51+ If already running, this has no effect. If the source was started and then
52+ stopped again, this will restart the ``self.run`` coroutine.
53+ """
54+ if self .stopped :
55+ self .stopped = False
56+ self .started = True
57+ self .loop .add_callback (self .run )
5658
5759 async def run (self ):
60+ """This coroutine will be invoked by start() and emit all data
61+
62+ You might either overrive ``_run()`` when all logic can be contained
63+ there, or override this method directly.
64+
65+ Note the use of ``.stopped`` to halt the coroutine, whether or not
66+
67+ """
5868 while not self .stopped :
5969 await self ._run ()
6070
71+ async def _run (self ):
72+ """This is the functionality to run on each cycle
73+
74+ Typically this may be used for polling some external IO source
75+ or time-based data emission. You might choose to include an
76+ ``await asyncio.sleep()`` for the latter.
77+ """
78+ raise NotImplementedError
79+
80+
81+ @Stream .register_api (staticmethod )
82+ class from_periodic (Source ):
83+ """Generate data from a function on given period
84+
85+ cf ``streamz.dataframe.PeriodicDataFrame``
86+
87+ Parameters
88+ ----------
89+ callback: callable
90+ Function to call on each iteration. Takes no arguments.
91+ poll_interval: float
92+ Time to sleep between calls (s)
93+ """
94+
95+ def __init__ (self , callback , poll_interval = 0.1 , ** kwargs ):
96+ self ._cb = callback
97+ self ._poll = poll_interval
98+ super ().__init__ (** kwargs )
99+
100+ async def _run (self ):
101+ await asyncio .gather (self ._emit (self ._cb ()))
102+ await asyncio .sleep (self ._poll )
103+
104+
105+ def PeriodicCallback (callback , callback_time , asynchronous = False , ** kwargs ): # pragma: no cover
106+ """For backward compatibility - please use Stream.from_periodic"""
107+ if kwargs :
108+ callback = lambda : callback (** kwargs )
109+ return Stream .from_periodic (callback , callback_time , asynchronous = asynchronous )
110+
61111
62112@Stream .register_api (staticmethod )
63113class from_textfile (Source ):
0 commit comments