django-permission-engine 0.1.0__tar.gz

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.
Files changed (37) hide show
  1. django_permission_engine-0.1.0/CHANGELOG.md +35 -0
  2. django_permission_engine-0.1.0/LICENSE +21 -0
  3. django_permission_engine-0.1.0/MANIFEST.in +7 -0
  4. django_permission_engine-0.1.0/PKG-INFO +170 -0
  5. django_permission_engine-0.1.0/README.md +126 -0
  6. django_permission_engine-0.1.0/django_permission_engine/__init__.py +32 -0
  7. django_permission_engine-0.1.0/django_permission_engine/apps.py +37 -0
  8. django_permission_engine-0.1.0/django_permission_engine/management/__init__.py +0 -0
  9. django_permission_engine-0.1.0/django_permission_engine/management/commands/__init__.py +0 -0
  10. django_permission_engine-0.1.0/django_permission_engine/management/commands/upr_list.py +92 -0
  11. django_permission_engine-0.1.0/django_permission_engine/management/commands/upr_sync.py +144 -0
  12. django_permission_engine-0.1.0/django_permission_engine/management/commands/upr_validate.py +88 -0
  13. django_permission_engine-0.1.0/django_permission_engine/migrations/0001_initial.py +73 -0
  14. django_permission_engine-0.1.0/django_permission_engine/migrations/__init__.py +0 -0
  15. django_permission_engine-0.1.0/django_permission_engine/models.py +284 -0
  16. django_permission_engine-0.1.0/django_permission_engine/permission_management.py +361 -0
  17. django_permission_engine-0.1.0/django_permission_engine/permissions.py +259 -0
  18. django_permission_engine-0.1.0/django_permission_engine/registry.py +417 -0
  19. django_permission_engine-0.1.0/django_permission_engine/urls.py +16 -0
  20. django_permission_engine-0.1.0/django_permission_engine/views.py +213 -0
  21. django_permission_engine-0.1.0/django_permission_engine.egg-info/PKG-INFO +170 -0
  22. django_permission_engine-0.1.0/django_permission_engine.egg-info/SOURCES.txt +36 -0
  23. django_permission_engine-0.1.0/django_permission_engine.egg-info/dependency_links.txt +1 -0
  24. django_permission_engine-0.1.0/django_permission_engine.egg-info/requires.txt +2 -0
  25. django_permission_engine-0.1.0/django_permission_engine.egg-info/top_level.txt +1 -0
  26. django_permission_engine-0.1.0/pyproject.toml +70 -0
  27. django_permission_engine-0.1.0/setup.cfg +23 -0
  28. django_permission_engine-0.1.0/setup.py +48 -0
  29. django_permission_engine-0.1.0/tests/test_all.py +10 -0
  30. django_permission_engine-0.1.0/tests/test_catalog_api.py +150 -0
  31. django_permission_engine-0.1.0/tests/test_integration.py +119 -0
  32. django_permission_engine-0.1.0/tests/test_management_commands.py +227 -0
  33. django_permission_engine-0.1.0/tests/test_models.py +217 -0
  34. django_permission_engine-0.1.0/tests/test_performance.py +85 -0
  35. django_permission_engine-0.1.0/tests/test_permission_definition.py +192 -0
  36. django_permission_engine-0.1.0/tests/test_permissions.py +233 -0
  37. django_permission_engine-0.1.0/tests/test_registry.py +226 -0
@@ -0,0 +1,35 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ### Added
11
+ - Initial release
12
+ - Permission registry engine
13
+ - Database models (Permission, Module, UserPermission)
14
+ - Declarative permission definitions with decorators
15
+ - DRF integration with PermissionRequired class
16
+ - Permission catalog API
17
+ - Management commands (upr_sync, upr_validate, upr_list)
18
+ - Comprehensive test suite
19
+ - Documentation
20
+
21
+ ## [0.1.0] - 2024-01-15
22
+
23
+ ### Added
24
+ - Core permission models
25
+ - Registry engine for permission synchronization
26
+ - Module and action decorators
27
+ - Runtime permission resolution
28
+ - DRF PermissionRequired class
29
+ - Permission catalog API endpoints
30
+ - Management commands for sync, validate, and list
31
+ - Test infrastructure and test suite
32
+ - Sphinx documentation setup
33
+
34
+ [Unreleased]: https://github.com/yourusername/django-permission-engine/compare/v0.1.0...HEAD
35
+ [0.1.0]: https://github.com/yourusername/django-permission-engine/releases/tag/v0.1.0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Your Name
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,7 @@
1
+ include README.md
2
+ include LICENSE
3
+ include CHANGELOG.md
4
+ include pyproject.toml
5
+ recursive-include django_permission_engine *.py
6
+ recursive-exclude * __pycache__
7
+ recursive-exclude * *.py[co]
@@ -0,0 +1,170 @@
1
+ Metadata-Version: 2.4
2
+ Name: django-permission-engine
3
+ Version: 0.1.0
4
+ Summary: Unified Permission Registry (UPR) for Django & DRF
5
+ Home-page: https://github.com/sarthaksnh5/django_permission_engine
6
+ Author: Sarthak
7
+ Author-email: sarthaksnh5@gmail.com
8
+ License: MIT
9
+ Project-URL: Documentation, https://django-permission-engine.readthedocs.io/
10
+ Project-URL: Source, https://github.com/sarthaksnh5/django_permission_engine
11
+ Project-URL: Tracker, https://github.com/sarthaksnh5/django_permission_engine/issues
12
+ Keywords: django permissions drf rest-framework
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.8
18
+ Classifier: Programming Language :: Python :: 3.9
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Framework :: Django
22
+ Classifier: Framework :: Django :: 3.2
23
+ Classifier: Framework :: Django :: 4.0
24
+ Classifier: Framework :: Django :: 4.1
25
+ Classifier: Framework :: Django :: 4.2
26
+ Requires-Python: >=3.8
27
+ Description-Content-Type: text/markdown
28
+ License-File: LICENSE
29
+ Requires-Dist: Django>=3.2
30
+ Requires-Dist: djangorestframework>=3.12
31
+ Dynamic: author
32
+ Dynamic: author-email
33
+ Dynamic: classifier
34
+ Dynamic: description
35
+ Dynamic: description-content-type
36
+ Dynamic: home-page
37
+ Dynamic: keywords
38
+ Dynamic: license
39
+ Dynamic: license-file
40
+ Dynamic: project-url
41
+ Dynamic: requires-dist
42
+ Dynamic: requires-python
43
+ Dynamic: summary
44
+
45
+ # Django Permission Engine
46
+
47
+ Unified Permission Registry (UPR) for Django & DRF
48
+
49
+ ## Overview
50
+
51
+ Django Permission Engine provides a single-source, action-aware, declarative permission system for Django and Django REST Framework. It eliminates permission drift and provides a maintainable foundation for complex permission requirements.
52
+
53
+ ## Features
54
+
55
+ - 🎯 **Single Source of Truth** - Define permissions once, everything else is derived
56
+ - 🔑 **Permission Keys** - Simple, immutable, string-based permission identifiers
57
+ - 🎬 **Action-Aware** - DRF actions automatically map to permissions
58
+ - 📊 **Frontend-Ready** - Permission catalog API for frontend consumption
59
+ - 🚫 **Drift Prevention** - Startup validation ensures code and database never drift
60
+ - ⚡ **Performance** - O(1) permission checks with optional caching
61
+
62
+ ## Installation
63
+
64
+ ```bash
65
+ pip install django-permission-engine
66
+ ```
67
+
68
+ **Note:** This library uses flexible version requirements (minimum versions only) to avoid conflicts with your existing Django and DRF installations. It will work with any compatible version you already have installed.
69
+
70
+ ## Quick Start
71
+
72
+ ### 1. Add to INSTALLED_APPS
73
+
74
+ ```python
75
+ INSTALLED_APPS = [
76
+ # ... other apps
77
+ 'rest_framework',
78
+ 'django_permission_engine',
79
+ ]
80
+ ```
81
+
82
+ ### 2. Configure
83
+
84
+ ```python
85
+ UPR_CONFIG = {
86
+ 'validate_on_startup': True,
87
+ 'strict_mode': True,
88
+ 'auto_sync': False,
89
+ }
90
+ ```
91
+
92
+ ### 3. Define Permissions
93
+
94
+ ```python
95
+ # upr_config.py
96
+ from django_permission_engine import module, action
97
+
98
+ @module('users', label='User Management')
99
+ class UsersModule:
100
+ crud = ['view', 'create', 'update', 'delete']
101
+ actions = ['reset_password']
102
+ ```
103
+
104
+ ### 4. Sync to Database
105
+
106
+ ```bash
107
+ python manage.py upr_sync
108
+ ```
109
+
110
+ ### 5. Assign Permissions to Users
111
+
112
+ Use the Permission Management API (admin only):
113
+
114
+ ```bash
115
+ # Assign permission to user
116
+ POST /api/permissions/users/1/assign/
117
+ {
118
+ "permission_key": "users.view"
119
+ }
120
+
121
+ # Bulk assign to multiple users
122
+ POST /api/permissions/bulk-assign/
123
+ {
124
+ "permission_keys": ["users.view", "users.create"],
125
+ "user_ids": [1, 2, 3]
126
+ }
127
+ ```
128
+
129
+ Or programmatically:
130
+
131
+ ```python
132
+ from django_permission_engine.models import Permission, UserPermission
133
+
134
+ user = User.objects.get(username='john')
135
+ permission = Permission.objects.get(key='users.view')
136
+ UserPermission.objects.get_or_create(user=user, permission=permission)
137
+ ```
138
+
139
+ ### 6. Use in ViewSets
140
+
141
+ ```python
142
+ from rest_framework import viewsets
143
+ from django_permission_engine.permissions import PermissionRequired
144
+
145
+ class UserViewSet(viewsets.ModelViewSet):
146
+ permission_classes = [PermissionRequired]
147
+ module = 'users'
148
+ queryset = User.objects.all()
149
+ serializer_class = UserSerializer
150
+ ```
151
+
152
+ ## Documentation
153
+
154
+ Full documentation is available in the `docs/` folder:
155
+
156
+ - [Architecture](docs/architecture.md)
157
+ - [Core Concepts](docs/core-concepts.md)
158
+ - [Permission Definition](docs/permission-definition.md)
159
+ - [DRF Integration](docs/drf-integration.md)
160
+ - [API Reference](docs/catalog-api.md)
161
+
162
+ ## Requirements
163
+
164
+ - Python 3.8+
165
+ - Django 3.2+
166
+ - Django REST Framework 3.12+
167
+
168
+ ## License
169
+
170
+ MIT License
@@ -0,0 +1,126 @@
1
+ # Django Permission Engine
2
+
3
+ Unified Permission Registry (UPR) for Django & DRF
4
+
5
+ ## Overview
6
+
7
+ Django Permission Engine provides a single-source, action-aware, declarative permission system for Django and Django REST Framework. It eliminates permission drift and provides a maintainable foundation for complex permission requirements.
8
+
9
+ ## Features
10
+
11
+ - 🎯 **Single Source of Truth** - Define permissions once, everything else is derived
12
+ - 🔑 **Permission Keys** - Simple, immutable, string-based permission identifiers
13
+ - 🎬 **Action-Aware** - DRF actions automatically map to permissions
14
+ - 📊 **Frontend-Ready** - Permission catalog API for frontend consumption
15
+ - 🚫 **Drift Prevention** - Startup validation ensures code and database never drift
16
+ - ⚡ **Performance** - O(1) permission checks with optional caching
17
+
18
+ ## Installation
19
+
20
+ ```bash
21
+ pip install django-permission-engine
22
+ ```
23
+
24
+ **Note:** This library uses flexible version requirements (minimum versions only) to avoid conflicts with your existing Django and DRF installations. It will work with any compatible version you already have installed.
25
+
26
+ ## Quick Start
27
+
28
+ ### 1. Add to INSTALLED_APPS
29
+
30
+ ```python
31
+ INSTALLED_APPS = [
32
+ # ... other apps
33
+ 'rest_framework',
34
+ 'django_permission_engine',
35
+ ]
36
+ ```
37
+
38
+ ### 2. Configure
39
+
40
+ ```python
41
+ UPR_CONFIG = {
42
+ 'validate_on_startup': True,
43
+ 'strict_mode': True,
44
+ 'auto_sync': False,
45
+ }
46
+ ```
47
+
48
+ ### 3. Define Permissions
49
+
50
+ ```python
51
+ # upr_config.py
52
+ from django_permission_engine import module, action
53
+
54
+ @module('users', label='User Management')
55
+ class UsersModule:
56
+ crud = ['view', 'create', 'update', 'delete']
57
+ actions = ['reset_password']
58
+ ```
59
+
60
+ ### 4. Sync to Database
61
+
62
+ ```bash
63
+ python manage.py upr_sync
64
+ ```
65
+
66
+ ### 5. Assign Permissions to Users
67
+
68
+ Use the Permission Management API (admin only):
69
+
70
+ ```bash
71
+ # Assign permission to user
72
+ POST /api/permissions/users/1/assign/
73
+ {
74
+ "permission_key": "users.view"
75
+ }
76
+
77
+ # Bulk assign to multiple users
78
+ POST /api/permissions/bulk-assign/
79
+ {
80
+ "permission_keys": ["users.view", "users.create"],
81
+ "user_ids": [1, 2, 3]
82
+ }
83
+ ```
84
+
85
+ Or programmatically:
86
+
87
+ ```python
88
+ from django_permission_engine.models import Permission, UserPermission
89
+
90
+ user = User.objects.get(username='john')
91
+ permission = Permission.objects.get(key='users.view')
92
+ UserPermission.objects.get_or_create(user=user, permission=permission)
93
+ ```
94
+
95
+ ### 6. Use in ViewSets
96
+
97
+ ```python
98
+ from rest_framework import viewsets
99
+ from django_permission_engine.permissions import PermissionRequired
100
+
101
+ class UserViewSet(viewsets.ModelViewSet):
102
+ permission_classes = [PermissionRequired]
103
+ module = 'users'
104
+ queryset = User.objects.all()
105
+ serializer_class = UserSerializer
106
+ ```
107
+
108
+ ## Documentation
109
+
110
+ Full documentation is available in the `docs/` folder:
111
+
112
+ - [Architecture](docs/architecture.md)
113
+ - [Core Concepts](docs/core-concepts.md)
114
+ - [Permission Definition](docs/permission-definition.md)
115
+ - [DRF Integration](docs/drf-integration.md)
116
+ - [API Reference](docs/catalog-api.md)
117
+
118
+ ## Requirements
119
+
120
+ - Python 3.8+
121
+ - Django 3.2+
122
+ - Django REST Framework 3.12+
123
+
124
+ ## License
125
+
126
+ MIT License
@@ -0,0 +1,32 @@
1
+ """
2
+ Django Permission Engine - Unified Permission Registry (UPR) for Django & DRF
3
+ """
4
+ from .registry import (
5
+ PermissionRegistry,
6
+ PermissionDefinition,
7
+ get_registry,
8
+ registry,
9
+ module,
10
+ action,
11
+ )
12
+ from .permissions import (
13
+ PermissionResolver,
14
+ PermissionRequired,
15
+ )
16
+
17
+ __version__ = "0.1.0"
18
+ __author__ = "Your Name"
19
+ __email__ = "your.email@example.com"
20
+
21
+ default_app_config = "django_permission_engine.apps.PermissionEngineConfig"
22
+
23
+ __all__ = [
24
+ "PermissionRegistry",
25
+ "PermissionDefinition",
26
+ "get_registry",
27
+ "registry",
28
+ "module",
29
+ "action",
30
+ "PermissionResolver",
31
+ "PermissionRequired",
32
+ ]
@@ -0,0 +1,37 @@
1
+ """
2
+ Django app configuration for Permission Engine
3
+ """
4
+ from django.apps import AppConfig
5
+
6
+
7
+ class PermissionEngineConfig(AppConfig):
8
+ """Django app configuration for Permission Engine"""
9
+
10
+ default_auto_field = "django.db.models.BigAutoField"
11
+ name = "django_permission_engine"
12
+ verbose_name = "Permission Engine"
13
+
14
+ def ready(self):
15
+ """Called when Django starts"""
16
+ from django.conf import settings
17
+
18
+ # Get UPR config
19
+ upr_config = getattr(settings, "UPR_CONFIG", {})
20
+ validate_on_startup = upr_config.get("validate_on_startup", False)
21
+ auto_sync = upr_config.get("auto_sync", False)
22
+
23
+ # Initialize registry if configured
24
+ if validate_on_startup or auto_sync:
25
+ from .registry import get_registry
26
+ registry = get_registry()
27
+
28
+ # Validate
29
+ if validate_on_startup:
30
+ errors = registry.validate()
31
+ if errors and registry.strict_mode:
32
+ from django.core.exceptions import ValidationError
33
+ raise ValidationError(f"Registry validation failed: {errors}")
34
+
35
+ # Auto-sync if configured
36
+ if auto_sync:
37
+ registry.sync()
@@ -0,0 +1,92 @@
1
+ """
2
+ Management command to list permissions
3
+ """
4
+ from django.core.management.base import BaseCommand
5
+ import json
6
+
7
+ from django_permission_engine.models import Permission
8
+
9
+
10
+ class Command(BaseCommand):
11
+ help = 'List all registered permissions'
12
+
13
+ def add_arguments(self, parser):
14
+ parser.add_argument(
15
+ '--module',
16
+ type=str,
17
+ help='Filter by module',
18
+ )
19
+ parser.add_argument(
20
+ '--type',
21
+ type=str,
22
+ choices=['crud', 'action'],
23
+ help='Filter by type',
24
+ )
25
+ parser.add_argument(
26
+ '--format',
27
+ type=str,
28
+ choices=['table', 'json', 'simple'],
29
+ default='table',
30
+ help='Output format',
31
+ )
32
+
33
+ def handle(self, *args, **options):
34
+ """Execute list command"""
35
+ module_filter = options.get('module')
36
+ type_filter = options.get('type')
37
+ format_type = options['format']
38
+
39
+ # Get permissions
40
+ if module_filter:
41
+ permissions = Permission.objects.filter(module=module_filter)
42
+ else:
43
+ permissions = Permission.objects.all()
44
+
45
+ if type_filter:
46
+ if type_filter == 'crud':
47
+ permissions = permissions.filter(capability__in=['view', 'create', 'update', 'delete'])
48
+ else:
49
+ permissions = permissions.exclude(capability__in=['view', 'create', 'update', 'delete'])
50
+
51
+ permissions = permissions.order_by('module', 'capability')
52
+
53
+ # Display
54
+ if format_type == 'json':
55
+ self._display_json(permissions)
56
+ elif format_type == 'simple':
57
+ self._display_simple(permissions)
58
+ else:
59
+ self._display_table(permissions)
60
+
61
+ def _display_table(self, permissions):
62
+ """Display permissions in table format"""
63
+ self.stdout.write('\nRegistered Permissions:\n')
64
+ self.stdout.write('-' * 80)
65
+ self.stdout.write('{:<40} {:<20} {:<20}'.format('Key', 'Module', 'Capability'))
66
+ self.stdout.write('-' * 80)
67
+
68
+ for perm in permissions:
69
+ self.stdout.write(
70
+ '{:<40} {:<20} {:<20}'.format(perm.key, perm.module, perm.capability)
71
+ )
72
+
73
+ self.stdout.write('-' * 80)
74
+ self.stdout.write(f'\nTotal: {permissions.count()} permissions')
75
+
76
+ def _display_simple(self, permissions):
77
+ """Display permissions in simple format"""
78
+ for perm in permissions:
79
+ self.stdout.write(perm.key)
80
+
81
+ def _display_json(self, permissions):
82
+ """Display permissions in JSON format"""
83
+ data = [
84
+ {
85
+ 'key': perm.key,
86
+ 'module': perm.module,
87
+ 'capability': perm.capability,
88
+ 'label': perm.label,
89
+ }
90
+ for perm in permissions
91
+ ]
92
+ self.stdout.write(json.dumps(data, indent=2))
@@ -0,0 +1,144 @@
1
+ """
2
+ Management command to sync permissions with database
3
+ """
4
+ from django.core.management.base import BaseCommand, CommandError
5
+ from django.db import transaction
6
+
7
+ from django_permission_engine import get_registry
8
+
9
+
10
+ class Command(BaseCommand):
11
+ help = 'Synchronize permission definitions with database'
12
+
13
+ def add_arguments(self, parser):
14
+ parser.add_argument(
15
+ '--dry-run',
16
+ action='store_true',
17
+ help='Show what would change without making changes',
18
+ )
19
+ parser.add_argument(
20
+ '--force',
21
+ action='store_true',
22
+ help='Force sync even with warnings',
23
+ )
24
+ parser.add_argument(
25
+ '--clean-orphans',
26
+ action='store_true',
27
+ help='Delete orphaned permissions',
28
+ )
29
+ parser.add_argument(
30
+ '--verbose',
31
+ action='store_true',
32
+ help='Show detailed output',
33
+ )
34
+
35
+ def handle(self, *args, **options):
36
+ """Execute sync command"""
37
+ dry_run = options['dry_run']
38
+ force = options['force']
39
+ clean_orphans = options['clean_orphans']
40
+ verbose = options['verbose']
41
+
42
+ # Get registry
43
+ registry = get_registry()
44
+
45
+ # Set orphan action
46
+ if clean_orphans:
47
+ registry.orphan_action = 'delete'
48
+ elif not force:
49
+ registry.orphan_action = 'warn'
50
+
51
+ try:
52
+ # Validate first
53
+ if verbose:
54
+ self.stdout.write('Validating permissions...')
55
+
56
+ errors = registry.validate()
57
+ if errors:
58
+ self.stdout.write(
59
+ self.style.ERROR(f'Validation errors found: {len(errors)}')
60
+ )
61
+ for error in errors:
62
+ self.stdout.write(self.style.ERROR(f' - {error}'))
63
+
64
+ if not force:
65
+ raise CommandError('Validation failed. Use --force to continue.')
66
+
67
+ # Sync
68
+ if dry_run:
69
+ self.stdout.write(self.style.WARNING('DRY RUN - No changes will be made'))
70
+ plan = registry.sync(dry_run=True)
71
+ self._display_plan(plan, verbose)
72
+ else:
73
+ self.stdout.write('Syncing permissions...')
74
+ result = registry.sync()
75
+ self._display_result(result, verbose)
76
+ self.stdout.write(
77
+ self.style.SUCCESS('Sync complete.')
78
+ )
79
+
80
+ except Exception as e:
81
+ raise CommandError(f'Sync failed: {e}')
82
+
83
+ def _display_plan(self, plan, verbose):
84
+ """Display sync plan"""
85
+ self.stdout.write('\nSync Plan:')
86
+
87
+ if plan['create']:
88
+ self.stdout.write(
89
+ self.style.SUCCESS(f' Would create: {len(plan["create"])} permissions')
90
+ )
91
+ if verbose:
92
+ for perm in plan['create']:
93
+ self.stdout.write(f' - {perm.key}')
94
+
95
+ if plan['update']:
96
+ self.stdout.write(
97
+ self.style.WARNING(f' Would update: {len(plan["update"])} permissions')
98
+ )
99
+ if verbose:
100
+ for perm in plan['update']:
101
+ self.stdout.write(f' - {perm.key}')
102
+
103
+ if plan['orphaned']:
104
+ self.stdout.write(
105
+ self.style.ERROR(f' Orphaned: {len(plan["orphaned"])} permissions')
106
+ )
107
+ if verbose:
108
+ for perm in plan['orphaned']:
109
+ self.stdout.write(f' - {perm.key}')
110
+
111
+ unchanged = len(get_registry().get_all_permissions()) - len(plan['create']) - len(plan['update'])
112
+ if unchanged > 0:
113
+ self.stdout.write(f' Unchanged: {unchanged} permissions')
114
+
115
+ def _display_result(self, result, verbose):
116
+ """Display sync result"""
117
+ if result['created']:
118
+ self.stdout.write(
119
+ self.style.SUCCESS(f' Created: {len(result["created"])} permissions')
120
+ )
121
+ if verbose:
122
+ for key in result['created']:
123
+ self.stdout.write(f' - {key}')
124
+
125
+ if result['updated']:
126
+ self.stdout.write(
127
+ self.style.WARNING(f' Updated: {len(result["updated"])} permissions')
128
+ )
129
+ if verbose:
130
+ for key in result['updated']:
131
+ self.stdout.write(f' - {key}')
132
+
133
+ if result['orphaned']:
134
+ self.stdout.write(
135
+ self.style.ERROR(f' Orphaned: {len(result["orphaned"])} permissions')
136
+ )
137
+ if verbose:
138
+ for key in result['orphaned']:
139
+ self.stdout.write(f' - {key}')
140
+
141
+ total = len(get_registry().get_all_permissions())
142
+ unchanged = total - len(result['created']) - len(result['updated'])
143
+ if unchanged > 0:
144
+ self.stdout.write(f' Unchanged: {unchanged} permissions')