Repository: cogent3/cogent3
Pull request #1900 (2024-06-17 04:37:09)

DEV: `SeqViewABC` and methods for translation on `SequenceCollection`, #1901
in this PR: 

#### NEW
- add `SeqViewABC`
- `SeqView` inherits from `SeqViewABC` and now requires `alphabet` as init arg

#### DEV:
- REMOVED `replace` method from `SeqView` -- wont add back unless things break 
- methods for `translation` (pending refactoring of `Sequence.translate`)
- methods for `reverse_complement`
- increased test coverage 
- refactor targets

coveralls: ## Pull Request Test Coverage Report for [Build 9542027750](https://coveralls.io/builds/68115602)


### Details

* **26** of **35**   **(74.29%)**  changed or added relevant lines in **2** files are covered.
* **34** unchanged lines in **2** files lost coverage.
* Overall coverage increased (+**0.2%**) to **91.166%**

---

|  Changes Missing Coverage | Covered Lines | Changed/Added Lines | % |
| :-----|--------------|--------|---: |
| [src/cogent3/core/new_alignment.py](https://coveralls.io/builds/68115602/source?filename=src%2Fcogent3%2Fcore%2Fnew_alignment.py#L706) | 25 | 34 | 73.53%
<!-- | **Total:** | **26** | **35** | **74.29%** | -->

|  Files with Coverage Reduction | New Missed Lines | % |
| :-----|--------------|--: |
| [src/cogent3/core/new_sequence.py](https://coveralls.io/builds/68115602/source?filename=src%2Fcogent3%2Fcore%2Fnew_sequence.py#L1535) | 3 | 62.05% |
| [src/cogent3/core/new_alignment.py](https://coveralls.io/builds/68115602/source?filename=src%2Fcogent3%2Fcore%2Fnew_alignment.py#L810) | 31 | 92.57% |
<!-- | **Total:** | **34** |  | -->

|  Totals | [![Coverage Status](https://coveralls.io/builds/68115602/badge)](https://coveralls.io/builds/68115602) |
| :-- | --: |
| Change from base [Build 9540771280](https://coveralls.io/builds/68114056): |  0.2% |
| Covered Lines: | 32313 |
| Relevant Lines: | 35444 |

---
##### 💛  - [Coveralls](https://coveralls.io)

coveralls: ## Pull Request Test Coverage Report for [Build 9542068155](https://coveralls.io/builds/68115657)


### Details

* **24** of **32**   **(75.0%)**  changed or added relevant lines in **2** files are covered.
* **3** unchanged lines in **1** file lost coverage.
* Overall coverage increased (+**0.2%**) to **91.166%**

---

|  Changes Missing Coverage | Covered Lines | Changed/Added Lines | % |
| :-----|--------------|--------|---: |
| [src/cogent3/core/new_alignment.py](https://coveralls.io/builds/68115657/source?filename=src%2Fcogent3%2Fcore%2Fnew_alignment.py#L706) | 23 | 31 | 74.19%
<!-- | **Total:** | **24** | **32** | **75.0%** | -->

|  Files with Coverage Reduction | New Missed Lines | % |
| :-----|--------------|--: |
| [src/cogent3/core/new_sequence.py](https://coveralls.io/builds/68115657/source?filename=src%2Fcogent3%2Fcore%2Fnew_sequence.py#L1535) | 3 | 62.05% |
<!-- | **Total:** | **3** |  | -->

|  Totals | [![Coverage Status](https://coveralls.io/builds/68115657/badge)](https://coveralls.io/builds/68115657) |
| :-- | --: |
| Change from base [Build 9540771280](https://coveralls.io/builds/68114056): |  0.2% |
| Covered Lines: | 32313 |
| Relevant Lines: | 35444 |

---
##### 💛  - [Coveralls](https://coveralls.io)

coveralls: ## Pull Request Test Coverage Report for [Build 9558928181](https://coveralls.io/builds/68142151)


### Details

* **24** of **32**   **(75.0%)**  changed or added relevant lines in **2** files are covered.
* **3** unchanged lines in **1** file lost coverage.
* Overall coverage increased (+**0.2%**) to **91.166%**

---

|  Changes Missing Coverage | Covered Lines | Changed/Added Lines | % |
| :-----|--------------|--------|---: |
| [src/cogent3/core/new_alignment.py](https://coveralls.io/builds/68142151/source?filename=src%2Fcogent3%2Fcore%2Fnew_alignment.py#L715) | 23 | 31 | 74.19%
<!-- | **Total:** | **24** | **32** | **75.0%** | -->

|  Files with Coverage Reduction | New Missed Lines | % |
| :-----|--------------|--: |
| [src/cogent3/core/new_sequence.py](https://coveralls.io/builds/68142151/source?filename=src%2Fcogent3%2Fcore%2Fnew_sequence.py#L1535) | 3 | 62.05% |
<!-- | **Total:** | **3** |  | -->

|  Totals | [![Coverage Status](https://coveralls.io/builds/68142151/badge)](https://coveralls.io/builds/68142151) |
| :-- | --: |
| Change from base [Build 9540771280](https://coveralls.io/builds/68114056): |  0.2% |
| Covered Lines: | 32313 |
| Relevant Lines: | 35444 |

---
##### 💛  - [Coveralls](https://coveralls.io)

coveralls: ## Pull Request Test Coverage Report for [Build 9559651271](https://coveralls.io/builds/68143057)


### Details

* **24** of **32**   **(75.0%)**  changed or added relevant lines in **2** files are covered.
* **3** unchanged lines in **1** file lost coverage.
* Overall coverage increased (+**0.2%**) to **91.166%**

---

|  Changes Missing Coverage | Covered Lines | Changed/Added Lines | % |
| :-----|--------------|--------|---: |
| [src/cogent3/core/new_alignment.py](https://coveralls.io/builds/68143057/source?filename=src%2Fcogent3%2Fcore%2Fnew_alignment.py#L715) | 23 | 31 | 74.19%
<!-- | **Total:** | **24** | **32** | **75.0%** | -->

|  Files with Coverage Reduction | New Missed Lines | % |
| :-----|--------------|--: |
| [src/cogent3/core/new_sequence.py](https://coveralls.io/builds/68143057/source?filename=src%2Fcogent3%2Fcore%2Fnew_sequence.py#L1535) | 3 | 62.05% |
<!-- | **Total:** | **3** |  | -->

|  Totals | [![Coverage Status](https://coveralls.io/builds/68143057/badge)](https://coveralls.io/builds/68143057) |
| :-- | --: |
| Change from base [Build 9540771280](https://coveralls.io/builds/68114056): |  0.2% |
| Covered Lines: | 32313 |
| Relevant Lines: | 35444 |

---
##### 💛  - [Coveralls](https://coveralls.io)

coveralls: ## Pull Request Test Coverage Report for [Build 9573583615](https://coveralls.io/builds/68165821)


### Details

* **79** of **97**   **(81.44%)**  changed or added relevant lines in **3** files are covered.
* **3** unchanged lines in **1** file lost coverage.
* Overall coverage increased (+**0.2%**) to **91.112%**

---

|  Changes Missing Coverage | Covered Lines | Changed/Added Lines | % |
| :-----|--------------|--------|---: |
| [src/cogent3/core/new_alignment.py](https://coveralls.io/builds/68165821/source?filename=src%2Fcogent3%2Fcore%2Fnew_alignment.py#L704) | 24 | 32 | 75.0%
| [src/cogent3/core/new_sequence.py](https://coveralls.io/builds/68165821/source?filename=src%2Fcogent3%2Fcore%2Fnew_sequence.py#L1089) | 54 | 64 | 84.38%
<!-- | **Total:** | **79** | **97** | **81.44%** | -->

|  Files with Coverage Reduction | New Missed Lines | % |
| :-----|--------------|--: |
| [src/cogent3/core/new_sequence.py](https://coveralls.io/builds/68165821/source?filename=src%2Fcogent3%2Fcore%2Fnew_sequence.py#L1540) | 3 | 60.93% |
<!-- | **Total:** | **3** |  | -->

|  Totals | [![Coverage Status](https://coveralls.io/builds/68165821/badge)](https://coveralls.io/builds/68165821) |
| :-- | --: |
| Change from base [Build 9540771280](https://coveralls.io/builds/68114056): |  0.2% |
| Covered Lines: | 32310 |
| Relevant Lines: | 35462 |

---
##### 💛  - [Coveralls](https://coveralls.io)

coveralls: ## Pull Request Test Coverage Report for [Build 9574166082](https://coveralls.io/builds/68166697)


### Details

* **88** of **106**   **(83.02%)**  changed or added relevant lines in **3** files are covered.
* **3** unchanged lines in **1** file lost coverage.
* Overall coverage increased (+**0.2%**) to **91.112%**

---

|  Changes Missing Coverage | Covered Lines | Changed/Added Lines | % |
| :-----|--------------|--------|---: |
| [src/cogent3/core/new_alignment.py](https://coveralls.io/builds/68166697/source?filename=src%2Fcogent3%2Fcore%2Fnew_alignment.py#L707) | 33 | 41 | 80.49%
| [src/cogent3/core/new_sequence.py](https://coveralls.io/builds/68166697/source?filename=src%2Fcogent3%2Fcore%2Fnew_sequence.py#L1089) | 54 | 64 | 84.38%
<!-- | **Total:** | **88** | **106** | **83.02%** | -->

|  Files with Coverage Reduction | New Missed Lines | % |
| :-----|--------------|--: |
| [src/cogent3/core/new_sequence.py](https://coveralls.io/builds/68166697/source?filename=src%2Fcogent3%2Fcore%2Fnew_sequence.py#L1540) | 3 | 60.93% |
<!-- | **Total:** | **3** |  | -->

|  Totals | [![Coverage Status](https://coveralls.io/builds/68166697/badge)](https://coveralls.io/builds/68166697) |
| :-- | --: |
| Change from base [Build 9540771280](https://coveralls.io/builds/68114056): |  0.2% |
| Covered Lines: | 32313 |
| Relevant Lines: | 35465 |

---
##### 💛  - [Coveralls](https://coveralls.io)

coveralls: ## Pull Request Test Coverage Report for [Build 9574658802](https://coveralls.io/builds/68167446)

### Warning: This coverage report may be inaccurate.

This pull request's base commit is no longer the HEAD commit of its target branch. This means it includes changes from outside the original pull request, including, potentially, unrelated coverage changes.

- For more information on this, see <a target="_blank" href="https://docs.coveralls.io/build-types#tracking-coverage-changes-with-pull_request-builds">Tracking coverage changes with pull request builds</a>.
- To avoid this issue with future PRs, see these <a target="_blank" href="https://docs.coveralls.io/build-types#recommended-ci-configurations">Recommended CI Configurations</a>.
- For a quick fix, <a target="_blank" href="https://github.blog/changelog/2022-02-03-more-ways-to-keep-your-pull-request-branch-up-to-date/#update-your-pull-request-branch-by-rebasing">rebase this PR at GitHub</a>. Your next report should be accurate.

### Details

* **97** of **113**   **(85.84%)**  changed or added relevant lines in **3** files are covered.
* **193** unchanged lines in **2** files lost coverage.
* Overall coverage increased (+**0.2%**) to **91.115%**

---

|  Changes Missing Coverage | Covered Lines | Changed/Added Lines | % |
| :-----|--------------|--------|---: |
| [src/cogent3/core/new_sequence.py](https://coveralls.io/builds/68167446/source?filename=src%2Fcogent3%2Fcore%2Fnew_sequence.py#L557) | 51 | 57 | 89.47%
| [src/cogent3/core/new_alignment.py](https://coveralls.io/builds/68167446/source?filename=src%2Fcogent3%2Fcore%2Fnew_alignment.py#L708) | 45 | 55 | 81.82%
<!-- | **Total:** | **97** | **113** | **85.84%** | -->

|  Files with Coverage Reduction | New Missed Lines | % |
| :-----|--------------|--: |
| [src/cogent3/core/new_alignment.py](https://coveralls.io/builds/68167446/source?filename=src%2Fcogent3%2Fcore%2Fnew_alignment.py#L824) | 36 | 92.55% |
| [src/cogent3/core/new_sequence.py](https://coveralls.io/builds/68167446/source?filename=src%2Fcogent3%2Fcore%2Fnew_sequence.py#L560) | 157 | 61.03% |
<!-- | **Total:** | **193** |  | -->

|  Totals | [![Coverage Status](https://coveralls.io/builds/68167446/badge)](https://coveralls.io/builds/68167446) |
| :-- | --: |
| Change from base [Build 9540771280](https://coveralls.io/builds/68114056): |  0.2% |
| Covered Lines: | 32315 |
| Relevant Lines: | 35466 |

---
##### 💛  - [Coveralls](https://coveralls.io)

coveralls: ## Pull Request Test Coverage Report for [Build 9574742299](https://coveralls.io/builds/68167607)

### Warning: This coverage report may be inaccurate.

This pull request's base commit is no longer the HEAD commit of its target branch. This means it includes changes from outside the original pull request, including, potentially, unrelated coverage changes.

- For more information on this, see <a target="_blank" href="https://docs.coveralls.io/build-types#tracking-coverage-changes-with-pull_request-builds">Tracking coverage changes with pull request builds</a>.
- To avoid this issue with future PRs, see these <a target="_blank" href="https://docs.coveralls.io/build-types#recommended-ci-configurations">Recommended CI Configurations</a>.
- For a quick fix, <a target="_blank" href="https://github.blog/changelog/2022-02-03-more-ways-to-keep-your-pull-request-branch-up-to-date/#update-your-pull-request-branch-by-rebasing">rebase this PR at GitHub</a>. Your next report should be accurate.

### Details

* **93** of **110**   **(84.55%)**  changed or added relevant lines in **3** files are covered.
* **3** unchanged lines in **1** file lost coverage.
* Overall coverage increased (+**0.2%**) to **91.115%**

---

|  Changes Missing Coverage | Covered Lines | Changed/Added Lines | % |
| :-----|--------------|--------|---: |
| [src/cogent3/core/new_alignment.py](https://coveralls.io/builds/68167607/source?filename=src%2Fcogent3%2Fcore%2Fnew_alignment.py#L716) | 35 | 43 | 81.4%
| [src/cogent3/core/new_sequence.py](https://coveralls.io/builds/68167607/source?filename=src%2Fcogent3%2Fcore%2Fnew_sequence.py#L1092) | 57 | 66 | 86.36%
<!-- | **Total:** | **93** | **110** | **84.55%** | -->

|  Files with Coverage Reduction | New Missed Lines | % |
| :-----|--------------|--: |
| [src/cogent3/core/new_sequence.py](https://coveralls.io/builds/68167607/source?filename=src%2Fcogent3%2Fcore%2Fnew_sequence.py#L1543) | 3 | 61.03% |
<!-- | **Total:** | **3** |  | -->

|  Totals | [![Coverage Status](https://coveralls.io/builds/68167607/badge)](https://coveralls.io/builds/68167607) |
| :-- | --: |
| Change from base [Build 9540771280](https://coveralls.io/builds/68114056): |  0.2% |
| Covered Lines: | 32315 |
| Relevant Lines: | 35466 |

---
##### 💛  - [Coveralls](https://coveralls.io)

coveralls: ## Pull Request Test Coverage Report for [Build 9589679825](https://coveralls.io/builds/68187442)

### Warning: This coverage report may be inaccurate.

This pull request's base commit is no longer the HEAD commit of its target branch. This means it includes changes from outside the original pull request, including, potentially, unrelated coverage changes.

- For more information on this, see <a target="_blank" href="https://docs.coveralls.io/build-types#tracking-coverage-changes-with-pull_request-builds">Tracking coverage changes with pull request builds</a>.
- To avoid this issue with future PRs, see these <a target="_blank" href="https://docs.coveralls.io/build-types#recommended-ci-configurations">Recommended CI Configurations</a>.
- For a quick fix, <a target="_blank" href="https://github.blog/changelog/2022-02-03-more-ways-to-keep-your-pull-request-branch-up-to-date/#update-your-pull-request-branch-by-rebasing">rebase this PR at GitHub</a>. Your next report should be accurate.

### Details

* **94** of **111**   **(84.68%)**  changed or added relevant lines in **3** files are covered.
* **39** unchanged lines in **4** files lost coverage.
* Overall coverage increased (+**0.1%**) to **91.071%**

---

|  Changes Missing Coverage | Covered Lines | Changed/Added Lines | % |
| :-----|--------------|--------|---: |
| [src/cogent3/core/new_alignment.py](https://coveralls.io/builds/68187442/source?filename=src%2Fcogent3%2Fcore%2Fnew_alignment.py#L716) | 34 | 42 | 80.95%
| [src/cogent3/core/new_sequence.py](https://coveralls.io/builds/68187442/source?filename=src%2Fcogent3%2Fcore%2Fnew_sequence.py#L1092) | 59 | 68 | 86.76%
<!-- | **Total:** | **94** | **111** | **84.68%** | -->

|  Files with Coverage Reduction | New Missed Lines | % |
| :-----|--------------|--: |
| [src/cogent3/core/alignment.py](https://coveralls.io/builds/68187442/source?filename=src%2Fcogent3%2Fcore%2Falignment.py#L571) | 1 | 93.76% |
| [src/cogent3/core/new_sequence.py](https://coveralls.io/builds/68187442/source?filename=src%2Fcogent3%2Fcore%2Fnew_sequence.py#L1543) | 3 | 60.92% |
| [src/cogent3/core/new_alphabet.py](https://coveralls.io/builds/68187442/source?filename=src%2Fcogent3%2Fcore%2Fnew_alphabet.py#L224) | 4 | 96.58% |
| [src/cogent3/core/new_moltype.py](https://coveralls.io/builds/68187442/source?filename=src%2Fcogent3%2Fcore%2Fnew_moltype.py#L323) | 31 | 90.77% |
<!-- | **Total:** | **39** |  | -->

|  Totals | [![Coverage Status](https://coveralls.io/builds/68187442/badge)](https://coveralls.io/builds/68187442) |
| :-- | --: |
| Change from base [Build 9540771280](https://coveralls.io/builds/68114056): |  0.1% |
| Covered Lines: | 32372 |
| Relevant Lines: | 35546 |

---
##### 💛  - [Coveralls](https://coveralls.io)


Patch:
diff --git a/src/cogent3/core/alignment.py b/src/cogent3/core/alignment.py
index 358f36df9..5bc4d6605 100644
--- a/src/cogent3/core/alignment.py
+++ b/src/cogent3/core/alignment.py
@@ -1445,7 +1445,7 @@ class _SequenceCollectionBase:
         """returns copy of self as an alignment of RNA moltype seqs"""
         return self.to_moltype("rna")
 
-    def to_protein(self):  # ported
+    def to_protein(self):  # will not port
         """returns copy of self as an alignment of PROTEIN moltype seqs"""
         return self.to_moltype("protein")
 
diff --git a/src/cogent3/core/new_alignment.py b/src/cogent3/core/new_alignment.py
index 7bc505b5d..c5bc4d0e6 100644
--- a/src/cogent3/core/new_alignment.py
+++ b/src/cogent3/core/new_alignment.py
@@ -37,6 +37,9 @@ from cogent3.util.misc import get_setting_from_environ, negate_condition
 
 DEFAULT_ANNOTATION_DB = BasicAnnotationDb
 
+OptInt = typing.Optional[int]
+OptStr = typing.Optional[str]
+OptCallable = typing.Optional[typing.Callable]
 PrimitiveSeqTypes = Union[str, bytes, numpy.ndarray]
 
 
@@ -45,10 +48,9 @@ def assign_sequential_names(num_seqs: int, base_name: str = "seq", start_at: int
     return [f"{base_name}_{i}" for i in range(start_at, start_at + num_seqs)]
 
 
-class SeqDataView(new_seq.SliceRecordABC):
+class SeqDataView(new_seq.SeqViewABC, new_seq.SliceRecordABC):
     """
-    A view class for SeqsData, providing properties for different
-    representations.
+    A view class for SeqsData, providing properties for different representations.
 
     self.seqs is a SeqsData() instance, but other properties are a reference to a
     single seqid only.
@@ -58,31 +60,32 @@ class SeqDataView(new_seq.SliceRecordABC):
     data = {"seq1": "ACGT", "seq2": "GTTTGCA"}
     sd = SeqsData(data=data)
     sdv = sd.get_seq_view(seqid="seq1")
+    sdv.array_value
+    # array([3, 1, 2, 0], dtype=int8)
     """
 
-    __slots__ = ("seqs", "start", "stop", "step", "_offset", "_seqid", "_seq_len")
+    __slots__ = ("seq", "start", "stop", "step", "_offset", "_seqid", "_seq_len")
 
     def __init__(
         self,
         *,
-        seqs: SeqsData,
+        seq: SeqsData,
         seq_len: int,
-        start: Optional[int] = None,
-        stop: Optional[int] = None,
-        step: Optional[int] = None,
+        start: OptInt = None,
+        stop: OptInt = None,
+        step: OptInt = None,
         offset: int = 0,
-        seqid: Optional[str] = None,
+        seqid: OptStr = None,
     ):
         if step == 0:
             raise ValueError("step cannot be 0")
         step = 1 if step is None else step
-
         self._seq_len = self._checked_seq_len(seq_len)
         func = (
             new_seq._input_vals_pos_step if step > 0 else new_seq._input_vals_neg_step
         )
         start, stop, step = func(self._seq_len, start, stop, step)
-        self.seqs = seqs
+        self.seq = seq
         self.start = start
         self.stop = stop
         self.step = step
@@ -96,7 +99,7 @@ class SeqDataView(new_seq.SliceRecordABC):
     @property
     def _zero_slice(self):
         return self.__class__(
-            seqs=self.seqs, seqid=self.seqid, seq_len=self._seq_len, start=0, stop=0
+            seq=self.seq, seqid=self.seqid, seq_len=self._seq_len, start=0, stop=0
         )
 
     @property
@@ -108,39 +111,34 @@ class SeqDataView(new_seq.SliceRecordABC):
         return self._seq_len
 
     def _get_init_kwargs(self):
-        return {"seqs": self.seqs, "seqid": self.seqid}
+        return {"seq": self.seq, "seqid": self.seqid}
 
     @property
     def str_value(self) -> str:
-        raw = self.seqs.get_seq_str(
+        """returns the sequence as a string"""
+        raw = self.seq.get_seq_str(
             seqid=self.seqid, start=self.parent_start, stop=self.parent_stop
         )
         return raw if self.step == 1 else raw[:: self.step]
 
     @property
     def array_value(self) -> numpy.ndarray:
-        raw = self.seqs.get_seq_array(
+        """returns the sequence as a numpy array"""
+        raw = self.seq.get_seq_array(
             seqid=self.seqid, start=self.parent_start, stop=self.parent_stop
         )
         return raw if self.step == 1 else raw[:: self.step]
 
     @property
     def bytes_value(self) -> bytes:
-        raw = self.seqs.get_seq_bytes(
+        """returns the sequence as bytes"""
+        raw = self.seq.get_seq_bytes(
             seqid=self.seqid, start=self.parent_start, stop=self.parent_stop
         )
         return raw if self.step == 1 else raw[:: self.step]
 
-    def __str__(self):
-        return self.str_value
-
-    def __array__(self):
-        return self.array_value
-
-    def __bytes__(self):
-        return self.bytes_value
-
     def __repr__(self) -> str:
+        # todo: add alphabet to the repr
         seq = f"{self[:10]!s}...{self[-5:]}" if len(self) > 15 else str(self)
         return (
             f"{self.__class__.__name__}(seq={seq}, start={self.start}, "
@@ -159,10 +157,7 @@ class SeqsDataABC(ABC):
     a SequenceCollection
     """
 
-    alphabet: new_alpha.CharAlphabet
-    make_seq: Callable = (
-        None  # refactor: type hint for callable should specificy input/return type
-    )
+    __slots__ = ()
 
     @abstractmethod
     def seq_lengths(self) -> dict[str, int]: ...
@@ -175,6 +170,8 @@ class SeqsDataABC(ABC):
     @abstractmethod
     def make_seq(self): ...
 
+    # refactor: docstring
+
     @make_seq.setter
     @abstractmethod
     def make_seq(self, make_seq: Callable) -> None: ...
@@ -185,17 +182,17 @@ class SeqsDataABC(ABC):
 
     @abstractmethod
     def get_seq_array(
-        self, *, seqid: str, start: int = None, stop: int = None
+        self, *, seqid: str, start: OptInt = None, stop: OptInt = None
     ) -> numpy.ndarray: ...
 
     @abstractmethod
     def get_seq_str(
-        self, *, seqid: str, start: int = None, stop: int = None
+        self, *, seqid: str, start: OptInt = None, stop: OptInt = None
     ) -> str: ...
 
     @abstractmethod
     def get_seq_bytes(
-        self, *, seqid: str, start: int = None, stop: int = None
+        self, *, seqid: str, start: OptInt = None, stop: OptInt = None
     ) -> bytes: ...
 
     @abstractmethod
@@ -220,14 +217,18 @@ class SeqsDataABC(ABC):
 
 
 class SeqsData(SeqsDataABC):
-    __slots__ = ("_data", "_alphabet", "_names", "_make_seq")
+    __slots__ = ("_data", "_alphabet", "_make_seq")
+    # todo: kath
+    # refactor: design
+    # SeqsData needs new fields that record the offsets and possibly whether
+    # the sequence is reversed
 
     def __init__(
         self,
         *,
         data: dict[str, PrimitiveSeqTypes],
         alphabet: new_alpha.CharAlphabet,
-        make_seq: Callable = None,
+        make_seq: OptCallable = None,
     ):
         self._alphabet = alphabet
         self._make_seq = make_seq
@@ -255,6 +256,7 @@ class SeqsData(SeqsDataABC):
 
     @make_seq.setter
     def make_seq(self, make_seq: Callable) -> None:
+        # refactor: add type hints for callable function
         self._make_seq = make_seq
 
     @property
@@ -262,26 +264,29 @@ class SeqsData(SeqsDataABC):
         return self._alphabet
 
     def seq_lengths(self) -> dict[str, int]:
+        """Returns lengths of sequences as dict of {name: length, ... }."""
         return {name: seq.shape[0] for name, seq in self._data.items()}
 
     def get_seq_array(
-        self, *, seqid: str, start: int = None, stop: int = None
+        self, *, seqid: str, start: OptInt = None, stop: OptInt = None
     ) -> numpy.ndarray:
         return self._data[seqid][start:stop]
 
-    def get_seq_str(self, *, seqid: str, start: int = None, stop: int = None) -> str:
+    def get_seq_str(
+        self, *, seqid: str, start: OptInt = None, stop: OptInt = None
+    ) -> str:
         return self._alphabet.from_indices(
             self.get_seq_array(seqid=seqid, start=start, stop=stop)
         )
 
     def get_seq_bytes(
-        self, *, seqid: str, start: int = None, stop: int = None
+        self, *, seqid: str, start: OptInt = None, stop: OptInt = None
     ) -> bytes:
         return self.get_seq_str(seqid=seqid, start=start, stop=stop).encode("utf8")
 
     def get_seq_view(self, seqid: str) -> SeqDataView:
         seq_len = len(self._data[seqid])
-        return SeqDataView(seqs=self, seqid=seqid, seq_len=seq_len)
+        return SeqDataView(seq=self, seqid=seqid, seq_len=seq_len)
 
     def subset(self, names: Union[str, typing.Sequence[str]]) -> SeqsData:
         """Returns a new SeqsData object with only the specified names."""
@@ -352,7 +357,7 @@ class SeqsData(SeqsDataABC):
         return (
             sdv
             if self.make_seq is None
-            else self.make_seq(seq=sdv.str_value, name=index)
+            else self.make_seq(seq=sdv, name=index, check_seq=False)
         )
 
     @__getitem__.register
@@ -368,9 +373,9 @@ class SequenceCollection:
         moltype: new_moltype.MolType,
         names: list[str] = None,
         info: Union[dict, InfoClass] = None,
-        source: str = None,  # refactor: need to deal with optional args type hints
+        source: OptStr = None,
         annotation_db: SupportsFeatures = None,
-    ):
+    ):  # refactor: need to deal with optional args type hints
         self._seqs_data = seqs_data
         self.moltype = moltype
         self.names = names
@@ -427,7 +432,9 @@ class SequenceCollection:
 
     def iter_seqs(
         self, seq_order: list = None
-    ) -> Iterator[Union[new_seq.Sequence, SeqsData]]:
+    ) -> Iterator[
+        Union[new_seq.Sequence, SeqsData]
+    ]:  # refactor: need to deal with optional args type hints
         """Iterates over sequences in the collection, in order.
 
         Parameters
@@ -575,6 +582,11 @@ class SequenceCollection:
             annotation_db=self.annotation_db,
         )
 
+    def rename_seqs(self, renamer):
+        # todo: kath
+        # returns new SeqsData instance
+        ...
+
     def to_dict(self, as_array: bool = False) -> dict[str, Union[str, numpy.ndarray]]:
         """Return a dictionary of sequences.
 
@@ -626,11 +638,7 @@ class SequenceCollection:
         if mtype is self.moltype:
             return self  # nothing to be done
 
-        alpha = (
-            mtype.degen_gapped_alphabet
-            if mtype.degen_gapped_alphabet is not None
-            else mtype.gapped_alphabet
-        )
+        alpha = mtype.most_degen_alphabet()
         try:
             new_seqs_data = self.seqs.to_alphabet(alpha)
         except ValueError as e:
@@ -645,6 +653,106 @@ class SequenceCollection:
             annotation_db=self.annotation_db,
         )
 
+    def to_dna(self):
+        """returns copy of self as an alignment of DNA moltype seqs"""
+        # refactor: design
+        # do we need this in addition to to_moltype?
+        return self.to_moltype("dna")
+
+    def to_rna(self):
+        """returns copy of self as an alignment of RNA moltype seqs"""
+        return self.to_moltype("rna")
+
+    def get_translation(
+        self,
+        gc: int = 1,
+        incomplete_ok: bool = False,
+        include_stop: bool = False,
+        trim_stop: bool = True,
+        **kwargs,
+    ):
+        """translate from nucleic acid to protein
+
+        Parameters
+        ----------
+        gc
+            genetic code, either the number or name
+            (use cogent3.core.genetic_code.available_codes)
+        incomplete_ok
+            codons that are mixes of nucleotide and gaps converted to '?'.
+            raises a ValueError if False
+        include_stop
+            whether to allow a stops in the translated sequence
+        trim_stop
+            exclude terminal stop codons if they exist
+        kwargs
+            related to construction of the resulting object
+
+        Returns
+        -------
+        A new instance of self translated into protein
+        """
+        # todo: kath, add check for when include_stop==True, the seq.get_translation method should be
+        # returning sequences with a protein_with_stop moltype.
+
+        if len(self.moltype.alphabet) != 4:
+            raise TypeError("Sequences must be a DNA/RNA")
+
+        translated = {}
+        if trim_stop and not include_stop:
+            seqs = self.trim_stop_codons(gc=gc, strict=not incomplete_ok)
+        else:
+            seqs = self
+        # do the translation
+        for seqname in seqs.names:
+            seq = seqs.seqs[seqname]
+            pep = seq.get_translation(
+                gc, incomplete_ok=True, include_stop=include_stop, trim_stop=trim_stop
+            )
+            translated[seqname] = pep
+        pep_moltype = pep.moltype
+        seqs_data = SeqsData(
+            data=translated,
+            alphabet=pep_moltype.most_degen_alphabet(),
+            make_seq=pep_moltype.make_seq,
+        )
+        return self.__class__(
+            seqs_data=seqs_data, info=self.info, source=self.source, **kwargs
+        )
+
+    def rc(self):
+        """Returns the reverse complement of all sequences in the collection.
+        A synonym for reverse_complement.
+
+        Notes
+        -----
+        Reverse complementing the collection will break the relationship to an
+        annotation_db if present.
+
+        """
+        rc_seqs = {name: self.seqs[name].rc() for name in self.names}
+        seqs_data = self.seqs.__class__(
+            data=rc_seqs, alphabet=self.moltype.alphabet, make_seq=self.moltype.make_seq
+        )
+        return self.__class__(
+            seqs_data=seqs_data,
+            name=self.name,
+            info=self.info,
+            moltype=self.moltype,
+        )
+
+    def reverse_complement(self):
+        """Returns the reverse complement of all sequences in the collection.
+        A synonym for rc.
+
+        Notes
+        -----
+        Reverse complementing the collection will break the relationship to an
+        annotation_db if present.
+
+        """
+        return self.rc()
+
     def distance_matrix(self, calc: str = "pdist"):
         """Estimated pairwise distance between sequences
 
@@ -719,6 +827,8 @@ class SequenceCollection:
         if self.annotation_db is None:
             self.annotation_db = type(seq_db)()
 
+        # refactor
+        # if we update a db with the same db, it just hangs... need to fix
         if self.annotation_db.compatible(seq_db, symmetric=False):
             # our db contains the tables in other, so we update in place
             self.annotation_db.update(annot_db=seq_db, seqids=self.names)
@@ -758,7 +868,7 @@ class SequenceCollection:
         biotype: str,
         name: str,
         spans: list[tuple[int, int]],
-        parent_id: Optional[str] = None,
+        parent_id: OptStr = None,
         strand: str = "+",
     ) -> Feature:
         """
@@ -799,10 +909,12 @@ class SequenceCollection:
         self,
         *,
         seqid: Union[str, Iterator[str]] = None,
-        biotype: Optional[str] = None,
-        name: Optional[str] = None,
+        biotype: OptStr = None,
+        name: OptStr = None,
+        start: OptInt = None,
+        stop: OptInt = None,
         allow_partial: bool = False,
-    ) -> Iterator[Feature]:
+    ) -> Iterator[Feature]:  # refactor: need to deal with optional args type hints
         """yields Feature instances
 
         Parameters
@@ -813,14 +925,21 @@ class SequenceCollection:
             biotype of the feature, e.g. CDS, gene
         name
             name of the feature
+        start
+            start position of the feature (not inclusive)
+        stop
+            stop position of the feature (inclusive)
         allow_partial
             allow features partially overlaping self
 
         Notes
         -----
-        When dealing with a nucleic acid moltype, the returned features will
+        - When dealing with a nucleic acid moltype, the returned features will
         yield a sequence segment that is consistently oriented irrespective
         of strand of the current instance.
+        - start is non-inclusive, so if allow_partial is False, only features
+        strictly starting after start will be returned.
+
         """
         if not self.annotation_db:
             return None
@@ -836,6 +955,8 @@ class SequenceCollection:
             biotype=biotype,
             name=name,
             on_alignment=False,
+            start=start,
+            stop=stop,
             allow_partial=allow_partial,
         ):
             seqname = seqid_to_seqname[feature["seqid"]]
@@ -872,7 +993,7 @@ class SequenceCollection:
 
         return alignment_to_phylip(self.to_dict())
 
-    def write(self, filename: str = None, file_format: str = None, **kwargs):
+    def write(self, filename: str, file_format: OptStr = None, **kwargs):
         """Write the sequences to a file, preserving order of sequences.
 
         Parameters
@@ -890,9 +1011,6 @@ class SequenceCollection:
         """
 
         # todo: kath, add support for json
-        if filename is None:
-            raise IOError("no filename specified")
-
         suffix, _ = get_format_suffixes(filename)
         if file_format is None and suffix:
             file_format = suffix
@@ -909,14 +1027,14 @@ class SequenceCollection:
 
     def dotplot(
         self,
-        name1: Optional[str] = None,
-        name2: Optional[str] = None,
+        name1: OptStr = None,
+        name2: OptStr = None,
         window: int = 20,
-        threshold: Optional[int] = None,
-        k: Optional[int] = None,
+        threshold: OptInt = None,
+        k: OptInt = None,
         min_gap: int = 0,
         width: int = 500,
-        title: Optional[str] = None,
+        title: OptStr = None,
         rc: bool = False,
         show_progress: bool = False,
     ):
@@ -1018,7 +1136,7 @@ class SequenceCollection:
     def apply_pssm(
         self,
         pssm: PSSM = None,
-        path: str = None,
+        path: OptStr = None,
         background: numpy.array = None,
         pseudocount: int = 0,
         names: list = None,
@@ -1072,19 +1190,10 @@ class SequenceCollection:
                 pssm = pwm.to_pssm(background=background, pseudocount=pseudocount)
 
         assert isinstance(pssm, PSSM)
-        array_align = hasattr(self, "array_seqs")
         assert set(pssm.motifs) == set(self.moltype)
-        if array_align and list(pssm.motifs) == list(self.moltype):
-            if names:
-                name_indices = [self.names.index(n) for n in names]
-                data = self.array_seqs.take(name_indices, axis=0)
-            else:
-                data = self.array_seqs
 
-            result = [pssm.score_indexed_seq(seq) for seq in ui.series(data)]
-        else:
-            seqs = [self.seqs[n] for n in names] if names else self.seqs
-            result = [pssm.score_seq(seq) for seq in ui.series(seqs)]
+        seqs = [self.seqs[n] for n in names] if names else self.seqs
+        result = [pssm.score_seq(seq) for seq in ui.series(seqs)]
 
         return numpy.array(result)
 
@@ -1215,15 +1324,18 @@ class SequenceCollection:
 
         Parameters
         ----------
+        alphabet
+            alphabet to use for motifs
         include_ambiguity
             if True resolved ambiguous codes are included in estimation of
-            frequencies, default is False.
+            frequencies.
         exclude_unobserved
             if True, motifs that are not present in the alignment are excluded
-            from the returned dictionary,
-            default is False.
+            from the returned dictionary.
         allow_gap
             allow gap motif
+        pseudocount
+            value to add to each count
 
         Notes
         -----
@@ -1234,12 +1346,12 @@ class SequenceCollection:
         if alphabet is None:
             alphabet = moltype.alphabet
             if allow_gap:
-                alphabet = alphabet.gapped
+                alphabet = moltype.gapped_alphabet
 
         counts = {}
         for seq_name in self.names:
             sequence = self.seqs[seq_name]
-            motif_len = alphabet.get_motif_len()
+            motif_len = alphabet.motif_len
             if motif_len > 1:
                 posns = list(range(0, len(sequence) + 1 - motif_len, motif_len))
                 sequence = [sequence[i : i + motif_len] for i in posns]
@@ -1281,7 +1393,23 @@ class SequenceCollection:
         exclude_unobserved: bool = False,
         warn: bool = False,
     ) -> MotifFreqsArray:
-        """return MotifFreqsArray per sequence"""
+        """return frequency array of motifs per sequence
+
+        Parameters
+        ----------
+        motif_length
+            number of characters per motif
+        include_ambiguity
+            if True, include motifs containing ambiguous characters
+        allow_gap
+            if True, include motifs containing a gap character
+        exclude_unobserved
+            if True, exclude motifs not present in the sequences in
+            the resulting array
+        warn
+            warns if motif_length > 1 and collection trimmed to produce motif
+            columns.
+        """
 
         counts = self.counts_per_seq(
             motif_length=motif_length,
@@ -1351,7 +1479,7 @@ class SequenceCollection:
         )
         return counts.row_sum()
 
-    def pad_seqs(self, pad_length: int = None):
+    def pad_seqs(self, pad_length: OptInt = None):
         """Returns copy in which sequences are padded to same length.
 
         Parameters
@@ -1608,7 +1736,7 @@ class SequenceCollection:
         self,
         name_order: Optional[typing.Sequence[str]] = None,
         wrap: int = 60,
-        limit: Optional[int] = None,
+        limit: OptInt = None,
         colors: Optional[Mapping[str, str]] = None,
         font_size: int = 12,
         font_family: str = "Lucida Console",
@@ -1759,10 +1887,10 @@ class SequenceCollection:
 
     def set_repr_policy(
         self,
-        num_seqs: Optional[int] = None,
-        num_pos: Optional[int] = None,
-        ref_name: Optional[str] = None,
-        wrap: Optional[int] = None,
+        num_seqs: OptInt = None,
+        num_pos: OptInt = None,
+        ref_name: OptInt = None,
+        wrap: OptInt = None,
     ):
         """specify policy for repr(self)
 
@@ -1848,17 +1976,17 @@ def _(seqs: dict) -> SupportsFeatures:
 
 @singledispatch
 def coerce_to_seqs_data_dict(
-    data, label_to_name: Callable = None
-) -> dict[
-    str, PrimitiveSeqTypes
-]:  # refactor: type hint for callable should specificy input/return type
+    data, label_to_name: OptCallable = None
+) -> dict[str, PrimitiveSeqTypes]:
+    # refactor: type hint for callable should specificy input/return type
+    # refactor: handle conversion of SeqView to SeqDataView.
     raise NotImplementedError(
         f"coerce_to_seqs_data_dict not implemented for {type(data)}"
     )
 
 
 @coerce_to_seqs_data_dict.register
-def _(data: dict, label_to_name: Callable = None) -> dict[str, PrimitiveSeqTypes]:
+def _(data: dict, label_to_name: OptCallable = None) -> dict[str, PrimitiveSeqTypes]:
     is_sequence = isinstance(next(iter(data.values()), None), new_seq.Sequence)
     return {
         (label_to_name(k) if label_to_name else k): (str(v) if is_sequence else v)
@@ -1867,14 +1995,14 @@ def _(data: dict, label_to_name: Callable = None) -> dict[str, PrimitiveSeqTypes
 
 
 @coerce_to_seqs_data_dict.register
-def _(data: list, label_to_name: Callable = None) -> dict[str, PrimitiveSeqTypes]:
+def _(data: list, label_to_name: OptCallable = None) -> dict[str, PrimitiveSeqTypes]:
     first = data[0]
     labelled_seqs = assign_names(first, data=data)
     return coerce_to_seqs_data_dict(labelled_seqs, label_to_name=label_to_name)
 
 
 @coerce_to_seqs_data_dict.register
-def _(data: set, label_to_name: Callable = None) -> dict[str, PrimitiveSeqTypes]:
+def _(data: set, label_to_name: OptCallable = None) -> dict[str, PrimitiveSeqTypes]:
     first = next(iter(data))
     labelled_seqs = assign_names(first, data=data)
     return coerce_to_seqs_data_dict(labelled_seqs, label_to_name=label_to_name)
@@ -1882,7 +2010,7 @@ def _(data: set, label_to_name: Callable = None) -> dict[str, PrimitiveSeqTypes]
 
 @coerce_to_seqs_data_dict.register
 def _(
-    data: SequenceCollection, label_to_name: Callable = None
+    data: SequenceCollection, label_to_name: OptCallable = None
 ) -> dict[str, PrimitiveSeqTypes]:
     return coerce_to_seqs_data_dict(
         data.to_dict(as_array=True), label_to_name=label_to_name
@@ -1919,7 +2047,7 @@ def make_unaligned_seqs(
     data: Union[dict[str, PrimitiveSeqTypes], SeqsData, list],
     *,
     moltype: Union[str, new_moltype.MolType],
-    label_to_name: Callable = None,
+    label_to_name: OptCallable = None,
     info: dict = None,
     source: Union[str, Path] = None,
     annotation_db: SupportsFeatures = None,
@@ -1958,9 +2086,7 @@ def make_unaligned_seqs(
     seqs_data = coerce_to_seqs_data_dict(data, label_to_name=label_to_name)
 
     moltype = new_moltype.get_moltype(moltype)
-    alphabet = (
-        moltype.degen_gapped_alphabet or moltype.gapped_alphabet or moltype.alphabet
-    )
+    alphabet = moltype.most_degen_alphabet()
 
     seqs_data = SeqsData(data=seqs_data, alphabet=alphabet)
     return make_unaligned_seqs(
@@ -1975,10 +2101,10 @@ def make_unaligned_seqs(
 
 @make_unaligned_seqs.register
 def _(
-    data: SeqsData,
+    data: SeqsDataABC,
     *,
     moltype: Union[str, new_moltype.MolType],
-    label_to_name: Callable = None,
+    label_to_name: OptCallable = None,
     info: dict = None,
     source: Union[str, Path] = None,
     annotation_db: SupportsFeatures = None,
diff --git a/src/cogent3/core/new_sequence.py b/src/cogent3/core/new_sequence.py
index e5e1e9631..f47b45330 100644
--- a/src/cogent3/core/new_sequence.py
+++ b/src/cogent3/core/new_sequence.py
@@ -24,6 +24,7 @@ from random import shuffle
 from numpy import array, floating, integer, issubdtype
 
 from cogent3._version import __version__
+from cogent3.core import new_alphabet
 from cogent3.core.annotation import Feature
 from cogent3.core.annotation_db import (
     BasicAnnotationDb,
@@ -32,10 +33,8 @@ from cogent3.core.annotation_db import (
     SupportsFeatures,
     load_annotations,
 )
-from cogent3.core.genetic_code import get_code
 from cogent3.core.info import Info as InfoClass
 from cogent3.core.location import FeatureMap, IndelMap, LostSpan
-from cogent3.core.new_alphabet import AlphabetError
 from cogent3.format.fasta import alignment_to_fasta
 from cogent3.maths.stats.contingency import CategoryCounts
 from cogent3.maths.stats.number import CategoryCounter
@@ -104,7 +103,7 @@ class Sequence:
         """
         self.moltype = moltype
         self.name = name
-        self._seq = _coerce_to_seqview(seq, name)
+        self._seq = _coerce_to_seqview(seq, name, self.moltype.most_degen_alphabet())
         info = info or {}
         self.info = InfoClass(**info)
         self._repr_policy = dict(num_pos=60)
@@ -114,6 +113,12 @@ class Sequence:
     def __str__(self):
         return str(self._seq)
 
+    def __bytes__(self):
+        return bytes(self._seq)
+
+    def __array__(self):
+        return array(self._seq)
+
     def to_fasta(self, make_seqlabel=None, block_size=60):
         """Return string of self in FASTA format, no trailing newline
 
@@ -547,6 +552,7 @@ class Sequence:
         Returns 0 if one sequence is empty.
         """
         # refactor: simplify
+        # refactor: array - make use of self._seq.array_value
         if not self or not other:
             return 0.0
 
@@ -1081,7 +1087,7 @@ class Sequence:
 
         s = moltype.coerce_str(self._seq.value)
         moltype.verify_sequence(s, gaps_allowed=True, wildcards_allowed=True)
-        sv = SeqView(seq=s)
+        sv = SeqView(seq=s, alphabet=moltype.most_degen_alphabet())
         new = moltype.make_seq(sv, name=self.name, info=self.info)
         new.annotation_db = self.annotation_db
         return new
@@ -1678,7 +1684,7 @@ class NucleicAcidSequenceMixin:
             return gc.is_stop(end3)
 
         if strict:
-            raise AlphabetError(f"{self.name!r} length not divisible by 3")
+            raise new_alphabet.AlphabetError(f"{self.name!r} length not divisible by 3")
 
         return False
 
@@ -1772,12 +1778,12 @@ class NucleicAcidSequenceMixin:
         #  with stop. The genetic code should also do the translation off
         #  raw data, either an array of ints or a string. So this method
         #  should only deal with trimming terminal stops, modulo 3 etc...
-        from cogent3.core import new_moltype
+        from cogent3.core import new_genetic_code, new_moltype
 
         protein = new_moltype.get_moltype(
             "protein_with_stop" if include_stop else "protein"
         )
-        gc = get_code(gc)
+        gc = new_genetic_code.get_code(gc)
         codon_alphabet = gc.get_alphabet(include_stop=include_stop).with_gap_motif()
         moltype = self.moltype
         # translate the codons
@@ -1794,9 +1800,9 @@ class NucleicAcidSequenceMixin:
                 resolved = moltype.resolve_ambiguity(
                     orig_codon, alphabet=codon_alphabet
                 )
-            except AlphabetError:
+            except new_alphabet.AlphabetError:
                 if not incomplete_ok or "-" not in orig_codon:
-                    raise AlphabetError(
+                    raise new_alphabet.AlphabetError(
                         f"unresolvable codon {orig_codon!r} in {self.name}"
                     )
                 resolved = (orig_codon,)
@@ -1807,14 +1813,16 @@ class NucleicAcidSequenceMixin:
                 elif "-" in codon:
                     aa = "?"
                     if not incomplete_ok:
-                        raise AlphabetError(f"incomplete codon {codon} in {self.name}")
+                        raise new_alphabet.AlphabetError(
+                            f"incomplete codon {codon} in {self.name}"
+                        )
                 else:
                     aa = gc[codon]
                     if aa == "*" and not include_stop:
                         continue
                 trans.append(aa)
             if not trans:
-                raise AlphabetError(orig_codon)
+                raise new_alphabet.AlphabetError(orig_codon)
             aa = protein.what_ambiguity(trans)
             translation.append(aa)
 
@@ -1905,11 +1913,11 @@ class SliceRecordABC(ABC):
     seq_len refers to a Python typing.Sequence object, e.g. array, str, list.
     """
 
-    start: int
-    stop: int
-    step: int
-    seq_len: int
-    _offset: int
+    __slots__ = ("start", "stop", "step", "_offset")
+
+    @property
+    @abstractmethod
+    def seq_len(self) -> int: ...
 
     @abstractmethod
     def _get_init_kwargs(self) -> dict:
@@ -1920,6 +1928,8 @@ class SliceRecordABC(ABC):
     @abstractmethod
     def copy(self): ...
 
+    # refactor: design
+    # can we remove the need for this method on the ABC and inheriting
     @property
     @abstractmethod
     def _zero_slice(self): ...
@@ -1996,12 +2006,11 @@ class SliceRecordABC(ABC):
         seq_index, _, _ = self._get_index(rel_index, include_boundary=include_boundary)
 
         offset = self.offset
-        if self.is_reversed:
-            abs_index = offset + self.seq_len + seq_index + 1
-        else:
-            abs_index = offset + seq_index
-
-        return abs_index
+        return (
+            offset + self.seq_len + seq_index + 1
+            if self.is_reversed
+            else offset + seq_index
+        )
 
     def relative_position(self, abs_index: int, stop: bool = False):
         """converts an index on the original "Python sequence" into an index
@@ -2281,13 +2290,60 @@ class SliceRecordABC(ABC):
         )
 
 
-class SeqView(SliceRecordABC):
-    __slots__ = ("seq", "start", "stop", "step", "_offset", "_seqid", "_seq_len")
+class SeqViewABC(ABC):
+    # refactor: docstring
+
+    __slots__ = ()
+
+    @property
+    def seqid(self) -> str: ...
+
+    @property
+    @abstractmethod
+    def str_value(self) -> str: ...
+
+    @property
+    @abstractmethod
+    def array_value(self) -> array: ...
+
+    @property
+    @abstractmethod
+    def bytes_value(self) -> bytes: ...
+
+    @abstractmethod
+    def copy(self, sliced: bool = False): ...
+
+    def __str__(self) -> str:
+        return self.str_value
+
+    def __array__(self, dtype=None):
+        arr = self.array_value
+        if dtype is not None:
+            arr = arr.astype(dtype)
+        return arr
+
+    def __bytes__(self):
+        return self.bytes_value
+
+
+class SeqView(SeqViewABC, SliceRecordABC):
+    # refactor: docstring
+    __slots__ = (
+        "seq",
+        "alphabet",
+        "start",
+        "stop",
+        "step",
+        "_offset",
+        "_seqid",
+        "_seq_len",
+    )
 
     def __init__(
         self,
         *,
         seq: str,
+        alphabet: new_alphabet.AlphabetABC,
         start: OptInt = None,
         stop: OptInt = None,
         step: OptInt = None,
@@ -2298,7 +2354,7 @@ class SeqView(SliceRecordABC):
         if step == 0:
             raise ValueError("step cannot be 0")
         step = 1 if step is None else step
-
+        self.alphabet = alphabet
         func = _input_vals_pos_step if step > 0 else _input_vals_neg_step
         start, stop, step = func(len(seq), start, stop, step)
         self.seq = seq
@@ -2314,7 +2370,7 @@ class SeqView(SliceRecordABC):
 
     @property
     def _zero_slice(self):
-        return self.__class__(seq="")
+        return self.__class__(seq="", alphabet=self.alphabet)
 
     @property
     def seqid(self) -> str:
@@ -2325,32 +2381,19 @@ class SeqView(SliceRecordABC):
         return self._seq_len
 
     def _get_init_kwargs(self):
-        return {"seq": self.seq, "seqid": self.seqid}
+        return {"seq": self.seq, "seqid": self.seqid, "alphabet": self.alphabet}
 
     @property
-    def value(self):
+    def str_value(self):
         return self.seq[self.start : self.stop : self.step]
 
-    def replace(self, old: str, new: str):
-        new_seq = self.seq.replace(old, new)
-        if len(old) == len(new):
-            return self.__class__(
-                seq=new_seq,
-                start=self.start,
-                stop=self.stop,
-                step=self.step,
-                offset=self.offset,
-                seqid=self.seqid,
-                seq_len=self.seq_len,
-            )
-
-        return self.__class__(seq=new_seq)
-
-    def __iter__(self):
-        return iter(self.value)
+    @property
+    def array_value(self):
+        return self.alphabet.to_indices(self.str_value)
 
-    def __str__(self) -> str:
-        return self.value
+    @property
+    def bytes_value(self):
+        return self.str_value.encode("utf-8")
 
     def __repr__(self) -> str:
         seq = f"{self.seq[:10]}...{self.seq[-5:]}" if self.seq_len > 15 else self.seq
@@ -2396,11 +2439,12 @@ class SeqView(SliceRecordABC):
         if not sliced:
             return self.__class__(
                 seq=self.seq,
+                seqid=self.seqid,
+                alphabet=self.alphabet,
                 start=self.start,
                 stop=self.stop,
                 step=self.step,
                 offset=self.offset,
-                seqid=self.seqid,
                 seq_len=self.seq_len,
             )
         return self.from_rich_dict(self.to_rich_dict())
@@ -2425,40 +2469,40 @@ class RnaSequence(Sequence, NucleicAcidSequenceMixin):
 
 
 @singledispatch
-def _coerce_to_seqview(data, seqid):
+def _coerce_to_seqview(data, seqid, alphabet) -> SeqViewABC:
     from cogent3.core.alignment import Aligned
 
     if isinstance(data, Aligned):
-        return _coerce_to_seqview(str(data), seqid)
+        return _coerce_to_seqview(str(data), seqid, alphabet)
     raise NotImplementedError(f"{type(data)}")
 
 
 @_coerce_to_seqview.register
-def _(data: SeqView, seqid):
+def _(data: SeqViewABC, seqid, alphabet) -> SeqViewABC:
     return data
 
 
 @_coerce_to_seqview.register
-def _(data: Sequence, seqid):
-    return _coerce_to_seqview(data._seq, seqid)
+def _(data: Sequence, seqid, alphabet) -> SeqViewABC:
+    return _coerce_to_seqview(data._seq, seqid, alphabet)
 
 
 @_coerce_to_seqview.register
-def _(data: str, seqid):
-    return SeqView(seq=data, seqid=seqid)
+def _(data: str, seqid, alphabet) -> SeqViewABC:
+    return SeqView(seq=data, seqid=seqid, alphabet=alphabet)
 
 
 @_coerce_to_seqview.register
-def _(data: bytes, seqid):
+def _(data: bytes, seqid, alphabet) -> SeqViewABC:
     data = data.decode("utf8")
-    return SeqView(seq=data, seqid=seqid)
+    return SeqView(seq=data, seqid=seqid, alphabet=alphabet)
 
 
 @_coerce_to_seqview.register
-def _(data: tuple, seqid):
-    return _coerce_to_seqview("".join(data), seqid)
+def _(data: tuple, seqid, alphabet) -> SeqViewABC:
+    return _coerce_to_seqview("".join(data), seqid, alphabet)
 
 
 @_coerce_to_seqview.register
-def _(data: list, seqid):
-    return _coerce_to_seqview("".join(data), seqid)
+def _(data: list, seqid, alphabet) -> SeqViewABC:
+    return _coerce_to_seqview("".join(data), seqid, alphabet)


Test patch:
diff --git a/tests/test_core/test_alignment.py b/tests/test_core/test_alignment.py
index d1a22e54e..e5b849b74 100644
--- a/tests/test_core/test_alignment.py
+++ b/tests/test_core/test_alignment.py
@@ -522,7 +522,7 @@ class SequenceCollectionBaseTests(object):
         got = aln.degap()
         self.assertEqual(got.info.path, "blah")
 
-    def test_with_modified_termini(self):
+    def test_with_modified_termini(self):  # will not port
         """SequenceCollection.with_modified_termini should code trailing gaps as ?"""
         aln = self.Class({"s1": "AATGR--", "s2": "-T-AG?-"}, moltype=DNA)
         self.assertEqual(
@@ -2074,7 +2074,7 @@ def test_to_dna_raises():
 
 
 @pytest.mark.parametrize("cls", (SequenceCollection, Alignment))
-def test_annotate_from_gff3(cls):  # ported for SequenceCollection
+def test_annotate_from_gff3(cls):  # will not port
     """annotate_from_gff should work on data from gff3 files"""
     fasta_path = os.path.join("data/c_elegans_WS199_dna_shortened.fasta")
     gff3_path = os.path.join("data/c_elegans_WS199_shortened_gff.gff3")
@@ -2914,7 +2914,7 @@ def test_to_unaligned_seqs(data):
 
 @pytest.mark.parametrize("moltype", ("dna", "protein"))
 @pytest.mark.parametrize("cls", (SequenceCollection, Alignment, ArrayAlignment))
-def test_same_moltype(cls, moltype):
+def test_same_moltype(cls, moltype):  # ported for SequenceCollection
     moltype = get_moltype(moltype)
     data = dict(s1="ACGTT", s2="ACCTT")
     seqs = cls(data, moltype=moltype)
@@ -2923,7 +2923,7 @@ def test_same_moltype(cls, moltype):
 
 
 @pytest.mark.parametrize("cls", (SequenceCollection, Alignment, ArrayAlignment))
-def test_add(cls):
+def test_add(cls):  # will not port for SequenceCollection
     """__add__ should concatenate sequence data, by name"""
     align1 = cls({"a": "AAAA", "b": "TTTT", "c": "CCCC"})
     align2 = cls({"a": "GGGG", "b": "----", "c": "NNNN"})
@@ -3185,7 +3185,7 @@ def test_aln_from_fasta_parser(cls):
     "d", ({"a": "AAAAA", "b": "BBBBB"}, {"a": b"AAAAA", "b": b"BBBBB"})
 )
 @pytest.mark.parametrize("cls", (Alignment, ArrayAlignment, SequenceCollection))
-def test_init_dict(cls, d):
+def test_init_dict(cls, d):  # will not port for SequenceCollection
     """SequenceCollection init from dict should work as expected"""
     # from bytes strings
     a = cls(d, names=["a", "b"])
diff --git a/tests/test_core/test_new_alignment.py b/tests/test_core/test_new_alignment.py
index 1e4436361..e9e83999c 100644
--- a/tests/test_core/test_new_alignment.py
+++ b/tests/test_core/test_new_alignment.py
@@ -1,4 +1,6 @@
+import json
 import os
+import pathlib
 import re
 
 from warnings import catch_warnings, filterwarnings
@@ -12,7 +14,8 @@ import cogent3.core.new_moltype as new_moltype
 import cogent3.core.new_sequence as new_seq
 
 # todo: kath, update to using new_sequence.load_seq when implemented
-from cogent3 import load_seq
+from cogent3 import load_seq, open_
+from cogent3.core.annotation import Feature
 from cogent3.core.annotation_db import GffAnnotationDb, load_annotations
 
 
@@ -62,7 +65,7 @@ def sdv_s2(dna_sd: new_aln.SeqsData) -> new_aln.SeqDataView:
     return dna_sd.get_seq_view(seqid="seq2")
 
 
-@pytest.fixture
+@pytest.fixture(scope="function")
 def seqs() -> new_aln.SequenceCollection:
     data = {"seq1": "AAAAAA", "seq2": "TTTT", "seq3": "ATTCCCC"}
     return new_aln.make_unaligned_seqs(data, moltype="dna")
@@ -148,7 +151,7 @@ def test_seqs_data_default_attributes(dna_sd: new_aln.SeqsData):
 
 def test_seqs_data_view_zero_step_raises(dna_sd):
     with pytest.raises(ValueError):
-        new_aln.SeqDataView(seqs=dna_sd, step=0, seq_len=4)
+        new_aln.SeqDataView(seq=dna_sd, step=0, seq_len=4)
 
 
 @pytest.mark.parametrize("seqid", ["seq1", "seq2"])
@@ -197,7 +200,7 @@ def test_seqs_data_get_seq_view(str_seqs_dict, alpha, seqid):
     seq_len = len(seq)
     got = sd.get_seq_view(seqid)
     assert isinstance(got, new_aln.SeqDataView)
-    assert got.seqs == sd
+    assert got.seq == sd
     assert got.stop == seq_len
     assert got.seqid == seqid
     assert got.seq_len == seq_len
@@ -272,7 +275,7 @@ def test_seqs_data_make_seq_setget(dna_sd):
 @pytest.mark.parametrize("seq", ("seq1", "seq2"))
 def test_seqs_data_getitem_str_1(dna_sd, seq):
     got = dna_sd[seq]
-    assert got.seqs == dna_sd
+    assert got.seq == dna_sd
     assert got.seqid == seq
 
 
@@ -280,7 +283,7 @@ def test_seqs_data_getitem_str_1(dna_sd, seq):
 def test_seqs_data_getitem_int(str_seqs_dict, alpha, idx):
     sd = new_aln.SeqsData(data=str_seqs_dict, alphabet=alpha)
     got = sd[idx]
-    assert got.seqs == sd
+    assert got.seq == sd
     assert got.seqid == list(str_seqs_dict)[idx]
 
 
@@ -290,7 +293,7 @@ def test_seqs_data_getitem_str(
     seqid,
 ):
     got = dna_sd[seqid]
-    assert got.seqs == dna_sd
+    assert got.seq == dna_sd
     assert got.seqid == seqid
 
 
@@ -370,7 +373,7 @@ def test_seqs_data_to_alphabet_invalid():
     ],
 )
 def test_seq_data_view_slice_returns_self(seq1: str, index: slice):
-    sdv = new_aln.SeqDataView(seqs=seq1, seqid="seq1", seq_len=len(seq1))
+    sdv = new_aln.SeqDataView(seq=seq1, seqid="seq1", seq_len=len(seq1))
     got = sdv[index]
     assert isinstance(got, new_aln.SeqDataView)
 
@@ -450,7 +453,7 @@ def test_get_aligned_view(aligned_dict, seqid):
     ad = new_aln.AlignedData.from_gapped_seqs(aligned_dict)
     got = ad.get_aligned_view(seqid)
     assert isinstance(got, new_aln.AlignedDataView)
-    assert got.seqs == ad
+    assert got.seq == ad
     assert got.stop == ad.align_len
     assert got.seq_len == ad.align_len
 
@@ -641,6 +644,14 @@ def test_sequence_collection_init_ordered(ordered1, ordered2):
     assert sec.names == ["c", "a"]
 
 
+@pytest.mark.xfail(reason="todo: kath, load_unaligned_seqs not creating new style seqs")
+def test_sequence_collection_info_source():
+    """info.source exists if load seqs given a filename"""
+    path = pathlib.Path("data/brca1.fasta")
+    seqs = new_aln.load_unaligned_seqs(path)
+    assert seqs.info.source == str(path)
+
+
 def test_sequence_collection_init_ambig():
     """SequenceCollection should tolerate ambiguous chars"""
     _ = new_aln.make_unaligned_seqs(["AAA", "CCC"], moltype="dna")
@@ -927,8 +938,20 @@ def test_sequence_collection_get_seq():
 
 def test_sequence_collection_degap():
     """SequenceCollection.degap should strip gaps from each seq"""
-    seqs = new_aln.make_unaligned_seqs({"s1": "ATGRY?", "s2": "T-AG??"}, moltype="dna")
-    assert seqs.degap().to_dict() == {"s1": "ATGRY", "s2": "TAG"}
+    data = {"s1": "ATGRY?", "s2": "T-AG??"}
+    seqs = new_aln.make_unaligned_seqs(data, moltype="dna")
+    got = seqs.degap().to_dict()
+    expect = {"s1": "ATGRY", "s2": "TAG"}
+    assert got == expect
+
+
+def test_sequence_collection_degap_info():
+    """.degap should preserve info attributes"""
+    data = {"s1": "ATGRY?", "s2": "T-AG??"}
+    aln = new_aln.make_unaligned_seqs(data, moltype="dna")
+    aln.info.path = "blah"
+    got = aln.degap()
+    assert got.info.path == "blah"
 
 
 def test_sequence_collection_to_fasta():
@@ -1017,11 +1040,126 @@ def test_sequence_collection_has_terminal_stop_strict():
         seq_coll.has_terminal_stop(gc=1, strict=True)
 
 
+@pytest.mark.xfail(
+    reason="todo: relies on Sequence.get_translation which requires a codon alphabet class'"
+)
+def test_sequence_collection_get_translation_trim_stop():
+    data = {"seq1": "GATTCCTAG", "seq2": "GATTCCTCC"}
+    seq_coll = new_aln.make_unaligned_seqs(data, moltype="dna")
+    got = seq_coll.get_translation(trim_stop=True)
+    expect = {"seq1": "DS", "seq2": "DSS"}
+    assert got.to_dict() == expect
+
+
+@pytest.mark.xfail(
+    reason="todo: relies on Sequence.get_translation which requires a codon alphabet class'"
+)
+def test_sequence_collection_get_translation_raises():
+    """should raise error if self.moltype is not a nucleic acid"""
+    data = {"seq1": "PAR", "seq2": "PQR"}
+    seq_coll = new_aln.make_unaligned_seqs(data, moltype="protein")
+    with pytest.raises(TypeError):
+        _ = seq_coll.get_translation(trim_stop=True)
+
+
+@pytest.mark.xfail(
+    reason="todo: relies on Sequence.get_translation which requires a codon alphabet class'"
+)
+@pytest.mark.parametrize(
+    "seqs", ({"seq1": "GATTTT", "seq2": "GATC??"}, {"seq1": "GAT---", "seq2": "?GATCT"})
+)
+def test_sequence_collection_get_translation2(seqs):
+    """SequenceCollection.get_translation translates each seq"""
+    seq_coll = new_aln.make_unaligned_seqs(seqs, moltype="dna")
+    got = seq_coll.get_translation()
+    assert len(got) == 2
+    assert got.moltype == new_moltype.PROTEIN
+
+
+@pytest.mark.xfail(
+    reason="todo: relies on Sequence.get_translation which requires a codon alphabet class'"
+)
+def test_sequence_collection_get_translation_with_stop():
+    data = {"seq1": "GATTAG", "seq2": "?GATCT"}
+    seq_coll = new_aln.make_unaligned_seqs(data, moltype="dna")
+    got = seq_coll.get_translation(include_stop=True)
+    assert got.to_dict() == {"seq1": "D*", "seq2": "XS"}
+
+
+@pytest.mark.xfail(
+    reason="todo: relies on Sequence.get_translation which requires a codon alphabet class'"
+)
+@pytest.mark.parametrize(
+    "data", ({"seq1": "GATTTT", "seq2": "GATC??"}, {"seq1": "GAT---", "seq2": "?GATCT"})
+)
+def test_sequence_collection_get_translation_error(data):
+    """SequenceCollection.get_translation translates each seq"""
+    # check for a failure when no moltype specified
+    seq_coll = new_aln.make_unaligned_seqs(data, moltype="dna")
+    with pytest.raises(TypeError):
+        seq_coll.get_translation()
+
+
+@pytest.mark.xfail(
+    reason="todo: relies on Sequence.get_translation which requires a codon alphabet class'"
+)
+@pytest.mark.parametrize(
+    "data",
+    (
+        {"seq1": "GATTTT", "seq2": "GATC??"},
+        {"seq1": "GAT---", "seq2": "?GATCT"},
+    ),
+)
+def test_sequence_collection_get_translation_info(data):
+    """SequenceCollection.get_translation preserves info attribute"""
+    seq_coll = new_aln.make_unaligned_seqs(data, moltype="dna")
+    got = seq_coll.get_translation()
+    assert got.info["key"] == "value"
+
+
+@pytest.mark.xfail(
+    reason="todo: relies on Sequence.get_translation which requires a codon alphabet class'"
+)
+def test_sequence_collection_get_translation_incomplete():
+    """get translation works on incomplete codons"""
+    data = {"seq1": "GATN--", "seq2": "?GATCT"}
+    seq_coll = new_aln.make_unaligned_seqs(data, moltype="dna")
+    got = seq_coll.get_translation(incomplete_ok=True)
+    assert got.to_dict() == {"seq1": "D?", "seq2": "XS"}
+    with pytest.raises(new_alpha.AlphabetError):
+        _ = seq_coll.get_translation(incomplete_ok=False)
+
+
+@pytest.mark.parametrize(
+    "gc,seqs",
+    (
+        (1, ("--AT-CTGA", "GATAAATT?")),
+        (1, ("ACGTGA---", "ACGAC----", "ACGCAATGA")),
+        (1, ("CCTCA-", "ATTTTA")),
+        (2, ("GATTTT", "TCCAGG")),
+    ),
+)
+def test_sequence_collection_trim_stop_codons(gc, seqs):
+    data = {f"s{i}": s for i, s in enumerate(seqs)}
+
+    expect = {}
+    for k, v in data.items():
+        if "-" in v:
+            v = re.sub("(TGA|AGG)(?=[-]*$)", "---", v)
+        else:
+            v = re.sub("(TGA|AGG)", "", v)
+        expect[k] = v
+    seqs = new_aln.make_unaligned_seqs(data, moltype="dna")
+    got = seqs.trim_stop_codons(gc=gc).to_dict()
+
+    assert got == expect
+
+
 @pytest.mark.parametrize(
     "gc,seqs",
     ((1, ("T-CTGC", "GATAA?")), (2, ("GATTTT", "TCCCGG")), (1, ("CCTGC", "GATAA"))),
 )
-def test_sequence_collection_trim_terminal_stops_no_stop(gc, seqs):
+def test_sequence_collection_trim_stop_codons_no_stop(gc, seqs):
     data = {f"s{i}": s for i, s in enumerate(seqs)}
     seqs = new_aln.make_unaligned_seqs(data, moltype="dna")
     got = seqs.trim_stop_codons(gc=gc)
@@ -1031,7 +1169,7 @@ def test_sequence_collection_trim_terminal_stops_no_stop(gc, seqs):
 @pytest.mark.parametrize(
     "data", ({"s1": "CCTCA", "s2": "ATTTT"}, {"s1": "CCTCA-", "s2": "ATTTTA"})
 )
-def test_sequence_collection_trim_terminal_stops_strict(data):
+def test_sequence_collection_trim_stop_codons_strict(data):
     seqs = new_aln.make_unaligned_seqs(data, moltype="dna")
     with pytest.raises(new_alpha.AlphabetError):
         seqs.trim_stop_codons(gc=1, strict=True)
@@ -1040,13 +1178,26 @@ def test_sequence_collection_trim_terminal_stops_strict(data):
 def test_sequence_collection_trim_stop_codons_info():
     """trim_stop_codons should preserve info attribute"""
     data = {"seq1": "ACGTAA", "seq2": "ACGACG", "seq3": "ACGCGT"}
-    coll = new_aln.make_unaligned_seqs(
+    seq_coll = new_aln.make_unaligned_seqs(
         data,
         moltype="dna",
         info={"key": "value"},
     )
-    coll = coll.trim_stop_codons()
-    assert coll.info["key"] == "value"
+    seq_coll = seq_coll.trim_stop_codons()
+    assert seq_coll.info["key"] == "value"
+
+
+def test_sequence_collection_trim_stop_codons_annotation_db(gff_db):
+    """trim_stop_codons should preserve info attribute"""
+    data = {"seq_1": "ACGTAA", "seq_2": "ACGACG", "seq_3": "ACGCGT"}
+    seq_coll = new_aln.make_unaligned_seqs(
+        data,
+        moltype="dna",
+        info={"key": "value"},
+    )
+    seq_coll.annotation_db = gff_db
+    trimmed = seq_coll.trim_stop_codons()
+    assert trimmed.annotation_db == seq_coll.annotation_db
 
 
 @pytest.fixture(scope="function")
@@ -1149,7 +1300,17 @@ def test_sequence_collection_get_identical_sets_text():
     assert got == expect
 
 
-def test_sequence_collection_get_similar():
+def test_sequence_collection_get_identical_sets_ragged_raises(ragged):
+    with pytest.raises(ValueError):
+        _ = ragged.get_identical_sets()
+
+
+def rev(seq):
+    return seq[::-1]
+
+
+@pytest.mark.parametrize("transform", [rev, lambda x: x, None])
+def test_sequence_collection_get_similar(transform):
     data = {
         "a": "AAAAAAAAAA",  # target
         "b": "AAAAAAAAAA",  # identical
@@ -1160,13 +1321,17 @@ def test_sequence_collection_get_similar():
     }
     seqs = new_aln.make_unaligned_seqs(data, moltype="dna")
     target = seqs.get_seq("a")
-    got = seqs.get_similar(target, min_similarity=0.8)
+    got = seqs.get_similar(target, min_similarity=0.8, transform=transform)
     assert got.names == ["a", "b", "c", "d"]
-    got = seqs.get_similar(target, min_similarity=0.81)
+    got = seqs.get_similar(target, min_similarity=0.81, transform=transform)
     assert got.names == ["a", "b", "c"]
-    got = seqs.get_similar(target, min_similarity=0.75, max_similarity=0.9)
+    got = seqs.get_similar(
+        target, min_similarity=0.75, max_similarity=0.9, transform=transform
+    )
     assert got.names == ["c", "d"]
-    got = seqs.get_similar(target, min_similarity=0.75, max_similarity=0.89)
+    got = seqs.get_similar(
+        target, min_similarity=0.75, max_similarity=0.89, transform=transform
+    )
     assert got.names == ["d"]
 
 
@@ -1179,6 +1344,60 @@ def test_sequence_collection_init_annotated_seqs():
     assert len(features) == 1
 
 
+def test_sequence_collection_get_motif_probs():
+    data = {"a": "AC-", "b": "AC", "c": "AC"}
+    aln = new_aln.make_unaligned_seqs(data, moltype="dna")
+    got = aln.get_motif_probs()
+    expect = {"A": 0.5, "C": 0.5, "G": 0.0, "T": 0.0}
+    assert got == expect
+
+    # exclude unobserved
+    data = {"a": "AC-", "b": "AC", "c": "AC"}
+    aln = new_aln.make_unaligned_seqs(data, moltype="dna")
+    got = aln.get_motif_probs(exclude_unobserved=True)
+    expect = {"A": 0.5, "C": 0.5}
+    assert got == expect
+
+    # allow gap
+    data = {"a": "----", "b": "ACGT", "c": "ACGT"}
+    aln = new_aln.make_unaligned_seqs(data, moltype="dna")
+    got = aln.get_motif_probs(allow_gap=True)
+    expect = {"A": 2 / 12, "C": 2 / 12, "G": 2 / 12, "T": 2 / 12, "-": 4 / 12}
+    assert got == expect
+
+    # add pseudocounts
+    data = {"a": "AC-", "b": "AC", "c": "AC"}
+    aln = new_aln.make_unaligned_seqs(data, moltype="dna")
+    got = aln.get_motif_probs(pseudocount=1)
+    expect = {"A": 4 / 10, "C": 4 / 10, "G": 1 / 10, "T": 1 / 10}
+
+    # pseudocount and allow gap
+    data = {"a": "AC-", "b": "AC", "c": "AC"}
+    aln = new_aln.make_unaligned_seqs(data, moltype="dna")
+    got = aln.get_motif_probs(pseudocount=1)
+    expect = {"A": 4 / 11, "C": 4 / 11, "G": 1 / 11, "T": 1 / 11, "-": 1 / 11}
+
+
+def test_sequence_collection_get_motif_probs_protein():
+    data = {"a": "MVSB", "b": "MVS", "c": "MVP"}
+    aln = new_aln.make_unaligned_seqs(data, moltype="protein")
+    got = aln.get_motif_probs()
+    alphabet = aln.moltype.alphabet
+    expect = {k: 0 for k in alphabet}
+    expect["M"] = 1 / 3
+    expect["V"] = 1 / 3
+    expect["S"] = 2 / 9
+    expect["P"] = 1 / 9
+    assert got == expect
+
+    got = aln.get_motif_probs(exclude_unobserved=True)
+    expect = {"M": 1 / 3, "V": 1 / 3, "S": 2 / 9, "P": 1 / 9}
+    assert got == expect
+
+    got = aln.get_motif_probs(include_ambiguity=True, exclude_unobserved=True)
+    expect = {"M": 3 / 10, "V": 3 / 10, "S": 2 / 10, "P": 1 / 10, "B": 1 / 10}
+
+
 def test_sequence_collection_counts_per_seq():
     """SequenceCollection.counts_per_seq handles motif length, allow_gaps etc.."""
     data = {"a": "AAAA??????", "b": "CCCGGG--NN", "c": "CCGGTTCCAA"}
@@ -1206,6 +1425,24 @@ def test_sequence_collection_counts_per_seq():
         assert c[k] == expect[k]
 
 
+def test_sequence_collection_probs_per_seq():
+    data = {"seq1": "AA??", "seq2": "CG-N", "seq3": "CGAA"}
+    coll = new_aln.make_unaligned_seqs(data, moltype="dna")
+    got = coll.probs_per_seq()
+    assert got["seq1", "A"] == 1.0
+    assert got["seq2", "C"] == 0.5
+    assert got["seq2", "G"] == 0.5
+    assert got["seq3", "C"] == 0.25
+    assert got["seq3", "G"] == 0.25
+    assert got["seq3", "A"] == 0.5
+
+    got = coll.probs_per_seq(allow_gap=True)
+    assert got["seq2", "-"] == 1 / 3
+
+    got = coll.probs_per_seq(include_ambiguity=True, allow_gap=True)
+    assert got["seq2", "N"] == 0.25
+
+
 def test_sequence_collection_counts():
     """SequenceCollection.counts handles motif length, allow_gaps etc.."""
     data = {"a": "AAAA??????", "b": "CCCGGG--NN"}
@@ -1219,7 +1456,7 @@ def test_sequence_collection_counts():
     assert all(got[k] == v for k, v in expect.items())
 
     got = coll.counts(motif_length=2, allow_gap=True)
-    expect.update({"--": 1})
+    expect["--"] = 1
     assert all(got[k] == v for k, v in expect.items())
 
     got = coll.counts(motif_length=2, include_ambiguity=True, allow_gap=True)
@@ -1275,6 +1512,59 @@ def test_sequence_collection_get_annotations_from_any_seq():
     assert "biotype='exon', name='annotation3', map=[3:6]/9" in str(got[0])
 
 
+def test_sequence_collection_add_feature(seqs):
+    _ = seqs.add_feature(seqid="seq1", biotype="xyz", name="abc", spans=[(1, 2)])
+    assert seqs.annotation_db.num_matches() == 1
+
+    # if seqid is not in seqs, raise error
+    with pytest.raises(ValueError):
+        _ = seqs.add_feature(seqid="bad_seq", biotype="xyz", name="abc", spans=[(1, 2)])
+
+
+def test_sequence_collection_make_feature(seqs):
+    data = {"seqid": "seq1", "biotype": "xyz", "name": "abc", "spans": [(1, 2)]}
+    feat = seqs.make_feature(feature=data)
+    assert isinstance(feat, Feature)
+    assert seqs.annotation_db.num_matches() == 0
+
+
+def test_sequence_collection_get_features(seqs):
+    # no annotation db should return empty list
+    got = seqs.get_features(seqid="seq1")
+    assert not list(got)
+
+    seqs.add_feature(seqid="seq1", biotype="xyz", name="abc", spans=[(1, 3)])
+    seqs.add_feature(seqid="seq2", biotype="xyz", name="abc", spans=[(1, 3)])
+    seqs.add_feature(seqid="seq3", biotype="xyz", name="abcde", spans=[(3, 5)])
+    got = seqs.get_features(seqid="seq1")
+    assert len(list(got)) == 1
+
+    got = seqs.get_features(biotype="xyz")
+    assert len(list(got)) == 3
+
+    got = seqs.get_features(name="abc")
+    assert len(list(got)) == 2
+
+    got = seqs.get_features(biotype="xyz", name="abc")
+    assert len(list(got)) == 2
+
+    got = seqs.get_features(biotype="xyz", start=0, stop=5, allow_partial=True)
+    assert len(list(got)) == 3
+
+    got = seqs.get_features(start=2, stop=6, allow_partial=False)
+    assert len(list(got)) == 1
+
+    got = seqs.get_features(biotype="xyz", start=2, stop=4, allow_partial=True)
+    assert len(list(got)) == 3
+
+    with pytest.raises(ValueError):
+        _ = list(
+            seqs.get_features(
+                seqid="bad_seq", biotype="xyz", start=2, stop=4, allow_partial=True
+            )
+        )
+
+
 def test_sequence_collection_copy_annotations(gff_db):
     """copy_annotations copies records from annotation db"""
     data = {"seq1": "ACGU", "seq2": "CGUA", "test_seq": "CCGU"}
@@ -1285,6 +1575,53 @@ def test_sequence_collection_copy_annotations(gff_db):
     seq_coll.copy_annotations(gff_db)
     assert seq_coll.annotation_db.num_matches() == expect
 
+    # copy annotations with no current annotations
+    seq_coll = new_aln.make_unaligned_seqs(data, moltype="rna")
+    seq_coll.copy_annotations(gff_db)
+    assert seq_coll.annotation_db.num_matches() == gff_db.num_matches()
+
+
+@pytest.mark.xfail(reason="todo: this just hangs... ")
+def test_sequence_collection_copy_annotations_same_annotations(gff_db):
+    # define a timeout for this test at 5 seconds
+    import signal
+
+    def handler(signum, frame):
+        raise TimeoutError("Test timed out")
+
+    signal.signal(signal.SIGALRM, handler)
+    signal.alarm(5)
+
+    data = {"seq1": "ACGU", "seq2": "CGUA", "test_seq": "CCGU"}
+    seq_coll = new_aln.make_unaligned_seqs(data, moltype="rna")
+    seq_coll = new_aln.make_unaligned_seqs(data, moltype="rna")
+
+    # copy annotations with the same annotation_db
+    seq_coll.annotation_db = gff_db
+    seq_coll.copy_annotations(gff_db)
+
+    assert seq_coll.annotation_db.num_matches() == gff_db.num_matches()
+
+
+def test_sequence_collection_copy_annotations_none_matching(gff_db):
+    """copy annotations should old copy annotations for matching seqids"""
+    data = {"name1": "ACGU", "name2": "CGUA", "name_3": "CCGU"}
+    seq_coll = new_aln.make_unaligned_seqs(data, moltype="rna")
+    seq_coll.add_feature(seqid="name1", biotype="xyz", name="abc", spans=[(1, 2)])
+    seq_coll.add_feature(seqid="name2", biotype="xyzzz", name="abc", spans=[(1, 2)])
+    expect = seq_coll.annotation_db.num_matches()
+    assert gff_db.num_matches() > 0
+    seq_coll.copy_annotations(gff_db)
+    assert seq_coll.annotation_db.num_matches() == expect
+
+
+def test_sequence_collection_copy_annotations_no_db(gff_db):
+    data = {"seq1": "ACGU", "seq2": "CGUA", "test_seq": "CCGU"}
+    seq_coll = new_aln.make_unaligned_seqs(data, moltype="rna")
+
+    seq_coll.copy_annotations(gff_db)
+    assert seq_coll.annotation_db.num_matches() == gff_db.num_matches()
+
 
 def test_sequence_collection_get_seq_annotated():
     """SequenceCollection.get_seq should return specified seq"""
@@ -1344,6 +1681,14 @@ def test_sequence_collection_copy_annotations_incompat_type_fails(seqcoll_db, se
         seqcoll_db.copy_annotations(seqs)
 
 
+@pytest.mark.parametrize("moltype", ("dna", "protein"))
+def test_sequence_collection_to_moltype_same_moltype(moltype):
+    data = dict(s1="ACGTT", s2="ACCTT")
+    seqs = new_aln.make_unaligned_seqs(data, moltype=moltype)
+    got = seqs.to_moltype(moltype=moltype)
+    assert got is seqs
+
+
 def test_sequence_collection_to_moltype_with_gaps():
     """correctly convert to specified moltype"""
     data = {"seq1": "ACGTACGTA", "seq2": "ACCGAA---", "seq3": "ACGTACGTT"}
@@ -1352,6 +1697,11 @@ def test_sequence_collection_to_moltype_with_gaps():
     assert dna_seqs.moltype.label == "dna"
     assert dna_seqs.to_dict() == data
 
+    # we can convert from text to dna to rna
+    rna_seqs = dna_seqs.to_moltype("rna")
+    assert rna_seqs.moltype.label == "rna"
+
+    # but not from text to rna directly
     with pytest.raises(ValueError):
         seqs.to_moltype("rna")
 
@@ -1377,27 +1727,81 @@ def test_sequence_collection_to_moltype_annotation_db():
     assert len(dna.annotation_db) == 3
 
 
-def test_sequence_collection_to_moltype_same_moltype():
+def test_sequence_collection_to_dna():
+    """correctly convert to dna"""
+    data = {"seq1": "ACGTACGTA", "seq2": "ACCGAA---", "seq3": "ACGTACGTT"}
+    seqs = new_aln.make_unaligned_seqs(data, moltype="text")
+
+    # convert from text to dna
+    dna_seqs = seqs.to_dna()
+    assert dna_seqs.moltype.label == "dna"
+    assert dna_seqs.to_dict() == data
+
+    # convert from rna to dna
+    data = {"seq1": "ACGUACGUA", "seq2": "ACCGAA---", "seq3": "ACGUACGUU"}
+    seqs = new_aln.make_unaligned_seqs(data, moltype="RNA")
+    dna_seqs = seqs.to_dna()
+    assert dna_seqs.moltype.label == "dna"
+    assert dna_seqs.to_dict() == {
+        name: seq.replace("U", "T") for name, seq in data.items()
+    }
+
+
+def test_sequence_collection_to_rna():
+    data = {"seq1": "ACGUACGUA", "seq2": "ACCGAA---", "seq3": "ACGUACGUU"}
+    seqs = new_aln.make_unaligned_seqs(data, moltype="text")
+
+    # convert from text to rna
+    rna_seqs = seqs.to_rna()
+    assert rna_seqs.moltype.label == "rna"
+    assert rna_seqs.to_dict() == data
+
+    # convert from dna to rna
     data = {"seq1": "ACGTACGTA", "seq2": "ACCGAA---", "seq3": "ACGTACGTT"}
     seqs = new_aln.make_unaligned_seqs(data, moltype="dna")
-    to_same_seqs = seqs.to_moltype("dna")
-    assert to_same_seqs is seqs
+    rna_seqs = seqs.to_rna()
+    assert rna_seqs.moltype.label == "rna"
+    assert rna_seqs.to_dict() == {
+        name: seq.replace("T", "U") for name, seq in data.items()
+    }
 
 
-def test_sequence_collection_dotplot():
-    """exercising dotplot method"""
+@pytest.fixture(scope="function")
+def dotplot_seqs():
     data = {
         "Human": "CAGATTTGGCAGTT-",
         "Mouse": "CAGATTCAGCAGGTG",
-        "Rat": "CAGATTCAGCAGGTG",
+        "Rat": "CAGATTCAGCAGG--G",
     }
-    seqs = new_aln.make_unaligned_seqs(
-        data,
-        moltype="dna",
-    )
-    _ = seqs.dotplot()
+    return new_aln.make_unaligned_seqs(data, moltype="dna")
+
+
+def test_sequence_collection_dotplot(dotplot_seqs):
+    """exercising dotplot method"""
+    # with provided names
+    plot = dotplot_seqs.dotplot(name1="Human", name2="Mouse")
+    assert str(plot.seq1) != str(plot.seq2)
+
+    # without providing names
+    plot = dotplot_seqs.dotplot()
+    assert str(plot.seq1) != str(plot.seq2)
+
+    # providing only one name
+    plot = dotplot_seqs.dotplot(name1="Human")
+    assert str(plot.seq1) != str(plot.seq2)
+
+    # a collection of one sequence should make dotplot with itself
+    less_seqs = dotplot_seqs.take_seqs("Human")
+    plot = less_seqs.dotplot()
+    assert str(plot.seq1) == str(plot.seq2)
+
+    # k larger than window should raise an error
     with pytest.raises(AssertionError):
-        seqs.dotplot(window=5, k=11)
+        dotplot_seqs.dotplot(window=5, k=11)
+
+    # names not in the collection should raise an error
+    with pytest.raises(ValueError):
+        dotplot_seqs.dotplot(name1="Human", name2="Dog")
 
 
 def test_sequence_collection_dotplot_annotated():
@@ -1523,6 +1927,13 @@ def test_sequence_collection_get_lengths(ambigs_coll):
     assert got == expect
 
 
+def test_sequence_collection_get_lengths_include_ambiguity(ambigs_coll):
+    # NOTE '?' is excluded as it could be a gap
+    got = ambigs_coll.get_lengths(include_ambiguity=True)
+    expect = {"a": 4, "b": 8}
+    assert got == expect
+
+
 def test_sequence_collection_get_lengths_allow_gap(ambigs_coll):
     got = ambigs_coll.get_lengths(include_ambiguity=True, allow_gap=True)
     expect = {"a": 10, "b": 10}
@@ -1538,6 +1949,18 @@ def test_sequence_collection_strand_symmetry():
     assert numpy.allclose(result["seq2"].observed.array, [[3, 0], [2, 1]])
 
 
+@pytest.mark.xfail(reason="todo: kath, decide on how to handle rename_seqs")
+def test_sequence_collection_rename_seqs():
+    """successfully rename sequences"""
+    data = {"seq1": "ACGTACGTA", "seq2": "ACCGAA---", "seq3": "ACGTACGTT"}
+    seqs = new_aln.make_unaligned_seqs(data, moltype="dna")
+    new = seqs.rename_seqs(lambda x: x.upper())
+    expect = {n.upper() for n in data}
+    assert set(new.names) == expect
+    # renamed contains the name map as info attribute
+    assert new.info.name_map == {k.upper(): k for k in data}
+
+
 def test_sequence_collection_apply_pssm():
     """should successfully produce pssm scores"""
     from cogent3.parse import jaspar
@@ -1622,6 +2045,18 @@ def test_sequence_collection_get_seq_entropy():
     assert numpy.allclose(entropy, numpy.array([e, 1.5]))
 
 
+@pytest.mark.xfail(reason="todo: kath, design of serialisation is unresolved")
+def test_sequence_collection_write_to_json(tmp_path):
+    # test writing to json file
+    data = {"a": "AAAA", "b": "TTTT", "c": "CCCC"}
+    aln = new_aln.make_unaligned_seqs(data, moltype="dna")
+    path = str(tmp_path / "sample.json")
+    aln.write(path)
+    with open_(path) as fn:
+        got = json.loads(fn.read())
+        assert got == aln.to_rich_dict()
+
+
 @pytest.mark.parametrize("moltype", ("dna", "rna"))
 def test_sequence_collection_distance_matrix_singleton_collection(moltype):
     """SequenceCollection.distance_matrix() should raise error if collection
@@ -1654,7 +2089,7 @@ def test_sequence_collection_distance_matrix_same_seq(moltype):
 
 
 @pytest.mark.parametrize("moltype", ("protein", "text", "bytes"))
-def test_sequence_collection_distance_matrix_fails_wrong_moltype(moltype):
+def test_sequence_collection_distance_matrix_raises_wrong_moltype(moltype):
     data = {"s1": "ACGTA", "s2": "ACGTA"}
     seqs = new_aln.make_unaligned_seqs(data, moltype=moltype)
     with pytest.raises(NotImplementedError):
diff --git a/tests/test_core/test_new_sequence.py b/tests/test_core/test_new_sequence.py
index dfb9bbb2c..5fc7f9bde 100644
--- a/tests/test_core/test_new_sequence.py
+++ b/tests/test_core/test_new_sequence.py
@@ -17,6 +17,16 @@ from cogent3.util.deserialise import deserialise_object
 from cogent3.util.misc import get_object_provenance
 
 
+@pytest.fixture(scope="function")
+def dna_alphabet():
+    return new_moltype.DNA.degen_gapped_alphabet
+
+
+@pytest.fixture(scope="function")
+def ascii_alphabet():
+    return new_moltype.ASCII.alphabet
+
+
 @pytest.mark.parametrize("name", ("dna", "rna", "protein", "protein_with_stop", "text"))
 def test_moltype_make_seq(name):
     raw = "ACGGA"
@@ -36,16 +46,20 @@ def test_moltype_make_bytes_seq():
 
 
 # Tests from test_sequence.py
+@pytest.mark.xfail(reason="refactor: how to serialise an alphabet")
 def test_to_json():
     """to_json roundtrip recreates to_dict"""
-    r = new_moltype.DNA.make_seq(seq="AAGGCC", name="seq1")
+    dna = new_moltype.DNA
+    r = dna.make_seq(seq="AAGGCC", name="seq1")
     got = json.loads(r.to_json())
-    seq = new_sequence.SeqView(seq="AAGGCC", seqid="seq1").to_rich_dict()
+    seq = new_sequence.SeqView(
+        seq="AAGGCC", seqid="seq1", alphabet=dna.most_degen_alphabet()
+    ).to_rich_dict()
 
     expect = {
         "name": "seq1",
         "seq": seq,
-        "moltype": r.moltype.label,
+        "moltype": dna.label,
         "info": None,
         "type": get_object_provenance(r),
         "version": __version__,
@@ -77,9 +91,9 @@ def test_offset_with_multiple_slices(DATA_DIR):
 
 
 @pytest.mark.parametrize("coord", ("start", "stop"))
-def test_seqview_to_rich_dict(coord):
+def test_seqview_to_rich_dict(coord, dna_alphabet):
     parent = "ACCCCGGAAAATTTTTTTTTAAGGGGGAAAAAAAAACCCCCCC"
-    sv = new_sequence.SeqView(seq=parent)
+    sv = new_sequence.SeqView(seq=parent, alphabet=dna_alphabet)
     plus = sv.to_rich_dict()
     minus = sv[::-1].to_rich_dict()
     plus = plus.pop("init_args")
@@ -94,9 +108,9 @@ def test_seqview_to_rich_dict(coord):
     reason="NotImplementedError: deserialising 'cogent3.core.new_sequence.SeqView' from json"
 )
 @pytest.mark.parametrize("reverse", (False, True))
-def test_seqview_round_trip(reverse):
+def test_seqview_round_trip(reverse, dna_alphabet):
     parent = "ACCCCGGAAAATTTTTTTTTAAGGGGGAAAAAAAAACCCCCCC"
-    sv = new_sequence.SeqView(seq=parent)
+    sv = new_sequence.SeqView(seq=parent, alphabet=dna_alphabet)
     sv = sv[::-1] if reverse else sv
 
     rd = sv.to_rich_dict()
@@ -106,10 +120,10 @@ def test_seqview_round_trip(reverse):
 
 
 @pytest.mark.parametrize("reverse", (False, True))
-def test_sliced_seqview_rich_dict(reverse):
+def test_sliced_seqview_rich_dict(reverse, dna_alphabet):
     parent = "ACCCCGGAAAATTTTTTTTTAAGGGGGAAAAAAAAACCCCCCC"
     sl = slice(2, 13)
-    sv = new_sequence.SeqView(seq=parent)[sl]
+    sv = new_sequence.SeqView(seq=parent, alphabet=dna_alphabet)[sl]
     sv = sv[::-1] if reverse else sv
     rd = sv.to_rich_dict()
     assert rd["init_args"]["seq"] == parent[sl]
@@ -126,14 +140,14 @@ def test_sliced_seqview_rich_dict(reverse):
     ),
 )
 @pytest.mark.parametrize("offset", (4, 0))
-def test_parent_start_stop(sl, offset):
+def test_parent_start_stop(sl, offset, ascii_alphabet):
     data = "0123456789"
     # check our slice matches the expectation for rest of test
     expect = "234" if sl.step > 0 else "432"
-    sv = new_sequence.SeqView(seq=data)
+    sv = new_sequence.SeqView(seq=data, alphabet=ascii_alphabet)
     sv.offset = offset
     sv = sv[sl]
-    assert sv.value == expect
+    assert sv.str_value == expect
     # now check that start / stop are always the same
     # irrespective of step sign
     assert (sv.parent_start, sv.parent_stop) == (2 + offset, 5 + offset)
@@ -146,26 +160,26 @@ def test_parent_start_stop(sl, offset):
         slice(None, None, -1),  # slice whole sequence minus strand
     ),
 )
-def test_parent_start_stop_limits(sl):
+def test_parent_start_stop_limits(sl, ascii_alphabet):
     data = "0123456789"
     # check our slice matches the expectation for rest of test
     expect = data[sl]
-    sv = new_sequence.SeqView(seq=data)
+    sv = new_sequence.SeqView(seq=data, alphabet=ascii_alphabet)
     sv = sv[sl]
-    assert sv.value == expect
+    assert sv.str_value == expect
     # now check that start / stop are always the same
     # irrespective of step sign
     assert (sv.parent_start, sv.parent_stop) == (0, 10)
 
 
 @pytest.mark.parametrize("rev", (False, True))
-def test_parent_start_stop_empty(rev):
+def test_parent_start_stop_empty(rev, ascii_alphabet):
     data = "0123456789"
     # check our slice matches the expectation for rest of test
     expect = ""
-    sv = new_sequence.SeqView(seq=data)
+    sv = new_sequence.SeqView(seq=data, alphabet=ascii_alphabet)
     sv = sv[0 : 0 : -1 if rev else 1]
-    assert sv.value == expect
+    assert sv.str_value == expect
     # now check that start / stop are always the same
     # irrespective of step sign
     assert (sv.parent_start, sv.parent_stop) == (0, 0)
@@ -173,15 +187,15 @@ def test_parent_start_stop_empty(rev):
 
 @pytest.mark.parametrize("rev", (False, True))
 @pytest.mark.parametrize("index", range(9))
-def test_parent_start_stop_singletons(index, rev):
+def test_parent_start_stop_singletons(index, rev, ascii_alphabet):
     data = "0123456789"
     start, stop = (-(10 - index), -(10 - index + 1)) if rev else (index, index + 1)
     sl = slice(start, stop, -1 if rev else 1)
     # check our slice matches the expectation for rest of test
     expect = data[sl]
-    sv = new_sequence.SeqView(seq=data)
+    sv = new_sequence.SeqView(seq=data, alphabet=ascii_alphabet)
     sv = sv[sl]
-    assert sv.value == expect
+    assert sv.str_value == expect
     # now check that start / stop are always the same
     # irrespective of step sign
     assert (sv.parent_start, sv.parent_stop) == (index, index + 1)
@@ -333,38 +347,55 @@ def test_parent_coordinates(rev):
     assert seq.parent_coordinates() == (None, 0, 0, 1)
 
 
-@pytest.mark.parametrize(
-    "cls", (new_moltype.DNA.make_seq, new_sequence.SeqView, str, bytes)
-)
-def test_coerce_to_seqview(cls):
+@pytest.mark.parametrize("cls", (str, bytes))
+def test_coerce_to_seqview_str_bytes(cls, dna_alphabet):
+    seq = "AC--GGTGGGAC"
+    seqid = "seq1"
+    s = bytes(seq, "utf8") if cls == bytes else seq
+    got = new_sequence._coerce_to_seqview(s, seqid, alphabet=dna_alphabet)
+    assert got.str_value == seq
+    assert isinstance(got, new_sequence.SeqView)
+
+
+def test_coerce_to_seqview_sequence(dna_alphabet):
     seq = "AC--GGTGGGAC"
     seqid = "seq1"
-    if cls in (str, bytes):
-        s = bytes(seq, "utf8") if cls == bytes else seq
-        got = new_sequence._coerce_to_seqview(s, seqid)
-    else:
-        got = new_sequence._coerce_to_seqview(cls(seq=seq), seqid)
-    assert got.value == seq
+    got = new_sequence._coerce_to_seqview(
+        new_moltype.DNA.make_seq(seq=seq), seqid, alphabet=dna_alphabet
+    )
+    assert got.str_value == seq
     assert isinstance(got, new_sequence.SeqView)
 
 
-def test_seqview_seqid():
-    sv = new_sequence.SeqView(seq="ACGGTGGGAC")
+def test_coerce_to_seqview_already_seqview(dna_alphabet):
+    seq = "AC--GGTGGGAC"
+    seqid = "seq1"
+    got = new_sequence._coerce_to_seqview(
+        new_sequence.SeqView(seq=seq, alphabet=dna_alphabet),
+        seqid,
+        alphabet=dna_alphabet,
+    )
+    assert got.str_value == seq
+    assert isinstance(got, new_sequence.SeqView)
+
+
+def test_seqview_seqid(dna_alphabet):
+    sv = new_sequence.SeqView(seq="ACGGTGGGAC", alphabet=dna_alphabet)
     assert sv.seqid is None
 
-    sv = new_sequence.SeqView(seq="ACGGTGGGAC", seqid="seq1")
+    sv = new_sequence.SeqView(seq="ACGGTGGGAC", seqid="seq1", alphabet=dna_alphabet)
     assert sv.seqid == "seq1"
 
 
-def test_seqview_rich_dict_round_trip_seqid():
-    sv = new_sequence.SeqView(seq="ACGGTGGGAC", seqid="seq1")
+def test_seqview_rich_dict_round_trip_seqid(dna_alphabet):
+    sv = new_sequence.SeqView(seq="ACGGTGGGAC", seqid="seq1", alphabet=dna_alphabet)
     rd = sv.to_rich_dict()
     assert rd["init_args"]["seqid"] == "seq1"
 
     got = new_sequence.SeqView.from_rich_dict(rd)
     assert got.seqid == "seq1"
 
-    sv = new_sequence.SeqView(seq="ACGGTGGGAC")
+    sv = new_sequence.SeqView(seq="ACGGTGGGAC", alphabet=dna_alphabet)
     rd = sv.to_rich_dict()
     assert rd["init_args"]["seqid"] is None
 
@@ -372,8 +403,8 @@ def test_seqview_rich_dict_round_trip_seqid():
     assert got.seqid is None
 
 
-def test_seqview_slice_propagates_seqid():
-    sv = new_sequence.SeqView(seq="ACGGTGGGAC", seqid="seq1")
+def test_seqview_slice_propagates_seqid(dna_alphabet):
+    sv = new_sequence.SeqView(seq="ACGGTGGGAC", seqid="seq1", alphabet=dna_alphabet)
     sliced_sv = sv[1:8:2]
     assert sliced_sv.seqid == "seq1"
 
@@ -400,8 +431,8 @@ def test_make_seq_assigns_to_seqview():
     assert seq.name == seq._seq.seqid == "s1"
 
 
-def test_empty_seqview_translate_position():
-    sv = new_sequence.SeqView(seq="")
+def test_empty_seqview_translate_position(dna_alphabet):
+    sv = new_sequence.SeqView(seq="", alphabet=dna_alphabet)
     assert sv.absolute_position(0) == 0
     assert sv.relative_position(0) == 0
 
@@ -410,10 +441,12 @@ def test_empty_seqview_translate_position():
 @pytest.mark.parametrize("stop", (None, 10, 8, 1, 0, -1, -11))
 @pytest.mark.parametrize("step", (None, 1, 2, -1, -2))
 @pytest.mark.parametrize("length", (1, 8, 999))
-def test_seqview_seq_len_init(start, stop, step, length):
+def test_seqview_seq_len_init(start, stop, step, length, dna_alphabet):
     # seq_len is length of seq when None
     seq_data = "A" * length
-    sv = new_sequence.SeqView(seq=seq_data, start=start, stop=stop, step=step)
+    sv = new_sequence.SeqView(
+        seq=seq_data, start=start, stop=stop, step=step, alphabet=dna_alphabet
+    )
     expect = len(seq_data)
     # Check property and slot
     assert sv.seq_len == expect
@@ -421,22 +454,33 @@ def test_seqview_seq_len_init(start, stop, step, length):
 
 
 @pytest.mark.parametrize("seq, seq_len", [("A", 0), ("", 1), ("A", 2)])
-def test_seqview_seq_len_mismatch(seq, seq_len):
+def test_seqview_seq_len_mismatch(seq, seq_len, dna_alphabet):
     # If provided, seq_len must match len(seq)
     with pytest.raises(AssertionError):
-        new_sequence.SeqView(seq=seq, seq_len=seq_len)
+        new_sequence.SeqView(seq=seq, seq_len=seq_len, alphabet=dna_alphabet)
 
 
-def test_seqview_copy_propagates_seq_len():
+def test_seqview_copy_propagates_seq_len(dna_alphabet):
     seq = "ACGGTGGGAC"
-    sv = new_sequence.SeqView(seq=seq)
+    sv = new_sequence.SeqView(seq=seq, alphabet=dna_alphabet)
     copied = sv.copy()
     assert copied.seq_len == len(seq)
 
 
-def test_seqview_seq_len_modified_seq():
+def test_seqview_seq_len_modified_seq(dna_alphabet):
     seq = "ACGGTGGGAC"
-    sv = new_sequence.SeqView(seq=seq)
+    sv = new_sequence.SeqView(seq=seq, alphabet=dna_alphabet)
 
     sv.seq = "ATGC"  # this should not modify seq_len
     assert sv.seq_len == len(seq)
+
+
+def test_sequence_str_bytes_array():
+    data = "ACGGTGGGAC"
+    seq = new_moltype.DNA.make_seq(seq=data)
+    print(type(seq))
+    assert str(seq) == data
+    assert bytes(seq) == data.encode("utf8")
+    assert numpy.array_equal(
+        numpy.array(seq), new_moltype.DNA.alphabet.to_indices(data)
+    )