Skip to content

Commit 55ec194

Browse files
committed
Second Edition
Changes in the code for the second edition of the book.
1 parent 691fd77 commit 55ec194

File tree

234 files changed

+2448
-1244
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

234 files changed

+2448
-1244
lines changed

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
The MIT License (MIT)
22

3-
Copyright (c) 2016-2019 Mariano Anaya
3+
Copyright (c) 2016-2020 Mariano Anaya
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

README.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Clean code in Python
22

3+
## Book
4+
5+
The source code for the examples listed in the
6+
[book](https://www.amazon.com/Clean-Code-Python-Refactor-codebase-ebook/dp/B07G19CHRM/ref=sr_1_1?s=books&ie=UTF8&qid=1535483811&sr=1-1)
7+
is under ``book/src``.
8+
39
## EuroPython 2016 Talk
410

511
Sources of the talk, as presented at EuroPython 2016.
@@ -9,9 +15,3 @@ The source code of the talk is under ``talk/src``.
915
[Slides](https://speakerdeck.com/rmariano/clean-code-in-python)
1016
[EuroPython Page](https://ep2016.europython.eu/conference/talks/clean-code-in-python)
1117
[Video](https://www.youtube.com/watch?v=7ADbOHW1dTA)
12-
13-
## Book
14-
15-
The source code for the examples listed in the
16-
[book](https://www.amazon.com/Clean-Code-Python-Refactor-codebase-ebook/dp/B07G19CHRM/ref=sr_1_1?s=books&ie=UTF8&qid=1535483811&sr=1-1)
17-
is under ``book/src``.

book/src/.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
__pycache__
2+
*.sw[op]
3+
.mypy_cache/
4+
*.tar.gz
5+
.pytest_cache/
6+
.pytype
7+
env/

book/src/Makefile

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,17 @@
1+
PYTHON=python3.9
2+
ENV_NAME=.env
3+
DOCKER_VERSION:=latest
4+
5+
.PHONY: clean
16
clean:
2-
find . -name "*.swp" -o -name "__pycache__" | xargs rm -fr
3-
find . -type d -name "*_cache*" | xargs rm -fr
7+
find . -name "*.swp" -o -name "__pycache__" -o -name ".mypy_cache" | xargs rm -fr
8+
rm -fr $(ENV_NAME)
49

10+
.PHONY: setup
511
setup:
6-
pip install -r requirements.txt
12+
$(PYTHON) -m venv $(ENV_NAME)
13+
$(ENV_NAME)/bin/python -m pip install -r requirements.txt
714

8-
.PHONY: clean setup
15+
.PHONY: shell
16+
shell:
17+
docker run -it rmariano/ccip:$(DOCKER_VERSION)

book/src/README.rst

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,38 @@
11
Clean Code in Python
22
--------------------
3+
Code for the book 'Clean code in Python', second edition.
4+
5+
Requirements
6+
============
7+
The code in this repository can be run directly in a local machine, provided some dependencies are met (explained in the
8+
next section). Alternatively, a Docker image is provided, which can be used to interact directly with.
9+
10+
Running it locally
11+
^^^^^^^^^^^^^^^^^^
12+
The code in this repository assumes Python 3.9+ is installed in the system, and that ``python3.9`` is available in the
13+
current path.
14+
15+
- Python 3.9+
16+
- GNU make and building tools for the dependencies (gcc, python3 dev files, etc.)
17+
18+
Docker
19+
^^^^^^
20+
A docker image is provided to make it simpler for readers to try out the code examples.
21+
You can obtain the image, and interact with it by running::
22+
23+
docker pull rmariano/ccip:latest
24+
docker run -it rmariano/ccip:latest
325

426
Setup
527
=====
6-
7-
Create a virtual environment, and once activated run::
28+
To install the dependencies, run::
829

930
make setup
1031

11-
This will install the common dependencies. Besides this, each chapter might
12-
have additional ones, for which another ``make setup`` will have to be run
13-
inside that particular directory.
32+
This will create a new virtual environment, called ``.env`` and install the dependencies on it. From this point, you
33+
need to activate the virtual environment to run the rest of the code examples. The environment is activated by running::
34+
35+
source .env/bin/activate
1436

1537
Each chapter has its corresponding directory given by its number.
1638

@@ -35,7 +57,7 @@ Chapters Index
3557
* Chapter 04: The SOLID Principles
3658
* Chapter 05: Decorators
3759
* Chapter 06: Getting More out of our Objects with Descriptors
38-
* Chapter 07: Using Generators
60+
* Chapter 07: Generators, Iterators, and Asynchronous Programming
3961
* Chapter 08: Unit Testing and Refactoring
4062
* Chapter 09: Common Design Patterns
4163
* Chapter 10: Clean Architecture

book/src/ch01/Makefile

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,23 @@
1+
.PHONY: typehint
12
typehint:
23
mypy --ignore-missing-imports src/
34

5+
.PHONY: test
46
test:
57
pytest tests/
68

9+
.PHONY: lint
710
lint:
811
pylint src/
912

13+
.PHONY: checklist
1014
checklist: lint typehint test
1115

16+
.PHONY: black
1217
black:
1318
black -l 79 *.py
1419

15-
setup:
16-
$(VIRTUAL_ENV)/bin/pip install -r requirements.txt
17-
18-
.PHONY: typehint test lint checklist black
20+
.PHONY: clean
21+
clean:
22+
find . -type f -name "*.pyc" | xargs rm -fr
23+
find . -type d -name __pycache__ | xargs rm -fr

book/src/ch01/README.rst

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
11
Chapter 01 - Introduction, Tools, and Formatting
22
================================================
33

4-
Install dependencies::
5-
6-
make setup
7-
84
Run the tests::
95

106
make test

book/src/ch01/requirements.txt

Lines changed: 0 additions & 2 deletions
This file was deleted.

book/src/ch01/src/annotations.py

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,22 @@
22
33
> Annotations
44
"""
5+
from dataclasses import dataclass
6+
from typing import Tuple
57

8+
Client = Tuple[int, str]
69

7-
class Point: # pylint: disable=R0903
8-
"""Example to be used as return type of locate"""
9-
def __init__(self, lat, long):
10-
self.lat = lat
11-
self.long = long
1210

11+
def process_clients(clients: list[Client]): # type: ignore
12+
...
1313

14-
def locate(latitude: float, longitude: float) -> Point:
15-
"""Find an object in the map by its coordinates"""
16-
return Point(latitude, longitude)
1714

18-
19-
class NewPoint: # pylint: disable=R0903
20-
"""Example to display its __annotations__ attribute."""
15+
@dataclass
16+
class Point:
2117
lat: float
2218
long: float
19+
20+
21+
def locate(latitude: float, longitude: float) -> Point:
22+
"""Find an object in the map by its coordinates"""
23+
return Point(latitude, longitude)

book/src/ch01/src/other.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
"""Clean Code in Python - Chapter 1: Introduction, Tools, and Formatting
2+
3+
> Extra code for isolated examples
4+
"""
5+
6+
7+
def data_from_response(response: dict) -> dict:
8+
"""If the response is OK, return its payload.
9+
- response: A dict like::
10+
{
11+
"status": 200, # <int>
12+
"timestamp": "....", # ISO format string of the current date time
13+
"payload": { ... } # dict with the returned data
14+
}
15+
- Returns a dictionary like::
16+
{"data": { .. } }
17+
18+
- Raises:
19+
- ValueError if the HTTP status is != 200
20+
"""
21+
if response["status"] != 200:
22+
raise ValueError
23+
return {"data": response["payload"]}

book/src/ch01/src/test_annotations.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
1-
"""Clean Code in Python - Chapter 01: Introcution, Tools, and Formatting
1+
"""Clean Code in Python - Chapter 01: Introduction, Tools, and Formatting
22
33
Tests for annotations examples
44
55
"""
66
import pytest
77

8-
from src.annotations import NewPoint, Point, locate
8+
from src.annotations import Point, locate
99

1010

1111
@pytest.mark.parametrize(
12-
"element,expected",
12+
"defined_object,expected",
1313
(
1414
(locate, {"latitude": float, "longitude": float, "return": Point}),
15-
(NewPoint, {"lat": float, "long": float}),
15+
(Point, {"lat": float, "long": float}),
1616
),
1717
)
18-
def test_annotations(element, expected):
19-
"""test the class/functions againts its expected annotations"""
20-
assert getattr(element, "__annotations__") == expected
18+
def test_annotations(defined_object, expected):
19+
"""test the class/functions against its expected annotations"""
20+
assert getattr(defined_object, "__annotations__") == expected
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
"""Clean Code in Python - Chapter 1: Introduction, Tools, and Formatting
2+
3+
> Tools for type hinting: examples
4+
"""
5+
from __future__ import annotations
6+
7+
import logging
8+
from typing import List, Union, Tuple
9+
10+
logging.basicConfig(level=logging.INFO)
11+
logger = logging.getLogger(__name__)
12+
13+
14+
def broadcast_notification(
15+
message: str, relevant_user_emails: Union[List[str], Tuple[str]]
16+
):
17+
for email in relevant_user_emails:
18+
logger.info("Sending %r to %r", message, email)
19+
20+
21+
broadcast_notification("welcome", ["user1@domain.com", "user2@domain.com"])
22+
broadcast_notification("welcome", "user1@domain.com") # type: ignore
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
"""Clean Code in Python - Chapter 1: Introduction, Tools, and Formatting
2+
3+
> Tools for type hinting: examples
4+
"""
5+
from __future__ import annotations
6+
7+
import logging
8+
from typing import Iterable
9+
10+
logging.basicConfig(level=logging.INFO)
11+
logger = logging.getLogger(__name__)
12+
13+
14+
def broadcast_notification(message: str, relevant_user_emails: Iterable[str]):
15+
for email in relevant_user_emails:
16+
logger.info("Sending %r to %r", message, email)
17+
18+
19+
broadcast_notification("welcome", ["user1@domain.com", "user2@domain.com"])
20+
broadcast_notification("welcome", "user1@domain.com")

book/src/ch02/Makefile

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,23 @@
11
PYTHON:=$(VIRTUAL_ENV)/bin/python
2+
export PYTHONPATH=src
23

4+
.PHONY: test
35
test:
4-
@$(PYTHON) -m doctest *.py
5-
@$(PYTHON) -m unittest *.py
6+
@$(PYTHON) -m doctest tests/*.py
7+
@$(PYTHON) -m unittest tests/*.py
68

7-
.PHONY: test
9+
.PHONY: typehint
10+
typehint:
11+
mypy src tests
12+
13+
.PHONY: lint
14+
lint:
15+
black --check --line-length=79 src tests
16+
17+
.PHONY: format
18+
format:
19+
black --line-length=79 src tests
20+
21+
.PHONY: clean
22+
clean:
23+
find . -type d -name __pycache__ | xargs rm -fr {}

book/src/ch02/properties.py

Lines changed: 0 additions & 27 deletions
This file was deleted.
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
"""Clean Code in Python - Second edition
2+
Chapter 02: Assignment expressions
3+
"""
4+
5+
import re
6+
from typing import Iterable, Set
7+
8+
ARN_REGEX = re.compile(r"arn:aws:[a-z0-9\-]*:[a-z0-9\-]*:(?P<account_id>\d+):.*")
9+
10+
11+
def collect_account_ids_from_arns(arns: Iterable[str]) -> Set[str]:
12+
"""Given several ARNs in the form
13+
14+
arn:partition:service:region:account-id:resource-id
15+
16+
Collect the unique account IDs found on those strings, and return them.
17+
"""
18+
collected_account_ids = set()
19+
for arn in arns:
20+
matched = re.match(ARN_REGEX, arn)
21+
if matched is not None:
22+
account_id = matched.groupdict()["account_id"]
23+
collected_account_ids.add(account_id)
24+
return collected_account_ids
25+
26+
27+
def collect_account_ids_from_arns2(arns: Iterable[str]) -> Set[str]:
28+
matched_arns = filter(None, (re.match(ARN_REGEX, arn) for arn in arns))
29+
return {m.groupdict()["account_id"] for m in matched_arns}
30+
31+
32+
def collect_account_ids_from_arns3(arns: Iterable[str]) -> Set[str]:
33+
return {
34+
matched.groupdict()["account_id"]
35+
for arn in arns
36+
if (matched := re.match(ARN_REGEX, arn)) is not None
37+
}
File renamed without changes.
File renamed without changes.

book/src/ch02/container.py renamed to book/src/ch02/src/container.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ def mark_coordinate(grid, coord):
1212
class Boundaries:
1313
def __init__(self, width, height):
1414
self.width = width
15-
self.height = height
15+
self.height = heigh
1616

1717
def __contains__(self, coord):
1818
x, y = coord

0 commit comments

Comments
 (0)