oplnk-python-utils 1.2.0__tar.gz → 1.2.1__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.0/oplnk_python_utils.egg-info → oplnk_python_utils-1.2.1}/PKG-INFO +55 -7
- {oplnk_python_utils-1.2.0 → oplnk_python_utils-1.2.1}/README.md +54 -6
- {oplnk_python_utils-1.2.0 → oplnk_python_utils-1.2.1}/oplnk_python_utils/__init__.py +1 -1
- {oplnk_python_utils-1.2.0 → oplnk_python_utils-1.2.1}/oplnk_python_utils/query/query_builder.py +73 -2
- {oplnk_python_utils-1.2.0 → oplnk_python_utils-1.2.1/oplnk_python_utils.egg-info}/PKG-INFO +55 -7
- {oplnk_python_utils-1.2.0 → oplnk_python_utils-1.2.1}/setup.py +1 -1
- {oplnk_python_utils-1.2.0 → oplnk_python_utils-1.2.1}/LICENSE +0 -0
- {oplnk_python_utils-1.2.0 → oplnk_python_utils-1.2.1}/MANIFEST.in +0 -0
- {oplnk_python_utils-1.2.0 → oplnk_python_utils-1.2.1}/oplnk_python_utils/cache/__init__.py +0 -0
- {oplnk_python_utils-1.2.0 → oplnk_python_utils-1.2.1}/oplnk_python_utils/cache/cache_utils.py +0 -0
- {oplnk_python_utils-1.2.0 → oplnk_python_utils-1.2.1}/oplnk_python_utils/core/__init__.py +0 -0
- {oplnk_python_utils-1.2.0 → oplnk_python_utils-1.2.1}/oplnk_python_utils/core/data.py +0 -0
- {oplnk_python_utils-1.2.0 → oplnk_python_utils-1.2.1}/oplnk_python_utils/core/parsing.py +0 -0
- {oplnk_python_utils-1.2.0 → oplnk_python_utils-1.2.1}/oplnk_python_utils/core/serialization.py +0 -0
- {oplnk_python_utils-1.2.0 → oplnk_python_utils-1.2.1}/oplnk_python_utils/query/__init__.py +0 -0
- {oplnk_python_utils-1.2.0 → oplnk_python_utils-1.2.1}/oplnk_python_utils/reports/__init__.py +0 -0
- {oplnk_python_utils-1.2.0 → oplnk_python_utils-1.2.1}/oplnk_python_utils.egg-info/SOURCES.txt +0 -0
- {oplnk_python_utils-1.2.0 → oplnk_python_utils-1.2.1}/oplnk_python_utils.egg-info/dependency_links.txt +0 -0
- {oplnk_python_utils-1.2.0 → oplnk_python_utils-1.2.1}/oplnk_python_utils.egg-info/not-zip-safe +0 -0
- {oplnk_python_utils-1.2.0 → oplnk_python_utils-1.2.1}/oplnk_python_utils.egg-info/requires.txt +0 -0
- {oplnk_python_utils-1.2.0 → oplnk_python_utils-1.2.1}/oplnk_python_utils.egg-info/top_level.txt +0 -0
- {oplnk_python_utils-1.2.0 → oplnk_python_utils-1.2.1}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: oplnk-python-utils
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.1
|
|
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 (
|
|
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":
|
|
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,60 @@ query = {
|
|
|
657
656
|
```
|
|
658
657
|
|
|
659
658
|
**Reserved fields:**
|
|
660
|
-
|
|
661
|
-
-
|
|
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
|
+
By default, when you populate a relation, the original ID field is automatically removed from the response (Strapi-like behavior):
|
|
668
|
+
|
|
669
|
+
```python
|
|
670
|
+
# Request with populate - establishment_id will be automatically excluded:
|
|
671
|
+
query = {
|
|
672
|
+
"populate": ["establishment"]
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
# Response - establishment_id is not present:
|
|
676
|
+
# {
|
|
677
|
+
# "data": [{
|
|
678
|
+
# "_id": "123",
|
|
679
|
+
# "name": "Product",
|
|
680
|
+
# "establishment": { // Populated object replaces the ID
|
|
681
|
+
# "_id": "est_123",
|
|
682
|
+
# "name": "Main Office"
|
|
683
|
+
# }
|
|
684
|
+
# // establishment_id is NOT here
|
|
685
|
+
# }]
|
|
686
|
+
# }
|
|
687
|
+
```
|
|
688
|
+
|
|
689
|
+
**To keep both the ID and populated object, explicitly request it in `fields`:**
|
|
690
|
+
|
|
691
|
+
```python
|
|
692
|
+
query = {
|
|
693
|
+
"populate": ["establishment"],
|
|
694
|
+
"fields": ["name", "establishment_id"] # Request the ID explicitly
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
# Response - both ID and populated object are present:
|
|
698
|
+
# {
|
|
699
|
+
# "data": [{
|
|
700
|
+
# "_id": "123",
|
|
701
|
+
# "name": "Product",
|
|
702
|
+
# "establishment_id": "est_123", // Now included
|
|
703
|
+
# "establishment": {
|
|
704
|
+
# "_id": "est_123",
|
|
705
|
+
# "name": "Main Office"
|
|
706
|
+
# }
|
|
707
|
+
# }]
|
|
708
|
+
# }
|
|
709
|
+
```
|
|
710
|
+
|
|
711
|
+
**For audit fields:** The original ID is always excluded (no option to keep it, since there's no `creation_user_id` field).
|
|
712
|
+
|
|
665
713
|
### Custom Pipelines
|
|
666
714
|
|
|
667
715
|
```python
|
|
@@ -581,7 +581,7 @@ relations = [
|
|
|
581
581
|
]
|
|
582
582
|
```
|
|
583
583
|
|
|
584
|
-
### Reserved Audit Fields (
|
|
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":
|
|
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,60 @@ query = {
|
|
|
615
614
|
```
|
|
616
615
|
|
|
617
616
|
**Reserved fields:**
|
|
618
|
-
|
|
619
|
-
-
|
|
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
|
+
By default, when you populate a relation, the original ID field is automatically removed from the response (Strapi-like behavior):
|
|
626
|
+
|
|
627
|
+
```python
|
|
628
|
+
# Request with populate - establishment_id will be automatically excluded:
|
|
629
|
+
query = {
|
|
630
|
+
"populate": ["establishment"]
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
# Response - establishment_id is not present:
|
|
634
|
+
# {
|
|
635
|
+
# "data": [{
|
|
636
|
+
# "_id": "123",
|
|
637
|
+
# "name": "Product",
|
|
638
|
+
# "establishment": { // Populated object replaces the ID
|
|
639
|
+
# "_id": "est_123",
|
|
640
|
+
# "name": "Main Office"
|
|
641
|
+
# }
|
|
642
|
+
# // establishment_id is NOT here
|
|
643
|
+
# }]
|
|
644
|
+
# }
|
|
645
|
+
```
|
|
646
|
+
|
|
647
|
+
**To keep both the ID and populated object, explicitly request it in `fields`:**
|
|
648
|
+
|
|
649
|
+
```python
|
|
650
|
+
query = {
|
|
651
|
+
"populate": ["establishment"],
|
|
652
|
+
"fields": ["name", "establishment_id"] # Request the ID explicitly
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
# Response - both ID and populated object are present:
|
|
656
|
+
# {
|
|
657
|
+
# "data": [{
|
|
658
|
+
# "_id": "123",
|
|
659
|
+
# "name": "Product",
|
|
660
|
+
# "establishment_id": "est_123", // Now included
|
|
661
|
+
# "establishment": {
|
|
662
|
+
# "_id": "est_123",
|
|
663
|
+
# "name": "Main Office"
|
|
664
|
+
# }
|
|
665
|
+
# }]
|
|
666
|
+
# }
|
|
667
|
+
```
|
|
668
|
+
|
|
669
|
+
**For audit fields:** The original ID is always excluded (no option to keep it, since there's no `creation_user_id` field).
|
|
670
|
+
|
|
623
671
|
### Custom Pipelines
|
|
624
672
|
|
|
625
673
|
```python
|
{oplnk_python_utils-1.2.0 → oplnk_python_utils-1.2.1}/oplnk_python_utils/query/query_builder.py
RENAMED
|
@@ -21,7 +21,7 @@ RESERVED_AUDIT_FIELDS = {
|
|
|
21
21
|
"creation_user": {
|
|
22
22
|
"collection": "users",
|
|
23
23
|
"field_name": "creation_user",
|
|
24
|
-
"alias": "
|
|
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": "
|
|
52
|
+
"alias": "modification_user",
|
|
53
53
|
"field_type": "id",
|
|
54
54
|
"project_exclude": {
|
|
55
55
|
"role": 0,
|
|
@@ -778,6 +778,48 @@ class QueryBuilder:
|
|
|
778
778
|
|
|
779
779
|
return pipeline_copy
|
|
780
780
|
|
|
781
|
+
def _get_populated_field_mappings(self, query_populate: Any) -> Dict[str, str]:
|
|
782
|
+
"""
|
|
783
|
+
Build a mapping of populated field names to their original ID field names.
|
|
784
|
+
|
|
785
|
+
For example: {"establishment": "establishment_id", "creation_user": "_original_creation_user"}
|
|
786
|
+
|
|
787
|
+
Args:
|
|
788
|
+
query_populate: The populate parameter (list or dict)
|
|
789
|
+
|
|
790
|
+
Returns:
|
|
791
|
+
Dict mapping populated field names to their original ID fields
|
|
792
|
+
"""
|
|
793
|
+
mappings = {}
|
|
794
|
+
|
|
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
|
|
809
|
+
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
|
|
820
|
+
|
|
821
|
+
return mappings
|
|
822
|
+
|
|
781
823
|
def sanitize_result(
|
|
782
824
|
self, query: Dict[str, Any], data_result: Any, meta_result: Any
|
|
783
825
|
) -> Dict[str, Any]:
|
|
@@ -800,6 +842,23 @@ class QueryBuilder:
|
|
|
800
842
|
if not meta_result or "totalCount" not in meta_result:
|
|
801
843
|
meta_result = {"totalCount": 0}
|
|
802
844
|
|
|
845
|
+
# Sanitize populate fields: remove original ID fields when populated
|
|
846
|
+
if "populate" in query and isinstance(data_result, list):
|
|
847
|
+
fields_requested = query.get("fields", [])
|
|
848
|
+
populated_mappings = self._get_populated_field_mappings(query["populate"])
|
|
849
|
+
|
|
850
|
+
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]
|
|
861
|
+
|
|
803
862
|
return {"data": data_result, "meta": meta_result}
|
|
804
863
|
|
|
805
864
|
def get_reserved_field_lookup(self, field_name: str) -> List[Dict[str, Any]]:
|
|
@@ -821,6 +880,12 @@ class QueryBuilder:
|
|
|
821
880
|
field_config = RESERVED_AUDIT_FIELDS[field_name]
|
|
822
881
|
|
|
823
882
|
lookup_pipeline = [
|
|
883
|
+
# Save the original ID before lookup (will be removed during sanitization)
|
|
884
|
+
{
|
|
885
|
+
"$addFields": {
|
|
886
|
+
f"_original_{field_config['field_name']}": f"${field_config['field_name']}"
|
|
887
|
+
}
|
|
888
|
+
},
|
|
824
889
|
{
|
|
825
890
|
"$lookup": {
|
|
826
891
|
"from": field_config["collection"],
|
|
@@ -878,7 +943,13 @@ class QueryBuilder:
|
|
|
878
943
|
else:
|
|
879
944
|
let_value = {"lid": f"${relation_field_name}"}
|
|
880
945
|
|
|
946
|
+
# Save the original ID field before lookup (will be sanitized unless requested in fields)
|
|
881
947
|
lookup_relation = [
|
|
948
|
+
{
|
|
949
|
+
"$addFields": {
|
|
950
|
+
f"_original_{relation_field_name}": f"${relation_field_name}"
|
|
951
|
+
}
|
|
952
|
+
},
|
|
882
953
|
{
|
|
883
954
|
"$lookup": {
|
|
884
955
|
"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.
|
|
3
|
+
Version: 1.2.1
|
|
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 (
|
|
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":
|
|
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,60 @@ query = {
|
|
|
657
656
|
```
|
|
658
657
|
|
|
659
658
|
**Reserved fields:**
|
|
660
|
-
|
|
661
|
-
-
|
|
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
|
+
By default, when you populate a relation, the original ID field is automatically removed from the response (Strapi-like behavior):
|
|
668
|
+
|
|
669
|
+
```python
|
|
670
|
+
# Request with populate - establishment_id will be automatically excluded:
|
|
671
|
+
query = {
|
|
672
|
+
"populate": ["establishment"]
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
# Response - establishment_id is not present:
|
|
676
|
+
# {
|
|
677
|
+
# "data": [{
|
|
678
|
+
# "_id": "123",
|
|
679
|
+
# "name": "Product",
|
|
680
|
+
# "establishment": { // Populated object replaces the ID
|
|
681
|
+
# "_id": "est_123",
|
|
682
|
+
# "name": "Main Office"
|
|
683
|
+
# }
|
|
684
|
+
# // establishment_id is NOT here
|
|
685
|
+
# }]
|
|
686
|
+
# }
|
|
687
|
+
```
|
|
688
|
+
|
|
689
|
+
**To keep both the ID and populated object, explicitly request it in `fields`:**
|
|
690
|
+
|
|
691
|
+
```python
|
|
692
|
+
query = {
|
|
693
|
+
"populate": ["establishment"],
|
|
694
|
+
"fields": ["name", "establishment_id"] # Request the ID explicitly
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
# Response - both ID and populated object are present:
|
|
698
|
+
# {
|
|
699
|
+
# "data": [{
|
|
700
|
+
# "_id": "123",
|
|
701
|
+
# "name": "Product",
|
|
702
|
+
# "establishment_id": "est_123", // Now included
|
|
703
|
+
# "establishment": {
|
|
704
|
+
# "_id": "est_123",
|
|
705
|
+
# "name": "Main Office"
|
|
706
|
+
# }
|
|
707
|
+
# }]
|
|
708
|
+
# }
|
|
709
|
+
```
|
|
710
|
+
|
|
711
|
+
**For audit fields:** The original ID is always excluded (no option to keep it, since there's no `creation_user_id` field).
|
|
712
|
+
|
|
665
713
|
### Custom Pipelines
|
|
666
714
|
|
|
667
715
|
```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.
|
|
5
|
+
version="1.2.1",
|
|
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
|
{oplnk_python_utils-1.2.0 → oplnk_python_utils-1.2.1}/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.0 → oplnk_python_utils-1.2.1}/oplnk_python_utils/core/serialization.py
RENAMED
|
File without changes
|
|
File without changes
|
{oplnk_python_utils-1.2.0 → oplnk_python_utils-1.2.1}/oplnk_python_utils/reports/__init__.py
RENAMED
|
File without changes
|
{oplnk_python_utils-1.2.0 → oplnk_python_utils-1.2.1}/oplnk_python_utils.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{oplnk_python_utils-1.2.0 → oplnk_python_utils-1.2.1}/oplnk_python_utils.egg-info/not-zip-safe
RENAMED
|
File without changes
|
{oplnk_python_utils-1.2.0 → oplnk_python_utils-1.2.1}/oplnk_python_utils.egg-info/requires.txt
RENAMED
|
File without changes
|
{oplnk_python_utils-1.2.0 → oplnk_python_utils-1.2.1}/oplnk_python_utils.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|