keyrunes-python-sdk 0.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- keyrunes_python_sdk-0.0.1.dist-info/LICENSE +661 -0
- keyrunes_python_sdk-0.0.1.dist-info/METADATA +880 -0
- keyrunes_python_sdk-0.0.1.dist-info/RECORD +10 -0
- keyrunes_python_sdk-0.0.1.dist-info/WHEEL +4 -0
- keyrunes_sdk/__init__.py +52 -0
- keyrunes_sdk/client.py +565 -0
- keyrunes_sdk/config.py +203 -0
- keyrunes_sdk/decorators.py +248 -0
- keyrunes_sdk/exceptions.py +43 -0
- keyrunes_sdk/models.py +100 -0
|
@@ -0,0 +1,880 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: keyrunes-python-sdk
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Python SDK for Keyrunes Authorization System
|
|
5
|
+
License: AGPL
|
|
6
|
+
Keywords: keyrunes,authorization,rbac,abac,security,authentication,permissions
|
|
7
|
+
Author: keyrunes
|
|
8
|
+
Author-email: contact@singularjourney.host
|
|
9
|
+
Maintainer: jonatasoli
|
|
10
|
+
Maintainer-email: contact@jonatasoli.dev
|
|
11
|
+
Requires-Python: >=3.10.1,<4.0
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: License :: Other/Proprietary License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
22
|
+
Classifier: Topic :: Security
|
|
23
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
24
|
+
Classifier: Topic :: System :: Systems Administration :: Authentication/Directory
|
|
25
|
+
Requires-Dist: httpx (>=0.28.1,<0.29.0)
|
|
26
|
+
Requires-Dist: pydantic[email] (>=2.0.0,<3.0.0)
|
|
27
|
+
Requires-Dist: pyjwt (>=2.9.0,<3.0.0)
|
|
28
|
+
Project-URL: Documentation, https://github.com/jonatasoli/keyrunes-python-sdk#readme
|
|
29
|
+
Project-URL: Homepage, https://keyrunes.com
|
|
30
|
+
Project-URL: Repository, https://github.com/jonatasoli/keyrunes-python-sdk
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
|
|
33
|
+
# Keyrunes SDK Python Client
|
|
34
|
+
|
|
35
|
+
[](https://github.com/Keyrunes/keyrunes-python-sdk/actions/workflows/ci.yml)
|
|
36
|
+
[](https://codecov.io/gh/Keyrunes/keyrunes-python-sdk)
|
|
37
|
+
[](https://www.python.org/downloads/)
|
|
38
|
+
[](LICENSE)
|
|
39
|
+
|
|
40
|
+
Python SDK for integration with the [Keyrunes Authorization System](https://github.com/Keyrunes/keyrunes), a modern high-performance authorization system built in Rust.
|
|
41
|
+
|
|
42
|
+
## Features
|
|
43
|
+
|
|
44
|
+
- Complete Authentication: Login, user and admin registration
|
|
45
|
+
- Group Verification: Check group membership
|
|
46
|
+
- Decorators: Ready-to-use authorization decorators (`@require_group`, `@require_admin`)
|
|
47
|
+
- Type Hints: Fully typed with mypy support
|
|
48
|
+
- Pydantic Models: Automatic data validation
|
|
49
|
+
|
|
50
|
+
## Installation
|
|
51
|
+
|
|
52
|
+
### Using Poetry (recommended)
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
poetry add keyrunes-sdk
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Using pip
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
pip install keyrunes-sdk
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Testing Examples Locally
|
|
65
|
+
|
|
66
|
+
1. Start local environment (Keyrunes + Postgres):
|
|
67
|
+
```bash
|
|
68
|
+
docker-compose up -d
|
|
69
|
+
```
|
|
70
|
+
2. Verify service health (API on port 3000):
|
|
71
|
+
```bash
|
|
72
|
+
curl http://localhost:3000/api/health
|
|
73
|
+
```
|
|
74
|
+
3. Run examples (use `KEYRUNES_BASE_URL` if you need to adjust the URL):
|
|
75
|
+
```bash
|
|
76
|
+
KEYRUNES_BASE_URL=http://localhost:3000 poetry run python examples/test_local.py
|
|
77
|
+
poetry run python examples/basic_usage.py
|
|
78
|
+
poetry run python examples/global_client_usage.py
|
|
79
|
+
```
|
|
80
|
+
> Tip: if your shell has an alias for `poetry`, use `\poetry` to bypass it.
|
|
81
|
+
|
|
82
|
+
## Tests and Coverage
|
|
83
|
+
|
|
84
|
+
- Run tests (uses pytest addopts already configured with coverage):
|
|
85
|
+
```bash
|
|
86
|
+
poetry run task test
|
|
87
|
+
```
|
|
88
|
+
- Run tests and generate explicit coverage:
|
|
89
|
+
```bash
|
|
90
|
+
poetry run task cov
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Ready-to-use Objects for Testing Login/Registration
|
|
94
|
+
|
|
95
|
+
Use `examples/test_objects.py` to generate unique payloads when testing manually and see usage examples:
|
|
96
|
+
|
|
97
|
+
### Available Functions
|
|
98
|
+
|
|
99
|
+
#### `user_registration_payload(suffix: str | None = None, password: str | None = None) -> dict`
|
|
100
|
+
Generates a dictionary with user registration data. Each call generates a unique suffix (UUID) and a random password to avoid conflicts.
|
|
101
|
+
|
|
102
|
+
**Parameters:**
|
|
103
|
+
- `suffix` (optional): Custom suffix. If not provided, generates a random UUID.
|
|
104
|
+
- `password` (optional): Custom password. If not provided, generates a secure random password (12 characters).
|
|
105
|
+
|
|
106
|
+
**Returns:** Dictionary with `username`, `email`, `password`, `department`, `role`.
|
|
107
|
+
|
|
108
|
+
**Example:**
|
|
109
|
+
```python
|
|
110
|
+
from examples.test_objects import user_registration_payload
|
|
111
|
+
|
|
112
|
+
user_data = user_registration_payload()
|
|
113
|
+
# {'username': 'user_a1b2c3', 'email': 'user_a1b2c3@example.com', 'password': 'random_generated_password', ...}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
#### `admin_registration_payload(suffix: str | None = None, admin_key: str | None = None, password: str | None = None) -> dict`
|
|
117
|
+
Generates a dictionary with admin registration data. Uses `ADMIN_KEY` from environment if available and generates random password.
|
|
118
|
+
|
|
119
|
+
**Parameters:**
|
|
120
|
+
- `suffix` (optional): Custom suffix.
|
|
121
|
+
- `admin_key` (optional): Admin key. If not provided, uses `ADMIN_KEY` from environment or default value.
|
|
122
|
+
- `password` (optional): Custom password. If not provided, generates a secure random password (12 characters).
|
|
123
|
+
|
|
124
|
+
**Returns:** Dictionary with `username`, `email`, `password`, `admin_key`.
|
|
125
|
+
|
|
126
|
+
**Example:**
|
|
127
|
+
```python
|
|
128
|
+
from examples.test_objects import admin_registration_payload
|
|
129
|
+
|
|
130
|
+
admin_data = admin_registration_payload()
|
|
131
|
+
# {'username': 'admin_x9y8z7', 'email': 'admin_x9y8z7@example.com', 'password': 'random_generated_password', ...}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
#### `login_payload(email: str, password: str) -> dict`
|
|
135
|
+
Generates a dictionary with login credentials in the format expected by the API.
|
|
136
|
+
|
|
137
|
+
**Parameters:**
|
|
138
|
+
- `email`: User email for login.
|
|
139
|
+
- `password`: User password (required).
|
|
140
|
+
|
|
141
|
+
**Returns:** Dictionary with `identity` (email) and `password`.
|
|
142
|
+
|
|
143
|
+
**Example:**
|
|
144
|
+
```python
|
|
145
|
+
from examples.test_objects import login_payload, user_registration_payload
|
|
146
|
+
|
|
147
|
+
user_data = user_registration_payload()
|
|
148
|
+
login_data = login_payload(email=user_data["email"], password=user_data["password"])
|
|
149
|
+
# {'identity': 'user_a1b2c3@example.com', 'password': 'random_generated_password'}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
**Note:** The `user_registration_payload()` and `admin_registration_payload()` functions now automatically generate random passwords. You can pass a custom password if needed.
|
|
153
|
+
|
|
154
|
+
## Quick Start
|
|
155
|
+
|
|
156
|
+
### Configuration with Custom Domain
|
|
157
|
+
|
|
158
|
+
The SDK works with both local instances and custom domains in production:
|
|
159
|
+
|
|
160
|
+
**Local (development):**
|
|
161
|
+
```python
|
|
162
|
+
from keyrunes_sdk import KeyrunesClient
|
|
163
|
+
|
|
164
|
+
client = KeyrunesClient(base_url="http://localhost:3000")
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
**Production (custom domain):**
|
|
168
|
+
```python
|
|
169
|
+
from keyrunes_sdk import KeyrunesClient
|
|
170
|
+
|
|
171
|
+
# Use your Keyrunes domain
|
|
172
|
+
client = KeyrunesClient(
|
|
173
|
+
base_url="https://auth.yourdomain.com",
|
|
174
|
+
api_key="your-optional-api-key" # If needed
|
|
175
|
+
)
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
**Environment variable:**
|
|
179
|
+
```python
|
|
180
|
+
import os
|
|
181
|
+
from keyrunes_sdk import KeyrunesClient
|
|
182
|
+
|
|
183
|
+
# Configure via environment variable
|
|
184
|
+
KEYRUNES_URL = os.getenv("KEYRUNES_BASE_URL", "http://localhost:3000")
|
|
185
|
+
client = KeyrunesClient(base_url=KEYRUNES_URL)
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Initializing the Client
|
|
189
|
+
|
|
190
|
+
```python
|
|
191
|
+
from keyrunes_sdk import KeyrunesClient
|
|
192
|
+
|
|
193
|
+
# Create client
|
|
194
|
+
client = KeyrunesClient(
|
|
195
|
+
base_url="https://keyrunes.example.com",
|
|
196
|
+
api_key="your-optional-api-key"
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
# Or use as context manager
|
|
200
|
+
with KeyrunesClient(base_url="https://keyrunes.example.com") as client:
|
|
201
|
+
# Your code here
|
|
202
|
+
pass
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### Global Client (Recommended)
|
|
206
|
+
|
|
207
|
+
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:
|
|
208
|
+
|
|
209
|
+
```python
|
|
210
|
+
from keyrunes_sdk import configure, require_group, require_admin
|
|
211
|
+
|
|
212
|
+
# Configure ONCE at application startup
|
|
213
|
+
client = configure("https://keyrunes.example.com")
|
|
214
|
+
client.login("admin@example.com", "password")
|
|
215
|
+
|
|
216
|
+
# Now use decorators WITHOUT passing the client!
|
|
217
|
+
@require_group("admins")
|
|
218
|
+
def delete_user(user_id: str):
|
|
219
|
+
print(f"Deleting user {user_id}")
|
|
220
|
+
|
|
221
|
+
@require_admin()
|
|
222
|
+
def system_config(user_id: str):
|
|
223
|
+
print(f"Configuring system")
|
|
224
|
+
|
|
225
|
+
# Use functions normally
|
|
226
|
+
delete_user(user_id="user123") # No client needed!
|
|
227
|
+
system_config(user_id="admin123") # No client needed!
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
**Example with multiple files:**
|
|
231
|
+
|
|
232
|
+
```python
|
|
233
|
+
# config.py
|
|
234
|
+
from keyrunes_sdk import configure
|
|
235
|
+
|
|
236
|
+
def init_app():
|
|
237
|
+
client = configure("https://keyrunes.example.com")
|
|
238
|
+
client.login("user@example.com", "password")
|
|
239
|
+
|
|
240
|
+
# services/admin.py
|
|
241
|
+
from keyrunes_sdk import require_group
|
|
242
|
+
|
|
243
|
+
@require_group("admins") # No client needed!
|
|
244
|
+
def delete_user(user_id: str):
|
|
245
|
+
pass
|
|
246
|
+
|
|
247
|
+
# main.py
|
|
248
|
+
from config import init_app
|
|
249
|
+
from services.admin import delete_user
|
|
250
|
+
|
|
251
|
+
init_app() # Configure once
|
|
252
|
+
delete_user(user_id="123") # Use anywhere!
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
> Tip: See `examples/global_client_usage.py` for a complete example!
|
|
256
|
+
|
|
257
|
+
### Authentication
|
|
258
|
+
|
|
259
|
+
#### Login
|
|
260
|
+
|
|
261
|
+
```python
|
|
262
|
+
# Login and get token
|
|
263
|
+
token = client.login("user@example.com", "password123")
|
|
264
|
+
print(f"Token: {token.access_token}")
|
|
265
|
+
print(f"User: {token.user.username}")
|
|
266
|
+
|
|
267
|
+
# Token is automatically configured in the client
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
#### User Registration
|
|
271
|
+
|
|
272
|
+
```python
|
|
273
|
+
# Register new user
|
|
274
|
+
user = client.register_user(
|
|
275
|
+
username="newuser",
|
|
276
|
+
email="newuser@example.com",
|
|
277
|
+
password="securepass123",
|
|
278
|
+
department="Engineering", # Additional attributes
|
|
279
|
+
role="Developer"
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
print(f"User created: {user.username}")
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
#### Admin Registration
|
|
286
|
+
|
|
287
|
+
```python
|
|
288
|
+
# Register admin (requires admin key)
|
|
289
|
+
admin = client.register_admin(
|
|
290
|
+
username="adminuser",
|
|
291
|
+
email="admin@example.com",
|
|
292
|
+
password="securepass123",
|
|
293
|
+
admin_key="secret-admin-key"
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
print(f"Admin created: {admin.username}")
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
### Group Verification
|
|
300
|
+
|
|
301
|
+
#### Manual Verification
|
|
302
|
+
|
|
303
|
+
```python
|
|
304
|
+
# Login first
|
|
305
|
+
client.login("user@example.com", "password")
|
|
306
|
+
|
|
307
|
+
# Check if user belongs to a group
|
|
308
|
+
has_access = client.has_group("user123", "admins")
|
|
309
|
+
|
|
310
|
+
if has_access:
|
|
311
|
+
print("User has admin access!")
|
|
312
|
+
else:
|
|
313
|
+
print("Access denied")
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
#### Get User Groups
|
|
317
|
+
|
|
318
|
+
```python
|
|
319
|
+
# Get current user groups
|
|
320
|
+
my_groups = client.get_user_groups()
|
|
321
|
+
print(f"My groups: {my_groups}")
|
|
322
|
+
|
|
323
|
+
# Get groups of another user
|
|
324
|
+
user_groups = client.get_user_groups("user123")
|
|
325
|
+
print(f"User groups: {user_groups}")
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
### Using Decorators
|
|
329
|
+
|
|
330
|
+
#### @require_group - Check Group
|
|
331
|
+
|
|
332
|
+
```python
|
|
333
|
+
from keyrunes_sdk import KeyrunesClient, require_group
|
|
334
|
+
|
|
335
|
+
client = KeyrunesClient("https://keyrunes.example.com")
|
|
336
|
+
client.login("admin@example.com", "password")
|
|
337
|
+
|
|
338
|
+
# Decorator: user needs to be in "admins" group
|
|
339
|
+
@require_group("admins", client=client)
|
|
340
|
+
def delete_user(user_id: str):
|
|
341
|
+
print(f"Deleting user {user_id}")
|
|
342
|
+
# Deletion code here
|
|
343
|
+
|
|
344
|
+
# Executes if user has the group, otherwise raises AuthorizationError
|
|
345
|
+
delete_user(user_id="user123")
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
#### Multiple Groups (ANY)
|
|
349
|
+
|
|
350
|
+
```python
|
|
351
|
+
# User needs to be in ANY of the groups
|
|
352
|
+
@require_group("admins", "moderators", all_groups=False)
|
|
353
|
+
def moderate_content(user_id: str, client: KeyrunesClient):
|
|
354
|
+
print(f"Moderating content for {user_id}")
|
|
355
|
+
|
|
356
|
+
moderate_content(user_id="user123", client=client)
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
#### Multiple Groups (ALL)
|
|
360
|
+
|
|
361
|
+
```python
|
|
362
|
+
# User needs to be in ALL groups
|
|
363
|
+
@require_group("admins", "verified", all_groups=True)
|
|
364
|
+
def sensitive_operation(user_id: str, client: KeyrunesClient):
|
|
365
|
+
print(f"Sensitive operation for {user_id}")
|
|
366
|
+
|
|
367
|
+
sensitive_operation(user_id="user123", client=client)
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
#### @require_admin - Check Admin
|
|
371
|
+
|
|
372
|
+
```python
|
|
373
|
+
from keyrunes_sdk import require_admin
|
|
374
|
+
|
|
375
|
+
# Only admins can execute
|
|
376
|
+
@require_admin(client=client)
|
|
377
|
+
def system_configuration(user_id: str):
|
|
378
|
+
print(f"Configuring system for admin {user_id}")
|
|
379
|
+
|
|
380
|
+
system_configuration(user_id="admin123")
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
#### Decorator with Client in Kwargs
|
|
384
|
+
|
|
385
|
+
```python
|
|
386
|
+
# Pass client as function parameter
|
|
387
|
+
@require_group("admins")
|
|
388
|
+
def admin_function(user_id: str, client: KeyrunesClient):
|
|
389
|
+
print(f"Admin function for {user_id}")
|
|
390
|
+
|
|
391
|
+
admin_function(user_id="user123", client=client)
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
## API Reference
|
|
395
|
+
|
|
396
|
+
### KeyrunesClient
|
|
397
|
+
|
|
398
|
+
#### Authentication Methods
|
|
399
|
+
|
|
400
|
+
- `login(username: str, password: str) -> Token`: Login
|
|
401
|
+
- `register_user(username: str, email: str, password: str, **attributes) -> User`: Register user
|
|
402
|
+
- `register_admin(username: str, email: str, password: str, admin_key: str, **attributes) -> User`: Register admin
|
|
403
|
+
|
|
404
|
+
#### User Methods
|
|
405
|
+
|
|
406
|
+
- `get_user(user_id: str) -> User`: Get user by ID
|
|
407
|
+
- `get_current_user() -> User`: Get current user (logged in)
|
|
408
|
+
- `get_user_groups(user_id: Optional[str] = None) -> List[str]`: Get user groups
|
|
409
|
+
|
|
410
|
+
## Complete API Reference
|
|
411
|
+
|
|
412
|
+
### KeyrunesClient Methods
|
|
413
|
+
|
|
414
|
+
#### Authentication
|
|
415
|
+
|
|
416
|
+
##### `login(username: str, password: str) -> Token`
|
|
417
|
+
Authenticates a user and returns an access token. The token is automatically configured in the client.
|
|
418
|
+
|
|
419
|
+
**Parameters:**
|
|
420
|
+
- `username`: Username or email of the user
|
|
421
|
+
- `password`: User password
|
|
422
|
+
|
|
423
|
+
**Returns:** `Token` object with `access_token`, `token_type`, `expires_in`, `refresh_token` (optional) and `user` (optional)
|
|
424
|
+
|
|
425
|
+
**Exceptions:**
|
|
426
|
+
- `AuthenticationError`: If credentials are invalid
|
|
427
|
+
|
|
428
|
+
**Example:**
|
|
429
|
+
```python
|
|
430
|
+
token = client.login("user@example.com", "password123")
|
|
431
|
+
print(f"Token: {token.access_token}")
|
|
432
|
+
print(f"User: {token.user.username if token.user else 'N/A'}")
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
#### Registration
|
|
436
|
+
|
|
437
|
+
##### `register_user(username: str, email: str, password: str, **attributes: Any) -> User`
|
|
438
|
+
Registers a new user in the system.
|
|
439
|
+
|
|
440
|
+
**Parameters:**
|
|
441
|
+
- `username`: Username (3-50 characters)
|
|
442
|
+
- `email`: User email (validated)
|
|
443
|
+
- `password`: Password (minimum 8 characters)
|
|
444
|
+
- `**attributes`: Additional attributes (e.g., `department="Engineering"`, `role="Developer"`)
|
|
445
|
+
|
|
446
|
+
**Returns:** Created `User` object
|
|
447
|
+
|
|
448
|
+
**Exceptions:**
|
|
449
|
+
- `AuthenticationError`: If registration fails
|
|
450
|
+
- `NetworkError`: If there is a network error or unexpected response format
|
|
451
|
+
|
|
452
|
+
**Example:**
|
|
453
|
+
```python
|
|
454
|
+
user = client.register_user(
|
|
455
|
+
username="newuser",
|
|
456
|
+
email="newuser@example.com",
|
|
457
|
+
password="securepass123",
|
|
458
|
+
department="Engineering",
|
|
459
|
+
role="Developer"
|
|
460
|
+
)
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
##### `register_admin(username: str, email: str, password: str, admin_key: str, **attributes: Any) -> User`
|
|
464
|
+
Registers a new admin user in the system.
|
|
465
|
+
|
|
466
|
+
**Parameters:**
|
|
467
|
+
- `username`: Username (3-50 characters)
|
|
468
|
+
- `email`: Admin email (validated)
|
|
469
|
+
- `password`: Password (minimum 8 characters)
|
|
470
|
+
- `admin_key`: Admin registration key (must match server's `ADMIN_KEY`)
|
|
471
|
+
- `**attributes`: Additional attributes
|
|
472
|
+
|
|
473
|
+
**Returns:** Created `User` object with admin privileges
|
|
474
|
+
|
|
475
|
+
**Exceptions:**
|
|
476
|
+
- `AuthenticationError`: If registration fails
|
|
477
|
+
- `AuthorizationError`: If admin key is invalid
|
|
478
|
+
- `NetworkError`: If there is a network error or unexpected response format
|
|
479
|
+
|
|
480
|
+
**Example:**
|
|
481
|
+
```python
|
|
482
|
+
admin = client.register_admin(
|
|
483
|
+
username="adminuser",
|
|
484
|
+
email="admin@example.com",
|
|
485
|
+
password="securepass123",
|
|
486
|
+
admin_key="secret-admin-key"
|
|
487
|
+
)
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
#### User Query
|
|
491
|
+
|
|
492
|
+
##### `get_current_user() -> User`
|
|
493
|
+
Gets information about the currently authenticated user.
|
|
494
|
+
|
|
495
|
+
**Returns:** `User` object of the current user
|
|
496
|
+
|
|
497
|
+
**Exceptions:**
|
|
498
|
+
- `AuthenticationError`: If there is no valid token
|
|
499
|
+
- `NetworkError`: If there is a network error
|
|
500
|
+
|
|
501
|
+
**Example:**
|
|
502
|
+
```python
|
|
503
|
+
user = client.get_current_user()
|
|
504
|
+
print(f"User: {user.username}, Email: {user.email}")
|
|
505
|
+
print(f"Groups: {user.groups}")
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
##### `get_user(user_id: str) -> User`
|
|
509
|
+
Gets information about a specific user by ID.
|
|
510
|
+
|
|
511
|
+
**Parameters:**
|
|
512
|
+
- `user_id`: User ID
|
|
513
|
+
|
|
514
|
+
**Returns:** `User` object
|
|
515
|
+
|
|
516
|
+
**Exceptions:**
|
|
517
|
+
- `AuthenticationError`: If there is no valid token
|
|
518
|
+
- `UserNotFoundError`: If user does not exist
|
|
519
|
+
- `NetworkError`: If there is a network error
|
|
520
|
+
|
|
521
|
+
**Example:**
|
|
522
|
+
```python
|
|
523
|
+
user = client.get_user("user123")
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
#### Group Verification
|
|
527
|
+
|
|
528
|
+
##### `has_group(user_id: str, group_id: str) -> bool`
|
|
529
|
+
Checks if a user belongs to a specific group.
|
|
530
|
+
|
|
531
|
+
**Parameters:**
|
|
532
|
+
- `user_id`: User ID
|
|
533
|
+
- `group_id`: Group ID to verify
|
|
534
|
+
|
|
535
|
+
**Returns:** `True` if user belongs to the group, `False` otherwise
|
|
536
|
+
|
|
537
|
+
**Exceptions:**
|
|
538
|
+
- `AuthenticationError`: If there is no valid token
|
|
539
|
+
- `GroupNotFoundError`: If group does not exist or user is not in the group
|
|
540
|
+
- `NetworkError`: If there is a network error
|
|
541
|
+
|
|
542
|
+
**Example:**
|
|
543
|
+
```python
|
|
544
|
+
is_admin = client.has_group("user123", "admins")
|
|
545
|
+
if is_admin:
|
|
546
|
+
print("User has admin privileges")
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
##### `get_user_groups(user_id: Optional[str] = None) -> List[str]`
|
|
550
|
+
Gets the list of groups for a user.
|
|
551
|
+
|
|
552
|
+
**Parameters:**
|
|
553
|
+
- `user_id` (optional): User ID. If `None`, returns groups of the current user
|
|
554
|
+
|
|
555
|
+
**Returns:** List of strings with group IDs
|
|
556
|
+
|
|
557
|
+
**Exceptions:**
|
|
558
|
+
- `AuthenticationError`: If there is no valid token
|
|
559
|
+
- `UserNotFoundError`: If user does not exist
|
|
560
|
+
- `NetworkError`: If there is a network error
|
|
561
|
+
|
|
562
|
+
**Example:**
|
|
563
|
+
```python
|
|
564
|
+
# Current user groups
|
|
565
|
+
my_groups = client.get_user_groups()
|
|
566
|
+
|
|
567
|
+
# Another user's groups
|
|
568
|
+
user_groups = client.get_user_groups("user123")
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
#### Utility Methods
|
|
572
|
+
|
|
573
|
+
##### `set_token(token: str) -> None`
|
|
574
|
+
Manually sets the authentication token in the client.
|
|
575
|
+
|
|
576
|
+
**Parameters:**
|
|
577
|
+
- `token`: JWT authentication token
|
|
578
|
+
|
|
579
|
+
**Example:**
|
|
580
|
+
```python
|
|
581
|
+
client.set_token("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...")
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
##### `clear_token() -> None`
|
|
585
|
+
Removes the authentication token from the client.
|
|
586
|
+
|
|
587
|
+
**Example:**
|
|
588
|
+
```python
|
|
589
|
+
client.clear_token()
|
|
590
|
+
```
|
|
591
|
+
|
|
592
|
+
##### `close() -> None`
|
|
593
|
+
Closes the HTTP session of the client. Useful for releasing resources.
|
|
594
|
+
|
|
595
|
+
**Example:**
|
|
596
|
+
```python
|
|
597
|
+
client.close()
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
##### Context Manager
|
|
601
|
+
|
|
602
|
+
The client can be used as a context manager to ensure automatic closing:
|
|
603
|
+
|
|
604
|
+
```python
|
|
605
|
+
with KeyrunesClient(base_url="https://keyrunes.example.com") as client:
|
|
606
|
+
token = client.login("user@example.com", "password")
|
|
607
|
+
user = client.get_current_user()
|
|
608
|
+
# Client is automatically closed when exiting the block
|
|
609
|
+
```
|
|
610
|
+
|
|
611
|
+
### Decorators
|
|
612
|
+
|
|
613
|
+
#### @require_group
|
|
614
|
+
|
|
615
|
+
```python
|
|
616
|
+
@require_group(*group_ids, client=None, user_id_param="user_id", all_groups=False)
|
|
617
|
+
```
|
|
618
|
+
|
|
619
|
+
**Parameters:**
|
|
620
|
+
- `*group_ids`: Group IDs to check
|
|
621
|
+
- `client`: KeyrunesClient instance (optional if passed via kwargs)
|
|
622
|
+
- `user_id_param`: Name of the parameter containing user_id (default: "user_id")
|
|
623
|
+
- `all_groups`: If True, user needs ALL groups; if False, ANY group (default: False)
|
|
624
|
+
|
|
625
|
+
#### @require_admin
|
|
626
|
+
|
|
627
|
+
```python
|
|
628
|
+
@require_admin(client=None, user_id_param="user_id")
|
|
629
|
+
```
|
|
630
|
+
|
|
631
|
+
**Parameters:**
|
|
632
|
+
- `client`: KeyrunesClient instance (optional if passed via kwargs)
|
|
633
|
+
- `user_id_param`: Name of the parameter containing user_id (default: "user_id")
|
|
634
|
+
|
|
635
|
+
### Models (Pydantic)
|
|
636
|
+
|
|
637
|
+
Pydantic models are provided for convenience, but are optional. They are useful for:
|
|
638
|
+
|
|
639
|
+
- Data validation before sending to the API
|
|
640
|
+
- Parsing API responses
|
|
641
|
+
- Type hints and IDE autocomplete
|
|
642
|
+
- Data structure consistency
|
|
643
|
+
|
|
644
|
+
**Available models:**
|
|
645
|
+
- `User`: User model
|
|
646
|
+
- `Group`: Group model
|
|
647
|
+
- `Token`: Authentication token model
|
|
648
|
+
- `UserRegistration`: User registration data
|
|
649
|
+
- `AdminRegistration`: Admin registration data
|
|
650
|
+
- `LoginCredentials`: Login credentials
|
|
651
|
+
- `GroupCheck`: Group verification result
|
|
652
|
+
|
|
653
|
+
#### Using with Flask, FastAPI or Django
|
|
654
|
+
|
|
655
|
+
If you are using Flask, FastAPI or Django, you can:
|
|
656
|
+
|
|
657
|
+
**Option 1: Use SDK models and map to your models**
|
|
658
|
+
|
|
659
|
+
```python
|
|
660
|
+
from keyrunes_sdk import KeyrunesClient
|
|
661
|
+
from keyrunes_sdk.models import User
|
|
662
|
+
from your_app.models import MyUser # Your SQLAlchemy/Django ORM model
|
|
663
|
+
|
|
664
|
+
client = KeyrunesClient("https://keyrunes.example.com")
|
|
665
|
+
token = client.login("user@example.com", "password")
|
|
666
|
+
|
|
667
|
+
# Get data from Keyrunes
|
|
668
|
+
keyrunes_user = client.get_current_user() # Returns SDK User
|
|
669
|
+
|
|
670
|
+
# Map to your model
|
|
671
|
+
my_user = MyUser(
|
|
672
|
+
id=keyrunes_user.id,
|
|
673
|
+
username=keyrunes_user.username,
|
|
674
|
+
email=keyrunes_user.email,
|
|
675
|
+
groups=keyrunes_user.groups,
|
|
676
|
+
# Add your own fields
|
|
677
|
+
created_at=datetime.now(),
|
|
678
|
+
# ... other fields from your model
|
|
679
|
+
)
|
|
680
|
+
```
|
|
681
|
+
|
|
682
|
+
**Option 2: Work with dictionaries**
|
|
683
|
+
|
|
684
|
+
```python
|
|
685
|
+
from keyrunes_sdk import KeyrunesClient
|
|
686
|
+
|
|
687
|
+
client = KeyrunesClient("https://keyrunes.example.com")
|
|
688
|
+
response = client._make_request("GET", "/api/v1/users/me")
|
|
689
|
+
# response is a dict, use as needed
|
|
690
|
+
user_data = response # {'id': '...', 'username': '...', ...}
|
|
691
|
+
```
|
|
692
|
+
|
|
693
|
+
**Option 3: Use only SDK models**
|
|
694
|
+
|
|
695
|
+
SDK models are Pydantic, so they work well with FastAPI directly:
|
|
696
|
+
|
|
697
|
+
```python
|
|
698
|
+
from fastapi import FastAPI
|
|
699
|
+
from keyrunes_sdk import KeyrunesClient
|
|
700
|
+
from keyrunes_sdk.models import User
|
|
701
|
+
|
|
702
|
+
app = FastAPI()
|
|
703
|
+
client = KeyrunesClient("https://keyrunes.example.com")
|
|
704
|
+
|
|
705
|
+
@app.get("/me", response_model=User)
|
|
706
|
+
async def get_current_user():
|
|
707
|
+
return client.get_current_user()
|
|
708
|
+
```
|
|
709
|
+
|
|
710
|
+
**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.
|
|
711
|
+
|
|
712
|
+
### Exceptions
|
|
713
|
+
|
|
714
|
+
- `KeyrunesError`: Base exception
|
|
715
|
+
- `AuthenticationError`: Authentication error
|
|
716
|
+
- `AuthorizationError`: Authorization error
|
|
717
|
+
- `GroupNotFoundError`: Group not found
|
|
718
|
+
- `UserNotFoundError`: User not found
|
|
719
|
+
- `NetworkError`: Network error
|
|
720
|
+
|
|
721
|
+
## Development
|
|
722
|
+
|
|
723
|
+
### Setup
|
|
724
|
+
|
|
725
|
+
```bash
|
|
726
|
+
# Clone repository
|
|
727
|
+
git clone https://github.com/Keyrunes/keyrunes-python-sdk.git
|
|
728
|
+
cd keyrunes-python-sdk
|
|
729
|
+
|
|
730
|
+
# Install dependencies
|
|
731
|
+
poetry install
|
|
732
|
+
|
|
733
|
+
# Activate virtual environment
|
|
734
|
+
poetry shell
|
|
735
|
+
```
|
|
736
|
+
|
|
737
|
+
### Run Tests
|
|
738
|
+
|
|
739
|
+
```bash
|
|
740
|
+
# Run all tests
|
|
741
|
+
poetry run pytest
|
|
742
|
+
|
|
743
|
+
# With verbose
|
|
744
|
+
poetry run pytest -v
|
|
745
|
+
|
|
746
|
+
# With coverage
|
|
747
|
+
poetry run pytest --cov=keyrunes_sdk --cov-report=html
|
|
748
|
+
|
|
749
|
+
# Run specific tests
|
|
750
|
+
poetry run pytest tests/test_client.py
|
|
751
|
+
poetry run pytest tests/test_decorators.py
|
|
752
|
+
poetry run pytest tests/test_models.py
|
|
753
|
+
```
|
|
754
|
+
|
|
755
|
+
### Local Testing with Docker Compose
|
|
756
|
+
|
|
757
|
+
Test the library against a real Keyrunes instance running locally:
|
|
758
|
+
|
|
759
|
+
#### 1. Start Keyrunes
|
|
760
|
+
|
|
761
|
+
```bash
|
|
762
|
+
# Start all services (Keyrunes, PostgreSQL, Redis)
|
|
763
|
+
docker-compose up -d
|
|
764
|
+
|
|
765
|
+
# Check status
|
|
766
|
+
docker-compose ps
|
|
767
|
+
|
|
768
|
+
# View logs
|
|
769
|
+
docker-compose logs -f keyrunes
|
|
770
|
+
```
|
|
771
|
+
|
|
772
|
+
**Available services:**
|
|
773
|
+
- Keyrunes API: http://localhost:3000
|
|
774
|
+
- PostgreSQL: localhost:5432
|
|
775
|
+
|
|
776
|
+
#### 2. Run Integration Tests
|
|
777
|
+
|
|
778
|
+
```bash
|
|
779
|
+
# Complete test script
|
|
780
|
+
poetry run python examples/test_local.py
|
|
781
|
+
|
|
782
|
+
# Or using taskipy
|
|
783
|
+
poetry run task test-local
|
|
784
|
+
```
|
|
785
|
+
|
|
786
|
+
#### 3. Run Examples
|
|
787
|
+
|
|
788
|
+
```bash
|
|
789
|
+
# Basic usage example
|
|
790
|
+
poetry run python examples/basic_usage.py
|
|
791
|
+
|
|
792
|
+
# Or using taskipy
|
|
793
|
+
poetry run task example-basic
|
|
794
|
+
```
|
|
795
|
+
|
|
796
|
+
#### 4. Stop Services
|
|
797
|
+
|
|
798
|
+
```bash
|
|
799
|
+
# Stop containers
|
|
800
|
+
docker-compose down
|
|
801
|
+
|
|
802
|
+
# Stop and remove volumes
|
|
803
|
+
docker-compose down -v
|
|
804
|
+
```
|
|
805
|
+
|
|
806
|
+
### Linting and Formatting
|
|
807
|
+
|
|
808
|
+
```bash
|
|
809
|
+
# Black (formatting)
|
|
810
|
+
poetry run black keyrunes_sdk tests
|
|
811
|
+
|
|
812
|
+
# isort (organize imports)
|
|
813
|
+
poetry run isort keyrunes_sdk tests
|
|
814
|
+
|
|
815
|
+
# flake8 (linting)
|
|
816
|
+
poetry run flake8 keyrunes_sdk tests
|
|
817
|
+
|
|
818
|
+
# mypy (type checking)
|
|
819
|
+
poetry run mypy keyrunes_sdk
|
|
820
|
+
```
|
|
821
|
+
|
|
822
|
+
## Tests
|
|
823
|
+
|
|
824
|
+
The library has 86% test coverage using:
|
|
825
|
+
|
|
826
|
+
- pytest: Test framework
|
|
827
|
+
- factory-boy: Factories for creating test data
|
|
828
|
+
- faker: Fake data generation for tests
|
|
829
|
+
- pytest-cov: Code coverage
|
|
830
|
+
- pytest-mock: Mocking
|
|
831
|
+
|
|
832
|
+
### Test Structure
|
|
833
|
+
|
|
834
|
+
```
|
|
835
|
+
tests/
|
|
836
|
+
├── __init__.py
|
|
837
|
+
├── conftest.py # Fixtures and configurations
|
|
838
|
+
├── factories.py # Factory Boy factories
|
|
839
|
+
├── test_client.py # Client tests
|
|
840
|
+
├── test_decorators.py # Decorator tests
|
|
841
|
+
└── test_models.py # Model tests
|
|
842
|
+
```
|
|
843
|
+
|
|
844
|
+
## Security
|
|
845
|
+
|
|
846
|
+
- All passwords must have at least 8 characters
|
|
847
|
+
- JWT tokens are used for authentication
|
|
848
|
+
- HTTPS is recommended for production
|
|
849
|
+
- Email validation using email-validator
|
|
850
|
+
|
|
851
|
+
## License
|
|
852
|
+
|
|
853
|
+
MIT License - see [LICENSE](LICENSE) for more details.
|
|
854
|
+
|
|
855
|
+
## Contributing
|
|
856
|
+
|
|
857
|
+
Contributions are welcome! Please:
|
|
858
|
+
|
|
859
|
+
1. Fork the project
|
|
860
|
+
2. Create a branch for your feature (`git checkout -b feature/AmazingFeature`)
|
|
861
|
+
3. Commit your changes (`git commit -m 'Add some AmazingFeature'`)
|
|
862
|
+
4. Push to the branch (`git push origin feature/AmazingFeature`)
|
|
863
|
+
5. Open a Pull Request
|
|
864
|
+
|
|
865
|
+
## Support
|
|
866
|
+
|
|
867
|
+
- [Report Bug](https://github.com/Keyrunes/keyrunes/issues)
|
|
868
|
+
- [Discussions](https://github.com/Keyrunes/keyrunes/discussions)
|
|
869
|
+
- Email: keyrunes@example.com
|
|
870
|
+
|
|
871
|
+
## Links
|
|
872
|
+
|
|
873
|
+
- [Keyrunes Main Repository](https://github.com/Keyrunes/keyrunes)
|
|
874
|
+
- [Complete Documentation](https://keyrunes.example.com/docs)
|
|
875
|
+
- [PyPI Package](https://pypi.org/project/keyrunes-sdk/)
|
|
876
|
+
|
|
877
|
+
---
|
|
878
|
+
|
|
879
|
+
Made with love for the Keyrunes community
|
|
880
|
+
|