xlwings-utils 25.0.2__tar.gz → 25.0.3.post0__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.2 → xlwings_utils-25.0.3.post0}/PKG-INFO +26 -17
- {xlwings_utils-25.0.2 → xlwings_utils-25.0.3.post0}/README.md +25 -16
- {xlwings_utils-25.0.2 → xlwings_utils-25.0.3.post0}/pyproject.toml +1 -1
- {xlwings_utils-25.0.2 → xlwings_utils-25.0.3.post0}/tests/test_xlwings_utils.py +24 -20
- {xlwings_utils-25.0.2 → xlwings_utils-25.0.3.post0}/xlwings_utils/xlwings_utils.py +102 -70
- {xlwings_utils-25.0.2 → xlwings_utils-25.0.3.post0}/xlwings_utils.egg-info/PKG-INFO +26 -17
- {xlwings_utils-25.0.2 → xlwings_utils-25.0.3.post0}/setup.cfg +0 -0
- {xlwings_utils-25.0.2 → xlwings_utils-25.0.3.post0}/xlwings_utils/__init__.py +0 -0
- {xlwings_utils-25.0.2 → xlwings_utils-25.0.3.post0}/xlwings_utils.egg-info/SOURCES.txt +0 -0
- {xlwings_utils-25.0.2 → xlwings_utils-25.0.3.post0}/xlwings_utils.egg-info/dependency_links.txt +0 -0
- {xlwings_utils-25.0.2 → xlwings_utils-25.0.3.post0}/xlwings_utils.egg-info/requires.txt +0 -0
- {xlwings_utils-25.0.2 → xlwings_utils-25.0.3.post0}/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.post0
|
|
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, max_retries_on_error=4):
|
|
26
30
|
"""
|
|
27
31
|
dropbox initialize
|
|
28
32
|
|
|
@@ -66,23 +70,23 @@ 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:
|
|
83
87
|
raise ValueError("no APP_SECRET found in environment.")
|
|
84
88
|
|
|
85
|
-
dbx = dropbox.Dropbox(oauth2_refresh_token=refresh_token, app_key=app_key, app_secret=app_secret)
|
|
89
|
+
dbx = dropbox.Dropbox(oauth2_refresh_token=refresh_token, app_key=app_key, app_secret=app_secret, max_retries_on_error=max_retries_on_error)
|
|
86
90
|
try:
|
|
87
91
|
dbx.files_list_folder(path="") # just to test proper credentials
|
|
88
92
|
except dropbox.exceptions.AuthError:
|
|
@@ -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
|
|
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(
|
|
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=
|
|
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
|
|
284
|
-
if number_of_rows is
|
|
286
|
+
if value is missing:
|
|
287
|
+
if number_of_rows is missing:
|
|
285
288
|
number_of_rows = 1
|
|
286
|
-
if number_of_columns is
|
|
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
|
|
298
|
+
if number_of_rows is not missing:
|
|
296
299
|
self.number_of_rows = number_of_rows
|
|
297
|
-
if number_of_columns is not
|
|
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=
|
|
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=
|
|
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=
|
|
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
|
|
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=
|
|
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=
|
|
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
|
|
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
|
-
|
|
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=
|
|
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
|
|
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
|
-
|
|
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=
|
|
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
|
|
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
|
-
|
|
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=
|
|
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
|
|
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
|
-
|
|
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=
|
|
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
|
|
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=
|
|
713
|
+
def __init__(self, enabled=missing, include_print=missing):
|
|
681
714
|
if hasattr(self, "stdout"):
|
|
682
|
-
if enabled is not
|
|
715
|
+
if enabled is not missing:
|
|
683
716
|
self.enabled = enabled
|
|
684
|
-
if include_print is not
|
|
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
|
|
690
|
-
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
|
|
691
724
|
|
|
692
|
-
def __call__(self, enabled=
|
|
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.
|
|
3
|
+
Version: 25.0.3.post0
|
|
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
|
{xlwings_utils-25.0.2 → xlwings_utils-25.0.3.post0}/xlwings_utils.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|