Skip to content

Commit 369c465

Browse files
committed
Merge branch 'release/0.11.1'
2 parents 603cab7 + 3b3a072 commit 369c465

17 files changed

Lines changed: 184 additions & 378 deletions

CHANGELOG.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,21 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
66

77
## [Unreleased]
88

9+
## [0.11.1] - 2022-01-06
10+
11+
### Added
12+
- `quiet` flag for `genomepy.Annotation`
13+
- `genomepy -v` flag
14+
15+
### Changed
16+
- `genomepy.Annotation` returns a `FileNotFoundError` instead of a `ValueError` where appropriate.
17+
- `download_assembly_report` refactored. Now downloads the report for the exact same assembly accession (and not the nearest NCBI assembly).
18+
- broader unit tests for UCSC assembly accession scraping
19+
20+
21+
### Fixed
22+
- inconsistent behaviour with assembly reports (#193 + #194)
23+
924
## [0.11.0] - 2021-11-18
1025

1126
### Added
@@ -368,6 +383,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
368383
- Added `-r` and `--match/--no-match` option to select sequences by regex.
369384

370385
[Unreleased]: https://github.com/vanheeringen-lab/genomepy/compare/master...develop
386+
[0.11.1]: https://github.com/vanheeringen-lab/genomepy/compare/0.11.0...0.11.1
371387
[0.11.0]: https://github.com/vanheeringen-lab/genomepy/compare/0.10.0...0.11.0
372388
[0.10.0]: https://github.com/vanheeringen-lab/genomepy/compare/0.9.3...0.10.0
373389
[0.9.3]: https://github.com/vanheeringen-lab/genomepy/compare/0.9.2...0.9.3

docs/release_checklist.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44

55
1. Make sure all tests pass.
66

7-
```shell
8-
pytest -vv --disable-pytest-warnings
9-
```
7+
```shell
8+
pytest -vv --disable-pytest-warnings
9+
```
1010

1111
2. Create release candidate with `git flow`:
1212

@@ -58,13 +58,13 @@
5858

5959
```shell
6060
git flow release finish ${new_version}
61-
```
61+
```
6262

6363
7. Push everything to github, including tags:
6464

65-
```shell
66-
git push --follow-tags origin develop
67-
```
65+
```shell
66+
git push --follow-tags origin develop
67+
```
6868

6969
8. Pull into master
7070

genomepy/__about__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
"""Metadata"""
2-
__version__ = "0.11.0"
2+
__version__ = "0.11.1"
33
__author__ = "Simon van Heeringen, Siebren Frölich, Maarten van der Sande"

genomepy/annotation/__init__.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ class Annotation:
2828
Genome name/directory/fasta or gene annotation BED/GTF file.
2929
genomes_dir : str, optional
3030
Genomes installation directory.
31+
quiet : bool, optional
32+
Silence init warnings
3133
3234
Returns
3335
-------
@@ -52,7 +54,7 @@ class Annotation:
5254
annotation_contigs: list = None
5355
"Contigs found in the gene annotation BED"
5456

55-
def __init__(self, name: str, genomes_dir: str = None):
57+
def __init__(self, name: str, genomes_dir: str = None, quiet: bool = False):
5658
# name and directory
5759
n, g = _get_name_and_dir(name, genomes_dir)
5860
self.name = n
@@ -65,12 +67,12 @@ def __init__(self, name: str, genomes_dir: str = None):
6567
suffixes = Path(fname).suffixes[-2:]
6668
b = fname
6769
if not (".bed" in suffixes or ".BED" in suffixes):
68-
b = _get_file(self.genome_dir, f"{self.name}.annotation.bed")
70+
b = _get_file(self.genome_dir, f"{self.name}.annotation.bed", not quiet)
6971
self.annotation_bed_file = b
7072
"path to the gene annotation BED file"
7173
g = fname
7274
if not (".gtf" in suffixes or ".GTF" in suffixes):
73-
g = _get_file(self.genome_dir, f"{self.name}.annotation.gtf")
75+
g = _get_file(self.genome_dir, f"{self.name}.annotation.gtf", not quiet)
7476
self.annotation_gtf_file = g
7577
"path to the gene annotation GTF file"
7678

@@ -385,7 +387,7 @@ def _get_name_and_dir(name, genomes_dir=None):
385387
elif name in os.listdir(genomes_dir):
386388
genome_dir = os.path.join(genomes_dir, name)
387389
else:
388-
raise ValueError(f"Could not find {name}")
390+
raise FileNotFoundError(f"Could not find {name}")
389391
return name, genome_dir
390392

391393

genomepy/annotation/mygene.py

Lines changed: 0 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -161,55 +161,6 @@ def _parse_mygene_input(field, product=None):
161161
return field, product
162162

163163

164-
# def ensembl_genome_info(self) -> Optional[Tuple[str, str, str]]:
165-
# """
166-
# Return Ensembl genome information for this genome.
167-
#
168-
# Requires accession numbers to match (excluding patch numbers)
169-
#
170-
# Returns
171-
# -------
172-
# (str, str, str)
173-
# Ensembl name, accession, taxonomy_id
174-
# """
175-
# _check_property(self.readme_file, "README.txt")
176-
#
177-
# metadata, _ = read_readme(self.readme_file)
178-
# if metadata.get("provider") == "Ensembl":
179-
# return metadata["name"], metadata["assembly_accession"], metadata["tax_id"]
180-
#
181-
# if metadata.get("assembly_accession", "na") == "na":
182-
# logger.warning("Cannot find a matching genome without an assembly accession.")
183-
# return
184-
#
185-
# asm_acc = metadata["assembly_accession"]
186-
# search_result = nearest_assembly(asm_acc, "Ensembl")
187-
# if search_result is None:
188-
# return
189-
#
190-
# return safe(search_result[0]), search_result[2], search_result[3]
191-
#
192-
#
193-
# def _query_mygene(
194-
# self,
195-
# query: Iterable[str],
196-
# field: str = "genomic_pos",
197-
# ) -> pd.DataFrame:
198-
# # mygene.info only queries the most recent version of the Ensembl database
199-
# # We can only safely continue if the local genome matched the Ensembl genome.
200-
# # Even if the local genome was installed via Ensembl, we still need to check
201-
# # if it is the same version
202-
# ensembl_info = ensembl_genome_info(self)
203-
# if ensembl_info is None:
204-
# return pd.DataFrame()
205-
#
206-
# tax_id = ensembl_info[2]
207-
# if not str(tax_id).isdigit():
208-
# raise ValueError("No taxomoy ID found")
209-
#
210-
# return query_mygene(query, tax_id, field)
211-
212-
213164
def _filter_query(query: pd.DataFrame) -> pd.DataFrame:
214165
"""per queried gene, keep the best matching, non-NaN, mapping"""
215166
if "notfound" in query:

genomepy/cli.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717

1818
@click.group(context_settings=CONTEXT_SETTINGS)
19-
@click.version_option(genomepy.__version__)
19+
@click.version_option(genomepy.__version__, "-v", "--version")
2020
def cli():
2121
pass # noqa
2222

genomepy/providers/__init__.py

Lines changed: 3 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
11
"""Provider class, modules & related functions"""
22
import os
3-
from typing import List, Optional
3+
from typing import Optional
44

55
import pandas as pd
66
from loguru import logger
77

88
from genomepy.files import read_readme
9-
from genomepy.providers.base import ASM_FORMAT
109
from genomepy.providers.ensembl import EnsemblProvider
1110
from genomepy.providers.gencode import GencodeProvider
1211
from genomepy.providers.local import LocalProvider
13-
from genomepy.providers.ncbi import NcbiProvider
12+
from genomepy.providers.ncbi import NcbiProvider, download_assembly_report
1413
from genomepy.providers.ucsc import UcscProvider
1514
from genomepy.providers.url import UrlProvider
1615
from genomepy.utils import get_genomes_dir, safe
@@ -22,7 +21,6 @@
2221
"search",
2322
"map_locations",
2423
"download_assembly_report",
25-
"nearest_assembly",
2624
"online_providers",
2725
]
2826

@@ -134,106 +132,6 @@ def search(term, provider: str = None):
134132
yield ret
135133

136134

137-
def _closest_patch_lvl(reference, targets):
138-
ref_patch = int(reference.split(".")[1]) if "." in reference else 0
139-
nearest = [999, []]
140-
for target in targets:
141-
tgt_patch = int(target.split(".")[1]) if "." in target else 0
142-
distance = abs(tgt_patch + 0.1 - ref_patch) # tiebreaker: newer > older patches
143-
if distance == nearest[0]:
144-
nearest[1].append(target)
145-
if distance < nearest[0]:
146-
nearest = [distance, [target]]
147-
return nearest[1]
148-
149-
150-
def _best_accession(reference: str, targets: list):
151-
"""Return the nearest accession ID from a list of IDs"""
152-
if len(targets) == 1:
153-
return targets[0]
154-
155-
# GCA/GCF
156-
matching_prefix = [t for t in targets if t.startswith(reference[0:3])]
157-
if len(matching_prefix) > 0:
158-
targets = matching_prefix
159-
160-
# patch levels
161-
# e.g. GCA_000002035.4 & GCA_000002035.3
162-
targets = _closest_patch_lvl(reference, targets)
163-
164-
if len(targets) > 1:
165-
logger.info(
166-
f"Multiple matching accession numbers found. Selecting the closest ({targets[0]})."
167-
)
168-
return targets[0]
169-
170-
171-
def _best_search_result(asm_acc: str, results: List[list]) -> list:
172-
"""Return the best search result based on accession IDs."""
173-
results = [res for res in results if res[2] is not None]
174-
175-
if len(results) > 1:
176-
accessions = [res[2] for res in results]
177-
bes_acc = _best_accession(asm_acc, accessions)
178-
results = [res for res in results if res[2] == bes_acc]
179-
180-
if len(results) > 0:
181-
return results[0]
182-
183-
184-
def nearest_assembly(asm_acc: str, provider: str) -> list:
185-
"""
186-
Return the search result of the assembly nearest to
187-
the given accession ID, in the specified provider.
188-
"""
189-
if not asm_acc.startswith(("GCA_", "GCF_")):
190-
raise ValueError("asm_acc must be an assembly accession ID.")
191-
192-
search_results = list(search(asm_acc, provider=provider))
193-
best_result = _best_search_result(asm_acc, search_results)
194-
if best_result is None:
195-
logger.warning(f"No assembly similar to {asm_acc} on {provider}.")
196-
return best_result
197-
198-
199-
def download_assembly_report(asm_acc: str, fname: str = None):
200-
"""
201-
Retrieve the NCBI assembly report.
202-
203-
Returns the assembly_report as a pandas DataFrame if fname is not specified.
204-
205-
Parameters
206-
----------
207-
asm_acc : str
208-
Assembly accession (GCA or GCF)
209-
fname : str, optional
210-
Save assembly_report to this filename.
211-
212-
Returns
213-
-------
214-
pandas.DataFrame
215-
NCBI assembly report.
216-
"""
217-
search_result = nearest_assembly(asm_acc, "NCBI")
218-
if search_result is None:
219-
return
220-
ncbi_acc = search_result[2]
221-
ncbi_name = search_result[0]
222-
223-
# NCBI FTP location of assembly report
224-
assembly_report = (
225-
f"https://ftp.ncbi.nlm.nih.gov/genomes/all/{ncbi_acc[0:3]}/"
226-
+ f"{ncbi_acc[4:7]}/{ncbi_acc[7:10]}/{ncbi_acc[10:13]}/"
227-
+ f"{ncbi_acc}_{ncbi_name}/{ncbi_acc}_{ncbi_name}_assembly_report.txt"
228-
)
229-
asm_report = pd.read_csv(assembly_report, sep="\t", comment="#", names=ASM_FORMAT)
230-
231-
if fname:
232-
asm_report.to_csv(fname, sep="\t", index=False)
233-
else:
234-
return asm_report
235-
236-
237135
def map_locations(
238136
frm: str, to: str, genomes_dir: Optional[str] = None
239137
) -> Optional[pd.DataFrame]:
@@ -279,7 +177,7 @@ def map_locations(
279177
logger.warning("Cannot map without an assembly report.")
280178
return
281179

282-
asm_report = pd.read_csv(frm_asm_report, sep="\t", comment="#")
180+
asm_report = pd.read_csv(frm_asm_report, sep="\t", comment="#", dtype=str)
283181
asm_report["ensembl_name"] = asm_report["Sequence-Name"]
284182
asm_report["ncbi_name"] = asm_report["Sequence-Name"]
285183
asm_report["ucsc_name"] = asm_report["UCSC-style-name"]

genomepy/providers/base.py

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,6 @@
1818
from genomepy.online import download_file
1919
from genomepy.utils import get_genomes_dir, get_localname, lower, mkdir_p, rm_rf, safe
2020

21-
ASM_FORMAT = [
22-
"Sequence-Name",
23-
"Sequence-Role",
24-
"Assigned-Molecule",
25-
"Assigned-Molecule-Location/Type",
26-
"GenBank-Accn",
27-
"Relationship",
28-
"RefSeq-Accn",
29-
"Assembly-Unit",
30-
"Sequence-Length",
31-
"UCSC-style-name",
32-
]
33-
3421

3522
class BaseProvider:
3623
"""
@@ -111,7 +98,7 @@ def genome_taxid(self, name: str) -> int:
11198
if tid.isdigit():
11299
return int(tid)
113100

114-
def assembly_accession(self, name: str) -> str:
101+
def assembly_accession(self, name: str) -> str or None:
115102
"""
116103
Return the assembly accession number (GCA* or GCF*) for a genome.
117104
@@ -523,7 +510,11 @@ def rename_contigs(annot_file):
523510
genome_dir = os.path.dirname(os.path.dirname(annot_file))
524511
asm_report = os.path.join(genome_dir, "assembly_report.txt")
525512
gencode2ucsc = pd.read_csv(
526-
asm_report, sep="\t", comment="#", usecols=["GenBank-Accn", "UCSC-style-name"]
513+
asm_report,
514+
sep="\t",
515+
comment="#",
516+
usecols=["GenBank-Accn", "UCSC-style-name"],
517+
dtype=str,
527518
)
528519
gtf = read_annot(annot_file)
529520

0 commit comments

Comments
 (0)