Skip to main content

Python SDK for Keyrunes Authorization System

Project description

Keyrunes SDK Python Client

Tests Coverage Python License

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

  1. Start local environment (Keyrunes + Postgres):
    docker-compose up -d
    
  2. Verify service health (API on port 3000):
    curl http://localhost:3000/api/health
    
  3. Run examples (use KEYRUNES_BASE_URL if 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 \poetry to 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, uses ADMIN_KEY from 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.py for 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: Login
  • register_user(username: str, email: str, password: str, **attributes) -> User: Register user
  • register_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 ID
  • get_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 user
  • password: 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 fails
  • NetworkError: 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's ADMIN_KEY)
  • **attributes: Additional attributes

Returns: Created User object with admin privileges

Exceptions:

  • AuthenticationError: If registration fails
  • AuthorizationError: If admin key is invalid
  • NetworkError: 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 token
  • NetworkError: 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 token
  • UserNotFoundError: If user does not exist
  • NetworkError: 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 ID
  • group_id: Group ID to verify

Returns: True if user belongs to the group, False otherwise

Exceptions:

  • AuthenticationError: If there is no valid token
  • GroupNotFoundError: If group does not exist or user is not in the group
  • NetworkError: 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. If None, returns groups of the current user

Returns: List of strings with group IDs

Exceptions:

  • AuthenticationError: If there is no valid token
  • UserNotFoundError: If user does not exist
  • NetworkError: 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 check
  • client: 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 model
  • Group: Group model
  • Token: Authentication token model
  • UserRegistration: User registration data
  • AdminRegistration: Admin registration data
  • LoginCredentials: Login credentials
  • GroupCheck: 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 exception
  • AuthenticationError: Authentication error
  • AuthorizationError: Authorization error
  • GroupNotFoundError: Group not found
  • UserNotFoundError: User not found
  • NetworkError: 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:

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:

  1. Fork the project
  2. Create a branch for your feature (git checkout -b feature/AmazingFeature)
  3. Commit your changes (git commit -m 'Add some AmazingFeature')
  4. Push to the branch (git push origin feature/AmazingFeature)
  5. Open a Pull Request

Support

Links


Made with love for the Keyrunes community

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

keyrunes_python_sdk-0.0.1.tar.gz (32.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

keyrunes_python_sdk-0.0.1-py3-none-any.whl (28.7 kB view details)

Uploaded Python 3

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

Hashes for keyrunes_python_sdk-0.0.1.tar.gz
Algorithm Hash digest
SHA256 1fd1dd5b832a3312c79a231b3fd9ff821b4765c39ce4ef18c2a55c1de7117911
MD5 9872f172c8c59d38fd30dd721c7783fa
BLAKE2b-256 e62e1cd75e4ab53c7430efe17e2adc8f03f85115bd57d83c83f2a2e8aa95f68d

See more details on using hashes here.

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

Hashes for keyrunes_python_sdk-0.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 840007dc8c1f1349dbec821f986d39870c02348526b951430d3f26a72fe8f07f
MD5 f1e688fa2c43ea3e474ae2c63096958d
BLAKE2b-256 79894dcb1a2eb06b685a06d725bf3b399de40ba23f20d0a89b2f266b5c525129

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page