xlwings-utils 0.0.5__py3-none-any.whl → 0.0.7__py3-none-any.whl

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.

@@ -5,22 +5,27 @@
5
5
  # /_/\_\|_| \_/\_/ |_||_| |_| \__, ||___/ _____ \__,_| \__||_||_||___/
6
6
  # |___/ |_____|
7
7
 
8
- __version__ = "0.0.5"
8
+ __version__ = "0.0.7"
9
9
 
10
10
 
11
11
  import dropbox
12
12
  from pathlib import Path
13
13
  import os
14
+ import sys
14
15
 
15
16
  _captured_stdout = []
16
17
  dbx = None
18
+ Pythonista = sys.platform == "ios"
17
19
 
18
20
 
19
21
  def dropbox_init(refresh_token=None, app_key=None, app_secret=None):
20
22
  """
21
23
  dropbox initialize
22
24
 
23
- This function has to be called prior to using any dropbox function
25
+ This function may to be called prior to using any dropbox function
26
+ to specify the request token, app key and app secret.
27
+ If these are specified as REFRESH_TOKEN, APP_KEY and APP_SECRET
28
+ environment variables, it is no necessary to call dropbox_init().
24
29
 
25
30
  Parameters
26
31
  ----------
@@ -44,54 +49,99 @@ def dropbox_init(refresh_token=None, app_key=None, app_secret=None):
44
49
  -------
45
50
  -
46
51
  """
52
+ global dbx
53
+
54
+ if Pythonista:
55
+ # under Pythonista, the environ is updated from the .environ.toml file, if present
56
+ environ_file = Path(os.environ["HOME"]) / "Documents" / ".environ.toml"
57
+
58
+ if environ_file.is_file():
59
+ with open(environ_file, "r") as f:
60
+ import toml
61
+
62
+ d = toml.load(f)
63
+ os.environ.update(d)
64
+
47
65
  if refresh_token is None:
48
- refresh_token = os.environ["REFRESH_TOKEN"]
66
+ if "REFRESH_TOKEN" in os.environ:
67
+ refresh_token = os.environ["REFRESH_TOKEN"]
68
+ else:
69
+ raise ValueError("no REFRESH_TOKEN found in environment.")
49
70
  if app_key is None:
50
- app_key = os.environ["APP_KEY"]
71
+ if "APP_KEY" in os.environ:
72
+ app_key = os.environ["APP_KEY"]
73
+ else:
74
+ raise ValueError("no APP_KEY found in environment.")
51
75
  if app_secret is None:
52
- app_secret = os.environ["APP_SECRET"]
76
+ if "APP_SECRET" in os.environ:
77
+ app_secret = os.environ["APP_SECRET"]
78
+ else:
79
+ raise ValueError("no APP_SECRET found in environment.")
53
80
 
54
- global dbx
55
81
  dbx = dropbox.Dropbox(oauth2_refresh_token=refresh_token, app_key=app_key, app_secret=app_secret)
82
+ try:
83
+ dbx.files_list_folder(path="") # just to test proper credentials
84
+ except dropbox.exceptions.AuthError:
85
+ raise ValueError("invalid dropbox credentials")
56
86
 
57
87
 
58
- def _check_dbx():
88
+ def _login_dbx():
59
89
  if dbx is None:
60
- raise ValueError("not initialized. Please call dropbox_init()")
90
+ dropbox_init() # use environment
61
91
 
62
92
 
63
- def list_dropbox(path="", recursive=False):
93
+ def list_dropbox(path="", recursive=False, show_files=True, show_folders=False):
64
94
  """
65
95
  list_dropbox
66
96
 
67
- returns all dropbox files in path
97
+ returns all dropbox files/folders in path
68
98
 
69
99
  Parameters
70
100
  ----------
71
101
  path : str or Pathlib.Path
72
102
  path from which to list all files (default: '')
73
103
 
74
-
75
104
  recursive : bool
76
105
  if True, recursively list files. if False (default) no recursion
77
106
 
107
+ show_files : bool
108
+ if True (default), show file entries
109
+ if False, do not show file entries
110
+
111
+ show_folders : bool
112
+ if True, show folder entries
113
+ if False (default), do not show folder entries
114
+
78
115
  Returns
79
116
  -------
80
- files, relative to path : list
117
+ files : list
118
+
119
+ Note
120
+ ----
121
+ Directory entries are never returned
122
+
123
+ Note
124
+ ----
125
+ If REFRESH_TOKEN, APP_KEY and APP_SECRET environment variables are specified,
126
+ it is not necessary to call dropbox_init() prior to any dropbox function.
81
127
  """
82
- _check_dbx()
128
+ _login_dbx()
83
129
  out = []
84
130
  result = dbx.files_list_folder(path, recursive=recursive)
85
131
 
86
132
  for entry in result.entries:
87
- if isinstance(entry, dropbox.files.FileMetadata):
133
+ if show_files and isinstance(entry, dropbox.files.FileMetadata):
88
134
  out.append(entry.path_display)
135
+ if show_folders and isinstance(entry, dropbox.files.FolderMetadata):
136
+ out.append(entry.path_display + "/")
89
137
 
90
138
  while result.has_more:
91
139
  result = dbx.files_list_folder_continue(result.cursor)
92
140
  for entry in result.entries:
93
- if isinstance(entry, dropbox.files.FileMetadata):
141
+ if show_files and isinstance(entry, dropbox.files.FileMetadata):
94
142
  out.append(entry.path_display)
143
+ if show_folders and isinstance(entry, dropbox.files.FolderMetadata):
144
+ out.append(entry.path_display + "/")
95
145
 
96
146
  return out
97
147
 
@@ -110,16 +160,21 @@ def read_dropbox(dropbox_path):
110
160
  Returns
111
161
  -------
112
162
  contents of the dropbox file : bytes
163
+
164
+ Note
165
+ ----
166
+ If REFRESH_TOKEN, APP_KEY and APP_SECRET environment variables are specified,
167
+ it is not necessary to call dropbox_init() prior to any dropbox function.
113
168
  """
114
169
 
115
- _check_dbx()
170
+ _login_dbx()
116
171
  metadata, response = dbx.files_download(dropbox_path)
117
172
  file_content = response.content
118
173
  return file_content
119
174
 
120
175
 
121
176
  def write_dropbox(dropbox_path, contents):
122
- _check_dbx()
177
+ _login_dbx()
123
178
  """
124
179
  write_dropbox
125
180
 
@@ -133,13 +188,53 @@ def write_dropbox(dropbox_path, contents):
133
188
  contents : bytes
134
189
  contents to be written
135
190
 
191
+ Note
192
+ ----
193
+ If REFRESH_TOKEN, APP_KEY and APP_SECRET environment variables are specified,
194
+ it is not necessary to call dropbox_init() prior to any dropbox function.
136
195
  """
137
196
  dbx.files_upload(contents, dropbox_path, mode=dropbox.files.WriteMode.overwrite)
138
197
 
139
198
 
140
- def list_local(path):
199
+ def list_local(path, recursive=False, show_files=True, show_folders=False):
200
+ """
201
+ list_local
202
+
203
+ returns all local files/folders in path
204
+
205
+ Parameters
206
+ ----------
207
+ path : str or Pathlib.Path
208
+ path from which to list all files (default: '')
209
+
210
+ recursive : bool
211
+ if True, recursively list files. if False (default) no recursion
212
+
213
+ show_files : bool
214
+ if True (default), show file entries
215
+ if False, do not show file entries
216
+
217
+ show_folders : bool
218
+ if True, show folder entries
219
+ if False (default), do not show folder entries
220
+
221
+ Returns
222
+ -------
223
+ files, relative to path : list
224
+ """
141
225
  path = Path(path)
142
- return list(path.iterdir())
226
+
227
+ result = []
228
+ for entry in path.iterdir():
229
+ if entry.is_file():
230
+ if show_files:
231
+ result.append(str(entry))
232
+ elif entry.is_dir():
233
+ if show_folders:
234
+ result.append(str(entry) + "/")
235
+ if recursive:
236
+ result.extend(list_local(entry, recursive=recursive, show_files=show_files, show_folders=show_folders))
237
+ return result
143
238
 
144
239
 
145
240
  def write_local(path, contents):
@@ -173,55 +268,51 @@ class block:
173
268
  block
174
269
  """
175
270
 
176
- def __init__(self, number_of_rows, number_of_columns):
271
+ def __init__(self, value=None, *, number_of_rows=None, number_of_columns=None,column_like=False):
177
272
  self.dict = {}
178
- self.number_of_columns = number_of_columns
179
- self.number_of_rows = number_of_rows
180
-
181
- @classmethod
182
- def from_list_of_lists(cls, list_of_lists, column_like=False):
183
- """
184
- from_list_of_lists
185
-
186
- Parameters
187
- ----------
188
- list_of_lists : list of lists
189
- to be used to fill the block
190
-
191
- columns_like : bool
192
- if False (default), one dimenional lists will be treated as a 1 high range (row-like)
193
-
194
- if True, one dimenional lists will be treated as a 1 wide range (column-like)
195
-
196
- This parameter is not used for proper 2 dimensional list of lists or scalars
197
-
198
- Returns
199
- -------
200
- block
201
-
202
- Note
203
- ----
204
- number_of_rows and number_of_columns will be retrieved from list_of_lists dimension
205
- """
206
- if not isinstance(list_of_lists, list):
207
- list_of_lists = [[list_of_lists]]
208
- if not isinstance(list_of_lists[0], list):
209
- if column_like:
210
- list_of_lists = [[value] for value in list_of_lists]
273
+ self.column_like=column_like
274
+ if value is None:
275
+ if number_of_rows is None:
276
+ number_of_rows=1
277
+ if number_of_columns is None:
278
+ number_of_columns=1
279
+ self.number_of_rows=number_of_rows
280
+ self.number_of_columns=number_of_columns
281
+
282
+ else:
283
+ if isinstance(value,block):
284
+ value=value.value
285
+ self.value=value
286
+ if number_of_rows is not None:
287
+ self.number_of_rows=number_of_rows
288
+ if number_of_columns is not None:
289
+ self.number_of_columns=number_of_columns
290
+
291
+
292
+ @property
293
+ def value(self):
294
+ return [[self.dict.get((row, column)) for column in range(1, self.number_of_columns + 1)] for row in range(1, self.number_of_rows + 1)]
295
+
296
+ @value.setter
297
+ def value(self,value):
298
+ if not isinstance(value, list):
299
+ value = [[value]]
300
+ if not isinstance(value[0], list):
301
+ if self.column_like:
302
+ value = [[item] for item in value]
211
303
  else:
212
- list_of_lists = [list_of_lists]
304
+ value = [value]
213
305
 
214
- self = cls(1, 1)
215
306
 
216
- self.number_of_rows = len(list_of_lists)
307
+ self.number_of_rows = len(value)
217
308
  self._number_of_columns = 0
218
309
 
219
- for row, row_contents in enumerate(list_of_lists, 1):
220
- for column, value in enumerate(row_contents, 1):
221
- if value is not None:
222
- self.dict[row, column] = value
223
- self._number_of_columns = max(self.number_of_columns, column)
224
- return self
310
+ for row, row_contents in enumerate(value, 1):
311
+ for column, item in enumerate(row_contents, 1):
312
+ if item is not None:
313
+ self.dict[row, column] = item
314
+ self._number_of_columns = max(self.number_of_columns, column)
315
+
225
316
 
226
317
  def __setitem__(self, row_column, value):
227
318
  row, column = row_column
@@ -238,14 +329,9 @@ class block:
238
329
  if column < 1 or column > self.number_of_columns:
239
330
  raise IndexError(f"column must be between 1 and {self.number_of_columns} not {column}")
240
331
  return self.dict.get((row, column))
241
-
242
- @property
243
- def as_list_of_lists(self):
244
- return [[self.dict.get((row, column)) for column in range(1, self.number_of_columns + 1)] for row in range(1, self.number_of_rows + 1)]
245
-
246
- @property
247
- def as_minimal_list_of_lists(self):
248
- return [[self.dict.get((row, column)) for column in range(1, self.maximum_column + 1)] for row in range(1, self.maximum_row + 1)]
332
+
333
+ def minimized(self):
334
+ return block(self, number_of_rows=self.highest_used_row_number, number_of_columns=self.highest_used_column_number)
249
335
 
250
336
  @property
251
337
  def number_of_rows(self):
@@ -253,7 +339,7 @@ class block:
253
339
 
254
340
  @number_of_rows.setter
255
341
  def number_of_rows(self, value):
256
- if value<1:
342
+ if value < 1:
257
343
  raise ValueError(f"number_of_rows should be >=1, not {value}")
258
344
  self._number_of_rows = value
259
345
  for row, column in list(self.dict):
@@ -266,29 +352,30 @@ class block:
266
352
 
267
353
  @number_of_columns.setter
268
354
  def number_of_columns(self, value):
269
- if value<1:
355
+ if value < 1:
270
356
  raise ValueError(f"number_of_columns should be >=1, not {value}")
271
357
  self._number_of_columns = value
272
358
  for row, column in list(self.dict):
273
359
  if column > self._number_of_columns:
274
360
  del self.dict[row, column]
361
+
275
362
 
276
363
  @property
277
- def maximum_row(self):
364
+ def highest_used_row_number(self):
278
365
  if self.dict:
279
366
  return max(row for (row, column) in self.dict)
280
367
  else:
281
368
  return 1
282
369
 
283
370
  @property
284
- def maximum_column(self):
371
+ def highest_used_column_number(self):
285
372
  if self.dict:
286
373
  return max(column for (row, column) in self.dict)
287
374
  else:
288
375
  return 1
289
376
 
290
377
  def __repr__(self):
291
- return f"block.from_list_of_lists({self.as_list_of_lists})"
378
+ return f"block({self.value})"
292
379
 
293
380
 
294
381
  def clear_captured_stdout():
@@ -300,7 +387,7 @@ def clear_captured_stdout():
300
387
 
301
388
  def captured_stdout_as_str():
302
389
  """
303
- returns the captured stdout as a list of strings
390
+ returns the captured stdout as a string
304
391
 
305
392
  Returns
306
393
  -------
@@ -311,7 +398,7 @@ def captured_stdout_as_str():
311
398
  return "".join(_captured_stdout)
312
399
 
313
400
 
314
- def captured_stdout_as_list_of_lists():
401
+ def captured_stdout_as_value():
315
402
  """
316
403
  returns the captured stdout as a list of lists
317
404
 
@@ -319,14 +406,17 @@ def captured_stdout_as_list_of_lists():
319
406
  -------
320
407
  captured stdout : list
321
408
  each line is an element of the list
322
- """
323
409
 
410
+ Note
411
+ ----
412
+ This can be used directly to fill a xlwings range
413
+ """
324
414
  return [[line] for line in captured_stdout_as_str().splitlines()]
325
415
 
326
416
 
327
417
  class capture_stdout:
328
418
  """
329
- specifies how to capture stdout
419
+ start capture stdout
330
420
 
331
421
  Parameters
332
422
  ----------
@@ -0,0 +1,132 @@
1
+ Metadata-Version: 2.4
2
+ Name: xlwings_utils
3
+ Version: 0.0.7
4
+ Summary: xlwings_utils
5
+ Author-email: Ruud van der Ham <rt.van.der.ham@gmail.com>
6
+ Project-URL: Homepage, https://github.com/salabim/xlwings_utils
7
+ Project-URL: Repository, https://github.com/salabim/xlwings_utils
8
+ Classifier: Development Status :: 5 - Production/Stable
9
+ Classifier: Programming Language :: Python :: 3 :: Only
10
+ Requires-Python: >=3.9
11
+ Description-Content-Type: text/markdown
12
+ Requires-Dist: dropbox
13
+ Requires-Dist: ssl
14
+
15
+ <img src="https://www.salabim.org/xlwings_utils_logo2.png">
16
+
17
+ ## Introduction
18
+
19
+ This module provides some useful functions to be used in xlwings lite.
20
+
21
+ ## Installation
22
+
23
+ Just add xlwings-utils to the *requirement.txt* tab. It might be required to add ssl.
24
+
25
+ In the script, add
26
+
27
+ ```ìmport xlwings_utils as xwu```
28
+
29
+ ## Dropbox support
30
+
31
+ The xlwings lite system does not provide access to the local file system. With this module, files can be copied between Dropbox and the local pyodide file system, making it possible to indirectly use the local file system.
32
+
33
+ It is only possible, as of now, to use full-access Dropbox apps.
34
+
35
+ 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.
36
+
37
+ Then, it is possible to list all files in a specified folder with the function `list_dropbox`.
38
+ It is also possible to get the folders and to access all underlying folders.
39
+
40
+ The function `read_dropbox` can be used to read a Dropbox file's contents (bytes).
41
+
42
+ The function `write_dropbox` can be used to write contents (bytes) to a Dropbox file.
43
+
44
+ The functions `list_local`, `read_local` and `write_local` offer similar functionality for the local file system (on pyodide).
45
+
46
+ So, a way to access a file on the system's drive (mapped to Dropbox) as a local file is:
47
+
48
+ ```
49
+ contents = xlwings_utils.read_dropbox('/downloads/file1.xls')
50
+ xlwings_utils.write_local('file1.xlsx')
51
+ df = pandas.read_excel"file1.xlsx")
52
+ ...
53
+ ```
54
+ And the other direction:
55
+ ```
56
+ contents = xlwings_utils.read_local('file1.gif')
57
+ xlwings_utils.write_dropbox('/downloads/file1.gif')
58
+ ```
59
+
60
+ ## Block support
61
+
62
+ The module contains a useful 2-dimensional data structure: *block*.
63
+ This can be useful to manipulate a range without accessing the range directly, which is expensive in terms of memory and execution time.
64
+ 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.
65
+ So, `my_block(lol)[row, col]` is roughly equivalent to `lol[row-1][col-1]`
66
+
67
+ A block stores the values internally as a dictionary and will only convert these to a list of lists when using `block.value`.
68
+
69
+ 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
70
+
71
+ ```
72
+ my_block = xwu.block.from_value(range.value)
73
+ ```
74
+ The dimensions (number of rows and number of columns) are automatically set.
75
+
76
+ Setting of an individual item (one-based, like range) can be done like
77
+ ```
78
+ my_block[row, column] = x
79
+ ```
80
+ And, likewise, reading an individual item can be done like
81
+ ```
82
+ x = my_block[row, column]
83
+ ```
84
+ It is not allowed t,o read or write outside the block dimensions.
85
+
86
+ It is also possible to define an empty block, like
87
+ ```
88
+ block = xlwings_utils.block(number_of_rows, number_columns)
89
+ ```
90
+ The dimensions can be queried or redefined with `block.number_of_rows` and
91
+ `block.number_of_columns`.
92
+
93
+ To assign a block to range, use
94
+ ```
95
+ range.value = block.value
96
+ ```
97
+
98
+ The property `block.highest`_used_row_number returns the
99
+
100
+ The method `block.minimized()` returns a block that has the dimensions of (highest_used_row_number, highest_used_column_number).
101
+
102
+ ## Capture stdout support
103
+
104
+ The module has support for capturing stdout and -later- using showing the captured output on a sheet.
105
+
106
+ To do that:
107
+
108
+ ```makes it possible to capture stdout writes, which
109
+ with xwu.capture_stdout():
110
+ ...
111
+ ```
112
+ and then the captured output can be copied to the screen, like
113
+
114
+ ```can then be copied to a worksheet in a later stage.
115
+ sheet.range(4,5).value = xwu.captured_stdout_as_value()
116
+ ```
117
+
118
+ Clearing the captured stdout buffer can be done with `xwu.clear_captured_std_out`.
119
+
120
+ Normally, stdout will be also sent to the xlwings lite UI panel. This can be suppressed with
121
+
122
+ ```
123
+ with xwu.capture_stdout(include_print=False):
124
+ ...
125
+ ```
126
+
127
+ ## Badges
128
+
129
+ ![PyPI](https://img.shields.io/pypi/v/xlwings-utils) ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/xlwings-utils) ![PyPI - Implementation](https://img.shields.io/pypi/implementation/xlwings-utils)
130
+ ![PyPI - License](https://img.shields.io/pypi/l/xlwings-utils) ![ruff](https://img.shields.io/badge/style-ruff-41B5BE?style=flat)
131
+ ![GitHub last commit](https://img.shields.io/github/last-commit/salabim/peek)
132
+
@@ -0,0 +1,6 @@
1
+ xlwings_utils/__init__.py,sha256=FdaRztevSu5akGL7KBUBRzqwLMRTdvVUuS2Kfp2f1Uc,68
2
+ xlwings_utils/xlwings_utils.py,sha256=gCAgs3x6BjHaCX8I3HVw-s84EIS4pkrYQzCK38yG5T4,12656
3
+ xlwings_utils-0.0.7.dist-info/METADATA,sha256=js1Qw-rZuxDke_i-AjBl8P7mXZ7aMocqlJYj9Gfgyjg,4962
4
+ xlwings_utils-0.0.7.dist-info/WHEEL,sha256=0CuiUZ_p9E4cD6NyLD6UG80LBXYyiSYZOKDm5lp32xk,91
5
+ xlwings_utils-0.0.7.dist-info/top_level.txt,sha256=kf5SEv0gZiRObPhUoYcc1O_iX_wwTOPeUIYvzyYeAM4,14
6
+ xlwings_utils-0.0.7.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.3.0)
2
+ Generator: setuptools (80.3.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,43 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: xlwings_utils
3
- Version: 0.0.5
4
- Summary: xlwings_utils
5
- Author-email: Ruud van der Ham <rt.van.der.ham@gmail.com>
6
- Project-URL: Homepage, https://github.com/salabim/xlwings_utils
7
- Project-URL: Repository, https://github.com/salabim/xlwings_utils
8
- Classifier: Development Status :: 5 - Production/Stable
9
- Classifier: Programming Language :: Python :: 3 :: Only
10
- Requires-Python: >=3.9
11
- Description-Content-Type: text/markdown
12
- Requires-Dist: dropbox
13
-
14
- # xlwings_utils
15
-
16
- ## Introduction
17
-
18
- This module provides some useful functions to be used in xlwings lite.
19
- The xlwings lite system does not provide access to the local file system. With this
20
- module, files can be copied between dropbox and the pyodide file systen. And
21
- therefore, it is possible to indirectly use the local file system.
22
-
23
- On top of that, this module makes it possible to capture stdout writes, which
24
- can then be copied to a worksheet in a later stage.
25
-
26
- ## Installation
27
-
28
- Just add xlwings-utils to the requirement tab.
29
-
30
- ## Dropbox support
31
-
32
- xlwings_lite only works with full access dropbox apps.
33
-
34
- In order to use dropbox, is is necessary to initialize the module with credentials.
35
-
36
- ## Capture stdout support
37
-
38
- Badges
39
-
40
- ![PyPI](https://img.shields.io/pypi/v/xlwings-utils) ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/xlwings-utils) ![PyPI - Implementation](https://img.shields.io/pypi/implementation/xlwings-utils)
41
- ![PyPI - License](https://img.shields.io/pypi/l/xlwings-utils) ![ruff](https://img.shields.io/badge/style-ruff-41B5BE?style=flat)
42
- ![GitHub last commit](https://img.shields.io/github/last-commit/salabim/peek)
43
-
@@ -1,6 +0,0 @@
1
- xlwings_utils/__init__.py,sha256=FdaRztevSu5akGL7KBUBRzqwLMRTdvVUuS2Kfp2f1Uc,68
2
- xlwings_utils/xlwings_utils.py,sha256=RiDWmEsUQclIjB5fYofA9D4WRIp3qQLMFq5-vEkOJ4E,9508
3
- xlwings_utils-0.0.5.dist-info/METADATA,sha256=XiPrWlKI2a683dGVOa7FMBsYt5SqOhGe8cu7FTQDU1w,1662
4
- xlwings_utils-0.0.5.dist-info/WHEEL,sha256=GHB6lJx2juba1wDgXDNlMTyM13ckjBMKf-OnwgKOCtA,91
5
- xlwings_utils-0.0.5.dist-info/top_level.txt,sha256=kf5SEv0gZiRObPhUoYcc1O_iX_wwTOPeUIYvzyYeAM4,14
6
- xlwings_utils-0.0.5.dist-info/RECORD,,