edrive-anyshare 1.0.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.
- edrive_anyshare-1.0.0/MANIFEST.in +2 -0
- edrive_anyshare-1.0.0/PKG-INFO +395 -0
- edrive_anyshare-1.0.0/README.md +380 -0
- edrive_anyshare-1.0.0/edrive/__init__.py +69 -0
- edrive_anyshare-1.0.0/edrive/auth.py +243 -0
- edrive_anyshare-1.0.0/edrive/documents.py +374 -0
- edrive_anyshare-1.0.0/edrive/endpoints.py +53 -0
- edrive_anyshare-1.0.0/edrive/errors.py +49 -0
- edrive_anyshare-1.0.0/edrive/models.py +90 -0
- edrive_anyshare-1.0.0/edrive/sharing.py +147 -0
- edrive_anyshare-1.0.0/edrive/transport.py +189 -0
- edrive_anyshare-1.0.0/edrive_anyshare.egg-info/PKG-INFO +395 -0
- edrive_anyshare-1.0.0/edrive_anyshare.egg-info/SOURCES.txt +22 -0
- edrive_anyshare-1.0.0/edrive_anyshare.egg-info/dependency_links.txt +1 -0
- edrive_anyshare-1.0.0/edrive_anyshare.egg-info/top_level.txt +1 -0
- edrive_anyshare-1.0.0/pyproject.toml +26 -0
- edrive_anyshare-1.0.0/setup.cfg +4 -0
- edrive_anyshare-1.0.0/tests/__init__.py +1 -0
- edrive_anyshare-1.0.0/tests/support/__init__.py +1 -0
- edrive_anyshare-1.0.0/tests/support/fake_edrive.py +131 -0
- edrive_anyshare-1.0.0/tests/test_auth_transport.py +193 -0
- edrive_anyshare-1.0.0/tests/test_documents_sharing.py +301 -0
- edrive_anyshare-1.0.0/tests/test_live.py +34 -0
- edrive_anyshare-1.0.0/tests/test_public_api.py +88 -0
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: edrive-anyshare
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: AnyShare eDrive login, directory, upload, and sharing client
|
|
5
|
+
Project-URL: Documentation, https://developers.aishutech.com/napi/documents/307
|
|
6
|
+
Keywords: anyshare,edrive,file-sharing,enterprise-content-management
|
|
7
|
+
Classifier: Development Status :: 3 - Alpha
|
|
8
|
+
Classifier: Intended Audience :: Developers
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
11
|
+
Classifier: Topic :: Internet
|
|
12
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
13
|
+
Requires-Python: >=3.9
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
# AnyShare eDrive Python package
|
|
17
|
+
|
|
18
|
+
This is a tenant-neutral Python client for AnyShare and eDrive deployments.
|
|
19
|
+
The server URL, account, password, and optional browser-login public key are
|
|
20
|
+
provided by the application, so the package is not tied to one provider or
|
|
21
|
+
domain.
|
|
22
|
+
|
|
23
|
+
It supports:
|
|
24
|
+
|
|
25
|
+
- browser-style AnyShare login
|
|
26
|
+
- reusable cookie-based sessions
|
|
27
|
+
- owned document-library and directory operations
|
|
28
|
+
- remote path resolution and directory creation
|
|
29
|
+
- recursive folder uploads
|
|
30
|
+
- permanent anonymous share-link reuse or creation
|
|
31
|
+
- authenticated requests to other supported AnyShare endpoints
|
|
32
|
+
|
|
33
|
+
The package uses the browser-login and legacy document/share endpoint shapes
|
|
34
|
+
implemented by the reference client. AnyShare deployments can expose
|
|
35
|
+
different features or endpoint versions; confirm the API contract for the
|
|
36
|
+
target tenant before enabling write operations.
|
|
37
|
+
|
|
38
|
+
## Requirements
|
|
39
|
+
|
|
40
|
+
- Python 3.9 or newer
|
|
41
|
+
- curl
|
|
42
|
+
- openssl
|
|
43
|
+
|
|
44
|
+
There are no third-party runtime dependencies. HTTPS connections use the
|
|
45
|
+
system curl installation and its certificate store.
|
|
46
|
+
|
|
47
|
+
## Install
|
|
48
|
+
|
|
49
|
+
Install the published distribution:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
python -m pip install edrive-anyshare
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Install this source tree for development:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
cd /path/to/edrive
|
|
59
|
+
python -m pip install -e .
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
The import name is edrive and the distribution name is edrive-anyshare.
|
|
63
|
+
|
|
64
|
+
## Configuration
|
|
65
|
+
|
|
66
|
+
The library accepts credentials explicitly and does not require or load an
|
|
67
|
+
environment file. This avoids silently selecting credentials or a tenant in a
|
|
68
|
+
library process.
|
|
69
|
+
|
|
70
|
+
For a shell-based application:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
export EDRIVE_BASE_URL=https://anyshare.example.com
|
|
74
|
+
export EDRIVE_USERNAME=your-account
|
|
75
|
+
export EDRIVE_PASSWORD=your-password
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
An application may read these optional variables with its own configuration
|
|
79
|
+
system and pass them to login:
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
import os
|
|
83
|
+
|
|
84
|
+
from edrive import login
|
|
85
|
+
|
|
86
|
+
with login(
|
|
87
|
+
os.environ["EDRIVE_USERNAME"],
|
|
88
|
+
os.environ["EDRIVE_PASSWORD"],
|
|
89
|
+
os.environ["EDRIVE_BASE_URL"],
|
|
90
|
+
) as session:
|
|
91
|
+
print(session.username)
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
The base URL must be an absolute http or https URL. Trailing slashes are
|
|
95
|
+
removed automatically, and query strings or fragments are rejected.
|
|
96
|
+
|
|
97
|
+
## Quick start: upload a folder
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
from edrive import ONDUP_OVERWRITE, login, upload_folder
|
|
101
|
+
|
|
102
|
+
with login(
|
|
103
|
+
"your-account",
|
|
104
|
+
"your-password",
|
|
105
|
+
"https://anyshare.example.com",
|
|
106
|
+
) as session:
|
|
107
|
+
result = upload_folder(
|
|
108
|
+
session,
|
|
109
|
+
"/path/to/local/folder",
|
|
110
|
+
"Documents/Reports",
|
|
111
|
+
ondup=ONDUP_OVERWRITE,
|
|
112
|
+
create_share_link=True,
|
|
113
|
+
)
|
|
114
|
+
print(result.share_url)
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
The first component of a remote path is an owned document-library name.
|
|
118
|
+
Missing nested folders are created by default. Pass remote_docid instead of
|
|
119
|
+
remote_path to skip library and path lookup:
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
result = upload_folder(
|
|
123
|
+
session,
|
|
124
|
+
"/path/to/local/folder",
|
|
125
|
+
remote_docid="gns://library/folder-id",
|
|
126
|
+
create_share_link=False,
|
|
127
|
+
)
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
If both remote_path and remote_docid are supplied, remote_docid takes
|
|
131
|
+
precedence. The default duplicate policy is ONDUP_OVERWRITE; use
|
|
132
|
+
ONDUP_RENAME to keep both files.
|
|
133
|
+
|
|
134
|
+
## Complete demonstration
|
|
135
|
+
|
|
136
|
+
The following script does not use an environment file. It asks for the tenant
|
|
137
|
+
URL, account, and password, lists the account's document libraries, and asks
|
|
138
|
+
for confirmation before uploading a local folder:
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
from getpass import getpass
|
|
142
|
+
from pathlib import Path
|
|
143
|
+
|
|
144
|
+
from edrive import (
|
|
145
|
+
ONDUP_OVERWRITE,
|
|
146
|
+
list_owned_doc_libs,
|
|
147
|
+
login,
|
|
148
|
+
upload_folder,
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
base_url = input("AnyShare URL: ").strip()
|
|
152
|
+
username = input("Account: ").strip()
|
|
153
|
+
password = getpass("Password: ")
|
|
154
|
+
local_folder = Path(input("Local folder: ").strip()).expanduser()
|
|
155
|
+
remote_path = input("Remote path, for example Documents/Reports: ").strip()
|
|
156
|
+
|
|
157
|
+
with login(username, password, base_url) as session:
|
|
158
|
+
libraries = list_owned_doc_libs(session)
|
|
159
|
+
print("Owned document libraries:")
|
|
160
|
+
for library in libraries:
|
|
161
|
+
print(" -", library.get("name"), library.get("id"))
|
|
162
|
+
|
|
163
|
+
if input("Upload this folder? [y/N] ").strip().lower() == "y":
|
|
164
|
+
result = upload_folder(
|
|
165
|
+
session,
|
|
166
|
+
local_folder,
|
|
167
|
+
remote_path,
|
|
168
|
+
ondup=ONDUP_OVERWRITE,
|
|
169
|
+
create_share_link=True,
|
|
170
|
+
)
|
|
171
|
+
print("Uploaded files:", len(result.uploaded_files))
|
|
172
|
+
print("Share URL:", result.share_url or "(not created)")
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
For a read-only connection check, stop after list_owned_doc_libs. Uploading
|
|
176
|
+
and share-link creation are write operations and depend on tenant permissions.
|
|
177
|
+
|
|
178
|
+
## Authentication
|
|
179
|
+
|
|
180
|
+
### login
|
|
181
|
+
|
|
182
|
+
```python
|
|
183
|
+
login(
|
|
184
|
+
username,
|
|
185
|
+
password,
|
|
186
|
+
base_url,
|
|
187
|
+
*,
|
|
188
|
+
cookiejar=None,
|
|
189
|
+
login_public_key=None,
|
|
190
|
+
)
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Returns an EdriveSession. The session follows the AnyShare web login
|
|
194
|
+
redirects, submits the CSRF/challenge login payload, encrypts the password
|
|
195
|
+
with RSA through openssl, and reads the OAuth token from the cookie jar.
|
|
196
|
+
|
|
197
|
+
Use the session as a context manager. A temporary cookie jar is deleted when
|
|
198
|
+
the context closes. A caller-provided cookie jar is retained on disk, but the
|
|
199
|
+
session object clears its reference after close:
|
|
200
|
+
|
|
201
|
+
```python
|
|
202
|
+
with login(
|
|
203
|
+
"your-account",
|
|
204
|
+
"your-password",
|
|
205
|
+
"https://anyshare.example.com",
|
|
206
|
+
cookiejar="/tmp/anyshare.cookies",
|
|
207
|
+
) as session:
|
|
208
|
+
pass
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
The package exports LOGIN_PUBLIC_KEY as the bundled default public key:
|
|
212
|
+
|
|
213
|
+
```python
|
|
214
|
+
from edrive import LOGIN_PUBLIC_KEY
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
If a tenant uses a different browser-login key, provide its PEM contents:
|
|
218
|
+
|
|
219
|
+
```python
|
|
220
|
+
from pathlib import Path
|
|
221
|
+
|
|
222
|
+
from edrive import login
|
|
223
|
+
|
|
224
|
+
tenant_key = Path("/secure/path/tenant-login-public-key.pem").read_text()
|
|
225
|
+
|
|
226
|
+
with login(
|
|
227
|
+
"your-account",
|
|
228
|
+
"your-password",
|
|
229
|
+
"https://anyshare.example.com",
|
|
230
|
+
login_public_key=tenant_key,
|
|
231
|
+
) as session:
|
|
232
|
+
pass
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
The public key is not a password and does not need to be kept secret. Do not
|
|
236
|
+
commit private keys, passwords, cookie jars, or real credentials.
|
|
237
|
+
|
|
238
|
+
## Browse document libraries and folders
|
|
239
|
+
|
|
240
|
+
```python
|
|
241
|
+
from edrive import (
|
|
242
|
+
create_dir,
|
|
243
|
+
find_child_dir,
|
|
244
|
+
list_dir,
|
|
245
|
+
list_owned_doc_libs,
|
|
246
|
+
login,
|
|
247
|
+
resolve_docid_by_name,
|
|
248
|
+
resolve_folder_path,
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
with login("user", "password", "https://anyshare.example.com") as session:
|
|
252
|
+
libraries = list_owned_doc_libs(session)
|
|
253
|
+
library_id = resolve_docid_by_name(session, "Documents")
|
|
254
|
+
|
|
255
|
+
listing = list_dir(session, library_id)
|
|
256
|
+
print(listing.get("dirs", []))
|
|
257
|
+
print(listing.get("files", []))
|
|
258
|
+
|
|
259
|
+
reports = find_child_dir(session, library_id, "Reports")
|
|
260
|
+
if reports is None:
|
|
261
|
+
reports = create_dir(session, library_id, "Reports")
|
|
262
|
+
|
|
263
|
+
folder_id, parent_id, created = resolve_folder_path(
|
|
264
|
+
session,
|
|
265
|
+
"Documents/Reports/2026",
|
|
266
|
+
create=True,
|
|
267
|
+
)
|
|
268
|
+
print(folder_id, parent_id, created)
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
Directory and library responses are returned as dictionaries containing the
|
|
272
|
+
fields supplied by the tenant. Document IDs may be ordinary IDs or AnyShare
|
|
273
|
+
URI-style values.
|
|
274
|
+
|
|
275
|
+
## Upload results
|
|
276
|
+
|
|
277
|
+
upload_folder returns UploadResult:
|
|
278
|
+
|
|
279
|
+
```python
|
|
280
|
+
result.share_url
|
|
281
|
+
result.share_id
|
|
282
|
+
result.share_link_created
|
|
283
|
+
result.local_path
|
|
284
|
+
result.remote_path
|
|
285
|
+
result.remote_folder_name
|
|
286
|
+
result.remote_folder_docid
|
|
287
|
+
result.remote_parent_docid
|
|
288
|
+
result.uploaded_files
|
|
289
|
+
result.created_dirs
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
Use result.to_dict() when the result must be serialized as JSON:
|
|
293
|
+
|
|
294
|
+
```python
|
|
295
|
+
payload = result.to_dict()
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
The upload process uses the AnyShare begin-upload, direct multipart upload,
|
|
299
|
+
and end-upload sequence. If an upload fails after a remote folder has been
|
|
300
|
+
created, the client does not delete that remote content automatically.
|
|
301
|
+
|
|
302
|
+
## Share links
|
|
303
|
+
|
|
304
|
+
```python
|
|
305
|
+
from edrive import (
|
|
306
|
+
create_anonymous_share_link,
|
|
307
|
+
get_or_create_permanent_share_link,
|
|
308
|
+
list_share_links,
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
links = list_share_links(session, "gns://library/folder-id")
|
|
312
|
+
|
|
313
|
+
link_id = create_anonymous_share_link(
|
|
314
|
+
session,
|
|
315
|
+
"gns://library/folder-id",
|
|
316
|
+
title="Reports",
|
|
317
|
+
allow=["display", "preview", "download"],
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
share = get_or_create_permanent_share_link(
|
|
321
|
+
session,
|
|
322
|
+
"gns://library/folder-id",
|
|
323
|
+
title="Reports",
|
|
324
|
+
)
|
|
325
|
+
print(share["url"], share["created"])
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
The helper reuses a permanent link when one already exists. A tenant may
|
|
329
|
+
return HTTP 202 when link creation requires approval; this is reported as a
|
|
330
|
+
typed error.
|
|
331
|
+
|
|
332
|
+
## Authenticated API requests
|
|
333
|
+
|
|
334
|
+
Use api_request for an endpoint not wrapped by a convenience function:
|
|
335
|
+
|
|
336
|
+
```python
|
|
337
|
+
from edrive import api_request
|
|
338
|
+
|
|
339
|
+
status, data = api_request(
|
|
340
|
+
session,
|
|
341
|
+
"GET",
|
|
342
|
+
"/api/efast/v1/owned-doc-lib",
|
|
343
|
+
)
|
|
344
|
+
if status == 200:
|
|
345
|
+
print(data)
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
The function:
|
|
349
|
+
|
|
350
|
+
- accepts relative paths or absolute http(s) URLs
|
|
351
|
+
- adds the session Bearer token when present
|
|
352
|
+
- encodes json_body as JSON
|
|
353
|
+
- returns (status, decoded_data)
|
|
354
|
+
- returns text when the response is not JSON
|
|
355
|
+
- returns None for an empty response
|
|
356
|
+
|
|
357
|
+
The exported default endpoint prefixes are API_PREFIX and
|
|
358
|
+
SHARE_LINK_PREFIX. They are kept in one module so tenant-specific endpoint
|
|
359
|
+
adaptations can be made without changing the public operation signatures.
|
|
360
|
+
|
|
361
|
+
## Errors
|
|
362
|
+
|
|
363
|
+
All client-specific errors inherit from EdriveError, which inherits from
|
|
364
|
+
RuntimeError:
|
|
365
|
+
|
|
366
|
+
```python
|
|
367
|
+
from edrive import (
|
|
368
|
+
EdriveAuthenticationError,
|
|
369
|
+
EdriveError,
|
|
370
|
+
EdriveHTTPError,
|
|
371
|
+
EdriveProtocolError,
|
|
372
|
+
EdriveTransportError,
|
|
373
|
+
EdriveUploadError,
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
try:
|
|
377
|
+
with login("user", "password", "https://anyshare.example.com") as session:
|
|
378
|
+
pass
|
|
379
|
+
except EdriveAuthenticationError:
|
|
380
|
+
print("The tenant rejected the login.")
|
|
381
|
+
except EdriveTransportError:
|
|
382
|
+
print("curl or the network was unavailable.")
|
|
383
|
+
except EdriveHTTPError:
|
|
384
|
+
print("AnyShare returned an unsuccessful status.")
|
|
385
|
+
except EdriveProtocolError:
|
|
386
|
+
print("The tenant response did not match the expected shape.")
|
|
387
|
+
except EdriveUploadError:
|
|
388
|
+
print("The direct upload failed.")
|
|
389
|
+
except EdriveError:
|
|
390
|
+
print("Another eDrive client error occurred.")
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
Passwords, access tokens, authorization values, cookies, and common login
|
|
394
|
+
fields are redacted from client-generated error messages. The library does
|
|
395
|
+
not configure application logging.
|