xlwings-utils 25.0.0.post1__tar.gz → 25.0.0.post2__tar.gz
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.
Potentially problematic release.
This version of xlwings-utils might be problematic. Click here for more details.
- {xlwings_utils-25.0.0.post1/xlwings_utils.egg-info → xlwings_utils-25.0.0.post2}/PKG-INFO +49 -1
- xlwings_utils-25.0.0.post1/PKG-INFO → xlwings_utils-25.0.0.post2/README.md +197 -163
- {xlwings_utils-25.0.0.post1 → xlwings_utils-25.0.0.post2}/pyproject.toml +1 -1
- {xlwings_utils-25.0.0.post1 → xlwings_utils-25.0.0.post2}/xlwings_utils/xlwings_utils.py +198 -2
- xlwings_utils-25.0.0.post1/README.md → xlwings_utils-25.0.0.post2/xlwings_utils.egg-info/PKG-INFO +211 -149
- {xlwings_utils-25.0.0.post1 → xlwings_utils-25.0.0.post2}/setup.cfg +0 -0
- {xlwings_utils-25.0.0.post1 → xlwings_utils-25.0.0.post2}/tests/test_xlwings_utils.py +0 -0
- {xlwings_utils-25.0.0.post1 → xlwings_utils-25.0.0.post2}/xlwings_utils/__init__.py +0 -0
- {xlwings_utils-25.0.0.post1 → xlwings_utils-25.0.0.post2}/xlwings_utils.egg-info/SOURCES.txt +0 -0
- {xlwings_utils-25.0.0.post1 → xlwings_utils-25.0.0.post2}/xlwings_utils.egg-info/dependency_links.txt +0 -0
- {xlwings_utils-25.0.0.post1 → xlwings_utils-25.0.0.post2}/xlwings_utils.egg-info/requires.txt +0 -0
- {xlwings_utils-25.0.0.post1 → xlwings_utils-25.0.0.post2}/xlwings_utils.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: xlwings_utils
|
|
3
|
-
Version: 25.0.0.
|
|
3
|
+
Version: 25.0.0.post2
|
|
4
4
|
Summary: xlwings_utils
|
|
5
5
|
Author-email: Ruud van der Ham <rt.van.der.ham@gmail.com>
|
|
6
6
|
Project-URL: Homepage, https://github.com/salabim/xlwings_utils
|
|
@@ -119,6 +119,54 @@ sheet.range(10,1).value = this_block.minimized().value
|
|
|
119
119
|
|
|
120
120
|
In this case, only the really processed rows are copied to the sheet.
|
|
121
121
|
|
|
122
|
+
With blocks, it is easy to use a sheet as an input for a project / scenario.
|
|
123
|
+
|
|
124
|
+
Like, something like
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
Of course we could access the various input fields with absolute ranges, but if something
|
|
128
|
+
changes later (like adding a row), all references have to be updated.
|
|
129
|
+
|
|
130
|
+
If we read the project sheet (partly) into a block, lookup methods are available to access 'fields' easily and future proof.
|
|
131
|
+
|
|
132
|
+
Let's see how this works with the above sheet. The block (bl) looks like
|
|
133
|
+
|
|
134
|
+
```
|
|
135
|
+
| 1 2 3 4 5
|
|
136
|
+
--+-------------------------------------------------------------------------------
|
|
137
|
+
1 | Project Factory1
|
|
138
|
+
2 | Name Mega1
|
|
139
|
+
3 | Start date 2025-05-17
|
|
140
|
+
4 | End date 2026-02-01
|
|
141
|
+
5 |
|
|
142
|
+
6 | Parts Width Length Height Weight
|
|
143
|
+
7 | A 10 5 5 100
|
|
144
|
+
8 | B 11 5 8 102
|
|
145
|
+
9 | C 12 2 3 91
|
|
146
|
+
10|
|
|
147
|
+
|
|
148
|
+
Now we can do
|
|
149
|
+
project = bl.lookup("Project")
|
|
150
|
+
name = bl.lookup("Start date")
|
|
151
|
+
start_date = bl.lookup("Start date")
|
|
152
|
+
end_date = bl.lookup("End date")
|
|
153
|
+
row1 = bl.lookup_row("Parts")
|
|
154
|
+
parts=[]
|
|
155
|
+
for row2 in range(row1 + 1, bl.highest_used_row_number + 1):
|
|
156
|
+
if not (part_name := bl.hlookup("Part",row1=row1, row2=row2)):
|
|
157
|
+
# stop when reach a 'blank' part_name
|
|
158
|
+
break
|
|
159
|
+
width = bl.hlookup("Width",row1=row1, row2=row2)
|
|
160
|
+
length = bl.hlookup("Length",row1=row1, row2=row2)
|
|
161
|
+
height = bl.hlookup("HeightL",row1=row1, row2=row2)
|
|
162
|
+
weight = bl.hlookup("Weight",row1=row1, row2=row2)
|
|
163
|
+
parts.append(Part(part_name, width, length, height, weight))
|
|
164
|
+
|
|
165
|
+
You see lookup, which is vertical lookup, which just scans column 1 for the given label and then returns the corresponding value from column 2.
|
|
166
|
+
|
|
167
|
+
Then, there's lookup_row, which also scans column1 for the given label (Parts), but returns the corresponding row (6). Store it in row1.
|
|
168
|
+
And then we just read the following rows (with hlookup) and access the required values.
|
|
169
|
+
|
|
122
170
|
## Capture stdout support
|
|
123
171
|
|
|
124
172
|
The module has support for capturing stdout and -later- using showing the captured output on a sheet.
|
|
@@ -1,163 +1,197 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
block
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
1
|
+
<img src="https://www.salabim.org/xlwings_utils_logo2.png">
|
|
2
|
+
|
|
3
|
+
## Introduction
|
|
4
|
+
|
|
5
|
+
This module provides some useful functions to be used in xlwings (lite).
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
Just add xlwings-utils to the *requirements.txt* tab.
|
|
10
|
+
|
|
11
|
+
In the script, add
|
|
12
|
+
|
|
13
|
+
```ìmport xlwings_utils as xwu```
|
|
14
|
+
|
|
15
|
+
> [!NOTE]
|
|
16
|
+
>
|
|
17
|
+
> The GitHub repository can be found on https://github.com/salabim/xlwings_utils .
|
|
18
|
+
|
|
19
|
+
## Dropbox support
|
|
20
|
+
|
|
21
|
+
The xlwings lite system does not provide access to the local file system. With this module, files can be copied between Dropbox and the local pyodide file system, making it possible to indirectly use the local file system.
|
|
22
|
+
|
|
23
|
+
It is only possible, as of now, to use full-access Dropbox apps.
|
|
24
|
+
|
|
25
|
+
The easiest way to use the Dropbox functionality is to add the credentials to the environment variables. Add REFRESH_TOKEN, APP_KEY and APP_SECRET with their corresponding values to the environment variables.
|
|
26
|
+
|
|
27
|
+
Then, it is possible to list all files in a specified folder with the function `list_dropbox`.
|
|
28
|
+
It is also possible to get the folders and to access all underlying folders.
|
|
29
|
+
|
|
30
|
+
The function `read_dropbox` can be used to read a Dropbox file's contents (bytes).
|
|
31
|
+
|
|
32
|
+
The function `write_dropbox` can be used to write contents (bytes) to a Dropbox file.
|
|
33
|
+
|
|
34
|
+
The functions `list_local`, `read_local` and `write_local` offer similar functionality for the local file system (on pyodide).
|
|
35
|
+
|
|
36
|
+
So, a way to access a file on the system's drive (mapped to Dropbox) as a local file is:
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
contents = xlwings_utils.read_dropbox('/downloads/file1.xls')
|
|
40
|
+
xlwings_utils.write_local('file1.xlsx')
|
|
41
|
+
df = pandas.read_excel"file1.xlsx")
|
|
42
|
+
...
|
|
43
|
+
```
|
|
44
|
+
And the other direction:
|
|
45
|
+
```
|
|
46
|
+
contents = xlwings_utils.read_local('file1.gif')
|
|
47
|
+
xlwings_utils.write_dropbox('/downloads/file1.gif')
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Block support
|
|
51
|
+
|
|
52
|
+
The module contains a useful 2-dimensional data structure: *block*.
|
|
53
|
+
This can be useful to manipulate a range without accessing the range directly, which is expensive in terms of memory and execution time.
|
|
54
|
+
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.
|
|
55
|
+
So, `my_block(lol)[row, col]` is roughly equivalent to `lol[row-1][col-1]`
|
|
56
|
+
|
|
57
|
+
A block stores the values internally as a dictionary and will only convert these to a list of lists when using `block.value`.
|
|
58
|
+
|
|
59
|
+
Converting the value of a range (usually a list of lists, but can also be a list or scalar) to a block can be done with
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
my_block = xwu.block.from_value(range.value)
|
|
63
|
+
```
|
|
64
|
+
The dimensions (number of rows and number of columns) are automatically set.
|
|
65
|
+
|
|
66
|
+
Setting of an individual item (one-based, like range) can be done like
|
|
67
|
+
```
|
|
68
|
+
my_block[row, column] = x
|
|
69
|
+
```
|
|
70
|
+
And, likewise, reading an individual item can be done like
|
|
71
|
+
```
|
|
72
|
+
x = my_block[row, column]
|
|
73
|
+
```
|
|
74
|
+
It is not allowed t,o read or write outside the block dimensions.
|
|
75
|
+
|
|
76
|
+
It is also possible to define an empty block, like
|
|
77
|
+
```
|
|
78
|
+
block = xlwings_utils.block(number_of_rows, number_columns)
|
|
79
|
+
```
|
|
80
|
+
The dimensions can be queried or redefined with `block.number_of_rows` and
|
|
81
|
+
`block.number_of_columns`.
|
|
82
|
+
|
|
83
|
+
To assign a block to range, use
|
|
84
|
+
```
|
|
85
|
+
range.value = block.value
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
The property `block.highest_used_row_number` returns the row number of the highest non-None cell.
|
|
89
|
+
|
|
90
|
+
The property `block.highest_used_column_number` returns the column_number of the highest non-None cell.
|
|
91
|
+
|
|
92
|
+
The method `block.minimized()` returns a block that has the dimensions of (highest_used_row_number, highest_used_column_number).
|
|
93
|
+
|
|
94
|
+
Particularly if we process an unknown number of lines, we can do something like:
|
|
95
|
+
|
|
96
|
+
```
|
|
97
|
+
this_block = xwu.block(number_of_rows=10000, number_of_columns=2)
|
|
98
|
+
for row in range(1, 10001):
|
|
99
|
+
this_block[row,1]= ...
|
|
100
|
+
this_block[row,2]= ...
|
|
101
|
+
if ...: # end condition
|
|
102
|
+
break
|
|
103
|
+
sheet.range(10,1).value = this_block.minimized().value
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
In this case, only the really processed rows are copied to the sheet.
|
|
107
|
+
|
|
108
|
+
With blocks, it is easy to use a sheet as an input for a project / scenario.
|
|
109
|
+
|
|
110
|
+
Like, something like
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
Of course we could access the various input fields with absolute ranges, but if something
|
|
114
|
+
changes later (like adding a row), all references have to be updated.
|
|
115
|
+
|
|
116
|
+
If we read the project sheet (partly) into a block, lookup methods are available to access 'fields' easily and future proof.
|
|
117
|
+
|
|
118
|
+
Let's see how this works with the above sheet. The block (bl) looks like
|
|
119
|
+
|
|
120
|
+
```
|
|
121
|
+
| 1 2 3 4 5
|
|
122
|
+
--+-------------------------------------------------------------------------------
|
|
123
|
+
1 | Project Factory1
|
|
124
|
+
2 | Name Mega1
|
|
125
|
+
3 | Start date 2025-05-17
|
|
126
|
+
4 | End date 2026-02-01
|
|
127
|
+
5 |
|
|
128
|
+
6 | Parts Width Length Height Weight
|
|
129
|
+
7 | A 10 5 5 100
|
|
130
|
+
8 | B 11 5 8 102
|
|
131
|
+
9 | C 12 2 3 91
|
|
132
|
+
10|
|
|
133
|
+
|
|
134
|
+
Now we can do
|
|
135
|
+
project = bl.lookup("Project")
|
|
136
|
+
name = bl.lookup("Start date")
|
|
137
|
+
start_date = bl.lookup("Start date")
|
|
138
|
+
end_date = bl.lookup("End date")
|
|
139
|
+
row1 = bl.lookup_row("Parts")
|
|
140
|
+
parts=[]
|
|
141
|
+
for row2 in range(row1 + 1, bl.highest_used_row_number + 1):
|
|
142
|
+
if not (part_name := bl.hlookup("Part",row1=row1, row2=row2)):
|
|
143
|
+
# stop when reach a 'blank' part_name
|
|
144
|
+
break
|
|
145
|
+
width = bl.hlookup("Width",row1=row1, row2=row2)
|
|
146
|
+
length = bl.hlookup("Length",row1=row1, row2=row2)
|
|
147
|
+
height = bl.hlookup("HeightL",row1=row1, row2=row2)
|
|
148
|
+
weight = bl.hlookup("Weight",row1=row1, row2=row2)
|
|
149
|
+
parts.append(Part(part_name, width, length, height, weight))
|
|
150
|
+
|
|
151
|
+
You see lookup, which is vertical lookup, which just scans column 1 for the given label and then returns the corresponding value from column 2.
|
|
152
|
+
|
|
153
|
+
Then, there's lookup_row, which also scans column1 for the given label (Parts), but returns the corresponding row (6). Store it in row1.
|
|
154
|
+
And then we just read the following rows (with hlookup) and access the required values.
|
|
155
|
+
|
|
156
|
+
## Capture stdout support
|
|
157
|
+
|
|
158
|
+
The module has support for capturing stdout and -later- using showing the captured output on a sheet.
|
|
159
|
+
|
|
160
|
+
This is rather important as printing in xlwings lite to the UI pane is rather slow.
|
|
161
|
+
|
|
162
|
+
In order to capture stdout output, use
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
```
|
|
166
|
+
with xwu.capture:
|
|
167
|
+
"""
|
|
168
|
+
code with print statements
|
|
169
|
+
"""
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
and then the captured output can be copied to a sheet, like
|
|
173
|
+
|
|
174
|
+
```
|
|
175
|
+
sheet.range(4,5).value = xwu.capture.value
|
|
176
|
+
```
|
|
177
|
+
Upon reading the value, the capture buffer will be emptied.
|
|
178
|
+
|
|
179
|
+
If you don't want the buffer to be emptied after accessing the value, use `xwu.capture.value_keep`.
|
|
180
|
+
|
|
181
|
+
The capture buffer can also be retrieved as a string with `xwu.capture.str` and `xwu.capture.str_keep`.
|
|
182
|
+
|
|
183
|
+
Clearing the captured stdout buffer can be done at any time with `xwu.capture.clear()`.
|
|
184
|
+
|
|
185
|
+
Normally, stdout will not be sent to the xlwings lite UI panel when captured with the `xwu.capture` context manager. However, if you specify `xwu.capture.include_print = True`, the output will be sent to the UI panel as well. Note that this setting remains active until a `xwu.capture.include_print = False` is issued.
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
## Contact info
|
|
189
|
+
|
|
190
|
+
You can contact Ruud van der Ham, the core developer, via ruud@salabim.org .
|
|
191
|
+
|
|
192
|
+
## Badges
|
|
193
|
+
|
|
194
|
+
  
|
|
195
|
+
 
|
|
196
|
+

|
|
197
|
+
|
|
@@ -12,6 +12,7 @@ import dropbox
|
|
|
12
12
|
from pathlib import Path
|
|
13
13
|
import os
|
|
14
14
|
import sys
|
|
15
|
+
import math
|
|
15
16
|
|
|
16
17
|
dbx = None
|
|
17
18
|
Pythonista = sys.platform == "ios"
|
|
@@ -287,6 +288,17 @@ class block:
|
|
|
287
288
|
if number_of_columns is not None:
|
|
288
289
|
self.number_of_columns = number_of_columns
|
|
289
290
|
|
|
291
|
+
@classmethod
|
|
292
|
+
def from_xlrd_sheet(cls,sheet, number_of_rows=None, number_of_columns=None):
|
|
293
|
+
v = [sheet.row_values(row_idx)[0 : sheet.ncols] for row_idx in range(0, sheet.nrows)]
|
|
294
|
+
return cls(v, number_of_rows=number_of_rows, number_of_columns=number_of_columns)
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
@classmethod
|
|
298
|
+
def from_dataframe(cls,df, number_of_rows=None, number_of_columns=None):
|
|
299
|
+
v=df.values.tolist()
|
|
300
|
+
return cls(v, number_of_rows=number_of_rows, number_of_columns=number_of_columns)
|
|
301
|
+
|
|
290
302
|
@property
|
|
291
303
|
def value(self):
|
|
292
304
|
return [[self.dict.get((row, column)) for column in range(1, self.number_of_columns + 1)] for row in range(1, self.number_of_rows + 1)]
|
|
@@ -306,7 +318,7 @@ class block:
|
|
|
306
318
|
|
|
307
319
|
for row, row_contents in enumerate(value, 1):
|
|
308
320
|
for column, item in enumerate(row_contents, 1):
|
|
309
|
-
if item
|
|
321
|
+
if item and not (isinstance(item,float) and math.isnan(item)):
|
|
310
322
|
self.dict[row, column] = item
|
|
311
323
|
self._number_of_columns = max(self.number_of_columns, column)
|
|
312
324
|
|
|
@@ -327,6 +339,12 @@ class block:
|
|
|
327
339
|
return self.dict.get((row, column))
|
|
328
340
|
|
|
329
341
|
def minimized(self):
|
|
342
|
+
"""
|
|
343
|
+
Returns
|
|
344
|
+
-------
|
|
345
|
+
minimized block : block
|
|
346
|
+
uses highest_used_row_number and highest_used_column_number to minimize the block
|
|
347
|
+
"""
|
|
330
348
|
return block(self, number_of_rows=self.highest_used_row_number, number_of_columns=self.highest_used_column_number)
|
|
331
349
|
|
|
332
350
|
@property
|
|
@@ -385,6 +403,42 @@ class block:
|
|
|
385
403
|
raise ValueError(f"{name}={column} > number_of_columns={self.number_of_columns}")
|
|
386
404
|
|
|
387
405
|
def vlookup(self, s, *, row_from=1, row_to=None, column1=1, column2=None):
|
|
406
|
+
"""
|
|
407
|
+
searches in column1 for row between row_from and row_to for s and returns the value found at (that row, column2)
|
|
408
|
+
|
|
409
|
+
Parameters
|
|
410
|
+
----------
|
|
411
|
+
s : any
|
|
412
|
+
value to seach for
|
|
413
|
+
|
|
414
|
+
row_from : int
|
|
415
|
+
row to start search (default 1)
|
|
416
|
+
|
|
417
|
+
should be between 1 and number_of_rows
|
|
418
|
+
|
|
419
|
+
row_to : int
|
|
420
|
+
row to end search (default number_of_rows)
|
|
421
|
+
|
|
422
|
+
should be between 1 and number_of_rows
|
|
423
|
+
|
|
424
|
+
column1 : int
|
|
425
|
+
column to search in (default 1)
|
|
426
|
+
|
|
427
|
+
should be between 1 and number_of_columns
|
|
428
|
+
|
|
429
|
+
column2 : int
|
|
430
|
+
column to return looked up value from (default column1 + 1)
|
|
431
|
+
|
|
432
|
+
should be between 1 and number_of_columns
|
|
433
|
+
|
|
434
|
+
Returns
|
|
435
|
+
-------
|
|
436
|
+
block[found row number, column2] : any
|
|
437
|
+
|
|
438
|
+
Note
|
|
439
|
+
----
|
|
440
|
+
If s is not found, a ValueError is raised
|
|
441
|
+
"""
|
|
388
442
|
if column2 is None:
|
|
389
443
|
column2 = column1 + 1
|
|
390
444
|
self._check_column(column2, "column2")
|
|
@@ -392,6 +446,40 @@ class block:
|
|
|
392
446
|
return self[row, column2]
|
|
393
447
|
|
|
394
448
|
def lookup_row(self, s, *, row_from=1, row_to=None, column1=1):
|
|
449
|
+
"""
|
|
450
|
+
searches in column1 for row between row_from and row_to for s and returns that row number
|
|
451
|
+
|
|
452
|
+
Parameters
|
|
453
|
+
----------
|
|
454
|
+
s : any
|
|
455
|
+
value to seach for
|
|
456
|
+
|
|
457
|
+
row_from : int
|
|
458
|
+
row to start search (default 1)
|
|
459
|
+
|
|
460
|
+
should be between 1 and number_of_rows
|
|
461
|
+
|
|
462
|
+
row_to : int
|
|
463
|
+
row to end search (default number_of_rows)
|
|
464
|
+
|
|
465
|
+
should be between 1 and number_of_rows
|
|
466
|
+
|
|
467
|
+
column1 : int
|
|
468
|
+
column to search in (default 1)
|
|
469
|
+
|
|
470
|
+
should be between 1 and number_of_columns
|
|
471
|
+
|
|
472
|
+
column2 : int
|
|
473
|
+
column to return looked up value from (default column1 + 1)
|
|
474
|
+
|
|
475
|
+
Returns
|
|
476
|
+
-------
|
|
477
|
+
row number where block[row nunber, column1] == s : int
|
|
478
|
+
|
|
479
|
+
Note
|
|
480
|
+
----
|
|
481
|
+
If s is not found, a ValueError is raised
|
|
482
|
+
"""
|
|
395
483
|
if row_to is None:
|
|
396
484
|
row_to = self.highest_used_row_number
|
|
397
485
|
self._check_row(row_from, "row_from")
|
|
@@ -404,13 +492,83 @@ class block:
|
|
|
404
492
|
raise ValueError(f"{s} not found")
|
|
405
493
|
|
|
406
494
|
def hlookup(self, s, *, column_from=1, column_to=None, row1=1, row2=None):
|
|
407
|
-
|
|
495
|
+
"""
|
|
496
|
+
searches in row1 for column between column_from and column_to for s and returns the value found at (that column, row2)
|
|
497
|
+
|
|
498
|
+
Parameters
|
|
499
|
+
----------
|
|
500
|
+
s : any
|
|
501
|
+
value to seach for
|
|
502
|
+
|
|
503
|
+
column_from : int
|
|
504
|
+
column to start search (default 1)
|
|
505
|
+
|
|
506
|
+
should be between 1 and number_of_columns
|
|
507
|
+
|
|
508
|
+
column_to : int
|
|
509
|
+
column to end search (default number_of_columns)
|
|
510
|
+
|
|
511
|
+
should be between 1 and number_of_columns
|
|
512
|
+
|
|
513
|
+
row1 : int
|
|
514
|
+
row to search in (default 1)
|
|
515
|
+
|
|
516
|
+
should be between 1 and number_of_rows
|
|
517
|
+
|
|
518
|
+
row2 : int
|
|
519
|
+
row to return looked up value from (default row1 + 1)
|
|
520
|
+
|
|
521
|
+
should be between 1 and number_of_rows
|
|
522
|
+
|
|
523
|
+
Returns
|
|
524
|
+
-------
|
|
525
|
+
block[row, found column, row2] : any
|
|
526
|
+
|
|
527
|
+
Note
|
|
528
|
+
----
|
|
529
|
+
If s is not found, a ValueError is raised
|
|
530
|
+
"""
|
|
531
|
+
if column2 is None:
|
|
408
532
|
row2 = row1 + 1
|
|
409
533
|
self._check_row(row2, "row2")
|
|
410
534
|
column = self.lookup_column(s, column_from=column_from, column_to=column_to, row1=row1)
|
|
411
535
|
return self[row2, column]
|
|
412
536
|
|
|
413
537
|
def lookup_column(self, s, *, column_from=1, column_to=None, row1=1):
|
|
538
|
+
"""
|
|
539
|
+
searches in row1 for column between column_from and column_to for s and returns that column number
|
|
540
|
+
|
|
541
|
+
Parameters
|
|
542
|
+
----------
|
|
543
|
+
s : any
|
|
544
|
+
value to seach for
|
|
545
|
+
|
|
546
|
+
column_from : int
|
|
547
|
+
column to start search (default 1)
|
|
548
|
+
|
|
549
|
+
should be between 1 and number_of_columns
|
|
550
|
+
|
|
551
|
+
column_to : int
|
|
552
|
+
column to end search (default number_of_columns)
|
|
553
|
+
|
|
554
|
+
should be between 1 and number_of_columns
|
|
555
|
+
|
|
556
|
+
row1 : int
|
|
557
|
+
row to search in (default 1)
|
|
558
|
+
|
|
559
|
+
should be between 1 and number_of_rows
|
|
560
|
+
|
|
561
|
+
row2 : int
|
|
562
|
+
row to return looked up value from (default row1 + 1)
|
|
563
|
+
|
|
564
|
+
Returns
|
|
565
|
+
-------
|
|
566
|
+
column number where block[row1, column number] == s : int
|
|
567
|
+
|
|
568
|
+
Note
|
|
569
|
+
----
|
|
570
|
+
If s is not found, a ValueError is raised
|
|
571
|
+
"""
|
|
414
572
|
if column_to is None:
|
|
415
573
|
column_to = self.highest_used_column_number
|
|
416
574
|
self._check_column(column_from, "column_from")
|
|
@@ -423,6 +581,44 @@ class block:
|
|
|
423
581
|
raise ValueError(f"{s} not found")
|
|
424
582
|
|
|
425
583
|
def lookup(self, s, *, row_from=1, row_to=None, column1=1, column2=None):
|
|
584
|
+
"""
|
|
585
|
+
searches in column1 for row between row_from and row_to for s and returns the value found at (that row, column2)
|
|
586
|
+
|
|
587
|
+
Parameters
|
|
588
|
+
----------
|
|
589
|
+
s : any
|
|
590
|
+
value to seach for
|
|
591
|
+
|
|
592
|
+
row_from : int
|
|
593
|
+
row to start search (default 1)
|
|
594
|
+
|
|
595
|
+
should be between 1 and number_of_rows
|
|
596
|
+
|
|
597
|
+
row_to : int
|
|
598
|
+
row to end search (default number_of_rows)
|
|
599
|
+
|
|
600
|
+
should be between 1 and number_of_rows
|
|
601
|
+
|
|
602
|
+
column1 : int
|
|
603
|
+
column to search in (default 1)
|
|
604
|
+
|
|
605
|
+
should be between 1 and number_of_columns
|
|
606
|
+
|
|
607
|
+
column2 : int
|
|
608
|
+
column to return looked up value from (default column1 + 1)
|
|
609
|
+
|
|
610
|
+
should be between 1 and number_of_columns
|
|
611
|
+
|
|
612
|
+
Returns
|
|
613
|
+
-------
|
|
614
|
+
block[found row number, column2] : any
|
|
615
|
+
|
|
616
|
+
Note
|
|
617
|
+
----
|
|
618
|
+
If s is not found, a ValueError is raised
|
|
619
|
+
|
|
620
|
+
This is exactly the same as vlookup.
|
|
621
|
+
"""
|
|
426
622
|
return self.vlookup(s, row_from=row_from, row_to=row_to, column1=column1, column2=column2)
|
|
427
623
|
|
|
428
624
|
|
xlwings_utils-25.0.0.post1/README.md → xlwings_utils-25.0.0.post2/xlwings_utils.egg-info/PKG-INFO
RENAMED
|
@@ -1,149 +1,211 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
>
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
contents
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: xlwings_utils
|
|
3
|
+
Version: 25.0.0.post2
|
|
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: dropbox
|
|
13
|
+
Requires-Dist: ssl
|
|
14
|
+
|
|
15
|
+
<img src="https://www.salabim.org/xlwings_utils_logo2.png">
|
|
16
|
+
|
|
17
|
+
## Introduction
|
|
18
|
+
|
|
19
|
+
This module provides some useful functions to be used in xlwings (lite).
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
Just add xlwings-utils to the *requirements.txt* tab.
|
|
24
|
+
|
|
25
|
+
In the script, add
|
|
26
|
+
|
|
27
|
+
```ìmport xlwings_utils as xwu```
|
|
28
|
+
|
|
29
|
+
> [!NOTE]
|
|
30
|
+
>
|
|
31
|
+
> The GitHub repository can be found on https://github.com/salabim/xlwings_utils .
|
|
32
|
+
|
|
33
|
+
## Dropbox support
|
|
34
|
+
|
|
35
|
+
The xlwings lite system does not provide access to the local file system. With this module, files can be copied between Dropbox and the local pyodide file system, making it possible to indirectly use the local file system.
|
|
36
|
+
|
|
37
|
+
It is only possible, as of now, to use full-access Dropbox apps.
|
|
38
|
+
|
|
39
|
+
The easiest way to use the Dropbox functionality is to add the credentials to the environment variables. Add REFRESH_TOKEN, APP_KEY and APP_SECRET with their corresponding values to the environment variables.
|
|
40
|
+
|
|
41
|
+
Then, it is possible to list all files in a specified folder with the function `list_dropbox`.
|
|
42
|
+
It is also possible to get the folders and to access all underlying folders.
|
|
43
|
+
|
|
44
|
+
The function `read_dropbox` can be used to read a Dropbox file's contents (bytes).
|
|
45
|
+
|
|
46
|
+
The function `write_dropbox` can be used to write contents (bytes) to a Dropbox file.
|
|
47
|
+
|
|
48
|
+
The functions `list_local`, `read_local` and `write_local` offer similar functionality for the local file system (on pyodide).
|
|
49
|
+
|
|
50
|
+
So, a way to access a file on the system's drive (mapped to Dropbox) as a local file is:
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
contents = xlwings_utils.read_dropbox('/downloads/file1.xls')
|
|
54
|
+
xlwings_utils.write_local('file1.xlsx')
|
|
55
|
+
df = pandas.read_excel"file1.xlsx")
|
|
56
|
+
...
|
|
57
|
+
```
|
|
58
|
+
And the other direction:
|
|
59
|
+
```
|
|
60
|
+
contents = xlwings_utils.read_local('file1.gif')
|
|
61
|
+
xlwings_utils.write_dropbox('/downloads/file1.gif')
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Block support
|
|
65
|
+
|
|
66
|
+
The module contains a useful 2-dimensional data structure: *block*.
|
|
67
|
+
This can be useful to manipulate a range without accessing the range directly, which is expensive in terms of memory and execution time.
|
|
68
|
+
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.
|
|
69
|
+
So, `my_block(lol)[row, col]` is roughly equivalent to `lol[row-1][col-1]`
|
|
70
|
+
|
|
71
|
+
A block stores the values internally as a dictionary and will only convert these to a list of lists when using `block.value`.
|
|
72
|
+
|
|
73
|
+
Converting the value of a range (usually a list of lists, but can also be a list or scalar) to a block can be done with
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
my_block = xwu.block.from_value(range.value)
|
|
77
|
+
```
|
|
78
|
+
The dimensions (number of rows and number of columns) are automatically set.
|
|
79
|
+
|
|
80
|
+
Setting of an individual item (one-based, like range) can be done like
|
|
81
|
+
```
|
|
82
|
+
my_block[row, column] = x
|
|
83
|
+
```
|
|
84
|
+
And, likewise, reading an individual item can be done like
|
|
85
|
+
```
|
|
86
|
+
x = my_block[row, column]
|
|
87
|
+
```
|
|
88
|
+
It is not allowed t,o read or write outside the block dimensions.
|
|
89
|
+
|
|
90
|
+
It is also possible to define an empty block, like
|
|
91
|
+
```
|
|
92
|
+
block = xlwings_utils.block(number_of_rows, number_columns)
|
|
93
|
+
```
|
|
94
|
+
The dimensions can be queried or redefined with `block.number_of_rows` and
|
|
95
|
+
`block.number_of_columns`.
|
|
96
|
+
|
|
97
|
+
To assign a block to range, use
|
|
98
|
+
```
|
|
99
|
+
range.value = block.value
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
The property `block.highest_used_row_number` returns the row number of the highest non-None cell.
|
|
103
|
+
|
|
104
|
+
The property `block.highest_used_column_number` returns the column_number of the highest non-None cell.
|
|
105
|
+
|
|
106
|
+
The method `block.minimized()` returns a block that has the dimensions of (highest_used_row_number, highest_used_column_number).
|
|
107
|
+
|
|
108
|
+
Particularly if we process an unknown number of lines, we can do something like:
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
this_block = xwu.block(number_of_rows=10000, number_of_columns=2)
|
|
112
|
+
for row in range(1, 10001):
|
|
113
|
+
this_block[row,1]= ...
|
|
114
|
+
this_block[row,2]= ...
|
|
115
|
+
if ...: # end condition
|
|
116
|
+
break
|
|
117
|
+
sheet.range(10,1).value = this_block.minimized().value
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
In this case, only the really processed rows are copied to the sheet.
|
|
121
|
+
|
|
122
|
+
With blocks, it is easy to use a sheet as an input for a project / scenario.
|
|
123
|
+
|
|
124
|
+
Like, something like
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
Of course we could access the various input fields with absolute ranges, but if something
|
|
128
|
+
changes later (like adding a row), all references have to be updated.
|
|
129
|
+
|
|
130
|
+
If we read the project sheet (partly) into a block, lookup methods are available to access 'fields' easily and future proof.
|
|
131
|
+
|
|
132
|
+
Let's see how this works with the above sheet. The block (bl) looks like
|
|
133
|
+
|
|
134
|
+
```
|
|
135
|
+
| 1 2 3 4 5
|
|
136
|
+
--+-------------------------------------------------------------------------------
|
|
137
|
+
1 | Project Factory1
|
|
138
|
+
2 | Name Mega1
|
|
139
|
+
3 | Start date 2025-05-17
|
|
140
|
+
4 | End date 2026-02-01
|
|
141
|
+
5 |
|
|
142
|
+
6 | Parts Width Length Height Weight
|
|
143
|
+
7 | A 10 5 5 100
|
|
144
|
+
8 | B 11 5 8 102
|
|
145
|
+
9 | C 12 2 3 91
|
|
146
|
+
10|
|
|
147
|
+
|
|
148
|
+
Now we can do
|
|
149
|
+
project = bl.lookup("Project")
|
|
150
|
+
name = bl.lookup("Start date")
|
|
151
|
+
start_date = bl.lookup("Start date")
|
|
152
|
+
end_date = bl.lookup("End date")
|
|
153
|
+
row1 = bl.lookup_row("Parts")
|
|
154
|
+
parts=[]
|
|
155
|
+
for row2 in range(row1 + 1, bl.highest_used_row_number + 1):
|
|
156
|
+
if not (part_name := bl.hlookup("Part",row1=row1, row2=row2)):
|
|
157
|
+
# stop when reach a 'blank' part_name
|
|
158
|
+
break
|
|
159
|
+
width = bl.hlookup("Width",row1=row1, row2=row2)
|
|
160
|
+
length = bl.hlookup("Length",row1=row1, row2=row2)
|
|
161
|
+
height = bl.hlookup("HeightL",row1=row1, row2=row2)
|
|
162
|
+
weight = bl.hlookup("Weight",row1=row1, row2=row2)
|
|
163
|
+
parts.append(Part(part_name, width, length, height, weight))
|
|
164
|
+
|
|
165
|
+
You see lookup, which is vertical lookup, which just scans column 1 for the given label and then returns the corresponding value from column 2.
|
|
166
|
+
|
|
167
|
+
Then, there's lookup_row, which also scans column1 for the given label (Parts), but returns the corresponding row (6). Store it in row1.
|
|
168
|
+
And then we just read the following rows (with hlookup) and access the required values.
|
|
169
|
+
|
|
170
|
+
## Capture stdout support
|
|
171
|
+
|
|
172
|
+
The module has support for capturing stdout and -later- using showing the captured output on a sheet.
|
|
173
|
+
|
|
174
|
+
This is rather important as printing in xlwings lite to the UI pane is rather slow.
|
|
175
|
+
|
|
176
|
+
In order to capture stdout output, use
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
```
|
|
180
|
+
with xwu.capture:
|
|
181
|
+
"""
|
|
182
|
+
code with print statements
|
|
183
|
+
"""
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
and then the captured output can be copied to a sheet, like
|
|
187
|
+
|
|
188
|
+
```
|
|
189
|
+
sheet.range(4,5).value = xwu.capture.value
|
|
190
|
+
```
|
|
191
|
+
Upon reading the value, the capture buffer will be emptied.
|
|
192
|
+
|
|
193
|
+
If you don't want the buffer to be emptied after accessing the value, use `xwu.capture.value_keep`.
|
|
194
|
+
|
|
195
|
+
The capture buffer can also be retrieved as a string with `xwu.capture.str` and `xwu.capture.str_keep`.
|
|
196
|
+
|
|
197
|
+
Clearing the captured stdout buffer can be done at any time with `xwu.capture.clear()`.
|
|
198
|
+
|
|
199
|
+
Normally, stdout will not be sent to the xlwings lite UI panel when captured with the `xwu.capture` context manager. However, if you specify `xwu.capture.include_print = True`, the output will be sent to the UI panel as well. Note that this setting remains active until a `xwu.capture.include_print = False` is issued.
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
## Contact info
|
|
203
|
+
|
|
204
|
+
You can contact Ruud van der Ham, the core developer, via ruud@salabim.org .
|
|
205
|
+
|
|
206
|
+
## Badges
|
|
207
|
+
|
|
208
|
+
  
|
|
209
|
+
 
|
|
210
|
+

|
|
211
|
+
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{xlwings_utils-25.0.0.post1 → xlwings_utils-25.0.0.post2}/xlwings_utils.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{xlwings_utils-25.0.0.post1 → xlwings_utils-25.0.0.post2}/xlwings_utils.egg-info/requires.txt
RENAMED
|
File without changes
|
{xlwings_utils-25.0.0.post1 → xlwings_utils-25.0.0.post2}/xlwings_utils.egg-info/top_level.txt
RENAMED
|
File without changes
|