This article will give step-by-step instructions on how to add software licensing (specifically, hardware-locked or node-locked licensing) to your Python app. There's also a full example app that you can download and play with without needing to add licensing to your app.
By the end of this article you'll have working licensing integrated with your application, and thus the ability to sell individual copies of your software.
This article will also cover the steps you need to take to bundle your Python app and licensing as a binary (using PyInstaller) making it easier to sell your work to your users.
This article shows you how to add hardware-locked licensing to your Python (using TurboActivate). To add floating-licensing to your Python app see the "Using TurboFloat with Python" article.
Before you can do anything, you need to login to your LimeLM account (or sign up). Then download TurboActivate for Windows, macOS (Mac OS X), Linux, or BSD. It contains the native library and source code examples needed to integrate hardware-locked licensing in your Python app:
Now we're going to walk you through each step you need to take in adding licensing, online activation, and timed-trials to your Python app. There are lots of ways you can add licensing to your app, but there are 2 popular styles:
Separate versions of your app. This style of licensing is where you build 2 versions of your product: a trial version and a full version.
Hybrid version of your app. This style of licensing is where your product is the trial version or the full version depending on whether the user is activated.
The first method (the separate versions method) is possible with TurboActivate, but we're not going to talk about it here because it's not very user friendly and it requires more work on your end. Instead we'll talk about making your app a hybrid Full/Trial app. That is, your users will be able to use your app in "trial mode" until either the trial expires or they purchase a product key and use it to activate your app.
If you haven't already signed up for LimeLM then sign up now. All plans have a 30-day free trial. Or, if you're just putting your toes in the water, there's even a free plan that has no time limit and doesn't require a credit card.
After you've created your account, download TurboActivate and extract it anywhere. After you extract it, inside the extracted folder you'll find 2 folders: the "API
" directory and the the "bin-*
" directory (for example, bin-windows
, for the Windows TurboActivate package).
Now you need to add TurboActivate to your app. You can do this one of 2 ways, either add "turboactivate" directory from from the "API/Python" project you extracted from the TurboActivate main package. Or you can install the TurboActivate Python class to be used globally:
pip install turboactivate
Or, to update the downloaded pip package do it like so:
pip install --upgrade turboactivate
If you haven't already created a new product in LimeLM, do it now. You can change any value later, so don't worry about making a mistake.
Go to your version page in LimeLM. Download the "TurboActivate.dat" file, and make a note of the Version GUID you see on your product version page (you'll be using it in the next step).
Copy the TurboActivate.dat file you just downloaded to sit alongside your "main" Python program. When you distribute your app to your customers you'll be including this TurboActivate.dat alongside your binary.
Side-note about TurboActivate.dat file: this file is an "information file" about your product version, and gives TurboActivate enough information to verify product keys and cryptographically signed activation data locally. It's a read-only file (license data will never be written to it).
TurboActivate uses native system calls to generate our proprietary "fingerprint" of the computer your customer is activating your software on. So, in addition to the "turboactivate" package that you either install from pip
(or download and extract from our TurboActivate package on your API page) you also need to put the TurboActivate.dll, libTurboActivate.so, or libTurboActivate.dylib in the same folder as your main Python script so that the TurboActivate Python package can make use of our native calls.
If you're playing around with our example Python app, the layout of the Python files and the native files is like so:
example.py
TurboActivate.dat
TurboActivate.dll
As you can see there's the "example.py
" file (which is the example app), there's the TurboActivate.dat file and the TurboActivate library file (in this case TurboActivate.dll).
Now, inside your app, you need to create a new TurboActivate object with the Version GUID found on the same page you downloaded the TurboActivate.dat from. In the example app we define the "ta
" variable in the "main" function and create TurboActivate object instance:
from turboactivate import (
TurboActivate,
IsGenuineResult,
TurboActivateError,
TurboActivateTrialExpiredError,
TA_USER,
TA_SYSTEM
)
#...
if __name__ == "__main__":
try:
# TODO: go to the version page at LimeLM and
# paste this GUID here
ta = TurboActivate("Paste GUID Here", TA_USER)
# ...
Replace the "Paste GUID Here" string with the Version GUID string your copied from your version page.
There are many ways you can use TurboActivate to add licensing to your application. For this example we're going to keep it simple.
The the example we're creating a simple variable "isGenuine
" to save whether the customer is activated and genuine. And then it's simply a matter of making a call to is_genuine_ex()
to see if the user is activated and to re-verify the activation with the LimeLM servers every 90 days, with a 14 day grace period:
# Don't use 0 for either of these values.
# We recommend 90, 14. But if you want to lower the values
# we don't recommend going below 7 days for each value.
# Anything lower and you're just punishing legit users.
DAYS_BETWEEN_CHECKS = 90
GRACE_PERIOD_LENGTH = 14
if __name__ == "__main__":
# support both Python 2 and 3
# for this simple example app
try:
input = raw_input
except NameError:
pass
# now begins the licensing bit of the code
isGenuine = False
try:
# TODO: go to the version page at LimeLM and
# paste this GUID here
ta = TurboActivate("Paste GUID Here", TA_USER)
# Check if we're activated, and every 90 days verify it with the activation servers
# In this example we won't show an error if the activation was done offline
# (see the 3rd parameter of the IsGenuine() function)
# https://wyday.com/limelm/help/offline-activation/
gen_r = ta.is_genuine_ex(DAYS_BETWEEN_CHECKS, GRACE_PERIOD_LENGTH, True)
isGenuine = (gen_r == IsGenuineResult.Genuine
or gen_r == IsGenuineResult.GenuineFeaturesChanged
# an internet error means the user is activated but
# TurboActivate failed to contact the LimeLM servers
or gen_r == IsGenuineResult.InternetError
)
if not isGenuine and ta.is_activated():
# There is still activation data on the computer, and it's valid.
# This means that IsGenuineEx() is saying "not activated" (a.k.a. TA_FAIL)
# because the customer blocked connections to the activation servers (intentionally or not)
# for nDaysBetweenChecks + nGraceDaysOnInetErr days.
# What you should do now is prompt the user telling them before they can use your app that they need
# to reverify with the activation servers.
print('You must reverify with the activation servers before you can use this app. ')
print('Type R and then press enter to retry after you\'ve ensured that you\'re connected to the internet. ')
print('Or to exit the app press X. ')
while True:
user_resp = sys.stdin.read(1)
if user_resp == 'x' or user_resp == 'X':
sys.exit("Exiting now. Bye.")
if user_resp == 'r' or user_resp == 'R':
# Now we're using TA_IsGenuine() to retry immediately. Note that we're not using
# TA_IsGenuineEx() because TA_IsGenuineEx() waits 5 hours after an internet failure
# before retrying to contact the servers. TA_IsGenuine() retries immediately.
igr = ta.is_genuine()
if igr == IsGenuineResult.Genuine or igr == IsGenuineResult.GenuineFeaturesChanged:
print('Successfully reverified with the servers! You can now continue to use the app!')
break
else:
print('Failed to reverify with the servers. ')
print('Make sure you\'re connected to the internet and that you\'re not blocking access to the activation servers. ')
print('Then press R to retry again. ')
else:
print('Invalid input. Press R to try to reverify with the servers. Press X to exit the app.')
except TurboActivateError as e:
sys.exit("Failed to check if activated: " + str(e))
This is a complete example showing how to check if the customer is genuinely activated, and how to handle the error cases. While it's longer than "toy" licensing solutions, it's built for the real world.
The code does the following:
It creates the new TurboActivate instance with your Version GUID.
Checks if the customer is activated and re-verifies with the servers every 90 days (with a 14-day grace period).
And if is_genuine_ex(x, y, z)
tells you the customer is not genuine then you can use is_activated()
to determine if they're "not genuine" because they were never activated or if it's because the customer has gone more than DaysBetweenChecks + GracePeriodLength
days since re-verifying with the activation servers.
And if it's a case where the customer must re-verify with the activation servers, then prompt them to do that (also included in the example project).
If the user has never activated, or if they've since deactivated, then you'll need to prompt the user to enter their product key. You can do this a couple of ways:
If you're just targeting Windows you can use our pre-built the TurboActivate Wizard to prompt the customer to enter their product key.
Or, prompt the user directly in your app and then save the product key (using the check_and_save_pkey()
function) and activate your app (using the activate()
function)
Here's an example of how you might prompt the user for their product key and activate the product key on the computer:
# Whether to prompt for the product key
prompt_for_key = False
if not isGenuine:
# ask the user if they want to enter their pkey
print('Would you like to enter your pkey (y/n) [n]: ')
prompt_res = sys.stdin.read(1)
if prompt_res != "" and prompt_res == "y":
prompt_for_key = True
else:
prompt_for_key = False
# Now actually prompt for the product key and try to activate
if prompt_for_key:
try:
# prompt the user for a product key
pkey = input('Enter your product key to activate: ')
if ta.check_and_save_pkey(pkey):
print("Product key saved successfully.")
else:
sys.exit("Product key was not valid for this product version")
except TurboActivateError as e:
sys.exit("Failed to check or save product key: " + str(e))
# try to activate the product key
try:
ta.activate()
isGenuine = True
print("Activated successfully!")
except TurboActivateError as e:
sys.exit("Failed to activate online: " + str(e))
TA_USER
vs. TA_SYSTEM
In the example above you can see we're using TA_USER
when creating the TurboActivate object instance (see step 6). This tells TurboActivate to store the activation data in directories that the user has the ability to write to. If you were using TA_SYSTEM
instead, this would tell TurboActivate to store the activation data to directories where all users on that machine would have the ability to read/write the activation data.
In both cases the activation data locks to the machine. The only difference is where the activation data is stored. We typically recommend using TA_SYSTEM
, however the only downside to using that flag is that the first time (and only the first time) you call a function with the TA_SYSTEM
flag your app needs "elevated" or "sudo" permission.
In this example we're going to use verified trials because they are accurate, fast, and allows you to track conversion of customers. Starting the verified trial and seeing how many days are remaining is as simple as this:
trial_days = 0
# We're going to use verified trials:
# https://wyday.com/limelm/help/trials/#verified
verified_trial = True
# Get the number of trial days remaining and print them
if not isGenuine:
try:
# Start or re-validate the trial if it has already started.
# This need to be called at least once before you can use
# any other trial functions.
ta.use_trial(verified_trial)
# Get the number of trial days remaining.
trial_days = ta.trial_days_remaining(verified_trial)
if trial_days > 0:
print("Trial days remaining %d" % trial_days)
else:
print("There are no trial days remaining. You must activate now to continue to use this app.")
except TurboActivateTrialExpiredError as e:
print("There are no trial days remaining. You must activate now to continue to use this app.")
except TurboActivateError as e:
print("Failed to start the trial: " + str(e))
There are multiple ways to distribute your Python app, but we'll focus on the most popular way: using PyInstaller.
Either install the PyInstaller like so:
pip install pyinstaller
Or update an existing PyInstaller installation:
pip install --upgrade pyinstaller
We recommend using at least version 3.4 of PyInstaller. You can check the version installed like so:
pip show pyinstaller
Name: PyInstaller Version: 3.4 Summary: PyInstaller bundles a Python application and all its dependencies into a single package. Home-page: http://www.pyinstaller.org Author: Giovanni Bajo, Hartmut Goebel, David Vierra, David Cortesi, Martin Zibricky Author-email: pyinstaller@googlegroups.com License: GPL license with a special exception which allows to use PyInstaller to build and distribute non-free programs (including commercial ones) Location: c:\program files\python37\lib\site-packages Requires: setuptools, pefile, macholib, altgraph, pywin32-ctypes Required-by:
Then creating the binary ready for distribution is as simple as first "cd
"ing into the directory where your Python script and TurboActivate library (TurboActivate.dll, libTurboActivate.so, or libTurboActivate.dylib) file is sitting as well as the TurboActivate.dat file:
cd c:\path\to\python-example\
Then, simply tell PyInstaller to include the TurboActivate library, TurboActivate.dat in the same folder as the compiled Python script:
pyinstaller --add-data "TurboActivate.dll;." --add-data "TurboActivate.dat;." example.py
Running this script on Windows will output a "dist" directory inside of which (a couple of levels deep) you'll find the executable ready to be bundled in an installer or executed as-is.