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.
@@ -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
+ [![Tests](https://github.com/Keyrunes/keyrunes-python-sdk/actions/workflows/ci.yml/badge.svg)](https://github.com/Keyrunes/keyrunes-python-sdk/actions/workflows/ci.yml)
36
+ [![Coverage](https://codecov.io/gh/Keyrunes/keyrunes-python-sdk/branch/main/graph/badge.svg)](https://codecov.io/gh/Keyrunes/keyrunes-python-sdk)
37
+ [![Python](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
38
+ [![License](https://img.shields.io/badge/license-MIT-blue.svg)](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
+