xlwings-utils 25.0.0.post2__tar.gz → 25.0.0.post4__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.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: xlwings_utils
3
- Version: 25.0.0.post2
3
+ Version: 25.0.0.post4
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
@@ -30,6 +30,16 @@ In the script, add
30
30
  >
31
31
  > The GitHub repository can be found on https://github.com/salabim/xlwings_utils .
32
32
 
33
+ General
34
+
35
+ It is recommended to put
36
+
37
+ ```
38
+ import xlwings_utils as xwu
39
+ ```
40
+
41
+ at the top of a xlwings lite script.
42
+
33
43
  ## Dropbox support
34
44
 
35
45
  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.
@@ -50,15 +60,15 @@ The functions `list_local`, `read_local` and `write_local` offer similar functio
50
60
  So, a way to access a file on the system's drive (mapped to Dropbox) as a local file is:
51
61
 
52
62
  ```
53
- contents = xlwings_utils.read_dropbox('/downloads/file1.xls')
54
- xlwings_utils.write_local('file1.xlsx')
63
+ contents = xwu.read_dropbox('/downloads/file1.xls')
64
+ xwu.write_local('file1.xlsx')
55
65
  df = pandas.read_excel"file1.xlsx")
56
66
  ...
57
67
  ```
58
68
  And the other direction:
59
69
  ```
60
- contents = xlwings_utils.read_local('file1.gif')
61
- xlwings_utils.write_dropbox('/downloads/file1.gif')
70
+ contents = xwu.read_local('file1.gif')
71
+ xwu.write_dropbox('/downloads/file1.gif')
62
72
  ```
63
73
 
64
74
  ## Block support
@@ -89,7 +99,7 @@ It is not allowed t,o read or write outside the block dimensions.
89
99
 
90
100
  It is also possible to define an empty block, like
91
101
  ```
92
- block = xlwings_utils.block(number_of_rows, number_columns)
102
+ block = xwu.block(number_of_rows, number_columns)
93
103
  ```
94
104
  The dimensions can be queried or redefined with `block.number_of_rows` and
95
105
  `block.number_of_columns`.
@@ -119,53 +129,71 @@ sheet.range(10,1).value = this_block.minimized().value
119
129
 
120
130
  In this case, only the really processed rows are copied to the sheet.
121
131
 
122
- With blocks, it is easy to use a sheet as an input for a project / scenario.
132
+ ### Looking up in a block
123
133
 
124
- Like, something like
134
+ With blocks, it is easy to use a sheet as an input for a project / scenario.
125
135
 
136
+ Something like
137
+ <img src="https://www.salabim.org/xlwings_utils/fig01.png">
126
138
 
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.
139
+ Of course, we could access the various input fields with absolute ranges, but if something changes later (like adding a row), all references would have to be updated.
129
140
 
130
- If we read the project sheet (partly) into a block, lookup methods are available to access 'fields' easily and future proof.
141
+ If we read the project sheet (partly) into a block, lookup methods are available to access 'fields' easily and future-proof.
131
142
 
132
- Let's see how this works with the above sheet. The block (bl) looks like
143
+ Let's see how this works with the above sheet. The corresponding block (bl) looks like
133
144
 
134
145
  ```
135
146
  | 1 2 3 4 5
136
147
  --+-------------------------------------------------------------------------------
137
148
  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
-
149
+ 2 | Start date 2025-05-17
150
+ 3 | End date 2026-02-01
151
+ 4 |
152
+ 5 | Parts Width Length Height Weight
153
+ 6 | A 10 5 5 100
154
+ 7 | B 11 5 8 102
155
+ 8 | C 12 2 3 91
156
+ 9 |
157
+ ```
148
158
  Now we can do
149
- project = bl.lookup("Project")
150
- name = bl.lookup("Start date")
159
+ ```project = bl.lookup("Project")
160
+ project = bl.lookup("Project")
151
161
  start_date = bl.lookup("Start date")
152
162
  end_date = bl.lookup("End date")
153
163
  row1 = bl.lookup_row("Parts")
154
164
  parts=[]
155
165
  for row2 in range(row1 + 1, bl.highest_used_row_number + 1):
156
166
  if not (part_name := bl.hlookup("Part",row1=row1, row2=row2)):
157
- # stop when reach a 'blank' part_name
167
+ # stop when a 'blank' part_name is found
158
168
  break
159
169
  width = bl.hlookup("Width",row1=row1, row2=row2)
160
170
  length = bl.hlookup("Length",row1=row1, row2=row2)
161
171
  height = bl.hlookup("HeightL",row1=row1, row2=row2)
162
172
  weight = bl.hlookup("Weight",row1=row1, row2=row2)
163
173
  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.
174
+ ```
175
+ First we do a couple of `lookup`s, which are vertical lookups, to scan column 1 for the given labels and return the corresponding values from column 2.
176
+
177
+ 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.
178
+ And then we just read the following rows (with `hlookup`) and access the required values.
179
+
180
+ ### Filling a block from other sources
181
+
182
+ The advantage of using a block instead of accessing these sources is, that they are one-based, just like in Excel.
183
+
184
+ It is possible to make a block from a xlrd worksheet with `block.from_xlrd_sheet`.
185
+
186
+ It is possible to make a block from a pandas dataframe with `block.from_dataframe`. Make sure that, if the dataframe is created by reading from an Excel sheet, headers=None should be specified, e.g. `df = pd.read_excel(filename, header=None)`.
187
+
188
+ It is possible to make a block from an openpyxl worksheet with `block.from_openpyxl_sheet`.
189
+
190
+ ### Writing a block to an openpyxl sheet
166
191
 
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.
192
+ In order to write (append) to an openpyxl sheet, use: block.to_openpyxl_sheet.
193
+
194
+ It is possible to make a block from a text file with `block.from_file`.
195
+
196
+ ### Writing a block to an openpyxl sheet
169
197
 
170
198
  ## Capture stdout support
171
199
 
@@ -173,30 +201,53 @@ The module has support for capturing stdout and -later- using showing the captur
173
201
 
174
202
  This is rather important as printing in xlwings lite to the UI pane is rather slow.
175
203
 
176
- In order to capture stdout output, use
204
+ In order to capture stdout output, it is required to first issue
177
205
 
206
+ ```caoture
207
+ capture = xwu.Capture()
208
+ ```
178
209
 
210
+ By this, capture is automatically enabled and print is disabled. Alternatively, it is possible to use
211
+
212
+ ```
213
+ capture = xwu.Capture(enabled=False)
214
+ ```
215
+
216
+ to disable the capture. And with
217
+
218
+ ```
219
+ capture = xwu.Capture(include_print=True)
179
220
  ```
180
- with xwu.capture:
221
+
222
+ the stdout output is captured and printed.
223
+
224
+ Capturing van be enabled and disabled at any time with `capture.enbaled = True` and `capture.enabled = False`.
225
+
226
+ And including print likewise with `capture.include_print`.
227
+
228
+ Alternatively, a context manager is provided:
229
+
230
+
231
+ ```
232
+ with capture:
181
233
  """
182
234
  code with print statements
183
235
  """
184
236
  ```
237
+ Note that stopping the capture, leaves the captured output in place, so it can be extended later.
185
238
 
186
- and then the captured output can be copied to a sheet, like
239
+ In either case, the captured output can be then copied to a sheet, like
187
240
 
188
241
  ```
189
- sheet.range(4,5).value = xwu.capture.value
242
+ sheet.range(4,5).value = capture.value
190
243
  ```
191
244
  Upon reading the value, the capture buffer will be emptied.
192
245
 
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`.
246
+ If you don't want the buffer to be emptied after accessing the value, use `capture.value_keep`.
196
247
 
197
- Clearing the captured stdout buffer can be done at any time with `xwu.capture.clear()`.
248
+ The capture buffer can also be retrieved as a string with `capture.str` and `capture.str_keep`.
198
249
 
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.
250
+ Clearing the captured stdout buffer can be done at any time with `capture.clear()`.
200
251
 
201
252
 
202
253
  ## Contact info
@@ -16,6 +16,16 @@ In the script, add
16
16
  >
17
17
  > The GitHub repository can be found on https://github.com/salabim/xlwings_utils .
18
18
 
19
+ General
20
+
21
+ It is recommended to put
22
+
23
+ ```
24
+ import xlwings_utils as xwu
25
+ ```
26
+
27
+ at the top of a xlwings lite script.
28
+
19
29
  ## Dropbox support
20
30
 
21
31
  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,15 +46,15 @@ The functions `list_local`, `read_local` and `write_local` offer similar functio
36
46
  So, a way to access a file on the system's drive (mapped to Dropbox) as a local file is:
37
47
 
38
48
  ```
39
- contents = xlwings_utils.read_dropbox('/downloads/file1.xls')
40
- xlwings_utils.write_local('file1.xlsx')
49
+ contents = xwu.read_dropbox('/downloads/file1.xls')
50
+ xwu.write_local('file1.xlsx')
41
51
  df = pandas.read_excel"file1.xlsx")
42
52
  ...
43
53
  ```
44
54
  And the other direction:
45
55
  ```
46
- contents = xlwings_utils.read_local('file1.gif')
47
- xlwings_utils.write_dropbox('/downloads/file1.gif')
56
+ contents = xwu.read_local('file1.gif')
57
+ xwu.write_dropbox('/downloads/file1.gif')
48
58
  ```
49
59
 
50
60
  ## Block support
@@ -75,7 +85,7 @@ It is not allowed t,o read or write outside the block dimensions.
75
85
 
76
86
  It is also possible to define an empty block, like
77
87
  ```
78
- block = xlwings_utils.block(number_of_rows, number_columns)
88
+ block = xwu.block(number_of_rows, number_columns)
79
89
  ```
80
90
  The dimensions can be queried or redefined with `block.number_of_rows` and
81
91
  `block.number_of_columns`.
@@ -105,53 +115,71 @@ sheet.range(10,1).value = this_block.minimized().value
105
115
 
106
116
  In this case, only the really processed rows are copied to the sheet.
107
117
 
108
- With blocks, it is easy to use a sheet as an input for a project / scenario.
118
+ ### Looking up in a block
109
119
 
110
- Like, something like
120
+ With blocks, it is easy to use a sheet as an input for a project / scenario.
111
121
 
122
+ Something like
123
+ <img src="https://www.salabim.org/xlwings_utils/fig01.png">
112
124
 
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.
125
+ Of course, we could access the various input fields with absolute ranges, but if something changes later (like adding a row), all references would have to be updated.
115
126
 
116
- If we read the project sheet (partly) into a block, lookup methods are available to access 'fields' easily and future proof.
127
+ If we read the project sheet (partly) into a block, lookup methods are available to access 'fields' easily and future-proof.
117
128
 
118
- Let's see how this works with the above sheet. The block (bl) looks like
129
+ Let's see how this works with the above sheet. The corresponding block (bl) looks like
119
130
 
120
131
  ```
121
132
  | 1 2 3 4 5
122
133
  --+-------------------------------------------------------------------------------
123
134
  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
-
135
+ 2 | Start date 2025-05-17
136
+ 3 | End date 2026-02-01
137
+ 4 |
138
+ 5 | Parts Width Length Height Weight
139
+ 6 | A 10 5 5 100
140
+ 7 | B 11 5 8 102
141
+ 8 | C 12 2 3 91
142
+ 9 |
143
+ ```
134
144
  Now we can do
135
- project = bl.lookup("Project")
136
- name = bl.lookup("Start date")
145
+ ```project = bl.lookup("Project")
146
+ project = bl.lookup("Project")
137
147
  start_date = bl.lookup("Start date")
138
148
  end_date = bl.lookup("End date")
139
149
  row1 = bl.lookup_row("Parts")
140
150
  parts=[]
141
151
  for row2 in range(row1 + 1, bl.highest_used_row_number + 1):
142
152
  if not (part_name := bl.hlookup("Part",row1=row1, row2=row2)):
143
- # stop when reach a 'blank' part_name
153
+ # stop when a 'blank' part_name is found
144
154
  break
145
155
  width = bl.hlookup("Width",row1=row1, row2=row2)
146
156
  length = bl.hlookup("Length",row1=row1, row2=row2)
147
157
  height = bl.hlookup("HeightL",row1=row1, row2=row2)
148
158
  weight = bl.hlookup("Weight",row1=row1, row2=row2)
149
159
  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.
160
+ ```
161
+ First we do a couple of `lookup`s, which are vertical lookups, to scan column 1 for the given labels and return the corresponding values from column 2.
162
+
163
+ 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.
164
+ And then we just read the following rows (with `hlookup`) and access the required values.
165
+
166
+ ### Filling a block from other sources
167
+
168
+ The advantage of using a block instead of accessing these sources is, that they are one-based, just like in Excel.
169
+
170
+ It is possible to make a block from a xlrd worksheet with `block.from_xlrd_sheet`.
171
+
172
+ It is possible to make a block from a pandas dataframe with `block.from_dataframe`. Make sure that, if the dataframe is created by reading from an Excel sheet, headers=None should be specified, e.g. `df = pd.read_excel(filename, header=None)`.
173
+
174
+ It is possible to make a block from an openpyxl worksheet with `block.from_openpyxl_sheet`.
175
+
176
+ ### Writing a block to an openpyxl sheet
152
177
 
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.
178
+ In order to write (append) to an openpyxl sheet, use: block.to_openpyxl_sheet.
179
+
180
+ It is possible to make a block from a text file with `block.from_file`.
181
+
182
+ ### Writing a block to an openpyxl sheet
155
183
 
156
184
  ## Capture stdout support
157
185
 
@@ -159,30 +187,53 @@ The module has support for capturing stdout and -later- using showing the captur
159
187
 
160
188
  This is rather important as printing in xlwings lite to the UI pane is rather slow.
161
189
 
162
- In order to capture stdout output, use
190
+ In order to capture stdout output, it is required to first issue
163
191
 
192
+ ```caoture
193
+ capture = xwu.Capture()
194
+ ```
164
195
 
196
+ By this, capture is automatically enabled and print is disabled. Alternatively, it is possible to use
197
+
198
+ ```
199
+ capture = xwu.Capture(enabled=False)
200
+ ```
201
+
202
+ to disable the capture. And with
203
+
204
+ ```
205
+ capture = xwu.Capture(include_print=True)
165
206
  ```
166
- with xwu.capture:
207
+
208
+ the stdout output is captured and printed.
209
+
210
+ Capturing van be enabled and disabled at any time with `capture.enbaled = True` and `capture.enabled = False`.
211
+
212
+ And including print likewise with `capture.include_print`.
213
+
214
+ Alternatively, a context manager is provided:
215
+
216
+
217
+ ```
218
+ with capture:
167
219
  """
168
220
  code with print statements
169
221
  """
170
222
  ```
223
+ Note that stopping the capture, leaves the captured output in place, so it can be extended later.
171
224
 
172
- and then the captured output can be copied to a sheet, like
225
+ In either case, the captured output can be then copied to a sheet, like
173
226
 
174
227
  ```
175
- sheet.range(4,5).value = xwu.capture.value
228
+ sheet.range(4,5).value = capture.value
176
229
  ```
177
230
  Upon reading the value, the capture buffer will be emptied.
178
231
 
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`.
232
+ If you don't want the buffer to be emptied after accessing the value, use `capture.value_keep`.
182
233
 
183
- Clearing the captured stdout buffer can be done at any time with `xwu.capture.clear()`.
234
+ The capture buffer can also be retrieved as a string with `capture.str` and `capture.str_keep`.
184
235
 
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.
236
+ Clearing the captured stdout buffer can be done at any time with `capture.clear()`.
186
237
 
187
238
 
188
239
  ## Contact info
@@ -10,7 +10,7 @@ authors = [
10
10
  { name = "Ruud van der Ham", email = "rt.van.der.ham@gmail.com" },
11
11
  ]
12
12
  description = "xlwings_utils"
13
- version = "25.0.0.post2"
13
+ version = "25.0.0.post4"
14
14
  readme = "README.md"
15
15
  requires-python = ">=3.9"
16
16
  dependencies = [
@@ -178,8 +178,20 @@ def test_capture(capsys):
178
178
  assert xwu.capture.value_keep == [['abc'], ['def'], ['ghi'], ['jkl']]
179
179
  assert xwu.capture.value == [['abc'], ['def'], ['ghi'], ['jkl']]
180
180
  assert xwu.capture.value == []
181
-
182
- # include_print is not testable with pytest
181
+
182
+ xwu.capture.enabled=True
183
+ print("abc")
184
+ print("def")
185
+ xwu.capture.enabled=False
186
+ print("xxx")
187
+ print("yyy")
188
+ xwu.capture.enabled=True
189
+ print("ghi")
190
+ print("jkl")
191
+ assert xwu.capture.str_keep == "abc\ndef\nghi\njkl\n"
192
+
193
+
194
+ # include_print is not testable with pytest
183
195
 
184
196
  if __name__ == "__main__":
185
197
  pytest.main(["-vv", "-s", "-x", __file__])
@@ -289,16 +289,30 @@ class block:
289
289
  self.number_of_columns = number_of_columns
290
290
 
291
291
  @classmethod
292
- def from_xlrd_sheet(cls,sheet, number_of_rows=None, number_of_columns=None):
292
+ def from_xlrd_sheet(cls, sheet, number_of_rows=None, number_of_columns=None):
293
293
  v = [sheet.row_values(row_idx)[0 : sheet.ncols] for row_idx in range(0, sheet.nrows)]
294
294
  return cls(v, number_of_rows=number_of_rows, number_of_columns=number_of_columns)
295
295
 
296
+ @classmethod
297
+ def from_openpyxl_sheet(cls, sheet, number_of_rows=None, number_of_columns=None):
298
+ v = [[cell.value for cell in row] for row in sheet.iter_rows()]
299
+ return cls(v, number_of_rows=number_of_rows, number_of_columns=number_of_columns)
300
+
301
+ @classmethod
302
+ def from_file(cls, filename, number_of_rows=None, number_of_columns=None):
303
+ with open(filename, "r") as f:
304
+ v = [[line if line else None] for line in f.read().splitlines()]
305
+ return cls(v, number_of_rows=number_of_rows, number_of_columns=number_of_columns)
296
306
 
297
307
  @classmethod
298
- def from_dataframe(cls,df, number_of_rows=None, number_of_columns=None):
299
- v=df.values.tolist()
308
+ def from_dataframe(cls, df, number_of_rows=None, number_of_columns=None):
309
+ v = df.values.tolist()
300
310
  return cls(v, number_of_rows=number_of_rows, number_of_columns=number_of_columns)
301
311
 
312
+ def to_openpyxl_sheet(self, sheet):
313
+ for row in self.value:
314
+ sheet.append(row)
315
+
302
316
  @property
303
317
  def value(self):
304
318
  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)]
@@ -318,7 +332,7 @@ class block:
318
332
 
319
333
  for row, row_contents in enumerate(value, 1):
320
334
  for column, item in enumerate(row_contents, 1):
321
- if item and not (isinstance(item,float) and math.isnan(item)):
335
+ if item and not (isinstance(item, float) and math.isnan(item)):
322
336
  self.dict[row, column] = item
323
337
  self._number_of_columns = max(self.number_of_columns, column)
324
338
 
@@ -405,36 +419,36 @@ class block:
405
419
  def vlookup(self, s, *, row_from=1, row_to=None, column1=1, column2=None):
406
420
  """
407
421
  searches in column1 for row between row_from and row_to for s and returns the value found at (that row, column2)
408
-
422
+
409
423
  Parameters
410
424
  ----------
411
425
  s : any
412
426
  value to seach for
413
-
427
+
414
428
  row_from : int
415
429
  row to start search (default 1)
416
-
430
+
417
431
  should be between 1 and number_of_rows
418
-
432
+
419
433
  row_to : int
420
434
  row to end search (default number_of_rows)
421
-
422
- should be between 1 and number_of_rows
423
-
435
+
436
+ should be between 1 and number_of_rows
437
+
424
438
  column1 : int
425
439
  column to search in (default 1)
426
-
440
+
427
441
  should be between 1 and number_of_columns
428
-
442
+
429
443
  column2 : int
430
444
  column to return looked up value from (default column1 + 1)
431
-
432
- should be between 1 and number_of_columns
433
-
445
+
446
+ should be between 1 and number_of_columns
447
+
434
448
  Returns
435
449
  -------
436
450
  block[found row number, column2] : any
437
-
451
+
438
452
  Note
439
453
  ----
440
454
  If s is not found, a ValueError is raised
@@ -448,34 +462,34 @@ class block:
448
462
  def lookup_row(self, s, *, row_from=1, row_to=None, column1=1):
449
463
  """
450
464
  searches in column1 for row between row_from and row_to for s and returns that row number
451
-
465
+
452
466
  Parameters
453
467
  ----------
454
468
  s : any
455
469
  value to seach for
456
-
470
+
457
471
  row_from : int
458
472
  row to start search (default 1)
459
-
473
+
460
474
  should be between 1 and number_of_rows
461
-
475
+
462
476
  row_to : int
463
477
  row to end search (default number_of_rows)
464
-
465
- should be between 1 and number_of_rows
466
-
478
+
479
+ should be between 1 and number_of_rows
480
+
467
481
  column1 : int
468
482
  column to search in (default 1)
469
-
483
+
470
484
  should be between 1 and number_of_columns
471
-
485
+
472
486
  column2 : int
473
487
  column to return looked up value from (default column1 + 1)
474
-
488
+
475
489
  Returns
476
490
  -------
477
- row number where block[row nunber, column1] == s : int
478
-
491
+ row number where block[row nunber, column1] == s : int
492
+
479
493
  Note
480
494
  ----
481
495
  If s is not found, a ValueError is raised
@@ -494,41 +508,41 @@ class block:
494
508
  def hlookup(self, s, *, column_from=1, column_to=None, row1=1, row2=None):
495
509
  """
496
510
  searches in row1 for column between column_from and column_to for s and returns the value found at (that column, row2)
497
-
511
+
498
512
  Parameters
499
513
  ----------
500
514
  s : any
501
515
  value to seach for
502
-
516
+
503
517
  column_from : int
504
518
  column to start search (default 1)
505
-
519
+
506
520
  should be between 1 and number_of_columns
507
-
521
+
508
522
  column_to : int
509
523
  column to end search (default number_of_columns)
510
-
511
- should be between 1 and number_of_columns
512
-
524
+
525
+ should be between 1 and number_of_columns
526
+
513
527
  row1 : int
514
528
  row to search in (default 1)
515
-
529
+
516
530
  should be between 1 and number_of_rows
517
-
531
+
518
532
  row2 : int
519
533
  row to return looked up value from (default row1 + 1)
520
-
521
- should be between 1 and number_of_rows
522
-
534
+
535
+ should be between 1 and number_of_rows
536
+
523
537
  Returns
524
538
  -------
525
539
  block[row, found column, row2] : any
526
-
540
+
527
541
  Note
528
542
  ----
529
543
  If s is not found, a ValueError is raised
530
544
  """
531
- if column2 is None:
545
+ if row2 is None:
532
546
  row2 = row1 + 1
533
547
  self._check_row(row2, "row2")
534
548
  column = self.lookup_column(s, column_from=column_from, column_to=column_to, row1=row1)
@@ -537,34 +551,34 @@ class block:
537
551
  def lookup_column(self, s, *, column_from=1, column_to=None, row1=1):
538
552
  """
539
553
  searches in row1 for column between column_from and column_to for s and returns that column number
540
-
554
+
541
555
  Parameters
542
556
  ----------
543
557
  s : any
544
558
  value to seach for
545
-
559
+
546
560
  column_from : int
547
561
  column to start search (default 1)
548
-
562
+
549
563
  should be between 1 and number_of_columns
550
-
564
+
551
565
  column_to : int
552
566
  column to end search (default number_of_columns)
553
-
554
- should be between 1 and number_of_columns
555
-
567
+
568
+ should be between 1 and number_of_columns
569
+
556
570
  row1 : int
557
571
  row to search in (default 1)
558
-
572
+
559
573
  should be between 1 and number_of_rows
560
-
574
+
561
575
  row2 : int
562
576
  row to return looked up value from (default row1 + 1)
563
-
577
+
564
578
  Returns
565
579
  -------
566
- column number where block[row1, column number] == s : int
567
-
580
+ column number where block[row1, column number] == s : int
581
+
568
582
  Note
569
583
  ----
570
584
  If s is not found, a ValueError is raised
@@ -583,46 +597,46 @@ class block:
583
597
  def lookup(self, s, *, row_from=1, row_to=None, column1=1, column2=None):
584
598
  """
585
599
  searches in column1 for row between row_from and row_to for s and returns the value found at (that row, column2)
586
-
600
+
587
601
  Parameters
588
602
  ----------
589
603
  s : any
590
604
  value to seach for
591
-
605
+
592
606
  row_from : int
593
607
  row to start search (default 1)
594
-
608
+
595
609
  should be between 1 and number_of_rows
596
-
610
+
597
611
  row_to : int
598
612
  row to end search (default number_of_rows)
599
-
600
- should be between 1 and number_of_rows
601
-
613
+
614
+ should be between 1 and number_of_rows
615
+
602
616
  column1 : int
603
617
  column to search in (default 1)
604
-
618
+
605
619
  should be between 1 and number_of_columns
606
-
620
+
607
621
  column2 : int
608
622
  column to return looked up value from (default column1 + 1)
609
-
610
- should be between 1 and number_of_columns
611
-
623
+
624
+ should be between 1 and number_of_columns
625
+
612
626
  Returns
613
627
  -------
614
628
  block[found row number, column2] : any
615
-
629
+
616
630
  Note
617
631
  ----
618
632
  If s is not found, a ValueError is raised
619
-
633
+
620
634
  This is exactly the same as vlookup.
621
635
  """
622
636
  return self.vlookup(s, row_from=row_from, row_to=row_to, column1=column1, column2=column2)
623
637
 
624
638
 
625
- class _capture:
639
+ class Capture:
626
640
  """
627
641
  specifies how to capture stdout
628
642
 
@@ -647,10 +661,10 @@ class _capture:
647
661
  self._buffer = []
648
662
 
649
663
  def __enter__(self):
650
- sys.stdout = self
664
+ self.enabled = True
651
665
 
652
666
  def __exit__(self, exc_type, exc_value, tb):
653
- sys.stdout = self.stdout
667
+ self.enabled = False
654
668
 
655
669
  def write(self, data):
656
670
  self._buffer.append(data)
@@ -662,6 +676,17 @@ class _capture:
662
676
  self.stdout.flush()
663
677
  self._buffer.append("\n")
664
678
 
679
+ @property
680
+ def enabled(self):
681
+ return sys.out == self
682
+
683
+ @enabled.setter
684
+ def enabled(self, value):
685
+ if value:
686
+ sys.stdout = self
687
+ else:
688
+ sys.stdout = self.stdout
689
+
665
690
  @property
666
691
  def value(self):
667
692
  result = self.value_keep
@@ -696,13 +721,6 @@ class _capture:
696
721
  self._include_print = value
697
722
 
698
723
 
699
- def reset():
700
- capture.include_print = False
701
- capture.clear()
702
-
703
-
704
- capture = _capture()
705
-
706
-
707
724
  if __name__ == "__main__":
708
725
  ...
726
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: xlwings_utils
3
- Version: 25.0.0.post2
3
+ Version: 25.0.0.post4
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
@@ -30,6 +30,16 @@ In the script, add
30
30
  >
31
31
  > The GitHub repository can be found on https://github.com/salabim/xlwings_utils .
32
32
 
33
+ General
34
+
35
+ It is recommended to put
36
+
37
+ ```
38
+ import xlwings_utils as xwu
39
+ ```
40
+
41
+ at the top of a xlwings lite script.
42
+
33
43
  ## Dropbox support
34
44
 
35
45
  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.
@@ -50,15 +60,15 @@ The functions `list_local`, `read_local` and `write_local` offer similar functio
50
60
  So, a way to access a file on the system's drive (mapped to Dropbox) as a local file is:
51
61
 
52
62
  ```
53
- contents = xlwings_utils.read_dropbox('/downloads/file1.xls')
54
- xlwings_utils.write_local('file1.xlsx')
63
+ contents = xwu.read_dropbox('/downloads/file1.xls')
64
+ xwu.write_local('file1.xlsx')
55
65
  df = pandas.read_excel"file1.xlsx")
56
66
  ...
57
67
  ```
58
68
  And the other direction:
59
69
  ```
60
- contents = xlwings_utils.read_local('file1.gif')
61
- xlwings_utils.write_dropbox('/downloads/file1.gif')
70
+ contents = xwu.read_local('file1.gif')
71
+ xwu.write_dropbox('/downloads/file1.gif')
62
72
  ```
63
73
 
64
74
  ## Block support
@@ -89,7 +99,7 @@ It is not allowed t,o read or write outside the block dimensions.
89
99
 
90
100
  It is also possible to define an empty block, like
91
101
  ```
92
- block = xlwings_utils.block(number_of_rows, number_columns)
102
+ block = xwu.block(number_of_rows, number_columns)
93
103
  ```
94
104
  The dimensions can be queried or redefined with `block.number_of_rows` and
95
105
  `block.number_of_columns`.
@@ -119,53 +129,71 @@ sheet.range(10,1).value = this_block.minimized().value
119
129
 
120
130
  In this case, only the really processed rows are copied to the sheet.
121
131
 
122
- With blocks, it is easy to use a sheet as an input for a project / scenario.
132
+ ### Looking up in a block
123
133
 
124
- Like, something like
134
+ With blocks, it is easy to use a sheet as an input for a project / scenario.
125
135
 
136
+ Something like
137
+ <img src="https://www.salabim.org/xlwings_utils/fig01.png">
126
138
 
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.
139
+ Of course, we could access the various input fields with absolute ranges, but if something changes later (like adding a row), all references would have to be updated.
129
140
 
130
- If we read the project sheet (partly) into a block, lookup methods are available to access 'fields' easily and future proof.
141
+ If we read the project sheet (partly) into a block, lookup methods are available to access 'fields' easily and future-proof.
131
142
 
132
- Let's see how this works with the above sheet. The block (bl) looks like
143
+ Let's see how this works with the above sheet. The corresponding block (bl) looks like
133
144
 
134
145
  ```
135
146
  | 1 2 3 4 5
136
147
  --+-------------------------------------------------------------------------------
137
148
  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
-
149
+ 2 | Start date 2025-05-17
150
+ 3 | End date 2026-02-01
151
+ 4 |
152
+ 5 | Parts Width Length Height Weight
153
+ 6 | A 10 5 5 100
154
+ 7 | B 11 5 8 102
155
+ 8 | C 12 2 3 91
156
+ 9 |
157
+ ```
148
158
  Now we can do
149
- project = bl.lookup("Project")
150
- name = bl.lookup("Start date")
159
+ ```project = bl.lookup("Project")
160
+ project = bl.lookup("Project")
151
161
  start_date = bl.lookup("Start date")
152
162
  end_date = bl.lookup("End date")
153
163
  row1 = bl.lookup_row("Parts")
154
164
  parts=[]
155
165
  for row2 in range(row1 + 1, bl.highest_used_row_number + 1):
156
166
  if not (part_name := bl.hlookup("Part",row1=row1, row2=row2)):
157
- # stop when reach a 'blank' part_name
167
+ # stop when a 'blank' part_name is found
158
168
  break
159
169
  width = bl.hlookup("Width",row1=row1, row2=row2)
160
170
  length = bl.hlookup("Length",row1=row1, row2=row2)
161
171
  height = bl.hlookup("HeightL",row1=row1, row2=row2)
162
172
  weight = bl.hlookup("Weight",row1=row1, row2=row2)
163
173
  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.
174
+ ```
175
+ First we do a couple of `lookup`s, which are vertical lookups, to scan column 1 for the given labels and return the corresponding values from column 2.
176
+
177
+ 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.
178
+ And then we just read the following rows (with `hlookup`) and access the required values.
179
+
180
+ ### Filling a block from other sources
181
+
182
+ The advantage of using a block instead of accessing these sources is, that they are one-based, just like in Excel.
183
+
184
+ It is possible to make a block from a xlrd worksheet with `block.from_xlrd_sheet`.
185
+
186
+ It is possible to make a block from a pandas dataframe with `block.from_dataframe`. Make sure that, if the dataframe is created by reading from an Excel sheet, headers=None should be specified, e.g. `df = pd.read_excel(filename, header=None)`.
187
+
188
+ It is possible to make a block from an openpyxl worksheet with `block.from_openpyxl_sheet`.
189
+
190
+ ### Writing a block to an openpyxl sheet
166
191
 
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.
192
+ In order to write (append) to an openpyxl sheet, use: block.to_openpyxl_sheet.
193
+
194
+ It is possible to make a block from a text file with `block.from_file`.
195
+
196
+ ### Writing a block to an openpyxl sheet
169
197
 
170
198
  ## Capture stdout support
171
199
 
@@ -173,30 +201,53 @@ The module has support for capturing stdout and -later- using showing the captur
173
201
 
174
202
  This is rather important as printing in xlwings lite to the UI pane is rather slow.
175
203
 
176
- In order to capture stdout output, use
204
+ In order to capture stdout output, it is required to first issue
177
205
 
206
+ ```caoture
207
+ capture = xwu.Capture()
208
+ ```
178
209
 
210
+ By this, capture is automatically enabled and print is disabled. Alternatively, it is possible to use
211
+
212
+ ```
213
+ capture = xwu.Capture(enabled=False)
214
+ ```
215
+
216
+ to disable the capture. And with
217
+
218
+ ```
219
+ capture = xwu.Capture(include_print=True)
179
220
  ```
180
- with xwu.capture:
221
+
222
+ the stdout output is captured and printed.
223
+
224
+ Capturing van be enabled and disabled at any time with `capture.enbaled = True` and `capture.enabled = False`.
225
+
226
+ And including print likewise with `capture.include_print`.
227
+
228
+ Alternatively, a context manager is provided:
229
+
230
+
231
+ ```
232
+ with capture:
181
233
  """
182
234
  code with print statements
183
235
  """
184
236
  ```
237
+ Note that stopping the capture, leaves the captured output in place, so it can be extended later.
185
238
 
186
- and then the captured output can be copied to a sheet, like
239
+ In either case, the captured output can be then copied to a sheet, like
187
240
 
188
241
  ```
189
- sheet.range(4,5).value = xwu.capture.value
242
+ sheet.range(4,5).value = capture.value
190
243
  ```
191
244
  Upon reading the value, the capture buffer will be emptied.
192
245
 
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`.
246
+ If you don't want the buffer to be emptied after accessing the value, use `capture.value_keep`.
196
247
 
197
- Clearing the captured stdout buffer can be done at any time with `xwu.capture.clear()`.
248
+ The capture buffer can also be retrieved as a string with `capture.str` and `capture.str_keep`.
198
249
 
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.
250
+ Clearing the captured stdout buffer can be done at any time with `capture.clear()`.
200
251
 
201
252
 
202
253
  ## Contact info