├── ERRATUM-PYTHON-DESIGN-PATTERN-PROXY.pdf
├── LICENSE
├── README.md
├── chapter01
├── abstract_factory.py
├── data
│ ├── movies.json
│ └── person.xml
├── factory_method.py
└── id.py
├── chapter02
├── apple_factory.py
├── builder.py
├── computer_builder.py
└── exercise_fluent_builder.py
├── chapter03
├── prototype.py
└── singleton.py
├── chapter04
├── adapter.py
└── external.py
├── chapter05
├── mymath.py
├── number_sum.py
└── number_sum_naive.py
├── chapter06
├── bridge.py
└── file.txt
├── chapter07
└── facade.py
├── chapter08
├── flyweight.py
├── lazy.py
├── mvc.py
└── proxy.py
├── chapter09
└── chain.py
├── chapter10
├── command.py
└── first-class.py
├── chapter11
└── observer.py
├── chapter12
└── state.py
├── chapter13
├── boiler.py
├── interpreter.py
├── iterator.py
├── memento.py
├── strategy.py
└── template.py
├── chapter14
├── peoplelist.py
├── rx_example1.py
├── rx_example2.py
├── rx_example3.py
├── rx_peoplelist_1.py
├── rx_peoplelist_2.py
└── rx_peoplelist_3.py
└── chapter15
├── cache_aside
├── cache_aside.py
├── data
│ ├── quotes.sqlite3
│ └── quotes_cache.csv
└── populate_db.py
├── circuit_breaker.py
├── circuit_breaker_in_flaskapp.py
├── retry_write_file.py
├── retry_write_file_retrying_module.py
├── retry_write_file_tenacity_module.py
├── service_first.py
├── service_second.py
├── test_service_first.py
├── test_service_second.py
└── throttling_in_flaskapp.py
/ERRATUM-PYTHON-DESIGN-PATTERN-PROXY.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Mastering-Python-Design-Patterns-Second-Edition/639c6712e0c5cf1cfd9576026d722b46e2bed09e/ERRATUM-PYTHON-DESIGN-PATTERN-PROXY.pdf
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Packt
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Mastering Python Design Patterns, Second Edition
2 |
3 |
4 |
5 | This is the code repository for [Mastering Python Design Patterns, Second Edition](https://www.packtpub.com/application-development/mastering-python-design-patterns-second-edition?utm_source=github&utm_medium=repository&utm_campaign=), published by Packt.
6 |
7 | **A guide to creating smart, efficient, and reusable software**
8 |
9 | ## What is this book about?
10 | Python is an object-oriented scripting language that is used in a wide range of categories. In software engineering, a design pattern is an elected solution for solving software design problems. Although they have been around for a while, design patterns remain one of the top topics in software engineering, and are a ready source for software developers to solve the problems they face on a regular basis. This book takes you through a variety of design patterns and explains them with real-world examples. You will get to grips with low-level details and concepts that show you how to write Python code, without focusing on common solutions as enabled in Java and C++. You'll also find sections on corrections, best practices, system architecture, and its designing aspects.
11 | This book covers the following exciting features:
12 | * Explore Factory Method and Abstract Factory for object creation
13 | * Clone objects using the Prototype pattern
14 | * Make incompatible interfaces compatible using the Adapter pattern
15 | * Secure an interface using the Proxy pattern
16 | * Choose an algorithm dynamically using the Strategy pattern
17 |
18 |
19 | If you feel this book is for you, get your [copy](https://www.amazon.com/dp/1788837487) today!
20 |
21 |
23 |
24 | ## Errata
25 |
26 | * Chapter 9, Implementation of Proxy Pattern section: The code block following the sentence "Let's recapitulate the full code of the proxy.py file:" is incorrect. The correction can be found at https://github.com/PacktPublishing/Mastering-Python-Design-Patterns-Second-Edition/blob/master/ERRATUM-PYTHON-DESIGN-PATTERN-PROXY.pdf.
27 |
28 | ## Instructions and Navigations
29 | All of the code is organized into folders. For example, Chapter02.
30 |
31 | The code will look like the following:
32 | ```
33 | class Musician:
34 | def __init__(self, name):
35 | self.name = name
36 | def __str__(self):
37 | return f'the musician {self.name}'
38 | def play(self):
39 | return 'plays music'
40 | ```
41 |
42 | **Following is what you need for this book:**
43 | This book is for intermediate Python developers. Prior knowledge of design patterns is not required to enjoy this book.
44 |
45 | With the following software and hardware list you can run all code files present in the book (Chapter 1-15).
46 | ### Software and Hardware List
47 | | Chapter | Software required | OS required |
48 | | -------- | ------------------------------------ | ----------------------------------- |
49 | |All chapters |Python 3.6.x | Windows, Mac OS X, and Linux (Any) |
50 | |Chapter 15 |SQLite 3.22.0 or later | Windows, Mac OS X, and Linux (Any) |
51 | |Chapter 15 |RabbitMQ 3.7.7 | Windows, Mac OS X, and Linux (Any) |
52 |
53 |
54 | ### Related products
55 | * Learn Python Programming - Second Edition [[Packt]](https://www.packtpub.com/application-development/learn-python-programming-second-edition?utm_source=github&utm_medium=repository&utm_campaign=1-788-99666-6) [[Amazon]](https://www.amazon.com/dp/1788996666)
56 |
57 | * Clean Code in Python [[Packt]](https://www.packtpub.com/application-development/clean-code-python?utm_source=github&utm_medium=repository&utm_campaign=978-1-78883-583-1) [[Amazon]](https://www.amazon.com/dp/1788835832)
58 |
59 |
60 | ## Get to Know the Author
61 | **Kamon Ayeva**
62 | is a web developer/DevOps engineer working with a variety of tools. He spends most of his time building projects using Python's powerful scripting capabilities, add-on libraries, and web frameworks such as Django or Flask. Kamon has been using Python in professional contexts for more than 12 years. He is also a Python instructor with a passion for teaching how to use Python features to quickly produce results.
63 |
64 |
65 | **Sakis Kasampalis**
66 | is a software engineer living in the Netherlands. He is not dogmatic about particular programming languages and tools; his principle is that the right tool should be used for the right job. One of his favorite tools is Python because he finds it very productive. Sakis was also the technical reviewer of Mastering Object-oriented Python and Learning Python Design Patterns, published by Packt Publishing.
67 |
68 |
69 | ### Suggestions and Feedback
70 | [Click here](https://docs.google.com/forms/d/e/1FAIpQLSdy7dATC6QmEL81FIUuymZ0Wy9vH1jHkvpY57OiMeKGqib_Ow/viewform) if you have any feedback or suggestions.
71 |
72 |
73 |
--------------------------------------------------------------------------------
/chapter01/abstract_factory.py:
--------------------------------------------------------------------------------
1 |
2 | # Frog game
3 |
4 | class Frog:
5 | def __init__(self, name):
6 | self.name = name
7 |
8 | def __str__(self):
9 | return self.name
10 |
11 | def interact_with(self, obstacle):
12 | act = obstacle.action()
13 | msg = f'{self} the Frog encounters {obstacle} and {act}!'
14 | print(msg)
15 |
16 | class Bug:
17 | def __str__(self):
18 | return 'a bug'
19 |
20 | def action(self):
21 | return 'eats it'
22 |
23 | class FrogWorld:
24 | def __init__(self, name):
25 | print(self)
26 | self.player_name = name
27 |
28 | def __str__(self):
29 | return '\n\n\t------ Frog World -------'
30 |
31 | def make_character(self):
32 | return Frog(self.player_name)
33 |
34 | def make_obstacle(self):
35 | return Bug()
36 |
37 |
38 | # Wizard game
39 |
40 | class Wizard:
41 | def __init__(self, name):
42 | self.name = name
43 |
44 | def __str__(self):
45 | return self.name
46 |
47 | def interact_with(self, obstacle):
48 | act = obstacle.action()
49 | msg = f'{self} the Wizard battles against {obstacle} and {act}!'
50 | print(msg)
51 |
52 | class Ork:
53 | def __str__(self):
54 | return 'an evil ork'
55 |
56 | def action(self):
57 | return 'kills it'
58 |
59 | class WizardWorld:
60 | def __init__(self, name):
61 | print(self)
62 | self.player_name = name
63 |
64 | def __str__(self):
65 | return '\n\n\t------ Wizard World -------'
66 |
67 | def make_character(self):
68 | return Wizard(self.player_name)
69 |
70 | def make_obstacle(self):
71 | return Ork()
72 |
73 | # Game environment
74 | class GameEnvironment:
75 | def __init__(self, factory):
76 | self.hero = factory.make_character()
77 | self.obstacle = factory.make_obstacle()
78 |
79 | def play(self):
80 | self.hero.interact_with(self.obstacle)
81 |
82 | def validate_age(name):
83 | try:
84 | age = input(f'Welcome {name}. How old are you? ')
85 | age = int(age)
86 | except ValueError as err:
87 | print(f"Age {age} is invalid, please try again...")
88 | return (False, age)
89 | return (True, age)
90 |
91 | def main():
92 | name = input("Hello. What's your name? ")
93 | valid_input = False
94 | while not valid_input:
95 | valid_input, age = validate_age(name)
96 | game = FrogWorld if age < 18 else WizardWorld
97 | environment = GameEnvironment(game(name))
98 | environment.play()
99 |
100 |
101 | if __name__ == '__main__':
102 | main()
103 |
--------------------------------------------------------------------------------
/chapter01/data/movies.json:
--------------------------------------------------------------------------------
1 | [
2 | {"title":"After Dark in Central Park",
3 | "year":1900,
4 | "director":null, "cast":null, "genre":null},
5 | {"title":"Boarding School Girls' Pajama Parade",
6 | "year":1900,
7 | "director":null, "cast":null, "genre":null},
8 | {"title":"Buffalo Bill's Wild West Parad",
9 | "year":1900,
10 | "director":null, "cast":null, "genre":null},
11 | {"title":"Caught",
12 | "year":1900,
13 | "director":null, "cast":null, "genre":null},
14 | {"title":"Clowns Spinning Hats",
15 | "year":1900,
16 | "director":null, "cast":null, "genre":null},
17 | {"title":"Capture of Boer Battery by British",
18 | "year":1900,
19 | "director":"James H. White", "cast":null, "genre":"Short documentary"},
20 | {"title":"The Enchanted Drawing",
21 | "year":1900,
22 | "director":"J. Stuart Blackton", "cast":null,"genre":null},
23 | {"title":"Family Troubles",
24 | "year":1900,
25 | "director":null, "cast":null, "genre":null},
26 | {"title":"Feeding Sea Lions",
27 | "year":1900,
28 | "director":null, "cast":"Paul Boyton", "genre":null}
29 | ]
--------------------------------------------------------------------------------
/chapter01/data/person.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | John
4 | Smith
5 | 25
6 |
7 | 21 2nd Street
8 | New York
9 | NY
10 | 10021
11 |
12 |
13 | 212 555-1234
14 | 646 555-4567
15 |
16 |
17 | male
18 |
19 |
20 |
21 | Jimy
22 | Liar
23 | 19
24 |
25 | 18 2nd Street
26 | New York
27 | NY
28 | 10021
29 |
30 |
31 | 212 555-1234
32 |
33 |
34 | male
35 |
36 |
37 |
38 | Patty
39 | Liar
40 | 20
41 |
42 | 18 2nd Street
43 | New York
44 | NY
45 | 10021
46 |
47 |
48 | 212 555-1234
49 | 001 452-8819
50 |
51 |
52 | female
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/chapter01/factory_method.py:
--------------------------------------------------------------------------------
1 | import json
2 | import xml.etree.ElementTree as etree
3 |
4 |
5 | class JSONDataExtractor:
6 |
7 | def __init__(self, filepath):
8 | self.data = dict()
9 | with open(filepath, mode='r', encoding='utf-8') as f:
10 | self.data = json.load(f)
11 |
12 | @property
13 | def parsed_data(self):
14 | return self.data
15 |
16 |
17 | class XMLDataExtractor:
18 |
19 | def __init__(self, filepath):
20 | self.tree = etree.parse(filepath)
21 |
22 | @property
23 | def parsed_data(self):
24 | return self.tree
25 |
26 |
27 | def dataextraction_factory(filepath):
28 | if filepath.endswith('json'):
29 | extractor = JSONDataExtractor
30 | elif filepath.endswith('xml'):
31 | extractor = XMLDataExtractor
32 | else:
33 | raise ValueError('Cannot extract data from {}'.format(filepath))
34 | return extractor(filepath)
35 |
36 |
37 | def extract_data_from(filepath):
38 | factory_obj = None
39 | try:
40 | factory_obj = dataextraction_factory(filepath)
41 | except ValueError as e:
42 | print(e)
43 | return factory_obj
44 |
45 |
46 | def main():
47 | sqlite_factory = extract_data_from('data/person.sq3')
48 | print()
49 |
50 | json_factory = extract_data_from('data/movies.json')
51 | json_data = json_factory.parsed_data
52 | print(f'Found: {len(json_data)} movies')
53 | for movie in json_data:
54 | print(f"Title: {movie['title']}")
55 | year = movie['year']
56 | if year:
57 | print(f"Year: {year}")
58 | director = movie['director']
59 | if director:
60 | print(f"Director: {director}")
61 | genre = movie['genre']
62 | if genre:
63 | print(f"Genre: {genre}")
64 | print()
65 |
66 | xml_factory = extract_data_from('data/person.xml')
67 | xml_data = xml_factory.parsed_data
68 | liars = xml_data.findall(f".//person[lastName='Liar']")
69 | print(f'found: {len(liars)} persons')
70 | for liar in liars:
71 | firstname = liar.find('firstName').text
72 | print(f'first name: {firstname}')
73 | lastname = liar.find('lastName').text
74 | print(f'last name: {lastname}')
75 | [print(f"phone number ({p.attrib['type']}):", p.text)
76 | for p in liar.find('phoneNumbers')]
77 | print()
78 | print()
79 |
80 |
81 | if __name__ == '__main__':
82 | main()
83 |
--------------------------------------------------------------------------------
/chapter01/id.py:
--------------------------------------------------------------------------------
1 |
2 | class A:
3 | pass
4 |
5 | if __name__ == '__main__':
6 | a = A()
7 | b = A()
8 |
9 | print(id(a) == id(b))
10 | print(a, b)
11 |
--------------------------------------------------------------------------------
/chapter02/apple_factory.py:
--------------------------------------------------------------------------------
1 |
2 | MINI14 = '1.4GHz Mac mini'
3 |
4 | class AppleFactory:
5 | class MacMini14:
6 | def __init__(self):
7 | self.memory = 4 # in gigabytes
8 | self.hdd = 500 # in gigabytes
9 | self.gpu = 'Intel HD Graphics 5000'
10 |
11 | def __str__(self):
12 | info = (f'Model: {MINI14}',
13 | f'Memory: {self.memory}GB',
14 | f'Hard Disk: {self.hdd}GB',
15 | f'Graphics Card: {self.gpu}')
16 | return '\n'.join(info)
17 |
18 | def build_computer(self, model):
19 | if model == MINI14:
20 | return self.MacMini14()
21 | else:
22 | print(f"I don't know how to build {model}")
23 |
24 | if __name__ == '__main__':
25 | afac = AppleFactory()
26 | mac_mini = afac.build_computer(MINI14)
27 | print(mac_mini)
28 |
--------------------------------------------------------------------------------
/chapter02/builder.py:
--------------------------------------------------------------------------------
1 |
2 | from enum import Enum
3 | import time
4 |
5 | PizzaProgress = Enum('PizzaProgress', 'queued preparation baking ready')
6 | PizzaDough = Enum('PizzaDough', 'thin thick')
7 | PizzaSauce = Enum('PizzaSauce', 'tomato creme_fraiche')
8 | PizzaTopping = Enum('PizzaTopping',
9 | 'mozzarella double_mozzarella bacon ham mushrooms red_onion oregano')
10 | STEP_DELAY = 3 # in seconds for the sake of the example
11 |
12 |
13 | class Pizza:
14 | def __init__(self, name):
15 | self.name = name
16 | self.dough = None
17 | self.sauce = None
18 | self.topping = []
19 |
20 | def __str__(self):
21 | return self.name
22 |
23 | def prepare_dough(self, dough):
24 | self.dough = dough
25 | print(f'preparing the {self.dough.name} dough of your {self}...')
26 | time.sleep(STEP_DELAY)
27 | print(f'done with the {self.dough.name} dough')
28 |
29 |
30 | class MargaritaBuilder:
31 | def __init__(self):
32 | self.pizza = Pizza('margarita')
33 | self.progress = PizzaProgress.queued
34 | self.baking_time = 5 # in seconds for the sake of the example
35 |
36 | def prepare_dough(self):
37 | self.progress = PizzaProgress.preparation
38 | self.pizza.prepare_dough(PizzaDough.thin)
39 |
40 | def add_sauce(self):
41 | print('adding the tomato sauce to your margarita...')
42 | self.pizza.sauce = PizzaSauce.tomato
43 | time.sleep(STEP_DELAY)
44 | print('done with the tomato sauce')
45 |
46 | def add_topping(self):
47 | topping_desc = 'double mozzarella, oregano'
48 | topping_items = (PizzaTopping.double_mozzarella, PizzaTopping.oregano)
49 | print(f'adding the topping ({topping_desc}) to your margarita')
50 | self.pizza.topping.append([t for t in topping_items])
51 | time.sleep(STEP_DELAY)
52 | print(f'done with the topping ({topping_desc})')
53 |
54 | def bake(self):
55 | self.progress = PizzaProgress.baking
56 | print(f'baking your margarita for {self.baking_time} seconds')
57 | time.sleep(self.baking_time)
58 | self.progress = PizzaProgress.ready
59 | print('your margarita is ready')
60 |
61 |
62 | class CreamyBaconBuilder:
63 | def __init__(self):
64 | self.pizza = Pizza('creamy bacon')
65 | self.progress = PizzaProgress.queued
66 | self.baking_time = 7 # in seconds for the sake of the example
67 |
68 | def prepare_dough(self):
69 | self.progress = PizzaProgress.preparation
70 | self.pizza.prepare_dough(PizzaDough.thick)
71 |
72 | def add_sauce(self):
73 | print('adding the crème fraîche sauce to your creamy bacon')
74 | self.pizza.sauce = PizzaSauce.creme_fraiche
75 | time.sleep(STEP_DELAY)
76 | print('done with the crème fraîche sauce')
77 |
78 | def add_topping(self):
79 | topping_desc = 'mozzarella, bacon, ham, mushrooms, red onion, oregano'
80 | topping_items = (PizzaTopping.mozzarella,
81 | PizzaTopping.bacon,
82 | PizzaTopping.ham,
83 | PizzaTopping.mushrooms,
84 | PizzaTopping.red_onion,
85 | PizzaTopping.oregano)
86 | print(f'adding the topping ({topping_desc}) to your creamy bacon')
87 | self.pizza.topping.append([t for t in topping_items])
88 | time.sleep(STEP_DELAY)
89 | print(f'done with the topping ({topping_desc})')
90 |
91 | def bake(self):
92 | self.progress = PizzaProgress.baking
93 | print(f'baking your creamy bacon for {self.baking_time} seconds')
94 | time.sleep(self.baking_time)
95 | self.progress = PizzaProgress.ready
96 | print('your creamy bacon is ready')
97 |
98 |
99 | class Waiter:
100 | def __init__(self):
101 | self.builder = None
102 |
103 | def construct_pizza(self, builder):
104 | self.builder = builder
105 | steps = (builder.prepare_dough,
106 | builder.add_sauce,
107 | builder.add_topping,
108 | builder.bake)
109 | [step() for step in steps]
110 |
111 | @property
112 | def pizza(self):
113 | return self.builder.pizza
114 |
115 |
116 | def validate_style(builders):
117 | try:
118 | input_msg = 'What pizza would you like, [m]argarita or [c]reamy bacon? '
119 | pizza_style = input(input_msg)
120 | builder = builders[pizza_style]()
121 | valid_input = True
122 | except KeyError:
123 | error_msg = 'Sorry, only margarita (key m) and creamy bacon (key c) are available'
124 | print(error_msg)
125 | return (False, None)
126 | return (True, builder)
127 |
128 |
129 | def main():
130 | builders = dict(m=MargaritaBuilder, c=CreamyBaconBuilder)
131 | valid_input = False
132 | while not valid_input:
133 | valid_input, builder = validate_style(builders)
134 | print()
135 | waiter = Waiter()
136 | waiter.construct_pizza(builder)
137 | pizza = waiter.pizza
138 | print()
139 | print(f'Enjoy your {pizza}!')
140 |
141 |
142 | if __name__ == '__main__':
143 | main()
144 |
--------------------------------------------------------------------------------
/chapter02/computer_builder.py:
--------------------------------------------------------------------------------
1 |
2 | class Computer:
3 | def __init__(self, serial_number):
4 | self.serial = serial_number
5 | self.memory = None # in gigabytes
6 | self.hdd = None # in gigabytes
7 | self.gpu = None
8 |
9 | def __str__(self):
10 | info = (f'Memory: {self.memory}GB',
11 | f'Hard Disk: {self.hdd}GB',
12 | f'Graphics Card: {self.gpu}')
13 | return '\n'.join(info)
14 |
15 | class ComputerBuilder:
16 | def __init__(self):
17 | self.computer = Computer('AG23385193')
18 |
19 | def configure_memory(self, amount):
20 | self.computer.memory = amount
21 |
22 | def configure_hdd(self, amount):
23 | self.computer.hdd = amount
24 |
25 | def configure_gpu(self, gpu_model):
26 | self.computer.gpu = gpu_model
27 |
28 | class HardwareEngineer:
29 | def __init__(self):
30 | self.builder = None
31 |
32 | def construct_computer(self, memory, hdd, gpu):
33 | self.builder = ComputerBuilder()
34 | steps = (self.builder.configure_memory(memory),
35 | self.builder.configure_hdd(hdd),
36 | self.builder.configure_gpu(gpu))
37 | [step for step in steps]
38 |
39 | @property
40 | def computer(self):
41 | return self.builder.computer
42 |
43 | def main():
44 | engineer = HardwareEngineer()
45 | engineer.construct_computer(hdd=500,
46 | memory=8,
47 | gpu='GeForce GTX 650 Ti')
48 | computer = engineer.computer
49 | print(computer)
50 |
51 | if __name__ == '__main__':
52 | main()
53 |
--------------------------------------------------------------------------------
/chapter02/exercise_fluent_builder.py:
--------------------------------------------------------------------------------
1 | class Pizza:
2 | def __init__(self, builder):
3 | self.garlic = builder.garlic
4 | self.extra_cheese = builder.extra_cheese
5 |
6 | def __str__(self):
7 | garlic = 'yes' if self.garlic else 'no'
8 | cheese = 'yes' if self.extra_cheese else 'no'
9 | info = (f'Garlic: {garlic}', f'Extra cheese: {cheese}')
10 | return '\n'.join(info)
11 |
12 | class PizzaBuilder:
13 | def __init__(self):
14 | self.extra_cheese = False
15 | self.garlic = False
16 |
17 | def add_garlic(self):
18 | self.garlic = True
19 | return self
20 |
21 | def add_extra_cheese(self):
22 | self.extra_cheese = True
23 | return self
24 |
25 | def build(self):
26 | return Pizza(self)
27 |
28 | if __name__ == '__main__':
29 | pizza = Pizza.PizzaBuilder().add_garlic().add_extra_cheese().build()
30 | print(pizza)
--------------------------------------------------------------------------------
/chapter03/prototype.py:
--------------------------------------------------------------------------------
1 | import copy
2 |
3 | class Website:
4 | def __init__(self, name, domain, description, author, **kwargs):
5 | '''Examples of optional attributes (kwargs):
6 | category, creation_date, technologies, keywords.
7 | '''
8 | self.name = name
9 | self.domain = domain
10 | self.description = description
11 | self.author = author
12 |
13 | for key in kwargs:
14 | setattr(self, key, kwargs[key])
15 |
16 | def __str__(self):
17 | summary = [f'Website "{self.name}"\n',]
18 |
19 | infos = vars(self).items()
20 | ordered_infos = sorted(infos)
21 | for attr, val in ordered_infos:
22 | if attr == 'name':
23 | continue
24 | summary.append(f'{attr}: {val}\n')
25 |
26 | return ''.join(summary)
27 |
28 |
29 | class Prototype:
30 | def __init__(self):
31 | self.objects = dict()
32 |
33 | def register(self, identifier, obj):
34 | self.objects[identifier] = obj
35 |
36 | def unregister(self, identifier):
37 | del self.objects[identifier]
38 |
39 | def clone(self, identifier, **attrs):
40 | found = self.objects.get(identifier)
41 | if not found:
42 | raise ValueError(f'Incorrect object identifier: {identifier}')
43 | obj = copy.deepcopy(found)
44 | for key in attrs:
45 | setattr(obj, key, attrs[key])
46 |
47 | return obj
48 |
49 | def main():
50 | keywords = ('python', 'data', 'apis', 'automation')
51 | site1 = Website('ContentGardening',
52 | domain='contentgardening.com',
53 | description='Automation and data-driven apps',
54 | author='Kamon Ayeva',
55 | category='Blog',
56 | keywords=keywords)
57 |
58 | prototype = Prototype()
59 | identifier = 'ka-cg-1'
60 | prototype.register(identifier, site1)
61 |
62 | site2 = prototype.clone(identifier,
63 | name='ContentGardeningPlayground',
64 | domain='play.contentgardening.com',
65 | description='Experimentation for techniques featured on the blog',
66 | category='Membership site',
67 | creation_date='2018-08-01')
68 |
69 | for site in (site1, site2):
70 | print(site)
71 | print(f'ID site1 : {id(site1)} != ID site2 : {id(site2)}')
72 |
73 | if __name__ == '__main__':
74 | main()
75 |
--------------------------------------------------------------------------------
/chapter03/singleton.py:
--------------------------------------------------------------------------------
1 |
2 |
3 | import urllib.parse
4 | import urllib.request
5 |
6 |
7 | class SingletonType(type):
8 | _instances = {}
9 | def __call__(cls, *args, **kwargs):
10 | if cls not in cls._instances:
11 | cls._instances[cls] = super(SingletonType, cls).__call__(*args, **kwargs)
12 | return cls._instances[cls]
13 |
14 |
15 | class URLFetcher(metaclass=SingletonType):
16 |
17 | def __init__(self):
18 | self.urls = []
19 |
20 | def fetch(self, url):
21 | req = urllib.request.Request(url)
22 | with urllib.request.urlopen(req) as response:
23 | if response.code == 200:
24 | the_page = response.read()
25 | print(the_page)
26 |
27 | urls = self.urls
28 | urls.append(url)
29 | self.urls = urls
30 |
31 | def dump_url_registry(self):
32 | return ', '.join(self.urls)
33 |
34 |
35 | def main():
36 |
37 | MY_URLS = ['http://www.voidspace.org.uk',
38 | 'http://google.com',
39 | 'http://python.org',
40 | 'https://www.python.org/error',
41 | ]
42 |
43 | print(URLFetcher() is URLFetcher())
44 |
45 | fetcher = URLFetcher()
46 | for url in MY_URLS:
47 | try:
48 | fetcher.fetch(url)
49 | except Exception as e:
50 | print(e)
51 |
52 | print('-------')
53 | done_urls = fetcher.dump_url_registry()
54 | print(f'Done URLs: {done_urls}')
55 |
56 | if __name__ == '__main__':
57 | main()
58 |
--------------------------------------------------------------------------------
/chapter04/adapter.py:
--------------------------------------------------------------------------------
1 |
2 | from external import Musician, Dancer
3 |
4 |
5 | class Club:
6 | def __init__(self, name):
7 | self.name = name
8 |
9 | def __str__(self):
10 | return f'the club {self.name}'
11 |
12 | def organize_event(self):
13 | return 'hires an artist to perform for the people'
14 |
15 |
16 | class Adapter:
17 | def __init__(self, obj, adapted_methods):
18 | self.obj = obj
19 | self.__dict__.update(adapted_methods)
20 |
21 | def __str__(self):
22 | return str(self.obj)
23 |
24 |
25 | def main():
26 |
27 | objects = [Club('Jazz Cafe'), Musician('Roy Ayers'), Dancer('Shane Sparks')]
28 |
29 | for obj in objects:
30 | if hasattr(obj, 'play') or hasattr(obj, 'dance'):
31 | if hasattr(obj, 'play'):
32 | adapted_methods = dict(organize_event=obj.play)
33 | elif hasattr(obj, 'dance'):
34 | adapted_methods = dict(organize_event=obj.dance)
35 |
36 | # referencing the adapted object here
37 | obj = Adapter(obj, adapted_methods)
38 |
39 | print(f'{obj} {obj.organize_event()}')
40 |
41 |
42 | if __name__ == "__main__":
43 | main()
--------------------------------------------------------------------------------
/chapter04/external.py:
--------------------------------------------------------------------------------
1 | class Musician:
2 | def __init__(self, name):
3 | self.name = name
4 |
5 | def __str__(self):
6 | return f'the musician {self.name}'
7 |
8 | def play(self):
9 | return 'plays music'
10 |
11 | class Dancer:
12 | def __init__(self, name):
13 | self.name = name
14 |
15 | def __str__(self):
16 | return f'the dancer {self.name}'
17 |
18 | def dance(self):
19 | return 'does a dance performance'
20 |
--------------------------------------------------------------------------------
/chapter05/mymath.py:
--------------------------------------------------------------------------------
1 |
2 | import functools
3 |
4 | def memoize(fn):
5 | cache = dict()
6 |
7 | @functools.wraps(fn)
8 | def memoizer(*args):
9 | if args not in cache:
10 | cache[args] = fn(*args)
11 | return cache[args]
12 |
13 | return memoizer
14 |
15 | @memoize
16 | def number_sum(n):
17 | '''Returns the sum of the first n numbers'''
18 | assert(n >= 0), 'n must be >= 0'
19 | if n == 0:
20 | return 0
21 | else:
22 | return n + number_sum(n-1)
23 |
24 | @memoize
25 | def fibonacci(n):
26 | '''Returns the suite of Fibonacci numbers'''
27 | assert(n >= 0), 'n must be >= 0'
28 | if n in (0, 1):
29 | return n
30 | else:
31 | return fibonacci(n-1) + fibonacci(n-2)
32 |
33 | def main():
34 | from timeit import Timer
35 |
36 | to_execute = [
37 | (number_sum,
38 | Timer('number_sum(300)', 'from __main__ import number_sum')),
39 | (fibonacci,
40 | Timer('fibonacci(100)', 'from __main__ import fibonacci'))
41 | ]
42 |
43 | for item in to_execute:
44 | fn = item[0]
45 | print(f'Function "{fn.__name__}": {fn.__doc__}')
46 | t = item[1]
47 | print(f'Time: {t.timeit()}')
48 | print()
49 |
50 | if __name__ == '__main__':
51 | main()
52 |
--------------------------------------------------------------------------------
/chapter05/number_sum.py:
--------------------------------------------------------------------------------
1 | sum_cache = {0:0}
2 |
3 | def number_sum(n):
4 | '''Returns the sum of the first n numbers'''
5 | assert(n >= 0), 'n must be >= 0'
6 |
7 | if n in sum_cache:
8 | return sum_cache[n]
9 | res = n + number_sum(n-1)
10 | # Add the value to the cache
11 | sum_cache[n] = res
12 | return res
13 |
14 | if __name__ == '__main__':
15 | from timeit import Timer
16 | t = Timer('number_sum(300)', 'from __main__ import number_sum')
17 | print('Time: ', t.timeit())
--------------------------------------------------------------------------------
/chapter05/number_sum_naive.py:
--------------------------------------------------------------------------------
1 |
2 | def number_sum(n):
3 | '''Returns the sum of the first n numbers'''
4 | assert(n >= 0), 'n must be >= 0'
5 |
6 | if n == 0:
7 | return 0
8 | else:
9 | return n + number_sum(n-1)
10 |
11 | if __name__ == '__main__':
12 | from timeit import Timer
13 | t = Timer('number_sum(30)', 'from __main__ import number_sum')
14 | print('Time: ', t.timeit())
--------------------------------------------------------------------------------
/chapter06/bridge.py:
--------------------------------------------------------------------------------
1 |
2 |
3 | import abc
4 | import urllib.parse
5 | import urllib.request
6 |
7 |
8 | class ResourceContent:
9 | """
10 | Define the abstraction's interface.
11 | Maintain a reference to an object which represents the Implementor.
12 | """
13 |
14 | def __init__(self, imp):
15 | self._imp = imp
16 |
17 | def show_content(self, path):
18 | self._imp.fetch(path)
19 |
20 |
21 | class ResourceContentFetcher(metaclass=abc.ABCMeta):
22 | """
23 | Define the interface (Implementor) for implementation classes that help fetch content.
24 | """
25 |
26 | @abc.abstractmethod
27 | def fetch(path):
28 | pass
29 |
30 |
31 | class URLFetcher(ResourceContentFetcher):
32 | """
33 | Implement the Implementor interface and define its concrete
34 | implementation.
35 | """
36 |
37 | def fetch(self, path):
38 | # path is an URL
39 | req = urllib.request.Request(path)
40 | with urllib.request.urlopen(req) as response:
41 | if response.code == 200:
42 | the_page = response.read()
43 | print(the_page)
44 |
45 |
46 | class LocalFileFetcher(ResourceContentFetcher):
47 | """
48 | Implement the Implementor interface and define its concrete
49 | implementation.
50 | """
51 |
52 | def fetch(self, path):
53 | # path is the filepath to a text file
54 | with open(path) as f:
55 | print(f.read())
56 |
57 |
58 | def main():
59 | url_fetcher = URLFetcher()
60 | iface = ResourceContent(url_fetcher)
61 | iface.show_content('http://python.org')
62 |
63 | print('===================')
64 |
65 | localfs_fetcher = LocalFileFetcher()
66 | iface = ResourceContent(localfs_fetcher)
67 | iface.show_content('file.txt')
68 |
69 |
70 | if __name__ == "__main__":
71 | main()
72 |
73 |
--------------------------------------------------------------------------------
/chapter06/file.txt:
--------------------------------------------------------------------------------
1 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin in nibh in enim euismod mattis placerat in velit. Donec malesuada risus sed venenatis pharetra. Proin tincidunt porttitor euismod. Etiam non odio sodales, tincidunt elit ac, sodales nisi. Donec massa felis, pharetra ut libero nec, consectetur venenatis dui. Phasellus nec sem non erat ultricies finibus. Donec sed blandit arcu. Aliquam erat volutpat. Donec aliquam ipsum risus, et accumsan nibh faucibus non. Aenean faucibus feugiat diam vitae rutrum. Sed ullamcorper leo sed orci efficitur rhoncus.
2 |
3 | Duis vitae dolor vestibulum nibh semper faucibus. Vivamus libero quam, ultrices quis sapien vel, blandit ultricies purus. Nunc nisl lorem, rutrum id aliquam nec, dictum non ligula. Duis ullamcorper, nulla quis luctus commodo, massa lorem tristique orci, quis aliquam diam est semper nisi. Maecenas tempor odio auctor nulla efficitur, non convallis tellus iaculis. Fusce quis purus nibh. Nulla tempor est vel metus sodales, in dapibus risus molestie. Donec tristique tellus in pretium pulvinar. Pellentesque ut vehicula mauris. Vivamus pellentesque, tellus in dictum vehicula, justo ex volutpat sem, at cursus nisl elit non ex. Sed sed leo eget eros lobortis laoreet. Sed ornare vitae mi a vestibulum. Suspendisse potenti. Donec sed ligula ac enim mattis posuere.
--------------------------------------------------------------------------------
/chapter07/facade.py:
--------------------------------------------------------------------------------
1 | from enum import Enum
2 | from abc import ABCMeta, abstractmethod
3 |
4 | State = Enum('State', 'new running sleeping restart zombie')
5 |
6 | class User:
7 | pass
8 |
9 | class Process:
10 | pass
11 |
12 | class File:
13 | pass
14 |
15 | class Server(metaclass=ABCMeta):
16 | @abstractmethod
17 | def __init__(self):
18 | pass
19 |
20 | def __str__(self):
21 | return self.name
22 |
23 | @abstractmethod
24 | def boot(self):
25 | pass
26 |
27 | @abstractmethod
28 | def kill(self, restart=True):
29 | pass
30 |
31 | class FileServer(Server):
32 | def __init__(self):
33 | '''actions required for initializing the file server'''
34 | self.name = 'FileServer'
35 | self.state = State.new
36 |
37 | def boot(self):
38 | print(f'booting the {self}')
39 | '''actions required for booting the file server'''
40 | self.state = State.running
41 |
42 | def kill(self, restart=True):
43 | print(f'Killing {self}')
44 | '''actions required for killing the file server'''
45 | self.state = State.restart if restart else State.zombie
46 |
47 | def create_file(self, user, name, permissions):
48 | '''check validity of permissions, user rights, etc.'''
49 | print(f"trying to create the file '{name}' for user '{user}' with permissions {permissions}")
50 |
51 | class ProcessServer(Server):
52 | def __init__(self):
53 | '''actions required for initializing the process server'''
54 | self.name = 'ProcessServer'
55 | self.state = State.new
56 |
57 | def boot(self):
58 | print(f'booting the {self}')
59 | '''actions required for booting the process server'''
60 | self.state = State.running
61 |
62 | def kill(self, restart=True):
63 | print(f'Killing {self}')
64 | '''actions required for killing the process server'''
65 | self.state = State.restart if restart else State.zombie
66 |
67 | def create_process(self, user, name):
68 | '''check user rights, generate PID, etc.'''
69 | print(f"trying to create the process '{name}' for user '{user}'")
70 |
71 | class WindowServer:
72 | pass
73 |
74 | class NetworkServer:
75 | pass
76 |
77 | class OperatingSystem:
78 | '''The Facade'''
79 | def __init__(self):
80 | self.fs = FileServer()
81 | self.ps = ProcessServer()
82 |
83 | def start(self):
84 | [i.boot() for i in (self.fs, self.ps)]
85 |
86 | def create_file(self, user, name, permissions):
87 | return self.fs.create_file(user, name, permissions)
88 |
89 | def create_process(self, user, name):
90 | return self.ps.create_process(user, name)
91 |
92 | def main():
93 | os = OperatingSystem()
94 | os.start()
95 | os.create_file('foo', 'hello', '-rw-r-r')
96 | os.create_process('bar', 'ls /tmp')
97 |
98 | if __name__ == '__main__':
99 | main()
100 |
101 |
--------------------------------------------------------------------------------
/chapter08/flyweight.py:
--------------------------------------------------------------------------------
1 | import random
2 | from enum import Enum
3 |
4 | CarType = Enum('CarType', 'subcompact compact suv')
5 |
6 | class Car:
7 | pool = dict()
8 |
9 | def __new__(cls, car_type):
10 | obj = cls.pool.get(car_type, None)
11 | if not obj:
12 | obj = object.__new__(cls)
13 | cls.pool[car_type] = obj
14 | obj.car_type = car_type
15 | return obj
16 |
17 | def render(self, color, x, y):
18 | type = self.car_type
19 | msg = f'render a car of type {type} and color {color} at ({x}, {y})'
20 | print(msg)
21 |
22 | def main():
23 | rnd = random.Random()
24 | #age_min, age_max = 1, 30 # in years
25 | colors = 'white black silver gray red blue brown beige yellow green'.split()
26 | min_point, max_point = 0, 100
27 | car_counter = 0
28 |
29 | for _ in range(10):
30 | c1 = Car(CarType.subcompact)
31 | c1.render(random.choice(colors),
32 | rnd.randint(min_point, max_point),
33 | rnd.randint(min_point, max_point))
34 | car_counter += 1
35 |
36 | for _ in range(3):
37 | c2 = Car(CarType.compact)
38 | c2.render(random.choice(colors),
39 | rnd.randint(min_point, max_point),
40 | rnd.randint(min_point, max_point))
41 | car_counter += 1
42 |
43 | for _ in range(5):
44 | c3 = Car(CarType.suv)
45 | c3.render(random.choice(colors),
46 | rnd.randint(min_point, max_point),
47 | rnd.randint(min_point, max_point))
48 | car_counter += 1
49 |
50 | print(f'cars rendered: {car_counter}')
51 | print(f'cars actually created: {len(Car.pool)}')
52 |
53 | c4 = Car(CarType.subcompact)
54 | c5 = Car(CarType.subcompact)
55 | c6 = Car(CarType.suv)
56 | print(f'{id(c4)} == {id(c5)}? {id(c4) == id(c5)}')
57 | print(f'{id(c5)} == {id(c6)}? {id(c5) == id(c6)}')
58 |
59 |
60 | if __name__ == '__main__':
61 | main()
62 |
63 |
--------------------------------------------------------------------------------
/chapter08/lazy.py:
--------------------------------------------------------------------------------
1 |
2 | class LazyProperty:
3 | def __init__(self, method):
4 | self.method = method
5 | self.method_name = method.__name__
6 | # print(f"function overriden: {self.fget}")
7 | # print(f"function's name: {self.func_name}")
8 |
9 | def __get__(self, obj, cls):
10 | if not obj:
11 | return None
12 | value = self.method(obj)
13 | # print(f'value {value}')
14 | setattr(obj, self.method_name, value)
15 | return value
16 |
17 |
18 | class Test:
19 | def __init__(self):
20 | self.x = 'foo'
21 | self.y = 'bar'
22 | self._resource = None
23 |
24 | @LazyProperty
25 | def resource(self):
26 | print(f'initializing self._resource which is: {self._resource}')
27 | self._resource = tuple(range(5)) # expensive
28 | return self._resource
29 |
30 |
31 | def main():
32 | t = Test()
33 | print(t.x)
34 | print(t.y)
35 | # do more work...
36 | print(t.resource)
37 | print(t.resource)
38 |
39 |
40 | if __name__ == '__main__':
41 | main()
42 |
--------------------------------------------------------------------------------
/chapter08/mvc.py:
--------------------------------------------------------------------------------
1 | quotes = (
2 | 'A man is not complete until he is married. Then he is finished.',
3 | 'As I said before, I never repeat myself.',
4 | 'Behind a successful man is an exhausted woman.',
5 | 'Black holes really suck...',
6 | 'Facts are stubborn things.'
7 | )
8 |
9 | class QuoteModel:
10 | def get_quote(self, n):
11 | try:
12 | value = quotes[n]
13 | except IndexError as err:
14 | value = 'Not found!'
15 | return value
16 |
17 | class QuoteTerminalView:
18 | def show(self, quote):
19 | print(f'And the quote is: "{quote}"')
20 |
21 | def error(self, msg):
22 | print(f'Error: {msg}')
23 |
24 | def select_quote(self):
25 | return input('Which quote number would you like to see? ')
26 |
27 | class QuoteTerminalController:
28 | def __init__(self):
29 | self.model = QuoteModel()
30 | self.view = QuoteTerminalView()
31 |
32 | def run(self):
33 | valid_input = False
34 | while not valid_input:
35 | try:
36 | n = self.view.select_quote()
37 | n = int(n)
38 | valid_input = True
39 | except ValueError as err:
40 | self.view.error(f"Incorrect index '{n}'")
41 | quote = self.model.get_quote(n)
42 | self.view.show(quote)
43 |
44 | def main():
45 | controller = QuoteTerminalController()
46 | while True:
47 | controller.run()
48 |
49 | if __name__ == '__main__':
50 | main()
51 |
--------------------------------------------------------------------------------
/chapter08/proxy.py:
--------------------------------------------------------------------------------
1 | class SensitiveInfo:
2 | def __init__(self):
3 | self.users = ['nick', 'tom', 'ben', 'mike']
4 |
5 | def read(self):
6 | nb = len(self.users)
7 | print(f"There are {nb} users: {' '.join(self.users)}")
8 |
9 | def add(self, user):
10 | self.users.append(user)
11 | print(f'Added user {user}')
12 |
13 | class Info:
14 | '''protection proxy to SensitiveInfo'''
15 |
16 | def __init__(self):
17 | self.protected = SensitiveInfo()
18 | self.secret = '0xdeadbeef'
19 |
20 | def read(self):
21 | self.protected.read()
22 |
23 | def add(self, user):
24 | sec = input('what is the secret? ')
25 | self.protected.add(user) if sec == self.secret else print("That's wrong!")
26 |
27 | def main():
28 | info = Info()
29 |
30 | while True:
31 | print('1. read list |==| 2. add user |==| 3. quit')
32 | key = input('choose option: ')
33 | if key == '1':
34 | info.read()
35 | elif key == '2':
36 | name = input('choose username: ')
37 | info.add(name)
38 | elif key == '3':
39 | exit()
40 | else:
41 | print(f'unknown option: {key}')
42 |
43 | if __name__ == '__main__':
44 | main()
--------------------------------------------------------------------------------
/chapter09/chain.py:
--------------------------------------------------------------------------------
1 | class Event:
2 | def __init__(self, name):
3 | self.name = name
4 |
5 | def __str__(self):
6 | return self.name
7 |
8 | class Widget:
9 | def __init__(self, parent=None):
10 | self.parent = parent
11 |
12 | def handle(self, event):
13 | handler = f'handle_{event}'
14 | if hasattr(self, handler):
15 | method = getattr(self, handler)
16 | method(event)
17 | elif self.parent is not None:
18 | self.parent.handle(event)
19 | elif hasattr(self, 'handle_default'):
20 | self.handle_default(event)
21 |
22 | class MainWindow(Widget):
23 | def handle_close(self, event):
24 | print(f'MainWindow: {event}')
25 |
26 | def handle_default(self, event):
27 | print(f'MainWindow Default: {event}')
28 |
29 | class SendDialog(Widget):
30 | def handle_paint(self, event):
31 | print(f'SendDialog: {event}')
32 |
33 | class MsgText(Widget):
34 | def handle_down(self, event):
35 | print(f'MsgText: {event}')
36 |
37 | def main():
38 | mw = MainWindow()
39 | sd = SendDialog(mw)
40 | msg = MsgText(sd)
41 |
42 | for e in ('down', 'paint', 'unhandled', 'close'):
43 | evt = Event(e)
44 | print(f'Sending event -{evt}- to MainWindow')
45 | mw.handle(evt)
46 | print(f'Sending event -{evt}- to SendDialog')
47 | sd.handle(evt)
48 | print(f'Sending event -{evt}- to MsgText')
49 | msg.handle(evt)
50 |
51 | if __name__ == '__main__':
52 | main()
53 |
--------------------------------------------------------------------------------
/chapter10/command.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | verbose = True
4 |
5 | class RenameFile:
6 |
7 | def __init__(self, src, dest):
8 | self.src = src
9 | self.dest = dest
10 |
11 | def execute(self):
12 | if verbose:
13 | print(f"[renaming '{self.src}' to '{self.dest}']")
14 | os.rename(self.src, self.dest)
15 |
16 | def undo(self):
17 | if verbose:
18 | print(f"[renaming '{self.dest}' back to '{self.src}']")
19 | os.rename(self.dest, self.src)
20 |
21 | class CreateFile:
22 |
23 | def __init__(self, path, txt='hello world\n'):
24 | self.path = path
25 | self.txt = txt
26 |
27 | def execute(self):
28 | if verbose:
29 | print(f"[creating file '{self.path}']")
30 | with open(self.path, mode='w', encoding='utf-8') as out_file:
31 | out_file.write(self.txt)
32 |
33 | def undo(self):
34 | delete_file(self.path)
35 |
36 | class ReadFile:
37 |
38 | def __init__(self, path):
39 | self.path = path
40 |
41 | def execute(self):
42 | if verbose:
43 | print(f"[reading file '{self.path}']")
44 | with open(self.path, mode='r', encoding='utf-8') as in_file:
45 | print(in_file.read(), end='')
46 |
47 | def delete_file(path):
48 | if verbose:
49 | print(f"deleting file {path}")
50 | os.remove(path)
51 |
52 | def main():
53 |
54 | orig_name, new_name = 'file1', 'file2'
55 |
56 | commands = (CreateFile(orig_name),
57 | ReadFile(orig_name),
58 | RenameFile(orig_name, new_name))
59 |
60 | [c.execute() for c in commands]
61 |
62 | answer = input('reverse the executed commands? [y/n] ')
63 |
64 | if answer not in 'yY':
65 | print(f"the result is {new_name}")
66 | exit()
67 |
68 | for c in reversed(commands):
69 | try:
70 | c.undo()
71 | except AttributeError as e:
72 | print("Error", str(e))
73 |
74 | if __name__ == "__main__":
75 | main()
--------------------------------------------------------------------------------
/chapter10/first-class.py:
--------------------------------------------------------------------------------
1 | import os
2 | verbose = True
3 |
4 | class CreateFile:
5 |
6 | def __init__(self, path, txt='hello world\n'):
7 | self.path = path
8 | self.txt = txt
9 |
10 | def execute(self):
11 | if verbose:
12 | print(f"[creating file '{self.path}']")
13 | with open(self.path, mode='w', encoding='utf-8') as out_file:
14 | out_file.write(self.txt)
15 |
16 | def undo(self):
17 | try:
18 | delete_file(self.path)
19 | except:
20 | print('delete action not successful...')
21 | print('... file was probably already deleted.')
22 |
23 | def delete_file(path):
24 | if verbose:
25 | print(f"deleting file {path}...")
26 | os.remove(path)
27 |
28 | def main():
29 |
30 | orig_name = 'file1'
31 | df=delete_file
32 |
33 | commands = [CreateFile(orig_name),]
34 | commands.append(df)
35 |
36 | for c in commands:
37 | try:
38 | c.execute()
39 | except AttributeError as e:
40 | df(orig_name)
41 |
42 | for c in reversed(commands):
43 | try:
44 | c.undo()
45 | except AttributeError as e:
46 | pass
47 |
48 | if __name__ == "__main__":
49 | main()
50 |
--------------------------------------------------------------------------------
/chapter11/observer.py:
--------------------------------------------------------------------------------
1 |
2 | class Publisher:
3 | def __init__(self):
4 | self.observers = []
5 |
6 | def add(self, observer):
7 | if observer not in self.observers:
8 | self.observers.append(observer)
9 | else:
10 | print(f'Failed to add: {observer}')
11 |
12 | def remove(self, observer):
13 | try:
14 | self.observers.remove(observer)
15 | except ValueError:
16 | print(f'Failed to remove: {observer}')
17 |
18 | def notify(self):
19 | [o.notify(self) for o in self.observers]
20 |
21 | class DefaultFormatter(Publisher):
22 | def __init__(self, name):
23 | Publisher.__init__(self)
24 | self.name = name
25 | self._data = 0
26 |
27 | def __str__(self):
28 | return f"{type(self).__name__}: '{self.name}' has data = {self._data}"
29 |
30 | @property
31 | def data(self):
32 | return self._data
33 |
34 | @data.setter
35 | def data(self, new_value):
36 | try:
37 | self._data = int(new_value)
38 | except ValueError as e:
39 | print(f'Error: {e}')
40 | else:
41 | self.notify()
42 |
43 | class HexFormatterObs:
44 | def notify(self, publisher):
45 | value = hex(publisher.data)
46 | print(f"{type(self).__name__}: '{publisher.name}' has now hex data = {value}")
47 |
48 | class BinaryFormatterObs:
49 | def notify(self, publisher):
50 | value = bin(publisher.data)
51 | print(f"{type(self).__name__}: '{publisher.name}' has now bin data = {value}")
52 |
53 | def main():
54 | df = DefaultFormatter('test1')
55 | print(df)
56 |
57 | print()
58 | hf = HexFormatterObs()
59 | df.add(hf)
60 | df.data = 3
61 | print(df)
62 |
63 | print()
64 | bf = BinaryFormatterObs()
65 | df.add(bf)
66 | df.data = 21
67 | print(df)
68 |
69 | print()
70 | df.remove(hf)
71 | df.data = 40
72 | print(df)
73 |
74 | print()
75 | df.remove(hf)
76 | df.add(bf)
77 |
78 | df.data = 'hello'
79 | print(df)
80 |
81 | print()
82 | df.data = 15.8
83 | print(df)
84 |
85 | if __name__ == '__main__':
86 | main()
87 |
--------------------------------------------------------------------------------
/chapter12/state.py:
--------------------------------------------------------------------------------
1 |
2 | from state_machine import (State, Event, acts_as_state_machine,
3 | after, before, InvalidStateTransition)
4 |
5 |
6 | @acts_as_state_machine
7 | class Process:
8 | created = State(initial=True)
9 | waiting = State()
10 | running = State()
11 | terminated = State()
12 | blocked = State()
13 | swapped_out_waiting = State()
14 | swapped_out_blocked = State()
15 |
16 | wait = Event(from_states=(created, running, blocked, swapped_out_waiting),
17 | to_state=waiting)
18 | run = Event(from_states=waiting,
19 | to_state=running)
20 | terminate = Event(from_states=running,
21 | to_state=terminated)
22 | block = Event(from_states=(running, swapped_out_blocked),
23 | to_state=blocked)
24 | swap_wait = Event(from_states=waiting,
25 | to_state=swapped_out_waiting)
26 | swap_block = Event(from_states=blocked,
27 | to_state=swapped_out_blocked)
28 |
29 | def __init__(self, name):
30 | self.name = name
31 |
32 | @after('wait')
33 | def wait_info(self):
34 | print(f'{self.name} entered waiting mode')
35 |
36 | @after('run')
37 | def run_info(self):
38 | print(f'{self.name} is running')
39 |
40 | @before('terminate')
41 | def terminate_info(self):
42 | print(f'{self.name} terminated')
43 |
44 | @after('block')
45 | def block_info(self):
46 | print(f'{self.name} is blocked')
47 |
48 | @after('swap_wait')
49 | def swap_wait_info(self):
50 | print(f'{self.name} is swapped out and waiting')
51 |
52 | @after('swap_block')
53 | def swap_block_info(self):
54 | print(f'{self.name} is swapped out and blocked')
55 |
56 | def transition(process, event, event_name):
57 | try:
58 | event()
59 | except InvalidStateTransition as err:
60 | print(f'Error: transition of {process.name} from {process.current_state} to {event_name} failed')
61 |
62 | def state_info(process):
63 | print(f'state of {process.name}: {process.current_state}')
64 |
65 | def main():
66 | RUNNING = 'running'
67 | WAITING = 'waiting'
68 | BLOCKED = 'blocked'
69 | TERMINATED = 'terminated'
70 |
71 | p1, p2 = Process('process1'), Process('process2')
72 | [state_info(p) for p in (p1, p2)]
73 |
74 | print()
75 | transition(p1, p1.wait, WAITING)
76 | transition(p2, p2.terminate, TERMINATED)
77 | [state_info(p) for p in (p1, p2)]
78 |
79 | print()
80 | transition(p1, p1.run, RUNNING)
81 | transition(p2, p2.wait, WAITING)
82 | [state_info(p) for p in (p1, p2)]
83 |
84 | print()
85 | transition(p2, p2.run, RUNNING)
86 | [state_info(p) for p in (p1, p2)]
87 |
88 | print()
89 | [transition(p, p.block, BLOCKED) for p in (p1, p2)]
90 | [state_info(p) for p in (p1, p2)]
91 |
92 | print()
93 | [transition(p, p.terminate, TERMINATED) for p in (p1, p2)]
94 | [state_info(p) for p in (p1, p2)]
95 |
96 | if __name__ == '__main__':
97 | main()
98 |
--------------------------------------------------------------------------------
/chapter13/boiler.py:
--------------------------------------------------------------------------------
1 | from pyparsing import Word, OneOrMore, Optional, Group, Suppress, alphanums
2 |
3 | class Boiler:
4 | def __init__(self):
5 | self.temperature = 83# in celsius
6 |
7 | def __str__(self):
8 | return f'boiler temperature: {self.temperature}'
9 |
10 | def increase_temperature(self, amount):
11 | print(f"increasing the boiler's temperature by {amount} degrees")
12 | self.temperature += amount
13 |
14 | def decrease_temperature(self, amount):
15 | print(f"decreasing the boiler's temperature by {amount} degrees")
16 | self.temperature -= amount
17 |
18 | word = Word(alphanums)
19 | command = Group(OneOrMore(word))
20 | token = Suppress("->")
21 | device = Group(OneOrMore(word))
22 | argument = Group(OneOrMore(word))
23 | event = command + token + device + Optional(token + argument)
24 |
25 | boiler = Boiler()
26 | print(boiler)
27 |
28 | cmd, dev, arg = event.parseString('increase -> boiler temperature -> 3 degrees')
29 | cmd_str = ' '.join(cmd)
30 | dev_str = ' '.join(dev)
31 |
32 | if 'increase' in cmd_str and 'boiler' in dev_str:
33 | boiler.increase_temperature(int(arg[0]))
34 |
35 | print(boiler)
36 |
--------------------------------------------------------------------------------
/chapter13/interpreter.py:
--------------------------------------------------------------------------------
1 |
2 | from pyparsing import Word, OneOrMore, Optional, Group, Suppress, alphanums
3 |
4 | class Gate:
5 | def __init__(self):
6 | self.is_open = False
7 |
8 | def __str__(self):
9 | return 'open' if self.is_open else 'closed'
10 |
11 | def open(self):
12 | print('opening the gate')
13 | self.is_open = True
14 |
15 | def close(self):
16 | print('closing the gate')
17 | self.is_open = False
18 |
19 | class Garage:
20 | def __init__(self):
21 | self.is_open = False
22 |
23 | def __str__(self):
24 | return 'open' if self.is_open else 'closed'
25 |
26 | def open(self):
27 | print('opening the garage')
28 | self.is_open = True
29 |
30 | def close(self):
31 | print('closing the garage')
32 | self.is_open = False
33 |
34 | class Aircondition:
35 | def __init__(self):
36 | self.is_on = False
37 |
38 | def __str__(self):
39 | return 'on' if self.is_on else 'off'
40 |
41 | def turn_on(self):
42 | print('turning on the air condition')
43 | self.is_on = True
44 |
45 | def turn_off(self):
46 | print('turning off the air condition')
47 | self.is_on = False
48 |
49 | class Heating:
50 | def __init__(self):
51 | self.is_on = False
52 |
53 | def __str__(self):
54 | return 'on' if self.is_on else 'off'
55 |
56 | def turn_on(self):
57 | print('turning on the heating')
58 | self.is_on = True
59 |
60 | def turn_off(self):
61 | print('turning off the heating')
62 | self.is_on = False
63 |
64 | class Boiler:
65 | def __init__(self):
66 | self.temperature = 83# in celsius
67 |
68 | def __str__(self):
69 | return f'boiler temperature: {self.temperature}'
70 |
71 | def increase_temperature(self, amount):
72 | print(f"increasing the boiler's temperature by {amount} degrees")
73 | self.temperature += amount
74 |
75 | def decrease_temperature(self, amount):
76 | print(f"decreasing the boiler's temperature by {amount} degrees")
77 | self.temperature -= amount
78 |
79 | class Fridge:
80 | def __init__(self):
81 | self.temperature = 2 # in celsius
82 |
83 | def __str__(self):
84 | return f'fridge temperature: {self.temperature}'
85 |
86 | def increase_temperature(self, amount):
87 | print(f"increasing the fridge's temperature by {amount} degrees")
88 | self.temperature += amount
89 |
90 | def decrease_temperature(self, amount):
91 | print(f"decreasing the fridge's temperature by {amount} degrees")
92 | self.temperature -= amount
93 |
94 | def main():
95 | word = Word(alphanums)
96 | command = Group(OneOrMore(word))
97 | token = Suppress("->")
98 | device = Group(OneOrMore(word))
99 | argument = Group(OneOrMore(word))
100 | event = command + token + device + Optional(token + argument)
101 |
102 | gate = Gate()
103 | garage = Garage()
104 | airco = Aircondition()
105 | heating = Heating()
106 | boiler = Boiler()
107 | fridge = Fridge()
108 |
109 | tests = ('open -> gate',
110 | 'close -> garage',
111 | 'turn on -> air condition',
112 | 'turn off -> heating',
113 | 'increase -> boiler temperature -> 5 degrees',
114 | 'decrease -> fridge temperature -> 2 degrees')
115 |
116 | open_actions = {'gate':gate.open,
117 | 'garage':garage.open,
118 | 'air condition':airco.turn_on,
119 | 'heating':heating.turn_on,
120 | 'boiler temperature':boiler.increase_temperature,
121 | 'fridge temperature':fridge.increase_temperature}
122 | close_actions = {'gate':gate.close,
123 | 'garage':garage.close,
124 | 'air condition':airco.turn_off,
125 | 'heating':heating.turn_off,
126 | 'boiler temperature':boiler.decrease_temperature,
127 | 'fridge temperature':fridge.decrease_temperature}
128 |
129 | for t in tests:
130 | if len(event.parseString(t)) == 2: # no argument
131 | cmd, dev = event.parseString(t)
132 | cmd_str, dev_str = ' '.join(cmd), ' '.join(dev)
133 | if 'open' in cmd_str or 'turn on' in cmd_str:
134 | open_actions[dev_str]()
135 | elif 'close' in cmd_str or 'turn off' in cmd_str:
136 | close_actions[dev_str]()
137 | elif len(event.parseString(t)) == 3: # argument
138 | cmd, dev, arg = event.parseString(t)
139 | cmd_str = ' '.join(cmd)
140 | dev_str = ' '.join(dev)
141 | arg_str = ' '.join(cmd)
142 | num_arg = 0
143 | try:
144 | # extract the numeric part
145 | num_arg = int(arg_str.split()[0])
146 | except ValueError as err:
147 | print(f"expected number but got: '{arg_str[0]}'")
148 | if 'increase' in cmd_str and num_arg > 0:
149 | open_actions[dev_str](num_arg)
150 | elif 'decrease' in cmd_str and num_arg > 0:
151 | close_actions[dev_str](num_arg)
152 |
153 | if __name__ == '__main__':
154 | main()
155 |
156 |
--------------------------------------------------------------------------------
/chapter13/iterator.py:
--------------------------------------------------------------------------------
1 | class FootballTeamIterator:
2 |
3 | def __init__(self, members):
4 | # the list of players and coaches
5 | self.members = members
6 | self.index = 0
7 |
8 | def __iter__(self):
9 | return self
10 |
11 | def __next__(self):
12 | if self.index < len(self.members):
13 | val = self.members[self.index]
14 | self.index += 1
15 | return val
16 | else:
17 | raise StopIteration()
18 |
19 |
20 | class FootballTeam:
21 |
22 | def __init__(self, members):
23 | self.members = members
24 |
25 | def __iter__(self):
26 | return FootballTeamIterator(self.members)
27 |
28 |
29 | def main():
30 | members = [f'player{str(x)}' for x in range(1, 23)]
31 | members = members + ['coach1', 'coach2', 'coach3']
32 | team = FootballTeam(members)
33 | team_it = iter(team)
34 |
35 | while True:
36 | print(next(team_it))
37 |
38 |
39 | if __name__ == '__main__':
40 | main()
--------------------------------------------------------------------------------
/chapter13/memento.py:
--------------------------------------------------------------------------------
1 | import pickle
2 |
3 | class Quote:
4 |
5 | def __init__(self, text, author):
6 | self.text = text
7 | self.author = author
8 |
9 | def save_state(self):
10 | current_state = pickle.dumps(self.__dict__)
11 |
12 | return current_state
13 |
14 | def restore_state(self, memento):
15 | previous_state = pickle.loads(memento)
16 |
17 | self.__dict__.clear()
18 | self.__dict__.update(previous_state)
19 |
20 | def __str__(self):
21 | return f'{self.text} - By {self.author}.'
22 |
23 | def main():
24 | print('Quote 1')
25 | q1 = Quote("A room without books is like a body without a soul.",
26 | 'Unknown author')
27 | print(f'\nOriginal version:\n{q1}')
28 | q1_mem = q1.save_state()
29 |
30 | # Now, we found the author's name
31 | q1.author = 'Marcus Tullius Cicero'
32 | print(f'\nWe found the author, and did an updated:\n{q1}')
33 |
34 | # Restoring previous state (Undo)
35 | q1.restore_state(q1_mem)
36 | print(f'\nWe had to restore the previous version:\n{q1}')
37 |
38 | print()
39 | print('Quote 2')
40 | q2 = Quote("To be you in a world that is constantly trying to make you be something else is the greatest accomplishment.",
41 | 'Ralph Waldo Emerson')
42 | print(f'\nOriginal version:\n{q2}')
43 | q2_mem1 = q2.save_state()
44 |
45 | # changes to the text
46 | q2.text = "To be yourself in a world that is constantly trying to make you something else is the greatest accomplishment."
47 | print(f'\nWe fixed the text:\n{q2}')
48 | q2_mem2 = q2.save_state()
49 |
50 | q2.text = "To be yourself when the world is constantly trying to make you something else is the greatest accomplishment."
51 | print(f'\nWe fixed the text again:\n{q2}')
52 |
53 | # Restoring previous state (Undo)
54 | q2.restore_state(q2_mem2)
55 | print(f'\nWe had to restore the 2nd version, the correct one:\n{q2}')
56 |
57 | if __name__ == "__main__":
58 | main()
59 |
60 |
--------------------------------------------------------------------------------
/chapter13/strategy.py:
--------------------------------------------------------------------------------
1 | import time
2 |
3 | def pairs(seq):
4 | n = len(seq)
5 | for i in range(n):
6 | yield seq[i], seq[(i + 1) % n]
7 |
8 | SLOW = 3 # in seconds
9 | LIMIT = 5 # in characters
10 | WARNING = 'too bad, you picked the slow algorithm :('
11 |
12 | def allUniqueSort(s):
13 | if len(s) > LIMIT:
14 | print(WARNING)
15 | time.sleep(SLOW)
16 | srtStr = sorted(s)
17 | for (c1, c2) in pairs(srtStr):
18 | if c1 == c2:
19 | return False
20 | return True
21 |
22 | def allUniqueSet(s):
23 | if len(s) < LIMIT:
24 | print(WARNING)
25 | time.sleep(SLOW)
26 |
27 | return True if len(set(s)) == len(s) else False
28 |
29 | def allUnique(word, strategy):
30 | return strategy(word)
31 |
32 | def main():
33 |
34 | WORD_IN_DESC = 'Insert word (type quit to exit)> '
35 | STRAT_IN_DESC = 'Choose strategy: [1] Use a set, [2] Sort and pair> '
36 |
37 | while True:
38 | word = None
39 | while not word:
40 | word = input(WORD_IN_DESC)
41 |
42 | if word == 'quit':
43 | print('bye')
44 | return
45 |
46 | strategy_picked = None
47 | strategies = { '1': allUniqueSet, '2': allUniqueSort }
48 | while strategy_picked not in strategies.keys():
49 | strategy_picked = input(STRAT_IN_DESC)
50 |
51 | try:
52 | strategy = strategies[strategy_picked]
53 | result = allUnique(word, strategy)
54 | print(f'allUnique({word}): {result}')
55 | except KeyError as err:
56 | print(f'Incorrect option: {strategy_picked}')
57 |
58 | if __name__ == "__main__":
59 | main()
60 |
--------------------------------------------------------------------------------
/chapter13/template.py:
--------------------------------------------------------------------------------
1 |
2 | from cowpy import cow
3 |
4 | def generate_banner(msg, style):
5 | print('-- start of banner --')
6 | print(style(msg))
7 | print('-- end of banner --nn')
8 |
9 | def dots_style(msg):
10 | msg = msg.capitalize()
11 | msg = '.' * 10 + msg + '.' * 10
12 | return msg
13 |
14 | def admire_style(msg):
15 | msg = msg.upper()
16 | return '!'.join(msg)
17 |
18 | def cow_style(msg):
19 | msg = cow.milk_random_cow(msg)
20 | return msg
21 |
22 | def main():
23 | styles = (dots_style, admire_style, cow_style)
24 | msg = 'happy coding'
25 | [generate_banner(msg, style) for style in styles]
26 |
27 | if __name__ == "__main__":
28 | main()
29 |
--------------------------------------------------------------------------------
/chapter14/peoplelist.py:
--------------------------------------------------------------------------------
1 |
2 | from faker import Faker
3 | fake = Faker()
4 |
5 |
6 | def populate():
7 |
8 | persons = []
9 | for _ in range(0, 20):
10 | p = {'firstname': fake.first_name(), 'lastname': fake.last_name()}
11 | persons.append(p)
12 |
13 | return iter(persons)
14 |
15 | if __name__ == '__main__':
16 | new_persons = populate()
17 |
18 | new_data = [f"{p['firstname']} {p['lastname']}" for p in new_persons]
19 | new_data = ", ".join(new_data) + ", "
20 |
21 | with open('people.txt', 'a') as f:
22 | f.write(new_data)
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/chapter14/rx_example1.py:
--------------------------------------------------------------------------------
1 | from rx import Observable, Observer
2 |
3 | def get_quotes():
4 | import contextlib, io
5 | zen = io.StringIO()
6 | with contextlib.redirect_stdout(zen):
7 | import this
8 |
9 | quotes = zen.getvalue().split('\n')[1:]
10 | return quotes
11 |
12 | def push_quotes(obs):
13 |
14 | quotes = get_quotes()
15 | for q in quotes:
16 | if q: # skip empty
17 | obs.on_next(q)
18 | obs.on_completed()
19 |
20 | class ZenQuotesObserver(Observer):
21 |
22 | def on_next(self, value):
23 | print(f"Received: {value}")
24 |
25 | def on_completed(self):
26 | print("Done!")
27 |
28 | def on_error(self, error):
29 | print(f"Error Occurred: {error}")
30 |
31 | source = Observable.create(push_quotes)
32 |
33 | source.subscribe(ZenQuotesObserver())
34 |
--------------------------------------------------------------------------------
/chapter14/rx_example2.py:
--------------------------------------------------------------------------------
1 | from rx import Observable, Observer
2 |
3 | def get_quotes():
4 | import contextlib, io
5 | zen = io.StringIO()
6 | with contextlib.redirect_stdout(zen):
7 | import this
8 |
9 | quotes = zen.getvalue().split('\n')[1:]
10 | return enumerate(quotes)
11 |
12 | zen_quotes = get_quotes()
13 |
14 | Observable.from_(zen_quotes) \
15 | .filter(lambda q: len(q[1]) > 0) \
16 | .subscribe(lambda value: print(f"Received: {value[0]} - {value[1]}"))
17 |
18 |
--------------------------------------------------------------------------------
/chapter14/rx_example3.py:
--------------------------------------------------------------------------------
1 | from rx import Observable, Observer
2 |
3 | def get_quotes():
4 | import contextlib, io
5 | zen = io.StringIO()
6 | with contextlib.redirect_stdout(zen):
7 | import this
8 |
9 | quotes = zen.getvalue().split('\n')[1:]
10 | return enumerate(quotes)
11 |
12 | zen_quotes = get_quotes()
13 |
14 | Observable.interval(5000) \
15 | .flat_map(lambda seq: Observable.from_(zen_quotes)) \
16 | .flat_map(lambda q: Observable.from_(q[1].split())) \
17 | .filter(lambda s: len(s) > 2) \
18 | .map(lambda s: s.replace('.', '').replace(',', '').replace('!', '').replace('-', '')) \
19 | .map(lambda s: s.lower()) \
20 | .subscribe(lambda value: print(f"Received: {value}"))
21 |
22 | input("Starting... Press any key to quit\n")
23 |
24 |
--------------------------------------------------------------------------------
/chapter14/rx_peoplelist_1.py:
--------------------------------------------------------------------------------
1 | from rx import Observable
2 |
3 | def firstnames_from_db(file_name):
4 | file = open(file_name)
5 |
6 | # collect and push stored people firstnames
7 | return Observable.from_(file) \
8 | .flat_map(lambda content: content.split(', ')) \
9 | .filter(lambda name: name!='') \
10 | .map(lambda name: name.split()[0]) \
11 | .group_by(lambda firstname: firstname) \
12 | .flat_map(lambda grp: grp.count().map(lambda ct: (grp.key, ct)))
13 |
14 | db_file = "people.txt"
15 |
16 | # Emit data every 5 seconds
17 | Observable.interval(5000) \
18 | .flat_map(lambda i: firstnames_from_db(db_file)) \
19 | .subscribe(lambda value: print(str(value)))
20 |
21 | # Keep alive until user presses any key
22 | input("Starting... Press any key to quit\n")
23 |
--------------------------------------------------------------------------------
/chapter14/rx_peoplelist_2.py:
--------------------------------------------------------------------------------
1 | from rx import Observable
2 |
3 |
4 | def frequent_firstnames_from_db(file_name):
5 | file = open(file_name)
6 |
7 | # collect and push only the frequent firstnames
8 | return Observable.from_(file) \
9 | .flat_map(lambda content: content.split(', ')) \
10 | .filter(lambda name: name!='') \
11 | .map(lambda name: name.split()[0]) \
12 | .group_by(lambda firstname: firstname) \
13 | .flat_map(lambda grp: grp.count().map(lambda ct: (grp.key, ct))) \
14 | .filter(lambda name_and_ct: name_and_ct[1] > 3)
15 |
16 | db_file = "people.txt"
17 |
18 | # Emit data every 5 seconds
19 | Observable.interval(5000) \
20 | .flat_map(lambda i: frequent_firstnames_from_db(db_file)) \
21 | .subscribe(lambda value: print(str(value)))
22 |
23 | # Keep alive until user presses any key
24 | input("Starting... Press any key to quit\n")
25 |
--------------------------------------------------------------------------------
/chapter14/rx_peoplelist_3.py:
--------------------------------------------------------------------------------
1 | from rx import Observable
2 |
3 |
4 | def frequent_firstnames_from_db(file_name):
5 | file = open(file_name)
6 |
7 | # collect and push only the frequent firstnames
8 | return Observable.from_(file) \
9 | .flat_map(lambda content: content.split(', ')) \
10 | .filter(lambda name: name!='') \
11 | .map(lambda name: name.split()[0]) \
12 | .group_by(lambda firstname: firstname) \
13 | .flat_map(lambda grp: grp.count().map(lambda ct: (grp.key, ct))) \
14 | .filter(lambda name_and_ct: name_and_ct[1] > 3)
15 |
16 | db_file = "people.txt"
17 |
18 | # Emit data every 5 seconds, but only if it changed
19 | Observable.interval(5000) \
20 | .flat_map(lambda i: frequent_firstnames_from_db(db_file)) \
21 | .distinct() \
22 | .subscribe(lambda value: print(str(value)))
23 |
24 | # Keep alive until user presses any key
25 | input("Starting... Press any key to quit\n")
26 |
--------------------------------------------------------------------------------
/chapter15/cache_aside/cache_aside.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import sqlite3
3 | import csv
4 |
5 | cache_key_prefix = "quote"
6 |
7 |
8 | class QuoteCache:
9 |
10 | def __init__(self, filename=""):
11 | self.filename = filename
12 |
13 | def get(self, key):
14 | with open(self.filename) as csv_file:
15 | items = csv.reader(csv_file, delimiter=';')
16 | for item in items:
17 | if item[0] == key.split('.')[1]:
18 | return item[1]
19 |
20 | def set(self, key, quote):
21 | existing = []
22 | with open(self.filename) as csv_file:
23 | items = csv.reader(csv_file, delimiter=';')
24 | existing = [cache_key_prefix + "." + item[0] for item in items]
25 |
26 | if key in existing:
27 | print("This is weird. The key already exists.")
28 | else:
29 | # save the new data
30 | with open(self.filename, "a", newline="") as csv_file:
31 | writer = csv.DictWriter(csv_file,
32 | fieldnames=['id', 'text'],
33 | delimiter=";")
34 | #print(f"Adding '{q[1]}' to cache")
35 | writer.writerow({'id': key.split('.')[1], 'text': quote})
36 |
37 |
38 | cache = QuoteCache('data/quotes_cache.csv')
39 |
40 | def get_quote(quote_id):
41 |
42 | # Return the item from cache if found in it. If not found in cache, read from data store.
43 | # Put the read item in cache and return it.
44 |
45 | quote = cache.get(f"quote.{quote_id}")
46 | out = ""
47 |
48 | if quote is None:
49 | try:
50 | db = sqlite3.connect('data/quotes.sqlite3')
51 | cursor = db.cursor()
52 | cursor.execute(f"SELECT text FROM quotes WHERE id = {quote_id}")
53 | for row in cursor:
54 | quote = row[0]
55 | print(f"Got '{quote}' FROM DB")
56 | except Exception as e:
57 | print(e)
58 | finally:
59 | # Close the db connection
60 | db.close()
61 |
62 | # and add it to the cache
63 | key = f"{cache_key_prefix}.{quote_id}"
64 | cache.set(key, quote)
65 |
66 | if quote:
67 | out = f"{quote} (FROM CACHE, with key 'quote.{quote_id}')"
68 |
69 | return out
70 |
71 |
72 | if __name__ == "__main__":
73 | args = sys.argv
74 |
75 | if args[1] == 'fetch':
76 | while True:
77 | quote_id = input('Enter the ID of the quote: ')
78 | q = get_quote(quote_id)
79 | if q:
80 | print(q)
81 |
--------------------------------------------------------------------------------
/chapter15/cache_aside/data/quotes.sqlite3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Mastering-Python-Design-Patterns-Second-Edition/639c6712e0c5cf1cfd9576026d722b46e2bed09e/chapter15/cache_aside/data/quotes.sqlite3
--------------------------------------------------------------------------------
/chapter15/cache_aside/data/quotes_cache.csv:
--------------------------------------------------------------------------------
1 | 76;Family card magazine should manage so.
2 | 48;Eight realize third commercial feeling soldier fund.
3 | 83;Establish assume decide myself second increase bar.
4 | 62;Society practice Mrs music admit likely.
5 | 87;Management girl technology summer.
6 | 99;Assume realize fly six.
7 | 82;Account me play figure chance.
8 | 42;Congress cause suffer join either foot.
9 | 5;As for continue collection.
10 | 21;Land talk similar card service.
11 | 100;Try size on change upon.
12 | 31;Born nearly cultural tax drop probably later.
13 |
--------------------------------------------------------------------------------
/chapter15/cache_aside/populate_db.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import sqlite3
3 | import csv
4 | from random import randint
5 |
6 | from faker import Faker
7 | fake = Faker()
8 |
9 |
10 | def setup_db():
11 |
12 | try:
13 | db = sqlite3.connect('data/quotes.sqlite3')
14 |
15 | # Get a cursor object
16 | cursor = db.cursor()
17 | cursor.execute('''
18 | CREATE TABLE quotes(id INTEGER PRIMARY KEY, text TEXT)
19 | ''')
20 |
21 | db.commit()
22 | except Exception as e:
23 | print(e)
24 | finally:
25 | db.close()
26 |
27 |
28 | def add_quotes(quotes_list):
29 | quotes = []
30 | try:
31 | db = sqlite3.connect('data/quotes.sqlite3')
32 |
33 | cursor = db.cursor()
34 |
35 | quotes = []
36 | for quote_text in quotes_list:
37 | quote_id = randint(1, 100)
38 | quote = (quote_id, quote_text)
39 |
40 | try:
41 | cursor.execute('''INSERT INTO quotes(id, text) VALUES(?, ?)''', quote)
42 | quotes.append(quote)
43 | except Exception as e:
44 | print(f"Error with quote id {quote_id}: {e}")
45 |
46 | db.commit()
47 | except Exception as e:
48 | print(e)
49 | finally:
50 | db.close()
51 |
52 | return quotes
53 |
54 |
55 | def main():
56 | args = sys.argv
57 |
58 | if args[1] == 'init':
59 | setup_db()
60 |
61 | elif args[1] == 'update_db_and_cache':
62 | quotes_list = [fake.sentence() for _ in range(1, 11)]
63 | quotes = add_quotes(quotes_list)
64 | print("New (fake) quotes added to the database:")
65 | for q in quotes:
66 | print(f"Added to DB: {q}")
67 |
68 | # Populate the cache with this content
69 | with open('data/quotes_cache.csv', "a", newline="") as csv_file:
70 | writer = csv.DictWriter(csv_file,
71 | fieldnames=['id', 'text'],
72 | delimiter=";")
73 | for q in quotes:
74 | print(f"Adding '{q[1]}' to cache")
75 | writer.writerow({'id': str(q[0]), 'text': q[1]})
76 |
77 | elif args[1] == 'update_db_only':
78 | quotes_list = [fake.sentence() for _ in range(1, 11)]
79 | quotes = add_quotes(quotes_list)
80 | print("New (fake) quotes added to the database ONLY:")
81 | for q in quotes:
82 | print(f"Added to DB: {q}")
83 |
84 |
85 | if __name__ == "__main__":
86 | main()
87 |
--------------------------------------------------------------------------------
/chapter15/circuit_breaker.py:
--------------------------------------------------------------------------------
1 |
2 | # our version of a script provided in https://github.com/veltra/pybreaker-playground
3 |
4 | import pybreaker
5 | from datetime import datetime
6 | import random
7 | from time import sleep
8 |
9 |
10 | breaker = pybreaker.CircuitBreaker(fail_max=2, reset_timeout=5)
11 |
12 | @breaker
13 | def fragile_function():
14 | if not random.choice([True, False]):
15 | print(' / OK', end='')
16 | else:
17 | print(' / FAIL', end='')
18 | raise Exception('This is a sample Exception')
19 |
20 |
21 | if __name__ == "__main__":
22 |
23 | while True:
24 | print(datetime.now().strftime('%Y-%m-%d %H:%M:%S'), end='')
25 |
26 | try:
27 | fragile_function()
28 | except Exception as e:
29 | print(' / {} {}'.format(type(e), e), end='')
30 | finally:
31 | print('')
32 | sleep(1)
33 |
--------------------------------------------------------------------------------
/chapter15/circuit_breaker_in_flaskapp.py:
--------------------------------------------------------------------------------
1 | import pybreaker
2 | import random
3 |
4 | from flask import Flask
5 | app = Flask(__name__)
6 |
7 | breaker = pybreaker.CircuitBreaker(fail_max=2, reset_timeout=5)
8 |
9 | @breaker
10 | def fragile_function():
11 | if not random.choice([True, False]):
12 | print(' / OK', end='')
13 | else:
14 | print(' / FAIL', end='')
15 | raise Exception('This is a sample Exception')
16 |
17 | @app.route("/")
18 | def hello():
19 |
20 | fragile_function()
21 | return "Hello World"
22 |
23 | if __name__ == "__main__":
24 | app.run(debug=True)
25 |
--------------------------------------------------------------------------------
/chapter15/retry_write_file.py:
--------------------------------------------------------------------------------
1 | import time
2 | import sys
3 | import os
4 |
5 |
6 | def create_file(filename, after_delay=5):
7 | time.sleep(after_delay)
8 |
9 | with open(filename, 'w') as f:
10 | f.write('A file creation test')
11 |
12 | def append_data_to_file(filename):
13 |
14 | if os.path.exists(filename):
15 | with open(filename, 'a') as f:
16 | f.write(' ...Updating the file')
17 | else:
18 | raise OSError
19 |
20 |
21 | FILENAME = 'file1.txt'
22 |
23 | if __name__ == "__main__":
24 | args = sys.argv
25 |
26 | if args[1] == 'create':
27 | create_file(FILENAME)
28 | print(f"Created file '{FILENAME}'")
29 | elif args[1] == 'update':
30 | while True:
31 | try:
32 | append_data_to_file(FILENAME)
33 | print("Success! We are done!")
34 | break
35 | except OSError as e:
36 | print("Error... Try again")
37 |
--------------------------------------------------------------------------------
/chapter15/retry_write_file_retrying_module.py:
--------------------------------------------------------------------------------
1 | import time
2 | import sys
3 | import os
4 | from retrying import retry
5 |
6 |
7 | def create_file(filename, after_delay=5):
8 | time.sleep(after_delay)
9 |
10 | with open(filename, 'w') as f:
11 | f.write('A file creation test')
12 |
13 | @retry
14 | def append_data_to_file(filename):
15 |
16 | if os.path.exists(filename):
17 | print("got the file... let's proceed!")
18 | with open(filename, 'a') as f:
19 | f.write(' ...Updating the file')
20 | return "OK"
21 | else:
22 | print("Error: Missing file, so we can't proceed. Retrying...")
23 | raise OSError
24 |
25 | FILENAME = 'file2.txt'
26 |
27 | if __name__ == "__main__":
28 | args = sys.argv
29 |
30 | if args[1] == 'create':
31 | create_file(FILENAME)
32 | print(f"Created file '{FILENAME}'")
33 | elif args[1] == 'update':
34 | while True:
35 | out = append_data_to_file(FILENAME)
36 | if out == "OK":
37 | print("Success! We are done!")
38 | break
39 |
--------------------------------------------------------------------------------
/chapter15/retry_write_file_tenacity_module.py:
--------------------------------------------------------------------------------
1 | import time
2 | import sys
3 | import os
4 | import tenacity
5 |
6 |
7 | def create_file(filename, after_delay=5):
8 | time.sleep(after_delay)
9 |
10 | with open(filename, 'w') as f:
11 | f.write('A file creation test')
12 |
13 | #@tenacity.retry(wait=tenacity.wait_fixed(2))
14 | @tenacity.retry(wait=tenacity.wait_exponential())
15 | def append_data_to_file(filename):
16 |
17 | if os.path.exists(filename):
18 | print("got the file... let's proceed!")
19 | with open(filename, 'a') as f:
20 | f.write(' ...Updating the file')
21 | return "OK"
22 | else:
23 | print("Error: Missing file, so we can't proceed. Retrying...")
24 | raise OSError
25 |
26 | FILENAME = 'file2.txt'
27 |
28 | if __name__ == "__main__":
29 | args = sys.argv
30 |
31 | if args[1] == 'create':
32 | create_file(FILENAME)
33 | print(f"Created file '{FILENAME}'")
34 | elif args[1] == 'update':
35 | while True:
36 | out = append_data_to_file(FILENAME)
37 | if out == "OK":
38 | print("Success! We are done!")
39 | break
40 |
--------------------------------------------------------------------------------
/chapter15/service_first.py:
--------------------------------------------------------------------------------
1 |
2 | from nameko.rpc import rpc
3 | from faker import Faker
4 |
5 | fake = Faker()
6 |
7 |
8 | class PeopleListService:
9 |
10 | name = 'peoplelist'
11 |
12 | @rpc
13 | def populate(self, number=20):
14 |
15 | names = []
16 | for _ in range(0, number):
17 | n = fake.name()
18 | names.append(n)
19 |
20 | return names
--------------------------------------------------------------------------------
/chapter15/service_second.py:
--------------------------------------------------------------------------------
1 |
2 | from nameko.rpc import rpc, RpcProxy
3 | from faker import Faker
4 | import csv
5 |
6 | fake = Faker()
7 |
8 | class PeopleListService:
9 |
10 | name = 'peoplelist'
11 |
12 | @rpc
13 | def populate(self, number=20):
14 |
15 | persons = []
16 | for _ in range(0, number):
17 | p = {'firstname': fake.first_name(),
18 | 'lastname': fake.last_name(),
19 | 'address': fake.address()}
20 | persons.append(p)
21 |
22 | return persons
23 |
24 |
25 | class PeopleDataPersistenceService:
26 |
27 | name = 'people_data_persistence'
28 | peoplelist_rpc = RpcProxy('peoplelist')
29 |
30 | @rpc
31 | def save(self, filename):
32 | persons = self.peoplelist_rpc.populate(number=25)
33 |
34 | with open(filename, "a", newline="") as csv_file:
35 | fieldnames = ["firstname", "lastname", "address"]
36 | writer = csv.DictWriter(csv_file,
37 | fieldnames=fieldnames,
38 | delimiter=";")
39 |
40 | for p in persons:
41 | writer.writerow(p)
42 |
43 | return f"Saved data for {len(persons)} new people"
--------------------------------------------------------------------------------
/chapter15/test_service_first.py:
--------------------------------------------------------------------------------
1 |
2 | from nameko.testing.services import worker_factory
3 | from service_first import PeopleListService
4 |
5 | def test_people():
6 | service_worker = worker_factory(PeopleListService)
7 | result = service_worker.populate()
8 | for name in result:
9 | print(name)
10 |
11 | if __name__ == "__main__":
12 | test_people()
--------------------------------------------------------------------------------
/chapter15/test_service_second.py:
--------------------------------------------------------------------------------
1 |
2 | from nameko.testing.services import worker_factory
3 | from nameko.standalone.rpc import ClusterRpcProxy
4 | from service_second import PeopleDataPersistenceService
5 |
6 | config = {
7 | 'AMQP_URI': "pyamqp://guest:guest@127.0.0.1"
8 | }
9 |
10 | def test_peopledata_persist():
11 | with ClusterRpcProxy(config) as cluster_rpc:
12 | out = cluster_rpc.people_data_persistence.save.call_async('people.csv')
13 | print(out.result())
14 |
15 | if __name__ == "__main__":
16 | test_peopledata_persist()
--------------------------------------------------------------------------------
/chapter15/throttling_in_flaskapp.py:
--------------------------------------------------------------------------------
1 | from flask import Flask
2 | from flask_limiter import Limiter
3 | from flask_limiter.util import get_remote_address
4 |
5 | from flask import Flask
6 | app = Flask(__name__)
7 | limiter = Limiter(
8 | app,
9 | key_func=get_remote_address,
10 | default_limits=["100 per day", "10 per hour"]
11 | )
12 |
13 | @app.route("/limited")
14 | def limited_api():
15 | return "Welcome to our API!"
16 |
17 | @app.route("/more_limited")
18 | @limiter.limit("2/minute")
19 | def more_limited_api():
20 | return "Welcome to our expensive, thus very limited, API!"
21 |
22 |
23 | if __name__ == "__main__":
24 | app.run(debug=True)
25 |
--------------------------------------------------------------------------------