xlwings-utils 25.0.1__py3-none-any.whl → 25.0.3__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.

@@ -5,7 +5,7 @@
5
5
  # /_/\_\|_| \_/\_/ |_||_| |_| \__, ||___/ _____ \__,_| \__||_||_||___/
6
6
  # |___/ |_____|
7
7
 
8
- __version__ = "25.0.1"
8
+ __version__ = "25.0.3"
9
9
 
10
10
 
11
11
  import dropbox
@@ -18,11 +18,15 @@ dbx = None
18
18
  Pythonista = sys.platform == "ios"
19
19
  try:
20
20
  import xlwings
21
+
21
22
  xlwings = True
22
23
  except ImportError:
23
- xlwings=False
24
+ xlwings = False
25
+
26
+ missing = object()
27
+
24
28
 
25
- def dropbox_init(refresh_token=None, app_key=None, app_secret=None):
29
+ def dropbox_init(refresh_token=missing, app_key=missing, app_secret=missing):
26
30
  """
27
31
  dropbox initialize
28
32
 
@@ -66,17 +70,17 @@ def dropbox_init(refresh_token=None, app_key=None, app_secret=None):
66
70
  d = toml.load(f)
67
71
  os.environ.update(d)
68
72
 
69
- if refresh_token is None:
73
+ if refresh_token is missing:
70
74
  if "REFRESH_TOKEN" in os.environ:
71
75
  refresh_token = os.environ["REFRESH_TOKEN"]
72
76
  else:
73
77
  raise ValueError("no REFRESH_TOKEN found in environment.")
74
- if app_key is None:
78
+ if app_key is missing:
75
79
  if "APP_KEY" in os.environ:
76
80
  app_key = os.environ["APP_KEY"]
77
81
  else:
78
82
  raise ValueError("no APP_KEY found in environment.")
79
- if app_secret is None:
83
+ if app_secret is missing:
80
84
  if "APP_SECRET" in os.environ:
81
85
  app_secret = os.environ["APP_SECRET"]
82
86
  else:
@@ -169,11 +173,15 @@ def read_dropbox(dropbox_path):
169
173
  ----
170
174
  If REFRESH_TOKEN, APP_KEY and APP_SECRET environment variables are specified,
171
175
  it is not necessary to call dropbox_init() prior to any dropbox function.
176
+
177
+ If the file size does not match the metadata, as sometimes happens on pyodide, an OSError exception will be raised.
172
178
  """
173
179
 
174
180
  _login_dbx()
175
181
  metadata, response = dbx.files_download(dropbox_path)
176
182
  file_content = response.content
183
+ if len(file_content) != metadata.size:
184
+ raise OSError(f"file size ({len(file_content)}) does not match metadata ({metadata.size})")
177
185
  return file_content
178
186
 
179
187
 
@@ -272,13 +280,13 @@ class block:
272
280
  block
273
281
  """
274
282
 
275
- def __init__(self, value=None, *, number_of_rows=None, number_of_columns=None, column_like=False):
283
+ def __init__(self, value=missing, *, number_of_rows=missing, number_of_columns=missing, column_like=False):
276
284
  self.dict = {}
277
285
  self.column_like = column_like
278
- if value is None:
279
- if number_of_rows is None:
286
+ if value is missing:
287
+ if number_of_rows is missing:
280
288
  number_of_rows = 1
281
- if number_of_columns is None:
289
+ if number_of_columns is missing:
282
290
  number_of_columns = 1
283
291
  self.number_of_rows = number_of_rows
284
292
  self.number_of_columns = number_of_columns
@@ -287,29 +295,29 @@ class block:
287
295
  if isinstance(value, block):
288
296
  value = value.value
289
297
  self.value = value
290
- if number_of_rows is not None:
298
+ if number_of_rows is not missing:
291
299
  self.number_of_rows = number_of_rows
292
- if number_of_columns is not None:
300
+ if number_of_columns is not missing:
293
301
  self.number_of_columns = number_of_columns
294
302
 
295
303
  @classmethod
296
- def from_xlrd_sheet(cls, sheet, number_of_rows=None, number_of_columns=None):
304
+ def from_xlrd_sheet(cls, sheet, number_of_rows=missing, number_of_columns=missing):
297
305
  v = [sheet.row_values(row_idx)[0 : sheet.ncols] for row_idx in range(0, sheet.nrows)]
298
306
  return cls(v, number_of_rows=number_of_rows, number_of_columns=number_of_columns)
299
307
 
300
308
  @classmethod
301
- def from_openpyxl_sheet(cls, sheet, number_of_rows=None, number_of_columns=None):
309
+ def from_openpyxl_sheet(cls, sheet, number_of_rows=missing, number_of_columns=missing):
302
310
  v = [[cell.value for cell in row] for row in sheet.iter_rows()]
303
311
  return cls(v, number_of_rows=number_of_rows, number_of_columns=number_of_columns)
304
312
 
305
313
  @classmethod
306
- def from_file(cls, filename, number_of_rows=None, number_of_columns=None):
314
+ def from_file(cls, filename, number_of_rows=missing, number_of_columns=missing):
307
315
  with open(filename, "r") as f:
308
- v = [[line if line else None] for line in f.read().splitlines()]
316
+ v = [[line if line else missing] for line in f.read().splitlines()]
309
317
  return cls(v, number_of_rows=number_of_rows, number_of_columns=number_of_columns)
310
318
 
311
319
  @classmethod
312
- def from_dataframe(cls, df, number_of_rows=None, number_of_columns=None):
320
+ def from_dataframe(cls, df, number_of_rows=missing, number_of_columns=missing):
313
321
  v = df.values.tolist()
314
322
  return cls(v, number_of_rows=number_of_rows, number_of_columns=number_of_columns)
315
323
 
@@ -420,7 +428,7 @@ class block:
420
428
  if column > self.number_of_columns:
421
429
  raise ValueError(f"{name}={column} > number_of_columns={self.number_of_columns}")
422
430
 
423
- def vlookup(self, s, *, row_from=1, row_to=None, column1=1, column2=None):
431
+ def vlookup(self, s, *, row_from=1, row_to=missing, column1=1, column2=missing, default=missing):
424
432
  """
425
433
  searches in column1 for row between row_from and row_to for s and returns the value found at (that row, column2)
426
434
 
@@ -449,21 +457,28 @@ class block:
449
457
 
450
458
  should be between 1 and number_of_columns
451
459
 
460
+ default : any
461
+ if s is not found, returns the default.
462
+
463
+ if omitted, a ValueError exception will be raised in that case
464
+
452
465
  Returns
453
466
  -------
454
467
  block[found row number, column2] : any
455
-
456
- Note
457
- ----
458
- If s is not found, a ValueError is raised
459
468
  """
460
- if column2 is None:
469
+ if column2 is missing:
461
470
  column2 = column1 + 1
462
471
  self._check_column(column2, "column2")
463
- row = self.lookup_row(s, row_from=row_from, row_to=row_to, column1=column1)
464
- return self[row, column2]
472
+ row = self.lookup_row(s, row_from=row_from, row_to=row_to, column1=column1, default=-1)
473
+ if row == -1:
474
+ if default is missing:
475
+ raise ValueError(f"{s} not found]")
476
+ else:
477
+ return default
478
+ else:
479
+ return self[row, column2]
465
480
 
466
- def lookup_row(self, s, *, row_from=1, row_to=None, column1=1):
481
+ def lookup_row(self, s, *, row_from=1, row_to=missing, column1=1, default=missing):
467
482
  """
468
483
  searches in column1 for row between row_from and row_to for s and returns that row number
469
484
 
@@ -490,15 +505,22 @@ class block:
490
505
  column2 : int
491
506
  column to return looked up value from (default column1 + 1)
492
507
 
508
+ default : any
509
+ if s is not found, returns the default.
510
+
511
+ if omitted, a ValueError exception will be raised
512
+
513
+ default : any
514
+ if s is not found, returns the default.
515
+
516
+ if omitted, a ValueError exception will be raised in that case
517
+
518
+
493
519
  Returns
494
520
  -------
495
521
  row number where block[row nunber, column1] == s : int
496
-
497
- Note
498
- ----
499
- If s is not found, a ValueError is raised
500
522
  """
501
- if row_to is None:
523
+ if row_to is missing:
502
524
  row_to = self.highest_used_row_number
503
525
  self._check_row(row_from, "row_from")
504
526
  self._check_row(row_to, "row_to")
@@ -507,9 +529,12 @@ class block:
507
529
  for row in range(row_from, row_to + 1):
508
530
  if self[row, column1] == s:
509
531
  return row
510
- raise ValueError(f"{s} not found")
532
+ if default is missing:
533
+ raise ValueError(f"{s} not found")
534
+ else:
535
+ return default
511
536
 
512
- def hlookup(self, s, *, column_from=1, column_to=None, row1=1, row2=None):
537
+ def hlookup(self, s, *, column_from=1, column_to=missing, row1=1, row2=missing, default=missing):
513
538
  """
514
539
  searches in row1 for column between column_from and column_to for s and returns the value found at (that column, row2)
515
540
 
@@ -538,21 +563,28 @@ class block:
538
563
 
539
564
  should be between 1 and number_of_rows
540
565
 
566
+ default : any
567
+ if s is not found, returns the default.
568
+
569
+ if omitted, a ValueError exception will be raised in that case
570
+
541
571
  Returns
542
572
  -------
543
573
  block[row, found column, row2] : any
544
-
545
- Note
546
- ----
547
- If s is not found, a ValueError is raised
548
574
  """
549
- if row2 is None:
575
+ if row2 is missing:
550
576
  row2 = row1 + 1
551
577
  self._check_row(row2, "row2")
552
- column = self.lookup_column(s, column_from=column_from, column_to=column_to, row1=row1)
553
- return self[row2, column]
578
+ column = self.lookup_column(s, column_from=column_from, column_to=column_to, row1=row1, default=-1)
579
+ if column == -1:
580
+ if default is missing:
581
+ raise ValueError(f"{s} not found")
582
+ else:
583
+ return default
584
+ else:
585
+ return self[row2, column]
554
586
 
555
- def lookup_column(self, s, *, column_from=1, column_to=None, row1=1):
587
+ def lookup_column(self, s, *, column_from=1, column_to=missing, row1=1, default=missing):
556
588
  """
557
589
  searches in row1 for column between column_from and column_to for s and returns that column number
558
590
 
@@ -579,15 +611,16 @@ class block:
579
611
  row2 : int
580
612
  row to return looked up value from (default row1 + 1)
581
613
 
614
+ default : any
615
+ if s is not found, returns the default.
616
+
617
+ if omitted, a ValueError exception will be raised in that case
618
+
582
619
  Returns
583
620
  -------
584
621
  column number where block[row1, column number] == s : int
585
-
586
- Note
587
- ----
588
- If s is not found, a ValueError is raised
589
622
  """
590
- if column_to is None:
623
+ if column_to is missing:
591
624
  column_to = self.highest_used_column_number
592
625
  self._check_column(column_from, "column_from")
593
626
  self._check_column(column_to, "column_to")
@@ -596,9 +629,12 @@ class block:
596
629
  for column in range(column_from, column_to + 1):
597
630
  if self[row1, column] == s:
598
631
  return column
599
- raise ValueError(f"{s} not found")
632
+ if default is missing:
633
+ raise ValueError(f"{s} not found")
634
+ else:
635
+ return default
600
636
 
601
- def lookup(self, s, *, row_from=1, row_to=None, column1=1, column2=None):
637
+ def lookup(self, s, *, row_from=1, row_to=missing, column1=1, column2=missing, default=missing):
602
638
  """
603
639
  searches in column1 for row between row_from and row_to for s and returns the value found at (that row, column2)
604
640
 
@@ -627,17 +663,20 @@ class block:
627
663
 
628
664
  should be between 1 and number_of_columns
629
665
 
666
+ default : any
667
+ if s is not found, returns the default.
668
+
669
+ if omitted, a ValueError exception will be raised in that case
670
+
630
671
  Returns
631
672
  -------
632
673
  block[found row number, column2] : any
633
674
 
634
675
  Note
635
676
  ----
636
- If s is not found, a ValueError is raised
637
-
638
677
  This is exactly the same as vlookup.
639
678
  """
640
- return self.vlookup(s, row_from=row_from, row_to=row_to, column1=column1, column2=column2)
679
+ return self.vlookup(s, row_from=row_from, row_to=row_to, column1=column1, column2=column2, default=default)
641
680
 
642
681
 
643
682
  class Capture:
@@ -648,9 +687,9 @@ class Capture:
648
687
  ----------
649
688
  enabled : bool
650
689
  if True (default), all stdout output is captured
651
-
652
- if False, stdout output is printed
653
-
690
+
691
+ if False, stdout output is printed
692
+
654
693
  include_print : bool
655
694
  if False (default), nothing will be printed if enabled is True
656
695
 
@@ -658,10 +697,9 @@ class Capture:
658
697
 
659
698
  Note
660
699
  ----
661
- Use this function, like ::
700
+ Use this like ::
662
701
 
663
- capture = xwu.Capture():
664
- ...
702
+ capture = xwu.Capture()
665
703
  """
666
704
 
667
705
  _instance = None
@@ -672,21 +710,21 @@ class Capture:
672
710
  cls._instance = super(Capture, cls).__new__(cls)
673
711
  return cls._instance
674
712
 
675
- def __init__(self, enabled=None, include_print=None):
713
+ def __init__(self, enabled=missing, include_print=missing):
676
714
  if hasattr(self, "stdout"):
677
- if enabled is not None:
715
+ if enabled is not missing:
678
716
  self.enabled = enabled
679
- if include_print is not None:
717
+ if include_print is not missing:
680
718
  self.include_print = include_print
681
719
  return
682
720
  self.stdout = sys.stdout
683
721
  self._buffer = []
684
- self.enabled = True if enabled is None else enabled
685
- self.include_print = False if include_print is None else include_print
722
+ self.enabled = True if enabled is missing else enabled
723
+ self.include_print = False if include_print is missing else include_print
686
724
 
687
- def __call__(self, enabled=None, include_print=None):
725
+ def __call__(self, enabled=missing, include_print=missing):
688
726
  return self.__class__(enabled, include_print)
689
-
727
+
690
728
  def __enter__(self):
691
729
  self.enabled = True
692
730
 
@@ -750,4 +788,3 @@ class Capture:
750
788
 
751
789
  if __name__ == "__main__":
752
790
  ...
753
-
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: xlwings_utils
3
- Version: 25.0.1
3
+ Version: 25.0.3
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
@@ -48,12 +48,19 @@ The xlwings lite system does not provide access to the local file system. With t
48
48
 
49
49
  It is only possible, as of now, to use full-access Dropbox apps.
50
50
 
51
- 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.
51
+ 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. Instructions on how to get these variables can be found here.
52
52
 
53
- Then, it is possible to list all files in a specified folder with the function `list_dropbox`.
53
+ In order to make a Dropbox app, and get the required environment variables, just execute this line from the command line (shell).
54
+
55
+ ```
56
+ python -c "exec(__import__('requests').get('https://salabim.org/dropbox setup.py').text)"
57
+ ```
58
+
59
+ Then, it is possible to list all files in a specified folder using the list_dropbox function.
54
60
  It is also possible to get the folders and to access all underlying folders.
55
61
 
56
- The function `read_dropbox` can be used to read a Dropbox file's contents (bytes).
62
+ The `read_dropbox` function can be used to read the contents (bytes) of a Dropbox file. If the file is not read correctly, which seems
63
+ to happen rather frequently, an OSError exception is raised.
57
64
 
58
65
  The function `write_dropbox` can be used to write contents (bytes) to a Dropbox file.
59
66
 
@@ -80,7 +87,7 @@ This can be useful to manipulate a range without accessing the range directly, w
80
87
  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.
81
88
  So, `my_block(lol)[row, col]` is roughly equivalent to `lol[row-1][col-1]`
82
89
 
83
- A block stores the values internally as a dictionary and will only convert these to a list of lists when using `block.value`.
90
+ A block stores the values internally as a dictionary and will only convert these to a list of lists when using `block.value` or `block.value_keep`.
84
91
 
85
92
  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
86
93
 
@@ -97,7 +104,7 @@ And, likewise, reading an individual item can be done like
97
104
  ```
98
105
  x = my_block[row, column]
99
106
  ```
100
- It is not allowed t,o read or write outside the block dimensions.
107
+ It is not allowed to read or write outside the block dimensions.
101
108
 
102
109
  It is also possible to define an empty block, like
103
110
  ```
@@ -138,9 +145,13 @@ With blocks, it is easy to use a sheet as an input for a project / scenario.
138
145
  Something like
139
146
  <img src="https://www.salabim.org/xlwings_utils/fig01.png">
140
147
 
141
- 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.
148
+ Of course, we could access the various input fields with absolute ranges, but if something changes later (such as adding a row), all references would need to be updated.
142
149
 
143
- If we read the project sheet (partly) into a block, lookup methods are available to access 'fields' easily and future-proof.
150
+ If we read the project sheet (partly) into a block, lookup methods are available to access *fields* easily and future-proof:
151
+
152
+ ```
153
+ bl = xwu.block.from_value(sheet.range((0,0),(100,10)).value)
154
+ ```
144
155
 
145
156
  Let's see how this works with the above sheet. The corresponding block (bl) looks like
146
157
 
@@ -174,29 +185,27 @@ for row2 in range(row1 + 1, bl.highest_used_row_number + 1):
174
185
  weight = bl.hlookup("Weight",row1=row1, row2=row2)
175
186
  parts.append(Part(part_name, width, length, height, weight))
176
187
  ```
177
- 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.
188
+ First, we perform a couple of vertical lookups to scan column 1 for the given labels and return the corresponding values from column 2.
178
189
 
179
190
  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.
180
- And then we just read the following rows (with `hlookup`) and access the required values.
191
+ We then read the following rows (using hlookups) and access the required values.
181
192
 
182
193
  ### Filling a block from other sources
183
194
 
184
- The advantage of using a block instead of accessing these sources is, that they are one-based, just like in Excel.
195
+ The advantage of using a block instead of accessing these sources is that they are one-based, just like in Excel.
185
196
 
186
- It is possible to make a block from a xlrd worksheet with `block.from_xlrd_sheet`.
197
+ It is possible to make a block from an xlrd worksheet with `block.from_xlrd_sheet`.
187
198
 
188
- 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)`.
199
+ It is possible to make a block from a pandas dataframe with `block.from_dataframe`. Ensure that, if the dataframe is created by reading from an Excel sheet, headers=None is specified, e.g., `df = pd.read_excel(filename, header=None)`.
189
200
 
190
201
  It is possible to make a block from an openpyxl worksheet with `block.from_openpyxl_sheet`.
191
202
 
192
- ### Writing a block to an openpyxl sheet
193
-
194
- In order to write (append) to an openpyxl sheet, use: block.to_openpyxl_sheet.
195
-
196
203
  It is possible to make a block from a text file with `block.from_file`.
197
204
 
198
205
  ### Writing a block to an openpyxl sheet
199
206
 
207
+ In order to write (append) to an openpyxl sheet, use: block.to_openpyxl_sheet.
208
+
200
209
  ## Capture stdout support
201
210
 
202
211
  The module has support for capturing stdout and -later- using showing the captured output on a sheet.
@@ -0,0 +1,6 @@
1
+ xlwings_utils/__init__.py,sha256=FdaRztevSu5akGL7KBUBRzqwLMRTdvVUuS2Kfp2f1Uc,68
2
+ xlwings_utils/xlwings_utils.py,sha256=GXuxgnbZ4WzhTy3QM16Ru1ofj4FIAauWN36kXDFFDQ8,23356
3
+ xlwings_utils-25.0.3.dist-info/METADATA,sha256=CapEwbNevIQnlpCndBQMkS3isd7tPWCK2NZnX7yA6xg,10223
4
+ xlwings_utils-25.0.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
5
+ xlwings_utils-25.0.3.dist-info/top_level.txt,sha256=kf5SEv0gZiRObPhUoYcc1O_iX_wwTOPeUIYvzyYeAM4,14
6
+ xlwings_utils-25.0.3.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.8.0)
2
+ Generator: setuptools (80.9.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=8_OKnH-UnvOZnybavnC4CEWAR27IiqfTGyezWja-s-o,21809
3
- xlwings_utils-25.0.1.dist-info/METADATA,sha256=G6E8CscnLZ6zinaPdl1XbSBQuDZcKfMi3AwdSVppb0k,9791
4
- xlwings_utils-25.0.1.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
5
- xlwings_utils-25.0.1.dist-info/top_level.txt,sha256=kf5SEv0gZiRObPhUoYcc1O_iX_wwTOPeUIYvzyYeAM4,14
6
- xlwings_utils-25.0.1.dist-info/RECORD,,