diff --git a/.gitignore b/.gitignore
index c560be3..72b5e14 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,3 +13,7 @@ py101_2nd_code.wpu
chapter22_type_hints/.mypy_cache/
*.pyc
+*.zip
+*.db
+*.pdf
+*.mypy_cache
\ No newline at end of file
diff --git a/appendix_b_git/arithmetic.py b/appendix_b_git/arithmetic.py
new file mode 100644
index 0000000..f49c8db
--- /dev/null
+++ b/appendix_b_git/arithmetic.py
@@ -0,0 +1,13 @@
+# arithmetic.py
+
+def add(x, y):
+ return x + y
+
+def divide(x, y):
+ return x / y
+
+def multiply(x, y):
+ return x * y
+
+def subtract(x, y):
+ return x - y
diff --git a/chapter27_decorators/amount_with_properties.py b/chapter27_decorators/amount_with_properties.py
new file mode 100644
index 0000000..6fce4ee
--- /dev/null
+++ b/chapter27_decorators/amount_with_properties.py
@@ -0,0 +1,24 @@
+class Amount:
+
+ def __init__(self):
+ # private attribute
+ self._amount = None
+
+ def get_amount(self):
+ return self._amount
+
+ def set_amount(self, value):
+ if isinstance(value, int) or isinstance(value, float):
+ self._amount = value
+ else:
+ print(f'Value must be an int or float')
+
+ amount = property(get_amount, set_amount)
+
+if __name__ == '__main__':
+ amt = Amount()
+ print(f'The current amount is {amt.amount}')
+ amt.amount = 'the'
+ print(f'The current amount is {amt.amount}')
+ amt.amount = 5.5
+ print(f'The current amount is {amt.amount}')
diff --git a/chapter27_decorators/amount_with_property_decorator.py b/chapter27_decorators/amount_with_property_decorator.py
new file mode 100644
index 0000000..fa6074d
--- /dev/null
+++ b/chapter27_decorators/amount_with_property_decorator.py
@@ -0,0 +1,25 @@
+class Amount:
+
+ def __init__(self):
+ # private attribute
+ self._amount = None
+
+ @property
+ def amount(self):
+ return self._amount
+
+ @amount.setter
+ def amount(self, value):
+ if isinstance(value, int) or isinstance(value, float):
+ self._amount = value
+ else:
+ print(f'Value must be an int or float')
+
+
+if __name__ == '__main__':
+ amt = Amount()
+ print(f'The current amount is {amt.amount}')
+ amt.amount = 'the'
+ print(f'The current amount is {amt.amount}')
+ amt.amount = 5.5
+ print(f'The current amount is {amt.amount}')
diff --git a/chapter27_decorators/amount_without_properties.py b/chapter27_decorators/amount_without_properties.py
new file mode 100644
index 0000000..a384f94
--- /dev/null
+++ b/chapter27_decorators/amount_without_properties.py
@@ -0,0 +1,22 @@
+class Amount:
+
+ def __init__(self):
+ # private attribute
+ self._amount = None
+
+ def get_amount(self):
+ return self._amount
+
+ def set_amount(self, value):
+ if isinstance(value, int) or isinstance(value, float):
+ self._amount = value
+ else:
+ print(f'Value must be an int or float')
+
+if __name__ == '__main__':
+ amt = Amount()
+ print(f'The current amount is {amt.get_amount()}')
+ amt.set_amount('the')
+ print(f'The current amount is {amt.get_amount()}')
+ amt.set_amount(5.5)
+ print(f'The current amount is {amt.get_amount()}')
diff --git a/chapter27_decorators/classmethod_example.py b/chapter27_decorators/classmethod_example.py
new file mode 100644
index 0000000..e35b726
--- /dev/null
+++ b/chapter27_decorators/classmethod_example.py
@@ -0,0 +1,27 @@
+class Time:
+ """an example 24-hour time class"""
+
+ def __init__(self, hour, minute):
+ self.hour = hour
+ self.minute = minute
+
+ def __repr__(self):
+ return "Time(%d, %d)" % (self.hour, self.minute)
+
+ @classmethod
+ def from_float(cls, moment):
+ """2.5 == 2 hours, 30 minutes, 0 seconds, 0 microseconds"""
+ hours = int(moment)
+ if hours:
+ moment = moment % hours
+ minutes = int(moment * 60)
+ return cls(hours, minutes)
+
+ def to_float(self):
+ """return self as a floating point number"""
+ return self.hour + self.minute / 60
+
+Time(7, 30)
+Time.from_float(5.75)
+t = Time(10, 15)
+t.to_float()
\ No newline at end of file
diff --git a/chapter27_decorators/decorator_class.py b/chapter27_decorators/decorator_class.py
new file mode 100644
index 0000000..1ab3964
--- /dev/null
+++ b/chapter27_decorators/decorator_class.py
@@ -0,0 +1,24 @@
+# decorator_class.py
+
+class info:
+
+ def __init__(self, arg1, arg2):
+ print('running __init__')
+ self.arg1 = arg1
+ self.arg2 = arg2
+ print('Decorator args: {}, {}'.format(arg1, arg2))
+
+ def __call__(self, function):
+ print('in __call__')
+
+ def wrapper(*args, **kwargs):
+ print('in wrapper()')
+ return function(*args, **kwargs)
+
+ return wrapper
+
+@info(3, 'Python')
+def treble(number):
+ return number * 3
+
+print(treble(5))
\ No newline at end of file
diff --git a/chapter27_decorators/decorator_syntax.py b/chapter27_decorators/decorator_syntax.py
new file mode 100644
index 0000000..1c5dd13
--- /dev/null
+++ b/chapter27_decorators/decorator_syntax.py
@@ -0,0 +1,16 @@
+# decorator_syntax.py
+
+def func_info(func):
+ def wrapper(*args):
+ print('Function name: ' + func.__name__)
+ print('Function docstring: ' + str(func.__doc__))
+ result = func(*args)
+ return result
+ return wrapper
+
+@func_info
+def treble(a: int) -> int:
+ """A function that triples its input"""
+ return a * 3
+
+print(treble(5))
diff --git a/chapter27_decorators/decorator_syntax_with_arguments.py b/chapter27_decorators/decorator_syntax_with_arguments.py
new file mode 100644
index 0000000..bfd6ea8
--- /dev/null
+++ b/chapter27_decorators/decorator_syntax_with_arguments.py
@@ -0,0 +1,24 @@
+# decorator_syntax_with_arguments.py
+
+def func_info(arg1, arg2):
+ print('Decorator arg1 = ' + str(arg1))
+ print('Decorator arg2 = ' + str(arg2))
+
+ def the_real_decorator(function):
+
+ def wrapper(*args, **kwargs):
+ print('Function {} args: {} kwargs: {}'
+ .format(
+ function.__name__,
+ str(args),
+ str(kwargs)))
+ return function(*args, **kwargs)
+ return wrapper
+
+ return the_real_decorator
+
+@func_info(3, 'Python')
+def treble(number):
+ return number * 3
+
+print(treble(5))
diff --git a/chapter27_decorators/first_decorator.py b/chapter27_decorators/first_decorator.py
new file mode 100644
index 0000000..57c6293
--- /dev/null
+++ b/chapter27_decorators/first_decorator.py
@@ -0,0 +1,16 @@
+
+
+def func_info(func):
+ def wrapper():
+ print('Function name: ' + func.__name__)
+ print('Function docstring: ' + str(func.__doc__))
+ result = func()
+ return result
+ return wrapper
+
+
+def treble():
+ return 3 * 3
+
+decorator = func_info(treble)
+print(decorator())
diff --git a/chapter27_decorators/first_decorator_updated.py b/chapter27_decorators/first_decorator_updated.py
new file mode 100644
index 0000000..1d61a9c
--- /dev/null
+++ b/chapter27_decorators/first_decorator_updated.py
@@ -0,0 +1,18 @@
+# first_decorator_updated.py
+
+def func_info(func):
+ def wrapper(*args):
+ print('Function name: ' + func.__name__)
+ print('Function docstring: ' + str(func.__doc__))
+ result = func(*args)
+ return result
+ return wrapper
+
+
+def treble(a: int) -> int:
+ """A function that triples its input"""
+ return a * 3
+
+
+my_decorator = func_info(treble)
+print(my_decorator(5))
diff --git a/chapter27_decorators/logging_decorator.py b/chapter27_decorators/logging_decorator.py
new file mode 100644
index 0000000..ca71d71
--- /dev/null
+++ b/chapter27_decorators/logging_decorator.py
@@ -0,0 +1,37 @@
+import logging
+
+def logging_formatter(logger, name):
+ """
+ Format logger and add file handler
+ """
+ fh = logging.FileHandler(f"{name}.log")
+ fmt = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
+ formatter = logging.Formatter(fmt)
+ fh.setFormatter(formatter)
+ logger.addHandler(fh)
+
+def log(func):
+ """
+ Log what function is called
+ """
+ def wrapper(*args, **kwargs):
+ name = func.__name__
+ logger = logging.getLogger(name)
+ logger.setLevel(logging.INFO)
+
+ # add logging formatter and file handler
+ logging_formatter(logger, name)
+
+ logger.info(f"Running function: {name}")
+ logger.info(f"{args=}, {kwargs=}")
+ result = func(*args, **kwargs)
+ logger.info("Result: %s" % result)
+ return func
+ return wrapper
+
+@log
+def treble(a):
+ return a * 3
+
+if __name__ == '__main__':
+ treble(5)
diff --git a/chapter27_decorators/stacked_decorator_tracing.py b/chapter27_decorators/stacked_decorator_tracing.py
new file mode 100644
index 0000000..07b25a9
--- /dev/null
+++ b/chapter27_decorators/stacked_decorator_tracing.py
@@ -0,0 +1,18 @@
+def bold(func):
+ print(f'You are wrapping {func.__name__} in bold')
+ def bold_wrapper():
+ return "" + func() + ""
+ return bold_wrapper
+
+def italic(func):
+ print(f'You are wrapping {func.__name__} in italic')
+ def italic_wrapper():
+ return "" + func() + ""
+ return italic_wrapper
+
+@bold
+@italic
+def formatted_text():
+ return 'Python rocks!'
+
+print(formatted_text())
\ No newline at end of file
diff --git a/chapter27_decorators/stacked_decorators.py b/chapter27_decorators/stacked_decorators.py
new file mode 100644
index 0000000..9ebdf05
--- /dev/null
+++ b/chapter27_decorators/stacked_decorators.py
@@ -0,0 +1,16 @@
+def bold(func):
+ def wrapper():
+ return "" + func() + ""
+ return wrapper
+
+def italic(func):
+ def wrapper():
+ return "" + func() + ""
+ return wrapper
+
+@bold
+@italic
+def formatted_text():
+ return 'Python rocks!'
+
+print(formatted_text())
\ No newline at end of file
diff --git a/chapter27_decorators/treble.log b/chapter27_decorators/treble.log
new file mode 100644
index 0000000..ccf0f36
--- /dev/null
+++ b/chapter27_decorators/treble.log
@@ -0,0 +1,3 @@
+2020-05-04 16:22:02,781 - treble - INFO - Running function: treble
+2020-05-04 16:22:02,781 - treble - INFO - args=(5,), kwargs={}
+2020-05-04 16:22:02,781 - treble - INFO - Result: 15
diff --git a/chapter29_profiling/callee_stats.py b/chapter29_profiling/callee_stats.py
new file mode 100644
index 0000000..a1a631c
--- /dev/null
+++ b/chapter29_profiling/callee_stats.py
@@ -0,0 +1,12 @@
+import pstats
+
+def formatted_stats_output(path):
+ p = pstats.Stats(path)
+ stripped_dirs = p.strip_dirs()
+ sorted_stats = stripped_dirs.sort_stats('filename')
+ sorted_stats.print_callers('\(main')
+ sorted_stats.print_callees('\(main')
+
+if __name__ =='__main__':
+ path = 'profile_output.txt'
+ formatted_stats_output(path)
\ No newline at end of file
diff --git a/chapter29_profiling/filtered_stats.py b/chapter29_profiling/filtered_stats.py
new file mode 100644
index 0000000..086c800
--- /dev/null
+++ b/chapter29_profiling/filtered_stats.py
@@ -0,0 +1,11 @@
+import pstats
+
+def formatted_stats_output(path):
+ p = pstats.Stats(path)
+ stripped_dirs = p.strip_dirs()
+ sorted_stats = stripped_dirs.sort_stats('filename')
+ sorted_stats.print_stats('\(main')
+
+if __name__ =='__main__':
+ path = 'profile_output.txt'
+ formatted_stats_output(path)
\ No newline at end of file
diff --git a/chapter29_profiling/formatted_output.py b/chapter29_profiling/formatted_output.py
new file mode 100644
index 0000000..d35ca8a
--- /dev/null
+++ b/chapter29_profiling/formatted_output.py
@@ -0,0 +1,11 @@
+import pstats
+
+def formatted_stats_output(path):
+ p = pstats.Stats(path)
+ stripped_dirs = p.strip_dirs()
+ sorted_stats = stripped_dirs.sort_stats('filename')
+ sorted_stats.print_stats()
+
+if __name__ =='__main__':
+ path = 'profile_output.txt'
+ formatted_stats_output(path)
\ No newline at end of file
diff --git a/chapter29_profiling/profile_output.txt b/chapter29_profiling/profile_output.txt
new file mode 100644
index 0000000..3b7e13e
Binary files /dev/null and b/chapter29_profiling/profile_output.txt differ
diff --git a/chapter29_profiling/profile_test.py b/chapter29_profiling/profile_test.py
new file mode 100644
index 0000000..6b8641b
--- /dev/null
+++ b/chapter29_profiling/profile_test.py
@@ -0,0 +1,24 @@
+# profile_test.py
+
+import time
+
+def quick():
+ print('Running quick')
+ return 1 + 1
+
+def average():
+ print('Running average')
+ time.sleep(0.5)
+
+def super_slow():
+ print('Running super slowly')
+ time.sleep(2)
+
+def main():
+ quick()
+ super_slow()
+ quick()
+ average()
+
+if __name__ == '__main__':
+ main()
\ No newline at end of file
diff --git a/chapter30_testing/add_doctest.py b/chapter30_testing/add_doctest.py
new file mode 100644
index 0000000..5e0c3cd
--- /dev/null
+++ b/chapter30_testing/add_doctest.py
@@ -0,0 +1,10 @@
+# add_doctest.py
+
+def add(a: int, b: int) -> int:
+ """
+ >>> add(1, 2)
+ 3
+ >>> add(4, 5)
+ 9
+ """
+ a + b
\ No newline at end of file
diff --git a/chapter30_testing/add_doctest_working.py b/chapter30_testing/add_doctest_working.py
new file mode 100644
index 0000000..b8c161b
--- /dev/null
+++ b/chapter30_testing/add_doctest_working.py
@@ -0,0 +1,10 @@
+# add_doctest.py
+
+def add(a: int, b: int) -> int:
+ """
+ >>> add(1, 2)
+ 3
+ >>> add(4, 5)
+ 9
+ """
+ return a + b
\ No newline at end of file
diff --git a/chapter30_testing/add_test_in_code.py b/chapter30_testing/add_test_in_code.py
new file mode 100644
index 0000000..3918d37
--- /dev/null
+++ b/chapter30_testing/add_test_in_code.py
@@ -0,0 +1,14 @@
+# add_test_in_code.py
+
+def add(a: int, b: int) -> int:
+ """
+ >>> add(1, 2)
+ 3
+ >>> add(4, 5)
+ 9
+ """
+ return a + b
+
+if __name__ == '__main__':
+ import doctest
+ doctest.testmod(verbose=True)
\ No newline at end of file
diff --git a/chapter30_testing/doctest_external.py b/chapter30_testing/doctest_external.py
new file mode 100644
index 0000000..e4861c1
--- /dev/null
+++ b/chapter30_testing/doctest_external.py
@@ -0,0 +1,4 @@
+# doctest_external.py
+
+def add(a: int, b: int) -> int:
+ return a + b
\ No newline at end of file
diff --git a/chapter30_testing/fizzbuzz_buzz_test/fizzbuzz.py b/chapter30_testing/fizzbuzz_buzz_test/fizzbuzz.py
new file mode 100644
index 0000000..5d4c54a
--- /dev/null
+++ b/chapter30_testing/fizzbuzz_buzz_test/fizzbuzz.py
@@ -0,0 +1,3 @@
+def process(number):
+ if number % 3 == 0:
+ return 'Fizz'
\ No newline at end of file
diff --git a/chapter30_testing/fizzbuzz_buzz_test/test_fizzbuzz.py b/chapter30_testing/fizzbuzz_buzz_test/test_fizzbuzz.py
new file mode 100644
index 0000000..93d0e2a
--- /dev/null
+++ b/chapter30_testing/fizzbuzz_buzz_test/test_fizzbuzz.py
@@ -0,0 +1,13 @@
+import fizzbuzz
+import unittest
+
+class TestFizzBuzz(unittest.TestCase):
+
+ def test_multiple_of_three(self):
+ self.assertEqual(fizzbuzz.process(6), 'Fizz')
+
+ def test_multiple_of_five(self):
+ self.assertEqual(fizzbuzz.process(20), 'Buzz')
+
+if __name__ == '__main__':
+ unittest.main()
\ No newline at end of file
diff --git a/chapter30_testing/fizzbuzz_buzz_test_passing/fizzbuzz.py b/chapter30_testing/fizzbuzz_buzz_test_passing/fizzbuzz.py
new file mode 100644
index 0000000..026ca50
--- /dev/null
+++ b/chapter30_testing/fizzbuzz_buzz_test_passing/fizzbuzz.py
@@ -0,0 +1,5 @@
+def process(number):
+ if number % 3 == 0:
+ return 'Fizz'
+ elif number % 5 == 0:
+ return 'Buzz'
\ No newline at end of file
diff --git a/chapter30_testing/fizzbuzz_buzz_test_passing/test_fizzbuzz.py b/chapter30_testing/fizzbuzz_buzz_test_passing/test_fizzbuzz.py
new file mode 100644
index 0000000..93d0e2a
--- /dev/null
+++ b/chapter30_testing/fizzbuzz_buzz_test_passing/test_fizzbuzz.py
@@ -0,0 +1,13 @@
+import fizzbuzz
+import unittest
+
+class TestFizzBuzz(unittest.TestCase):
+
+ def test_multiple_of_three(self):
+ self.assertEqual(fizzbuzz.process(6), 'Fizz')
+
+ def test_multiple_of_five(self):
+ self.assertEqual(fizzbuzz.process(20), 'Buzz')
+
+if __name__ == '__main__':
+ unittest.main()
\ No newline at end of file
diff --git a/chapter30_testing/fizzbuzz_final_test/fizzbuzz.py b/chapter30_testing/fizzbuzz_final_test/fizzbuzz.py
new file mode 100644
index 0000000..7510daa
--- /dev/null
+++ b/chapter30_testing/fizzbuzz_final_test/fizzbuzz.py
@@ -0,0 +1,7 @@
+def process(number):
+ if number % 3 == 0 and number % 5 == 0:
+ return 'FizzBuzz'
+ elif number % 3 == 0:
+ return 'Fizz'
+ elif number % 5 == 0:
+ return 'Buzz'
\ No newline at end of file
diff --git a/chapter30_testing/fizzbuzz_final_test/test_fizzbuzz.py b/chapter30_testing/fizzbuzz_final_test/test_fizzbuzz.py
new file mode 100644
index 0000000..1326335
--- /dev/null
+++ b/chapter30_testing/fizzbuzz_final_test/test_fizzbuzz.py
@@ -0,0 +1,20 @@
+import fizzbuzz
+import unittest
+
+class TestFizzBuzz(unittest.TestCase):
+
+ def test_multiple_of_three(self):
+ self.assertEqual(fizzbuzz.process(6), 'Fizz')
+
+ def test_multiple_of_five(self):
+ self.assertEqual(fizzbuzz.process(20), 'Buzz')
+
+ def test_fizzbuzz(self):
+ self.assertEqual(fizzbuzz.process(15), 'FizzBuzz')
+
+ def test_regular_numbers(self):
+ self.assertEqual(fizzbuzz.process(2), 2)
+ self.assertEqual(fizzbuzz.process(98), 98)
+
+if __name__ == '__main__':
+ unittest.main()
\ No newline at end of file
diff --git a/chapter30_testing/fizzbuzz_final_test_passing/fizzbuzz.py b/chapter30_testing/fizzbuzz_final_test_passing/fizzbuzz.py
new file mode 100644
index 0000000..6407376
--- /dev/null
+++ b/chapter30_testing/fizzbuzz_final_test_passing/fizzbuzz.py
@@ -0,0 +1,9 @@
+def process(number):
+ if number % 3 == 0 and number % 5 == 0:
+ return 'FizzBuzz'
+ elif number % 3 == 0:
+ return 'Fizz'
+ elif number % 5 == 0:
+ return 'Buzz'
+ else:
+ return number
\ No newline at end of file
diff --git a/chapter30_testing/fizzbuzz_final_test_passing/test_fizzbuzz.py b/chapter30_testing/fizzbuzz_final_test_passing/test_fizzbuzz.py
new file mode 100644
index 0000000..1326335
--- /dev/null
+++ b/chapter30_testing/fizzbuzz_final_test_passing/test_fizzbuzz.py
@@ -0,0 +1,20 @@
+import fizzbuzz
+import unittest
+
+class TestFizzBuzz(unittest.TestCase):
+
+ def test_multiple_of_three(self):
+ self.assertEqual(fizzbuzz.process(6), 'Fizz')
+
+ def test_multiple_of_five(self):
+ self.assertEqual(fizzbuzz.process(20), 'Buzz')
+
+ def test_fizzbuzz(self):
+ self.assertEqual(fizzbuzz.process(15), 'FizzBuzz')
+
+ def test_regular_numbers(self):
+ self.assertEqual(fizzbuzz.process(2), 2)
+ self.assertEqual(fizzbuzz.process(98), 98)
+
+if __name__ == '__main__':
+ unittest.main()
\ No newline at end of file
diff --git a/chapter30_testing/fizzbuzz_fizz_test/fizzbuzz.py b/chapter30_testing/fizzbuzz_fizz_test/fizzbuzz.py
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/chapter30_testing/fizzbuzz_fizz_test/fizzbuzz.py
@@ -0,0 +1 @@
+
diff --git a/chapter30_testing/fizzbuzz_fizz_test/test_fizzbuzz.py b/chapter30_testing/fizzbuzz_fizz_test/test_fizzbuzz.py
new file mode 100644
index 0000000..344ed73
--- /dev/null
+++ b/chapter30_testing/fizzbuzz_fizz_test/test_fizzbuzz.py
@@ -0,0 +1,10 @@
+import fizzbuzz
+import unittest
+
+class TestFizzBuzz(unittest.TestCase):
+
+ def test_multiple_of_three(self):
+ self.assertEqual(fizzbuzz.process(6), 'Fizz')
+
+if __name__ == '__main__':
+ unittest.main()
\ No newline at end of file
diff --git a/chapter30_testing/fizzbuzz_fizz_test_passing/fizzbuzz.py b/chapter30_testing/fizzbuzz_fizz_test_passing/fizzbuzz.py
new file mode 100644
index 0000000..5d4c54a
--- /dev/null
+++ b/chapter30_testing/fizzbuzz_fizz_test_passing/fizzbuzz.py
@@ -0,0 +1,3 @@
+def process(number):
+ if number % 3 == 0:
+ return 'Fizz'
\ No newline at end of file
diff --git a/chapter30_testing/fizzbuzz_fizz_test_passing/test_fizzbuzz.py b/chapter30_testing/fizzbuzz_fizz_test_passing/test_fizzbuzz.py
new file mode 100644
index 0000000..344ed73
--- /dev/null
+++ b/chapter30_testing/fizzbuzz_fizz_test_passing/test_fizzbuzz.py
@@ -0,0 +1,10 @@
+import fizzbuzz
+import unittest
+
+class TestFizzBuzz(unittest.TestCase):
+
+ def test_multiple_of_three(self):
+ self.assertEqual(fizzbuzz.process(6), 'Fizz')
+
+if __name__ == '__main__':
+ unittest.main()
\ No newline at end of file
diff --git a/chapter30_testing/fizzbuzz_fizzbuzz_test/fizzbuzz.py b/chapter30_testing/fizzbuzz_fizzbuzz_test/fizzbuzz.py
new file mode 100644
index 0000000..026ca50
--- /dev/null
+++ b/chapter30_testing/fizzbuzz_fizzbuzz_test/fizzbuzz.py
@@ -0,0 +1,5 @@
+def process(number):
+ if number % 3 == 0:
+ return 'Fizz'
+ elif number % 5 == 0:
+ return 'Buzz'
\ No newline at end of file
diff --git a/chapter30_testing/fizzbuzz_fizzbuzz_test/test_fizzbuzz.py b/chapter30_testing/fizzbuzz_fizzbuzz_test/test_fizzbuzz.py
new file mode 100644
index 0000000..d2b08ed
--- /dev/null
+++ b/chapter30_testing/fizzbuzz_fizzbuzz_test/test_fizzbuzz.py
@@ -0,0 +1,16 @@
+import fizzbuzz
+import unittest
+
+class TestFizzBuzz(unittest.TestCase):
+
+ def test_multiple_of_three(self):
+ self.assertEqual(fizzbuzz.process(6), 'Fizz')
+
+ def test_multiple_of_five(self):
+ self.assertEqual(fizzbuzz.process(20), 'Buzz')
+
+ def test_fizzbuzz(self):
+ self.assertEqual(fizzbuzz.process(15), 'FizzBuzz')
+
+if __name__ == '__main__':
+ unittest.main()
\ No newline at end of file
diff --git a/chapter30_testing/fizzbuzz_fizzbuzz_test_passing/fizzbuzz.py b/chapter30_testing/fizzbuzz_fizzbuzz_test_passing/fizzbuzz.py
new file mode 100644
index 0000000..7510daa
--- /dev/null
+++ b/chapter30_testing/fizzbuzz_fizzbuzz_test_passing/fizzbuzz.py
@@ -0,0 +1,7 @@
+def process(number):
+ if number % 3 == 0 and number % 5 == 0:
+ return 'FizzBuzz'
+ elif number % 3 == 0:
+ return 'Fizz'
+ elif number % 5 == 0:
+ return 'Buzz'
\ No newline at end of file
diff --git a/chapter30_testing/fizzbuzz_fizzbuzz_test_passing/test_fizzbuzz.py b/chapter30_testing/fizzbuzz_fizzbuzz_test_passing/test_fizzbuzz.py
new file mode 100644
index 0000000..d2b08ed
--- /dev/null
+++ b/chapter30_testing/fizzbuzz_fizzbuzz_test_passing/test_fizzbuzz.py
@@ -0,0 +1,16 @@
+import fizzbuzz
+import unittest
+
+class TestFizzBuzz(unittest.TestCase):
+
+ def test_multiple_of_three(self):
+ self.assertEqual(fizzbuzz.process(6), 'Fizz')
+
+ def test_multiple_of_five(self):
+ self.assertEqual(fizzbuzz.process(20), 'Buzz')
+
+ def test_fizzbuzz(self):
+ self.assertEqual(fizzbuzz.process(15), 'FizzBuzz')
+
+if __name__ == '__main__':
+ unittest.main()
\ No newline at end of file
diff --git a/chapter30_testing/test.txt b/chapter30_testing/test.txt
new file mode 100644
index 0000000..5ae2e6b
--- /dev/null
+++ b/chapter30_testing/test.txt
@@ -0,0 +1,7 @@
+The following are tests for doctest_external
+
+>>> from doctest_external import add
+>>> add(1, 2)
+3
+>>> add(4, 5)
+9
\ No newline at end of file
diff --git a/chapter31_jupyter/Hello World.ipynb b/chapter31_jupyter/Hello World.ipynb
new file mode 100644
index 0000000..9309eed
--- /dev/null
+++ b/chapter31_jupyter/Hello World.ipynb
@@ -0,0 +1,45 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "This is an example of printing in Python: `print('Hello')`\n",
+ "\n",
+ "Code block:\n",
+ "\n",
+ "```python\n",
+ "print('hello world')\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.6.4"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/chapter32_argparse/file_parser.py b/chapter32_argparse/file_parser.py
new file mode 100644
index 0000000..9b3db74
--- /dev/null
+++ b/chapter32_argparse/file_parser.py
@@ -0,0 +1,20 @@
+# file_parser.py
+
+import argparse
+
+def file_parser(input_file, output_file=''):
+ print(f'Processing {input_file}')
+ print('Finished processing')
+ if output_file:
+ print(f'Creating {output_file}')
+
+def main():
+ parser = argparse.ArgumentParser('File parser')
+ parser.add_argument('--infile', help='Input file')
+ parser.add_argument('--out', help='Output file')
+ args = parser.parse_args()
+ if args.infile:
+ file_parser(args.infile, args.out)
+
+if __name__ == '__main__':
+ main()
diff --git a/chapter32_argparse/file_parser_aliases.py b/chapter32_argparse/file_parser_aliases.py
new file mode 100644
index 0000000..d56f8b9
--- /dev/null
+++ b/chapter32_argparse/file_parser_aliases.py
@@ -0,0 +1,23 @@
+# file_parser_aliases.py
+
+import argparse
+
+def file_parser(input_file, output_file=''):
+ print(f'Processing {input_file}')
+ print('Finished processing')
+ if output_file:
+ print(f'Creating {output_file}')
+
+def main():
+ parser = argparse.ArgumentParser('File parser',
+ description='PyParse - The File Processor',
+ epilog='Thank you for choosing PyParse!',
+ add_help=False)
+ parser.add_argument('-i', '--infile', help='Input file for conversion')
+ parser.add_argument('-o', '--out', help='Converted output file')
+ args = parser.parse_args()
+ if args.infile:
+ file_parser(args.infile, args.out)
+
+if __name__ == '__main__':
+ main()
\ No newline at end of file
diff --git a/chapter32_argparse/file_parser_aliases2.py b/chapter32_argparse/file_parser_aliases2.py
new file mode 100644
index 0000000..e64d642
--- /dev/null
+++ b/chapter32_argparse/file_parser_aliases2.py
@@ -0,0 +1,23 @@
+# file_parser_aliases2.py
+
+import argparse
+
+def file_parser(input_file, output_file=''):
+ print(f'Processing {input_file}')
+ print('Finished processing')
+ if output_file:
+ print(f'Creating {output_file}')
+
+def main():
+ parser = argparse.ArgumentParser('File parser',
+ description='PyParse - The File Processor',
+ epilog='Thank you for choosing PyParse!',
+ add_help=False)
+ parser.add_argument('-i', '--infile', help='Input file for conversion')
+ parser.add_argument('-o', '--out', help='Converted output file')
+ args = parser.parse_args()
+ if args.infile:
+ file_parser(args.infile, args.out)
+
+if __name__ == '__main__':
+ main()
\ No newline at end of file
diff --git a/chapter32_argparse/file_parser_exclusive.py b/chapter32_argparse/file_parser_exclusive.py
new file mode 100644
index 0000000..7eb3b82
--- /dev/null
+++ b/chapter32_argparse/file_parser_exclusive.py
@@ -0,0 +1,24 @@
+# file_parser_exclusive.py
+
+import argparse
+
+def file_parser(input_file, output_file=''):
+ print(f'Processing {input_file}')
+ print('Finished processing')
+ if output_file:
+ print(f'Creating {output_file}')
+
+def main():
+ parser = argparse.ArgumentParser('File parser',
+ description='PyParse - The File Processor',
+ epilog='Thank you for choosing PyParse!',
+ add_help=False)
+ group = parser.add_mutually_exclusive_group()
+ group.add_argument('-i', '--infile', help='Input file for conversion')
+ group.add_argument('-o', '--out', help='Converted output file')
+ args = parser.parse_args()
+ if args.infile:
+ file_parser(args.infile, args.out)
+
+if __name__ == '__main__':
+ main()
\ No newline at end of file
diff --git a/chapter32_argparse/file_parser_no_help.py b/chapter32_argparse/file_parser_no_help.py
new file mode 100644
index 0000000..b69614e
--- /dev/null
+++ b/chapter32_argparse/file_parser_no_help.py
@@ -0,0 +1,23 @@
+# file_parser_no_help.py
+
+import argparse
+
+def file_parser(input_file, output_file=''):
+ print(f'Processing {input_file}')
+ print('Finished processing')
+ if output_file:
+ print(f'Creating {output_file}')
+
+def main():
+ parser = argparse.ArgumentParser('File parser',
+ description='PyParse - The File Processor',
+ epilog='Thank you for choosing PyParse!',
+ add_help=False)
+ parser.add_argument('--infile', help='Input file for conversion')
+ parser.add_argument('--out', help='Converted output file')
+ args = parser.parse_args()
+ if args.infile:
+ file_parser(args.infile, args.out)
+
+if __name__ == '__main__':
+ main()
\ No newline at end of file
diff --git a/chapter32_argparse/file_parser_with_description.py b/chapter32_argparse/file_parser_with_description.py
new file mode 100644
index 0000000..c755a64
--- /dev/null
+++ b/chapter32_argparse/file_parser_with_description.py
@@ -0,0 +1,22 @@
+# file_parser_with_description.py
+
+import argparse
+
+def file_parser(input_file, output_file=''):
+ print(f'Processing {input_file}')
+ print('Finished processing')
+ if output_file:
+ print(f'Creating {output_file}')
+
+def main():
+ parser = argparse.ArgumentParser('File parser',
+ description='PyParse - The File Processor',
+ epilog='Thank you for choosing PyParse!')
+ parser.add_argument('--infile', help='Input file for conversion')
+ parser.add_argument('--out', help='Converted output file')
+ args = parser.parse_args()
+ if args.infile:
+ file_parser(args.infile, args.out)
+
+if __name__ == '__main__':
+ main()
\ No newline at end of file
diff --git a/chapter32_argparse/pysearch.py b/chapter32_argparse/pysearch.py
new file mode 100644
index 0000000..9a2e0cc
--- /dev/null
+++ b/chapter32_argparse/pysearch.py
@@ -0,0 +1,49 @@
+# pysearch.py
+
+import argparse
+import pathlib
+
+
+def search_folder(path, extension, file_size=None):
+ """
+ Search folder for files
+ """
+ folder = pathlib.Path(path)
+ files = list(folder.rglob(f'*.{extension}'))
+
+ if not files:
+ print(f'No files found with {extension=}')
+ return
+
+ if file_size is not None:
+ files = [f for f in files
+ if f.stat().st_size > file_size]
+
+ print(f'{len(files)} *.{extension} files found:')
+ for file_path in files:
+ print(file_path)
+
+
+def main():
+ parser = argparse.ArgumentParser(
+ 'PySearch',
+ description='PySearch - The Python Powered File Searcher')
+ parser.add_argument('-p', '--path',
+ help='The path to search for files',
+ required=True,
+ dest='path')
+ parser.add_argument('-e', '--ext',
+ help='The extension to search for',
+ required=True,
+ dest='extension')
+ parser.add_argument('-s', '--size',
+ help='The file size to filter on in bytes',
+ type=int,
+ dest='size',
+ default=None)
+
+ args = parser.parse_args()
+ search_folder(args.path, args.extension, args.size)
+
+if __name__ == '__main__':
+ main()
\ No newline at end of file
diff --git a/chapter32_argparse/sys_args.py b/chapter32_argparse/sys_args.py
new file mode 100644
index 0000000..2d1e1a2
--- /dev/null
+++ b/chapter32_argparse/sys_args.py
@@ -0,0 +1,10 @@
+# sys_args.py
+
+import sys
+
+def main():
+ print('You passed the following arguments:')
+ print(sys.argv)
+
+if __name__ == '__main__':
+ main()
\ No newline at end of file
diff --git a/chapter33_xml/appts.xml b/chapter33_xml/appts.xml
new file mode 100644
index 0000000..5b59f28
--- /dev/null
+++ b/chapter33_xml/appts.xml
@@ -0,0 +1,21 @@
+
+
+
+ 1181251680
+ 040000008200E000
+ 1181572063
+
+
+ 1800
+ Bring pizza home
+
+
+ 1181253977
+ sdlkjlkadhdakhdfd
+ 1181588888
+ TX
+ Dallas
+ 1800
+ Bring pizza home
+
+
\ No newline at end of file
diff --git a/chapter33_xml/create_xml.py b/chapter33_xml/create_xml.py
new file mode 100644
index 0000000..e10ad5f
--- /dev/null
+++ b/chapter33_xml/create_xml.py
@@ -0,0 +1,26 @@
+# create_xml.py
+
+import xml.etree.ElementTree as ET
+
+
+def create_xml(xml_file):
+ root_element = ET.Element('note_taker')
+ note_element = ET.Element('note')
+ root_element.append(note_element)
+
+ # add note sub-elements
+ to_element = ET.SubElement(note_element, 'to')
+ to_element.text = 'Mike'
+ from_element = ET.SubElement(note_element, 'from')
+ from_element.text = 'Nick'
+ heading_element = ET.SubElement(note_element, 'heading')
+ heading_element.text = 'Appointment'
+ body_element = ET.SubElement(note_element, 'body')
+ body_element.text = 'blah blah'
+
+ tree = ET.ElementTree(root_element)
+ with open(xml_file, "wb") as fh:
+ tree.write(fh)
+
+if __name__ == '__main__':
+ create_xml('test_create.xml')
\ No newline at end of file
diff --git a/chapter33_xml/edit_xml.py b/chapter33_xml/edit_xml.py
new file mode 100644
index 0000000..d2177c3
--- /dev/null
+++ b/chapter33_xml/edit_xml.py
@@ -0,0 +1,18 @@
+# edit_xml.py
+
+
+import xml.etree.cElementTree as ET
+
+def edit_xml(xml_file, output_file, from_person):
+ tree = ET.ElementTree(file=xml_file)
+ root = tree.getroot()
+
+ for from_element in tree.iter(tag='from'):
+ from_element.text = from_person
+
+ tree = ET.ElementTree(root)
+ with open(output_file, "wb") as f:
+ tree.write(f)
+
+if __name__ == '__main__':
+ edit_xml('note.xml', 'output.xml', 'Guido')
\ No newline at end of file
diff --git a/chapter33_xml/lxml_output.xml b/chapter33_xml/lxml_output.xml
new file mode 100644
index 0000000..aa4455c
--- /dev/null
+++ b/chapter33_xml/lxml_output.xml
@@ -0,0 +1,15 @@
+
+
+ Guido
+ Nadine
+ Reminder
+ Don't forget the milk
+ I'm new!
+
+
+ Nicole
+ Nadine
+ Appointment
+ Eye doctor
+
+
diff --git a/chapter33_xml/note.xml b/chapter33_xml/note.xml
new file mode 100644
index 0000000..737369c
--- /dev/null
+++ b/chapter33_xml/note.xml
@@ -0,0 +1,15 @@
+
+
+
+ Mike
+ Nadine
+ Reminder
+ Don't forget the milk
+
+
+ Nicole
+ Nadine
+ Appointment
+ Eye doctor
+
+
\ No newline at end of file
diff --git a/chapter33_xml/output.xml b/chapter33_xml/output.xml
new file mode 100644
index 0000000..f5539f4
--- /dev/null
+++ b/chapter33_xml/output.xml
@@ -0,0 +1,14 @@
+
+
+ Mike
+ Guido
+ Reminder
+ Don't forget the milk
+
+
+ Nicole
+ Guido
+ Appointment
+ Eye doctor
+
+
\ No newline at end of file
diff --git a/chapter33_xml/parse_xml.py b/chapter33_xml/parse_xml.py
new file mode 100644
index 0000000..4083e82
--- /dev/null
+++ b/chapter33_xml/parse_xml.py
@@ -0,0 +1,19 @@
+# parse_xml.py
+
+from xml.etree.ElementTree import ElementTree
+
+def parse_xml(xml_file):
+ tree = ElementTree(file=xml_file)
+ root_element = tree.getroot()
+ print(f"The root element's tag is '{root_element.tag}'")
+
+ for child_element in root_element:
+ print(f'{child_element.tag=}, {child_element.text=}')
+ if child_element.tag == 'note':
+ for note_element in child_element:
+ print(f'{note_element.tag=}, {note_element.text=}')
+
+
+if __name__ == '__main__':
+ parse_xml('note.xml')
+
diff --git a/chapter33_xml/parse_xml_with_lxml.py b/chapter33_xml/parse_xml_with_lxml.py
new file mode 100644
index 0000000..d81ba59
--- /dev/null
+++ b/chapter33_xml/parse_xml_with_lxml.py
@@ -0,0 +1,39 @@
+# parse_xml_with_lxml.py
+
+from lxml import etree, objectify
+
+def parse_xml(xml_file):
+ with open(xml_file) as f:
+ xml = f.read()
+
+ root = objectify.fromstring(xml)
+
+ # Get an element
+ to = root.note.to
+ print(f'The {to=}')
+
+ # print out all the note element's tags and text values
+ for note in root.getchildren():
+ for note_element in note.getchildren():
+ print(f'{note_element.tag=}, {note_element.text=}')
+ print()
+
+ # modify a text value
+ print(f'Original: {root.note.to=}')
+ root.note.to = 'Guido'
+ print(f'Modified: {root.note.to=}')
+
+ # add a new element
+ root.note.new_element = "I'm new!"
+
+ # cleanup the XML before writing to disk
+ objectify.deannotate(root)
+ etree.cleanup_namespaces(root)
+ obj_xml = etree.tostring(root, pretty_print=True)
+
+ # save your xml
+ with open("lxml_output.xml", "wb") as f:
+ f.write(obj_xml)
+
+if __name__ == '__main__':
+ parse_xml('note.xml')
\ No newline at end of file
diff --git a/chapter33_xml/test_create.xml b/chapter33_xml/test_create.xml
new file mode 100644
index 0000000..ef03836
--- /dev/null
+++ b/chapter33_xml/test_create.xml
@@ -0,0 +1,2 @@
+
+MikeNickAppointmentblah blah
\ No newline at end of file
diff --git a/chapter33_xml/xml_tree_iterator.py b/chapter33_xml/xml_tree_iterator.py
new file mode 100644
index 0000000..c7b0b95
--- /dev/null
+++ b/chapter33_xml/xml_tree_iterator.py
@@ -0,0 +1,13 @@
+# xml_tree_iterator.py
+
+from xml.etree.cElementTree import ElementTree
+
+def parse_xml(xml_file):
+ tree = ElementTree(file=xml_file)
+ print("Iterating using a tree iterator")
+ for elem in tree.iter():
+ print(f'{elem.tag=}, {elem.text=}')
+
+
+if __name__ == '__main__':
+ parse_xml('note.xml')
diff --git a/chapter34_json/create_json_file.py b/chapter34_json/create_json_file.py
new file mode 100644
index 0000000..6466678
--- /dev/null
+++ b/chapter34_json/create_json_file.py
@@ -0,0 +1,21 @@
+# create_json_file.py
+
+import json
+
+def create_json_file(path, obj):
+ with open(path, 'w') as fh:
+ json.dump(obj, fh)
+
+if __name__ == '__main__':
+ j = {"menu": {
+ "id": "file",
+ "value": "File",
+ "popup": {
+ "menuitem": [
+ {"value": "New", "onclick": "CreateNewDoc()"},
+ {"value": "Open", "onclick": "OpenDoc()"},
+ {"value": "Close", "onclick": "CloseDoc()"}
+ ]
+ }
+ }}
+ create_json_file('test.json', j)
\ No newline at end of file
diff --git a/chapter34_json/example.json b/chapter34_json/example.json
new file mode 100644
index 0000000..1faefce
--- /dev/null
+++ b/chapter34_json/example.json
@@ -0,0 +1 @@
+{"menu": {"id": "file", "value": "File", "popup": {"menuitem": [{"value": "New", "onclick": "CreateNewDoc()"}, {"value": "Open", "onclick": "OpenDoc()"}, {"value": "Close", "onclick": "CloseDoc()"}]}}}
\ No newline at end of file
diff --git a/chapter34_json/load_json_file.py b/chapter34_json/load_json_file.py
new file mode 100644
index 0000000..769a42c
--- /dev/null
+++ b/chapter34_json/load_json_file.py
@@ -0,0 +1,12 @@
+# load_json_file.py
+
+import json
+
+def load_json_file(path):
+ with open(path) as fh:
+ j_obj = json.load(fh)
+ print(type(j_obj))
+
+
+if __name__ == '__main__':
+ load_json_file('example.json')
\ No newline at end of file
diff --git a/chapter35_scrape_website/downloading_files.py b/chapter35_scrape_website/downloading_files.py
new file mode 100644
index 0000000..ee766f7
--- /dev/null
+++ b/chapter35_scrape_website/downloading_files.py
@@ -0,0 +1,15 @@
+import urllib.request
+
+def download_file(url):
+ urllib.request.urlretrieve(url, "code.zip")
+
+def alternate_download(url):
+ with urllib.request.urlopen(url) as response:
+ data = response.read()
+
+ with open("code2.zip", "wb") as code:
+ code.write(data)
+
+if __name__ == '__main__':
+ url = 'http://www.blog.pythonlibrary.org/wp-content/uploads/2012/06/wxDbViewer.zip'
+ download_file(url)
\ No newline at end of file
diff --git a/chapter35_scrape_website/scraper.py b/chapter35_scrape_website/scraper.py
new file mode 100644
index 0000000..2da8526
--- /dev/null
+++ b/chapter35_scrape_website/scraper.py
@@ -0,0 +1,29 @@
+# scraper.py
+
+import urllib.request
+
+from bs4 import BeautifulSoup
+
+
+def download_html(url):
+ with urllib.request.urlopen(url) as response:
+ html = response.read()
+ return html
+
+def scraper(url):
+ html = download_html(url)
+ soup = BeautifulSoup(html, 'html.parser')
+
+ title_links = soup.findAll('h1')
+ articles = {}
+ for link in title_links:
+ if link.a:
+ articles[link.a['href']] = link.text.strip()
+
+ for article in articles:
+ print(f'{articles[article]} - {article}')
+
+
+if __name__ == '__main__':
+ url = 'https://www.blog.pythonlibrary.org'
+ scraper(url)
\ No newline at end of file
diff --git a/chapter36_csv/books.csv b/chapter36_csv/books.csv
new file mode 100644
index 0000000..9833652
--- /dev/null
+++ b/chapter36_csv/books.csv
@@ -0,0 +1,4 @@
+book_title,author,publisher,pub_date,isbn
+Python 101,Mike Driscoll, Mike Driscoll,2020,123456789
+wxPython Recipes,Mike Driscoll,Apress,2018,978-1-4842-3237-8
+Python Interviews,Mike Driscoll,Packt Publishing,2018,9781788399081
\ No newline at end of file
diff --git a/chapter36_csv/csv_dict_reader.py b/chapter36_csv/csv_dict_reader.py
new file mode 100644
index 0000000..0e0acc4
--- /dev/null
+++ b/chapter36_csv/csv_dict_reader.py
@@ -0,0 +1,12 @@
+# csv_dict_reader.py
+
+import csv
+
+def process_csv_dict_reader(file_obj):
+ reader = csv.DictReader(file_obj)
+ for line in reader:
+ print(f'{line["book_title"]} by {line["author"]}')
+
+if __name__ == '__main__':
+ with open('books.csv', newline='') as csvfile:
+ process_csv_dict_reader(csvfile)
\ No newline at end of file
diff --git a/chapter36_csv/csv_dict_writer.py b/chapter36_csv/csv_dict_writer.py
new file mode 100644
index 0000000..964144b
--- /dev/null
+++ b/chapter36_csv/csv_dict_writer.py
@@ -0,0 +1,28 @@
+# csv_dict_writer.py
+
+import csv
+
+def csv_dict_writer(path, headers, data):
+ with open(path, 'w', newline='') as csvfile:
+ writer = csv.DictWriter(csvfile, delimiter=',',
+ fieldnames=headers)
+ writer.writeheader()
+ for record in data:
+ writer.writerow(record)
+
+if __name__ == '__main__':
+ data = '''book_title,author,publisher,pub_date,isbn
+ Python 101,Mike Driscoll, Mike Driscoll,2020,123456789
+ wxPython Recipes,Mike Driscoll,Apress,2018,978-1-4842-3237-8
+ Python Interviews,Mike Driscoll,Packt Publishing,2018,9781788399081'''
+ records = []
+ for line in data.splitlines():
+ records.append(line.strip().split(','))
+ headers = records.pop(0)
+
+ list_of_dicts = []
+ for row in records:
+ my_dict = dict(zip(headers, row))
+ list_of_dicts.append(my_dict)
+
+ csv_dict_writer('output_dict.csv', headers, list_of_dicts)
\ No newline at end of file
diff --git a/chapter36_csv/csv_reader.py b/chapter36_csv/csv_reader.py
new file mode 100644
index 0000000..ccdc251
--- /dev/null
+++ b/chapter36_csv/csv_reader.py
@@ -0,0 +1,12 @@
+# csv_reader.py
+
+import csv
+
+def process_csv(path):
+ with open(path, newline='') as csvfile:
+ reader = csv.reader(csvfile)
+ for row in reader:
+ print(row)
+
+if __name__ == '__main__':
+ process_csv('books.csv')
\ No newline at end of file
diff --git a/chapter36_csv/csv_reader_no_header.py b/chapter36_csv/csv_reader_no_header.py
new file mode 100644
index 0000000..5030e66
--- /dev/null
+++ b/chapter36_csv/csv_reader_no_header.py
@@ -0,0 +1,14 @@
+# csv_reader_no_header.py
+
+import csv
+
+def process_csv(path):
+ with open(path, newline='') as csvfile:
+ reader = csv.reader(csvfile)
+ # Skip the header
+ next(reader, None)
+ for row in reader:
+ print(row)
+
+if __name__ == '__main__':
+ process_csv('books.csv')
\ No newline at end of file
diff --git a/chapter36_csv/csv_writer.py b/chapter36_csv/csv_writer.py
new file mode 100644
index 0000000..0fa594b
--- /dev/null
+++ b/chapter36_csv/csv_writer.py
@@ -0,0 +1,19 @@
+# csv_writer.py
+
+import csv
+
+def csv_writer(path, data):
+ with open(path, 'w', newline='') as csvfile:
+ writer = csv.writer(csvfile, delimiter=',')
+ for row in data:
+ writer.writerow(row)
+
+if __name__ == '__main__':
+ data = '''book_title,author,publisher,pub_date,isbn
+ Python 101,Mike Driscoll, Mike Driscoll,2020,123456789
+ wxPython Recipes,Mike Driscoll,Apress,2018,978-1-4842-3237-8
+ Python Interviews,Mike Driscoll,Packt Publishing,2018,9781788399081'''
+ records = []
+ for line in data.splitlines():
+ records.append(line.strip().split(','))
+ csv_writer('output.csv', records)
\ No newline at end of file
diff --git a/chapter36_csv/csv_writer_rows.py b/chapter36_csv/csv_writer_rows.py
new file mode 100644
index 0000000..0143498
--- /dev/null
+++ b/chapter36_csv/csv_writer_rows.py
@@ -0,0 +1,18 @@
+# csv_writer_rows.py
+
+import csv
+
+def csv_writer(path, data):
+ with open(path, 'w', newline='') as csvfile:
+ writer = csv.writer(csvfile, delimiter=',')
+ writer.writerows(data)
+
+if __name__ == '__main__':
+ data = '''book_title,author,publisher,pub_date,isbn
+ Python 101,Mike Driscoll, Mike Driscoll,2020,123456789
+ wxPython Recipes,Mike Driscoll,Apress,2018,978-1-4842-3237-8
+ Python Interviews,Mike Driscoll,Packt Publishing,2018,9781788399081'''
+ records = []
+ for line in data.splitlines():
+ records.append(line.strip().split(','))
+ csv_writer('output2.csv', records)
\ No newline at end of file
diff --git a/chapter36_csv/output.csv b/chapter36_csv/output.csv
new file mode 100644
index 0000000..6d9c4de
--- /dev/null
+++ b/chapter36_csv/output.csv
@@ -0,0 +1,4 @@
+book_title,author,publisher,pub_date,isbn
+Python 101,Mike Driscoll, Mike Driscoll,2020,123456789
+wxPython Recipes,Mike Driscoll,Apress,2018,978-1-4842-3237-8
+Python Interviews,Mike Driscoll,Packt Publishing,2018,9781788399081
diff --git a/chapter36_csv/output_dict.csv b/chapter36_csv/output_dict.csv
new file mode 100644
index 0000000..6d9c4de
--- /dev/null
+++ b/chapter36_csv/output_dict.csv
@@ -0,0 +1,4 @@
+book_title,author,publisher,pub_date,isbn
+Python 101,Mike Driscoll, Mike Driscoll,2020,123456789
+wxPython Recipes,Mike Driscoll,Apress,2018,978-1-4842-3237-8
+Python Interviews,Mike Driscoll,Packt Publishing,2018,9781788399081
diff --git a/chapter37_sqlite/add_data.py b/chapter37_sqlite/add_data.py
new file mode 100644
index 0000000..77c6a82
--- /dev/null
+++ b/chapter37_sqlite/add_data.py
@@ -0,0 +1,25 @@
+# add_data.py
+
+import sqlite3
+
+conn = sqlite3.connect("books.db")
+cursor = conn.cursor()
+
+# insert a record into the database
+cursor.execute("""INSERT INTO books
+ VALUES ('Python 101', 'Mike Driscoll', '9/01/2020',
+ 'Mouse Vs Python', 'epub')"""
+ )
+
+# save data to database
+conn.commit()
+
+# insert multiple records using the more secure "?" method
+books = [('Python Interviews', 'Mike Driscoll',
+ '2/1/2018', 'Packt Publishing', 'softcover'),
+ ('Automate the Boring Stuff with Python',
+ 'Al Sweigart', '', 'No Starch Press', 'PDF'),
+ ('The Well-Grounded Python Developer',
+ 'Doug Farrell', '2020', 'Manning', 'Kindle')]
+cursor.executemany("INSERT INTO books VALUES (?,?,?,?,?)", books)
+conn.commit()
\ No newline at end of file
diff --git a/chapter37_sqlite/create_database.py b/chapter37_sqlite/create_database.py
new file mode 100644
index 0000000..54ab740
--- /dev/null
+++ b/chapter37_sqlite/create_database.py
@@ -0,0 +1,13 @@
+# create_database.py
+
+import sqlite3
+
+conn = sqlite3.connect("books.db")
+
+cursor = conn.cursor()
+
+# create a table
+cursor.execute("""CREATE TABLE books
+ (title text, author text, release_date text,
+ publisher text, book_type text)
+ """)
\ No newline at end of file
diff --git a/chapter37_sqlite/delete_record.py b/chapter37_sqlite/delete_record.py
new file mode 100644
index 0000000..43c0726
--- /dev/null
+++ b/chapter37_sqlite/delete_record.py
@@ -0,0 +1,17 @@
+# delete_record.py
+
+import sqlite3
+
+def delete_author(author):
+ conn = sqlite3.connect("books.db")
+ cursor = conn.cursor()
+
+ sql = f"""
+ DELETE FROM books
+ WHERE author = '{author}'
+ """
+ cursor.execute(sql)
+ conn.commit()
+
+if __name__ == '__main__':
+ delete_author(author='Al Sweigart')
\ No newline at end of file
diff --git a/chapter37_sqlite/queries.py b/chapter37_sqlite/queries.py
new file mode 100644
index 0000000..7cb106f
--- /dev/null
+++ b/chapter37_sqlite/queries.py
@@ -0,0 +1,29 @@
+# queries.py
+
+import sqlite3
+
+def get_cursor():
+ conn = sqlite3.connect("books.db")
+ return conn.cursor()
+
+def select_all_records_by_author(cursor, author):
+ sql = "SELECT * FROM books WHERE author=?"
+ cursor.execute(sql, [author])
+ print(cursor.fetchall()) # or use fetchone()
+ print("\nHere is a listing of the rows in the table\n")
+ for row in cursor.execute("SELECT rowid, * FROM books ORDER BY author"):
+ print(row)
+
+def select_using_like(cursor, text):
+ print("\nLIKE query results:\n")
+ sql = f"""
+ SELECT * FROM books
+ WHERE title LIKE '{text}%'"""
+ cursor.execute(sql)
+ print(cursor.fetchall())
+
+if __name__ == '__main__':
+ cursor = get_cursor()
+ select_all_records_by_author(cursor,
+ author='Mike Driscoll')
+ select_using_like(cursor, text='Python')
\ No newline at end of file
diff --git a/chapter37_sqlite/update_record.py b/chapter37_sqlite/update_record.py
new file mode 100644
index 0000000..46ae430
--- /dev/null
+++ b/chapter37_sqlite/update_record.py
@@ -0,0 +1,19 @@
+# update_record.py
+
+import sqlite3
+
+
+def update_author(old_name, new_name):
+ conn = sqlite3.connect("books.db")
+ cursor = conn.cursor()
+ sql = f"""
+ UPDATE books
+ SET author = '{new_name}'
+ WHERE author = '{old_name}'
+ """
+ cursor.execute(sql)
+ conn.commit()
+
+if __name__ == '__main__':
+ update_author(old_name='Mike Driscoll',
+ new_name='Michael Driscoll')
\ No newline at end of file
diff --git a/chapter38_excel/books.xlsx b/chapter38_excel/books.xlsx
new file mode 100644
index 0000000..f57e615
Binary files /dev/null and b/chapter38_excel/books.xlsx differ
diff --git a/chapter38_excel/creating_sheets.py b/chapter38_excel/creating_sheets.py
new file mode 100644
index 0000000..d8bdb38
--- /dev/null
+++ b/chapter38_excel/creating_sheets.py
@@ -0,0 +1,18 @@
+# creating_sheets.py
+
+import openpyxl
+
+def create_worksheets(path):
+ workbook = openpyxl.Workbook()
+ print(workbook.sheetnames)
+ # Add a new worksheet
+ workbook.create_sheet()
+ print(workbook.sheetnames)
+ # Insert a worksheet
+ workbook.create_sheet(index=1,
+ title='Second sheet')
+ print(workbook.sheetnames)
+ workbook.save(path)
+
+if __name__ == '__main__':
+ create_worksheets('sheets.xlsx')
\ No newline at end of file
diff --git a/chapter38_excel/delete_demo.py b/chapter38_excel/delete_demo.py
new file mode 100644
index 0000000..58098a3
--- /dev/null
+++ b/chapter38_excel/delete_demo.py
@@ -0,0 +1,21 @@
+# delete_demo.py
+
+from openpyxl import Workbook
+
+def deleting_cols_rows(path):
+ workbook = Workbook()
+ sheet = workbook.active
+ sheet['A1'] = 'Hello'
+ sheet['B1'] = 'from'
+ sheet['C1'] = 'OpenPyXL'
+ sheet['A2'] = 'row 2'
+ sheet['A3'] = 'row 3'
+ sheet['A4'] = 'row 4'
+ # Delete column A
+ sheet.delete_cols(idx=1)
+ # delete 2 rows starting on the second row
+ sheet.delete_rows(idx=2, amount=2)
+ workbook.save(path)
+
+if __name__ == '__main__':
+ deleting_cols_rows('deleting.xlsx')
\ No newline at end of file
diff --git a/chapter38_excel/delete_sheets.py b/chapter38_excel/delete_sheets.py
new file mode 100644
index 0000000..026234e
--- /dev/null
+++ b/chapter38_excel/delete_sheets.py
@@ -0,0 +1,17 @@
+# delete_sheets.py
+
+import openpyxl
+
+def create_worksheets(path):
+ workbook = openpyxl.Workbook()
+ workbook.create_sheet()
+ # Insert a worksheet
+ workbook.create_sheet(index=1,
+ title='Second sheet')
+ print(workbook.sheetnames)
+ del workbook['Second sheet']
+ print(workbook.sheetnames)
+ workbook.save(path)
+
+if __name__ == '__main__':
+ create_worksheets('del_sheets.xlsx')
\ No newline at end of file
diff --git a/chapter38_excel/insert_demo.py b/chapter38_excel/insert_demo.py
new file mode 100644
index 0000000..8046b29
--- /dev/null
+++ b/chapter38_excel/insert_demo.py
@@ -0,0 +1,18 @@
+# insert_demo.py
+
+from openpyxl import Workbook
+
+def inserting_cols_rows(path):
+ workbook = Workbook()
+ sheet = workbook.active
+ sheet['A1'] = 'Hello'
+ sheet['A2'] = 'from'
+ sheet['A3'] = 'OpenPyXL'
+ # insert a column before A
+ sheet.insert_cols(idx=1)
+ # insert 2 rows starting on the second row
+ sheet.insert_rows(idx=2, amount=2)
+ workbook.save(path)
+
+if __name__ == '__main__':
+ inserting_cols_rows('inserting.xlsx')
\ No newline at end of file
diff --git a/chapter38_excel/iterating_over_cell_values.py b/chapter38_excel/iterating_over_cell_values.py
new file mode 100644
index 0000000..3f64c5e
--- /dev/null
+++ b/chapter38_excel/iterating_over_cell_values.py
@@ -0,0 +1,14 @@
+# iterating_over_cell_values.py
+
+from openpyxl import load_workbook
+
+def iterating_over_values(path):
+ workbook = load_workbook(filename=path)
+ sheet = workbook.active
+ for value in sheet.iter_rows(min_row=1, max_row=3,
+ min_col=1, max_col=3,
+ values_only=True):
+ print(value)
+
+if __name__ == '__main__':
+ iterating_over_values('books.xlsx')
\ No newline at end of file
diff --git a/chapter38_excel/iterating_over_cells.py b/chapter38_excel/iterating_over_cells.py
new file mode 100644
index 0000000..0cdd545
--- /dev/null
+++ b/chapter38_excel/iterating_over_cells.py
@@ -0,0 +1,12 @@
+# iterating_over_cells.py
+
+from openpyxl import load_workbook
+
+def iterating_range(path):
+ workbook = load_workbook(filename=path)
+ sheet = workbook.active
+ for cell in sheet['A']:
+ print(cell)
+
+if __name__ == '__main__':
+ iterating_range('books.xlsx')
\ No newline at end of file
diff --git a/chapter38_excel/open_workbook.py b/chapter38_excel/open_workbook.py
new file mode 100644
index 0000000..8e889d7
--- /dev/null
+++ b/chapter38_excel/open_workbook.py
@@ -0,0 +1,13 @@
+# open_workbook.py
+
+from openpyxl import load_workbook
+
+def open_workbook(path):
+ workbook = load_workbook(filename=path)
+ print(f'Worksheet names: {workbook.sheetnames}')
+ sheet = workbook.active
+ print(sheet)
+ print(f'The title of the Worksheet is: {sheet.title}')
+
+if __name__ == '__main__':
+ open_workbook('books.xlsx')
\ No newline at end of file
diff --git a/chapter38_excel/remove_sheets.py b/chapter38_excel/remove_sheets.py
new file mode 100644
index 0000000..9dbeb4f
--- /dev/null
+++ b/chapter38_excel/remove_sheets.py
@@ -0,0 +1,17 @@
+# remove_sheets.py
+
+import openpyxl
+
+def create_worksheets(path):
+ workbook = openpyxl.Workbook()
+ sheet1 = workbook.create_sheet()
+ # Insert a worksheet
+ workbook.create_sheet(index=1,
+ title='Second sheet')
+ print(workbook.sheetnames)
+ workbook.remove(workbook['Second sheet'])
+ print(workbook.sheetnames)
+ workbook.save(path)
+
+if __name__ == '__main__':
+ create_worksheets('remove_sheets.xlsx')
\ No newline at end of file
diff --git a/chapter38_excel/workbook_cells.py b/chapter38_excel/workbook_cells.py
new file mode 100644
index 0000000..82d6668
--- /dev/null
+++ b/chapter38_excel/workbook_cells.py
@@ -0,0 +1,24 @@
+# workbook_cells.py
+
+from openpyxl import load_workbook
+
+def get_cell_info(path):
+ workbook = load_workbook(filename=path)
+ sheet = workbook.active
+ print(sheet)
+ print(f'The title of the Worksheet is: {sheet.title}')
+ print(f'The value of {sheet["A2"].value=}')
+ print(f'The value of {sheet["A3"].value=}')
+ cell = sheet['B3']
+ print(f'{cell.value=}')
+
+def get_info_by_coord(path):
+ workbook = load_workbook(filename=path)
+ sheet = workbook.active
+ cell = sheet['A2']
+ print(f'Row {cell.row}, Col {cell.column} = {cell.value}')
+ print(f'{cell.value=} is at {cell.coordinate=}')
+
+if __name__ == '__main__':
+ get_cell_info('books.xlsx')
+ get_info_by_coord('books.xlsx')
\ No newline at end of file
diff --git a/chapter38_excel/writing_hello.py b/chapter38_excel/writing_hello.py
new file mode 100644
index 0000000..09be8ec
--- /dev/null
+++ b/chapter38_excel/writing_hello.py
@@ -0,0 +1,14 @@
+# writing_hello.py
+
+from openpyxl import Workbook
+
+def create_workbook(path):
+ workbook = Workbook()
+ sheet = workbook.active
+ sheet['A1'] = 'Hello'
+ sheet['A2'] = 'from'
+ sheet['A3'] = 'OpenPyXL'
+ workbook.save(path)
+
+if __name__ == '__main__':
+ create_workbook('hello.xlsx')
\ No newline at end of file
diff --git a/chapter39_reportlab/canvas_form.py b/chapter39_reportlab/canvas_form.py
new file mode 100644
index 0000000..f7cc75e
--- /dev/null
+++ b/chapter39_reportlab/canvas_form.py
@@ -0,0 +1,23 @@
+# canvas_form.py
+
+from reportlab.lib.pagesizes import letter
+from reportlab.pdfgen import canvas
+
+def form(path):
+ my_canvas = canvas.Canvas(path, pagesize=letter)
+ my_canvas.setLineWidth(.3)
+ my_canvas.setFont('Helvetica', 12)
+ my_canvas.drawString(30, 750, 'OFFICIAL COMMUNIQUE')
+ my_canvas.drawString(30, 735, 'OF ACME INDUSTRIES')
+ my_canvas.drawString(500, 750, "12/12/2010")
+ my_canvas.line(480, 747, 580, 747)
+ my_canvas.drawString(275, 725, 'AMOUNT OWED:')
+ my_canvas.drawString(500, 725, "$1,000.00")
+ my_canvas.line(378, 723, 580, 723)
+ my_canvas.drawString(30, 703, 'RECEIVED BY:')
+ my_canvas.line(120, 700, 580, 700)
+ my_canvas.drawString(120, 703, "JOHN DOE")
+ my_canvas.save()
+
+if __name__ == '__main__':
+ form('canvas_form.pdf')
\ No newline at end of file
diff --git a/chapter39_reportlab/drawing_polygons.py b/chapter39_reportlab/drawing_polygons.py
new file mode 100644
index 0000000..8c838e7
--- /dev/null
+++ b/chapter39_reportlab/drawing_polygons.py
@@ -0,0 +1,16 @@
+# drawing_polygons.py
+
+from reportlab.lib.pagesizes import letter
+from reportlab.pdfgen import canvas
+
+def draw_shapes():
+ my_canvas = canvas.Canvas("drawing_polygons.pdf")
+ my_canvas.setStrokeColorRGB(0.2, 0.5, 0.3)
+ my_canvas.rect(10, 740, 100, 80, stroke=1, fill=0)
+ my_canvas.ellipse(10, 680, 100, 630, stroke=1, fill=1)
+ my_canvas.wedge(10, 600, 100, 550, 45, 90, stroke=1, fill=0)
+ my_canvas.circle(300, 600, 50)
+ my_canvas.save()
+
+if __name__ == '__main__':
+ draw_shapes()
\ No newline at end of file
diff --git a/chapter39_reportlab/hello_platypus.py b/chapter39_reportlab/hello_platypus.py
new file mode 100644
index 0000000..7e29ee4
--- /dev/null
+++ b/chapter39_reportlab/hello_platypus.py
@@ -0,0 +1,25 @@
+# hello_platypus.py
+
+from reportlab.lib.pagesizes import letter
+from reportlab.platypus import SimpleDocTemplate, Paragraph
+from reportlab.lib.styles import getSampleStyleSheet
+
+def hello():
+ doc = SimpleDocTemplate("hello_platypus.pdf",
+ pagesize=letter,
+ rightMargin=72,
+ leftMargin=72,
+ topMargin=72,
+ bottomMargin=18)
+ styles = getSampleStyleSheet()
+
+ flowables = []
+
+ text = "Hello, I'm a Paragraph"
+ para = Paragraph(text, style=styles["Normal"])
+ flowables.append(para)
+
+ doc.build(flowables)
+
+if __name__ == '__main__':
+ hello()
\ No newline at end of file
diff --git a/chapter39_reportlab/hello_reportlab.py b/chapter39_reportlab/hello_reportlab.py
new file mode 100644
index 0000000..f1d75f9
--- /dev/null
+++ b/chapter39_reportlab/hello_reportlab.py
@@ -0,0 +1,7 @@
+# hello_reportlab.py
+
+from reportlab.pdfgen import canvas
+
+my_canvas = canvas.Canvas("hello.pdf")
+my_canvas.drawString(100, 750, "Welcome to Reportlab!")
+my_canvas.save()
\ No newline at end of file
diff --git a/chapter39_reportlab/image_on_canvas.py b/chapter39_reportlab/image_on_canvas.py
new file mode 100644
index 0000000..8be2a08
--- /dev/null
+++ b/chapter39_reportlab/image_on_canvas.py
@@ -0,0 +1,16 @@
+# image_on_canvas.py
+
+from reportlab.lib.pagesizes import letter
+from reportlab.pdfgen import canvas
+
+
+def add_image(image_path):
+ my_canvas = canvas.Canvas("canvas_image.pdf",
+ pagesize=letter)
+ my_canvas.drawImage(image_path, 30, 600,
+ width=100, height=100)
+ my_canvas.save()
+
+if __name__ == '__main__':
+ image_path = 'snakehead.jpg'
+ add_image(image_path)
\ No newline at end of file
diff --git a/chapter39_reportlab/platypus_multipage.py b/chapter39_reportlab/platypus_multipage.py
new file mode 100644
index 0000000..9a0fe76
--- /dev/null
+++ b/chapter39_reportlab/platypus_multipage.py
@@ -0,0 +1,26 @@
+# platypus_multipage.py
+
+from reportlab.lib.pagesizes import letter
+from reportlab.lib.styles import getSampleStyleSheet
+from reportlab.lib.units import inch
+from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer
+
+
+def create_document():
+ doc = SimpleDocTemplate("platypus_multipage.pdf",
+ pagesize=letter)
+ styles = getSampleStyleSheet()
+ flowables = []
+ spacer = Spacer(1, 0.25*inch)
+
+ # Create a lot of content to make a multipage PDF
+ for i in range(50):
+ text = 'Paragraph #{}'.format(i)
+ para = Paragraph(text, styles["Normal"])
+ flowables.append(para)
+ flowables.append(spacer)
+
+ doc.build(flowables)
+
+if __name__ == '__main__':
+ create_document()
\ No newline at end of file
diff --git a/chapter39_reportlab/simple_table.py b/chapter39_reportlab/simple_table.py
new file mode 100644
index 0000000..19c201d
--- /dev/null
+++ b/chapter39_reportlab/simple_table.py
@@ -0,0 +1,21 @@
+# simple_table.py
+
+from reportlab.lib.pagesizes import letter
+from reportlab.platypus import SimpleDocTemplate, Table
+
+def simple_table():
+ doc = SimpleDocTemplate("simple_table.pdf", pagesize=letter)
+ flowables = []
+
+ data = [['col_{}'.format(x) for x in range(1, 6)],
+ [str(x) for x in range(1, 6)],
+ ['a', 'b', 'c', 'd', 'e']
+ ]
+
+ tbl = Table(data)
+ flowables.append(tbl)
+
+ doc.build(flowables)
+
+if __name__ == '__main__':
+ simple_table()
\ No newline at end of file
diff --git a/chapter39_reportlab/simple_table_with_style.py b/chapter39_reportlab/simple_table_with_style.py
new file mode 100644
index 0000000..db47c7e
--- /dev/null
+++ b/chapter39_reportlab/simple_table_with_style.py
@@ -0,0 +1,29 @@
+# simple_table_with_style.py
+
+from reportlab.lib import colors
+from reportlab.lib.pagesizes import letter
+from reportlab.platypus import SimpleDocTemplate, Table, TableStyle
+
+def simple_table_with_style():
+ doc = SimpleDocTemplate("simple_table_with_style.pdf",
+ pagesize=letter)
+ flowables = []
+
+ data = [['col_{}'.format(x) for x in range(1, 6)],
+ [str(x) for x in range(1, 6)],
+ ['a', 'b', 'c', 'd', 'e']
+ ]
+
+ tblstyle = TableStyle(
+ [('BACKGROUND', (0, 0), (-1, 0), colors.red),
+ ('TEXTCOLOR', (0, 1), (-1, 1), colors.blue)
+ ])
+
+ tbl = Table(data)
+ tbl.setStyle(tblstyle)
+ flowables.append(tbl)
+
+ doc.build(flowables)
+
+if __name__ == '__main__':
+ simple_table_with_style()
\ No newline at end of file
diff --git a/chapter39_reportlab/snakehead.jpg b/chapter39_reportlab/snakehead.jpg
new file mode 100644
index 0000000..db89582
Binary files /dev/null and b/chapter39_reportlab/snakehead.jpg differ
diff --git a/chapter40_graphs/bar_chart.py b/chapter40_graphs/bar_chart.py
new file mode 100644
index 0000000..11808f3
--- /dev/null
+++ b/chapter40_graphs/bar_chart.py
@@ -0,0 +1,14 @@
+# bar_chart.py
+
+import matplotlib.pyplot as plt
+
+def bar_chart(numbers, labels, pos):
+ plt.bar(pos, numbers, color='blue')
+ plt.xticks(ticks=pos, labels=labels)
+ plt.show()
+
+if __name__ == '__main__':
+ numbers = [2, 1, 4, 6]
+ labels = ['Electric', 'Solar', 'Diesel', 'Unleaded']
+ pos = list(range(4))
+ bar_chart(numbers, labels, pos)
\ No newline at end of file
diff --git a/chapter40_graphs/bar_chart.title.py b/chapter40_graphs/bar_chart.title.py
new file mode 100644
index 0000000..a68a7b9
--- /dev/null
+++ b/chapter40_graphs/bar_chart.title.py
@@ -0,0 +1,18 @@
+# bar_chart_title.py
+
+import matplotlib.pyplot as plt
+
+def bar_chart(numbers, labels, pos):
+ plt.bar(pos, [4, 5, 6, 3], color='green')
+ plt.bar(pos, numbers, color='blue')
+ plt.xticks(ticks=pos, labels=labels)
+ plt.title('Gas Used in Various Vehicles')
+ plt.xlabel('Vehicle Types')
+ plt.ylabel('Number of Vehicles')
+ plt.show()
+
+if __name__ == '__main__':
+ numbers = [2, 1, 4, 6]
+ labels = ['Electric', 'Solar', 'Diesel', 'Unleaded']
+ pos = list(range(4))
+ bar_chart(numbers, labels, pos)
\ No newline at end of file
diff --git a/chapter40_graphs/bar_chart_labels.py b/chapter40_graphs/bar_chart_labels.py
new file mode 100644
index 0000000..2625f0f
--- /dev/null
+++ b/chapter40_graphs/bar_chart_labels.py
@@ -0,0 +1,16 @@
+# bar_chart_labels.py
+
+import matplotlib.pyplot as plt
+
+def bar_chart(numbers, labels, pos):
+ plt.bar(pos, numbers, color='blue')
+ plt.xticks(ticks=pos, labels=labels)
+ plt.xlabel('Vehicle Types')
+ plt.ylabel('Number of Vehicles')
+ plt.show()
+
+if __name__ == '__main__':
+ numbers = [2, 1, 4, 6]
+ labels = ['Electric', 'Solar', 'Diesel', 'Unleaded']
+ pos = list(range(4))
+ bar_chart(numbers, labels, pos)
\ No newline at end of file
diff --git a/chapter40_graphs/bar_chart_legend.py b/chapter40_graphs/bar_chart_legend.py
new file mode 100644
index 0000000..049be31
--- /dev/null
+++ b/chapter40_graphs/bar_chart_legend.py
@@ -0,0 +1,20 @@
+# bar_chart_legend.py
+
+import matplotlib.pyplot as plt
+
+def bar_chart(numbers, labels, pos):
+ plt.bar(pos, [4, 5, 6, 3], color='green')
+ plt.bar(pos, numbers, color='blue')
+ plt.xticks(ticks=pos, labels=labels)
+ plt.xlabel('Vehicle Types')
+ plt.ylabel('Number of Vehicles')
+ plt.title('Gas Used in Various Vehicles')
+ plt.legend(['First Label', 'Second Label'],
+ loc='upper left')
+ plt.show()
+
+if __name__ == '__main__':
+ numbers = [2, 1, 4, 6]
+ labels = ['Electric', 'Solar', 'Diesel', 'Unleaded']
+ pos = list(range(4))
+ bar_chart(numbers, labels, pos)
\ No newline at end of file
diff --git a/chapter40_graphs/bar_charth.py b/chapter40_graphs/bar_charth.py
new file mode 100644
index 0000000..cdd2763
--- /dev/null
+++ b/chapter40_graphs/bar_charth.py
@@ -0,0 +1,14 @@
+# bar_charth.py
+
+import matplotlib.pyplot as plt
+
+def bar_charth(numbers, labels, pos):
+ plt.barh(pos, numbers, color='blue')
+ plt.yticks(ticks=pos, labels=labels)
+ plt.show()
+
+if __name__ == '__main__':
+ numbers = [2, 1, 4, 6]
+ labels = ['Electric', 'Solar', 'Diesel', 'Unleaded']
+ pos = list(range(4))
+ bar_charth(numbers, labels, pos)
\ No newline at end of file
diff --git a/chapter40_graphs/line_plot.py b/chapter40_graphs/line_plot.py
new file mode 100644
index 0000000..03b7dd5
--- /dev/null
+++ b/chapter40_graphs/line_plot.py
@@ -0,0 +1,12 @@
+# line_plot.py
+
+import matplotlib.pyplot as plt
+
+def line_plot(numbers):
+ plt.plot(numbers)
+ plt.ylabel('Random numbers')
+ plt.show()
+
+if __name__ == '__main__':
+ numbers = [2, 4, 1, 6]
+ line_plot(numbers)
\ No newline at end of file
diff --git a/chapter40_graphs/multiple_figures.py b/chapter40_graphs/multiple_figures.py
new file mode 100644
index 0000000..ea4bf17
--- /dev/null
+++ b/chapter40_graphs/multiple_figures.py
@@ -0,0 +1,16 @@
+# multiple_figures.py
+
+import matplotlib.pyplot as plt
+
+def line_plot(numbers, numbers2):
+ first_plot = plt.figure(1)
+ plt.plot(numbers)
+
+ second_plot = plt.figure(2)
+ plt.plot(numbers2)
+ plt.show()
+
+if __name__ == '__main__':
+ numbers = [2, 4, 1, 6]
+ more_numbers = [5, 1, 10, 3]
+ line_plot(numbers, more_numbers)
\ No newline at end of file
diff --git a/chapter40_graphs/multiple_plots.py b/chapter40_graphs/multiple_plots.py
new file mode 100644
index 0000000..1b9b9af
--- /dev/null
+++ b/chapter40_graphs/multiple_plots.py
@@ -0,0 +1,18 @@
+# multiple_plots.py
+
+import matplotlib.pyplot as plt
+import numpy as np
+
+def multiple_plots():
+ # Some example data to display
+ x = np.linspace(0, 2 * np.pi, 400)
+ y = np.sin(x ** 2)
+
+ fig, axs = plt.subplots(2)
+ fig.suptitle('Vertically stacked subplots')
+ axs[0].plot(x, y)
+ axs[1].plot(x, -y)
+ plt.show()
+
+if __name__ == '__main__':
+ multiple_plots()
\ No newline at end of file
diff --git a/chapter40_graphs/multiple_plots2.py b/chapter40_graphs/multiple_plots2.py
new file mode 100644
index 0000000..f0487cf
--- /dev/null
+++ b/chapter40_graphs/multiple_plots2.py
@@ -0,0 +1,15 @@
+# multiple_plots2.py
+
+import matplotlib.pyplot as plt
+
+def multiple_plots():
+ numbers = [2, 4, 1, 6]
+ more_numbers = [5, 1, 10, 3]
+ fig, axs = plt.subplots(2)
+ fig.suptitle('Vertically stacked subplots')
+ axs[0].plot(numbers)
+ axs[1].plot(more_numbers)
+ plt.show()
+
+if __name__ == '__main__':
+ multiple_plots()
\ No newline at end of file
diff --git a/chapter40_graphs/pie_chart_fancy.py b/chapter40_graphs/pie_chart_fancy.py
new file mode 100644
index 0000000..fef5f86
--- /dev/null
+++ b/chapter40_graphs/pie_chart_fancy.py
@@ -0,0 +1,19 @@
+# pie_chart.py
+
+import matplotlib.pyplot as plt
+
+def pie_chart():
+ numbers = [40, 35, 15, 10]
+ labels = ['Python', 'Ruby', 'C++', 'PHP']
+ # Explode the first slice (Python)
+ explode = (0.1, 0, 0, 0)
+
+ fig1, ax1 = plt.subplots()
+ ax1.pie(numbers, explode=explode, labels=labels,
+ shadow=True, startangle=90,
+ autopct='%1.1f%%')
+ ax1.axis('equal')
+ plt.show()
+
+if __name__ == '__main__':
+ pie_chart()
\ No newline at end of file
diff --git a/chapter40_graphs/pie_chart_plain.py b/chapter40_graphs/pie_chart_plain.py
new file mode 100644
index 0000000..d28980c
--- /dev/null
+++ b/chapter40_graphs/pie_chart_plain.py
@@ -0,0 +1,14 @@
+# pie_chart_plain.py
+
+import matplotlib.pyplot as plt
+
+def pie_chart():
+ numbers = [40, 35, 15, 10]
+ labels = ['Python', 'Ruby', 'C++', 'PHP']
+
+ fig1, ax1 = plt.subplots()
+ ax1.pie(numbers, labels=labels)
+ plt.show()
+
+if __name__ == '__main__':
+ pie_chart()
\ No newline at end of file
diff --git a/chapter41_images/blur.py b/chapter41_images/blur.py
new file mode 100644
index 0000000..4dff2cf
--- /dev/null
+++ b/chapter41_images/blur.py
@@ -0,0 +1,13 @@
+# blur.py
+
+from PIL import Image
+from PIL import ImageFilter
+
+
+def blur(path, modified_photo):
+ image = Image.open(path)
+ blurred_image = image.filter(ImageFilter.BLUR)
+ blurred_image.save(modified_photo)
+
+if __name__ == '__main__':
+ blur('butterfly.jpg', 'butterfly_blurred.jpg')
\ No newline at end of file
diff --git a/chapter41_images/border.py b/chapter41_images/border.py
new file mode 100644
index 0000000..675d3ff
--- /dev/null
+++ b/chapter41_images/border.py
@@ -0,0 +1,20 @@
+# border.py
+
+from PIL import Image, ImageOps
+
+
+def add_border(input_image, output_image, border):
+ img = Image.open(input_image)
+
+ if isinstance(border, int) or isinstance(border, tuple):
+ bimg = ImageOps.expand(img, border=border)
+ else:
+ raise RuntimeError('Border is not an integer or tuple!')
+
+ bimg.save(output_image)
+
+if __name__ == '__main__':
+ in_img = 'butterfly_grey.jpg'
+
+ add_border(in_img, output_image='butterfly_border.jpg',
+ border=100)
diff --git a/chapter41_images/border2.py b/chapter41_images/border2.py
new file mode 100644
index 0000000..671cbd1
--- /dev/null
+++ b/chapter41_images/border2.py
@@ -0,0 +1,20 @@
+# border2.py
+
+from PIL import Image, ImageOps
+
+
+def add_border(input_image, output_image, border):
+ img = Image.open(input_image)
+
+ if isinstance(border, int) or isinstance(border, tuple):
+ bimg = ImageOps.expand(img, border=border)
+ else:
+ raise RuntimeError('Border is not an integer or tuple!')
+
+ bimg.save(output_image)
+
+if __name__ == '__main__':
+ in_img = 'butterfly_grey.jpg'
+
+ add_border(in_img, output_image='butterfly_border2.jpg',
+ border=(10, 50))
diff --git a/chapter41_images/butterfly.jpg b/chapter41_images/butterfly.jpg
new file mode 100644
index 0000000..9750c31
Binary files /dev/null and b/chapter41_images/butterfly.jpg differ
diff --git a/chapter41_images/butterfly_grey.jpg b/chapter41_images/butterfly_grey.jpg
new file mode 100644
index 0000000..f416524
Binary files /dev/null and b/chapter41_images/butterfly_grey.jpg differ
diff --git a/chapter41_images/colored_border.py b/chapter41_images/colored_border.py
new file mode 100644
index 0000000..2c2d37a
--- /dev/null
+++ b/chapter41_images/colored_border.py
@@ -0,0 +1,25 @@
+# colored_border.py
+
+from PIL import Image, ImageOps
+
+def add_border(input_image, output_image, border, color=0):
+ img = Image.open(input_image)
+
+ if isinstance(border, int) or isinstance(
+ border, tuple):
+ bimg = ImageOps.expand(img,
+ border=border,
+ fill=color)
+ else:
+ msg = 'Border is not an integer or tuple!'
+ raise RuntimeError(msg)
+
+ bimg.save(output_image)
+
+if __name__ == '__main__':
+ in_img = 'butterfly_grey.jpg'
+
+ add_border(in_img,
+ output_image='butterfly_border_red.jpg',
+ border=100,
+ color='indianred')
\ No newline at end of file
diff --git a/chapter41_images/cropping.py b/chapter41_images/cropping.py
new file mode 100644
index 0000000..7eb5883
--- /dev/null
+++ b/chapter41_images/cropping.py
@@ -0,0 +1,12 @@
+# cropping.py
+
+from PIL import Image
+
+
+def crop_image(path, cropped_path):
+ image = Image.open(path)
+ cropped = image.crop((40, 590, 979, 1500))
+ cropped.save(cropped_path)
+
+if __name__ == '__main__':
+ crop_image('ducks.jpg', 'ducks_cropped.jpg')
\ No newline at end of file
diff --git a/chapter41_images/ducks.jpg b/chapter41_images/ducks.jpg
new file mode 100644
index 0000000..86837a1
Binary files /dev/null and b/chapter41_images/ducks.jpg differ
diff --git a/chapter41_images/get_histogram.py b/chapter41_images/get_histogram.py
new file mode 100644
index 0000000..32964ab
--- /dev/null
+++ b/chapter41_images/get_histogram.py
@@ -0,0 +1,16 @@
+# get_histrogram.py
+
+import matplotlib.pyplot as plt
+
+from PIL import Image
+
+
+def get_image_histrogram(path):
+ image = Image.open(path)
+ histogram = image.histogram()
+ plt.hist(histogram, bins=len(histogram))
+ plt.xlabel('Histogram')
+ plt.show()
+
+if __name__ == '__main__':
+ get_image_histrogram('butterfly.jpg')
\ No newline at end of file
diff --git a/chapter41_images/get_image_info.py b/chapter41_images/get_image_info.py
new file mode 100644
index 0000000..1b6736f
--- /dev/null
+++ b/chapter41_images/get_image_info.py
@@ -0,0 +1,12 @@
+# get_image_info.py
+
+from PIL import Image
+
+def get_image_info(path):
+ image = Image.open(path)
+ print(f'This image is {image.width} x {image.height}')
+ exif = image._getexif()
+ print(exif)
+
+if __name__ == '__main__':
+ get_image_info('ducks.jpg')
\ No newline at end of file
diff --git a/chapter41_images/jellyfish.jpg b/chapter41_images/jellyfish.jpg
new file mode 100644
index 0000000..3cee85e
Binary files /dev/null and b/chapter41_images/jellyfish.jpg differ
diff --git a/chapter41_images/lizard.jpg b/chapter41_images/lizard.jpg
new file mode 100644
index 0000000..535baf8
Binary files /dev/null and b/chapter41_images/lizard.jpg differ
diff --git a/chapter41_images/open_image.py b/chapter41_images/open_image.py
new file mode 100644
index 0000000..3459f7c
--- /dev/null
+++ b/chapter41_images/open_image.py
@@ -0,0 +1,6 @@
+# open_image.py
+
+from PIL import Image
+
+image = Image.open('jellyfish.jpg')
+image.show()
\ No newline at end of file
diff --git a/chapter41_images/resize_image.py b/chapter41_images/resize_image.py
new file mode 100644
index 0000000..bec9953
--- /dev/null
+++ b/chapter41_images/resize_image.py
@@ -0,0 +1,23 @@
+# resize_image.py
+
+from PIL import Image
+
+def resize_image(input_image_path,
+ output_image_path,
+ size):
+ original_image = Image.open(input_image_path)
+ width, height = original_image.size
+ print(f'The original image size is {width} wide x {height} '
+ f'high')
+
+ resized_image = original_image.resize(size)
+ width, height = resized_image.size
+ print(f'The resized image size is {width} wide x {height} '
+ f'high')
+ resized_image.show()
+ resized_image.save(output_image_path)
+
+if __name__ == '__main__':
+ resize_image(input_image_path='lizard.jpg',
+ output_image_path='lizard_small.jpg',
+ size=(800, 400))
\ No newline at end of file
diff --git a/chapter41_images/scale_image.py b/chapter41_images/scale_image.py
new file mode 100644
index 0000000..aa2febc
--- /dev/null
+++ b/chapter41_images/scale_image.py
@@ -0,0 +1,37 @@
+# scale_image.py
+
+from PIL import Image
+
+def scale_image(input_image_path,
+ output_image_path,
+ width=None,
+ height=None
+ ):
+ original_image = Image.open(input_image_path)
+ w, h = original_image.size
+ print(f'The original image size is {w} wide x {h} '
+ 'high')
+
+ if width and height:
+ max_size = (width, height)
+ elif width:
+ max_size = (width, h)
+ elif height:
+ max_size = (w, height)
+ else:
+ # No width or height specified
+ raise ValueError('Width or height required!')
+
+ original_image.thumbnail(max_size, Image.ANTIALIAS)
+ original_image.save(output_image_path)
+
+ scaled_image = Image.open(output_image_path)
+ width, height = scaled_image.size
+ print(f'The scaled image size is {width} wide x {height} '
+ 'high')
+
+
+if __name__ == '__main__':
+ scale_image(input_image_path='lizard.jpg',
+ output_image_path='lizard_scaled.jpg',
+ width=800)
\ No newline at end of file
diff --git a/chapter41_images/sharpen.py b/chapter41_images/sharpen.py
new file mode 100644
index 0000000..494b0fa
--- /dev/null
+++ b/chapter41_images/sharpen.py
@@ -0,0 +1,13 @@
+# sharpen.py
+
+from PIL import Image
+from PIL import ImageFilter
+
+
+def sharpen(path, modified_photo):
+ image = Image.open(path)
+ sharpened_image = image.filter(ImageFilter.SHARPEN)
+ sharpened_image.save(modified_photo)
+
+if __name__ == '__main__':
+ sharpen('butterfly.jpg', 'butterfly_sharper.jpg')
\ No newline at end of file
diff --git a/chapter42_gui/button_events.py b/chapter42_gui/button_events.py
new file mode 100644
index 0000000..b282fab
--- /dev/null
+++ b/chapter42_gui/button_events.py
@@ -0,0 +1,41 @@
+# button_events.py
+
+import wx
+
+
+class MyPanel(wx.Panel):
+
+ def __init__(self, parent):
+ super().__init__(parent)
+
+ button = wx.Button(self, label='Press Me')
+ button.Bind(wx.EVT_BUTTON, self.on_button1)
+ button2 = wx.Button(self, label='Second button')
+ button2.Bind(wx.EVT_BUTTON, self.on_button2)
+
+ main_sizer = wx.BoxSizer(wx.HORIZONTAL)
+ main_sizer.Add(button, proportion=1,
+ flag=wx.ALL | wx.CENTER | wx.EXPAND,
+ border=5)
+ main_sizer.Add(button2, 0, wx.ALL, 5)
+ self.SetSizer(main_sizer)
+
+ def on_button1(self, event):
+ print('You clicked the first button')
+
+ def on_button2(self, event):
+ print('You clicked the second button')
+
+
+class MyFrame(wx.Frame):
+
+ def __init__(self):
+ super().__init__(None, title='Hello World')
+ panel = MyPanel(self)
+ self.Show()
+
+
+if __name__ == '__main__':
+ app = wx.App(redirect=False)
+ frame = MyFrame()
+ app.MainLoop()
\ No newline at end of file
diff --git a/chapter42_gui/hello_with_panel.py b/chapter42_gui/hello_with_panel.py
new file mode 100644
index 0000000..f6bb3e8
--- /dev/null
+++ b/chapter42_gui/hello_with_panel.py
@@ -0,0 +1,23 @@
+# hello_with_panel.py
+
+import wx
+
+
+class MyPanel(wx.Panel):
+
+ def __init__(self, parent):
+ super().__init__(parent)
+ button = wx.Button(self, label='Press Me')
+
+class MyFrame(wx.Frame):
+
+ def __init__(self):
+ super().__init__(None, title='Hello World')
+ panel = MyPanel(self)
+ self.Show()
+
+
+if __name__ == '__main__':
+ app = wx.App(redirect=False)
+ frame = MyFrame()
+ app.MainLoop()
\ No newline at end of file
diff --git a/chapter42_gui/hello_wx.py b/chapter42_gui/hello_wx.py
new file mode 100644
index 0000000..7dfe858
--- /dev/null
+++ b/chapter42_gui/hello_wx.py
@@ -0,0 +1,8 @@
+# hello_wx.py
+
+import wx
+
+app = wx.App(False)
+frame = wx.Frame(parent=None, title='Hello World')
+frame.Show()
+app.MainLoop()
diff --git a/chapter42_gui/hello_wx_class.py b/chapter42_gui/hello_wx_class.py
new file mode 100644
index 0000000..7a23617
--- /dev/null
+++ b/chapter42_gui/hello_wx_class.py
@@ -0,0 +1,15 @@
+# hello_wx_class.py
+
+import wx
+
+class MyFrame(wx.Frame):
+
+ def __init__(self):
+ super().__init__(None, title='Hello World')
+ self.Show()
+
+if __name__ == '__main__':
+ app = wx.App(False)
+ frame = MyFrame()
+ frame.Show()
+ app.MainLoop()
diff --git a/chapter42_gui/image_viewer.py b/chapter42_gui/image_viewer.py
new file mode 100644
index 0000000..38dd872
--- /dev/null
+++ b/chapter42_gui/image_viewer.py
@@ -0,0 +1,34 @@
+# image_viewer.py
+
+import wx
+
+class ImagePanel(wx.Panel):
+
+ def __init__(self, parent, image_size):
+ super().__init__(parent)
+
+ img = wx.Image(*image_size)
+ self.image_ctrl = wx.StaticBitmap(self,
+ bitmap=wx.Bitmap(img))
+ browse_btn = wx.Button(self, label='Browse')
+
+ main_sizer = wx.BoxSizer(wx.VERTICAL)
+ main_sizer.Add(self.image_ctrl, 0, wx.ALL, 5)
+ main_sizer.Add(browse_btn)
+ self.SetSizer(main_sizer)
+ main_sizer.Fit(parent)
+ self.Layout()
+
+
+class MainFrame(wx.Frame):
+
+ def __init__(self):
+ super().__init__(None, title='Image Viewer')
+ panel = ImagePanel(self, image_size=(240,240))
+ self.Show()
+
+
+if __name__ == '__main__':
+ app = wx.App(redirect=False)
+ frame = MainFrame()
+ app.MainLoop()
\ No newline at end of file
diff --git a/chapter42_gui/image_viewer_working.py b/chapter42_gui/image_viewer_working.py
new file mode 100644
index 0000000..3abd01f
--- /dev/null
+++ b/chapter42_gui/image_viewer_working.py
@@ -0,0 +1,78 @@
+# image_viewer_working.py
+
+import wx
+
+class ImagePanel(wx.Panel):
+
+ def __init__(self, parent, image_size):
+ super().__init__(parent)
+ self.max_size = 240
+
+ img = wx.Image(*image_size)
+ self.image_ctrl = wx.StaticBitmap(self,
+ bitmap=wx.Bitmap(img))
+
+ browse_btn = wx.Button(self, label='Browse')
+ browse_btn.Bind(wx.EVT_BUTTON, self.on_browse)
+
+ self.photo_txt = wx.TextCtrl(self, size=(200, -1))
+
+ main_sizer = wx.BoxSizer(wx.VERTICAL)
+ hsizer = wx.BoxSizer(wx.HORIZONTAL)
+
+ main_sizer.Add(self.image_ctrl, 0, wx.ALL, 5)
+ hsizer.Add(browse_btn, 0, wx.ALL, 5)
+ hsizer.Add(self.photo_txt, 0, wx.ALL, 5)
+ main_sizer.Add(hsizer, 0, wx.ALL, 5)
+
+ self.SetSizer(main_sizer)
+ main_sizer.Fit(parent)
+ self.Layout()
+
+ def on_browse(self, event):
+ """
+ Browse for an image file
+ @param event: The event object
+ """
+ wildcard = "JPEG files (*.jpg)|*.jpg"
+ with wx.FileDialog(None, "Choose a file",
+ wildcard=wildcard,
+ style=wx.ID_OPEN) as dialog:
+ if dialog.ShowModal() == wx.ID_OK:
+ self.photo_txt.SetValue(dialog.GetPaths()[0])
+ self.load_image()
+
+ def load_image(self):
+ """
+ Load the image and display it to the user
+ """
+ filepath = self.photo_txt.GetValue()
+ img = wx.Image(filepath, wx.BITMAP_TYPE_ANY)
+
+ # scale the image, preserving the aspect ratio
+ W = img.GetWidth()
+ H = img.GetHeight()
+ if W > H:
+ NewW = self.max_size
+ NewH = self.max_size * H / W
+ else:
+ NewH = self.max_size
+ NewW = self.max_size * W / H
+ img = img.Scale(NewW,NewH)
+
+ self.image_ctrl.SetBitmap(wx.Bitmap(img))
+ self.Refresh()
+
+
+class MainFrame(wx.Frame):
+
+ def __init__(self):
+ super().__init__(None, title='Image Viewer')
+ panel = ImagePanel(self, image_size=(240,240))
+ self.Show()
+
+
+if __name__ == '__main__':
+ app = wx.App(redirect=False)
+ frame = MainFrame()
+ app.MainLoop()
\ No newline at end of file
diff --git a/chapter42_gui/sizer_with_two_widgets.py b/chapter42_gui/sizer_with_two_widgets.py
new file mode 100644
index 0000000..f69c706
--- /dev/null
+++ b/chapter42_gui/sizer_with_two_widgets.py
@@ -0,0 +1,33 @@
+# sizer_with_two_widgets.py
+
+import wx
+
+
+class MyPanel(wx.Panel):
+
+ def __init__(self, parent):
+ super().__init__(parent)
+
+ button = wx.Button(self, label='Press Me')
+ button2 = wx.Button(self, label='Second button')
+
+ main_sizer = wx.BoxSizer(wx.HORIZONTAL)
+ main_sizer.Add(button, proportion=1,
+ flag=wx.ALL | wx.CENTER | wx.EXPAND,
+ border=5)
+ main_sizer.Add(button2, 0, wx.ALL, 5)
+ self.SetSizer(main_sizer)
+
+
+class MyFrame(wx.Frame):
+
+ def __init__(self):
+ super().__init__(None, title='Hello World')
+ panel = MyPanel(self)
+ self.Show()
+
+
+if __name__ == '__main__':
+ app = wx.App(redirect=False)
+ frame = MyFrame()
+ app.MainLoop()
\ No newline at end of file
diff --git a/chapter42_gui/stacked_buttons.py b/chapter42_gui/stacked_buttons.py
new file mode 100644
index 0000000..f8ce7de
--- /dev/null
+++ b/chapter42_gui/stacked_buttons.py
@@ -0,0 +1,25 @@
+# stacked_buttons.py
+
+import wx
+
+
+class MyPanel(wx.Panel):
+
+ def __init__(self, parent):
+ super().__init__(parent)
+ button = wx.Button(self, label='Press Me')
+ button2 = wx.Button(self, label='Press Me too')
+ button3 = wx.Button(self, label='Another button')
+
+class MyFrame(wx.Frame):
+
+ def __init__(self):
+ super().__init__(None, title='Hello World')
+ panel = MyPanel(self)
+ self.Show()
+
+
+if __name__ == '__main__':
+ app = wx.App(redirect=False)
+ frame = MyFrame()
+ app.MainLoop()
\ No newline at end of file
diff --git a/chapter43_packages/arithmetic/__init__.py b/chapter43_packages/arithmetic/__init__.py
new file mode 100644
index 0000000..be3c5fc
--- /dev/null
+++ b/chapter43_packages/arithmetic/__init__.py
@@ -0,0 +1,2 @@
+# __init__.py
+from . import add
\ No newline at end of file
diff --git a/chapter43_packages/arithmetic/add.py b/chapter43_packages/arithmetic/add.py
new file mode 100644
index 0000000..07fb1d6
--- /dev/null
+++ b/chapter43_packages/arithmetic/add.py
@@ -0,0 +1,4 @@
+# add.py
+
+def add(x, y):
+ return x + y
\ No newline at end of file
diff --git a/chapter43_packages/arithmetic/divide.py b/chapter43_packages/arithmetic/divide.py
new file mode 100644
index 0000000..1f6707b
--- /dev/null
+++ b/chapter43_packages/arithmetic/divide.py
@@ -0,0 +1,4 @@
+# divide.py
+
+def divide(x, y):
+ return x / y
\ No newline at end of file
diff --git a/chapter43_packages/arithmetic/multiply.py b/chapter43_packages/arithmetic/multiply.py
new file mode 100644
index 0000000..0a4dcbc
--- /dev/null
+++ b/chapter43_packages/arithmetic/multiply.py
@@ -0,0 +1,4 @@
+# multiply.py
+
+def multiply(x, y):
+ return x * y
\ No newline at end of file
diff --git a/chapter43_packages/arithmetic/subtract.py b/chapter43_packages/arithmetic/subtract.py
new file mode 100644
index 0000000..f9340ac
--- /dev/null
+++ b/chapter43_packages/arithmetic/subtract.py
@@ -0,0 +1,4 @@
+# subtract.py
+
+def subtract(x, y):
+ return x - y
\ No newline at end of file
diff --git a/chapter43_packages/module/arithmetic.py b/chapter43_packages/module/arithmetic.py
new file mode 100644
index 0000000..f49c8db
--- /dev/null
+++ b/chapter43_packages/module/arithmetic.py
@@ -0,0 +1,13 @@
+# arithmetic.py
+
+def add(x, y):
+ return x + y
+
+def divide(x, y):
+ return x / y
+
+def multiply(x, y):
+ return x * y
+
+def subtract(x, y):
+ return x - y
diff --git a/chapter43_packages/module/test_arithmetic.py b/chapter43_packages/module/test_arithmetic.py
new file mode 100644
index 0000000..f259dcf
--- /dev/null
+++ b/chapter43_packages/module/test_arithmetic.py
@@ -0,0 +1,22 @@
+# test_arithmetic.py
+
+import arithmetic
+import unittest
+
+class TestArithmetic(unittest.TestCase):
+
+ def test_addition(self):
+ self.assertEqual(arithmetic.add(1, 2), 3)
+
+ def test_subtraction(self):
+ self.assertEqual(arithmetic.subtract(2, 1), 1)
+
+ def test_multiplication(self):
+ self.assertEqual(arithmetic.multiply(5, 5), 25)
+
+ def test_division(self):
+ self.assertEqual(arithmetic.divide(8, 2), 4)
+
+
+if __name__ == '__main__':
+ unittest.main()
\ No newline at end of file
diff --git a/chapter43_packages/my_package/LICENSE b/chapter43_packages/my_package/LICENSE
new file mode 100644
index 0000000..153d416
--- /dev/null
+++ b/chapter43_packages/my_package/LICENSE
@@ -0,0 +1,165 @@
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+
+ This version of the GNU Lesser General Public License incorporates
+the terms and conditions of version 3 of the GNU General Public
+License, supplemented by the additional permissions listed below.
+
+ 0. Additional Definitions.
+
+ As used herein, "this License" refers to version 3 of the GNU Lesser
+General Public License, and the "GNU GPL" refers to version 3 of the GNU
+General Public License.
+
+ "The Library" refers to a covered work governed by this License,
+other than an Application or a Combined Work as defined below.
+
+ An "Application" is any work that makes use of an interface provided
+by the Library, but which is not otherwise based on the Library.
+Defining a subclass of a class defined by the Library is deemed a mode
+of using an interface provided by the Library.
+
+ A "Combined Work" is a work produced by combining or linking an
+Application with the Library. The particular version of the Library
+with which the Combined Work was made is also called the "Linked
+Version".
+
+ The "Minimal Corresponding Source" for a Combined Work means the
+Corresponding Source for the Combined Work, excluding any source code
+for portions of the Combined Work that, considered in isolation, are
+based on the Application, and not on the Linked Version.
+
+ The "Corresponding Application Code" for a Combined Work means the
+object code and/or source code for the Application, including any data
+and utility programs needed for reproducing the Combined Work from the
+Application, but excluding the System Libraries of the Combined Work.
+
+ 1. Exception to Section 3 of the GNU GPL.
+
+ You may convey a covered work under sections 3 and 4 of this License
+without being bound by section 3 of the GNU GPL.
+
+ 2. Conveying Modified Versions.
+
+ If you modify a copy of the Library, and, in your modifications, a
+facility refers to a function or data to be supplied by an Application
+that uses the facility (other than as an argument passed when the
+facility is invoked), then you may convey a copy of the modified
+version:
+
+ a) under this License, provided that you make a good faith effort to
+ ensure that, in the event an Application does not supply the
+ function or data, the facility still operates, and performs
+ whatever part of its purpose remains meaningful, or
+
+ b) under the GNU GPL, with none of the additional permissions of
+ this License applicable to that copy.
+
+ 3. Object Code Incorporating Material from Library Header Files.
+
+ The object code form of an Application may incorporate material from
+a header file that is part of the Library. You may convey such object
+code under terms of your choice, provided that, if the incorporated
+material is not limited to numerical parameters, data structure
+layouts and accessors, or small macros, inline functions and templates
+(ten or fewer lines in length), you do both of the following:
+
+ a) Give prominent notice with each copy of the object code that the
+ Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the object code with a copy of the GNU GPL and this license
+ document.
+
+ 4. Combined Works.
+
+ You may convey a Combined Work under terms of your choice that,
+taken together, effectively do not restrict modification of the
+portions of the Library contained in the Combined Work and reverse
+engineering for debugging such modifications, if you also do each of
+the following:
+
+ a) Give prominent notice with each copy of the Combined Work that
+ the Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the Combined Work with a copy of the GNU GPL and this license
+ document.
+
+ c) For a Combined Work that displays copyright notices during
+ execution, include the copyright notice for the Library among
+ these notices, as well as a reference directing the user to the
+ copies of the GNU GPL and this license document.
+
+ d) Do one of the following:
+
+ 0) Convey the Minimal Corresponding Source under the terms of this
+ License, and the Corresponding Application Code in a form
+ suitable for, and under terms that permit, the user to
+ recombine or relink the Application with a modified version of
+ the Linked Version to produce a modified Combined Work, in the
+ manner specified by section 6 of the GNU GPL for conveying
+ Corresponding Source.
+
+ 1) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (a) uses at run time
+ a copy of the Library already present on the user's computer
+ system, and (b) will operate properly with a modified version
+ of the Library that is interface-compatible with the Linked
+ Version.
+
+ e) Provide Installation Information, but only if you would otherwise
+ be required to provide such information under section 6 of the
+ GNU GPL, and only to the extent that such information is
+ necessary to install and execute a modified version of the
+ Combined Work produced by recombining or relinking the
+ Application with a modified version of the Linked Version. (If
+ you use option 4d0, the Installation Information must accompany
+ the Minimal Corresponding Source and Corresponding Application
+ Code. If you use option 4d1, you must provide the Installation
+ Information in the manner specified by section 6 of the GNU GPL
+ for conveying Corresponding Source.)
+
+ 5. Combined Libraries.
+
+ You may place library facilities that are a work based on the
+Library side by side in a single library together with other library
+facilities that are not Applications and are not covered by this
+License, and convey such a combined library under terms of your
+choice, if you do both of the following:
+
+ a) Accompany the combined library with a copy of the same work based
+ on the Library, uncombined with any other library facilities,
+ conveyed under the terms of this License.
+
+ b) Give prominent notice with the combined library that part of it
+ is a work based on the Library, and explaining where to find the
+ accompanying uncombined form of the same work.
+
+ 6. Revised Versions of the GNU Lesser General Public License.
+
+ The Free Software Foundation may publish revised and/or new versions
+of the GNU Lesser General Public License from time to time. Such new
+versions will be similar in spirit to the present version, but may
+differ in detail to address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Library as you received it specifies that a certain numbered version
+of the GNU Lesser General Public License "or any later version"
+applies to it, you have the option of following the terms and
+conditions either of that published version or of any later version
+published by the Free Software Foundation. If the Library as you
+received it does not specify a version number of the GNU Lesser
+General Public License, you may choose any version of the GNU Lesser
+General Public License ever published by the Free Software Foundation.
+
+ If the Library as you received it specifies that a proxy can decide
+whether future versions of the GNU Lesser General Public License shall
+apply, that proxy's public statement of acceptance of any version is
+permanent authorization for you to choose that version for the
+Library.
\ No newline at end of file
diff --git a/chapter43_packages/my_package/README.md b/chapter43_packages/my_package/README.md
new file mode 100644
index 0000000..e69de29
diff --git a/chapter43_packages/my_package/arithmetic/__init__.py b/chapter43_packages/my_package/arithmetic/__init__.py
new file mode 100644
index 0000000..be3c5fc
--- /dev/null
+++ b/chapter43_packages/my_package/arithmetic/__init__.py
@@ -0,0 +1,2 @@
+# __init__.py
+from . import add
\ No newline at end of file
diff --git a/chapter43_packages/my_package/arithmetic/add.py b/chapter43_packages/my_package/arithmetic/add.py
new file mode 100644
index 0000000..07fb1d6
--- /dev/null
+++ b/chapter43_packages/my_package/arithmetic/add.py
@@ -0,0 +1,4 @@
+# add.py
+
+def add(x, y):
+ return x + y
\ No newline at end of file
diff --git a/chapter43_packages/my_package/arithmetic/divide.py b/chapter43_packages/my_package/arithmetic/divide.py
new file mode 100644
index 0000000..1f6707b
--- /dev/null
+++ b/chapter43_packages/my_package/arithmetic/divide.py
@@ -0,0 +1,4 @@
+# divide.py
+
+def divide(x, y):
+ return x / y
\ No newline at end of file
diff --git a/chapter43_packages/my_package/arithmetic/multiply.py b/chapter43_packages/my_package/arithmetic/multiply.py
new file mode 100644
index 0000000..0a4dcbc
--- /dev/null
+++ b/chapter43_packages/my_package/arithmetic/multiply.py
@@ -0,0 +1,4 @@
+# multiply.py
+
+def multiply(x, y):
+ return x * y
\ No newline at end of file
diff --git a/chapter43_packages/my_package/arithmetic/subtract.py b/chapter43_packages/my_package/arithmetic/subtract.py
new file mode 100644
index 0000000..f9340ac
--- /dev/null
+++ b/chapter43_packages/my_package/arithmetic/subtract.py
@@ -0,0 +1,4 @@
+# subtract.py
+
+def subtract(x, y):
+ return x - y
\ No newline at end of file
diff --git a/chapter43_packages/my_package/setup.py b/chapter43_packages/my_package/setup.py
new file mode 100644
index 0000000..4684c74
--- /dev/null
+++ b/chapter43_packages/my_package/setup.py
@@ -0,0 +1,22 @@
+import setuptools
+
+with open("README.md", "r") as fh:
+ long_description = fh.read()
+
+setuptools.setup(
+ name="arithmetic-YOUR-USERNAME-HERE", # Replace with your own username
+ version="0.0.1",
+ author="Mike Driscoll",
+ author_email="driscoll@example.com",
+ description="A simple arithmetic package",
+ long_description=long_description,
+ long_description_content_type="text/markdown",
+ url="https://github.com/driscollis/arithmetic",
+ packages=setuptools.find_packages(),
+ classifiers=[
+ "Programming Language :: Python :: 3",
+ "License :: OSI Approved :: MIT License",
+ "Operating System :: OS Independent",
+ ],
+ python_requires='>=3.6',
+)
\ No newline at end of file
diff --git a/chapter44_pyinstaller/image_viewer.py b/chapter44_pyinstaller/image_viewer.py
new file mode 100644
index 0000000..8e4dec4
--- /dev/null
+++ b/chapter44_pyinstaller/image_viewer.py
@@ -0,0 +1,78 @@
+# image_viewer_working.py
+
+import wx
+
+class ImagePanel(wx.Panel):
+
+ def __init__(self, parent, image_size):
+ super().__init__(parent)
+ self.max_size = 240
+
+ img = wx.Image(*image_size)
+ self.image_ctrl = wx.StaticBitmap(self,
+ bitmap=wx.Bitmap(img))
+
+ browse_btn = wx.Button(self, label='Browse')
+ browse_btn.Bind(wx.EVT_BUTTON, self.on_browse)
+
+ self.photo_txt = wx.TextCtrl(self, size=(200, -1))
+
+ main_sizer = wx.BoxSizer(wx.VERTICAL)
+ hsizer = wx.BoxSizer(wx.HORIZONTAL)
+
+ main_sizer.Add(self.image_ctrl, 0, wx.ALL, 5)
+ hsizer.Add(browse_btn, 0, wx.ALL, 5)
+ hsizer.Add(self.photo_txt, 0, wx.ALL, 5)
+ main_sizer.Add(hsizer, 0, wx.ALL, 5)
+
+ self.SetSizer(main_sizer)
+ main_sizer.Fit(parent)
+ self.Layout()
+
+ def on_browse(self, event):
+ """
+ Browse for an image file
+ @param event: The event object
+ """
+ wildcard = "JPEG files (*.jpg)|*.jpg"
+ with wx.FileDialog(None, "Choose a file",
+ wildcard=wildcard,
+ style=wx.ID_OPEN) as dialog:
+ if dialog.ShowModal() == wx.ID_OK:
+ self.photo_txt.SetValue(dialog.GetPath())
+ self.load_image()
+
+ def load_image(self):
+ """
+ Load the image and display it to the user
+ """
+ filepath = self.photo_txt.GetValue()
+ img = wx.Image(filepath, wx.BITMAP_TYPE_ANY)
+
+ # scale the image, preserving the aspect ratio
+ W = img.GetWidth()
+ H = img.GetHeight()
+ if W > H:
+ NewW = self.max_size
+ NewH = self.max_size * H / W
+ else:
+ NewH = self.max_size
+ NewW = self.max_size * W / H
+ img = img.Scale(NewW,NewH)
+
+ self.image_ctrl.SetBitmap(wx.Bitmap(img))
+ self.Refresh()
+
+
+class MainFrame(wx.Frame):
+
+ def __init__(self):
+ super().__init__(None, title='Image Viewer')
+ panel = ImagePanel(self, image_size=(240,240))
+ self.Show()
+
+
+if __name__ == '__main__':
+ app = wx.App(redirect=False)
+ frame = MainFrame()
+ app.MainLoop()
\ No newline at end of file
diff --git a/chapter44_pyinstaller/pysearch.py b/chapter44_pyinstaller/pysearch.py
new file mode 100644
index 0000000..9a2e0cc
--- /dev/null
+++ b/chapter44_pyinstaller/pysearch.py
@@ -0,0 +1,49 @@
+# pysearch.py
+
+import argparse
+import pathlib
+
+
+def search_folder(path, extension, file_size=None):
+ """
+ Search folder for files
+ """
+ folder = pathlib.Path(path)
+ files = list(folder.rglob(f'*.{extension}'))
+
+ if not files:
+ print(f'No files found with {extension=}')
+ return
+
+ if file_size is not None:
+ files = [f for f in files
+ if f.stat().st_size > file_size]
+
+ print(f'{len(files)} *.{extension} files found:')
+ for file_path in files:
+ print(file_path)
+
+
+def main():
+ parser = argparse.ArgumentParser(
+ 'PySearch',
+ description='PySearch - The Python Powered File Searcher')
+ parser.add_argument('-p', '--path',
+ help='The path to search for files',
+ required=True,
+ dest='path')
+ parser.add_argument('-e', '--ext',
+ help='The extension to search for',
+ required=True,
+ dest='extension')
+ parser.add_argument('-s', '--size',
+ help='The file size to filter on in bytes',
+ type=int,
+ dest='size',
+ default=None)
+
+ args = parser.parse_args()
+ search_folder(args.path, args.extension, args.size)
+
+if __name__ == '__main__':
+ main()
\ No newline at end of file
diff --git a/chapter46_mac/image_viewer.py b/chapter46_mac/image_viewer.py
new file mode 100644
index 0000000..8e4dec4
--- /dev/null
+++ b/chapter46_mac/image_viewer.py
@@ -0,0 +1,78 @@
+# image_viewer_working.py
+
+import wx
+
+class ImagePanel(wx.Panel):
+
+ def __init__(self, parent, image_size):
+ super().__init__(parent)
+ self.max_size = 240
+
+ img = wx.Image(*image_size)
+ self.image_ctrl = wx.StaticBitmap(self,
+ bitmap=wx.Bitmap(img))
+
+ browse_btn = wx.Button(self, label='Browse')
+ browse_btn.Bind(wx.EVT_BUTTON, self.on_browse)
+
+ self.photo_txt = wx.TextCtrl(self, size=(200, -1))
+
+ main_sizer = wx.BoxSizer(wx.VERTICAL)
+ hsizer = wx.BoxSizer(wx.HORIZONTAL)
+
+ main_sizer.Add(self.image_ctrl, 0, wx.ALL, 5)
+ hsizer.Add(browse_btn, 0, wx.ALL, 5)
+ hsizer.Add(self.photo_txt, 0, wx.ALL, 5)
+ main_sizer.Add(hsizer, 0, wx.ALL, 5)
+
+ self.SetSizer(main_sizer)
+ main_sizer.Fit(parent)
+ self.Layout()
+
+ def on_browse(self, event):
+ """
+ Browse for an image file
+ @param event: The event object
+ """
+ wildcard = "JPEG files (*.jpg)|*.jpg"
+ with wx.FileDialog(None, "Choose a file",
+ wildcard=wildcard,
+ style=wx.ID_OPEN) as dialog:
+ if dialog.ShowModal() == wx.ID_OK:
+ self.photo_txt.SetValue(dialog.GetPath())
+ self.load_image()
+
+ def load_image(self):
+ """
+ Load the image and display it to the user
+ """
+ filepath = self.photo_txt.GetValue()
+ img = wx.Image(filepath, wx.BITMAP_TYPE_ANY)
+
+ # scale the image, preserving the aspect ratio
+ W = img.GetWidth()
+ H = img.GetHeight()
+ if W > H:
+ NewW = self.max_size
+ NewH = self.max_size * H / W
+ else:
+ NewH = self.max_size
+ NewW = self.max_size * W / H
+ img = img.Scale(NewW,NewH)
+
+ self.image_ctrl.SetBitmap(wx.Bitmap(img))
+ self.Refresh()
+
+
+class MainFrame(wx.Frame):
+
+ def __init__(self):
+ super().__init__(None, title='Image Viewer')
+ panel = ImagePanel(self, image_size=(240,240))
+ self.Show()
+
+
+if __name__ == '__main__':
+ app = wx.App(redirect=False)
+ frame = MainFrame()
+ app.MainLoop()
\ No newline at end of file