diff --git a/README.md b/README.md index 95e294f..277b641 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ working across different projects via [VisualMode](https://www.visualmode.dev/). For a steady stream of TILs, [sign up for my newsletter](https://visualmode.kit.com/newsletter). -_1804 TILs and counting..._ +_1805 TILs and counting..._ See some of the other learning resources I work on: @@ -1069,6 +1069,7 @@ If you've learned something here, support my efforts writing daily TILs by - [Create A Range Of Descending Values](python/create-a-range-of-descending-values.md) - [Deduplicate A List Into A Tuple](python/deduplicate-a-list-into-a-tuple.md) - [Define Sequence Of Tests With Parametrize Decorator](python/define-sequence-of-tests-with-parametrize-decorator.md) +- [Define Typed Class Interface With Protocol](python/define-typed-class-interface-with-protocol.md) - [Dunder Methods](python/dunder-methods.md) - [Easy Key-Value Aggregates With defaultdict](python/easy-key-value-aggregates-with-defaultdict.md) - [Get Absolute Seconds From `timedelta` Object](python/get-absolute-seconds-from-timedelta-object.md) diff --git a/python/define-typed-class-interface-with-protocol.md b/python/define-typed-class-interface-with-protocol.md new file mode 100644 index 0000000..bc770a9 --- /dev/null +++ b/python/define-typed-class-interface-with-protocol.md @@ -0,0 +1,42 @@ +# Define Typed Class Interface With Protocol + +In [`py-vmt`](https://github.com/jbranchaud/py-vmt) I am defining different +storage access layers for the CLI to use. I want a consistent interface that the +core CLI logic can depend on regardless of whether it is a JSON file or a SQLite +database. To achieve that I can define a class of unimplemented functions that +inherits from +[`typing.Protocol`](https://typing.python.org/en/latest/spec/protocol.html). + +```python +from typing import Protocol + +class SessionRepository(Protocol): + def active_session(self) -> Session | None: ... + def write_active_session(self, session) -> None: ... + def append_session(self, session) -> None: ... + def all_sessions(self) -> list[Session]: ... + def clear_active_session(self) -> None: ... +``` + +Notice that none of these have default implementations. The `...` indicates that +class implementing this protocol will define the implementation of those +functions. + +Now, my `CliContext` class, which needs some kind of `SessionRepository` to +function can indicate as much in `__init__`. + +```python +class CliContext: + def __init__(self, verbose: bool, repo: SessionRepository | None = None) -> None: + self.verbose: bool = verbose + self.active_session: Session | None = None + self.repo: SessionRepository = repo or JsonRepository() + self.active_session = self.repo.active_session() +``` + +If `JsonRepository` doesn't define all of the methods specified in the protocol, +then a type error will occur wherever it clashes with `SessionRepository`. Now +as I implement `SqliteRepository` I have a standard interface to build against +that I know I can seamlessly swap in. + +[source](https://typing.python.org/en/latest/reference/protocols.html#simple-user-defined-protocols)