xlwings-utils 25.0.0.post1__py3-none-any.whl → 25.0.0.post3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of xlwings-utils might be problematic. Click here for more details.

@@ -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,31 @@ 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
+ @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)
306
+
307
+ @classmethod
308
+ def from_dataframe(cls, df, number_of_rows=None, number_of_columns=None):
309
+ v = df.values.tolist()
310
+ return cls(v, number_of_rows=number_of_rows, number_of_columns=number_of_columns)
311
+
312
+ def to_openpyxl_sheet(self, sheet):
313
+ for row in self.value:
314
+ sheet.append(row)
315
+
290
316
  @property
291
317
  def value(self):
292
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)]
@@ -306,7 +332,7 @@ class block:
306
332
 
307
333
  for row, row_contents in enumerate(value, 1):
308
334
  for column, item in enumerate(row_contents, 1):
309
- if item is not None:
335
+ if item and not (isinstance(item, float) and math.isnan(item)):
310
336
  self.dict[row, column] = item
311
337
  self._number_of_columns = max(self.number_of_columns, column)
312
338
 
@@ -327,6 +353,12 @@ class block:
327
353
  return self.dict.get((row, column))
328
354
 
329
355
  def minimized(self):
356
+ """
357
+ Returns
358
+ -------
359
+ minimized block : block
360
+ uses highest_used_row_number and highest_used_column_number to minimize the block
361
+ """
330
362
  return block(self, number_of_rows=self.highest_used_row_number, number_of_columns=self.highest_used_column_number)
331
363
 
332
364
  @property
@@ -385,6 +417,42 @@ class block:
385
417
  raise ValueError(f"{name}={column} > number_of_columns={self.number_of_columns}")
386
418
 
387
419
  def vlookup(self, s, *, row_from=1, row_to=None, column1=1, column2=None):
420
+ """
421
+ searches in column1 for row between row_from and row_to for s and returns the value found at (that row, column2)
422
+
423
+ Parameters
424
+ ----------
425
+ s : any
426
+ value to seach for
427
+
428
+ row_from : int
429
+ row to start search (default 1)
430
+
431
+ should be between 1 and number_of_rows
432
+
433
+ row_to : int
434
+ row to end search (default number_of_rows)
435
+
436
+ should be between 1 and number_of_rows
437
+
438
+ column1 : int
439
+ column to search in (default 1)
440
+
441
+ should be between 1 and number_of_columns
442
+
443
+ column2 : int
444
+ column to return looked up value from (default column1 + 1)
445
+
446
+ should be between 1 and number_of_columns
447
+
448
+ Returns
449
+ -------
450
+ block[found row number, column2] : any
451
+
452
+ Note
453
+ ----
454
+ If s is not found, a ValueError is raised
455
+ """
388
456
  if column2 is None:
389
457
  column2 = column1 + 1
390
458
  self._check_column(column2, "column2")
@@ -392,6 +460,40 @@ class block:
392
460
  return self[row, column2]
393
461
 
394
462
  def lookup_row(self, s, *, row_from=1, row_to=None, column1=1):
463
+ """
464
+ searches in column1 for row between row_from and row_to for s and returns that row number
465
+
466
+ Parameters
467
+ ----------
468
+ s : any
469
+ value to seach for
470
+
471
+ row_from : int
472
+ row to start search (default 1)
473
+
474
+ should be between 1 and number_of_rows
475
+
476
+ row_to : int
477
+ row to end search (default number_of_rows)
478
+
479
+ should be between 1 and number_of_rows
480
+
481
+ column1 : int
482
+ column to search in (default 1)
483
+
484
+ should be between 1 and number_of_columns
485
+
486
+ column2 : int
487
+ column to return looked up value from (default column1 + 1)
488
+
489
+ Returns
490
+ -------
491
+ row number where block[row nunber, column1] == s : int
492
+
493
+ Note
494
+ ----
495
+ If s is not found, a ValueError is raised
496
+ """
395
497
  if row_to is None:
396
498
  row_to = self.highest_used_row_number
397
499
  self._check_row(row_from, "row_from")
@@ -404,6 +506,42 @@ class block:
404
506
  raise ValueError(f"{s} not found")
405
507
 
406
508
  def hlookup(self, s, *, column_from=1, column_to=None, row1=1, row2=None):
509
+ """
510
+ searches in row1 for column between column_from and column_to for s and returns the value found at (that column, row2)
511
+
512
+ Parameters
513
+ ----------
514
+ s : any
515
+ value to seach for
516
+
517
+ column_from : int
518
+ column to start search (default 1)
519
+
520
+ should be between 1 and number_of_columns
521
+
522
+ column_to : int
523
+ column to end search (default number_of_columns)
524
+
525
+ should be between 1 and number_of_columns
526
+
527
+ row1 : int
528
+ row to search in (default 1)
529
+
530
+ should be between 1 and number_of_rows
531
+
532
+ row2 : int
533
+ row to return looked up value from (default row1 + 1)
534
+
535
+ should be between 1 and number_of_rows
536
+
537
+ Returns
538
+ -------
539
+ block[row, found column, row2] : any
540
+
541
+ Note
542
+ ----
543
+ If s is not found, a ValueError is raised
544
+ """
407
545
  if row2 is None:
408
546
  row2 = row1 + 1
409
547
  self._check_row(row2, "row2")
@@ -411,6 +549,40 @@ class block:
411
549
  return self[row2, column]
412
550
 
413
551
  def lookup_column(self, s, *, column_from=1, column_to=None, row1=1):
552
+ """
553
+ searches in row1 for column between column_from and column_to for s and returns that column number
554
+
555
+ Parameters
556
+ ----------
557
+ s : any
558
+ value to seach for
559
+
560
+ column_from : int
561
+ column to start search (default 1)
562
+
563
+ should be between 1 and number_of_columns
564
+
565
+ column_to : int
566
+ column to end search (default number_of_columns)
567
+
568
+ should be between 1 and number_of_columns
569
+
570
+ row1 : int
571
+ row to search in (default 1)
572
+
573
+ should be between 1 and number_of_rows
574
+
575
+ row2 : int
576
+ row to return looked up value from (default row1 + 1)
577
+
578
+ Returns
579
+ -------
580
+ column number where block[row1, column number] == s : int
581
+
582
+ Note
583
+ ----
584
+ If s is not found, a ValueError is raised
585
+ """
414
586
  if column_to is None:
415
587
  column_to = self.highest_used_column_number
416
588
  self._check_column(column_from, "column_from")
@@ -423,6 +595,44 @@ class block:
423
595
  raise ValueError(f"{s} not found")
424
596
 
425
597
  def lookup(self, s, *, row_from=1, row_to=None, column1=1, column2=None):
598
+ """
599
+ searches in column1 for row between row_from and row_to for s and returns the value found at (that row, column2)
600
+
601
+ Parameters
602
+ ----------
603
+ s : any
604
+ value to seach for
605
+
606
+ row_from : int
607
+ row to start search (default 1)
608
+
609
+ should be between 1 and number_of_rows
610
+
611
+ row_to : int
612
+ row to end search (default number_of_rows)
613
+
614
+ should be between 1 and number_of_rows
615
+
616
+ column1 : int
617
+ column to search in (default 1)
618
+
619
+ should be between 1 and number_of_columns
620
+
621
+ column2 : int
622
+ column to return looked up value from (default column1 + 1)
623
+
624
+ should be between 1 and number_of_columns
625
+
626
+ Returns
627
+ -------
628
+ block[found row number, column2] : any
629
+
630
+ Note
631
+ ----
632
+ If s is not found, a ValueError is raised
633
+
634
+ This is exactly the same as vlookup.
635
+ """
426
636
  return self.vlookup(s, row_from=row_from, row_to=row_to, column1=column1, column2=column2)
427
637
 
428
638
 
@@ -451,10 +661,10 @@ class _capture:
451
661
  self._buffer = []
452
662
 
453
663
  def __enter__(self):
454
- sys.stdout = self
664
+ self.enabled = True
455
665
 
456
666
  def __exit__(self, exc_type, exc_value, tb):
457
- sys.stdout = self.stdout
667
+ self.enabled = False
458
668
 
459
669
  def write(self, data):
460
670
  self._buffer.append(data)
@@ -466,6 +676,17 @@ class _capture:
466
676
  self.stdout.flush()
467
677
  self._buffer.append("\n")
468
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
+
469
690
  @property
470
691
  def value(self):
471
692
  result = self.value_keep
@@ -501,12 +722,14 @@ class _capture:
501
722
 
502
723
 
503
724
  def reset():
725
+ capture.enabled = False
504
726
  capture.include_print = False
505
727
  capture.clear()
506
-
728
+ print('reset',sys.stdout)
507
729
 
508
730
  capture = _capture()
509
731
 
510
732
 
511
733
  if __name__ == "__main__":
512
734
  ...
735
+
@@ -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.post3
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,13 +119,80 @@ 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
+ ### Looking up in a block
123
+
124
+ With blocks, it is easy to use a sheet as an input for a project / scenario.
125
+
126
+ Something like
127
+ <img src="https://www.salabim.org/xlwings_utils/fig01.png">
128
+
129
+ 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.
130
+
131
+ If we read the project sheet (partly) into a block, lookup methods are available to access 'fields' easily and future-proof.
132
+
133
+ Let's see how this works with the above sheet. The corresponding block (bl) looks like
134
+
135
+ ```
136
+ | 1 2 3 4 5
137
+ --+-------------------------------------------------------------------------------
138
+ 1 | Project Factory1
139
+ 2 | Start date 2025-05-17
140
+ 3 | End date 2026-02-01
141
+ 4 |
142
+ 5 | Parts Width Length Height Weight
143
+ 6 | A 10 5 5 100
144
+ 7 | B 11 5 8 102
145
+ 8 | C 12 2 3 91
146
+ 9 |
147
+ ```
148
+ Now we can do
149
+ ```project = bl.lookup("Project")
150
+ project = bl.lookup("Project")
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 a 'blank' part_name is found
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
+ 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.
166
+
167
+ 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.
168
+ And then we just read the following rows (with `hlookup`) and access the required values.
169
+
170
+ ### Filling a block from other sources
171
+
172
+ The advantage of using a block instead of accessing these sources is, that they are one-based, just like in Excel.
173
+
174
+ It is possible to make a block from a xlrd worksheet with `block.from_xlrd_sheet`.
175
+
176
+ 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)`.
177
+
178
+ It is possible to make a block from an openpyxl worksheet with `block.from_openpyxl_sheet`.
179
+
180
+ ### Writing a block to an openpyxl sheet
181
+
182
+ In order to write (append) to an openpyxl sheet, use: block.to_openpyxl_sheet.
183
+
184
+ It is possible to make a block from a text file with `block.from_file`.
185
+
186
+ ### Writing a block to an openpyxl sheet
187
+
122
188
  ## Capture stdout support
123
189
 
124
190
  The module has support for capturing stdout and -later- using showing the captured output on a sheet.
125
191
 
126
192
  This is rather important as printing in xlwings lite to the UI pane is rather slow.
127
193
 
128
- In order to capture stdout output, use
194
+ In order to capture stdout output, use `xwu.capture.enabled = True`. And to stop capturing, use `xwu.capture.enabled = False`.
195
+ Alternatively, a context manager is provided:
129
196
 
130
197
 
131
198
  ```
@@ -134,8 +201,9 @@ with xwu.capture:
134
201
  code with print statements
135
202
  """
136
203
  ```
204
+ Note that stopping the capture, leaves the captured output in place, so it can be extended later.
137
205
 
138
- and then the captured output can be copied to a sheet, like
206
+ In either case, the captured output can be then copied to a sheet, like
139
207
 
140
208
  ```
141
209
  sheet.range(4,5).value = xwu.capture.value
@@ -0,0 +1,6 @@
1
+ xlwings_utils/__init__.py,sha256=FdaRztevSu5akGL7KBUBRzqwLMRTdvVUuS2Kfp2f1Uc,68
2
+ xlwings_utils/xlwings_utils.py,sha256=o1iCbb5BmXDeVelksKNTJrSkGZ7e0y-iwnYlITKr3dQ,21039
3
+ xlwings_utils-25.0.0.post3.dist-info/METADATA,sha256=Mwx61halKRiHZAvXI5eoSrLiV5lqUCwKVkZu-tZ3P5E,9537
4
+ xlwings_utils-25.0.0.post3.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
5
+ xlwings_utils-25.0.0.post3.dist-info/top_level.txt,sha256=kf5SEv0gZiRObPhUoYcc1O_iX_wwTOPeUIYvzyYeAM4,14
6
+ xlwings_utils-25.0.0.post3.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.7.1)
2
+ Generator: setuptools (80.8.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,6 +0,0 @@
1
- xlwings_utils/__init__.py,sha256=FdaRztevSu5akGL7KBUBRzqwLMRTdvVUuS2Kfp2f1Uc,68
2
- xlwings_utils/xlwings_utils.py,sha256=UQenav-IEe7Hl8IQKNVLYDBscEdmvkR4IQbptsq3Obg,14830
3
- xlwings_utils-25.0.0.post1.dist-info/METADATA,sha256=Vh86pfRnT_0wuFKqO643CMLViq2YdcqAGoaNx53eEiE,6166
4
- xlwings_utils-25.0.0.post1.dist-info/WHEEL,sha256=Nw36Djuh_5VDukK0H78QzOX-_FQEo6V37m3nkm96gtU,91
5
- xlwings_utils-25.0.0.post1.dist-info/top_level.txt,sha256=kf5SEv0gZiRObPhUoYcc1O_iX_wwTOPeUIYvzyYeAM4,14
6
- xlwings_utils-25.0.0.post1.dist-info/RECORD,,