├── .gitignore ├── .vscode ├── .ropeproject │ ├── config.py │ └── objectdb └── settings.json ├── 1 - coupling and cohesion ├── coupling-cohesion-after.py └── coupling-cohesion-before.py ├── 10 - object creation ├── object-pool-context.py ├── object-pool.py └── singleton.py ├── 2 - dependency inversion ├── dependency-inversion-after.py └── dependency-inversion-before.py ├── 3 - strategy pattern ├── strategy-after-fn.py ├── strategy-after.py └── strategy-before.py ├── 4 - observer pattern ├── .DS_Store ├── api │ ├── __init__.py │ ├── plan.py │ └── user.py ├── api_v2 │ ├── __init__.py │ ├── email_listener.py │ ├── event.py │ ├── log_listener.py │ ├── plan.py │ ├── slack_listener.py │ └── user.py ├── lib │ ├── __init__.py │ ├── db.py │ ├── email.py │ ├── log.py │ ├── slack.py │ └── stringtools.py ├── observer-after.py └── observer-before.py ├── 5 - unit testing ├── .DS_Store ├── vehicle_info_after.py ├── vehicle_info_before.py └── vehicle_info_test.py ├── 6 - template method & bridge ├── trading-after.py ├── trading-before.py └── with-bridge.py ├── 7 - dealing with errors ├── advanced │ ├── logging-decorator.py │ └── retry-decorator.py ├── after-context │ ├── app.py │ ├── application.db │ ├── create-db.py │ ├── db.py │ ├── error-handling-context.py │ └── error-handling.py ├── after │ ├── app.py │ ├── application.db │ ├── create-db.py │ ├── db.py │ └── error-handling.py ├── before │ ├── app.py │ ├── application.db │ ├── create-db.py │ ├── db.py │ └── error-handling.py └── monadic-error-handling │ ├── application.db │ └── example.py ├── 8 - mvc ├── mvc-after-strategy.py ├── mvc-after.py └── mvc-before.py ├── 9 - solid ├── dependency-inversion-after.py ├── dependency-inversion-before.py ├── interface-segregation-after-comp.py ├── interface-segregation-after.py ├── interface-segregation-before.py ├── liskov-substitution-after.py ├── liskov-substitution-before.py ├── open-closed-after.py ├── open-closed-before.py ├── single-responsibility-after.py └── single-responsibility-before.py ├── LICENSE ├── README.md ├── better-rust ├── Cargo.lock ├── Cargo.toml ├── coupling_and_cohesion │ ├── Cargo.toml │ └── src │ │ ├── coupling_cohesion_after.rs │ │ ├── coupling_cohesion_before.rs │ │ └── main.rs ├── macros │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── solid │ ├── Cargo.toml │ └── src │ │ ├── dependency_inversion_after.rs │ │ ├── dependency_inversion_before.rs │ │ ├── interface_segregation_after.rs │ │ ├── interface_segregation_before.rs │ │ ├── lib.rs │ │ ├── liskov_substitution_after.rs │ │ ├── liskov_substitution_before.rs │ │ ├── main.rs │ │ ├── open_closed_after.rs │ │ ├── open_closed_before.rs │ │ ├── single_responsibility_after.rs │ │ └── single_responsibility_before.rs └── src │ └── main.rs └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | # misc 2 | .DS_Store 3 | *.pyc 4 | /better-rust/target 5 | -------------------------------------------------------------------------------- /.vscode/.ropeproject/config.py: -------------------------------------------------------------------------------- 1 | # The default ``config.py`` 2 | # flake8: noqa 3 | 4 | 5 | def set_prefs(prefs): 6 | """This function is called before opening the project""" 7 | 8 | # Specify which files and folders to ignore in the project. 9 | # Changes to ignored resources are not added to the history and 10 | # VCSs. Also they are not returned in `Project.get_files()`. 11 | # Note that ``?`` and ``*`` match all characters but slashes. 12 | # '*.pyc': matches 'test.pyc' and 'pkg/test.pyc' 13 | # 'mod*.pyc': matches 'test/mod1.pyc' but not 'mod/1.pyc' 14 | # '.svn': matches 'pkg/.svn' and all of its children 15 | # 'build/*.o': matches 'build/lib.o' but not 'build/sub/lib.o' 16 | # 'build//*.o': matches 'build/lib.o' and 'build/sub/lib.o' 17 | prefs['ignored_resources'] = ['*.pyc', '*~', '.ropeproject', 18 | '.hg', '.svn', '_svn', '.git', '.tox'] 19 | 20 | # Specifies which files should be considered python files. It is 21 | # useful when you have scripts inside your project. Only files 22 | # ending with ``.py`` are considered to be python files by 23 | # default. 24 | # prefs['python_files'] = ['*.py'] 25 | 26 | # Custom source folders: By default rope searches the project 27 | # for finding source folders (folders that should be searched 28 | # for finding modules). You can add paths to that list. Note 29 | # that rope guesses project source folders correctly most of the 30 | # time; use this if you have any problems. 31 | # The folders should be relative to project root and use '/' for 32 | # separating folders regardless of the platform rope is running on. 33 | # 'src/my_source_folder' for instance. 34 | # prefs.add('source_folders', 'src') 35 | 36 | # You can extend python path for looking up modules 37 | # prefs.add('python_path', '~/python/') 38 | 39 | # Should rope save object information or not. 40 | prefs['save_objectdb'] = True 41 | prefs['compress_objectdb'] = False 42 | 43 | # If `True`, rope analyzes each module when it is being saved. 44 | prefs['automatic_soa'] = True 45 | # The depth of calls to follow in static object analysis 46 | prefs['soa_followed_calls'] = 0 47 | 48 | # If `False` when running modules or unit tests "dynamic object 49 | # analysis" is turned off. This makes them much faster. 50 | prefs['perform_doa'] = True 51 | 52 | # Rope can check the validity of its object DB when running. 53 | prefs['validate_objectdb'] = True 54 | 55 | # How many undos to hold? 56 | prefs['max_history_items'] = 32 57 | 58 | # Shows whether to save history across sessions. 59 | prefs['save_history'] = True 60 | prefs['compress_history'] = False 61 | 62 | # Set the number spaces used for indenting. According to 63 | # :PEP:`8`, it is best to use 4 spaces. Since most of rope's 64 | # unit-tests use 4 spaces it is more reliable, too. 65 | prefs['indent_size'] = 4 66 | 67 | # Builtin and c-extension modules that are allowed to be imported 68 | # and inspected by rope. 69 | prefs['extension_modules'] = [] 70 | 71 | # Add all standard c-extensions to extension_modules list. 72 | prefs['import_dynload_stdmods'] = True 73 | 74 | # If `True` modules with syntax errors are considered to be empty. 75 | # The default value is `False`; When `False` syntax errors raise 76 | # `rope.base.exceptions.ModuleSyntaxError` exception. 77 | prefs['ignore_syntax_errors'] = False 78 | 79 | # If `True`, rope ignores unresolvable imports. Otherwise, they 80 | # appear in the importing namespace. 81 | prefs['ignore_bad_imports'] = False 82 | 83 | # If `True`, rope will insert new module imports as 84 | # `from import ` by default. 85 | prefs['prefer_module_from_imports'] = False 86 | 87 | # If `True`, rope will transform a comma list of imports into 88 | # multiple separate import statements when organizing 89 | # imports. 90 | prefs['split_imports'] = False 91 | 92 | # If `True`, rope will remove all top-level import statements and 93 | # reinsert them at the top of the module when making changes. 94 | prefs['pull_imports_to_top'] = True 95 | 96 | # If `True`, rope will sort imports alphabetically by module name instead 97 | # of alphabetically by import statement, with from imports after normal 98 | # imports. 99 | prefs['sort_imports_alphabetically'] = False 100 | 101 | # Location of implementation of 102 | # rope.base.oi.type_hinting.interfaces.ITypeHintingFactory In general 103 | # case, you don't have to change this value, unless you're an rope expert. 104 | # Change this value to inject you own implementations of interfaces 105 | # listed in module rope.base.oi.type_hinting.providers.interfaces 106 | # For example, you can add you own providers for Django Models, or disable 107 | # the search type-hinting in a class hierarchy, etc. 108 | prefs['type_hinting_factory'] = ( 109 | 'rope.base.oi.type_hinting.factory.default_type_hinting_factory') 110 | 111 | 112 | def project_opened(project): 113 | """This function is called after opening the project""" 114 | # Do whatever you like here! 115 | -------------------------------------------------------------------------------- /.vscode/.ropeproject/objectdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArjanCodes/betterpython/11a95a40ee341d559e8709b8b6c676eca2dd978c/.vscode/.ropeproject/objectdb -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.testing.unittestArgs": [ 3 | "-v", 4 | "-s", 5 | "./5 - unit testing", 6 | "-p", 7 | "*_test.py" 8 | ], 9 | "python.testing.pytestEnabled": false, 10 | "python.testing.nosetestsEnabled": false, 11 | "python.testing.unittestEnabled": true, 12 | "cSpell.words": ["liskov"] 13 | } 14 | -------------------------------------------------------------------------------- /1 - coupling and cohesion/coupling-cohesion-after.py: -------------------------------------------------------------------------------- 1 | import string 2 | import random 3 | 4 | class VehicleInfo: 5 | 6 | def __init__(self, brand, electric, catalogue_price): 7 | self.brand = brand 8 | self.electric = electric 9 | self.catalogue_price = catalogue_price 10 | 11 | def compute_tax(self): 12 | tax_percentage = 0.05 13 | if self.electric: 14 | tax_percentage = 0.02 15 | return tax_percentage * self.catalogue_price 16 | 17 | def print(self): 18 | print(f"Brand: {self.brand}") 19 | print(f"Payable tax: {self.compute_tax()}") 20 | 21 | class Vehicle: 22 | 23 | def __init__(self, id, license_plate, info): 24 | self.id = id 25 | self.license_plate = license_plate 26 | self.info = info 27 | 28 | def print(self): 29 | print(f"Id: {self.id}") 30 | print(f"License plate: {self.license_plate}") 31 | self.info.print() 32 | 33 | 34 | class VehicleRegistry: 35 | 36 | def __init__(self): 37 | self.vehicle_info = { } 38 | self.add_vehicle_info("Tesla Model 3", True, 60000) 39 | self.add_vehicle_info("Volkswagen ID3", True, 35000) 40 | self.add_vehicle_info("BMW 5", False, 45000) 41 | self.add_vehicle_info("Tesla Model Y", True, 75000) 42 | 43 | def add_vehicle_info(self, brand, electric, catalogue_price): 44 | self.vehicle_info[brand] = VehicleInfo(brand, electric, catalogue_price) 45 | 46 | def generate_vehicle_id(self, length): 47 | return ''.join(random.choices(string.ascii_uppercase, k=length)) 48 | 49 | def generate_vehicle_license(self, id): 50 | return f"{id[:2]}-{''.join(random.choices(string.digits, k=2))}-{''.join(random.choices(string.ascii_uppercase, k=2))}" 51 | 52 | def create_vehicle(self, brand): 53 | id = self.generate_vehicle_id(12) 54 | license_plate = self.generate_vehicle_license(id) 55 | return Vehicle(id, license_plate, self.vehicle_info[brand]) 56 | 57 | 58 | class Application: 59 | 60 | def register_vehicle(self, brand: string): 61 | # create a registry instance 62 | registry = VehicleRegistry() 63 | 64 | vehicle = registry.create_vehicle(brand) 65 | 66 | # print out the vehicle information 67 | vehicle.print() 68 | 69 | app = Application() 70 | app.register_vehicle("Volkswagen ID3") 71 | -------------------------------------------------------------------------------- /1 - coupling and cohesion/coupling-cohesion-before.py: -------------------------------------------------------------------------------- 1 | import string 2 | import random 3 | 4 | class VehicleRegistry: 5 | 6 | def generate_vehicle_id(self, length): 7 | return ''.join(random.choices(string.ascii_uppercase, k=length)) 8 | 9 | def generate_vehicle_license(self, id): 10 | return f"{id[:2]}-{''.join(random.choices(string.digits, k=2))}-{''.join(random.choices(string.ascii_uppercase, k=2))}" 11 | 12 | 13 | class Application: 14 | 15 | def register_vehicle(self, brand: string): 16 | # create a registry instance 17 | registry = VehicleRegistry() 18 | 19 | # generate a vehicle id of length 12 20 | vehicle_id = registry.generate_vehicle_id(12) 21 | 22 | # now generate a license plate for the vehicle 23 | # using the first two characters of the vehicle id 24 | license_plate = registry.generate_vehicle_license(vehicle_id) 25 | 26 | # compute the catalogue price 27 | catalogue_price = 0 28 | if brand == "Tesla Model 3": 29 | catalogue_price = 60000 30 | elif brand == "Volkswagen ID3": 31 | catalogue_price = 35000 32 | elif brand == "BMW 5": 33 | catalogue_price = 45000 34 | 35 | # compute the tax percentage (default 5% of the catalogue price, except for electric cars where it is 2%) 36 | tax_percentage = 0.05 37 | if brand == "Tesla Model 3" or brand == "Volkswagen ID3": 38 | tax_percentage = 0.02 39 | 40 | # compute the payable tax 41 | payable_tax = tax_percentage * catalogue_price 42 | 43 | # print out the vehicle registration information 44 | print("Registration complete. Vehicle information:") 45 | print(f"Brand: {brand}") 46 | print(f"Id: {vehicle_id}") 47 | print(f"License plate: {license_plate}") 48 | print(f"Payable tax: {payable_tax}") 49 | 50 | app = Application() 51 | app.register_vehicle("Volkswagen ID3") 52 | -------------------------------------------------------------------------------- /10 - object creation/object-pool-context.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | class PoolManager(): 4 | def __init__(self, pool): 5 | self.pool = pool 6 | def __enter__(self): 7 | self.obj = self.pool.acquire() 8 | return self.obj 9 | def __exit__(self, type, value, traceback): 10 | self.pool.release(self.obj) 11 | 12 | class Reusable: 13 | def test(self): 14 | print(f"Using object {id(self)}") 15 | 16 | class ReusablePool: 17 | 18 | def __init__(self, size): 19 | self.size = size 20 | self.free: List[Reusable] = [] 21 | self.in_use: List[Reusable] = [] 22 | for _ in range(0, size): 23 | self.free.append(Reusable()) 24 | 25 | def acquire(self) -> Reusable: 26 | assert len(self.free) > 0 27 | r = self.free[0] 28 | self.free.remove(r) 29 | self.in_use.append(r) 30 | return r 31 | 32 | def release(self, r: Reusable): 33 | self.in_use.remove(r) 34 | self.free.append(r) 35 | 36 | # Create reusable pool 37 | pool= ReusablePool(2) 38 | 39 | with PoolManager(pool) as r: 40 | r.test() 41 | 42 | with PoolManager(pool) as r2: 43 | r2.test() -------------------------------------------------------------------------------- /10 - object creation/object-pool.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | class Reusable: 4 | def test(self): 5 | print(f"Using object {id(self)}") 6 | 7 | class ReusablePool: 8 | 9 | def __init__(self, size): 10 | self.size = size 11 | self.free: List[Reusable] = [] 12 | self.in_use: List[Reusable] = [] 13 | for _ in range(0, size): 14 | self.free.append(Reusable()) 15 | 16 | def acquire(self) -> Reusable: 17 | assert len(self.free) > 0 18 | r = self.free[0] 19 | self.free.remove(r) 20 | self.in_use.append(r) 21 | return r 22 | 23 | def release(self, r: Reusable): 24 | self.in_use.remove(r) 25 | self.free.append(r) 26 | 27 | pool = ReusablePool(2) 28 | r = pool.acquire() 29 | r2 = pool.acquire() 30 | 31 | r.test() 32 | r2.test() 33 | 34 | pool.release(r2) 35 | r3 = pool.acquire() 36 | r3.test() 37 | -------------------------------------------------------------------------------- /10 - object creation/singleton.py: -------------------------------------------------------------------------------- 1 | class Singleton(type): 2 | _instances = {} 3 | def __call__(cls, *args, **kwargs): 4 | if cls not in cls._instances: 5 | cls._instances[cls] = super().__call__(*args, **kwargs) 6 | return cls._instances[cls] 7 | 8 | class Logger(metaclass=Singleton): 9 | def __init__(self): 10 | print("Creating Logger") 11 | 12 | def log(self, msg): 13 | print(msg) 14 | 15 | class CustomLogger(Logger): 16 | def __init__(self): 17 | print("Creating Custom logger") 18 | super().__init__() 19 | 20 | logger = CustomLogger() 21 | logger2 = CustomLogger() 22 | print(logger) 23 | print(logger2) 24 | logger.log("Hello") 25 | logger2.log("Helloooo") -------------------------------------------------------------------------------- /2 - dependency inversion/dependency-inversion-after.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | 3 | 4 | class Switchable(ABC): 5 | @abstractmethod 6 | def turn_on(self): 7 | pass 8 | 9 | @abstractmethod 10 | def turn_off(self): 11 | pass 12 | 13 | 14 | class LightBulb(Switchable): 15 | def turn_on(self): 16 | print("LightBulb: turned on...") 17 | 18 | def turn_off(self): 19 | print("LightBulb: turned off...") 20 | 21 | 22 | class Fan(Switchable): 23 | def turn_on(self): 24 | print("Fan: turned on...") 25 | 26 | def turn_off(self): 27 | print("Fan: turned off...") 28 | 29 | 30 | class ElectricPowerSwitch: 31 | 32 | def __init__(self, c: Switchable): 33 | self.client = c 34 | self.on = False 35 | 36 | def press(self): 37 | if self.on: 38 | self.client.turn_off() 39 | self.on = False 40 | else: 41 | self.client.turn_on() 42 | self.on = True 43 | 44 | 45 | l = LightBulb() 46 | f = Fan() 47 | switch = ElectricPowerSwitch(f) 48 | switch.press() 49 | switch.press() 50 | -------------------------------------------------------------------------------- /2 - dependency inversion/dependency-inversion-before.py: -------------------------------------------------------------------------------- 1 | class LightBulb: 2 | def turn_on(self): 3 | print("LightBulb: turned on...") 4 | 5 | def turn_off(self): 6 | print("LightBulb: turned off...") 7 | 8 | 9 | class ElectricPowerSwitch: 10 | 11 | def __init__(self, l: LightBulb): 12 | self.lightBulb = l 13 | self.on = False 14 | 15 | def press(self): 16 | if self.on: 17 | self.lightBulb.turn_off() 18 | self.on = False 19 | else: 20 | self.lightBulb.turn_on() 21 | self.on = True 22 | 23 | 24 | l = LightBulb() 25 | switch = ElectricPowerSwitch(l) 26 | switch.press() 27 | switch.press() 28 | -------------------------------------------------------------------------------- /3 - strategy pattern/strategy-after-fn.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass, field 2 | import string 3 | import random 4 | from typing import List, Callable 5 | 6 | 7 | def generate_id(length: int = 8) -> str: 8 | # helper function for generating an id 9 | return ''.join(random.choices(string.ascii_uppercase, k=length)) 10 | 11 | 12 | @dataclass 13 | class SupportTicket: 14 | id: str = field(init=False, default_factory=generate_id) 15 | customer: str 16 | issue: str 17 | 18 | 19 | SupportTickets = List[SupportTicket] 20 | 21 | Ordering = Callable[[SupportTickets], SupportTickets] 22 | 23 | 24 | def fifo_ordering(list: SupportTickets) -> SupportTickets: 25 | return list.copy() 26 | 27 | 28 | def filo_ordering(list: SupportTickets) -> SupportTickets: 29 | list_copy = list.copy() 30 | list_copy.reverse() 31 | return list_copy 32 | 33 | 34 | def random_ordering(list: SupportTickets) -> SupportTickets: 35 | list_copy = list.copy() 36 | random.shuffle(list_copy) 37 | return list_copy 38 | 39 | 40 | def blackhole_ordering(_: SupportTickets) -> SupportTickets: 41 | return [] 42 | 43 | 44 | class CustomerSupport: 45 | 46 | def __init__(self): 47 | self.tickets: SupportTickets = [] 48 | 49 | def create_ticket(self, customer, issue): 50 | self.tickets.append(SupportTicket(customer, issue)) 51 | 52 | def process_tickets(self, ordering: Ordering): 53 | # create the ordered list 54 | ticket_list = ordering(self.tickets) 55 | 56 | # if it's empty, don't do anything 57 | if len(ticket_list) == 0: 58 | print("There are no tickets to process. Well done!") 59 | return 60 | 61 | # go through the tickets in the list 62 | for ticket in ticket_list: 63 | self.process_ticket(ticket) 64 | 65 | def process_ticket(self, ticket: SupportTicket): 66 | print("==================================") 67 | print(f"Processing ticket id: {ticket.id}") 68 | print(f"Customer: {ticket.customer}") 69 | print(f"Issue: {ticket.issue}") 70 | print("==================================") 71 | 72 | 73 | def main() -> None: 74 | # create the application 75 | app = CustomerSupport() 76 | 77 | # register a few tickets 78 | app.create_ticket("John Smith", "My computer makes strange sounds!") 79 | app.create_ticket("Linus Sebastian", 80 | "I can't upload any videos, please help.") 81 | app.create_ticket( 82 | "Arjan Egges", "VSCode doesn't automatically solve my bugs.") 83 | 84 | # process the tickets 85 | app.process_tickets(random_ordering) 86 | 87 | 88 | if __name__ == '__main__': 89 | main() 90 | -------------------------------------------------------------------------------- /3 - strategy pattern/strategy-after.py: -------------------------------------------------------------------------------- 1 | import string 2 | import random 3 | from typing import List 4 | from abc import ABC, abstractmethod 5 | 6 | 7 | def generate_id(length=8): 8 | # helper function for generating an id 9 | return ''.join(random.choices(string.ascii_uppercase, k=length)) 10 | 11 | 12 | class SupportTicket: 13 | 14 | def __init__(self, customer, issue): 15 | self.id = generate_id() 16 | self.customer = customer 17 | self.issue = issue 18 | 19 | 20 | class TicketOrderingStrategy(ABC): 21 | @abstractmethod 22 | def create_ordering(self, list: List[SupportTicket]) -> List[SupportTicket]: 23 | pass 24 | 25 | 26 | class FIFOOrderingStrategy(TicketOrderingStrategy): 27 | def create_ordering(self, list: List[SupportTicket]) -> List[SupportTicket]: 28 | return list.copy() 29 | 30 | 31 | class FILOOrderingStrategy(TicketOrderingStrategy): 32 | def create_ordering(self, list: List[SupportTicket]) -> List[SupportTicket]: 33 | list_copy = list.copy() 34 | list_copy.reverse() 35 | return list_copy 36 | 37 | 38 | class RandomOrderingStrategy(TicketOrderingStrategy): 39 | def create_ordering(self, list: List[SupportTicket]) -> List[SupportTicket]: 40 | list_copy = list.copy() 41 | random.shuffle(list_copy) 42 | return list_copy 43 | 44 | 45 | class BlackHoleStrategy(TicketOrderingStrategy): 46 | def create_ordering(self, list: List[SupportTicket]) -> List[SupportTicket]: 47 | return [] 48 | 49 | 50 | class CustomerSupport: 51 | 52 | def __init__(self, processing_strategy: TicketOrderingStrategy): 53 | self.tickets = [] 54 | self.processing_strategy = processing_strategy 55 | 56 | def create_ticket(self, customer, issue): 57 | self.tickets.append(SupportTicket(customer, issue)) 58 | 59 | def process_tickets(self): 60 | # create the ordered list 61 | ticket_list = self.processing_strategy.create_ordering(self.tickets) 62 | 63 | # if it's empty, don't do anything 64 | if len(ticket_list) == 0: 65 | print("There are no tickets to process. Well done!") 66 | return 67 | 68 | # go through the tickets in the list 69 | for ticket in ticket_list: 70 | self.process_ticket(ticket) 71 | 72 | def process_ticket(self, ticket: SupportTicket): 73 | print("==================================") 74 | print(f"Processing ticket id: {ticket.id}") 75 | print(f"Customer: {ticket.customer}") 76 | print(f"Issue: {ticket.issue}") 77 | print("==================================") 78 | 79 | 80 | # create the application 81 | app = CustomerSupport(RandomOrderingStrategy()) 82 | 83 | # register a few tickets 84 | app.create_ticket("John Smith", "My computer makes strange sounds!") 85 | app.create_ticket("Linus Sebastian", "I can't upload any videos, please help.") 86 | app.create_ticket("Arjan Egges", "VSCode doesn't automatically solve my bugs.") 87 | 88 | # process the tickets 89 | app.process_tickets() 90 | -------------------------------------------------------------------------------- /3 - strategy pattern/strategy-before.py: -------------------------------------------------------------------------------- 1 | import string 2 | import random 3 | from typing import List 4 | 5 | 6 | def generate_id(length=8): 7 | # helper function for generating an id 8 | return ''.join(random.choices(string.ascii_uppercase, k=length)) 9 | 10 | 11 | class SupportTicket: 12 | 13 | def __init__(self, customer, issue): 14 | self.id = generate_id() 15 | self.customer = customer 16 | self.issue = issue 17 | 18 | 19 | class CustomerSupport: 20 | 21 | def __init__(self, processing_strategy: str = "fifo"): 22 | self.tickets = [] 23 | self.processing_strategy = processing_strategy 24 | 25 | def create_ticket(self, customer, issue): 26 | self.tickets.append(SupportTicket(customer, issue)) 27 | 28 | def process_tickets(self): 29 | # if it's empty, don't do anything 30 | if len(self.tickets) == 0: 31 | print("There are no tickets to process. Well done!") 32 | return 33 | 34 | if self.processing_strategy == "fifo": 35 | for ticket in self.tickets: 36 | self.process_ticket(ticket) 37 | elif self.processing_strategy == "filo": 38 | for ticket in reversed(self.tickets): 39 | self.process_ticket(ticket) 40 | elif self.processing_strategy == "random": 41 | list_copy = self.tickets.copy() 42 | random.shuffle(list_copy) 43 | for ticket in list_copy: 44 | self.process_ticket(ticket) 45 | 46 | def process_ticket(self, ticket: SupportTicket): 47 | print("==================================") 48 | print(f"Processing ticket id: {ticket.id}") 49 | print(f"Customer: {ticket.customer}") 50 | print(f"Issue: {ticket.issue}") 51 | print("==================================") 52 | 53 | 54 | # create the application 55 | app = CustomerSupport("filo") 56 | 57 | # register a few tickets 58 | app.create_ticket("John Smith", "My computer makes strange sounds!") 59 | app.create_ticket("Linus Sebastian", "I can't upload any videos, please help.") 60 | app.create_ticket("Arjan Egges", "VSCode doesn't automatically solve my bugs.") 61 | 62 | # process the tickets 63 | app.process_tickets() 64 | -------------------------------------------------------------------------------- /4 - observer pattern/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArjanCodes/betterpython/11a95a40ee341d559e8709b8b6c676eca2dd978c/4 - observer pattern/.DS_Store -------------------------------------------------------------------------------- /4 - observer pattern/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArjanCodes/betterpython/11a95a40ee341d559e8709b8b6c676eca2dd978c/4 - observer pattern/api/__init__.py -------------------------------------------------------------------------------- /4 - observer pattern/api/plan.py: -------------------------------------------------------------------------------- 1 | from lib.email import send_email 2 | from lib.db import create_user, find_user 3 | from lib.log import log 4 | from lib.slack import post_slack_message 5 | 6 | def upgrade_plan(email: str): 7 | # find the user 8 | user = find_user(email) 9 | 10 | # upgrade the plan 11 | user.plan = "paid" 12 | 13 | # post a Slack message to sales department 14 | post_slack_message("sales", 15 | f"{user.name} has upgraded their plan.") 16 | 17 | # send a thank you email 18 | send_email(user.name, user.email, 19 | "Thank you", 20 | f"Thanks for upgrading, {user.name}! You're gonna love it. \nRegards, The DevNotes team") 21 | 22 | # write server log 23 | log(f"User with email address {user.email} has upgraded their plan") -------------------------------------------------------------------------------- /4 - observer pattern/api/user.py: -------------------------------------------------------------------------------- 1 | from lib.email import send_email 2 | from lib.db import create_user, find_user 3 | from lib.log import log 4 | from lib.slack import post_slack_message 5 | from lib.stringtools import get_random_string 6 | 7 | def register_new_user(name: str, password: str, email: str): 8 | # create an entry in the database 9 | user = create_user(name, password, email) 10 | 11 | # post a Slack message to sales department 12 | post_slack_message("sales", 13 | f"{user.name} has registered with email address {user.email}. Please spam this person incessantly.") 14 | 15 | # send a welcome email 16 | send_email(user.name, user.email, 17 | "Welcome", 18 | f"Thanks for registering, {user.name}!\nRegards, The DevNotes team") 19 | 20 | # write server log 21 | log(f"User registered with email address {user.email}") 22 | 23 | def password_forgotten(email: str): 24 | # retrieve the user 25 | user = find_user(email) 26 | 27 | # generate a password reset code 28 | user.reset_code = get_random_string(16) 29 | 30 | # send a password reset message 31 | send_email(user.name, user.email, "Reset your password", 32 | f"To reset your password, use this very secure code: {user.reset_code}.\nRegards, The DevNotes team") 33 | 34 | # write server log 35 | log(f"User with email address {user.email} requested a password reset") 36 | -------------------------------------------------------------------------------- /4 - observer pattern/api_v2/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArjanCodes/betterpython/11a95a40ee341d559e8709b8b6c676eca2dd978c/4 - observer pattern/api_v2/__init__.py -------------------------------------------------------------------------------- /4 - observer pattern/api_v2/email_listener.py: -------------------------------------------------------------------------------- 1 | from lib.email import send_email 2 | from .event import subscribe 3 | 4 | def handle_user_registered_event(user): 5 | # send a welcome email 6 | send_email(user.name, user.email, 7 | "Welcome", 8 | f"Thanks for registering, {user.name}!\nRegards, The DevNotes team") 9 | 10 | def handle_user_password_forgotten_event(user): 11 | # send a password reset message 12 | send_email(user.name, user.email, "Reset your password", 13 | f"To reset your password, use this very secure code: {user.reset_code}.\nRegards, The DevNotes team") 14 | 15 | def handle_user_upgrade_plan_event(user): 16 | # send a thank you email 17 | send_email(user.name, user.email, 18 | "Thank you", 19 | f"Thanks for upgrading, {user.name}! You're gonna love it. \nRegards, The DevNotes team") 20 | 21 | 22 | def setup_email_event_handlers(): 23 | subscribe("user_registered", handle_user_registered_event) 24 | subscribe("user_password_forgotten", handle_user_password_forgotten_event) 25 | subscribe("user_upgrade_plan", handle_user_upgrade_plan_event) -------------------------------------------------------------------------------- /4 - observer pattern/api_v2/event.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | 3 | # Default value of the dictionary will be list 4 | subscribers = defaultdict(list) 5 | 6 | def subscribe(event_type: str, fn): 7 | subscribers[event_type].append(fn) 8 | 9 | def post_event(event_type: str, data): 10 | if not event_type in subscribers: 11 | return 12 | for fn in subscribers[event_type]: 13 | fn(data) 14 | 15 | 16 | -------------------------------------------------------------------------------- /4 - observer pattern/api_v2/log_listener.py: -------------------------------------------------------------------------------- 1 | from lib.log import log 2 | from .event import subscribe 3 | 4 | def handle_user_registered_event(user): 5 | log(f"User registered with email address {user.email}") 6 | 7 | def handle_user_password_forgotten_event(user): 8 | log(f"User with email address {user.email} requested a password reset") 9 | 10 | def handle_user_upgrade_plan_event(user): 11 | log(f"User with email address {user.email} has upgraded their plan") 12 | 13 | def setup_log_event_handlers(): 14 | subscribe("user_registered", handle_user_registered_event) 15 | subscribe("user_password_forgotten", handle_user_password_forgotten_event) 16 | subscribe("user_upgrade_plan", handle_user_upgrade_plan_event) -------------------------------------------------------------------------------- /4 - observer pattern/api_v2/plan.py: -------------------------------------------------------------------------------- 1 | from lib.db import create_user, find_user 2 | from .event import post_event 3 | 4 | def upgrade_plan(email: str): 5 | # find the user 6 | user = find_user(email) 7 | 8 | # upgrade the plan 9 | user.plan = "paid" 10 | 11 | # post an event 12 | post_event("user_upgrade_plan", user) -------------------------------------------------------------------------------- /4 - observer pattern/api_v2/slack_listener.py: -------------------------------------------------------------------------------- 1 | from lib.slack import post_slack_message 2 | from .event import subscribe 3 | 4 | def handle_user_registered_event(user): 5 | post_slack_message("sales", 6 | f"{user.name} has registered with email address {user.email}. Please spam this person incessantly.") 7 | 8 | def handle_user_upgrade_plan_event(user): 9 | post_slack_message("sales", 10 | f"{user.name} has upgraded their plan.") 11 | 12 | def setup_slack_event_handlers(): 13 | subscribe("user_registered", handle_user_registered_event) 14 | subscribe("user_upgrade_plan", handle_user_upgrade_plan_event) -------------------------------------------------------------------------------- /4 - observer pattern/api_v2/user.py: -------------------------------------------------------------------------------- 1 | from lib.db import create_user, find_user 2 | from lib.stringtools import get_random_string 3 | from .event import post_event 4 | 5 | def register_new_user(name: str, password: str, email: str): 6 | # create an entry in the database 7 | user = create_user(name, password, email) 8 | 9 | # post an event 10 | post_event("user_registered", user) 11 | 12 | def password_forgotten(email: str): 13 | # retrieve the user 14 | user = find_user(email) 15 | 16 | # generate a password reset code 17 | user.reset_code = get_random_string(16) 18 | 19 | # post an event 20 | post_event("user_password_forgotten", user) 21 | 22 | def reset_password(code: str, email: str, password: str): 23 | 24 | # retrieve the user 25 | user = find_user(email) 26 | 27 | # reset the password 28 | user.reset_password(code, password) 29 | -------------------------------------------------------------------------------- /4 - observer pattern/lib/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArjanCodes/betterpython/11a95a40ee341d559e8709b8b6c676eca2dd978c/4 - observer pattern/lib/__init__.py -------------------------------------------------------------------------------- /4 - observer pattern/lib/db.py: -------------------------------------------------------------------------------- 1 | from hashlib import blake2b 2 | 3 | # the 'database' of users 4 | users = [] 5 | 6 | class User: 7 | def __init__(self, name: str, password: str, email: str): 8 | self.name = name 9 | self.password = blake2b(password.encode('UTF-8')).hexdigest() 10 | self.email = email 11 | self.plan = "basic" 12 | self.reset_code = "" 13 | 14 | def __repr__(self): 15 | return f"NAME: {self.name}, EMAIL: {self.email}, PASSWD: {self.password}" 16 | 17 | def reset_password(self, code: str, new_password: str): 18 | if code != self.reset_code: 19 | raise Exception("Invalid password reset code.") 20 | self.password = blake2b(new_password.encode('UTF-8')).hexdigest() 21 | 22 | # stub methods for actions related to creating a new user 23 | def create_user(name: str, password: str, email: str): 24 | print(f"DB: creating user database entry for {name} ({email}).") 25 | new_user = User(name, password, email) 26 | users.append(new_user) 27 | return new_user 28 | 29 | def find_user(email: str): 30 | for user in users: 31 | if user.email == email: 32 | return user 33 | raise Exception(f"User with email address {email} not found.") -------------------------------------------------------------------------------- /4 - observer pattern/lib/email.py: -------------------------------------------------------------------------------- 1 | def send_email(name: str, address: str, subject: str, body: str): 2 | print(f"Sending email to {name} ({address})") 3 | print("==========") 4 | print(f"Subject: {subject}\n") 5 | print(body) 6 | -------------------------------------------------------------------------------- /4 - observer pattern/lib/log.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | def log(msg: str): 4 | print(f"{datetime.now()} - {msg}") 5 | -------------------------------------------------------------------------------- /4 - observer pattern/lib/slack.py: -------------------------------------------------------------------------------- 1 | def post_slack_message(channel: str, msg: str): 2 | print(f"[SlackBot - {channel}]: {msg}") -------------------------------------------------------------------------------- /4 - observer pattern/lib/stringtools.py: -------------------------------------------------------------------------------- 1 | import random 2 | import string 3 | 4 | def get_random_string(length): 5 | letters = string.ascii_lowercase 6 | return ''.join(random.choice(letters) for i in range(length)) -------------------------------------------------------------------------------- /4 - observer pattern/observer-after.py: -------------------------------------------------------------------------------- 1 | from api_v2.slack_listener import setup_slack_event_handlers 2 | from api_v2.log_listener import setup_log_event_handlers 3 | from api_v2.email_listener import setup_email_event_handlers 4 | 5 | from api_v2.user import register_new_user, password_forgotten 6 | from api_v2.plan import upgrade_plan 7 | 8 | # initialize the event structure 9 | setup_slack_event_handlers() 10 | setup_log_event_handlers() 11 | setup_email_event_handlers() 12 | 13 | 14 | # register a new user 15 | register_new_user("Arjan", "BestPasswordEva", "hi@arjanegges.com") 16 | 17 | # send a password reset message 18 | password_forgotten("hi@arjanegges.com") 19 | 20 | # upgrade the plan 21 | upgrade_plan("hi@arjanegges.com") 22 | -------------------------------------------------------------------------------- /4 - observer pattern/observer-before.py: -------------------------------------------------------------------------------- 1 | from api.user import register_new_user, password_forgotten 2 | from api.plan import upgrade_plan 3 | 4 | # register a new user 5 | register_new_user("Arjan", "BestPasswordEva", "hi@arjanegges.com") 6 | 7 | # send a password reset message 8 | password_forgotten("hi@arjanegges.com") 9 | 10 | # upgrade the plan 11 | upgrade_plan("hi@arjanegges.com") 12 | -------------------------------------------------------------------------------- /5 - unit testing/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArjanCodes/betterpython/11a95a40ee341d559e8709b8b6c676eca2dd978c/5 - unit testing/.DS_Store -------------------------------------------------------------------------------- /5 - unit testing/vehicle_info_after.py: -------------------------------------------------------------------------------- 1 | class VehicleInfo: 2 | 3 | def __init__(self, brand, electric, catalogue_price): 4 | self.brand = brand 5 | self.electric = electric 6 | self.catalogue_price = catalogue_price 7 | 8 | # This method computes the tax payable for this particular vehicle and 9 | # returns that as a positive floating point value. 10 | # You can optionally provide an amount below which no tax is computed 11 | def compute_tax(self, tax_exemption_amount: int = 0): 12 | if tax_exemption_amount < 0: 13 | raise ValueError(f"tax_exemption_amount should be a positive number, but received {tax_exemption_amount} instead.") 14 | tax_percentage = 0.05 15 | if self.electric: 16 | tax_percentage = 0.02 17 | return tax_percentage * (max(self.catalogue_price - tax_exemption_amount, 0)) 18 | 19 | # you can only lease this car if the catalogue price is not more than 70% of 20 | # your year income; year_income should be >= 0 21 | def can_lease(self, year_income: int) -> bool: 22 | if year_income < 0: 23 | raise ValueError(f"year_income should be a positive number, but received {year_income} instead.") 24 | return self.catalogue_price <= 0.7 * year_income 25 | -------------------------------------------------------------------------------- /5 - unit testing/vehicle_info_before.py: -------------------------------------------------------------------------------- 1 | class VehicleInfo: 2 | 3 | def __init__(self, brand, electric, catalogue_price): 4 | self.brand = brand 5 | self.electric = electric 6 | self.catalogue_price = catalogue_price 7 | 8 | # This method computes the tax payable for this particular vehicle and 9 | # returns that as a positive floating point value. 10 | # You can optionally provide an amount below which no tax is computed 11 | def compute_tax(self, tax_exemption_amount: int = 0) -> float: 12 | if tax_exemption_amount < 0: 13 | raise ValueError(f"tax_exemption_amount should be a positive number, but received {tax_exemption_amount} instead.") 14 | tax_percentage = 0.05 15 | if self.electric: 16 | tax_percentage = 0.02 17 | return tax_percentage * (self.catalogue_price - tax_exemption_amount) 18 | 19 | # you can only lease this car if the catalogue price is not more than 70% of 20 | # your year income; year_income should be >= 0 21 | def can_lease(self, year_income: int) -> bool: 22 | # to do 23 | pass 24 | 25 | # create a vehicle info object 26 | v = VehicleInfo("BMW", False, 10000) 27 | 28 | # compute the tax 29 | print(f"BMW tax: {v.compute_tax()}") 30 | -------------------------------------------------------------------------------- /5 - unit testing/vehicle_info_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from vehicle_info_after import VehicleInfo 3 | 4 | class TestVehicleInfoMethods(unittest.TestCase): 5 | 6 | pass 7 | 8 | # def test_compute_tax_non_electric(self): 9 | # v = VehicleInfo("BMW", False, 10000) 10 | # self.assertEqual(v.compute_tax(), 500) 11 | 12 | # def test_compute_tax_electric(self): 13 | # v = VehicleInfo("BMW", True, 10000) 14 | # self.assertEqual(v.compute_tax(), 200) 15 | 16 | # def test_compute_tax_exemption(self): 17 | # v = VehicleInfo("BMW", False, 10000) 18 | # self.assertEqual(v.compute_tax(5000), 250) 19 | 20 | # def test_compute_tax_exemption_negative(self): 21 | # v = VehicleInfo("BMW", False, 10000) 22 | # self.assertRaises(ValueError, v.compute_tax, -5000) 23 | 24 | # def test_compute_tax_exemption_high(self): 25 | # v = VehicleInfo("BMW", False, 10000) 26 | # self.assertEqual(v.compute_tax(20000), 0) 27 | 28 | # def test_can_lease_false(self): 29 | # v = VehicleInfo("BMW", False, 10000) 30 | # self.assertEqual(v.can_lease(5000), False) 31 | 32 | # def test_can_lease_true(self): 33 | # v = VehicleInfo("BMW", False, 10000) 34 | # self.assertEqual(v.can_lease(15000), True) 35 | 36 | # def test_can_lease_negative_income(self): 37 | # v = VehicleInfo("BMW", False, 10000) 38 | # self.assertRaises(ValueError, v.can_lease, -5000) 39 | 40 | # run the actual unittests 41 | unittest.main() 42 | -------------------------------------------------------------------------------- /6 - template method & bridge/trading-after.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from typing import List 3 | 4 | class TradingBot(ABC): 5 | 6 | def connect(self): 7 | print(f"Connecting to Crypto exchange...") 8 | 9 | def get_market_data(self, coin: str) -> List[float]: 10 | return [10, 12, 18, 14] 11 | 12 | def check_prices(self, coin: str): 13 | self.connect() 14 | prices = self.get_market_data(coin) 15 | should_buy = self.should_buy(prices) 16 | should_sell = self.should_sell(prices) 17 | if should_buy: 18 | print(f"You should buy {coin}!") 19 | elif should_sell: 20 | print(f"You should sell {coin}!") 21 | else: 22 | print(f"No action needed for {coin}.") 23 | 24 | @abstractmethod 25 | def should_buy(self, prices: List[float]) -> bool: 26 | pass 27 | 28 | @abstractmethod 29 | def should_sell(self, prices: List[float]) -> bool: 30 | pass 31 | 32 | class AverageTrader(TradingBot): 33 | 34 | def list_average(self, l: List[float]) -> float: 35 | return sum(l) / len(l) 36 | 37 | def should_buy(self, prices: List[float]) -> bool: 38 | return prices[-1] < self.list_average(prices) 39 | 40 | def should_sell(self, prices: List[float]) -> bool: 41 | return prices[-1] > self.list_average(prices) 42 | 43 | class MinMaxTrader(TradingBot): 44 | 45 | def should_buy(self, prices: List[float]) -> bool: 46 | return prices[-1] == min(prices) 47 | 48 | def should_sell(self, prices: List[float]) -> bool: 49 | return prices[-1] == max(prices) 50 | 51 | application = MinMaxTrader() 52 | application.check_prices("BTC/USD") 53 | -------------------------------------------------------------------------------- /6 - template method & bridge/trading-before.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | class Application: 4 | 5 | def __init__(self, trading_strategy = "average"): 6 | self.trading_strategy = trading_strategy 7 | 8 | def connect(self): 9 | print(f"Connecting to Crypto exchange...") 10 | 11 | def get_market_data(self, coin: str) -> List[float]: 12 | return [10, 12, 18, 14] 13 | 14 | def list_average(self, l: List[float]) -> float: 15 | return sum(l) / len(l) 16 | 17 | def should_buy(self, prices: List[float]) -> bool: 18 | if self.trading_strategy == "minmax": 19 | return prices[-1] == min(prices) 20 | else: 21 | return prices[-1] < self.list_average(prices) 22 | 23 | def should_sell(self, prices: List[float]) -> bool: 24 | if self.trading_strategy == "minmax": 25 | return prices[-1] == max(prices) 26 | else: 27 | return prices[-1] > self.list_average(prices) 28 | 29 | def check_prices(self, coin: str): 30 | self.connect() 31 | prices = self.get_market_data(coin) 32 | should_buy = self.should_buy(prices) 33 | should_sell = self.should_sell(prices) 34 | if should_buy: 35 | print(f"You should buy {coin}!") 36 | elif should_sell: 37 | print(f"You should sell {coin}!") 38 | else: 39 | print(f"No action needed for {coin}.") 40 | 41 | application = Application("average") 42 | application.check_prices("BTC/USD") 43 | -------------------------------------------------------------------------------- /6 - template method & bridge/with-bridge.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from typing import List 3 | 4 | class Exchange(ABC): 5 | @abstractmethod 6 | def connect(self): 7 | pass 8 | 9 | @abstractmethod 10 | def get_market_data(self, coin: str) -> List[float]: 11 | pass 12 | 13 | class Binance(Exchange): 14 | def connect(self): 15 | print(f"Connecting to Binance exchange...") 16 | 17 | def get_market_data(self, coin: str) -> List[float]: 18 | return [10, 12, 18, 14] 19 | 20 | class Coinbase(Exchange): 21 | def connect(self): 22 | print(f"Connecting to Coinbase exchange...") 23 | 24 | def get_market_data(self, coin: str) -> List[float]: 25 | return [10, 12, 18, 20] 26 | 27 | 28 | class TradingBot(ABC): 29 | 30 | def __init__(self, exchange: Exchange): 31 | self.exchange = exchange 32 | 33 | def check_prices(self, coin: str): 34 | self.exchange.connect() 35 | prices = self.exchange.get_market_data(coin) 36 | should_buy = self.should_buy(prices) 37 | should_sell = self.should_sell(prices) 38 | if should_buy: 39 | print(f"You should buy {coin}!") 40 | elif should_sell: 41 | print(f"You should sell {coin}!") 42 | else: 43 | print(f"No action needed for {coin}.") 44 | 45 | @abstractmethod 46 | def should_buy(self, prices: List[float]) -> bool: 47 | pass 48 | 49 | @abstractmethod 50 | def should_sell(self, prices: List[float]) -> bool: 51 | pass 52 | 53 | class AverageTrader(TradingBot): 54 | def list_average(self, l: List[float]) -> float: 55 | return sum(l) / len(l) 56 | 57 | def should_buy(self, prices: List[float]) -> bool: 58 | return prices[-1] < self.list_average(prices) 59 | 60 | def should_sell(self, prices: List[float]) -> bool: 61 | return prices[-1] > self.list_average(prices) 62 | 63 | class MinMaxTrader(TradingBot): 64 | 65 | def should_buy(self, prices: List[float]) -> bool: 66 | return prices[-1] == min(prices) 67 | 68 | def should_sell(self, prices: List[float]) -> bool: 69 | return prices[-1] == max(prices) 70 | 71 | application = AverageTrader(Coinbase()) 72 | application.check_prices("BTC/USD") 73 | -------------------------------------------------------------------------------- /7 - dealing with errors/advanced/logging-decorator.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from functools import wraps 3 | 4 | # Example from: https://www.geeksforgeeks.org/create-an-exception-logging-decorator-in-python/ 5 | 6 | def create_logger(): 7 | 8 | # create a logger object 9 | logger = logging.getLogger('exc_logger') 10 | logger.setLevel(logging.INFO) 11 | 12 | # create a file to store all the 13 | # logged exceptions 14 | logfile = logging.FileHandler('exc_logger.log') 15 | 16 | fmt = '%(asctime)s - %(name)s - %(levelname)s - %(message)s' 17 | formatter = logging.Formatter(fmt) 18 | 19 | logfile.setFormatter(formatter) 20 | logger.addHandler(logfile) 21 | 22 | return logger 23 | 24 | logger = create_logger() 25 | 26 | # you will find a log file 27 | # created in a given path 28 | print(logger) 29 | 30 | def exception(logger): 31 | def decorator(func): 32 | @wraps(func) 33 | def wrapper(*args, **kwargs): 34 | try: 35 | return func(*args, **kwargs) 36 | except: 37 | issue = "exception in "+func.__name__+"\n" 38 | issue = issue+"=============\n" 39 | logger.exception(issue) 40 | raise 41 | return wrapper 42 | return decorator 43 | 44 | 45 | @exception(logger) 46 | def divideByZero(): 47 | return 12/0 48 | 49 | # Driver Code 50 | if __name__ == '__main__': 51 | divideByZero() 52 | 53 | -------------------------------------------------------------------------------- /7 - dealing with errors/advanced/retry-decorator.py: -------------------------------------------------------------------------------- 1 | import time 2 | import math 3 | from functools import wraps 4 | 5 | def retry(ExceptionToCheck, tries=4, delay=3, backoff=2, logger=None): 6 | """Retry calling the decorated function using an exponential backoff. 7 | 8 | http://www.saltycrane.com/blog/2009/11/trying-out-retry-decorator-python/ 9 | original from: http://wiki.python.org/moin/PythonDecoratorLibrary#Retry 10 | 11 | :param ExceptionToCheck: the exception to check. may be a tuple of 12 | exceptions to check 13 | :type ExceptionToCheck: Exception or tuple 14 | :param tries: number of times to try (not retry) before giving up 15 | :type tries: int 16 | :param delay: initial delay between retries in seconds 17 | :type delay: int 18 | :param backoff: backoff multiplier e.g. value of 2 will double the delay 19 | each retry 20 | :type backoff: int 21 | :param logger: logger to use. If None, print 22 | :type logger: logging.Logger instance 23 | """ 24 | def deco_retry(f): 25 | 26 | @wraps(f) 27 | def f_retry(*args, **kwargs): 28 | mtries, mdelay = tries, delay 29 | while mtries > 1: 30 | try: 31 | return f(*args, **kwargs) 32 | except ExceptionToCheck as e: 33 | msg = "%s, Retrying in %d seconds..." % (str(e), mdelay) 34 | if logger: 35 | logger.warning(msg) 36 | else: 37 | print(msg) 38 | time.sleep(mdelay) 39 | mtries -= 1 40 | mdelay *= backoff 41 | return f(*args, **kwargs) 42 | 43 | return f_retry # true decorator 44 | 45 | return deco_retry 46 | 47 | @retry(Exception, tries=4) 48 | def test_fail(text): 49 | raise Exception("Fail") 50 | 51 | test_fail("it works!") -------------------------------------------------------------------------------- /7 - dealing with errors/after-context/app.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, jsonify, abort 2 | from db import fetch_blogs, fetch_blog, NotFoundError, NotAuthorizedError 3 | 4 | app = Flask(__name__) 5 | 6 | @app.route('/') 7 | def hello_world(): 8 | return 'Hello, World!' 9 | 10 | @app.route('/blogs') 11 | def all_blogs(): 12 | return jsonify(fetch_blogs()) 13 | 14 | @app.route('/blogs/') 15 | def get_blog(id): 16 | try: 17 | return jsonify(fetch_blog(id)) 18 | except NotFoundError: 19 | abort(404, description="Resource not found") 20 | except NotAuthorizedError: 21 | abort(403, description="Access denied") 22 | -------------------------------------------------------------------------------- /7 - dealing with errors/after-context/application.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArjanCodes/betterpython/11a95a40ee341d559e8709b8b6c676eca2dd978c/7 - dealing with errors/after-context/application.db -------------------------------------------------------------------------------- /7 - dealing with errors/after-context/create-db.py: -------------------------------------------------------------------------------- 1 | import sqlite3 2 | con = sqlite3.connect('application.db') 3 | 4 | cur = con.cursor() 5 | 6 | # Create table 7 | cur.execute('''CREATE TABLE blogs 8 | (id text not null primary key, date text, title text, content text, public integer)''') 9 | 10 | # Insert a few rows of data 11 | cur.execute("INSERT INTO blogs VALUES ('first-blog', '2021-03-07', 'My first blog' ,'Some content', 1)") 12 | cur.execute("INSERT INTO blogs VALUES ('private-blog', '2021-03-07', 'Secret blog' ,'This is a secret', 0)") 13 | 14 | # Save (commit) the changes 15 | con.commit() 16 | 17 | # We can also close the connection if we are done with it. 18 | # Just be sure any changes have been committed or they will be lost. 19 | con.close() -------------------------------------------------------------------------------- /7 - dealing with errors/after-context/db.py: -------------------------------------------------------------------------------- 1 | import sqlite3 2 | 3 | class SQLite(): 4 | def __init__(self, file='application.db'): 5 | self.file=file 6 | def __enter__(self): 7 | self.conn = sqlite3.connect(self.file) 8 | return self.conn.cursor() 9 | def __exit__(self, type, value, traceback): 10 | print("Closing the connection") 11 | self.conn.close() 12 | 13 | class NotFoundError(Exception): 14 | pass 15 | 16 | class NotAuthorizedError(Exception): 17 | pass 18 | 19 | def blog_lst_to_json(item): 20 | return { 21 | 'id': item[0], 22 | 'published': item[1], 23 | 'title': item[2], 24 | 'content': item[3], 25 | 'public': bool(item[4]) 26 | } 27 | 28 | def fetch_blogs(): 29 | try: 30 | with SQLite('application.db') as cur: 31 | 32 | # execute the query 33 | cur.execute('SELECT * FROM blogs where public=1') 34 | 35 | # fetch the data and turn into a dict 36 | return list(map(blog_lst_to_json, cur.fetchall())) 37 | except Exception as e: 38 | print(e) 39 | return [] 40 | 41 | def fetch_blog(id: str): 42 | try: 43 | with SQLite('application.db') as cur: 44 | 45 | # execute the query and fetch the data 46 | cur.execute(f"SELECT * FROM blogs where id=?", [id]) 47 | result = cur.fetchone() 48 | 49 | # return the result or raise an error 50 | if result is None: 51 | raise NotFoundError(f'Unable to find blog with id {id}.') 52 | 53 | data = blog_lst_to_json(result) 54 | if not data['public']: 55 | raise NotAuthorizedError(f'You are not allowed to access blog with id {id}.') 56 | return data 57 | except sqlite3.OperationalError: 58 | raise NotFoundError(f'Unable to find blog with id {id}.') 59 | -------------------------------------------------------------------------------- /7 - dealing with errors/after-context/error-handling-context.py: -------------------------------------------------------------------------------- 1 | import sqlite3 2 | 3 | class SQLite(): 4 | def __init__(self, file='application.db'): 5 | self.file=file 6 | def __enter__(self): 7 | self.conn = sqlite3.connect(self.file) 8 | return self.conn.cursor() 9 | def __exit__(self, type, value, traceback): 10 | self.conn.close() -------------------------------------------------------------------------------- /7 - dealing with errors/after-context/error-handling.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, jsonify, abort 2 | from db import fetch_blogs, fetch_blog, NotFoundError, NotAuthorizedError 3 | 4 | app = Flask(__name__) 5 | 6 | @app.route('/') 7 | def hello_world(): 8 | return 'Hello, World!' 9 | 10 | @app.route('/blogs') 11 | def all_blogs(): 12 | return jsonify(fetch_blogs()) 13 | 14 | @app.route('/blogs/') 15 | def get_blog(id): 16 | try: 17 | return jsonify(fetch_blog(id)) 18 | except NotFoundError: 19 | abort(404, description="Resource not found") 20 | except NotAuthorizedError: 21 | abort(403, description="Access denied") 22 | -------------------------------------------------------------------------------- /7 - dealing with errors/after/app.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, jsonify, abort 2 | from db import fetch_blogs, fetch_blog, NotFoundError, NotAuthorizedError 3 | 4 | app = Flask(__name__) 5 | 6 | @app.route('/') 7 | def hello_world(): 8 | return 'Hello, World!' 9 | 10 | @app.route('/blogs') 11 | def all_blogs(): 12 | return jsonify(fetch_blogs()) 13 | 14 | @app.route('/blogs/') 15 | def get_blog(id): 16 | try: 17 | return jsonify(fetch_blog(id)) 18 | except NotFoundError: 19 | abort(404, description="Resource not found") 20 | except NotAuthorizedError: 21 | abort(403, description="Access denied") 22 | -------------------------------------------------------------------------------- /7 - dealing with errors/after/application.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArjanCodes/betterpython/11a95a40ee341d559e8709b8b6c676eca2dd978c/7 - dealing with errors/after/application.db -------------------------------------------------------------------------------- /7 - dealing with errors/after/create-db.py: -------------------------------------------------------------------------------- 1 | import sqlite3 2 | con = sqlite3.connect('application.db') 3 | 4 | cur = con.cursor() 5 | 6 | # Create table 7 | cur.execute('''CREATE TABLE blogs 8 | (id text not null primary key, date text, title text, content text, public integer)''') 9 | 10 | # Insert a few rows of data 11 | cur.execute("INSERT INTO blogs VALUES ('first-blog', '2021-03-07', 'My first blog' ,'Some content', 1)") 12 | cur.execute("INSERT INTO blogs VALUES ('private-blog', '2021-03-07', 'Secret blog' ,'This is a secret', 0)") 13 | 14 | # Save (commit) the changes 15 | con.commit() 16 | 17 | # We can also close the connection if we are done with it. 18 | # Just be sure any changes have been committed or they will be lost. 19 | con.close() -------------------------------------------------------------------------------- /7 - dealing with errors/after/db.py: -------------------------------------------------------------------------------- 1 | import sqlite3 2 | 3 | class NotFoundError(Exception): 4 | pass 5 | 6 | class NotAuthorizedError(Exception): 7 | pass 8 | 9 | def blog_lst_to_json(item): 10 | return { 11 | 'id': item[0], 12 | 'published': item[1], 13 | 'title': item[2], 14 | 'content': item[3], 15 | 'public': bool(item[4]) 16 | } 17 | 18 | def fetch_blogs(): 19 | try: 20 | # connect to the database 21 | con = sqlite3.connect('application.db') 22 | cur = con.cursor() 23 | 24 | # execute the query 25 | cur.execute('SELECT * FROM blogs where public=1') 26 | 27 | # fetch the data and turn into a dict 28 | result = list(map(blog_lst_to_json, cur.fetchall())) 29 | 30 | return result 31 | except Exception as e: 32 | print(e) 33 | return [] 34 | finally: 35 | print("Closing the database") 36 | # close the database 37 | con.close() 38 | 39 | def fetch_blog(id: str): 40 | try: 41 | # connect to the database 42 | con = sqlite3.connect('application.db') 43 | cur = con.cursor() 44 | 45 | # execute the query and fetch the data 46 | cur.execute(f"SELECT * FROM blogs where id=?", [id]) 47 | result = cur.fetchone() 48 | 49 | # return the result or raise an error 50 | if result is None: 51 | raise NotFoundError(f'Unable to find blog with id {id}.') 52 | 53 | data = blog_lst_to_json(result) 54 | if not data['public']: 55 | raise NotAuthorizedError(f'You are not allowed to access blog with id {id}.') 56 | return data 57 | except sqlite3.OperationalError as e: 58 | print(e) 59 | raise NotFoundError(f'Unable to find blog with id {id}.') 60 | finally: 61 | print("Closing the database") 62 | # close the database 63 | con.close() 64 | -------------------------------------------------------------------------------- /7 - dealing with errors/after/error-handling.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, jsonify, abort 2 | from db import fetch_blogs, fetch_blog, NotFoundError, NotAuthorizedError 3 | 4 | app = Flask(__name__) 5 | 6 | @app.route('/') 7 | def hello_world(): 8 | return 'Hello, World!' 9 | 10 | @app.route('/blogs') 11 | def all_blogs(): 12 | return jsonify(fetch_blogs()) 13 | 14 | @app.route('/blogs/') 15 | def get_blog(id): 16 | try: 17 | return jsonify(fetch_blog(id)) 18 | except NotFoundError: 19 | abort(404, description="Resource not found") 20 | except NotAuthorizedError: 21 | abort(403, description="Access denied") 22 | -------------------------------------------------------------------------------- /7 - dealing with errors/before/app.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, jsonify, abort 2 | 3 | app = Flask(__name__) 4 | 5 | @app.route('/') 6 | def hello_world(): 7 | return 'Hello, World!' 8 | -------------------------------------------------------------------------------- /7 - dealing with errors/before/application.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArjanCodes/betterpython/11a95a40ee341d559e8709b8b6c676eca2dd978c/7 - dealing with errors/before/application.db -------------------------------------------------------------------------------- /7 - dealing with errors/before/create-db.py: -------------------------------------------------------------------------------- 1 | import sqlite3 2 | con = sqlite3.connect('application.db') 3 | 4 | cur = con.cursor() 5 | 6 | # Create table 7 | cur.execute('''CREATE TABLE blogs 8 | (id text not null primary key, date text, title text, content text, public integer)''') 9 | 10 | # Insert a few rows of data 11 | cur.execute("INSERT INTO blogs VALUES ('first-blog', '2021-03-07', 'My first blog' ,'Some content', 1)") 12 | cur.execute("INSERT INTO blogs VALUES ('private-blog', '2021-03-07', 'Secret blog' ,'This is a secret', 0)") 13 | 14 | # Save (commit) the changes 15 | con.commit() 16 | 17 | # We can also close the connection if we are done with it. 18 | # Just be sure any changes have been committed or they will be lost. 19 | con.close() -------------------------------------------------------------------------------- /7 - dealing with errors/before/db.py: -------------------------------------------------------------------------------- 1 | import sqlite3 2 | 3 | def blog_lst_to_json(item): 4 | return { 5 | 'id': item[0], 6 | 'published': item[1], 7 | 'title': item[2], 8 | 'content': item[3], 9 | 'public': bool(item[4]) 10 | } 11 | 12 | def fetch_blogs(): 13 | pass 14 | 15 | def fetch_blog(id: str): 16 | pass 17 | 18 | -------------------------------------------------------------------------------- /7 - dealing with errors/before/error-handling.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, jsonify, abort 2 | 3 | app = Flask(__name__) 4 | 5 | @app.route('/') 6 | def hello_world(): 7 | return 'Hello, World!' 8 | -------------------------------------------------------------------------------- /7 - dealing with errors/monadic-error-handling/application.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArjanCodes/betterpython/11a95a40ee341d559e8709b8b6c676eca2dd978c/7 - dealing with errors/monadic-error-handling/application.db -------------------------------------------------------------------------------- /7 - dealing with errors/monadic-error-handling/example.py: -------------------------------------------------------------------------------- 1 | import sqlite3 2 | from returns.result import Result, safe 3 | from returns.pipeline import flow 4 | from returns.pointfree import bind 5 | 6 | class SQLite(): 7 | def __init__(self, file='application.db'): 8 | self.file = file 9 | self.conn = None 10 | def __enter__(self): 11 | self.conn = sqlite3.connect(self.file) 12 | return self.conn.cursor() 13 | def __exit__(self, type, value, traceback): 14 | if self.conn: self.conn.close() 15 | 16 | class NotFoundError(Exception): 17 | pass 18 | 19 | class NotAuthorizedError(Exception): 20 | pass 21 | 22 | def fetch_blog(blog_id) -> Result['Blog', Exception]: 23 | return flow( 24 | blog_id, 25 | fetch_blog_from_db, 26 | bind(blog_to_dict), 27 | bind(verify_access) 28 | ) 29 | 30 | @safe 31 | def fetch_blog_from_db(blog_id): 32 | """Fetches blog from SQLite3 database.""" 33 | with SQLite('application.db') as cur: 34 | cur.execute(f"SELECT * FROM blogs where id=?", [blog_id]) 35 | result = cur.fetchone() 36 | if result is None: 37 | raise NotFoundError(f'Unable to find blog with id {blog_id}.') 38 | return result 39 | 40 | @safe 41 | def blog_to_dict(item) -> 'Blog': 42 | """Convert SQLite result to dictionary.""" 43 | return { 44 | 'id': item[0], 45 | 'published': item[1], 46 | 'title': item[2], 47 | 'content': item[3], 48 | 'public': bool(item[4]) 49 | } 50 | 51 | @safe 52 | def verify_access(blog) -> 'Blog': 53 | """Check that blog is accessible.""" 54 | blog_id = blog['id'] 55 | blog_public = blog['public'] 56 | if not blog_public: 57 | raise NotAuthorizedError(f'You are not allowed to access blog with id {blog_id}.') 58 | return blog 59 | 60 | res = fetch_blog("first-blog") 61 | print(res) -------------------------------------------------------------------------------- /8 - mvc/mvc-after-strategy.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | import uuid 3 | import string 4 | import random 5 | from abc import ABC, abstractmethod 6 | 7 | # functional strategy 8 | def generate_uuid1(): 9 | return uuid.uuid1() 10 | 11 | def generate_uuid4(): 12 | return uuid.uuid4() 13 | 14 | def generate_simple_id(): 15 | return ''.join(random.choices(string.ascii_lowercase, k=30)) 16 | 17 | class Model: 18 | def __init__(self): 19 | self.uuid = [] 20 | 21 | class Controller: 22 | def __init__(self, model, view, generate_uuid): 23 | self.model = model 24 | self.view = view 25 | self.generate_uuid = generate_uuid 26 | 27 | def start(self): 28 | self.view.setup(self) 29 | self.view.start_main_loop() 30 | 31 | def handle_click_generate_uuid(self): 32 | # generate a uuid and add it to the list 33 | self.model.uuid.append(self.generate_uuid()) 34 | self.view.append_to_list(self.model.uuid[-1]) 35 | 36 | def handle_click_clear_list(self): 37 | # clear the uuid list in the model and the view 38 | self.model.uuid = [] 39 | self.view.clear_list() 40 | 41 | class View(ABC): 42 | @abstractmethod 43 | def setup(self, controller): 44 | pass 45 | 46 | @abstractmethod 47 | def append_to_list(self, item): 48 | pass 49 | 50 | @abstractmethod 51 | def clear_list(self): 52 | pass 53 | 54 | @abstractmethod 55 | def start_main_loop(self): 56 | pass 57 | 58 | class TkView(View): 59 | def setup(self, controller): 60 | 61 | # setup tkinter 62 | self.root = tk.Tk() 63 | self.root.geometry("400x400") 64 | self.root.title("UUIDGen") 65 | 66 | # create the gui 67 | self.frame = tk.Frame(self.root) 68 | self.frame.pack(fill=tk.BOTH, expand=1) 69 | self.label = tk.Label(self.frame, text="Result:") 70 | self.label.pack() 71 | self.list = tk.Listbox(self.frame) 72 | self.list.pack(fill=tk.BOTH, expand=1) 73 | self.generate_uuid_button = tk.Button(self.frame, text="Generate UUID", command=controller.handle_click_generate_uuid) 74 | self.generate_uuid_button.pack() 75 | self.clear_button = tk.Button(self.frame, text="Clear list", command=controller.handle_click_clear_list) 76 | self.clear_button.pack() 77 | 78 | def append_to_list(self, item): 79 | self.list.insert(tk.END, item) 80 | 81 | def clear_list(self): 82 | self.list.delete(0, tk.END) 83 | 84 | def start_main_loop(self): 85 | # start the loop 86 | self.root.mainloop() 87 | 88 | # create the MVC & start the application 89 | c = Controller(Model(), TkView(), generate_simple_id) 90 | c.start() -------------------------------------------------------------------------------- /8 - mvc/mvc-after.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | import uuid 3 | from abc import ABC, abstractmethod 4 | 5 | class Model: 6 | def __init__(self): 7 | self.uuid = [] 8 | 9 | def append(self, item): 10 | self.uuid.append(item) 11 | 12 | def clear(self): 13 | self.uuid = [] 14 | 15 | class Controller: 16 | def __init__(self, model, view): 17 | self.model = model 18 | self.view = view 19 | 20 | def start(self): 21 | self.view.setup(self) 22 | self.view.start_main_loop() 23 | 24 | def handle_click_generate_uuid(self): 25 | # generate a uuid and add it to the list 26 | newid = uuid.uuid4() 27 | self.model.append(newid) 28 | self.view.append_to_list(newid) 29 | 30 | def handle_click_clear_list(self): 31 | # clear the uuid list in the model and the view 32 | self.model.clear() 33 | self.view.clear_list() 34 | 35 | class View(ABC): 36 | @abstractmethod 37 | def setup(self, controller): 38 | pass 39 | 40 | @abstractmethod 41 | def append_to_list(self, item): 42 | pass 43 | 44 | @abstractmethod 45 | def clear_list(self): 46 | pass 47 | 48 | @abstractmethod 49 | def start_main_loop(self): 50 | pass 51 | 52 | class TkView(View): 53 | def setup(self, controller): 54 | 55 | # setup tkinter 56 | self.root = tk.Tk() 57 | self.root.geometry("400x400") 58 | self.root.title("UUIDGen") 59 | 60 | # create the gui 61 | self.frame = tk.Frame(self.root) 62 | self.frame.pack(fill=tk.BOTH, expand=1) 63 | self.label = tk.Label(self.frame, text="Result:") 64 | self.label.pack() 65 | self.list = tk.Listbox(self.frame) 66 | self.list.pack(fill=tk.BOTH, expand=1) 67 | self.generate_uuid_button = tk.Button(self.frame, text="Generate UUID", command=controller.handle_click_generate_uuid) 68 | self.generate_uuid_button.pack() 69 | self.clear_button = tk.Button(self.frame, text="Clear list", command=controller.handle_click_clear_list) 70 | self.clear_button.pack() 71 | 72 | def append_to_list(self, item): 73 | self.list.insert(tk.END, item) 74 | 75 | def clear_list(self): 76 | self.list.delete(0, tk.END) 77 | 78 | def start_main_loop(self): 79 | # start the loop 80 | self.root.mainloop() 81 | 82 | # create the MVC & start the application 83 | c = Controller(Model(), TkView()) 84 | c.start() -------------------------------------------------------------------------------- /8 - mvc/mvc-before.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | import uuid 3 | 4 | class UUIDGen(): 5 | def __init__(self): 6 | # setup tkinter 7 | self.root = tk.Tk() 8 | self.root.geometry("400x400") 9 | self.root.title("UUIDGen") 10 | 11 | # create the gui 12 | self.frame = tk.Frame(self.root) 13 | self.frame.pack(fill=tk.BOTH, expand=1) 14 | self.label = tk.Label(self.frame, text="Result:") 15 | self.label.pack() 16 | self.list = tk.Listbox(self.frame) 17 | self.list.pack(fill=tk.BOTH, expand=1) 18 | self.generate_uuid_button = tk.Button(self.frame, text="Generate UUID", command=self.handle_click_generate_uuid) 19 | self.generate_uuid_button.pack() 20 | self.clear_button = tk.Button(self.frame, text="Clear list", command=self.handle_click_clear_list) 21 | self.clear_button.pack() 22 | 23 | # initialize the uuid list 24 | self.uuid = [] 25 | 26 | # start the loop 27 | self.root.mainloop() 28 | 29 | def handle_click_generate_uuid(self): 30 | # generate a uuid and add it to the list 31 | self.uuid.append(uuid.uuid4()) 32 | self.list.insert(tk.END, self.uuid[-1]) 33 | 34 | def handle_click_clear_list(self): 35 | # clear the uuid list and delete it from the list 36 | self.uuid = [] 37 | self.list.delete(0, tk.END) 38 | 39 | # start the application 40 | u = UUIDGen() -------------------------------------------------------------------------------- /9 - solid/dependency-inversion-after.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | 3 | 4 | class Order: 5 | 6 | def __init__(self): 7 | self.items = [] 8 | self.quantities = [] 9 | self.prices = [] 10 | self.status = "open" 11 | 12 | def add_item(self, name, quantity, price): 13 | self.items.append(name) 14 | self.quantities.append(quantity) 15 | self.prices.append(price) 16 | 17 | def total_price(self): 18 | total = 0 19 | for i in range(len(self.prices)): 20 | total += self.quantities[i] * self.prices[i] 21 | return total 22 | 23 | 24 | class Authorizer(ABC): 25 | @abstractmethod 26 | def is_authorized(self) -> bool: 27 | pass 28 | 29 | 30 | class AuthorizerSMS(Authorizer): 31 | 32 | def __init__(self): 33 | self.authorized = False 34 | 35 | def verify_code(self, code): 36 | print(f"Verifying SMS code {code}") 37 | self.authorized = True 38 | 39 | def is_authorized(self) -> bool: 40 | return self.authorized 41 | 42 | 43 | class AuthorizerGoogle(Authorizer): 44 | 45 | def __init__(self): 46 | self.authorized = False 47 | 48 | def verify_code(self, code): 49 | print(f"Verifying Google auth code {code}") 50 | self.authorized = True 51 | 52 | def is_authorized(self) -> bool: 53 | return self.authorized 54 | 55 | 56 | class AuthorizerRobot(Authorizer): 57 | 58 | def __init__(self): 59 | self.authorized = False 60 | 61 | def not_a_robot(self): 62 | self.authorized = True 63 | 64 | def is_authorized(self) -> bool: 65 | return self.authorized 66 | 67 | 68 | class PaymentProcessor(ABC): 69 | 70 | @abstractmethod 71 | def pay(self, order): 72 | pass 73 | 74 | 75 | class DebitPaymentProcessor(PaymentProcessor): 76 | 77 | def __init__(self, security_code, authorizer: Authorizer): 78 | self.security_code = security_code 79 | self.authorizer = authorizer 80 | 81 | def pay(self, order): 82 | if not self.authorizer.is_authorized(): 83 | raise Exception("Not authorized") 84 | print("Processing debit payment type") 85 | print(f"Verifying security code: {self.security_code}") 86 | order.status = "paid" 87 | 88 | 89 | class CreditPaymentProcessor(PaymentProcessor): 90 | 91 | def __init__(self, security_code): 92 | self.security_code = security_code 93 | 94 | def pay(self, order): 95 | print("Processing credit payment type") 96 | print(f"Verifying security code: {self.security_code}") 97 | order.status = "paid" 98 | 99 | 100 | class PaypalPaymentProcessor(PaymentProcessor): 101 | 102 | def __init__(self, email_address, authorizer: Authorizer): 103 | self.email_address = email_address 104 | self.authorizer = authorizer 105 | 106 | def pay(self, order): 107 | if not self.authorizer.is_authorized(): 108 | raise Exception("Not authorized") 109 | print("Processing paypal payment type") 110 | print(f"Using email address: {self.email_address}") 111 | order.status = "paid" 112 | 113 | 114 | order = Order() 115 | order.add_item("Keyboard", 1, 50) 116 | order.add_item("SSD", 1, 150) 117 | order.add_item("USB cable", 2, 5) 118 | 119 | print(order.total_price()) 120 | authorizer = AuthorizerRobot() 121 | # authorizer.verify_code(465839) 122 | authorizer.not_a_robot() 123 | processor = PaypalPaymentProcessor("hi@arjancodes.com", authorizer) 124 | processor.pay(order) 125 | -------------------------------------------------------------------------------- /9 - solid/dependency-inversion-before.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | 3 | 4 | class Order: 5 | 6 | def __init__(self): 7 | self.items = [] 8 | self.quantities = [] 9 | self.prices = [] 10 | self.status = "open" 11 | 12 | def add_item(self, name, quantity, price): 13 | self.items.append(name) 14 | self.quantities.append(quantity) 15 | self.prices.append(price) 16 | 17 | def total_price(self): 18 | total = 0 19 | for i in range(len(self.prices)): 20 | total += self.quantities[i] * self.prices[i] 21 | return total 22 | 23 | 24 | class SMSAuthorizer: 25 | 26 | def __init__(self): 27 | self.authorized = False 28 | 29 | def verify_code(self, code): 30 | print(f"Verifying SMS code {code}") 31 | self.authorized = True 32 | 33 | def is_authorized(self) -> bool: 34 | return self.authorized 35 | 36 | 37 | class PaymentProcessor(ABC): 38 | 39 | @abstractmethod 40 | def pay(self, order): 41 | pass 42 | 43 | 44 | class DebitPaymentProcessor(PaymentProcessor): 45 | 46 | def __init__(self, security_code, authorizer: SMSAuthorizer): 47 | self.security_code = security_code 48 | self.authorizer = authorizer 49 | 50 | def pay(self, order): 51 | if not self.authorizer.is_authorized(): 52 | raise Exception("Not authorized") 53 | print("Processing debit payment type") 54 | print(f"Verifying security code: {self.security_code}") 55 | order.status = "paid" 56 | 57 | 58 | class CreditPaymentProcessor(PaymentProcessor): 59 | 60 | def __init__(self, security_code): 61 | self.security_code = security_code 62 | 63 | def pay(self, order): 64 | print("Processing credit payment type") 65 | print(f"Verifying security code: {self.security_code}") 66 | order.status = "paid" 67 | 68 | 69 | class PaypalPaymentProcessor(PaymentProcessor): 70 | 71 | def __init__(self, email_address, authorizer: SMSAuthorizer): 72 | self.email_address = email_address 73 | self.authorizer = authorizer 74 | 75 | def pay(self, order): 76 | if not self.authorizer.is_authorized(): 77 | raise Exception("Not authorized") 78 | print("Processing paypal payment type") 79 | print(f"Using email address: {self.email_address}") 80 | order.status = "paid" 81 | 82 | 83 | order = Order() 84 | order.add_item("Keyboard", 1, 50) 85 | order.add_item("SSD", 1, 150) 86 | order.add_item("USB cable", 2, 5) 87 | 88 | print(order.total_price()) 89 | authorizer = SMSAuthorizer() 90 | # authorizer.verify_code(465839) 91 | processor = PaypalPaymentProcessor("hi@arjancodes.com", authorizer) 92 | processor.pay(order) 93 | -------------------------------------------------------------------------------- /9 - solid/interface-segregation-after-comp.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | 3 | 4 | class Order: 5 | 6 | def __init__(self): 7 | self.items = [] 8 | self.quantities = [] 9 | self.prices = [] 10 | self.status = "open" 11 | 12 | def add_item(self, name, quantity, price): 13 | self.items.append(name) 14 | self.quantities.append(quantity) 15 | self.prices.append(price) 16 | 17 | def total_price(self): 18 | total = 0 19 | for i in range(len(self.prices)): 20 | total += self.quantities[i] * self.prices[i] 21 | return total 22 | 23 | 24 | class SMSAuthorizer: 25 | 26 | def __init__(self): 27 | self.authorized = False 28 | 29 | def verify_code(self, code): 30 | print(f"Verifying SMS code {code}") 31 | self.authorized = True 32 | 33 | def is_authorized(self) -> bool: 34 | return self.authorized 35 | 36 | 37 | class PaymentProcessor(ABC): 38 | 39 | @abstractmethod 40 | def pay(self, order): 41 | pass 42 | 43 | 44 | class DebitPaymentProcessor(PaymentProcessor): 45 | 46 | def __init__(self, security_code, authorizer: SMSAuthorizer): 47 | self.security_code = security_code 48 | self.authorizer = authorizer 49 | 50 | def pay(self, order): 51 | if not self.authorizer.is_authorized(): 52 | raise Exception("Not authorized") 53 | print("Processing debit payment type") 54 | print(f"Verifying security code: {self.security_code}") 55 | order.status = "paid" 56 | 57 | 58 | class CreditPaymentProcessor(PaymentProcessor): 59 | 60 | def __init__(self, security_code): 61 | self.security_code = security_code 62 | 63 | def pay(self, order): 64 | print("Processing credit payment type") 65 | print(f"Verifying security code: {self.security_code}") 66 | order.status = "paid" 67 | 68 | 69 | class PaypalPaymentProcessor(PaymentProcessor): 70 | 71 | def __init__(self, email_address, authorizer: SMSAuthorizer): 72 | self.email_address = email_address 73 | self.authorizer = authorizer 74 | 75 | def pay(self, order): 76 | if not self.authorizer.is_authorized(): 77 | raise Exception("Not authorized") 78 | print("Processing paypal payment type") 79 | print(f"Using email address: {self.email_address}") 80 | order.status = "paid" 81 | 82 | 83 | order = Order() 84 | order.add_item("Keyboard", 1, 50) 85 | order.add_item("SSD", 1, 150) 86 | order.add_item("USB cable", 2, 5) 87 | 88 | print(order.total_price()) 89 | authorizer = SMSAuthorizer() 90 | # authorizer.verify_code(465839) 91 | processor = PaypalPaymentProcessor("hi@arjancodes.com", authorizer) 92 | processor.pay(order) 93 | -------------------------------------------------------------------------------- /9 - solid/interface-segregation-after.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | 3 | 4 | class Order: 5 | 6 | def __init__(self): 7 | self.items = [] 8 | self.quantities = [] 9 | self.prices = [] 10 | self.status = "open" 11 | 12 | def add_item(self, name, quantity, price): 13 | self.items.append(name) 14 | self.quantities.append(quantity) 15 | self.prices.append(price) 16 | 17 | def total_price(self): 18 | total = 0 19 | for i in range(len(self.prices)): 20 | total += self.quantities[i] * self.prices[i] 21 | return total 22 | 23 | 24 | class PaymentProcessor(ABC): 25 | 26 | @abstractmethod 27 | def pay(self, order): 28 | pass 29 | 30 | 31 | class PaymentProcessorSMS(PaymentProcessor): 32 | 33 | @abstractmethod 34 | def auth_sms(self, code): 35 | pass 36 | 37 | @abstractmethod 38 | def pay(self, order): 39 | pass 40 | 41 | 42 | class DebitPaymentProcessor(PaymentProcessorSMS): 43 | 44 | def __init__(self, security_code): 45 | self.security_code = security_code 46 | self.verified = False 47 | 48 | def auth_sms(self, code): 49 | print(f"Verifying SMS code {code}") 50 | self.verified = True 51 | 52 | def pay(self, order): 53 | if not self.verified: 54 | raise Exception("Not authorized") 55 | print("Processing debit payment type") 56 | print(f"Verifying security code: {self.security_code}") 57 | order.status = "paid" 58 | 59 | 60 | class CreditPaymentProcessor(PaymentProcessor): 61 | 62 | def __init__(self, security_code): 63 | self.security_code = security_code 64 | 65 | def pay(self, order): 66 | print("Processing credit payment type") 67 | print(f"Verifying security code: {self.security_code}") 68 | order.status = "paid" 69 | 70 | 71 | class PaypalPaymentProcessor(PaymentProcessorSMS): 72 | 73 | def __init__(self, email_address): 74 | self.email_address = email_address 75 | self.verified = False 76 | 77 | def auth_sms(self, code): 78 | print(f"Verifying SMS code {code}") 79 | self.verified = True 80 | 81 | def pay(self, order): 82 | if not self.verified: 83 | raise Exception("Not authorized") 84 | print("Processing paypal payment type") 85 | print(f"Using email address: {self.email_address}") 86 | order.status = "paid" 87 | 88 | 89 | order = Order() 90 | order.add_item("Keyboard", 1, 50) 91 | order.add_item("SSD", 1, 150) 92 | order.add_item("USB cable", 2, 5) 93 | 94 | print(order.total_price()) 95 | processor = PaypalPaymentProcessor("hi@arjancodes.com") 96 | processor.auth_sms(465839) 97 | processor.pay(order) 98 | -------------------------------------------------------------------------------- /9 - solid/interface-segregation-before.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | 3 | 4 | class Order: 5 | 6 | def __init__(self): 7 | self.items = [] 8 | self.quantities = [] 9 | self.prices = [] 10 | self.status = "open" 11 | 12 | def add_item(self, name, quantity, price): 13 | self.items.append(name) 14 | self.quantities.append(quantity) 15 | self.prices.append(price) 16 | 17 | def total_price(self): 18 | total = 0 19 | for i in range(len(self.prices)): 20 | total += self.quantities[i] * self.prices[i] 21 | return total 22 | 23 | 24 | class PaymentProcessor(ABC): 25 | 26 | @abstractmethod 27 | def auth_sms(self, code): 28 | pass 29 | 30 | @abstractmethod 31 | def pay(self, order): 32 | pass 33 | 34 | 35 | class DebitPaymentProcessor(PaymentProcessor): 36 | 37 | def __init__(self, security_code): 38 | self.security_code = security_code 39 | self.verified = False 40 | 41 | def auth_sms(self, code): 42 | print(f"Verifying SMS code {code}") 43 | self.verified = True 44 | 45 | def pay(self, order): 46 | if not self.verified: 47 | raise Exception("Not authorized") 48 | print("Processing debit payment type") 49 | print(f"Verifying security code: {self.security_code}") 50 | order.status = "paid" 51 | 52 | 53 | class CreditPaymentProcessor(PaymentProcessor): 54 | 55 | def __init__(self, security_code): 56 | self.security_code = security_code 57 | 58 | def auth_sms(self, code): 59 | raise Exception("Credit card payments don't support SMS code authorization.") 60 | 61 | def pay(self, order): 62 | print("Processing credit payment type") 63 | print(f"Verifying security code: {self.security_code}") 64 | order.status = "paid" 65 | 66 | 67 | class PaypalPaymentProcessor(PaymentProcessor): 68 | 69 | def __init__(self, email_address): 70 | self.email_address = email_address 71 | self.verified = False 72 | 73 | def auth_sms(self, code): 74 | print(f"Verifying SMS code {code}") 75 | self.verified = True 76 | 77 | def pay(self, order): 78 | if not self.verified: 79 | raise Exception("Not authorized") 80 | print("Processing paypal payment type") 81 | print(f"Using email address: {self.email_address}") 82 | order.status = "paid" 83 | 84 | 85 | order = Order() 86 | order.add_item("Keyboard", 1, 50) 87 | order.add_item("SSD", 1, 150) 88 | order.add_item("USB cable", 2, 5) 89 | 90 | print(order.total_price()) 91 | processor = DebitPaymentProcessor("2349875") 92 | processor.auth_sms(465839) 93 | processor.pay(order) 94 | -------------------------------------------------------------------------------- /9 - solid/liskov-substitution-after.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | 3 | 4 | class Order: 5 | 6 | def __init__(self): 7 | self.items = [] 8 | self.quantities = [] 9 | self.prices = [] 10 | self.status = "open" 11 | 12 | def add_item(self, name, quantity, price): 13 | self.items.append(name) 14 | self.quantities.append(quantity) 15 | self.prices.append(price) 16 | 17 | def total_price(self): 18 | total = 0 19 | for i in range(len(self.prices)): 20 | total += self.quantities[i] * self.prices[i] 21 | return total 22 | 23 | 24 | class PaymentProcessor(ABC): 25 | 26 | @abstractmethod 27 | def pay(self, order): 28 | pass 29 | 30 | 31 | class DebitPaymentProcessor(PaymentProcessor): 32 | 33 | def __init__(self, security_code): 34 | self.security_code = security_code 35 | 36 | def pay(self, order): 37 | print("Processing debit payment type") 38 | print(f"Verifying security code: {self.security_code}") 39 | order.status = "paid" 40 | 41 | 42 | class CreditPaymentProcessor(PaymentProcessor): 43 | 44 | def __init__(self, security_code): 45 | self.security_code = security_code 46 | 47 | def pay(self, order): 48 | print("Processing credit payment type") 49 | print(f"Verifying security code: {self.security_code}") 50 | order.status = "paid" 51 | 52 | 53 | class PaypalPaymentProcessor(PaymentProcessor): 54 | 55 | def __init__(self, email_address): 56 | self.email_address = email_address 57 | 58 | def pay(self, order): 59 | print("Processing paypal payment type") 60 | print(f"Using email address: {self.email_address}") 61 | order.status = "paid" 62 | 63 | 64 | order = Order() 65 | order.add_item("Keyboard", 1, 50) 66 | order.add_item("SSD", 1, 150) 67 | order.add_item("USB cable", 2, 5) 68 | 69 | print(order.total_price()) 70 | processor = PaypalPaymentProcessor("hi@arjancodes.com") 71 | processor.pay(order) 72 | -------------------------------------------------------------------------------- /9 - solid/liskov-substitution-before.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | 3 | 4 | class Order: 5 | 6 | def __init__(self): 7 | self.items = [] 8 | self.quantities = [] 9 | self.prices = [] 10 | self.status = "open" 11 | 12 | def add_item(self, name, quantity, price): 13 | self.items.append(name) 14 | self.quantities.append(quantity) 15 | self.prices.append(price) 16 | 17 | def total_price(self): 18 | total = 0 19 | for i in range(len(self.prices)): 20 | total += self.quantities[i] * self.prices[i] 21 | return total 22 | 23 | 24 | class PaymentProcessor(ABC): 25 | 26 | @abstractmethod 27 | def pay(self, order, security_code): 28 | pass 29 | 30 | 31 | class DebitPaymentProcessor(PaymentProcessor): 32 | def pay(self, order, security_code): 33 | print("Processing debit payment type") 34 | print(f"Verifying security code: {security_code}") 35 | order.status = "paid" 36 | 37 | 38 | class CreditPaymentProcessor(PaymentProcessor): 39 | def pay(self, order, security_code): 40 | print("Processing credit payment type") 41 | print(f"Verifying security code: {security_code}") 42 | order.status = "paid" 43 | 44 | 45 | class PaypalPaymentProcessor(PaymentProcessor): 46 | def pay(self, order, security_code): 47 | print("Processing paypal payment type") 48 | print(f"Using email address: {security_code}") 49 | order.status = "paid" 50 | 51 | 52 | order = Order() 53 | order.add_item("Keyboard", 1, 50) 54 | order.add_item("SSD", 1, 150) 55 | order.add_item("USB cable", 2, 5) 56 | 57 | print(order.total_price()) 58 | processor = PaypalPaymentProcessor() 59 | processor.pay(order, "hi@arjancodes.com") 60 | -------------------------------------------------------------------------------- /9 - solid/open-closed-after.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | 3 | 4 | class Order: 5 | 6 | def __init__(self): 7 | self.items = [] 8 | self.quantities = [] 9 | self.prices = [] 10 | self.status = "open" 11 | 12 | def add_item(self, name, quantity, price): 13 | self.items.append(name) 14 | self.quantities.append(quantity) 15 | self.prices.append(price) 16 | 17 | def total_price(self): 18 | total = 0 19 | for i in range(len(self.prices)): 20 | total += self.quantities[i] * self.prices[i] 21 | return total 22 | 23 | 24 | class PaymentProcessor(ABC): 25 | 26 | @abstractmethod 27 | def pay(self, order, security_code): 28 | pass 29 | 30 | 31 | class DebitPaymentProcessor(PaymentProcessor): 32 | def pay(self, order, security_code): 33 | print("Processing debit payment type") 34 | print(f"Verifying security code: {security_code}") 35 | order.status = "paid" 36 | 37 | 38 | class CreditPaymentProcessor(PaymentProcessor): 39 | def pay(self, order, security_code): 40 | print("Processing credit payment type") 41 | print(f"Verifying security code: {security_code}") 42 | order.status = "paid" 43 | 44 | 45 | order = Order() 46 | order.add_item("Keyboard", 1, 50) 47 | order.add_item("SSD", 1, 150) 48 | order.add_item("USB cable", 2, 5) 49 | 50 | print(order.total_price()) 51 | processor = DebitPaymentProcessor() 52 | processor.pay(order, "0372846") 53 | -------------------------------------------------------------------------------- /9 - solid/open-closed-before.py: -------------------------------------------------------------------------------- 1 | class Order: 2 | 3 | def __init__(self): 4 | self.items = [] 5 | self.quantities = [] 6 | self.prices = [] 7 | self.status = "open" 8 | 9 | def add_item(self, name, quantity, price): 10 | self.items.append(name) 11 | self.quantities.append(quantity) 12 | self.prices.append(price) 13 | 14 | def total_price(self): 15 | total = 0 16 | for i in range(len(self.prices)): 17 | total += self.quantities[i] * self.prices[i] 18 | return total 19 | 20 | 21 | class PaymentProcessor: 22 | def pay_debit(self, order, security_code): 23 | print("Processing debit payment type") 24 | print(f"Verifying security code: {security_code}") 25 | order.status = "paid" 26 | 27 | def pay_credit(self, order, security_code): 28 | print("Processing credit payment type") 29 | print(f"Verifying security code: {security_code}") 30 | order.status = "paid" 31 | 32 | 33 | order = Order() 34 | order.add_item("Keyboard", 1, 50) 35 | order.add_item("SSD", 1, 150) 36 | order.add_item("USB cable", 2, 5) 37 | 38 | print(order.total_price()) 39 | processor = PaymentProcessor() 40 | processor.pay_debit(order, "0372846") 41 | -------------------------------------------------------------------------------- /9 - solid/single-responsibility-after.py: -------------------------------------------------------------------------------- 1 | class Order: 2 | 3 | def __init__(self): 4 | self.items = [] 5 | self.quantities = [] 6 | self.prices = [] 7 | self.status = "open" 8 | 9 | def add_item(self, name, quantity, price): 10 | self.items.append(name) 11 | self.quantities.append(quantity) 12 | self.prices.append(price) 13 | 14 | def total_price(self): 15 | total = 0 16 | for i in range(len(self.prices)): 17 | total += self.quantities[i] * self.prices[i] 18 | return total 19 | 20 | 21 | class PaymentProcessor: 22 | def pay_debit(self, order, security_code): 23 | print("Processing debit payment type") 24 | print(f"Verifying security code: {security_code}") 25 | order.status = "paid" 26 | 27 | def pay_credit(self, order, security_code): 28 | print("Processing credit payment type") 29 | print(f"Verifying security code: {security_code}") 30 | order.status = "paid" 31 | 32 | 33 | order = Order() 34 | order.add_item("Keyboard", 1, 50) 35 | order.add_item("SSD", 1, 150) 36 | order.add_item("USB cable", 2, 5) 37 | 38 | print(order.total_price()) 39 | processor = PaymentProcessor() 40 | processor.pay_debit(order, "0372846") 41 | -------------------------------------------------------------------------------- /9 - solid/single-responsibility-before.py: -------------------------------------------------------------------------------- 1 | class Order: 2 | 3 | def __init__(self): 4 | self.items = [] 5 | self.quantities = [] 6 | self.prices = [] 7 | self.status = "open" 8 | 9 | def add_item(self, name, quantity, price): 10 | self.items.append(name) 11 | self.quantities.append(quantity) 12 | self.prices.append(price) 13 | 14 | def total_price(self): 15 | total = 0 16 | for i in range(len(self.prices)): 17 | total += self.quantities[i] * self.prices[i] 18 | return total 19 | 20 | def pay(self, payment_type, security_code): 21 | if payment_type == "debit": 22 | print("Processing debit payment type") 23 | print(f"Verifying security code: {security_code}") 24 | self.status = "paid" 25 | elif payment_type == "credit": 26 | print("Processing credit payment type") 27 | print(f"Verifying security code: {security_code}") 28 | self.status = "paid" 29 | else: 30 | raise Exception(f"Unknown payment type: {payment_type}") 31 | 32 | 33 | order = Order() 34 | order.add_item("Keyboard", 1, 50) 35 | order.add_item("SSD", 1, 150) 36 | order.add_item("USB cable", 2, 5) 37 | 38 | print(order.total_price()) 39 | order.pay("debit", "0372846") 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 egges 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 | # Write Better Python Code 2 | 3 | This repository contains the code examples used in my Write Better Python Code series published on YouTube: 4 | 5 | https://www.youtube.com/playlist?list=PLC0nd42SBTaNuP4iB4L6SJlMaHE71FG6N 6 | -------------------------------------------------------------------------------- /better-rust/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "better-rust" 7 | version = "0.1.0" 8 | 9 | [[package]] 10 | name = "cfg-if" 11 | version = "1.0.0" 12 | source = "registry+https://github.com/rust-lang/crates.io-index" 13 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 14 | 15 | [[package]] 16 | name = "coupling_and_cohesion" 17 | version = "0.1.0" 18 | dependencies = [ 19 | "macros", 20 | "rand", 21 | ] 22 | 23 | [[package]] 24 | name = "getrandom" 25 | version = "0.2.14" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" 28 | dependencies = [ 29 | "cfg-if", 30 | "libc", 31 | "wasi", 32 | ] 33 | 34 | [[package]] 35 | name = "libc" 36 | version = "0.2.153" 37 | source = "registry+https://github.com/rust-lang/crates.io-index" 38 | checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" 39 | 40 | [[package]] 41 | name = "macros" 42 | version = "0.1.0" 43 | dependencies = [ 44 | "quote", 45 | "syn", 46 | ] 47 | 48 | [[package]] 49 | name = "ppv-lite86" 50 | version = "0.2.17" 51 | source = "registry+https://github.com/rust-lang/crates.io-index" 52 | checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" 53 | 54 | [[package]] 55 | name = "proc-macro2" 56 | version = "1.0.81" 57 | source = "registry+https://github.com/rust-lang/crates.io-index" 58 | checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" 59 | dependencies = [ 60 | "unicode-ident", 61 | ] 62 | 63 | [[package]] 64 | name = "quote" 65 | version = "1.0.36" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" 68 | dependencies = [ 69 | "proc-macro2", 70 | ] 71 | 72 | [[package]] 73 | name = "rand" 74 | version = "0.8.5" 75 | source = "registry+https://github.com/rust-lang/crates.io-index" 76 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 77 | dependencies = [ 78 | "libc", 79 | "rand_chacha", 80 | "rand_core", 81 | ] 82 | 83 | [[package]] 84 | name = "rand_chacha" 85 | version = "0.3.1" 86 | source = "registry+https://github.com/rust-lang/crates.io-index" 87 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 88 | dependencies = [ 89 | "ppv-lite86", 90 | "rand_core", 91 | ] 92 | 93 | [[package]] 94 | name = "rand_core" 95 | version = "0.6.4" 96 | source = "registry+https://github.com/rust-lang/crates.io-index" 97 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 98 | dependencies = [ 99 | "getrandom", 100 | ] 101 | 102 | [[package]] 103 | name = "solid" 104 | version = "0.1.0" 105 | dependencies = [ 106 | "macros", 107 | ] 108 | 109 | [[package]] 110 | name = "syn" 111 | version = "1.0.109" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 114 | dependencies = [ 115 | "proc-macro2", 116 | "quote", 117 | "unicode-ident", 118 | ] 119 | 120 | [[package]] 121 | name = "unicode-ident" 122 | version = "1.0.12" 123 | source = "registry+https://github.com/rust-lang/crates.io-index" 124 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 125 | 126 | [[package]] 127 | name = "wasi" 128 | version = "0.11.0+wasi-snapshot-preview1" 129 | source = "registry+https://github.com/rust-lang/crates.io-index" 130 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 131 | -------------------------------------------------------------------------------- /better-rust/Cargo.toml: -------------------------------------------------------------------------------- 1 | workspace = { members = ["solid", "macros", "coupling_and_cohesion"] } 2 | [package] 3 | name = "better-rust" 4 | version = "0.1.0" 5 | edition = "2021" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | -------------------------------------------------------------------------------- /better-rust/coupling_and_cohesion/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "coupling_and_cohesion" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | rand = "0.8.5" 10 | macros = { path = "../macros" } 11 | -------------------------------------------------------------------------------- /better-rust/coupling_and_cohesion/src/coupling_cohesion_after.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use rand::{self, Rng}; 3 | 4 | fn generate_random_string(length: usize, digits: bool) -> String { 5 | rand::thread_rng() 6 | .sample_iter(rand::distributions::Alphanumeric) 7 | .map(char::from) 8 | .filter(|c| if digits { c.is_digit(10) } else { !c.is_digit(10) }) 9 | .map(|c| c.to_ascii_uppercase()) 10 | .take(length) 11 | .collect() 12 | } 13 | 14 | #[derive(Clone)] 15 | pub struct VehicleInfo { 16 | pub brand: String, 17 | pub electric: bool, 18 | pub catalogue_price: u32, 19 | } 20 | 21 | impl VehicleInfo { 22 | pub fn new(brand: &str, electric: bool, catalogue_price: u32) -> Self { 23 | Self { brand: brand.to_string(), electric, catalogue_price } 24 | } 25 | 26 | pub fn compute_tax(&self) -> f32 { 27 | let mut tax_percentage = 0.05; 28 | if self.electric { 29 | tax_percentage = 0.02; 30 | } 31 | 32 | tax_percentage * self.catalogue_price as f32 33 | } 34 | 35 | pub fn print(&self) { 36 | println!("Brand: {}", self.brand); 37 | println!("Payable tax: {:#?}", self.compute_tax()); 38 | } 39 | } 40 | 41 | pub struct Vehicle { 42 | pub id: String, 43 | pub license_plate: String, 44 | pub info: VehicleInfo, 45 | } 46 | 47 | impl Vehicle { 48 | pub fn new(id: &str, license_plate: String, info: VehicleInfo) -> Self { 49 | Self { id: id.to_string(), license_plate, info } 50 | } 51 | 52 | pub fn print(&self) { 53 | println!("Registration complete. Vehicle information:"); 54 | println!("Id: {}", self.id); 55 | println!("License plate: {}", self.license_plate); 56 | self.info.print(); 57 | } 58 | } 59 | pub struct VehicleRegistry { 60 | pub vehicle_info: HashMap, 61 | } 62 | 63 | impl VehicleRegistry { 64 | pub fn new() -> Self { 65 | let mut vehicle_registry = Self { vehicle_info: HashMap::new() }; 66 | 67 | vehicle_registry.add_vehicle_info("Tesla Model 3", true, 60000); 68 | vehicle_registry.add_vehicle_info("Volkswagen ID3", true, 35000); 69 | vehicle_registry.add_vehicle_info("BMW 5", false, 45000); 70 | vehicle_registry.add_vehicle_info("Tesla Model Y", true, 75000); 71 | 72 | vehicle_registry 73 | } 74 | 75 | fn add_vehicle_info(&mut self, brand: &str, electric: bool, catalogue_price: u32) { 76 | self.vehicle_info.insert(brand.to_string(), VehicleInfo::new(brand, electric, catalogue_price)); 77 | } 78 | 79 | pub fn generate_vehicle_id(&self, length: usize) -> String { 80 | generate_random_string(length, false) 81 | } 82 | 83 | pub fn generate_vehicle_license(&self, id: &str) -> String { 84 | let front = &id[0..2]; 85 | let middle = generate_random_string(2, true); 86 | let last = generate_random_string(2, false); 87 | 88 | format!("{front}-{middle}-{last}") 89 | } 90 | 91 | pub fn create_vehicle(&self, brand: &str) -> Vehicle { 92 | let id = self.generate_vehicle_id(12); 93 | let license_plate = self.generate_vehicle_license(&id); 94 | 95 | Vehicle::new(&id, license_plate, self.vehicle_info.get(brand).unwrap().clone()) 96 | } 97 | } 98 | 99 | pub struct Application; 100 | 101 | impl Application { 102 | pub fn register_vehicle(&self, brand: &str) { 103 | // create a registry instance 104 | let registry = VehicleRegistry::new(); 105 | 106 | let vehicle = registry.create_vehicle(brand); 107 | 108 | // print out the vehicle information 109 | vehicle.print(); 110 | } 111 | } -------------------------------------------------------------------------------- /better-rust/coupling_and_cohesion/src/coupling_cohesion_before.rs: -------------------------------------------------------------------------------- 1 | use rand::{self, Rng}; 2 | 3 | fn generate_random_string(length: usize, digits: bool) -> String { 4 | rand::thread_rng() 5 | .sample_iter(rand::distributions::Alphanumeric) 6 | .map(char::from) 7 | .filter(|c| if digits { c.is_digit(10) } else { !c.is_digit(10) }) 8 | .map(|c| c.to_ascii_uppercase()) 9 | .take(length) 10 | .collect() 11 | } 12 | 13 | pub struct VehicleRegistry; 14 | 15 | impl VehicleRegistry { 16 | pub fn generate_vehicle_id(&self, length: usize) -> String { 17 | generate_random_string(length, false) 18 | } 19 | 20 | pub fn generate_vehicle_license(&self, id: &str) -> String { 21 | let front = &id[0..2]; 22 | let middle = generate_random_string(2, true); 23 | let last = generate_random_string(2, false); 24 | 25 | format!("{front}-{middle}-{last}") 26 | } 27 | } 28 | 29 | pub struct Application; 30 | 31 | impl Application { 32 | pub fn register_vehicle(&self, brand: &str) { 33 | // create a registry instance 34 | let registry = VehicleRegistry; 35 | 36 | // generate a vehicle id of length 12 37 | let vehicle_id = registry.generate_vehicle_id(12); 38 | 39 | // now generate a license plate for the vehicle 40 | // using the first two characters of the vehicle id 41 | let license_plate = registry.generate_vehicle_license(&vehicle_id); 42 | 43 | // compute the catalogue price 44 | let mut catalogue_price = 0; 45 | 46 | if brand == "Tesla Model 3" { 47 | catalogue_price = 60000; 48 | } else if brand == "Volkswagen ID3" { 49 | catalogue_price = 35000; 50 | } else if brand == "BMW 5" { 51 | catalogue_price = 45000; 52 | } 53 | 54 | // compute the tax percentage (default 5% of the catalogue price, except for electric cars where it is 2%) 55 | let mut tax_percentage = 0.05; 56 | 57 | if brand == "Tesla Model 3" || brand == "Volkswagen ID3" { 58 | tax_percentage = 0.02; 59 | } 60 | 61 | // compute the payable tax 62 | let payable_tax = tax_percentage * catalogue_price as f32; 63 | 64 | // print out the vehicle registration information 65 | println!("Registration complete. Vehicle information:"); 66 | println!("Brand: {brand}"); 67 | println!("Id: {vehicle_id}"); 68 | println!("License plate: {license_plate}"); 69 | println!("Payable tax: {payable_tax:?}"); 70 | } 71 | } -------------------------------------------------------------------------------- /better-rust/coupling_and_cohesion/src/main.rs: -------------------------------------------------------------------------------- 1 | mod coupling_cohesion_before; 2 | mod coupling_cohesion_after; 3 | 4 | fn main() { 5 | coupling_cohesion_before(); 6 | 7 | coupling_cohesion_after(); 8 | } 9 | 10 | #[macros::example] 11 | fn coupling_cohesion_before() { 12 | use coupling_cohesion_before::*; 13 | 14 | let app = Application; 15 | 16 | app.register_vehicle("Volkswagen ID3"); 17 | } 18 | 19 | #[macros::example] 20 | fn coupling_cohesion_after() { 21 | use coupling_cohesion_after::*; 22 | 23 | let app = Application; 24 | 25 | app.register_vehicle("Volkswagen ID3"); 26 | } 27 | -------------------------------------------------------------------------------- /better-rust/macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "macros" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [lib] 9 | proc-macro = true 10 | 11 | [dependencies] 12 | syn = { version = "*", features = ["full"]} 13 | quote = "1.0" -------------------------------------------------------------------------------- /better-rust/macros/src/lib.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | use quote::quote; 3 | use syn; 4 | 5 | #[proc_macro_attribute] 6 | pub fn example(_attr: TokenStream, item: TokenStream) -> TokenStream { 7 | let input = syn::parse_macro_input!(item as syn::ItemFn); 8 | let ident = input.sig.ident; 9 | let name = ident.clone().to_string(); 10 | let block = input.block; 11 | 12 | let gen = quote! { 13 | fn #ident() { 14 | println!("running `{}`:\n", #name); 15 | #block; 16 | println!("-------------\n"); 17 | } 18 | }; 19 | 20 | gen.into() 21 | } -------------------------------------------------------------------------------- /better-rust/solid/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "solid" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | macros ={ path = "../macros" } -------------------------------------------------------------------------------- /better-rust/solid/src/dependency_inversion_after.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | 3 | pub struct Order<'a> { 4 | pub items: Vec<&'a str>, 5 | pub quantities: Vec, 6 | pub prices: Vec, 7 | pub status: &'a str, 8 | } 9 | 10 | impl<'a> Order<'a> { 11 | pub fn new() -> Self { 12 | Self { items: vec![], quantities: vec![], prices: vec![], status: "open" } 13 | } 14 | 15 | pub fn add_item(&mut self, name: &'a str, quantity: u32, price: f32) { 16 | self.items.push(name); 17 | self.quantities.push(quantity); 18 | self.prices.push(price); 19 | } 20 | 21 | pub fn total_price(&self) -> f32 { 22 | let mut total: f32 = 0.0; 23 | 24 | for (i, price) in self.prices.iter().enumerate() { 25 | total += self.quantities[i] as f32 * price; 26 | } 27 | 28 | total 29 | } 30 | } 31 | 32 | pub trait Authorizer { 33 | fn is_authorized(&self) -> bool; 34 | } 35 | 36 | pub struct SMSAuthorizer { 37 | pub authorized: bool, 38 | } 39 | 40 | impl SMSAuthorizer { 41 | pub fn new() -> Self { 42 | Self { authorized: false } 43 | } 44 | 45 | pub fn verify_code(&mut self, code: u32) { 46 | println!("Verifying SMS code {code}"); 47 | self.authorized = true; 48 | } 49 | } 50 | 51 | impl Authorizer for SMSAuthorizer { 52 | fn is_authorized(&self) -> bool { 53 | self.authorized 54 | } 55 | } 56 | 57 | pub struct AuthorizerRobot { 58 | pub authorized: bool, 59 | } 60 | 61 | impl AuthorizerRobot { 62 | pub fn new() -> Self { 63 | Self { authorized: false } 64 | } 65 | 66 | pub fn not_a_robot(&mut self) { 67 | println!("Are you a robot?"); 68 | self.authorized = true; 69 | } 70 | } 71 | 72 | impl Authorizer for AuthorizerRobot { 73 | fn is_authorized(&self) -> bool { 74 | self.authorized 75 | } 76 | } 77 | 78 | pub trait PaymentProcessor { 79 | fn pay(&self, order: &mut Order); 80 | } 81 | 82 | pub struct DebitPaymentProcessor<'a> { 83 | pub security_code: &'a str, 84 | pub authorizer: &'a RefCell, 85 | } 86 | 87 | impl<'a> DebitPaymentProcessor<'a> { 88 | pub fn new(security_code: &'a str, authorizer: &'a RefCell) -> Self { 89 | Self { security_code, authorizer } 90 | } 91 | } 92 | 93 | impl PaymentProcessor for DebitPaymentProcessor<'_> { 94 | fn pay(&self, order: &mut Order) { 95 | if !self.authorizer.borrow().is_authorized() { 96 | panic!("Not authorized"); 97 | } 98 | 99 | println!("Processing debit payment type"); 100 | println!("Verifying security code: {}", {self.security_code}); 101 | order.status = "paid"; 102 | } 103 | } 104 | 105 | pub struct CreditPaymentProcessor<'a> { 106 | pub security_code: &'a str, 107 | } 108 | 109 | impl<'a> CreditPaymentProcessor<'a> { 110 | pub fn new(security_code: &'a str) -> Self { 111 | Self { security_code } 112 | } 113 | } 114 | 115 | impl PaymentProcessor for CreditPaymentProcessor<'_> { 116 | fn pay(&self, order: &mut Order) { 117 | println!("Processing credit payment type"); 118 | println!("Verifying security code: {}", {self.security_code}); 119 | order.status = "paid"; 120 | } 121 | } 122 | 123 | pub struct PaypalPaymentProcessor<'a> { 124 | pub email_address: &'a str, 125 | pub authorizer: &'a RefCell, 126 | } 127 | 128 | impl<'a> PaypalPaymentProcessor<'a> { 129 | pub fn new(email_address: &'a str, authorizer: &'a RefCell) -> Self { 130 | Self { email_address, authorizer } 131 | } 132 | } 133 | 134 | impl PaymentProcessor for PaypalPaymentProcessor<'_> { 135 | fn pay(&self, order: &mut Order) { 136 | if !self.authorizer.borrow().is_authorized() { 137 | panic!("Not authorized"); 138 | } 139 | 140 | println!("Processing paypal payment type"); 141 | println!("Using email address: {}", {self.email_address}); 142 | order.status = "paid"; 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /better-rust/solid/src/dependency_inversion_before.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | 3 | pub struct Order<'a> { 4 | pub items: Vec<&'a str>, 5 | pub quantities: Vec, 6 | pub prices: Vec, 7 | pub status: &'a str, 8 | } 9 | 10 | impl<'a> Order<'a> { 11 | pub fn new() -> Self { 12 | Self { items: vec![], quantities: vec![], prices: vec![], status: "open" } 13 | } 14 | 15 | pub fn add_item(&mut self, name: &'a str, quantity: u32, price: f32) { 16 | self.items.push(name); 17 | self.quantities.push(quantity); 18 | self.prices.push(price); 19 | } 20 | 21 | pub fn total_price(&self) -> f32 { 22 | let mut total: f32 = 0.0; 23 | 24 | for (i, price) in self.prices.iter().enumerate() { 25 | total += self.quantities[i] as f32 * price; 26 | } 27 | 28 | total 29 | } 30 | } 31 | 32 | pub struct SMSAuthorizer { 33 | pub authorized: bool, 34 | } 35 | 36 | impl SMSAuthorizer { 37 | pub fn new() -> Self { 38 | Self { authorized: false } 39 | } 40 | 41 | pub fn verify_code(&mut self, code: u32) { 42 | println!("Verifying SMS code {code}"); 43 | self.authorized = true; 44 | } 45 | 46 | fn is_authorized(&self) -> bool { 47 | self.authorized 48 | } 49 | } 50 | 51 | pub trait PaymentProcessor { 52 | fn pay(&self, order: &mut Order); 53 | } 54 | 55 | pub struct DebitPaymentProcessor<'a> { 56 | pub security_code: &'a str, 57 | pub authorizer: &'a RefCell, 58 | } 59 | 60 | impl<'a> DebitPaymentProcessor<'a> { 61 | pub fn new(security_code: &'a str, authorizer: &'a RefCell) -> Self { 62 | Self { security_code, authorizer } 63 | } 64 | } 65 | 66 | impl PaymentProcessor for DebitPaymentProcessor<'_> { 67 | fn pay(&self, order: &mut Order) { 68 | if !self.authorizer.borrow().is_authorized() { 69 | panic!("Not authorized"); 70 | } 71 | 72 | println!("Processing debit payment type"); 73 | println!("Verifying security code: {}", {self.security_code}); 74 | order.status = "paid"; 75 | } 76 | } 77 | 78 | pub struct CreditPaymentProcessor<'a> { 79 | pub security_code: &'a str, 80 | } 81 | 82 | impl<'a> CreditPaymentProcessor<'a> { 83 | pub fn new(security_code: &'a str) -> Self { 84 | Self { security_code } 85 | } 86 | } 87 | 88 | impl PaymentProcessor for CreditPaymentProcessor<'_> { 89 | fn pay(&self, order: &mut Order) { 90 | println!("Processing credit payment type"); 91 | println!("Verifying security code: {}", {self.security_code}); 92 | order.status = "paid"; 93 | } 94 | } 95 | 96 | pub struct PaypalPaymentProcessor<'a> { 97 | pub email_address: &'a str, 98 | pub authorizer: &'a RefCell, 99 | } 100 | 101 | impl<'a> PaypalPaymentProcessor<'a> { 102 | pub fn new(email_address: &'a str, authorizer: &'a RefCell) -> Self { 103 | Self { email_address, authorizer } 104 | } 105 | } 106 | 107 | impl PaymentProcessor for PaypalPaymentProcessor<'_> { 108 | fn pay(&self, order: &mut Order) { 109 | if !self.authorizer.borrow().is_authorized() { 110 | panic!("Not authorized"); 111 | } 112 | 113 | println!("Processing paypal payment type"); 114 | println!("Using email address: {}", {self.email_address}); 115 | order.status = "paid"; 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /better-rust/solid/src/interface_segregation_after.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | 3 | pub struct Order<'a> { 4 | pub items: Vec<&'a str>, 5 | pub quantities: Vec, 6 | pub prices: Vec, 7 | pub status: &'a str, 8 | } 9 | 10 | impl<'a> Order<'a> { 11 | pub fn new() -> Self { 12 | Self { items: vec![], quantities: vec![], prices: vec![], status: "open" } 13 | } 14 | 15 | pub fn add_item(&mut self, name: &'a str, quantity: u32, price: f32) { 16 | self.items.push(name); 17 | self.quantities.push(quantity); 18 | self.prices.push(price); 19 | } 20 | 21 | pub fn total_price(&self) -> f32 { 22 | let mut total: f32 = 0.0; 23 | 24 | for (i, price) in self.prices.iter().enumerate() { 25 | total += self.quantities[i] as f32 * price; 26 | } 27 | 28 | total 29 | } 30 | } 31 | 32 | pub struct SMSAuthorizer { 33 | pub authorized: bool, 34 | } 35 | 36 | impl SMSAuthorizer { 37 | pub fn new() -> Self { 38 | Self { authorized: false } 39 | } 40 | 41 | pub fn verify_code(&mut self, code: u32) { 42 | println!("Verifying SMS code {code}"); 43 | self.authorized = true; 44 | } 45 | 46 | fn is_authorized(&self) -> bool { 47 | self.authorized 48 | } 49 | } 50 | 51 | pub trait PaymentProcessor { 52 | fn pay(&self, order: &mut Order); 53 | } 54 | 55 | pub struct DebitPaymentProcessor<'a> { 56 | pub security_code: &'a str, 57 | pub authorizer: &'a RefCell, 58 | } 59 | 60 | impl<'a> DebitPaymentProcessor<'a> { 61 | pub fn new(security_code: &'a str, authorizer: &'a RefCell) -> Self { 62 | Self { security_code, authorizer } 63 | } 64 | } 65 | 66 | impl PaymentProcessor for DebitPaymentProcessor<'_> { 67 | fn pay(&self, order: &mut Order) { 68 | if !self.authorizer.borrow().is_authorized() { 69 | panic!("Not authorized"); 70 | } 71 | 72 | println!("Processing debit payment type"); 73 | println!("Verifying security code: {}", {self.security_code}); 74 | order.status = "paid"; 75 | } 76 | } 77 | 78 | pub struct CreditPaymentProcessor<'a> { 79 | pub security_code: &'a str, 80 | } 81 | 82 | impl<'a> CreditPaymentProcessor<'a> { 83 | pub fn new(security_code: &'a str) -> Self { 84 | Self { security_code } 85 | } 86 | } 87 | 88 | impl PaymentProcessor for CreditPaymentProcessor<'_> { 89 | fn pay(&self, order: &mut Order) { 90 | println!("Processing credit payment type"); 91 | println!("Verifying security code: {}", {self.security_code}); 92 | order.status = "paid"; 93 | } 94 | } 95 | 96 | pub struct PaypalPaymentProcessor<'a> { 97 | pub email_address: &'a str, 98 | pub authorizer: &'a RefCell, 99 | } 100 | 101 | impl<'a> PaypalPaymentProcessor<'a> { 102 | pub fn new(email_address: &'a str, authorizer: &'a RefCell) -> Self { 103 | Self { email_address, authorizer } 104 | } 105 | } 106 | 107 | impl PaymentProcessor for PaypalPaymentProcessor<'_> { 108 | fn pay(&self, order: &mut Order) { 109 | if !self.authorizer.borrow().is_authorized() { 110 | panic!("Not authorized"); 111 | } 112 | 113 | println!("Processing paypal payment type"); 114 | println!("Using email address: {}", {self.email_address}); 115 | order.status = "paid"; 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /better-rust/solid/src/interface_segregation_before.rs: -------------------------------------------------------------------------------- 1 | 2 | pub struct Order<'a> { 3 | pub items: Vec<&'a str>, 4 | pub quantities: Vec, 5 | pub prices: Vec, 6 | pub status: &'a str, 7 | } 8 | 9 | impl<'a> Order<'a> { 10 | pub fn new() -> Self { 11 | Self { items: vec![], quantities: vec![], prices: vec![], status: "open" } 12 | } 13 | 14 | pub fn add_item(&mut self, name: &'a str, quantity: u32, price: f32) { 15 | self.items.push(name); 16 | self.quantities.push(quantity); 17 | self.prices.push(price); 18 | } 19 | 20 | pub fn total_price(&self) -> f32 { 21 | let mut total: f32 = 0.0; 22 | 23 | for (i, price) in self.prices.iter().enumerate() { 24 | total += self.quantities[i] as f32 * price; 25 | } 26 | 27 | total 28 | } 29 | } 30 | 31 | pub trait PaymentProcessor { 32 | fn auth_sms(&mut self, code: u32); 33 | 34 | fn pay(&self, order: &mut Order); 35 | } 36 | 37 | pub struct DebitPaymentProcessor<'a> { 38 | pub security_code: &'a str, 39 | pub verified: bool, 40 | } 41 | 42 | impl<'a> DebitPaymentProcessor<'a> { 43 | pub fn new(security_code: &'a str) -> Self { 44 | Self { security_code, verified: false } 45 | } 46 | } 47 | 48 | impl PaymentProcessor for DebitPaymentProcessor<'_> { 49 | fn auth_sms(&mut self, code: u32) { 50 | println!("Verifying SMS code {code}"); 51 | self.verified = true; 52 | } 53 | 54 | fn pay(&self, order: &mut Order) { 55 | println!("Processing debit payment type"); 56 | println!("Verifying security code: {}", {self.security_code}); 57 | order.status = "paid"; 58 | } 59 | } 60 | 61 | pub struct CreditPaymentProcessor<'a> { 62 | pub security_code: &'a str, 63 | } 64 | 65 | impl<'a> CreditPaymentProcessor<'a> { 66 | pub fn new(security_code: &'a str) -> Self { 67 | Self { security_code } 68 | } 69 | } 70 | 71 | impl PaymentProcessor for CreditPaymentProcessor<'_> { 72 | fn auth_sms(&mut self, _code: u32) { 73 | panic!("Credit card payments don't support SMS code authorization."); 74 | } 75 | 76 | fn pay(&self, order: &mut Order) { 77 | println!("Processing credit payment type"); 78 | println!("Verifying security code: {}", {self.security_code}); 79 | order.status = "paid"; 80 | } 81 | } 82 | 83 | pub struct PaypalPaymentProcessor<'a> { 84 | pub email_address: &'a str, 85 | pub verified: bool, 86 | } 87 | 88 | impl<'a> PaypalPaymentProcessor<'a> { 89 | pub fn new(email_address: &'a str) -> Self { 90 | Self { email_address, verified: false } 91 | } 92 | } 93 | 94 | impl PaymentProcessor for PaypalPaymentProcessor<'_> { 95 | fn auth_sms(&mut self, code: u32) { 96 | println!("Verifying SMS code {code}"); 97 | self.verified = true; 98 | } 99 | 100 | fn pay(&self, order: &mut Order) { 101 | println!("Processing paypal payment type"); 102 | println!("Using email address: {}", {self.email_address}); 103 | order.status = "paid"; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /better-rust/solid/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod single_responsibility_before; 2 | pub mod single_responsibility_after; 3 | pub mod open_closed_before; 4 | pub mod open_closed_after; 5 | pub mod liskov_substitution_before; 6 | pub mod liskov_substitution_after; 7 | pub mod interface_segregation_before; 8 | pub mod interface_segregation_after; 9 | pub mod dependency_inversion_before; 10 | pub mod dependency_inversion_after; -------------------------------------------------------------------------------- /better-rust/solid/src/liskov_substitution_after.rs: -------------------------------------------------------------------------------- 1 | 2 | pub struct Order<'a> { 3 | pub items: Vec<&'a str>, 4 | pub quantities: Vec, 5 | pub prices: Vec, 6 | pub status: &'a str, 7 | } 8 | 9 | impl<'a> Order<'a> { 10 | pub fn new() -> Self { 11 | Self { items: vec![], quantities: vec![], prices: vec![], status: "open" } 12 | } 13 | 14 | pub fn add_item(&mut self, name: &'a str, quantity: u32, price: f32) { 15 | self.items.push(name); 16 | self.quantities.push(quantity); 17 | self.prices.push(price); 18 | } 19 | 20 | pub fn total_price(&self) -> f32 { 21 | let mut total: f32 = 0.0; 22 | 23 | for (i, price) in self.prices.iter().enumerate() { 24 | total += self.quantities[i] as f32 * price; 25 | } 26 | 27 | total 28 | } 29 | } 30 | 31 | pub trait PaymentProcessor { 32 | fn pay(&self, order: &mut Order); 33 | } 34 | 35 | pub struct DebitPaymentProcessor<'a> { 36 | pub security_code: &'a str, 37 | } 38 | 39 | impl<'a> DebitPaymentProcessor<'a> { 40 | pub fn new(security_code: &'a str) -> Self { 41 | Self { security_code } 42 | } 43 | } 44 | 45 | impl PaymentProcessor for DebitPaymentProcessor<'_> { 46 | fn pay(&self, order: &mut Order) { 47 | println!("Processing debit payment type"); 48 | println!("Verifying security code: {}", {self.security_code}); 49 | order.status = "paid"; 50 | } 51 | } 52 | 53 | pub struct CreditPaymentProcessor<'a> { 54 | pub security_code: &'a str, 55 | } 56 | 57 | impl<'a> CreditPaymentProcessor<'a> { 58 | pub fn new(security_code: &'a str) -> Self { 59 | Self { security_code } 60 | } 61 | } 62 | 63 | impl PaymentProcessor for CreditPaymentProcessor<'_> { 64 | fn pay(&self, order: &mut Order) { 65 | println!("Processing credit payment type"); 66 | println!("Verifying security code: {}", {self.security_code}); 67 | order.status = "paid"; 68 | } 69 | } 70 | 71 | pub struct PaypalPaymentProcessor<'a> { 72 | pub email_address: &'a str, 73 | } 74 | 75 | impl<'a> PaypalPaymentProcessor<'a> { 76 | pub fn new(email_address: &'a str) -> Self { 77 | Self { email_address } 78 | } 79 | } 80 | 81 | impl PaymentProcessor for PaypalPaymentProcessor<'_> { 82 | fn pay(&self, order: &mut Order) { 83 | println!("Processing paypal payment type"); 84 | println!("Using email address: {}", {self.email_address}); 85 | order.status = "paid"; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /better-rust/solid/src/liskov_substitution_before.rs: -------------------------------------------------------------------------------- 1 | 2 | pub struct Order<'a> { 3 | pub items: Vec<&'a str>, 4 | pub quantities: Vec, 5 | pub prices: Vec, 6 | pub status: &'a str, 7 | } 8 | 9 | impl<'a> Order<'a> { 10 | pub fn new() -> Self { 11 | Self { items: vec![], quantities: vec![], prices: vec![], status: "open" } 12 | } 13 | 14 | pub fn add_item(&mut self, name: &'a str, quantity: u32, price: f32) { 15 | self.items.push(name); 16 | self.quantities.push(quantity); 17 | self.prices.push(price); 18 | } 19 | 20 | pub fn total_price(&self) -> f32 { 21 | let mut total: f32 = 0.0; 22 | 23 | for (i, price) in self.prices.iter().enumerate() { 24 | total += self.quantities[i] as f32 * price; 25 | } 26 | 27 | total 28 | } 29 | } 30 | 31 | pub trait PaymentProcessor { 32 | fn pay(&self, order: &mut Order, security_code: &str); 33 | } 34 | 35 | pub struct DebitPaymentProcessor; 36 | 37 | impl PaymentProcessor for DebitPaymentProcessor { 38 | fn pay(&self, order: &mut Order, security_code: &str) { 39 | println!("Processing debit payment type"); 40 | println!("Verifying security code: {security_code}"); 41 | order.status = "paid"; 42 | } 43 | } 44 | 45 | pub struct CreditPaymentProcessor; 46 | 47 | impl PaymentProcessor for CreditPaymentProcessor { 48 | fn pay(&self, order: &mut Order, security_code: &str) { 49 | println!("Processing credit payment type"); 50 | println!("Verifying security code: {security_code}"); 51 | order.status = "paid"; 52 | } 53 | } 54 | 55 | pub struct PaypalPaymentProcessor; 56 | 57 | impl PaymentProcessor for PaypalPaymentProcessor { 58 | fn pay(&self, order: &mut Order, security_code: &str) { 59 | println!("Processing paypal payment type"); 60 | println!("Using email address: {security_code}"); 61 | order.status = "paid"; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /better-rust/solid/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | 3 | fn main() { 4 | single_responsibility_before(); 5 | 6 | single_responsibility_after(); 7 | 8 | open_closed(); 9 | 10 | liskov_substitution(); 11 | 12 | interface_segregation_before(); 13 | 14 | interface_segregation_after(); 15 | 16 | dependency_inversion(); 17 | } 18 | 19 | #[macros::example] 20 | fn single_responsibility_before() { 21 | use solid::single_responsibility_before::*; 22 | 23 | let mut order = Order::new(); 24 | 25 | order.add_item("Keyboard", 1, 50.0); 26 | order.add_item("SSD", 1, 150.0); 27 | order.add_item("USB cable", 2, 5.0); 28 | 29 | println!("{}", order.total_price()); 30 | order.pay("debit", "0372846"); 31 | } 32 | 33 | #[macros::example] 34 | fn single_responsibility_after() { 35 | use solid::single_responsibility_after::*; 36 | 37 | let mut order = Order::new(); 38 | 39 | order.add_item("Keyboard", 1, 50.0); 40 | order.add_item("SSD", 1, 150.0); 41 | order.add_item("USB cable", 2, 5.0); 42 | 43 | println!("{}", order.total_price()); 44 | 45 | let processor = PaymentProcessor {}; 46 | 47 | processor.pay_debit(&mut order, "0372846"); 48 | } 49 | 50 | #[macros::example] 51 | fn open_closed() { 52 | use solid::open_closed_after::*; 53 | 54 | let mut order = Order::new(); 55 | 56 | order.add_item("Keyboard", 1, 50.0); 57 | order.add_item("SSD", 1, 150.0); 58 | order.add_item("USB cable", 2, 5.0); 59 | 60 | println!("{}", order.total_price()); 61 | 62 | let processor = DebitPaymentProcessor {}; 63 | 64 | processor.pay(&mut order, "0372846"); 65 | } 66 | 67 | #[macros::example] 68 | fn liskov_substitution() { 69 | use solid::liskov_substitution_after::*; 70 | 71 | let mut order = Order::new(); 72 | 73 | order.add_item("Keyboard", 1, 50.0); 74 | order.add_item("SSD", 1, 150.0); 75 | order.add_item("USB cable", 2, 5.0); 76 | 77 | println!("{}", order.total_price()); 78 | 79 | let processor = PaypalPaymentProcessor::new("hi@arjancodes.com"); 80 | 81 | processor.pay(&mut order); 82 | } 83 | 84 | #[macros::example] 85 | fn interface_segregation_before() { 86 | use solid::interface_segregation_before::*; 87 | 88 | let mut order = Order::new(); 89 | 90 | order.add_item("Keyboard", 1, 50.0); 91 | order.add_item("SSD", 1, 150.0); 92 | order.add_item("USB cable", 2, 5.0); 93 | 94 | println!("{}", order.total_price()); 95 | 96 | let mut processor = DebitPaymentProcessor::new("2349875"); 97 | processor.auth_sms(465839); 98 | processor.pay(&mut order); 99 | } 100 | 101 | #[macros::example] 102 | fn interface_segregation_after() { 103 | use solid::interface_segregation_after::*; 104 | 105 | let mut order = Order::new(); 106 | 107 | order.add_item("Keyboard", 1, 50.0); 108 | order.add_item("SSD", 1, 150.0); 109 | order.add_item("USB cable", 2, 5.0); 110 | 111 | println!("{}", order.total_price()); 112 | 113 | let authorizer = RefCell::new(SMSAuthorizer::new()); 114 | 115 | let processor = PaypalPaymentProcessor::new("hi@arjancodes.com", &authorizer); 116 | authorizer.borrow_mut().verify_code(465839); 117 | processor.pay(&mut order); 118 | } 119 | 120 | #[macros::example] 121 | fn dependency_inversion() { 122 | use solid::dependency_inversion_after::*; 123 | 124 | let mut order = Order::new(); 125 | 126 | order.add_item("Keyboard", 1, 50.0); 127 | order.add_item("SSD", 1, 150.0); 128 | order.add_item("USB cable", 2, 5.0); 129 | 130 | println!("{}", order.total_price()); 131 | 132 | // let authorizer = RefCell::new(SMSAuthorizer::new()); 133 | let authorizer = RefCell::new(AuthorizerRobot::new()); 134 | 135 | let processor = PaypalPaymentProcessor::new("hi@arjancodes.com", &authorizer); 136 | // authorizer.borrow_mut().verify_code(465839); 137 | authorizer.borrow_mut().not_a_robot(); 138 | processor.pay(&mut order); 139 | } -------------------------------------------------------------------------------- /better-rust/solid/src/open_closed_after.rs: -------------------------------------------------------------------------------- 1 | 2 | pub struct Order<'a> { 3 | pub items: Vec<&'a str>, 4 | pub quantities: Vec, 5 | pub prices: Vec, 6 | pub status: &'a str, 7 | } 8 | 9 | impl<'a> Order<'a> { 10 | pub fn new() -> Self { 11 | Self { items: vec![], quantities: vec![], prices: vec![], status: "open" } 12 | } 13 | 14 | pub fn add_item(&mut self, name: &'a str, quantity: u32, price: f32) { 15 | self.items.push(name); 16 | self.quantities.push(quantity); 17 | self.prices.push(price); 18 | } 19 | 20 | pub fn total_price(&self) -> f32 { 21 | let mut total: f32 = 0.0; 22 | 23 | for (i, price) in self.prices.iter().enumerate() { 24 | total += self.quantities[i] as f32 * price; 25 | } 26 | 27 | total 28 | } 29 | } 30 | 31 | pub trait PaymentProcessor { 32 | fn pay(&self, order: &mut Order, security_code: &str); 33 | } 34 | 35 | pub struct DebitPaymentProcessor; 36 | 37 | impl PaymentProcessor for DebitPaymentProcessor { 38 | fn pay(&self, order: &mut Order, security_code: &str) { 39 | println!("Processing debit payment type"); 40 | println!("Verifying security code: {security_code}"); 41 | order.status = "paid"; 42 | } 43 | } 44 | 45 | pub struct CreditPaymentProcessor; 46 | 47 | impl PaymentProcessor for CreditPaymentProcessor { 48 | fn pay(&self, order: &mut Order, security_code: &str) { 49 | println!("Processing credit payment type"); 50 | println!("Verifying security code: {security_code}"); 51 | order.status = "paid"; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /better-rust/solid/src/open_closed_before.rs: -------------------------------------------------------------------------------- 1 | 2 | pub struct Order<'a> { 3 | pub items: Vec<&'a str>, 4 | pub quantities: Vec, 5 | pub prices: Vec, 6 | pub status: &'a str, 7 | } 8 | 9 | impl<'a> Order<'a> { 10 | pub fn new() -> Self { 11 | Self { items: vec![], quantities: vec![], prices: vec![], status: "open" } 12 | } 13 | 14 | pub fn add_item(&mut self, name: &'a str, quantity: u32, price: f32) { 15 | self.items.push(name); 16 | self.quantities.push(quantity); 17 | self.prices.push(price); 18 | } 19 | 20 | pub fn total_price(&self) -> f32 { 21 | let mut total: f32 = 0.0; 22 | 23 | for (i, price) in self.prices.iter().enumerate() { 24 | total += self.quantities[i] as f32 * price; 25 | } 26 | 27 | total 28 | } 29 | } 30 | 31 | pub struct PaymentProcessor; 32 | 33 | impl PaymentProcessor { 34 | pub fn pay_debit(&self, order: &mut Order, security_code: &str) { 35 | println!("Processing debit payment type"); 36 | println!("Verifying security code: {security_code}"); 37 | order.status = "paid"; 38 | } 39 | 40 | pub fn pay_credit(&self, order: &mut Order, security_code: &str) { 41 | println!("Processing credit payment type"); 42 | println!("Verifying security code: {security_code}"); 43 | order.status = "paid"; 44 | } 45 | } -------------------------------------------------------------------------------- /better-rust/solid/src/single_responsibility_after.rs: -------------------------------------------------------------------------------- 1 | 2 | pub struct Order<'a> { 3 | pub items: Vec<&'a str>, 4 | pub quantities: Vec, 5 | pub prices: Vec, 6 | pub status: &'a str, 7 | } 8 | 9 | impl<'a> Order<'a> { 10 | pub fn new() -> Self { 11 | Self { items: vec![], quantities: vec![], prices: vec![], status: "open" } 12 | } 13 | 14 | pub fn add_item(&mut self, name: &'a str, quantity: u32, price: f32) { 15 | self.items.push(name); 16 | self.quantities.push(quantity); 17 | self.prices.push(price); 18 | } 19 | 20 | pub fn total_price(&self) -> f32 { 21 | let mut total: f32 = 0.0; 22 | 23 | for (i, price) in self.prices.iter().enumerate() { 24 | total += self.quantities[i] as f32 * price; 25 | } 26 | 27 | total 28 | } 29 | } 30 | 31 | pub struct PaymentProcessor; 32 | 33 | impl PaymentProcessor { 34 | pub fn pay_debit(&self, order: &mut Order, security_code: &str) { 35 | println!("Processing debit payment type"); 36 | println!("Verifying security code: {security_code}"); 37 | order.status = "paid"; 38 | } 39 | 40 | pub fn pay_credit(&self, order: &mut Order, security_code: &str) { 41 | println!("Processing credit payment type"); 42 | println!("Verifying security code: {security_code}"); 43 | order.status = "paid"; 44 | } 45 | } -------------------------------------------------------------------------------- /better-rust/solid/src/single_responsibility_before.rs: -------------------------------------------------------------------------------- 1 | 2 | pub struct Order<'a> { 3 | pub items: Vec<&'a str>, 4 | pub quantities: Vec, 5 | pub prices: Vec, 6 | pub status: &'a str, 7 | } 8 | 9 | impl<'a> Order<'a> { 10 | pub fn new() -> Self { 11 | Self { items: vec![], quantities: vec![], prices: vec![], status: "open" } 12 | } 13 | 14 | pub fn add_item(&mut self, name: &'a str, quantity: u32, price: f32) { 15 | self.items.push(name); 16 | self.quantities.push(quantity); 17 | self.prices.push(price); 18 | } 19 | 20 | pub fn total_price(&self) -> f32 { 21 | let mut total: f32 = 0.0; 22 | 23 | for (i, price) in self.prices.iter().enumerate() { 24 | total += self.quantities[i] as f32 * price; 25 | } 26 | 27 | total 28 | } 29 | 30 | pub fn pay(&mut self, payment_type: &str, security_code: &str) { 31 | if payment_type == "debit" { 32 | println!("Processing debit payment type"); 33 | println!("Verifying security code: {security_code}"); 34 | self.status = "paid"; 35 | } else if payment_type == "credit" { 36 | println!("Processing credit payment type"); 37 | println!("Verifying security code: {security_code}"); 38 | self.status = "paid"; 39 | } else { 40 | panic!("Unknown payment type: {payment_type}"); 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /better-rust/src/main.rs: -------------------------------------------------------------------------------- 1 | 2 | fn main() { 3 | println!("Hello, world!"); 4 | } 5 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | flask 2 | returns 3 | --------------------------------------------------------------------------------