Python SDK for Keyrunes Authorization System
Project description
Keyrunes SDK Python Client
Python SDK for integration with the Keyrunes Authorization System, a modern high-performance authorization system built in Rust.
Features
- 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
Installation
Using Poetry (recommended)
poetry add keyrunes-sdk
Using pip
pip install keyrunes-sdk
Testing Examples Locally
- 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.
Tests and Coverage
- Run tests (uses pytest addopts already configured with coverage):
poetry run task test
- Run tests and generate explicit coverage:
poetry run task cov
Ready-to-use Objects for Testing Login/Registration
Use examples/test_objects.py to generate unique payloads when testing manually and see usage examples:
Available Functions
user_registration_payload(suffix: str | None = None, password: str | None = None) -> dict
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', ...}
login_payload(email: str, password: str) -> dict
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.
Quick Start
Configuration with Custom Domain
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)
Initializing the Client
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
pass
Global Client (Recommended)
The 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!
Authentication
Login
# 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
User Registration
# 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}")
Admin Registration
# 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}")
Group Verification
Manual Verification
# 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 User Groups
# 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}")
Using Decorators
@require_group - Check Group
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")
Multiple Groups (ANY)
# 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)
Multiple Groups (ALL)
# 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)
@require_admin - Check Admin
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")
Decorator with Client in Kwargs
# 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)
API Reference
KeyrunesClient
Authentication Methods
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
User Methods
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
Complete API Reference
KeyrunesClient Methods
Authentication
login(username: str, password: str) -> Token
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'}")
Registration
register_user(username: str, email: str, password: str, **attributes: Any) -> User
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"
)
register_admin(username: str, email: str, password: str, admin_key: str, **attributes: Any) -> User
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"
)
User Query
get_current_user() -> User
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}")
get_user(user_id: str) -> User
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")
Group Verification
has_group(user_id: str, group_id: str) -> bool
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")
get_user_groups(user_id: Optional[str] = None) -> List[str]
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")
Utility Methods
set_token(token: str) -> None
Manually sets the authentication token in the client.
Parameters:
token: JWT authentication token
Example:
client.set_token("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...")
clear_token() -> None
Removes the authentication token from the client.
Example:
client.clear_token()
close() -> None
Closes the HTTP session of the client. Useful for releasing resources.
Example:
client.close()
Context Manager
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
Decorators
@require_group
@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
@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")
Models (Pydantic)
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
Using with Flask, FastAPI or Django
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.
Exceptions
KeyrunesError: Base exceptionAuthenticationError: Authentication errorAuthorizationError: Authorization errorGroupNotFoundError: Group not foundUserNotFoundError: User not foundNetworkError: Network error
Development
Setup
# 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 Tests
# 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.py
Local Testing with Docker Compose
Test the library against a real Keyrunes instance running locally:
1. Start Keyrunes
# Start all services (Keyrunes, PostgreSQL, Redis)
docker-compose up -d
# Check status
docker-compose ps
# View logs
docker-compose logs -f keyrunes
Available services:
- Keyrunes API: http://localhost:3000
- PostgreSQL: localhost:5432
2. Run Integration Tests
# Complete test script
poetry run python examples/test_local.py
# Or using taskipy
poetry run task test-local
3. Run Examples
# Basic usage example
poetry run python examples/basic_usage.py
# Or using taskipy
poetry run task example-basic
4. Stop Services
# Stop containers
docker-compose down
# Stop and remove volumes
docker-compose down -v
Linting and Formatting
# 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_sdk
Tests
The 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
Test Structure
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
Security
- All passwords must have at least 8 characters
- JWT tokens are used for authentication
- HTTPS is recommended for production
- Email validation using email-validator
License
MIT License - see LICENSE for more details.
Contributing
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
Support
Links
Made with love for the Keyrunes community
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 keyrunes_python_sdk-0.0.1.tar.gz.
File metadata
- Download URL: keyrunes_python_sdk-0.0.1.tar.gz
- Upload date:
- Size: 32.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/2.1.3 CPython/3.13.7 Linux/6.17.9-arch1-1
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1fd1dd5b832a3312c79a231b3fd9ff821b4765c39ce4ef18c2a55c1de7117911
|
|
| MD5 |
9872f172c8c59d38fd30dd721c7783fa
|
|
| BLAKE2b-256 |
e62e1cd75e4ab53c7430efe17e2adc8f03f85115bd57d83c83f2a2e8aa95f68d
|
File details
Details for the file keyrunes_python_sdk-0.0.1-py3-none-any.whl.
File metadata
- Download URL: keyrunes_python_sdk-0.0.1-py3-none-any.whl
- Upload date:
- Size: 28.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/2.1.3 CPython/3.13.7 Linux/6.17.9-arch1-1
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
840007dc8c1f1349dbec821f986d39870c02348526b951430d3f26a72fe8f07f
|
|
| MD5 |
f1e688fa2c43ea3e474ae2c63096958d
|
|
| BLAKE2b-256 |
79894dcb1a2eb06b685a06d725bf3b399de40ba23f20d0a89b2f266b5c525129
|