REVEN-Axion 2018v1.5.0
Licenses and the python API

When using python scripts, one must take care that connecting to a project through the API may require a license token according to the rules detailed on the licensing page.

This page is intended to give you general information and advice regarding the interaction between licenses and the python API.

Checking license availability

You can programmatically check how many license tokens you have left:

import reven_api
launcher = reven_api.launcher_connection("localhost", 8080)
print(launcher.system_licenses_info().mono_left) # will print the number of remaining mono license tokens
print(launcher.system_licenses_info().team_left) # same with team license tokens

You can use this to check whether or not you still have license tokens available before taking certain actions:

def check_license(launcher):
return launcher.system_licenses_info().mono_left != 0
def try_license(launcher):
if not check_license(launcher):
throw RuntimeError("No license available")

Note that the above is not safe with regards to concurrency. In concurrent contexts, another script could take the license token between your call to try_license and the actual action that requires the license.

The currently accepted way to "test and lock" a license is using a try: block around the connection attempt:

import reven
try:
p = reven.Project(host, port)
# License taken here
# do something with p
print(p.traces())
except reven.reven_api.LicenseError:
print("No license available")

Getting the progress of an execution without license token

You can programmatically check the execution status of a running server even if you have no available license token.

The reven_api.LicenseError contains the required information to construct an ExecutionProgress object corresponding to the current progress of the server:

def get_execution_status(host, port):
try:
project = reven.Project(host, port)
return project.execution_status() # we had a license, normally return the ExecutionStatus
except reven.reven_api.LicenseError as le:
# no license, return the ExecutionStatus from the LicenseError
return reven.ExecutionProgress.from_license_error(le)

This function allows you to monitor the status of the running servers, for instance using the low-level python API:

launcher = reven_api.launcher_connection(host, 8080)
def print_status_servers(launcher):
for server in launcher.list_servers():
project = server.project
port = server.reven_server.port
try:
status = get_execution_status(host, port)
print("{} on {}: {}".format(project, port, status))
except RuntimeError as re:
print("{} on {}: an error occurred ({})".format(project, port, re))
while True:
print_status_servers(launcher)
print("")
time.sleep(4)

Note that the get_execution_status method will take a license token if there is one available, which can prevent other python scripts or an Axion client to connect. This is why the loop that calls print_status_servers() waits some time between two calls: it allows other clients to connect during the call to sleep.

You can find the full code for this example on this page.

Assignation costs two connections

The following python code does not work as intended with a single license:

import reven
p = reven.Project(host, port1) # first connection established
p = reven.Project(host, port2) # Here, python does the following:
# 1. instantiate new Project object and attempt to connect to server
# 2. At this point, the first Project object is still referenced by p, so it still owns the license token
# 3. As a result, connection to the server at port2 fails
# 4. the failed connection is assigned to p

From here, the old value of p which contained the first connection should be released. However, there are still other possible issues.

You can fix this issue by explicitly disconnecting from p before reassigning it:

import reven
p = reven.Project(host, port1) # first connection established
p.disconnect()
p = reven.Project(host, port2) # second connection established
[...]
p.disconnect()

Shell leaks

Some interactive python shells may keep references to past objects. This is the case of ipython, for instance:

# on ipython
reven.Project(host, port1) # First connection established, reference to object is kept by ipython shell
reven.Project(host, port2) # Second connection refused

This one is harder to fix, since it's not obvious how to access the reference that ipython keeps. It is best to avoid creating unbound temporaries in ipython. Note that closing the shell will return the license token(s).

Plugin leaks

Connections that are opened in plugins must be closed.

# bad_plugin.py
import reven
project = None
def axion_connected():
global project
host, port = axion.connection_info()
project = reven.Project(str(host), port)
def axion_disconnected():
# do nothing, leak the connection!
# good_plugin.py
...
def axion_disconnected():
if project is not None:
project.disconnect()

Exception leaks

Throwing an exception may keep a reference to a connection. This is mostly a problem in plugins.

# ugly_plugin.py
def axion_connected():
global project
host, port = axion.connection_info()
p = reven.Project(str(host), port)
raise RuntimeError("Oops") # p will keep the connection
project = p
# p is kept in the stack frame referenced by the exception
def axion_disconnected():
if project is not None:
project.disconnect()

Automatic scope management

When doing explicit connection management (calling explicitly Project.disconnect()), handling the control-flow can be tricky.

Another way would be to use a "with ... as ...:" construct, which would allow you to release a connection when leaving a scope, regardless of whether or not exceptions occur. To use this construct, you need to build a wrapper class:

# file reven_connection_handler.py
import reven
class RevenConnectionHandler(object):
"""
RevenConnectionHandler is a wrapper around a reven.Project object in order to ensure that the connection is released
when leaving a particular scope.
Usage:
>>> import reven
>>> with RevenConnectionHandler(host, port) as project: # The connection becomes valid here
>>> trace = project.trace()[0] # Do something with project
>>> point = trace.point(0, 0) # do something with project
>>> ### The connecion is automatically released at the end of this scope
>>> # project has been released here
"""
def __init__(self, host, port):
self.host = host
self.port = port
def __enter__(self):
print("Establishing reven connection")
self.project = reven.Project(self.host, self.port)
return self.project
def __exit__(self, exc_type, exc_val, exc_tb):
print("Closing reven connection")
self.project.disconnect()