xlwings-utils 25.0.10__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.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: xlwings_utils
3
- Version: 25.0.10
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` and `ssl` (even if `dropbox` is not used) to the *requirements.txt* tab.
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` and `ssl` (even if `dropbox` is not used) to the *requirements.txt* tab.
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.10"
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",
@@ -5,28 +5,33 @@
5
5
  # /_/\_\|_| \_/\_/ |_||_| |_| \__, ||___/ _____ \__,_| \__||_||_||___/
6
6
  # |___/ |_____|
7
7
 
8
- __version__ = "25.0.10"
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
- global dbx
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
- _dbx = dropbox.Dropbox(oauth2_refresh_token=refresh_token, app_key=app_key, app_secret=app_secret, **kwargs)
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
- _dbx.files_list_folder(path="") # just to test proper credentials
83
- except dropbox.exceptions.AuthError:
94
+ resp.raise_for_status()
95
+ except requests.exceptions.HTTPError:
84
96
  raise ValueError("invalid dropbox credentials")
85
- return _dbx
97
+ _token = resp.json()["access_token"]
86
98
 
87
99
 
88
- def _login_dbx():
89
- global dbx
90
- if dbx is None:
91
- dbx = dropbox_init() # use environment
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
- _login_dbx()
126
- out = []
127
- result = dbx.files_list_folder(path, recursive=recursive)
128
-
129
- for entry in result.entries:
130
- if show_files and isinstance(entry, dropbox.files.FileMetadata):
131
- out.append(entry.path_display)
132
- if show_folders and isinstance(entry, dropbox.files.FolderMetadata):
133
- out.append(entry.path_display + "/")
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"])
134
151
 
135
- while result.has_more:
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, max_retries=100):
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
- _login_dbx()
174
- for read_dropbox.retries in range(max_retries + 1):
175
- metadata, response = dbx.files_download(dropbox_path)
176
- file_content = response.content
177
- if len(file_content) == metadata.size:
178
- return file_content
179
- raise OSError(f"after {max_retries} still no valid response")
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
- _login_dbx()
202
- dbx.files_upload(contents, dropbox_path, mode=dropbox.files.WriteMode.overwrite)
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):