oplnk-python-utils 1.2.1__tar.gz → 1.2.2__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 (22) hide show
  1. {oplnk_python_utils-1.2.1/oplnk_python_utils.egg-info → oplnk_python_utils-1.2.2}/PKG-INFO +31 -11
  2. {oplnk_python_utils-1.2.1 → oplnk_python_utils-1.2.2}/README.md +30 -10
  3. {oplnk_python_utils-1.2.1 → oplnk_python_utils-1.2.2}/oplnk_python_utils/__init__.py +1 -1
  4. {oplnk_python_utils-1.2.1 → oplnk_python_utils-1.2.2}/oplnk_python_utils/query/query_builder.py +44 -47
  5. {oplnk_python_utils-1.2.1 → oplnk_python_utils-1.2.2/oplnk_python_utils.egg-info}/PKG-INFO +31 -11
  6. {oplnk_python_utils-1.2.1 → oplnk_python_utils-1.2.2}/setup.py +1 -1
  7. {oplnk_python_utils-1.2.1 → oplnk_python_utils-1.2.2}/LICENSE +0 -0
  8. {oplnk_python_utils-1.2.1 → oplnk_python_utils-1.2.2}/MANIFEST.in +0 -0
  9. {oplnk_python_utils-1.2.1 → oplnk_python_utils-1.2.2}/oplnk_python_utils/cache/__init__.py +0 -0
  10. {oplnk_python_utils-1.2.1 → oplnk_python_utils-1.2.2}/oplnk_python_utils/cache/cache_utils.py +0 -0
  11. {oplnk_python_utils-1.2.1 → oplnk_python_utils-1.2.2}/oplnk_python_utils/core/__init__.py +0 -0
  12. {oplnk_python_utils-1.2.1 → oplnk_python_utils-1.2.2}/oplnk_python_utils/core/data.py +0 -0
  13. {oplnk_python_utils-1.2.1 → oplnk_python_utils-1.2.2}/oplnk_python_utils/core/parsing.py +0 -0
  14. {oplnk_python_utils-1.2.1 → oplnk_python_utils-1.2.2}/oplnk_python_utils/core/serialization.py +0 -0
  15. {oplnk_python_utils-1.2.1 → oplnk_python_utils-1.2.2}/oplnk_python_utils/query/__init__.py +0 -0
  16. {oplnk_python_utils-1.2.1 → oplnk_python_utils-1.2.2}/oplnk_python_utils/reports/__init__.py +0 -0
  17. {oplnk_python_utils-1.2.1 → oplnk_python_utils-1.2.2}/oplnk_python_utils.egg-info/SOURCES.txt +0 -0
  18. {oplnk_python_utils-1.2.1 → oplnk_python_utils-1.2.2}/oplnk_python_utils.egg-info/dependency_links.txt +0 -0
  19. {oplnk_python_utils-1.2.1 → oplnk_python_utils-1.2.2}/oplnk_python_utils.egg-info/not-zip-safe +0 -0
  20. {oplnk_python_utils-1.2.1 → oplnk_python_utils-1.2.2}/oplnk_python_utils.egg-info/requires.txt +0 -0
  21. {oplnk_python_utils-1.2.1 → oplnk_python_utils-1.2.2}/oplnk_python_utils.egg-info/top_level.txt +0 -0
  22. {oplnk_python_utils-1.2.1 → oplnk_python_utils-1.2.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: oplnk-python-utils
3
- Version: 1.2.1
3
+ Version: 1.2.2
4
4
  Summary: Reusable utilities for caching, data manipulation, parsing, and MongoDB query building
5
5
  Home-page: https://github.com/openlinkspa/oplnk-python-utils
6
6
  Author: OpenLink SpA
@@ -664,42 +664,62 @@ These fields are only populated when explicitly requested via the `populate` par
664
664
 
665
665
  ### Populate Field Sanitization (v1.2.1)
666
666
 
667
- By default, when you populate a relation, the original ID field is automatically removed from the response (Strapi-like behavior):
667
+ The behavior of original ID fields depends on whether they are being populated:
668
+
669
+ #### Without populate - ID field shown as-is:
670
+
671
+ ```python
672
+ # No populate - field shown normally
673
+ query = {
674
+ "fields": ["name", "establishment_id"]
675
+ }
676
+
677
+ # Response:
678
+ # {
679
+ # "data": [{
680
+ # "_id": "123",
681
+ # "name": "Product",
682
+ # "establishment_id": "est_123" // Shown normally
683
+ # }]
684
+ # }
685
+ ```
686
+
687
+ #### With populate - ID field removed by default:
668
688
 
669
689
  ```python
670
- # Request with populate - establishment_id will be automatically excluded:
690
+ # With populate but no explicit fields request
671
691
  query = {
672
692
  "populate": ["establishment"]
673
693
  }
674
694
 
675
- # Response - establishment_id is not present:
695
+ # Response - establishment_id automatically excluded:
676
696
  # {
677
697
  # "data": [{
678
698
  # "_id": "123",
679
699
  # "name": "Product",
680
- # "establishment": { // Populated object replaces the ID
700
+ # "establishment": { // Populated object
681
701
  # "_id": "est_123",
682
702
  # "name": "Main Office"
683
703
  # }
684
- # // establishment_id is NOT here
685
704
  # }]
686
705
  # }
687
706
  ```
688
707
 
689
- **To keep both the ID and populated object, explicitly request it in `fields`:**
708
+ #### With populate AND explicit fields request - show both:
690
709
 
691
710
  ```python
711
+ # Request both populate and the original ID field explicitly
692
712
  query = {
693
713
  "populate": ["establishment"],
694
- "fields": ["name", "establishment_id"] # Request the ID explicitly
714
+ "fields": ["name", "establishment_id"] # Explicitly request the ID
695
715
  }
696
716
 
697
- # Response - both ID and populated object are present:
717
+ # Response - both are present:
698
718
  # {
699
719
  # "data": [{
700
720
  # "_id": "123",
701
721
  # "name": "Product",
702
- # "establishment_id": "est_123", // Now included
722
+ # "establishment_id": "est_123", // Shown because explicitly requested
703
723
  # "establishment": {
704
724
  # "_id": "est_123",
705
725
  # "name": "Main Office"
@@ -708,7 +728,7 @@ query = {
708
728
  # }
709
729
  ```
710
730
 
711
- **For audit fields:** The original ID is always excluded (no option to keep it, since there's no `creation_user_id` field).
731
+ **For audit fields:** The original ID is always excluded and never shown (no `creation_user_id` field exists to request).
712
732
 
713
733
  ### Custom Pipelines
714
734
 
@@ -622,42 +622,62 @@ These fields are only populated when explicitly requested via the `populate` par
622
622
 
623
623
  ### Populate Field Sanitization (v1.2.1)
624
624
 
625
- By default, when you populate a relation, the original ID field is automatically removed from the response (Strapi-like behavior):
625
+ The behavior of original ID fields depends on whether they are being populated:
626
+
627
+ #### Without populate - ID field shown as-is:
628
+
629
+ ```python
630
+ # No populate - field shown normally
631
+ query = {
632
+ "fields": ["name", "establishment_id"]
633
+ }
634
+
635
+ # Response:
636
+ # {
637
+ # "data": [{
638
+ # "_id": "123",
639
+ # "name": "Product",
640
+ # "establishment_id": "est_123" // Shown normally
641
+ # }]
642
+ # }
643
+ ```
644
+
645
+ #### With populate - ID field removed by default:
626
646
 
627
647
  ```python
628
- # Request with populate - establishment_id will be automatically excluded:
648
+ # With populate but no explicit fields request
629
649
  query = {
630
650
  "populate": ["establishment"]
631
651
  }
632
652
 
633
- # Response - establishment_id is not present:
653
+ # Response - establishment_id automatically excluded:
634
654
  # {
635
655
  # "data": [{
636
656
  # "_id": "123",
637
657
  # "name": "Product",
638
- # "establishment": { // Populated object replaces the ID
658
+ # "establishment": { // Populated object
639
659
  # "_id": "est_123",
640
660
  # "name": "Main Office"
641
661
  # }
642
- # // establishment_id is NOT here
643
662
  # }]
644
663
  # }
645
664
  ```
646
665
 
647
- **To keep both the ID and populated object, explicitly request it in `fields`:**
666
+ #### With populate AND explicit fields request - show both:
648
667
 
649
668
  ```python
669
+ # Request both populate and the original ID field explicitly
650
670
  query = {
651
671
  "populate": ["establishment"],
652
- "fields": ["name", "establishment_id"] # Request the ID explicitly
672
+ "fields": ["name", "establishment_id"] # Explicitly request the ID
653
673
  }
654
674
 
655
- # Response - both ID and populated object are present:
675
+ # Response - both are present:
656
676
  # {
657
677
  # "data": [{
658
678
  # "_id": "123",
659
679
  # "name": "Product",
660
- # "establishment_id": "est_123", // Now included
680
+ # "establishment_id": "est_123", // Shown because explicitly requested
661
681
  # "establishment": {
662
682
  # "_id": "est_123",
663
683
  # "name": "Main Office"
@@ -666,7 +686,7 @@ query = {
666
686
  # }
667
687
  ```
668
688
 
669
- **For audit fields:** The original ID is always excluded (no option to keep it, since there's no `creation_user_id` field).
689
+ **For audit fields:** The original ID is always excluded and never shown (no `creation_user_id` field exists to request).
670
690
 
671
691
  ### Custom Pipelines
672
692
 
@@ -7,7 +7,7 @@ from . import core
7
7
  from . import cache
8
8
  from . import query
9
9
 
10
- __version__ = "1.2.1"
10
+ __version__ = "1.2.2"
11
11
  __author__ = "OpenLink SpA"
12
12
 
13
13
  __all__ = ["core", "cache", "query", "reports", "__version__"]
@@ -778,47 +778,46 @@ class QueryBuilder:
778
778
 
779
779
  return pipeline_copy
780
780
 
781
- def _get_populated_field_mappings(self, query_populate: Any) -> Dict[str, str]:
781
+ def _get_populated_fields_list(self, query_populate: Any) -> List[str]:
782
782
  """
783
- Build a mapping of populated field names to their original ID field names.
783
+ Get list of field names that are being populated.
784
784
 
785
- For example: {"establishment": "establishment_id", "creation_user": "_original_creation_user"}
785
+ For example: ["establishment", "creation_user"]
786
786
 
787
787
  Args:
788
788
  query_populate: The populate parameter (list or dict)
789
789
 
790
790
  Returns:
791
- Dict mapping populated field names to their original ID fields
791
+ List of field names being populated
792
792
  """
793
- mappings = {}
793
+ populated = []
794
794
 
795
795
  if isinstance(query_populate, list):
796
- for field in query_populate:
797
- if field in RESERVED_AUDIT_FIELDS:
798
- # Audit fields: map to the temporary original field
799
- mappings[field] = f"_original_{field}"
800
- else:
801
- # Normal relations: try to find the ID field
802
- for relation in self.relations:
803
- relation_name = relation[1]
804
- # Remove _id or _ids suffix to get base name
805
- base_name = relation_name.replace("_ids", "").replace("_id", "")
806
- if base_name == field:
807
- mappings[field] = relation_name
808
- break
796
+ populated = query_populate
809
797
  elif isinstance(query_populate, dict):
810
- for field in query_populate.keys():
811
- if field in RESERVED_AUDIT_FIELDS:
812
- mappings[field] = f"_original_{field}"
813
- else:
814
- for relation in self.relations:
815
- relation_name = relation[1]
816
- base_name = relation_name.replace("_ids", "").replace("_id", "")
817
- if base_name == field:
818
- mappings[field] = relation_name
819
- break
798
+ populated = list(query_populate.keys())
799
+
800
+ return populated
801
+
802
+ def _get_id_field_for_relation(self, relation_name: str) -> Optional[str]:
803
+ """
804
+ Get the ID field name for a given relation.
805
+
806
+ For example: "establishment" -> "establishment_id"
820
807
 
821
- return mappings
808
+ Args:
809
+ relation_name: The relation name (e.g., "establishment")
810
+
811
+ Returns:
812
+ The ID field name (e.g., "establishment_id") or None if not found
813
+ """
814
+ for relation in self.relations:
815
+ field_name = relation[1]
816
+ # Remove _id or _ids suffix to get base name
817
+ base_name = field_name.replace("_ids", "").replace("_id", "")
818
+ if base_name == relation_name:
819
+ return field_name
820
+ return None
822
821
 
823
822
  def sanitize_result(
824
823
  self, query: Dict[str, Any], data_result: Any, meta_result: Any
@@ -842,22 +841,26 @@ class QueryBuilder:
842
841
  if not meta_result or "totalCount" not in meta_result:
843
842
  meta_result = {"totalCount": 0}
844
843
 
845
- # Sanitize populate fields: remove original ID fields when populated
844
+ # Sanitize populate fields: remove original ID fields when populated (unless requested in fields)
846
845
  if "populate" in query and isinstance(data_result, list):
847
846
  fields_requested = query.get("fields", [])
848
- populated_mappings = self._get_populated_field_mappings(query["populate"])
847
+ populated_fields = self._get_populated_fields_list(query["populate"])
849
848
 
850
849
  for doc in data_result:
851
- for populated_field, original_id_field in populated_mappings.items():
852
- # If it's a reserved audit field (starts with _original_), always remove the temp field
853
- if original_id_field.startswith("_original_"):
854
- if original_id_field in doc:
855
- del doc[original_id_field]
856
- # For normal relations, only remove if not explicitly requested in fields
857
- else:
858
- if original_id_field not in fields_requested:
859
- if original_id_field in doc:
860
- del doc[original_id_field]
850
+ # Always remove temporary audit field copies
851
+ for field in RESERVED_AUDIT_FIELDS.keys():
852
+ temp_field = f"_original_{field}"
853
+ if temp_field in doc:
854
+ del doc[temp_field]
855
+
856
+ # For normal relations: remove original ID unless explicitly requested in fields
857
+ for populated_field in populated_fields:
858
+ if populated_field not in RESERVED_AUDIT_FIELDS:
859
+ # Try to find the ID field for this relation
860
+ id_field = self._get_id_field_for_relation(populated_field)
861
+ if id_field and id_field not in fields_requested:
862
+ if id_field in doc:
863
+ del doc[id_field]
861
864
 
862
865
  return {"data": data_result, "meta": meta_result}
863
866
 
@@ -943,13 +946,7 @@ class QueryBuilder:
943
946
  else:
944
947
  let_value = {"lid": f"${relation_field_name}"}
945
948
 
946
- # Save the original ID field before lookup (will be sanitized unless requested in fields)
947
949
  lookup_relation = [
948
- {
949
- "$addFields": {
950
- f"_original_{relation_field_name}": f"${relation_field_name}"
951
- }
952
- },
953
950
  {
954
951
  "$lookup": {
955
952
  "from": f"{_prefix}{collection_name_relation}",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: oplnk-python-utils
3
- Version: 1.2.1
3
+ Version: 1.2.2
4
4
  Summary: Reusable utilities for caching, data manipulation, parsing, and MongoDB query building
5
5
  Home-page: https://github.com/openlinkspa/oplnk-python-utils
6
6
  Author: OpenLink SpA
@@ -664,42 +664,62 @@ These fields are only populated when explicitly requested via the `populate` par
664
664
 
665
665
  ### Populate Field Sanitization (v1.2.1)
666
666
 
667
- By default, when you populate a relation, the original ID field is automatically removed from the response (Strapi-like behavior):
667
+ The behavior of original ID fields depends on whether they are being populated:
668
+
669
+ #### Without populate - ID field shown as-is:
670
+
671
+ ```python
672
+ # No populate - field shown normally
673
+ query = {
674
+ "fields": ["name", "establishment_id"]
675
+ }
676
+
677
+ # Response:
678
+ # {
679
+ # "data": [{
680
+ # "_id": "123",
681
+ # "name": "Product",
682
+ # "establishment_id": "est_123" // Shown normally
683
+ # }]
684
+ # }
685
+ ```
686
+
687
+ #### With populate - ID field removed by default:
668
688
 
669
689
  ```python
670
- # Request with populate - establishment_id will be automatically excluded:
690
+ # With populate but no explicit fields request
671
691
  query = {
672
692
  "populate": ["establishment"]
673
693
  }
674
694
 
675
- # Response - establishment_id is not present:
695
+ # Response - establishment_id automatically excluded:
676
696
  # {
677
697
  # "data": [{
678
698
  # "_id": "123",
679
699
  # "name": "Product",
680
- # "establishment": { // Populated object replaces the ID
700
+ # "establishment": { // Populated object
681
701
  # "_id": "est_123",
682
702
  # "name": "Main Office"
683
703
  # }
684
- # // establishment_id is NOT here
685
704
  # }]
686
705
  # }
687
706
  ```
688
707
 
689
- **To keep both the ID and populated object, explicitly request it in `fields`:**
708
+ #### With populate AND explicit fields request - show both:
690
709
 
691
710
  ```python
711
+ # Request both populate and the original ID field explicitly
692
712
  query = {
693
713
  "populate": ["establishment"],
694
- "fields": ["name", "establishment_id"] # Request the ID explicitly
714
+ "fields": ["name", "establishment_id"] # Explicitly request the ID
695
715
  }
696
716
 
697
- # Response - both ID and populated object are present:
717
+ # Response - both are present:
698
718
  # {
699
719
  # "data": [{
700
720
  # "_id": "123",
701
721
  # "name": "Product",
702
- # "establishment_id": "est_123", // Now included
722
+ # "establishment_id": "est_123", // Shown because explicitly requested
703
723
  # "establishment": {
704
724
  # "_id": "est_123",
705
725
  # "name": "Main Office"
@@ -708,7 +728,7 @@ query = {
708
728
  # }
709
729
  ```
710
730
 
711
- **For audit fields:** The original ID is always excluded (no option to keep it, since there's no `creation_user_id` field).
731
+ **For audit fields:** The original ID is always excluded and never shown (no `creation_user_id` field exists to request).
712
732
 
713
733
  ### Custom Pipelines
714
734
 
@@ -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.1",
5
+ version="1.2.2",
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",