xlwings-utils 25.0.9__tar.gz → 25.1.0__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.9 → xlwings_utils-25.1.0}/PKG-INFO +4 -5
- {xlwings_utils-25.0.9 → xlwings_utils-25.1.0}/README.md +3 -3
- {xlwings_utils-25.0.9 → xlwings_utils-25.1.0}/pyproject.toml +2 -4
- {xlwings_utils-25.0.9 → xlwings_utils-25.1.0}/tests/test_xlwings_utils.py +83 -45
- {xlwings_utils-25.0.9 → xlwings_utils-25.1.0}/xlwings_utils/xlwings_utils.py +111 -54
- xlwings_utils-25.1.0/xlwings_utils/xlwings_utils_old.py +1064 -0
- {xlwings_utils-25.0.9 → xlwings_utils-25.1.0}/xlwings_utils.egg-info/PKG-INFO +4 -5
- {xlwings_utils-25.0.9 → xlwings_utils-25.1.0}/xlwings_utils.egg-info/SOURCES.txt +1 -1
- xlwings_utils-25.0.9/xlwings_utils.egg-info/requires.txt +0 -1
- {xlwings_utils-25.0.9 → xlwings_utils-25.1.0}/setup.cfg +0 -0
- {xlwings_utils-25.0.9 → xlwings_utils-25.1.0}/xlwings_utils/__init__.py +0 -0
- {xlwings_utils-25.0.9 → xlwings_utils-25.1.0}/xlwings_utils.egg-info/dependency_links.txt +0 -0
- {xlwings_utils-25.0.9 → xlwings_utils-25.1.0}/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.1.0
|
|
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
|
|
@@ -9,7 +9,6 @@ Classifier: Development Status :: 5 - Production/Stable
|
|
|
9
9
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
10
10
|
Requires-Python: >=3.9
|
|
11
11
|
Description-Content-Type: text/markdown
|
|
12
|
-
Requires-Dist: dropbox
|
|
13
12
|
|
|
14
13
|
<img src="https://www.salabim.org/xlwings_utils_logo2.png">
|
|
15
14
|
|
|
@@ -19,7 +18,7 @@ This module provides some useful functions to be used in xlwings (lite).
|
|
|
19
18
|
|
|
20
19
|
## Installation
|
|
21
20
|
|
|
22
|
-
Just add `xlwings-utils`
|
|
21
|
+
Just add `xlwings-utils` to the *requirements.txt* tab.
|
|
23
22
|
|
|
24
23
|
In the script, add
|
|
25
24
|
|
|
@@ -66,10 +65,10 @@ The file `dropbox setup.py` can also be found in the repo of xlwings_lite .
|
|
|
66
65
|
Then, it is possible to list all files in a specified folder using the list_dropbox function.
|
|
67
66
|
It is also possible to get at all folders and to access all underlying folders.
|
|
68
67
|
|
|
69
|
-
The `read_dropbox` function can be used to read the contents (bytes) of a Dropbox file. As reading from Dropbox under pyodide is rather unreliable, xlwings_utils automatically retries several times (by default 100 times). The actual number of retries can be found with `read_dropbox.retries`.
|
|
70
|
-
|
|
71
68
|
The function `write_dropbox` can be used to write contents (bytes) to a Dropbox file.
|
|
72
69
|
|
|
70
|
+
The function `delete_from_dropbox` can be used to delete a Dropbox file.
|
|
71
|
+
|
|
73
72
|
The functions `list_local`, `read_local` and `write_local` offer similar functionality for the local file system (on pyodide).
|
|
74
73
|
|
|
75
74
|
So, a way to access a file on the system's drive (mapped to Dropbox) as a local file is:
|
|
@@ -6,7 +6,7 @@ This module provides some useful functions to be used in xlwings (lite).
|
|
|
6
6
|
|
|
7
7
|
## Installation
|
|
8
8
|
|
|
9
|
-
Just add `xlwings-utils`
|
|
9
|
+
Just add `xlwings-utils` to the *requirements.txt* tab.
|
|
10
10
|
|
|
11
11
|
In the script, add
|
|
12
12
|
|
|
@@ -53,10 +53,10 @@ The file `dropbox setup.py` can also be found in the repo of xlwings_lite .
|
|
|
53
53
|
Then, it is possible to list all files in a specified folder using the list_dropbox function.
|
|
54
54
|
It is also possible to get at all folders and to access all underlying folders.
|
|
55
55
|
|
|
56
|
-
The `read_dropbox` function can be used to read the contents (bytes) of a Dropbox file. As reading from Dropbox under pyodide is rather unreliable, xlwings_utils automatically retries several times (by default 100 times). The actual number of retries can be found with `read_dropbox.retries`.
|
|
57
|
-
|
|
58
56
|
The function `write_dropbox` can be used to write contents (bytes) to a Dropbox file.
|
|
59
57
|
|
|
58
|
+
The function `delete_from_dropbox` can be used to delete a Dropbox file.
|
|
59
|
+
|
|
60
60
|
The functions `list_local`, `read_local` and `write_local` offer similar functionality for the local file system (on pyodide).
|
|
61
61
|
|
|
62
62
|
So, a way to access a file on the system's drive (mapped to Dropbox) as a local file is:
|
|
@@ -10,12 +10,10 @@ 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
|
|
13
|
+
version = "25.1.0"
|
|
14
14
|
readme = "README.md"
|
|
15
15
|
requires-python = ">=3.9"
|
|
16
|
-
dependencies = [
|
|
17
|
-
"dropbox",
|
|
18
|
-
]
|
|
16
|
+
dependencies = []
|
|
19
17
|
classifiers = [
|
|
20
18
|
"Development Status :: 5 - Production/Stable",
|
|
21
19
|
"Programming Language :: Python :: 3 :: Only",
|
|
@@ -67,6 +67,39 @@ def test_block2():
|
|
|
67
67
|
assert this_block.value == [[1, 2, 3, None], [4, 5, 6, None], [None, None, None, None]]
|
|
68
68
|
|
|
69
69
|
|
|
70
|
+
def test_highest_used():
|
|
71
|
+
this_block = xwu.block(number_of_rows=4, number_of_columns=4)
|
|
72
|
+
assert this_block._highest_used_row_number is None
|
|
73
|
+
assert this_block.highest_used_row_number == 1
|
|
74
|
+
assert this_block.highest_used_column_number == 1
|
|
75
|
+
this_block[2, 3] = 1
|
|
76
|
+
assert this_block._highest_used_row_number is not None
|
|
77
|
+
assert this_block.highest_used_row_number == 2
|
|
78
|
+
assert this_block.highest_used_column_number == 3
|
|
79
|
+
this_block[1, 2] = 1
|
|
80
|
+
assert this_block._highest_used_row_number is not None
|
|
81
|
+
assert this_block.highest_used_row_number == 2
|
|
82
|
+
assert this_block.highest_used_column_number == 3
|
|
83
|
+
this_block[3, 4] = 1
|
|
84
|
+
assert this_block._highest_used_row_number is not None
|
|
85
|
+
assert this_block.highest_used_row_number == 3
|
|
86
|
+
assert this_block.highest_used_column_number == 4
|
|
87
|
+
this_block[3, 4] = None
|
|
88
|
+
assert this_block._highest_used_row_number is None
|
|
89
|
+
assert this_block.highest_used_row_number == 2
|
|
90
|
+
assert this_block.highest_used_column_number == 3
|
|
91
|
+
this_block[2, 3] = None
|
|
92
|
+
assert this_block._highest_used_row_number is None
|
|
93
|
+
this_block[2, 3] = 1
|
|
94
|
+
assert this_block._highest_used_row_number is None
|
|
95
|
+
this_block[2, 3] = None
|
|
96
|
+
this_block[1, 2] = None
|
|
97
|
+
assert this_block._highest_used_row_number is None
|
|
98
|
+
assert this_block.highest_used_row_number == 1
|
|
99
|
+
assert this_block.highest_used_column_number == 1
|
|
100
|
+
assert this_block._highest_used_row_number is not None
|
|
101
|
+
|
|
102
|
+
|
|
70
103
|
def test_block_one_dimension():
|
|
71
104
|
this_block = xwu.block.from_value([1, 2, 3])
|
|
72
105
|
assert this_block.value == [[1, 2, 3]]
|
|
@@ -79,20 +112,23 @@ def test_block_scalar():
|
|
|
79
112
|
this_block = xwu.block.from_value(1)
|
|
80
113
|
assert this_block.value == [[1]]
|
|
81
114
|
|
|
115
|
+
|
|
82
116
|
def test_transpose():
|
|
83
117
|
this_block = xwu.block.from_value([[1, 2, 3], [4, 5, 6]])
|
|
84
|
-
transposed_block=this_block.transposed()
|
|
85
|
-
assert transposed_block.value==[[1,4],[2,5],[3,6]]
|
|
118
|
+
transposed_block = this_block.transposed()
|
|
119
|
+
assert transposed_block.value == [[1, 4], [2, 5], [3, 6]]
|
|
120
|
+
|
|
86
121
|
|
|
87
122
|
def test_delete_none():
|
|
88
123
|
this_block = xwu.block.from_value([[1, 2, None], [4, 5, None]])
|
|
89
|
-
assert len(this_block.dict)==4
|
|
90
|
-
this_block[1,1]=None
|
|
91
|
-
assert len(this_block.dict)==3
|
|
92
|
-
this_block[1,1]=None
|
|
93
|
-
assert len(this_block.dict)==3
|
|
94
|
-
assert this_block.value==[[None, 2, None], [4, 5, None]]
|
|
95
|
-
|
|
124
|
+
assert len(this_block.dict) == 4
|
|
125
|
+
this_block[1, 1] = None
|
|
126
|
+
assert len(this_block.dict) == 3
|
|
127
|
+
this_block[1, 1] = None
|
|
128
|
+
assert len(this_block.dict) == 3
|
|
129
|
+
assert this_block.value == [[None, 2, None], [4, 5, None]]
|
|
130
|
+
|
|
131
|
+
|
|
96
132
|
def test_raise():
|
|
97
133
|
this_block = xwu.block(number_of_rows=4, number_of_columns=6)
|
|
98
134
|
with pytest.raises(IndexError):
|
|
@@ -115,52 +151,55 @@ def test_raise():
|
|
|
115
151
|
with pytest.raises(IndexError):
|
|
116
152
|
this_block[1, 7] = 1
|
|
117
153
|
|
|
154
|
+
|
|
118
155
|
def test_lookup():
|
|
119
|
-
bl=xwu.block.from_value([[1,"One", "Un"],[2, "Two", "Deux"],[3,"Three","Trois"]])
|
|
120
|
-
assert bl.lookup(1)=="One"
|
|
121
|
-
assert bl.lookup(3, column2=3)=="Trois"
|
|
156
|
+
bl = xwu.block.from_value([[1, "One", "Un"], [2, "Two", "Deux"], [3, "Three", "Trois"]])
|
|
157
|
+
assert bl.lookup(1) == "One"
|
|
158
|
+
assert bl.lookup(3, column2=3) == "Trois"
|
|
122
159
|
with pytest.raises(ValueError):
|
|
123
160
|
bl.lookup(4)
|
|
124
|
-
assert bl.lookup(4,default=
|
|
161
|
+
assert bl.lookup(4, default="x") == "x"
|
|
125
162
|
with pytest.raises(ValueError):
|
|
126
163
|
bl.lookup(1, column1=4)
|
|
127
164
|
with pytest.raises(ValueError):
|
|
128
165
|
bl.lookup(1, column1=3)
|
|
129
|
-
assert bl.lookup_row(1)==1
|
|
130
|
-
assert bl.lookup_row(3)==3
|
|
166
|
+
assert bl.lookup_row(1) == 1
|
|
167
|
+
assert bl.lookup_row(3) == 3
|
|
168
|
+
|
|
131
169
|
|
|
132
170
|
def test_vookup():
|
|
133
|
-
bl=xwu.block.from_value([[1,"One", "Un"],[2, "Two", "Deux"],[3,"Three","Trois"]])
|
|
134
|
-
assert bl.vlookup(1)=="One"
|
|
135
|
-
assert bl.vlookup(3, column2=3)=="Trois"
|
|
171
|
+
bl = xwu.block.from_value([[1, "One", "Un"], [2, "Two", "Deux"], [3, "Three", "Trois"]])
|
|
172
|
+
assert bl.vlookup(1) == "One"
|
|
173
|
+
assert bl.vlookup(3, column2=3) == "Trois"
|
|
136
174
|
with pytest.raises(ValueError):
|
|
137
175
|
bl.vlookup(4)
|
|
138
|
-
assert bl.vlookup(4, default=
|
|
176
|
+
assert bl.vlookup(4, default="x") == "x"
|
|
139
177
|
with pytest.raises(ValueError):
|
|
140
178
|
bl.vlookup(1, column1=4)
|
|
141
179
|
with pytest.raises(ValueError):
|
|
142
180
|
bl.vlookup(1, column1=3)
|
|
143
181
|
|
|
182
|
+
|
|
144
183
|
def test_hlookup():
|
|
145
|
-
bl=xwu.block.from_value([[1,2,3],"One Two Three".split(), "Un Deux Trois".split()])
|
|
146
|
-
|
|
147
|
-
assert bl.hlookup(1)=="One"
|
|
148
|
-
assert bl.hlookup(3, row2=3)=="Trois"
|
|
184
|
+
bl = xwu.block.from_value([[1, 2, 3], "One Two Three".split(), "Un Deux Trois".split()])
|
|
185
|
+
|
|
186
|
+
assert bl.hlookup(1) == "One"
|
|
187
|
+
assert bl.hlookup(3, row2=3) == "Trois"
|
|
149
188
|
with pytest.raises(ValueError):
|
|
150
189
|
bl.hlookup(4)
|
|
151
|
-
assert bl.hlookup(4,default=
|
|
190
|
+
assert bl.hlookup(4, default="x") == "x"
|
|
152
191
|
with pytest.raises(ValueError):
|
|
153
192
|
bl.hlookup(1, row1=4)
|
|
154
193
|
with pytest.raises(ValueError):
|
|
155
194
|
bl.hlookup(1, row1=3)
|
|
156
|
-
assert bl.lookup_column(1)==1
|
|
157
|
-
assert bl.lookup_column(3)==3
|
|
195
|
+
assert bl.lookup_column(1) == 1
|
|
196
|
+
assert bl.lookup_column(3) == 3
|
|
158
197
|
|
|
159
198
|
|
|
160
199
|
def test_capture(capsys):
|
|
161
200
|
print("abc")
|
|
162
201
|
print("def")
|
|
163
|
-
capture=xwu.Capture()
|
|
202
|
+
capture = xwu.Capture()
|
|
164
203
|
out, err = capsys.readouterr()
|
|
165
204
|
assert out == "abc\ndef\n"
|
|
166
205
|
assert capture.str_keep == ""
|
|
@@ -172,10 +211,10 @@ def test_capture(capsys):
|
|
|
172
211
|
out, err = capsys.readouterr()
|
|
173
212
|
assert out == ""
|
|
174
213
|
assert capture.str_keep == "abc\ndef\n"
|
|
175
|
-
assert capture.value_keep == [[
|
|
214
|
+
assert capture.value_keep == [["abc"], ["def"]]
|
|
176
215
|
assert capture.str == "abc\ndef\n"
|
|
177
216
|
assert capture.value == []
|
|
178
|
-
|
|
217
|
+
|
|
179
218
|
with capture:
|
|
180
219
|
print("abc")
|
|
181
220
|
print("def")
|
|
@@ -188,28 +227,27 @@ def test_capture(capsys):
|
|
|
188
227
|
print("def")
|
|
189
228
|
with capture:
|
|
190
229
|
print("ghi")
|
|
191
|
-
print("jkl")
|
|
230
|
+
print("jkl")
|
|
192
231
|
out, err = capsys.readouterr()
|
|
193
232
|
assert out == ""
|
|
194
|
-
assert capture.str_keep == "abc\ndef\nghi\njkl\n"
|
|
195
|
-
assert capture.value_keep == [[
|
|
196
|
-
assert capture.value == [[
|
|
233
|
+
assert capture.str_keep == "abc\ndef\nghi\njkl\n"
|
|
234
|
+
assert capture.value_keep == [["abc"], ["def"], ["ghi"], ["jkl"]]
|
|
235
|
+
assert capture.value == [["abc"], ["def"], ["ghi"], ["jkl"]]
|
|
197
236
|
assert capture.value == []
|
|
198
|
-
|
|
199
|
-
capture.enabled=True
|
|
237
|
+
|
|
238
|
+
capture.enabled = True
|
|
200
239
|
print("abc")
|
|
201
|
-
print("def")
|
|
202
|
-
capture.enabled=False
|
|
240
|
+
print("def")
|
|
241
|
+
capture.enabled = False
|
|
203
242
|
print("xxx")
|
|
204
|
-
print("yyy")
|
|
205
|
-
capture.enabled=True
|
|
243
|
+
print("yyy")
|
|
244
|
+
capture.enabled = True
|
|
206
245
|
print("ghi")
|
|
207
|
-
print("jkl")
|
|
208
|
-
assert capture.str_keep == "abc\ndef\nghi\njkl\n"
|
|
246
|
+
print("jkl")
|
|
247
|
+
assert capture.str_keep == "abc\ndef\nghi\njkl\n"
|
|
248
|
+
|
|
249
|
+
# include_print is not testable with pytest
|
|
250
|
+
|
|
209
251
|
|
|
210
|
-
|
|
211
|
-
# include_print is not testable with pytest
|
|
212
|
-
|
|
213
252
|
if __name__ == "__main__":
|
|
214
253
|
pytest.main(["-vv", "-s", "-x", __file__])
|
|
215
|
-
|
|
@@ -5,28 +5,33 @@
|
|
|
5
5
|
# /_/\_\|_| \_/\_/ |_||_| |_| \__, ||___/ _____ \__,_| \__||_||_||___/
|
|
6
6
|
# |___/ |_____|
|
|
7
7
|
|
|
8
|
-
__version__ = "25.0
|
|
8
|
+
__version__ = "25.1.0"
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
import dropbox
|
|
12
11
|
from pathlib import Path
|
|
13
12
|
import os
|
|
14
13
|
import sys
|
|
15
14
|
import math
|
|
16
15
|
import base64
|
|
16
|
+
import requests
|
|
17
|
+
import json
|
|
17
18
|
|
|
18
|
-
dbx = None
|
|
19
19
|
Pythonista = sys.platform == "ios"
|
|
20
|
+
|
|
20
21
|
try:
|
|
21
22
|
import xlwings
|
|
22
23
|
|
|
23
24
|
xlwings = True
|
|
25
|
+
import pyodide_http
|
|
24
26
|
|
|
25
27
|
except ImportError:
|
|
26
28
|
xlwings = False
|
|
27
29
|
|
|
28
30
|
missing = object()
|
|
29
31
|
|
|
32
|
+
_token = None
|
|
33
|
+
missing = object()
|
|
34
|
+
|
|
30
35
|
|
|
31
36
|
def dropbox_init(refresh_token=missing, app_key=missing, app_secret=missing, **kwargs):
|
|
32
37
|
"""
|
|
@@ -59,7 +64,10 @@ def dropbox_init(refresh_token=missing, app_key=missing, app_secret=missing, **k
|
|
|
59
64
|
-------
|
|
60
65
|
dropbox object
|
|
61
66
|
"""
|
|
62
|
-
|
|
67
|
+
|
|
68
|
+
global _token
|
|
69
|
+
if xlwings:
|
|
70
|
+
pyodide_http.patch_all() # to enable chunked mode
|
|
63
71
|
|
|
64
72
|
if refresh_token is missing:
|
|
65
73
|
if "DROPBOX.REFRESH_TOKEN" in os.environ:
|
|
@@ -77,18 +85,22 @@ def dropbox_init(refresh_token=missing, app_key=missing, app_secret=missing, **k
|
|
|
77
85
|
else:
|
|
78
86
|
raise ValueError("no DROPBOX.APP_SECRET found in environment.")
|
|
79
87
|
|
|
80
|
-
|
|
88
|
+
resp = requests.post(
|
|
89
|
+
"https://api.dropbox.com/oauth2/token",
|
|
90
|
+
data={"grant_type": "refresh_token", "refresh_token": refresh_token, "client_id": app_key, "client_secret": app_secret},
|
|
91
|
+
timeout=30,
|
|
92
|
+
)
|
|
81
93
|
try:
|
|
82
|
-
|
|
83
|
-
except
|
|
94
|
+
resp.raise_for_status()
|
|
95
|
+
except requests.exceptions.HTTPError:
|
|
84
96
|
raise ValueError("invalid dropbox credentials")
|
|
85
|
-
|
|
97
|
+
_token = resp.json()["access_token"]
|
|
86
98
|
|
|
87
99
|
|
|
88
|
-
def
|
|
89
|
-
global
|
|
90
|
-
if
|
|
91
|
-
|
|
100
|
+
def _login_dropbox():
|
|
101
|
+
global _token
|
|
102
|
+
if _token is None:
|
|
103
|
+
dropbox_init() # use environment
|
|
92
104
|
|
|
93
105
|
|
|
94
106
|
def list_dropbox(path="", recursive=False, show_files=True, show_folders=False):
|
|
@@ -122,28 +134,31 @@ def list_dropbox(path="", recursive=False, show_files=True, show_folders=False):
|
|
|
122
134
|
If REFRESH_TOKEN, APP_KEY and APP_SECRET environment variables are specified,
|
|
123
135
|
it is not necessary to call dropbox_init() prior to any dropbox function.
|
|
124
136
|
"""
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
137
|
+
_login_dropbox()
|
|
138
|
+
|
|
139
|
+
API_RPC = "https://api.dropboxapi.com/2"
|
|
140
|
+
headers = {"Authorization": f"Bearer {_token}", "Content-Type": "application/json"}
|
|
141
|
+
payload = {"path": path, "recursive": recursive, "include_deleted": False}
|
|
142
|
+
r = requests.post("https://api.dropboxapi.com/2/files/list_folder", headers=headers, json=payload, timeout=30)
|
|
143
|
+
r.raise_for_status()
|
|
144
|
+
data = r.json()
|
|
145
|
+
entries = data["entries"]
|
|
146
|
+
while data.get("has_more"):
|
|
147
|
+
r = requests.post(f"{API_RPC}/files/list_folder/continue", headers=headers, json={"cursor": data["cursor"]}, timeout=30)
|
|
148
|
+
r.raise_for_status()
|
|
149
|
+
data = r.json()
|
|
150
|
+
entries.extend(data["entries"])
|
|
128
151
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
result = dbx.files_list_folder_continue(result.cursor)
|
|
137
|
-
for entry in result.entries:
|
|
138
|
-
if show_files and isinstance(entry, dropbox.files.FileMetadata):
|
|
139
|
-
out.append(entry.path_display)
|
|
140
|
-
if show_folders and isinstance(entry, dropbox.files.FolderMetadata):
|
|
141
|
-
out.append(entry.path_display + "/")
|
|
142
|
-
|
|
143
|
-
return out
|
|
152
|
+
result = []
|
|
153
|
+
for entry in entries:
|
|
154
|
+
if show_files and entry[".tag"] == "file":
|
|
155
|
+
result.append(entry["path_display"])
|
|
156
|
+
if show_folders and entry[".tag"] == "folder":
|
|
157
|
+
result.append(entry["path_display"] + "/")
|
|
158
|
+
return result
|
|
144
159
|
|
|
145
160
|
|
|
146
|
-
def read_dropbox(dropbox_path
|
|
161
|
+
def read_dropbox(dropbox_path):
|
|
147
162
|
"""
|
|
148
163
|
read_dropbox
|
|
149
164
|
|
|
@@ -154,9 +169,6 @@ def read_dropbox(dropbox_path, max_retries=100):
|
|
|
154
169
|
dropbox_path : str or Pathlib.Path
|
|
155
170
|
path to read from
|
|
156
171
|
|
|
157
|
-
max_retries : int
|
|
158
|
-
number of retries (default: 100)
|
|
159
|
-
|
|
160
172
|
Returns
|
|
161
173
|
-------
|
|
162
174
|
contents of the dropbox file : bytes
|
|
@@ -165,18 +177,20 @@ def read_dropbox(dropbox_path, max_retries=100):
|
|
|
165
177
|
----
|
|
166
178
|
If REFRESH_TOKEN, APP_KEY and APP_SECRET environment variables are specified,
|
|
167
179
|
it is not necessary to call dropbox_init() prior to any dropbox function.
|
|
168
|
-
|
|
169
|
-
As reading from dropbox is very unreliable under pyodide, reading will have to be retried (by default maximum 100 times).
|
|
170
|
-
The number of retries can be found with read_dropbox.retries.
|
|
171
180
|
"""
|
|
172
181
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
182
|
+
_login_dropbox()
|
|
183
|
+
headers = {"Authorization": f"Bearer {_token}", "Dropbox-API-Arg": json.dumps({"path": dropbox_path})}
|
|
184
|
+
with requests.post("https://content.dropboxapi.com/2/files/download", headers=headers, stream=True, timeout=60) as r:
|
|
185
|
+
try:
|
|
186
|
+
r.raise_for_status()
|
|
187
|
+
except requests.exceptions.HTTPError as e:
|
|
188
|
+
raise FileNotFoundError(f"file {dropbox_path} not found. Original message is {e}") from None
|
|
189
|
+
chunks = []
|
|
190
|
+
for chunk in r.iter_content(chunk_size=1024):
|
|
191
|
+
if chunk:
|
|
192
|
+
chunks.append(chunk)
|
|
193
|
+
return b"".join(chunks)
|
|
180
194
|
|
|
181
195
|
|
|
182
196
|
def write_dropbox(dropbox_path, contents):
|
|
@@ -198,8 +212,36 @@ def write_dropbox(dropbox_path, contents):
|
|
|
198
212
|
If REFRESH_TOKEN, APP_KEY and APP_SECRET environment variables are specified,
|
|
199
213
|
it is not necessary to call dropbox_init() prior to any dropbox function.
|
|
200
214
|
"""
|
|
201
|
-
|
|
202
|
-
|
|
215
|
+
_login_dropbox()
|
|
216
|
+
headers = {
|
|
217
|
+
"Authorization": f"Bearer {_token}",
|
|
218
|
+
"Dropbox-API-Arg": json.dumps(
|
|
219
|
+
{
|
|
220
|
+
"path": dropbox_path, # Where it will be saved in Dropbox
|
|
221
|
+
"mode": "overwrite", # "add" or "overwrite"
|
|
222
|
+
"autorename": False,
|
|
223
|
+
"mute": False,
|
|
224
|
+
}
|
|
225
|
+
),
|
|
226
|
+
"Content-Type": "application/octet-stream",
|
|
227
|
+
}
|
|
228
|
+
response = requests.post("https://content.dropboxapi.com/2/files/upload", headers=headers, data=contents)
|
|
229
|
+
return response
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def delete_from_dropbox(dropbox_path):
|
|
233
|
+
_login_dropbox()
|
|
234
|
+
|
|
235
|
+
headers = {"Authorization": f"Bearer {_token}", "Content-Type": "application/json"}
|
|
236
|
+
|
|
237
|
+
data = {
|
|
238
|
+
"path": dropbox_path # Path in Dropbox, starting with /
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
response = requests.post("https://api.dropboxapi.com/2/files/delete_v2", headers=headers, data=json.dumps(data))
|
|
242
|
+
if response.status_code == 200:
|
|
243
|
+
return
|
|
244
|
+
raise FileNotFoundError(f"dropbox file {dropbox_path} not found")
|
|
203
245
|
|
|
204
246
|
|
|
205
247
|
def list_local(path, recursive=False, show_files=True, show_folders=False):
|
|
@@ -278,6 +320,8 @@ class block:
|
|
|
278
320
|
self.dict = {}
|
|
279
321
|
self.number_of_rows = number_of_rows
|
|
280
322
|
self.number_of_columns = number_of_columns
|
|
323
|
+
self._highest_used_row_number = None
|
|
324
|
+
self._highest_used_column_number = None
|
|
281
325
|
|
|
282
326
|
def __eq__(self, other):
|
|
283
327
|
if isinstance(other, block):
|
|
@@ -460,8 +504,14 @@ class block:
|
|
|
460
504
|
if value is None:
|
|
461
505
|
if (row, column) in self.dict:
|
|
462
506
|
del self.dict[row, column]
|
|
507
|
+
self._highest_used_row_number = None # invalidate cached value
|
|
508
|
+
self._highest_used_column_number = None # invalidate cached value
|
|
463
509
|
else:
|
|
464
510
|
self.dict[row, column] = value
|
|
511
|
+
if self._highest_used_row_number:
|
|
512
|
+
self._highest_used_row_number = max(self._highest_used_row_number, row)
|
|
513
|
+
if self._highest_used_column_number:
|
|
514
|
+
self._highest_used_column_number = max(self._highest_used_column_number, column)
|
|
465
515
|
|
|
466
516
|
def __getitem__(self, row_column):
|
|
467
517
|
row, column = row_column
|
|
@@ -488,6 +538,7 @@ class block:
|
|
|
488
538
|
def number_of_rows(self, value):
|
|
489
539
|
if value < 1:
|
|
490
540
|
raise ValueError(f"number_of_rows should be >=1, not {value}")
|
|
541
|
+
self._highest_used_row_number = None
|
|
491
542
|
self._number_of_rows = value
|
|
492
543
|
for row, column in list(self.dict):
|
|
493
544
|
if row > self._number_of_rows:
|
|
@@ -501,6 +552,7 @@ class block:
|
|
|
501
552
|
def number_of_columns(self, value):
|
|
502
553
|
if value < 1:
|
|
503
554
|
raise ValueError(f"number_of_columns should be >=1, not {value}")
|
|
555
|
+
self._highest_used_column_number = None
|
|
504
556
|
self._number_of_columns = value
|
|
505
557
|
for row, column in list(self.dict):
|
|
506
558
|
if column > self._number_of_columns:
|
|
@@ -508,17 +560,22 @@ class block:
|
|
|
508
560
|
|
|
509
561
|
@property
|
|
510
562
|
def highest_used_row_number(self):
|
|
511
|
-
if self.
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
563
|
+
if not self._highest_used_row_number:
|
|
564
|
+
if self.dict:
|
|
565
|
+
self._highest_used_row_number = max(row for (row, column) in self.dict)
|
|
566
|
+
else:
|
|
567
|
+
self._highest_used_row_number = 1
|
|
568
|
+
return self._highest_used_row_number
|
|
515
569
|
|
|
516
570
|
@property
|
|
517
571
|
def highest_used_column_number(self):
|
|
518
|
-
if self.
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
572
|
+
if not self._highest_used_column_number:
|
|
573
|
+
if self.dict:
|
|
574
|
+
self._highest_used_column_number = max(column for (row, column) in self.dict)
|
|
575
|
+
else:
|
|
576
|
+
self._highest_used_column_number = 1
|
|
577
|
+
|
|
578
|
+
return self._highest_used_column_number
|
|
522
579
|
|
|
523
580
|
def __repr__(self):
|
|
524
581
|
return f"block({self.value})"
|