Accsyn Python API Documentation

rev 1.019, last updated 20.09.18


Summary


The Accsyn Python API allows for easy integration into Python (v2.7 & v3.4+) enabled third party applications. With the API you can for example:

  • Create automated workflows that reacts on an event and then dispatches data (files/metadata) across the globe, in a safe and controlled environment.

  • Fetch and display Accsyn transfer jobs in your own user interface, also enabling control in terms of pause, resume and abort.

  • Handle shares automatically, allowing expose of project dedicated file areas with one or more external users, upon an event in your production systems.

Other resources:

  • This documentation covers basic API usage, more usage examples are displayed within domain web application beneath Help section @ https://<yourdomain>.accsyn.com.

  • Our tutorials covers how to setup Accsyn to achieve a certain functionality and many of them come with complete source code, find the tutorials here: support.accsyn.com.

  • Find source code samples using the API at our public GitHub:


How does it work?


The API act as a bridge between your application and the Accsyn HTTPS REST API, simplifying actions such as spawning a new file transfer, query and modify ongoing transfers. Admin tasks can also be performed using the API such as inviting users, creating shares and defining ACLs.


IMPORTANT NOTE: The Python API are not able to send files, it can only tell Accsyn to spawn a transfer between a server (typically identified by a domain, share and path) and a client (typically identified by a user name/E-mail and/or client ID/hostname). This means that the API can work independent of a locally installed file transfer client if the intention is to transfer files to and from the device API is running from.


Obtaining


Install using PIP:

pip install accsyn-python-api


Download from git:

git clone https://github.com/accsyn/accsyn-python-api



Create and test a session


Importing the Python package


The Python API requires you to supply your Accsyn domain, your username (E-mail) and either API key or password when creating a new session:

import accsyn_api


Creating the session


session = accsyn_api.Session(domain='acmefilm',username='john@user.com', api_key='f0a8b9a9-879f-4174-9d08-322eea196efc')

or

session = accsyn_api.Session(domain='acmefilm',username='john@user.com', pwd='password')

or

session = accsyn_api.Session(domain='acmefilm',username='john@user.com', session_key='6152693980700117321a5f8c')


The following environment variables are picked up if set by python parent process:

  • "ACCSYN_DOMAIN" => "domain".

  • "ACCSYN_API_USER" => "username".

  • "ACCSYN_API_KEY" => "api_key".


NOTES

    • Your API key can be obtained online or from desktop app @ Prefs>Setup API environment, or by running "accsyn user get_api_key" from your terminal/*nix shell.

    • Remember to treat the API key as a secret password as it will grant access to all data on your Accsyn shared storage.

    • Session authenticated with password will expire within 10h, make sure to design your application to check for "session_expired:true" being supplied when a error message is supplied. In that case you will need to re-create your session and retry operation.

    • Add verbose=True to session creation if you want to see verbose debugging output.

    • Add path_logfile=/path/to/my.log.file if you want all stdout should go to disk.


Testing the session


print session.find_one("User")

Should output your user profile:


{

"id":"5b0faf03304bfd4810dbd5fc",

"code”:"john@user.com",

"modified":datetime("2018-06-04 07:06:41.028619")

}



Error handling


If an error occurs, an exception will be raised and the exception message can fetched afterwards by issuing:

print session.get_last_message()


Network proxy


If you live on a network that does not have network access, the Python API can utilise a SOCKS (v4/v5) proxy of yours or an Accsyn daemon acting as a proxy (refer to the Accsyn admin manual on how to setup such a proxy).

SOCKS proxy

Supply proxy="socks:<hostname or IP>:<port>" when creating session or set the ACCSYN_PROXY environment variable.

Accsyn proxy

Supply proxy="accsyn:<hostname or IP>:<port>" when creating session or set the ACCSYN_PROXY environment variable.


Data types


Roles


Three built-in "roles" (permission/clearance) levels exists for Accsyn users, the clearance dictates what data can be read and write through the API:

  • Admins; Are allowed to read and write all data – configure Accsyn.

  • Employees; Are allowed full access to jobs and data on all shares.

  • Users; (i.e. remote freelancers/customers) Besides receiving packages, they are only allowed to access share's explicitly given access through ACLs (Access Control Lists). Restricted users automatically is given a share @ default location - <root share>/accsyn/<user id (email)>.


Entities


Data read from Accsyn using the API arrives as JSON dictionaries, categorised by entity types:


To retrieve a list of all known entity types:

session.find("entitytypes")

This will return a list of entity types as string, i.e. ["user","organization","job",..].



Attributes


Each entity has its own attributes, such as “id” or “code”(an Accsyn abbreviation for an unique “name”).

To retrieve a list of known attributes for an entity:

session.find('attributes WHERE entitytype=job')

This will return a list of attributes entities of that entity type can have, i.e. ["id","code","status",...].

Note: Depending the clearance, some attributes might be not visible if accessing the API as an restricted user.


By default, all readable attributes are returned. To return attributes only allowed during creation and edit:

session.find('attributes WHERE entitytype=job', create=True)

session.find('attributes WHERE entitytype=job', update=True)


Admin pages sample API expressions


Your Accsyn admin pages contains sample API calls related to the area you are within, ideal for finding the exact syntax of a specific call. Simply look for the Help link at bottom of each page, a CLI sample is also usually supplied.



Create an entity


Creating an entity


Create a job (entity), sending a single file from "projects" share to a user:

jobs = session.create("job",{

"source":"share=projects/thefilm/latest_edit.mov",

"destination":"lars@edit.com"

})

This will return a list with job(s) created, with its new ID supplied, as would have been returned by a find_one query. If creation failed, an Exception will be thrown.


Create a job sending multiple files from a home share, with metadata and not touching the second file even if it differ in size and date, stored at relative paths in queue 'lowprio' with metadata:

jobs = session.create("job",{

"code":"proj001_delivery",

"queue":"lowprio",

"tasks":[

{

"source":"share=lars@edit.com/proj001/delivery.zip",

"destination":"lars@edit.com:proj001/delivery.zip"

},

{

"source":"share=lars@edit.com/proj001/reference/delivery_notes.pdf",

"destination":"lars@edit.com:proj001/reference/delivery_notes.pdf",

"settings":{"transfer_ignore_existing":"file"}

}

],

"metadata":{

"project":"proj001"

}

})


Another example sending two large directories, one at a time, to a remote site. With an additional notification email being sent to notify@thecorp.co.uk:

jobs = session.create("job",{

"code":"alpha_pitch",

"email":"+notify@thecorp.co.uk",

"tasks":[

{

"source":"share=projects/proj001/_PITCH",

"destination":"site=london"

},

{

"source":"share=projects/proj001/_REFERENCE",

"destination":"site=london"

}

],

"settings":{"task_bucketsize":"1"}

})


Create a job from JSON on disk:

job = session.create("job","/tmp/accsyn/jobsubmit/A5F1D.json")

Print the results:

print session.str(session.find_one("job where id=%s"%job["id"]))

Note: The "str" API function is a convenience function that formats JSON nicely with indentation (i.e. json.dumps(dict, indent=4)).


For a complete job JSON syntax reference and best practices, please consult the Transfer job specification document.



Query one or more entitities


The Accsyn python API accepts queries very similar to SQL queries as input, and return a single JSON dict or a list of JSON dicts as return value.

Note: as it might be similar to SQL, it does not fully support the SQL query syntax. Refer to examples in this reference to find out capabilities and limitations.


Get job named “my_transfer”:

session.find_one('Job WHERE code="alpha_pitch"')

The job will be returned as a Python dict, None will be returned if no match was found/user have no permission to read the job.


Get a list with all jobs destined “lars@edit.com”, only bringing back attributes "id","code" and "status":

session.find('job WHERE dest=user:lars@edit.com', attributes=['id','code','status'])


The jobs will be returned as a Python list of dictionaries, list will only include job matching query and user have permission to read.

If find/find_one query fails, invalid syntax or other error, an Exception will be thrown.


Get a list of all share allowed access to:

session.find('share')


To print the JSON dict with indentation:

session.str(session.find_one('job WHERE status=failed'), indent=2)


Expressions

As mentioned, the query syntax is not as evolved as for example SQL. Accsyn API currently support AND/OR operations using the following syntax:

session.find('acl WHERE (ident=user:5bfeb0381da7ee4095fa217e AND target=share:ee5b4429-78e2-46ab-96cd-67a0be059e95)')


Job specific attributes

Job queries return all jobs by default, to have active jobs or only finished jobs returned, set the finished attribute. To retrieve only active jobs:

session.find('job', finished=False)

Only return job that are finished or aborted:

session.find('job', finished=True)



Updating an entity

Update a job entity, in this case rename a job with named "my_transfer" (ID: 5a7325f8b7ef72f5f9d74bf4) and also pause it:

job = session.find_one('job WHERE code="my_transfer"')

updated_job = session.update_one("job", job["id"], {

"code":"my_transfer_1",

"status":"paused"

})

Will return the job dict, as would have been returned by a find_one query. If update failed, an Exception will be thrown.

Note: The Accsyn Python API does not support SQL-like atom transaction ending with a commit statement.



Deleting an entity

To delete a job:

job = session.delete_one("job","5a7325f8b7ef72f5f9d74bf4")

Will return the job dict, as would have been returned by a find_one query. If deletion failed, an Exception will be thrown.



Tasks


A task is a file or directory that should be transferred within a job.


Query tasks


Job tasks are fetched by supplying the job ID query merged with task query:

session.find('task WHERE job.id=5a7325f8b7ef72f5f9d74bf4')


Query may include additional expressions after an AND marker, attributes allowed:

  • id; The internal Accsyn ID of task.

  • uri; The uri/name(code) of task, usually "0", "1" and so on.

  • status; The status of task, can be "pending"(waiting for user to choose download location),"queued", "booting","executing","failed","done,"onhold","excluded".

  • size; The size of file or directory, a positive number.


session.find('task WHERE job.id=5a7325f8b7ef72f5f9d74bf4 AND status<>executing')


Create tasks


Add a file(task) to an existing job, mirroring paths to same destination as rest of job:

session.create("task", {"tasks":["/Volumes/projects/creatures_showreel_2018.mov"]}, job["id"]))

Returns {"success":True} if everything goes well, exception thrown otherwise. If another task exists with the same source and destination, no task will be added and instead the existing task will be retried. To have Accsyn reject duplicate tasks, supply attribute allow_duplicates=False to create call.

Note: tasks without destination can only be created for jobs sending files with mirrored paths.


Add with new destination path:

session.create("task", {"tasks":[{"source":"share=projects/_REF/creatures_showreel_2018.mov","destination":"share=projects/TMP/creatures_showreel_2018_tmp.mov"}]}, job["id"])

Notes:

    • Only users themselves own the right to add files from/to their local harddrive/storage for upload/download.

    • Pending files (tasks without destination path:s) can not be added to a job that remote user already have started downloading.

    • Operation will fail if another task exists with same source and destination path


Updating task(s)


The current task attributes can be updated through API:

  • status;

Tasks are always updated in group with values supplied as a list of dicts instead of a single dict:

job = session.find_one('job WHERE code="my_transfer"')

updated_job = session.update_many("task", [{

"id":"cc5f2afa-9ae4-46e0-9273-82ac802b20ff",

"status":"onhold"

}], entityid=job["id"])

Will return the updated tasks, as would have been returned by a task find query.



Settings


Settings are key="string value" entries on domain(global), queue, share, client or job. Settings are hierarchal which means that settings can overridden, for a example a queue setting can be overridden on a job and so on.

Important note:

      • Setting values are always on string form, and will required to be converted by you as appropriate.

Get settings

session.find('settings WHERE job.id=5a7325f8b7ef72f5f9d74bf4')

Will return a JSON containing the settings. To retrieve them merged upstream, supply upstream=True argument:

session.find('settings WHERE job.id=5a7325f8b7ef72f5f9d74bf4', upstream=True)



File operations


Besides working with entities, the API supports file operation on share level (admins, employees) or on a specific share (admins, employees and restricted users depending on ACLs).


List files on remote server


To list files on the share “thefilm-DIT”, subfolder TO_ACMEFILM:

session.ls("share=thefilm-DIT/TO_ACMEFILM")

Optional arguments:

  • recursive Do a recursive listing, True or False. Default False, see below.

  • maxdepth (Recursive listing) Limit number of levels to descend, positive integer greater than or equal 1.

  • directories_only Return only directories in listing, True or False. Default is False.

  • files_only Return only files in listing, True or False. Default is False. Note: Providing recursive=True won't have any affect.

  • getsize Calculate and return size of directories.


If succeeds, this will return a list of dictionaries, one for each file found:

{

"result": [

{

"type":1,

"filename":"pitch.mov",

"size":81632,

"modified":datetime(..)

},

..

]

}


  • Type; The type of file; 0 is directory/folder, 1 is a file.

  • Filename; The name of the file or directory.

  • Size; The size of the file or directory.

  • Modified; A Python datetime object holding the date file was last modified.


Failure scenarios:

  • Permission denied; An exception will be thrown.

  • Share/Path does not exists; An exception will be thrown, obtain the message by calling session.get_last_message().


By default, it does not dig down into sub directories. Add "recursive=True" to the call in order to have all (visible) files returned:

session.ls("share=thefilm-DIT", recursive=True)

If succeeds, this will return a list of dictionaries, one for each file found, with further descendant files in a list:

{

"result": [

{

"filename":"TO_ACMEFILM",

..,

"files":[

{"filename":"A001_C001.mov",...},

{...},

]

},

..

]

}


Check if a file/directory exist


session.exists("workarea=thefilm-DIT/TO_ACMEFILM/final.mov")


If succeeds, this will return true or false:

{

"result": True

}


Failure scenarios (will cause an exception to be thrown, obtain the message by calling session.get_last_message()):

  • Permission denied; you do not have read access to folder.

  • Server is down; server at organization is not online to perform the requested operation.

  • Share/Path does not exists;


Get size on disk

Get total size of file or all files beneath a directory:

session.getsize("share=thefilm-DIT/TO_ACMEFILM")

Optional arguments:

  • maxdepth Limit number of levels to descend, positive integer greater than or equal 1.


If succeeds, this will return the size of the file/directory:

{

"result": 10462211

}



An exception will be thrown if listing fails. Possible reasons include:

  • Permission denied; you do not have read access to folder.

  • Server is down; server at organization is not online to perform the requested operation.

  • Share/Path does not exists;

Note: Obtain the most recent failure message by issuing: session.get_last_message()


Running file operation on multiple target directories in one call


In scenarios were you require speed and need to operate on multiple directories, you can provide a list of target path statements. Giving an example with 'ls' command:

session.ls(["share=thefilm-DIT/source","share=assets/exported"], recursive=True)

If succeeds, this will return a list of a list with dictionaries, one for each path and file found, with further descendant files in a list. If path does not exists, an empty result is returned and a error message is supplied:

{

"result": [

{

[

{

"path":"share=thefilm-DIT/source",

"result": {

"filename":"TO_ACMEFILM",

..,

"files":[

{"filename":"A001_C001.mov",...},

{...},

]

},

..

},

{

"path":"share=assets/exported",

"result":null,

"message":“File/directory ‘exported’ does not exist!"

}

]

}

]

}


If you need different arguments for each path statement to override the global, supply as dicts with these set:

session.ls([

{

"path":"share=thefilm-DIT/source",

"recursive":True

},

{

"path":"share=thefilm-DIT/render",

"maxdepth": 3

}

], recursive=False)



Publishing


The Accsyn publish workflow is an extended upload mechanism allowing you to check the files before upload, decide where the should be written and allow user to input metadata. It is described in detail in this tutorial: Tutorial - Publish workflow.

The Python API supports the publish workflow and can be used for building your own client-side tools.


Pre publish


Supply the list of file-/directory names that should be checked at server end, supplying an unique ID and sub files-/directories as necessary. The ID should be a uuid4 and is required for Accsyn to relate each publish entry with each task on job submit later on :

result = session.publish([

{

"id":"33d59998-c980-4b82-9f6d-06ce27201d26",

"filename":"first_file",

"size":10240,

"is_dir":false

},

{

"id":"dda32b0f-36e5-4a5f-9379-fdfabfd482e1",

"filename":"second_directory",

"is_dir":true,

"files":[{

"filename":"file.0001.dat",

"size":1000000

}]

}

])


Which will return back the same list with additional entries appended by your pre-publish hook:

{

"files":[{

"id":"33d59998-c980-4b82-9f6d-06ce27201d26",

"filename":"first_file",

"size":10240,

"is_dir":false,

"warning":"Will be uploaded but now published",

"can_upload":true,

},

{

"id":"dda32b0f-36e5-4a5f-9379-fdfabfd482e1",

"filename":"second_directory",

"is_dir":true,

"files":[{

"filename":"file.0001.dat",

"size":1000000

}],

"ident":"abcd1234",

"can_publish":true,

"info":"Verified publish, please enter comment and time report"

}],

"time_report":true,

"comment":true,

"guidelines":".."

}


or throw an exception if something went wrong.

Then submit job as you would normally, with this publish data supplied as a hook and corresponding ID:s supplied to tasks aswell:

jobs = session.create("job",{

"code":"abcd1234_publish",

"tasks":[

{

"id":"33d59998-c980-4b82-9f6d-06ce27201d26",

"source":"/Volumes/NAS01/first_file",

},

{

"id":"dda32b0f-36e5-4a5f-9379-fdfabfd482e1",

"source":"/Volumes/NAS01/second_directory",

}

],

"hooks":[

{

"when":"hook-job-publish-server",

"data":{"files":result}

}

]

})

This will cause hook to be run after files has been transferred to the correct location at server, as supplied by pre publish hooks.



Misc


Retrieve session key:

session.get_session_key()

Will return a string with your session key.

Note: The session key can only be used by other API instances within the same device and should be treated as a secret password and not to be shared with other users as they would gain access to files on your shares. They will expire, typically within an hour and will need to be refreshed.


Check if GUI or server is running:(since 1.1.4)

session.gui_is_running()

session.server_is_running()

Will return True if GUI/Server is running, False if offline, None if not installed/detected.



Administrating Accsyn using the API


Basic administration can be done using the API, this guide will not cover this as it can be found within the web application/admin portal beneath the Help section at each page @ https://<yourdomain>.accsyn.com.



NOTE: Functionality described below is planned for v1.3 and might not make it to the final release v1.2 if not requested.


Create directory

To create directory “__UPLOAD” at the share “projects”:

session.mkdir("share=projects/__UPLOAD")

Will return ; {"result":true} if successful, otherwise an exception to be thrown, obtain the message by calling session.get_last_message()):

  • Permission denied; You do not have write access to the parent folder.

  • Parent directory does not exist;

  • Directory already exists;


Rename/move a file or directory

Move file “share=thefilm-DIT/TO_ACMEFILM/pitch.mov” to “share=thefilm-DIT/TO_ACMEFILM/QT/pitch.mov”:

session.mv("share=thefilm-DIT/TO_ACMEFILM/pitch.mov","share=thefilm-DIT/TO_ACMEFILM/QT/pitch.mov")

If rename went well {“result”:true} will be returned, otherwise an exception to be thrown, obtain the message by calling session.get_last_message()):

  • Permission denied; You do not have read access to the source file/folder or do not have write access to the destination file/folder.

  • Source file/directory does not exist.

  • Destination parent directory does not exist.


Delete a file or directory

WARNING: Automising file removal through API calls can cause unwanted directories to be deleted, always test/dry run your calls before you put them into production!

Remove the directory “share=thefilm-DIT/TO_ACMEFILM/QT”:

session.rm("share=thefilm-DIT/TO_ACMEFILM/QT")

Will return {“result”:true} if successful, otherwise an exception to be thrown, obtain the message by calling session.get_last_message()):

  • Permission denied; You do not have write access to the file/folder that is to be deleted.

  • If target is a directory and contains files, exception will say: {“message”:”Cannot delete non-empty directory 'share=thefilm-DIT/TO_ACMEFILM/QT'!”}. To have it deleted anyway, supply the force flag: session.delete("share=thefilm-DIT/TO_ACMEFILM/QT",force=True).

  • The removal failed to to locked files or other permission problems on server. Contact domain adminstrator.