Python SDK for integration with the Keyrunes Authorization System, a modern high-performance authorization system built in Rust.
- Complete Authentication: Login, user and admin registration
- Group Verification: Check group membership
- Decorators: Ready-to-use authorization decorators (
@require_group,@require_admin) - Type Hints: Fully typed with mypy support
- Pydantic Models: Automatic data validation
poetry add keyrunes-sdkpip install keyrunes-sdk- Start local environment (Keyrunes + Postgres):
docker-compose up -d
- Verify service health (API on port 3000):
curl http://localhost:3000/api/health
- Run examples (use
KEYRUNES_BASE_URLif you need to adjust the URL):KEYRUNES_BASE_URL=http://localhost:3000 poetry run python examples/test_local.py poetry run python examples/basic_usage.py poetry run python examples/global_client_usage.py
Tip: if your shell has an alias for
poetry, use\poetryto bypass it.
- Run tests (uses pytest addopts already configured with coverage):
poetry run task test - Run tests and generate explicit coverage:
poetry run task cov
Use examples/test_objects.py to generate unique payloads when testing manually and see usage examples:
Generates a dictionary with user registration data. Each call generates a unique suffix (UUID) and a random password to avoid conflicts.
Parameters:
suffix(optional): Custom suffix. If not provided, generates a random UUID.password(optional): Custom password. If not provided, generates a secure random password (12 characters).
Returns: Dictionary with username, email, password, department, role.
Example:
from examples.test_objects import user_registration_payload
user_data = user_registration_payload()
# {'username': 'user_a1b2c3', 'email': 'user_a1b2c3@example.com', 'password': 'random_generated_password', ...}admin_registration_payload(suffix: str | None = None, admin_key: str | None = None, password: str | None = None) -> dict
Generates a dictionary with admin registration data. Uses ADMIN_KEY from environment if available and generates random password.
Parameters:
suffix(optional): Custom suffix.admin_key(optional): Admin key. If not provided, usesADMIN_KEYfrom environment or default value.password(optional): Custom password. If not provided, generates a secure random password (12 characters).
Returns: Dictionary with username, email, password, admin_key.
Example:
from examples.test_objects import admin_registration_payload
admin_data = admin_registration_payload()
# {'username': 'admin_x9y8z7', 'email': 'admin_x9y8z7@example.com', 'password': 'random_generated_password', ...}Generates a dictionary with login credentials in the format expected by the API.
Parameters:
email: User email for login.password: User password (required).
Returns: Dictionary with identity (email) and password.
Example:
from examples.test_objects import login_payload, user_registration_payload
user_data = user_registration_payload()
login_data = login_payload(email=user_data["email"], password=user_data["password"])
# {'identity': 'user_a1b2c3@example.com', 'password': 'random_generated_password'}Note: The user_registration_payload() and admin_registration_payload() functions now automatically generate random passwords. You can pass a custom password if needed.
The SDK works with both local instances and custom domains in production:
Local (development):
from keyrunes_sdk import KeyrunesClient
client = KeyrunesClient(base_url="http://localhost:3000")Production (custom domain):
from keyrunes_sdk import KeyrunesClient
# Use your Keyrunes domain
client = KeyrunesClient(
base_url="https://auth.yourdomain.com",
api_key="your-optional-api-key" # If needed
)Environment variable:
import os
from keyrunes_sdk import KeyrunesClient
# Configure via environment variable
KEYRUNES_URL = os.getenv("KEYRUNES_BASE_URL", "http://localhost:3000")
client = KeyrunesClient(base_url=KEYRUNES_URL)from keyrunes_sdk import KeyrunesClient
# Create client
client = KeyrunesClient(
base_url="https://keyrunes.example.com",
api_key="your-optional-api-key"
)
# Or use as context manager
with KeyrunesClient(base_url="https://keyrunes.example.com") as client:
# Your code here
passThe most elegant way to use the library is to configure a global client once and use it throughout the project without passing the client in each decorator:
from keyrunes_sdk import configure, require_group, require_admin
# Configure ONCE at application startup
client = configure("https://keyrunes.example.com")
client.login("admin@example.com", "password")
# Now use decorators WITHOUT passing the client!
@require_group("admins")
def delete_user(user_id: str):
print(f"Deleting user {user_id}")
@require_admin()
def system_config(user_id: str):
print(f"Configuring system")
# Use functions normally
delete_user(user_id="user123") # No client needed!
system_config(user_id="admin123") # No client needed!Example with multiple files:
# config.py
from keyrunes_sdk import configure
def init_app():
client = configure("https://keyrunes.example.com")
client.login("user@example.com", "password")
# services/admin.py
from keyrunes_sdk import require_group
@require_group("admins") # No client needed!
def delete_user(user_id: str):
pass
# main.py
from config import init_app
from services.admin import delete_user
init_app() # Configure once
delete_user(user_id="123") # Use anywhere!Tip: See
examples/global_client_usage.pyfor a complete example!
# Login and get token
token = client.login("user@example.com", "password123")
print(f"Token: {token.access_token}")
print(f"User: {token.user.username}")
# Token is automatically configured in the client# Register new user
user = client.register_user(
username="newuser",
email="newuser@example.com",
password="securepass123",
department="Engineering", # Additional attributes
role="Developer"
)
print(f"User created: {user.username}")# Register admin (requires admin key)
admin = client.register_admin(
username="adminuser",
email="admin@example.com",
password="securepass123",
admin_key="secret-admin-key"
)
print(f"Admin created: {admin.username}")# Login first
client.login("user@example.com", "password")
# Check if user belongs to a group
has_access = client.has_group("user123", "admins")
if has_access:
print("User has admin access!")
else:
print("Access denied")# Get current user groups
my_groups = client.get_user_groups()
print(f"My groups: {my_groups}")
# Get groups of another user
user_groups = client.get_user_groups("user123")
print(f"User groups: {user_groups}")from keyrunes_sdk import KeyrunesClient, require_group
client = KeyrunesClient("https://keyrunes.example.com")
client.login("admin@example.com", "password")
# Decorator: user needs to be in "admins" group
@require_group("admins", client=client)
def delete_user(user_id: str):
print(f"Deleting user {user_id}")
# Deletion code here
# Executes if user has the group, otherwise raises AuthorizationError
delete_user(user_id="user123")# User needs to be in ANY of the groups
@require_group("admins", "moderators", all_groups=False)
def moderate_content(user_id: str, client: KeyrunesClient):
print(f"Moderating content for {user_id}")
moderate_content(user_id="user123", client=client)# User needs to be in ALL groups
@require_group("admins", "verified", all_groups=True)
def sensitive_operation(user_id: str, client: KeyrunesClient):
print(f"Sensitive operation for {user_id}")
sensitive_operation(user_id="user123", client=client)from keyrunes_sdk import require_admin
# Only admins can execute
@require_admin(client=client)
def system_configuration(user_id: str):
print(f"Configuring system for admin {user_id}")
system_configuration(user_id="admin123")# Pass client as function parameter
@require_group("admins")
def admin_function(user_id: str, client: KeyrunesClient):
print(f"Admin function for {user_id}")
admin_function(user_id="user123", client=client)login(username: str, password: str) -> Token: Loginregister_user(username: str, email: str, password: str, **attributes) -> User: Register userregister_admin(username: str, email: str, password: str, admin_key: str, **attributes) -> User: Register admin
get_user(user_id: str) -> User: Get user by IDget_current_user() -> User: Get current user (logged in)get_user_groups(user_id: Optional[str] = None) -> List[str]: Get user groups
Authenticates a user and returns an access token. The token is automatically configured in the client.
Parameters:
username: Username or email of the userpassword: User password
Returns: Token object with access_token, token_type, expires_in, refresh_token (optional) and user (optional)
Exceptions:
AuthenticationError: If credentials are invalid
Example:
token = client.login("user@example.com", "password123")
print(f"Token: {token.access_token}")
print(f"User: {token.user.username if token.user else 'N/A'}")Registers a new user in the system.
Parameters:
username: Username (3-50 characters)email: User email (validated)password: Password (minimum 8 characters)**attributes: Additional attributes (e.g.,department="Engineering",role="Developer")
Returns: Created User object
Exceptions:
AuthenticationError: If registration failsNetworkError: If there is a network error or unexpected response format
Example:
user = client.register_user(
username="newuser",
email="newuser@example.com",
password="securepass123",
department="Engineering",
role="Developer"
)Registers a new admin user in the system.
Parameters:
username: Username (3-50 characters)email: Admin email (validated)password: Password (minimum 8 characters)admin_key: Admin registration key (must match server'sADMIN_KEY)**attributes: Additional attributes
Returns: Created User object with admin privileges
Exceptions:
AuthenticationError: If registration failsAuthorizationError: If admin key is invalidNetworkError: If there is a network error or unexpected response format
Example:
admin = client.register_admin(
username="adminuser",
email="admin@example.com",
password="securepass123",
admin_key="secret-admin-key"
)Gets information about the currently authenticated user.
Returns: User object of the current user
Exceptions:
AuthenticationError: If there is no valid tokenNetworkError: If there is a network error
Example:
user = client.get_current_user()
print(f"User: {user.username}, Email: {user.email}")
print(f"Groups: {user.groups}")Gets information about a specific user by ID.
Parameters:
user_id: User ID
Returns: User object
Exceptions:
AuthenticationError: If there is no valid tokenUserNotFoundError: If user does not existNetworkError: If there is a network error
Example:
user = client.get_user("user123")Checks if a user belongs to a specific group.
Parameters:
user_id: User IDgroup_id: Group ID to verify
Returns: True if user belongs to the group, False otherwise
Exceptions:
AuthenticationError: If there is no valid tokenGroupNotFoundError: If group does not exist or user is not in the groupNetworkError: If there is a network error
Example:
is_admin = client.has_group("user123", "admins")
if is_admin:
print("User has admin privileges")Gets the list of groups for a user.
Parameters:
user_id(optional): User ID. IfNone, returns groups of the current user
Returns: List of strings with group IDs
Exceptions:
AuthenticationError: If there is no valid tokenUserNotFoundError: If user does not existNetworkError: If there is a network error
Example:
# Current user groups
my_groups = client.get_user_groups()
# Another user's groups
user_groups = client.get_user_groups("user123")Manually sets the authentication token in the client.
Parameters:
token: JWT authentication token
Example:
client.set_token("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...")Removes the authentication token from the client.
Example:
client.clear_token()Closes the HTTP session of the client. Useful for releasing resources.
Example:
client.close()The client can be used as a context manager to ensure automatic closing:
with KeyrunesClient(base_url="https://keyrunes.example.com") as client:
token = client.login("user@example.com", "password")
user = client.get_current_user()
# Client is automatically closed when exiting the block@require_group(*group_ids, client=None, user_id_param="user_id", all_groups=False)Parameters:
*group_ids: Group IDs to checkclient: KeyrunesClient instance (optional if passed via kwargs)user_id_param: Name of the parameter containing user_id (default: "user_id")all_groups: If True, user needs ALL groups; if False, ANY group (default: False)
@require_admin(client=None, user_id_param="user_id")Parameters:
client: KeyrunesClient instance (optional if passed via kwargs)user_id_param: Name of the parameter containing user_id (default: "user_id")
Pydantic models are provided for convenience, but are optional. They are useful for:
- Data validation before sending to the API
- Parsing API responses
- Type hints and IDE autocomplete
- Data structure consistency
Available models:
User: User modelGroup: Group modelToken: Authentication token modelUserRegistration: User registration dataAdminRegistration: Admin registration dataLoginCredentials: Login credentialsGroupCheck: Group verification result
If you are using Flask, FastAPI or Django, you can:
Option 1: Use SDK models and map to your models
from keyrunes_sdk import KeyrunesClient
from keyrunes_sdk.models import User
from your_app.models import MyUser # Your SQLAlchemy/Django ORM model
client = KeyrunesClient("https://keyrunes.example.com")
token = client.login("user@example.com", "password")
# Get data from Keyrunes
keyrunes_user = client.get_current_user() # Returns SDK User
# Map to your model
my_user = MyUser(
id=keyrunes_user.id,
username=keyrunes_user.username,
email=keyrunes_user.email,
groups=keyrunes_user.groups,
# Add your own fields
created_at=datetime.now(),
# ... other fields from your model
)Option 2: Work with dictionaries
from keyrunes_sdk import KeyrunesClient
client = KeyrunesClient("https://keyrunes.example.com")
response = client._make_request("GET", "/api/v1/users/me")
# response is a dict, use as needed
user_data = response # {'id': '...', 'username': '...', ...}Option 3: Use only SDK models
SDK models are Pydantic, so they work well with FastAPI directly:
from fastapi import FastAPI
from keyrunes_sdk import KeyrunesClient
from keyrunes_sdk.models import User
app = FastAPI()
client = KeyrunesClient("https://keyrunes.example.com")
@app.get("/me", response_model=User)
async def get_current_user():
return client.get_current_user()Note: SDK models are mainly for validation and parsing. You can add your own fields in your project models (Flask-SQLAlchemy, Django ORM, etc.) and map Keyrunes data as needed.
KeyrunesError: Base exceptionAuthenticationError: Authentication errorAuthorizationError: Authorization errorGroupNotFoundError: Group not foundUserNotFoundError: User not foundNetworkError: Network error
# Clone repository
git clone https://github.com/Keyrunes/keyrunes-python-sdk.git
cd keyrunes-python-sdk
# Install dependencies
poetry install
# Activate virtual environment
poetry shell# Run all tests
poetry run pytest
# With verbose
poetry run pytest -v
# With coverage
poetry run pytest --cov=keyrunes_sdk --cov-report=html
# Run specific tests
poetry run pytest tests/test_client.py
poetry run pytest tests/test_decorators.py
poetry run pytest tests/test_models.pyTest the library against a real Keyrunes instance running locally:
# Start all services (Keyrunes, PostgreSQL, Redis)
docker-compose up -d
# Check status
docker-compose ps
# View logs
docker-compose logs -f keyrunesAvailable services:
- Keyrunes API: http://localhost:3000
- PostgreSQL: localhost:5432
# Complete test script
poetry run python examples/test_local.py
# Or using taskipy
poetry run task test-local# Basic usage example
poetry run python examples/basic_usage.py
# Or using taskipy
poetry run task example-basic# Stop containers
docker-compose down
# Stop and remove volumes
docker-compose down -v# Black (formatting)
poetry run black keyrunes_sdk tests
# isort (organize imports)
poetry run isort keyrunes_sdk tests
# flake8 (linting)
poetry run flake8 keyrunes_sdk tests
# mypy (type checking)
poetry run mypy keyrunes_sdkThe library has 86% test coverage using:
- pytest: Test framework
- factory-boy: Factories for creating test data
- faker: Fake data generation for tests
- pytest-cov: Code coverage
- pytest-mock: Mocking
tests/
├── __init__.py
├── conftest.py # Fixtures and configurations
├── factories.py # Factory Boy factories
├── test_client.py # Client tests
├── test_decorators.py # Decorator tests
└── test_models.py # Model tests
- All passwords must have at least 8 characters
- JWT tokens are used for authentication
- HTTPS is recommended for production
- Email validation using email-validator
MIT License - see LICENSE for more details.
Contributions are welcome! Please:
- Fork the project
- Create a branch for your feature (
git checkout -b feature/AmazingFeature) - Commit your changes (
git commit -m 'Add some AmazingFeature') - Push to the branch (
git push origin feature/AmazingFeature) - Open a Pull Request
Made with love for the Keyrunes community