.. _machine-analytics-sample-python-script: Sample Python Script ==================== Overview -------- The goal of this sample python script is to demonstrate how to use the Machine Analytics API to create a capture and upload a log file. The flow of the script is as follows:: 1. Authenticate user to gain access to APIs. 2. Obtain a modeler type and capture type. 3. Create a modeler of type obtained above 4. Create a capture and upload log file to backend 5. List your builds 6. Do some cleanup Global variables ---------------- Base url where our data endpoints live. The {} is a place holder .. sourcecode:: python DATA_URL = 'https://data.authentise.com/{}/' URL for authentication .. sourcecode:: python USERS_URL = 'https://users.authentise.com/sessions/' Path pointing to file to be uploaded .. sourcecode:: python CAPTURE_PATH = 'modeler_status_1.log' HTTP requests headers .. sourcecode:: python HEADERS = { 'Content-Type' : 'application/json', 'Accept' : 'application/json', } Authentise username and password for authentication .. sourcecode:: python user_data = { 'username' : 'engineering@authentise.com', 'password' : 'password', } The attributes of the modeler .. sourcecode:: python MODELER_DATA = { 'ip_address' : '10.0.0.10', 'name' : 'My printer name', 'description' : 'Blue Steel', 'serial_number' : 'ABBA', } Function calls -------------- 1. Authenticate ``_make_request('POST', USER_URL, user_data)`` Send the request to the endpoint so we can authenticate the session and be granted permissions. 2. List modeler types to create a modeler ``modeler_type, capture_type = _get_capture_type_status_uri(_list_modeler_types())`` Get a modeler type and its associated capture_type. 3. Create modeler ``modeler = _create_modeler(modeler_type)`` Create modeler and assigns it to the modeler variable. 4. Create a capture and upload log file. ``_create_capture(modeler, capture_type)`` Use the modeler and capture_type we got above to create a capture and upload the file to the backend. 5. List your builds. ``builds = _list_builds()`` Call the build endpoint so we can see the progress of our build based on the capture upload. 6. Clean up. ``_delete_modeler(modeler['location'])`` Because this is a sample script I used a fake modeler and didn't want to clutter up the database. Considerations -------------- This script is a sample of how to call the endpoints to create a capture in Python. It is intended as an example for you to build a script that would be suited to your environment. We'll be using the python ``requests`` library. It's a 3rd-party library, but the API should be clear enough to understand well how it works without being familiar with the library specifics Sample code ----------- **Python sample script** .. sourcecode:: python #!/usr/bin/env python3 import gzip import pprint import requests """change these global variables for your environment""" DATA_URL = 'https://data.authentise.com/{}/' USERS_URL = 'https://users.authentise.com/sessions/' CAPTURE_PATH = 'modeler_status_1.log' HEADERS = { 'Content-Type' : 'application/json', 'Accept' : 'application/json', } USER_DATA = { 'username' : 'test@authentise.com', 'password' : '*******', } session = requests.Session() def _make_request(method, url, payload=None, headers=HEADERS): response = session.request(method, url, json=payload, headers=HEADERS) if not response.ok: raise Exception("Bad response from {}: {}".format(url, response.json())) if method == 'POST': return response.headers elif method == 'PUT': return None elif method == 'DELETE': return None else: return response.json() The sample code above forms the basis for working with the variou APIs of 3DIAX. All requests we make will include the correct ``Content-Type`` and ``Accept`` header to tell the API we are sending JSON and expect to receive JSON. The first thing we'll need to do in our script is to decide which kind of modeler we are going to imitate. To do that we will list the different capture types. .. sourcecode:: python # ...snip... def _list_capture_types(): return _make_request('GET', DATA_URL.format('capture-type')) def main(): # authenticate _make_request('POST', USERS_URL, USER_DATA) # show the different capture types pprint.pprint(_list_capture_types()) return if __name__ == '__main__': main() If we run the above script as-is we'll get back a list of capture-type resources. It looks something like this .. sourcecode:: javascript { "resources": [ { "capture_type": "catex.modeler.status", "interval": 300, "uri": "https://data.authentise.com/capture-type/f9fa10dc-3bdb-463f-a6a6-7e6934aeae97/", "modeler_type": "https://data.authentise.com/modeler-type/1f124472-efd7-441d-b659-c480e5f55e14/" }, { "capture_type": "catex.modeler.jobs", "interval": null, "uri": "https://data.authentise.com/capture-type/dfa7a7b6-fc30-453e-b516-76b0a9bfe768/", "modeler_type": "https://data.authentise.com/modeler-type/1f124472-efd7-441d-b659-c480e5f55e14/" }, { "capture_type": "connex.modeler.log", "interval": 300, "uri": "https://data.authentise.com/capture-type/e428be7e-c42c-4b16-8972-c819ba5d4611/", "modeler_type": "https://data.authentise.com/modeler-type/85be369e-83a2-4f39-ba7c-90887abc3898/" } ] } For this tutorial we will use ``catex.modeler.status`` - that's the log file that certain Stratasys FDM machines create at regular intervals during normal operation that tell us the material present, the job they are printing, temperature, etc. In order to actually send up this log information we need to create a ``modeler`` resource. This represents a printer in the system. In order to create a modeler we need to know what types the system supports .. sourcecode:: python # ...snip... def _list_modeler_types(): return _make_request('GET', DATA_URL.format('modeler-type')) def main(): # authenticate _make_request('POST', USERS_URL, USER_DATA) # show the different capture types pprint.pprint(_list_modeler_types()) return if __name__ == '__main__': main() When we run the code above we get the data on the modeler types in the system. It looks something like this .. sourcecode:: javascript { "resources": [ { "description": null, "name": "Fortus 250", "uri": "https://data.authentise.com/modeler-type/728d2c92-75b6-4c67-a1e4-3e587c32e375/", "technology": "FDM", "manufacturer": "Stratasys", "capture_types": [ { "capture_type": "catex.modeler.jobs", "interval": null, "uri": "https://data.authentise.com/capture-type/bc53aced-b309-4bdf-8ece-69947f4a9db7/" }, { "capture_type": "catex.modeler.status", "interval": 300, "uri": "https://data.authentise.com/capture-type/98f40df0-318f-4ac0-87f5-765430d1d127/" } ] }, { "description": null, "name": "Fortus 400", "uri": "https://data.authentise.com/modeler-type/1df33a4c-1b96-4098-a44a-5c98c92b018b/", "technology": "FDM", "manufacturer": "Stratasys", "capture_types": [ { "capture_type": "catex.modeler.jobs", "interval": null, "uri": "https://data.authentise.com/capture-type/64251c64-3b2c-4265-a8e8-48599494a902/" }, { "capture_type": "catex.modeler.status", "interval": 300, "uri": "https://data.authentise.com/capture-type/103691f1-8188-4aad-b43e-a29ef3b726d7/" } ] }, { "description": null, "name": "Objet 350", "uri": "https://data.authentise.com/modeler-type/85be369e-83a2-4f39-ba7c-90887abc3898/", "technology": "Binder Jet", "manufacturer": "Stratasys", "capture_types": [ { "capture_type": "connex.modeler.log", "interval": 300, "uri": "https://data.authentise.com/capture-type/e428be7e-c42c-4b16-8972-c819ba5d4611/" } ] }, { "description": null, "name": "Uprint", "uri": "https://data.authentise.com/modeler-type/1f124472-efd7-441d-b659-c480e5f55e14/", "technology": "FDM", "manufacturer": "Stratasys", "capture_types": [ { "capture_type": "catex.modeler.jobs", "interval": null, "uri": "https://data.authentise.com/capture-type/dfa7a7b6-fc30-453e-b516-76b0a9bfe768/" }, { "capture_type": "catex.modeler.status", "interval": 300, "uri": "https://data.authentise.com/capture-type/f9fa10dc-3bdb-463f-a6a6-7e6934aeae97/" } ] } ] } We can see here that the ``Fortus 250`` supports the capture type we're interested in. Each of these resources are referenced by URI. We're going to use the modeler ``https://data.authentise.com/modeler-type/728d2c92-75b6-4c67-a1e4-3e587c32e375/`` and the capture type ``https://data.authentise.com/capture-type/98f40df0-318f-4ac0-87f5-765430d1d127/`` Now we need to create a modeler and a capture for the modeler that uses those URIs .. sourcecode:: python # ... snip ... def _create_modeler(modeler_type_uri): MODELER_DATA['type'] = modeler_type_uri return _make_request('POST', DATA_URL.format('modeler'), MODELER_DATA) def _delete_modeler(modeler): _make_request('DELETE', modeler) def _list_builds(): return _make_request('GET', DATA_URL.format('build')) def _create_capture(modeler, capture_type): payload = { 'modeler' : modeler['location'], 'capture_type': capture_type } headers = _make_request('POST', DATA_URL.format('capture'), payload) capture_upload_url = headers["x-upload-location"] capture_uri = headers["Location"] return capture_uri, capture_upload_url def main(): # authenticate _make_request('POST', USERS_URL, USER_DATA) modeler_type = 'https://data.authentise.com/modeler-type/728d2c92-75b6-4c67-a1e4-3e587c32e375/' capture_type = 'https://data.authentise.com/capture-type/98f40df0-318f-4ac0-87f5-765430d1d127/' # create the mode1ler modeler = _create_modeler(modeler_type) pprint.pprint('Created modeler uri:{}'.format(modeler['location'])) # create a capture capture, upload_url = _create_capture(modeler, capture_type) print("Created capture {}".format(capture) We have now created a modeler and created a capture for our modeler. Normally Authentise Echo will send a new capture for each modeler it listens to once every 5 minutes. This is how the system knows that the printer is healthy and keeps track of what the printer is doing. In our example script we have created the capture but we have not uploaded any data. Creating the capture merely creates a location where the data could be uploaded The capture that gets uploaded has a format that is specific to each printer type. The ``catex.modeler.status`` capture type expects a file that looks like this .. code:: set machineStatus(general) { -modelerStatus "Building" -modelerExplanation ": " -startTime "1457952053" -elapsedBuildTime 12721 -modCount 143 -modelerType "vt2l" -currentModelingLayer 34 -partCurrentTemp 355 -supportCurrentTemp 241 -envelopeCurrentTemp 100 -partTip "T12" -supportTip "T12SR100" -partSetTemp 355 -supportSetTemp 240 -envelopeSetTemp 100 -serverTime 1457964774 -serverTimeZoneOffset 21600 -serverTimeDSTValue 1 -controllerVersion " 3.17.2516.0 " -compatibleCMBVersion 9.3 }; set machineStatus(cassette) { { -spool 0 -name "NYL12" -status "ACTIVE" -remainingMatl 25.209132 -mfgDate "05122015" -mfgLot "6621" -initialMatl 92.300000 -serialNumber 278929307 } }; set machineStatus(currentJob) { -jobName "my-job" -jobid 123 -owner "Eli Ribble" -submitTime "1457951706" -buildTime 12721 -estimatedBuildTime 27474 -startTime "1457952053" -endTime "1457952053" -completionStatus "In Progress" -partTotalMatl 3.1626620 -supportTotalMatl 0.0954441 -partConsumed 1.5615234 -supportConsumed 0.0926666 -partTip "T12" -supportTip "T12" -partMatlName "NYL12" -supportMatlName "SR-110" -totalLayers 69 -currentLayer 34 -partMinX 11.465531 -partMinY 0.058877 -partMinZ 0.014056 -partMaxX 16.191124 -partMaxY 14.199600 -partMaxZ 0.541973 -comment "Packed file written by Control Center!!!!!Model color: black. " -producer "10.6 (4739)" -buildMode "" -pack "{ sacTower,1140 CLIP, CAMERA FLEX ARM FLEX CLIP BODY*51 }" }; set machineStatus(queue) { }; set machineStatus(previousJob) { -position {-1} -jobName {} -jobId 0 -owner {} -submitTime 0 -estimatedBuildTime 0 -startTime 0 -endTime 0 -buildTime 0 -completionStatus {} -partMatlName {} -supportMatlName {} -partTotalMatl 0 -supportTotalMatl 0 -partConsumed 0 -supportConsumed 0 -partTip T16 -supportTip T16 -totalLayers 0 -currentLayer 0 -partMinX 0 -partMinY 0 -partMinZ 0 -partMaxX 0 -partMaxY 0 -partMaxZ 0 -comment {} -producer {} -pack {} }; You can manipulate that file to set things like the jobName (current set to ``my-job``) or jobid (currently set to ``123``). The system will parse this file and then update information about the printer, including creating ``build`` resources that represent what is being built Here we'll show the code for how to upload the file .. sourcecode:: python # ... snip ... def _create_modeler(modeler_type_uri): MODELER_DATA['type'] = modeler_type_uri return _make_request('POST', DATA_URL.format('modeler'), MODELER_DATA) def _delete_modeler(modeler): _make_request('DELETE', modeler) def _list_builds(): return _make_request('GET', DATA_URL.format('build')) def _create_capture(modeler, capture_type): payload = { 'modeler' : modeler['location'], 'capture_type': capture_type } headers = _make_request('POST', DATA_URL.format('capture'), payload) capture_upload_url = headers["x-upload-location"] capture_uri = headers["Location"] return capture_uri, capture_upload_url def _upload_capture(capture, upload_url, path): with open(path, 'rb') as f: content = gzip.compress(f.read()) response = session.put( upload_url, data = content, headers = {'Content-Type': 'application/x-gzip'}, ) assert response.ok, response.text def main(): # authenticate _make_request('POST', USERS_URL, USER_DATA) modeler_type = 'https://data.authentise.com/modeler-type/728d2c92-75b6-4c67-a1e4-3e587c32e375/' capture_type = 'https://data.authentise.com/capture-type/98f40df0-318f-4ac0-87f5-765430d1d127/' # create the mode1ler modeler = _create_modeler(modeler_type)['location'] pprint.pprint('Created modeler uri:{}'.format(modeler)) # create a capture capture, upload_url = _create_capture(modeler, capture_type) # upload the status file _upload_capture(capture, upload_url, CAPTURE_PATH) # list builds while we wait for the processing try: while True: builds = _list_builds() if len(builds['resources']) > 0: pprint.pprint(builds) else: pprint.pprint('no builds') time.sleep(5) except KeyboardInterrupt: pass # optional clean up _delete_modeler(modeler['location']) if __name__ == '__main__': main() This will look for a file at ``CAPTURE_PATH`` to upload, read it, gzip it and then upload it to the url given when the capture was created. The capture file will then be processed. While we wait for the processing we loop and list the builds. When the capture is successfully processed we should see a new build in our list. This indicates that the file was processed successfully and 3DIAX has detected the build in our status file The complete source code for our script is below .. sourcecode:: python #!/usr/bin/env python3 import gzip import pprint import json import requests import time """change these global variables for your environment""" DATA_URL = 'https://data.authentise.com/{}/' USERS_URL = 'https://users.authentise.com/sessions/' CAPTURE_PATH = 'modeler_status_1.log' HEADERS = { 'Content-Type' : 'application/json', 'Accept' : 'application/json', } USER_DATA = { 'username' : 'test@authentise.com', 'password' : '******', } session = requests.Session() def _make_request(method, url, payload=None, headers=HEADERS): response = session.request(method, url, json=payload, headers=HEADERS) if not response.ok: raise Exception("Bad response from {}: {}".format(url, response.json())) if method == 'POST': return response.headers elif method == 'PUT': return None elif method == 'DELETE': return None else: return response.json() def _list_capture_types(): return _make_request('GET', DATA_URL.format('capture-type')) def _list_modeler_types(): return _make_request('GET', DATA_URL.format('modeler-type')) def _create_modeler(modeler_type_uri): payload = { 'description' : 'some fake modeler', 'ip_address' : '127.0.0.1', 'name' : 'test modeler', 'serial_number' : '123', 'type' : modeler_type_uri, } return _make_request('POST', DATA_URL.format('modeler'), payload) def _delete_modeler(modeler): _make_request('DELETE', modeler) def _list_builds(): return _make_request('GET', DATA_URL.format('build')) def _get_capture_type_status_uri(modeler_types): for capture_types in modeler_types['resources']: for capture_type in capture_types['capture_types']: if capture_type['capture_type'] == 'catex.modeler.status': return capture_types['uri'], capture_type['uri'] def _create_capture(modeler, capture_type): payload = { 'modeler' : modeler, 'capture_type': capture_type } headers = _make_request('POST', DATA_URL.format('capture'), payload) capture_upload_url = headers["x-upload-location"] capture_uri = headers["Location"] return capture_uri, capture_upload_url def _upload_capture(capture, upload_url, path): with open(path, 'rb') as f: content = gzip.compress(f.read()) response = requests.put( upload_url, data=content, headers={"content-type": "application/x-gzip"} ) assert response.ok, response.text def main(): # authenticate _make_request('POST', USERS_URL, USER_DATA) # show the different capture types #pprint.pprint(_list_capture_types()) # show the different capture types #pprint.pprint(_list_modeler_types()) modeler_type = 'https://data.authentise.com/modeler-type/728d2c92-75b6-4c67-a1e4-3e587c32e375/' capture_type = 'https://data.authentise.com/capture-type/98f40df0-318f-4ac0-87f5-765430d1d127/' # create the mode1ler modeler = _create_modeler(modeler_type)['location'] pprint.pprint('Created modeler uri:{}'.format(modeler)) # create a capture capture, upload_url = _create_capture(modeler, capture_type) pprint.pprint('Created capture: {} with upload URL {}'.format(capture, upload_url)) # upload the status file _upload_capture(capture, upload_url, CAPTURE_PATH) # list builds while we wait for the processing try: while True: builds = _list_builds() if len(builds['resources']) > 0: pprint.pprint(builds) else: pprint.pprint('no builds') time.sleep(5) except KeyboardInterrupt: pass # optional clean up _delete_modeler(modeler) print('Deleted modeler {}'.format(modeler)) if __name__ == '__main__': main()