micropython-stubber 1.16.3__py3-none-any.whl → 1.17.0__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.
- {micropython_stubber-1.16.3.dist-info → micropython_stubber-1.17.0.dist-info}/METADATA +1 -1
- {micropython_stubber-1.16.3.dist-info → micropython_stubber-1.17.0.dist-info}/RECORD +48 -49
- stubber/__init__.py +1 -1
- stubber/basicgit.py +11 -13
- stubber/board/createstubs.py +138 -97
- stubber/board/createstubs_db.py +211 -239
- stubber/board/createstubs_db_min.py +322 -844
- stubber/board/createstubs_db_mpy.mpy +0 -0
- stubber/board/createstubs_lvgl.py +91 -137
- stubber/board/createstubs_lvgl_min.py +87 -129
- stubber/board/createstubs_lvgl_mpy.mpy +0 -0
- stubber/board/createstubs_mem.py +164 -199
- stubber/board/createstubs_mem_min.py +297 -791
- stubber/board/createstubs_mem_mpy.mpy +0 -0
- stubber/board/createstubs_min.py +286 -1009
- stubber/board/createstubs_mpy.mpy +0 -0
- stubber/board/modulelist.txt +1 -2
- stubber/codemod/_partials/__init__.py +1 -1
- stubber/codemod/_partials/db_main.py +90 -72
- stubber/codemod/_partials/modules_reader.py +29 -17
- stubber/codemod/board.py +2 -4
- stubber/codemod/enrich.py +1 -1
- stubber/commands/build_cmd.py +6 -4
- stubber/commands/get_docstubs_cmd.py +6 -11
- stubber/commands/get_frozen_cmd.py +6 -11
- stubber/commands/switch_cmd.py +6 -4
- stubber/freeze/freeze_manifest_2.py +2 -1
- stubber/freeze/get_frozen.py +28 -13
- stubber/minify.py +51 -38
- stubber/publish/candidates.py +15 -23
- stubber/publish/defaults.py +2 -2
- stubber/publish/merge_docstubs.py +5 -7
- stubber/publish/missing_class_methods.py +2 -2
- stubber/publish/pathnames.py +2 -2
- stubber/publish/publish.py +2 -1
- stubber/publish/stubpackage.py +20 -41
- stubber/rst/lookup.py +9 -7
- stubber/rst/reader.py +2 -1
- stubber/stubber.py +5 -6
- stubber/update_fallback.py +3 -1
- stubber/utils/__init__.py +1 -1
- stubber/utils/config.py +7 -9
- stubber/utils/repos.py +6 -5
- stubber/utils/versions.py +48 -7
- stubber/variants.py +3 -3
- stubber/board/logging.py +0 -99
- {micropython_stubber-1.16.3.dist-info → micropython_stubber-1.17.0.dist-info}/LICENSE +0 -0
- {micropython_stubber-1.16.3.dist-info → micropython_stubber-1.17.0.dist-info}/WHEEL +0 -0
- {micropython_stubber-1.16.3.dist-info → micropython_stubber-1.17.0.dist-info}/entry_points.txt +0 -0
stubber/board/createstubs.py
CHANGED
@@ -4,7 +4,6 @@ Create stubs for (all) modules on a MicroPython board
|
|
4
4
|
# Copyright (c) 2019-2023 Jos Verlinde
|
5
5
|
|
6
6
|
import gc
|
7
|
-
import logging
|
8
7
|
import os
|
9
8
|
import sys
|
10
9
|
from time import sleep
|
@@ -24,11 +23,49 @@ try:
|
|
24
23
|
except ImportError:
|
25
24
|
from ucollections import OrderedDict # type: ignore
|
26
25
|
|
27
|
-
|
28
|
-
__version__ = "v1.16.3"
|
26
|
+
__version__ = "v1.17.0"
|
29
27
|
ENOENT = 2
|
30
28
|
_MAX_CLASS_LEVEL = 2 # Max class nesting
|
31
|
-
LIBS = ["
|
29
|
+
LIBS = ["lib", "/lib", "/sd/lib", "/flash/lib", "."]
|
30
|
+
|
31
|
+
|
32
|
+
# our own logging module to avoid dependency on and interfering with logging module
|
33
|
+
class logging:
|
34
|
+
# DEBUG = 10
|
35
|
+
INFO = 20
|
36
|
+
WARNING = 30
|
37
|
+
ERROR = 40
|
38
|
+
level = INFO
|
39
|
+
prnt = print
|
40
|
+
|
41
|
+
@staticmethod
|
42
|
+
def getLogger(name):
|
43
|
+
return logging()
|
44
|
+
|
45
|
+
@classmethod
|
46
|
+
def basicConfig(cls, level):
|
47
|
+
cls.level = level
|
48
|
+
|
49
|
+
# def debug(self, msg):
|
50
|
+
# if self.level <= logging.DEBUG:
|
51
|
+
# self.prnt("DEBUG :", msg)
|
52
|
+
|
53
|
+
def info(self, msg):
|
54
|
+
if self.level <= logging.INFO:
|
55
|
+
self.prnt("INFO :", msg)
|
56
|
+
|
57
|
+
def warning(self, msg):
|
58
|
+
if self.level <= logging.WARNING:
|
59
|
+
self.prnt("WARN :", msg)
|
60
|
+
|
61
|
+
def error(self, msg):
|
62
|
+
if self.level <= logging.ERROR:
|
63
|
+
self.prnt("ERROR :", msg)
|
64
|
+
|
65
|
+
|
66
|
+
log = logging.getLogger("stubber")
|
67
|
+
logging.basicConfig(level=logging.INFO)
|
68
|
+
# logging.basicConfig(level=logging.DEBUG)
|
32
69
|
|
33
70
|
|
34
71
|
class Stubber:
|
@@ -40,11 +77,9 @@ class Stubber:
|
|
40
77
|
raise NotImplementedError("MicroPython 1.13.0 cannot be stubbed")
|
41
78
|
except AttributeError:
|
42
79
|
pass
|
43
|
-
self.log = logging.getLogger("stubber")
|
44
|
-
self._report = [] # type: list[str]
|
45
80
|
self.info = _info()
|
46
|
-
|
47
|
-
|
81
|
+
log.info("Port: {}".format(self.info["port"]))
|
82
|
+
log.info("Board: {}".format(self.info["board"]))
|
48
83
|
gc.collect()
|
49
84
|
if firmware_id:
|
50
85
|
self._fwid = firmware_id.lower()
|
@@ -62,11 +97,11 @@ class Stubber:
|
|
62
97
|
path = get_root()
|
63
98
|
|
64
99
|
self.path = "{}/stubs/{}".format(path, self.flat_fwid).replace("//", "/")
|
65
|
-
|
100
|
+
# log.debug(self.path)
|
66
101
|
try:
|
67
102
|
ensure_folder(path + "/")
|
68
103
|
except OSError:
|
69
|
-
|
104
|
+
log.error("error creating stub folder {}".format(path))
|
70
105
|
self.problematic = [
|
71
106
|
"upip",
|
72
107
|
"upysh",
|
@@ -85,21 +120,23 @@ class Stubber:
|
|
85
120
|
]
|
86
121
|
# there is no option to discover modules from micropython, list is read from an external file.
|
87
122
|
self.modules = [] # type: list[str]
|
123
|
+
self._json_name = None
|
124
|
+
self._json_first = False
|
88
125
|
|
89
126
|
def get_obj_attributes(self, item_instance: object):
|
90
127
|
"extract information of the objects members and attributes"
|
91
128
|
# name_, repr_(value), type as text, item_instance
|
92
129
|
_result = []
|
93
130
|
_errors = []
|
94
|
-
|
131
|
+
# log.debug("get attributes {} {}".format(repr(item_instance), item_instance))
|
95
132
|
for name in dir(item_instance):
|
96
|
-
if name.startswith("
|
133
|
+
if name.startswith("__") and not name in self.modules:
|
97
134
|
continue
|
98
|
-
|
135
|
+
# log.debug("get attribute {}".format(name))
|
99
136
|
try:
|
100
137
|
val = getattr(item_instance, name)
|
101
138
|
# name , item_repr(value) , type as text, item_instance, order
|
102
|
-
|
139
|
+
# log.debug("attribute {}:{}".format(name, val))
|
103
140
|
try:
|
104
141
|
type_text = repr(type(val)).split("'")[1]
|
105
142
|
except IndexError:
|
@@ -132,21 +169,23 @@ class Stubber:
|
|
132
169
|
|
133
170
|
def create_all_stubs(self):
|
134
171
|
"Create stubs for all configured modules"
|
135
|
-
|
172
|
+
log.info("Start micropython-stubber {} on {}".format(__version__, self._fwid))
|
173
|
+
self.report_start()
|
136
174
|
gc.collect()
|
137
175
|
for module_name in self.modules:
|
138
176
|
self.create_one_stub(module_name)
|
139
|
-
self.
|
177
|
+
self.report_end()
|
178
|
+
log.info("Finally done")
|
140
179
|
|
141
180
|
def create_one_stub(self, module_name: str):
|
142
181
|
if module_name in self.problematic:
|
143
|
-
|
182
|
+
log.warning("Skip module: {:<25} : Known problematic".format(module_name))
|
144
183
|
return False
|
145
184
|
if module_name in self.excluded:
|
146
|
-
|
185
|
+
log.warning("Skip module: {:<25} : Excluded".format(module_name))
|
147
186
|
return False
|
148
187
|
|
149
|
-
file_name = "{}/{}.
|
188
|
+
file_name = "{}/{}.pyi".format(self.path, module_name.replace(".", "/"))
|
150
189
|
gc.collect()
|
151
190
|
result = False
|
152
191
|
try:
|
@@ -161,10 +200,10 @@ class Stubber:
|
|
161
200
|
|
162
201
|
Args:
|
163
202
|
- module_name (str): name of the module to document. This module will be imported.
|
164
|
-
- file_name (Optional[str]): the 'path/filename.
|
203
|
+
- file_name (Optional[str]): the 'path/filename.pyi' to write to. If omitted will be created based on the module name.
|
165
204
|
"""
|
166
205
|
if file_name is None:
|
167
|
-
fname = module_name.replace(".", "_") + ".
|
206
|
+
fname = module_name.replace(".", "_") + ".pyi"
|
168
207
|
file_name = self.path + "/" + fname
|
169
208
|
else:
|
170
209
|
fname = file_name.split("/")[-1]
|
@@ -178,10 +217,10 @@ class Stubber:
|
|
178
217
|
try:
|
179
218
|
new_module = __import__(module_name, None, None, ("*"))
|
180
219
|
m1 = gc.mem_free() # type: ignore
|
181
|
-
|
220
|
+
log.info("Stub module: {:<25} to file: {:<70} mem:{:>5}".format(module_name, fname, m1))
|
182
221
|
|
183
222
|
except ImportError:
|
184
|
-
|
223
|
+
# log.debug("Skip module: {:<25} {:<79}".format(module_name, "Module not found."))
|
185
224
|
return False
|
186
225
|
|
187
226
|
# Start a new file
|
@@ -193,22 +232,20 @@ class Stubber:
|
|
193
232
|
module_name, self._fwid, info_, __version__
|
194
233
|
)
|
195
234
|
fp.write(s)
|
196
|
-
fp.write(
|
235
|
+
fp.write(
|
236
|
+
"from __future__ import annotations\nfrom typing import Any, Generator\nfrom _typeshed import Incomplete\n\n"
|
237
|
+
)
|
197
238
|
self.write_object_stub(fp, new_module, module_name, "")
|
198
239
|
|
199
|
-
self.
|
240
|
+
self.report_add(module_name, file_name)
|
200
241
|
|
201
242
|
if module_name not in {"os", "sys", "logging", "gc"}:
|
202
243
|
# try to unload the module unless we use it
|
203
244
|
try:
|
204
245
|
del new_module
|
205
246
|
except (OSError, KeyError): # lgtm [py/unreachable-statement]
|
206
|
-
|
207
|
-
#
|
208
|
-
# try:
|
209
|
-
# del sys.modules[module_name]
|
210
|
-
# except KeyError:
|
211
|
-
# self.log.warning("could not del sys.modules[{}]".format(module_name))
|
247
|
+
log.warning("could not del new_module")
|
248
|
+
# do not try to delete from sys.modules - most times it does not work anyway
|
212
249
|
gc.collect()
|
213
250
|
return True
|
214
251
|
|
@@ -216,14 +253,14 @@ class Stubber:
|
|
216
253
|
"Write a module/object stub to an open file. Can be called recursive."
|
217
254
|
gc.collect()
|
218
255
|
if object_expr in self.problematic:
|
219
|
-
|
256
|
+
log.warning("SKIPPING problematic module:{}".format(object_expr))
|
220
257
|
return
|
221
258
|
|
222
|
-
#
|
259
|
+
# # log.debug("DUMP : {}".format(object_expr))
|
223
260
|
items, errors = self.get_obj_attributes(object_expr)
|
224
261
|
|
225
262
|
if errors:
|
226
|
-
|
263
|
+
log.error(errors)
|
227
264
|
|
228
265
|
for item_name, item_repr, item_type_txt, item_instance, _ in items:
|
229
266
|
# name_, repr_(value), type as text, item_instance, order
|
@@ -231,7 +268,7 @@ class Stubber:
|
|
231
268
|
# do not create stubs for these primitives
|
232
269
|
continue
|
233
270
|
if item_name[0].isdigit():
|
234
|
-
|
271
|
+
log.warning("NameError: invalid name {}".format(item_name))
|
235
272
|
continue
|
236
273
|
# Class expansion only on first 3 levels (bit of a hack)
|
237
274
|
if (
|
@@ -240,7 +277,7 @@ class Stubber:
|
|
240
277
|
# and not obj_name.endswith(".Pin")
|
241
278
|
# avoid expansion of Pin.cpu / Pin.board to avoid crashes on most platforms
|
242
279
|
):
|
243
|
-
|
280
|
+
# log.debug("{0}class {1}:".format(indent, item_name))
|
244
281
|
superclass = ""
|
245
282
|
is_exception = (
|
246
283
|
item_name.endswith("Exception")
|
@@ -263,7 +300,7 @@ class Stubber:
|
|
263
300
|
# write classdef
|
264
301
|
fp.write(s)
|
265
302
|
# first write the class literals and methods
|
266
|
-
|
303
|
+
# log.debug("# recursion over class {0}".format(item_name))
|
267
304
|
self.write_object_stub(
|
268
305
|
fp,
|
269
306
|
item_instance,
|
@@ -277,7 +314,7 @@ class Stubber:
|
|
277
314
|
s += indent + " ...\n\n"
|
278
315
|
fp.write(s)
|
279
316
|
elif any(word in item_type_txt for word in ["method", "function", "closure"]):
|
280
|
-
|
317
|
+
# log.debug("# def {1} function/method/closure, type = '{0}'".format(item_type_txt, item_name))
|
281
318
|
# module Function or class method
|
282
319
|
# will accept any number of params
|
283
320
|
# return type Any/Incomplete
|
@@ -295,7 +332,7 @@ class Stubber:
|
|
295
332
|
s = "{}def {}({}*args, **kwargs) -> {}:\n".format(indent, item_name, first, ret)
|
296
333
|
s += indent + " ...\n\n"
|
297
334
|
fp.write(s)
|
298
|
-
|
335
|
+
# log.debug("\n" + s)
|
299
336
|
elif item_type_txt == "<class 'module'>":
|
300
337
|
# Skip imported modules
|
301
338
|
# fp.write("# import {}\n".format(item_name))
|
@@ -305,28 +342,32 @@ class Stubber:
|
|
305
342
|
t = item_type_txt[8:-2]
|
306
343
|
s = ""
|
307
344
|
|
308
|
-
if t in
|
345
|
+
if t in ("str", "int", "float", "bool", "bytearray", "bytes"):
|
309
346
|
# known type: use actual value
|
310
|
-
s = "{0}{1} = {2} # type: {3}\n".format(indent, item_name, item_repr, t)
|
311
|
-
|
347
|
+
# s = "{0}{1} = {2} # type: {3}\n".format(indent, item_name, item_repr, t)
|
348
|
+
s = "{0}{1}: {3} = {2}\n".format(indent, item_name, item_repr, t)
|
349
|
+
elif t in ("dict", "list", "tuple"):
|
312
350
|
# dict, list , tuple: use empty value
|
313
351
|
ev = {"dict": "{}", "list": "[]", "tuple": "()"}
|
314
|
-
s = "{0}{1} = {2} # type: {3}\n".format(indent, item_name, ev[t], t)
|
352
|
+
# s = "{0}{1} = {2} # type: {3}\n".format(indent, item_name, ev[t], t)
|
353
|
+
s = "{0}{1}: {3} = {2}\n".format(indent, item_name, ev[t], t)
|
315
354
|
else:
|
316
355
|
# something else
|
317
|
-
if t in
|
356
|
+
if t in ("object", "set", "frozenset", "Pin", "generator"): # "FileIO"
|
318
357
|
# https://docs.python.org/3/tutorial/classes.html#item_instance-objects
|
319
|
-
#
|
320
|
-
|
358
|
+
# use these types for the attribute
|
359
|
+
if t == "generator":
|
360
|
+
t = "Generator"
|
361
|
+
s = "{0}{1}: {2} ## = {4}\n".format(indent, item_name, t, item_type_txt, item_repr)
|
321
362
|
else:
|
322
363
|
# Requires Python 3.6 syntax, which is OK for the stubs/pyi
|
323
364
|
t = "Incomplete"
|
324
|
-
s = "{0}{1}
|
365
|
+
s = "{0}{1}: {2} ## {3} = {4}\n".format(indent, item_name, t, item_type_txt, item_repr)
|
325
366
|
fp.write(s)
|
326
|
-
|
367
|
+
# log.debug("\n" + s)
|
327
368
|
else:
|
328
369
|
# keep only the name
|
329
|
-
|
370
|
+
# log.debug("# all other, type = '{0}'".format(item_type_txt))
|
330
371
|
fp.write("# all other, type = '{0}'\n".format(item_type_txt))
|
331
372
|
|
332
373
|
fp.write(indent + item_name + " # type: Incomplete\n")
|
@@ -352,7 +393,7 @@ class Stubber:
|
|
352
393
|
"Remove all files from the stub folder"
|
353
394
|
if path is None:
|
354
395
|
path = self.path
|
355
|
-
|
396
|
+
log.info("Clean/remove files in folder: {}".format(path))
|
356
397
|
try:
|
357
398
|
os.stat(path) # TEMP workaround mpremote listdir bug -
|
358
399
|
items = os.listdir(path)
|
@@ -370,43 +411,53 @@ class Stubber:
|
|
370
411
|
except OSError:
|
371
412
|
pass
|
372
413
|
|
373
|
-
def
|
374
|
-
"
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
414
|
+
def report_start(self, filename: str = "modules.json"):
|
415
|
+
"""Start a report of the modules that have been stubbed
|
416
|
+
"create json with list of exported modules"""
|
417
|
+
self._json_name = "{}/{}".format(self.path, filename)
|
418
|
+
self._json_first = True
|
419
|
+
ensure_folder(self._json_name)
|
420
|
+
log.info("Report file: {}".format(self._json_name))
|
380
421
|
gc.collect()
|
381
422
|
try:
|
382
423
|
# write json by node to reduce memory requirements
|
383
|
-
with open(
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
self.
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
424
|
+
with open(self._json_name, "w") as f:
|
425
|
+
f.write("{")
|
426
|
+
f.write(dumps({"firmware": self.info})[1:-1])
|
427
|
+
f.write(",\n")
|
428
|
+
f.write(dumps({"stubber": {"version": __version__}, "stubtype": "firmware"})[1:-1])
|
429
|
+
f.write(",\n")
|
430
|
+
f.write('"modules" :[\n')
|
431
|
+
|
432
|
+
except OSError as e:
|
433
|
+
log.error("Failed to create the report.")
|
434
|
+
self._json_name = None
|
435
|
+
raise e
|
436
|
+
|
437
|
+
def report_add(self, module_name: str, stub_file: str):
|
438
|
+
"Add a module to the report"
|
439
|
+
# write json by node to reduce memory requirements
|
440
|
+
if not self._json_name:
|
441
|
+
raise Exception("No report file")
|
442
|
+
try:
|
443
|
+
with open(self._json_name, "a") as f:
|
444
|
+
if not self._json_first:
|
445
|
+
f.write(",\n")
|
446
|
+
else:
|
447
|
+
self._json_first = False
|
448
|
+
line = '{{"module": "{}", "file": "{}"}}'.format(module_name, stub_file.replace("\\", "/"))
|
449
|
+
f.write(line)
|
402
450
|
|
403
|
-
|
404
|
-
|
405
|
-
f.write(",\n")
|
406
|
-
f.write(n)
|
451
|
+
except OSError:
|
452
|
+
log.error("Failed to create the report.")
|
407
453
|
|
408
|
-
def
|
409
|
-
|
454
|
+
def report_end(self):
|
455
|
+
if not self._json_name:
|
456
|
+
raise Exception("No report file")
|
457
|
+
with open(self._json_name, "a") as f:
|
458
|
+
f.write("\n]}")
|
459
|
+
# is used as sucess indicator
|
460
|
+
log.info("Path: {}".format(self.path))
|
410
461
|
|
411
462
|
|
412
463
|
def ensure_folder(path: str):
|
@@ -452,7 +503,7 @@ def _build(s):
|
|
452
503
|
def _info(): # type:() -> dict[str, str]
|
453
504
|
info = OrderedDict(
|
454
505
|
{
|
455
|
-
"family": sys.implementation.name,
|
506
|
+
"family": sys.implementation.name, # type: ignore
|
456
507
|
"version": "",
|
457
508
|
"build": "",
|
458
509
|
"ver": "",
|
@@ -482,9 +533,9 @@ def _info(): # type:() -> dict[str, str]
|
|
482
533
|
info["board"] = _machine
|
483
534
|
info["cpu"] = _machine.split("with")[-1].strip()
|
484
535
|
info["mpy"] = (
|
485
|
-
sys.implementation._mpy
|
536
|
+
sys.implementation._mpy # type: ignore
|
486
537
|
if "_mpy" in dir(sys.implementation)
|
487
|
-
else sys.implementation.mpy
|
538
|
+
else sys.implementation.mpy # type: ignore
|
488
539
|
if "mpy" in dir(sys.implementation)
|
489
540
|
else ""
|
490
541
|
)
|
@@ -582,9 +633,10 @@ def get_boardname() -> str:
|
|
582
633
|
"Read the board name from the boardname.py file that may have been created upfront"
|
583
634
|
try:
|
584
635
|
from boardname import BOARDNAME # type: ignore
|
636
|
+
|
585
637
|
log.info("Found BOARDNAME: {}".format(BOARDNAME))
|
586
638
|
except ImportError:
|
587
|
-
log.
|
639
|
+
log.warning("BOARDNAME not found")
|
588
640
|
BOARDNAME = ""
|
589
641
|
return BOARDNAME
|
590
642
|
|
@@ -639,10 +691,6 @@ def is_micropython() -> bool:
|
|
639
691
|
# pylint: disable=unused-variable,eval-used
|
640
692
|
try:
|
641
693
|
# either test should fail on micropython
|
642
|
-
# a) https://docs.micropython.org/en/latest/genrst/syntax.html#spaces
|
643
|
-
# Micropython : SyntaxError
|
644
|
-
# a = eval("1and 0") # lgtm [py/unused-local-variable]
|
645
|
-
# Eval blocks some minification aspects
|
646
694
|
|
647
695
|
# b) https://docs.micropython.org/en/latest/genrst/builtin_types.html#bytes-with-keywords-not-implemented
|
648
696
|
# Micropython: NotImplementedError
|
@@ -907,16 +955,9 @@ def main():
|
|
907
955
|
gc.collect()
|
908
956
|
|
909
957
|
stubber.create_all_stubs()
|
910
|
-
stubber.report()
|
911
958
|
|
912
959
|
|
913
960
|
if __name__ == "__main__" or is_micropython():
|
914
|
-
try:
|
915
|
-
log = logging.getLogger("stubber")
|
916
|
-
logging.basicConfig(level=logging.INFO)
|
917
|
-
# logging.basicConfig(level=logging.DEBUG)
|
918
|
-
except NameError:
|
919
|
-
pass
|
920
961
|
if not file_exists("no_auto_stubber.txt"):
|
921
962
|
try:
|
922
963
|
gc.threshold(4 * 1024) # type: ignore
|