Skip to content

Commit

Permalink
Merge pull request #5 from cbefus/abc
Browse files Browse the repository at this point in the history
Rework to use an abstract base class.
  • Loading branch information
cbefus authored Sep 10, 2018
2 parents 782f973 + 041dc16 commit 5901ad8
Show file tree
Hide file tree
Showing 7 changed files with 138 additions and 134 deletions.
50 changes: 10 additions & 40 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,25 +82,7 @@ So we present you with an **Optional** object as an alternative.
print(thing)
```

6. But you **can't** get the value without first checking for presence:
```python
thing = some_func_returning_an_optional()
print(thing.get()) # **will raise an exception**

```
but:
```python
thing = some_func_returning_an_optional()
if thing.is_present(): # could use is_empty() as alternative
print(thing.get()) # **does not throw**
```
instead of:
```python
if thing is not None:
print(thing)
```

7. You **can't** get the value if its empty:
6. You **can't** get the value if its empty:
```python
thing = some_func_returning_an_optional()
if thing.is_empty():
Expand All @@ -112,7 +94,7 @@ So we present you with an **Optional** object as an alternative.
print(None) # very odd
```

8. **__Best Usage:__** You can chain on presence:
7. **__Best Usage:__** You can chain on presence:
```python
thing = some_func_returning_an_optional()
thing.if_present(lambda thing: print(thing))
Expand All @@ -123,7 +105,7 @@ So we present you with an **Optional** object as an alternative.
print(thing)
```

9. **__Best Usage:__** You can chain on non presence:
8. **__Best Usage:__** You can chain on non presence:
```python
thing = some_func_returning_an_optional()
thing.if_present(lambda thing: print(thing)).or_else(lambda _: print("PANTS!"))
Expand All @@ -136,45 +118,33 @@ So we present you with an **Optional** object as an alternative.
print("PANTS!")
```

10. **__Best Usage:__** You can map a function:
9. **__Best Usage:__** You can map a function:
```python
def mapping_func(thing):
return thing + "PANTS"

thing_to_map = Optional.of("thing")
mapped_thing = thing_to_map.map(mapping_func) # returns Optional.of("thingPANTS")
```
Note that if the mapping function returns `None` then the map call will return `Optional.empty()`. Also
if you call `map` on an empty optional it will return `Optional.empty()`.
11. **__Best Usage:__** You can flat map a function which returns an Optional.

10. **__Best Usage:__** You can flat map a function which returns an Optional.
```python
def flat_mapping_func(thing):
return Optional.of(thing + "PANTS")

thing_to_map = Optional.of("thing")
mapped_thing = thing_to_map.map(mapping_func) # returns Optional.of("thingPANTS")
```
Note that this does not return an Optional of an Optional. __Use this for mapping functions which return optionals.__
Note that this does not return an Optional of an Optional. __Use this for mapping functions which return optionals.__
If the mapping function you use with this does not return an Optional, calling `flat_map` will raise a
`FlatMapFunctionDoesNotReturnOptionalException`.

12. You can compare two optionals:
11. You can compare two optionals:
```python
Optional.empty() == Optional.empty() # True
Optional.of("thing") == Optional.of("thing") # True
Optional.of("thing") == Optional.empty() # False
Optional.of("thing") == Optional.of("PANTS") # False
```












83 changes: 0 additions & 83 deletions optional.py

This file was deleted.

1 change: 1 addition & 0 deletions optional/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .optional import *
12 changes: 12 additions & 0 deletions optional/compatible_abc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
"""compatible_abc
This module exports a single class, CompatibleABC.
It is necessary to provide the same behavior in
Python 2 and Python 3.
The implementation was taken from https://stackoverflow.com/a/38668373
"""
from abc import ABCMeta


CompatibleABC = ABCMeta('ABC', (object,), {'__slots__': ()})
109 changes: 109 additions & 0 deletions optional/optional.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
from abc import abstractmethod

from .compatible_abc import CompatibleABC


class Optional(object):
@staticmethod
def of(thing):
return _Nothing() if thing is None else _Something(thing)

@staticmethod
def empty():
return _Nothing()


class _AbstractOptional(CompatibleABC):

@abstractmethod
def is_present(self):
pass

def is_empty(self):
return not self.is_present()

@abstractmethod
def get(self):
pass

@abstractmethod
def if_present(self, consumer):
pass

@abstractmethod
def or_else(self, procedure):
pass

@abstractmethod
def map(self, func):
pass

@abstractmethod
def flat_map(self, func):
pass


class _Nothing(_AbstractOptional):
def is_present(self):
return False

def get(self):
raise OptionalAccessOfEmptyException(
"You cannot call get on an empty optional"
)

def if_present(self, consumer):
return self

def or_else(self, procedure):
return procedure()

def map(self, func):
return self

def flat_map(self, func):
return self

def __eq__(self, other):
return isinstance(other, _Nothing)


class _Something(_AbstractOptional):
def __init__(self, value):
self.__value = value

def is_present(self):
return True

def get(self):
return self.__value

def if_present(self, consumer):
consumer(self.get())
return self

def or_else(self, procedure):
return self

def map(self, func):
return Optional.of(func(self.get()))

def flat_map(self, func):
res = func(self.get())
if not isinstance(res, _AbstractOptional):
raise FlatMapFunctionDoesNotReturnOptionalException(
"Mapping function to flat_map must return Optional."
)

return res

def __eq__(self, other):
return isinstance(other, _Something) and self.get() == other.get()


class OptionalAccessOfEmptyException(Exception):
pass


class FlatMapFunctionDoesNotReturnOptionalException(Exception):
pass
Empty file added test/__init__.py
Empty file.
17 changes: 6 additions & 11 deletions test_optional.py → test/test_optional.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import unittest

from optional import Optional, OptionalAccessWithoutCheckingPresenceException, OptionalAccessOfEmptyException, FlatMapFunctionDoesNotReturnOptionalException
from optional import (
Optional,
OptionalAccessOfEmptyException,
FlatMapFunctionDoesNotReturnOptionalException
)


class TestOptional(unittest.TestCase):

def test_can_instantiate(self):
Optional(None)
Optional.of(None)

def test_instantiate_empty(self):
optional = Optional.empty()
Expand All @@ -28,11 +32,6 @@ def test_is_not_present_with_empty(self):
optional = Optional.of(None)
self.assertFalse(optional.is_present())

def test_cannot_get_without_checking_presence(self):
optional = Optional.of("thing")
with self.assertRaises(OptionalAccessWithoutCheckingPresenceException):
optional.get()

def test_cannot_get_from_empty_even_after_checking(self):
optional = Optional.empty()
self.assertTrue(optional.is_empty())
Expand Down Expand Up @@ -156,7 +155,3 @@ def test_non_empty_optionals_with_non_equal_content_are_not_equal(self):

def test_non_empty_optionals_with_equal_content_are_equal(self):
self.assertEqual(Optional.of("PANTS"), Optional.of("PANTS"))


if __name__ == '__main__':
unittest.main()

0 comments on commit 5901ad8

Please sign in to comment.