From 9aa53334b27bf92f5eb0f319855f1502e0c2ff0f Mon Sep 17 00:00:00 2001 From: cbefus Date: Tue, 23 Jun 2020 11:22:41 -0500 Subject: [PATCH] First attempt at adding type hints. Tests pass. Seems to work in python 2 and 3... --- optional/abstract_optional.py | 17 +++++++++++++++-- optional/nothing.py | 18 +++++++++++++++++- optional/optional.py | 3 +++ optional/something.py | 18 +++++++++++++++++- poetry.lock | 16 +++++++++++++++- pyproject.toml | 1 + 6 files changed, 68 insertions(+), 5 deletions(-) diff --git a/optional/abstract_optional.py b/optional/abstract_optional.py index f209ef1..d1d9655 100644 --- a/optional/abstract_optional.py +++ b/optional/abstract_optional.py @@ -1,47 +1,60 @@ from abc import abstractmethod - from .compatible_abc import CompatibleABC +from typing import TypeVar, Generic, Callable, Union, NoReturn + + +T = TypeVar('T') -class AbstractOptional(CompatibleABC): +class AbstractOptional(CompatibleABC, Generic[T]): @abstractmethod def is_empty(self): + # type: () -> bool pass @abstractmethod def get(self): + # type: () -> Union[T, NoReturn] pass @abstractmethod def get_or_default(self, default_value): + # type: (T) -> T pass @abstractmethod def get_or_raise(self, exception): + # type: (type) -> Union[T, NoReturn] pass @abstractmethod def if_present(self, consumer): + # type: (Callable) -> 'AbstractOptional' pass @abstractmethod def or_else(self, procedure): + # type: (Callable) -> 'AbstractOptional' pass @abstractmethod def or_else_raise(self, raiseable): + # type: (Callable) -> Union['AbstractOptional', NoReturn] pass @abstractmethod def map(self, func): + # type: (Callable) -> 'AbstractOptional' pass @abstractmethod def flat_map(self, func): + # type: (Callable) -> 'AbstractOptional' pass def is_present(self): + # type: () -> bool return not self.is_empty() __bool__ = __nonzero__ = is_present diff --git a/optional/nothing.py b/optional/nothing.py index 7ed693a..548f1da 100644 --- a/optional/nothing.py +++ b/optional/nothing.py @@ -1,43 +1,59 @@ from .abstract_optional import AbstractOptional from .exceptions import OptionalAccessOfEmptyException +from typing import TypeVar, Generic, Callable, NoReturn -class Nothing(AbstractOptional): +T = TypeVar('T') + + +class Nothing(AbstractOptional, Generic[T]): def __init__(self, optional): + # type: ('AbstractOptional') -> None self.__optional = optional def is_empty(self): + # type: () -> bool return True def get(self): + # type: () -> NoReturn raise OptionalAccessOfEmptyException( "You cannot call get on an empty optional" ) def get_or_default(self, default_value): + # type: (T) -> T return default_value def get_or_raise(self, raiseable): + # type: (type) -> NoReturn raise raiseable def if_present(self, consumer): + # type: (Callable) -> 'AbstractOptional' return self def or_else(self, supplier): + # type: (Callable) -> 'AbstractOptional' return self.__optional.of(supplier()) def or_else_raise(self, raiseable): + # type: (type) -> NoReturn raise raiseable def map(self, func): + # type: (Callable) -> 'AbstractOptional' return self def flat_map(self, func): + # type: (Callable) -> 'AbstractOptional' return self def __eq__(self, other): + # type: ('AbstractOptional') -> bool return isinstance(other, Nothing) def __repr__(self): + # type: () -> str return 'Optional.empty()' diff --git a/optional/optional.py b/optional/optional.py index a8fc083..807a344 100644 --- a/optional/optional.py +++ b/optional/optional.py @@ -1,12 +1,15 @@ from .nothing import Nothing from .something import Something +from typing import Any, Union class Optional(object): @classmethod def of(cls, thing=None): + # type: (Any) -> Union['Something', 'Nothing'] return Nothing(cls) if thing is None else Something(thing, cls) @classmethod def empty(cls): + # type: () -> 'Nothing' return Nothing(cls) diff --git a/optional/something.py b/optional/something.py index e55c292..f57376a 100644 --- a/optional/something.py +++ b/optional/something.py @@ -1,9 +1,14 @@ from .abstract_optional import AbstractOptional from .exceptions import FlatMapFunctionDoesNotReturnOptionalException +from typing import TypeVar, Generic, Callable -class Something(AbstractOptional): +T = TypeVar('T') + + +class Something(AbstractOptional, Generic[T]): def __init__(self, value, optional): + # type: (T, 'AbstractOptional') -> None if value is None: raise ValueError('Invalid value for Something: None') @@ -11,31 +16,40 @@ def __init__(self, value, optional): self.__optional = optional def is_empty(self): + # type: () -> bool return False def get(self): + # type: () -> T return self.__value def get_or_default(self, default_value): + # type: (T) -> T return self.get() def get_or_raise(self, raiseable): + # type: (type) -> T return self.get() def if_present(self, consumer): + # type: (Callable) -> 'AbstractOptional' consumer(self.get()) return self def or_else(self, supplier): + # type: (Callable) -> 'AbstractOptional' return self def or_else_raise(self, raiseable): + # type: (type) -> 'AbstractOptional' return self def map(self, func): + # type: (Callable) -> 'AbstractOptional' return self.__optional.of(func(self.get())) def flat_map(self, func): + # type: (Callable) -> 'AbstractOptional' res = func(self.get()) if not isinstance(res, AbstractOptional): raise FlatMapFunctionDoesNotReturnOptionalException( @@ -45,7 +59,9 @@ def flat_map(self, func): return res def __eq__(self, other): + # type: ('AbstractOptional') -> bool return isinstance(other, Something) and self.get() == other.get() def __repr__(self): + # type: () -> str return 'Optional.of({!r})'.format(self.get()) diff --git a/poetry.lock b/poetry.lock index e5e3c2f..dab2280 100644 --- a/poetry.lock +++ b/poetry.lock @@ -376,6 +376,14 @@ optional = false python-versions = "*" version = "0.10.0" +[[package]] +category = "main" +description = "Type Hints for Python" +name = "typing" +optional = false +python-versions = "*" +version = "3.7.4.1" + [[package]] category = "dev" description = "HTTP library with thread-safe connection pooling, file post, and more." @@ -430,7 +438,7 @@ docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] testing = ["pathlib2", "unittest2", "jaraco.itertools", "func-timeout"] [metadata] -content-hash = "3826f2f061e3ae460de2a5cd68bbb046743b05cdd91c1f43419740330f2394f4" +content-hash = "4e69d8ff86dda4fb582e295493f950a001f9deb7cecffc135e3ccb55f6553665" python-versions = "~2.7 || ^3.5" [metadata.files] @@ -610,6 +618,7 @@ pytest-cov = [ {file = "pytest_cov-2.8.1-py2.py3-none-any.whl", hash = "sha256:cdbdef4f870408ebdbfeb44e63e07eb18bb4619fae852f6e760645fa36172626"}, ] requests = [ + {file = "requests-2.23.0-py2.7.egg", hash = "sha256:5d2d0ffbb515f39417009a46c14256291061ac01ba8f875b90cad137de83beb4"}, {file = "requests-2.23.0-py2.py3-none-any.whl", hash = "sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee"}, {file = "requests-2.23.0.tar.gz", hash = "sha256:b3f43d496c6daba4493e7c431722aeb7dbc6288f52a6e04e7b6023b0247817e6"}, ] @@ -635,6 +644,11 @@ toml = [ {file = "toml-0.10.0-py2.py3-none-any.whl", hash = "sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e"}, {file = "toml-0.10.0.tar.gz", hash = "sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c"}, ] +typing = [ + {file = "typing-3.7.4.1-py2-none-any.whl", hash = "sha256:c8cabb5ab8945cd2f54917be357d134db9cc1eb039e59d1606dc1e60cb1d9d36"}, + {file = "typing-3.7.4.1-py3-none-any.whl", hash = "sha256:f38d83c5a7a7086543a0f649564d661859c5146a85775ab90c0d2f93ffaa9714"}, + {file = "typing-3.7.4.1.tar.gz", hash = "sha256:91dfe6f3f706ee8cc32d38edbbf304e9b7583fb37108fef38229617f8b3eba23"}, +] urllib3 = [ {file = "urllib3-1.25.8-py2.py3-none-any.whl", hash = "sha256:2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc"}, {file = "urllib3-1.25.8.tar.gz", hash = "sha256:87716c2d2a7121198ebcb7ce7cccf6ce5e9ba539041cfbaeecfb641dc0bf6acc"}, diff --git a/pyproject.toml b/pyproject.toml index 8e3f1dc..4d03be0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,6 +23,7 @@ keywords = ["optional datatype library"] [tool.poetry.dependencies] python = "~2.7 || ^3.5" +typing = "^3.7.4" [tool.poetry.dev-dependencies] pytest = "^3.0"