utg-base 1.10.1__tar.gz → 1.11.1__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 (73) hide show
  1. {utg_base-1.10.1 → utg_base-1.11.1}/PKG-INFO +1 -1
  2. {utg_base-1.10.1 → utg_base-1.11.1}/pyproject.toml +1 -1
  3. utg_base-1.11.1/src/utg_base/permissions/decorators.py +25 -0
  4. utg_base-1.11.1/src/utg_base/permissions/utils.py +76 -0
  5. utg_base-1.11.1/src/utg_base/u_services/migrations/__init__.py +0 -0
  6. {utg_base-1.10.1 → utg_base-1.11.1}/src/utg_base/utils/__init__.py +1 -0
  7. {utg_base-1.10.1 → utg_base-1.11.1}/src/utg_base/utils/data.py +43 -0
  8. utg_base-1.11.1/src/utg_base/utils/string.py +41 -0
  9. utg_base-1.10.1/src/utg_base/utils/permission.py +0 -24
  10. {utg_base-1.10.1 → utg_base-1.11.1}/README.md +0 -0
  11. {utg_base-1.10.1 → utg_base-1.11.1}/src/utg_base/__init__.py +0 -0
  12. {utg_base-1.10.1 → utg_base-1.11.1}/src/utg_base/api/__init__.py +0 -0
  13. {utg_base-1.10.1 → utg_base-1.11.1}/src/utg_base/api/base.py +0 -0
  14. {utg_base-1.10.1 → utg_base-1.11.1}/src/utg_base/api/pagination.py +0 -0
  15. {utg_base-1.10.1 → utg_base-1.11.1}/src/utg_base/api/permissions.py +0 -0
  16. {utg_base-1.10.1 → utg_base-1.11.1}/src/utg_base/api/routers.py +0 -0
  17. {utg_base-1.10.1 → utg_base-1.11.1}/src/utg_base/api/serializers.py +0 -0
  18. {utg_base-1.10.1 → utg_base-1.11.1}/src/utg_base/api/spectacular.py +0 -0
  19. {utg_base-1.10.1 → utg_base-1.11.1}/src/utg_base/api/views.py +0 -0
  20. {utg_base-1.10.1 → utg_base-1.11.1}/src/utg_base/authentications/__init__.py +0 -0
  21. {utg_base-1.10.1 → utg_base-1.11.1}/src/utg_base/authentications/microservice_authentication.py +0 -0
  22. {utg_base-1.10.1 → utg_base-1.11.1}/src/utg_base/authentications/models.py +0 -0
  23. {utg_base-1.10.1 → utg_base-1.11.1}/src/utg_base/celery/__init__.py +0 -0
  24. {utg_base-1.10.1 → utg_base-1.11.1}/src/utg_base/celery/apps.py +0 -0
  25. {utg_base-1.10.1 → utg_base-1.11.1}/src/utg_base/celery/filters/__init__.py +0 -0
  26. {utg_base-1.10.1 → utg_base-1.11.1}/src/utg_base/celery/filters/task_result.py +0 -0
  27. {utg_base-1.10.1 → utg_base-1.11.1}/src/utg_base/celery/management/__init__.py +0 -0
  28. {utg_base-1.10.1 → utg_base-1.11.1}/src/utg_base/celery/management/commands/__init__.py +0 -0
  29. {utg_base-1.10.1 → utg_base-1.11.1}/src/utg_base/celery/management/commands/migrate_tasks.py +0 -0
  30. {utg_base-1.10.1 → utg_base-1.11.1}/src/utg_base/celery/serializers/__init__.py +0 -0
  31. {utg_base-1.10.1 → utg_base-1.11.1}/src/utg_base/celery/serializers/periodic_task.py +0 -0
  32. {utg_base-1.10.1 → utg_base-1.11.1}/src/utg_base/celery/serializers/task_result.py +0 -0
  33. {utg_base-1.10.1 → utg_base-1.11.1}/src/utg_base/celery/urls.py +0 -0
  34. {utg_base-1.10.1 → utg_base-1.11.1}/src/utg_base/celery/views/__init__.py +0 -0
  35. {utg_base-1.10.1 → utg_base-1.11.1}/src/utg_base/celery/views/periodic_task.py +0 -0
  36. {utg_base-1.10.1 → utg_base-1.11.1}/src/utg_base/celery/views/task_result.py +0 -0
  37. {utg_base-1.10.1 → utg_base-1.11.1}/src/utg_base/constants/__init__.py +0 -0
  38. {utg_base-1.10.1 → utg_base-1.11.1}/src/utg_base/constants/available_languages.py +0 -0
  39. {utg_base-1.10.1 → utg_base-1.11.1}/src/utg_base/env.py +0 -0
  40. {utg_base-1.10.1 → utg_base-1.11.1}/src/utg_base/logging.py +0 -0
  41. {utg_base-1.10.1 → utg_base-1.11.1}/src/utg_base/middleware/__init__.py +0 -0
  42. {utg_base-1.10.1 → utg_base-1.11.1}/src/utg_base/middleware/debug.py +0 -0
  43. {utg_base-1.10.1 → utg_base-1.11.1}/src/utg_base/middleware/locale.py +0 -0
  44. {utg_base-1.10.1 → utg_base-1.11.1}/src/utg_base/migration/__init__.py +0 -0
  45. {utg_base-1.10.1 → utg_base-1.11.1}/src/utg_base/migration/apps.py +0 -0
  46. {utg_base-1.10.1 → utg_base-1.11.1}/src/utg_base/migration/management/__init__.py +0 -0
  47. {utg_base-1.10.1 → utg_base-1.11.1}/src/utg_base/migration/management/commands/__init__.py +0 -0
  48. {utg_base-1.10.1 → utg_base-1.11.1}/src/utg_base/migration/management/commands/makemigrations.py +0 -0
  49. {utg_base-1.10.1 → utg_base-1.11.1}/src/utg_base/models/__init__.py +0 -0
  50. {utg_base-1.10.1 → utg_base-1.11.1}/src/utg_base/models/jwt_user.py +0 -0
  51. {utg_base-1.10.1 → utg_base-1.11.1}/src/utg_base/models/timestamp.py +0 -0
  52. {utg_base-1.10.1/src/utg_base/references_api → utg_base-1.11.1/src/utg_base/permissions}/__init__.py +0 -0
  53. {utg_base-1.10.1/src/utg_base/references_api/migrations → utg_base-1.11.1/src/utg_base/references_api}/__init__.py +0 -0
  54. {utg_base-1.10.1 → utg_base-1.11.1}/src/utg_base/references_api/admin.py +0 -0
  55. {utg_base-1.10.1 → utg_base-1.11.1}/src/utg_base/references_api/apps.py +0 -0
  56. {utg_base-1.10.1/src/utg_base/u_services → utg_base-1.11.1/src/utg_base/references_api/migrations}/__init__.py +0 -0
  57. {utg_base-1.10.1 → utg_base-1.11.1}/src/utg_base/references_api/models.py +0 -0
  58. {utg_base-1.10.1 → utg_base-1.11.1}/src/utg_base/references_api/urls.py +0 -0
  59. {utg_base-1.10.1 → utg_base-1.11.1}/src/utg_base/references_api/utils.py +0 -0
  60. {utg_base-1.10.1 → utg_base-1.11.1}/src/utg_base/services/__init__.py +0 -0
  61. {utg_base-1.10.1 → utg_base-1.11.1}/src/utg_base/services/base_api.py +0 -0
  62. {utg_base-1.10.1/src/utg_base/u_services/migrations → utg_base-1.11.1/src/utg_base/u_services}/__init__.py +0 -0
  63. {utg_base-1.10.1 → utg_base-1.11.1}/src/utg_base/u_services/apps.py +0 -0
  64. {utg_base-1.10.1 → utg_base-1.11.1}/src/utg_base/u_services/constants.py +0 -0
  65. {utg_base-1.10.1 → utg_base-1.11.1}/src/utg_base/u_services/migrations/0001_initial.py +0 -0
  66. {utg_base-1.10.1 → utg_base-1.11.1}/src/utg_base/u_services/models.py +0 -0
  67. {utg_base-1.10.1 → utg_base-1.11.1}/src/utg_base/u_services/u_requests.py +0 -0
  68. {utg_base-1.10.1 → utg_base-1.11.1}/src/utg_base/utils/date.py +0 -0
  69. {utg_base-1.10.1 → utg_base-1.11.1}/src/utg_base/utils/dict_util.py +0 -0
  70. {utg_base-1.10.1 → utg_base-1.11.1}/src/utg_base/utils/response_processors.py +0 -0
  71. {utg_base-1.10.1 → utg_base-1.11.1}/src/utg_base/utils/sql.py +0 -0
  72. {utg_base-1.10.1 → utg_base-1.11.1}/src/utg_base/utils/thread.py +0 -0
  73. {utg_base-1.10.1 → utg_base-1.11.1}/src/utg_base/utils/translation.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: utg-base
3
- Version: 1.10.1
3
+ Version: 1.11.1
4
4
  Summary: UTG Base Package
5
5
  Author: Olimboy
6
6
  Author-email: shavkatov.olimboy@mail.ru
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "utg-base"
3
- version = "1.10.1"
3
+ version = "1.11.1"
4
4
  description = "UTG Base Package"
5
5
  authors = ["Olimboy <shavkatov.olimboy@mail.ru>", "Rovshen <rovshenashirov1619@gmail.com>"]
6
6
  readme = "README.md"
@@ -0,0 +1,25 @@
1
+ from functools import wraps
2
+ from typing import Literal
3
+
4
+ from rest_framework.exceptions import PermissionDenied
5
+
6
+ from .utils import has_perms as has_permissions
7
+
8
+
9
+ def has_perm(*permission: str, operator: Literal["OR", "AND"] = "OR"):
10
+ """
11
+ Usage:
12
+ has_perm('PERM1')
13
+ has_perm('PERM1', 'PERM2')
14
+
15
+ :param permission: One or many permissions.
16
+ """
17
+ def decorator(view_func):
18
+ view_func._perms = permission
19
+ @wraps(view_func)
20
+ def _wrapped_view(self, request, *args, **kwargs):
21
+ if not has_permissions(user_id=request.user.id, perms=list(permission), operator=operator):
22
+ raise PermissionDenied
23
+ return view_func(self, request, *args, **kwargs)
24
+ return _wrapped_view
25
+ return decorator
@@ -0,0 +1,76 @@
1
+ from typing import Literal
2
+
3
+ from django.http import HttpRequest
4
+ from django.urls import URLResolver, URLPattern, get_resolver
5
+ from django_redis import get_redis_connection
6
+ from redis.client import Redis
7
+ from rest_framework.request import Request
8
+
9
+ from utg_base.env import env
10
+ from utg_base.u_services import u_requests
11
+ from utg_base.u_services.constants import UServices
12
+ from utg_base.utils import to_snake_case
13
+ from utg_base.utils.data import safe_get
14
+
15
+
16
+ def generate_perm_cache_key(user_id: str):
17
+ return f"user:{user_id}:permissions"
18
+
19
+
20
+ def has_perm(user_id, perm):
21
+ redis_conn: Redis = get_redis_connection("shared")
22
+
23
+ return bool(redis_conn.sismember(f'user:{user_id}:permissions', perm))
24
+
25
+
26
+ def has_perms(user_id: str, perms: list[str], operator: Literal["OR", "AND"] = "OR"):
27
+ redis_conn: Redis = get_redis_connection("shared")
28
+
29
+ result = redis_conn.smismember(f'user:{user_id}:permissions', perms)
30
+
31
+ if operator == "OR":
32
+ return any(result)
33
+ return all(result)
34
+
35
+
36
+ def _get_permissions(url_patterns):
37
+ permissions = set()
38
+
39
+ for item in url_patterns:
40
+ if isinstance(item, URLPattern):
41
+ view = item.callback
42
+
43
+ view_cls = safe_get(view, 'view_class')
44
+ # for View
45
+ if view_cls:
46
+ for method_name in ["get", "post", "put", "patch", "delete"]:
47
+ perms = safe_get(view_cls, f'{method_name}._perms', set())
48
+ permissions.update(perms)
49
+ # for ViewSet
50
+ else:
51
+ view_cls = safe_get(view, 'cls')
52
+ actions: dict = safe_get(view, 'actions', {})
53
+ for method_name in actions.values():
54
+ perms = safe_get(view_cls, f'{method_name}._perms', set())
55
+ permissions.update(perms)
56
+
57
+ elif isinstance(item, URLResolver):
58
+ permissions.update(_get_permissions(
59
+ item.url_patterns
60
+ ))
61
+
62
+ return permissions
63
+
64
+
65
+ def sync_permissions():
66
+ resolver = get_resolver()
67
+ perm_codes = list(_get_permissions(resolver.url_patterns))
68
+
69
+ request = Request(HttpRequest())
70
+ try:
71
+ u_requests.post(request, UServices.USER_MANAGEMENT, '/api/permissions/', data={
72
+ "service": to_snake_case(env('APP_NAME')),
73
+ "codes": perm_codes
74
+ })
75
+ except Exception as e:
76
+ print(e)
@@ -1,2 +1,3 @@
1
1
  from .data import deep_map, deep_round, safe_sum, safe_subtract
2
2
  from .dict_util import get_by_dotted
3
+ from .string import to_snake_case
@@ -104,3 +104,46 @@ def safe_divide(*args, allow_null=False):
104
104
  result /= value
105
105
 
106
106
  return result
107
+
108
+
109
+ def safe_get(obj, dotted_path: str, default=None):
110
+ """
111
+ Universal safe getter: supports dicts, objects, lists, and mixes of them.
112
+
113
+ Args:
114
+ obj: Any object, dict, list, etc.
115
+ dotted_path (str): Path like 'a.b.c' or 'items.0.name'.
116
+ default: Returned if path does not exist.
117
+
118
+ Returns:
119
+ Any: The resolved value or default.
120
+ """
121
+ parts = dotted_path.split('.')
122
+
123
+ for part in parts:
124
+ if obj is None:
125
+ return default
126
+
127
+ # --- Handle LIST index ---
128
+ if isinstance(obj, (list, tuple)) and part.isdigit():
129
+ idx = int(part)
130
+ if 0 <= idx < len(obj):
131
+ obj = obj[idx]
132
+ else:
133
+ return default
134
+
135
+ # --- Handle DICT ---
136
+ elif isinstance(obj, dict):
137
+ if part in obj:
138
+ obj = obj.get(part)
139
+ else:
140
+ return default
141
+
142
+ # --- Handle OBJECT attribute ---
143
+ else:
144
+ if hasattr(obj, part):
145
+ obj = getattr(obj, part)
146
+ else:
147
+ return default
148
+
149
+ return obj
@@ -0,0 +1,41 @@
1
+ import re
2
+
3
+ def to_snake_case(s: str) -> str:
4
+ """
5
+ Convert any string into snake_case.
6
+ Handles:
7
+ - camelCase / PascalCase
8
+ - mixed separators (-, space, ., !, etc.)
9
+ - multiple underscores
10
+ - trims leading/trailing separators
11
+ """
12
+ if not isinstance(s, str):
13
+ raise TypeError("Input must be a string")
14
+
15
+ # Trim spaces
16
+ s = s.strip()
17
+ if not s:
18
+ return ""
19
+
20
+ # Insert underscore between lower/number and upper case letters
21
+ # "helloWorld" -> "hello_World"
22
+ s = re.sub(r'(?<=[0-9a-z])(?=[A-Z])', '_', s)
23
+
24
+ # Insert underscore between acronyms and normal words
25
+ # "HTTPResponse" -> "HTTP_Response"
26
+ s = re.sub(r'(?<=[A-Z])(?=[A-Z][a-z])', '_', s)
27
+
28
+ # Replace all non-word chars with underscore
29
+ # \W = anything except letters, digits, underscore
30
+ s = re.sub(r'\W+', '_', s)
31
+
32
+ # Replace spaces if any remain
33
+ s = s.replace(" ", "_")
34
+
35
+ # Collapse multiple underscores
36
+ s = re.sub(r'_+', '_', s)
37
+
38
+ # Remove leading/trailing underscores
39
+ s = s.strip('_')
40
+
41
+ return s.lower()
@@ -1,24 +0,0 @@
1
- from typing import Literal
2
-
3
- from django_redis import get_redis_connection
4
- from redis.client import Redis
5
-
6
-
7
- def generate_perm_cache_key(user_id: str):
8
- return f"user:{user_id}:permissions"
9
-
10
-
11
- def has_perm(user_id, perm):
12
- redis_conn: Redis = get_redis_connection("shared")
13
-
14
- return bool(redis_conn.sismember(f'user:{user_id}:permissions', perm))
15
-
16
-
17
- def has_perms(user_id: str, perms: list[str], operator: Literal["OR", "AND"] = "OR"):
18
- redis_conn: Redis = get_redis_connection("shared")
19
-
20
- result = redis_conn.smismember(f'user:{user_id}:permissions', perms)
21
-
22
- if operator == "OR":
23
- return any(result)
24
- return all(result)
File without changes
File without changes