Skip to content

Commit 778e9e4

Browse files
committed
Update docs
1 parent 252defd commit 778e9e4

File tree

4 files changed

+103
-69
lines changed

4 files changed

+103
-69
lines changed

README.md

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ You can install the latest `expression` from PyPI by running `pip` (or
6363
> pip install expression
6464
```
6565

66-
To add Pydantic support, install the `pydantic` extra:
66+
To add Pydantic v2 support, install the `pydantic` extra:
6767

6868
```console
6969
> pip install expression[pydantic]
@@ -505,9 +505,7 @@ Guide](https://google.github.io/styleguide/pyguide.html).
505505

506506
Code checks are done using
507507

508-
- [Black](https://github.com/psf/black)
509-
- [flake8](https://github.com/PyCQA/flake8)
510-
- [isort](https://github.com/PyCQA/isort)
508+
- [Ruff](https://github.com/astral-sh/ruff)
511509

512510
To run code checks on changed files every time you commit, install the pre-commit hooks
513511
by running:

docs/reference/union.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@
33
# Tagged Unions
44

55
```{eval-rst}
6-
.. automodule:: expression.core.union
6+
.. automodule:: expression.core.tagged_union
77
:members:
8-
```
8+
```

docs/tutorial/data_modelling.md

Lines changed: 99 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ from __future__ import annotations
2525
from dataclasses import dataclass
2626
from typing import Generic, Tuple, TypeVar, final
2727
28-
from expression import Tag, TaggedUnion, match
28+
from expression import case, tag, tagged_union
2929
3030
_T = TypeVar("_T")
3131
```
@@ -50,18 +50,20 @@ cannot. With tagged unions each of the union cases produces the same type which
5050
we use a static create method for to create each of the union cases.
5151

5252
```{code-cell} python
53-
@final
54-
class Shape(TaggedUnion):
55-
RECTANGLE = Tag[Rectangle]()
56-
CIRCLE = Tag[Circle]()
53+
@tagged_union
54+
class Shape:
55+
tag: Literal["rectangle", "circle"] = tag()
56+
57+
rectangle: Rectangle = case()
58+
circle: Circle = case()
5759
5860
@staticmethod
59-
def rectangle(width: float, length: float) -> Shape:
60-
return Shape(Shape.RECTANGLE, Rectangle(width, length))
61+
def Rectangle(width: float, length: float) -> Shape:
62+
return Shape(rectangle=Rectangle(width, length))
6163
6264
@staticmethod
63-
def circle(radius: float) -> Shape:
64-
return Shape(Shape.CIRCLE, Circle(radius))
65+
def Circle(radius: float) -> Shape:
66+
return Shape(circle=Circle(radius))
6567
```
6668

6769
A more complex type modelling example:
@@ -74,97 +76,102 @@ from expression import TaggedUnion, match
7476
from expression.core.union import Tag
7577
7678
77-
@final
78-
class Suit(TaggedUnion):
79-
HEARTS = Tag[None]
80-
SPADES = Tag[None]()
81-
CLUBS = Tag[None]()
82-
DIAMONDS = Tag[None]()
79+
@tagged_union
80+
class Suit:
81+
tag: Literal["spades", "hearts", "clubs", "diamonds"] = tag()
82+
83+
spades: Literal[True] = case()
84+
hearts: Literal[True] = case()
85+
clubs: Literal[True] = case()
86+
diamonds: Literal[True] = case()
8387
8488
@staticmethod
85-
def hearts() -> Suit:
86-
return Suit(Suit.HEARTS())
89+
def Spades() -> Suit:
90+
return Suit(spades=True)
8791
8892
@staticmethod
89-
def spades() -> Suit:
90-
return Suit(Suit.SPADES)
93+
def Hearts() -> Suit:
94+
return Suit(hearts=True)
9195
9296
@staticmethod
93-
def clubs() -> Suit:
94-
return Suit(Suit.CLUBS)
97+
def Clubs() -> Suit:
98+
return Suit(clubs=True)
9599
96100
@staticmethod
97-
def diamonds() -> Suit:
98-
return Suit(Suit.DIAMONDS)
101+
def Diamonds() -> Suit:
102+
return Suit(diamonds=True)
99103
104+
@tagged_union
105+
class Face:
106+
tag: Literal["jack", "queen", "king", "ace"] = tag()
100107
101-
@final
102-
class Face(TaggedUnion):
103-
JACK = Tag[None]()
104-
QUEEN = Tag[None]()
105-
KIND = Tag[None]()
106-
ACE = Tag[None]()
108+
jack: Literal[True] = case()
109+
queen: Literal[True] = case()
110+
king: Literal[True] = case()
111+
ace: Literal[True] = case()
107112
108113
@staticmethod
109-
def jack() -> Face:
110-
return Face(Face.JACK)
114+
def Jack() -> Face:
115+
return Face(jack=True)
111116
112117
@staticmethod
113-
def queen() -> Face:
114-
return Face(Face.QUEEN)
118+
def Queen() -> Face:
119+
return Face(queen=True)
115120
116121
@staticmethod
117-
def king() -> Face:
118-
return Face(Face.KIND)
122+
def King() -> Face:
123+
return Face(king=True)
119124
120125
@staticmethod
121-
def ace() -> Face:
122-
return Face(Face.ACE)
126+
def Ace() -> Face:
127+
return Face(ace=True)
128+
123129
130+
@tagged_union
131+
class Card:
132+
tag: Literal["value", "face", "joker"] = tag()
124133
125-
@final
126-
class Card(TaggedUnion):
127-
FACE_CARD = Tag[Tuple[Suit, Face]]()
128-
VALUE_CARD = Tag[Tuple[Suit, int]]()
129-
JOKER = Tag[None]()
134+
face: tuple[Suit, Face] = case()
135+
value: tuple[Suit, int] = case()
136+
joker: Literal[True] = case()
130137
131138
@staticmethod
132-
def face_card(suit: Suit, face: Face) -> Card:
133-
return Card(Card.FACE_CARD, suit=suit, face=face)
139+
def Face(suit: Suit, face: Face) -> Card:
140+
return Card(face=(suit, face))
134141
135142
@staticmethod
136-
def value_card(suit: Suit, value: int) -> Card:
137-
return Card(Card.VALUE_CARD, suit=suit, value=value)
143+
def Value(suit: Suit, value: int) -> Card:
144+
if value < 1 or value > 10:
145+
raise ValueError("Value must be between 1 and 10")
146+
return Card(value=(suit, value))
138147
139148
@staticmethod
140149
def Joker() -> Card:
141-
return Card(Card.JOKER)
150+
return Card(joker=True)
142151
143152
144-
jack_of_hearts = Card.face_card(Suit.hearts(), Face.jack())
145-
three_of_clubs = Card.value_card(Suit.clubs(), 3)
153+
jack_of_hearts = Card.Face(suit=Suit.Hearts(), face=Face.Jack())
154+
three_of_clubs = Card.Value(suit=Suit.Clubs(), value=3)
146155
joker = Card.Joker()
147156
```
148157

149158
We can now use our types with pattern matching to create our domain logic:
150159

151160
```{code-cell} python
152161
def calculate_value(card: Card) -> int:
153-
with match(card) as case:
154-
if case(Card.JOKER):
155-
return 0
156-
if case(Card.FACE_CARD(suit=Suit.SPADES, face=Face.QUEEN)):
162+
match card:
163+
case Card(tag="face", face=(Suit(spades=True), Face(queen=True))):
157164
return 40
158-
if case(Card.FACE_CARD(face=Face.ACE)):
165+
case Card(tag="face", face=(_suit, Face(ace=True))):
159166
return 15
160-
if case(Card.FACE_CARD()):
161-
return 10
162-
if case(Card.VALUE_CARD(value=10)):
167+
case Card(tag="face", face=(_suit, _face)):
163168
return 10
164-
if case._:
165-
return 5
166-
167-
assert False
169+
case Card(tag="value", value=(_suit, value)):
170+
return value
171+
case Card(tag="joker", joker=True):
172+
return 0
173+
case _:
174+
raise AssertionError("Should not match")
168175
169176
170177
rummy_score = calculate_value(jack_of_hearts)
@@ -175,4 +182,34 @@ print("Score: ", rummy_score)
175182
176183
rummy_score = calculate_value(joker)
177184
print("Score: ", rummy_score)
178-
```
185+
```
186+
187+
## Single case tagged unions
188+
189+
You can also use tagged unions to create single case tagged unions. This is useful
190+
when you want to create a type that is different from the underlying type. For example
191+
you may want to create a type that is a string but is a different type to a normal
192+
string:
193+
194+
```{code-cell} python
195+
@tagged_union(frozen=True, repr=False)
196+
class SecurePassword:
197+
password: str = case()
198+
199+
# Override __str__ and __repr__ to make sure we don't leak the password in logs
200+
def __str__(self) -> str:
201+
return "********"
202+
203+
def __repr__(self) -> str:
204+
return "SecurePassword(password='********')"
205+
206+
password = SecurePassword(password="secret")
207+
match password:
208+
case SecurePassword(password=p):
209+
assert p == "secret"
210+
211+
```
212+
213+
This will make sure that the password is not leaked in logs or when printed to the
214+
console, and that we don't assign a password to a normal string anywhere in our code.
215+

docs/tutorial/railway.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -262,4 +262,3 @@ A simplified type called [`Try`](reference_try) is also available. It's a result
262262
that is pinned to `Exception` i.e., `Result[TSource, Exception]`. This makes the code
263263
simpler since you don't have specify the error type every time you declare the type of
264264
your result.
265-

0 commit comments

Comments
 (0)