oplnk-python-utils 1.2.3__tar.gz → 1.2.5__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.
- {oplnk_python_utils-1.2.3/oplnk_python_utils.egg-info → oplnk_python_utils-1.2.5}/PKG-INFO +1 -1
- {oplnk_python_utils-1.2.3 → oplnk_python_utils-1.2.5}/oplnk_python_utils/__init__.py +1 -1
- {oplnk_python_utils-1.2.3 → oplnk_python_utils-1.2.5}/oplnk_python_utils/query/query_builder.py +72 -14
- {oplnk_python_utils-1.2.3 → oplnk_python_utils-1.2.5/oplnk_python_utils.egg-info}/PKG-INFO +1 -1
- {oplnk_python_utils-1.2.3 → oplnk_python_utils-1.2.5}/setup.py +1 -1
- {oplnk_python_utils-1.2.3 → oplnk_python_utils-1.2.5}/LICENSE +0 -0
- {oplnk_python_utils-1.2.3 → oplnk_python_utils-1.2.5}/MANIFEST.in +0 -0
- {oplnk_python_utils-1.2.3 → oplnk_python_utils-1.2.5}/README.md +0 -0
- {oplnk_python_utils-1.2.3 → oplnk_python_utils-1.2.5}/oplnk_python_utils/cache/__init__.py +0 -0
- {oplnk_python_utils-1.2.3 → oplnk_python_utils-1.2.5}/oplnk_python_utils/cache/cache_utils.py +0 -0
- {oplnk_python_utils-1.2.3 → oplnk_python_utils-1.2.5}/oplnk_python_utils/core/__init__.py +0 -0
- {oplnk_python_utils-1.2.3 → oplnk_python_utils-1.2.5}/oplnk_python_utils/core/data.py +0 -0
- {oplnk_python_utils-1.2.3 → oplnk_python_utils-1.2.5}/oplnk_python_utils/core/parsing.py +0 -0
- {oplnk_python_utils-1.2.3 → oplnk_python_utils-1.2.5}/oplnk_python_utils/core/serialization.py +0 -0
- {oplnk_python_utils-1.2.3 → oplnk_python_utils-1.2.5}/oplnk_python_utils/query/__init__.py +0 -0
- {oplnk_python_utils-1.2.3 → oplnk_python_utils-1.2.5}/oplnk_python_utils/reports/__init__.py +0 -0
- {oplnk_python_utils-1.2.3 → oplnk_python_utils-1.2.5}/oplnk_python_utils.egg-info/SOURCES.txt +0 -0
- {oplnk_python_utils-1.2.3 → oplnk_python_utils-1.2.5}/oplnk_python_utils.egg-info/dependency_links.txt +0 -0
- {oplnk_python_utils-1.2.3 → oplnk_python_utils-1.2.5}/oplnk_python_utils.egg-info/not-zip-safe +0 -0
- {oplnk_python_utils-1.2.3 → oplnk_python_utils-1.2.5}/oplnk_python_utils.egg-info/requires.txt +0 -0
- {oplnk_python_utils-1.2.3 → oplnk_python_utils-1.2.5}/oplnk_python_utils.egg-info/top_level.txt +0 -0
- {oplnk_python_utils-1.2.3 → oplnk_python_utils-1.2.5}/setup.cfg +0 -0
{oplnk_python_utils-1.2.3 → oplnk_python_utils-1.2.5}/oplnk_python_utils/query/query_builder.py
RENAMED
|
@@ -626,10 +626,11 @@ class QueryBuilder:
|
|
|
626
626
|
return {"$sort": sort_pipeline}
|
|
627
627
|
|
|
628
628
|
def build_fields_pipeline(
|
|
629
|
-
self, fields_params: List[str], populate_params: Optional[
|
|
629
|
+
self, fields_params: List[str], populate_params: Optional[Any] = None
|
|
630
630
|
) -> Dict[str, Any]:
|
|
631
631
|
"""
|
|
632
632
|
Build fields projection pipeline with support for dot notation and array notation.
|
|
633
|
+
Automatically includes aliases for populated fields when fields is specified.
|
|
633
634
|
"""
|
|
634
635
|
fields_pipeline = {}
|
|
635
636
|
|
|
@@ -648,15 +649,43 @@ class QueryBuilder:
|
|
|
648
649
|
|
|
649
650
|
# Process populate_params if provided
|
|
650
651
|
if populate_params:
|
|
651
|
-
|
|
652
|
+
# Convert to list if it's a dict
|
|
653
|
+
populate_list = (
|
|
654
|
+
populate_params
|
|
655
|
+
if isinstance(populate_params, list)
|
|
656
|
+
else list(populate_params.keys())
|
|
657
|
+
)
|
|
658
|
+
|
|
659
|
+
for field in populate_list:
|
|
652
660
|
try:
|
|
661
|
+
# Add the original field so sanitization can work
|
|
653
662
|
normalized_field = normalize_field_path(field)
|
|
654
663
|
if validate_field_path(normalized_field):
|
|
655
664
|
fields_pipeline[normalized_field] = 1
|
|
665
|
+
|
|
666
|
+
# Add the populated alias for audit fields
|
|
667
|
+
if field in RESERVED_AUDIT_FIELDS:
|
|
668
|
+
alias = RESERVED_AUDIT_FIELDS[field]["alias"]
|
|
669
|
+
normalized_alias = normalize_field_path(alias)
|
|
670
|
+
if validate_field_path(normalized_alias):
|
|
671
|
+
fields_pipeline[normalized_alias] = 1
|
|
656
672
|
else:
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
673
|
+
# For normal relations, find the alias and add it
|
|
674
|
+
for relation in self.relations:
|
|
675
|
+
relation_name = relation[1]
|
|
676
|
+
base_name = relation_name.replace("_ids", "").replace(
|
|
677
|
+
"_id", ""
|
|
678
|
+
)
|
|
679
|
+
if base_name == field:
|
|
680
|
+
# Use custom alias if provided (5th element)
|
|
681
|
+
if len(relation) > 4 and relation[4]:
|
|
682
|
+
alias = relation[4]
|
|
683
|
+
else:
|
|
684
|
+
alias = base_name
|
|
685
|
+
normalized_alias = normalize_field_path(alias)
|
|
686
|
+
if validate_field_path(normalized_alias):
|
|
687
|
+
fields_pipeline[normalized_alias] = 1
|
|
688
|
+
break
|
|
660
689
|
except Exception as e:
|
|
661
690
|
print(
|
|
662
691
|
f"Warning: Error processing populate field '{field}' in projection: {e}"
|
|
@@ -842,27 +871,56 @@ class QueryBuilder:
|
|
|
842
871
|
meta_result = {"totalCount": 0}
|
|
843
872
|
|
|
844
873
|
# Sanitize populate fields: remove original ID fields when populated (unless requested in fields)
|
|
874
|
+
# Also ensure populated fields return null if lookup failed (instead of missing)
|
|
845
875
|
if "populate" in query and isinstance(data_result, list):
|
|
846
876
|
fields_requested = query.get("fields", [])
|
|
847
877
|
populated_fields = self._get_populated_fields_list(query["populate"])
|
|
848
878
|
|
|
849
879
|
for doc in data_result:
|
|
850
|
-
# For audit fields
|
|
880
|
+
# For audit fields
|
|
851
881
|
for field_name, field_config in RESERVED_AUDIT_FIELDS.items():
|
|
852
882
|
if field_name in populated_fields:
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
883
|
+
alias = field_config["alias"]
|
|
884
|
+
original_exists = field_name in doc
|
|
885
|
+
alias_exists = alias in doc
|
|
886
|
+
|
|
887
|
+
# If populate was requested but alias doesn't exist, set to null
|
|
888
|
+
# (only if the original field exists - meaning the field is part of the document)
|
|
889
|
+
if not alias_exists and original_exists:
|
|
890
|
+
doc[alias] = None
|
|
891
|
+
|
|
892
|
+
# Remove original field if not explicitly requested in fields
|
|
893
|
+
if alias_exists and field_name not in fields_requested:
|
|
894
|
+
if original_exists:
|
|
859
895
|
del doc[field_name]
|
|
860
896
|
|
|
861
|
-
# For normal relations
|
|
897
|
+
# For normal relations
|
|
862
898
|
for populated_field in populated_fields:
|
|
863
899
|
if populated_field not in RESERVED_AUDIT_FIELDS:
|
|
864
|
-
#
|
|
900
|
+
# Find the ID field and alias for this relation
|
|
865
901
|
id_field = self._get_id_field_for_relation(populated_field)
|
|
902
|
+
|
|
903
|
+
# Determine the alias (same as base name or custom alias)
|
|
904
|
+
alias = populated_field
|
|
905
|
+
for relation in self.relations:
|
|
906
|
+
relation_name = relation[1]
|
|
907
|
+
base_name = relation_name.replace("_ids", "").replace(
|
|
908
|
+
"_id", ""
|
|
909
|
+
)
|
|
910
|
+
if base_name == populated_field:
|
|
911
|
+
# Use custom alias if provided (5th element)
|
|
912
|
+
if len(relation) > 4 and relation[4]:
|
|
913
|
+
alias = relation[4]
|
|
914
|
+
break
|
|
915
|
+
|
|
916
|
+
original_exists = id_field and id_field in doc
|
|
917
|
+
alias_exists = alias in doc
|
|
918
|
+
|
|
919
|
+
# If populate was requested but alias doesn't exist, set to null
|
|
920
|
+
if not alias_exists and original_exists:
|
|
921
|
+
doc[alias] = None
|
|
922
|
+
|
|
923
|
+
# Remove original ID field if not explicitly requested in fields
|
|
866
924
|
if id_field and id_field not in fields_requested:
|
|
867
925
|
if id_field in doc:
|
|
868
926
|
del doc[id_field]
|
|
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
|
|
2
2
|
|
|
3
3
|
setup(
|
|
4
4
|
name="oplnk-python-utils",
|
|
5
|
-
version="1.2.
|
|
5
|
+
version="1.2.5",
|
|
6
6
|
author="OpenLink SpA",
|
|
7
7
|
author_email="contacto@openlink.cl",
|
|
8
8
|
description="Reusable utilities for caching, data manipulation, parsing, and MongoDB query building",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{oplnk_python_utils-1.2.3 → oplnk_python_utils-1.2.5}/oplnk_python_utils/cache/cache_utils.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{oplnk_python_utils-1.2.3 → oplnk_python_utils-1.2.5}/oplnk_python_utils/core/serialization.py
RENAMED
|
File without changes
|
|
File without changes
|
{oplnk_python_utils-1.2.3 → oplnk_python_utils-1.2.5}/oplnk_python_utils/reports/__init__.py
RENAMED
|
File without changes
|
{oplnk_python_utils-1.2.3 → oplnk_python_utils-1.2.5}/oplnk_python_utils.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{oplnk_python_utils-1.2.3 → oplnk_python_utils-1.2.5}/oplnk_python_utils.egg-info/not-zip-safe
RENAMED
|
File without changes
|
{oplnk_python_utils-1.2.3 → oplnk_python_utils-1.2.5}/oplnk_python_utils.egg-info/requires.txt
RENAMED
|
File without changes
|
{oplnk_python_utils-1.2.3 → oplnk_python_utils-1.2.5}/oplnk_python_utils.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|