diff --git a/lightmotif-py/lightmotif/io.rs b/lightmotif-py/lightmotif/io.rs
index ea1432c50d2b01d2e88bf1c4e9b4eb906f9ab0fe..6d18d4807794f922c234fdfbc1636f419db973bc 100644
--- a/lightmotif-py/lightmotif/io.rs
+++ b/lightmotif-py/lightmotif/io.rs
@@ -100,8 +100,11 @@ impl UniprobeMotif {
 
 #[pyclass(module = "lightmotif.lib", extends = Motif)]
 pub struct TransfacMotif {
+    #[pyo3(get)]
     id: Option<String>,
+    #[pyo3(get)]
     accession: Option<String>,
+    #[pyo3(get)]
     description: Option<String>,
 }
 
diff --git a/lightmotif-py/lightmotif/lib.pyi b/lightmotif-py/lightmotif/lib.pyi
new file mode 100644
index 0000000000000000000000000000000000000000..41cc93cb789a436a202217a607c0769e63be9540
--- /dev/null
+++ b/lightmotif-py/lightmotif/lib.pyi
@@ -0,0 +1,152 @@
+import typing
+from typing import Dict, List, Iterable, Union, Optional, BinaryIO, Iterator, Generic
+from os import PathLike
+
+try:
+    from typing import Literal
+except ImportError:
+    from typing_extensions import Literal  # type: ignore
+
+__version__: str
+__author__: str
+
+AVX2_SUPPORTED: bool
+
+FORMAT = Literal["jaspar", "jaspar16", "uniprobe", "transfac"]
+
+class EncodedSequence:
+    def __init__(self, sequence: str, protein: bool = False) -> None: ...
+    def __str__(self) -> str: ...
+    def __len__(self) -> int: ...
+    def __copy__(self) -> EncodedSequence: ...
+    def __buffer__(self, flags: int) -> memoryview: ...
+    def copy(self) -> EncodedSequence: ...
+    def stripe(self) -> StripedSequence: ...
+
+class StripedSequence:
+    def __copy__(self) -> StripedSequence: ...
+    def __buffer__(self, flags: int) -> memoryview: ...
+    def copy(self) -> StripedSequence: ...
+
+class CountMatrix:
+    def __init__(
+        self, values: Dict[str, Iterable[int]], protein: bool = False
+    ) -> None: ...
+    def __eq__(self, other: object) -> bool: ...
+    def __len__(self) -> int: ...
+    def __getitem__(self, index: int) -> List[int]: ...
+    def normalize(
+        self, pseudocount: Union[None, float, Dict[str, float]] = None
+    ) -> WeightMatrix: ...
+
+class WeightMatrix:
+    def __eq__(self, other: object) -> bool: ...
+    def __len__(self) -> int: ...
+    def __getitem__(self, index: int) -> List[int]: ...
+    def log_odds(
+        self, background: Union[None, Dict[str, float]] = None, base: float = 2.0
+    ) -> ScoringMatrix: ...
+
+class ScoringMatrix:
+    def __init__(
+        self, values: Dict[str, Iterable[float]], protein: bool = False
+    ) -> None: ...
+    def __len__(self) -> int: ...
+    def __eq__(self, other: object) -> bool: ...
+    def __buffer__(self, flags: int) -> memoryview: ...
+    def calculate(self, sequence: StripedSequence) -> StripedScores: ...
+    def pvalue(self, score: float) -> float: ...
+    def score(self, pvalue: float) -> float: ...
+    def reverse_complement(self) -> ScoringMatrix: ...
+
+class StripedScores:
+    def __len__(self) -> int: ...
+    def __getitem__(self, index: int) -> float: ...
+    def __buffer__(self, flags: int) -> memoryview: ...
+    def threshold(self, threshold: float) -> List[int]: ...
+    def max(self) -> Optional[float]: ...
+    def argmax(self) -> Optional[int]: ...
+
+class Motif:
+    @property
+    def counts(self) -> Optional[CountMatrix]: ...
+    @property
+    def pwm(self) -> WeightMatrix: ...
+    @property
+    def pssm(self) -> ScoringMatrix: ...
+    @property
+    def name(self) -> Optional[str]: ...
+
+class TransfacMotif(Motif):
+    @property
+    def counts(self) -> CountMatrix: ...
+
+class JasparMotif(Motif):
+    @property
+    def counts(self) -> CountMatrix: ...
+
+class UniprobeMotif(Motif):
+    @property
+    def counts(self) -> None: ...
+
+class Scanner(Iterator[Hit]):
+    def __iter__(self) -> Scanner: ...
+    def __next__(self) -> Hit: ...
+
+class Hit:
+    @property
+    def position(self) -> int: ...
+    @property
+    def score(self) -> float: ...
+
+M = typing.TypeVar("M", bound=Motif)
+
+class Loader(Generic[M], Iterator[M]):
+    def __iter__(self) -> Loader[M]: ...
+    def __next__(self) -> M: ...
+
+def create(
+    sequences: Iterable[str], protein: bool = False, name: Optional[str] = None
+) -> Motif: ...
+def stripe(sequence: str, protein: bool = False) -> StripedSequence: ...
+def scan(
+    pssm: ScoringMatrix,
+    sequence: StripedSequence,
+    threshold: float = 0.0,
+    block_size: int = 256,
+) -> Scanner: ...
+@typing.overload
+def load(
+    file: Union[BinaryIO, PathLike[str]],
+    format: Literal["jaspar"],
+    *,
+    protein: bool = False
+) -> Loader[JasparMotif]: ...
+@typing.overload
+def load(
+    file: Union[BinaryIO, PathLike[str]],
+    format: Literal["jaspar16"],
+    *,
+    protein: bool = False
+) -> Loader[JasparMotif]: ...
+@typing.overload
+def load(
+    file: Union[BinaryIO, PathLike[str]],
+    format: Literal["uniprobe"],
+    *,
+    protein: bool = False
+) -> Loader[UniprobeMotif]: ...
+@typing.overload
+def load(
+    file: Union[BinaryIO, PathLike[str]],
+    format: Literal["transfac"],
+    *,
+    protein: bool = False
+) -> Loader[TransfacMotif]: ...
+@typing.overload
+def load(
+    file: Union[BinaryIO, PathLike[str]],
+    format: FORMAT = "jaspar",
+    *,
+    protein: bool = False
+) -> Loader[Motif]: ...
diff --git a/lightmotif-py/lightmotif/lib.rs b/lightmotif-py/lightmotif/lib.rs
index 310aae7bdf3ef068cab29b56e2c74ae013444f20..36918f86e7cf6a406cad557fd43e9704cc4a4401 100644
--- a/lightmotif-py/lightmotif/lib.rs
+++ b/lightmotif-py/lightmotif/lib.rs
@@ -1202,10 +1202,9 @@ pub fn create(sequences: Bound<PyAny>, protein: bool, name: Option<String>) -> P
 /// Encode and stripe a text sequence.
 #[pyfunction]
 #[pyo3(signature = (sequence, protein=false))]
-pub fn stripe(sequence: Bound<PyAny>, protein: bool) -> PyResult<StripedSequence> {
+pub fn stripe(sequence: Bound<PyString>, protein: bool) -> PyResult<StripedSequence> {
     let py = sequence.py();
-    let s = sequence.extract::<Bound<PyString>>()?;
-    let encoded = EncodedSequence::__init__(s, protein).and_then(|e| Py::new(py, e))?;
+    let encoded = EncodedSequence::__init__(sequence, protein).and_then(|e| Py::new(py, e))?;
     let striped = encoded.borrow(py).stripe();
     striped
 }
diff --git a/lightmotif-py/lightmotif/py.typed b/lightmotif-py/lightmotif/py.typed
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/setup.cfg b/setup.cfg
index 946c470708e7d6ae2155c88d08517e1e223f9276..34bcb00515a6b25a6869de73b75b4eda31e99648 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -32,7 +32,7 @@ classifiers =
     Topic :: Scientific/Engineering :: Bio-Informatics
     Topic :: Scientific/Engineering :: Medical Science Apps.
     Topic :: Software Development :: Libraries :: Python Modules
-    # Typing :: Typed
+    Typing :: Typed
 project_urls =
     Bug Tracker = https://github.com/althonos/lightmotif/issues
     Changelog = https://github.com/althonos/lightmotif/blob/master/CHANGELOG.md