seer-pas-sdk 1.1.1__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.
- {seer_pas_sdk-1.1.1 → seer_pas_sdk-1.2.1}/PKG-INFO +1 -1
- {seer_pas_sdk-1.1.1 → seer_pas_sdk-1.2.1}/docs/index.qmd +57 -30
- {seer_pas_sdk-1.1.1 → seer_pas_sdk-1.2.1}/seer_pas_sdk/common/__init__.py +53 -6
- {seer_pas_sdk-1.1.1 → seer_pas_sdk-1.2.1}/seer_pas_sdk/core/sdk.py +244 -194
- {seer_pas_sdk-1.1.1 → seer_pas_sdk-1.2.1}/seer_pas_sdk/core/unsupported.py +295 -195
- {seer_pas_sdk-1.1.1 → seer_pas_sdk-1.2.1}/seer_pas_sdk.egg-info/PKG-INFO +1 -1
- {seer_pas_sdk-1.1.1 → seer_pas_sdk-1.2.1}/.github/workflows/lint.yml +0 -0
- {seer_pas_sdk-1.1.1 → seer_pas_sdk-1.2.1}/.github/workflows/publish.yml +0 -0
- {seer_pas_sdk-1.1.1 → seer_pas_sdk-1.2.1}/.github/workflows/test.yml +0 -0
- {seer_pas_sdk-1.1.1 → seer_pas_sdk-1.2.1}/.gitignore +0 -0
- {seer_pas_sdk-1.1.1 → seer_pas_sdk-1.2.1}/.pre-commit-config.yaml +0 -0
- {seer_pas_sdk-1.1.1 → seer_pas_sdk-1.2.1}/LICENSE.txt +0 -0
- {seer_pas_sdk-1.1.1 → seer_pas_sdk-1.2.1}/README.md +0 -0
- {seer_pas_sdk-1.1.1 → seer_pas_sdk-1.2.1}/docs/_quarto.yml +0 -0
- {seer_pas_sdk-1.1.1 → seer_pas_sdk-1.2.1}/pyproject.toml +0 -0
- {seer_pas_sdk-1.1.1 → seer_pas_sdk-1.2.1}/seer_pas_sdk/__init__.py +0 -0
- {seer_pas_sdk-1.1.1 → seer_pas_sdk-1.2.1}/seer_pas_sdk/auth/__init__.py +0 -0
- {seer_pas_sdk-1.1.1 → seer_pas_sdk-1.2.1}/seer_pas_sdk/auth/auth.py +0 -0
- {seer_pas_sdk-1.1.1 → seer_pas_sdk-1.2.1}/seer_pas_sdk/common/errors.py +0 -0
- {seer_pas_sdk-1.1.1 → seer_pas_sdk-1.2.1}/seer_pas_sdk/common/groupanalysis.py +0 -0
- {seer_pas_sdk-1.1.1 → seer_pas_sdk-1.2.1}/seer_pas_sdk/core/__init__.py +0 -0
- {seer_pas_sdk-1.1.1 → seer_pas_sdk-1.2.1}/seer_pas_sdk/objects/__init__.py +0 -0
- {seer_pas_sdk-1.1.1 → seer_pas_sdk-1.2.1}/seer_pas_sdk/objects/groupanalysis.py +0 -0
- {seer_pas_sdk-1.1.1 → seer_pas_sdk-1.2.1}/seer_pas_sdk/objects/headers.py +0 -0
- {seer_pas_sdk-1.1.1 → seer_pas_sdk-1.2.1}/seer_pas_sdk/objects/platemap.py +0 -0
- {seer_pas_sdk-1.1.1 → seer_pas_sdk-1.2.1}/seer_pas_sdk/objects/volcanoplot.py +0 -0
- {seer_pas_sdk-1.1.1 → seer_pas_sdk-1.2.1}/seer_pas_sdk.egg-info/SOURCES.txt +0 -0
- {seer_pas_sdk-1.1.1 → seer_pas_sdk-1.2.1}/seer_pas_sdk.egg-info/dependency_links.txt +0 -0
- {seer_pas_sdk-1.1.1 → seer_pas_sdk-1.2.1}/seer_pas_sdk.egg-info/requires.txt +0 -0
- {seer_pas_sdk-1.1.1 → seer_pas_sdk-1.2.1}/seer_pas_sdk.egg-info/top_level.txt +0 -0
- {seer_pas_sdk-1.1.1 → seer_pas_sdk-1.2.1}/setup.cfg +0 -0
- {seer_pas_sdk-1.1.1 → seer_pas_sdk-1.2.1}/tests/__init__.py +0 -0
- {seer_pas_sdk-1.1.1 → seer_pas_sdk-1.2.1}/tests/conftest.py +0 -0
- {seer_pas_sdk-1.1.1 → seer_pas_sdk-1.2.1}/tests/objects/__init__.py +0 -0
- {seer_pas_sdk-1.1.1 → seer_pas_sdk-1.2.1}/tests/objects/test_platemap.py +0 -0
- {seer_pas_sdk-1.1.1 → seer_pas_sdk-1.2.1}/tests/test_auth.py +0 -0
- {seer_pas_sdk-1.1.1 → seer_pas_sdk-1.2.1}/tests/test_common.py +0 -0
- {seer_pas_sdk-1.1.1 → seer_pas_sdk-1.2.1}/tests/test_objects.py +0 -0
- {seer_pas_sdk-1.1.1 → seer_pas_sdk-1.2.1}/tests/test_sdk.py +0 -0
- {seer_pas_sdk-1.1.1 → seer_pas_sdk-1.2.1}/tests/unsupported_platemap.py +0 -0
|
@@ -18,12 +18,27 @@ $ pip install seer-pas-sdk
|
|
|
18
18
|
This page gives an overview of the SDK's feature. Complete documentation for each class / method can be found [here](reference/).
|
|
19
19
|
|
|
20
20
|
### Configuration
|
|
21
|
-
PAS has a simple authorization system that
|
|
21
|
+
The PAS SDK has a simple authorization system that involves your username and password fields like on the web app. You can define your username and password for your own ready reference and convenience as follows:
|
|
22
22
|
```{python}
|
|
23
23
|
USERNAME = "gnu403"
|
|
24
24
|
PASSWORD = "Test!234567"
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
+
The PAS SDK requires either a `tenant` or `tenant_id` argument in the SDK object constructor.
|
|
28
|
+
|
|
29
|
+
`tenant` refers to the user provided name of the tenant.
|
|
30
|
+
|
|
31
|
+
`tenant_id` refers to the immutable and unique identifier of the tenant.
|
|
32
|
+
`tenant_id` is an absolute reference to the tenant, even if the tenant name is changed.
|
|
33
|
+
|
|
34
|
+
More details on multi-tenant management can be found in the [Multi Tenant Management](#multi-tenant-management) section below.
|
|
35
|
+
|
|
36
|
+
You can define your tenant name or tenant ID as follows:
|
|
37
|
+
```{python}
|
|
38
|
+
TENANT = "My Tenant Name"
|
|
39
|
+
TENANT_ID = "abc1234abc1234"
|
|
40
|
+
```
|
|
41
|
+
|
|
27
42
|
You may also choose to pass in an `instance` param in the SDK object to instantiate the PAS SDK to the EU or US instance.:
|
|
28
43
|
```{python}
|
|
29
44
|
INSTANCE = "US"
|
|
@@ -38,10 +53,13 @@ After importing the SeerSDK module, you can instantiate an object in the followi
|
|
|
38
53
|
from seer_pas_sdk import SeerSDK
|
|
39
54
|
|
|
40
55
|
# Instantiate an SDK object with your credentials:
|
|
41
|
-
sdk = SeerSDK(USERNAME, PASSWORD)
|
|
56
|
+
sdk = SeerSDK(USERNAME, PASSWORD, tenant=TENANT)
|
|
42
57
|
|
|
43
|
-
#
|
|
44
|
-
sdk = SeerSDK(USERNAME, PASSWORD, INSTANCE)
|
|
58
|
+
# Instantiate an SDK object with your credentials and instance:
|
|
59
|
+
sdk = SeerSDK(USERNAME, PASSWORD, INSTANCE, tenant=TENANT)
|
|
60
|
+
|
|
61
|
+
# Instantiate an SDK object with your credentials and tenant ID:
|
|
62
|
+
sdk = SeerSDK(USERNAME, PASSWORD, INSTANCE, tenant_id=TENANT_ID)
|
|
45
63
|
```
|
|
46
64
|
|
|
47
65
|
```{python}
|
|
@@ -56,18 +74,16 @@ Additional information and examples can also be found below.
|
|
|
56
74
|
### Multi Tenant Management
|
|
57
75
|
Introduced in version 0.2.0
|
|
58
76
|
|
|
59
|
-
By default, you will be active in your home tenant upon log in. The home tenant is defined as the organization account that issued the original invitation for the user to join PAS.
|
|
60
|
-
The optional 'tenant' parameter is available in the SeerSDK constructor to navigate directly to a desired tenant.
|
|
61
|
-
A notification message will display upon login.
|
|
62
|
-
|
|
63
|
-
|
|
64
77
|
The following tools are available to navigate between tenants:
|
|
65
78
|
```{python}
|
|
66
79
|
#| eval: false
|
|
67
80
|
from seer_pas_sdk import SeerSDK
|
|
68
81
|
|
|
82
|
+
# Assume tenant upon login
|
|
69
83
|
sdk = SeerSDK(USERNAME, PASSWORD, INSTANCE, tenant='My Active Tenant')
|
|
70
84
|
|
|
85
|
+
sdk = SeerSDK(USERNAME, PASSWORD, INSTANCE, tenant_id='myuuidstring-1234')
|
|
86
|
+
|
|
71
87
|
# Retrieve value of current active tenant
|
|
72
88
|
print(sdk.get_active_tenant())
|
|
73
89
|
|
|
@@ -337,7 +353,7 @@ example = sdk.find_msruns(sample_ids)
|
|
|
337
353
|
log(example)
|
|
338
354
|
```
|
|
339
355
|
```
|
|
340
|
-
[{'id': '81c6a180-15e0-11ee-bdf1-bbaa73585acf', '
|
|
356
|
+
[{'id': '81c6a180-15e0-11ee-bdf1-bbaa73585acf', 'sample_uuid': '812139c0-15e0-11ee-bdf1-bbaa73585acf', 'raw_file_path': '7ec8cad0-15e0-11ee-bdf1-bbaa73585acf/20230628182044224/TestFile2.raw', 'well_location': 'D11', 'nanoparticle': '', 'instrument_name': '', 'created_by': '04936dea-d255-4130-8e82-2f28938a8f9a', 'created_timestamp': '2023-06-28T18:20:49.006Z', 'last_modified_by': '04936dea-d255-4130-8e82-2f28938a8f9a', 'last_modified_timestamp': '2023-06-28T18:20:49.006Z', 'user_group': None, 'sample_id': 'A112', 'nanoparticle_id': '', 'control': '', 'control_id': '', 'date_sample_prep': '', 'sample_volume': '', 'peptide_concentration': '', 'peptide_mass_sample': '', 'dilution_factor': '', 'kit_id': None, 'injection_timestamp': None, 'ms_instrument_sn': None, 'recon_volume': None, 'gradient': None}, {'id': '816a9ed0-15e0-11ee-bdf1-bbaa73585acf', 'sample_uuid': '803e05b0-15e0-11ee-bdf1-bbaa73585acf', 'raw_file_path': '7ec8cad0-15e0-11ee-bdf1-bbaa73585acf/20230628182044224/TestFile1.raw', 'well_location': 'C11', 'nanoparticle': 'NONE', 'instrument_name': '', 'created_by': '04936dea-d255-4130-8e82-2f28938a8f9a', 'created_timestamp': '2023-06-28T18:20:48.408Z', 'last_modified_by': '04936dea-d255-4130-8e82-2f28938a8f9a', 'last_modified_timestamp': '2023-06-28T18:20:48.408Z', 'user_group': None, 'sample_id': 'A111', 'nanoparticle_id': 'NONE', 'control': 'MPE Control', 'control_id': 'MPE Control', 'date_sample_prep': '', 'sample_volume': '20.0', 'peptide_concentration': '59.514', 'peptide_mass_sample': '8.57', 'dilution_factor': '1.0', 'kit_id': None, 'injection_timestamp': None, 'ms_instrument_sn': None, 'recon_volume': None, 'gradient': None}]
|
|
341
357
|
```
|
|
342
358
|
|
|
343
359
|
There is also an option to return everything as a DataFrame instead:
|
|
@@ -347,7 +363,7 @@ example = sdk.find_msruns(sample_ids, as_df=True)
|
|
|
347
363
|
log(example)
|
|
348
364
|
```
|
|
349
365
|
```
|
|
350
|
-
id
|
|
366
|
+
id sample_uuid raw_file_path well_location nanoparticle instrument_name created_by created_timestamp last_modified_by last_modified_timestamp space sample_id nanoparticle_id control control_id date_sample_prep sample_volume peptide_concentration peptide_mass_sample dilution_factor kit_id injection_timestamp ms_instrument_sn recon_volume gradient
|
|
351
367
|
0 81c6a180-15e0-11ee-bdf1-bbaa73585acf 812139c0-15e0-11ee-bdf1-bbaa73585acf 7ec8cad0-15e0-11ee-bdf1-bbaa73585acf/202306281... D11 04936dea-d255-4130-8e82-2f28938a8f9a 2023-06-28T18:20:49.006Z 04936dea-d255-4130-8e82-2f28938a8f9a 2023-06-28T18:20:49.006Z None A112 None None None None None
|
|
352
368
|
1 816a9ed0-15e0-11ee-bdf1-bbaa73585acf 803e05b0-15e0-11ee-bdf1-bbaa73585acf 7ec8cad0-15e0-11ee-bdf1-bbaa73585acf/202306281... C11 NONE 04936dea-d255-4130-8e82-2f28938a8f9a 2023-06-28T18:20:48.408Z 04936dea-d255-4130-8e82-2f28938a8f9a 2023-06-28T18:20:48.408Z None A111 NONE MPE Control MPE Control 20.0 59.514 8.57 1.0 None None None None None
|
|
353
369
|
```
|
|
@@ -578,10 +594,17 @@ log(analysis)
|
|
|
578
594
|
|
|
579
595
|
|
|
580
596
|
### Find Analyses
|
|
581
|
-
Returns a list of analyses objects for the authenticated user. If
|
|
597
|
+
Returns a list of analyses objects for the authenticated user. If `None` is provided for all query arguments, returns all analyses available to the user within the active tenant.
|
|
582
598
|
|
|
583
599
|
###### <u>Params</u>
|
|
584
|
-
`analysis_id`: (`str`, optional) Unique ID of the analysis to be fetched, defaulted to None.
|
|
600
|
+
* `analysis_id`: (`str`, optional) Unique ID of the analysis to be fetched, defaulted to None.
|
|
601
|
+
* `analysis_name`: (`str`, optional) Name of the analysis to be fetched, defaulted to None. Results will be matched on a substring basis.
|
|
602
|
+
* `folder_id`: (`str`, optional) Unique ID of the folder to fetch analyses from, defaulted to None.
|
|
603
|
+
* `folder_name`: (`str`, optional) Name of the folder to fetch analyses from, defaulted to None.
|
|
604
|
+
* `project_id`: (`str`, optional) Unique ID of the project to filter the result set of analyses, defaulted to None.
|
|
605
|
+
* `project_name`: (`str`, optional) Name of the project to filter the result set of analyses, defaulted to None.
|
|
606
|
+
* `plate_name`: (`str`, optional) Name of a plate to filter the result set of analyses, defaulted to None.
|
|
607
|
+
* `as_df`: (`bool`, optional) Whether the result should be converted to a DataFrame, defaulted to False.
|
|
585
608
|
<br>
|
|
586
609
|
|
|
587
610
|
###### <u>Returns</u>
|
|
@@ -955,11 +978,9 @@ log(sdk.group_analysis_results(group_analysis_id, box_plot_info))
|
|
|
955
978
|
Downloads the FASTA file(s) associated with an analysis protocol. You can specify an analysis_id (the function will resolve the protocol automatically) or provide an analysis_protocol_id directly.
|
|
956
979
|
|
|
957
980
|
###### <u>Params</u>
|
|
958
|
-
* `analysis_protocol_id`: (`str`, optional) ID of the analysis protocol
|
|
959
|
-
|
|
960
|
-
* `
|
|
961
|
-
|
|
962
|
-
* `download_path`: (`str`, optional) Directory to save files to. Defaults to the current working directory.
|
|
981
|
+
* `analysis_protocol_id`: (`str`, optional) The unique ID of the analysis protocol associated with the FASTA files to download.
|
|
982
|
+
* `analysis_id`: (`str`, optional) The unique ID of the analysis whose protocol FASTA file(s) will be downloaded.
|
|
983
|
+
* `analysis_name`: (`str`, optional) The name of the analysis whose protocol FASTA file(s) will be downloaded.
|
|
963
984
|
|
|
964
985
|
Note: Provide either analysis_id or analysis_protocol_id (but not both).
|
|
965
986
|
|
|
@@ -977,6 +998,10 @@ sdk.download_analysis_protocol_fasta(
|
|
|
977
998
|
)
|
|
978
999
|
```
|
|
979
1000
|
|
|
1001
|
+
```
|
|
1002
|
+
['./uniprot_human_2023_08.fasta', './contaminants.fasta']
|
|
1003
|
+
```
|
|
1004
|
+
|
|
980
1005
|
Download by analysis protocol ID to a specific folder:
|
|
981
1006
|
```{python}
|
|
982
1007
|
#| eval: false
|
|
@@ -991,16 +1016,20 @@ sdk.download_analysis_protocol_fasta(
|
|
|
991
1016
|
```
|
|
992
1017
|
<br>
|
|
993
1018
|
|
|
994
|
-
### Get Analysis Protocol FASTA
|
|
995
|
-
Returns
|
|
1019
|
+
### Get Analysis Protocol FASTA URLs
|
|
1020
|
+
Returns download URLs for the FASTA file(s) associated with an analysis protocol. You can specify an analysis_id (the function will resolve the protocol automatically) or provide an analysis_protocol_id directly.
|
|
1021
|
+
|
|
1022
|
+
Download URLs are valid for 15 minutes after generation.
|
|
996
1023
|
|
|
997
1024
|
###### <u>Params</u>
|
|
998
|
-
* `analysis_protocol_id`: (`str`, optional) ID of the analysis protocol
|
|
999
|
-
* `analysis_id`: (`str`, optional) ID of the analysis whose protocol FASTA file(s)
|
|
1000
|
-
|
|
1025
|
+
* `analysis_protocol_id`: (`str`, optional) The unique ID of the analysis protocol associated with the FASTA files.
|
|
1026
|
+
* `analysis_id`: (`str`, optional) The unique ID of the analysis whose protocol FASTA file(s) should be retrieved.
|
|
1027
|
+
* `analysis_name`: (`str`, optional) The name of the analysis whose protocol FASTA file(s) should be retrieved.
|
|
1028
|
+
|
|
1029
|
+
If both parameters are provided, `analysis_protocol_id` takes precedence.
|
|
1001
1030
|
|
|
1002
1031
|
###### <u>Returns</u>
|
|
1003
|
-
* links: (`
|
|
1032
|
+
* links: (`dict`) Dictionary containing filename and signed URL as key-value pairs for the FASTA files linked to the protocol.
|
|
1004
1033
|
|
|
1005
1034
|
###### <u>Examples</u>
|
|
1006
1035
|
Get by analysis ID:
|
|
@@ -1012,10 +1041,8 @@ sdk.get_analysis_protocol_fasta_link(
|
|
|
1012
1041
|
```
|
|
1013
1042
|
|
|
1014
1043
|
```
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
{"filename": "contaminants.fasta", "url": "https://...signed..."}
|
|
1018
|
-
]
|
|
1044
|
+
{"uniprot_human_2023_08.fasta" : "https://...signed...",
|
|
1045
|
+
"contaminants.fasta" : "https://...signed..."}
|
|
1019
1046
|
```
|
|
1020
1047
|
Get by analysis protocol ID:
|
|
1021
1048
|
```{python}
|
|
@@ -1026,8 +1053,8 @@ sdk.get_analysis_protocol_fasta_link(
|
|
|
1026
1053
|
```
|
|
1027
1054
|
```
|
|
1028
1055
|
[
|
|
1029
|
-
{"
|
|
1030
|
-
|
|
1056
|
+
{"uniprot_human_2023_08.fasta" : "https://...signed...",
|
|
1057
|
+
"contaminants.fasta" : "https://...signed..."}
|
|
1031
1058
|
]
|
|
1032
1059
|
```
|
|
1033
1060
|
<hr>
|
|
@@ -99,7 +99,7 @@ def dict_to_df(data):
|
|
|
99
99
|
|
|
100
100
|
|
|
101
101
|
# Most cases appear to be a .tsv file.
|
|
102
|
-
def download_df(url, is_tsv=True, dtype={}):
|
|
102
|
+
def download_df(url, is_tsv=True, dtype={}, usecols=None):
|
|
103
103
|
"""
|
|
104
104
|
Fetches a TSV/CSV file from a URL and returns as a Pandas DataFrame.
|
|
105
105
|
|
|
@@ -114,6 +114,9 @@ def download_df(url, is_tsv=True, dtype={}):
|
|
|
114
114
|
dtype : dict
|
|
115
115
|
Data type conversion when intaking columns. e.g. {'a': str, 'b': np.float64}
|
|
116
116
|
|
|
117
|
+
usecols : list
|
|
118
|
+
Subset of columns to download. If not specified, downloads all columns.
|
|
119
|
+
|
|
117
120
|
Returns
|
|
118
121
|
-------
|
|
119
122
|
pandas.core.frame.DataFrame
|
|
@@ -139,11 +142,9 @@ def download_df(url, is_tsv=True, dtype={}):
|
|
|
139
142
|
|
|
140
143
|
if not url:
|
|
141
144
|
return pd.DataFrame()
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
else:
|
|
146
|
-
csv = pd.read_csv(url_content, dtype=dtype)
|
|
145
|
+
csv = pd.read_csv(
|
|
146
|
+
url, dtype=dtype, sep="\t" if is_tsv else ",", usecols=usecols
|
|
147
|
+
)
|
|
147
148
|
return csv
|
|
148
149
|
|
|
149
150
|
|
|
@@ -679,6 +680,52 @@ def camel_case(s):
|
|
|
679
680
|
return "".join([s[0].lower(), s[1:]])
|
|
680
681
|
|
|
681
682
|
|
|
683
|
+
def validate_d_zip_file(file):
|
|
684
|
+
"""
|
|
685
|
+
Return True if a .d.zip file aligns with Seer requirements for PAS upload.
|
|
686
|
+
|
|
687
|
+
Parameters
|
|
688
|
+
----------
|
|
689
|
+
file : str
|
|
690
|
+
The name of the zip file.
|
|
691
|
+
|
|
692
|
+
Returns
|
|
693
|
+
-------
|
|
694
|
+
bool
|
|
695
|
+
True if the .d.zip file is valid, False otherwise.
|
|
696
|
+
"""
|
|
697
|
+
|
|
698
|
+
if not file.lower().endswith(".d.zip"):
|
|
699
|
+
return False
|
|
700
|
+
|
|
701
|
+
basename = os.path.basename(file)
|
|
702
|
+
|
|
703
|
+
# Remove the .zip extension to get the .d folder name
|
|
704
|
+
d_name = basename[:-4]
|
|
705
|
+
|
|
706
|
+
try:
|
|
707
|
+
with zipfile.ZipFile(file, "r") as zf:
|
|
708
|
+
names = zf.namelist()
|
|
709
|
+
|
|
710
|
+
except:
|
|
711
|
+
return False
|
|
712
|
+
|
|
713
|
+
if not names:
|
|
714
|
+
return False
|
|
715
|
+
|
|
716
|
+
# check for files at the root level
|
|
717
|
+
root_entries = [n for n in names if "/" not in n.rstrip("/")]
|
|
718
|
+
if root_entries:
|
|
719
|
+
return False
|
|
720
|
+
|
|
721
|
+
# find folders
|
|
722
|
+
top_level = {n.split("/")[0] for n in names}
|
|
723
|
+
if len(top_level) != 1 or d_name not in top_level:
|
|
724
|
+
return False
|
|
725
|
+
|
|
726
|
+
return True
|
|
727
|
+
|
|
728
|
+
|
|
682
729
|
def rename_d_zip_file(source, destination):
|
|
683
730
|
"""
|
|
684
731
|
Renames a .d.zip file. The function extracts the contents of the source zip file, renames the inner .d folder, and rezips the contents into the destination zip file.
|