Python client for Táillí License Manager - offline seat-based licensing
Project description
Táillí License Client for Python
A friendly Python client for the Táillí License Manager. Manage seats and API keys for offline/air-gapped deployments with minimal code.
Installation
pip install tailli-license
Or install from source:
cd license-manager/clients/python
pip install -e .
Quick Start
from tailli_license import LicenseClient
# Connect to your License Manager
client = LicenseClient(
base_url="http://localhost:8787",
product_id="my-product"
)
# Use a seat with automatic cleanup
with client.seat() as seat:
print(f"Allocated seat: {seat.seat_id}")
# Do your licensed work here
run_my_application()
# Seat automatically released when done
Features
- Simple context manager - Seats are automatically released, even on exceptions
- Auto-refresh - Tokens are refreshed in the background, no manual work needed
- Zero dependencies - Uses only Python standard library
- Type hints - Full type annotations for IDE support
- Friendly errors - Clear exceptions for common issues (seat limit, expired license)
Usage Examples
Check License Status
from tailli_license import LicenseClient
client = LicenseClient("http://localhost:8787", product_id="video-processor")
status = client.status()
print(f"Product: {status['productId']}")
print(f"Seats: {status['seatsInUse']}/{status['seats']} in use")
print(f"Expires: {status['expiry']}")
print(f"Features: {', '.join(status['features'])}")
Use a Seat (Recommended)
The context manager is the cleanest way to use seats:
from tailli_license import LicenseClient, SeatLimitError, LicenseExpiredError
client = LicenseClient("http://localhost:8787", product_id="video-processor")
try:
with client.seat() as seat:
print(f"Got seat {seat.seat_id}")
# Your licensed code here
process_videos()
except SeatLimitError:
print("All seats are in use. Please try again later.")
except LicenseExpiredError:
print("Your license has expired. Please contact support.")
Add Context for Auditing
Track who's using seats for compliance and debugging:
with client.seat(context={"user": "alice", "workstation": "lab-pc-42"}) as seat:
run_analysis()
Manual Seat Management
For more control, manage seats directly:
# Allocate a seat
seat = client.allocate(context={"user": "bob"})
try:
# Do work...
for batch in data_batches:
process(batch)
finally:
# Always release when done
seat.release()
Disable Auto-Refresh
For short operations, you might not need background refresh:
with client.seat(auto_refresh=False) as seat:
quick_operation() # Done in < 1 minute
Check Feature Entitlements
See what features your license enables:
entitlements = client.get_entitlements()
if "ai-enhancement" in entitlements["features"]:
enable_ai_features()
if "batch-processing" in entitlements["features"]:
enable_batch_mode()
print(f"API keys: {entitlements['keysIssued']}/{entitlements['maxKeys']} used")
Verify User API Keys
Validate JWTs issued by the License Manager:
try:
payload = client.verify_key(user_provided_token)
print(f"Valid token for product: {payload['productId']}")
print(f"Features: {payload['features']}")
except LicenseError:
print("Invalid or expired token")
Error Handling
The client provides specific exceptions for common scenarios:
from tailli_license import (
LicenseClient,
LicenseError, # Base class for all errors
SeatLimitError, # All seats are in use
LicenseExpiredError, # License has expired
ConnectionError, # Can't reach License Manager
)
client = LicenseClient("http://localhost:8787", product_id="my-product")
try:
with client.seat() as seat:
do_work()
except SeatLimitError:
# All seats taken - maybe queue the request or notify user
queue_for_later()
except LicenseExpiredError:
# License expired - notify admin
notify_admin("License expired!")
except ConnectionError:
# Can't reach LM - maybe it's down or network issue
use_cached_state_or_fail_gracefully()
except LicenseError as e:
# Other license-related errors
log_error(f"License error: {e}")
Configuration
Client Options
client = LicenseClient(
base_url="http://localhost:8787", # License Manager URL
product_id="my-product", # Your product ID
timeout=30.0, # Request timeout in seconds
)
Seat Options
with client.seat(
context={"user": "alice"}, # Metadata for auditing
auto_refresh=True, # Keep token fresh (default: True)
) as seat:
pass
Logging
Enable debug logging to see what's happening:
import logging
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger("tailli_license")
logger.setLevel(logging.DEBUG)
Thread Safety
The LicenseClient is thread-safe. You can share one client across threads:
from concurrent.futures import ThreadPoolExecutor
client = LicenseClient("http://localhost:8787", product_id="my-product")
def worker(task_id):
with client.seat(context={"task": task_id}) as seat:
process_task(task_id)
with ThreadPoolExecutor(max_workers=4) as executor:
executor.map(worker, range(10))
Each seat() call gets its own seat allocation.
API Reference
LicenseClient
| Method | Description |
|---|---|
status() |
Get license status (seats, expiry, features) |
allocate(context, auto_refresh) |
Allocate a seat, returns Seat object |
seat(context, auto_refresh) |
Context manager for seat allocation |
verify_key(token) |
Verify a user API key (JWT) |
get_entitlements() |
Get feature entitlements |
Seat
| Method/Attribute | Description |
|---|---|
seat_id |
Unique identifier for this seat |
token |
Current authentication token |
expires_at |
Token expiration time |
release() |
Release the seat back to the pool |
refresh() |
Manually refresh the token |
License
MIT License - see the main Táillí repository for details.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file tailli_license-0.1.0.tar.gz.
File metadata
- Download URL: tailli_license-0.1.0.tar.gz
- Upload date:
- Size: 11.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
19d57bad04f23048ce801d5d5e1b25f7aed9332938d6f68b2d708822c89380bd
|
|
| MD5 |
ebd4ebebffdae1e2ab167c13922976df
|
|
| BLAKE2b-256 |
71e740bf46509a1722089a606a6cfaaac56d7b3b569202a9270eaed5bf6f0133
|
Provenance
The following attestation bundles were made for tailli_license-0.1.0.tar.gz:
Publisher:
publish-python-client.yml on RigrAI/tailli
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
tailli_license-0.1.0.tar.gz -
Subject digest:
19d57bad04f23048ce801d5d5e1b25f7aed9332938d6f68b2d708822c89380bd - Sigstore transparency entry: 919425160
- Sigstore integration time:
-
Permalink:
RigrAI/tailli@0f9788d3e8082d625358e58530b5ee79a39f9b6d -
Branch / Tag:
refs/tags/py-license-v0.1.0 - Owner: https://github.com/RigrAI
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-python-client.yml@0f9788d3e8082d625358e58530b5ee79a39f9b6d -
Trigger Event:
release
-
Statement type:
File details
Details for the file tailli_license-0.1.0-py3-none-any.whl.
File metadata
- Download URL: tailli_license-0.1.0-py3-none-any.whl
- Upload date:
- Size: 8.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
da60a37702b26a972a3819dada6494cbb4c107beb5581ae7acedd52de1393022
|
|
| MD5 |
792427118a2a397be855ab7c72eeda9d
|
|
| BLAKE2b-256 |
c6c668b4b58739bb10831eddb0dbbd4a346048294ecf990b9ee357c0aef33ca8
|
Provenance
The following attestation bundles were made for tailli_license-0.1.0-py3-none-any.whl:
Publisher:
publish-python-client.yml on RigrAI/tailli
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
tailli_license-0.1.0-py3-none-any.whl -
Subject digest:
da60a37702b26a972a3819dada6494cbb4c107beb5581ae7acedd52de1393022 - Sigstore transparency entry: 919425164
- Sigstore integration time:
-
Permalink:
RigrAI/tailli@0f9788d3e8082d625358e58530b5ee79a39f9b6d -
Branch / Tag:
refs/tags/py-license-v0.1.0 - Owner: https://github.com/RigrAI
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-python-client.yml@0f9788d3e8082d625358e58530b5ee79a39f9b6d -
Trigger Event:
release
-
Statement type: