oplnk-python-utils 1.2.0__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.0/oplnk_python_utils.egg-info → oplnk_python_utils-1.2.2}/PKG-INFO +75 -7
  2. {oplnk_python_utils-1.2.0 → oplnk_python_utils-1.2.2}/README.md +74 -6
  3. {oplnk_python_utils-1.2.0 → oplnk_python_utils-1.2.2}/oplnk_python_utils/__init__.py +1 -1
  4. {oplnk_python_utils-1.2.0 → oplnk_python_utils-1.2.2}/oplnk_python_utils/query/query_builder.py +70 -2
  5. {oplnk_python_utils-1.2.0 → oplnk_python_utils-1.2.2/oplnk_python_utils.egg-info}/PKG-INFO +75 -7
  6. {oplnk_python_utils-1.2.0 → oplnk_python_utils-1.2.2}/setup.py +1 -1
  7. {oplnk_python_utils-1.2.0 → oplnk_python_utils-1.2.2}/LICENSE +0 -0
  8. {oplnk_python_utils-1.2.0 → oplnk_python_utils-1.2.2}/MANIFEST.in +0 -0
  9. {oplnk_python_utils-1.2.0 → oplnk_python_utils-1.2.2}/oplnk_python_utils/cache/__init__.py +0 -0
  10. {oplnk_python_utils-1.2.0 → oplnk_python_utils-1.2.2}/oplnk_python_utils/cache/cache_utils.py +0 -0
  11. {oplnk_python_utils-1.2.0 → oplnk_python_utils-1.2.2}/oplnk_python_utils/core/__init__.py +0 -0
  12. {oplnk_python_utils-1.2.0 → oplnk_python_utils-1.2.2}/oplnk_python_utils/core/data.py +0 -0
  13. {oplnk_python_utils-1.2.0 → oplnk_python_utils-1.2.2}/oplnk_python_utils/core/parsing.py +0 -0
  14. {oplnk_python_utils-1.2.0 → oplnk_python_utils-1.2.2}/oplnk_python_utils/core/serialization.py +0 -0
  15. {oplnk_python_utils-1.2.0 → oplnk_python_utils-1.2.2}/oplnk_python_utils/query/__init__.py +0 -0
  16. {oplnk_python_utils-1.2.0 → oplnk_python_utils-1.2.2}/oplnk_python_utils/reports/__init__.py +0 -0
  17. {oplnk_python_utils-1.2.0 → oplnk_python_utils-1.2.2}/oplnk_python_utils.egg-info/SOURCES.txt +0 -0
  18. {oplnk_python_utils-1.2.0 → oplnk_python_utils-1.2.2}/oplnk_python_utils.egg-info/dependency_links.txt +0 -0
  19. {oplnk_python_utils-1.2.0 → oplnk_python_utils-1.2.2}/oplnk_python_utils.egg-info/not-zip-safe +0 -0
  20. {oplnk_python_utils-1.2.0 → oplnk_python_utils-1.2.2}/oplnk_python_utils.egg-info/requires.txt +0 -0
  21. {oplnk_python_utils-1.2.0 → oplnk_python_utils-1.2.2}/oplnk_python_utils.egg-info/top_level.txt +0 -0
  22. {oplnk_python_utils-1.2.0 → 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.0
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
@@ -623,7 +623,7 @@ relations = [
623
623
  ]
624
624
  ```
625
625
 
626
- ### Reserved Audit Fields (NEW in v1.2.0)
626
+ ### Reserved Audit Fields (v1.2.1)
627
627
 
628
628
  The QueryBuilder automatically handles common audit fields without requiring manual configuration:
629
629
 
@@ -642,12 +642,11 @@ query = {
642
642
  "populate": ["creation_user", "modification_user"]
643
643
  }
644
644
 
645
- # Result will include:
645
+ # Result will include (original ID automatically removed, replaced by populated object):
646
646
  # {
647
647
  # "data": [{
648
648
  # "_id": "123",
649
- # "creation_user": "user_id_string",
650
- # "creation_user_info": { // Automatically populated
649
+ # "creation_user": { // Replaces the original ID field
651
650
  # "_id": "user_id_string",
652
651
  # "name": "John Doe",
653
652
  # "person": "person_id"
@@ -657,11 +656,80 @@ query = {
657
656
  ```
658
657
 
659
658
  **Reserved fields:**
660
- - `creation_user` → populated as `creation_user_info`
661
- - `modification_user` → populated as `modification_user_info`
659
+
660
+ - `creation_user` → automatically populated, original ID is excluded
661
+ - `modification_user` → automatically populated, original ID is excluded
662
662
 
663
663
  These fields are only populated when explicitly requested via the `populate` parameter, following the Strapi philosophy of on-demand loading.
664
664
 
665
+ ### Populate Field Sanitization (v1.2.1)
666
+
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:
688
+
689
+ ```python
690
+ # With populate but no explicit fields request
691
+ query = {
692
+ "populate": ["establishment"]
693
+ }
694
+
695
+ # Response - establishment_id automatically excluded:
696
+ # {
697
+ # "data": [{
698
+ # "_id": "123",
699
+ # "name": "Product",
700
+ # "establishment": { // Populated object
701
+ # "_id": "est_123",
702
+ # "name": "Main Office"
703
+ # }
704
+ # }]
705
+ # }
706
+ ```
707
+
708
+ #### With populate AND explicit fields request - show both:
709
+
710
+ ```python
711
+ # Request both populate and the original ID field explicitly
712
+ query = {
713
+ "populate": ["establishment"],
714
+ "fields": ["name", "establishment_id"] # Explicitly request the ID
715
+ }
716
+
717
+ # Response - both are present:
718
+ # {
719
+ # "data": [{
720
+ # "_id": "123",
721
+ # "name": "Product",
722
+ # "establishment_id": "est_123", // Shown because explicitly requested
723
+ # "establishment": {
724
+ # "_id": "est_123",
725
+ # "name": "Main Office"
726
+ # }
727
+ # }]
728
+ # }
729
+ ```
730
+
731
+ **For audit fields:** The original ID is always excluded and never shown (no `creation_user_id` field exists to request).
732
+
665
733
  ### Custom Pipelines
666
734
 
667
735
  ```python
@@ -581,7 +581,7 @@ relations = [
581
581
  ]
582
582
  ```
583
583
 
584
- ### Reserved Audit Fields (NEW in v1.2.0)
584
+ ### Reserved Audit Fields (v1.2.1)
585
585
 
586
586
  The QueryBuilder automatically handles common audit fields without requiring manual configuration:
587
587
 
@@ -600,12 +600,11 @@ query = {
600
600
  "populate": ["creation_user", "modification_user"]
601
601
  }
602
602
 
603
- # Result will include:
603
+ # Result will include (original ID automatically removed, replaced by populated object):
604
604
  # {
605
605
  # "data": [{
606
606
  # "_id": "123",
607
- # "creation_user": "user_id_string",
608
- # "creation_user_info": { // Automatically populated
607
+ # "creation_user": { // Replaces the original ID field
609
608
  # "_id": "user_id_string",
610
609
  # "name": "John Doe",
611
610
  # "person": "person_id"
@@ -615,11 +614,80 @@ query = {
615
614
  ```
616
615
 
617
616
  **Reserved fields:**
618
- - `creation_user` → populated as `creation_user_info`
619
- - `modification_user` → populated as `modification_user_info`
617
+
618
+ - `creation_user` → automatically populated, original ID is excluded
619
+ - `modification_user` → automatically populated, original ID is excluded
620
620
 
621
621
  These fields are only populated when explicitly requested via the `populate` parameter, following the Strapi philosophy of on-demand loading.
622
622
 
623
+ ### Populate Field Sanitization (v1.2.1)
624
+
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:
646
+
647
+ ```python
648
+ # With populate but no explicit fields request
649
+ query = {
650
+ "populate": ["establishment"]
651
+ }
652
+
653
+ # Response - establishment_id automatically excluded:
654
+ # {
655
+ # "data": [{
656
+ # "_id": "123",
657
+ # "name": "Product",
658
+ # "establishment": { // Populated object
659
+ # "_id": "est_123",
660
+ # "name": "Main Office"
661
+ # }
662
+ # }]
663
+ # }
664
+ ```
665
+
666
+ #### With populate AND explicit fields request - show both:
667
+
668
+ ```python
669
+ # Request both populate and the original ID field explicitly
670
+ query = {
671
+ "populate": ["establishment"],
672
+ "fields": ["name", "establishment_id"] # Explicitly request the ID
673
+ }
674
+
675
+ # Response - both are present:
676
+ # {
677
+ # "data": [{
678
+ # "_id": "123",
679
+ # "name": "Product",
680
+ # "establishment_id": "est_123", // Shown because explicitly requested
681
+ # "establishment": {
682
+ # "_id": "est_123",
683
+ # "name": "Main Office"
684
+ # }
685
+ # }]
686
+ # }
687
+ ```
688
+
689
+ **For audit fields:** The original ID is always excluded and never shown (no `creation_user_id` field exists to request).
690
+
623
691
  ### Custom Pipelines
624
692
 
625
693
  ```python
@@ -7,7 +7,7 @@ from . import core
7
7
  from . import cache
8
8
  from . import query
9
9
 
10
- __version__ = "1.2.0"
10
+ __version__ = "1.2.2"
11
11
  __author__ = "OpenLink SpA"
12
12
 
13
13
  __all__ = ["core", "cache", "query", "reports", "__version__"]
@@ -21,7 +21,7 @@ RESERVED_AUDIT_FIELDS = {
21
21
  "creation_user": {
22
22
  "collection": "users",
23
23
  "field_name": "creation_user",
24
- "alias": "creation_user_info",
24
+ "alias": "creation_user",
25
25
  "field_type": "id",
26
26
  "project_exclude": {
27
27
  "role": 0,
@@ -49,7 +49,7 @@ RESERVED_AUDIT_FIELDS = {
49
49
  "modification_user": {
50
50
  "collection": "users",
51
51
  "field_name": "modification_user",
52
- "alias": "modification_user_info",
52
+ "alias": "modification_user",
53
53
  "field_type": "id",
54
54
  "project_exclude": {
55
55
  "role": 0,
@@ -778,6 +778,47 @@ class QueryBuilder:
778
778
 
779
779
  return pipeline_copy
780
780
 
781
+ def _get_populated_fields_list(self, query_populate: Any) -> List[str]:
782
+ """
783
+ Get list of field names that are being populated.
784
+
785
+ For example: ["establishment", "creation_user"]
786
+
787
+ Args:
788
+ query_populate: The populate parameter (list or dict)
789
+
790
+ Returns:
791
+ List of field names being populated
792
+ """
793
+ populated = []
794
+
795
+ if isinstance(query_populate, list):
796
+ populated = query_populate
797
+ elif isinstance(query_populate, dict):
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"
807
+
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
821
+
781
822
  def sanitize_result(
782
823
  self, query: Dict[str, Any], data_result: Any, meta_result: Any
783
824
  ) -> Dict[str, Any]:
@@ -800,6 +841,27 @@ class QueryBuilder:
800
841
  if not meta_result or "totalCount" not in meta_result:
801
842
  meta_result = {"totalCount": 0}
802
843
 
844
+ # Sanitize populate fields: remove original ID fields when populated (unless requested in fields)
845
+ if "populate" in query and isinstance(data_result, list):
846
+ fields_requested = query.get("fields", [])
847
+ populated_fields = self._get_populated_fields_list(query["populate"])
848
+
849
+ for doc in data_result:
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]
864
+
803
865
  return {"data": data_result, "meta": meta_result}
804
866
 
805
867
  def get_reserved_field_lookup(self, field_name: str) -> List[Dict[str, Any]]:
@@ -821,6 +883,12 @@ class QueryBuilder:
821
883
  field_config = RESERVED_AUDIT_FIELDS[field_name]
822
884
 
823
885
  lookup_pipeline = [
886
+ # Save the original ID before lookup (will be removed during sanitization)
887
+ {
888
+ "$addFields": {
889
+ f"_original_{field_config['field_name']}": f"${field_config['field_name']}"
890
+ }
891
+ },
824
892
  {
825
893
  "$lookup": {
826
894
  "from": field_config["collection"],
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: oplnk-python-utils
3
- Version: 1.2.0
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
@@ -623,7 +623,7 @@ relations = [
623
623
  ]
624
624
  ```
625
625
 
626
- ### Reserved Audit Fields (NEW in v1.2.0)
626
+ ### Reserved Audit Fields (v1.2.1)
627
627
 
628
628
  The QueryBuilder automatically handles common audit fields without requiring manual configuration:
629
629
 
@@ -642,12 +642,11 @@ query = {
642
642
  "populate": ["creation_user", "modification_user"]
643
643
  }
644
644
 
645
- # Result will include:
645
+ # Result will include (original ID automatically removed, replaced by populated object):
646
646
  # {
647
647
  # "data": [{
648
648
  # "_id": "123",
649
- # "creation_user": "user_id_string",
650
- # "creation_user_info": { // Automatically populated
649
+ # "creation_user": { // Replaces the original ID field
651
650
  # "_id": "user_id_string",
652
651
  # "name": "John Doe",
653
652
  # "person": "person_id"
@@ -657,11 +656,80 @@ query = {
657
656
  ```
658
657
 
659
658
  **Reserved fields:**
660
- - `creation_user` → populated as `creation_user_info`
661
- - `modification_user` → populated as `modification_user_info`
659
+
660
+ - `creation_user` → automatically populated, original ID is excluded
661
+ - `modification_user` → automatically populated, original ID is excluded
662
662
 
663
663
  These fields are only populated when explicitly requested via the `populate` parameter, following the Strapi philosophy of on-demand loading.
664
664
 
665
+ ### Populate Field Sanitization (v1.2.1)
666
+
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:
688
+
689
+ ```python
690
+ # With populate but no explicit fields request
691
+ query = {
692
+ "populate": ["establishment"]
693
+ }
694
+
695
+ # Response - establishment_id automatically excluded:
696
+ # {
697
+ # "data": [{
698
+ # "_id": "123",
699
+ # "name": "Product",
700
+ # "establishment": { // Populated object
701
+ # "_id": "est_123",
702
+ # "name": "Main Office"
703
+ # }
704
+ # }]
705
+ # }
706
+ ```
707
+
708
+ #### With populate AND explicit fields request - show both:
709
+
710
+ ```python
711
+ # Request both populate and the original ID field explicitly
712
+ query = {
713
+ "populate": ["establishment"],
714
+ "fields": ["name", "establishment_id"] # Explicitly request the ID
715
+ }
716
+
717
+ # Response - both are present:
718
+ # {
719
+ # "data": [{
720
+ # "_id": "123",
721
+ # "name": "Product",
722
+ # "establishment_id": "est_123", // Shown because explicitly requested
723
+ # "establishment": {
724
+ # "_id": "est_123",
725
+ # "name": "Main Office"
726
+ # }
727
+ # }]
728
+ # }
729
+ ```
730
+
731
+ **For audit fields:** The original ID is always excluded and never shown (no `creation_user_id` field exists to request).
732
+
665
733
  ### Custom Pipelines
666
734
 
667
735
  ```python
@@ -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.0",
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",