protein-quest 0.3.1__py3-none-any.whl → 0.4.0__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.

Potentially problematic release.


This version of protein-quest might be problematic. Click here for more details.

protein_quest/taxonomy.py CHANGED
@@ -9,9 +9,9 @@ from typing import Literal, get_args
9
9
  from aiohttp.client import ClientResponse
10
10
  from aiohttp_retry import RetryClient
11
11
  from cattrs.gen import make_dict_structure_fn, override
12
- from cattrs.preconf.orjson import make_converter
13
12
  from yarl import URL
14
13
 
14
+ from protein_quest.converter import converter
15
15
  from protein_quest.go import TextIOWrapper
16
16
  from protein_quest.utils import friendly_session
17
17
 
@@ -42,8 +42,6 @@ class SearchTaxonResponse:
42
42
  results: list[Taxon]
43
43
 
44
44
 
45
- converter = make_converter()
46
-
47
45
  converter.register_structure_hook(
48
46
  Taxon,
49
47
  make_dict_structure_fn(
protein_quest/uniprot.py CHANGED
@@ -201,7 +201,7 @@ def _build_sparql_generic_query(select_clause: str, where_clause: str, limit: in
201
201
  """)
202
202
 
203
203
 
204
- def _build_sparql_generic_by_uniprot_accesions_query(
204
+ def _build_sparql_generic_by_uniprot_accessions_query(
205
205
  uniprot_accs: Iterable[str], select_clause: str, where_clause: str, limit: int = 10_000, groupby_clause=""
206
206
  ) -> str:
207
207
  values = " ".join(f'("{ac}")' for ac in uniprot_accs)
@@ -269,7 +269,7 @@ def _build_sparql_query_pdb(uniprot_accs: Iterable[str], limit=10_000) -> str:
269
269
  """)
270
270
 
271
271
  groupby_clause = "?protein ?pdb_db ?pdb_method ?pdb_resolution"
272
- return _build_sparql_generic_by_uniprot_accesions_query(
272
+ return _build_sparql_generic_by_uniprot_accessions_query(
273
273
  uniprot_accs, select_clause, where_clause, limit, groupby_clause
274
274
  )
275
275
 
@@ -284,7 +284,7 @@ def _build_sparql_query_af(uniprot_accs: Iterable[str], limit=10_000) -> str:
284
284
  ?protein rdfs:seeAlso ?af_db .
285
285
  ?af_db up:database <http://purl.uniprot.org/database/AlphaFoldDB> .
286
286
  """)
287
- return _build_sparql_generic_by_uniprot_accesions_query(uniprot_accs, select_clause, dedent(where_clause), limit)
287
+ return _build_sparql_generic_by_uniprot_accessions_query(uniprot_accs, select_clause, dedent(where_clause), limit)
288
288
 
289
289
 
290
290
  def _build_sparql_query_emdb(uniprot_accs: Iterable[str], limit=10_000) -> str:
@@ -297,7 +297,7 @@ def _build_sparql_query_emdb(uniprot_accs: Iterable[str], limit=10_000) -> str:
297
297
  ?protein rdfs:seeAlso ?emdb_db .
298
298
  ?emdb_db up:database <http://purl.uniprot.org/database/EMDB> .
299
299
  """)
300
- return _build_sparql_generic_by_uniprot_accesions_query(uniprot_accs, select_clause, dedent(where_clause), limit)
300
+ return _build_sparql_generic_by_uniprot_accessions_query(uniprot_accs, select_clause, dedent(where_clause), limit)
301
301
 
302
302
 
303
303
  def _execute_sparql_search(
@@ -509,3 +509,156 @@ def search4emdb(uniprot_accs: Iterable[str], limit: int = 10_000, timeout: int =
509
509
  )
510
510
  limit_check("Search for EMDB entries on uniprot", limit, len(raw_results))
511
511
  return _flatten_results_emdb(raw_results)
512
+
513
+
514
+ def _build_complex_sparql_query(uniprot_accs: Iterable[str], limit: int) -> str:
515
+ """Builds a SPARQL query to retrieve ComplexPortal information for given UniProt accessions.
516
+
517
+ Example:
518
+
519
+ ```sparql
520
+ PREFIX up: <http://purl.uniprot.org/core/>
521
+ PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
522
+ PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
523
+
524
+ SELECT
525
+ ?protein
526
+ ?cp_db
527
+ ?cp_comment
528
+ (GROUP_CONCAT(DISTINCT ?member; separator=",") AS ?complex_members)
529
+ (COUNT(DISTINCT ?member) AS ?member_count)
530
+ WHERE {
531
+ # Input UniProt accessions
532
+ VALUES (?ac) { ("P05067") ("P60709") ("Q05471")}
533
+ BIND (IRI(CONCAT("http://purl.uniprot.org/uniprot/", ?ac)) AS ?protein)
534
+
535
+ # ComplexPortal cross-reference for each input protein
536
+ ?protein a up:Protein ;
537
+ rdfs:seeAlso ?cp_db .
538
+ ?cp_db up:database <http://purl.uniprot.org/database/ComplexPortal> .
539
+ OPTIONAL { ?cp_db rdfs:comment ?cp_comment . }
540
+
541
+ # All member proteins of the same ComplexPortal complex
542
+ ?member a up:Protein ;
543
+ rdfs:seeAlso ?cp_db .
544
+ }
545
+ GROUP BY ?protein ?cp_db ?cp_comment
546
+ ORDER BY ?protein ?cp_db
547
+ LIMIT 500
548
+ ```
549
+
550
+ """
551
+ select_clause = dedent("""\
552
+ ?protein ?cp_db ?cp_comment
553
+ (GROUP_CONCAT(DISTINCT ?member; separator=",") AS ?complex_members)
554
+ """)
555
+ where_clause = dedent("""
556
+ # --- Complex Info ---
557
+ ?protein a up:Protein ;
558
+ rdfs:seeAlso ?cp_db .
559
+ ?cp_db up:database <http://purl.uniprot.org/database/ComplexPortal> .
560
+ OPTIONAL { ?cp_db rdfs:comment ?cp_comment . }
561
+ # All member proteins of the same ComplexPortal complex
562
+ ?member a up:Protein ;
563
+ rdfs:seeAlso ?cp_db .
564
+ """)
565
+ group_by = dedent("""
566
+ ?protein ?cp_db ?cp_comment
567
+ """)
568
+ return _build_sparql_generic_by_uniprot_accessions_query(
569
+ uniprot_accs, select_clause, where_clause, limit, groupby_clause=group_by
570
+ )
571
+
572
+
573
+ @dataclass(frozen=True)
574
+ class ComplexPortalEntry:
575
+ """A ComplexPortal entry.
576
+
577
+ Parameters:
578
+ query_protein: The UniProt accession used to find entry.
579
+ complex_id: The ComplexPortal identifier (for example "CPX-1234").
580
+ complex_url: The URL to the ComplexPortal entry.
581
+ complex_title: The title of the complex.
582
+ members: UniProt accessions which are members of the complex.
583
+ """
584
+
585
+ query_protein: str
586
+ complex_id: str
587
+ complex_url: str
588
+ complex_title: str
589
+ members: set[str]
590
+
591
+
592
+ def _flatten_results_complex(raw_results) -> list[ComplexPortalEntry]:
593
+ results = []
594
+ for raw_result in raw_results:
595
+ query_protein = raw_result["protein"]["value"].split("/")[-1]
596
+ complex_id = raw_result["cp_db"]["value"].split("/")[-1]
597
+ complex_url = f"https://www.ebi.ac.uk/complexportal/complex/{complex_id}"
598
+ complex_title = raw_result.get("cp_comment", {}).get("value", "")
599
+ members = {m.split("/")[-1] for m in raw_result["complex_members"]["value"].split(",")}
600
+ results.append(
601
+ ComplexPortalEntry(
602
+ query_protein=query_protein,
603
+ complex_id=complex_id,
604
+ complex_url=complex_url,
605
+ complex_title=complex_title,
606
+ members=members,
607
+ )
608
+ )
609
+ return results
610
+
611
+
612
+ def search4macromolecular_complexes(
613
+ uniprot_accs: Iterable[str], limit: int = 10_000, timeout: int = 1_800
614
+ ) -> list[ComplexPortalEntry]:
615
+ """Search for macromolecular complexes by UniProtKB accessions.
616
+
617
+ Queries for references to/from https://www.ebi.ac.uk/complexportal/ database in the Uniprot SPARQL endpoint.
618
+
619
+ Args:
620
+ uniprot_accs: UniProt accessions.
621
+ limit: Maximum number of results to return.
622
+ timeout: Timeout for the SPARQL query in seconds.
623
+
624
+ Returns:
625
+ List of ComplexPortalEntry objects.
626
+ """
627
+ sparql_query = _build_complex_sparql_query(uniprot_accs, limit)
628
+ logger.info("Executing SPARQL query for macromolecular complexes: %s", sparql_query)
629
+ raw_results = _execute_sparql_search(
630
+ sparql_query=sparql_query,
631
+ timeout=timeout,
632
+ )
633
+ limit_check("Search for complexes", limit, len(raw_results))
634
+ return _flatten_results_complex(raw_results)
635
+
636
+
637
+ def search4interaction_partners(
638
+ uniprot_acc: str, excludes: set[str] | None = None, limit: int = 10_000, timeout: int = 1_800
639
+ ) -> dict[str, set[str]]:
640
+ """Search for interaction partners of a given UniProt accession using ComplexPortal database references.
641
+
642
+ Args:
643
+ uniprot_acc: UniProt accession to search interaction partners for.
644
+ excludes: Set of UniProt accessions to exclude from the results.
645
+ For example already known interaction partners.
646
+ If None then no complex members are excluded.
647
+ limit: Maximum number of results to return.
648
+ timeout: Timeout for the SPARQL query in seconds.
649
+
650
+ Returns:
651
+ Dictionary with UniProt accessions of interaction partners as keys and sets of ComplexPortal entry IDs
652
+ in which the interaction occurs as values.
653
+ """
654
+ ucomplexes = search4macromolecular_complexes([uniprot_acc], limit=limit, timeout=timeout)
655
+ hits: dict[str, set[str]] = {}
656
+ if excludes is None:
657
+ excludes = set()
658
+ for ucomplex in ucomplexes:
659
+ for member in ucomplex.members:
660
+ if member != uniprot_acc and member not in excludes:
661
+ if member not in hits:
662
+ hits[member] = set()
663
+ hits[member].add(ucomplex.complex_id)
664
+ return hits
protein_quest/utils.py CHANGED
@@ -2,11 +2,12 @@
2
2
 
3
3
  import asyncio
4
4
  import logging
5
+ import shutil
5
6
  from collections.abc import Coroutine, Iterable
6
7
  from contextlib import asynccontextmanager
7
8
  from pathlib import Path
8
9
  from textwrap import dedent
9
- from typing import Any
10
+ from typing import Any, Literal, get_args
10
11
 
11
12
  import aiofiles
12
13
  import aiohttp
@@ -138,3 +139,29 @@ def run_async[R](coroutine: Coroutine[Any, Any, R]) -> R:
138
139
  return asyncio.run(coroutine)
139
140
  except RuntimeError as e:
140
141
  raise NestedAsyncIOLoopError from e
142
+
143
+
144
+ CopyMethod = Literal["copy", "symlink"]
145
+ copy_methods = set(get_args(CopyMethod))
146
+
147
+
148
+ def copyfile(source: Path, target: Path, copy_method: CopyMethod = "copy"):
149
+ """Make target path be same file as source by either copying or symlinking.
150
+
151
+ Args:
152
+ source: The source file to copy or symlink.
153
+ target: The target file to create.
154
+ copy_method: The method to use for copying.
155
+
156
+ Raises:
157
+ FileNotFoundError: If the source file or parent of target does not exist.
158
+ ValueError: If the method is not "copy" or "symlink".
159
+ """
160
+ if copy_method == "copy":
161
+ shutil.copyfile(source, target)
162
+ elif copy_method == "symlink":
163
+ rel_source = source.relative_to(target.parent, walk_up=True)
164
+ target.symlink_to(rel_source)
165
+ else:
166
+ msg = f"Unknown method: {copy_method}"
167
+ raise ValueError(msg)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: protein_quest
3
- Version: 0.3.1
3
+ Version: 0.4.0
4
4
  Summary: Search/retrieve/filter proteins and protein structures
5
5
  Project-URL: Homepage, https://github.com/haddocking/protein-quest
6
6
  Project-URL: Issues, https://github.com/haddocking/protein-quest/issues
@@ -56,17 +56,23 @@ graph TB;
56
56
  searchuniprot --> |uniprot_accessions|searchpdbe[/Search PDBe/]
57
57
  searchuniprot --> |uniprot_accessions|searchaf[/Search Alphafold/]
58
58
  searchuniprot -. uniprot_accessions .-> searchemdb[/Search EMDB/]
59
+ searchintactionpartners[/Search interaction partners/] -.-x |uniprot_accessions|searchuniprot
60
+ searchcomplexes[/Search complexes/]
59
61
  searchpdbe -->|pdb_ids|fetchpdbe[Retrieve PDBe]
60
62
  searchaf --> |uniprot_accessions|fetchad(Retrieve AlphaFold)
61
63
  searchemdb -. emdb_ids .->fetchemdb[Retrieve EMDB]
62
- fetchpdbe -->|mmcif_files_with_uniprot_acc| chainfilter{Filter on chain of uniprot}
63
- chainfilter --> |mmcif_files| residuefilter{Filter on chain length}
64
- fetchad -->|pdb_files| confidencefilter{Filter out low confidence}
64
+ fetchpdbe -->|mmcif_files| chainfilter{{Filter on chain of uniprot}}
65
+ chainfilter --> |mmcif_files| residuefilter{{Filter on chain length}}
66
+ fetchad -->|mmcif_files| confidencefilter{{Filter out low confidence}}
67
+ confidencefilter --> |mmcif_files| ssfilter{{Filter on secondary structure}}
68
+ residuefilter --> |mmcif_files| ssfilter
65
69
  classDef dashedBorder stroke-dasharray: 5 5;
66
70
  goterm:::dashedBorder
67
71
  taxonomy:::dashedBorder
68
72
  searchemdb:::dashedBorder
69
73
  fetchemdb:::dashedBorder
74
+ searchintactionpartners:::dashedBorder
75
+ searchcomplexes:::dashedBorder
70
76
  ```
71
77
 
72
78
  (Dotted nodes and edges are side-quests.)
@@ -175,6 +181,18 @@ protein-quest filter residue \
175
181
  ./filtered-chains ./filtered
176
182
  ```
177
183
 
184
+ ### To filter on secondary structure
185
+
186
+ To filter on structure being mostly alpha helices and have no beta sheets.
187
+
188
+ ```shell
189
+ protein-quest filter secondary-structure \
190
+ --ratio-min-helix-residues 0.5 \
191
+ --ratio-max-sheet-residues 0.0 \
192
+ --write-stats filtered-ss/stats.csv \
193
+ ./filtered-chains ./filtered-ss
194
+ ```
195
+
178
196
  ### Search Taxonomy
179
197
 
180
198
  ```shell
@@ -190,6 +208,32 @@ You can use following command to search for a Gene Ontology (GO) term.
190
208
  protein-quest search go --limit 5 --aspect cellular_component apoptosome -
191
209
  ```
192
210
 
211
+ ### Search for interaction partners
212
+
213
+ Use https://www.ebi.ac.uk/complexportal to find interaction partners of given UniProt accession.
214
+
215
+ ```shell
216
+ protein-quest search interaction-partners Q05471 interaction-partners-of-Q05471.txt
217
+ ```
218
+
219
+ The `interaction-partners-of-Q05471.txt` file contains uniprot accessions (one per line).
220
+
221
+ ### Search for complexes
222
+
223
+ Given Uniprot accessions search for macromolecular complexes at https://www.ebi.ac.uk/complexportal
224
+ and return the complex entries and their members.
225
+
226
+ ```shell
227
+ echo Q05471 | protein-quest search complexes - complexes.csv
228
+ ```
229
+
230
+ The `complexes.csv` looks like
231
+
232
+ ```csv
233
+ query_protein,complex_id,complex_url,complex_title,members
234
+ Q05471,CPX-2122,https://www.ebi.ac.uk/complexportal/complex/CPX-2122,Swr1 chromatin remodelling complex,P31376;P35817;P38326;P53201;P53930;P60010;P80428;Q03388;Q03433;Q03940;Q05471;Q06707;Q12464;Q12509
235
+ ```
236
+
193
237
  ## Model Context Protocol (MCP) server
194
238
 
195
239
  Protein quest can also help LLMs like Claude Sonnet 4 by providing a [set of tools](https://modelcontextprotocol.io/docs/learn/server-concepts#tools-ai-actions) for protein structures.
@@ -0,0 +1,26 @@
1
+ protein_quest/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ protein_quest/__version__.py,sha256=je7v2gXyxr6yRVCFAS0wS-iABSLJOuCb-IPR-x90UAU,56
3
+ protein_quest/cli.py,sha256=9Cgvn5BXbrAloIU2KCiFxLxJSyAoa2RLdmuB0HGsUJM,43078
4
+ protein_quest/converter.py,sha256=Y-Oxf7lDNbEicL6GS-IpNWDwaAiHgIgs5bFAcEHCKdQ,1441
5
+ protein_quest/emdb.py,sha256=QEeU0VJQ4lLM-o5yAU3QZlrtzDZNgnC5fCjlqPtTyAY,1370
6
+ protein_quest/filters.py,sha256=-gasSXR4g5SzYSYbkfcDwR-tm2KCAhCMdpIVJrUPR1w,5224
7
+ protein_quest/go.py,sha256=lZNEcw8nTc9wpV3cl4y2FG9Lsj8wsXQ6zemmAQs_DWE,5650
8
+ protein_quest/mcp_server.py,sha256=CXw5rTStunXdAVQ3BWPXy19zmgQGwV5uPcWlN1HF9do,7389
9
+ protein_quest/parallel.py,sha256=ZJrLO1t2HXs4EeNctytvBTyROPBq-4-gLf35PiolHf0,3468
10
+ protein_quest/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
+ protein_quest/ss.py,sha256=qOr0aMycNAtZmXXvhCN-KZH3Qp4EejnBcE6fsFgCrmY,10343
12
+ protein_quest/taxonomy.py,sha256=4mKv8zll4mX02Ow8CTvyqMJE2KJZvcq3QlTjjjLOJJk,5072
13
+ protein_quest/uniprot.py,sha256=DIwQYzWZREZ7SGhkJT4Ozgl36pdz47FNfZ1QoEgEaXE,24239
14
+ protein_quest/utils.py,sha256=z4PPPcog6nvPhA93DWVf7stv5uJ4h_2BP5owdhoO5mo,5626
15
+ protein_quest/alphafold/__init__.py,sha256=Ktasi5BRp71wO7-PpOGDpIRRtBEefs8knIdlKQeLQpk,51
16
+ protein_quest/alphafold/confidence.py,sha256=pYIuwYdkuPuHLagcX1dSvSyZ_84xboRLfHUxkEoc4MY,6766
17
+ protein_quest/alphafold/entry_summary.py,sha256=GtE3rT7wH3vIOOeiXY2s80Fo6EzdoqlcvakW8K591Yk,1257
18
+ protein_quest/alphafold/fetch.py,sha256=iFHORaO-2NvPwmpm33tfOFUcSJx8mBGwMXxwc4bRuk8,11336
19
+ protein_quest/pdbe/__init__.py,sha256=eNNHtN60NAGea7gvRkIzkoTXsYPK99s-ldIcKWYO6So,61
20
+ protein_quest/pdbe/fetch.py,sha256=tlCrWoaOrwxnQFrf-PnimUUa6lmtHwwysS51efYsBcA,2379
21
+ protein_quest/pdbe/io.py,sha256=iGLvmsD-eEYnrgZDYfkGWIDCzwDRRD5dwqB480talCs,10037
22
+ protein_quest-0.4.0.dist-info/METADATA,sha256=y5DAnM4mhSincjslsvQZ4zk1QcMysGmnsBltK_Vz4MQ,8842
23
+ protein_quest-0.4.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
24
+ protein_quest-0.4.0.dist-info/entry_points.txt,sha256=f1RtOxv9TFBO3w01EMEuFXBTMsqKsQcKlkxmj9zE-0g,57
25
+ protein_quest-0.4.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
26
+ protein_quest-0.4.0.dist-info/RECORD,,
@@ -1,24 +0,0 @@
1
- protein_quest/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- protein_quest/__version__.py,sha256=Bu2gp24I4eIxc1qgY2e0PnF8N-szjUpFQwVAe10IRAo,56
3
- protein_quest/cli.py,sha256=xjiWtRDqv-Ruv1fpvXq4dmDSuuyewxw81akDs1ktVbI,31772
4
- protein_quest/emdb.py,sha256=QEeU0VJQ4lLM-o5yAU3QZlrtzDZNgnC5fCjlqPtTyAY,1370
5
- protein_quest/filters.py,sha256=3vqfFH87Lz7r9uYiSvwMxzShMfRNv1Zv_freJtDljrU,4051
6
- protein_quest/go.py,sha256=ycV3-grxuIKFt28bFgH6iRKmt5AEGi7txoTbaAnBxQE,5684
7
- protein_quest/mcp_server.py,sha256=1_CGC0peqoNUFBvgFWupKwIWjmHsKxN5Vxy1K7dt5Dw,7130
8
- protein_quest/parallel.py,sha256=ZJrLO1t2HXs4EeNctytvBTyROPBq-4-gLf35PiolHf0,3468
9
- protein_quest/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
- protein_quest/taxonomy.py,sha256=wPzLjum5n_SEkL2rHUKvyRnjL1pG7bhEnE2vMmXixEc,5105
11
- protein_quest/uniprot.py,sha256=8qWV4GWqHTRfed0bE_TdgsLYcnDT_vzKu-6JxIgapJQ,18680
12
- protein_quest/utils.py,sha256=YhlTJreIr1bExbh1M514l6sz4GmLVa3RN57mI1kjjuw,4730
13
- protein_quest/alphafold/__init__.py,sha256=Ktasi5BRp71wO7-PpOGDpIRRtBEefs8knIdlKQeLQpk,51
14
- protein_quest/alphafold/confidence.py,sha256=GGd_vYsqVvs9InvFKtqHdGKB_61GHllPmDyIztvzG7E,5625
15
- protein_quest/alphafold/entry_summary.py,sha256=GtE3rT7wH3vIOOeiXY2s80Fo6EzdoqlcvakW8K591Yk,1257
16
- protein_quest/alphafold/fetch.py,sha256=1mDbQNm01cxlwFNDsKHBWD7MEwzB3PaheskdaLN7XJs,11491
17
- protein_quest/pdbe/__init__.py,sha256=eNNHtN60NAGea7gvRkIzkoTXsYPK99s-ldIcKWYO6So,61
18
- protein_quest/pdbe/fetch.py,sha256=tlCrWoaOrwxnQFrf-PnimUUa6lmtHwwysS51efYsBcA,2379
19
- protein_quest/pdbe/io.py,sha256=J6fHlRLHLALnpxDgSUUnFCNFV9Hr3u6eJDO6j81ftT4,6936
20
- protein_quest-0.3.1.dist-info/METADATA,sha256=fWvmMbm5aEMb3WbWgPAqwEOWeYJSY47iuZLaRIgBuuk,7305
21
- protein_quest-0.3.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
22
- protein_quest-0.3.1.dist-info/entry_points.txt,sha256=f1RtOxv9TFBO3w01EMEuFXBTMsqKsQcKlkxmj9zE-0g,57
23
- protein_quest-0.3.1.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
24
- protein_quest-0.3.1.dist-info/RECORD,,