accsyn-python-api 3.0.3__py3-none-any.whl → 3.1.0__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.
- accsyn_api/_version.py +1 -1
- accsyn_api/session.py +101 -170
- accsyn_python_api-3.1.0.dist-info/METADATA +115 -0
- accsyn_python_api-3.1.0.dist-info/RECORD +6 -0
- {accsyn_python_api-3.0.3.dist-info → accsyn_python_api-3.1.0.dist-info}/WHEEL +1 -2
- accsyn_python_api-3.0.3.dist-info/METADATA +0 -57
- accsyn_python_api-3.0.3.dist-info/RECORD +0 -7
- accsyn_python_api-3.0.3.dist-info/top_level.txt +0 -1
accsyn_api/_version.py
CHANGED
accsyn_api/session.py
CHANGED
|
@@ -14,7 +14,7 @@ import uuid
|
|
|
14
14
|
import hashlib
|
|
15
15
|
import copy
|
|
16
16
|
|
|
17
|
-
import urllib
|
|
17
|
+
import urllib.parse
|
|
18
18
|
import base64
|
|
19
19
|
import io
|
|
20
20
|
import gzip
|
|
@@ -25,13 +25,6 @@ import requests
|
|
|
25
25
|
|
|
26
26
|
from ._version import __version__
|
|
27
27
|
|
|
28
|
-
if sys.version_info.major >= 3:
|
|
29
|
-
import io
|
|
30
|
-
|
|
31
|
-
else:
|
|
32
|
-
# Python 2 backward compability
|
|
33
|
-
import binascii
|
|
34
|
-
|
|
35
28
|
try:
|
|
36
29
|
requests.packages.urllib3.disable_warnings()
|
|
37
30
|
except BaseException:
|
|
@@ -44,7 +37,7 @@ logging.basicConfig(
|
|
|
44
37
|
)
|
|
45
38
|
|
|
46
39
|
ACCSYN_BACKEND_DOMAIN = "accsyn.com"
|
|
47
|
-
ACCSYN_BACKEND_MASTER_HOSTNAME = "master.{}"
|
|
40
|
+
ACCSYN_BACKEND_MASTER_HOSTNAME = f"master.{ACCSYN_BACKEND_DOMAIN}"
|
|
48
41
|
ACCSYN_PORT = 443
|
|
49
42
|
DEFAULT_EVENT_PAYLOAD_COMPRESS_SIZE_TRESHOLD = 100 * 1024 # Compress event data payloads above 100k
|
|
50
43
|
|
|
@@ -52,7 +45,7 @@ CLEARANCE_SUPPORT = "support"
|
|
|
52
45
|
CLEARANCE_ADMIN = "admin"
|
|
53
46
|
CLEARANCE_EMPLOYEE = "employee"
|
|
54
47
|
CLEARANCE_STANDARD = "standard"
|
|
55
|
-
CLEARANCE_CLIENT = CLEARANCE_STANDARD
|
|
48
|
+
CLEARANCE_CLIENT = CLEARANCE_STANDARD # BWCOMP
|
|
56
49
|
CLEARANCE_NONE = "none"
|
|
57
50
|
|
|
58
51
|
CLIENT_TYPE_APP = 0
|
|
@@ -68,13 +61,14 @@ CLIENT_STATE_OFFLINE = "offline"
|
|
|
68
61
|
CLIENT_STATE_DISABLED = "disabled"
|
|
69
62
|
CLIENT_STATE_DISABLED_OFFLINE = "disabled-offline"
|
|
70
63
|
|
|
64
|
+
|
|
71
65
|
class JSONEncoder(json.JSONEncoder):
|
|
72
66
|
"""JSON serialiser."""
|
|
73
67
|
|
|
74
68
|
def default(self, obj):
|
|
75
69
|
if isinstance(obj, datetime.date) or isinstance(obj, datetime.datetime):
|
|
76
70
|
return obj.strftime("%Y-%m-%dT%H:%M:%S")
|
|
77
|
-
return super(
|
|
71
|
+
return super().default(obj)
|
|
78
72
|
|
|
79
73
|
|
|
80
74
|
class JSONDecoder(json.JSONDecoder):
|
|
@@ -145,7 +139,7 @@ class Session(object):
|
|
|
145
139
|
|
|
146
140
|
def __init__(
|
|
147
141
|
self,
|
|
148
|
-
|
|
142
|
+
workspace=None,
|
|
149
143
|
username=None,
|
|
150
144
|
api_key=None,
|
|
151
145
|
hostname=None,
|
|
@@ -153,26 +147,29 @@ class Session(object):
|
|
|
153
147
|
proxy=None,
|
|
154
148
|
verbose=False,
|
|
155
149
|
pretty_json=False,
|
|
156
|
-
dev=False,
|
|
157
150
|
path_logfile=None,
|
|
158
151
|
timeout=None,
|
|
159
152
|
connect_timeout=None,
|
|
153
|
+
domain=None,
|
|
160
154
|
):
|
|
161
155
|
"""
|
|
162
156
|
Initiate a new API session object. Throws exception upon authentication failure.
|
|
163
157
|
|
|
164
|
-
:param
|
|
158
|
+
:param workspace: The accsyn workspace code (or read from ACCSYN_WORKSPACE environment variable)
|
|
165
159
|
:param username: The accsyn username (or read from ACCSYN_API_USER environment variable)
|
|
166
160
|
:param api_key: The secret API key for authentication (or read from ACCSYN_API_KEY environment variable)
|
|
167
|
-
:param hostname: Override hostname/IP to connect to.
|
|
168
|
-
:param port: Override default port 443
|
|
161
|
+
:param hostname: Override the hostname/IP of the workspace to connect to.
|
|
162
|
+
:param port: Override default port (443/TCP)
|
|
169
163
|
:param proxy: The proxy settings (or read from ACCSYN_PROXY environment variable).
|
|
170
|
-
:param verbose:
|
|
164
|
+
:param verbose: Printing verbose debugging output to stdout.
|
|
171
165
|
:param pretty_json: (verbose) Print pretty formatted JSON.
|
|
172
|
-
:param dev: Dev mode.
|
|
173
166
|
:param path_logfile: Output all log messages to this logfile instead of stdout.
|
|
174
167
|
:param timeout: Timeout in seconds for API calls - waiting for response.
|
|
175
168
|
:param connect_timeout: Timeout in seconds for API calls - waiting for connection.
|
|
169
|
+
:param domain: (Backward compatibility) The accsyn domain (or read from ACCSYN_DOMAIN environment variable)
|
|
170
|
+
|
|
171
|
+
.. deprecated:: 3.1.0
|
|
172
|
+
Use the :param workspace: parameter instead
|
|
176
173
|
"""
|
|
177
174
|
# Generate a session ID
|
|
178
175
|
self.__version__ = __version__
|
|
@@ -182,25 +179,22 @@ class Session(object):
|
|
|
182
179
|
self._be_verbose = verbose
|
|
183
180
|
self._pretty_json = pretty_json
|
|
184
181
|
self._proxy = proxy
|
|
185
|
-
self._dev =
|
|
182
|
+
self._dev = os.environ.get('AS_DEV', 'false') in ['true', '1']
|
|
186
183
|
Session._p_logfile = path_logfile
|
|
187
184
|
self._role = CLEARANCE_NONE
|
|
188
|
-
self._verbose("Creating accsyn Python API session (v{})"
|
|
185
|
+
self._verbose(f"Creating accsyn Python API session (v{__version__})")
|
|
189
186
|
for key in os.environ:
|
|
190
187
|
if key.startswith("FILMHUB_"):
|
|
191
|
-
Session._warning('
|
|
192
|
-
if not
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
raise AccsynException(
|
|
202
|
-
"Please supply your accsyn domain/workspace or set " "ACCSYN_DOMAIN environment!"
|
|
203
|
-
)
|
|
188
|
+
Session._warning(f'Detected deprecated FilmHUB environment variable "{key}", please migrate!')
|
|
189
|
+
if not workspace:
|
|
190
|
+
workspace = domain
|
|
191
|
+
if not workspace:
|
|
192
|
+
for key in ["ACCSYN_WORKSPACE", "ACCSYN_DOMAIN", "ACCSYN_ORG"]:
|
|
193
|
+
if key in os.environ:
|
|
194
|
+
workspace = os.environ[key]
|
|
195
|
+
break
|
|
196
|
+
if not workspace:
|
|
197
|
+
raise AccsynException("Please supply your accsyn workspace domain or set ACCSYN_WORKSPACE environment!")
|
|
204
198
|
if not username:
|
|
205
199
|
username = os.environ.get("ACCSYN_API_USER")
|
|
206
200
|
if not username:
|
|
@@ -220,24 +214,26 @@ class Session(object):
|
|
|
220
214
|
if self._dev:
|
|
221
215
|
self._hostname = "127.0.0.1"
|
|
222
216
|
else:
|
|
223
|
-
#
|
|
217
|
+
# Resolve workspace hostname
|
|
224
218
|
response = self._rest(
|
|
225
219
|
"GET",
|
|
226
220
|
ACCSYN_BACKEND_MASTER_HOSTNAME,
|
|
227
221
|
"J3PKTtDvolDMBtTy6AFGA",
|
|
228
|
-
|
|
222
|
+
dict(ident=workspace),
|
|
229
223
|
)
|
|
230
224
|
# Store hostname
|
|
231
225
|
if "message" in response:
|
|
232
226
|
raise AccsynException(response["message"])
|
|
233
|
-
result = response.get('result',
|
|
234
|
-
assert "hostname" in result, "No API endpoint hostname were provided for
|
|
227
|
+
result = response.get('result', dict())
|
|
228
|
+
assert "hostname" in result, "No API endpoint hostname were provided for workspace {}!".format(
|
|
229
|
+
workspace
|
|
230
|
+
)
|
|
235
231
|
self._hostname = result["hostname"]
|
|
236
232
|
if self._port is None:
|
|
237
233
|
self._port = result["port"]
|
|
238
234
|
if self._port is None:
|
|
239
235
|
self._port = ACCSYN_PORT if not self._dev else 8181
|
|
240
|
-
self.
|
|
236
|
+
self._workspace = workspace
|
|
241
237
|
self._username = username
|
|
242
238
|
self._api_key = api_key
|
|
243
239
|
self._last_message = None
|
|
@@ -294,11 +290,8 @@ class Session(object):
|
|
|
294
290
|
session_id=self._session_id,
|
|
295
291
|
)
|
|
296
292
|
headers = {
|
|
297
|
-
"Authorization": "basic {}:{}"
|
|
298
|
-
|
|
299
|
-
Session._base64_encode(self._api_key),
|
|
300
|
-
),
|
|
301
|
-
"X-Accsyn-Workspace": self._domain,
|
|
293
|
+
"Authorization": f"basic {Session._base64_encode(self._username)}:{Session._base64_encode(self._api_key)}",
|
|
294
|
+
"X-Accsyn-Workspace": self._workspace,
|
|
302
295
|
}
|
|
303
296
|
response = self._rest(
|
|
304
297
|
"PUT",
|
|
@@ -344,17 +337,17 @@ class Session(object):
|
|
|
344
337
|
)
|
|
345
338
|
else:
|
|
346
339
|
if isinstance(data, list):
|
|
347
|
-
data =
|
|
340
|
+
data = dict(tasks=data)
|
|
348
341
|
assert data is not None and 0 < len(data), "Empty create data submitted!"
|
|
349
342
|
if entitytype == "queue":
|
|
350
343
|
data["type"] = 2
|
|
351
344
|
elif entitytype == "task" and "tasks" not in data:
|
|
352
|
-
data =
|
|
345
|
+
data = dict(tasks=data)
|
|
353
346
|
if entitytype in ["job", "task"]:
|
|
354
347
|
data["allow_duplicates"] = allow_duplicates
|
|
355
348
|
d = self._event(
|
|
356
349
|
"POST",
|
|
357
|
-
"
|
|
350
|
+
f"{Session._get_base_uri(entitytype)}/create",
|
|
358
351
|
data,
|
|
359
352
|
query=entitytype_id,
|
|
360
353
|
)
|
|
@@ -399,11 +392,11 @@ class Session(object):
|
|
|
399
392
|
|
|
400
393
|
retval = None
|
|
401
394
|
d = self.decode_query(query)
|
|
402
|
-
data =
|
|
395
|
+
data = dict()
|
|
403
396
|
if d["entitytype"] == "entitytypes":
|
|
404
397
|
# Ask cloud server, the Python API is rarely updated and should not
|
|
405
398
|
# need to know
|
|
406
|
-
d = self._event("GET", "entitytypes",
|
|
399
|
+
d = self._event("GET", "entitytypes", dict())
|
|
407
400
|
if d:
|
|
408
401
|
retval = d["result"]
|
|
409
402
|
elif d["entitytype"] == "attributes":
|
|
@@ -419,7 +412,7 @@ class Session(object):
|
|
|
419
412
|
d = self._event(
|
|
420
413
|
"GET",
|
|
421
414
|
"attributes",
|
|
422
|
-
|
|
415
|
+
dict(entitytype=entitytype, create=create, update=update),
|
|
423
416
|
)
|
|
424
417
|
if d:
|
|
425
418
|
retval = d["result"]
|
|
@@ -427,9 +420,9 @@ class Session(object):
|
|
|
427
420
|
# Send query to server, first determine uri
|
|
428
421
|
uri_base = Session._get_base_uri(d["entitytype"])
|
|
429
422
|
if d["entitytype"] == "queue":
|
|
430
|
-
data =
|
|
423
|
+
data = dict(type=2)
|
|
431
424
|
elif d["entitytype"] == "job":
|
|
432
|
-
data =
|
|
425
|
+
data = dict(type=1)
|
|
433
426
|
if finished is not None:
|
|
434
427
|
data["finished"] = finished
|
|
435
428
|
if offline is not None:
|
|
@@ -442,7 +435,7 @@ class Session(object):
|
|
|
442
435
|
data["skip"] = skip
|
|
443
436
|
if attributes:
|
|
444
437
|
data["attributes"] = attributes
|
|
445
|
-
d = self._event("GET", "{}/find"
|
|
438
|
+
d = self._event("GET", f"{uri_base}/find", data, query=d.get("expression"))
|
|
446
439
|
if d:
|
|
447
440
|
retval = d["result"]
|
|
448
441
|
return retval
|
|
@@ -491,8 +484,8 @@ class Session(object):
|
|
|
491
484
|
d = self.decode_query(query)
|
|
492
485
|
# Send query to server, first determine uri
|
|
493
486
|
uri_base = Session._get_base_uri(d["entitytype"])
|
|
494
|
-
data =
|
|
495
|
-
d = self._event("GET", "
|
|
487
|
+
data = dict()
|
|
488
|
+
d = self._event("GET", f"{uri_base}/report", data, query=d.get("expression"))
|
|
496
489
|
return d["report"]
|
|
497
490
|
|
|
498
491
|
def metrics(self, query, attributes=["speed"], time=None):
|
|
@@ -514,7 +507,7 @@ class Session(object):
|
|
|
514
507
|
}
|
|
515
508
|
if not time is None:
|
|
516
509
|
data["time"] = time
|
|
517
|
-
d = self._event("GET", "
|
|
510
|
+
d = self._event("GET", f"{uri_base}/metrics", data, query=d.get("expression"))
|
|
518
511
|
return d["result"]
|
|
519
512
|
|
|
520
513
|
# Update an entity
|
|
@@ -538,7 +531,7 @@ class Session(object):
|
|
|
538
531
|
assert 0 < len(data or {}) and isinstance(data, dict), "Invalid data supplied, must be dict and have content!"
|
|
539
532
|
response = self._event(
|
|
540
533
|
"PUT",
|
|
541
|
-
"
|
|
534
|
+
f"{Session._get_base_uri(entitytype)}/edit",
|
|
542
535
|
data,
|
|
543
536
|
entityid=entityid,
|
|
544
537
|
)
|
|
@@ -555,7 +548,7 @@ class Session(object):
|
|
|
555
548
|
:return: The updated entity data, as dictionary
|
|
556
549
|
|
|
557
550
|
.. deprecated:: 2.0.2
|
|
558
|
-
Since
|
|
551
|
+
Since version 2.0.2 you should use the :func:`update` function instead
|
|
559
552
|
|
|
560
553
|
'''
|
|
561
554
|
return self.update(entitytype, entityid, data)
|
|
@@ -570,7 +563,7 @@ class Session(object):
|
|
|
570
563
|
:return: The updated sub entities (tasks), as dictionaries.
|
|
571
564
|
|
|
572
565
|
.. deprecated:: 2.0.2
|
|
573
|
-
Since
|
|
566
|
+
Since version 2.0.2 you should use the :func:`update` function instead
|
|
574
567
|
"""
|
|
575
568
|
assert 0 < len(entitytype or "") and Session._is_str(
|
|
576
569
|
entitytype
|
|
@@ -584,7 +577,7 @@ class Session(object):
|
|
|
584
577
|
assert 0 < len(data or []) and isinstance(data, list), "Invalid data supplied, must be a list!"
|
|
585
578
|
response = self._event(
|
|
586
579
|
"PUT",
|
|
587
|
-
"
|
|
580
|
+
f"{Session._get_base_uri(entitytype)}/edit",
|
|
588
581
|
data,
|
|
589
582
|
entityid=entityid,
|
|
590
583
|
)
|
|
@@ -631,8 +624,8 @@ class Session(object):
|
|
|
631
624
|
raise Exception("Please supply type of server assignment (main " "or site) in assignment data!")
|
|
632
625
|
response = self._event(
|
|
633
626
|
"PUT",
|
|
634
|
-
"
|
|
635
|
-
|
|
627
|
+
f"{Session._get_base_uri(entitytype_parent)}/edit",
|
|
628
|
+
dict([(what, client_id)]),
|
|
636
629
|
entityid=share_id,
|
|
637
630
|
)
|
|
638
631
|
if response:
|
|
@@ -655,7 +648,7 @@ class Session(object):
|
|
|
655
648
|
if entitytype.lower() == "share":
|
|
656
649
|
response = self._event(
|
|
657
650
|
"GET",
|
|
658
|
-
"
|
|
651
|
+
f"{Session._get_base_uri(entitytype)}/servers",
|
|
659
652
|
{},
|
|
660
653
|
entityid=entityid,
|
|
661
654
|
)
|
|
@@ -701,8 +694,8 @@ class Session(object):
|
|
|
701
694
|
raise Exception("Please supply type of server assignment (main " "or site) in assignment data!")
|
|
702
695
|
response = self._event(
|
|
703
696
|
"PUT",
|
|
704
|
-
"
|
|
705
|
-
|
|
697
|
+
f"{Session._get_base_uri(entitytype_parent)}/edit",
|
|
698
|
+
dict([(f"{what}_clear", client_id)]),
|
|
706
699
|
entityid=share_id,
|
|
707
700
|
)
|
|
708
701
|
if response:
|
|
@@ -731,7 +724,7 @@ class Session(object):
|
|
|
731
724
|
), "Invalid entity ID supplied, must be of string type!"
|
|
732
725
|
response = self._event(
|
|
733
726
|
"DELETE",
|
|
734
|
-
"
|
|
727
|
+
f"{Session._get_base_uri(entitytype)}/offline",
|
|
735
728
|
{},
|
|
736
729
|
entityid=entityid,
|
|
737
730
|
)
|
|
@@ -755,7 +748,7 @@ class Session(object):
|
|
|
755
748
|
), "Invalid entity ID supplied, must be of string type!"
|
|
756
749
|
response = self._event(
|
|
757
750
|
"DELETE",
|
|
758
|
-
"
|
|
751
|
+
f"{Session._get_base_uri(entitytype)}/delete",
|
|
759
752
|
{},
|
|
760
753
|
entityid=entityid,
|
|
761
754
|
)
|
|
@@ -977,7 +970,7 @@ class Session(object):
|
|
|
977
970
|
result += d["size"]
|
|
978
971
|
return result
|
|
979
972
|
|
|
980
|
-
event_data =
|
|
973
|
+
event_data = dict(files=data, size=recursive_get_size(data))
|
|
981
974
|
response = self._event("PUT", "workspace/publish/preprocess", event_data)
|
|
982
975
|
return response["result"]
|
|
983
976
|
|
|
@@ -985,7 +978,7 @@ class Session(object):
|
|
|
985
978
|
|
|
986
979
|
def get_setting(self, name=None, scope='workspace', entity_id=None, integration=None, data=None):
|
|
987
980
|
'''Retrive *name* setting for the given *scope* (workspace, job, share..), for optional *entity_id* or *integration* (ftrack,..)'''
|
|
988
|
-
evt_data =
|
|
981
|
+
evt_data = dict(scope=scope, name=name)
|
|
989
982
|
if entity_id:
|
|
990
983
|
evt_data['ident'] = entity_id
|
|
991
984
|
if integration:
|
|
@@ -997,7 +990,7 @@ class Session(object):
|
|
|
997
990
|
|
|
998
991
|
def set_setting(self, name, value, scope='workspace', entity_id=None, integration=None, data=None):
|
|
999
992
|
'''Set the setting identified by *name* to *value* for *entity_id* within *scope*.'''
|
|
1000
|
-
evt_data =
|
|
993
|
+
evt_data = dict(scope=scope, name=name, value=value)
|
|
1001
994
|
if entity_id:
|
|
1002
995
|
evt_data['ident'] = entity_id
|
|
1003
996
|
if integration:
|
|
@@ -1010,10 +1003,10 @@ class Session(object):
|
|
|
1010
1003
|
# Misc
|
|
1011
1004
|
def get_api_key(self):
|
|
1012
1005
|
"""Fetch API key, by default disabled in backend."""
|
|
1013
|
-
return self._event("GET", "user/api_key",
|
|
1006
|
+
return self._event("GET", "user/api_key", dict())["api_key"]
|
|
1014
1007
|
|
|
1015
1008
|
def gui_is_running(self):
|
|
1016
|
-
"""
|
|
1009
|
+
"""Backward compability"""
|
|
1017
1010
|
return self.app_is_running()
|
|
1018
1011
|
|
|
1019
1012
|
def app_is_running(self):
|
|
@@ -1028,7 +1021,7 @@ class Session(object):
|
|
|
1028
1021
|
"GET",
|
|
1029
1022
|
"client/find",
|
|
1030
1023
|
{},
|
|
1031
|
-
query="user={
|
|
1024
|
+
query=f"user={self._uid} AND code={Session.get_hostname()} AND type={CLIENT_TYPE_APP}",
|
|
1032
1025
|
)["result"]
|
|
1033
1026
|
retval = None
|
|
1034
1027
|
if 0 < len(result):
|
|
@@ -1039,7 +1032,7 @@ class Session(object):
|
|
|
1039
1032
|
return retval
|
|
1040
1033
|
|
|
1041
1034
|
def server_is_running(self):
|
|
1042
|
-
"""
|
|
1035
|
+
"""Backward compatibility"""
|
|
1043
1036
|
return self.daemon_is_running()
|
|
1044
1037
|
|
|
1045
1038
|
def daemon_is_running(self):
|
|
@@ -1054,8 +1047,7 @@ class Session(object):
|
|
|
1054
1047
|
"GET",
|
|
1055
1048
|
"client/find",
|
|
1056
1049
|
{},
|
|
1057
|
-
query="user={
|
|
1058
|
-
CLIENT_TYPE_SERVER, CLIENT_TYPE_USERSERVER),
|
|
1050
|
+
query=f"user={self._uid} AND code={Session.get_hostname()} AND (type={CLIENT_TYPE_SERVER} OR type={CLIENT_TYPE_USERSERVER})",
|
|
1059
1051
|
)["result"]
|
|
1060
1052
|
retval = None
|
|
1061
1053
|
if 0 < len(result):
|
|
@@ -1071,7 +1063,7 @@ class Session(object):
|
|
|
1071
1063
|
assert len(operation) > 0, 'No operation provided'
|
|
1072
1064
|
return self._event(
|
|
1073
1065
|
"PUT",
|
|
1074
|
-
"workspace/integration/{}/utility"
|
|
1066
|
+
f"workspace/integration/{name}/utility",
|
|
1075
1067
|
{
|
|
1076
1068
|
'operation': operation,
|
|
1077
1069
|
'data': data,
|
|
@@ -1134,7 +1126,7 @@ class Session(object):
|
|
|
1134
1126
|
if port is None:
|
|
1135
1127
|
port = self._port or ACCSYN_PORT
|
|
1136
1128
|
if hostname is None:
|
|
1137
|
-
hostname = "{}.{}"
|
|
1129
|
+
hostname = f"{self._workspace}.{ACCSYN_BACKEND_DOMAIN}"
|
|
1138
1130
|
# Proxy set?
|
|
1139
1131
|
proxy_type = None
|
|
1140
1132
|
proxy_hostname = None
|
|
@@ -1162,12 +1154,12 @@ class Session(object):
|
|
|
1162
1154
|
if proxy_type == "accsyn":
|
|
1163
1155
|
if proxy_port == -1:
|
|
1164
1156
|
proxy_port = 80
|
|
1165
|
-
self._verbose("Using accsyn proxy @ {}:{}"
|
|
1157
|
+
self._verbose(f"Using accsyn proxy @ {proxy_hostname}:{proxy_port}")
|
|
1166
1158
|
hostname = proxy_hostname
|
|
1167
1159
|
port = proxy_port
|
|
1168
1160
|
elif proxy_type in ["socks", "socks5"]:
|
|
1169
1161
|
try:
|
|
1170
|
-
self._verbose("Using SOCKS5 proxy @ {}:{}"
|
|
1162
|
+
self._verbose(f"Using SOCKS5 proxy @ {proxy_hostname}:{proxy_port}")
|
|
1171
1163
|
import socks
|
|
1172
1164
|
|
|
1173
1165
|
socks.setdefaultproxy(socks.PROXY_TYPE_SOCKS5, proxy_hostname, proxy_port)
|
|
@@ -1176,17 +1168,12 @@ class Session(object):
|
|
|
1176
1168
|
Session._warning('Python lacks SOCKS support, please install "pysocks" and' " try again...")
|
|
1177
1169
|
raise ie
|
|
1178
1170
|
elif proxy_type is not None:
|
|
1179
|
-
raise AccsynException('Unknown proxy type "{}"!'
|
|
1180
|
-
url = "http{}://{}:{}/api/v3{}"
|
|
1181
|
-
"s" if ssl else "",
|
|
1182
|
-
hostname,
|
|
1183
|
-
port,
|
|
1184
|
-
("/" if not uri.startswith("/") else "") + uri,
|
|
1185
|
-
)
|
|
1171
|
+
raise AccsynException(f'Unknown proxy type "{proxy_type}"!')
|
|
1172
|
+
url = f"http{'s' if ssl else ''}://{hostname}:{port}/api/v3{('/' if not uri.startswith('/') else '') + uri}"
|
|
1186
1173
|
if timeout is None:
|
|
1187
1174
|
timeout = self.timeout
|
|
1188
1175
|
if data is None:
|
|
1189
|
-
data =
|
|
1176
|
+
data = dict()
|
|
1190
1177
|
# Wait 10s to reach machine, 2min for it to send back data
|
|
1191
1178
|
CONNECT_TO, READ_TO = (self.connect_timeout, timeout)
|
|
1192
1179
|
r = None
|
|
@@ -1197,31 +1184,15 @@ class Session(object):
|
|
|
1197
1184
|
headers_effective = copy.deepcopy(headers)
|
|
1198
1185
|
elif self._api_key:
|
|
1199
1186
|
headers_effective = {
|
|
1200
|
-
"Authorization": "basic {}:{}"
|
|
1201
|
-
|
|
1202
|
-
Session._base64_encode(self._api_key)
|
|
1203
|
-
),
|
|
1204
|
-
"X-Accsyn-Workspace": self._domain,
|
|
1187
|
+
"Authorization": f"basic {Session._base64_encode(self._username)}:{Session._base64_encode(self._api_key)}",
|
|
1188
|
+
"X-Accsyn-Workspace": self._workspace,
|
|
1205
1189
|
}
|
|
1206
|
-
headers_effective["X-Accsyn-Device"] =
|
|
1207
|
-
__version__
|
|
1208
|
-
sys.platform,
|
|
1209
|
-
Session.get_hostname(),
|
|
1210
|
-
os.name,
|
|
1190
|
+
headers_effective["X-Accsyn-Device"] = (
|
|
1191
|
+
f"PythonAPI v{__version__} @ {sys.platform} {Session.get_hostname()}({os.name})"
|
|
1211
1192
|
)
|
|
1212
|
-
|
|
1213
|
-
t_start = int(round(time.time() * 1000))
|
|
1214
|
-
else:
|
|
1215
|
-
t_start = long(round(time.time() * 1000))
|
|
1193
|
+
t_start = int(round(time.time() * 1000))
|
|
1216
1194
|
try:
|
|
1217
|
-
self._verbose(
|
|
1218
|
-
"REST %s %s, data: %s"
|
|
1219
|
-
% (
|
|
1220
|
-
method,
|
|
1221
|
-
url,
|
|
1222
|
-
data if not self._pretty_json else Session.str(data),
|
|
1223
|
-
)
|
|
1224
|
-
)
|
|
1195
|
+
self._verbose(f"REST {method} {url}, data: {data if not self._pretty_json else Session.str(data)}")
|
|
1225
1196
|
if method.lower() == "get":
|
|
1226
1197
|
r = requests.get(
|
|
1227
1198
|
url,
|
|
@@ -1259,48 +1230,28 @@ class Session(object):
|
|
|
1259
1230
|
except BaseException:
|
|
1260
1231
|
# if timeout <= 0:
|
|
1261
1232
|
raise AccsynException(
|
|
1262
|
-
"Could not reach {}:{}! Make sure backend({}) can"
|
|
1263
|
-
" be reached from you location and no firewall is "
|
|
1264
|
-
"blocking outgoing TCP traffic at port {}. "
|
|
1265
|
-
"Details: {}".format(
|
|
1266
|
-
hostname,
|
|
1267
|
-
port,
|
|
1268
|
-
hostname,
|
|
1269
|
-
port,
|
|
1270
|
-
traceback.format_exc() if not quiet else "(quiet)",
|
|
1271
|
-
)
|
|
1233
|
+
f"Could not reach {hostname}:{port}! Make sure backend({hostname}) can be reached from you location and no firewall is blocking outgoing TCP traffic at port {port}. Details: {traceback.format_exc() if not quiet else '(quiet)'}"
|
|
1272
1234
|
)
|
|
1273
1235
|
try:
|
|
1274
1236
|
retval = json.loads(r.text, cls=JSONDecoder)
|
|
1275
1237
|
if not quiet:
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
uri,
|
|
1280
|
-
method,
|
|
1281
|
-
Session._obscure_dict_string(
|
|
1282
|
-
Session._safely_printable(
|
|
1283
|
-
str(retval) if not self._pretty_json else Session.str(retval)
|
|
1284
|
-
).replace("'", '"')
|
|
1285
|
-
),
|
|
1286
|
-
t_start - t_end + 1,
|
|
1238
|
+
str_result = Session._obscure_dict_string(
|
|
1239
|
+
Session._safely_printable(str(retval) if not self._pretty_json else Session.str(retval)).replace(
|
|
1240
|
+
"'", '"'
|
|
1287
1241
|
)
|
|
1288
1242
|
)
|
|
1243
|
+
self._verbose(f"{hostname}/{uri} REST {method} result: {str_result} (~{t_start - t_end + 1}ms)")
|
|
1289
1244
|
except BaseException:
|
|
1290
1245
|
sys.stderr.write(traceback.format_exc())
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
method
|
|
1294
|
-
Session._obscure_dict_string(Session._safely_printable(str(data)).replace("'", '"')),
|
|
1295
|
-
r.text,
|
|
1296
|
-
traceback.format_exc(),
|
|
1246
|
+
str_data = Session._obscure_dict_string(Session._safely_printable(str(data)).replace("'", '"'))
|
|
1247
|
+
message = (
|
|
1248
|
+
f'The {url} REST {method} {str_data} operation failed! Details: {r.text} {traceback.format_exc()}'
|
|
1297
1249
|
)
|
|
1298
1250
|
Session._warning(message)
|
|
1299
1251
|
raise AccsynException(message)
|
|
1300
1252
|
|
|
1301
1253
|
if "exception" in retval:
|
|
1302
|
-
message = "{} caused an exception! Please contact {} admin for more"
|
|
1303
|
-
" further support.".format(uri, self._domain)
|
|
1254
|
+
message = f"{uri} caused an exception! Please contact {self._workspace} admin for more further support."
|
|
1304
1255
|
Session._warning(message)
|
|
1305
1256
|
if self._role in [CLEARANCE_ADMIN, CLEARANCE_SUPPORT]:
|
|
1306
1257
|
Session._warning(retval["exception"])
|
|
@@ -1328,7 +1279,7 @@ class Session(object):
|
|
|
1328
1279
|
assert self._uid, "Login before posting event!"
|
|
1329
1280
|
event = {
|
|
1330
1281
|
"audience": "api",
|
|
1331
|
-
"
|
|
1282
|
+
"workspace": self._workspace,
|
|
1332
1283
|
"eid": str(uuid.uuid4()),
|
|
1333
1284
|
"session": self._session_id,
|
|
1334
1285
|
"uri": uri,
|
|
@@ -1364,12 +1315,7 @@ class Session(object):
|
|
|
1364
1315
|
b = out.getvalue()
|
|
1365
1316
|
event["gz_data"] = base64.b64encode(b).decode('utf-8')
|
|
1366
1317
|
self._verbose(
|
|
1367
|
-
"Compressed event payload
|
|
1368
|
-
% (
|
|
1369
|
-
size,
|
|
1370
|
-
len(event["gz_data"]),
|
|
1371
|
-
(100 * len(event["gz_data"]) / size),
|
|
1372
|
-
)
|
|
1318
|
+
f"Compressed event payload {size}>{len(event['gz_data'])}({100 * len(event['gz_data']) / size}%)"
|
|
1373
1319
|
)
|
|
1374
1320
|
did_compress_payload = True
|
|
1375
1321
|
if not did_compress_payload:
|
|
@@ -1433,18 +1379,18 @@ class Session(object):
|
|
|
1433
1379
|
s += query[idx]
|
|
1434
1380
|
if idx_part_start < len(query):
|
|
1435
1381
|
parts.append(query[idx_part_start:])
|
|
1436
|
-
self._verbose('Query: "{}", parts: "{}"'
|
|
1382
|
+
self._verbose(f'Query: "{query}", parts: "{parts}"')
|
|
1437
1383
|
assert len(parts) == 1 or 3 <= len(parts), (
|
|
1438
1384
|
"Query has invalid syntax; statements can either be "
|
|
1439
1385
|
'single ("<entity>"") or with a WHERE statement '
|
|
1440
1386
|
'("<(sub)entity> WHERE {<entity>.}id=..{ AND ..}"")'
|
|
1441
1387
|
)
|
|
1442
1388
|
if len(parts) == 1:
|
|
1443
|
-
return
|
|
1389
|
+
return dict(entitytype=parts[0].lower())
|
|
1444
1390
|
else:
|
|
1445
1391
|
assert (
|
|
1446
1392
|
parts[1].strip().lower() == "where"
|
|
1447
|
-
), 'Invalid query "{}", should be on the form
|
|
1393
|
+
), f'Invalid query "{query}", should be on the form "<entitytype> where <expression>".'
|
|
1448
1394
|
# Decode expression
|
|
1449
1395
|
return {
|
|
1450
1396
|
"entitytype": parts[0].lower(),
|
|
@@ -1488,14 +1434,11 @@ class Session(object):
|
|
|
1488
1434
|
|
|
1489
1435
|
def _verbose(self, s):
|
|
1490
1436
|
if self._be_verbose:
|
|
1491
|
-
Session._info("[ACCSYN_API]
|
|
1437
|
+
Session._info(f"[ACCSYN_API] {s}")
|
|
1492
1438
|
|
|
1493
1439
|
@staticmethod
|
|
1494
1440
|
def _safe_dumps(d, indent=None):
|
|
1495
|
-
if
|
|
1496
|
-
return json.dumps(d if not isinstance(d, list) else list(d.values()), cls=JSONEncoder, indent=indent)
|
|
1497
|
-
else:
|
|
1498
|
-
return json.dumps(d, cls=JSONEncoder, indent=indent)
|
|
1441
|
+
return json.dumps(d if not isinstance(d, list) else list(d.values()), cls=JSONEncoder, indent=indent)
|
|
1499
1442
|
|
|
1500
1443
|
@staticmethod
|
|
1501
1444
|
def _safely_printable(s):
|
|
@@ -1503,37 +1446,25 @@ class Session(object):
|
|
|
1503
1446
|
|
|
1504
1447
|
@staticmethod
|
|
1505
1448
|
def _is_str(s):
|
|
1506
|
-
|
|
1507
|
-
return isinstance(s, str)
|
|
1508
|
-
else:
|
|
1509
|
-
return isinstance(s, str) or isinstance(s, unicode)
|
|
1449
|
+
return isinstance(s, str)
|
|
1510
1450
|
|
|
1511
1451
|
@staticmethod
|
|
1512
1452
|
def _url_quote(url):
|
|
1513
|
-
|
|
1514
|
-
return urllib.parse.quote(Session._safe_dumps(url))
|
|
1515
|
-
else:
|
|
1516
|
-
return urllib.quote(Session._safe_dumps(url))
|
|
1453
|
+
return urllib.parse.quote(Session._safe_dumps(url))
|
|
1517
1454
|
|
|
1518
1455
|
@staticmethod
|
|
1519
1456
|
def _json_serial(obj):
|
|
1520
1457
|
"""JSON serializer for *obj not serializable by default json code."""
|
|
1521
1458
|
if isinstance(obj, datetime.datetime) or isinstance(obj, datetime.date):
|
|
1522
1459
|
return obj.isoformat()
|
|
1523
|
-
raise TypeError("Type
|
|
1460
|
+
raise TypeError(f"Type {type(obj)} not serializable")
|
|
1524
1461
|
|
|
1525
1462
|
@staticmethod
|
|
1526
1463
|
def _base64_encode(s):
|
|
1527
1464
|
"""Produce a BASE64 encoded string."""
|
|
1528
|
-
|
|
1529
|
-
return (base64.b64encode(s.encode("utf-8"))).decode("ascii")
|
|
1530
|
-
else:
|
|
1531
|
-
if isinstance(s, str) or isinstance(s, unicode):
|
|
1532
|
-
return base64.b64encode(s)
|
|
1533
|
-
else:
|
|
1534
|
-
return binascii.b2a_base64(s)
|
|
1465
|
+
return (base64.b64encode(s.encode("utf-8"))).decode("ascii")
|
|
1535
1466
|
|
|
1536
1467
|
|
|
1537
1468
|
class AccsynException(Exception):
|
|
1538
1469
|
def __init__(self, message):
|
|
1539
|
-
super(
|
|
1470
|
+
super().__init__(message)
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: accsyn-python-api
|
|
3
|
+
Version: 3.1.0
|
|
4
|
+
Summary: A Python API for accsyn programmable fast and secure data delivery software
|
|
5
|
+
Home-page: https://accsyn.com
|
|
6
|
+
License: Apache-2.0
|
|
7
|
+
Keywords: accsyn,api,file-transfer,delivery
|
|
8
|
+
Author: Henrik Norin
|
|
9
|
+
Author-email: support@accsyn.com
|
|
10
|
+
Requires-Python: >=3.8,<4.0
|
|
11
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Topic :: Internet :: File Transfer Protocol (FTP)
|
|
23
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
24
|
+
Requires-Dist: requests (>=2.25.0,<3.0.0)
|
|
25
|
+
Project-URL: Bug Tracker, https://github.com/accsyn/accsyn-python-api/issues
|
|
26
|
+
Project-URL: Documentation, https://accsyn-python-api.readthedocs.io
|
|
27
|
+
Project-URL: Repository, https://github.com/accsyn/accsyn-python-api.git
|
|
28
|
+
Project-URL: Support, https://support.accsyn.com
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
|
|
31
|
+
# accsyn-python-api
|
|
32
|
+
Official accsyn fast and secure file delivery Python API
|
|
33
|
+
|
|
34
|
+
Python API support can be found [here](https://support.accsyn.com/workflows/python-api).
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
Changelog:
|
|
38
|
+
----------
|
|
39
|
+
|
|
40
|
+
See doc/release_notes.rst
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
Documentation:
|
|
44
|
+
--------------
|
|
45
|
+
|
|
46
|
+
[https://accsyn-python-api.readthedocs.io/en/latest](https://accsyn-python-api.readthedocs.io/en/latest)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
Development Setup:
|
|
50
|
+
------------------
|
|
51
|
+
|
|
52
|
+
This project uses Poetry for dependency management. To get started:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
# Install Poetry (if not already installed)
|
|
56
|
+
curl -sSL https://install.python-poetry.org | python3 -
|
|
57
|
+
|
|
58
|
+
# Install dependencies
|
|
59
|
+
poetry install
|
|
60
|
+
|
|
61
|
+
# Install with documentation dependencies
|
|
62
|
+
poetry install --with docs
|
|
63
|
+
|
|
64
|
+
# Activate the virtual environment
|
|
65
|
+
poetry shell
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Building Documentation:
|
|
69
|
+
----------------------
|
|
70
|
+
|
|
71
|
+
To build the documentation locally:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
# Install with docs dependencies
|
|
75
|
+
poetry install --with docs
|
|
76
|
+
|
|
77
|
+
# Build docs
|
|
78
|
+
cd doc
|
|
79
|
+
poetry run sphinx-build -T -E -b html -d _build/doctrees -D language=en . ../dist/doc
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Or use the shorter command:
|
|
83
|
+
```bash
|
|
84
|
+
poetry run sphinx-build -b html doc dist/doc
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Development Tools:
|
|
88
|
+
-----------------
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
# Format code
|
|
92
|
+
poetry run black .
|
|
93
|
+
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Building and Publishing:
|
|
97
|
+
-----------------------
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
# Build the package
|
|
101
|
+
poetry build
|
|
102
|
+
|
|
103
|
+
# Publish to PyPI (requires authentication)
|
|
104
|
+
poetry publish
|
|
105
|
+
|
|
106
|
+
# Or publish to test PyPI first
|
|
107
|
+
poetry config repositories.testpypi https://test.pypi.org/legacy/
|
|
108
|
+
poetry publish -r testpypi
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
accsyn(r) - secure high speed file delivery and workflow sync
|
|
112
|
+
https://accsyn.com
|
|
113
|
+
https://support.accsyn.com
|
|
114
|
+
|
|
115
|
+
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
accsyn_api/__init__.py,sha256=vSGlQJ7mqd5eE2vElmJ_hnvRy61FkMqFpUXyhhVapTY,121
|
|
2
|
+
accsyn_api/_version.py,sha256=9g_6UZ5D9rC6Se-GIbtxRPMgxhZsos3JAQ2D7E-3Km0,94
|
|
3
|
+
accsyn_api/session.py,sha256=PypVpJbVI5gOf90jGcLNwy82jWD5kZGkNJRiuGezIj8,56270
|
|
4
|
+
accsyn_python_api-3.1.0.dist-info/METADATA,sha256=NNAJc33_JgOUjYtgTSzvRzJKIimh7lbX4EUpBYm40yU,2936
|
|
5
|
+
accsyn_python_api-3.1.0.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
|
|
6
|
+
accsyn_python_api-3.1.0.dist-info/RECORD,,
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: accsyn-python-api
|
|
3
|
-
Version: 3.0.3
|
|
4
|
-
Summary: A Python API for accsyn programmable fast and secure data delivery software
|
|
5
|
-
Home-page: https://github.com/accsyn/accsyn-python-api.git
|
|
6
|
-
Author: Henrik Norin
|
|
7
|
-
Author-email: henrik.norin@accsyn.com
|
|
8
|
-
License: Apache License (2.0)
|
|
9
|
-
Classifier: Programming Language :: Python :: 3
|
|
10
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
-
Classifier: Operating System :: OS Independent
|
|
12
|
-
Requires-Python: >=2.7.9, <4.0
|
|
13
|
-
Description-Content-Type: text/markdown
|
|
14
|
-
Requires-Dist: requests
|
|
15
|
-
|
|
16
|
-
# accsyn-python-api
|
|
17
|
-
Official accsyn fast film delivery Python API
|
|
18
|
-
|
|
19
|
-
Complete Python API reference can be found [here](https://support.accsyn.com/python-api).
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
Changelog:
|
|
23
|
-
----------
|
|
24
|
-
|
|
25
|
-
See doc/release_notes.rst
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
Documentation:
|
|
29
|
-
--------------
|
|
30
|
-
|
|
31
|
-
[https://accsyn-python-api.readthedocs.io/en/latest](https://accsyn-python-api.readthedocs.io/en/latest)
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
Building:
|
|
35
|
-
---------
|
|
36
|
-
|
|
37
|
-
To build the documentation locally, run:
|
|
38
|
-
|
|
39
|
-
```
|
|
40
|
-
cd doc
|
|
41
|
-
pip install -r requirements.txt
|
|
42
|
-
python -m sphinx -T -E -b html -d _build/doctrees -D language=en . ../dist/doc
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
Deploying:
|
|
46
|
-
----------
|
|
47
|
-
|
|
48
|
-
```
|
|
49
|
-
python setup.py sdist bdist_wheel
|
|
50
|
-
twine upload --verbose --username accsyn dist/*
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
Henrik Norin, HDR AB, 2023
|
|
54
|
-
accsyn(r) - secure data delivery and workflow sync
|
|
55
|
-
https://accsyn.com
|
|
56
|
-
https://support.accsyn.com
|
|
57
|
-
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
accsyn_api/__init__.py,sha256=vSGlQJ7mqd5eE2vElmJ_hnvRy61FkMqFpUXyhhVapTY,121
|
|
2
|
-
accsyn_api/_version.py,sha256=SkA5Pq2uOISkNluIl0WMV5KQjV-UfWwM2jXzz9g37Sc,94
|
|
3
|
-
accsyn_api/session.py,sha256=H4mLJOKIju54A-vFcRnzZgeBcVLYw3Sub70speUSJvI,57977
|
|
4
|
-
accsyn_python_api-3.0.3.dist-info/METADATA,sha256=O1K0WMODXSnWfjNmYmTcqZePuJ9TzZGVzFyF6pSg928,1326
|
|
5
|
-
accsyn_python_api-3.0.3.dist-info/WHEEL,sha256=bFJAMchF8aTQGUgMZzHJyDDMPTO3ToJ7x23SLJa1SVo,92
|
|
6
|
-
accsyn_python_api-3.0.3.dist-info/top_level.txt,sha256=L5p6Syb_jH-p3-TSIARG35mGD7OALyDLMGziEzpDyFs,11
|
|
7
|
-
accsyn_python_api-3.0.3.dist-info/RECORD,,
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
accsyn_api
|