base-api-utils 1.1.94__tar.gz → 1.1.96__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.
- {base_api_utils-1.1.94/src/base_api_utils.egg-info → base_api_utils-1.1.96}/PKG-INFO +1 -1
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/pyproject.toml +1 -1
- base_api_utils-1.1.96/src/base_api_utils/serializers/v2/__init__.py +2 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/serializers/v2/base_model_serializer.py +1 -1
- base_api_utils-1.1.96/src/base_api_utils/utils/fingerprint_helpers.py +190 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96/src/base_api_utils.egg-info}/PKG-INFO +1 -1
- base_api_utils-1.1.94/src/base_api_utils/serializers/v2/__init__.py +0 -2
- base_api_utils-1.1.94/src/base_api_utils/utils/fingerprint_helpers.py +0 -99
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/LICENSE +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/README.md +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/setup.cfg +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/__init__.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/commands/__init__.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/commands/failed_task_management_mixin.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/domain/__init__.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/domain/event_message.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/domain/event_message_factory.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/domain/events.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/dtos/__init__.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/dtos/authz_user.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/fields/__init__.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/fields/email_list_field.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/filters/__init__.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/filters/base_filter.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/filters/custom_ordering_filter.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/filters/filter_utils_mixin.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/handlers/__init__.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/handlers/base_event_handler.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/handlers/sponsor_event_handler.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/handlers/sponsorship_addon_event_handler.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/handlers/sponsorship_event_handler.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/handlers/summit_event_handler.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/ioc/__init__.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/ioc/container.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/models/__init__.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/models/item_image.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/security/__init__.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/security/abstract_access_token_service.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/security/abstract_oauth2_api_client.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/security/access_token_service.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/security/check_sponsor_user_permissions.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/security/group_required.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/security/oauth2_authentication.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/security/oauth2_client_factory.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/security/oauth2_scope_required.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/security/permission_event_handler.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/security/shared.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/security/user_permissions_service.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/serializers/__init__.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/serializers/base_model_serializer.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/serializers/email_list_char_field.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/serializers/nullable_slug_related_field.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/serializers/serializers_registry.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/serializers/task_result_serializer.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/serializers/timestamp_field.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/serializers/v2/abstract_serializer.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/serializers/v2/expands.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/serializers/v2/query_params.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/services/__init__.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/services/abstract_domain_events_service.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/services/abstract_event_dispatcher.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/services/abstract_images_service.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/services/abstract_sponsor_metadata_service.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/services/abstract_storage_service.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/services/abstract_summit_metadata_service.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/services/amqp_service.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/services/celery_failed.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/services/event_dispatcher.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/services/images_service.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/services/s3_service.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/services/strategies/__init__.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/services/strategies/abstract_task_runner_strategy.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/services/strategies/publish_domain_event_strategy.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/tests/__init__.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/tests/test_failed_celery_task_management.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/urls.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/utils/__init__.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/utils/async_helpers.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/utils/circuit_breaker.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/utils/config.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/utils/custom_auto_schema.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/utils/db_connection_helpers.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/utils/debounce_helpers.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/utils/enums.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/utils/env_var_eval.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/utils/exceptions.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/utils/external_errors.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/utils/file_lock.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/utils/http_helpers.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/utils/inject_job.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/utils/model_helpers.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/utils/oauth2_custom_schema_base.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/utils/pagination.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/utils/rate_utils.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/utils/read_write_serializer_mixin.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/utils/serializers.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/utils/service_utils.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/utils/string.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/utils/tests.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/views/__init__.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/views/base_view.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/views/parent_children_crud.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/views/root_entity_crud.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/views/tasks_result_view.py +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils.egg-info/SOURCES.txt +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils.egg-info/dependency_links.txt +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils.egg-info/requires.txt +0 -0
- {base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: base-api-utils
|
|
3
|
-
Version: 1.1.
|
|
3
|
+
Version: 1.1.96
|
|
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
|
|
@@ -3,7 +3,7 @@ from rest_framework import serializers
|
|
|
3
3
|
from . import AbstractSerializer
|
|
4
4
|
from ..timestamp_field import TimestampField
|
|
5
5
|
|
|
6
|
-
class
|
|
6
|
+
class BaseModelSerializer(AbstractSerializer, serializers.ModelSerializer):
|
|
7
7
|
id = serializers.ReadOnlyField(source="pk")
|
|
8
8
|
created = TimestampField(read_only=True, required=False)
|
|
9
9
|
modified = TimestampField(read_only=True, required=False)
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
"""
|
|
2
|
+
1 - Fingerprint utility functions for computing SHA-256 based identity hashes.
|
|
3
|
+
|
|
4
|
+
These functions enable change detection to determine when entities need to be
|
|
5
|
+
created or updated by computing deterministic fingerprints from their field values.
|
|
6
|
+
|
|
7
|
+
2 - FingerPrintMixin to centralize models fingerprint generation.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import hashlib
|
|
11
|
+
from datetime import datetime
|
|
12
|
+
from typing import Any, Dict, Iterable, Optional
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def compute_sha256(data: str) -> str:
|
|
16
|
+
"""
|
|
17
|
+
Compute SHA-256 hash of the input string.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
data: The string to hash
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
64-character lowercase hex string
|
|
24
|
+
"""
|
|
25
|
+
return hashlib.sha256(data.encode('utf-8')).hexdigest()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def format_value(value: Any) -> Optional[str]:
|
|
29
|
+
"""
|
|
30
|
+
Format a field value for inclusion in a fingerprint string.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
value: The value to format
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
Formatted string representation, or None if the value should be skipped
|
|
37
|
+
"""
|
|
38
|
+
if value is None:
|
|
39
|
+
return None
|
|
40
|
+
|
|
41
|
+
if isinstance(value, bool):
|
|
42
|
+
return "true" if value else "false"
|
|
43
|
+
|
|
44
|
+
if isinstance(value, datetime):
|
|
45
|
+
return value.isoformat()
|
|
46
|
+
|
|
47
|
+
return str(value)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def build_fingerprint_string(fields: Dict[str, Any]) -> str:
|
|
51
|
+
"""
|
|
52
|
+
Build a deterministic string from field name-value pairs.
|
|
53
|
+
|
|
54
|
+
Fields are sorted by name for determinism. Fields with None values are skipped.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
fields: Dictionary of field names to values
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
Concatenated string of "name=value" pairs separated by "|"
|
|
61
|
+
"""
|
|
62
|
+
parts = []
|
|
63
|
+
for key in sorted(fields.keys()):
|
|
64
|
+
formatted = format_value(fields[key])
|
|
65
|
+
if formatted is not None:
|
|
66
|
+
parts.append(f"{key}={formatted}")
|
|
67
|
+
return "|".join(parts)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def format_id_list(ids: Iterable[int]) -> str:
|
|
71
|
+
"""
|
|
72
|
+
Format a list of IDs as a comma-separated string.
|
|
73
|
+
|
|
74
|
+
IDs are sorted numerically for determinism.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
ids: Iterable of integer IDs
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
Comma-separated string of sorted IDs
|
|
81
|
+
"""
|
|
82
|
+
return ",".join(str(id_) for id_ in sorted(ids))
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def combine_fingerprints(fingerprints: Iterable[str]) -> str:
|
|
86
|
+
"""
|
|
87
|
+
Combine multiple fingerprints into a single hash.
|
|
88
|
+
|
|
89
|
+
Fingerprints are sorted for determinism before being concatenated and hashed.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
fingerprints: Iterable of fingerprint hex strings
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
64-character lowercase hex string, or empty string if no fingerprints
|
|
96
|
+
"""
|
|
97
|
+
sorted_fps = sorted(fingerprints)
|
|
98
|
+
if not sorted_fps:
|
|
99
|
+
return ""
|
|
100
|
+
combined = "|".join(sorted_fps)
|
|
101
|
+
return compute_sha256(combined)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class FingerPrintMixin:
|
|
105
|
+
def _get_base_fingerprint_fields(self) -> dict:
|
|
106
|
+
return {}
|
|
107
|
+
|
|
108
|
+
def _get_specific_fingerprint_fields(self) -> dict:
|
|
109
|
+
return {}
|
|
110
|
+
|
|
111
|
+
def _get_m2m_fingerprint_fields(self) -> dict:
|
|
112
|
+
return {}
|
|
113
|
+
|
|
114
|
+
def _get_base_m2m_fields(self) -> dict:
|
|
115
|
+
return {}
|
|
116
|
+
|
|
117
|
+
def _get_children_accessors(self) -> list[dict]:
|
|
118
|
+
"""
|
|
119
|
+
Returns a list of child descriptor dicts, each with:
|
|
120
|
+
- 'accessor': the related manager name as a string (e.g. 'meta_fields')
|
|
121
|
+
- 'order_by': field name used to sort the collection (e.g. 'id')
|
|
122
|
+
|
|
123
|
+
Subclasses should override this to declare their child relations.
|
|
124
|
+
Example:
|
|
125
|
+
return [{'accessor': 'meta_fields', 'order_by': 'id'}]
|
|
126
|
+
"""
|
|
127
|
+
return []
|
|
128
|
+
|
|
129
|
+
def _resolve_children(self, accessor: str, order_by: str) -> list:
|
|
130
|
+
"""
|
|
131
|
+
Resolves a child collection respecting three possible sources,
|
|
132
|
+
in priority order:
|
|
133
|
+
|
|
134
|
+
1. In-memory prefetch cache (_prefetched_objects_cache) — avoids DB hits
|
|
135
|
+
when objects have been built or prefetched outside the ORM.
|
|
136
|
+
2. Database query via the related manager — only when the instance
|
|
137
|
+
has already been persisted (pk is set).
|
|
138
|
+
3. Empty list — for unsaved instances with no prefetched data.
|
|
139
|
+
"""
|
|
140
|
+
cache = getattr(self, '_prefetched_objects_cache', {})
|
|
141
|
+
|
|
142
|
+
if accessor in cache:
|
|
143
|
+
# Sort in-memory using the order_by field (strip leading '-' for reverse)
|
|
144
|
+
reverse = order_by.startswith('-')
|
|
145
|
+
field = order_by.lstrip('-')
|
|
146
|
+
return sorted(cache[accessor], key=lambda x: getattr(x, field), reverse=reverse)
|
|
147
|
+
|
|
148
|
+
if self.pk:
|
|
149
|
+
return list(getattr(self, accessor).all().order_by(order_by))
|
|
150
|
+
|
|
151
|
+
return []
|
|
152
|
+
|
|
153
|
+
def _compute_own_fingerprint(self, field_overrides=None) -> str:
|
|
154
|
+
all_fields = {
|
|
155
|
+
**self._get_base_fingerprint_fields(),
|
|
156
|
+
**self._get_specific_fingerprint_fields(),
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
# Append M2M relation IDs as formatted strings
|
|
160
|
+
for key, id_list in self._get_m2m_fingerprint_fields().items():
|
|
161
|
+
if id_list:
|
|
162
|
+
all_fields[key] = format_id_list(id_list)
|
|
163
|
+
|
|
164
|
+
# Allow caller to substitute values without mutating the instance
|
|
165
|
+
if field_overrides:
|
|
166
|
+
all_fields.update(field_overrides)
|
|
167
|
+
|
|
168
|
+
return compute_sha256(build_fingerprint_string(all_fields))
|
|
169
|
+
|
|
170
|
+
def fingerprint(self, field_overrides=None) -> str:
|
|
171
|
+
|
|
172
|
+
# 1. Compute this instance's own hash
|
|
173
|
+
own_hash = self._compute_own_fingerprint(field_overrides)
|
|
174
|
+
|
|
175
|
+
# 2. Recursively collect hashes from each child collection
|
|
176
|
+
children_hashes = []
|
|
177
|
+
for descriptor in self._get_children_accessors():
|
|
178
|
+
children = self._resolve_children(
|
|
179
|
+
accessor=descriptor['accessor'],
|
|
180
|
+
order_by=descriptor['order_by'],
|
|
181
|
+
)
|
|
182
|
+
for child in children:
|
|
183
|
+
children_hashes.append(child.fingerprint())
|
|
184
|
+
|
|
185
|
+
# 3. Combine own hash with children hashes into a single fingerprint
|
|
186
|
+
if children_hashes:
|
|
187
|
+
combined = combine_fingerprints(children_hashes)
|
|
188
|
+
return compute_sha256(f"{own_hash}|{combined}")
|
|
189
|
+
|
|
190
|
+
return own_hash
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: base-api-utils
|
|
3
|
-
Version: 1.1.
|
|
3
|
+
Version: 1.1.96
|
|
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
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Fingerprint utility functions for computing SHA-256 based identity hashes.
|
|
3
|
-
|
|
4
|
-
These functions enable change detection to determine when entities need to be
|
|
5
|
-
created or updated by computing deterministic fingerprints from their field values.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
import hashlib
|
|
9
|
-
from datetime import datetime
|
|
10
|
-
from typing import Any, Dict, Iterable, Optional
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
def compute_sha256(data: str) -> str:
|
|
14
|
-
"""
|
|
15
|
-
Compute SHA-256 hash of the input string.
|
|
16
|
-
|
|
17
|
-
Args:
|
|
18
|
-
data: The string to hash
|
|
19
|
-
|
|
20
|
-
Returns:
|
|
21
|
-
64-character lowercase hex string
|
|
22
|
-
"""
|
|
23
|
-
return hashlib.sha256(data.encode('utf-8')).hexdigest()
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
def format_value(value: Any) -> Optional[str]:
|
|
27
|
-
"""
|
|
28
|
-
Format a field value for inclusion in a fingerprint string.
|
|
29
|
-
|
|
30
|
-
Args:
|
|
31
|
-
value: The value to format
|
|
32
|
-
|
|
33
|
-
Returns:
|
|
34
|
-
Formatted string representation, or None if the value should be skipped
|
|
35
|
-
"""
|
|
36
|
-
if value is None:
|
|
37
|
-
return None
|
|
38
|
-
|
|
39
|
-
if isinstance(value, bool):
|
|
40
|
-
return "true" if value else "false"
|
|
41
|
-
|
|
42
|
-
if isinstance(value, datetime):
|
|
43
|
-
return value.isoformat()
|
|
44
|
-
|
|
45
|
-
return str(value)
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
def build_fingerprint_string(fields: Dict[str, Any]) -> str:
|
|
49
|
-
"""
|
|
50
|
-
Build a deterministic string from field name-value pairs.
|
|
51
|
-
|
|
52
|
-
Fields are sorted by name for determinism. Fields with None values are skipped.
|
|
53
|
-
|
|
54
|
-
Args:
|
|
55
|
-
fields: Dictionary of field names to values
|
|
56
|
-
|
|
57
|
-
Returns:
|
|
58
|
-
Concatenated string of "name=value" pairs separated by "|"
|
|
59
|
-
"""
|
|
60
|
-
parts = []
|
|
61
|
-
for key in sorted(fields.keys()):
|
|
62
|
-
formatted = format_value(fields[key])
|
|
63
|
-
if formatted is not None:
|
|
64
|
-
parts.append(f"{key}={formatted}")
|
|
65
|
-
return "|".join(parts)
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
def format_id_list(ids: Iterable[int]) -> str:
|
|
69
|
-
"""
|
|
70
|
-
Format a list of IDs as a comma-separated string.
|
|
71
|
-
|
|
72
|
-
IDs are sorted numerically for determinism.
|
|
73
|
-
|
|
74
|
-
Args:
|
|
75
|
-
ids: Iterable of integer IDs
|
|
76
|
-
|
|
77
|
-
Returns:
|
|
78
|
-
Comma-separated string of sorted IDs
|
|
79
|
-
"""
|
|
80
|
-
return ",".join(str(id_) for id_ in sorted(ids))
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
def combine_fingerprints(fingerprints: Iterable[str]) -> str:
|
|
84
|
-
"""
|
|
85
|
-
Combine multiple fingerprints into a single hash.
|
|
86
|
-
|
|
87
|
-
Fingerprints are sorted for determinism before being concatenated and hashed.
|
|
88
|
-
|
|
89
|
-
Args:
|
|
90
|
-
fingerprints: Iterable of fingerprint hex strings
|
|
91
|
-
|
|
92
|
-
Returns:
|
|
93
|
-
64-character lowercase hex string, or empty string if no fingerprints
|
|
94
|
-
"""
|
|
95
|
-
sorted_fps = sorted(fingerprints)
|
|
96
|
-
if not sorted_fps:
|
|
97
|
-
return ""
|
|
98
|
-
combined = "|".join(sorted_fps)
|
|
99
|
-
return compute_sha256(combined)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/domain/event_message_factory.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/fields/email_list_field.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/filters/custom_ordering_filter.py
RENAMED
|
File without changes
|
{base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/filters/filter_utils_mixin.py
RENAMED
|
File without changes
|
|
File without changes
|
{base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/handlers/base_event_handler.py
RENAMED
|
File without changes
|
{base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/handlers/sponsor_event_handler.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/handlers/summit_event_handler.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/security/access_token_service.py
RENAMED
|
File without changes
|
|
File without changes
|
{base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/security/group_required.py
RENAMED
|
File without changes
|
{base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/security/oauth2_authentication.py
RENAMED
|
File without changes
|
{base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/security/oauth2_client_factory.py
RENAMED
|
File without changes
|
{base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/security/oauth2_scope_required.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/serializers/timestamp_field.py
RENAMED
|
File without changes
|
|
File without changes
|
{base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/serializers/v2/expands.py
RENAMED
|
File without changes
|
{base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/serializers/v2/query_params.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/services/celery_failed.py
RENAMED
|
File without changes
|
{base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/services/event_dispatcher.py
RENAMED
|
File without changes
|
{base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/services/images_service.py
RENAMED
|
File without changes
|
|
File without changes
|
{base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/services/strategies/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/utils/custom_auto_schema.py
RENAMED
|
File without changes
|
{base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/utils/db_connection_helpers.py
RENAMED
|
File without changes
|
{base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/utils/debounce_helpers.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/views/parent_children_crud.py
RENAMED
|
File without changes
|
{base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/views/root_entity_crud.py
RENAMED
|
File without changes
|
{base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils/views/tasks_result_view.py
RENAMED
|
File without changes
|
|
File without changes
|
{base_api_utils-1.1.94 → base_api_utils-1.1.96}/src/base_api_utils.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|