Repository: cogent3/cogent3
Pull request #1961 (2024-07-23 06:13:32)

ENH: begin Alignment refactor
In this PR: 
- re-add `AlignedData` class and helper code- thanks @fredjaya!
- add `Aligned` class which wraps an `AlignedDataView`
- add `Alignment` and tests of its basic usage 


Patch:
diff --git a/src/cogent3/core/alignment.py b/src/cogent3/core/alignment.py
index 219e2a8cf..deb9a1a86 100644
--- a/src/cogent3/core/alignment.py
+++ b/src/cogent3/core/alignment.py
@@ -2553,7 +2553,7 @@ class AlignmentI(object):
         app = cogent3.get_app(app_name, **kwargs)
         return app(self)
 
-    def iter_positions(self, pos_order=None):
+    def iter_positions(self, pos_order=None):  # ported
         """Iterates over positions in the alignment, in order.
 
         pos_order refers to a list of indices (ints) specifying the column
@@ -4791,7 +4791,7 @@ class Alignment(AlignmentI, SequenceCollection):
         """
         return self.named_seqs[seqname].data
 
-    def get_gapped_seq(self, seq_name, recode_gaps=False):
+    def get_gapped_seq(self, seq_name, recode_gaps=False):  # ported
         """Return a gapped Sequence object for the specified seqname.
 
         Note: always returns Sequence object, not ArraySequence.
diff --git a/src/cogent3/core/new_alignment.py b/src/cogent3/core/new_alignment.py
index fe8d09714..9a10cf074 100644
--- a/src/cogent3/core/new_alignment.py
+++ b/src/cogent3/core/new_alignment.py
@@ -21,6 +21,7 @@ from cogent3.core.annotation_db import (
     SupportsFeatures,
 )
 from cogent3.core.info import Info as InfoClass
+from cogent3.core.location import IndelMap
 from cogent3.core.profile import (
     PSSM,
     MotifCountsArray,
@@ -49,7 +50,9 @@ OptDict = Optional[dict]
 OptCallable = Optional[Callable]
 OptRenamerCallable = Optional[Callable[[str], str]]
 OptPathType = Union[str, Path, None]
+StrORArray = Union[str, numpy.ndarray]
 StrORBytesORArray = Union[str, bytes, numpy.ndarray]
+MolTypes = Union[str, new_moltype.MolType]
 
 
 class MakeSeqCallable(typing.Protocol):
@@ -94,14 +97,14 @@ class SeqDataView(new_sequence.SeqViewABC, new_sequence.SliceRecordABC):
     # array([3, 1, 2, 0], dtype=int8)
     """
 
-    __slots__ = ("seq", "start", "stop", "step", "_offset", "_seqid", "_seq_len")
+    __slots__ = ("parent", "start", "stop", "step", "_offset", "_seqid", "_parent_len")
 
     def __init__(
         self,
         *,
-        seq: SeqsData,
+        parent: SeqsData,
         seqid: str,
-        seq_len: int,
+        parent_len: int,
         start: OptInt = None,
         stop: OptInt = None,
         step: OptInt = None,
@@ -110,14 +113,14 @@ class SeqDataView(new_sequence.SeqViewABC, new_sequence.SliceRecordABC):
         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)
+        self._parent_len = self._checked_seq_len(parent_len)
         func = (
             new_sequence._input_vals_pos_step
             if step > 0
             else new_sequence._input_vals_neg_step
         )
-        start, stop, step = func(self._seq_len, start, stop, step)
-        self.seq = seq
+        start, stop, step = func(self._parent_len, start, stop, step)
+        self.parent = parent
         self.start = start
         self.stop = stop
         self.step = step
@@ -131,7 +134,11 @@ class SeqDataView(new_sequence.SeqViewABC, new_sequence.SliceRecordABC):
     @property
     def _zero_slice(self):
         return self.__class__(
-            seq=self.seq, seqid=self.seqid, seq_len=self._seq_len, start=0, stop=0
+            parent=self.parent,
+            seqid=self.seqid,
+            parent_len=self._parent_len,
+            start=0,
+            stop=0,
         )
 
     @property
@@ -139,16 +146,16 @@ class SeqDataView(new_sequence.SeqViewABC, new_sequence.SliceRecordABC):
         return self._seqid
 
     @property
-    def seq_len(self) -> int:
-        return self._seq_len
+    def parent_len(self) -> int:
+        return self._parent_len
 
     def _get_init_kwargs(self):
-        return {"seq": self.seq, "seqid": self.seqid}
+        return {"parent": self.parent, "seqid": self.seqid}
 
     @property
     def str_value(self) -> str:
         """returns the sequence as a string"""
-        raw = self.seq.get_seq_str(
+        raw = self.parent.get_seq_str(
             seqid=self.seqid, start=self.parent_start, stop=self.parent_stop
         )
         return raw if self.step == 1 else raw[:: self.step]
@@ -156,7 +163,7 @@ class SeqDataView(new_sequence.SeqViewABC, new_sequence.SliceRecordABC):
     @property
     def array_value(self) -> numpy.ndarray:
         """returns the sequence as a numpy array"""
-        raw = self.seq.get_seq_array(
+        raw = self.parent.get_seq_array(
             seqid=self.seqid, start=self.parent_start, stop=self.parent_stop
         )
         return raw if self.step == 1 else raw[:: self.step]
@@ -164,18 +171,32 @@ class SeqDataView(new_sequence.SeqViewABC, new_sequence.SliceRecordABC):
     @property
     def bytes_value(self) -> bytes:
         """returns the sequence as bytes"""
-        raw = self.seq.get_seq_bytes(
+        raw = self.parent.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) -> str:
+        return self.str_value
+
+    def __array__(self, dtype=None, copy=None) -> numpy.ndarray:
+        if copy is False:
+            raise ValueError("`copy=False` isn't supported. A copy is always created.")
+        arr = self.array_value
+        if dtype:
+            arr = arr.astype(dtype)
+        return arr
+
+    def __bytes__(self) -> bytes:
+        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}, "
+            f"{self.__class__.__name__}(parent={seq}, start={self.start}, "
             f"stop={self.stop}, step={self.step}, offset={self.offset}, "
-            f"seqid={self.seqid!r}, seq_len={self.seq_len})"
+            f"seqid={self.seqid!r}, parent_len={self.parent_len})"
         )
 
     # refactor: design, do we support copy? do we support copy with sliced?
@@ -201,14 +222,14 @@ class SeqDataView(new_sequence.SeqViewABC, new_sequence.SliceRecordABC):
         data["init_args"]["step"] = self.step
 
         if self.is_reversed:
-            adj = self.seq_len + 1
+            adj = self.parent_len + 1
             start, stop = self.stop + adj, self.start + adj
         else:
             start, stop = self.start, self.stop
 
-        data["init_args"]["seq"] = self.str_value[start:stop]
+        data["init_args"]["parent"] = self.str_value[start:stop]
         data["init_args"]["offset"] = int(self.parent_start)
-        data["init_args"]["seq_len"] = len(self)
+        data["init_args"]["parent_len"] = len(self)
 
         return data
 
@@ -262,13 +283,13 @@ class SeqsDataABC(ABC):
     ) -> bytes: ...
 
     @abstractmethod
-    def get_seq_view(self, seqid: str) -> new_sequence.SliceRecordABC: ...
+    def get_view(self, seqid: str) -> new_sequence.SliceRecordABC: ...
 
     @abstractmethod
     def subset(self, names: Union[str, typing.Sequence[str]]) -> SeqsDataABC: ...
 
     @abstractmethod
-    def to_alphabet(self, alphabet: new_alphabet.CharAlphabet) -> SeqsDataABC: ...
+    def to_alphabet(self, alphabet: new_alphabet.AlphabetABC) -> SeqsDataABC: ...
 
     @abstractmethod
     def add_seqs(self, seqs) -> SeqsDataABC: ...
@@ -313,7 +334,7 @@ class SeqsData(SeqsDataABC):
         self,
         *,
         data: dict[str, StrORBytesORArray],
-        alphabet: new_alphabet.CharAlphabet,
+        alphabet: new_alphabet.AlphabetABC,
         make_seq: Optional[MakeSeqCallable] = None,
         reversed_seqs: dict[str, bool] = None,
     ):
@@ -375,10 +396,10 @@ class SeqsData(SeqsDataABC):
     ) -> bytes:
         return self.get_seq_str(seqid=seqid, start=start, stop=stop).encode("utf8")
 
-    def get_seq_view(self, seqid: str) -> SeqDataView:
+    def get_view(self, seqid: str) -> SeqDataView:
         seq_len = len(self._data[seqid])
         step = -1 if self._reversed_seqs.get(seqid, False) else 1
-        return SeqDataView(seq=self, seqid=seqid, seq_len=seq_len, step=step)
+        return SeqDataView(parent=self, seqid=seqid, parent_len=seq_len, step=step)
 
     def subset(self, names: Union[str, typing.Sequence[str]]) -> SeqsData:
         """Returns a new SeqsData object with only the specified names."""
@@ -435,7 +456,7 @@ class SeqsData(SeqsDataABC):
         )
 
     def to_alphabet(
-        self, alphabet: new_alphabet.CharAlphabet, check_valid=True
+        self, alphabet: new_alphabet.AlphabetABC, check_valid=True
     ) -> SeqsData:
         # refactor: design -- map directly between arrays?
         # refactor: design -- better way to check if alphabets are DNA and RNA?
@@ -478,7 +499,7 @@ class SeqsData(SeqsDataABC):
 
     @__getitem__.register
     def _(self, index: str) -> Union[new_sequence.Sequence, new_sequence.SeqViewABC]:
-        sdv = self.get_seq_view(seqid=index)
+        sdv = self.get_view(seqid=index)
         return (
             sdv
             if self.make_seq is None
@@ -538,6 +559,7 @@ class SequenceCollection:
             info = InfoClass(info) if info else InfoClass()
         self.info = info
         self.source = source
+        # todo: kath - move _repr_policy to method so it can be reimplemented in Aligment class
         self._repr_policy = dict(num_seqs=10, num_pos=60, ref_name="longest", wrap=60)
         self._annotation_db = annotation_db or DEFAULT_ANNOTATION_DB()
 
@@ -1532,7 +1554,7 @@ class SequenceCollection:
 
     def get_motif_probs(
         self,
-        alphabet: new_alphabet.CharAlphabet = None,
+        alphabet: new_alphabet.AlphabetABC = None,
         include_ambiguity: bool = False,
         exclude_unobserved: bool = False,
         allow_gap: bool = False,
@@ -2374,3 +2396,722 @@ def _(
         annotation_db=annotation_db,
         source=source,
     )
+
+
+@singledispatch
+def seq_to_gap_coords(
+    seq: StrORBytesORArray,
+    *,
+    alphabet: new_alphabet.AlphabetABC,
+    make_seq: MakeSeqCallable,
+) -> tuple[StrORBytesORArray, IndelMap]:
+    """
+    Takes a sequence with (or without) gaps and returns an ungapped sequence
+    and a map of the position and length of gaps in the original parent sequence
+    """
+    raise NotImplementedError(f"seq_to_gap_coords not implemented for type {type(seq)}")
+
+
+@seq_to_gap_coords.register
+def _(
+    seq: str, *, alphabet: new_alphabet.AlphabetABC, make_seq: MakeSeqCallable
+) -> tuple[str, numpy.ndarray]:
+    seq = make_seq(seq=seq)
+    indel_map, ungapped_seq = seq.parse_out_gaps()
+
+    if indel_map.num_gaps == 0:
+        return str(ungapped_seq), numpy.array([], dtype=int)
+
+    return str(ungapped_seq), numpy.array(
+        [indel_map.gap_pos, indel_map.cum_gap_lengths]
+    ).T
+
+
+@seq_to_gap_coords.register
+def _(
+    seq: numpy.ndarray,
+    *,
+    alphabet: new_alphabet.AlphabetABC,
+    make_seq: MakeSeqCallable,
+) -> tuple[numpy.ndarray, numpy.ndarray]:
+    gaps_bool = seq == alphabet.gap_index
+    ungapped = seq[~gaps_bool]
+
+    # no gaps in seq
+    if numpy.array_equal(ungapped, seq):
+        return ungapped, numpy.array([], dtype=int)
+
+    parent_len = len(seq)
+    in_gap = False
+    parent_coords = []
+    start = 0
+    for i, gapped in enumerate(gaps_bool):
+        if gapped and not in_gap:
+            start = i
+            in_gap = True
+        if not gapped and in_gap:
+            parent_coords.append([start, i])
+            in_gap = False
+        if gapped and i == parent_len - 1:
+            # end of sequence
+            parent_coords.append([start, i + 1])
+
+    # format for IndelMap
+    parent_coords = numpy.array(parent_coords)
+    gap_lengths = numpy.array([end - start for start, end in parent_coords])
+    cum_lengths = gap_lengths.cumsum()
+    # get gap start positions in sequence coords
+    gap_pos = parent_coords.T[0] - numpy.append(0, cum_lengths[:-1, numpy.newaxis])
+
+    return ungapped, numpy.array([gap_pos, cum_lengths]).T
+
+
+@seq_to_gap_coords.register
+def _(
+    seq: bytes, *, alphabet: new_alphabet.AlphabetABC, make_seq: MakeSeqCallable
+) -> tuple[bytes, numpy.ndarray]:
+    seq, map_array = seq_to_gap_coords(
+        seq.decode("utf-8"), alphabet=alphabet, make_seq=make_seq
+    )
+    return seq.encode("utf-8"), map_array
+
+
+class SliceRecord(new_sequence.SliceRecordABC):
+    __slots__ = "_parent_len"
+
+    def __init__(
+        self, start: OptInt, stop: OptInt, step: OptInt, offset: int, seq_len: int
+    ):
+        self.start = start
+        self.stop = stop
+        self.step = step
+        self._offset = offset
+        self._seq_len = seq_len
+
+    @property
+    def parent_len(self) -> int:
+        return self._parent_len
+
+    def _get_init_kwargs(self) -> dict:
+        return {}
+
+    def copy(self, sliced: bool = False):
+        # todo: kath
+        return self
+
+    @property
+    def _zero_slice(self): ...
+
+
+class Aligned:
+    """A single sequence in an alignment."""
+
+    def __init__(self, data: AlignedDataView):
+        self._data = data
+
+    @property
+    def data(self) -> AlignedDataView:
+        return self._data
+
+    @property
+    def map(self) -> IndelMap:
+        return self.data.map
+
+    @property
+    def seq(self) -> new_sequence.Sequence:
+        """Returns Sequence object, excluding gaps."""
+        return self.data.parent.make_seq(seq=self.data.str_value, name=self.data.seqid)
+
+    @property
+    def gapped_seq(self) -> new_sequence.Sequence:
+        """Returns Sequence object, including gaps."""
+        return self.data.parent.make_seq(
+            seq=self.data.gapped_str_value, name=self.data.seqid
+        )
+
+    def __str__(self) -> str:
+        return str(self.gapped_seq)
+
+    def __array__(self, dtype=None, copy=None) -> numpy.ndarray:
+        if copy is False:
+            raise ValueError("`copy=False` isn't supported. A copy is always created.")
+        return numpy.array(self.gapped_seq, dtype=dtype)
+
+    def __bytes__(self) -> bytes:
+        return bytes(self.gapped_seq)
+
+    def __iter__(self):
+        """Iterates over sequence one motif (e.g. char) at a time, incl. gaps"""
+        yield self.gapped_seq
+
+    @singledispatchmethod
+    def __getitem__(self, span: Union[int, slice]):
+        raise NotImplementedError(f"__getitem__ not implemented for {type(span)}")
+
+    @__getitem__.register
+    def _(self, span: int):
+        return self.__getitem__(slice(span, span + 1))
+
+    @__getitem__.register
+    def _(self, span: slice):
+        # todo: kath
+        # implement for reverse complement
+        if span.step and span.step < 0:
+            raise NotImplementedError("Cannot reverse an Aligned")
+
+        return self.__class__(data=self.data[span.start : span.stop])
+
+
+class AlignedSeqsDataABC(SeqsDataABC):
+    @classmethod
+    @abstractmethod
+    def from_aligned_seqs(
+        cls,
+        *,
+        data: dict[str, StrORArray],
+        alphabet: new_alphabet.AlphabetABC,
+        make_seq: Optional[MakeSeqCallable] = None,
+    ): ...
+
+    @property
+    @abstractmethod
+    def align_len(self) -> int: ...
+
+    @abstractmethod
+    def get_gapped_seq_array(
+        self,
+        *,
+        seqid: str,
+        start: OptInt = None,
+        stop: OptInt = None,
+    ) -> numpy.ndarray: ...
+
+    @abstractmethod
+    def get_gapped_seq_str(
+        self,
+        *,
+        seqid: str,
+        start: OptInt = None,
+        stop: OptInt = None,
+    ) -> str: ...
+
+    @abstractmethod
+    def get_gapped_seq_bytes(
+        self,
+        *,
+        seqid: str,
+        start: OptInt = None,
+        stop: OptInt = None,
+    ) -> bytes: ...
+
+
+class AlignedSeqsData(AlignedSeqsDataABC):
+    # refactor: docstring
+
+    __slots__ = (
+        "_seqs",
+        "_gaps",
+        "_alphabet",
+        "_make_seq",
+        "_align_len",
+    )
+
+    def __init__(
+        self,
+        *,
+        seqs: Optional[dict[str, StrORBytesORArray]],
+        gaps: Optional[dict[str, numpy.ndarray]],
+        alphabet: new_alphabet.AlphabetABC,
+        align_len: OptInt = None,
+        make_seq: Optional[MakeSeqCallable] = None,
+        check: bool = True,
+    ):
+        self._alphabet = alphabet
+        self._make_seq = make_seq
+        if check:
+            if not seqs or not gaps:
+                raise ValueError("Both seqs and gaps must be provided.")
+            if set(seqs.keys()) != set(gaps.keys()):
+                raise ValueError("Keys in seqs and gaps must be identical.")
+            seq_lengths = {
+                len(v) + l[-1][1] for v, l in zip(seqs.values(), gaps.values())
+            }
+            if len(seq_lengths) != 1:
+                raise ValueError("All sequence lengths must be the same.")
+            align_len = seq_lengths.pop()
+        self._seqs = {}
+        for k, v in seqs.items():
+            seq = self._alphabet.to_indices(v)
+            self._seqs[k] = seq
+            seq.flags.writeable = False
+        self._gaps = {}
+        for k, v in gaps.items():
+            self._gaps[k] = v
+            v.flags.writeable = False
+        self._align_len = align_len or len(next(iter(self._seqs.values())))
+
+    @classmethod
+    def from_aligned_seqs(
+        cls,
+        *,
+        data: dict[str, StrORArray],
+        alphabet: new_alphabet.AlphabetABC,
+        make_seq: Optional[MakeSeqCallable] = None,
+    ):
+        """Construct an AlignedSeqsData object from a dict of aligned sequences
+
+        Parameters
+        ----------
+        data
+            dict of gapped sequences {name: seq, ...}. sequences must all be
+            the same length
+        moltype
+            the molecular type of the sequences e.g., 'dna', 'protein' or a
+            MolType instance
+        """
+        seq_lengths = {len(v) for v in data.values()}
+        if len(seq_lengths) != 1:
+            raise ValueError("All sequence lengths must be the same.")
+
+        align_len = seq_lengths.pop()
+        seqs = {}
+        gaps = {}
+        for name, seq in data.items():
+            seq, gap_map = seq_to_gap_coords(seq, alphabet=alphabet, make_seq=make_seq)
+            seq = alphabet.to_indices(seq)
+            seq.flags.writeable = False
+            gap_map.flags.writeable = False
+            seqs[name], gaps[name] = seq, gap_map
+
+        return cls(
+            seqs=seqs,
+            gaps=gaps,
+            alphabet=alphabet,
+            make_seq=make_seq,
+            align_len=align_len,
+            check=False,
+        )
+
+    def __len__(self):
+        return self.align_len
+
+    @singledispatchmethod
+    def __getitem__(self, index: Union[str, int]) -> Aligned:
+        raise NotImplementedError(f"__getitem__ not implemented for {type(index)}")
+
+    @__getitem__.register
+    def _(self, index: str) -> Aligned:
+        adv = self.get_view(seqid=index)
+        return Aligned(data=adv)
+
+    @__getitem__.register
+    def _(self, index: int) -> Aligned:
+        return self[self.names[index]]
+
+    @property
+    def names(self) -> tuple[str]:
+        return list(self._seqs.keys())
+
+    @property
+    def alphabet(self) -> new_alphabet.CharAlphabet:
+        return self._alphabet
+
+    @property
+    def make_seq(self) -> Optional[MakeSeqCallable]:
+        """if set, returns a function that takes 'seq' and 'name' as keyword
+        arguments and returns a corresponding Sequence from the collection.
+
+        Notes
+        -----
+        Can be set with any callable function that takes 'seq' and 'name' as
+        keyword arguments. Typically set with '<moltype-instance>.make_seq'.
+        """
+        return self._make_seq
+
+    @make_seq.setter
+    def make_seq(self, make_seq: MakeSeqCallable) -> None:
+        self._make_seq = make_seq
+
+    @property
+    def align_len(self) -> int:
+        return self._align_len
+
+    def seq_lengths(self) -> dict[str, int]:
+        """Returns lengths of ungapped sequences as dict of {name: length, ... }."""
+        return {name: len(seq) for name, seq in self._seqs.items()}
+
+    def get_view(self, seqid: str) -> AlignedDataView:
+        return AlignedDataView(parent=self, seqid=seqid, parent_len=self.align_len)
+
+    def get_gaps(self, seqid: str) -> numpy.ndarray:
+        return self._gaps[seqid]
+
+    def get_seq_array(
+        self,
+        *,
+        seqid: str,
+        start: OptInt = None,
+        stop: OptInt = None,
+    ) -> numpy.ndarray:
+        """Return ungapped sequence corresponding to seqid as an array of indices.
+        start/stop are in alignment coordinates and the alignment coordinates
+        are converted to sequence coordinates. Excludes gaps.
+        """
+        seq = self._seqs[seqid]
+        # if there's no gaps, just slice the sequence
+        if len(seq) == self.align_len:
+            return seq[start:stop]
+
+        gaps = self._gaps[seqid]
+        indel_map = IndelMap(
+            gap_pos=gaps[:, 0], cum_gap_lengths=gaps[:, 1], parent_length=self.align_len
+        )
+
+        new_map = (
+            indel_map[start:stop]
+            if start is not None or stop is not None
+            else indel_map
+        )
+        seq_start = indel_map.get_seq_index(start or 0)
+        seq_end = indel_map.get_seq_index(stop or self.align_len)
+        return seq[seq_start:seq_end] if new_map.useful else seq[:0]
+
+    def get_gapped_seq_array(
+        self,
+        *,
+        seqid: str,
+        start: OptInt = None,
+        stop: OptInt = None,
+    ) -> numpy.ndarray:
+        """Return sequence data corresponding to seqid as an array of indices.
+        start/stop are in alignment coordinates. Includes gaps.
+        """
+        seq = self._seqs[seqid]
+
+        # if there's no gaps, just slice the sequence
+        if len(seq) == self.align_len:
+            return seq[start:stop]
+
+        # refactor: design
+        # direct numpy array approach would be faster
+        gaps = self._gaps[seqid]
+        indel_map = IndelMap(
+            gap_pos=gaps[:, 0], cum_gap_lengths=gaps[:, 1], parent_length=self.align_len
+        )
+
+        gapped = numpy.array([], dtype=numpy.uint8)
+        for span in indel_map.spans:
+            if span.lost:
+                unknown = self.alphabet.gap_index
+                seg = numpy.full(span.length, unknown, dtype=numpy.uint8)
+            else:
+                seg = seq[span.start : span.end]
+
+            gapped = numpy.concatenate((gapped, seg))
+
+        return gapped[start:stop]
+
+    def get_seq_str(
+        self,
+        *,
+        seqid: str,
+        start: OptInt = None,
+        stop: OptInt = None,
+    ) -> str:
+        """Return ungapped sequence corresponding to seqid as a string.
+        start/stop are in alignment coordinates and the alignment coordinates
+        are converted to sequence coordinates. Excludes gaps."""
+
+        return self.alphabet.from_indices(
+            self.get_seq_array(seqid=seqid, start=start, stop=stop)
+        )
+
+    def get_gapped_seq_str(
+        self,
+        *,
+        seqid: str,
+        start: OptInt = None,
+        stop: OptInt = None,
+    ) -> str:
+        """Return sequence corresponding to seqid as a string.
+        start/stop are in alignment coordinates. Includes gaps."""
+
+        return self.alphabet.from_indices(
+            self.get_gapped_seq_array(seqid=seqid, start=start, stop=stop)
+        )
+
+    def get_seq_bytes(
+        self,
+        *,
+        seqid: str,
+        start: OptInt = None,
+        stop: OptInt = None,
+    ) -> bytes:
+        """Return ungapped sequence corresponding to seqid as a bytes string.
+        start/stop are in alignment coordinates and the alignment coordinates
+        are converted to sequence coordinates. Excludes gaps."""
+        return self.get_seq_str(seqid=seqid, start=start, stop=stop).encode("utf8")
+
+    def get_gapped_seq_bytes(
+        self,
+        *,
+        seqid: str,
+        start: OptInt = None,
+        stop: OptInt = None,
+    ) -> bytes:
+        """Return sequence corresponding to seqid as a bytes string.
+        start/stop are in alignment coordinates. Includes gaps."""
+        return self.get_gapped_seq_str(seqid=seqid, start=start, stop=stop).encode(
+            "utf8"
+        )
+
+    def add_seqs(
+        self, seqs: dict[str, StrORArray], force_unique_keys=True
+    ) -> AlignedSeqsData:
+        """Returns a new AlignedSeqsData object with added sequences."""
+        if force_unique_keys and any(name in self.names for name in seqs):
+            raise ValueError("One or more sequence names already exist in collection")
+        # refactor: design
+        # instead of re-computing the gaps, can we construct the new gaps and append to
+        # the existing gaps?
+
+        new_seq_lens = {len(seq) for seq in seqs.values()}
+        if len(new_seq_lens) != 1 or new_seq_lens.pop() != self.align_len:
+            raise ValueError(
+                "All sequences must be the same length as existing sequences"
+            )
+
+        old_seqs = {name: self.get_gapped_seq_array(seqid=name) for name in self.names}
+        new_data = old_seqs | seqs
+
+        return self.__class__.from_aligned_seqs(
+            data=new_data, alphabet=self.alphabet, make_seq=self._make_seq
+        )
+
+    def reversed(self):
+        # todo: kath
+        ...
+
+    def subset(self, names: Union[str, typing.Sequence[str]]):
+        # todo: kath
+        ...
+
+    def to_alphabet(self, alphabet: new_alphabet.AlphabetABC):
+        # todo: kath
+        ...
+
+    def to_rich_dict(self):
+        # todo: kath
+        ...
+
+
+class AlignedDataView(new_sequence.SeqViewABC, new_sequence.SliceRecordABC):
+    # refactor: docstring
+    def __init__(
+        self,
+        *,
+        parent: AlignedSeqsDataABC,
+        seqid: str,
+        parent_len: int,
+        start: OptInt = None,
+        stop: OptInt = None,
+        step: OptInt = None,
+        offset: int = 0,
+    ):
+        if step and step < 1:
+            raise ValueError(f"step cannot be {step}")
+        step = 1 if step is None else step
+        self.parent = parent
+        self._parent_len = self._checked_seq_len(parent_len)
+        func = (
+            new_sequence._input_vals_pos_step
+            if step > 0
+            else new_sequence._input_vals_neg_step
+        )
+        start, stop, step = func(self._parent_len, start, stop, step)
+        self.start = start
+        self.stop = stop
+        self.step = step
+        self._offset = offset
+        self._seqid = seqid
+
+    @property
+    def seqid(self) -> str:
+        return self._seqid
+
+    @property
+    def parent_len(self) -> int:
+        return self._parent_len
+
+    @property
+    def map(self) -> IndelMap:
+        gap_pos_gap_length = self.parent.get_gaps(self.seqid)[self.start : self.stop]
+        return IndelMap(
+            gap_pos=gap_pos_gap_length[:, 0],
+            cum_gap_lengths=gap_pos_gap_length[:, 1],
+            parent_length=self.parent_len,
+        )
+
+    @property
+    def str_value(self) -> str:
+        return self.parent.get_seq_str(
+            seqid=self.seqid, start=self.start, stop=self.stop
+        )
+
+    @property
+    def gapped_str_value(self) -> str:
+        return self.parent.get_gapped_seq_str(
+            seqid=self.seqid, start=self.start, stop=self.stop
+        )
+
+    @property
+    def array_value(self) -> numpy.ndarray:
+        return self.parent.get_seq_array(
+            seqid=self.seqid, start=self.start, stop=self.stop
+        )
+
+    @property
+    def gapped_array_value(self) -> numpy.ndarray:
+        return self.parent.get_gapped_seq_array(
+            seqid=self.seqid, start=self.start, stop=self.stop
+        )
+
+    @property
+    def bytes_value(self) -> bytes:
+        return self.parent.get_seq_bytes(
+            seqid=self.seqid, start=self.start, stop=self.stop
+        )
+
+    @property
+    def gapped_bytes_value(self) -> bytes:
+        return self.parent.get_gapped_seq_bytes(
+            seqid=self.seqid, start=self.start, stop=self.stop
+        )
+
+    def __str__(self) -> str:
+        # refactor: design
+        # should this return gapped or ungapped?
+        return self.str_value
+
+    def __array__(self, dtype=None, copy=None) -> numpy.ndarray:
+        if copy is False:
+            raise ValueError("`copy=False` isn't supported. A copy is always created.")
+        arr = self.array_value
+        if dtype:
+            arr = arr.astype(dtype)
+        return arr
+
+    def __bytes__(self) -> bytes:
+        return self.bytes_value
+
+    def copy(self, sliced: bool = False):
+        return self
+
+    def to_rich_dict(self) -> dict:
+        # todo: kath
+        ...
+
+    def _get_init_kwargs(self) -> dict:
+        return {"parent": self.parent, "seqid": self.seqid}
+
+    def _checked_seq_len(self, seq_len: int) -> int:
+        assert seq_len == self.parent.align_len
+        return seq_len
+
+    @property
+    def _zero_slice(self):
+        return self.__class__(
+            parent=self.parent,
+            seqid=self.seqid,
+            parent_len=self._parent_len,
+            start=0,
+            stop=0,
+        )
+
+
+class Alignment(SequenceCollection):
+    def __init__(
+        self,
+        **kwargs,
+    ):
+        super().__init__(**kwargs)
+
+    @property
+    def seqs(self) -> AlignedSeqsDataABC:
+        return self._seqs_data
+
+    def get_seq(
+        self, seqname: str, copy_annotations: bool = False
+    ) -> new_sequence.Sequence:
+        """Return a Sequence object for the specified seqname."""
+        seq = self.seqs[seqname].seq
+        if copy_annotations:
+            seq.annotation_db = type(self.annotation_db)()
+            seq.annotation_db.update(annot_db=self.annotation_db, seqids=seqname)
+        else:
+            seq.annotation_db = self.annotation_db
+
+        return seq
+
+    @c3warn.deprecated_args(
+        version="2025.6", reason="naming consistency", old_new=[("seq_name", "seqname")]
+    )
+    def get_gapped_seq(self, seqname):
+        """Return a gapped Sequence object for the specified seqname."""
+        return self.seqs[seqname].gapped_seq
+
+    def iter_positions(
+        self, pos_order: list = None
+    ) -> typing.Iterator[list, list, list]:
+        """Iterates over positions in the alignment, in order.
+
+        Parameters
+        ----------
+        pos_order
+            list of indices specifying the column order. If None, the
+            positions are iterated in order.
+
+        Returns
+        -------
+        yields lists of elemenets for each position (column) in the alignment
+        """
+        # refactor: array
+        # this could also iter columns of indices as a numpy array - could be an optional arg
+        get = self.seqs.__getitem__
+        pos_order = pos_order or range(self.seqs.align_len)
+        seq_order = self.names
+        for pos in pos_order:
+            yield [str(get(seq)[pos]) for seq in seq_order]
+
+
+@singledispatch
+def make_aligned_seqs(
+    data: Union[dict[str, StrORBytesORArray], AlignedSeqsData],
+    *,
+    moltype: str,
+    info: dict = None,
+) -> Alignment:
+    raise NotImplementedError(f"make_aligned_seqs not implemented for {type(data)}")
+
+
+@make_aligned_seqs.register
+def _(data: dict, *, moltype: str, info: dict = None) -> Alignment:
+    # todo: implement a coerce to AlignedSeqsData dict function
+    moltype = new_moltype.get_moltype(moltype)
+    alphabet = moltype.most_degen_alphabet()
+    aligned_data = AlignedSeqsData.from_aligned_seqs(
+        data=data, alphabet=alphabet, make_seq=moltype.make_seq
+    )
+    return make_aligned_seqs(aligned_data, moltype=moltype, info=info)
+
+
+@make_aligned_seqs.register
+def _(data: AlignedSeqsDataABC, *, moltype: str, info: dict = None) -> Alignment:
+    moltype = new_moltype.get_moltype(moltype)
+    if not moltype.is_compatible_alphabet(data.alphabet):
+        raise ValueError(
+            f"Provided moltype: {moltype.label} is not compatible with AlignedSeqsData",
+            f" alphabet: {data.alphabet}",
+        )
+
+    return Alignment(seqs_data=data, moltype=moltype, info=info)
diff --git a/src/cogent3/core/new_alphabet.py b/src/cogent3/core/new_alphabet.py
index 5bf9ec86f..d8bb5b83c 100644
--- a/src/cogent3/core/new_alphabet.py
+++ b/src/cogent3/core/new_alphabet.py
@@ -12,6 +12,7 @@ from cogent3.util.deserialise import register_deserialiser
 from cogent3.util.misc import get_object_provenance
 
 StrORBytes = typing.Union[str, bytes]
+StrORArray = typing.Union[str, numpy.ndarray]
 StrORBytesORArray = typing.Union[str, bytes, numpy.ndarray]
 OptInt = typing.Optional[int]
 OptStr = typing.Optional[str]
diff --git a/src/cogent3/core/new_moltype.py b/src/cogent3/core/new_moltype.py
index 6af06d3f3..cd39870c5 100644
--- a/src/cogent3/core/new_moltype.py
+++ b/src/cogent3/core/new_moltype.py
@@ -20,9 +20,9 @@ OptFloat = typing.Optional[float]
 OptCallable = typing.Optional[typing.Callable]
 SeqStrType = typing.Union[list[str], tuple[str, ...]]
 StrORBytes = typing.Union[str, bytes]
+StrORArray = typing.Union[str, numpy.ndarray]
 StrORBytesORArray = typing.Union[str, bytes, numpy.ndarray]
 SeqStrBytesType = typing.Union[list[StrORBytes], tuple[StrORBytes, ...]]
-StrORArray = typing.Union[str, numpy.ndarray]
 
 IUPAC_gap = "-"
 
diff --git a/src/cogent3/core/new_sequence.py b/src/cogent3/core/new_sequence.py
index 85624aa1f..ae0717668 100644
--- a/src/cogent3/core/new_sequence.py
+++ b/src/cogent3/core/new_sequence.py
@@ -148,8 +148,10 @@ class Sequence:
                 result = self.moltype.complement(result)
         return result
 
-    def __array__(self):
-        result = array(self._seq)
+    def __array__(self, dtype=None, copy=None):
+        if copy is False:
+            raise ValueError("`copy=False` isn't supported. A copy is always created.")
+        result = array(self._seq, dtype=dtype)
         if self._seq.is_reversed:
             with contextlib.suppress(TypeError):
                 result = self.moltype.complement(result)
@@ -866,9 +868,8 @@ class Sequence:
         if value == self._annotation_db:
             return
 
-        if check and value:
-            if not isinstance(value, SupportsFeatures):
-                raise TypeError(f"{type(value)} does not satisfy SupportsFeatures")
+        if check and value and not isinstance(value, SupportsFeatures):
+            raise TypeError(f"{type(value)} does not satisfy SupportsFeatures")
 
         self._annotation_db = value
 
@@ -1153,7 +1154,7 @@ class Sequence:
                 f"Changing from old moltype={self.moltype.label} to new "
                 f"moltype={moltype.label} is not valid for this data"
             )
-        sv = SeqView(seq=seq, alphabet=moltype.most_degen_alphabet())
+        sv = SeqView(parent=seq, alphabet=moltype.most_degen_alphabet())
         new = self.__class__(moltype=moltype, seq=sv, name=self.name, info=self.info)
         new.annotation_db = self.annotation_db
         return new
@@ -1277,8 +1278,8 @@ class Sequence:
         return new
 
     def gapped_by_map_segment_iter(
-        self, segment_map, allow_gaps=True, recode_gaps=False
-    ) -> str:
+        self, segment_map: IndelMap, allow_gaps: bool = True, recode_gaps: bool = False
+    ) -> typing.Iterator[str, str, str]:
         if not allow_gaps and not segment_map.complete:
             raise ValueError(f"gap(s) in map {segment_map}")
 
@@ -1291,12 +1292,14 @@ class Sequence:
 
             yield seg
 
-    def gapped_by_map_motif_iter(self, map):
-        for segment in self.gapped_by_map_segment_iter(map):
+    def gapped_by_map_motif_iter(
+        self, segment_map: IndelMap
+    ) -> typing.Iterator[str, str, str]:
+        for segment in self.gapped_by_map_segment_iter(segment_map):
             yield from segment
 
-    def gapped_by_map(self, map, recode_gaps=False):
-        segments = self.gapped_by_map_segment_iter(map, True, recode_gaps)
+    def gapped_by_map(self, segment_map: IndelMap, recode_gaps: bool = False):
+        segments = self.gapped_by_map_segment_iter(segment_map, True, recode_gaps)
         return self.__class__(
             moltype=self.moltype,
             seq="".join(segments),
@@ -1304,9 +1307,9 @@ class Sequence:
             info=self.info,
         )
 
-    def _mapped(self, map):
+    def _mapped(self, segment_map: IndelMap):
         # Called by generic __getitem__
-        segments = self.gapped_by_map_segment_iter(map, allow_gaps=False)
+        segments = self.gapped_by_map_segment_iter(segment_map, allow_gaps=False)
         return self.__class__(
             moltype=self.moltype, seq="".join(segments), name=self.name, info=self.info
         )
@@ -1523,7 +1526,7 @@ class Sequence:
         ]
 
     def get_drawables(
-        self, *, biotype: typing.Optional[str, typing.Iterable[str]] = None
+        self, *, biotype: typing.Optional[StrORIterableStr] = None
     ) -> dict:
         """returns a dict of drawables, keyed by type
 
@@ -2002,19 +2005,15 @@ class SliceRecordABC(ABC):
     offset
         can be set with any additional offset that exists before the start of
         the underlying data
-    seq_len
+    parent_len
         length of the underlying data (not including offset)
-
-    Notes
-    -----
-    seq_len refers to a Python typing.Sequence object, e.g. array, str, list.
     """
 
     __slots__ = ("start", "stop", "step", "_offset")
 
     @property
     @abstractmethod
-    def seq_len(self) -> int: ...
+    def parent_len(self) -> int: ...
 
     @abstractmethod
     def _get_init_kwargs(self) -> dict:
@@ -2052,7 +2051,7 @@ class SliceRecordABC(ABC):
         if self.is_reversed:
             # self.stop becomes the start, self.stop will be negative
             assert self.stop < 0, "expected stop on reverse strand SeqView < 0"
-            start = self.stop + self.seq_len + 1
+            start = self.stop + self.parent_len + 1
         else:
             start = self.start
 
@@ -2074,7 +2073,7 @@ class SliceRecordABC(ABC):
         if self.is_reversed:
             # self.start becomes the stop, self.start will be negative
             assert self.start < 0, "expected start on reverse strand SeqView < 0"
-            stop = self.start + self.seq_len + 1
+            stop = self.start + self.parent_len + 1
         else:
             stop = self.stop
         return self.offset + stop
@@ -2104,7 +2103,7 @@ class SliceRecordABC(ABC):
 
         offset = self.offset
         return (
-            offset + self.seq_len + seq_index + 1
+            offset + self.parent_len + seq_index + 1
             if self.is_reversed
             else offset + seq_index
         )
@@ -2128,7 +2127,7 @@ class SliceRecordABC(ABC):
             offset = self.offset
 
             if (
-                tmp := (self.seq_len - abs_index + offset + self.start + 1)
+                tmp := (self.parent_len - abs_index + offset + self.start + 1)
             ) % self.step == 0 or stop:
                 rel_pos = tmp // abs(self.step)
             else:
@@ -2157,7 +2156,7 @@ class SliceRecordABC(ABC):
                 stop=stop,
                 step=step,
                 offset=self.offset,
-                seq_len=self.seq_len,
+                parent_len=self.parent_len,
                 **kwargs,
             )
 
@@ -2251,7 +2250,7 @@ class SliceRecordABC(ABC):
         # checking for zero-length slice
         if stop < start:
             return self._zero_slice
-        if start > self.seq_len:
+        if start > self.parent_len:
             return self._zero_slice
 
         return self.__class__(
@@ -2259,7 +2258,7 @@ class SliceRecordABC(ABC):
             stop=min(self.stop, stop),
             step=self.step * step,
             offset=self.offset,
-            seq_len=self.seq_len,
+            parent_len=self.parent_len,
             **kwargs,
         )
 
@@ -2287,7 +2286,7 @@ class SliceRecordABC(ABC):
             stop=max(self.stop, stop),
             step=self.step * step,
             offset=self.offset,
-            seq_len=self.seq_len,
+            parent_len=self.parent_len,
             **kwargs,
         )
 
@@ -2311,28 +2310,28 @@ class SliceRecordABC(ABC):
         # max possible start is "true stop" - step, because stop is not inclusive
         # "true stop" - step is converted to -ve index via subtracting len(self)
         if slice_start >= len(self):
-            start = (self.start + len(self) * self.step - self.step) - self.seq_len
+            start = (self.start + len(self) * self.step - self.step) - self.parent_len
         elif slice_start >= 0:
-            start = (self.start + slice_start * self.step) - self.seq_len
+            start = (self.start + slice_start * self.step) - self.parent_len
         else:
             start = (
                 self.start
                 + len(self) * self.step
                 + slice_start * self.step
-                - self.seq_len
+                - self.parent_len
             )
 
-        if slice_stop >= self.seq_len:
+        if slice_stop >= self.parent_len:
             return self._zero_slice
 
         if slice_stop >= 0:
-            stop = self.start + (slice_stop * self.step) - self.seq_len
+            stop = self.start + (slice_stop * self.step) - self.parent_len
         else:
             stop = (
                 self.start
                 + (len(self) * self.step)
                 + (slice_stop * self.step)
-                - self.seq_len
+                - self.parent_len
             )
 
         if start >= 0 or stop >= 0:
@@ -2340,10 +2339,10 @@ class SliceRecordABC(ABC):
 
         return self.__class__(
             start=start,
-            stop=max(stop, self.start - self.seq_len - 1),
+            stop=max(stop, self.start - self.parent_len - 1),
             step=self.step * step,
             offset=self.offset,
-            seq_len=self.seq_len,
+            parent_len=self.parent_len,
             **kwargs,
         )
 
@@ -2353,28 +2352,30 @@ class SliceRecordABC(ABC):
         # max start is "true stop" + abs(step), because stop is not inclusive
         # "true stop" adjust for if abs(stop-start) % step != 0
         if slice_start >= len(self):
-            start = self.seq_len + self.start + len(self) * self.step + abs(self.step)
+            start = (
+                self.parent_len + self.start + len(self) * self.step + abs(self.step)
+            )
         elif slice_start >= 0:
-            start = self.seq_len + (self.start + slice_start * self.step)
+            start = self.parent_len + (self.start + slice_start * self.step)
         else:
-            start = self.seq_len + (
+            start = self.parent_len + (
                 self.start + len(self) * self.step + slice_start * self.step
             )
 
         if slice_stop >= 0:
-            stop = self.seq_len + (self.start + slice_stop * self.step)
-            if stop <= self.seq_len + self.stop:
+            stop = self.parent_len + (self.start + slice_stop * self.step)
+            if stop <= self.parent_len + self.stop:
                 return self._zero_slice
         else:
-            stop = self.seq_len + (
+            stop = self.parent_len + (
                 self.start + len(self) * self.step + slice_stop * self.step
             )
-            if stop > self.seq_len + self.start:
-                stop = self.seq_len + self.start + 1
+            if stop > self.parent_len + self.start:
+                stop = self.parent_len + self.start + 1
 
         # if -ve, it's an invalid slice becomes zero
         # checking for zero-length slice
-        if stop < start or start > self.seq_len or min(start, stop) < 0:
+        if stop < start or start > self.parent_len or min(start, stop) < 0:
             return self._zero_slice
 
         return self.__class__(
@@ -2382,7 +2383,7 @@ class SliceRecordABC(ABC):
             stop=stop,
             step=self.step * step,
             offset=self.offset,
-            seq_len=self.seq_len,
+            parent_len=self.parent_len,
             **kwargs,
         )
 
@@ -2421,17 +2422,14 @@ class SeqViewABC(ABC):
     @abstractmethod
     def to_rich_dict(self) -> dict: ...
 
-    def __str__(self) -> str:
-        return self.str_value
+    @abstractmethod
+    def __str__(self) -> str: ...
 
-    def __array__(self, dtype=None):
-        arr = self.array_value
-        if dtype is not None:
-            arr = arr.astype(dtype)
-        return arr
+    @abstractmethod
+    def __array__(self, dtype=None, copy=None): ...
 
-    def __bytes__(self):
-        return self.bytes_value
+    @abstractmethod
+    def __bytes__(self): ...
 
 
 class SeqView(SeqViewABC, SliceRecordABC):
@@ -2443,7 +2441,7 @@ class SeqView(SeqViewABC, SliceRecordABC):
 
     Parameters
     ----------
-    seq
+    parent
         the original sequence data
     alphabet
         the alphabet object defining valid characters for the sequence
@@ -2457,69 +2455,69 @@ class SeqView(SeqViewABC, SliceRecordABC):
         an offset used for adjusting the view's starting position. Defaults to 0
     seqid
         the name or identifier of the sequence
-    seq_len
+    parent_len
         the length of the sequence. Defaults to the length of the input sequence
 
     """
 
     __slots__ = (
-        "seq",
+        "parent",
         "alphabet",
         "start",
         "stop",
         "step",
         "_offset",
         "_seqid",
-        "_seq_len",
+        "_parent_len",
     )
 
     def __init__(
         self,
         *,
-        seq: str,
+        parent: str,
         alphabet: new_alphabet.AlphabetABC,
         start: OptInt = None,
         stop: OptInt = None,
         step: OptInt = None,
         offset: int = 0,
         seqid: OptStr = None,
-        seq_len: OptInt = None,
+        parent_len: OptInt = None,
     ):
         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
+        start, stop, step = func(len(parent), start, stop, step)
+        self.parent = parent
         self.start = start
         self.stop = stop
         self.step = step
         self._offset = offset
         self._seqid = seqid
 
-        if seq_len is not None and seq_len != len(seq):
-            raise AssertionError(f"{seq_len} != {len(self.seq)})")
-        self._seq_len = seq_len or len(self.seq)
+        if parent_len is not None and parent_len != len(parent):
+            raise AssertionError(f"{parent_len} != {len(self.parent)})")
+        self._parent_len = parent_len or len(self.parent)
 
     @property
     def _zero_slice(self):
-        return self.__class__(seq="", alphabet=self.alphabet)
+        return self.__class__(parent="", alphabet=self.alphabet)
 
     @property
     def seqid(self) -> str:
         return self._seqid
 
     @property
-    def seq_len(self) -> int:
-        return self._seq_len
+    def parent_len(self) -> int:
+        return self._parent_len
 
     def _get_init_kwargs(self):
-        return {"seq": self.seq, "seqid": self.seqid, "alphabet": self.alphabet}
+        return {"parent": self.parent, "seqid": self.seqid, "alphabet": self.alphabet}
 
     @property
     def str_value(self):
-        return self.seq[self.start : self.stop : self.step]
+        return self.parent[self.start : self.stop : self.step]
 
     @property
     def array_value(self):
@@ -2529,12 +2527,30 @@ class SeqView(SeqViewABC, SliceRecordABC):
     def bytes_value(self):
         return self.str_value.encode("utf-8")
 
+    def __str__(self) -> str:
+        return self.str_value
+
+    def __array__(self, dtype=None, copy=None) -> numpy.ndarray:
+        if copy is False:
+            raise ValueError("`copy=False` isn't supported. A copy is always created.")
+        arr = self.array_value
+        if dtype is not None:
+            arr = arr.astype(dtype)
+        return arr
+
+    def __bytes__(self) -> bytes:
+        return self.bytes_value
+
     def __repr__(self) -> str:
-        seq = f"{self.seq[:10]}...{self.seq[-5:]}" if self.seq_len > 15 else self.seq
+        seq = (
+            f"{self.parent[:10]}...{self.parent[-5:]}"
+            if self.parent_len > 15
+            else self.parent
+        )
         return (
-            f"{self.__class__.__name__}(seq={seq!r}, start={self.start}, "
+            f"{self.__class__.__name__}(parent={seq!r}, start={self.start}, "
             f"stop={self.stop}, step={self.step}, offset={self.offset}, "
-            f"seqid={self.seqid!r}, seq_len={self.seq_len})"
+            f"seqid={self.seqid!r}, parent_len={self.parent_len})"
         )
 
     def to_rich_dict(self) -> dict[str, str | dict[str, str]]:
@@ -2555,12 +2571,12 @@ class SeqView(SeqViewABC, SliceRecordABC):
 
         data["init_args"]["step"] = self.step
         if self.is_reversed:
-            adj = self.seq_len + 1
+            adj = self.parent_len + 1
             start, stop = self.stop + adj, self.start + adj
         else:
             start, stop = self.start, self.stop
 
-        data["init_args"]["seq"] = self.seq[start:stop]
+        data["init_args"]["seq"] = self.parent[start:stop]
         data["init_args"]["offset"] = int(self.parent_start)
         data["init_args"]["alphabet"] = self.alphabet.to_rich_dict()
         return data
@@ -2576,18 +2592,18 @@ class SeqView(SeqViewABC, SliceRecordABC):
         """
         if not sliced:
             return self.__class__(
-                seq=self.seq,
+                parent=self.parent,
                 seqid=self.seqid,
                 alphabet=self.alphabet,
                 start=self.start,
                 stop=self.stop,
                 step=self.step,
                 offset=self.offset,
-                seq_len=self.seq_len,
+                parent_len=self._parent_len,
             )
         data = self.to_rich_dict()
         return self.__class__(
-            seq=data["init_args"]["seq"],
+            parent=data["init_args"]["seq"],
             seqid=self.seqid,
             alphabet=self.alphabet,
             step=self.step,
@@ -2618,13 +2634,13 @@ def _(data: Sequence, seqid, alphabet) -> SeqViewABC:
 
 @_coerce_to_seqview.register
 def _(data: str, seqid, alphabet) -> SeqViewABC:
-    return SeqView(seq=data, seqid=seqid, alphabet=alphabet)
+    return SeqView(parent=data, seqid=seqid, alphabet=alphabet)
 
 
 @_coerce_to_seqview.register
 def _(data: bytes, seqid, alphabet) -> SeqViewABC:
     data = data.decode("utf8")
-    return SeqView(seq=data, seqid=seqid, alphabet=alphabet)
+    return SeqView(parent=data, seqid=seqid, alphabet=alphabet)
 
 
 @_coerce_to_seqview.register


Test patch:
diff --git a/tests/test_core/test_alignment.py b/tests/test_core/test_alignment.py
index cbcd423c2..bff4a3ead 100644
--- a/tests/test_core/test_alignment.py
+++ b/tests/test_core/test_alignment.py
@@ -1493,7 +1493,7 @@ class AlignmentBaseTests(SequenceCollectionBaseTests):
         # but setting drop_invalid=False allows calc
         dists = aln.distance_matrix(calc="paralinear", drop_invalid=True)
 
-    def test_get_gapped_seq(self):
+    def test_get_gapped_seq(self):  # ported
         """Alignment.get_gapped_seq should return seq, with gaps"""
         aln = self.Class({"seq1": "--TTT?", "seq2": "GATC??"})
         self.assertEqual(str(aln.get_gapped_seq("seq1")), "--TTT?")
@@ -3106,7 +3106,7 @@ def test_coevolution_segments():
 
 
 @pytest.mark.parametrize("cls", (Alignment, ArrayAlignment))
-def test_iter_positions(cls):
+def test_iter_positions(cls):  # ported
     data = {"a": "AAAAAA", "b": "AAA---", "c": "AAAA--"}
     r = cls({k: data[k] for k in "cb"})
     assert list(r.iter_positions(pos_order=[5, 1, 3])) == list(
@@ -3753,7 +3753,7 @@ def test_quick_tree(cls, calc, brca1_data):
 
 
 @pytest.mark.parametrize("raw", ("-AAAGGGGGAACCCT", "AAAGGGGGAACCCT"))
-def test_slice_aligned(raw):
+def test_slice_aligned(raw):  # ported
     imap, seq = DNA.make_seq(seq=raw, name="x").parse_out_gaps()
     al = Aligned(imap, seq)
     sliced = al[:-3]
diff --git a/tests/test_core/test_new_alignment.py b/tests/test_core/test_new_alignment.py
index 8b5fbbc82..900b84aae 100644
--- a/tests/test_core/test_new_alignment.py
+++ b/tests/test_core/test_new_alignment.py
@@ -41,14 +41,29 @@ def arr_seqs_dict():
 
 
 @pytest.fixture
-def alpha(moltype="dna"):
-    moltype = new_moltype.get_moltype(moltype)
+def dna_alphabet():
+    moltype = new_moltype.get_moltype("dna")
     return moltype.degen_gapped_alphabet
 
 
 @pytest.fixture
-def dna_sd(str_seqs_dict: dict[str, str], alpha):
-    return new_alignment.SeqsData(data=str_seqs_dict, alphabet=alpha)
+def dna_moltype():
+    return new_moltype.get_moltype("dna")
+
+
+@pytest.fixture
+def dna_make_seq():
+    return new_moltype.get_moltype("dna").make_seq
+
+
+@pytest.fixture
+def rna_moltype():
+    return new_moltype.get_moltype("rna")
+
+
+@pytest.fixture
+def dna_sd(str_seqs_dict: dict[str, str], dna_alphabet):
+    return new_alignment.SeqsData(data=str_seqs_dict, alphabet=dna_alphabet)
 
 
 @pytest.fixture
@@ -58,7 +73,7 @@ def int_arr():
 
 @pytest.fixture
 def sdv_s2(dna_sd: new_alignment.SeqsData) -> new_alignment.SeqDataView:
-    return dna_sd.get_seq_view(seqid="seq2")
+    return dna_sd.get_view(seqid="seq2")
 
 
 @pytest.fixture(scope="function")
@@ -133,6 +148,14 @@ def seqcoll_db(DATA_DIR):
     return seq_coll
 
 
+def make_typed(seq, data_type, moltype):
+    if data_type is numpy.ndarray:
+        seq = moltype.most_degen_alphabet().to_indices(seq)
+    elif data_type is bytes:
+        seq = seq.encode("utf-8")
+    return seq
+
+
 def test_seqs_data_default_attributes(dna_sd: new_alignment.SeqsData):
     assert dna_sd.names == ["seq1", "seq2", "seq3"]
     assert isinstance(dna_sd.alphabet, new_alphabet.CharAlphabet)
@@ -140,41 +163,41 @@ def test_seqs_data_default_attributes(dna_sd: new_alignment.SeqsData):
 
 def test_seqs_data_view_zero_step_raises(dna_sd):
     with pytest.raises(ValueError):
-        new_alignment.SeqDataView(seq=dna_sd, seqid=seq1, step=0, seq_len=4)
+        new_alignment.SeqDataView(parent=dna_sd, seqid=seq1, step=0, parent_len=4)
 
 
 @pytest.mark.parametrize("seqid", ["seq1", "seq2"])
 def test_seqs_data_view_repr_default(dna_sd: new_alignment.SeqsData, seqid: str):
     seq = dna_sd.get_seq_str(seqid=seqid)
     seq_len = len(seq)
-    expect = f"SeqDataView(seq={seq}, start=0, stop={seq_len}, step=1, offset=0, seqid='{seqid}', seq_len={seq_len})"
-    got = dna_sd.get_seq_view(seqid)
+    expect = f"SeqDataView(parent={seq}, start=0, stop={seq_len}, step=1, offset=0, seqid='{seqid}', parent_len={seq_len})"
+    got = dna_sd.get_view(seqid)
     assert repr(got) == expect
 
 
-def test_seqs_data_view_repr_default_long(alpha):
+def test_seqs_data_view_repr_default_long(dna_alphabet):
     longseq = "CGATCGTAGTACGTGTCAAGTCTGAC"
     seq_len = len(longseq)
     trunc = f"{longseq[:10]}...{longseq[-5:]}"
-    expect = f"SeqDataView(seq={trunc}, start=0, stop={seq_len}, step=1, offset=0, seqid='long', seq_len={seq_len})"
+    expect = f"SeqDataView(parent={trunc}, start=0, stop={seq_len}, step=1, offset=0, seqid='long', parent_len={seq_len})"
 
     d = {"long": longseq}
-    sd = new_alignment.SeqsData(data=d, alphabet=alpha)
-    got = sd.get_seq_view(seqid="long")
+    sd = new_alignment.SeqsData(data=d, alphabet=dna_alphabet)
+    got = sd.get_view(seqid="long")
     assert repr(got) == expect
 
 
 @pytest.mark.parametrize("seqid", ["seq1", "seq2"])
 @pytest.mark.parametrize("sliced", (False, True))
 @pytest.mark.parametrize("step", (1, -1))
-def test_seqs_data_view_copy(alpha, seqid, sliced, step):
+def test_seqs_data_view_copy(dna_alphabet, seqid, sliced, step):
     seq1 = "ATGTTCTC"
     seq2 = "ATGAACTCATT"
     start, stop = 2, 6
     data = {"seq1": seq1, "seq2": seq2}
 
-    sd = new_alignment.SeqsData(data=data, alphabet=alpha)
-    sdv = sd.get_seq_view(seqid=seqid)
+    sd = new_alignment.SeqsData(data=data, alphabet=dna_alphabet)
+    sdv = sd.get_view(seqid=seqid)
     sliced_sdv = sdv[start:stop:step]
 
     copied_sdv = sliced_sdv.copy(sliced=sliced)
@@ -183,25 +206,25 @@ def test_seqs_data_view_copy(alpha, seqid, sliced, step):
 
 
 @pytest.mark.parametrize("seqid", ["seq1", "seq2"])
-def test_seqs_data_get_seq_view(str_seqs_dict, alpha, seqid):
-    sd = new_alignment.SeqsData(data=str_seqs_dict, alphabet=alpha)
+def test_seqs_data_get_seq_view(str_seqs_dict, dna_alphabet, seqid):
+    sd = new_alignment.SeqsData(data=str_seqs_dict, alphabet=dna_alphabet)
     seq = str_seqs_dict[seqid]
-    seq_len = len(seq)
-    got = sd.get_seq_view(seqid)
+    parent_len = len(seq)
+    got = sd.get_view(seqid)
     assert isinstance(got, new_alignment.SeqDataView)
-    assert got.seq == sd
-    assert got.stop == seq_len
+    assert got.parent == sd
+    assert got.stop == parent_len
     assert got.seqid == seqid
-    assert got.seq_len == seq_len
+    assert got.parent_len == parent_len
 
 
 @pytest.mark.parametrize("seq", ("seq1", "seq2"))
 @pytest.mark.parametrize("start", (None, -1, 0, 1, 4))
 @pytest.mark.parametrize("stop", (None, -1, 0, 1, 4))
-def test_seqs_data_get_seq_str(str_seqs_dict, alpha, seq, start, stop):
+def test_seqs_data_get_seq_str(str_seqs_dict, dna_alphabet, seq, start, stop):
     # slicing should be tested in test_get_seq_array
     expect = str_seqs_dict[seq][start:stop]
-    sd = new_alignment.SeqsData(data=str_seqs_dict, alphabet=alpha)
+    sd = new_alignment.SeqsData(data=str_seqs_dict, alphabet=dna_alphabet)
     got = sd.get_seq_str(seqid=seq, start=start, stop=stop)
     assert expect == got
 
@@ -211,36 +234,36 @@ def test_seqs_data_get_seq_str_empty(dna_sd: new_alignment.SeqsData):
         dna_sd.get_seq_str()
 
 
-def test_seqs_data_names(str_seqs_dict, alpha):
+def test_seqs_data_names(str_seqs_dict, dna_alphabet):
     expect = str_seqs_dict.keys()
-    sd = new_alignment.SeqsData(data=str_seqs_dict, alphabet=alpha)
+    sd = new_alignment.SeqsData(data=str_seqs_dict, alphabet=dna_alphabet)
     got = sd.names
     # returns iterator
     assert list(got) == list(expect)
 
 
-def test_seqs_data_seq_lengths(str_seqs_dict, arr_seqs_dict, alpha):
+def test_seqs_data_seq_lengths(str_seqs_dict, arr_seqs_dict, dna_alphabet):
     expect = {k: len(v) for k, v in str_seqs_dict.items()}
-    sd = new_alignment.SeqsData(data=str_seqs_dict, alphabet=alpha)
+    sd = new_alignment.SeqsData(data=str_seqs_dict, alphabet=dna_alphabet)
     got = sd.seq_lengths()
     assert got == expect
 
     expect = {k: len(v) for k, v in arr_seqs_dict.items()}
-    sd = new_alignment.SeqsData(data=arr_seqs_dict, alphabet=alpha)
+    sd = new_alignment.SeqsData(data=arr_seqs_dict, alphabet=dna_alphabet)
     got = sd.seq_lengths()
     assert got == expect
 
 
-def test_seqs_data_get_seq_array(str_seqs_dict, alpha):
+def test_seqs_data_get_seq_array(str_seqs_dict, dna_alphabet):
     # seq1
     expect = numpy.array([2, 1, 3, 0], dtype="uint8")
-    sd = new_alignment.SeqsData(data=str_seqs_dict, alphabet=alpha)
+    sd = new_alignment.SeqsData(data=str_seqs_dict, alphabet=dna_alphabet)
     got = sd.get_seq_array(seqid="seq1")
     assert numpy.array_equal(got, expect)
 
     # seq2
     expect = numpy.array([3, 0, 0, 0, 3, 1, 2], dtype="uint8")
-    sd = new_alignment.SeqsData(data=str_seqs_dict, alphabet=alpha)
+    sd = new_alignment.SeqsData(data=str_seqs_dict, alphabet=dna_alphabet)
     got = sd.get_seq_array(seqid="seq2")
     assert numpy.array_equal(got, expect)
 
@@ -263,15 +286,15 @@ 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.seq == dna_sd
+    assert got.parent == dna_sd
     assert got.seqid == seq
 
 
 @pytest.mark.parametrize("idx", (0, 1))
-def test_seqs_data_getitem_int(str_seqs_dict, alpha, idx):
-    sd = new_alignment.SeqsData(data=str_seqs_dict, alphabet=alpha)
+def test_seqs_data_getitem_int(str_seqs_dict, dna_alphabet, idx):
+    sd = new_alignment.SeqsData(data=str_seqs_dict, alphabet=dna_alphabet)
     got = sd[idx]
-    assert got.seq == sd
+    assert got.parent == sd
     assert got.seqid == list(str_seqs_dict)[idx]
 
 
@@ -281,7 +304,7 @@ def test_seqs_data_getitem_str(
     seqid,
 ):
     got = dna_sd[seqid]
-    assert got.seq == dna_sd
+    assert got.parent == dna_sd
     assert got.seqid == seqid
 
 
@@ -354,9 +377,9 @@ def test_seqs_data_to_alphabet_invalid():
 
 
 @pytest.mark.parametrize("reverse", (False, True))
-def test_seqs_data_round_trip(reverse, alpha):
+def test_seqs_data_round_trip(reverse, dna_alphabet):
     seqs_data = new_alignment.SeqsData(
-        data={"seq1": "ACGG", "seq2": "CGCA", "seq3": "CCG-"}, alphabet=alpha
+        data={"seq1": "ACGG", "seq2": "CGCA", "seq3": "CCG-"}, alphabet=dna_alphabet
     )
     seqs_data = seqs_data.reverse_seqs() if reverse else seqs_data
 
@@ -365,7 +388,7 @@ def test_seqs_data_round_trip(reverse, alpha):
     assert isinstance(got, new_alignment.SeqsData)
     assert got.to_rich_dict() == seqs_data.to_rich_dict()
     expect = "ACGG"[::-1] if reverse else "ACGG"
-    assert str(got.get_seq_view("seq1")) == expect
+    assert str(got.get_view("seq1")) == expect
 
 
 @pytest.mark.parametrize(
@@ -380,7 +403,7 @@ def test_seqs_data_round_trip(reverse, alpha):
     ],
 )
 def test_seq_data_view_slice_returns_self(seq1: str, index: slice):
-    sdv = new_alignment.SeqDataView(seq=seq1, seqid="seq1", seq_len=len(seq1))
+    sdv = new_alignment.SeqDataView(parent=seq1, seqid="seq1", parent_len=len(seq1))
     got = sdv[index]
     assert isinstance(got, new_alignment.SeqDataView)
 
@@ -389,12 +412,12 @@ def test_seq_data_view_slice_returns_self(seq1: str, index: slice):
 @pytest.mark.parametrize("start", (None, 0, 1, 4, -1, -4))
 @pytest.mark.parametrize("stop", (None, 0, 1, 4, -1, -4))
 @pytest.mark.parametrize("step", (None, 1, 2, 3, -1, -2, -3))
-def test_seq_data_view_value(str_seqs_dict: dict, alpha, start, stop, step):
+def test_seq_data_view_value(str_seqs_dict: dict, dna_alphabet, start, stop, step):
     seq = "seq2"
     expect = str_seqs_dict[seq][start:stop:step]
-    sd = new_alignment.SeqsData(data=str_seqs_dict, alphabet=alpha)
+    sd = new_alignment.SeqsData(data=str_seqs_dict, alphabet=dna_alphabet)
     # Get SeqDataView on seq
-    sdv = sd.get_seq_view(seqid=seq)
+    sdv = sd.get_view(seqid=seq)
     sdv2 = sdv[start:stop:step]
     got = sdv2.str_value
     assert got == expect
@@ -405,7 +428,7 @@ def test_seq_data_view_to_rich_dict(rev):
     data = {"seq1": "ACGG", "seq2": "CGCA", "seq3": "CCG-"}
     alpha = new_moltype.DNA.degen_gapped_alphabet
     sd = new_alignment.SeqsData(data=data, alphabet=alpha)
-    sdv = sd.get_seq_view(seqid="seq1")
+    sdv = sd.get_view(seqid="seq1")
     sdv = sdv[::-1] if rev else sdv
     got = sdv.to_rich_dict()
     expect = {
@@ -413,8 +436,8 @@ def test_seq_data_view_to_rich_dict(rev):
             "seqid": "seq1",
             "step": sdv.step,
             "offset": sdv.offset,
-            "seq_len": sdv.seq_len,
-            "seq": sdv.str_value,
+            "parent_len": sdv.parent_len,
+            "parent": sdv.str_value,
         },
         "type": get_object_provenance(sdv),
         "version": __version__,
@@ -426,12 +449,12 @@ def test_seq_data_view_to_rich_dict(rev):
 @pytest.mark.parametrize("start", (None, 0, 1, 4, -1, -4))
 @pytest.mark.parametrize("stop", (None, 0, 1, 4, -1, -4))
 @pytest.mark.parametrize("step", (None, 1, 2, 3, -1, -2, -3))
-def test_seqs_data_array_value(arr_seqs_dict: dict, alpha, start, stop, step):
+def test_seqs_data_array_value(arr_seqs_dict: dict, dna_alphabet, start, stop, step):
     seq = "seq2"
     expect = arr_seqs_dict[seq][start:stop:step]
-    sd = new_alignment.SeqsData(data=arr_seqs_dict, alphabet=alpha)
+    sd = new_alignment.SeqsData(data=arr_seqs_dict, alphabet=dna_alphabet)
     # Get SeqDataView on seq
-    sdv = sd.get_seq_view(seqid=seq)
+    sdv = sd.get_view(seqid=seq)
     got = sdv.array_value[start:stop:step]
     assert numpy.array_equal(got, expect)
 
@@ -439,13 +462,13 @@ def test_seqs_data_array_value(arr_seqs_dict: dict, alpha, start, stop, step):
 @pytest.mark.parametrize("start", (None, 0, 1, 4, -1, -4))
 @pytest.mark.parametrize("stop", (None, 0, 1, 4, -1, -4))
 @pytest.mark.parametrize("step", (None, 1, 2, 3, -1, -2, -3))
-def test_seqs_data_bytes_value(str_seqs_dict: dict, alpha, start, stop, step):
+def test_seqs_data_bytes_value(str_seqs_dict: dict, dna_alphabet, start, stop, step):
     seq = "seq2"
     expect = str_seqs_dict[seq][start:stop:step]
     expect = expect.encode("utf8")
-    sd = new_alignment.SeqsData(data=str_seqs_dict, alphabet=alpha)
+    sd = new_alignment.SeqsData(data=str_seqs_dict, alphabet=dna_alphabet)
     # Get SeqDataView on seq
-    sdv = sd.get_seq_view(seqid=seq)
+    sdv = sd.get_view(seqid=seq)
     got = sdv.bytes_value[start:stop:step]
     assert expect == got
 
@@ -539,11 +562,13 @@ def test_sequence_collection_init(seqs):
     assert seqs.seqs.make_seq is not None
 
 
-def test_sequence_collection_names_is_list():
+@pytest.mark.parametrize(
+    "collection_maker",
+    (new_alignment.make_unaligned_seqs, new_alignment.make_aligned_seqs),
+)
+def test_sequence_collection_names_is_list(collection_maker):
     """expected to be a list"""
-    seqs = new_alignment.make_unaligned_seqs(
-        {"a": b"AAAAA", "b": b"TTTTT"}, moltype="dna"
-    )
+    seqs = collection_maker({"a": b"AAAAA", "b": b"TTTTT"}, moltype="dna")
     assert isinstance(seqs.names, list)
 
 
@@ -1769,12 +1794,16 @@ def test_sequence_collection_dotplot_annotated():
     _ = seqs.dotplot(show_progress=False)
 
 
-def test_sequence_collection_add_seqs():
+@pytest.mark.parametrize(
+    "collection_maker",
+    [new_alignment.make_unaligned_seqs, new_alignment.make_aligned_seqs],
+)
+def test_sequence_collection_add_seqs(collection_maker):
     data = dict(
         [("name1", "AAA"), ("name2", "AAA"), ("name3", "AAA"), ("name4", "AAA")]
     )
-    data2 = dict([("name5", "BBB"), ("name6", "CCC")])
-    aln = new_alignment.make_unaligned_seqs(data, moltype="text", info={"key": "foo"})
+    data2 = dict([("name5", "TTT"), ("name6", "CCC")])
+    aln = collection_maker(data, moltype="dna", info={"key": "foo"})
     out_aln = aln.add_seqs(data2)
     assert len(out_aln.names) == 6
 
@@ -2177,3 +2206,513 @@ def test_sequence_collection_reverse_complement():
 
     got = got.reverse_complement()
     assert got.to_dict() == data
+
+
+# Alignment tests
+
+
+@pytest.fixture
+def aligned_dict():
+    return dict(seq1="ACG--T", seq2="-CGAAT", seq3="------", seq4="--GA--")
+
+
+@pytest.fixture
+def aligned_array_dict():
+    return dict(
+        seq1=numpy.array([1, 2, 3, 4, 4], dtype=numpy.uint8),
+        seq2=numpy.array([4, 2, 4, 3, 4], dtype=numpy.uint8),
+        seq3=numpy.array([4, 4, 4, 4, 4], dtype=numpy.uint8),
+        seq4=numpy.array([4, 4, 1, 4, 4], dtype=numpy.uint8),
+    )
+
+
+@pytest.fixture
+def aligned_seqs_data(aligned_dict, dna_alphabet, dna_make_seq):
+    return new_alignment.AlignedSeqsData.from_aligned_seqs(
+        data=aligned_dict, alphabet=dna_alphabet, make_seq=dna_make_seq
+    )
+
+
+@pytest.fixture
+def gap_seqs():
+    return [
+        ("A---CTG-C", [[1, 3], [4, 4]]),
+        ("-GTAC----", [[0, 1], [4, 5]]),
+        ("---A--T--", [[0, 3], [1, 5], [2, 7]]),
+    ]
+
+
+@pytest.mark.parametrize("seqid", ("seq1", "seq2", "seq3", "seq4"))
+def test_aligned_initialisation(aligned_dict, seqid, dna_alphabet, dna_make_seq):
+    """indexing a AlignedSeqsData object should return an Aligned object"""
+    ad = new_alignment.AlignedSeqsData.from_aligned_seqs(
+        data=aligned_dict, alphabet=dna_alphabet, make_seq=dna_make_seq
+    )
+    got = ad[seqid]
+    assert isinstance(got, new_alignment.Aligned)
+    assert got.data.parent is ad
+
+
+@pytest.mark.parametrize("start, stop", [(0, 6), (0, 3), (3, 6), (1, 2)])
+@pytest.mark.parametrize("seqid", ("seq1", "seq2", "seq3", "seq4"))
+def test_aligned_str(aligned_dict, seqid, start, stop, dna_moltype):
+    """str() of an Aligned instance should return gapped sequence cast to a string"""
+    aln = new_alignment.make_aligned_seqs(aligned_dict, moltype=dna_moltype)
+    aligned = aln.seqs[seqid][start:stop]
+    got = str(aligned)
+    expect = aligned_dict[seqid][start:stop]
+    assert got == expect
+
+
+@pytest.mark.parametrize("seqid", ("seq1", "seq2", "seq3", "seq4"))
+def test_aligned_array(aligned_dict, seqid, dna_moltype):
+    """array() of an Aligned instance should return gapped sequence as an array"""
+    aln = new_alignment.make_aligned_seqs(aligned_dict, moltype=dna_moltype)
+    aligned = aln.seqs[seqid]
+    got = numpy.array(aligned)
+    expect = dna_moltype.degen_gapped_alphabet.to_indices(aligned_dict[seqid])
+    assert numpy.array_equal(got, expect)
+
+
+@pytest.mark.parametrize("seqid", ("seq1", "seq2", "seq3", "seq4"))
+def test_aligned_bytes(aligned_dict, seqid, dna_moltype):
+    aln = new_alignment.make_aligned_seqs(aligned_dict, moltype=dna_moltype)
+    aligned = aln.seqs[seqid]
+    got = bytes(aligned)
+    expect = dna_moltype.degen_gapped_alphabet.array_to_bytes(
+        dna_moltype.degen_gapped_alphabet.to_indices(aligned_dict[seqid])
+    )
+    assert got == expect
+
+
+@pytest.mark.parametrize(
+    "slicer",
+    (
+        slice(0, 3),
+        slice(1, 4),
+        slice(3, 6),
+        slice(None, -3),
+        slice(-10, -3),
+        slice(-10, None),
+    ),
+)
+@pytest.mark.parametrize("seqid", ("seq1", "seq2"))
+def test_slice_aligned(seqid, slicer, dna_alphabet, dna_make_seq):
+    seqs = dict(seq1="-AAGGGGGAACCCT", seq2="AAAGGGGGAACCCT")
+    aligned_seqs_data = new_alignment.AlignedSeqsData.from_aligned_seqs(
+        data=seqs, alphabet=dna_alphabet, make_seq=dna_make_seq
+    )
+    al = aligned_seqs_data[seqid]
+    sliced = al[slicer]
+    assert str(sliced) == seqs[seqid][slicer]
+
+
+@pytest.mark.parametrize("seqid", ("seq1", "seq2"))
+@pytest.mark.parametrize("i", (*range(6), slice(0, 2), slice(1, 4), slice(3, 5)))
+def test_aligned_seq_returns_sequence(seqid, i, dna_alphabet, dna_make_seq):
+    """Aligned.seq should return the sequence without gaps, if the index corresponds
+    to a gap position, it will return an empty Sequence"""
+    aligned_dict = dict(seq1="ACG--T", seq2="-CGAAT")
+    ad = new_alignment.AlignedSeqsData.from_aligned_seqs(
+        data=aligned_dict, alphabet=dna_alphabet, make_seq=dna_make_seq
+    )
+    aligned = ad[seqid]
+    got = aligned[i].seq
+    expect = aligned_dict[seqid][i].replace("-", "")
+    assert got == expect
+
+
+@pytest.mark.parametrize("i", range(3))
+def test_seq_to_gap_coords_str(gap_seqs, i, dna_alphabet, dna_make_seq):
+    seq, gap_coords = gap_seqs[i]
+    got_ungapped, got_map = new_alignment.seq_to_gap_coords(
+        seq, alphabet=dna_alphabet, make_seq=dna_make_seq
+    )
+    assert got_ungapped == seq.replace("-", "")
+    assert numpy.array_equal(got_map, gap_coords)
+
+
+def test_seq_to_gap_coords_str_all_gaps(dna_alphabet, dna_make_seq):
+    parent_seq = "-----"
+    expect_gaplen = numpy.array([len(parent_seq)])
+    got_ungap, got_map = new_alignment.seq_to_gap_coords(
+        parent_seq, alphabet=dna_alphabet, make_seq=dna_make_seq
+    )
+    assert got_ungap == ""
+    assert got_map[:, 1] == expect_gaplen
+
+
+def test_seq_to_gap_coords_str_no_gaps(dna_alphabet, dna_make_seq):
+    parent_seq = "ACTGC"
+    got_ungap, got_empty_arr = new_alignment.seq_to_gap_coords(
+        parent_seq, alphabet=dna_alphabet, make_seq=dna_make_seq
+    )
+    assert got_ungap == parent_seq
+    assert got_empty_arr.size == 0
+
+
+def test_seq_to_gap_coords_arr_all_gaps(dna_alphabet, dna_make_seq):
+    parent_seq = dna_alphabet.to_indices("-----")
+    got_ungap, got_map = new_alignment.seq_to_gap_coords(
+        parent_seq, alphabet=dna_alphabet, make_seq=dna_make_seq
+    )
+    assert got_ungap.size == 0
+    assert numpy.array_equal(got_map, numpy.array([[0, 5]]))
+
+
+def test_seq_to_gap_coords_arr_no_gaps(dna_alphabet, dna_make_seq):
+    parent_seq = dna_alphabet.to_indices("ACTGC")
+    got_ungap, got_empty_arr = new_alignment.seq_to_gap_coords(
+        parent_seq, alphabet=dna_alphabet, make_seq=dna_make_seq
+    )
+    assert numpy.array_equal(got_ungap, parent_seq)
+    assert got_empty_arr.size == 0
+
+
+@pytest.mark.parametrize("i", range(3))
+def test_seq_to_gap_coords_arr(gap_seqs, i, dna_alphabet, dna_make_seq):
+    seq, gap_coords = gap_seqs[i]
+    seq = dna_alphabet.to_indices(seq)  # convert to array repr
+    got_ungapped, got_map = new_alignment.seq_to_gap_coords(
+        seq, alphabet=dna_alphabet, make_seq=dna_make_seq
+    )
+    assert numpy.array_equal(got_ungapped, seq[seq != 4])  # gap_char = 4
+    assert numpy.array_equal(got_map, gap_coords)
+
+
+@pytest.mark.parametrize("i", range(3))
+def test_seq_to_gap_coords_arr_dispatch_equal(gap_seqs, i, dna_alphabet, dna_make_seq):
+    """seq_to_gap_coords should return the same gap coords when input is a string/array/bytes"""
+    seq_str, _ = gap_seqs[i]
+    seq_array = dna_alphabet.to_indices(seq_str)
+    seq_bytes = dna_alphabet.array_to_bytes(seq_array)
+    _, got_from_str = new_alignment.seq_to_gap_coords(
+        seq_str, alphabet=dna_alphabet, make_seq=dna_make_seq
+    )
+    _, got_from_arr = new_alignment.seq_to_gap_coords(
+        seq_array, alphabet=dna_alphabet, make_seq=dna_make_seq
+    )
+    _, got_from_bytes = new_alignment.seq_to_gap_coords(
+        seq_bytes, alphabet=dna_alphabet, make_seq=dna_make_seq
+    )
+    assert numpy.array_equal(got_from_str, got_from_arr)
+    assert numpy.array_equal(got_from_str, got_from_bytes)
+
+
+def test_aligned_seqs_data_from_string_unequal_seqlens(dna_alphabet, dna_make_seq):
+    data = dict(seq1="A-A", seq2="AAAAAAA--")
+    with pytest.raises(ValueError):
+        _ = new_alignment.AlignedSeqsData.from_aligned_seqs(
+            data=data, alphabet=dna_alphabet, make_seq=dna_make_seq
+        )
+
+
+def test_aligned_seqs_data_from_string_returns_self(
+    aligned_dict, dna_alphabet, dna_make_seq
+):
+    got = new_alignment.AlignedSeqsData.from_aligned_seqs(
+        data=aligned_dict, alphabet=dna_alphabet, make_seq=dna_make_seq
+    )
+    assert isinstance(got, new_alignment.AlignedSeqsData)
+    assert got.names == ["seq1", "seq2", "seq3", "seq4"]
+    assert got.align_len == 6
+
+
+def test_aligned_seqs_data_init_check(gap_seqs, dna_alphabet):
+    seqs = {f"seq_{i}": seq.replace("-", "") for i, (seq, _) in enumerate(gap_seqs)}
+    gaps = {f"seq_{i}": numpy.array(gaps) for i, (_, gaps) in enumerate(gap_seqs)}
+    ad = new_alignment.AlignedSeqsData(seqs=seqs, gaps=gaps, alphabet=dna_alphabet)
+    assert ad.get_seq_str(seqid="seq_0") == seqs["seq_0"]
+    assert numpy.array_equal(ad.get_gaps(seqid="seq_0"), gaps["seq_0"])
+
+
+@pytest.mark.parametrize("seqid", ("seq1", "seq2", "seq3", "seq4"))
+@pytest.mark.parametrize("moltype", ("dna_moltype", "rna_moltype"))
+def test_aligned_seqs_data_get_seq_array(aligned_array_dict, seqid, moltype, request):
+    moltype = request.getfixturevalue(moltype)
+    ad = new_alignment.AlignedSeqsData.from_aligned_seqs(
+        data=aligned_array_dict,
+        alphabet=moltype.degen_gapped_alphabet,
+        make_seq=moltype.make_seq,
+    )
+    got = ad.get_seq_array(seqid=seqid)
+    expect = aligned_array_dict[seqid]
+    expect = expect[expect != 4]  # remove gaps
+    assert numpy.array_equal(got, expect)
+
+
+@pytest.mark.parametrize("seqid", ("seq1", "seq2", "seq3", "seq4"))
+@pytest.mark.parametrize("moltype", ("dna_moltype", "rna_moltype"))
+def test_aligned_seqs_data_get_gapped_seq_array(
+    aligned_array_dict, seqid, moltype, request
+):
+    moltype = request.getfixturevalue(moltype)
+    ad = new_alignment.AlignedSeqsData.from_aligned_seqs(
+        data=aligned_array_dict,
+        alphabet=moltype.degen_gapped_alphabet,
+        make_seq=moltype.make_seq,
+    )
+    got = ad.get_gapped_seq_array(seqid=seqid)
+    expect = aligned_array_dict[seqid]
+    assert numpy.array_equal(got, expect)
+
+
+@pytest.mark.parametrize("seqid", ("seq1", "seq2", "seq3", "seq4"))
+@pytest.mark.parametrize("moltype", ("dna_moltype", "rna_moltype"))
+def test_aligned_seqs_data_get_seq_str(aligned_array_dict, seqid, moltype, request):
+    moltype = request.getfixturevalue(moltype)
+    ad = new_alignment.AlignedSeqsData.from_aligned_seqs(
+        data=aligned_array_dict,
+        alphabet=moltype.degen_gapped_alphabet,
+        make_seq=moltype.make_seq,
+    )
+    got = ad.get_seq_str(seqid=seqid)
+    expect = moltype.degen_gapped_alphabet.from_indices(aligned_array_dict[seqid])
+    expect = expect.replace("-", "")  # remove gaps
+    assert got == expect
+
+
+@pytest.mark.parametrize("seqid", ("seq1", "seq2", "seq3", "seq4"))
+@pytest.mark.parametrize("moltype", ("dna_moltype", "rna_moltype"))
+def test_aligned_seqs_data_get_gapped_seq_str(
+    aligned_array_dict, seqid, moltype, request
+):
+    moltype = request.getfixturevalue(moltype)
+    ad = new_alignment.AlignedSeqsData.from_aligned_seqs(
+        data=aligned_array_dict,
+        alphabet=moltype.degen_gapped_alphabet,
+        make_seq=moltype.make_seq,
+    )
+    got = ad.get_gapped_seq_str(seqid=seqid)
+    expect = moltype.degen_gapped_alphabet.from_indices(aligned_array_dict[seqid])
+    assert got == expect
+
+
+@pytest.mark.parametrize("seqid", ("seq1", "seq2", "seq3", "seq4"))
+@pytest.mark.parametrize("moltype", ("dna_moltype", "rna_moltype"))
+def test_aligned_seqs_data_get_seq_bytes(aligned_array_dict, seqid, moltype, request):
+    moltype = request.getfixturevalue(moltype)
+    ad = new_alignment.AlignedSeqsData.from_aligned_seqs(
+        data=aligned_array_dict,
+        alphabet=moltype.degen_gapped_alphabet,
+        make_seq=moltype.make_seq,
+    )
+    got = ad.get_seq_bytes(seqid=seqid)
+    raw = aligned_array_dict[seqid]
+    raw = raw[raw != 4]
+    expect = moltype.degen_gapped_alphabet.array_to_bytes(raw)
+    assert got == expect
+
+
+@pytest.mark.parametrize("seqid", ("seq1", "seq2", "seq3", "seq4"))
+@pytest.mark.parametrize("moltype", ("dna_moltype", "rna_moltype"))
+def test_aligned_seqs_data_get_gapped_seq_bytes(
+    aligned_array_dict, seqid, moltype, request
+):
+    moltype = request.getfixturevalue(moltype)
+    ad = new_alignment.AlignedSeqsData.from_aligned_seqs(
+        data=aligned_array_dict,
+        alphabet=moltype.degen_gapped_alphabet,
+        make_seq=moltype.make_seq,
+    )
+    got = ad.get_gapped_seq_bytes(seqid=seqid)
+    raw = aligned_array_dict[seqid]
+    expect = moltype.degen_gapped_alphabet.array_to_bytes(raw)
+    assert got == expect
+
+
+@pytest.mark.parametrize("seqid", ("seq1", "seq2", "seq3", "seq4"))
+def test_aligned_seqs_data_seq_lengths(seqid, aligned_dict, dna_alphabet, dna_make_seq):
+    ad = new_alignment.AlignedSeqsData.from_aligned_seqs(
+        data=aligned_dict, alphabet=dna_alphabet, make_seq=dna_make_seq
+    )
+    got = ad.seq_lengths()[seqid]
+    expect = len(aligned_dict[seqid].replace("-", ""))
+    assert got == expect
+
+
+def test_aligned_seqs_data_add_seqs(dna_alphabet, dna_make_seq):
+    data = {"seq1": "ACGT-", "seq2": "ACG-T", "seq3": "A---T"}
+    ad = new_alignment.AlignedSeqsData.from_aligned_seqs(
+        data=data, alphabet=dna_alphabet, make_seq=dna_make_seq
+    )
+    new_data = {"seq4": "ACGTT", "seq5": "ACG--", "seq6": "-----"}
+    new_ad = ad.add_seqs(new_data)
+    assert new_ad.names == ["seq1", "seq2", "seq3", "seq4", "seq5", "seq6"]
+
+
+def test_aligned_seqs_data_add_seqs_diff_lengths_raises(dna_alphabet, dna_make_seq):
+    """adding sequences of different lengths should raise an error"""
+
+    data = {"seq1": "ACGT-", "seq2": "ACG-T"}
+    ad = new_alignment.AlignedSeqsData.from_aligned_seqs(
+        data=data, alphabet=dna_alphabet, make_seq=dna_make_seq
+    )
+    new_data = {"seq3": "AC", "seq4": "A-"}
+    with pytest.raises(ValueError):
+        _ = ad.add_seqs(new_data)
+
+    new_data = {"seq3": "AAA", "seq4": "A-"}
+    with pytest.raises(ValueError):
+        _ = ad.add_seqs(new_data)
+
+
+def test_aligned_seqs_data_add_seqs_diff_moltype_raises(dna_alphabet, dna_make_seq):
+    """adding sequences of different moltype should raise an error"""
+    data = {"seq1": "ACGT-", "seq2": "ACG-T"}  # DNA
+    ad = new_alignment.AlignedSeqsData.from_aligned_seqs(
+        data=data, alphabet=dna_alphabet, make_seq=dna_make_seq
+    )
+    new_data = {"seq3": "ACGU-", "seq4": "ACG-U"}  # RNA
+    with pytest.raises(AssertionError):
+        _ = ad.add_seqs(new_data)
+
+
+def test_aligned_seqs_data_add_seqs_duplicate_keys_raises(dna_alphabet, dna_make_seq):
+    """AlignedSeqsData.add_seqs should raise an error if their are duplicated
+    sequence names"""
+    data = {"seq1": "ACGT-", "seq2": "ACG-T"}
+    ad = new_alignment.AlignedSeqsData.from_aligned_seqs(
+        data=data, alphabet=dna_alphabet, make_seq=dna_make_seq
+    )
+    new_data = {"seq2": "ACGT-", "seq3": "ACT-T"}  # seq2 is duplicated
+    with pytest.raises(ValueError):
+        _ = ad.add_seqs(new_data)
+
+    # if we set force_unique_keys=False, it should not raise an error
+    new_ad = ad.add_seqs(new_data, force_unique_keys=False)
+    assert new_ad.names == ["seq1", "seq2", "seq3"]
+
+
+@pytest.mark.parametrize("seqid", ("seq1", "seq2", "seq3", "seq4"))
+def test_aligned_seqs_data_get_aligned_view(
+    aligned_dict, seqid, dna_alphabet, dna_make_seq
+):
+    ad = new_alignment.AlignedSeqsData.from_aligned_seqs(
+        data=aligned_dict, alphabet=dna_alphabet, make_seq=dna_make_seq
+    )
+    got = ad.get_view(seqid)
+    assert isinstance(got, new_alignment.AlignedDataView)
+    assert got.parent == ad
+    assert got.stop == ad.align_len
+    assert got.parent_len == ad.align_len
+
+
+@pytest.mark.parametrize("seqid", ("seq1", "seq2", "seq3", "seq4"))
+def test_aligned_data_view_array(aligned_array_dict, dna_alphabet, dna_make_seq, seqid):
+    ad = new_alignment.AlignedSeqsData.from_aligned_seqs(
+        data=aligned_array_dict, alphabet=dna_alphabet, make_seq=dna_make_seq
+    )
+    view = ad.get_view(seqid)
+    got = numpy.array(view)
+    expect = aligned_array_dict[seqid][aligned_array_dict[seqid] != 4]  # remove gaps
+    assert numpy.array_equal(got, expect)
+
+
+@pytest.mark.parametrize("seqid", ("seq1", "seq2", "seq3", "seq4"))
+def test_aligned_data_view_bytes(aligned_array_dict, dna_alphabet, dna_make_seq, seqid):
+    ad = new_alignment.AlignedSeqsData.from_aligned_seqs(
+        data=aligned_array_dict, alphabet=dna_alphabet, make_seq=dna_make_seq
+    )
+    view = ad.get_view(seqid)
+    got = bytes(view)
+    expect = aligned_array_dict[seqid][aligned_array_dict[seqid] != 4]  # remove gaps
+    expect = dna_alphabet.array_to_bytes(expect)  # convert to bytes
+    assert numpy.array_equal(got, expect)
+
+
+@pytest.mark.parametrize("seqid", ("seq1", "seq2", "seq3", "seq4"))
+def test_aligned_data_view_zero_slice(
+    aligned_array_dict, dna_alphabet, dna_make_seq, seqid
+):
+    # an invalid slice should return a empty view
+    ad = new_alignment.AlignedSeqsData.from_aligned_seqs(
+        data=aligned_array_dict, alphabet=dna_alphabet, make_seq=dna_make_seq
+    )
+    view = ad.get_view(seqid)
+    got = view[:0]
+    assert len(got) == 0
+    got = view[15:20]
+    print(got)
+    assert len(got) == 0
+
+
+def test_alignment_init(aligned_dict, dna_moltype, dna_alphabet, dna_make_seq):
+    ad = new_alignment.AlignedSeqsData.from_aligned_seqs(
+        data=aligned_dict, alphabet=dna_alphabet, make_seq=dna_make_seq
+    )
+    aln = new_alignment.Alignment(seqs_data=ad, moltype=dna_moltype)
+    assert aln.moltype == dna_moltype
+
+
+def test_make_aligned_seqs_dict(aligned_dict):
+    aln = new_alignment.make_aligned_seqs(aligned_dict, moltype="dna")
+    assert isinstance(aln, new_alignment.Alignment)
+    # if we index a seq, it should be an Aligned instance
+    assert isinstance(aln.seqs["seq1"], new_alignment.Aligned)
+    # if we use .get_seq, it should be a Sequence instance
+    assert isinstance(aln.get_seq("seq1"), new_sequence.Sequence)
+    assert aln.names == ["seq1", "seq2", "seq3", "seq4"]
+    assert aln.to_dict() == aligned_dict
+
+
+def test_make_aligned_seqs_aligned_seqs_data(aligned_dict, dna_alphabet, dna_make_seq):
+    aligned_seqs_data = new_alignment.AlignedSeqsData.from_aligned_seqs(
+        data=aligned_dict, alphabet=dna_alphabet, make_seq=dna_make_seq
+    )
+    aln = new_alignment.make_aligned_seqs(aligned_seqs_data, moltype="dna")
+    assert isinstance(aln, new_alignment.Alignment)
+    assert aln.moltype.label == "dna"
+    assert aln.names == ["seq1", "seq2", "seq3", "seq4"]
+    assert aligned_dict == aln.to_dict()
+
+
+def test_make_aligned_seqs_incompatible_moltype(
+    aligned_dict, dna_alphabet, dna_make_seq
+):
+    ad = new_alignment.AlignedSeqsData.from_aligned_seqs(
+        data=aligned_dict, alphabet=dna_alphabet, make_seq=dna_make_seq
+    )
+    with pytest.raises(ValueError):
+        _ = new_alignment.make_aligned_seqs(ad, moltype="rna")
+
+
+def test_make_aligned_seqs_incompatible_type():
+    with pytest.raises(NotImplementedError):
+        _ = new_alignment.make_aligned_seqs("TGCA", moltype="dna")
+
+
+def test_alignment_get_gapped_seq():
+    """Alignment.get_gapped_seq should return seq, with gaps"""
+    aln = new_alignment.make_aligned_seqs(
+        {"seq1": "--TTT?", "seq2": "GATC??"}, moltype="dna"
+    )
+    got = aln.get_gapped_seq(seqname="seq1")
+    expect = "--TTT?"
+    assert got == expect
+
+
+def test_iter_positions():
+    data = {"a": "AAAAAA", "b": "AAA---", "c": "AAAA--"}
+    r = new_alignment.make_aligned_seqs({k: data[k] for k in "cb"}, moltype="dna")
+    assert list(r.iter_positions(pos_order=[5, 1, 3])) == list(
+        map(list, ["--", "AA", "A-"])
+    )
+    # reorder names
+    r = new_alignment.make_aligned_seqs(data, moltype="dna")
+    cols = list(r.iter_positions())
+    assert cols == list(map(list, ["AAA", "AAA", "AAA", "A-A", "A--", "A--"]))
+
+
+def test_alignment_to_dict():
+    data = {"a": "--A-BC-", "b": "-CB-A--", "c": "--D-EF-"}
+    aln = new_alignment.make_aligned_seqs(data, moltype="protein")
+    assert aln.to_dict() == data
+
+
+def test_alignment_get_lengths():
+    data = {"a": "--C-CC-", "b": "--T-T--", "c": "--A-GG-"}
+    aln = new_alignment.make_aligned_seqs(data, moltype="dna")
+    got = aln.get_lengths()
+    expect = {name: len(seq.replace("-", "")) for name, seq in data.items()}
+    assert got == expect
diff --git a/tests/test_core/test_new_sequence.py b/tests/test_core/test_new_sequence.py
index 96c25fab9..54024faf8 100644
--- a/tests/test_core/test_new_sequence.py
+++ b/tests/test_core/test_new_sequence.py
@@ -36,7 +36,7 @@ def bytes_alphabet():
 @pytest.fixture(scope="function")
 def integer_seq(bytes_alphabet):
     """Used for slicing tests"""
-    return new_sequence.SeqView(seq="0123456789", alphabet=bytes_alphabet)
+    return new_sequence.SeqView(parent="0123456789", alphabet=bytes_alphabet)
 
 
 @pytest.mark.parametrize("name", ("dna", "rna", "protein", "protein_with_stop", "text"))
@@ -1003,7 +1003,7 @@ def test_seqview_initialisation(start, stop, step, bytes_alphabet):
     """Initialising a SeqView should work with range of provided values"""
     seq_data = "0123456789"
     got = new_sequence.SeqView(
-        seq=seq_data, start=start, stop=stop, step=step, alphabet=bytes_alphabet
+        parent=seq_data, start=start, stop=stop, step=step, alphabet=bytes_alphabet
     )
     expected = seq_data[start:stop:step]
     assert got.str_value == expected
@@ -1013,7 +1013,7 @@ def test_seqview_initialisation(start, stop, step, bytes_alphabet):
 def test_seqview_index(index, bytes_alphabet):
     """SeqView with default values can be sliced with a single index, when within the length of the sequence"""
     seq_data = "0123456789"
-    sv = new_sequence.SeqView(seq=seq_data, alphabet=bytes_alphabet)
+    sv = new_sequence.SeqView(parent=seq_data, alphabet=bytes_alphabet)
     got = sv[index]
     expected = seq_data[index]
     assert got.str_value == expected
@@ -1022,18 +1022,18 @@ def test_seqview_index(index, bytes_alphabet):
 
 def test_seqview_index_null(ascii_alphabet):
     "Indexing a SeqView of length 0 should return an IndexError"
-    sv = new_sequence.SeqView(seq="", alphabet=ascii_alphabet)
+    sv = new_sequence.SeqView(parent="", alphabet=ascii_alphabet)
     with pytest.raises(IndexError):
         _ = sv[0]
 
 
 def test_seqview_step_0(bytes_alphabet):
     "Initialising or slicing a SeqView with a step of 0 should return an IndexError"
-    sv = new_sequence.SeqView(seq="0123456789", alphabet=bytes_alphabet)
+    sv = new_sequence.SeqView(parent="0123456789", alphabet=bytes_alphabet)
     with pytest.raises(ValueError):
         _ = sv[::0]
     with pytest.raises(ValueError):
-        _ = new_sequence.SeqView(seq="0123456789", alphabet=bytes_alphabet, step=0)
+        _ = new_sequence.SeqView(parent="0123456789", alphabet=bytes_alphabet, step=0)
 
 
 @pytest.mark.parametrize("start", (0, 2, 4))
@@ -1044,7 +1044,7 @@ def test_seqview_invalid_index(start, bytes_alphabet):
     pos_boundary_index = length
     neg_boundary_index = -length - 1
 
-    sv = new_sequence.SeqView(seq=seq, start=start, alphabet=bytes_alphabet)
+    sv = new_sequence.SeqView(parent=seq, start=start, alphabet=bytes_alphabet)
     with pytest.raises(IndexError):
         _ = sv[pos_boundary_index]
     with pytest.raises(IndexError):
@@ -1060,7 +1060,9 @@ def test_seqview_invalid_index_positive_step_gt_1(start, bytes_alphabet):
     neg_boundary_index = -length - 1
     pos_boundary_index = length
 
-    sv = new_sequence.SeqView(seq=seq, start=start, step=step, alphabet=bytes_alphabet)
+    sv = new_sequence.SeqView(
+        parent=seq, start=start, step=step, alphabet=bytes_alphabet
+    )
     with pytest.raises(IndexError):
         _ = sv[pos_boundary_index]
     with pytest.raises(IndexError):
@@ -1078,7 +1080,7 @@ def test_seqview_invalid_index_reverse_step(stop, bytes_alphabet):
     pos_boundary_index = length
 
     sv = new_sequence.SeqView(
-        seq=seq, start=start, stop=stop, step=step, alphabet=bytes_alphabet
+        parent=seq, start=start, stop=stop, step=step, alphabet=bytes_alphabet
     )
     with pytest.raises(IndexError):
         _ = sv[pos_boundary_index]
@@ -1097,7 +1099,7 @@ def test_seqview_invalid_index_reverse_step_gt_1(stop, bytes_alphabet):
     pos_boundary_index = length
 
     sv = new_sequence.SeqView(
-        seq=seq, start=start, stop=stop, step=step, alphabet=bytes_alphabet
+        parent=seq, start=start, stop=stop, step=step, alphabet=bytes_alphabet
     )
     with pytest.raises(IndexError):
         _ = sv[pos_boundary_index]
@@ -1106,7 +1108,7 @@ def test_seqview_invalid_index_reverse_step_gt_1(stop, bytes_alphabet):
 
 
 def test_seqview_slice_null(ascii_alphabet):
-    sv = new_sequence.SeqView(seq="", alphabet=ascii_alphabet)
+    sv = new_sequence.SeqView(parent="", alphabet=ascii_alphabet)
     assert len(sv) == 0
     got = sv[2:]
     assert len(got) == 0
@@ -1118,7 +1120,7 @@ def test_seqview_start_out_of_bounds(bytes_alphabet):
     init_start, init_stop, init_step = 2, 10, 1
     boundary = abs((init_start - init_stop) // init_step)
     sv = new_sequence.SeqView(
-        seq=seq,
+        parent=seq,
         start=init_start,
         stop=init_stop,
         step=init_step,
@@ -1134,7 +1136,7 @@ def test_seqview_start_out_of_bounds_step_gt_1(bytes_alphabet):
     init_start, init_stop, init_step = 2, 10, 2
     boundary = abs((init_start - init_stop) // init_step)
     sv = new_sequence.SeqView(
-        seq=seq,
+        parent=seq,
         start=init_start,
         stop=init_stop,
         step=init_step,
@@ -1152,7 +1154,7 @@ def test_seqview_start_out_of_bounds_reverse_step(bytes_alphabet):
     boundary_neg = -abs((init_start - init_stop) // init_step) - 1
 
     sv = new_sequence.SeqView(
-        seq=seq,
+        parent=seq,
         start=init_start,
         stop=init_stop,
         step=init_step,
@@ -1176,7 +1178,7 @@ def test_seqview_start_out_of_bounds_reverse_step(bytes_alphabet):
 def test_seqview_defaults(simple_slices, bytes_alphabet):
     """SeqView should accept slices with all combinations of default parameters"""
     seq = "0123456789"
-    got = new_sequence.SeqView(seq=seq, alphabet=bytes_alphabet)[simple_slices]
+    got = new_sequence.SeqView(parent=seq, alphabet=bytes_alphabet)[simple_slices]
     expected = seq[simple_slices]
     assert got.str_value == expected
 
@@ -1196,7 +1198,7 @@ def test_seqview_defaults(simple_slices, bytes_alphabet):
 def test_seqview_sliced_index(index, simple_slices, bytes_alphabet):
     """SeqView that has been sliced with default parameters, can then be indexed"""
     seq = "0123456789"
-    sv = new_sequence.SeqView(seq=seq, alphabet=bytes_alphabet)
+    sv = new_sequence.SeqView(parent=seq, alphabet=bytes_alphabet)
     got = sv[simple_slices][index]
     expected = seq[simple_slices][index]
     assert got.str_value == expected
@@ -1207,7 +1209,7 @@ def test_seqview_sliced_index(index, simple_slices, bytes_alphabet):
 def test_seqview_reverse_slice(first_step, second_step, bytes_alphabet):
     """subsequent slices may reverse the previous slice"""
     seq = "0123456789"
-    sv = new_sequence.SeqView(seq=seq, step=first_step, alphabet=bytes_alphabet)
+    sv = new_sequence.SeqView(parent=seq, step=first_step, alphabet=bytes_alphabet)
     got = sv[::second_step]
     expected = seq[::first_step][::second_step]
     assert got.str_value == expected
@@ -1226,11 +1228,15 @@ def test_seqview_rev_sliced_index(index, start, stop, step, seq, bytes_alphabet)
     except IndexError:
         with pytest.raises(IndexError):
             _ = new_sequence.SeqView(
-                seq=seq_data, start=start, stop=stop, step=step, alphabet=bytes_alphabet
+                parent=seq_data,
+                start=start,
+                stop=stop,
+                step=step,
+                alphabet=bytes_alphabet,
             )[index].str_value
     else:  # if no index error, SeqView should match python slicing
         got = new_sequence.SeqView(
-            seq=seq_data, start=start, stop=stop, step=step, alphabet=bytes_alphabet
+            parent=seq_data, start=start, stop=stop, step=step, alphabet=bytes_alphabet
         )[index].str_value
         assert got == expected
 
@@ -1242,7 +1248,7 @@ def test_seqview_rev_sliced_index(index, start, stop, step, seq, bytes_alphabet)
 def test_seqview_init_with_negatives(seq, start, stop, step, bytes_alphabet):
     "SeqView initialisation should handle any combination of positive and negative slices"
     got = new_sequence.SeqView(
-        seq=seq, start=start, stop=stop, step=step, alphabet=bytes_alphabet
+        parent=seq, start=start, stop=stop, step=step, alphabet=bytes_alphabet
     )
     expected = seq[start:stop:step]
     assert got.str_value == expected
@@ -1254,7 +1260,7 @@ def test_seqview_init_with_negatives(seq, start, stop, step, bytes_alphabet):
 @pytest.mark.parametrize("step", (1, 2, -1, -2))
 def test_seqview_slice_with_negatives(seq, start, stop, step, bytes_alphabet):
     """SeqView should handle any combination of positive and negative slices"""
-    sv = new_sequence.SeqView(seq=seq, alphabet=bytes_alphabet)
+    sv = new_sequence.SeqView(parent=seq, alphabet=bytes_alphabet)
     got = sv[start:stop:step]
     expected = seq[start:stop:step]
     assert got.str_value == expected
@@ -1271,7 +1277,7 @@ def test_subsequent_slice_forward(
 ):
     """SeqView should handle subsequent forward slice"""
     seq = "0123456789"
-    sv = new_sequence.SeqView(seq=seq, alphabet=bytes_alphabet)
+    sv = new_sequence.SeqView(parent=seq, alphabet=bytes_alphabet)
     got = sv[start:stop:step][start_2:stop_2:step_2]
     expected = seq[start:stop:step][start_2:stop_2:step_2]
     assert got.str_value == expected
@@ -1394,7 +1400,7 @@ def test_subsequent_slice_neg_stop(slice_1, slice_2, ascii_alphabet):
     subsequent slices may overlap or be within previous slices
     """
     seq_data = "abcdefghijk"
-    sv = new_sequence.SeqView(seq=seq_data, alphabet=ascii_alphabet)
+    sv = new_sequence.SeqView(parent=seq_data, alphabet=ascii_alphabet)
     assert sv[slice_1][slice_2].str_value == seq_data[slice_1][slice_2]
 
 
@@ -1477,7 +1483,7 @@ def test_subsequent_slice_neg_start(slice_1, slice_2, ascii_alphabet):
     subsequent slices may or may not overlap or be within previous slices
     """
     seq_data = "abcdefghijk"
-    sv = new_sequence.SeqView(seq=seq_data, alphabet=ascii_alphabet)
+    sv = new_sequence.SeqView(parent=seq_data, alphabet=ascii_alphabet)
     assert sv[slice_1][slice_2].str_value == seq_data[slice_1][slice_2]
 
 
@@ -1516,7 +1522,7 @@ def test_subsequent_slice_neg_step(slice_1, slice_2, ascii_alphabet):
     subsequent slices may overlap or be within previous slices
     """
     seq_data = "0123456789"
-    sv = new_sequence.SeqView(seq=seq_data, alphabet=ascii_alphabet)
+    sv = new_sequence.SeqView(parent=seq_data, alphabet=ascii_alphabet)
     assert sv[slice_1][slice_2].str_value == seq_data[slice_1][slice_2]
 
 
@@ -1532,7 +1538,7 @@ def test_subsequent_slice_neg_step(slice_1, slice_2, ascii_alphabet):
 def test_subslice_3(sub_slices_triple, ascii_alphabet):
     """SeqView should handle three subsequent slices"""
     seq_data = "abcdefghijk"
-    sv = new_sequence.SeqView(seq=seq_data, alphabet=ascii_alphabet)
+    sv = new_sequence.SeqView(parent=seq_data, alphabet=ascii_alphabet)
     slice_1, slice_2, slice_3 = sub_slices_triple
     assert (
         sv[slice_1][slice_2][slice_3].str_value == seq_data[slice_1][slice_2][slice_3]
@@ -1552,7 +1558,7 @@ def test_triple_slice(
     integer_seq, start, stop, step, start_2, stop_2, step_2, start_3, stop_3, step_3
 ):
     """SeqView should handle subsequent forward slice"""
-    seq = integer_seq.seq
+    seq = integer_seq.parent
     got = integer_seq[start:stop:step][start_2:stop_2:step_2][start_3:stop_3:step_3]
     expected = seq[start:stop:step][start_2:stop_2:step_2][start_3:stop_3:step_3]
 
@@ -1564,36 +1570,32 @@ def test_seqview_repr():
     alpha = new_moltype.DNA.most_degen_alphabet()
     # Short sequence, defaults
     seq = "ACGT"
-    view = new_sequence.SeqView(seq=seq, alphabet=alpha)
-    expected = (
-        "SeqView(seq='ACGT', start=0, stop=4, step=1, offset=0, seqid=None, seq_len=4)"
-    )
+    view = new_sequence.SeqView(parent=seq, alphabet=alpha)
+    expected = "SeqView(parent='ACGT', start=0, stop=4, step=1, offset=0, seqid=None, parent_len=4)"
     assert repr(view) == expected
 
     # Long sequence
     seq = "ACGT" * 10
-    view = new_sequence.SeqView(seq=seq, alphabet=alpha)
-    expected = "SeqView(seq='ACGTACGTAC...TACGT', start=0, stop=40, step=1, offset=0, seqid=None, seq_len=40)"
+    view = new_sequence.SeqView(parent=seq, alphabet=alpha)
+    expected = "SeqView(parent='ACGTACGTAC...TACGT', start=0, stop=40, step=1, offset=0, seqid=None, parent_len=40)"
     assert repr(view) == expected
 
     # Non-zero start, stop, and step values
     seq = "ACGT" * 10
-    view = new_sequence.SeqView(seq=seq, start=5, stop=35, step=2, alphabet=alpha)
-    expected = "SeqView(seq='ACGTACGTAC...TACGT', start=5, stop=35, step=2, offset=0, seqid=None, seq_len=40)"
+    view = new_sequence.SeqView(parent=seq, start=5, stop=35, step=2, alphabet=alpha)
+    expected = "SeqView(parent='ACGTACGTAC...TACGT', start=5, stop=35, step=2, offset=0, seqid=None, parent_len=40)"
     assert repr(view) == expected
 
     # offset
     seq = "ACGT"
-    view = new_sequence.SeqView(seq=seq, offset=5, alphabet=alpha)
-    expected = (
-        "SeqView(seq='ACGT', start=0, stop=4, step=1, offset=5, seqid=None, seq_len=4)"
-    )
+    view = new_sequence.SeqView(parent=seq, offset=5, alphabet=alpha)
+    expected = "SeqView(parent='ACGT', start=0, stop=4, step=1, offset=5, seqid=None, parent_len=4)"
     assert repr(view) == expected
 
     # seqid
     seq = "ACGT"
-    view = new_sequence.SeqView(seq=seq, seqid="seq1", alphabet=alpha)
-    expected = "SeqView(seq='ACGT', start=0, stop=4, step=1, offset=0, seqid='seq1', seq_len=4)"
+    view = new_sequence.SeqView(parent=seq, seqid="seq1", alphabet=alpha)
+    expected = "SeqView(parent='ACGT', start=0, stop=4, step=1, offset=0, seqid='seq1', parent_len=4)"
     assert repr(view) == expected
 
 
@@ -1976,7 +1978,7 @@ def test_relative_position_step_GT_one(integer_seq):
 @pytest.mark.parametrize("sliced", (False, True))
 @pytest.mark.parametrize("rev", (False, True))
 def test_seqview_copy(sliced, rev, integer_seq):
-    raw_data = integer_seq.seq
+    raw_data = integer_seq.parent
     integer_seq = integer_seq[::-1] if rev else integer_seq
     raw_data = raw_data[::-1] if rev else raw_data
 
@@ -1987,7 +1989,9 @@ def test_seqview_copy(sliced, rev, integer_seq):
 
     assert copied.str_value == raw_data[slice_start:slice_end]
     assert copied.is_reversed == integer_seq.is_reversed
-    assert sliced and copied.seq is not sv.seq or copied.seq is integer_seq.seq
+    assert (
+        sliced and copied.parent is not sv.parent or copied.parent is integer_seq.parent
+    )
 
 
 def test_relative_position_with_remainder(integer_seq):
@@ -2102,7 +2106,7 @@ def test_to_rich_dict():
     r = dna.make_seq(seq="AAGGCC", name="seq1")
     got = r.to_rich_dict()
     seq = new_sequence.SeqView(
-        seq="AAGGCC", seqid="seq1", alphabet=dna.most_degen_alphabet()
+        parent="AAGGCC", seqid="seq1", alphabet=dna.most_degen_alphabet()
     ).to_rich_dict()
 
     expect = {
@@ -2124,7 +2128,7 @@ def test_sequence_to_json():
     r = dna.make_seq(seq="AAGGCC", name="seq1")
     got = json.loads(r.to_json())
     seq = new_sequence.SeqView(
-        seq="AAGGCC", seqid="seq1", alphabet=dna.most_degen_alphabet()
+        parent="AAGGCC", seqid="seq1", alphabet=dna.most_degen_alphabet()
     ).to_rich_dict()
 
     expect = {
@@ -2143,7 +2147,7 @@ def test_sequence_to_json():
 @pytest.mark.parametrize("coord", ("start", "stop"))
 def test_seqview_to_rich_dict(coord, dna_alphabet):
     parent = "ACCCCGGAAAATTTTTTTTTAAGGGGGAAAAAAAAACCCCCCC"
-    sv = new_sequence.SeqView(seq=parent, alphabet=dna_alphabet)
+    sv = new_sequence.SeqView(parent=parent, alphabet=dna_alphabet)
     plus = sv.to_rich_dict()
     minus = sv[::-1].to_rich_dict()
     plus = plus.pop("init_args")
@@ -2158,7 +2162,7 @@ def test_seqview_to_rich_dict(coord, dna_alphabet):
 def test_sliced_seqview_rich_dict(reverse, dna_alphabet):
     parent = "ACCCCGGAAAATTTTTTTTTAAGGGGGAAAAAAAAACCCCCCC"
     sl = slice(2, 13)
-    sv = new_sequence.SeqView(seq=parent, alphabet=dna_alphabet)[sl]
+    sv = new_sequence.SeqView(parent=parent, alphabet=dna_alphabet)[sl]
     sv = sv[::-1] if reverse else sv
     rd = sv.to_rich_dict()
     assert rd["init_args"]["seq"] == parent[sl]
@@ -2179,7 +2183,7 @@ 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, alphabet=ascii_alphabet)
+    sv = new_sequence.SeqView(parent=data, alphabet=ascii_alphabet)
     sv.offset = offset
     sv = sv[sl]
     assert sv.str_value == expect
@@ -2199,7 +2203,7 @@ 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, alphabet=ascii_alphabet)
+    sv = new_sequence.SeqView(parent=data, alphabet=ascii_alphabet)
     sv = sv[sl]
     assert sv.str_value == expect
     # now check that start / stop are always the same
@@ -2212,7 +2216,7 @@ 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, alphabet=ascii_alphabet)
+    sv = new_sequence.SeqView(parent=data, alphabet=ascii_alphabet)
     sv = sv[0 : 0 : -1 if rev else 1]
     assert sv.str_value == expect
     # now check that start / stop are always the same
@@ -2228,7 +2232,7 @@ def test_parent_start_stop_singletons(index, rev, ascii_alphabet):
     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, alphabet=ascii_alphabet)
+    sv = new_sequence.SeqView(parent=data, alphabet=ascii_alphabet)
     sv = sv[sl]
     assert sv.str_value == expect
     # now check that start / stop are always the same
@@ -2405,7 +2409,7 @@ 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),
+        new_sequence.SeqView(parent=seq, alphabet=dna_alphabet),
         seqid,
         alphabet=dna_alphabet,
     )
@@ -2414,25 +2418,25 @@ def test_coerce_to_seqview_already_seqview(dna_alphabet):
 
 
 def test_seqview_seqid(dna_alphabet):
-    sv = new_sequence.SeqView(seq="ACGGTGGGAC", alphabet=dna_alphabet)
+    sv = new_sequence.SeqView(parent="ACGGTGGGAC", alphabet=dna_alphabet)
     assert sv.seqid is None
 
-    sv = new_sequence.SeqView(seq="ACGGTGGGAC", seqid="seq1", alphabet=dna_alphabet)
+    sv = new_sequence.SeqView(parent="ACGGTGGGAC", seqid="seq1", alphabet=dna_alphabet)
     assert sv.seqid == "seq1"
 
 
 def test_seqview_to_rich_dict_seqid(dna_alphabet):
-    sv = new_sequence.SeqView(seq="ACGGTGGGAC", seqid="seq1", alphabet=dna_alphabet)
+    sv = new_sequence.SeqView(parent="ACGGTGGGAC", seqid="seq1", alphabet=dna_alphabet)
     rd = sv.to_rich_dict()
     assert rd["init_args"]["seqid"] == "seq1"
 
-    sv = new_sequence.SeqView(seq="ACGGTGGGAC", alphabet=dna_alphabet)
+    sv = new_sequence.SeqView(parent="ACGGTGGGAC", alphabet=dna_alphabet)
     rd = sv.to_rich_dict()
     assert rd["init_args"]["seqid"] is None
 
 
 def test_seqview_slice_propagates_seqid(dna_alphabet):
-    sv = new_sequence.SeqView(seq="ACGGTGGGAC", seqid="seq1", alphabet=dna_alphabet)
+    sv = new_sequence.SeqView(parent="ACGGTGGGAC", seqid="seq1", alphabet=dna_alphabet)
     sliced_sv = sv[1:8:2]
     assert sliced_sv.seqid == "seq1"
 
@@ -2458,7 +2462,7 @@ def test_sequences_propogates_seqid_seqview(dna_alphabet):
     # creating a Sequence with a seqview does not change the seqid of the SeqView.
     seq = new_moltype.DNA.make_seq(
         seq=new_sequence.SeqView(
-            seq="ACGGTGGGAC", seqid="parent_name", alphabet=dna_alphabet
+            parent="ACGGTGGGAC", seqid="parent_name", alphabet=dna_alphabet
         ),
         name="seq_name",
     )
@@ -2467,7 +2471,7 @@ def test_sequences_propogates_seqid_seqview(dna_alphabet):
 
     # creating a Sequence with an unnamed seqview does not name the SeqView.
     seq = new_moltype.DNA.make_seq(
-        seq=new_sequence.SeqView(seq="ACGGTGGGAC", alphabet=dna_alphabet),
+        seq=new_sequence.SeqView(parent="ACGGTGGGAC", alphabet=dna_alphabet),
         name="seq_name",
     )
     assert seq.name == "seq_name"
@@ -2480,7 +2484,7 @@ def test_make_seq_assigns_to_seqview():
 
 
 def test_empty_seqview_translate_position(dna_alphabet):
-    sv = new_sequence.SeqView(seq="", alphabet=dna_alphabet)
+    sv = new_sequence.SeqView(parent="", alphabet=dna_alphabet)
     assert sv.absolute_position(0) == 0
     assert sv.relative_position(0) == 0
 
@@ -2493,34 +2497,34 @@ 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, alphabet=dna_alphabet
+        parent=seq_data, start=start, stop=stop, step=step, alphabet=dna_alphabet
     )
     expect = len(seq_data)
     # Check property and slot
-    assert sv.seq_len == expect
-    assert sv._seq_len == expect
+    assert sv.parent_len == expect
+    assert sv._parent_len == expect
 
 
 @pytest.mark.parametrize("seq, seq_len", [("A", 0), ("", 1), ("A", 2)])
 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, alphabet=dna_alphabet)
+        new_sequence.SeqView(parent=seq, parent_len=seq_len, alphabet=dna_alphabet)
 
 
 def test_seqview_copy_propagates_seq_len(dna_alphabet):
     seq = "ACGGTGGGAC"
-    sv = new_sequence.SeqView(seq=seq, alphabet=dna_alphabet)
+    sv = new_sequence.SeqView(parent=seq, alphabet=dna_alphabet)
     copied = sv.copy()
-    assert copied.seq_len == len(seq)
+    assert copied.parent_len == len(seq)
 
 
 def test_seqview_seq_len_modified_seq(dna_alphabet):
     seq = "ACGGTGGGAC"
-    sv = new_sequence.SeqView(seq=seq, alphabet=dna_alphabet)
+    sv = new_sequence.SeqView(parent=seq, alphabet=dna_alphabet)
 
-    sv.seq = "ATGC"  # this should not modify seq_len
-    assert sv.seq_len == len(seq)
+    sv.parent = "ATGC"  # this should not modify seq_len
+    assert sv.parent_len == len(seq)
 
 
 def test_sequence_str_bytes_array():