base-api-utils 1.1.111.dev10__tar.gz → 1.1.113__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 (112) hide show
  1. {base_api_utils-1.1.111.dev10/src/base_api_utils.egg-info → base_api_utils-1.1.113}/PKG-INFO +1 -1
  2. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/pyproject.toml +1 -1
  3. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/utils/circuit_breaker.py +8 -2
  4. base_api_utils-1.1.113/src/base_api_utils/utils/oauth2_custom_schema_base.py +34 -0
  5. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113/src/base_api_utils.egg-info}/PKG-INFO +1 -1
  6. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/tests/test_circuit_breaker.py +47 -0
  7. base_api_utils-1.1.111.dev10/src/base_api_utils/utils/oauth2_custom_schema_base.py +0 -30
  8. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/LICENSE +0 -0
  9. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/README.md +0 -0
  10. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/setup.cfg +0 -0
  11. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/__init__.py +0 -0
  12. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/commands/__init__.py +0 -0
  13. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/commands/failed_task_management_mixin.py +0 -0
  14. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/domain/__init__.py +0 -0
  15. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/domain/event_message.py +0 -0
  16. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/domain/event_message_factory.py +0 -0
  17. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/domain/events.py +0 -0
  18. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/dtos/__init__.py +0 -0
  19. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/dtos/authz_user.py +0 -0
  20. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/fields/__init__.py +0 -0
  21. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/fields/email_list_field.py +0 -0
  22. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/filters/__init__.py +0 -0
  23. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/filters/base_filter.py +0 -0
  24. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/filters/custom_ordering_filter.py +0 -0
  25. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/filters/filter_utils_mixin.py +0 -0
  26. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/handlers/__init__.py +0 -0
  27. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/handlers/base_event_handler.py +0 -0
  28. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/handlers/sponsor_event_handler.py +0 -0
  29. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/handlers/sponsorship_addon_event_handler.py +0 -0
  30. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/handlers/sponsorship_event_handler.py +0 -0
  31. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/handlers/summit_event_handler.py +0 -0
  32. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/ioc/__init__.py +0 -0
  33. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/ioc/container.py +0 -0
  34. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/models/__init__.py +0 -0
  35. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/models/item_image.py +0 -0
  36. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/security/__init__.py +0 -0
  37. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/security/abstract_access_token_service.py +0 -0
  38. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/security/abstract_oauth2_api_client.py +0 -0
  39. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/security/access_token_service.py +0 -0
  40. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/security/check_sponsor_user_permissions.py +0 -0
  41. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/security/group_required.py +0 -0
  42. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/security/oauth2_authentication.py +0 -0
  43. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/security/oauth2_client_factory.py +0 -0
  44. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/security/oauth2_scope_required.py +0 -0
  45. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/security/permission_event_handler.py +0 -0
  46. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/security/shared.py +0 -0
  47. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/security/user_permissions_service.py +0 -0
  48. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/serializers/__init__.py +0 -0
  49. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/serializers/base_model_serializer.py +0 -0
  50. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/serializers/email_list_char_field.py +0 -0
  51. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/serializers/nullable_slug_related_field.py +0 -0
  52. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/serializers/serializers_registry.py +0 -0
  53. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/serializers/task_result_serializer.py +0 -0
  54. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/serializers/timestamp_field.py +0 -0
  55. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/serializers/v2/__init__.py +0 -0
  56. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/serializers/v2/abstract_serializer.py +0 -0
  57. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/serializers/v2/base_model_serializer.py +0 -0
  58. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/serializers/v2/expands.py +0 -0
  59. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/serializers/v2/query_params.py +0 -0
  60. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/serializers/v2/spectacular.py +0 -0
  61. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/services/__init__.py +0 -0
  62. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/services/abstract_domain_events_service.py +0 -0
  63. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/services/abstract_event_dispatcher.py +0 -0
  64. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/services/abstract_images_service.py +0 -0
  65. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/services/abstract_sponsor_metadata_service.py +0 -0
  66. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/services/abstract_storage_service.py +0 -0
  67. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/services/abstract_summit_metadata_service.py +0 -0
  68. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/services/amqp_service.py +0 -0
  69. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/services/celery_failed.py +0 -0
  70. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/services/event_dispatcher.py +0 -0
  71. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/services/images_service.py +0 -0
  72. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/services/s3_service.py +0 -0
  73. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/services/strategies/__init__.py +0 -0
  74. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/services/strategies/abstract_task_runner_strategy.py +0 -0
  75. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/services/strategies/publish_domain_event_strategy.py +0 -0
  76. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/tests/__init__.py +0 -0
  77. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/tests/test_failed_celery_task_management.py +0 -0
  78. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/urls.py +0 -0
  79. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/utils/__init__.py +0 -0
  80. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/utils/async_helpers.py +0 -0
  81. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/utils/config.py +0 -0
  82. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/utils/custom_auto_schema.py +0 -0
  83. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/utils/db_connection_helpers.py +0 -0
  84. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/utils/debounce_helpers.py +0 -0
  85. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/utils/enums.py +0 -0
  86. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/utils/env_var_eval.py +0 -0
  87. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/utils/exceptions.py +0 -0
  88. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/utils/expand_queryset_optimization_mixin.py +0 -0
  89. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/utils/external_errors.py +0 -0
  90. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/utils/file_lock.py +0 -0
  91. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/utils/fingerprint_helpers.py +0 -0
  92. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/utils/http_helpers.py +0 -0
  93. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/utils/inject_job.py +0 -0
  94. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/utils/look_up_value_mixin.py +0 -0
  95. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/utils/model_helpers.py +0 -0
  96. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/utils/pagination.py +0 -0
  97. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/utils/rate_utils.py +0 -0
  98. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/utils/read_write_serializer_mixin.py +0 -0
  99. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/utils/serializers.py +0 -0
  100. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/utils/service_utils.py +0 -0
  101. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/utils/string.py +0 -0
  102. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/utils/tests.py +0 -0
  103. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/views/__init__.py +0 -0
  104. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/views/base_view.py +0 -0
  105. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/views/parent_children_crud.py +0 -0
  106. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/views/root_entity_crud.py +0 -0
  107. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils/views/tasks_result_view.py +0 -0
  108. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils.egg-info/SOURCES.txt +0 -0
  109. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils.egg-info/dependency_links.txt +0 -0
  110. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils.egg-info/requires.txt +0 -0
  111. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/src/base_api_utils.egg-info/top_level.txt +0 -0
  112. {base_api_utils-1.1.111.dev10 → base_api_utils-1.1.113}/tests/test_lazy_imports.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: base-api-utils
3
- Version: 1.1.111.dev10
3
+ Version: 1.1.113
4
4
  Summary: Django Rest Framework micro services common utilities
5
5
  Author-email: Román Gutierrez <roman@tipit.net>, Sebastian Marcet <sebastian@tipit.net>
6
6
  Project-URL: Homepage, https://github.com/fntechgit/base-api-utils
@@ -7,7 +7,7 @@ where = ["src"]
7
7
 
8
8
  [project]
9
9
  name = "base-api-utils"
10
- version = "1.1.111.dev10"
10
+ version = "1.1.113"
11
11
  dependencies = [
12
12
  "boto3",
13
13
  "celery",
@@ -1,5 +1,6 @@
1
1
  import pybreaker
2
2
  import requests
3
+ from rest_framework.exceptions import NotFound, ValidationError
3
4
 
4
5
  from .external_errors import NamedCircuitBreakerError
5
6
 
@@ -18,10 +19,15 @@ class NamedCircuitBreaker(pybreaker.CircuitBreaker):
18
19
  if isinstance(exception, requests.exceptions.HTTPError):
19
20
  status_code = exception.response.status_code
20
21
  return 400 <= status_code < 500
22
+ # invoke_with_retry converts HTTP 404 → DRF NotFound and HTTP 412 → DRF
23
+ # ValidationError before the exception reaches the breaker. These are still
24
+ # 4xx business errors and must not count as circuit breaker failures.
25
+ if isinstance(exception, (NotFound, ValidationError)):
26
+ return True
21
27
  return False
22
28
 
23
- # Get existing exclude or create new one
24
- exclude = kwargs.get('exclude', []) or []
29
+ # Pop existing exclude from kwargs to avoid passing it twice to super()
30
+ exclude = kwargs.pop('exclude', []) or []
25
31
  if not isinstance(exclude, list):
26
32
  exclude = list(exclude)
27
33
 
@@ -0,0 +1,34 @@
1
+ from drf_spectacular.extensions import OpenApiAuthenticationExtension
2
+ from oauthlib.openid.connect.core.grant_types import refresh_token
3
+
4
+ from .config import config
5
+
6
+ class OAuth2CustomSchemaBase(OpenApiAuthenticationExtension):
7
+ name = config('OPEN_API_SECURITY_SCHEMA_NAME')
8
+
9
+ def get_security_definition(self, auto_schema):
10
+ unique_scopes = set()
11
+
12
+ endpoints = config("OAUTH2.CLIENT.ENDPOINTS")
13
+
14
+ for endpoint, methods in endpoints.items():
15
+ for method, details in methods.items():
16
+ scopes = details.get("scopes")
17
+ if scopes:
18
+ unique_scopes.update(scopes.split())
19
+
20
+ base_url = config("OAUTH2.IDP.BASE_URL").rstrip("/")
21
+ token_endpoint = config("OAUTH2.IDP.TOKEN_ENDPOINT").lstrip("/")
22
+ refresh_token_endpoint = config("OAUTH2.IDP.REFRESH_TOKEN_ENDPOINT").lstrip("/")
23
+
24
+ return {
25
+ "type": "oauth2",
26
+ "flows": {
27
+ 'clientCredentials': {
28
+ "tokenUrl": f"{base_url}/{token_endpoint}",
29
+ 'refreshUrl': f"{base_url}/{refresh_token_endpoint}",
30
+ "scopes": {scope: "" for scope in sorted(unique_scopes)},
31
+ },
32
+ },
33
+ "description": "OAuth2 client credentials authentication.",
34
+ }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: base-api-utils
3
- Version: 1.1.111.dev10
3
+ Version: 1.1.113
4
4
  Summary: Django Rest Framework micro services common utilities
5
5
  Author-email: Román Gutierrez <roman@tipit.net>, Sebastian Marcet <sebastian@tipit.net>
6
6
  Project-URL: Homepage, https://github.com/fntechgit/base-api-utils
@@ -7,6 +7,7 @@ import time
7
7
  import pybreaker
8
8
  import pytest
9
9
  import requests
10
+ from rest_framework.exceptions import NotFound, ValidationError
10
11
 
11
12
 
12
13
  def _load_module(name, file_path):
@@ -148,6 +149,52 @@ class TestNamedCircuitBreaker:
148
149
  # Breaker should still be closed
149
150
  assert breaker.current_state == "closed"
150
151
 
152
+ def test_drf_not_found_does_not_trip_breaker(self):
153
+ """DRF NotFound (converted from HTTP 404 by invoke_with_retry) must not trip the breaker."""
154
+ breaker = self._make_breaker(fail_max=2)
155
+
156
+ def raise_not_found():
157
+ raise NotFound("Resource not found")
158
+
159
+ for _ in range(5):
160
+ with pytest.raises(NotFound):
161
+ breaker.call(raise_not_found)
162
+
163
+ assert breaker.current_state == "closed"
164
+
165
+ def test_drf_validation_error_does_not_trip_breaker(self):
166
+ """DRF ValidationError (converted from HTTP 412 by invoke_with_retry) must not trip the breaker."""
167
+ breaker = self._make_breaker(fail_max=2)
168
+
169
+ def raise_validation_error():
170
+ raise ValidationError("Precondition failed")
171
+
172
+ for _ in range(5):
173
+ with pytest.raises(ValidationError):
174
+ breaker.call(raise_validation_error)
175
+
176
+ assert breaker.current_state == "closed"
177
+
178
+ def test_exclude_kwarg_not_passed_twice(self):
179
+ """Passing exclude= to NamedCircuitBreaker should not cause TypeError."""
180
+ custom_exclude = [lambda e: isinstance(e, ValueError)]
181
+ breaker = NamedCircuitBreaker(
182
+ name="test_service",
183
+ fail_max=2,
184
+ reset_timeout=1,
185
+ exclude=custom_exclude,
186
+ )
187
+
188
+ # Custom exclusion should work alongside built-in 4xx exclusion
189
+ def raise_value_error():
190
+ raise ValueError("excluded")
191
+
192
+ for _ in range(5):
193
+ with pytest.raises(ValueError):
194
+ breaker.call(raise_value_error)
195
+
196
+ assert breaker.current_state == "closed"
197
+
151
198
 
152
199
  @pytest.mark.unit
153
200
  class TestCallWithBreaker:
@@ -1,30 +0,0 @@
1
- from drf_spectacular.extensions import OpenApiAuthenticationExtension
2
-
3
- from .config import config
4
-
5
- class OAuth2CustomSchemaBase(OpenApiAuthenticationExtension):
6
- name = config('OPEN_API_SECURITY_SCHEMA_NAME')
7
-
8
- def get_security_definition(self, auto_schema):
9
- unique_scopes = set()
10
-
11
- endpoints = config('OAUTH2.CLIENT.ENDPOINTS')
12
-
13
- for endpoint, methods in endpoints.items():
14
- for method, details in methods.items():
15
- if 'scopes' in details and details['scopes']:
16
- scopes_list = details['scopes'].split()
17
- unique_scopes.update(scopes_list)
18
-
19
- return {
20
- 'type': 'oauth2',
21
- 'flows': {
22
- 'implicit': {
23
- 'authorizationUrl': f'{config('OAUTH2.IDP.BASE_URL')}/{config('OAUTH2.IDP.AUTHORIZATION_ENDPOINT')}',
24
- 'tokenUrl': f'{config('OAUTH2.IDP.BASE_URL')}/{config('OAUTH2.IDP.TOKEN_ENDPOINT')}',
25
- 'refreshUrl': f'{config('OAUTH2.IDP.BASE_URL')}/{config('OAUTH2.IDP.REFRESH_TOKEN_ENDPOINT')}',
26
- 'scopes': {scope: '' for scope in unique_scopes}
27
- },
28
- },
29
- 'description': 'OAuth2 authentication.'
30
- }