tdrpa.tdworker 1.2.13.2__py312-none-win_amd64.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 (101) hide show
  1. tdrpa/_tdxlwings/__init__.py +193 -0
  2. tdrpa/_tdxlwings/__pycache__/__init__.cpython-311.pyc +0 -0
  3. tdrpa/_tdxlwings/__pycache__/__init__.cpython-38.pyc +0 -0
  4. tdrpa/_tdxlwings/__pycache__/_win32patch.cpython-311.pyc +0 -0
  5. tdrpa/_tdxlwings/__pycache__/_win32patch.cpython-38.pyc +0 -0
  6. tdrpa/_tdxlwings/__pycache__/_xlwindows.cpython-311.pyc +0 -0
  7. tdrpa/_tdxlwings/__pycache__/_xlwindows.cpython-38.pyc +0 -0
  8. tdrpa/_tdxlwings/__pycache__/apps.cpython-311.pyc +0 -0
  9. tdrpa/_tdxlwings/__pycache__/apps.cpython-38.pyc +0 -0
  10. tdrpa/_tdxlwings/__pycache__/base_classes.cpython-311.pyc +0 -0
  11. tdrpa/_tdxlwings/__pycache__/base_classes.cpython-38.pyc +0 -0
  12. tdrpa/_tdxlwings/__pycache__/com_server.cpython-311.pyc +0 -0
  13. tdrpa/_tdxlwings/__pycache__/com_server.cpython-38.pyc +0 -0
  14. tdrpa/_tdxlwings/__pycache__/constants.cpython-311.pyc +0 -0
  15. tdrpa/_tdxlwings/__pycache__/constants.cpython-38.pyc +0 -0
  16. tdrpa/_tdxlwings/__pycache__/expansion.cpython-311.pyc +0 -0
  17. tdrpa/_tdxlwings/__pycache__/expansion.cpython-38.pyc +0 -0
  18. tdrpa/_tdxlwings/__pycache__/main.cpython-311.pyc +0 -0
  19. tdrpa/_tdxlwings/__pycache__/main.cpython-38.pyc +0 -0
  20. tdrpa/_tdxlwings/__pycache__/udfs.cpython-311.pyc +0 -0
  21. tdrpa/_tdxlwings/__pycache__/udfs.cpython-38.pyc +0 -0
  22. tdrpa/_tdxlwings/__pycache__/utils.cpython-311.pyc +0 -0
  23. tdrpa/_tdxlwings/__pycache__/utils.cpython-38.pyc +0 -0
  24. tdrpa/_tdxlwings/_win32patch.py +90 -0
  25. tdrpa/_tdxlwings/_xlmac.py +2240 -0
  26. tdrpa/_tdxlwings/_xlwindows.py +2518 -0
  27. tdrpa/_tdxlwings/addin/Dictionary.cls +474 -0
  28. tdrpa/_tdxlwings/addin/IWebAuthenticator.cls +71 -0
  29. tdrpa/_tdxlwings/addin/WebClient.cls +772 -0
  30. tdrpa/_tdxlwings/addin/WebHelpers.bas +3203 -0
  31. tdrpa/_tdxlwings/addin/WebRequest.cls +875 -0
  32. tdrpa/_tdxlwings/addin/WebResponse.cls +453 -0
  33. tdrpa/_tdxlwings/addin/xlwings.xlam +0 -0
  34. tdrpa/_tdxlwings/apps.py +35 -0
  35. tdrpa/_tdxlwings/base_classes.py +1092 -0
  36. tdrpa/_tdxlwings/cli.py +1306 -0
  37. tdrpa/_tdxlwings/com_server.py +385 -0
  38. tdrpa/_tdxlwings/constants.py +3080 -0
  39. tdrpa/_tdxlwings/conversion/__init__.py +103 -0
  40. tdrpa/_tdxlwings/conversion/framework.py +147 -0
  41. tdrpa/_tdxlwings/conversion/numpy_conv.py +34 -0
  42. tdrpa/_tdxlwings/conversion/pandas_conv.py +184 -0
  43. tdrpa/_tdxlwings/conversion/standard.py +321 -0
  44. tdrpa/_tdxlwings/expansion.py +83 -0
  45. tdrpa/_tdxlwings/ext/__init__.py +3 -0
  46. tdrpa/_tdxlwings/ext/sql.py +73 -0
  47. tdrpa/_tdxlwings/html/xlwings-alert.html +71 -0
  48. tdrpa/_tdxlwings/js/xlwings.js +577 -0
  49. tdrpa/_tdxlwings/js/xlwings.ts +729 -0
  50. tdrpa/_tdxlwings/mac_dict.py +6399 -0
  51. tdrpa/_tdxlwings/main.py +5205 -0
  52. tdrpa/_tdxlwings/mistune/__init__.py +63 -0
  53. tdrpa/_tdxlwings/mistune/block_parser.py +366 -0
  54. tdrpa/_tdxlwings/mistune/inline_parser.py +216 -0
  55. tdrpa/_tdxlwings/mistune/markdown.py +84 -0
  56. tdrpa/_tdxlwings/mistune/renderers.py +220 -0
  57. tdrpa/_tdxlwings/mistune/scanner.py +121 -0
  58. tdrpa/_tdxlwings/mistune/util.py +41 -0
  59. tdrpa/_tdxlwings/pro/__init__.py +40 -0
  60. tdrpa/_tdxlwings/pro/_xlcalamine.py +536 -0
  61. tdrpa/_tdxlwings/pro/_xlofficejs.py +146 -0
  62. tdrpa/_tdxlwings/pro/_xlremote.py +1293 -0
  63. tdrpa/_tdxlwings/pro/custom_functions_code.js +150 -0
  64. tdrpa/_tdxlwings/pro/embedded_code.py +60 -0
  65. tdrpa/_tdxlwings/pro/udfs_officejs.py +549 -0
  66. tdrpa/_tdxlwings/pro/utils.py +199 -0
  67. tdrpa/_tdxlwings/quickstart.xlsm +0 -0
  68. tdrpa/_tdxlwings/quickstart_addin.xlam +0 -0
  69. tdrpa/_tdxlwings/quickstart_addin_ribbon.xlam +0 -0
  70. tdrpa/_tdxlwings/quickstart_fastapi/main.py +47 -0
  71. tdrpa/_tdxlwings/quickstart_fastapi/requirements.txt +3 -0
  72. tdrpa/_tdxlwings/quickstart_standalone.xlsm +0 -0
  73. tdrpa/_tdxlwings/reports.py +12 -0
  74. tdrpa/_tdxlwings/rest/__init__.py +1 -0
  75. tdrpa/_tdxlwings/rest/api.py +368 -0
  76. tdrpa/_tdxlwings/rest/serializers.py +103 -0
  77. tdrpa/_tdxlwings/server.py +14 -0
  78. tdrpa/_tdxlwings/udfs.py +775 -0
  79. tdrpa/_tdxlwings/utils.py +777 -0
  80. tdrpa/_tdxlwings/xlwings-0.31.6.applescript +30 -0
  81. tdrpa/_tdxlwings/xlwings.bas +2061 -0
  82. tdrpa/_tdxlwings/xlwings_custom_addin.bas +2042 -0
  83. tdrpa/_tdxlwings/xlwingslib.cp38-win_amd64.pyd +0 -0
  84. tdrpa/tdworker/__init__.pyi +12 -0
  85. tdrpa/tdworker/_clip.pyi +50 -0
  86. tdrpa/tdworker/_excel.pyi +743 -0
  87. tdrpa/tdworker/_file.pyi +77 -0
  88. tdrpa/tdworker/_img.pyi +226 -0
  89. tdrpa/tdworker/_network.pyi +94 -0
  90. tdrpa/tdworker/_os.pyi +47 -0
  91. tdrpa/tdworker/_sp.pyi +21 -0
  92. tdrpa/tdworker/_w.pyi +129 -0
  93. tdrpa/tdworker/_web.pyi +995 -0
  94. tdrpa/tdworker/_winE.pyi +228 -0
  95. tdrpa/tdworker/_winK.pyi +74 -0
  96. tdrpa/tdworker/_winM.pyi +117 -0
  97. tdrpa/tdworker.cp312-win_amd64.pyd +0 -0
  98. tdrpa_tdworker-1.2.13.2.dist-info/METADATA +38 -0
  99. tdrpa_tdworker-1.2.13.2.dist-info/RECORD +101 -0
  100. tdrpa_tdworker-1.2.13.2.dist-info/WHEEL +5 -0
  101. tdrpa_tdworker-1.2.13.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,775 @@
1
+ import asyncio
2
+ import concurrent
3
+ import copy
4
+ import functools
5
+ import inspect
6
+ import json
7
+ import logging
8
+ import os
9
+ import os.path
10
+ import re
11
+ import tempfile
12
+ import threading
13
+ from importlib import (
14
+ import_module,
15
+ reload,
16
+ )
17
+ from random import random
18
+
19
+ import pythoncom
20
+ import pywintypes
21
+ from win32com.client import Dispatch
22
+
23
+ import tdrpa._tdxlwings as xlwings
24
+
25
+ from . import Book, LicenseError, Range, __pro__, apps, conversion
26
+ from .utils import VBAWriter, exception, read_config_sheet
27
+
28
+ logger = logging.getLogger(__name__)
29
+ cache = {}
30
+
31
+ com_executor = concurrent.futures.ThreadPoolExecutor(initializer=pythoncom.CoInitialize)
32
+
33
+
34
+ async def async_thread(base, my_has_dynamic_array, func, args, cache_key, expand):
35
+ try:
36
+ if expand:
37
+ stashme = await base.get_formula_array()
38
+ elif my_has_dynamic_array:
39
+ stashme = await base.get_formula2()
40
+ else:
41
+ stashme = await base.get_formula()
42
+
43
+ loop = asyncio.get_running_loop()
44
+ cache[cache_key] = await loop.run_in_executor(
45
+ com_executor, functools.partial(func, *args)
46
+ )
47
+
48
+ if expand:
49
+ await base.set_formula_array(stashme)
50
+ elif my_has_dynamic_array:
51
+ await base.set_formula2(stashme)
52
+ else:
53
+ await base.set_formula(stashme)
54
+ except: # noqa: E722
55
+ exception(logger, "async_thread failed")
56
+
57
+
58
+ async def async_thread_nocaller(
59
+ func,
60
+ args,
61
+ ):
62
+ try:
63
+ loop = asyncio.get_running_loop()
64
+ await loop.run_in_executor(com_executor, functools.partial(func, *args))
65
+
66
+ except: # noqa: E722
67
+ exception(logger, "async_thread failed")
68
+
69
+
70
+ def func_sig(f):
71
+ s = inspect.signature(f)
72
+ vararg = None
73
+ args = []
74
+ defaults = []
75
+ for p in s.parameters.values():
76
+ if p.kind is inspect.Parameter.POSITIONAL_OR_KEYWORD:
77
+ args.append(p.name)
78
+ if p.default is not inspect.Signature.empty:
79
+ defaults.append(p.default)
80
+ elif p.kind is inspect.Parameter.VAR_POSITIONAL:
81
+ args.append(p.name)
82
+ vararg = p.name
83
+ else:
84
+ raise Exception("xlwings does not support UDFs with keyword arguments")
85
+ return {"args": args, "defaults": defaults, "vararg": vararg}
86
+
87
+
88
+ def get_category(**func_kwargs):
89
+ if "category" in func_kwargs:
90
+ category = func_kwargs.pop("category")
91
+ if isinstance(category, int):
92
+ if 1 <= category <= 14:
93
+ return category
94
+ raise Exception(
95
+ "There is only 14 build-in categories available in Excel. "
96
+ "Please use a string value to specify a custom category."
97
+ )
98
+ if isinstance(category, str):
99
+ return category[:255]
100
+ raise Exception(
101
+ "Category {0} should either be a predefined Excel category (int value) "
102
+ "or a custom one (str value).".format(category)
103
+ )
104
+ return "xlwings" # Default category
105
+
106
+
107
+ def get_async_mode(**func_kwargs):
108
+ if "async_mode" in func_kwargs:
109
+ value = func_kwargs.pop("async_mode")
110
+ if value in ["threading"]:
111
+ return value
112
+ raise Exception('The only supported async_mode mode is currently "threading".')
113
+ else:
114
+ return None
115
+
116
+
117
+ def check_bool(kw, default, **func_kwargs):
118
+ if kw in func_kwargs:
119
+ check = func_kwargs.pop(kw)
120
+ if isinstance(check, bool):
121
+ return check
122
+ raise Exception(
123
+ '{0} only takes boolean values. ("{1}" provided).'.format(kw, check)
124
+ )
125
+ return default
126
+
127
+
128
+ def xlfunc(f=None, **kwargs):
129
+ def inner(f):
130
+ if not hasattr(f, "__xlfunc__"):
131
+ xlf = f.__xlfunc__ = {}
132
+ xlf["name"] = f.__name__
133
+ xlf["sub"] = False
134
+ xlargs = xlf["args"] = []
135
+ xlargmap = xlf["argmap"] = {}
136
+ sig = func_sig(f)
137
+ nArgs = len(sig["args"])
138
+ nDefaults = len(sig["defaults"])
139
+ nRequiredArgs = nArgs - nDefaults
140
+ if sig["vararg"] and nDefaults > 0:
141
+ raise Exception(
142
+ "xlwings does not support UDFs "
143
+ "with both optional and variable length arguments"
144
+ )
145
+ for vpos, vname in enumerate(sig["args"]):
146
+ arg_info = {
147
+ "name": vname,
148
+ "pos": vpos,
149
+ "vba": None,
150
+ "doc": "Positional argument " + str(vpos + 1),
151
+ "vararg": vname == sig["vararg"],
152
+ "options": {},
153
+ }
154
+ if vpos >= nRequiredArgs:
155
+ arg_info["optional"] = sig["defaults"][vpos - nRequiredArgs]
156
+ xlargs.append(arg_info)
157
+ xlargmap[vname] = xlargs[-1]
158
+ xlf["ret"] = {
159
+ "doc": f.__doc__
160
+ if f.__doc__ is not None
161
+ else "Python function '"
162
+ + f.__name__
163
+ + "' defined in '"
164
+ + str(f.__code__.co_filename)
165
+ + "'.",
166
+ "options": {},
167
+ }
168
+ f.__xlfunc__["category"] = get_category(**kwargs)
169
+ f.__xlfunc__["call_in_wizard"] = check_bool(
170
+ "call_in_wizard", default=True, **kwargs
171
+ )
172
+ f.__xlfunc__["volatile"] = check_bool("volatile", default=False, **kwargs)
173
+ f.__xlfunc__["async_mode"] = get_async_mode(**kwargs)
174
+ return f
175
+
176
+ if f is None:
177
+ return inner
178
+ else:
179
+ return inner(f)
180
+
181
+
182
+ def xlsub(f=None, **kwargs):
183
+ def inner(f):
184
+ f = xlfunc(**kwargs)(f)
185
+ f.__xlfunc__["sub"] = True
186
+ return f
187
+
188
+ if f is None:
189
+ return inner
190
+ else:
191
+ return inner(f)
192
+
193
+
194
+ def xlret(convert=None, **kwargs):
195
+ if convert is not None:
196
+ kwargs["convert"] = convert
197
+
198
+ def inner(f):
199
+ xlf = xlfunc(f).__xlfunc__
200
+ xlr = xlf["ret"]
201
+ xlr["options"].update(kwargs)
202
+ return f
203
+
204
+ return inner
205
+
206
+
207
+ def xlarg(arg, convert=None, **kwargs):
208
+ if convert is not None:
209
+ kwargs["convert"] = convert
210
+
211
+ def inner(f):
212
+ xlf = xlfunc(f).__xlfunc__
213
+ if arg.lstrip("*") not in xlf["argmap"]:
214
+ raise Exception("Invalid argument name '" + arg + "'.")
215
+ xla = xlf["argmap"][arg.lstrip("*")]
216
+ for special in ("vba", "doc"):
217
+ if special in kwargs:
218
+ xla[special] = kwargs.pop(special)
219
+ xla["options"].update(kwargs)
220
+ return f
221
+
222
+ return inner
223
+
224
+
225
+ udf_modules = {}
226
+
227
+ RPC_E_SERVERCALL_RETRYLATER = {-2147418111, -2146777998}
228
+ MAX_BACKOFF_MS = 512
229
+
230
+
231
+ class ComRange(Range):
232
+ """
233
+ A Range subclass that stores the impl as
234
+ a serialized COM object so it can be passed between
235
+ threads easily
236
+
237
+ https://devblogs.microsoft.com/oldnewthing/20151021-00/?p=91311
238
+ """
239
+
240
+ def __init__(self, rng):
241
+ super().__init__(impl=rng.impl)
242
+
243
+ self._ser_thread = threading.get_ident()
244
+ self._ser = pythoncom.CoMarshalInterThreadInterfaceInStream(
245
+ pythoncom.IID_IDispatch, rng.api
246
+ )
247
+ self._ser_resultCLSID = self._impl.api.CLSID
248
+
249
+ self._deser_thread = None
250
+ self._deser = None
251
+
252
+ @property
253
+ def impl(self):
254
+ if threading.get_ident() == self._ser_thread:
255
+ return self._impl
256
+ elif threading.get_ident() == self._deser_thread:
257
+ return self._deser
258
+
259
+ assert self._deser is None, f"already deserialized on {self._deser_thread}"
260
+ self._deser_thread = threading.get_ident()
261
+
262
+ deser = pythoncom.CoGetInterfaceAndReleaseStream(
263
+ self._ser, pythoncom.IID_IDispatch
264
+ )
265
+ dispatch = Dispatch(deser, resultCLSID=self._ser_resultCLSID)
266
+
267
+ self._ser = None # single-use
268
+ self._deser = xlwings._xlwindows.Range(xl=dispatch)
269
+
270
+ return self._deser
271
+
272
+ def __copy__(self):
273
+ """
274
+ We need to re-serialize the COM object as they're
275
+ single-use
276
+ """
277
+ return ComRange(self)
278
+
279
+ async def _com(self, fn, *args, backoff=1):
280
+ """
281
+ :param backoff: if the call fails, time to wait in ms
282
+ before the next one. Random exponential backoff to
283
+ a cap.
284
+ """
285
+
286
+ loop = asyncio.get_running_loop()
287
+
288
+ try:
289
+ return await loop.run_in_executor(
290
+ com_executor, functools.partial(fn, copy.copy(self), *args)
291
+ )
292
+ except AttributeError:
293
+ # the Dispatch object that the `com_executor` thread
294
+ # didn't deserialize properly, as Excel was too busy
295
+ # to handle the TypeInfo call when requested
296
+ pass
297
+ except Exception as e:
298
+ if getattr(e, "hresult", 0) not in RPC_E_SERVERCALL_RETRYLATER:
299
+ raise
300
+
301
+ await asyncio.sleep(backoff / 1e3)
302
+ return await self._com(
303
+ fn, *args, backoff=min(backoff * round(1 + random()), MAX_BACKOFF_MS)
304
+ )
305
+
306
+ async def clear_contents(self):
307
+ await self._com(lambda rng: rng.impl.clear_contents())
308
+
309
+ async def set_formula_array(self, f):
310
+ await self._com(lambda rng: setattr(rng.impl, "formula_array", f))
311
+
312
+ async def set_formula(self, f):
313
+ await self._com(lambda rng: setattr(rng.impl, "formula", f))
314
+
315
+ async def set_formula2(self, f):
316
+ await self._com(lambda rng: setattr(rng.impl, "formula2", f))
317
+
318
+ async def get_shape(self):
319
+ return await self._com(lambda rng: rng.impl.shape)
320
+
321
+ async def get_formula_array(self):
322
+ return await self._com(lambda rng: rng.impl.formula_array)
323
+
324
+ async def get_formula(self):
325
+ return await self._com(lambda rng: rng.impl.formula)
326
+
327
+ async def get_formula2(self):
328
+ return await self._com(lambda rng: rng.impl.formula2)
329
+
330
+ async def get_address(self):
331
+ return await self._com(lambda rng: rng.impl.address)
332
+
333
+
334
+ async def delayed_resize_dynamic_array_formula(target_range, caller):
335
+ try:
336
+ await asyncio.sleep(0.1)
337
+
338
+ stashme = await caller.get_formula_array()
339
+ if not stashme:
340
+ stashme = await caller.get_formula()
341
+
342
+ c_y, c_x = await caller.get_shape()
343
+ t_y, t_x = await target_range.get_shape()
344
+ if c_x > t_x or c_y > t_y:
345
+ await caller.clear_contents()
346
+
347
+ # this will call the UDF again (hitting the cache),
348
+ # but you'll have the right size output this time
349
+ # (`caller` will be `target_range`). We'll have to
350
+ # be careful not to block the async loop!
351
+ await target_range.set_formula_array(stashme)
352
+
353
+ except: # noqa: E722
354
+ exception(logger, "couldn't resize")
355
+
356
+
357
+ def get_udf_module(module_name, xl_workbook):
358
+ module_info = udf_modules.get(module_name, None)
359
+ if module_info is not None:
360
+ module = module_info["module"]
361
+ # If filetime is None, it's not reloadable
362
+ if module_info["filetime"] is not None:
363
+ mtime = os.path.getmtime(module_info["filename"])
364
+ if mtime != module_info["filetime"]:
365
+ module = reload(module)
366
+ module_info["filetime"] = mtime
367
+ module_info["module"] = module
368
+ else:
369
+ # Handle embedded code (Excel only)
370
+ if xl_workbook:
371
+ wb = Book(impl=xlwings._xlwindows.Book(Dispatch(xl_workbook)))
372
+ for sheet in wb.sheets:
373
+ if sheet.name.endswith(".py") and not __pro__:
374
+ raise LicenseError("Embedded code requires a valid LICENSE_KEY.")
375
+ elif sheet.name.endswith(".py") and __pro__:
376
+ from .pro.embedded_code import dump_embedded_code
377
+ from .pro.utils import get_embedded_code_temp_dir
378
+
379
+ dump_embedded_code(wb, get_embedded_code_temp_dir())
380
+
381
+ module = import_module(module_name)
382
+ filename = os.path.normcase(module.__file__.lower())
383
+
384
+ try: # getmtime fails for zip imports and frozen modules
385
+ mtime = os.path.getmtime(filename)
386
+ except OSError:
387
+ mtime = None
388
+
389
+ udf_modules[module_name] = {
390
+ "filename": filename,
391
+ "filetime": mtime,
392
+ "module": module,
393
+ }
394
+
395
+ return module
396
+
397
+
398
+ def get_cache_key(func, args, caller):
399
+ """only use this if function is called from cells, not VBA"""
400
+ xw_caller = Range(impl=xlwings._xlwindows.Range(xl=caller))
401
+ return (
402
+ func.__name__
403
+ + str(args)
404
+ + str(xw_caller.sheet.book.app.pid)
405
+ + xw_caller.sheet.book.name
406
+ + xw_caller.sheet.name
407
+ + xw_caller.address.split(":")[0]
408
+ )
409
+
410
+
411
+ def call_udf(module_name, func_name, args, this_workbook=None, caller=None):
412
+ """
413
+ This method executes the UDF synchronously from the COM server thread
414
+ """
415
+ module = get_udf_module(module_name, this_workbook)
416
+ func = getattr(module, func_name)
417
+ func_info = func.__xlfunc__
418
+ args_info = func_info["args"]
419
+ ret_info = func_info["ret"]
420
+ is_dynamic_array = ret_info["options"].get("expand")
421
+ xw_caller = Range(impl=xlwings._xlwindows.Range(xl=caller))
422
+
423
+ # If there is the 'reserved' argument "caller", assign the caller object
424
+ for info in args_info:
425
+ if info["name"] == "caller":
426
+ args = list(args)
427
+ args[info["pos"]] = ComRange(xw_caller)
428
+ args = tuple(args)
429
+
430
+ writing = func_info.get("writing", None)
431
+ if writing and writing == xw_caller.address:
432
+ return func_info["rval"]
433
+
434
+ args = list(args)
435
+ for i, arg in enumerate(args):
436
+ arg_info = args_info[min(i, len(args_info) - 1)]
437
+ if isinstance(arg, int) and arg == -2147352572: # missing
438
+ args[i] = arg_info.get("optional", None)
439
+ elif xlwings._xlwindows.is_range_instance(arg):
440
+ args[i] = conversion.read(
441
+ Range(impl=xlwings._xlwindows.Range(xl=arg)),
442
+ None,
443
+ arg_info["options"],
444
+ )
445
+ else:
446
+ args[i] = conversion.read(None, arg, arg_info["options"])
447
+ if this_workbook:
448
+ xlwings._xlwindows.BOOK_CALLER = Dispatch(this_workbook)
449
+
450
+ from .com_server import loop
451
+
452
+ if func_info["async_mode"] and func_info["async_mode"] == "threading":
453
+ if caller is None:
454
+ asyncio.run_coroutine_threadsafe(
455
+ async_thread_nocaller(func, args),
456
+ loop,
457
+ )
458
+ return [[0]]
459
+ cache_key = get_cache_key(func, args, caller)
460
+ cached_value = cache.get(cache_key)
461
+ if (
462
+ cached_value is not None
463
+ ): # test against None as np arrays don't have a truth value
464
+ if not is_dynamic_array: # for dynamic arrays, the cache is cleared below
465
+ del cache[cache_key]
466
+ ret = cached_value
467
+ else:
468
+ ret = [["#N/A waiting..." * xw_caller.columns.count] * xw_caller.rows.count]
469
+
470
+ # this does a lot of nested COM calls, so do this all
471
+ # synchronously on the COM thread until there is async
472
+ # support for Sheet, Book & App.
473
+ my_has_dynamic_array = has_dynamic_array(xw_caller.sheet.book.app.pid)
474
+
475
+ asyncio.run_coroutine_threadsafe(
476
+ async_thread(
477
+ ComRange(xw_caller),
478
+ my_has_dynamic_array,
479
+ func,
480
+ args,
481
+ cache_key,
482
+ is_dynamic_array,
483
+ ),
484
+ loop,
485
+ )
486
+ return ret
487
+ else:
488
+ if is_dynamic_array:
489
+ cache_key = get_cache_key(func, args, caller)
490
+ cached_value = cache.get(cache_key)
491
+ if cached_value is not None:
492
+ ret = cached_value
493
+ else:
494
+ if inspect.iscoroutinefunction(func):
495
+ ret = asyncio.run_coroutine_threadsafe(func(*args), loop).result()
496
+ else:
497
+ ret = func(*args)
498
+ cache[cache_key] = ret
499
+ elif inspect.iscoroutinefunction(func):
500
+ ret = asyncio.run_coroutine_threadsafe(func(*args), loop).result()
501
+ else:
502
+ ret = func(*args)
503
+
504
+ xl_result = conversion.write(ret, None, ret_info["options"])
505
+
506
+ if is_dynamic_array:
507
+ current_size = (len(xw_caller.rows), len(xw_caller.columns))
508
+ result_size = (1, 1)
509
+ if type(xl_result) is list:
510
+ result_height = len(xl_result)
511
+ result_width = result_height and len(xl_result[0])
512
+ result_size = (max(1, result_height), max(1, result_width))
513
+ if current_size != result_size:
514
+ target_range = xw_caller.resize(*result_size)
515
+
516
+ asyncio.run_coroutine_threadsafe(
517
+ delayed_resize_dynamic_array_formula(
518
+ target_range=ComRange(target_range), caller=ComRange(xw_caller)
519
+ ),
520
+ loop,
521
+ )
522
+ else:
523
+ del cache[cache_key]
524
+
525
+ return xl_result
526
+
527
+
528
+ def generate_vba_wrapper(module_name, module, f, xl_workbook):
529
+ vba = VBAWriter(f)
530
+
531
+ for svar in map(lambda attr: getattr(module, attr), dir(module)):
532
+ if hasattr(svar, "__xlfunc__"):
533
+ xlfunc = svar.__xlfunc__
534
+ fname = xlfunc["name"]
535
+ call_in_wizard = xlfunc["call_in_wizard"]
536
+ volatile = xlfunc["volatile"]
537
+
538
+ ftype = "Sub" if xlfunc["sub"] else "Function"
539
+
540
+ func_sig = ftype + " " + fname + "("
541
+
542
+ first = True
543
+ vararg = ""
544
+ for arg in xlfunc["args"]:
545
+ if arg["name"] == "caller":
546
+ arg[
547
+ "vba"
548
+ ] = "Nothing" # will be replaced with caller under call_udf
549
+ if not arg["vba"]:
550
+ argname = arg["name"]
551
+ if not first:
552
+ func_sig += ", "
553
+ if "optional" in arg:
554
+ func_sig += "Optional "
555
+ elif arg["vararg"]:
556
+ func_sig += "ParamArray "
557
+ vararg = argname
558
+ func_sig += argname
559
+ if arg["vararg"]:
560
+ func_sig += "()"
561
+ first = False
562
+ func_sig += ")"
563
+
564
+ with vba.block(func_sig):
565
+ if ftype == "Function":
566
+ if not call_in_wizard:
567
+ vba.writeln(
568
+ 'If (Not Application.CommandBars("Standard")'
569
+ ".Controls(1).Enabled) Then Exit Function"
570
+ )
571
+ if volatile:
572
+ vba.writeln("Application.Volatile")
573
+
574
+ if vararg != "":
575
+ vba.writeln("Dim argsArray() As Variant")
576
+ non_varargs = [
577
+ arg["vba"] or arg["name"]
578
+ for arg in xlfunc["args"]
579
+ if not arg["vararg"]
580
+ ]
581
+ vba.writeln(
582
+ "argsArray = Array(%s)" % tuple({", ".join(non_varargs)})
583
+ )
584
+
585
+ vba.writeln(
586
+ "ReDim Preserve argsArray(0 to UBound("
587
+ + vararg
588
+ + ") - LBound("
589
+ + vararg
590
+ + ") + "
591
+ + str(len(non_varargs))
592
+ + ")"
593
+ )
594
+ vba.writeln(
595
+ "For k = LBound(" + vararg + ") To UBound(" + vararg + ")"
596
+ )
597
+ vba.writeln(
598
+ "argsArray("
599
+ + str(len(non_varargs))
600
+ + " + k - LBound("
601
+ + vararg
602
+ + ")) = "
603
+ + argname
604
+ + "(k)"
605
+ )
606
+ vba.writeln("Next k")
607
+
608
+ args_vba = "argsArray"
609
+ else:
610
+ args_vba = (
611
+ "Array("
612
+ + ", ".join(arg["vba"] or arg["name"] for arg in xlfunc["args"])
613
+ + ")"
614
+ )
615
+
616
+ # Add-ins work with ActiveWorkbook instead of ThisWorkbook
617
+ vba_workbook = (
618
+ "ActiveWorkbook"
619
+ if xl_workbook.Name.endswith(".xlam")
620
+ else "ThisWorkbook"
621
+ )
622
+
623
+ if ftype == "Sub":
624
+ with vba.block('#If App = "Microsoft Excel" Then'):
625
+ vba.writeln(
626
+ 'XLPy.CallUDF "{module_name}", "{fname}", '
627
+ "{args_vba}, {vba_workbook}, Application.Caller",
628
+ module_name=module_name,
629
+ fname=fname,
630
+ args_vba=args_vba,
631
+ vba_workbook=vba_workbook,
632
+ )
633
+ with vba.block("#Else"):
634
+ vba.writeln(
635
+ 'XLPy.CallUDF "{module_name}", "{fname}", {args_vba}',
636
+ module_name=module_name,
637
+ fname=fname,
638
+ args_vba=args_vba,
639
+ )
640
+ vba.writeln("#End If")
641
+ else:
642
+ with vba.block('#If App = "Microsoft Excel" Then'):
643
+ vba.writeln(
644
+ "If TypeOf Application.Caller Is Range Then "
645
+ "On Error GoTo failed"
646
+ )
647
+ vba.writeln(
648
+ '{fname} = XLPy.CallUDF("{module_name}", "{fname}", '
649
+ "{args_vba}, {vba_workbook}, Application.Caller)",
650
+ module_name=module_name,
651
+ fname=fname,
652
+ args_vba=args_vba,
653
+ vba_workbook=vba_workbook,
654
+ )
655
+ vba.writeln("Exit " + ftype)
656
+ with vba.block("#Else"):
657
+ vba.writeln(
658
+ '{fname} = XLPy.CallUDF("{module_name}", '
659
+ '"{fname}", {args_vba})',
660
+ module_name=module_name,
661
+ fname=fname,
662
+ args_vba=args_vba,
663
+ )
664
+ vba.writeln("Exit " + ftype)
665
+ vba.writeln("#End If")
666
+
667
+ vba.write_label("failed")
668
+ vba.writeln(fname + " = Err.Description")
669
+
670
+ vba.writeln("End " + ftype)
671
+ vba.writeln("")
672
+
673
+
674
+ def import_udfs(module_names, xl_workbook):
675
+ module_names = module_names.split(";")
676
+
677
+ tf = tempfile.NamedTemporaryFile(mode="w", delete=False)
678
+
679
+ vba = VBAWriter(tf.file)
680
+
681
+ vba.writeln('Attribute VB_Name = "xlwings_udfs"')
682
+
683
+ vba.writeln(
684
+ "'Autogenerated code by xlwings - changes will be lost with next import!"
685
+ )
686
+ vba.writeln(
687
+ """#Const App = "Microsoft Excel" 'Adjust when using outside of Excel"""
688
+ )
689
+
690
+ if __pro__:
691
+ for module_name in module_names:
692
+ sheet_config = read_config_sheet(
693
+ Book(impl=xlwings._xlwindows.Book(Dispatch(xl_workbook)))
694
+ )
695
+ code_map = sheet_config.get("RELEASE_EMBED_CODE_MAP", "{}")
696
+ sheetname_to_path = json.loads(code_map)
697
+ if module_name + ".py" in sheetname_to_path:
698
+ real_module_name = sheetname_to_path[module_name + ".py"][:-3]
699
+ module_names.remove(module_name)
700
+ module_names.append(real_module_name)
701
+
702
+ for module_name in module_names:
703
+ module = get_udf_module(module_name, xl_workbook)
704
+ generate_vba_wrapper(module_name, module, tf.file, xl_workbook)
705
+
706
+ tf.close()
707
+
708
+ try:
709
+ xl_workbook.VBProject.VBComponents.Remove(
710
+ xl_workbook.VBProject.VBComponents("xlwings_udfs")
711
+ )
712
+ except pywintypes.com_error:
713
+ pass
714
+
715
+ try:
716
+ xl_workbook.VBProject.VBComponents.Import(tf.name)
717
+ except pywintypes.com_error:
718
+ # Fallback. Some users get in Excel "Automation error 440" with this traceback
719
+ # in Python: pywintypes.com_error: (-2147352567, 'Exception occurred.',
720
+ # (0, None, None, None, 0, -2146827284), None)
721
+ xl_workbook.Application.Run("ImportXlwingsUdfsModule", tf.name)
722
+
723
+ for module_name in module_names:
724
+ module = get_udf_module(module_name, xl_workbook)
725
+ for mvar in map(lambda attr: getattr(module, attr), dir(module)):
726
+ if hasattr(mvar, "__xlfunc__"):
727
+ xlfunc = mvar.__xlfunc__
728
+ xlret = xlfunc["ret"]
729
+ xlargs = xlfunc["args"]
730
+ fname = xlfunc["name"]
731
+ fdoc = xlret["doc"][:255]
732
+ fcategory = xlfunc["category"]
733
+
734
+ excel_version = [
735
+ int(x) for x in re.split("[,\\.]", xl_workbook.Application.Version)
736
+ ]
737
+ if excel_version[0] >= 14:
738
+ argdocs = [arg["doc"][:255] for arg in xlargs if not arg["vba"]]
739
+ xl_workbook.Application.MacroOptions(
740
+ "'" + xl_workbook.Name + "'!" + fname,
741
+ Description=fdoc,
742
+ HasMenu=False,
743
+ MenuText=None,
744
+ HasShortcutKey=False,
745
+ ShortcutKey=None,
746
+ Category=fcategory,
747
+ StatusBar=None,
748
+ HelpContextID=None,
749
+ HelpFile=None,
750
+ ArgumentDescriptions=argdocs if argdocs else None,
751
+ )
752
+ else:
753
+ xl_workbook.Application.MacroOptions(
754
+ "'" + xl_workbook.Name + "'!" + fname, Description=fdoc
755
+ )
756
+
757
+ # try to delete the temp file - doesn't matter too much if it fails
758
+ try:
759
+ os.unlink(tf.name)
760
+ except: # noqa: E722
761
+ pass
762
+ msg = f'Imported functions from the following modules: {", ".join(module_names)}'
763
+ logger.info(msg) if logger.hasHandlers() else print(msg)
764
+
765
+
766
+ @functools.lru_cache(None)
767
+ def has_dynamic_array(pid):
768
+ """This check in this form doesn't work on macOS,
769
+ that's why it's here and not in utils
770
+ """
771
+ try:
772
+ apps[pid].api.WorksheetFunction.Unique("dummy")
773
+ return True
774
+ except (AttributeError, pywintypes.com_error):
775
+ return False