pygpt-net 2.6.11__py3-none-any.whl → 2.6.13__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.
pygpt_net/CHANGELOG.txt CHANGED
@@ -1,3 +1,14 @@
1
+ 2.6.13 (2025-08-19)
2
+
3
+ - Fix: Do not load the index in experts if it is not provided.
4
+ - Fix: Load remote images in the webview.
5
+ - Fix: Presets list refresh.
6
+ - Optimize context items reload.
7
+
8
+ 2.6.12 (2025-08-19)
9
+
10
+ - Optimized web renderer memory cleanup.
11
+
1
12
  2.6.11 (2025-08-18)
2
13
 
3
14
  - Added the ability to close the dialog window with the Esc key.
pygpt_net/__init__.py CHANGED
@@ -6,15 +6,15 @@
6
6
  # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
7
  # MIT License #
8
8
  # Created By : Marcin Szczygliński #
9
- # Updated Date: 2025.08.18 00:00:00 #
9
+ # Updated Date: 2025.08.19 00:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  __author__ = "Marcin Szczygliński"
13
13
  __copyright__ = "Copyright 2025, Marcin Szczygliński"
14
14
  __credits__ = ["Marcin Szczygliński"]
15
15
  __license__ = "MIT"
16
- __version__ = "2.6.11"
17
- __build__ = "2025-08-18"
16
+ __version__ = "2.6.13"
17
+ __build__ = "2025-08-19"
18
18
  __maintainer__ = "Marcin Szczygliński"
19
19
  __github__ = "https://github.com/szczyglis-dev/py-gpt"
20
20
  __report__ = "https://github.com/szczyglis-dev/py-gpt/issues"
pygpt_net/app.py CHANGED
@@ -6,7 +6,7 @@
6
6
  # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
7
  # MIT License #
8
8
  # Created By : Marcin Szczygliński #
9
- # Updated Date: 2025.08.16 00:00:00 #
9
+ # Updated Date: 2025.08.19 07:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import os
@@ -25,6 +25,12 @@ if platform.system() == 'Windows':
25
25
  # enable debug logging
26
26
  # os.environ["QT_LOGGING_RULES"] = "*.debug=true"
27
27
  # os.environ["QTWEBENGINE_REMOTE_DEBUGGING"] = "9222"
28
+ os.environ["QTWEBENGINE_CHROMIUM_FLAGS"] = (
29
+ "--renderer-process-limit=1 "
30
+ "--process-per-site "
31
+ "--enable-precise-memory-info "
32
+ "--js-flags=--expose-gc"
33
+ )
28
34
 
29
35
  _original_open = builtins.open
30
36
 
@@ -291,8 +291,8 @@ class Output:
291
291
 
292
292
  if mode != MODE_ASSISTANT:
293
293
  self.window.controller.kernel.stack.handle() # handle reply
294
- event = RenderEvent(RenderEvent.RELOAD)
295
- self.window.dispatch(event) # reload chat window
294
+ # event = RenderEvent(RenderEvent.RELOAD)
295
+ # self.window.dispatch(event) # reload chat window
296
296
 
297
297
  mem_clean()
298
298
 
@@ -6,7 +6,7 @@
6
6
  # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
7
  # MIT License #
8
8
  # Created By : Marcin Szczygliński #
9
- # Updated Date: 2025.08.18 01:00:00 #
9
+ # Updated Date: 2025.08.19 07:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from typing import Dict, Any
@@ -238,7 +238,7 @@ class Response:
238
238
  self.window.core.ctx.update_item(ctx)
239
239
 
240
240
  # update ctx meta
241
- if mode in [MODE_AGENT_LLAMA, MODE_AGENT_OPENAI] and ctx.meta is not None:
241
+ if mode in (MODE_AGENT_LLAMA, MODE_AGENT_OPENAI) and ctx.meta is not None:
242
242
  self.window.core.ctx.replace(ctx.meta)
243
243
  self.window.core.ctx.save(ctx.meta.id)
244
244
  # update preset if exists
@@ -265,11 +265,16 @@ class Response:
265
265
  self.window.dispatch(event) # show cmd waiting
266
266
  self.window.controller.chat.output.handle_end(ctx, mode) # handle end.
267
267
 
268
- event = RenderEvent(RenderEvent.RELOAD)
268
+ data = {
269
+ "meta": ctx.meta,
270
+ "ctx": ctx,
271
+ "stream": self.window.core.config.get("stream", False),
272
+ }
273
+ event = RenderEvent(RenderEvent.END, data)
269
274
  self.window.dispatch(event)
270
275
 
271
276
  # if continue reasoning
272
- if global_mode not in [MODE_AGENT_LLAMA, MODE_AGENT_OPENAI]:
277
+ if global_mode not in (MODE_AGENT_LLAMA, MODE_AGENT_OPENAI):
273
278
  return # no agent mode, nothing to do
274
279
 
275
280
  if ctx.extra is None or (type(ctx.extra) == dict and "agent_finish" not in ctx.extra):
@@ -6,7 +6,7 @@
6
6
  # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
7
  # MIT License #
8
8
  # Created By : Marcin Szczygliński #
9
- # Updated Date: 2025.08.18 01:00:00 #
9
+ # Updated Date: 2025.08.19 07:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import base64
@@ -65,6 +65,7 @@ class StreamWorker(QRunnable):
65
65
  self.signals = WorkerSignals()
66
66
  self.ctx = ctx
67
67
  self.window = window
68
+ self.stream = None
68
69
 
69
70
  @Slot()
70
71
  def run(self):
@@ -90,8 +91,7 @@ class StreamWorker(QRunnable):
90
91
  force_func_call = False
91
92
  stopped = False
92
93
  chunk_type: ChunkType = "raw"
93
- generator = ctx.stream
94
- ctx.stream = None
94
+ generator = self.stream
95
95
 
96
96
  base_data = {
97
97
  "meta": ctx.meta,
@@ -363,6 +363,7 @@ class StreamWorker(QRunnable):
363
363
  finally:
364
364
  output = "".join(output_parts)
365
365
  output_parts.clear()
366
+ del output_parts
366
367
 
367
368
  if has_unclosed_code_tag(output):
368
369
  output += "\n```"
@@ -374,11 +375,14 @@ class StreamWorker(QRunnable):
374
375
  pass
375
376
 
376
377
  del generator
378
+ self.stream = None
377
379
 
378
380
  ctx.output = output
379
381
  ctx.set_tokens(ctx.input_tokens, output_tokens)
380
382
  core.ctx.update_item(ctx)
381
383
 
384
+ output = None
385
+
382
386
  if files and not stopped:
383
387
  core.debug.info("[chat] Container files found, downloading...")
384
388
  try:
@@ -464,11 +468,13 @@ class Stream:
464
468
  self.extra = extra if extra is not None else {}
465
469
 
466
470
  worker = StreamWorker(ctx, self.window)
467
- self.worker = worker
468
471
 
472
+ worker.stream = ctx.stream
469
473
  worker.signals.eventReady.connect(self.handleEvent)
470
474
  worker.signals.errorOccurred.connect(self.handleError)
471
475
  worker.signals.end.connect(self.handleEnd)
476
+ ctx.stream = None
477
+ self.worker = worker
472
478
 
473
479
  self.window.core.debug.info("[chat] Stream begin...")
474
480
  self.window.threadpool.start(worker)
@@ -900,7 +900,7 @@ class Editor:
900
900
  if not is_new:
901
901
  no_scroll = True
902
902
  self.window.core.presets.save(id)
903
- self.window.controller.presets.refresh(no_scroll=no_scroll)
903
+ self.window.controller.presets.refresh()
904
904
 
905
905
  # close dialog
906
906
  if close:
@@ -237,6 +237,7 @@ class Runner:
237
237
  model = context.model
238
238
  vector_store_idx = extra.get("agent_idx", None)
239
239
  system_prompt = context.system_prompt
240
+ is_expert_call = context.is_expert_call
240
241
  max_steps = self.window.core.config.get("agent.llama.steps", 10)
241
242
  is_cmd = self.window.core.command.is_cmd(inline=False)
242
243
  llm = self.window.core.idx.llm.get(model, stream=False)
@@ -301,6 +302,7 @@ class Runner:
301
302
  "verbose": verbose,
302
303
  "history": history,
303
304
  "llm": llm,
305
+ "is_expert_call": is_expert_call,
304
306
  }
305
307
  # TODO: add support for other modes
306
308
  if mode == AGENT_MODE_WORKFLOW:
@@ -106,6 +106,7 @@ class LlamaWorkflow(BaseRunner):
106
106
  verbose: bool = False,
107
107
  history: List[CtxItem] = None,
108
108
  llm: Any = None,
109
+ is_expert_call: bool = False,
109
110
  ) -> Union[CtxItem, None]:
110
111
  """
111
112
  Run agent workflow
@@ -117,6 +118,7 @@ class LlamaWorkflow(BaseRunner):
117
118
  :param verbose: verbose mode
118
119
  :param history: chat history
119
120
  :param llm: LLM instance
121
+ :param is_expert_call: if True, run as expert call
120
122
  :return: True if success
121
123
  """
122
124
  if self.is_stopped():
@@ -124,6 +126,9 @@ class LlamaWorkflow(BaseRunner):
124
126
 
125
127
  memory = self.window.core.idx.chat.get_memory_buffer(history, llm)
126
128
  agent_ctx = Context(agent)
129
+ flush = True
130
+ if is_expert_call:
131
+ flush = False
127
132
  try:
128
133
  ctx = await self.run_agent(
129
134
  agent=agent,
@@ -134,6 +139,7 @@ class LlamaWorkflow(BaseRunner):
134
139
  item_ctx=ctx,
135
140
  signals=signals,
136
141
  use_partials=False, # use partials for streaming
142
+ flush=flush, # flush output buffer to webview
137
143
  )
138
144
  except WorkflowCancelledByUser:
139
145
  print("\n\n[STOP] Workflow stopped by user.")
@@ -202,6 +208,7 @@ class LlamaWorkflow(BaseRunner):
202
208
  item_ctx: Optional[CtxItem] = None,
203
209
  signals: Optional[BridgeSignals] = None,
204
210
  use_partials: bool = True,
211
+ flush: bool = True,
205
212
  ):
206
213
  """
207
214
  Run agent workflow
@@ -215,6 +222,7 @@ class LlamaWorkflow(BaseRunner):
215
222
  :param item_ctx: Optional CtxItem for additional context
216
223
  :param signals: Optional BridgeSignals for communication
217
224
  :param use_partials: If True, use partial context items for streaming
225
+ :param flush: If True, flush the output buffer before starting
218
226
  :return: handler for the agent workflow
219
227
  """
220
228
  handler = agent.run(
@@ -237,7 +245,8 @@ class LlamaWorkflow(BaseRunner):
237
245
  # persist current output on stop
238
246
  item_ctx.output = item_ctx.live_output
239
247
  self.window.core.ctx.update_item(item_ctx)
240
- self.end_stream(item_ctx, signals)
248
+ if flush:
249
+ self.end_stream(item_ctx, signals)
241
250
  await handler.cancel_run() # cancel, will raise WorkflowCancelledByUser
242
251
  break
243
252
  if isinstance(event, ToolCallResult):
@@ -247,7 +256,7 @@ class LlamaWorkflow(BaseRunner):
247
256
  formatted = "\n```output\n" + str(event.tool_output) + "\n```\n"
248
257
  item_ctx.live_output += formatted
249
258
  item_ctx.stream = formatted
250
- if item_ctx.stream_agent_output:
259
+ if item_ctx.stream_agent_output and flush:
251
260
  self.send_stream(item_ctx, signals, begin)
252
261
  elif isinstance(event, ToolCall):
253
262
  if "code" in event.tool_kwargs:
@@ -257,7 +266,7 @@ class LlamaWorkflow(BaseRunner):
257
266
  formatted = "\n```python\n" + str(event.tool_kwargs['code']) + "\n```\n"
258
267
  item_ctx.live_output += formatted
259
268
  item_ctx.stream = formatted
260
- if item_ctx.stream_agent_output:
269
+ if item_ctx.stream_agent_output and flush:
261
270
  self.send_stream(item_ctx, signals, begin)
262
271
  elif isinstance(event, StepEvent):
263
272
  self.set_busy(signals)
@@ -278,7 +287,7 @@ class LlamaWorkflow(BaseRunner):
278
287
  if event.delta:
279
288
  item_ctx.live_output += event.delta
280
289
  item_ctx.stream = event.delta
281
- if item_ctx.stream_agent_output:
290
+ if item_ctx.stream_agent_output and flush:
282
291
  self.send_stream(item_ctx, signals, begin) # send stream to webview
283
292
  begin = False
284
293
  elif isinstance(event, AgentOutput):
pygpt_net/core/ctx/bag.py CHANGED
@@ -6,7 +6,7 @@
6
6
  # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
7
  # MIT License #
8
8
  # Created By : Marcin Szczygliński #
9
- # Updated Date: 2025.08.11 00:00:00 #
9
+ # Updated Date: 2025.08.19 07:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from typing import List
@@ -44,6 +44,7 @@ class Bag:
44
44
  def clear_items(self):
45
45
  """Clear items"""
46
46
  self.items.clear()
47
+ self.items = []
47
48
 
48
49
  def count_items(self) -> int:
49
50
  """
@@ -6,7 +6,7 @@
6
6
  # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
7
  # MIT License #
8
8
  # Created By : Marcin Szczygliński #
9
- # Updated Date: 2025.08.18 01:00:00 #
9
+ # Updated Date: 2025.08.19 07:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import gc
@@ -338,8 +338,17 @@ class Debug:
338
338
  :param label: label for memory usage
339
339
  :return: formatted memory usage string
340
340
  """
341
- mem_mb = self._process.memory_info().rss / (1024 * 1024)
342
- data = f"Memory Usage: {mem_mb:.2f} MB"
341
+ rss_mb = self._process.memory_info().rss / (1024 * 1024)
342
+ uss_mb = getattr(self._process.memory_full_info(), "uss", 0) / 1024 / 1024
343
+ data = f"RSS={rss_mb:.0f} MB USS={uss_mb:.0f} MB"
344
+
345
+ children_parts = []
346
+ for c in self._process.children(recursive=True):
347
+ children_parts.append(
348
+ f"{c.pid} {c.name()} {round(c.memory_info().rss / 1024 / 1024)} MB"
349
+ )
350
+ if children_parts:
351
+ data += "\n" + "\n".join(children_parts)
343
352
  print(f"[{label}] {data}")
344
353
  return data
345
354
 
@@ -651,7 +651,7 @@ class ExpertWorker(QRunnable):
651
651
 
652
652
  # index to use
653
653
  use_index = False
654
- if db_idx:
654
+ if db_idx and db_idx != '_':
655
655
  use_index = True
656
656
  if use_index:
657
657
  index, llm = self.window.core.idx.chat.get_index(db_idx, model_data, stream=False)
@@ -213,14 +213,13 @@ class Presets:
213
213
  """
214
214
  attr = self._MODE_TO_ATTR.get(mode)
215
215
  if not attr:
216
- raise IndexError(idx)
216
+ return
217
217
  i = 0
218
218
  for key, item in self.items.items():
219
219
  if getattr(item, attr, False):
220
220
  if i == idx:
221
221
  return key
222
222
  i += 1
223
- raise IndexError(idx)
224
223
 
225
224
  def get_by_id(self, mode: str, id: str) -> Optional[PresetItem]:
226
225
  """
@@ -6,7 +6,7 @@
6
6
  # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
7
  # MIT License #
8
8
  # Created By : Marcin Szczygliński #
9
- # Updated Date: 2025.08.18 01:00:00 #
9
+ # Updated Date: 2025.08.19 07:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import os
@@ -42,9 +42,7 @@ class Body:
42
42
  <script type="text/javascript" src="qrc:///js/highlight.min.js"></script>
43
43
  <script type="text/javascript" src="qrc:///js/katex.min.js"></script>
44
44
  <script>
45
-
46
45
  const DEBUG_MODE = false;
47
-
48
46
  let scrollTimeout = null;
49
47
  let prevScroll = 0;
50
48
  let bridge;
@@ -69,6 +67,23 @@ class Body:
69
67
  let pendingHighlightMath = false;
70
68
  let scrollScheduled = false;
71
69
 
70
+ // timers
71
+ let tipsTimers = [];
72
+
73
+ // clear previous references
74
+ function resetEphemeralDomRefs() {
75
+ domLastCodeBlock = null;
76
+ domLastParagraphBlock = null;
77
+ }
78
+ function dropIfDetached() {
79
+ if (domLastCodeBlock && !domLastCodeBlock.isConnected) domLastCodeBlock = null;
80
+ if (domLastParagraphBlock && !domLastParagraphBlock.isConnected) domLastParagraphBlock = null;
81
+ }
82
+ function stopTipsTimers() {
83
+ tipsTimers.forEach(clearTimeout);
84
+ tipsTimers = [];
85
+ }
86
+
72
87
  history.scrollRestoration = "manual";
73
88
  document.addEventListener('keydown', function(event) {
74
89
  if (event.ctrlKey && event.key === 'f') {
@@ -137,6 +152,7 @@ class Body:
137
152
  }
138
153
  function hideTips() {
139
154
  if (tips_hidden) return;
155
+ stopTipsTimers();
140
156
  const t = els.tips || document.getElementById('tips');
141
157
  if (t) t.style.display = 'none';
142
158
  tips_hidden = true;
@@ -151,21 +167,23 @@ class Body:
151
167
  function cycleTips() {
152
168
  if (tips_hidden) return;
153
169
  if (tips.length === 0) return;
154
- let tipContainer = els.tips || document.getElementById('tips');
155
170
  let currentTip = 0;
156
171
  function showNextTip() {
157
172
  if (tips_hidden) return;
173
+ const tipContainer = els.tips || document.getElementById('tips');
174
+ if (!tipContainer) return;
158
175
  tipContainer.innerHTML = tips[currentTip];
159
176
  tipContainer.classList.add('visible');
160
- setTimeout(function() {
177
+ tipsTimers.push(setTimeout(function() {
161
178
  if (tips_hidden) return;
162
179
  tipContainer.classList.remove('visible');
163
- setTimeout(function(){
180
+ tipsTimers.push(setTimeout(function(){
164
181
  currentTip = (currentTip + 1) % tips.length;
165
182
  showNextTip();
166
- }, 1000);
167
- }, 15000);
183
+ }, 1000));
184
+ }, 15000));
168
185
  }
186
+ stopTipsTimers();
169
187
  showNextTip();
170
188
  }
171
189
  function renderMath(root) {
@@ -224,7 +242,7 @@ class Body:
224
242
  }
225
243
  }
226
244
  function getStreamContainer() {
227
- if (domOutputStream && document.body.contains(domOutputStream)) {
245
+ if (domOutputStream && domOutputStream.isConnected) {
228
246
  return domOutputStream;
229
247
  }
230
248
  let element = els.appendOutput || document.getElementById('_append_output_');
@@ -247,6 +265,24 @@ class Body:
247
265
  scheduleScroll();
248
266
  }
249
267
  }
268
+ function clean() {
269
+ if (DEBUG_MODE) {
270
+ log("-- CLEAN DOM --");
271
+ }
272
+ const el = els.nodes || document.getElementById('_nodes_');
273
+ if (el) {
274
+ el.replaceChildren();
275
+ }
276
+ resetEphemeralDomRefs();
277
+ els = {};
278
+ try {
279
+ if (window.gc) {
280
+ window.gc();
281
+ }
282
+ } catch (e) {
283
+ // gc not available
284
+ }
285
+ }
250
286
  function appendExtra(id, content) {
251
287
  hideTips();
252
288
  prevScroll = 0;
@@ -270,6 +306,7 @@ class Body:
270
306
  if (element) {
271
307
  element.remove();
272
308
  }
309
+ resetEphemeralDomRefs();
273
310
  highlightCode();
274
311
  scheduleScroll();
275
312
  }
@@ -280,13 +317,14 @@ class Body:
280
317
  const elements = container.querySelectorAll('.msg-box');
281
318
  let remove = false;
282
319
  elements.forEach(function(element) {
283
- if (element.id.endsWith('-' + id)) {
320
+ if (element.id && element.id.endsWith('-' + id)) {
284
321
  remove = true;
285
322
  }
286
323
  if (remove) {
287
324
  element.remove();
288
325
  }
289
326
  });
327
+ resetEphemeralDomRefs();
290
328
  highlightCode(true, container);
291
329
  scheduleScroll();
292
330
  }
@@ -347,6 +385,7 @@ class Body:
347
385
  }
348
386
  }
349
387
  function appendStream(name_header, content, chunk, replace = false, is_code_block = false) {
388
+ dropIfDetached(); // clear references to detached elements
350
389
  hideTips();
351
390
  if (DEBUG_MODE) {
352
391
  log("APPEND CHUNK: {" + chunk + "}, CONTENT: {"+content+"}, replace: " + replace + ", is_code_block: " + is_code_block);
@@ -561,6 +600,7 @@ class Body:
561
600
  element.replaceChildren();
562
601
  element.classList.add('empty_list');
563
602
  }
603
+ resetEphemeralDomRefs();
564
604
  }
565
605
  function clearInput() {
566
606
  const element = els.appendInput || document.getElementById('_append_input_');
@@ -585,9 +625,11 @@ class Body:
585
625
  element.classList.add('hidden');
586
626
  setTimeout(function() {
587
627
  element.replaceChildren();
628
+ resetEphemeralDomRefs();
588
629
  }, 1000);
589
630
  } else {
590
631
  element.replaceChildren();
632
+ resetEphemeralDomRefs();
591
633
  }
592
634
  }
593
635
  }
@@ -6,7 +6,7 @@
6
6
  # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
7
  # MIT License #
8
8
  # Created By : Marcin Szczygliński #
9
- # Updated Date: 2025.08.12 19:00:00 #
9
+ # Updated Date: 2025.08.19 07:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import io
@@ -36,6 +36,7 @@ class PidData:
36
36
  self.last_time_called = 0
37
37
  self.cooldown = 1 / 6
38
38
  self.throttling_min_chars = 5000
39
+ self.header = None
39
40
 
40
41
  @property
41
42
  def buffer(self) -> str:
@@ -43,6 +44,9 @@ class PidData:
43
44
 
44
45
  @buffer.setter
45
46
  def buffer(self, value: str):
47
+ if value is None or value == "":
48
+ self._buffer.close()
49
+ self._buffer = io.StringIO()
46
50
  self._buffer.seek(0)
47
51
  self._buffer.truncate(0)
48
52
  if value:
@@ -57,6 +61,9 @@ class PidData:
57
61
 
58
62
  @live_buffer.setter
59
63
  def live_buffer(self, value: str):
64
+ if value is None or value == "":
65
+ self._live_buffer.close()
66
+ self._live_buffer = io.StringIO()
60
67
  self._live_buffer.seek(0)
61
68
  self._live_buffer.truncate(0)
62
69
  if value:
@@ -71,6 +78,9 @@ class PidData:
71
78
 
72
79
  @html.setter
73
80
  def html(self, value: str):
81
+ if value is None or value == "":
82
+ self._html.close()
83
+ self._html = io.StringIO()
74
84
  self._html.seek(0)
75
85
  self._html.truncate(0)
76
86
  if value:
@@ -85,6 +95,9 @@ class PidData:
85
95
 
86
96
  @document.setter
87
97
  def document(self, value: str):
98
+ if value is None or value == "":
99
+ self._document.close()
100
+ self._document = io.StringIO()
88
101
  self._document.seek(0)
89
102
  self._document.truncate(0)
90
103
  if value:
@@ -99,9 +112,12 @@ class PidData:
99
112
 
100
113
  :param all: If True, clear all data, otherwise only buffers
101
114
  """
102
- for buf in (self._html, self._document, self._buffer, self._live_buffer):
103
- buf.seek(0)
104
- buf.truncate(0)
115
+ for name in ("_buffer", "_live_buffer", "_html", "_document"):
116
+ try:
117
+ getattr(self, name).close()
118
+ except Exception:
119
+ pass
120
+ setattr(self, name, io.StringIO())
105
121
 
106
122
  if all:
107
123
  self.item = None