cdhit-reader 0.2.0__tar.gz → 0.5.0__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.
Files changed (38) hide show
  1. {cdhit-reader-0.2.0/cdhit_reader.egg-info → cdhit_reader-0.5.0}/PKG-INFO +29 -3
  2. {cdhit-reader-0.2.0 → cdhit_reader-0.5.0}/README.md +18 -0
  3. cdhit_reader-0.5.0/cdhit_reader/__init__.py +42 -0
  4. {cdhit-reader-0.2.0 → cdhit_reader-0.5.0}/cdhit_reader/_cli.py +15 -16
  5. {cdhit-reader-0.2.0 → cdhit_reader-0.5.0}/cdhit_reader/_compare.py +48 -42
  6. cdhit_reader-0.5.0/cdhit_reader/_compare_otus.py +332 -0
  7. {cdhit-reader-0.2.0 → cdhit_reader-0.5.0}/cdhit_reader/_fasta.py +60 -33
  8. {cdhit-reader-0.2.0 → cdhit_reader-0.5.0}/cdhit_reader/_reader.py +95 -166
  9. {cdhit-reader-0.2.0 → cdhit_reader-0.5.0}/cdhit_reader/_version.py +1 -1
  10. cdhit_reader-0.5.0/cdhit_reader/test/test_clstr.py +179 -0
  11. cdhit_reader-0.5.0/cdhit_reader/test/test_clustering.py +66 -0
  12. cdhit_reader-0.5.0/cdhit_reader/test/test_compare.py +10 -0
  13. cdhit_reader-0.5.0/cdhit_reader/test/test_compare_otus.py +130 -0
  14. cdhit_reader-0.5.0/cdhit_reader/test/test_examples.py +44 -0
  15. cdhit_reader-0.5.0/cdhit_reader/test/test_fasta.py +58 -0
  16. cdhit_reader-0.5.0/cdhit_reader/test/test_integration.py +30 -0
  17. cdhit_reader-0.5.0/cdhit_reader/test/test_package_import.py +55 -0
  18. {cdhit-reader-0.2.0 → cdhit_reader-0.5.0/cdhit_reader.egg-info}/PKG-INFO +29 -3
  19. {cdhit-reader-0.2.0 → cdhit_reader-0.5.0}/cdhit_reader.egg-info/SOURCES.txt +9 -1
  20. cdhit_reader-0.5.0/cdhit_reader.egg-info/entry_points.txt +4 -0
  21. cdhit_reader-0.5.0/setup.py +9 -0
  22. cdhit-reader-0.2.0/cdhit_reader/__init__.py +0 -26
  23. cdhit-reader-0.2.0/cdhit_reader/test/test_clstr.py +0 -88
  24. cdhit-reader-0.2.0/cdhit_reader.egg-info/entry_points.txt +0 -3
  25. cdhit-reader-0.2.0/setup.py +0 -5
  26. {cdhit-reader-0.2.0 → cdhit_reader-0.5.0}/LICENSE.md +0 -0
  27. {cdhit-reader-0.2.0 → cdhit_reader-0.5.0}/MANIFEST.in +0 -0
  28. {cdhit-reader-0.2.0 → cdhit_reader-0.5.0}/cdhit_reader/_testit.py +0 -0
  29. {cdhit-reader-0.2.0 → cdhit_reader-0.5.0}/cdhit_reader/test/__init__.py +0 -0
  30. {cdhit-reader-0.2.0 → cdhit_reader-0.5.0}/cdhit_reader/test/aa.clstr +0 -0
  31. {cdhit-reader-0.2.0 → cdhit_reader-0.5.0}/cdhit_reader/test/nt.clstr +0 -0
  32. {cdhit-reader-0.2.0 → cdhit_reader-0.5.0}/cdhit_reader.egg-info/dependency_links.txt +0 -0
  33. {cdhit-reader-0.2.0 → cdhit_reader-0.5.0}/cdhit_reader.egg-info/requires.txt +0 -0
  34. {cdhit-reader-0.2.0 → cdhit_reader-0.5.0}/cdhit_reader.egg-info/top_level.txt +0 -0
  35. {cdhit-reader-0.2.0 → cdhit_reader-0.5.0}/cdhit_reader.egg-info/zip-safe +0 -0
  36. {cdhit-reader-0.2.0 → cdhit_reader-0.5.0}/pyproject.toml +0 -0
  37. {cdhit-reader-0.2.0 → cdhit_reader-0.5.0}/setup.cfg +0 -0
  38. {cdhit-reader-0.2.0 → cdhit_reader-0.5.0}/version.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: cdhit-reader
3
- Version: 0.2.0
3
+ Version: 0.5.0
4
4
  Summary: CD-HIT cluster parser
5
5
  Home-page: https://github.com/telatin/cdhit-parser
6
6
  Download-URL: https://github.com/telatin/cdhit-parser
@@ -18,8 +18,16 @@ Classifier: License :: OSI Approved :: MIT License
18
18
  Classifier: Operating System :: OS Independent
19
19
  Classifier: Programming Language :: Python
20
20
  Description-Content-Type: text/markdown
21
- Provides-Extra: cli
22
21
  License-File: LICENSE.md
22
+ Requires-Dist: click>=7.1.2
23
+ Requires-Dist: importlib-resources>=1.4.0
24
+ Requires-Dist: more-itertools>=8.4.0
25
+ Requires-Dist: pytest>=5.4.3
26
+ Requires-Dist: xopen>=1.0.1
27
+ Provides-Extra: cli
28
+ Requires-Dist: plotille; extra == "cli"
29
+ Dynamic: download-url
30
+ Dynamic: license-file
23
31
 
24
32
  # cdhit-parser
25
33
 
@@ -52,6 +60,24 @@ Load all clusters in to a list:
52
60
  clusters = read_cdhit(input).read_items()
53
61
  ```
54
62
 
63
+ ### Sequence to cluster lookup
64
+
65
+ `Clustering` loads a whole .clstr file and provides a reverse index
66
+ (`seqcluster`) mapping each sequence name to the name of its cluster:
67
+
68
+ ```python
69
+ from cdhit_reader import Clustering
70
+
71
+ clustering = Clustering.from_file(input)
72
+
73
+ print(len(clustering)) # number of clusters
74
+ for cluster in clustering: # iterate over the clusters
75
+ print(cluster.name)
76
+
77
+ # Which cluster does a sequence belong to?
78
+ print(clustering.seqcluster["seq1.A"]) # e.g. "Cluster 0"
79
+ ```
80
+
55
81
  ## Read FASTA file
56
82
 
57
83
  ```python
@@ -29,6 +29,24 @@ Load all clusters in to a list:
29
29
  clusters = read_cdhit(input).read_items()
30
30
  ```
31
31
 
32
+ ### Sequence to cluster lookup
33
+
34
+ `Clustering` loads a whole .clstr file and provides a reverse index
35
+ (`seqcluster`) mapping each sequence name to the name of its cluster:
36
+
37
+ ```python
38
+ from cdhit_reader import Clustering
39
+
40
+ clustering = Clustering.from_file(input)
41
+
42
+ print(len(clustering)) # number of clusters
43
+ for cluster in clustering: # iterate over the clusters
44
+ print(cluster.name)
45
+
46
+ # Which cluster does a sequence belong to?
47
+ print(clustering.seqcluster["seq1.A"]) # e.g. "Cluster 0"
48
+ ```
49
+
32
50
  ## Read FASTA file
33
51
 
34
52
  ```python
@@ -0,0 +1,42 @@
1
+ from importlib import import_module
2
+
3
+ from ._reader import ParsingError, ClusterSequence, Cluster, Clustering, ClstrReader, read_cdhit, SeqType, Strand
4
+ from ._fasta import FastaParsingError, Sequence, FastaReader, read_fasta
5
+ from ._version import __version__
6
+
7
+ _LAZY_EXPORTS = {
8
+ "cli": ("._cli", "cli"),
9
+ "compare": ("._compare", "compare"),
10
+ "test": ("._testit", "test"),
11
+ }
12
+
13
+ __all__ = [
14
+ "ParsingError",
15
+ "FastaParsingError",
16
+ "ClusterSequence",
17
+ "Cluster",
18
+ "Clustering",
19
+ "ClstrReader",
20
+ "read_cdhit",
21
+ "FastaReader",
22
+ "read_fasta",
23
+ "SeqType",
24
+ "Strand",
25
+ "Sequence",
26
+ "__version__",
27
+ "cli",
28
+ "compare",
29
+ "test",
30
+ ]
31
+
32
+
33
+ def __getattr__(name):
34
+ if name in _LAZY_EXPORTS:
35
+ module_name, attribute_name = _LAZY_EXPORTS[name]
36
+ module = import_module(module_name, __name__)
37
+ return getattr(module, attribute_name)
38
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
39
+
40
+
41
+ def __dir__():
42
+ return sorted(set(globals()) | set(__all__))
@@ -1,12 +1,9 @@
1
-
2
- import sys
3
1
  from statistics import mean
4
2
 
5
3
  import click
6
4
 
7
5
  from ._reader import read_cdhit
8
6
  from ._version import __version__
9
- #from ._writer import write_fasta
10
7
 
11
8
 
12
9
  @click.command(context_settings=dict(help_option_names=["-h", "--help"]))
@@ -22,31 +19,33 @@ def cli(clstr, stats: bool, hist: bool, all: bool):
22
19
  \b
23
20
  Warning
24
21
  -------
25
- The commad line interface is in EXPERIMENTAL stage.
22
+ The commad line interface is in EXPERIMENTAL stage.
26
23
  """
27
24
 
28
25
  nitems = 0
29
26
  nseqs = 0
30
27
  seq_lens = []
31
- for item in read_cdhit(clstr):
32
- seq_lens.append(len(item))
33
- nitems += 1
34
- nseqs += len(item)
35
- if all:
36
- print(item)
37
- for s in item.sequences:
38
- print(f" {s}")
28
+ with read_cdhit(clstr) as reader:
29
+ for item in reader:
30
+ seq_lens.append(len(item))
31
+ nitems += 1
32
+ nseqs += len(item)
33
+ if all:
34
+ print(item)
35
+ for s in item.sequences:
36
+ print(f" {s}")
39
37
  if stats:
40
38
  click.echo(f"Input file: {clstr}")
41
39
  click.echo(f"Number of clusters: {nitems}")
42
40
  click.echo(f"Total sequences: {nseqs}")
43
-
44
- msg = f"Cluster size: min {min(seq_lens)}, mean {mean(seq_lens):.2f}, max {max(seq_lens)}"
45
- click.echo(msg)
41
+
42
+ if seq_lens:
43
+ msg = f"Cluster size: min {min(seq_lens)}, mean {mean(seq_lens):.2f}, max {max(seq_lens)}"
44
+ click.echo(msg)
46
45
 
47
46
  if hist:
48
47
  show_hist(seq_lens)
49
48
 
50
49
 
51
50
  def show_hist(seq_lens):
52
- pass
51
+ pass
@@ -1,26 +1,35 @@
1
- from email.policy import default
2
1
  import sys
3
- from statistics import mean
2
+ import os
3
+ import subprocess
4
+ import tempfile
4
5
 
5
6
  import click
7
+ from xopen import xopen
6
8
 
7
9
  from ._reader import read_cdhit
8
10
  from ._version import __version__
9
- from xopen import xopen
10
- import os
11
- import tempfile
12
- import subprocess
11
+
12
+ TAG1 = "1:::"
13
+ TAG2 = "2:::"
14
+
13
15
 
14
16
  def has_cdhit():
15
17
  cmd = ["cd-hit", "-h"]
16
- # Run cmd and save stdout as "cdout" variable, ignore exit status
18
+ # Run cmd and inspect stdout, ignore exit status
17
19
  try:
18
20
  cdout = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE).stdout.decode("utf-8")
19
- if "CD-HIT" in cdout:
20
- return True
21
+ return "CD-HIT" in cdout
21
22
  except Exception:
22
23
  return False
23
-
24
+
25
+
26
+ def strip_tag(name: str) -> str:
27
+ """
28
+ Remove the relabeling prefix (TAG1/TAG2, same length) from a sequence name.
29
+ """
30
+ return name[len(TAG1):]
31
+
32
+
24
33
  def read_fasta(path):
25
34
  name = None
26
35
  comment = ""
@@ -38,11 +47,13 @@ def read_fasta(path):
38
47
  seq += line
39
48
  yield name, comment, seq
40
49
 
50
+
41
51
  def relabel_fasta(path, prefix, outpath):
42
52
  with xopen(outpath, "a") as out:
43
53
  for name, comment, seq in read_fasta(path):
44
54
  out.write(">{}{}{}{}\n{}\n".format(prefix, name, " " if len(comment) > 1 else "", comment, seq))
45
55
 
56
+
46
57
  def split_cluster(cluster, tag1, tag2):
47
58
  pool1, pool2 = [], []
48
59
  for sequence in cluster.sequences:
@@ -52,17 +63,19 @@ def split_cluster(cluster, tag1, tag2):
52
63
  pool2.append(sequence)
53
64
  else:
54
65
  raise ValueError("Sequence {} does not start with {} or {}".format(sequence.name, tag1, tag2))
66
+
67
+
55
68
  @click.command(context_settings=dict(help_option_names=["-h", "--help"]))
56
69
  @click.version_option(__version__)
57
70
  @click.argument("fasta1", type=click.Path(exists=True))
58
71
  @click.argument("fasta2", type=click.Path(exists=True))
59
- @click.option("--tag1", help="Name of the first dataset")
60
- @click.option("--tag2", help="Name of the second dataset")
72
+ @click.option("--tag1", help="Name of the first dataset")
73
+ @click.option("--tag2", help="Name of the second dataset")
61
74
  @click.option("--id", help="Identity threshold [default: 0.9]", default=0.95, type=float)
62
75
  @click.option("--type", type=click.STRING, help="Type of the sequences (nucl or prot)")
63
- @click.option("--tempdir",type=click.Path(exists=True), help="Temporary directory for intermediate files", default=tempfile.gettempdir())
76
+ @click.option("--tempdir", type=click.Path(exists=True), help="Temporary directory for intermediate files", default=tempfile.gettempdir())
64
77
  @click.option("--verbose", default=False, is_flag=True, help="Show verbose information")
65
- def compare(fasta1, fasta2, tag1: str, tag2: str, tempdir, type: str, id: float,verbose: bool):
78
+ def compare(fasta1, fasta2, tag1: str, tag2: str, tempdir, type: str, id: float, verbose: bool):
66
79
  """
67
80
  Compare FASTA files
68
81
 
@@ -75,12 +88,9 @@ def compare(fasta1, fasta2, tag1: str, tag2: str, tempdir, type: str, id: float,
75
88
  click.echo("cd-hit is not installed. Please install it and try again.")
76
89
  sys.exit(1)
77
90
  # Generate temporary directory inside "tempdir"
78
- TAG1 = "1:::"
79
- TAG2 = "2:::"
80
91
  # tmp is a temporary directory not to be deleted
81
92
  tmp = tempfile.mkdtemp(dir=tempdir, prefix="cdhit_")
82
93
 
83
- #tmp = tempfile.TemporaryDirectory(dir=tempdir, prefix="cdhit_reader_", suffix=".tmp")
84
94
  if verbose:
85
95
  print("Temporary directory: {}".format(tmp), file=sys.stderr)
86
96
 
@@ -97,42 +107,41 @@ def compare(fasta1, fasta2, tag1: str, tag2: str, tempdir, type: str, id: float,
97
107
  if prefix1 == prefix2:
98
108
  print("Warning: prefixes are identical ({}): specify manual prefixes with --tag1 and --tag2".format(prefix1))
99
109
  exit(1)
100
-
110
+
101
111
  # Delete fasta_file if present:
102
112
  if os.path.exists(fasta_file):
103
113
  os.remove(fasta_file)
104
-
114
+
105
115
  if verbose:
106
116
  print("Relabeling {} to {}".format(fasta1, fasta_file), file=sys.stderr)
107
-
117
+
108
118
  relabel_fasta(fasta1, TAG1, fasta_file)
109
119
 
110
120
  if verbose:
111
121
  print("Relabeling {} to {}".format(fasta2, fasta_file), file=sys.stderr)
112
-
122
+
113
123
  relabel_fasta(fasta2, TAG2, fasta_file)
114
124
 
115
125
  tags = {
116
126
  TAG1: prefix1,
117
127
  TAG2: prefix2
118
- }
128
+ }
119
129
  if verbose:
120
130
  print("Relabeling {} to {} (prefix: {})".format(fasta1, fasta_file, prefix1), file=sys.stderr)
121
131
  print("Relabeling {} to {} (prefix: {})".format(fasta2, fasta_file, prefix2), file=sys.stderr)
122
-
123
- cmd = ["cd-hit" if type=="prot" else "cd-hit-est", "-i", fasta_file, "-o", clstr_file, "-c", str(id), "-d", "1000"]
132
+
133
+ cmd = ["cd-hit" if type == "prot" else "cd-hit-est", "-i", fasta_file, "-o", clstr_file, "-c", str(id), "-d", "1000"]
124
134
  if verbose:
125
135
  print("Running {}".format(" ".join(cmd)), file=sys.stderr)
126
136
  subprocess.run(cmd, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
127
137
 
128
-
129
- stats = {TAG1: [], TAG2: [], "both": [], "multi": [], "dupl_" + TAG1: [], "dupl_" + TAG2: []}
138
+ stats = {TAG1: [], TAG2: [], "both": [], "multi": [], "dupl_" + TAG1: [], "dupl_" + TAG2: []}
130
139
  for cluster in read_cdhit(clstr_file + ".clstr"):
131
-
132
- pool = (cluster.refname)[0:len(TAG1)]
140
+
141
+ pool = cluster.refname[:len(TAG1)]
133
142
  if pool != TAG1 and pool != TAG2:
134
143
  raise ValueError("Cluster {} does not start with {} or {}".format(cluster.refname, TAG1, TAG2))
135
- seqname = cluster.refname[len(pool) + 1:]
144
+ seqname = strip_tag(cluster.refname)
136
145
  if len(cluster) == 1:
137
146
  # Singleton
138
147
  stats[pool].append(seqname)
@@ -141,31 +150,28 @@ def compare(fasta1, fasta2, tag1: str, tag2: str, tempdir, type: str, id: float,
141
150
  pair = []
142
151
  check = False
143
152
  for seq in cluster.sequences:
144
- sub_pool = (seq.name)[0:len(TAG1)]
145
- sub_seqname = (seq.name)[len(pool) + 1:]
153
+ sub_pool = seq.name[:len(TAG1)]
154
+ sub_seqname = strip_tag(seq.name)
146
155
  pair.append(sub_seqname)
147
156
  if sub_pool != pool:
148
157
  check = True
149
- if check == True:
158
+ if check:
150
159
  stats["both"].append(":".join(pair))
151
160
  else:
152
- stats["dupl_" + pool ].append(":".join(pair))
161
+ stats["dupl_" + pool].append(":".join(pair))
153
162
  else:
154
-
163
+
155
164
  stats["multi"].append(",".join(i.name for i in cluster.sequences))
156
-
157
165
 
158
- for key, list in stats.items():
159
- print("{} {}".format(key, len(list)), file=sys.stderr)
160
- for seqnames in list:
166
+ for key, seqnames_list in stats.items():
167
+ print("{} {}".format(key, len(seqnames_list)), file=sys.stderr)
168
+ for seqnames in seqnames_list:
161
169
  key = tags[key] if key in tags else key
162
- key = "dupl_" + tags[key[-1*len(TAG1):]] if key[-1*len(TAG1):] in tags else key
170
+ key = "dupl_" + tags[key[-1 * len(TAG1):]] if key[-1 * len(TAG1):] in tags else key
163
171
  seqnames = seqnames.replace(TAG1, tags[TAG1] + "#").replace(TAG2, tags[TAG2] + "#")
164
-
172
+
165
173
  print(key, seqnames, sep="\t")
166
174
 
167
- #if not verbose:
168
- # os.remove(tmp)
169
175
 
170
176
  def show_hist(seq_lens):
171
177
  pass