utg-base 1.5.1__tar.gz → 1.8.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 (62) hide show
  1. {utg_base-1.5.1 → utg_base-1.8.1}/PKG-INFO +4 -4
  2. utg_base-1.8.1/README.md +1 -0
  3. {utg_base-1.5.1 → utg_base-1.8.1}/pyproject.toml +2 -2
  4. utg_base-1.8.1/src/utg_base/api/spectacular.py +33 -0
  5. {utg_base-1.5.1 → utg_base-1.8.1}/src/utg_base/celery/apps.py +1 -1
  6. {utg_base-1.5.1 → utg_base-1.8.1}/src/utg_base/env.py +4 -0
  7. {utg_base-1.5.1 → utg_base-1.8.1}/src/utg_base/logging.py +13 -11
  8. utg_base-1.8.1/src/utg_base/middleware/debug.py +10 -0
  9. utg_base-1.8.1/src/utg_base/migration/apps.py +6 -0
  10. utg_base-1.8.1/src/utg_base/migration/management/commands/__init__.py +0 -0
  11. utg_base-1.8.1/src/utg_base/migration/management/commands/makemigrations.py +94 -0
  12. utg_base-1.8.1/src/utg_base/references_api/__init__.py +0 -0
  13. utg_base-1.8.1/src/utg_base/references_api/migrations/__init__.py +0 -0
  14. {utg_base-1.5.1 → utg_base-1.8.1}/src/utg_base/references_api/utils.py +3 -3
  15. {utg_base-1.5.1 → utg_base-1.8.1}/src/utg_base/services/base_api.py +73 -31
  16. utg_base-1.8.1/src/utg_base/utils/thread.py +67 -0
  17. {utg_base-1.5.1 → utg_base-1.8.1}/src/utg_base/utils/translation.py +2 -3
  18. utg_base-1.5.1/README.md +0 -1
  19. {utg_base-1.5.1 → utg_base-1.8.1}/src/utg_base/__init__.py +0 -0
  20. {utg_base-1.5.1 → utg_base-1.8.1}/src/utg_base/api/__init__.py +0 -0
  21. {utg_base-1.5.1 → utg_base-1.8.1}/src/utg_base/api/base.py +0 -0
  22. {utg_base-1.5.1 → utg_base-1.8.1}/src/utg_base/api/pagination.py +0 -0
  23. {utg_base-1.5.1 → utg_base-1.8.1}/src/utg_base/api/permissions.py +0 -0
  24. {utg_base-1.5.1 → utg_base-1.8.1}/src/utg_base/api/routers.py +0 -0
  25. {utg_base-1.5.1 → utg_base-1.8.1}/src/utg_base/api/serializers.py +0 -0
  26. {utg_base-1.5.1 → utg_base-1.8.1}/src/utg_base/api/views.py +0 -0
  27. {utg_base-1.5.1 → utg_base-1.8.1}/src/utg_base/authentications/__init__.py +0 -0
  28. {utg_base-1.5.1 → utg_base-1.8.1}/src/utg_base/authentications/microservice_authentication.py +0 -0
  29. {utg_base-1.5.1 → utg_base-1.8.1}/src/utg_base/authentications/models.py +0 -0
  30. {utg_base-1.5.1 → utg_base-1.8.1}/src/utg_base/celery/__init__.py +0 -0
  31. {utg_base-1.5.1 → utg_base-1.8.1}/src/utg_base/celery/filters/__init__.py +0 -0
  32. {utg_base-1.5.1 → utg_base-1.8.1}/src/utg_base/celery/filters/task_result.py +0 -0
  33. {utg_base-1.5.1 → utg_base-1.8.1}/src/utg_base/celery/management/__init__.py +0 -0
  34. {utg_base-1.5.1 → utg_base-1.8.1}/src/utg_base/celery/management/commands/__init__.py +0 -0
  35. {utg_base-1.5.1 → utg_base-1.8.1}/src/utg_base/celery/management/commands/migrate_tasks.py +0 -0
  36. {utg_base-1.5.1 → utg_base-1.8.1}/src/utg_base/celery/serializers/__init__.py +0 -0
  37. {utg_base-1.5.1 → utg_base-1.8.1}/src/utg_base/celery/serializers/periodic_task.py +0 -0
  38. {utg_base-1.5.1 → utg_base-1.8.1}/src/utg_base/celery/serializers/task_result.py +0 -0
  39. {utg_base-1.5.1 → utg_base-1.8.1}/src/utg_base/celery/urls.py +0 -0
  40. {utg_base-1.5.1 → utg_base-1.8.1}/src/utg_base/celery/views/__init__.py +0 -0
  41. {utg_base-1.5.1 → utg_base-1.8.1}/src/utg_base/celery/views/periodic_task.py +0 -0
  42. {utg_base-1.5.1 → utg_base-1.8.1}/src/utg_base/celery/views/task_result.py +0 -0
  43. {utg_base-1.5.1 → utg_base-1.8.1}/src/utg_base/constants/__init__.py +0 -0
  44. {utg_base-1.5.1 → utg_base-1.8.1}/src/utg_base/constants/available_languages.py +0 -0
  45. {utg_base-1.5.1 → utg_base-1.8.1}/src/utg_base/middleware/__init__.py +0 -0
  46. {utg_base-1.5.1 → utg_base-1.8.1}/src/utg_base/middleware/locale.py +0 -0
  47. {utg_base-1.5.1/src/utg_base/references_api → utg_base-1.8.1/src/utg_base/migration}/__init__.py +0 -0
  48. {utg_base-1.5.1/src/utg_base/references_api/migrations → utg_base-1.8.1/src/utg_base/migration/management}/__init__.py +0 -0
  49. {utg_base-1.5.1 → utg_base-1.8.1}/src/utg_base/models/__init__.py +0 -0
  50. {utg_base-1.5.1 → utg_base-1.8.1}/src/utg_base/models/jwt_user.py +0 -0
  51. {utg_base-1.5.1 → utg_base-1.8.1}/src/utg_base/models/timestamp.py +0 -0
  52. {utg_base-1.5.1 → utg_base-1.8.1}/src/utg_base/references_api/admin.py +0 -0
  53. {utg_base-1.5.1 → utg_base-1.8.1}/src/utg_base/references_api/apps.py +0 -0
  54. {utg_base-1.5.1 → utg_base-1.8.1}/src/utg_base/references_api/models.py +0 -0
  55. {utg_base-1.5.1 → utg_base-1.8.1}/src/utg_base/references_api/urls.py +0 -0
  56. {utg_base-1.5.1 → utg_base-1.8.1}/src/utg_base/services/__init__.py +0 -0
  57. {utg_base-1.5.1 → utg_base-1.8.1}/src/utg_base/utils/__init__.py +0 -0
  58. {utg_base-1.5.1 → utg_base-1.8.1}/src/utg_base/utils/data.py +0 -0
  59. {utg_base-1.5.1 → utg_base-1.8.1}/src/utg_base/utils/date.py +0 -0
  60. {utg_base-1.5.1 → utg_base-1.8.1}/src/utg_base/utils/dict_util.py +0 -0
  61. {utg_base-1.5.1 → utg_base-1.8.1}/src/utg_base/utils/response_processors.py +0 -0
  62. {utg_base-1.5.1 → utg_base-1.8.1}/src/utg_base/utils/sql.py +0 -0
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: utg-base
3
- Version: 1.5.1
3
+ Version: 1.8.1
4
4
  Summary: UTG Base Package
5
- Author: Rovshen
6
- Author-email: rovshenashirov1619@gmail.com
5
+ Author: Olimboy
6
+ Author-email: shavkatov.olimboy@mail.ru
7
7
  Requires-Python: >=3.14,<4.0
8
8
  Classifier: Programming Language :: Python :: 3
9
9
  Requires-Dist: celery (>=5.5.3,<6.0.0)
@@ -21,4 +21,4 @@ Requires-Dist: inflect (>=7.2.1,<8.0.0)
21
21
  Requires-Dist: openpyxl (>=3.1.2,<4.0.0)
22
22
  Description-Content-Type: text/markdown
23
23
 
24
- # UzTransGas Base
24
+ # UTG Base
@@ -0,0 +1 @@
1
+ # UTG Base
@@ -1,8 +1,8 @@
1
1
  [tool.poetry]
2
2
  name = "utg-base"
3
- version = "1.5.1"
3
+ version = "1.8.1"
4
4
  description = "UTG Base Package"
5
- authors = ["Rovshen <rovshenashirov1619@gmail.com>"]
5
+ authors = ["Olimboy <shavkatov.olimboy@mail.ru>", "Rovshen <rovshenashirov1619@gmail.com>"]
6
6
  readme = "README.md"
7
7
 
8
8
  [tool.poetry.dependencies]
@@ -0,0 +1,33 @@
1
+ from django.conf import settings
2
+ from django.http import HttpResponseForbidden
3
+ from django.urls import path
4
+ from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView
5
+ from rest_framework.views import APIView
6
+
7
+ from utg_base.env import env
8
+
9
+
10
+ class DebugTokenRequiredMixin(APIView):
11
+ """
12
+ If Request header X-Debug-Token not found then 403
13
+ """
14
+
15
+ def dispatch(self, request, *args, **kwargs):
16
+ token = request.headers.get("X-Debug-Token")
17
+ if not settings.DEBUG and token != env("DJANGO_SECRET_KEY"):
18
+ return HttpResponseForbidden("Forbidden")
19
+ return super().dispatch(request, *args, **kwargs)
20
+
21
+
22
+ class ProtectedSpectacularAPIView(DebugTokenRequiredMixin, SpectacularAPIView):
23
+ pass
24
+
25
+
26
+ class ProtectedSpectacularSwaggerView(DebugTokenRequiredMixin, SpectacularSwaggerView):
27
+ pass
28
+
29
+
30
+ urlpatterns = [
31
+ path('api/swagger/schema/', ProtectedSpectacularAPIView.as_view(), name='schema'),
32
+ path('api/docs/', ProtectedSpectacularSwaggerView.as_view(url_name='schema'), name='docs'),
33
+ ]
@@ -1,6 +1,6 @@
1
1
  from django.apps import AppConfig
2
2
 
3
3
 
4
- class QApiConfig(AppConfig):
4
+ class CeleryApiConfig(AppConfig):
5
5
  default_auto_field = "django.db.models.BigAutoField"
6
6
  name = "utg_base.celery"
@@ -6,6 +6,10 @@ from django.conf import settings
6
6
 
7
7
  dotenv.load_dotenv(settings.BASE_DIR / '.env')
8
8
 
9
+ assert os.environ.get('VAULT_URL') is not None, ("VAULT_URL not defined, please set VAULT_URL on .env file")
10
+ assert os.environ.get('VAULT_TOKEN') is not None, ("VAULT_TOKEN not defined, please set VAULT_TOKEN on .env file")
11
+ assert os.environ.get('VAULT_PATH') is not None, ("VAULT_PATH not defined, please set VAULT_PATH on .env file")
12
+
9
13
  client = hvac.Client(
10
14
  url=os.environ.get("VAULT_URL"),
11
15
  token=os.environ.get("VAULT_TOKEN"),
@@ -1,6 +1,8 @@
1
1
  import logging
2
2
  import os
3
3
 
4
+ from utg_base.env import env
5
+
4
6
 
5
7
  class UtgBaseFilter(logging.Filter):
6
8
  BASE_DIR = os.getcwd()
@@ -21,10 +23,10 @@ class UtgBaseFilter(logging.Filter):
21
23
 
22
24
 
23
25
  class UtgBaseLogging:
24
- enable_console_info = int(os.environ.get('LOGGING_CONSOLE_INFO', True))
25
- enable_db_debug = int(os.environ.get('LOGGING_DB_DEBUG', False))
26
- enable_loki = int(os.environ.get('LOGGING_LOKI', True))
27
- enable_loki_debug = int(os.environ.get('LOGGING_LOKI_DEBUG', False))
26
+ enable_console_info = int(env('LOGGING_CONSOLE_INFO', True))
27
+ enable_db_debug = int(env('LOGGING_DB_DEBUG', False))
28
+ enable_loki = int(env('LOGGING_LOKI', True))
29
+ enable_loki_debug = int(env('LOGGING_LOKI_DEBUG', False))
28
30
 
29
31
  logger = logging.getLogger('main')
30
32
 
@@ -63,19 +65,19 @@ class UtgBaseLogging:
63
65
  'filters': ['trim_path'],
64
66
  },
65
67
  'loki': {
66
- 'level': os.environ.get('LOKI_LEVEL', 'ERROR'), # required
68
+ 'level': env('LOKI_LEVEL', 'ERROR'), # required
67
69
  'class': 'django_loki.LokiHttpHandler', # required
68
- 'host': os.environ.get('LOKI_HOST', os.environ.get('DB_HOST')),
70
+ 'host': env('LOKI_HOST', env('DB_HOST')),
69
71
  # required, your grafana/Loki server host, e.g:192.168.25.30
70
72
  'formatter': 'loki', # required, loki formatter,
71
- 'port': int(os.environ.get('LOKI_PORT', 3100)),
73
+ 'port': int(env('LOKI_PORT', 3100)),
72
74
  # optional, your grafana/Loki server port, default is 3100
73
- 'timeout': float(os.environ.get('LOKI_TIMEOUT', 0.5)),
75
+ 'timeout': float(env('LOKI_TIMEOUT', 0.5)),
74
76
  # optional, request Loki-server by http or https time out, default is 0.5
75
- 'protocol': os.environ.get('LOKI_PROTOCOL', 'http'),
77
+ 'protocol': env('LOKI_PROTOCOL', 'http'),
76
78
  # optional, Loki-server protocol, default is http
77
- 'source': os.environ.get('LOKI_SOURCE', 'Loki'), # optional, label name for Loki, default is Loki
78
- 'src_host': os.environ.get('LOKI_SRC_HOST', 'localhost'),
79
+ 'source': env('LOKI_SOURCE', 'Loki'), # optional, label name for Loki, default is Loki
80
+ 'src_host': env('LOKI_SRC_HOST', 'localhost'),
79
81
  # optional, label name for Loki, default is localhost
80
82
  'tz': 'Asia/Tashkent',
81
83
  # optional, timezone for formatting timestamp, default is UTC, e.g:Asia/Shanghai
@@ -0,0 +1,10 @@
1
+ from django.utils.deprecation import MiddlewareMixin
2
+
3
+ from utg_base.env import env
4
+
5
+
6
+ class DebugOverrideMiddleware(MiddlewareMixin):
7
+ def process_exception(self, request, exception):
8
+ if request.headers.get("X-Debug-Token") == env("DJANGO_SECRET_KEY"):
9
+ from django.views import debug
10
+ return debug.technical_500_response(request, type(exception), exception, exception.__traceback__)
@@ -0,0 +1,6 @@
1
+ from django.apps import AppConfig
2
+
3
+
4
+ class MigrationApiConfig(AppConfig):
5
+ default_auto_field = "django.db.models.BigAutoField"
6
+ name = "utg_base.migration"
@@ -0,0 +1,94 @@
1
+ import os
2
+
3
+ from django.core.management.commands.makemigrations import Command as BaseMakeMigrations
4
+ from django.core.management.utils import run_formatters
5
+ from django.db.migrations.writer import MigrationWriter
6
+ from django.db.migrations.operations import CreateModel
7
+
8
+
9
+ class Command(BaseMakeMigrations):
10
+
11
+ def write_migration_files(self, changes, update_previous_migration_paths=None):
12
+ """
13
+ Take a changes dict and write them out as migration files.
14
+ """
15
+ directory_created = {}
16
+ for app_label, app_migrations in changes.items():
17
+ if self.verbosity >= 1:
18
+ self.log(self.style.MIGRATE_HEADING("Migrations for '%s':" % app_label))
19
+ for migration in app_migrations:
20
+ # Describe the migration
21
+ writer = MigrationWriter(migration, self.include_header)
22
+
23
+ # BEGIN Customizing
24
+ # fix 'created_at', 'updated_at' fields order
25
+ for operation in migration.operations:
26
+ if not isinstance(operation, CreateModel):
27
+ continue
28
+ _fields = []
29
+ _end_fields = []
30
+ for field_name, field in operation.fields:
31
+ if field_name in ['created_at', 'updated_at']:
32
+ _end_fields.append((field_name, field))
33
+ else:
34
+ _fields.append((field_name, field))
35
+
36
+ operation.fields = _fields + _end_fields
37
+
38
+ # END Customizing
39
+
40
+ if self.verbosity >= 1:
41
+ # Display a relative path if it's below the current working
42
+ # directory, or an absolute path otherwise.
43
+ migration_string = self.get_relative_path(writer.path)
44
+ self.log(" %s\n" % self.style.MIGRATE_LABEL(migration_string))
45
+ for operation in migration.operations:
46
+ self.log(" %s" % operation.formatted_description())
47
+ if self.scriptable:
48
+ self.stdout.write(migration_string)
49
+ if not self.dry_run:
50
+ # Write the migrations file to the disk.
51
+ migrations_directory = os.path.dirname(writer.path)
52
+ if not directory_created.get(app_label):
53
+ os.makedirs(migrations_directory, exist_ok=True)
54
+ init_path = os.path.join(migrations_directory, "__init__.py")
55
+ if not os.path.isfile(init_path):
56
+ open(init_path, "w").close()
57
+ # We just do this once per app
58
+ directory_created[app_label] = True
59
+ migration_string = writer.as_string()
60
+ with open(writer.path, "w", encoding="utf-8") as fh:
61
+ fh.write(migration_string)
62
+ self.written_files.append(writer.path)
63
+ if update_previous_migration_paths:
64
+ prev_path = update_previous_migration_paths[app_label]
65
+ rel_prev_path = self.get_relative_path(prev_path)
66
+ if writer.needs_manual_porting:
67
+ migration_path = self.get_relative_path(writer.path)
68
+ self.log(
69
+ self.style.WARNING(
70
+ f"Updated migration {migration_path} requires "
71
+ f"manual porting.\n"
72
+ f"Previous migration {rel_prev_path} was kept and "
73
+ f"must be deleted after porting functions manually."
74
+ )
75
+ )
76
+ else:
77
+ os.remove(prev_path)
78
+ self.log(f"Deleted {rel_prev_path}")
79
+ elif self.verbosity == 3:
80
+ # Alternatively, makemigrations --dry-run --verbosity 3
81
+ # will log the migrations rather than saving the file to
82
+ # the disk.
83
+ self.log(
84
+ self.style.MIGRATE_HEADING(
85
+ "Full migrations file '%s':" % writer.filename
86
+ )
87
+ )
88
+ self.log(writer.as_string())
89
+ run_formatters(self.written_files, stderr=self.stderr)
90
+
91
+ def handle(self, *args, **options):
92
+ self.written_files = []
93
+ result = super().handle(*args, **options)
94
+ return result
File without changes
@@ -102,7 +102,7 @@ def create_view_set(model: Model):
102
102
 
103
103
 
104
104
  def create_serializer_for_create(model: Model):
105
- @extend_schema_serializer(component_name='Admin' + get_serializer_name(model))
105
+ @extend_schema_serializer(component_name='Admin1' + get_serializer_name(model))
106
106
  class Serializer(serializers.ModelSerializer):
107
107
  class Meta:
108
108
  fields = '__all__'
@@ -114,7 +114,7 @@ def create_serializer_for_create(model: Model):
114
114
  def create_serializer_for_partial_update(model: Model):
115
115
  fields_list = get_model_fields_list(model)
116
116
 
117
- @extend_schema_serializer(component_name='Admin' + get_serializer_name(model))
117
+ @extend_schema_serializer(component_name='Admin2' + get_serializer_name(model))
118
118
  class Serializer(serializers.ModelSerializer):
119
119
  class Meta:
120
120
  fields = '__all__'
@@ -147,7 +147,7 @@ def create_serializer(model: Model, depth=0):
147
147
  parsed_url = urlparse(value.url)
148
148
  return f'{parsed_url.path}?{parsed_url.query}'
149
149
 
150
- @extend_schema_serializer(component_name='Admin' + get_serializer_name(model))
150
+ @extend_schema_serializer(component_name='Admin3' + get_serializer_name(model))
151
151
  class Serializer(serializers.ModelSerializer):
152
152
  def __init__(self, *args, **kwargs):
153
153
  super().__init__(*args, **kwargs)
@@ -14,10 +14,19 @@ class BaseServiceAPI:
14
14
  base_url: str = None
15
15
  default_response_processor: Callable[[Response], Any] = None
16
16
 
17
- def request(self, method, path, data=None, json=None, params=None, headers=None,
18
- authenticator: Callable[[dict], dict] | None = 'default',
19
- response_processor: Callable[[Response], Any] | None = 'default',
20
- **kwargs):
17
+ @classmethod
18
+ def request(
19
+ cls,
20
+ method,
21
+ path,
22
+ data=None,
23
+ json=None,
24
+ params=None,
25
+ headers=None,
26
+ authenticator: Callable[[dict], dict] | None = 'default',
27
+ response_processor: Callable[[Response], Any] | None = 'default',
28
+ **kwargs
29
+ ):
21
30
  """
22
31
  Make an HTTP request using the requests' library.
23
32
 
@@ -53,12 +62,12 @@ class BaseServiceAPI:
53
62
  if callable(authenticator):
54
63
  headers = authenticator(headers)
55
64
 
56
- if authenticator == 'default' and hasattr(self, 'authenticate') and callable(self.authenticate):
57
- headers = self.authenticate(headers)
65
+ if authenticator == 'default' and hasattr(cls, 'authenticate') and callable(cls.authenticate):
66
+ headers = cls.authenticate(headers)
58
67
 
59
68
  response = requests.request(
60
69
  method=method,
61
- url=self.base_url + path,
70
+ url=cls.base_url + path,
62
71
  data=data,
63
72
  json=json,
64
73
  params=params,
@@ -68,8 +77,8 @@ class BaseServiceAPI:
68
77
  )
69
78
 
70
79
  if response_processor == 'default':
71
- if hasattr(self, 'default_response_processor') and callable(self.default_response_processor):
72
- return self.default_response_processor(response)
80
+ if hasattr(cls, 'default_response_processor') and callable(cls.default_response_processor):
81
+ return cls.default_response_processor(response)
73
82
  return response
74
83
 
75
84
  if response_processor is not None:
@@ -77,8 +86,9 @@ class BaseServiceAPI:
77
86
 
78
87
  return response
79
88
 
80
- def call(self, method, path, data=None, json=None, params=None, headers=None):
81
- data = self.request(
89
+ @classmethod
90
+ def call(cls, method, path, data=None, json=None, params=None, headers=None):
91
+ data = cls.request(
82
92
  method=method,
83
93
  path=path,
84
94
  data=data,
@@ -88,7 +98,7 @@ class BaseServiceAPI:
88
98
  response_processor=call_processor
89
99
  )
90
100
  return {
91
- 'path': self.base_url + path,
101
+ 'path': cls.base_url + path,
92
102
  'method': method,
93
103
  'status': data['status'],
94
104
  'reason': data['reason'],
@@ -96,10 +106,11 @@ class BaseServiceAPI:
96
106
  'response': data['response'],
97
107
  }
98
108
 
99
- def authenticate(self, headers: dict) -> dict:
109
+ @classmethod
110
+ def authenticate(cls, headers: dict) -> dict:
100
111
  """
101
112
  Example authenticate method:
102
- authenticate(self, headers):
113
+ authenticate(cls, headers):
103
114
  headers['Authorization'] = 'Bearer YOUR_ACCESS_TOKEN'
104
115
  return headers
105
116
  :param headers:
@@ -107,22 +118,53 @@ class BaseServiceAPI:
107
118
  """
108
119
  return headers
109
120
 
110
- def get(self, path, data=None, json=None, params=None, headers=None,
121
+ @classmethod
122
+ def get(
123
+ cls,
124
+ path,
125
+ data=None,
126
+ json=None,
127
+ params=None,
128
+ headers=None,
111
129
  authenticator: Callable[[dict], dict] | None = 'default',
112
- response_processor: Callable[[Response], Any] | None = 'default'):
113
- return self.request('get', path, data, json, params, headers, authenticator, response_processor)
114
-
115
- def post(self, path, data=None, json=None, params=None, headers=None,
116
- authenticator: Callable[[dict], dict] | None = 'default',
117
- response_processor: Callable[[Response], Any] | None = 'default'):
118
- return self.request('post', path, data, json, params, headers, authenticator, response_processor)
119
-
120
- def put(self, path, data=None, json=None, params=None, headers=None,
130
+ response_processor: Callable[[Response], Any] | None = 'default'
131
+ ):
132
+ return cls.request('get', path, data, json, params, headers, authenticator, response_processor)
133
+
134
+ @classmethod
135
+ def post(
136
+ cls,
137
+ path,
138
+ data=None,
139
+ json=None,
140
+ params=None,
141
+ headers=None,
121
142
  authenticator: Callable[[dict], dict] | None = 'default',
122
- response_processor: Callable[[Response], Any] | None = 'default'):
123
- return self.request('put', path, data, json, params, headers, authenticator, response_processor)
124
-
125
- def patch(self, path, data=None, json=None, params=None, headers=None,
126
- authenticator: Callable[[dict], dict] | None = 'default',
127
- response_processor: Callable[[Response], Any] | None = 'default'):
128
- return self.request('patch', path, data, json, params, headers, authenticator, response_processor)
143
+ response_processor: Callable[[Response], Any] | None = 'default'
144
+ ):
145
+ return cls.request('post', path, data, json, params, headers, authenticator, response_processor)
146
+
147
+ @classmethod
148
+ def put(
149
+ cls,
150
+ path,
151
+ data=None,
152
+ json=None,
153
+ params=None,
154
+ headers=None,
155
+ authenticator: Callable[[dict], dict] | None = 'default',
156
+ response_processor: Callable[[Response], Any] | None = 'default'
157
+ ):
158
+ return cls.request('put', path, data, json, params, headers, authenticator, response_processor)
159
+
160
+ @classmethod
161
+ def patch(
162
+ cls,
163
+ path,
164
+ data=None,
165
+ json=None,
166
+ params=None, headers=None,
167
+ authenticator: Callable[[dict], dict] | None = 'default',
168
+ response_processor: Callable[[Response], Any] | None = 'default'
169
+ ):
170
+ return cls.request('patch', path, data, json, params, headers, authenticator, response_processor)
@@ -0,0 +1,67 @@
1
+ import multiprocessing
2
+ from concurrent.futures import ThreadPoolExecutor, as_completed
3
+ from typing import Callable, Any, Iterable, Tuple, Dict, Union
4
+
5
+
6
+ class ThreadPoolException(Exception):
7
+ """Exception to wrap the error in each task."""
8
+
9
+ def __init__(self, func: str, exception: Exception):
10
+ self.func = func
11
+ self.exception = exception
12
+ super().__init__(f"Exception in {func}: {exception}")
13
+
14
+
15
+ def parallel_execute(
16
+ *tasks: Union[
17
+ Callable, # func
18
+ Tuple[Callable, Iterable], # (func, args)
19
+ Tuple[Callable, Iterable, Dict[str, Any]] # (func, args, kwargs)
20
+ ],
21
+ max_workers: int = None
22
+ ) -> list[Any]:
23
+ """
24
+ Executes multiple functions in parallel.
25
+ Each can be without arguments or with args/kwargs.
26
+
27
+ Example:
28
+ parallel_execute(func1, func2)
29
+ parallel_execute((func1, (1, 2)), (func2, (), {"x": 5}))
30
+
31
+ The sequence is preserved.
32
+
33
+ :return: Result list or ThreadPoolException objects
34
+ """
35
+ if not tasks:
36
+ return []
37
+
38
+ cpu_count = multiprocessing.cpu_count()
39
+ max_workers = max_workers or min(cpu_count * 10, len(tasks))
40
+ results = [None] * len(tasks)
41
+
42
+ with ThreadPoolExecutor(max_workers=max_workers) as executor:
43
+ futures = {}
44
+
45
+ for i, task in enumerate(tasks):
46
+ # Task unpacking
47
+ if callable(task):
48
+ func, args, kwargs = task, (), {}
49
+ elif isinstance(task, tuple):
50
+ func = task[0]
51
+ args = task[1] if len(task) > 1 else ()
52
+ kwargs = task[2] if len(task) > 2 else {}
53
+ else:
54
+ raise ValueError(f"Invalid task format: {task}")
55
+
56
+ futures[executor.submit(func, *args, **kwargs)] = i
57
+
58
+ for future in as_completed(futures):
59
+ idx = futures[future]
60
+ func = tasks[idx][0] if isinstance(tasks[idx], tuple) else tasks[idx]
61
+ func_name = getattr(func, "__name__", str(func))
62
+ try:
63
+ results[idx] = future.result()
64
+ except Exception as e:
65
+ results[idx] = ThreadPoolException(func=func_name, exception=e)
66
+
67
+ return results
@@ -1,13 +1,12 @@
1
- import os
2
-
3
1
  import requests
4
2
  from django.utils.translation import get_language
5
3
 
6
4
  from utg_base.constants import AVAILABLE_LANGUAGES
5
+ from utg_base.env import env
7
6
 
8
7
 
9
8
  def translate(key: str) -> str:
10
- base_url = os.environ.get('TRANSLATION_SERVICE_URL')
9
+ base_url = env('TRANSLATION_SERVICE_URL')
11
10
  lang = get_language()
12
11
  if lang == 'uz-cyr':
13
12
  lang = 'crl'
utg_base-1.5.1/README.md DELETED
@@ -1 +0,0 @@
1
- # UzTransGas Base