xlwings-utils 25.0.2__tar.gz → 25.0.3__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.2
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.
@@ -34,12 +34,19 @@ The xlwings lite system does not provide access to the local file system. With t
34
34
 
35
35
  It is only possible, as of now, to use full-access Dropbox apps.
36
36
 
37
- 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.
37
+ 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.
38
38
 
39
- Then, it is possible to list all files in a specified folder with the function `list_dropbox`.
39
+ In order to make a Dropbox app, and get the required environment variables, just execute this line from the command line (shell).
40
+
41
+ ```
42
+ python -c "exec(__import__('requests').get('https://salabim.org/dropbox setup.py').text)"
43
+ ```
44
+
45
+ Then, it is possible to list all files in a specified folder using the list_dropbox function.
40
46
  It is also possible to get the folders and to access all underlying folders.
41
47
 
42
- The function `read_dropbox` can be used to read a Dropbox file's contents (bytes).
48
+ 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
49
+ to happen rather frequently, an OSError exception is raised.
43
50
 
44
51
  The function `write_dropbox` can be used to write contents (bytes) to a Dropbox file.
45
52
 
@@ -66,7 +73,7 @@ This can be useful to manipulate a range without accessing the range directly, w
66
73
  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.
67
74
  So, `my_block(lol)[row, col]` is roughly equivalent to `lol[row-1][col-1]`
68
75
 
69
- A block stores the values internally as a dictionary and will only convert these to a list of lists when using `block.value`.
76
+ 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`.
70
77
 
71
78
  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
72
79
 
@@ -83,7 +90,7 @@ And, likewise, reading an individual item can be done like
83
90
  ```
84
91
  x = my_block[row, column]
85
92
  ```
86
- It is not allowed t,o read or write outside the block dimensions.
93
+ It is not allowed to read or write outside the block dimensions.
87
94
 
88
95
  It is also possible to define an empty block, like
89
96
  ```
@@ -124,9 +131,13 @@ With blocks, it is easy to use a sheet as an input for a project / scenario.
124
131
  Something like
125
132
  <img src="https://www.salabim.org/xlwings_utils/fig01.png">
126
133
 
127
- 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.
134
+ 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.
128
135
 
129
- If we read the project sheet (partly) into a block, lookup methods are available to access 'fields' easily and future-proof.
136
+ If we read the project sheet (partly) into a block, lookup methods are available to access *fields* easily and future-proof:
137
+
138
+ ```
139
+ bl = xwu.block.from_value(sheet.range((0,0),(100,10)).value)
140
+ ```
130
141
 
131
142
  Let's see how this works with the above sheet. The corresponding block (bl) looks like
132
143
 
@@ -160,29 +171,27 @@ for row2 in range(row1 + 1, bl.highest_used_row_number + 1):
160
171
  weight = bl.hlookup("Weight",row1=row1, row2=row2)
161
172
  parts.append(Part(part_name, width, length, height, weight))
162
173
  ```
163
- 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.
174
+ First, we perform a couple of vertical lookups to scan column 1 for the given labels and return the corresponding values from column 2.
164
175
 
165
176
  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.
166
- And then we just read the following rows (with `hlookup`) and access the required values.
177
+ We then read the following rows (using hlookups) and access the required values.
167
178
 
168
179
  ### Filling a block from other sources
169
180
 
170
- The advantage of using a block instead of accessing these sources is, that they are one-based, just like in Excel.
181
+ The advantage of using a block instead of accessing these sources is that they are one-based, just like in Excel.
171
182
 
172
- It is possible to make a block from a xlrd worksheet with `block.from_xlrd_sheet`.
183
+ It is possible to make a block from an xlrd worksheet with `block.from_xlrd_sheet`.
173
184
 
174
- 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)`.
185
+ 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)`.
175
186
 
176
187
  It is possible to make a block from an openpyxl worksheet with `block.from_openpyxl_sheet`.
177
188
 
178
- ### Writing a block to an openpyxl sheet
179
-
180
- In order to write (append) to an openpyxl sheet, use: block.to_openpyxl_sheet.
181
-
182
189
  It is possible to make a block from a text file with `block.from_file`.
183
190
 
184
191
  ### Writing a block to an openpyxl sheet
185
192
 
193
+ In order to write (append) to an openpyxl sheet, use: block.to_openpyxl_sheet.
194
+
186
195
  ## Capture stdout support
187
196
 
188
197
  The module has support for capturing stdout and -later- using showing the captured output on a sheet.
@@ -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.2"
13
+ version = "25.0.3"
14
14
  readme = "README.md"
15
15
  requires-python = ">=3.9"
16
16
  dependencies = [
@@ -108,6 +108,7 @@ def test_lookup():
108
108
  assert bl.lookup(3, column2=3)=="Trois"
109
109
  with pytest.raises(ValueError):
110
110
  bl.lookup(4)
111
+ assert bl.lookup(4,default='x')=='x'
111
112
  with pytest.raises(ValueError):
112
113
  bl.lookup(1, column1=4)
113
114
  with pytest.raises(ValueError):
@@ -121,6 +122,7 @@ def test_vookup():
121
122
  assert bl.vlookup(3, column2=3)=="Trois"
122
123
  with pytest.raises(ValueError):
123
124
  bl.vlookup(4)
125
+ assert bl.vlookup(4, default='x')=='x'
124
126
  with pytest.raises(ValueError):
125
127
  bl.vlookup(1, column1=4)
126
128
  with pytest.raises(ValueError):
@@ -133,6 +135,7 @@ def test_hlookup():
133
135
  assert bl.hlookup(3, row2=3)=="Trois"
134
136
  with pytest.raises(ValueError):
135
137
  bl.hlookup(4)
138
+ assert bl.hlookup(4,default='x')=='x'
136
139
  with pytest.raises(ValueError):
137
140
  bl.hlookup(1, row1=4)
138
141
  with pytest.raises(ValueError):
@@ -144,51 +147,52 @@ def test_hlookup():
144
147
  def test_capture(capsys):
145
148
  print("abc")
146
149
  print("def")
150
+ capture=xwu.Capture()
147
151
  out, err = capsys.readouterr()
148
152
  assert out == "abc\ndef\n"
149
- assert xwu.capture.str_keep == ""
150
- assert xwu.capture.value_keep == []
153
+ assert capture.str_keep == ""
154
+ assert capture.value_keep == []
151
155
 
152
- with xwu.capture:
156
+ with capture:
153
157
  print("abc")
154
158
  print("def")
155
159
  out, err = capsys.readouterr()
156
160
  assert out == ""
157
- assert xwu.capture.str_keep == "abc\ndef\n"
158
- assert xwu.capture.value_keep == [['abc'], ['def']]
159
- assert xwu.capture.str == "abc\ndef\n"
160
- assert xwu.capture.value == []
161
+ assert capture.str_keep == "abc\ndef\n"
162
+ assert capture.value_keep == [['abc'], ['def']]
163
+ assert capture.str == "abc\ndef\n"
164
+ assert capture.value == []
161
165
 
162
- with xwu.capture:
166
+ with capture:
163
167
  print("abc")
164
168
  print("def")
165
169
  out, err = capsys.readouterr()
166
- xwu.capture.clear()
167
- assert xwu.capture.str_keep == ""
170
+ capture.clear()
171
+ assert capture.str_keep == ""
168
172
 
169
- with xwu.capture:
173
+ with capture:
170
174
  print("abc")
171
175
  print("def")
172
- with xwu.capture:
176
+ with capture:
173
177
  print("ghi")
174
178
  print("jkl")
175
179
  out, err = capsys.readouterr()
176
180
  assert out == ""
177
- assert xwu.capture.str_keep == "abc\ndef\nghi\njkl\n"
178
- assert xwu.capture.value_keep == [['abc'], ['def'], ['ghi'], ['jkl']]
179
- assert xwu.capture.value == [['abc'], ['def'], ['ghi'], ['jkl']]
180
- assert xwu.capture.value == []
181
+ assert capture.str_keep == "abc\ndef\nghi\njkl\n"
182
+ assert capture.value_keep == [['abc'], ['def'], ['ghi'], ['jkl']]
183
+ assert capture.value == [['abc'], ['def'], ['ghi'], ['jkl']]
184
+ assert capture.value == []
181
185
 
182
- xwu.capture.enabled=True
186
+ capture.enabled=True
183
187
  print("abc")
184
188
  print("def")
185
- xwu.capture.enabled=False
189
+ capture.enabled=False
186
190
  print("xxx")
187
191
  print("yyy")
188
- xwu.capture.enabled=True
192
+ capture.enabled=True
189
193
  print("ghi")
190
194
  print("jkl")
191
- assert xwu.capture.str_keep == "abc\ndef\nghi\njkl\n"
195
+ assert capture.str_keep == "abc\ndef\nghi\njkl\n"
192
196
 
193
197
 
194
198
  # include_print is not testable with pytest
@@ -5,7 +5,7 @@
5
5
  # /_/\_\|_| \_/\_/ |_||_| |_| \__, ||___/ _____ \__,_| \__||_||_||___/
6
6
  # |___/ |_____|
7
7
 
8
- __version__ = "25.0.2"
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,19 +173,18 @@ 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.
172
-
173
- If the filesize does not match the metadata, as sometimes happens on pyodide, an IOError exception will be raised.
176
+
177
+ If the file size does not match the metadata, as sometimes happens on pyodide, an OSError exception will be raised.
174
178
  """
175
179
 
176
180
  _login_dbx()
177
181
  metadata, response = dbx.files_download(dropbox_path)
178
182
  file_content = response.content
179
183
  if len(file_content) != metadata.size:
180
- raise OSError('filesize does not match metadata')
184
+ raise OSError(f"file size ({len(file_content)}) does not match metadata ({metadata.size})")
181
185
  return file_content
182
186
 
183
187
 
184
-
185
188
  def write_dropbox(dropbox_path, contents):
186
189
  _login_dbx()
187
190
  """
@@ -277,13 +280,13 @@ class block:
277
280
  block
278
281
  """
279
282
 
280
- 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):
281
284
  self.dict = {}
282
285
  self.column_like = column_like
283
- if value is None:
284
- if number_of_rows is None:
286
+ if value is missing:
287
+ if number_of_rows is missing:
285
288
  number_of_rows = 1
286
- if number_of_columns is None:
289
+ if number_of_columns is missing:
287
290
  number_of_columns = 1
288
291
  self.number_of_rows = number_of_rows
289
292
  self.number_of_columns = number_of_columns
@@ -292,29 +295,29 @@ class block:
292
295
  if isinstance(value, block):
293
296
  value = value.value
294
297
  self.value = value
295
- if number_of_rows is not None:
298
+ if number_of_rows is not missing:
296
299
  self.number_of_rows = number_of_rows
297
- if number_of_columns is not None:
300
+ if number_of_columns is not missing:
298
301
  self.number_of_columns = number_of_columns
299
302
 
300
303
  @classmethod
301
- 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):
302
305
  v = [sheet.row_values(row_idx)[0 : sheet.ncols] for row_idx in range(0, sheet.nrows)]
303
306
  return cls(v, number_of_rows=number_of_rows, number_of_columns=number_of_columns)
304
307
 
305
308
  @classmethod
306
- 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):
307
310
  v = [[cell.value for cell in row] for row in sheet.iter_rows()]
308
311
  return cls(v, number_of_rows=number_of_rows, number_of_columns=number_of_columns)
309
312
 
310
313
  @classmethod
311
- 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):
312
315
  with open(filename, "r") as f:
313
- 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()]
314
317
  return cls(v, number_of_rows=number_of_rows, number_of_columns=number_of_columns)
315
318
 
316
319
  @classmethod
317
- 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):
318
321
  v = df.values.tolist()
319
322
  return cls(v, number_of_rows=number_of_rows, number_of_columns=number_of_columns)
320
323
 
@@ -425,7 +428,7 @@ class block:
425
428
  if column > self.number_of_columns:
426
429
  raise ValueError(f"{name}={column} > number_of_columns={self.number_of_columns}")
427
430
 
428
- 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):
429
432
  """
430
433
  searches in column1 for row between row_from and row_to for s and returns the value found at (that row, column2)
431
434
 
@@ -454,21 +457,28 @@ class block:
454
457
 
455
458
  should be between 1 and number_of_columns
456
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
+
457
465
  Returns
458
466
  -------
459
467
  block[found row number, column2] : any
460
-
461
- Note
462
- ----
463
- If s is not found, a ValueError is raised
464
468
  """
465
- if column2 is None:
469
+ if column2 is missing:
466
470
  column2 = column1 + 1
467
471
  self._check_column(column2, "column2")
468
- row = self.lookup_row(s, row_from=row_from, row_to=row_to, column1=column1)
469
- 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]
470
480
 
471
- 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):
472
482
  """
473
483
  searches in column1 for row between row_from and row_to for s and returns that row number
474
484
 
@@ -495,15 +505,22 @@ class block:
495
505
  column2 : int
496
506
  column to return looked up value from (default column1 + 1)
497
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
+
498
519
  Returns
499
520
  -------
500
521
  row number where block[row nunber, column1] == s : int
501
-
502
- Note
503
- ----
504
- If s is not found, a ValueError is raised
505
522
  """
506
- if row_to is None:
523
+ if row_to is missing:
507
524
  row_to = self.highest_used_row_number
508
525
  self._check_row(row_from, "row_from")
509
526
  self._check_row(row_to, "row_to")
@@ -512,9 +529,12 @@ class block:
512
529
  for row in range(row_from, row_to + 1):
513
530
  if self[row, column1] == s:
514
531
  return row
515
- raise ValueError(f"{s} not found")
532
+ if default is missing:
533
+ raise ValueError(f"{s} not found")
534
+ else:
535
+ return default
516
536
 
517
- 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):
518
538
  """
519
539
  searches in row1 for column between column_from and column_to for s and returns the value found at (that column, row2)
520
540
 
@@ -543,21 +563,28 @@ class block:
543
563
 
544
564
  should be between 1 and number_of_rows
545
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
+
546
571
  Returns
547
572
  -------
548
573
  block[row, found column, row2] : any
549
-
550
- Note
551
- ----
552
- If s is not found, a ValueError is raised
553
574
  """
554
- if row2 is None:
575
+ if row2 is missing:
555
576
  row2 = row1 + 1
556
577
  self._check_row(row2, "row2")
557
- column = self.lookup_column(s, column_from=column_from, column_to=column_to, row1=row1)
558
- 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]
559
586
 
560
- 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):
561
588
  """
562
589
  searches in row1 for column between column_from and column_to for s and returns that column number
563
590
 
@@ -584,15 +611,16 @@ class block:
584
611
  row2 : int
585
612
  row to return looked up value from (default row1 + 1)
586
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
+
587
619
  Returns
588
620
  -------
589
621
  column number where block[row1, column number] == s : int
590
-
591
- Note
592
- ----
593
- If s is not found, a ValueError is raised
594
622
  """
595
- if column_to is None:
623
+ if column_to is missing:
596
624
  column_to = self.highest_used_column_number
597
625
  self._check_column(column_from, "column_from")
598
626
  self._check_column(column_to, "column_to")
@@ -601,9 +629,12 @@ class block:
601
629
  for column in range(column_from, column_to + 1):
602
630
  if self[row1, column] == s:
603
631
  return column
604
- raise ValueError(f"{s} not found")
632
+ if default is missing:
633
+ raise ValueError(f"{s} not found")
634
+ else:
635
+ return default
605
636
 
606
- 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):
607
638
  """
608
639
  searches in column1 for row between row_from and row_to for s and returns the value found at (that row, column2)
609
640
 
@@ -632,17 +663,20 @@ class block:
632
663
 
633
664
  should be between 1 and number_of_columns
634
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
+
635
671
  Returns
636
672
  -------
637
673
  block[found row number, column2] : any
638
674
 
639
675
  Note
640
676
  ----
641
- If s is not found, a ValueError is raised
642
-
643
677
  This is exactly the same as vlookup.
644
678
  """
645
- 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)
646
680
 
647
681
 
648
682
  class Capture:
@@ -653,9 +687,9 @@ class Capture:
653
687
  ----------
654
688
  enabled : bool
655
689
  if True (default), all stdout output is captured
656
-
657
- if False, stdout output is printed
658
-
690
+
691
+ if False, stdout output is printed
692
+
659
693
  include_print : bool
660
694
  if False (default), nothing will be printed if enabled is True
661
695
 
@@ -663,10 +697,9 @@ class Capture:
663
697
 
664
698
  Note
665
699
  ----
666
- Use this function, like ::
700
+ Use this like ::
667
701
 
668
- capture = xwu.Capture():
669
- ...
702
+ capture = xwu.Capture()
670
703
  """
671
704
 
672
705
  _instance = None
@@ -677,21 +710,21 @@ class Capture:
677
710
  cls._instance = super(Capture, cls).__new__(cls)
678
711
  return cls._instance
679
712
 
680
- def __init__(self, enabled=None, include_print=None):
713
+ def __init__(self, enabled=missing, include_print=missing):
681
714
  if hasattr(self, "stdout"):
682
- if enabled is not None:
715
+ if enabled is not missing:
683
716
  self.enabled = enabled
684
- if include_print is not None:
717
+ if include_print is not missing:
685
718
  self.include_print = include_print
686
719
  return
687
720
  self.stdout = sys.stdout
688
721
  self._buffer = []
689
- self.enabled = True if enabled is None else enabled
690
- 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
691
724
 
692
- def __call__(self, enabled=None, include_print=None):
725
+ def __call__(self, enabled=missing, include_print=missing):
693
726
  return self.__class__(enabled, include_print)
694
-
727
+
695
728
  def __enter__(self):
696
729
  self.enabled = True
697
730
 
@@ -755,4 +788,3 @@ class Capture:
755
788
 
756
789
  if __name__ == "__main__":
757
790
  ...
758
-
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: xlwings_utils
3
- Version: 25.0.2
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.
File without changes