xlwings-utils 25.0.1__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.
- {xlwings_utils-25.0.1 → xlwings_utils-25.0.3}/PKG-INFO +26 -17
- {xlwings_utils-25.0.1 → xlwings_utils-25.0.3}/README.md +25 -16
- {xlwings_utils-25.0.1 → xlwings_utils-25.0.3}/pyproject.toml +1 -1
- {xlwings_utils-25.0.1 → xlwings_utils-25.0.3}/tests/test_xlwings_utils.py +24 -20
- {xlwings_utils-25.0.1 → xlwings_utils-25.0.3}/xlwings_utils/xlwings_utils.py +102 -65
- {xlwings_utils-25.0.1 → xlwings_utils-25.0.3}/xlwings_utils.egg-info/PKG-INFO +26 -17
- {xlwings_utils-25.0.1 → xlwings_utils-25.0.3}/setup.cfg +0 -0
- {xlwings_utils-25.0.1 → xlwings_utils-25.0.3}/xlwings_utils/__init__.py +0 -0
- {xlwings_utils-25.0.1 → xlwings_utils-25.0.3}/xlwings_utils.egg-info/SOURCES.txt +0 -0
- {xlwings_utils-25.0.1 → xlwings_utils-25.0.3}/xlwings_utils.egg-info/dependency_links.txt +0 -0
- {xlwings_utils-25.0.1 → xlwings_utils-25.0.3}/xlwings_utils.egg-info/requires.txt +0 -0
- {xlwings_utils-25.0.1 → xlwings_utils-25.0.3}/xlwings_utils.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: xlwings_utils
|
|
3
|
-
Version: 25.0.
|
|
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
|
-
|
|
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
|
|
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
|
|
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 (
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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`.
|
|
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
|
-
|
|
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
|
|
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
|
|
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 (
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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`.
|
|
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.
|
|
@@ -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
|
|
150
|
-
assert
|
|
153
|
+
assert capture.str_keep == ""
|
|
154
|
+
assert capture.value_keep == []
|
|
151
155
|
|
|
152
|
-
with
|
|
156
|
+
with capture:
|
|
153
157
|
print("abc")
|
|
154
158
|
print("def")
|
|
155
159
|
out, err = capsys.readouterr()
|
|
156
160
|
assert out == ""
|
|
157
|
-
assert
|
|
158
|
-
assert
|
|
159
|
-
assert
|
|
160
|
-
assert
|
|
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
|
|
166
|
+
with capture:
|
|
163
167
|
print("abc")
|
|
164
168
|
print("def")
|
|
165
169
|
out, err = capsys.readouterr()
|
|
166
|
-
|
|
167
|
-
assert
|
|
170
|
+
capture.clear()
|
|
171
|
+
assert capture.str_keep == ""
|
|
168
172
|
|
|
169
|
-
with
|
|
173
|
+
with capture:
|
|
170
174
|
print("abc")
|
|
171
175
|
print("def")
|
|
172
|
-
with
|
|
176
|
+
with capture:
|
|
173
177
|
print("ghi")
|
|
174
178
|
print("jkl")
|
|
175
179
|
out, err = capsys.readouterr()
|
|
176
180
|
assert out == ""
|
|
177
|
-
assert
|
|
178
|
-
assert
|
|
179
|
-
assert
|
|
180
|
-
assert
|
|
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
|
-
|
|
186
|
+
capture.enabled=True
|
|
183
187
|
print("abc")
|
|
184
188
|
print("def")
|
|
185
|
-
|
|
189
|
+
capture.enabled=False
|
|
186
190
|
print("xxx")
|
|
187
191
|
print("yyy")
|
|
188
|
-
|
|
192
|
+
capture.enabled=True
|
|
189
193
|
print("ghi")
|
|
190
194
|
print("jkl")
|
|
191
|
-
assert
|
|
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.
|
|
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=
|
|
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
|
|
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
|
|
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
|
|
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=
|
|
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
|
|
279
|
-
if number_of_rows is
|
|
286
|
+
if value is missing:
|
|
287
|
+
if number_of_rows is missing:
|
|
280
288
|
number_of_rows = 1
|
|
281
|
-
if number_of_columns is
|
|
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
|
|
298
|
+
if number_of_rows is not missing:
|
|
291
299
|
self.number_of_rows = number_of_rows
|
|
292
|
-
if number_of_columns is not
|
|
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=
|
|
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=
|
|
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=
|
|
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
|
|
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=
|
|
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=
|
|
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
|
|
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
|
-
|
|
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=
|
|
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
|
|
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
|
-
|
|
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=
|
|
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
|
|
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
|
-
|
|
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=
|
|
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
|
|
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
|
-
|
|
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=
|
|
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
|
|
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=
|
|
713
|
+
def __init__(self, enabled=missing, include_print=missing):
|
|
676
714
|
if hasattr(self, "stdout"):
|
|
677
|
-
if enabled is not
|
|
715
|
+
if enabled is not missing:
|
|
678
716
|
self.enabled = enabled
|
|
679
|
-
if include_print is not
|
|
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
|
|
685
|
-
self.include_print = False if include_print is
|
|
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=
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
|
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 (
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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`.
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|