lionagi 0.2.0__py3-none-any.whl → 0.2.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. lionagi/__init__.py +2 -1
  2. lionagi/core/generic/graph.py +10 -3
  3. lionagi/core/generic/node.py +5 -1
  4. lionagi/core/report/base.py +1 -0
  5. lionagi/core/report/form.py +1 -1
  6. lionagi/core/session/directive_mixin.py +1 -1
  7. lionagi/core/unit/template/plan.py +1 -1
  8. lionagi/core/work/work.py +4 -2
  9. lionagi/core/work/work_edge.py +96 -0
  10. lionagi/core/work/work_function.py +36 -4
  11. lionagi/core/work/work_function_node.py +44 -0
  12. lionagi/core/work/work_queue.py +50 -26
  13. lionagi/core/work/work_task.py +155 -0
  14. lionagi/core/work/worker.py +225 -37
  15. lionagi/core/work/worker_engine.py +179 -0
  16. lionagi/core/work/worklog.py +9 -11
  17. lionagi/tests/test_core/generic/test_structure.py +193 -0
  18. lionagi/tests/test_core/graph/__init__.py +0 -0
  19. lionagi/tests/test_core/graph/test_graph.py +70 -0
  20. lionagi/tests/test_core/graph/test_tree.py +75 -0
  21. lionagi/tests/test_core/mail/__init__.py +0 -0
  22. lionagi/tests/test_core/mail/test_mail.py +62 -0
  23. lionagi/tests/test_core/test_structure/__init__.py +0 -0
  24. lionagi/tests/test_core/test_structure/test_base_structure.py +196 -0
  25. lionagi/tests/test_core/test_structure/test_graph.py +54 -0
  26. lionagi/tests/test_core/test_structure/test_tree.py +48 -0
  27. lionagi/version.py +1 -1
  28. {lionagi-0.2.0.dist-info → lionagi-0.2.2.dist-info}/METADATA +5 -4
  29. {lionagi-0.2.0.dist-info → lionagi-0.2.2.dist-info}/RECORD +32 -18
  30. {lionagi-0.2.0.dist-info → lionagi-0.2.2.dist-info}/LICENSE +0 -0
  31. {lionagi-0.2.0.dist-info → lionagi-0.2.2.dist-info}/WHEEL +0 -0
  32. {lionagi-0.2.0.dist-info → lionagi-0.2.2.dist-info}/top_level.txt +0 -0
@@ -16,11 +16,12 @@ limitations under the License.
16
16
 
17
17
  from abc import ABC
18
18
  from functools import wraps
19
- import asyncio
19
+ import inspect
20
20
  from lionagi import logging as _logging
21
- from lionagi.libs.ln_func_call import pcall
22
21
  from lionagi.core.work.work_function import WorkFunction
23
22
  from lionagi.core.work.work import Work
23
+ from lionagi.core.report.form import Form
24
+ from lionagi.core.collections.abc import get_lion_id
24
25
 
25
26
 
26
27
  class Worker(ABC):
@@ -30,19 +31,30 @@ class Worker(ABC):
30
31
  Attributes:
31
32
  name (str): The name of the worker.
32
33
  work_functions (dict[str, WorkFunction]): Dictionary mapping assignments to WorkFunction objects.
34
+ forms (dict[str, Form]): Dictionary mapping form identifier to Form objects.
35
+ default_form (str|None): The default form to be used by the worker.
33
36
  """
34
37
 
35
38
  name: str = "Worker"
36
- work_functions: dict[str, WorkFunction] = {}
37
39
 
38
- def __init__(self) -> None:
39
- self.stopped = False
40
+ def __init__(self, forms=None, default_form=None) -> None:
41
+ """
42
+ Initializes a new instance of Worker.
43
+
44
+ Args:
45
+ forms (dict[str, Form], optional): Dictionary mapping form identifier to Form objects.
46
+ default_form (str|None, optional): The default form to be used by the worker.
47
+ """
48
+ self.work_functions: dict[str, WorkFunction] = {}
49
+ self.forms: dict[str, Form] = forms or {}
50
+ self.default_form = default_form
51
+ self._validate_worklink_functions()
40
52
 
41
53
  async def stop(self):
42
54
  """
43
55
  Stops the worker and all associated work functions.
44
56
  """
45
- self.stopped = True
57
+ # self.stopped = True
46
58
  _logging.info(f"Stopping worker {self.name}")
47
59
  non_stopped_ = []
48
60
 
@@ -69,36 +81,73 @@ class Worker(ABC):
69
81
  and not self.stopped
70
82
  )
71
83
 
72
- async def process(self, refresh_time=1):
84
+ async def change_default_form(self, form_key):
73
85
  """
74
- Processes all work functions periodically.
86
+ Changes the default form to the specified form key.
75
87
 
76
88
  Args:
77
- refresh_time (int): Time interval between each process cycle.
89
+ form_key (str): The key of the form to set as the default.
90
+
91
+ Raises:
92
+ ValueError: If the form key does not exist in the forms dictionary.
93
+
94
+ """
95
+ if form_key not in self.forms.keys():
96
+ raise ValueError(f"Unable to change default form. Key {form_key} does not exist.")
97
+ self.default_form = self.forms[form_key]
98
+
99
+ def _get_decorated_functions(self, decorator_attr, name_only=True):
78
100
  """
79
- while await self.is_progressable():
80
- await pcall([i.process(refresh_time) for i in self.work_functions.values()])
81
- await asyncio.sleep(refresh_time)
101
+ Retrieves decorated functions based on the specified decorator attribute.
102
+
103
+ Args:
104
+ decorator_attr (str): The attribute name of the decorator.
105
+ name_only (bool, optional): Whether to return only the function names. Defaults to True.
82
106
 
83
- # TODO: Implement process method
107
+ Returns:
108
+ list: List of decorated function names or tuples containing function details.
109
+ """
110
+ decorated_functions = []
111
+ for name, func in inspect.getmembers(self.__class__, predicate=inspect.isfunction):
112
+ if hasattr(func, decorator_attr):
113
+ if name_only:
114
+ decorated_functions.append(name)
115
+ else:
116
+ decorator_params = getattr(func, decorator_attr)
117
+ decorated_functions.append((name, func, decorator_params))
118
+ return decorated_functions
119
+
120
+ def _validate_worklink_functions(self):
121
+ """
122
+ Validates worklink functions to ensure they have the required parameters.
123
+ """
124
+ worklink_decorated_function = self._get_decorated_functions(decorator_attr="_worklink_decorator_params", name_only=False)
125
+ for func_name, func, _ in worklink_decorated_function:
126
+ func_signature = inspect.signature(func)
127
+ if "from_work" not in func_signature.parameters and "from_result" not in func_signature.parameters:
128
+ raise ValueError(f"Either \"from_work\" or \"from_result\" must be a parameter in function {func_name}")
84
129
 
85
- # async def process(self, refresh_time=1):
86
- # while not self.stopped:
87
- # tasks = [
88
- # asyncio.create_task(func.process(refresh_time=refresh_time))
89
- # for func in self.work_functions.values()
90
- # ]
91
- # await asyncio.wait(tasks)
92
- # await asyncio.sleep(refresh_time)
130
+ def construct_all_work_functions(self):
131
+ """
132
+ Constructs all work functions for the worker.
133
+ """
134
+ if getattr(self, "work_functions", None) is None:
135
+ self.work_functions = {}
136
+ work_decorated_function = self._get_decorated_functions(decorator_attr="_work_decorator_params", name_only=False)
137
+ for func_name, func, dec_params in work_decorated_function:
138
+ if func_name not in self.work_functions:
139
+ self.work_functions[func_name] = WorkFunction(**dec_params)
93
140
 
94
- async def _wrapper(
141
+ async def _work_wrapper(
95
142
  self,
96
143
  *args,
97
- func=None,
144
+ function=None,
98
145
  assignment=None,
146
+ form_param_key=None,
99
147
  capacity=None,
100
148
  retry_kwargs=None,
101
149
  guidance=None,
150
+ refresh_time=1,
102
151
  **kwargs,
103
152
  ):
104
153
  """
@@ -107,47 +156,90 @@ class Worker(ABC):
107
156
  Args:
108
157
  func (Callable): The function to be executed.
109
158
  assignment (str): The assignment description.
110
- capacity (int): Capacity for the work log.
159
+ form_param_key (str): The key to identify the form parameter in
160
+ the function's signature. This parameter is used to locate and fill
161
+ the appropriate form according to the assignment. Raises an error
162
+ if the form parameter key is not found in the function's signature.
163
+ capacity (int): Capacity for the work queue batch processing.
111
164
  retry_kwargs (dict): Retry arguments for the function.
112
165
  guidance (str): Guidance or documentation for the function.
113
166
  """
114
167
  if getattr(self, "work_functions", None) is None:
115
168
  self.work_functions = {}
116
169
 
117
- if func.__name__ not in self.work_functions:
118
- self.work_functions[func.__name__] = WorkFunction(
170
+ if function.__name__ not in self.work_functions:
171
+ self.work_functions[function.__name__] = WorkFunction(
119
172
  assignment=assignment,
120
- function=func,
173
+ function=function,
121
174
  retry_kwargs=retry_kwargs or {},
122
- guidance=guidance or func.__doc__,
175
+ guidance=guidance or function.__doc__,
123
176
  capacity=capacity,
177
+ refresh_time=refresh_time
124
178
  )
125
179
 
126
- work_func: WorkFunction = self.work_functions[func.__name__]
127
- task = asyncio.create_task(work_func.perform(self, *args, **kwargs))
128
- work = Work(async_task=task)
180
+ work_func: WorkFunction = self.work_functions[function.__name__]
181
+
182
+ # locate form that should be filled according to the assignment
183
+ if form_param_key:
184
+ func_signature = inspect.signature(function)
185
+ if form_param_key not in func_signature.parameters:
186
+ raise KeyError(f"Failed to locate form. \"{form_param_key}\" is not defined in the function.")
187
+ if "self" in func_signature.parameters:
188
+ bound_args = func_signature.bind(None, *args, **kwargs)
189
+ else:
190
+ bound_args = func_signature.bind(*args, **kwargs)
191
+ bound_args.apply_defaults()
192
+ arguments = bound_args.arguments
193
+
194
+ form_key = arguments.get(form_param_key)
195
+ try:
196
+ form_key = get_lion_id(form_key)
197
+ except:
198
+ pass
199
+ form = self.forms.get(form_key) or self.default_form
200
+
201
+ if form:
202
+ subform = form.__class__(assignment=work_func.assignment, task=work_func.guidance)
203
+ for k in subform.input_fields:
204
+ v = getattr(form, k, None)
205
+ setattr(subform, k, v)
206
+ subform.origin = form
207
+ kwargs = {"form": subform} | kwargs
208
+ else:
209
+ raise ValueError(f"Cannot locate form in Worker's forms and default_form is not available.")
210
+
211
+ task = work_func.perform(self, *args, **kwargs)
212
+ work = Work(async_task=task, async_task_name=work_func.name)
129
213
  await work_func.worklog.append(work)
130
- return True
214
+ return work
131
215
 
132
216
 
133
217
  def work(
134
218
  assignment=None,
219
+ form_param_key=None,
135
220
  capacity=10,
136
221
  guidance=None,
137
222
  retry_kwargs=None,
138
- refresh_time=1,
139
223
  timeout=10,
224
+ refresh_time=1
140
225
  ):
141
226
  """
142
227
  Decorator to mark a method as a work function.
143
228
 
144
229
  Args:
145
230
  assignment (str): The assignment description of the work function.
146
- capacity (int): Capacity for the work log.
231
+ form_param_key (str): The key to identify the form parameter in
232
+ the function's signature. This parameter is used to locate and fill
233
+ the appropriate form according to the assignment. Raises an error
234
+ if the form parameter key is not found in the function's signature.
235
+ capacity (int): Capacity for the work queue batch processing.
147
236
  guidance (str): Guidance or documentation for the work function.
148
237
  retry_kwargs (dict): Retry arguments for the work function.
149
- refresh_time (int): Time interval between each process cycle.
150
238
  timeout (int): Timeout for the work function.
239
+ refresh_time (int, optional): Refresh time for the work log queue.
240
+
241
+ Returns:
242
+ Callable: The decorated function.
151
243
  """
152
244
 
153
245
  def decorator(func):
@@ -157,22 +249,118 @@ def work(
157
249
  *args,
158
250
  func=func,
159
251
  assignment=assignment,
252
+ form_param_key=form_param_key,
160
253
  capacity=capacity,
161
254
  retry_kwargs=retry_kwargs,
162
255
  guidance=guidance,
256
+ refresh_time=refresh_time,
163
257
  **kwargs,
164
258
  ):
259
+ if not inspect.iscoroutinefunction(func):
260
+ raise TypeError(f"{func.__name__} must be an asynchronous function")
165
261
  retry_kwargs = retry_kwargs or {}
166
262
  retry_kwargs["timeout"] = retry_kwargs.get("timeout", timeout)
167
- return await self._wrapper(
263
+ return await self._work_wrapper(
168
264
  *args,
169
- func=func,
265
+ function=func,
170
266
  assignment=assignment,
267
+ form_param_key=form_param_key,
171
268
  capacity=capacity,
172
269
  retry_kwargs=retry_kwargs,
173
270
  guidance=guidance,
271
+ refresh_time=refresh_time,
174
272
  **kwargs,
175
273
  )
274
+ wrapper._work_decorator_params = {"assignment": assignment,
275
+ "function": func,
276
+ "retry_kwargs": retry_kwargs,
277
+ "guidance": guidance,
278
+ "capacity": capacity,
279
+ "refresh_time": refresh_time}
280
+
281
+ return wrapper
282
+
283
+ return decorator
284
+
285
+
286
+ def worklink(
287
+ from_: str,
288
+ to_: str,
289
+ auto_schedule: bool = True
290
+ ):
291
+ """
292
+ Decorator to create a link between two work functions.
293
+
294
+ Args:
295
+ from_ (str): The name of the source work function.
296
+ to_ (str): The name of the target work function.
297
+ auto_schedule (bool, optional): Whether to automatically schedule the next work function. Defaults to True.
298
+
299
+ Returns:
300
+ Callable: The decorated function.
301
+ """
302
+ def decorator(func):
303
+ @wraps(func)
304
+ async def wrapper(
305
+ self: Worker,
306
+ *args,
307
+ func=func,
308
+ from_=from_,
309
+ to_=to_,
310
+ **kwargs
311
+ ):
312
+ if not inspect.iscoroutinefunction(func):
313
+ raise TypeError(f"{func.__name__} must be an asynchronous function")
314
+
315
+ work_funcs = self._get_decorated_functions(decorator_attr="_work_decorator_params")
316
+ if from_ not in work_funcs or to_ not in work_funcs:
317
+ raise ValueError("Invalid link. 'from_' and 'to_' must be the name of work decorated functions.")
318
+
319
+ func_signature = inspect.signature(func)
320
+ if "from_work" not in func_signature.parameters and "from_result" not in func_signature.parameters:
321
+ raise ValueError(f"Either \"from_work\" or \"from_result\" must be a parameter in function {func.__name__}")
322
+
323
+ if "self" in func_signature.parameters:
324
+ bound_args = func_signature.bind(None, *args, **kwargs)
325
+ else:
326
+ bound_args = func_signature.bind(*args, **kwargs)
327
+ bound_args.apply_defaults()
328
+ arguments = bound_args.arguments
329
+ if "kwargs" in arguments:
330
+ arguments.update(arguments.pop("kwargs"))
331
+
332
+ if from_work := arguments.get("from_work"):
333
+ if not isinstance(from_work, Work):
334
+ raise ValueError("Invalid type for from_work. Only work objects are accepted.")
335
+ if from_work.async_task_name != from_:
336
+ raise ValueError(f"Invalid work object in from_work. "
337
+ f"async_task_name \"{from_work.async_task_name}\" does not match from_ \"{from_}\"")
338
+
339
+ next_params = await func(self, *args, **kwargs)
340
+ to_work_func = getattr(self, to_)
341
+ if next_params is None:
342
+ return
343
+ if isinstance(next_params, list):
344
+ if wrapper.auto_schedule:
345
+ return await to_work_func(*next_params)
346
+ elif isinstance(next_params, dict):
347
+ if wrapper.auto_schedule:
348
+ return await to_work_func(**next_params)
349
+ elif isinstance(next_params, tuple) and len(next_params) == 2:
350
+ if isinstance(next_params[0], list) and isinstance(next_params[1], dict):
351
+ if wrapper.auto_schedule:
352
+ return await to_work_func(*next_params[0], **next_params[1])
353
+ else:
354
+ raise TypeError(f"Invalid return type {func.__name__}")
355
+ else:
356
+ raise TypeError(f"Invalid return type {func.__name__}")
357
+
358
+ return next_params
359
+
360
+ wrapper.auto_schedule = auto_schedule
361
+ wrapper._worklink_decorator_params = {"func": func,
362
+ "from_": from_,
363
+ "to_": to_}
176
364
 
177
365
  return wrapper
178
366
 
@@ -0,0 +1,179 @@
1
+ import asyncio
2
+ from lionagi.core.work.work import WorkStatus
3
+ from lionagi.core.work.worker import Worker
4
+ from lionagi.core.work.work_task import WorkTask
5
+ from lionagi.core.work.work_edge import WorkEdge
6
+ from lionagi.core.work.work_function_node import WorkFunctionNode
7
+
8
+ from lionagi.core.collections.pile import pile
9
+ from lionagi.core.generic.graph import Graph
10
+
11
+
12
+ class WorkerEngine:
13
+ """
14
+ A class representing an engine that manages and executes work tasks for a worker.
15
+
16
+ Attributes:
17
+ worker (Worker): The worker instance that the engine manages.
18
+ tasks (Pile): A pile of tasks to be executed.
19
+ active_tasks (Pile): A pile of currently active tasks.
20
+ failed_tasks (Pile): A pile of tasks that have failed.
21
+ worker_graph (Graph): A graph representing the relationships between work functions.
22
+ refresh_time (int): The time interval for refreshing the work log queue.
23
+ _stop_event (asyncio.Event): An event to signal stopping the execution.
24
+ """
25
+
26
+ def __init__(self, worker: Worker, refresh_time=1):
27
+ """
28
+ Initializes a new instance of WorkerEngine.
29
+
30
+ Args:
31
+ worker (Worker): The worker instance to be managed.
32
+ refresh_time (int): The time interval for refreshing the work log queue.
33
+ """
34
+ self.worker = worker
35
+ self.tasks = pile()
36
+ self.active_tasks = pile()
37
+ self.failed_tasks = pile()
38
+ self.worker_graph = Graph()
39
+ self._construct_work_functions()
40
+ self._construct_workedges()
41
+ self.refresh_time = refresh_time
42
+ self._stop_event = asyncio.Event()
43
+
44
+ async def add_task(self, *args, task_function: str, task_name=None, task_max_steps=10, task_post_processing=None, **kwargs):
45
+ """
46
+ Adds a new task to the task queue.
47
+
48
+ Args:
49
+ task_function (str): The name of the task function to execute.
50
+ task_name (str, optional): The name of the task.
51
+ task_max_steps (int, optional): The maximum number of steps for the task.
52
+ task_post_processing (Callable, optional): The post-processing function for the task.
53
+ *args: Positional arguments for the task function.
54
+ **kwargs: Keyword arguments for the task function.
55
+
56
+ Returns:
57
+ WorkTask: The newly created task.
58
+ """
59
+ task = WorkTask(name=task_name, max_steps=task_max_steps, post_processing=task_post_processing)
60
+ self.tasks.append(task)
61
+ function = getattr(self.worker, task_function)
62
+ work = await function(*args, **kwargs)
63
+ task.current_work = work
64
+ task.work_history.append(work)
65
+ return task
66
+
67
+ async def activate_work_queues(self):
68
+ """
69
+ Activates the work queues for all work functions.
70
+ """
71
+ for work_function in self.worker.work_functions.values():
72
+ if not work_function.worklog.queue.execution_mode:
73
+ asyncio.create_task(work_function.worklog.queue.execute())
74
+
75
+ async def stop(self):
76
+ """
77
+ Stops the execution of tasks.
78
+ """
79
+ self._stop_event.set()
80
+
81
+ @property
82
+ def stopped(self) -> bool:
83
+ """
84
+ Checks if the execution has been stopped.
85
+
86
+ Returns:
87
+ bool: True if stopped, otherwise False.
88
+ """
89
+ return self._stop_event.is_set()
90
+
91
+ async def process(self, task: WorkTask):
92
+ """
93
+ Processes a single task.
94
+
95
+ Args:
96
+ task (WorkTask): The task to be processed.
97
+ """
98
+ current_work_func_name = task.current_work.async_task_name
99
+ current_work_func = self.worker.work_functions[current_work_func_name]
100
+ updated_task = await task.process(current_work_func)
101
+ if updated_task == "COMPLETED":
102
+ self.active_tasks.pop(task)
103
+ elif updated_task == "FAILED":
104
+ self.active_tasks.pop(task)
105
+ self.failed_tasks.append(task)
106
+ elif isinstance(updated_task, list):
107
+ self.tasks.include(updated_task)
108
+ self.active_tasks.include(updated_task)
109
+ else:
110
+ await asyncio.sleep(self.refresh_time)
111
+
112
+ async def execute(self, stop_queue=True):
113
+ """
114
+ Executes all tasks in the task queue.
115
+
116
+ Args:
117
+ stop_queue (bool, optional): Whether to stop the queue after execution. Defaults to True.
118
+ """
119
+ for task in self.tasks:
120
+ if task.status == WorkStatus.PENDING:
121
+ task.status = WorkStatus.IN_PROGRESS
122
+ self.active_tasks.append(task)
123
+
124
+ await self.activate_work_queues()
125
+ self._stop_event.clear()
126
+
127
+ while len(self.active_tasks) > 0 and not self.stopped:
128
+ for work_function in self.worker.work_functions.values():
129
+ if len(work_function.worklog.pending) > 0:
130
+ await work_function.worklog.forward()
131
+ tasks = list(self.active_tasks)
132
+ await asyncio.gather(*[self.process(task) for task in tasks])
133
+ await asyncio.sleep(self.refresh_time)
134
+
135
+ if stop_queue:
136
+ await self.stop()
137
+ await self.worker.stop()
138
+
139
+ async def execute_lasting(self):
140
+ """
141
+ Executes tasks continuously until stopped.
142
+ """
143
+ self._stop_event.clear()
144
+
145
+ async def execute_lasting_inner():
146
+ while not self.stopped:
147
+ await self.execute(stop_queue=False)
148
+ await asyncio.sleep(self.refresh_time)
149
+ asyncio.create_task(execute_lasting_inner())
150
+
151
+ def _construct_work_functions(self):
152
+ """
153
+ Constructs work functions for the worker.
154
+ """
155
+ if getattr(self.worker, "work_functions", None) is None:
156
+ self.worker.work_functions = {}
157
+ work_decorated_function = self.worker._get_decorated_functions(decorator_attr="_work_decorator_params",
158
+ name_only=False)
159
+ for func_name, func, dec_params in work_decorated_function:
160
+ if func_name not in self.worker.work_functions:
161
+ self.worker.work_functions[func_name] = WorkFunctionNode(**dec_params)
162
+ self.worker_graph.add_node(self.worker.work_functions[func_name])
163
+ else:
164
+ if not isinstance(self.worker.work_functions[func_name], WorkFunctionNode):
165
+ raise TypeError(f"WorkFunction {func_name} already exists but is not a WorkFunctionNode. "
166
+ f"If you would like to use it in WorkerEngine, please convert it to a "
167
+ f"WorkFunctionNode, or initiate a new worker, or pop it from work_function dict")
168
+
169
+ def _construct_workedges(self):
170
+ """
171
+ Constructs work edges for the worker graph.
172
+ """
173
+ worklink_decorated_function = self.worker._get_decorated_functions(decorator_attr="_worklink_decorator_params",
174
+ name_only=False)
175
+
176
+ for func_name, func, dec_params in worklink_decorated_function:
177
+ head = self.worker.work_functions[dec_params["from_"]]
178
+ tail = self.worker.work_functions[dec_params["to_"]]
179
+ self.worker_graph.add_edge(head=head, tail=tail, convert_function=func, associated_worker=self.worker, edge_class=WorkEdge)
@@ -30,19 +30,21 @@ class WorkLog(Progressable):
30
30
  queue (WorkQueue): A queue to manage the execution of work items.
31
31
  """
32
32
 
33
- def __init__(self, capacity=10, workpile=None):
33
+ def __init__(self, capacity=10, workpile=None, refresh_time=1):
34
34
  """
35
35
  Initializes a new instance of WorkLog.
36
36
 
37
37
  Args:
38
- capacity (int): The capacity of the work queue.
38
+ capacity (int): The capacity of the work queue batch processing.
39
39
  workpile (Pile, optional): An optional pile of initial work items.
40
+ refresh_time (int, optional): The time interval to refresh the work log queue.
41
+ Defaults to 1.
40
42
  """
41
43
  self.pile = (
42
44
  workpile if workpile and isinstance(workpile, Pile) else pile({}, Work)
43
45
  )
44
46
  self.pending = progression(workpile) if workpile else progression()
45
- self.queue = WorkQueue(capacity=capacity)
47
+ self.queue = WorkQueue(capacity=capacity, refresh_time=refresh_time)
46
48
 
47
49
  async def append(self, work: Work):
48
50
  """
@@ -56,15 +58,11 @@ class WorkLog(Progressable):
56
58
 
57
59
  async def forward(self):
58
60
  """
59
- Forwards pending work items to the queue if capacity allows.
61
+ Forwards pending work items to the queue.
60
62
  """
61
- if not self.queue.available_capacity:
62
- return
63
- else:
64
- while len(self.pending) > 0 and self.queue.available_capacity:
65
- work: Work = self.pile[self.pending.popleft()]
66
- work.status = WorkStatus.IN_PROGRESS
67
- await self.queue.enqueue(work)
63
+ while len(self.pending) > 0:
64
+ work: Work = self.pile[self.pending.popleft()]
65
+ await self.queue.enqueue(work)
68
66
 
69
67
  async def stop(self):
70
68
  """