oplnk-python-utils 1.2.2__py3-none-any.whl → 1.2.4__py3-none-any.whl

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.
@@ -7,7 +7,7 @@ from . import core
7
7
  from . import cache
8
8
  from . import query
9
9
 
10
- __version__ = "1.2.2"
10
+ __version__ = "1.2.4"
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",
24
+ "alias": "creation_user_info",
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",
52
+ "alias": "modification_user_info",
53
53
  "field_type": "id",
54
54
  "project_exclude": {
55
55
  "role": 0,
@@ -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[List] = None
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
- for field in populate_params:
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
- print(
658
- f"Warning: Invalid populate field path '{field}', skipping in projection"
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}"
@@ -847,11 +876,16 @@ class QueryBuilder:
847
876
  populated_fields = self._get_populated_fields_list(query["populate"])
848
877
 
849
878
  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]
879
+ # For audit fields: remove original if populated version exists and not requested in fields
880
+ for field_name, field_config in RESERVED_AUDIT_FIELDS.items():
881
+ if field_name in populated_fields:
882
+ # If the populated alias exists and original field is not explicitly requested
883
+ if (
884
+ field_config["alias"] in doc
885
+ and field_name not in fields_requested
886
+ ):
887
+ if field_name in doc:
888
+ del doc[field_name]
855
889
 
856
890
  # For normal relations: remove original ID unless explicitly requested in fields
857
891
  for populated_field in populated_fields:
@@ -883,12 +917,6 @@ class QueryBuilder:
883
917
  field_config = RESERVED_AUDIT_FIELDS[field_name]
884
918
 
885
919
  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
- },
892
920
  {
893
921
  "$lookup": {
894
922
  "from": field_config["collection"],
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: oplnk-python-utils
3
- Version: 1.2.2
3
+ Version: 1.2.4
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
@@ -642,11 +642,11 @@ query = {
642
642
  "populate": ["creation_user", "modification_user"]
643
643
  }
644
644
 
645
- # Result will include (original ID automatically removed, replaced by populated object):
645
+ # Result will include (original ID removed, replaced by populated object):
646
646
  # {
647
647
  # "data": [{
648
648
  # "_id": "123",
649
- # "creation_user": { // Replaces the original ID field
649
+ # "creation_user_info": { // Populated object
650
650
  # "_id": "user_id_string",
651
651
  # "name": "John Doe",
652
652
  # "person": "person_id"
@@ -657,8 +657,8 @@ query = {
657
657
 
658
658
  **Reserved fields:**
659
659
 
660
- - `creation_user` → automatically populated, original ID is excluded
661
- - `modification_user` → automatically populated, original ID is excluded
660
+ - `creation_user` → automatically populated as `creation_user_info`, original ID is excluded
661
+ - `modification_user` → automatically populated as `modification_user_info`, 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
 
@@ -666,10 +666,10 @@ These fields are only populated when explicitly requested via the `populate` par
666
666
 
667
667
  The behavior of original ID fields depends on whether they are being populated:
668
668
 
669
- #### Without populate - ID field shown as-is:
669
+ #### Without populate - ID field shown as-is (from database):
670
670
 
671
671
  ```python
672
- # No populate - field shown normally
672
+ # No populate - field shown normally from database
673
673
  query = {
674
674
  "fields": ["name", "establishment_id"]
675
675
  }
@@ -728,7 +728,32 @@ query = {
728
728
  # }
729
729
  ```
730
730
 
731
- **For audit fields:** The original ID is always excluded and never shown (no `creation_user_id` field exists to request).
731
+ #### Audit fields behavior:
732
+
733
+ ```python
734
+ # Without populate - original field shown
735
+ query = {
736
+ "fields": ["name", "creation_user"]
737
+ }
738
+ # Response:
739
+ # { "creation_user": "user_123" }
740
+
741
+ # With populate - original field removed, *_info shown instead
742
+ query = {
743
+ "populate": ["creation_user"]
744
+ }
745
+ # Response:
746
+ # { "creation_user_info": { "_id": "user_123", ... } }
747
+ # Note: creation_user field is NOT shown
748
+
749
+ # With populate AND explicit request for original - show both
750
+ query = {
751
+ "populate": ["creation_user"],
752
+ "fields": ["creation_user"] # Explicitly request the ID
753
+ }
754
+ # Response:
755
+ # { "creation_user": "user_123", "creation_user_info": { "_id": "user_123", ... } }
756
+ ```
732
757
 
733
758
  ### Custom Pipelines
734
759
 
@@ -1,4 +1,4 @@
1
- oplnk_python_utils/__init__.py,sha256=xAFF7NxcXq6DJzsfnFa-ThN5WNccGMYc1Uk25qsSRn8,354
1
+ oplnk_python_utils/__init__.py,sha256=LUCUi980Jgbw9QD1nwy31YRJJMDiXNzpk262kizyi1Y,354
2
2
  oplnk_python_utils/cache/__init__.py,sha256=UtCAGUH8WinNMeh-MIOLPZeRMUn5_6Jn_QC2iKWvIN0,646
3
3
  oplnk_python_utils/cache/cache_utils.py,sha256=Ln4sRgM1UELekCfMYZdNdUVecsshaqrcoUrvHRfj4jE,8597
4
4
  oplnk_python_utils/core/__init__.py,sha256=HmT7BR9WCGcu47Bd-eMdSSTImKnzsaalzYVThgTuyMA,253
@@ -6,10 +6,10 @@ oplnk_python_utils/core/data.py,sha256=BwOIGeS4c7gGOOUcx-2RpFJsznjAtBVETRv63A_3P
6
6
  oplnk_python_utils/core/parsing.py,sha256=EdI_uQJWalXL7yen5iyQ6J5PRLgz6Xi_szWaUDOhlgQ,5190
7
7
  oplnk_python_utils/core/serialization.py,sha256=B4g3x6yGjmXySNtOHqqFrq8AlguYz4gldxCJY21UNdQ,1164
8
8
  oplnk_python_utils/query/__init__.py,sha256=dd-PRf5fkoQvAbcONumwrRveS_Ejnw-3Q0hA7ZcF6Ic,147
9
- oplnk_python_utils/query/query_builder.py,sha256=CQJDQjLJGZL6GEtR0wKB90qqPBh6v1j_ONIFt5_uMb4,59656
9
+ oplnk_python_utils/query/query_builder.py,sha256=MVZsRsvNdwDhv-exzNe46UDtBf3EqcuhaRQmaMQ1Bqg,61362
10
10
  oplnk_python_utils/reports/__init__.py,sha256=CPxWaRgXt9sHK_6mQwsXnUxg9yzJ2GHSlQ4lVNpkanY,94
11
- oplnk_python_utils-1.2.2.dist-info/licenses/LICENSE,sha256=JgkAUZElZ6gNf7C2aZPq5-GEjUG2t_yYAKvuhy1OKy0,1090
12
- oplnk_python_utils-1.2.2.dist-info/METADATA,sha256=IpJK5Pc9tgid0Gql5q-Vabi-y8E8K2f3GPCRK8D-kGA,24294
13
- oplnk_python_utils-1.2.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
14
- oplnk_python_utils-1.2.2.dist-info/top_level.txt,sha256=TPZ-emvWspdR-OxTNOvQt-cDrZ4m7P4RMnxsXHOeorc,19
15
- oplnk_python_utils-1.2.2.dist-info/RECORD,,
11
+ oplnk_python_utils-1.2.4.dist-info/licenses/LICENSE,sha256=JgkAUZElZ6gNf7C2aZPq5-GEjUG2t_yYAKvuhy1OKy0,1090
12
+ oplnk_python_utils-1.2.4.dist-info/METADATA,sha256=T2WJjJefVSCSX1pKioSytnB8LbtiiNBn_Egg2QAybxY,24924
13
+ oplnk_python_utils-1.2.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
14
+ oplnk_python_utils-1.2.4.dist-info/top_level.txt,sha256=TPZ-emvWspdR-OxTNOvQt-cDrZ4m7P4RMnxsXHOeorc,19
15
+ oplnk_python_utils-1.2.4.dist-info/RECORD,,