xlwings-utils 25.2.1__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.
- xlwings_utils/__init__.py +5 -0
- xlwings_utils/dropbox.py +258 -0
- xlwings_utils/local.py +53 -0
- xlwings_utils/nextcloud.py +236 -0
- xlwings_utils/xlwings_utils.py +794 -0
- xlwings_utils-25.2.1.dist-info/METADATA +391 -0
- xlwings_utils-25.2.1.dist-info/RECORD +9 -0
- xlwings_utils-25.2.1.dist-info/WHEEL +5 -0
- xlwings_utils-25.2.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: xlwings_utils
|
|
3
|
+
Version: 25.2.1
|
|
4
|
+
Summary: xlwings_utils
|
|
5
|
+
Author-email: Ruud van der Ham <rt.van.der.ham@gmail.com>
|
|
6
|
+
Project-URL: Homepage, https://github.com/salabim/xlwings_utils
|
|
7
|
+
Project-URL: Repository, https://github.com/salabim/xlwings_utils
|
|
8
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
9
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
10
|
+
Requires-Python: >=3.9
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
Requires-Dist: requests
|
|
13
|
+
|
|
14
|
+
<img src="https://www.salabim.org/xlwings_utils_logo2.png">
|
|
15
|
+
|
|
16
|
+
## Introduction
|
|
17
|
+
|
|
18
|
+
This module provides some useful functions to be used in xlwings (lite).
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
Just add `xlwings-utils` to the *requirements.txt* tab.
|
|
23
|
+
|
|
24
|
+
In the script, add
|
|
25
|
+
|
|
26
|
+
```ìmport xlwings_utils as xwu```
|
|
27
|
+
|
|
28
|
+
For *regular* installations, use
|
|
29
|
+
|
|
30
|
+
```pip install xlwings_utils```
|
|
31
|
+
|
|
32
|
+
> [!NOTE]
|
|
33
|
+
>
|
|
34
|
+
> The GitHub repository can be found on https://github.com/salabim/xlwings_utils .
|
|
35
|
+
|
|
36
|
+
General
|
|
37
|
+
|
|
38
|
+
It is recommended to put
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
import xlwings_utils as xwu
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
at the top of an xlwings lite script.
|
|
45
|
+
|
|
46
|
+
If an application runs under xlwings, `xwu.xlwings` will be True. False, if not.
|
|
47
|
+
|
|
48
|
+
## Cloud support
|
|
49
|
+
|
|
50
|
+
The xlwings lite system does not provide access to the local file system. With xlwings_lites, files can be copied between a cloud storage provider and the local pyodide file system, making it possible to indirectly use the local file system.
|
|
51
|
+
|
|
52
|
+
Currently, xlwings_utils supports full-access Dropbox apps and Nextcloud (although the latter does not work under xlwings Lite .
|
|
53
|
+
|
|
54
|
+
### Dropbox
|
|
55
|
+
|
|
56
|
+
The easiest way to use the Dropbox functionality is to add the credentials to the environment variables. Add DROPBOX.REFRESH_TOKEN,
|
|
57
|
+
DROPBOX.APP_KEY and DROPBOX.APP_SECRET with their corresponding values to the environment variables.
|
|
58
|
+
Instructions on how to get these variables can be found here.
|
|
59
|
+
|
|
60
|
+
In order to make a Dropbox app, and get the required environment variables, just execute this line from the command line (shell).
|
|
61
|
+
```
|
|
62
|
+
python -c "exec(__import__('requests').get('https://salabim.org/dropbox setup.py').text)"
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
The file `dropbox setup.py` can also be found in the repo of xlwings_lite .
|
|
66
|
+
|
|
67
|
+
Then, it is possible to list all files in a specified folder using the `dropbox.dir()` function.
|
|
68
|
+
It is also possible to get at all folders and to access all underlying folders.
|
|
69
|
+
|
|
70
|
+
The function `dropbox.read()` can be used to read a Dropbox file (as bytes).
|
|
71
|
+
|
|
72
|
+
The function `dropbox.write()` can be used to write contents (bytes) to Dropbox
|
|
73
|
+
|
|
74
|
+
The function `dropbox.delete()` can be used to delete a Dropbox file.
|
|
75
|
+
|
|
76
|
+
### Nextcloud
|
|
77
|
+
|
|
78
|
+
The easiest way to use the Nextcloud functionality is to add the credentials to the environment variables. Add NEXTCLOUD.URL, NEXTCLOUD.USERNAME and NEXTCLOUD.PASSWORD with their corresponding values to the environment variables.
|
|
79
|
+
Instructions on how to get these variables can be found here.
|
|
80
|
+
|
|
81
|
+
Login to the file section of the browser version of your Nextcloud provider, like
|
|
82
|
+
https://use11.thegood.cloud/apps/files/files
|
|
83
|
+
|
|
84
|
+
Then click on Files settings (bottom left) and you will see a header WebDAV. NEXTCLOUD.URL is the given URL.
|
|
85
|
+
Next, click *If you have enabled 2FA, you must create and use a new app password by clicking here.* " Then, create an app and copy the password to NEXTCLOUD.PASSWORD. Finally NEXTCLOUD.USERNAME is the user name.
|
|
86
|
+
|
|
87
|
+
Then, it is possible to list all files in a specified folder using the `nextcloud.dir()` function.
|
|
88
|
+
It is also possible to get at all folders and to access all underlying folders.
|
|
89
|
+
|
|
90
|
+
The function `nextcloud.read()` can be used to read a Nextcloud file (as bytes).
|
|
91
|
+
|
|
92
|
+
The function `nextcloud.write()` can be used to write contents (bytes) to Nextcloud
|
|
93
|
+
|
|
94
|
+
The function `nextcloud.delete()` can be used to delete a Nextcloud file.
|
|
95
|
+
|
|
96
|
+
> [!IMPORTANT]
|
|
97
|
+
>
|
|
98
|
+
> As of now, nextcloud does not work under xlwings Lite, because of limitations in a sandboxed environments (it runs under native Python though)
|
|
99
|
+
|
|
100
|
+
### for all cloud services
|
|
101
|
+
|
|
102
|
+
Access is the same for Dropbox and Nextcloud.
|
|
103
|
+
|
|
104
|
+
So, a way to access a file on the system's drive (mapped to Dropbox) as a local file is:
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
cloud = xwu.dropbox
|
|
108
|
+
contents = cloud.read('/downloads/file1.xls')
|
|
109
|
+
local.write('file1.xlsx')
|
|
110
|
+
df = pandas.read_excel"file1.xlsx")
|
|
111
|
+
...
|
|
112
|
+
```
|
|
113
|
+
And the other direction:
|
|
114
|
+
```
|
|
115
|
+
contents = local.read('file1.gif')
|
|
116
|
+
cloud.write('/downloads/file1.gif')
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Block support
|
|
120
|
+
|
|
121
|
+
The module contains a useful 2-dimensional data structure: *block*.
|
|
122
|
+
This can be useful for manipulating a range without accessing it directly, which is expensive in terms of memory and execution time.
|
|
123
|
+
The advantage over an ordinary list of lists is that a block is index one-based, in line with range and addressing is done with a row, column tuple.
|
|
124
|
+
So, `my_block[row, col]` is roughly equivalent to `lol[row-1][col-1]`
|
|
125
|
+
|
|
126
|
+
A block stores the values internally as a dictionary and will only convert these to a list of lists when using `block.value`.
|
|
127
|
+
|
|
128
|
+
Converting of a range to a block can be done like
|
|
129
|
+
|
|
130
|
+
```
|
|
131
|
+
rng = book.sheets.active.range((5,7),(8,100))
|
|
132
|
+
my_block = xwu.block.from_range(rng
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
A value of a range (list of lists, list of scalars or scalar can be converted to a block with
|
|
136
|
+
```
|
|
137
|
+
my_block = xwu.block.from_value(rng.value)
|
|
138
|
+
```
|
|
139
|
+
The dimensions (number of rows and number of columns) are automatically set.
|
|
140
|
+
|
|
141
|
+
Setting of an individual item (one-based, like range) can be done like
|
|
142
|
+
```
|
|
143
|
+
my_block[row, column] = x
|
|
144
|
+
```
|
|
145
|
+
And, likewise, reading an individual item can be done like
|
|
146
|
+
```
|
|
147
|
+
x = my_block[row, column]
|
|
148
|
+
```
|
|
149
|
+
It is not allowed to read or write outside the block dimensions.
|
|
150
|
+
|
|
151
|
+
It is also possible to define an empty block, like
|
|
152
|
+
```
|
|
153
|
+
block = xwu.block(number_of_rows, number_columns)
|
|
154
|
+
```
|
|
155
|
+
The dimensions can be queried or redefined with `block.number_of_rows` and
|
|
156
|
+
`block.number_of_columns`.
|
|
157
|
+
|
|
158
|
+
It is also possible to get a copy of a block with different dimensions:
|
|
159
|
+
|
|
160
|
+
```
|
|
161
|
+
bl = my.block.reshape(number_of_rows=2, number_of_columns=10)
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
The property `block.highest_used_row_number` returns the row number of the highest non-None cell.
|
|
165
|
+
|
|
166
|
+
The property `block.highest_used_column_number` returns the column_number of the highest non-None cell.
|
|
167
|
+
|
|
168
|
+
The method `block.minimized()` returns a block that has the dimensions of (highest_used_row_number, highest_used_column_number).
|
|
169
|
+
|
|
170
|
+
Particularly if we process an unknown number of lines, we can do something like:
|
|
171
|
+
|
|
172
|
+
```
|
|
173
|
+
this_block = xwu.block(number_of_rows=10000, number_of_columns=2)
|
|
174
|
+
for row in range(1, 10001):
|
|
175
|
+
this_block[row,1]= ...
|
|
176
|
+
this_block[row,2]= ...
|
|
177
|
+
if ...: # end condition
|
|
178
|
+
break
|
|
179
|
+
sheet.range((10,1)).value = this_block.minimized().value
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
In this case, only the really processed rows are copied to the sheet.
|
|
183
|
+
|
|
184
|
+
### Looking up in a block
|
|
185
|
+
|
|
186
|
+
With blocks, it is easy to use a sheet as an input for a project / scenario.
|
|
187
|
+
|
|
188
|
+
Something like
|
|
189
|
+
<img src="https://www.salabim.org/xlwings_utils/fig01.png">
|
|
190
|
+
|
|
191
|
+
Of course, we could access the various input fields with absolute ranges, but if something changes later (such as adding a row), all references would need to be updated.
|
|
192
|
+
|
|
193
|
+
If we read the project sheet (partly) into a block, lookup methods are available to access *fields* easily and future-proof:
|
|
194
|
+
|
|
195
|
+
```
|
|
196
|
+
bl = xwu.block.from_range(sheet.range(1,1),(100,10)))
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Let's see how this works with the above sheet. The corresponding block (bl) looks like
|
|
200
|
+
|
|
201
|
+
```
|
|
202
|
+
| 1 2 3 4 5
|
|
203
|
+
--+-------------------------------------------------------------------------------
|
|
204
|
+
1 | Project Factory1
|
|
205
|
+
2 | Start date 2025-05-17
|
|
206
|
+
3 | End date 2026-02-01
|
|
207
|
+
4 |
|
|
208
|
+
5 | Parts Width Length Height Weight
|
|
209
|
+
6 | A 10 5 5 100
|
|
210
|
+
7 | B 11 5 8 102
|
|
211
|
+
8 | C 12 2 3 91
|
|
212
|
+
9 |
|
|
213
|
+
```
|
|
214
|
+
Now we can do
|
|
215
|
+
```
|
|
216
|
+
project = bl.lookup("Project")
|
|
217
|
+
project = bl.lookup("Project")
|
|
218
|
+
start_date = bl.lookup("Start date")
|
|
219
|
+
end_date = bl.lookup("End date")
|
|
220
|
+
row1 = bl.lookup_row("Parts")
|
|
221
|
+
parts=[]
|
|
222
|
+
for row2 in range(row1 + 1, bl.highest_used_row_number + 1):
|
|
223
|
+
if not (part_name := bl.hlookup("Part",row1=row1, row2=row2)):
|
|
224
|
+
# stop when a 'blank' part_name is found
|
|
225
|
+
break
|
|
226
|
+
width = bl.hlookup("Width",row1=row1, row2=row2)
|
|
227
|
+
length = bl.hlookup("Length",row1=row1, row2=row2)
|
|
228
|
+
height = bl.hlookup("HeightL",row1=row1, row2=row2)
|
|
229
|
+
weight = bl.hlookup("Weight",row1=row1, row2=row2)
|
|
230
|
+
parts.append(Part(part_name, width, length, height, weight))
|
|
231
|
+
```
|
|
232
|
+
First, we perform a couple of vertical lookups to scan column 1 for the given labels and return the corresponding values from column 2.
|
|
233
|
+
|
|
234
|
+
Then, there's `lookup_row`, which also scans column1 for the given label (Parts), but returns the corresponding row (5). It is then stored in row1.
|
|
235
|
+
We then read the following rows (using hlookups) and access the required values.
|
|
236
|
+
|
|
237
|
+
### Filling a block from other sources
|
|
238
|
+
|
|
239
|
+
The advantage of using a block instead of accessing these sources directly is that they are one-based, just like in Excel.
|
|
240
|
+
|
|
241
|
+
It is possible to make a block from an xlrd worksheet with `block.from_xlrd_sheet`.
|
|
242
|
+
|
|
243
|
+
It is possible to create a block from a panda dataFrame using `block.from_dataframe`. Ensure that, if the dataframe is created by reading from an Excel sheet, headers=None is specified, e.g., `df = pd.read_excel(filename, header=None)`.
|
|
244
|
+
|
|
245
|
+
It is possible to make a block from an openpyxl worksheet with `block.from_openpyxl_sheet`.
|
|
246
|
+
|
|
247
|
+
It is possible to make a block from a text file with `block.from_file`.
|
|
248
|
+
|
|
249
|
+
### Writing a block to an openpyxl sheet
|
|
250
|
+
|
|
251
|
+
In order to write (append) to an openpyxl sheet, use: block.to_openpyxl_sheet.
|
|
252
|
+
|
|
253
|
+
## Capture stdout support
|
|
254
|
+
|
|
255
|
+
The module has support for capturing stdout and -later- using showing the captured output on a sheet.
|
|
256
|
+
|
|
257
|
+
This is rather important as printing in xlwings lite to the UI pane is rather slow.
|
|
258
|
+
|
|
259
|
+
In order to capture stdout output, it is required to first issue
|
|
260
|
+
|
|
261
|
+
```caoture
|
|
262
|
+
capture = xwu.Capture()
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
By this, capture is automatically enabled and print is disabled. Alternatively, it is possible to use
|
|
266
|
+
|
|
267
|
+
```
|
|
268
|
+
capture = xwu.Capture(enabled=False)
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
to disable the capture. And with
|
|
272
|
+
|
|
273
|
+
```
|
|
274
|
+
capture = xwu.Capture(include_print=True)
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
the stdout output is captured and printed.
|
|
278
|
+
|
|
279
|
+
Capturing can be enabled and disabled at any time with `capture.enabled = True` and `capture.enabled = False`.
|
|
280
|
+
|
|
281
|
+
And include print, likewise, with `capture.include_print`.
|
|
282
|
+
|
|
283
|
+
Alternatively, a context manager is provided:
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
```
|
|
287
|
+
with capture:
|
|
288
|
+
"""
|
|
289
|
+
code with print statements
|
|
290
|
+
"""
|
|
291
|
+
```
|
|
292
|
+
Note that stopping the capture, leaves the captured output in place, so it can be extended later.
|
|
293
|
+
|
|
294
|
+
In either case, the captured output can then be copied to a sheet, like
|
|
295
|
+
|
|
296
|
+
```
|
|
297
|
+
sheet.range(4,5).value = capture.value
|
|
298
|
+
```
|
|
299
|
+
Upon reading the value, the capture buffer will be emptied.
|
|
300
|
+
|
|
301
|
+
If you don't want the buffer to be emptied after accessing the value, use `capture.value_keep`.
|
|
302
|
+
|
|
303
|
+
The capture buffer can also be retrieved as a string with `capture.str` and `capture.str_keep`.
|
|
304
|
+
|
|
305
|
+
Clearing the captured stdout buffer can be done at any time with `capture.clear()`.
|
|
306
|
+
|
|
307
|
+
## Functionality for accessing local files via VBA
|
|
308
|
+
|
|
309
|
+
Currently, *xlwings Lite* does not provide access to the local file system. Therefore, xlwings_utils offers some functionality to trigger a VBA script as well as functionality to encode a file in the pyodide file system to a VBA sheet and to trigger writing the encoded file(s) to the local file system.
|
|
310
|
+
|
|
311
|
+
Files can be encoded into a block, like:
|
|
312
|
+
|
|
313
|
+
```
|
|
314
|
+
bl = xwu.block.encode_file("film1.mp4")
|
|
315
|
+
book.sheets["VBA"].range((10,1)).value=bl.value
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
With this code, column A will be filled with an encoded copy of the files *film1.mp4* . This can then be used with a suitable VBA macro to decode to the real file system. A VBA macro can be triggered with `xwu.trigger_macro()`. This requires an Excel worksheet where cell A1 is reserved for communication with xlwings lite. This worksheet needs to contain a macro, like
|
|
319
|
+
|
|
320
|
+
```
|
|
321
|
+
Private Sub Worksheet_Calculate()
|
|
322
|
+
If Me.Range("A1").Formula = "=NOW()" Then
|
|
323
|
+
Me.Range("A1").Value = Null
|
|
324
|
+
Call MacroToExecute
|
|
325
|
+
End If
|
|
326
|
+
End Sub
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
, where `MacroToExecute` should contain the user code, most likely code to decode file(s) encoded.
|
|
330
|
+
|
|
331
|
+
The repo contains a VBA module called xlwings_utils.bas with code to decode encoded files.
|
|
332
|
+
Just add the .bas file to a worksheet and call like
|
|
333
|
+
|
|
334
|
+
```
|
|
335
|
+
Sub MacroToExecute()
|
|
336
|
+
DecodeFile(Me, 10, 1)
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
In this example, the file *film1.mp4* will be downloaded into the current directory.
|
|
340
|
+
|
|
341
|
+
The module xlwings_utils also contains code to encode a local file to a sheet:
|
|
342
|
+
|
|
343
|
+
```
|
|
344
|
+
Sub MacroToExecute()
|
|
345
|
+
EncodeFile(Me, "data.json", 10, 1)
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
This will place an encoded version of *data.json* on the sheet.
|
|
349
|
+
|
|
350
|
+
Then, the file can be copied to the pyodide file system with
|
|
351
|
+
|
|
352
|
+
```
|
|
353
|
+
bl = block(xw.range((10,1),(50000,1)).decode_to_files())
|
|
354
|
+
```
|
|
355
|
+
## Miscellaneous
|
|
356
|
+
|
|
357
|
+
xlwings_utils provides a useful `timer` decorator that may be used to show the name, the entry time, the exit time and the duration of a xlwings script.
|
|
358
|
+
|
|
359
|
+
To use this, put the decorator immediately after the `xw.script` decorator, like:
|
|
360
|
+
|
|
361
|
+
```
|
|
362
|
+
@xw.script
|
|
363
|
+
@xwu.timer
|
|
364
|
+
def MyScript(book: xw.Book):
|
|
365
|
+
...
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
This will print something like:
|
|
369
|
+
|
|
370
|
+
```
|
|
371
|
+
Done MyScript 11:51:13.24 - 11:51:20.28 (7.04s)
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
## Contact info
|
|
383
|
+
|
|
384
|
+
You can contact Ruud van der Ham, the core developer, at ruud@salabim.org.
|
|
385
|
+
|
|
386
|
+
## Badges
|
|
387
|
+
|
|
388
|
+
  
|
|
389
|
+
 
|
|
390
|
+

|
|
391
|
+
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
xlwings_utils/__init__.py,sha256=BcgybR6ZBGP6Mdn34V6P_Sd0dFeQ5YHQSbOl1kOqWRM,137
|
|
2
|
+
xlwings_utils/dropbox.py,sha256=Leh-99eHwCP2h-I8LixCi8tL9n_E6I6EdZS1i1KqNH4,7806
|
|
3
|
+
xlwings_utils/local.py,sha256=ECt9z4iMK3ym-C2MDcVLid3mFEGZqMlQm7iZr8ayPFI,1381
|
|
4
|
+
xlwings_utils/nextcloud.py,sha256=gHSDUS-p3d2jwnIULLMhdk41vRRyznPHjShT4JH8lm8,6485
|
|
5
|
+
xlwings_utils/xlwings_utils.py,sha256=ACDgaht9FAvz6Vn3R2S-Z9Gb2Coc1cCUYJC6MT4t8YA,22994
|
|
6
|
+
xlwings_utils-25.2.1.dist-info/METADATA,sha256=Hx1wmMd6JV87ekiR1aKSFP5PbrHUDLdWTFdVpIzvdQ8,14187
|
|
7
|
+
xlwings_utils-25.2.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
8
|
+
xlwings_utils-25.2.1.dist-info/top_level.txt,sha256=kf5SEv0gZiRObPhUoYcc1O_iX_wwTOPeUIYvzyYeAM4,14
|
|
9
|
+
xlwings_utils-25.2.1.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
xlwings_utils
|