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.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: xlwings_utils
3
- Version: 25.0.0.post1
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
- Metadata-Version: 2.4
2
- Name: xlwings_utils
3
- Version: 25.0.0.post1
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
- ## Capture stdout support
123
-
124
- The module has support for capturing stdout and -later- using showing the captured output on a sheet.
125
-
126
- This is rather important as printing in xlwings lite to the UI pane is rather slow.
127
-
128
- In order to capture stdout output, use
129
-
130
-
131
- ```
132
- with xwu.capture:
133
- """
134
- code with print statements
135
- """
136
- ```
137
-
138
- and then the captured output can be copied to a sheet, like
139
-
140
- ```
141
- sheet.range(4,5).value = xwu.capture.value
142
- ```
143
- Upon reading the value, the capture buffer will be emptied.
144
-
145
- If you don't want the buffer to be emptied after accessing the value, use `xwu.capture.value_keep`.
146
-
147
- The capture buffer can also be retrieved as a string with `xwu.capture.str` and `xwu.capture.str_keep`.
148
-
149
- Clearing the captured stdout buffer can be done at any time with `xwu.capture.clear()`.
150
-
151
- 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.
152
-
153
-
154
- ## Contact info
155
-
156
- You can contact Ruud van der Ham, the core developer, via ruud@salabim.org .
157
-
158
- ## Badges
159
-
160
- ![PyPI](https://img.shields.io/pypi/v/xlwings-utils) ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/xlwings-utils) ![PyPI - Implementation](https://img.shields.io/pypi/implementation/xlwings-utils)
161
- ![PyPI - License](https://img.shields.io/pypi/l/xlwings-utils) ![ruff](https://img.shields.io/badge/style-ruff-41B5BE?style=flat)
162
- ![GitHub last commit](https://img.shields.io/github/last-commit/salabim/peek)
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
+ ![PyPI](https://img.shields.io/pypi/v/xlwings-utils) ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/xlwings-utils) ![PyPI - Implementation](https://img.shields.io/pypi/implementation/xlwings-utils)
195
+ ![PyPI - License](https://img.shields.io/pypi/l/xlwings-utils) ![ruff](https://img.shields.io/badge/style-ruff-41B5BE?style=flat)
196
+ ![GitHub last commit](https://img.shields.io/github/last-commit/salabim/peek)
197
+
@@ -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.post1"
13
+ version = "25.0.0.post2"
14
14
  readme = "README.md"
15
15
  requires-python = ">=3.9"
16
16
  dependencies = [
@@ -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 is not None:
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
- if row2 is None:
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
 
@@ -1,149 +1,211 @@
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
- ## Capture stdout support
109
-
110
- The module has support for capturing stdout and -later- using showing the captured output on a sheet.
111
-
112
- This is rather important as printing in xlwings lite to the UI pane is rather slow.
113
-
114
- In order to capture stdout output, use
115
-
116
-
117
- ```
118
- with xwu.capture:
119
- """
120
- code with print statements
121
- """
122
- ```
123
-
124
- and then the captured output can be copied to a sheet, like
125
-
126
- ```
127
- sheet.range(4,5).value = xwu.capture.value
128
- ```
129
- Upon reading the value, the capture buffer will be emptied.
130
-
131
- If you don't want the buffer to be emptied after accessing the value, use `xwu.capture.value_keep`.
132
-
133
- The capture buffer can also be retrieved as a string with `xwu.capture.str` and `xwu.capture.str_keep`.
134
-
135
- Clearing the captured stdout buffer can be done at any time with `xwu.capture.clear()`.
136
-
137
- 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.
138
-
139
-
140
- ## Contact info
141
-
142
- You can contact Ruud van der Ham, the core developer, via ruud@salabim.org .
143
-
144
- ## Badges
145
-
146
- ![PyPI](https://img.shields.io/pypi/v/xlwings-utils) ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/xlwings-utils) ![PyPI - Implementation](https://img.shields.io/pypi/implementation/xlwings-utils)
147
- ![PyPI - License](https://img.shields.io/pypi/l/xlwings-utils) ![ruff](https://img.shields.io/badge/style-ruff-41B5BE?style=flat)
148
- ![GitHub last commit](https://img.shields.io/github/last-commit/salabim/peek)
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
+ ![PyPI](https://img.shields.io/pypi/v/xlwings-utils) ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/xlwings-utils) ![PyPI - Implementation](https://img.shields.io/pypi/implementation/xlwings-utils)
209
+ ![PyPI - License](https://img.shields.io/pypi/l/xlwings-utils) ![ruff](https://img.shields.io/badge/style-ruff-41B5BE?style=flat)
210
+ ![GitHub last commit](https://img.shields.io/github/last-commit/salabim/peek)
211
+