From ff436f9ef8a7b62c193f60dd69f8bf3248f7d04b Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Wed, 20 Jan 2021 22:32:36 -0300 Subject: [PATCH 001/166] examples for ch.20: Concurrency Models --- 20-concurrency/primes/primes.py | 51 ++++++++++++++++ 20-concurrency/primes/procs.py | 46 +++++++++++++++ 20-concurrency/primes/sequential.py | 26 +++++++++ 20-concurrency/primes/spinner_async_nap.py | 58 +++++++++++++++++++ .../primes/spinner_async_prime_no_spin.py | 40 +++++++++++++ 20-concurrency/primes/spinner_thread.py | 46 +++++++++++++++ 20-concurrency/primes/threads.py | 41 +++++++++++++ 7 files changed, 308 insertions(+) create mode 100755 20-concurrency/primes/primes.py create mode 100644 20-concurrency/primes/procs.py create mode 100644 20-concurrency/primes/sequential.py create mode 100644 20-concurrency/primes/spinner_async_nap.py create mode 100644 20-concurrency/primes/spinner_async_prime_no_spin.py create mode 100644 20-concurrency/primes/spinner_thread.py create mode 100644 20-concurrency/primes/threads.py diff --git a/20-concurrency/primes/primes.py b/20-concurrency/primes/primes.py new file mode 100755 index 0000000..63dd7a1 --- /dev/null +++ b/20-concurrency/primes/primes.py @@ -0,0 +1,51 @@ +import math +import itertools + + +PRIME_FIXTURE = [ + (2, True), + (142702110479723, True), + (299593572317531, True), + (3333333333333301, True), + (3333333333333333, False), + (3333335652092209, False), + (4444444444444423, True), + (4444444444444444, False), + (4444444488888889, False), + (5555553133149889, False), + (5555555555555503, True), + (5555555555555555, False), + (6666666666666666, False), + (6666666666666719, True), + (6666667141414921, False), + (7777777536340681, False), + (7777777777777753, True), + (7777777777777777, False), + (9999999999999917, True), + (9999999999999999, False), +] + +NUMBERS = [n for n, _ in PRIME_FIXTURE] + +# tag::IS_PRIME[] +def is_prime(n) -> bool: + if n < 2: + return False + if n == 2: + return True + if n % 2 == 0: + return False + + root = int(math.floor(math.sqrt(n))) + for i in range(3, root + 1, 2): + if n % i == 0: + return False + return True +# end::IS_PRIME[] + +if __name__ == '__main__': + + for n, prime in PRIME_FIXTURE: + prime_res = is_prime(n) + assert prime_res == prime + print(n, prime) diff --git a/20-concurrency/primes/procs.py b/20-concurrency/primes/procs.py new file mode 100644 index 0000000..3238fda --- /dev/null +++ b/20-concurrency/primes/procs.py @@ -0,0 +1,46 @@ +# tag::PRIMES_PROC_TOP[] +from time import perf_counter +from typing import Tuple, List, NamedTuple +from multiprocessing import Process, SimpleQueue # <1> +from multiprocessing import queues # <2> + +from primes import is_prime, NUMBERS + +class Result(NamedTuple): # <3> + flag: bool + elapsed: float + +JobQueue = queues.SimpleQueue[Tuple[int, Result]] # <4> + +def check(n: int) -> Result: # <5> + t0 = perf_counter() + res = is_prime(n) + return Result(res, perf_counter() - t0) + +def job(n: int, results: JobQueue) -> None: # <6> + results.put((n, check(n))) # <7> +# end::PRIMES_PROC_TOP[] + +# tag::PRIMES_PROC_MAIN[] +def main() -> None: + t0 = perf_counter() + results: JobQueue = SimpleQueue() # <1> + workers: List[Process] = [] # <2> + + for n in NUMBERS: + worker = Process(target=job, args=(n, results)) # <3> + worker.start() # <4> + workers.append(worker) # <5> + + for _ in workers: # <6> + n, (prime, elapsed) = results.get() # <7> + label = 'P' if prime else ' ' + print(f'{n:16} {label} {elapsed:9.6f}s') + + + time = perf_counter() - t0 + print('Total time:', f'{time:0.2f}s') + +if __name__ == '__main__': + main() +# end::PRIMES_PROC_MAIN[] diff --git a/20-concurrency/primes/sequential.py b/20-concurrency/primes/sequential.py new file mode 100644 index 0000000..a1b9ae7 --- /dev/null +++ b/20-concurrency/primes/sequential.py @@ -0,0 +1,26 @@ +from time import perf_counter +from typing import NamedTuple + +from primes import is_prime, NUMBERS + +class Result(NamedTuple): # <1> + flag: bool + elapsed: float + +def check(n: int) -> Result: # <2> + t0 = perf_counter() + flag = is_prime(n) + return Result(flag, perf_counter() - t0) + +def main() -> None: + t0 = perf_counter() + for n in NUMBERS: # <3> + prime, elapsed = check(n) + label = 'P' if prime else ' ' + print(f'{n:16} {label} {elapsed:9.6f}s') + + elapsed = perf_counter() - t0 # <4> + print('Total time:', f'{elapsed:0.2f}s') + +if __name__ == '__main__': + main() diff --git a/20-concurrency/primes/spinner_async_nap.py b/20-concurrency/primes/spinner_async_nap.py new file mode 100644 index 0000000..30fd4f2 --- /dev/null +++ b/20-concurrency/primes/spinner_async_nap.py @@ -0,0 +1,58 @@ +# spinner_async_experiment.py + +# credits: Example by Luciano Ramalho inspired by +# Michele Simionato's multiprocessing example in the python-list: +# https://mail.python.org/pipermail/python-list/2009-February/675659.html + +import asyncio +import itertools +import math + +# tag::SPINNER_ASYNC_NAP[] +async def is_prime(n): + if n < 2: + return False + if n == 2: + return True + if n % 2 == 0: + return False + + sleep = asyncio.sleep # <1> + root = int(math.floor(math.sqrt(n))) + for i in range(3, root + 1, 2): + if n % i == 0: + return False + if i % 100_000 == 1: # <2> + await sleep(0) + return True +# end::SPINNER_ASYNC_NAP[] + + +async def spin(msg: str) -> None: + for char in itertools.cycle(r'\|/-'): + status = f'\r{char} {msg}' + print(status, flush=True, end='') + try: + await asyncio.sleep(.1) + except asyncio.CancelledError: + break + blanks = ' ' * len(status) + print(f'\r{blanks}\r', end='') + +async def slow() -> int: + await is_prime(5_000_111_000_222_021) # <4> + return 42 + +async def supervisor() -> int: + spinner = asyncio.create_task(spin('thinking!')) # <1> + print('spinner object:', spinner) # <2> + result = await slow() # <3> + spinner.cancel() # <5> + return result + +def main() -> None: + result = asyncio.run(supervisor()) + print('Answer:', result) + +if __name__ == '__main__': + main() diff --git a/20-concurrency/primes/spinner_async_prime_no_spin.py b/20-concurrency/primes/spinner_async_prime_no_spin.py new file mode 100644 index 0000000..4905e25 --- /dev/null +++ b/20-concurrency/primes/spinner_async_prime_no_spin.py @@ -0,0 +1,40 @@ +# spinner_async_experiment.py + +# credits: Example by Luciano Ramalho inspired by +# Michele Simionato's multiprocessing example in the python-list: +# https://mail.python.org/pipermail/python-list/2009-February/675659.html + +import asyncio +import itertools + +import primes + +async def spin(msg: str) -> None: + for char in itertools.cycle(r'\|/-'): + status = f'\r{char} {msg}' + print(status, flush=True, end='') + try: + await asyncio.sleep(.1) + except asyncio.CancelledError: + break + print('THIS WILL NEVER BE OUTPUT') + +# tag::SPINNER_ASYNC_EXPERIMENT[] +async def slow() -> int: + primes.is_prime(5_000_111_000_222_021) # <4> + return 42 + +async def supervisor() -> int: + spinner = asyncio.create_task(spin('thinking!')) # <1> + print('spinner object:', spinner) # <2> + result = await slow() # <3> + spinner.cancel() # <5> + return result +# end::SPINNER_ASYNC_EXPERIMENT[] + +def main() -> None: + result = asyncio.run(supervisor()) + print('Answer:', result) + +if __name__ == '__main__': + main() diff --git a/20-concurrency/primes/spinner_thread.py b/20-concurrency/primes/spinner_thread.py new file mode 100644 index 0000000..795e116 --- /dev/null +++ b/20-concurrency/primes/spinner_thread.py @@ -0,0 +1,46 @@ +# spinner_thread.py + +# credits: Adapted from Michele Simionato's +# multiprocessing example in the python-list: +# https://mail.python.org/pipermail/python-list/2009-February/675659.html + +# tag::SPINNER_THREAD_TOP[] +from threading import Thread, Event +import itertools +import time + +from primes import is_prime + +def spin(msg: str, done: Event) -> None: # <1> + for char in itertools.cycle(r'\|/-'): # <2> + status = f'\r{char} {msg}' # <3> + print(status, end='', flush=True) + if done.wait(.1): # <4> + break # <5> + blanks = ' ' * len(status) + print(f'\r{blanks}\r', end='') # <6> + +def slow() -> int: + is_prime(5_000_111_000_222_021) # <7> + return 42 +# end::SPINNER_THREAD_TOP[] + +# tag::SPINNER_THREAD_REST[] +def supervisor() -> int: # <1> + done = Event() # <2> + spinner = Thread(target=spin, + args=('thinking!', done)) # <3> + print('spinner object:', spinner) # <4> + spinner.start() # <5> + result = slow() # <6> + done.set() # <7> + spinner.join() # <8> + return result + +def main() -> None: + result = supervisor() # <9> + print('Answer:', result) + +if __name__ == '__main__': + main() +# end::SPINNER_THREAD_REST[] diff --git a/20-concurrency/primes/threads.py b/20-concurrency/primes/threads.py new file mode 100644 index 0000000..8e9a6b9 --- /dev/null +++ b/20-concurrency/primes/threads.py @@ -0,0 +1,41 @@ +from time import perf_counter +from typing import Tuple, List, NamedTuple +from threading import Thread +from queue import SimpleQueue + +from primes import is_prime, NUMBERS + +class Result(NamedTuple): # <3> + flag: bool + elapsed: float + +JobQueue = SimpleQueue[Tuple[int, Result]] # <4> + +def check(n: int) -> Result: # <5> + t0 = perf_counter() + res = is_prime(n) + return Result(res, perf_counter() - t0) + +def job(n: int, results: JobQueue) -> None: # <6> + results.put((n, check(n))) # <7> + +def main() -> None: + t0 = perf_counter() + results: JobQueue = SimpleQueue() # <1> + workers: List[Thread] = [] # <2> + + for n in NUMBERS: + worker = Thread(target=job, args=(n, results)) # <3> + worker.start() # <4> + workers.append(worker) # <5> + + for _ in workers: # <6> + n, (prime, elapsed) = results.get() # <7> + label = 'P' if prime else ' ' + print(f'{n:16} {label} {elapsed:9.6f}s') + + time = perf_counter() - t0 + print('Total time:', f'{time:0.2f}s') + +if __name__ == '__main__': + main() From ebeec917906bb0c9b6cdd987e60dd06841571264 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Wed, 20 Jan 2021 23:19:20 -0300 Subject: [PATCH 002/166] Python 3.7 example without some types --- 20-concurrency/primes/procs_py37.py | 43 +++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 20-concurrency/primes/procs_py37.py diff --git a/20-concurrency/primes/procs_py37.py b/20-concurrency/primes/procs_py37.py new file mode 100644 index 0000000..c786e9d --- /dev/null +++ b/20-concurrency/primes/procs_py37.py @@ -0,0 +1,43 @@ +# tag::PRIMES_PROC_TOP[] +from time import perf_counter +from typing import Tuple, List, NamedTuple +from multiprocessing import Process, SimpleQueue # <1> + +from primes import is_prime, NUMBERS + +class Result(NamedTuple): # <3> + flag: bool + elapsed: float + +def check(n: int) -> Result: # <5> + t0 = perf_counter() + res = is_prime(n) + return Result(res, perf_counter() - t0) + +def job(n: int, results) -> None: # <6> + results.put((n, check(n))) # <7> +# end::PRIMES_PROC_TOP[] + +# tag::PRIMES_PROC_MAIN[] +def main() -> None: + t0 = perf_counter() + results = SimpleQueue() # <1> + workers: List[Process] = [] # <2> + + for n in NUMBERS: + worker = Process(target=job, args=(n, results)) # <3> + worker.start() # <4> + workers.append(worker) # <5> + + for _ in workers: # <6> + n, (prime, elapsed) = results.get() # <7> + label = 'P' if prime else ' ' + print(f'{n:16} {label} {elapsed:9.6f}s') + + + time = perf_counter() - t0 + print('Total time:', f'{time:0.2f}s') + +if __name__ == '__main__': + main() +# end::PRIMES_PROC_MAIN[] From c7328ff623cd70d031d17e3ed535aa24224d6387 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Wed, 20 Jan 2021 23:27:26 -0300 Subject: [PATCH 003/166] Python 3.7 example without some types --- 20-concurrency/primes/procs_py37.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/20-concurrency/primes/procs_py37.py b/20-concurrency/primes/procs_py37.py index c786e9d..2ab5d37 100644 --- a/20-concurrency/primes/procs_py37.py +++ b/20-concurrency/primes/procs_py37.py @@ -14,14 +14,14 @@ def check(n: int) -> Result: # <5> res = is_prime(n) return Result(res, perf_counter() - t0) -def job(n: int, results) -> None: # <6> +def job(n: int, results: SimpleQueue) -> None: # <6> results.put((n, check(n))) # <7> # end::PRIMES_PROC_TOP[] # tag::PRIMES_PROC_MAIN[] def main() -> None: t0 = perf_counter() - results = SimpleQueue() # <1> + results = SimpleQueue() # type: ignore workers: List[Process] = [] # <2> for n in NUMBERS: From 07856d4f3008dcd3a0860abf85d9630658774e0e Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Wed, 20 Jan 2021 23:28:18 -0300 Subject: [PATCH 004/166] Python 3.7 example without some types --- 20-concurrency/primes/threads_py37.py | 39 +++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 20-concurrency/primes/threads_py37.py diff --git a/20-concurrency/primes/threads_py37.py b/20-concurrency/primes/threads_py37.py new file mode 100644 index 0000000..296d26c --- /dev/null +++ b/20-concurrency/primes/threads_py37.py @@ -0,0 +1,39 @@ +from time import perf_counter +from typing import Tuple, List, NamedTuple +from threading import Thread +from queue import SimpleQueue + +from primes import is_prime, NUMBERS + +class Result(NamedTuple): # <3> + flag: bool + elapsed: float + +def check(n: int) -> Result: # <5> + t0 = perf_counter() + res = is_prime(n) + return Result(res, perf_counter() - t0) + +def job(n: int, results: SimpleQueue) -> None: # <6> + results.put((n, check(n))) # <7> + +def main() -> None: + t0 = perf_counter() + results = SimpleQueue() # type: ignore + workers: List[Thread] = [] # <2> + + for n in NUMBERS: + worker = Thread(target=job, args=(n, results)) # <3> + worker.start() # <4> + workers.append(worker) # <5> + + for _ in workers: # <6> + n, (prime, elapsed) = results.get() # <7> + label = 'P' if prime else ' ' + print(f'{n:16} {label} {elapsed:9.6f}s') + + time = perf_counter() - t0 + print('Total time:', f'{time:0.2f}s') + +if __name__ == '__main__': + main() From a3bf91bc59daed1f5dec43e05431841f6564abb8 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Fri, 29 Jan 2021 21:05:03 -0300 Subject: [PATCH 005/166] ch20 files --- 20-concurrency/primes/log-procs.txt | 260 +++++++++++ 20-concurrency/primes/procs.py | 45 +- 20-concurrency/primes/run_procs.sh | 2 + .../primes/spinner_prime_async_broken.py | 40 ++ .../primes/spinner_prime_async_nap.py | 59 +++ 20-concurrency/primes/spinner_prime_proc.py | 44 ++ 20-concurrency/primes/spinner_prime_thread.py | 43 ++ 20-concurrency/primes/stats-procs.ipynb | 437 ++++++++++++++++++ 20-concurrency/primes/threads.py | 43 +- 9 files changed, 945 insertions(+), 28 deletions(-) create mode 100644 20-concurrency/primes/log-procs.txt create mode 100755 20-concurrency/primes/run_procs.sh create mode 100644 20-concurrency/primes/spinner_prime_async_broken.py create mode 100644 20-concurrency/primes/spinner_prime_async_nap.py create mode 100644 20-concurrency/primes/spinner_prime_proc.py create mode 100644 20-concurrency/primes/spinner_prime_thread.py create mode 100644 20-concurrency/primes/stats-procs.ipynb diff --git a/20-concurrency/primes/log-procs.txt b/20-concurrency/primes/log-procs.txt new file mode 100644 index 0000000..0212f6a --- /dev/null +++ b/20-concurrency/primes/log-procs.txt @@ -0,0 +1,260 @@ +1 42.05 +2 22.81 +3 15.95 +4 13.28 +5 12.27 +6 10.38 +7 11.00 +8 11.41 +9 12.17 +10 12.69 +11 11.34 +12 11.35 +13 10.88 +14 11.63 +15 11.79 +16 11.49 +17 11.29 +18 10.78 +19 10.92 +20 11.07 +1 40.47 +2 22.93 +3 15.88 +4 13.25 +5 12.22 +6 10.95 +7 10.81 +8 11.26 +9 11.96 +10 12.43 +11 11.19 +12 10.91 +13 11.03 +14 10.56 +15 10.62 +16 11.73 +17 11.00 +18 12.81 +19 12.38 +20 11.28 +1 40.81 +2 22.79 +3 15.83 +4 13.27 +5 12.33 +6 10.65 +7 10.94 +8 11.48 +9 11.78 +10 12.78 +11 11.31 +12 10.97 +13 10.80 +14 10.93 +15 10.69 +16 10.57 +17 10.54 +18 10.55 +19 10.79 +20 10.64 +1 40.85 +2 22.67 +3 16.05 +4 13.21 +5 12.53 +6 10.84 +7 10.80 +8 11.31 +9 11.69 +10 12.51 +11 11.22 +12 11.35 +13 11.00 +14 10.64 +15 10.89 +16 10.49 +17 10.55 +18 10.76 +19 10.54 +20 10.75 +1 40.41 +2 22.75 +3 15.87 +4 13.19 +5 12.33 +6 10.50 +7 10.84 +8 11.55 +9 11.79 +10 12.53 +11 11.24 +12 11.13 +13 10.89 +14 10.52 +15 10.74 +16 10.68 +17 10.88 +18 10.61 +19 11.07 +20 10.71 +1 40.45 +2 22.73 +3 16.35 +4 13.09 +5 12.19 +6 10.39 +7 11.01 +8 11.30 +9 11.81 +10 12.24 +11 11.21 +12 11.13 +13 10.66 +14 10.56 +15 10.91 +16 10.49 +17 10.58 +18 10.61 +19 10.60 +20 10.69 +1 40.36 +2 22.64 +3 15.95 +4 13.20 +5 12.27 +6 10.34 +7 10.47 +8 11.34 +9 11.68 +10 12.30 +11 11.04 +12 10.85 +13 10.78 +14 10.64 +15 10.63 +16 10.58 +17 10.67 +18 10.64 +19 10.71 +20 10.68 +1 40.70 +2 22.71 +3 15.81 +4 13.10 +5 12.29 +6 10.34 +7 10.40 +8 11.40 +9 11.71 +10 12.56 +11 11.29 +12 10.99 +13 10.51 +14 10.69 +15 10.53 +16 11.04 +17 10.67 +18 10.71 +19 10.86 +20 10.78 +1 40.69 +2 22.80 +3 15.88 +4 13.13 +5 12.19 +6 10.36 +7 10.77 +8 11.32 +9 11.66 +10 12.29 +11 11.06 +12 10.89 +13 10.66 +14 10.61 +15 10.36 +16 10.57 +17 10.94 +18 10.57 +19 10.81 +20 10.72 +1 40.81 +2 22.76 +3 15.84 +4 13.10 +5 12.25 +6 10.33 +7 10.58 +8 11.51 +9 11.69 +10 12.45 +11 11.51 +12 11.53 +13 10.61 +14 10.52 +15 10.57 +16 10.57 +17 10.76 +18 10.60 +19 10.66 +20 10.73 +1 40.84 +2 22.83 +3 15.86 +4 13.27 +5 12.39 +6 10.45 +7 10.87 +8 11.42 +9 11.70 +10 12.55 +11 11.43 +12 10.98 +13 10.81 +14 10.69 +15 10.68 +16 10.71 +17 10.80 +18 10.76 +19 10.90 +20 11.02 +1 40.99 +2 22.99 +3 16.10 +4 13.46 +5 12.45 +6 10.47 +7 10.79 +8 11.74 +9 11.64 +10 12.37 +11 11.20 +12 11.09 +13 10.82 +14 10.61 +15 10.56 +16 10.53 +17 10.60 +18 10.81 +19 10.72 +20 10.62 +1 40.94 +2 23.09 +3 16.03 +4 13.40 +5 12.51 +6 10.38 +7 10.58 +8 11.37 +9 11.75 +10 12.87 +11 12.11 +12 11.37 +13 11.84 +14 11.30 +15 11.29 +16 11.36 +17 11.01 +18 11.37 +19 11.07 +20 10.94 \ No newline at end of file diff --git a/20-concurrency/primes/procs.py b/20-concurrency/primes/procs.py index 3238fda..27263d7 100644 --- a/20-concurrency/primes/procs.py +++ b/20-concurrency/primes/procs.py @@ -1,8 +1,9 @@ # tag::PRIMES_PROC_TOP[] from time import perf_counter -from typing import Tuple, List, NamedTuple -from multiprocessing import Process, SimpleQueue # <1> +from typing import Tuple, NamedTuple +from multiprocessing import Process, SimpleQueue, cpu_count # <1> from multiprocessing import queues # <2> +import sys from primes import is_prime, NUMBERS @@ -10,33 +11,47 @@ class Result(NamedTuple): # <3> flag: bool elapsed: float -JobQueue = queues.SimpleQueue[Tuple[int, Result]] # <4> +JobQueue = queues.SimpleQueue[int] # <4> +ResultQueue = queues.SimpleQueue[Tuple[int, Result]] # <5> -def check(n: int) -> Result: # <5> +def check(n: int) -> Result: # <6> t0 = perf_counter() res = is_prime(n) return Result(res, perf_counter() - t0) -def job(n: int, results: JobQueue) -> None: # <6> - results.put((n, check(n))) # <7> +def worker(jobs: JobQueue, results: ResultQueue) -> None: # <7> + while n := jobs.get(): # <8> + result = check(n) # <9> + results.put((n, result)) # <10> # end::PRIMES_PROC_TOP[] # tag::PRIMES_PROC_MAIN[] def main() -> None: + if len(sys.argv) < 2: # <1> + workers = cpu_count() + else: + workers = int(sys.argv[1]) + t0 = perf_counter() - results: JobQueue = SimpleQueue() # <1> - workers: List[Process] = [] # <2> + jobs: JobQueue = SimpleQueue() # <2> + results: ResultQueue = SimpleQueue() + + print(f'Checking {len(NUMBERS)} numbers with {workers} processes:') - for n in NUMBERS: - worker = Process(target=job, args=(n, results)) # <3> - worker.start() # <4> - workers.append(worker) # <5> + for n in NUMBERS: # <3> + jobs.put(n) - for _ in workers: # <6> + for _ in range(workers): + proc = Process(target=worker, args=(jobs, results)) # <4> + proc.start() # <5> + jobs.put(0) # <6> + + while True: n, (prime, elapsed) = results.get() # <7> label = 'P' if prime else ' ' - print(f'{n:16} {label} {elapsed:9.6f}s') - + print(f'{n:16} {label} {elapsed:9.6f}s') # <8> + if jobs.empty(): # <9> + break time = perf_counter() - t0 print('Total time:', f'{time:0.2f}s') diff --git a/20-concurrency/primes/run_procs.sh b/20-concurrency/primes/run_procs.sh new file mode 100755 index 0000000..9f57e47 --- /dev/null +++ b/20-concurrency/primes/run_procs.sh @@ -0,0 +1,2 @@ +#/bin/bash +for i in {1..20}; do echo -n $i; python3 procs.py $i | tail -1; done diff --git a/20-concurrency/primes/spinner_prime_async_broken.py b/20-concurrency/primes/spinner_prime_async_broken.py new file mode 100644 index 0000000..2290ece --- /dev/null +++ b/20-concurrency/primes/spinner_prime_async_broken.py @@ -0,0 +1,40 @@ +# spinner_async_experiment.py + +# credits: Example by Luciano Ramalho inspired by +# Michele Simionato's multiprocessing example in the python-list: +# https://mail.python.org/pipermail/python-list/2009-February/675659.html + +import asyncio +import itertools + +import primes + +async def spin(msg: str) -> None: + for char in itertools.cycle(r'\|/-'): + status = f'\r{char} {msg}' + print(status, flush=True, end='') + try: + await asyncio.sleep(.1) + except asyncio.CancelledError: + break + print('THIS WILL NEVER BE OUTPUT') + +async def check(n: int) -> int: + return primes.is_prime(n) # <4> + +async def supervisor(n: int) -> int: + spinner = asyncio.create_task(spin('thinking!')) # <1> + print('spinner object:', spinner) # <2> + result = await check(n) # <3> + spinner.cancel() # <5> + return result +# end::SPINNER_ASYNC_EXPERIMENT[] + +def main() -> None: + n = 5_000_111_000_222_021 + result = asyncio.run(supervisor(n)) + msg = 'is' if result else 'is not' + print(f'{n:,} {msg} prime') + +if __name__ == '__main__': + main() diff --git a/20-concurrency/primes/spinner_prime_async_nap.py b/20-concurrency/primes/spinner_prime_async_nap.py new file mode 100644 index 0000000..c1f82ad --- /dev/null +++ b/20-concurrency/primes/spinner_prime_async_nap.py @@ -0,0 +1,59 @@ +# spinner_async_experiment.py + +# credits: Example by Luciano Ramalho inspired by +# Michele Simionato's multiprocessing example in the python-list: +# https://mail.python.org/pipermail/python-list/2009-February/675659.html + +import asyncio +import itertools +import math + +# tag::SPINNER_ASYNC_NAP[] +async def is_prime(n): + if n < 2: + return False + if n == 2: + return True + if n % 2 == 0: + return False + + sleep = asyncio.sleep # <1> + root = int(math.floor(math.sqrt(n))) + for i in range(3, root + 1, 2): + if n % i == 0: + return False + if i % 100_000 == 1: # <2> + await sleep(0) + return True +# end::SPINNER_ASYNC_NAP[] + + +async def spin(msg: str) -> None: + for char in itertools.cycle(r'\|/-'): + status = f'\r{char} {msg}' + print(status, flush=True, end='') + try: + await asyncio.sleep(.1) + except asyncio.CancelledError: + break + blanks = ' ' * len(status) + print(f'\r{blanks}\r', end='') + +async def check(n: int) -> int: + return await is_prime(n) # <4> + +async def supervisor(n: int) -> int: + spinner = asyncio.create_task(spin('thinking!')) # <1> + print('spinner object:', spinner) # <2> + result = await check(n) # <3> + spinner.cancel() # <5> + return result + +def main() -> None: + n = 5_000_111_000_222_021 + result = asyncio.run(supervisor(n)) + msg = 'is' if result else 'is not' + print(f'{n:,} {msg} prime') + +if __name__ == '__main__': + main() diff --git a/20-concurrency/primes/spinner_prime_proc.py b/20-concurrency/primes/spinner_prime_proc.py new file mode 100644 index 0000000..e1454d1 --- /dev/null +++ b/20-concurrency/primes/spinner_prime_proc.py @@ -0,0 +1,44 @@ +# spinner_prime_proc.py + +# credits: Adapted from Michele Simionato's +# multiprocessing example in the python-list: +# https://mail.python.org/pipermail/python-list/2009-February/675659.html + +from multiprocessing import Process, Event +from multiprocessing import synchronize +import itertools +import time + +from primes import is_prime + +def spin(msg: str, done: synchronize.Event) -> None: # <1> + for char in itertools.cycle(r'\|/-'): # <2> + status = f'\r{char} {msg}' # <3> + print(status, end='', flush=True) + if done.wait(.1): # <4> + break # <5> + blanks = ' ' * len(status) + print(f'\r{blanks}\r', end='') # <6> + +def check(n: int) -> int: + return is_prime(n) + +def supervisor(n: int) -> int: # <1> + done = Event() # <2> + spinner = Process(target=spin, + args=('thinking!', done)) # <3> + print('spinner object:', spinner) # <4> + spinner.start() # <5> + result = check(n) # <6> + done.set() # <7> + spinner.join() # <8> + return result + +def main() -> None: + n = 5_000_111_000_222_021 + result = supervisor(n) # <9> + msg = 'is' if result else 'is not' + print(f'{n:,} {msg} prime') + +if __name__ == '__main__': + main() diff --git a/20-concurrency/primes/spinner_prime_thread.py b/20-concurrency/primes/spinner_prime_thread.py new file mode 100644 index 0000000..11db5d9 --- /dev/null +++ b/20-concurrency/primes/spinner_prime_thread.py @@ -0,0 +1,43 @@ +# spinner_prime_thread.py + +# credits: Adapted from Michele Simionato's +# multiprocessing example in the python-list: +# https://mail.python.org/pipermail/python-list/2009-February/675659.html + +from threading import Thread, Event +import itertools +import time + +from primes import is_prime + +def spin(msg: str, done: Event) -> None: # <1> + for char in itertools.cycle(r'\|/-'): # <2> + status = f'\r{char} {msg}' # <3> + print(status, end='', flush=True) + if done.wait(.1): # <4> + break # <5> + blanks = ' ' * len(status) + print(f'\r{blanks}\r', end='') # <6> + +def check(n: int) -> int: + return is_prime(n) + +def supervisor(n: int) -> int: # <1> + done = Event() # <2> + spinner = Thread(target=spin, + args=('thinking!', done)) # <3> + print('spinner object:', spinner) # <4> + spinner.start() # <5> + result = check(n) # <6> + done.set() # <7> + spinner.join() # <8> + return result + +def main() -> None: + n = 5_000_111_000_222_021 + result = supervisor(n) # <9> + msg = 'is' if result else 'is not' + print(f'{n:,} {msg} prime') + +if __name__ == '__main__': + main() diff --git a/20-concurrency/primes/stats-procs.ipynb b/20-concurrency/primes/stats-procs.ipynb new file mode 100644 index 0000000..ce8cae7 --- /dev/null +++ b/20-concurrency/primes/stats-procs.ipynb @@ -0,0 +1,437 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "([42.05,\n", + " 40.47,\n", + " 40.81,\n", + " 40.85,\n", + " 40.41,\n", + " 40.45,\n", + " 40.36,\n", + " 40.7,\n", + " 40.69,\n", + " 40.81,\n", + " 40.84,\n", + " 40.99,\n", + " 40.94],\n", + " [11.07,\n", + " 11.28,\n", + " 10.64,\n", + " 10.75,\n", + " 10.71,\n", + " 10.69,\n", + " 10.68,\n", + " 10.78,\n", + " 10.72,\n", + " 10.73,\n", + " 11.02,\n", + " 10.62,\n", + " 10.94])" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "lines = \"\"\"1 42.05\n", + "2 22.81\n", + "3 15.95\n", + "4 13.28\n", + "5 12.27\n", + "6 10.38\n", + "7 11.00\n", + "8 11.41\n", + "9 12.17\n", + "10 12.69\n", + "11 11.34\n", + "12 11.35\n", + "13 10.88\n", + "14 11.63\n", + "15 11.79\n", + "16 11.49\n", + "17 11.29\n", + "18 10.78\n", + "19 10.92\n", + "20 11.07\n", + "1 40.47\n", + "2 22.93\n", + "3 15.88\n", + "4 13.25\n", + "5 12.22\n", + "6 10.95\n", + "7 10.81\n", + "8 11.26\n", + "9 11.96\n", + "10 12.43\n", + "11 11.19\n", + "12 10.91\n", + "13 11.03\n", + "14 10.56\n", + "15 10.62\n", + "16 11.73\n", + "17 11.00\n", + "18 12.81\n", + "19 12.38\n", + "20 11.28\n", + "1 40.81\n", + "2 22.79\n", + "3 15.83\n", + "4 13.27\n", + "5 12.33\n", + "6 10.65\n", + "7 10.94\n", + "8 11.48\n", + "9 11.78\n", + "10 12.78\n", + "11 11.31\n", + "12 10.97\n", + "13 10.80\n", + "14 10.93\n", + "15 10.69\n", + "16 10.57\n", + "17 10.54\n", + "18 10.55\n", + "19 10.79\n", + "20 10.64\n", + "1 40.85\n", + "2 22.67\n", + "3 16.05\n", + "4 13.21\n", + "5 12.53\n", + "6 10.84\n", + "7 10.80\n", + "8 11.31\n", + "9 11.69\n", + "10 12.51\n", + "11 11.22\n", + "12 11.35\n", + "13 11.00\n", + "14 10.64\n", + "15 10.89\n", + "16 10.49\n", + "17 10.55\n", + "18 10.76\n", + "19 10.54\n", + "20 10.75\n", + "1 40.41\n", + "2 22.75\n", + "3 15.87\n", + "4 13.19\n", + "5 12.33\n", + "6 10.50\n", + "7 10.84\n", + "8 11.55\n", + "9 11.79\n", + "10 12.53\n", + "11 11.24\n", + "12 11.13\n", + "13 10.89\n", + "14 10.52\n", + "15 10.74\n", + "16 10.68\n", + "17 10.88\n", + "18 10.61\n", + "19 11.07\n", + "20 10.71\n", + "1 40.45\n", + "2 22.73\n", + "3 16.35\n", + "4 13.09\n", + "5 12.19\n", + "6 10.39\n", + "7 11.01\n", + "8 11.30\n", + "9 11.81\n", + "10 12.24\n", + "11 11.21\n", + "12 11.13\n", + "13 10.66\n", + "14 10.56\n", + "15 10.91\n", + "16 10.49\n", + "17 10.58\n", + "18 10.61\n", + "19 10.60\n", + "20 10.69\n", + "1 40.36\n", + "2 22.64\n", + "3 15.95\n", + "4 13.20\n", + "5 12.27\n", + "6 10.34\n", + "7 10.47\n", + "8 11.34\n", + "9 11.68\n", + "10 12.30\n", + "11 11.04\n", + "12 10.85\n", + "13 10.78\n", + "14 10.64\n", + "15 10.63\n", + "16 10.58\n", + "17 10.67\n", + "18 10.64\n", + "19 10.71\n", + "20 10.68\n", + "1 40.70\n", + "2 22.71\n", + "3 15.81\n", + "4 13.10\n", + "5 12.29\n", + "6 10.34\n", + "7 10.40\n", + "8 11.40\n", + "9 11.71\n", + "10 12.56\n", + "11 11.29\n", + "12 10.99\n", + "13 10.51\n", + "14 10.69\n", + "15 10.53\n", + "16 11.04\n", + "17 10.67\n", + "18 10.71\n", + "19 10.86\n", + "20 10.78\n", + "1 40.69\n", + "2 22.80\n", + "3 15.88\n", + "4 13.13\n", + "5 12.19\n", + "6 10.36\n", + "7 10.77\n", + "8 11.32\n", + "9 11.66\n", + "10 12.29\n", + "11 11.06\n", + "12 10.89\n", + "13 10.66\n", + "14 10.61\n", + "15 10.36\n", + "16 10.57\n", + "17 10.94\n", + "18 10.57\n", + "19 10.81\n", + "20 10.72\n", + "1 40.81\n", + "2 22.76\n", + "3 15.84\n", + "4 13.10\n", + "5 12.25\n", + "6 10.33\n", + "7 10.58\n", + "8 11.51\n", + "9 11.69\n", + "10 12.45\n", + "11 11.51\n", + "12 11.53\n", + "13 10.61\n", + "14 10.52\n", + "15 10.57\n", + "16 10.57\n", + "17 10.76\n", + "18 10.60\n", + "19 10.66\n", + "20 10.73\n", + "1 40.84\n", + "2 22.83\n", + "3 15.86\n", + "4 13.27\n", + "5 12.39\n", + "6 10.45\n", + "7 10.87\n", + "8 11.42\n", + "9 11.70\n", + "10 12.55\n", + "11 11.43\n", + "12 10.98\n", + "13 10.81\n", + "14 10.69\n", + "15 10.68\n", + "16 10.71\n", + "17 10.80\n", + "18 10.76\n", + "19 10.90\n", + "20 11.02\n", + "1 40.99\n", + "2 22.99\n", + "3 16.10\n", + "4 13.46\n", + "5 12.45\n", + "6 10.47\n", + "7 10.79\n", + "8 11.74\n", + "9 11.64\n", + "10 12.37\n", + "11 11.20\n", + "12 11.09\n", + "13 10.82\n", + "14 10.61\n", + "15 10.56\n", + "16 10.53\n", + "17 10.60\n", + "18 10.81\n", + "19 10.72\n", + "20 10.62\n", + "1 40.94\n", + "2 23.09\n", + "3 16.03\n", + "4 13.40\n", + "5 12.51\n", + "6 10.38\n", + "7 10.58\n", + "8 11.37\n", + "9 11.75\n", + "10 12.87\n", + "11 12.11\n", + "12 11.37\n", + "13 11.84\n", + "14 11.30\n", + "15 11.29\n", + "16 11.36\n", + "17 11.01\n", + "18 11.37\n", + "19 11.07\n", + "20 10.94\"\"\"\n", + "\n", + "from collections import defaultdict\n", + "\n", + "times = defaultdict(list)\n", + "for line in lines.split('\\n'):\n", + " n, s = line.strip().split()\n", + " n, s = int(n), float(s)\n", + " times[n].append(s)\n", + "\n", + "times[1], times[20]" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "10.39" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from statistics import median, mean\n", + "\n", + "median_times = []\n", + "mean_times = []\n", + "for procs, ts in times.items():\n", + " median_times.append(median(ts))\n", + " mean_times.append(mean(ts))\n", + " \n", + "min(median_times)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "import pandas as pd\n", + "\n", + "df=pd.DataFrame(\n", + " {'procs': range(1,21),\n", + " 'median': median_times})" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Text(0.5, 0, 'processes')" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "plt.plot('procs', 'median', data=df, marker='+')\n", + "plt.ylim([0, 50])\n", + "plt.ylabel('time (s)')\n", + "plt.xlabel('processes')\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Dúvidas\n", + "\n", + "1. O eixo x são valores discretos: a quantidade de processos em uso. Como faço para que o eixo x seja marcado com os números de 1 a 20, um por um (20 ticks)? \n", + "2. Como alargar o retângulo do gráfico uns 50%, para que os números no eixo x não fiquem muito apertados?\n", + "3. Como exibir uma linha horizontal na altura do valor mínimo, que é 10.39?\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.8.3" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/20-concurrency/primes/threads.py b/20-concurrency/primes/threads.py index 8e9a6b9..7ac275a 100644 --- a/20-concurrency/primes/threads.py +++ b/20-concurrency/primes/threads.py @@ -1,38 +1,55 @@ from time import perf_counter -from typing import Tuple, List, NamedTuple +from typing import Tuple, NamedTuple from threading import Thread from queue import SimpleQueue +import sys +import os from primes import is_prime, NUMBERS -class Result(NamedTuple): # <3> +class Result(NamedTuple): flag: bool elapsed: float -JobQueue = SimpleQueue[Tuple[int, Result]] # <4> +JobQueue = SimpleQueue[int] +ResultQueue = SimpleQueue[Tuple[int, Result]] -def check(n: int) -> Result: # <5> +def check(n: int) -> Result: t0 = perf_counter() res = is_prime(n) return Result(res, perf_counter() - t0) -def job(n: int, results: JobQueue) -> None: # <6> - results.put((n, check(n))) # <7> +def worker(jobs: JobQueue, results: ResultQueue) -> None: + while n := jobs.get(): + result = check(n) + results.put((n, result)) def main() -> None: + if len(sys.argv) < 2: # <1> + workers = os.cpu_count() or 1 # make mypy happy + else: + workers = int(sys.argv[1]) + t0 = perf_counter() - results: JobQueue = SimpleQueue() # <1> - workers: List[Thread] = [] # <2> + jobs: JobQueue = SimpleQueue() # <2> + results: ResultQueue = SimpleQueue() + + print(f'Checking {len(NUMBERS)} numbers with {workers} threads:') + + for n in NUMBERS: # <3> + jobs.put(n) - for n in NUMBERS: - worker = Thread(target=job, args=(n, results)) # <3> - worker.start() # <4> - workers.append(worker) # <5> + for _ in range(workers): + proc = Thread(target=worker, args=(jobs, results)) # <4> + proc.start() # <5> + jobs.put(0) # <6> - for _ in workers: # <6> + while True: n, (prime, elapsed) = results.get() # <7> label = 'P' if prime else ' ' print(f'{n:16} {label} {elapsed:9.6f}s') + if jobs.empty(): # <8> + break time = perf_counter() - t0 print('Total time:', f'{time:0.2f}s') From c0514d0e4a17d325fb77ab9c605a0b8ca155ba34 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Fri, 29 Jan 2021 21:39:36 -0300 Subject: [PATCH 006/166] ch20: time x processes graph --- 20-concurrency/primes/stats-procs.ipynb | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/20-concurrency/primes/stats-procs.ipynb b/20-concurrency/primes/stats-procs.ipynb index ce8cae7..a1df6d9 100644 --- a/20-concurrency/primes/stats-procs.ipynb +++ b/20-concurrency/primes/stats-procs.ipynb @@ -367,7 +367,7 @@ { "data": { "text/plain": [ - "Text(0.5, 0, 'processes')" + "" ] }, "execution_count": 4, @@ -376,7 +376,7 @@ }, { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ "
" ] @@ -388,10 +388,11 @@ } ], "source": [ - "plt.plot('procs', 'median', data=df, marker='+')\n", + "plt.plot('procs', 'median', data=df, marker='+', linestyle=\"None\")\n", "plt.ylim([0, 50])\n", "plt.ylabel('time (s)')\n", - "plt.xlabel('processes')\n" + "plt.xlabel('processes')\n", + "plt.axhline(min(median_times), color='red')" ] }, { @@ -402,7 +403,7 @@ "\n", "1. O eixo x são valores discretos: a quantidade de processos em uso. Como faço para que o eixo x seja marcado com os números de 1 a 20, um por um (20 ticks)? \n", "2. Como alargar o retângulo do gráfico uns 50%, para que os números no eixo x não fiquem muito apertados?\n", - "3. Como exibir uma linha horizontal na altura do valor mínimo, que é 10.39?\n" + "3. ~~Como exibir uma linha horizontal na altura do valor mínimo, que é 10.39?~~ feito: [fonte](https://twitter.com/hcpyz/status/1355310776916062211)\n" ] }, { From 4a6983cb6adebd0c55ceacbe84f777b5eaa151e7 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Fri, 29 Jan 2021 21:57:19 -0300 Subject: [PATCH 007/166] ch20: time x processes graph --- 20-concurrency/primes/stats-procs.ipynb | 29 ++++++++++++------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/20-concurrency/primes/stats-procs.ipynb b/20-concurrency/primes/stats-procs.ipynb index a1df6d9..a07206d 100644 --- a/20-concurrency/primes/stats-procs.ipynb +++ b/20-concurrency/primes/stats-procs.ipynb @@ -365,20 +365,17 @@ }, "outputs": [ { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] }, { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ - "
" + "
" ] }, "metadata": { @@ -388,22 +385,24 @@ } ], "source": [ + "plt.figure(figsize=(8, 4))\n", "plt.plot('procs', 'median', data=df, marker='+', linestyle=\"None\")\n", + "plt.axhline(min(median_times), color='red', linewidth=.5)\n", "plt.ylim([0, 50])\n", "plt.ylabel('time (s)')\n", "plt.xlabel('processes')\n", - "plt.axhline(min(median_times), color='red')" + "plt.xticks([i for i in range(1, 21)])\n", + "print()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### Dúvidas\n", + "Agradeço a esses generosos tuiteiros pela ajuda pra fazer esse gráfico:\n", "\n", - "1. O eixo x são valores discretos: a quantidade de processos em uso. Como faço para que o eixo x seja marcado com os números de 1 a 20, um por um (20 ticks)? \n", - "2. Como alargar o retângulo do gráfico uns 50%, para que os números no eixo x não fiquem muito apertados?\n", - "3. ~~Como exibir uma linha horizontal na altura do valor mínimo, que é 10.39?~~ feito: [fonte](https://twitter.com/hcpyz/status/1355310776916062211)\n" + "* [@hcpyz](https://twitter.com/hcpyz/status/1355310776916062211)\n", + "* [@guilhrme_alves](https://twitter.com/guilhrme_alves/status/1355314798796414982)" ] }, { From 93bb4407fac7ecf56b4f3ca87f3cb717a9ed3a56 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Fri, 29 Jan 2021 22:57:22 -0300 Subject: [PATCH 008/166] ch20: time x processes graph --- 20-concurrency/primes/stats-procs.ipynb | 145 +++++++++++++++--------- 1 file changed, 91 insertions(+), 54 deletions(-) diff --git a/20-concurrency/primes/stats-procs.ipynb b/20-concurrency/primes/stats-procs.ipynb index a07206d..f5319ef 100644 --- a/20-concurrency/primes/stats-procs.ipynb +++ b/20-concurrency/primes/stats-procs.ipynb @@ -4,43 +4,7 @@ "cell_type": "code", "execution_count": 1, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "([42.05,\n", - " 40.47,\n", - " 40.81,\n", - " 40.85,\n", - " 40.41,\n", - " 40.45,\n", - " 40.36,\n", - " 40.7,\n", - " 40.69,\n", - " 40.81,\n", - " 40.84,\n", - " 40.99,\n", - " 40.94],\n", - " [11.07,\n", - " 11.28,\n", - " 10.64,\n", - " 10.75,\n", - " 10.71,\n", - " 10.69,\n", - " 10.68,\n", - " 10.78,\n", - " 10.72,\n", - " 10.73,\n", - " 11.02,\n", - " 10.62,\n", - " 10.94])" - ] - }, - "execution_count": 1, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "lines = \"\"\"1 42.05\n", "2 22.81\n", @@ -301,8 +265,51 @@ "17 11.01\n", "18 11.37\n", "19 11.07\n", - "20 10.94\"\"\"\n", - "\n", + "20 10.94\"\"\"" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "([42.05,\n", + " 40.47,\n", + " 40.81,\n", + " 40.85,\n", + " 40.41,\n", + " 40.45,\n", + " 40.36,\n", + " 40.7,\n", + " 40.69,\n", + " 40.81,\n", + " 40.84,\n", + " 40.99,\n", + " 40.94],\n", + " [11.07,\n", + " 11.28,\n", + " 10.64,\n", + " 10.75,\n", + " 10.71,\n", + " 10.69,\n", + " 10.68,\n", + " 10.78,\n", + " 10.72,\n", + " 10.73,\n", + " 11.02,\n", + " 10.62,\n", + " 10.94])" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ "from collections import defaultdict\n", "\n", "times = defaultdict(list)\n", @@ -316,7 +323,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 3, "metadata": {}, "outputs": [ { @@ -325,7 +332,7 @@ "10.39" ] }, - "execution_count": 2, + "execution_count": 3, "metadata": {}, "output_type": "execute_result" } @@ -344,7 +351,44 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1 40.81\n", + "2 22.79\n", + "3 15.88\n", + "4 13.21\n", + "5 12.29\n", + "6 10.39\n", + "7 10.8\n", + "8 11.4\n", + "9 11.71\n", + "10 12.51\n", + "11 11.24\n", + "12 11.09\n", + "13 10.81\n", + "14 10.64\n", + "15 10.68\n", + "16 10.58\n", + "17 10.76\n", + "18 10.71\n", + "19 10.81\n", + "20 10.73\n" + ] + } + ], + "source": [ + "for i, t, in enumerate(median_times, 1):\n", + " print(i, t)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, "metadata": {}, "outputs": [], "source": [ @@ -359,7 +403,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 6, "metadata": { "scrolled": true }, @@ -373,7 +417,7 @@ }, { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ "
" ] @@ -386,8 +430,8 @@ ], "source": [ "plt.figure(figsize=(8, 4))\n", - "plt.plot('procs', 'median', data=df, marker='+', linestyle=\"None\")\n", - "plt.axhline(min(median_times), color='red', linewidth=.5)\n", + "plt.plot('procs', 'median', data=df, marker='+', markersize=10, linestyle='None')\n", + "plt.axhline(min(median_times), linestyle=(0, (1, 3)), color='red')\n", "plt.ylim([0, 50])\n", "plt.ylabel('time (s)')\n", "plt.xlabel('processes')\n", @@ -399,18 +443,11 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Agradeço a esses generosos tuiteiros pela ajuda pra fazer esse gráfico:\n", + "Thanks to these generous twitizens for helping me customize this graph:\n", "\n", "* [@hcpyz](https://twitter.com/hcpyz/status/1355310776916062211)\n", "* [@guilhrme_alves](https://twitter.com/guilhrme_alves/status/1355314798796414982)" ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { From b69e0c2023259bd4bfdc5f549ca34f1814056f89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= <6774676+eumiro@users.noreply.github.com> Date: Sun, 31 Jan 2021 22:48:38 +0100 Subject: [PATCH 009/166] Modernize code to Python 3.6+ and some cleanup --- 01-data-model/vector2d.py | 3 +- 02-array-seq/bisect_demo.py | 22 ++++++------- 02-array-seq/bisect_insort.py | 4 +-- 02-array-seq/listcomp_speed.py | 2 +- 02-array-seq/metro_lat_long.py | 7 ++-- 03-dict-set/index_default.py | 2 +- 03-dict-set/support/container_perftest.py | 6 ++-- .../support/container_perftest_datagen.py | 14 ++++---- 03-dict-set/support/hashdiff.py | 12 +++---- 03-dict-set/transformdict.py | 33 +++++++++---------- 04-text-byte/charfinder/cf.py | 2 +- 04-text-byte/default_encodings.py | 2 +- 04-text-byte/numerics_demo.py | 4 +-- 04-text-byte/ramanujan.py | 2 +- 04-text-byte/sanitize.py | 1 - 05-record-like/dataclass/coordinates.py | 2 +- 05-record-like/dataclass/hackerclub.py | 2 +- .../dataclass/hackerclub_annotated.py | 2 +- 05-record-like/dataclass/resource.py | 2 +- 05-record-like/dataclass/resource_repr.py | 2 +- .../typing_namedtuple/coordinates.py | 2 +- .../typing_namedtuple/nocheck_demo.py | 2 +- 06-obj-ref/bus.py | 1 - 06-obj-ref/cheese.py | 4 +-- 07-1class-func/tagger.py | 2 -- 08-def-type-hints/RPN_calc/calc.py | 2 +- 08-def-type-hints/bus.py | 1 - 08-def-type-hints/charindex.py | 2 +- 08-def-type-hints/clip_annot_1ed.py | 2 +- 08-def-type-hints/colors.py | 2 +- 08-def-type-hints/columnize2.py | 2 +- 08-def-type-hints/columnize_test.py | 4 +-- 08-def-type-hints/comparable/mymax.py | 2 +- 08-def-type-hints/comparable/top.py | 2 +- 08-def-type-hints/coordinates/coordinates.py | 2 +- 08-def-type-hints/mode/mode_T.py | 2 +- 08-def-type-hints/mode/mode_number.py | 2 +- 08-def-type-hints/passdrill.py | 5 ++- 08-def-type-hints/replacer.py | 2 +- 08-def-type-hints/replacer2.py | 2 +- 08-def-type-hints/typeddict/books_any.py | 2 +- .../typeddict/test_books_check_fails.py | 3 -- 09-closure-deco/average.py | 2 +- 09-closure-deco/average_oo.py | 2 +- 09-closure-deco/fibo_demo.py | 2 +- 09-closure-deco/fibo_demo_lru.py | 2 +- 10-dp-1class-func/classic_strategy.py | 5 ++- 10-dp-1class-func/classic_strategy_test.py | 2 +- .../monkeytype/classic_strategy.py | 6 ++-- .../monkeytype/classic_strategy_test.py | 2 +- 10-dp-1class-func/pytypes/classic_strategy.py | 6 ++-- .../pytypes/classic_strategy_test.py | 2 +- 10-dp-1class-func/strategy.py | 5 ++- 10-dp-1class-func/strategy_best.py | 2 +- 10-dp-1class-func/strategy_best2.py | 5 ++- 10-dp-1class-func/strategy_best3.py | 5 ++- 10-dp-1class-func/strategy_best4.py | 5 ++- 10-dp-1class-func/strategy_param.py | 13 ++++---- 10-dp-1class-func/strategy_param_test.py | 2 +- 10-dp-1class-func/strategy_test.py | 2 +- 10-dp-1class-func/untyped/classic_strategy.py | 5 ++- 10-dp-1class-func/untyped/promotions.py | 1 - 10-dp-1class-func/untyped/strategy.py | 5 ++- 10-dp-1class-func/untyped/strategy_best.py | 5 ++- 10-dp-1class-func/untyped/strategy_best2.py | 5 ++- 10-dp-1class-func/untyped/strategy_best3.py | 5 ++- 10-dp-1class-func/untyped/strategy_best4.py | 5 ++- 10-dp-1class-func/untyped/strategy_param.py | 11 +++---- 10-dp-1class-func/untyped/strategy_param2.py | 13 ++++---- 11-pythonic-obj/mem_test.py | 8 ++--- 11-pythonic-obj/private/Expose.java | 2 +- 11-pythonic-obj/private/expose.py | 2 +- 11-pythonic-obj/private/leakprivate.py | 4 +-- 11-pythonic-obj/private/no_respect.py | 3 +- 11-pythonic-obj/vector2d_v3.py | 2 +- 11-pythonic-obj/vector2d_v3_prophash.py | 2 +- 11-pythonic-obj/vector2d_v3_slots.py | 2 +- 20-concurrency/primes/primes.py | 3 +- 20-concurrency/primes/procs_py37.py | 2 +- 20-concurrency/primes/spinner_async_nap.py | 2 +- .../primes/spinner_prime_async_nap.py | 2 +- 20-concurrency/primes/spinner_prime_proc.py | 1 - 20-concurrency/primes/spinner_prime_thread.py | 1 - 20-concurrency/primes/spinner_thread.py | 1 - 20-concurrency/primes/threads_py37.py | 2 +- README.md | 2 +- 86 files changed, 153 insertions(+), 189 deletions(-) diff --git a/01-data-model/vector2d.py b/01-data-model/vector2d.py index a910ac8..557772b 100644 --- a/01-data-model/vector2d.py +++ b/01-data-model/vector2d.py @@ -26,7 +26,6 @@ >>> abs(v * 3) 15.0 - """ @@ -39,7 +38,7 @@ def __init__(self, x=0, y=0): self.y = y def __repr__(self): - return 'Vector(%r, %r)' % (self.x, self.y) + return f'Vector({self.x!r}, {self.y!r})' def __abs__(self): return hypot(self.x, self.y) diff --git a/02-array-seq/bisect_demo.py b/02-array-seq/bisect_demo.py index ec34b9c..d0ea6c2 100644 --- a/02-array-seq/bisect_demo.py +++ b/02-array-seq/bisect_demo.py @@ -11,11 +11,11 @@ 23 @ 11 | | | | | | | | | | |23 22 @ 9 | | | | | | | | |22 10 @ 5 | | | | |10 - 8 @ 5 | | | | |8 - 5 @ 3 | | |5 - 2 @ 1 |2 - 1 @ 1 |1 - 0 @ 0 0 + 8 @ 5 | | | | |8 + 5 @ 3 | | |5 + 2 @ 1 |2 + 1 @ 1 |1 + 0 @ 0 0 Demonstration of ``bisect.bisect_left``:: @@ -27,11 +27,11 @@ 23 @ 9 | | | | | | | | |23 22 @ 9 | | | | | | | | |22 10 @ 5 | | | | |10 - 8 @ 4 | | | |8 - 5 @ 2 | |5 - 2 @ 1 |2 - 1 @ 0 1 - 0 @ 0 0 + 8 @ 4 | | | |8 + 5 @ 2 | |5 + 2 @ 1 |2 + 1 @ 0 1 + 0 @ 0 0 """ @@ -59,7 +59,7 @@ def demo(bisect_fn): bisect_fn = bisect.bisect print('DEMO:', bisect_fn.__name__) # <5> - print('haystack ->', ' '.join('%2d' % n for n in HAYSTACK)) + print('haystack ->', ' '.join(f'{n:2}' for n in HAYSTACK)) demo(bisect_fn) # END BISECT_DEMO diff --git a/02-array-seq/bisect_insort.py b/02-array-seq/bisect_insort.py index b49aeae..12277c7 100644 --- a/02-array-seq/bisect_insort.py +++ b/02-array-seq/bisect_insort.py @@ -7,6 +7,6 @@ my_list = [] for i in range(SIZE): - new_item = random.randrange(SIZE*2) + new_item = random.randrange(SIZE * 2) bisect.insort(my_list, new_item) - print('%2d ->' % new_item, my_list) + print(f'{new_item:2} ->', my_list) diff --git a/02-array-seq/listcomp_speed.py b/02-array-seq/listcomp_speed.py index 0ec8b58..8e3f5d8 100644 --- a/02-array-seq/listcomp_speed.py +++ b/02-array-seq/listcomp_speed.py @@ -10,7 +10,7 @@ def non_ascii(c): def clock(label, cmd): res = timeit.repeat(cmd, setup=SETUP, number=TIMES) - print(label, *('{:.3f}'.format(x) for x in res)) + print(label, *(f'{x:.3f}' for x in res)) clock('listcomp :', '[ord(s) for s in symbols if ord(s) > 127]') clock('listcomp + func :', '[ord(s) for s in symbols if non_ascii(ord(s))]') diff --git a/02-array-seq/metro_lat_long.py b/02-array-seq/metro_lat_long.py index 37ec551..25b2373 100644 --- a/02-array-seq/metro_lat_long.py +++ b/02-array-seq/metro_lat_long.py @@ -4,7 +4,7 @@ Demonstration of nested tuple unpacking:: >>> main() - | lat. | long. + | lat. | long. Mexico City | 19.4333 | -99.1333 New York-Newark | 40.8086 | -74.0204 Sao Paulo | -23.5478 | -46.6358 @@ -20,11 +20,10 @@ ] def main(): - print('{:15} | {:^9} | {:^9}'.format('', 'lat.', 'long.')) - fmt = '{:15} | {:9.4f} | {:9.4f}' + print(f'{"":15} | {"lat.":^9} | {"long.":^9}') for name, cc, pop, (latitude, longitude) in metro_areas: # <2> if longitude <= 0: # <3> - print(fmt.format(name, latitude, longitude)) + print(f'{name:15} | {latitude:9.4f} | {longitude:9.4f}') if __name__ == '__main__': main() diff --git a/03-dict-set/index_default.py b/03-dict-set/index_default.py index 60279a9..839ffd7 100644 --- a/03-dict-set/index_default.py +++ b/03-dict-set/index_default.py @@ -16,7 +16,7 @@ for line_no, line in enumerate(fp, 1): for match in WORD_RE.finditer(line): word = match.group() - column_no = match.start()+1 + column_no = match.start() + 1 location = (line_no, column_no) index[word].append(location) # <2> diff --git a/03-dict-set/support/container_perftest.py b/03-dict-set/support/container_perftest.py index f044e96..ea1453e 100644 --- a/03-dict-set/support/container_perftest.py +++ b/03-dict-set/support/container_perftest.py @@ -41,15 +41,15 @@ def test(container_type, verbose): size=size, verbose=verbose) test = TEST.format(verbose=verbose) tt = timeit.repeat(stmt=test, setup=setup, repeat=5, number=1) - print('|{:{}d}|{:f}'.format(size, MAX_EXPONENT + 1, min(tt))) + print(f'|{size:{MAX_EXPONENT + 1}d}|{min(tt):f}') -if __name__=='__main__': +if __name__ == '__main__': if '-v' in sys.argv: sys.argv.remove('-v') verbose = True else: verbose = False if len(sys.argv) != 2: - print('Usage: %s ' % sys.argv[0]) + print(f'Usage: {sys.argv[0]} ') else: test(sys.argv[1], verbose) diff --git a/03-dict-set/support/container_perftest_datagen.py b/03-dict-set/support/container_perftest_datagen.py index 0f01ddd..df1be7e 100644 --- a/03-dict-set/support/container_perftest_datagen.py +++ b/03-dict-set/support/container_perftest_datagen.py @@ -2,8 +2,8 @@ Generate data for container performance test """ -import random import array +import random MAX_EXPONENT = 7 HAYSTACK_LEN = 10 ** MAX_EXPONENT @@ -12,26 +12,26 @@ needles = array.array('d') -sample = {1/random.random() for i in range(SAMPLE_LEN)} -print('initial sample: %d elements' % len(sample)) +sample = {1 / random.random() for i in range(SAMPLE_LEN)} +print(f'initial sample: {len(sample)} elements') # complete sample, in case duplicate random numbers were discarded while len(sample) < SAMPLE_LEN: - sample.add(1/random.random()) + sample.add(1 / random.random()) -print('complete sample: %d elements' % len(sample)) +print(f'complete sample: {len(sample)} elements') sample = array.array('d', sample) random.shuffle(sample) not_selected = sample[:NEEDLES_LEN // 2] -print('not selected: %d samples' % len(not_selected)) +print(f'not selected: {len(not_selected)} samples') print(' writing not_selected.arr') with open('not_selected.arr', 'wb') as fp: not_selected.tofile(fp) selected = sample[NEEDLES_LEN // 2:] -print('selected: %d samples' % len(selected)) +print(f'selected: {len(selected)} samples') print(' writing selected.arr') with open('selected.arr', 'wb') as fp: selected.tofile(fp) diff --git a/03-dict-set/support/hashdiff.py b/03-dict-set/support/hashdiff.py index 163e300..97fe72d 100644 --- a/03-dict-set/support/hashdiff.py +++ b/03-dict-set/support/hashdiff.py @@ -1,17 +1,17 @@ import sys MAX_BITS = len(format(sys.maxsize, 'b')) -print('%s-bit Python build' % (MAX_BITS + 1)) +print(f'{MAX_BITS + 1}-bit Python build') def hash_diff(o1, o2): - h1 = '{:>0{}b}'.format(hash(o1), MAX_BITS) - h2 = '{:>0{}b}'.format(hash(o2), MAX_BITS) + h1 = f'{hash(o1):>0{MAX_BITS}b}' + h2 = f'{hash(o2):>0{MAX_BITS}b}' diff = ''.join('!' if b1 != b2 else ' ' for b1, b2 in zip(h1, h2)) - count = '!= {}'.format(diff.count('!')) + count = f'!= {diff.count("!")}' width = max(len(repr(o1)), len(repr(o2)), 8) sep = '-' * (width * 2 + MAX_BITS) - return '{!r:{width}} {}\n{:{width}} {} {}\n{!r:{width}} {}\n{}'.format( - o1, h1, ' ' * width, diff, count, o2, h2, sep, width=width) + return (f'{o1!r:{width}} {h1}\n{" ":{width}} {diff} {count}\n' + f'{o2!r:{width}} {h2}\n{sep}') if __name__ == '__main__': print(hash_diff(1, 1.0)) diff --git a/03-dict-set/transformdict.py b/03-dict-set/transformdict.py index 4c6a7e7..b0cc002 100644 --- a/03-dict-set/transformdict.py +++ b/03-dict-set/transformdict.py @@ -17,7 +17,7 @@ class TransformDict(MutableMapping): - '''Dictionary that calls a transformation function when looking + """Dictionary that calls a transformation function when looking up keys, but preserves the original keys. >>> d = TransformDict(str.lower) @@ -26,18 +26,18 @@ class TransformDict(MutableMapping): True >>> set(d.keys()) {'Foo'} - ''' + """ __slots__ = ('_transform', '_original', '_data') def __init__(self, transform, init_dict=None, **kwargs): - '''Create a new TransformDict with the given *transform* function. + """Create a new TransformDict with the given *transform* function. *init_dict* and *kwargs* are optional initializers, as in the dict constructor. - ''' + """ if not callable(transform): - msg = 'expected a callable, got %r' - raise TypeError(msg % transform.__class__) + raise TypeError( + f'expected a callable, got {transform.__class__!r}') self._transform = transform # transformed => original self._original = {} @@ -48,7 +48,7 @@ def __init__(self, transform, init_dict=None, **kwargs): self.update(kwargs) def getitem(self, key): - 'D.getitem(key) -> (stored key, value)' + """D.getitem(key) -> (stored key, value)""" transformed = self._transform(key) original = self._original[transformed] value = self._data[transformed] @@ -56,7 +56,7 @@ def getitem(self, key): @property def transform_func(self): - "This TransformDict's transformation function" + """This TransformDict's transformation function""" return self._transform # Minimum set of methods required for MutableMapping @@ -83,7 +83,7 @@ def __delitem__(self, key): # Methods overridden to mitigate the performance overhead. def clear(self): - 'D.clear() -> None. Remove all items from D.' + """D.clear() -> None. Remove all items from D.""" self._data.clear() self._original.clear() @@ -91,14 +91,14 @@ def __contains__(self, key): return self._transform(key) in self._data def get(self, key, default=None): - 'D.get(k[,d]) -> D[k] if k in D, else d. d defaults to None.' + """D.get(k[,d]) -> D[k] if k in D, else d. d defaults to None.""" return self._data.get(self._transform(key), default) def pop(self, key, default=_sentinel): - '''D.pop(k[,d]) -> v, remove key and return corresponding value. + """D.pop(k[,d]) -> v, remove key and return corresponding value. If key is not found, d is returned if given, otherwise KeyError is raised. - ''' + """ transformed = self._transform(key) if default is _sentinel: del self._original[transformed] @@ -108,16 +108,16 @@ def pop(self, key, default=_sentinel): return self._data.pop(transformed, default) def popitem(self): - '''D.popitem() -> (k, v), remove and return some (key, value) pair + """D.popitem() -> (k, v), remove and return some (key, value) pair as a 2-tuple; but raise KeyError if D is empty. - ''' + """ transformed, value = self._data.popitem() return self._original.pop(transformed), value # Other methods def copy(self): - 'D.copy() -> a shallow copy of D' + """D.copy() -> a shallow copy of D""" other = self.__class__(self._transform) other._original = self._original.copy() other._data = self._data.copy() @@ -137,5 +137,4 @@ def __repr__(self): except TypeError: # Some keys are unhashable, fall back on .items() equiv = list(self.items()) - return '%s(%r, %s)' % (self.__class__.__name__, - self._transform, repr(equiv)) + return f'{self.__class__.__name__}({self._transform!r}, {equiv!r})' diff --git a/04-text-byte/charfinder/cf.py b/04-text-byte/charfinder/cf.py index 5717521..8976a66 100755 --- a/04-text-byte/charfinder/cf.py +++ b/04-text-byte/charfinder/cf.py @@ -8,7 +8,7 @@ def find(*query_words, first=FIRST, last=LAST): # <2> query = {w.upper() for w in query_words} # <3> count = 0 - for code in range(first, last + 1): + for code in range(first, last + 1): char = chr(code) # <4> name = unicodedata.name(char, None) # <5> if name and query.issubset(name.split()): # <6> diff --git a/04-text-byte/default_encodings.py b/04-text-byte/default_encodings.py index 2daf95b..c230dea 100644 --- a/04-text-byte/default_encodings.py +++ b/04-text-byte/default_encodings.py @@ -18,4 +18,4 @@ for expression in expressions.split(): value = eval(expression) - print(expression.rjust(30), '->', repr(value)) + print(f'{expression:>30} -> {value!r}') diff --git a/04-text-byte/numerics_demo.py b/04-text-byte/numerics_demo.py index ce43f1b..12f27c2 100644 --- a/04-text-byte/numerics_demo.py +++ b/04-text-byte/numerics_demo.py @@ -7,12 +7,12 @@ sample = '1\xbc\xb2\u0969\u136b\u216b\u2466\u2480\u3285' for char in sample: - print('U+%04x' % ord(char), # <1> + print(f'U+{ord(char):04x}', # <1> char.center(6), # <2> 're_dig' if re_digit.match(char) else '-', # <3> 'isdig' if char.isdigit() else '-', # <4> 'isnum' if char.isnumeric() else '-', # <5> - format(unicodedata.numeric(char), '5.2f'), # <6> + f'{unicodedata.numeric(char):5.2f}', # <6> unicodedata.name(char), # <7> sep='\t') # end::NUMERICS_DEMO[] diff --git a/04-text-byte/ramanujan.py b/04-text-byte/ramanujan.py index cafe072..3906429 100644 --- a/04-text-byte/ramanujan.py +++ b/04-text-byte/ramanujan.py @@ -11,7 +11,7 @@ text_bytes = text_str.encode('utf_8') # <5> -print('Text', repr(text_str), sep='\n ') +print(f'Text\n {text_str!r}') print('Numbers') print(' str :', re_numbers_str.findall(text_str)) # <6> print(' bytes:', re_numbers_bytes.findall(text_bytes)) # <7> diff --git a/04-text-byte/sanitize.py b/04-text-byte/sanitize.py index a86cb55..15eb09a 100644 --- a/04-text-byte/sanitize.py +++ b/04-text-byte/sanitize.py @@ -1,4 +1,3 @@ - """ Radical folding and text sanitizing. diff --git a/05-record-like/dataclass/coordinates.py b/05-record-like/dataclass/coordinates.py index 55fedbd..7bcd7cd 100644 --- a/05-record-like/dataclass/coordinates.py +++ b/05-record-like/dataclass/coordinates.py @@ -16,7 +16,7 @@ class Coordinate: lat: float long: float - + def __str__(self): ns = 'N' if self.lat >= 0 else 'S' we = 'E' if self.long >= 0 else 'W' diff --git a/05-record-like/dataclass/hackerclub.py b/05-record-like/dataclass/hackerclub.py index 8ab2a05..4d9112e 100644 --- a/05-record-like/dataclass/hackerclub.py +++ b/05-record-like/dataclass/hackerclub.py @@ -30,7 +30,7 @@ # tag::HACKERCLUB[] from dataclasses import dataclass -from club import ClubMember +from club import ClubMember @dataclass class HackerClubMember(ClubMember): # <1> diff --git a/05-record-like/dataclass/hackerclub_annotated.py b/05-record-like/dataclass/hackerclub_annotated.py index a25a443..a760a55 100644 --- a/05-record-like/dataclass/hackerclub_annotated.py +++ b/05-record-like/dataclass/hackerclub_annotated.py @@ -31,7 +31,7 @@ # tag::HACKERCLUB[] from dataclasses import dataclass from typing import ClassVar, Set -from club import ClubMember +from club import ClubMember @dataclass class HackerClubMember(ClubMember): diff --git a/05-record-like/dataclass/resource.py b/05-record-like/dataclass/resource.py index b768f09..90bfaf0 100644 --- a/05-record-like/dataclass/resource.py +++ b/05-record-like/dataclass/resource.py @@ -14,7 +14,7 @@ >>> description = 'Improving the design of existing code' >>> book = Resource('978-0-13-475759-9', 'Refactoring, 2nd Edition', ... ['Martin Fowler', 'Kent Beck'], date(2018, 11, 19), - ... ResourceType.BOOK, description, 'EN', + ... ResourceType.BOOK, description, 'EN', ... ['computer programming', 'OOP']) >>> book # doctest: +NORMALIZE_WHITESPACE Resource(identifier='978-0-13-475759-9', title='Refactoring, 2nd Edition', diff --git a/05-record-like/dataclass/resource_repr.py b/05-record-like/dataclass/resource_repr.py index ee0a731..e32c88d 100644 --- a/05-record-like/dataclass/resource_repr.py +++ b/05-record-like/dataclass/resource_repr.py @@ -21,7 +21,7 @@ >>> description = 'Improving the design of existing code' >>> book = Resource('978-0-13-475759-9', 'Refactoring, 2nd Edition', ... ['Martin Fowler', 'Kent Beck'], date(2018, 11, 19), - ... ResourceType.BOOK, description, 'EN', + ... ResourceType.BOOK, description, 'EN', ... ['computer programming', 'OOP']) # tag::DOCTEST[] diff --git a/05-record-like/typing_namedtuple/coordinates.py b/05-record-like/typing_namedtuple/coordinates.py index a3d7401..382d804 100644 --- a/05-record-like/typing_namedtuple/coordinates.py +++ b/05-record-like/typing_namedtuple/coordinates.py @@ -14,7 +14,7 @@ class Coordinate(NamedTuple): lat: float long: float - + def __str__(self): ns = 'N' if self.lat >= 0 else 'S' we = 'E' if self.long >= 0 else 'W' diff --git a/05-record-like/typing_namedtuple/nocheck_demo.py b/05-record-like/typing_namedtuple/nocheck_demo.py index a8b5ed4..e1193ec 100644 --- a/05-record-like/typing_namedtuple/nocheck_demo.py +++ b/05-record-like/typing_namedtuple/nocheck_demo.py @@ -6,4 +6,4 @@ class Coordinate(typing.NamedTuple): long: float trash = Coordinate('foo', None) # <1> -print(trash) +print(trash) diff --git a/06-obj-ref/bus.py b/06-obj-ref/bus.py index 778afa2..b4b4038 100644 --- a/06-obj-ref/bus.py +++ b/06-obj-ref/bus.py @@ -1,4 +1,3 @@ - """ >>> import copy >>> bus1 = Bus(['Alice', 'Bill', 'Claire', 'David']) diff --git a/06-obj-ref/cheese.py b/06-obj-ref/cheese.py index 924e5d0..d58085d 100644 --- a/06-obj-ref/cheese.py +++ b/06-obj-ref/cheese.py @@ -2,7 +2,7 @@ >>> import weakref >>> stock = weakref.WeakValueDictionary() >>> catalog = [Cheese('Red Leicester'), Cheese('Tilsit'), -... Cheese('Brie'), Cheese('Parmesan')] +... Cheese('Brie'), Cheese('Parmesan')] ... >>> for cheese in catalog: ... stock[cheese.kind] = cheese @@ -24,5 +24,5 @@ def __init__(self, kind): self.kind = kind def __repr__(self): - return 'Cheese(%r)' % self.kind + return f'Cheese({self.kind!r})' # end::CHEESE_CLASS[] diff --git a/07-1class-func/tagger.py b/07-1class-func/tagger.py index 52c3ddc..f132bef 100644 --- a/07-1class-func/tagger.py +++ b/07-1class-func/tagger.py @@ -1,5 +1,3 @@ - - """ # tag::TAG_DEMO[] >>> tag('br') # <1> diff --git a/08-def-type-hints/RPN_calc/calc.py b/08-def-type-hints/RPN_calc/calc.py index 5827eb9..967b094 100755 --- a/08-def-type-hints/RPN_calc/calc.py +++ b/08-def-type-hints/RPN_calc/calc.py @@ -2,7 +2,7 @@ import sys from array import array -from typing import Mapping, MutableSequence, Callable, Iterable, Sequence, Union, Any +from typing import Mapping, MutableSequence, Callable, Iterable, Union, Any OPERATORS: Mapping[str, Callable[[float, float], float]] = { diff --git a/08-def-type-hints/bus.py b/08-def-type-hints/bus.py index 778afa2..b4b4038 100644 --- a/08-def-type-hints/bus.py +++ b/08-def-type-hints/bus.py @@ -1,4 +1,3 @@ - """ >>> import copy >>> bus1 = Bus(['Alice', 'Bill', 'Claire', 'David']) diff --git a/08-def-type-hints/charindex.py b/08-def-type-hints/charindex.py index 790c464..36d20e3 100644 --- a/08-def-type-hints/charindex.py +++ b/08-def-type-hints/charindex.py @@ -17,7 +17,7 @@ import unicodedata from typing import Dict, Set, Iterator -RE_WORD = re.compile('\w+') +RE_WORD = re.compile(r'\w+') STOP_CODE = sys.maxunicode + 1 def tokenize(text: str) -> Iterator[str]: # <1> diff --git a/08-def-type-hints/clip_annot_1ed.py b/08-def-type-hints/clip_annot_1ed.py index ec4b392..4c49239 100644 --- a/08-def-type-hints/clip_annot_1ed.py +++ b/08-def-type-hints/clip_annot_1ed.py @@ -19,7 +19,7 @@ # tag::CLIP_ANNOT[] -def clip(text:str, max_len:'int > 0'=80) -> str: # <1> +def clip(text: str, max_len: 'int > 0'=80) -> str: # <1> """Return text clipped at the last space before or after max_len """ end = None diff --git a/08-def-type-hints/colors.py b/08-def-type-hints/colors.py index 270cd51..36518d6 100644 --- a/08-def-type-hints/colors.py +++ b/08-def-type-hints/colors.py @@ -21,7 +21,7 @@ def rgb2hex(color=Tuple[int, int, int]) -> str: if any(c not in range(256) for c in color): - raise ValueError('Color components must be in range(256)') + raise ValueError('Color components must be in range(256)') values = (f'{n % 256:02x}' for n in color) return '#' + ''.join(values) diff --git a/08-def-type-hints/columnize2.py b/08-def-type-hints/columnize2.py index 68929b5..67b5fdd 100644 --- a/08-def-type-hints/columnize2.py +++ b/08-def-type-hints/columnize2.py @@ -31,7 +31,7 @@ def demo() -> None: print(f'{cell:5}', end='') print() print() - + if __name__ == '__main__': demo() diff --git a/08-def-type-hints/columnize_test.py b/08-def-type-hints/columnize_test.py index 8a0b0f0..5d9446f 100644 --- a/08-def-type-hints/columnize_test.py +++ b/08-def-type-hints/columnize_test.py @@ -48,7 +48,7 @@ def test_columnize_8_in_3(): def test_columnize_8_in_5(): # Not the right number of columns, but the right number of rows. - # This acually looks better, so it's OK! + # This actually looks better, so it's OK! sequence = 'ABCDEFGH' expected = [ ('A', 'C', 'E', 'G'), @@ -60,7 +60,7 @@ def test_columnize_8_in_5(): def test_columnize_7_in_5(): # Not the right number of columns, but the right number of rows. - # This acually looks better, so it's OK! + # This actually looks better, so it's OK! sequence = 'ABCDEFG' expected = [ ('A', 'C', 'E', 'G'), diff --git a/08-def-type-hints/comparable/mymax.py b/08-def-type-hints/comparable/mymax.py index b69a296..26dfec2 100644 --- a/08-def-type-hints/comparable/mymax.py +++ b/08-def-type-hints/comparable/mymax.py @@ -24,7 +24,7 @@ def max(__iterable: Iterable[_CT], *, key: None = ...) -> _CT: def max(__iterable: Iterable[_T], *, key: Callable[[_T], _CT]) -> _T: ... @overload -def max(__iterable: Iterable[_CT], *, key: None = ..., +def max(__iterable: Iterable[_CT], *, key: None = ..., default: _DT) -> Union[_CT, _DT]: ... @overload diff --git a/08-def-type-hints/comparable/top.py b/08-def-type-hints/comparable/top.py index 51b7751..c751635 100644 --- a/08-def-type-hints/comparable/top.py +++ b/08-def-type-hints/comparable/top.py @@ -8,7 +8,7 @@ >>> l = 'mango pear apple kiwi banana'.split() >>> top(l, 3) ['pear', 'mango', 'kiwi'] ->>> +>>> >>> l2 = [(len(s), s) for s in l] >>> l2 [(5, 'mango'), (4, 'pear'), (5, 'apple'), (4, 'kiwi'), (6, 'banana')] diff --git a/08-def-type-hints/coordinates/coordinates.py b/08-def-type-hints/coordinates/coordinates.py index 6986f8a..ca64d79 100644 --- a/08-def-type-hints/coordinates/coordinates.py +++ b/08-def-type-hints/coordinates/coordinates.py @@ -14,6 +14,6 @@ PRECISION = 9 -def geohash(lat_lon = Tuple[float, float]) -> str: +def geohash(lat_lon: Tuple[float, float]) -> str: return gh.encode(*lat_lon, PRECISION) # end::GEOHASH[] \ No newline at end of file diff --git a/08-def-type-hints/mode/mode_T.py b/08-def-type-hints/mode/mode_T.py index d646f5c..46953ee 100644 --- a/08-def-type-hints/mode/mode_T.py +++ b/08-def-type-hints/mode/mode_T.py @@ -13,7 +13,7 @@ def mode(data: Iterable[T]) -> T: def demo() -> None: from typing import List, Set, TYPE_CHECKING - pop:List[Set] = [set(), set()] + pop: List[Set] = [set(), set()] m = mode(pop) if TYPE_CHECKING: reveal_type(pop) diff --git a/08-def-type-hints/mode/mode_number.py b/08-def-type-hints/mode/mode_number.py index 3eed434..999387f 100644 --- a/08-def-type-hints/mode/mode_number.py +++ b/08-def-type-hints/mode/mode_number.py @@ -13,7 +13,7 @@ def mode(data: Iterable[NumberT]) -> NumberT: def demo() -> None: - from typing import List, Set, TYPE_CHECKING + from typing import TYPE_CHECKING pop = [Fraction(1, 2), Fraction(1, 3), Fraction(1, 4), Fraction(1, 2)] m = mode(pop) if TYPE_CHECKING: diff --git a/08-def-type-hints/passdrill.py b/08-def-type-hints/passdrill.py index 7523f5e..612e504 100755 --- a/08-def-type-hints/passdrill.py +++ b/08-def-type-hints/passdrill.py @@ -57,7 +57,7 @@ def load_hash() -> Tuple[bytes, bytes]: sys.exit(2) salt, stored_hash = salted_hash.split(b':') - return (b64decode(salt), b64decode(stored_hash)) + return b64decode(salt), b64decode(stored_hash) def practice() -> None: @@ -83,8 +83,7 @@ def practice() -> None: print(f' {answer}\thits={correct}\tmisses={turn-correct}') if turn: - pct = correct / turn * 100 - print(f'\n{turn} turns. {pct:0.1f}% correct.') + print(f'\n{turn} turns. {correct / turn:.1%} correct.') def main(argv: Sequence[str]) -> None: diff --git a/08-def-type-hints/replacer.py b/08-def-type-hints/replacer.py index ea24a88..73ec77d 100644 --- a/08-def-type-hints/replacer.py +++ b/08-def-type-hints/replacer.py @@ -30,7 +30,7 @@ def demo() -> None: l33t = [(p[0], p[1]) for p in 'a4 e3 i1 o0'.split()] text = 'mad skilled noob powned leet' print(zip_replace(text, l33t)) - + if __name__ == '__main__': demo() diff --git a/08-def-type-hints/replacer2.py b/08-def-type-hints/replacer2.py index 9bf3986..786c8d7 100644 --- a/08-def-type-hints/replacer2.py +++ b/08-def-type-hints/replacer2.py @@ -33,7 +33,7 @@ def demo() -> None: l33t = [FromTo(*p) for p in 'a4 e3 i1 o0'.split()] text = 'mad skilled noob powned leet' print(zip_replace(text, l33t)) - + if __name__ == '__main__': demo() diff --git a/08-def-type-hints/typeddict/books_any.py b/08-def-type-hints/typeddict/books_any.py index 49d500f..e696ff3 100644 --- a/08-def-type-hints/typeddict/books_any.py +++ b/08-def-type-hints/typeddict/books_any.py @@ -17,7 +17,7 @@ def to_xml(book: BookDict) -> str: # <1> for key, value in book.items(): if isinstance(value, list): # <3> elements.extend(AUTHOR_EL.format(n) - for n in value) + for n in value) else: tag = key.upper() elements.append(f'<{tag}>{value}') diff --git a/08-def-type-hints/typeddict/test_books_check_fails.py b/08-def-type-hints/typeddict/test_books_check_fails.py index 45cbe83..6166f97 100644 --- a/08-def-type-hints/typeddict/test_books_check_fails.py +++ b/08-def-type-hints/typeddict/test_books_check_fails.py @@ -1,6 +1,3 @@ -import json -from typing import cast - from books import BookDict, to_xml XML_SAMPLE = """ diff --git a/09-closure-deco/average.py b/09-closure-deco/average.py index 5ac9855..e317f40 100644 --- a/09-closure-deco/average.py +++ b/09-closure-deco/average.py @@ -28,6 +28,6 @@ def make_averager(): def averager(new_value): series.append(new_value) total = sum(series) - return total/len(series) + return total / len(series) return averager diff --git a/09-closure-deco/average_oo.py b/09-closure-deco/average_oo.py index 154f055..bb05380 100644 --- a/09-closure-deco/average_oo.py +++ b/09-closure-deco/average_oo.py @@ -10,7 +10,7 @@ """ -class Averager(): +class Averager: def __init__(self): self.series = [] diff --git a/09-closure-deco/fibo_demo.py b/09-closure-deco/fibo_demo.py index 8b76796..045b24d 100644 --- a/09-closure-deco/fibo_demo.py +++ b/09-closure-deco/fibo_demo.py @@ -5,7 +5,7 @@ def fibonacci(n): if n < 2: return n - return fibonacci(n-2) + fibonacci(n-1) + return fibonacci(n - 2) + fibonacci(n - 1) if __name__ == '__main__': diff --git a/09-closure-deco/fibo_demo_lru.py b/09-closure-deco/fibo_demo_lru.py index 1a0cf11..90936f9 100644 --- a/09-closure-deco/fibo_demo_lru.py +++ b/09-closure-deco/fibo_demo_lru.py @@ -8,7 +8,7 @@ def fibonacci(n): if n < 2: return n - return fibonacci(n-2) + fibonacci(n-1) + return fibonacci(n - 2) + fibonacci(n - 1) if __name__ == '__main__': diff --git a/10-dp-1class-func/classic_strategy.py b/10-dp-1class-func/classic_strategy.py index c2ffe34..dc72e19 100644 --- a/10-dp-1class-func/classic_strategy.py +++ b/10-dp-1class-func/classic_strategy.py @@ -8,7 +8,7 @@ >>> ann = Customer('Ann Smith', 1100) >>> cart = [LineItem('banana', 4, .5), # <2> ... LineItem('apple', 10, 1.5), - ... LineItem('watermellon', 5, 5.0)] + ... LineItem('watermelon', 5, 5.0)] >>> Order(joe, cart, FidelityPromo()) # <3> >>> Order(ann, cart, FidelityPromo()) # <4> @@ -72,8 +72,7 @@ def due(self) -> float: return self.total() - discount def __repr__(self): - fmt = '' - return fmt.format(self.total(), self.due()) + return f'' class Promotion(ABC): # the Strategy: an abstract base class diff --git a/10-dp-1class-func/classic_strategy_test.py b/10-dp-1class-func/classic_strategy_test.py index 94999ec..8735811 100644 --- a/10-dp-1class-func/classic_strategy_test.py +++ b/10-dp-1class-func/classic_strategy_test.py @@ -21,7 +21,7 @@ def cart_plain() -> List[LineItem]: return [ LineItem('banana', 4, 0.5), LineItem('apple', 10, 1.5), - LineItem('watermellon', 5, 5.0), + LineItem('watermelon', 5, 5.0), ] diff --git a/10-dp-1class-func/monkeytype/classic_strategy.py b/10-dp-1class-func/monkeytype/classic_strategy.py index 06bfedd..0057d96 100644 --- a/10-dp-1class-func/monkeytype/classic_strategy.py +++ b/10-dp-1class-func/monkeytype/classic_strategy.py @@ -8,7 +8,7 @@ >>> ann = Customer('Ann Smith', 1100) >>> cart = [LineItem('banana', 4, .5), # <2> ... LineItem('apple', 10, 1.5), - ... LineItem('watermellon', 5, 5.0)] + ... LineItem('watermelon', 5, 5.0)] >>> Order(joe, cart, FidelityPromo()) # <3> >>> Order(ann, cart, FidelityPromo()) # <4> @@ -29,7 +29,6 @@ # tag::CLASSIC_STRATEGY[] from abc import ABC, abstractmethod -from collections import namedtuple import typing @@ -69,8 +68,7 @@ def due(self): return self.total() - discount def __repr__(self): - fmt = '' - return fmt.format(self.total(), self.due()) + return f'' class Promotion(ABC): # the Strategy: an abstract base class diff --git a/10-dp-1class-func/monkeytype/classic_strategy_test.py b/10-dp-1class-func/monkeytype/classic_strategy_test.py index ede42e3..b730962 100644 --- a/10-dp-1class-func/monkeytype/classic_strategy_test.py +++ b/10-dp-1class-func/monkeytype/classic_strategy_test.py @@ -20,7 +20,7 @@ def customer_fidelity_1100() -> Customer: def cart_plain() -> List[LineItem]: return [LineItem('banana', 4, .5), LineItem('apple', 10, 1.5), - LineItem('watermellon', 5, 5.0)] + LineItem('watermelon', 5, 5.0)] def test_fidelity_promo_no_discount(customer_fidelity_0, cart_plain) -> None: diff --git a/10-dp-1class-func/pytypes/classic_strategy.py b/10-dp-1class-func/pytypes/classic_strategy.py index 68b19c0..4cc833f 100644 --- a/10-dp-1class-func/pytypes/classic_strategy.py +++ b/10-dp-1class-func/pytypes/classic_strategy.py @@ -8,7 +8,7 @@ >>> ann = Customer('Ann Smith', 1100) >>> cart = [LineItem('banana', 4, .5), # <2> ... LineItem('apple', 10, 1.5), - ... LineItem('watermellon', 5, 5.0)] + ... LineItem('watermelon', 5, 5.0)] >>> Order(joe, cart, FidelityPromo()) # <3> >>> Order(ann, cart, FidelityPromo()) # <4> @@ -29,7 +29,6 @@ # tag::CLASSIC_STRATEGY[] from abc import ABC, abstractmethod -from collections import namedtuple import typing from pytypes import typelogged @@ -71,8 +70,7 @@ def due(self): return self.total() - discount def __repr__(self): - fmt = '' - return fmt.format(self.total(), self.due()) + return f'' @typelogged diff --git a/10-dp-1class-func/pytypes/classic_strategy_test.py b/10-dp-1class-func/pytypes/classic_strategy_test.py index ede42e3..b730962 100644 --- a/10-dp-1class-func/pytypes/classic_strategy_test.py +++ b/10-dp-1class-func/pytypes/classic_strategy_test.py @@ -20,7 +20,7 @@ def customer_fidelity_1100() -> Customer: def cart_plain() -> List[LineItem]: return [LineItem('banana', 4, .5), LineItem('apple', 10, 1.5), - LineItem('watermellon', 5, 5.0)] + LineItem('watermelon', 5, 5.0)] def test_fidelity_promo_no_discount(customer_fidelity_0, cart_plain) -> None: diff --git a/10-dp-1class-func/strategy.py b/10-dp-1class-func/strategy.py index 6e464c7..2ab49d1 100644 --- a/10-dp-1class-func/strategy.py +++ b/10-dp-1class-func/strategy.py @@ -8,7 +8,7 @@ >>> ann = Customer('Ann Smith', 1100) >>> cart = [LineItem('banana', 4, .5), ... LineItem('apple', 10, 1.5), - ... LineItem('watermellon', 5, 5.0)] + ... LineItem('watermelon', 5, 5.0)] >>> Order(joe, cart, fidelity_promo) # <2> >>> Order(ann, cart, fidelity_promo) @@ -71,8 +71,7 @@ def due(self) -> float: return self.total() - discount def __repr__(self): - fmt = '' - return fmt.format(self.total(), self.due()) + return f'' # <2> diff --git a/10-dp-1class-func/strategy_best.py b/10-dp-1class-func/strategy_best.py index 4054175..a7c4d74 100644 --- a/10-dp-1class-func/strategy_best.py +++ b/10-dp-1class-func/strategy_best.py @@ -7,7 +7,7 @@ >>> ann = Customer('Ann Smith', 1100) >>> cart = [LineItem('banana', 4, .5), ... LineItem('apple', 10, 1.5), - ... LineItem('watermellon', 5, 5.0)] + ... LineItem('watermelon', 5, 5.0)] >>> banana_cart = [LineItem('banana', 30, .5), ... LineItem('apple', 10, 1.5)] >>> big_cart = [LineItem(str(item_code), 1, 1.0) diff --git a/10-dp-1class-func/strategy_best2.py b/10-dp-1class-func/strategy_best2.py index 65b3556..62a993e 100644 --- a/10-dp-1class-func/strategy_best2.py +++ b/10-dp-1class-func/strategy_best2.py @@ -7,7 +7,7 @@ >>> ann = Customer('Ann Smith', 1100) >>> cart = [LineItem('banana', 4, .5), ... LineItem('apple', 10, 1.5), - ... LineItem('watermellon', 5, 5.0)] + ... LineItem('watermelon', 5, 5.0)] >>> Order(joe, cart, fidelity_promo) >>> Order(ann, cart, fidelity_promo) @@ -69,8 +69,7 @@ def due(self): return self.total() - discount def __repr__(self): - fmt = '' - return fmt.format(self.total(), self.due()) + return f'' def fidelity_promo(order): diff --git a/10-dp-1class-func/strategy_best3.py b/10-dp-1class-func/strategy_best3.py index 0574505..39ce3bf 100644 --- a/10-dp-1class-func/strategy_best3.py +++ b/10-dp-1class-func/strategy_best3.py @@ -8,7 +8,7 @@ >>> ann = Customer('Ann Smith', 1100) >>> cart = [LineItem('banana', 4, .5), ... LineItem('apple', 10, 1.5), - ... LineItem('watermellon', 5, 5.0)] + ... LineItem('watermelon', 5, 5.0)] >>> Order(joe, cart, fidelity_promo) >>> Order(ann, cart, fidelity_promo) @@ -73,8 +73,7 @@ def due(self): return self.total() - discount def __repr__(self): - fmt = '' - return fmt.format(self.total(), self.due()) + return f'' # tag::STRATEGY_BEST3[] diff --git a/10-dp-1class-func/strategy_best4.py b/10-dp-1class-func/strategy_best4.py index b2d12ae..955ac3a 100644 --- a/10-dp-1class-func/strategy_best4.py +++ b/10-dp-1class-func/strategy_best4.py @@ -8,7 +8,7 @@ >>> ann = Customer('Ann Smith', 1100) >>> cart = [LineItem('banana', 4, .5), ... LineItem('apple', 10, 1.5), - ... LineItem('watermellon', 5, 5.0)] + ... LineItem('watermelon', 5, 5.0)] >>> Order(joe, cart, fidelity) >>> Order(ann, cart, fidelity) @@ -71,8 +71,7 @@ def due(self): return self.total() - discount def __repr__(self): - fmt = '' - return fmt.format(self.total(), self.due()) + return f'' # tag::STRATEGY_BEST4[] diff --git a/10-dp-1class-func/strategy_param.py b/10-dp-1class-func/strategy_param.py index 5447189..7318530 100644 --- a/10-dp-1class-func/strategy_param.py +++ b/10-dp-1class-func/strategy_param.py @@ -6,7 +6,7 @@ >>> ann = Customer('Ann Smith', 1100) >>> cart = [LineItem('banana', 4, .5), ... LineItem('apple', 10, 1.5), - ... LineItem('watermellon', 5, 5.0)] + ... LineItem('watermelon', 5, 5.0)] >>> Order(joe, cart, fidelity_promo(10)) >>> Order(ann, cart, fidelity_promo(10)) @@ -73,8 +73,7 @@ def due(self) -> float: return self.total() - discount def __repr__(self): - fmt = '' - return fmt.format(self.total(), self.due()) + return f'' # tag::STRATEGY_PARAM[] @@ -85,7 +84,7 @@ def __repr__(self): def fidelity_promo(percent: float) -> Promotion: """discount for customers with 1000 or more fidelity points""" return lambda order: ( - order.total() * percent / 100.0 if order.customer.fidelity >= 1000 else 0 + order.total() * percent / 100 if order.customer.fidelity >= 1000 else 0 ) @@ -96,7 +95,7 @@ def discounter(order: Order) -> float: discount = 0 for item in order.cart: if item.quantity >= 20: - discount += item.total() * percent / 100.0 + discount += item.total() * percent / 100 return discount return discounter @@ -111,13 +110,13 @@ def __init__(self, percent: float): def __call__(self, order: Order) -> float: distinct_items = {item.product for item in order.cart} if len(distinct_items) >= 10: - return order.total() * self.percent / 100.0 + return order.total() * self.percent / 100 return 0 def general_discount(percent: float, order: Order) -> float: """unrestricted discount; usage: ``partial(general_discount, 5)``""" - return order.total() * percent / 100.0 + return order.total() * percent / 100 # end::STRATEGY[] diff --git a/10-dp-1class-func/strategy_param_test.py b/10-dp-1class-func/strategy_param_test.py index f7b07e3..d550e2b 100644 --- a/10-dp-1class-func/strategy_param_test.py +++ b/10-dp-1class-func/strategy_param_test.py @@ -23,7 +23,7 @@ def cart_plain() -> List[LineItem]: return [ LineItem('banana', 4, 0.5), LineItem('apple', 10, 1.5), - LineItem('watermellon', 5, 5.0), + LineItem('watermelon', 5, 5.0), ] diff --git a/10-dp-1class-func/strategy_test.py b/10-dp-1class-func/strategy_test.py index 26640fa..bf225ae 100644 --- a/10-dp-1class-func/strategy_test.py +++ b/10-dp-1class-func/strategy_test.py @@ -21,7 +21,7 @@ def cart_plain() -> List[LineItem]: return [ LineItem('banana', 4, 0.5), LineItem('apple', 10, 1.5), - LineItem('watermellon', 5, 5.0), + LineItem('watermelon', 5, 5.0), ] diff --git a/10-dp-1class-func/untyped/classic_strategy.py b/10-dp-1class-func/untyped/classic_strategy.py index 2242fdd..b969780 100644 --- a/10-dp-1class-func/untyped/classic_strategy.py +++ b/10-dp-1class-func/untyped/classic_strategy.py @@ -8,7 +8,7 @@ >>> ann = Customer('Ann Smith', 1100) >>> cart = [LineItem('banana', 4, .5), # <2> ... LineItem('apple', 10, 1.5), - ... LineItem('watermellon', 5, 5.0)] + ... LineItem('watermelon', 5, 5.0)] >>> Order(joe, cart, FidelityPromo()) # <3> >>> Order(ann, cart, FidelityPromo()) # <4> @@ -65,8 +65,7 @@ def due(self): return self.total() - discount def __repr__(self): - fmt = '' - return fmt.format(self.total(), self.due()) + return f'' class Promotion(ABC): # the Strategy: an abstract base class diff --git a/10-dp-1class-func/untyped/promotions.py b/10-dp-1class-func/untyped/promotions.py index a8795a0..aed4d1b 100644 --- a/10-dp-1class-func/untyped/promotions.py +++ b/10-dp-1class-func/untyped/promotions.py @@ -1,4 +1,3 @@ - def fidelity_promo(order): """5% discount for customers with 1000 or more fidelity points""" return order.total() * .05 if order.customer.fidelity >= 1000 else 0 diff --git a/10-dp-1class-func/untyped/strategy.py b/10-dp-1class-func/untyped/strategy.py index b0c9c86..1f8ad4c 100644 --- a/10-dp-1class-func/untyped/strategy.py +++ b/10-dp-1class-func/untyped/strategy.py @@ -8,7 +8,7 @@ >>> ann = Customer('Ann Smith', 1100) >>> cart = [LineItem('banana', 4, .5), ... LineItem('apple', 10, 1.5), - ... LineItem('watermellon', 5, 5.0)] + ... LineItem('watermelon', 5, 5.0)] >>> Order(joe, cart, fidelity_promo) # <2> >>> Order(ann, cart, fidelity_promo) @@ -64,8 +64,7 @@ def due(self): return self.total() - discount def __repr__(self): - fmt = '' - return fmt.format(self.total(), self.due()) + return f'' # <2> diff --git a/10-dp-1class-func/untyped/strategy_best.py b/10-dp-1class-func/untyped/strategy_best.py index 89281a8..c0585f7 100644 --- a/10-dp-1class-func/untyped/strategy_best.py +++ b/10-dp-1class-func/untyped/strategy_best.py @@ -7,7 +7,7 @@ >>> ann = Customer('Ann Smith', 1100) >>> cart = [LineItem('banana', 4, .5), ... LineItem('apple', 10, 1.5), - ... LineItem('watermellon', 5, 5.0)] + ... LineItem('watermelon', 5, 5.0)] >>> Order(joe, cart, fidelity_promo) >>> Order(ann, cart, fidelity_promo) @@ -71,8 +71,7 @@ def due(self): return self.total() - discount def __repr__(self): - fmt = '' - return fmt.format(self.total(), self.due()) + return f'' def fidelity_promo(order): diff --git a/10-dp-1class-func/untyped/strategy_best2.py b/10-dp-1class-func/untyped/strategy_best2.py index c7a9eff..1f4700f 100644 --- a/10-dp-1class-func/untyped/strategy_best2.py +++ b/10-dp-1class-func/untyped/strategy_best2.py @@ -7,7 +7,7 @@ >>> ann = Customer('Ann Smith', 1100) >>> cart = [LineItem('banana', 4, .5), ... LineItem('apple', 10, 1.5), - ... LineItem('watermellon', 5, 5.0)] + ... LineItem('watermelon', 5, 5.0)] >>> Order(joe, cart, fidelity_promo) >>> Order(ann, cart, fidelity_promo) @@ -71,8 +71,7 @@ def due(self): return self.total() - discount def __repr__(self): - fmt = '' - return fmt.format(self.total(), self.due()) + return f'' def fidelity_promo(order): diff --git a/10-dp-1class-func/untyped/strategy_best3.py b/10-dp-1class-func/untyped/strategy_best3.py index 1b30114..de6ce4d 100644 --- a/10-dp-1class-func/untyped/strategy_best3.py +++ b/10-dp-1class-func/untyped/strategy_best3.py @@ -8,7 +8,7 @@ >>> ann = Customer('Ann Smith', 1100) >>> cart = [LineItem('banana', 4, .5), ... LineItem('apple', 10, 1.5), - ... LineItem('watermellon', 5, 5.0)] + ... LineItem('watermelon', 5, 5.0)] >>> Order(joe, cart, fidelity_promo) >>> Order(ann, cart, fidelity_promo) @@ -75,8 +75,7 @@ def due(self): return self.total() - discount def __repr__(self): - fmt = '' - return fmt.format(self.total(), self.due()) + return f'' # tag::STRATEGY_BEST3[] diff --git a/10-dp-1class-func/untyped/strategy_best4.py b/10-dp-1class-func/untyped/strategy_best4.py index afa05e1..b523752 100644 --- a/10-dp-1class-func/untyped/strategy_best4.py +++ b/10-dp-1class-func/untyped/strategy_best4.py @@ -8,7 +8,7 @@ >>> ann = Customer('Ann Smith', 1100) >>> cart = [LineItem('banana', 4, .5), ... LineItem('apple', 10, 1.5), - ... LineItem('watermellon', 5, 5.0)] + ... LineItem('watermelon', 5, 5.0)] >>> Order(joe, cart, fidelity) >>> Order(ann, cart, fidelity) @@ -72,8 +72,7 @@ def due(self): return self.total() - discount def __repr__(self): - fmt = '' - return fmt.format(self.total(), self.due()) + return f'' # tag::STRATEGY_BEST4[] diff --git a/10-dp-1class-func/untyped/strategy_param.py b/10-dp-1class-func/untyped/strategy_param.py index 4c23623..d5cc931 100644 --- a/10-dp-1class-func/untyped/strategy_param.py +++ b/10-dp-1class-func/untyped/strategy_param.py @@ -6,7 +6,7 @@ >>> ann = Customer('Ann Smith', 1100) >>> cart = [LineItem('banana', 4, .5), ... LineItem('apple', 10, 1.5), - ... LineItem('watermellon', 5, 5.0)] + ... LineItem('watermelon', 5, 5.0)] >>> Order(joe, cart, fidelity_promo(10)) >>> Order(ann, cart, fidelity_promo(10)) @@ -60,13 +60,12 @@ def due(self): return self.total() - discount def __repr__(self): - fmt = '' - return fmt.format(self.total(), self.due()) + return f'' def fidelity_promo(percent): """discount for customers with 1000 or more fidelity points""" - return lambda order: (order.total() * percent/100.0 + return lambda order: (order.total() * percent / 100 if order.customer.fidelity >= 1000 else 0) @@ -76,7 +75,7 @@ def discounter(order): discount = 0 for item in order.cart: if item.quantity >= 20: - discount += item.total() * percent/100.0 + discount += item.total() * percent / 100 return discount return discounter @@ -86,6 +85,6 @@ def large_order_promo(percent): def discounter(order): distinct_items = {item.product for item in order.cart} if len(distinct_items) >= 10: - return order.total() * percent / 100.0 + return order.total() * percent / 100 return 0 return discounter diff --git a/10-dp-1class-func/untyped/strategy_param2.py b/10-dp-1class-func/untyped/strategy_param2.py index 91d77f2..625bbca 100644 --- a/10-dp-1class-func/untyped/strategy_param2.py +++ b/10-dp-1class-func/untyped/strategy_param2.py @@ -6,7 +6,7 @@ >>> ann = Customer('Ann Smith', 1100) >>> cart = [LineItem('banana', 4, .5), ... LineItem('apple', 10, 1.5), - ... LineItem('watermellon', 5, 5.0)] + ... LineItem('watermelon', 5, 5.0)] >>> Order(joe, cart, FidelityPromo(10)) >>> Order(ann, cart, FidelityPromo(10)) @@ -60,11 +60,10 @@ def due(self): return self.total() - discount def __repr__(self): - fmt = '' - return fmt.format(self.total(), self.due()) + return f'' -class Promotion(): +class Promotion: """compute discount for order""" def __init__(self, percent): @@ -79,7 +78,7 @@ class FidelityPromo(Promotion): def __call__(self, order): if order.customer.fidelity >= 1000: - return order.total() * self.percent/100.0 + return order.total() * self.percent / 100 return 0 @@ -90,7 +89,7 @@ def __call__(self, order): discount = 0 for item in order.cart: if item.quantity >= 20: - discount += item.total() * self.percent/100.0 + discount += item.total() * self.percent / 100 return discount @@ -100,5 +99,5 @@ class LargeOrderPromo(Promotion): def __call__(self, order): distinct_items = {item.product for item in order.cart} if len(distinct_items) >= 10: - return order.total() * self.percent / 100.0 + return order.total() * self.percent / 100 return 0 diff --git a/11-pythonic-obj/mem_test.py b/11-pythonic-obj/mem_test.py index ecf6c04..c18527a 100644 --- a/11-pythonic-obj/mem_test.py +++ b/11-pythonic-obj/mem_test.py @@ -8,17 +8,17 @@ module_name = sys.argv[1].replace('.py', '') module = importlib.import_module(module_name) else: - print('Usage: {} '.format()) + print(f'Usage: {sys.argv[0]} ') sys.exit(1) fmt = 'Selected Vector2d type: {.__name__}.{.__name__}' print(fmt.format(module, module.Vector2d)) mem_init = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss -print('Creating {:,} Vector2d instances'.format(NUM_VECTORS)) +print(f'Creating {NUM_VECTORS:,} Vector2d instances') vectors = [module.Vector2d(3.0, 4.0) for i in range(NUM_VECTORS)] mem_final = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss -print('Initial RAM usage: {:14,}'.format(mem_init)) -print(' Final RAM usage: {:14,}'.format(mem_final)) +print(f'Initial RAM usage: {mem_init:14,}') +print(f' Final RAM usage: {mem_final:14,}') diff --git a/11-pythonic-obj/private/Expose.java b/11-pythonic-obj/private/Expose.java index dac57fe..35c2dbd 100644 --- a/11-pythonic-obj/private/Expose.java +++ b/11-pythonic-obj/private/Expose.java @@ -18,7 +18,7 @@ public static void main(String[] args) { System.out.println("message.secret = " + wasHidden); } catch (IllegalAccessException e) { - // this will not happen after setAcessible(true) + // this will not happen after setAccessible(true) System.err.println(e); } } diff --git a/11-pythonic-obj/private/expose.py b/11-pythonic-obj/private/expose.py index 1df710c..094acbd 100644 --- a/11-pythonic-obj/private/expose.py +++ b/11-pythonic-obj/private/expose.py @@ -3,4 +3,4 @@ message = Confidential('top secret text') secret_field = Confidential.getDeclaredField('secret') secret_field.setAccessible(True) # break the lock! -print 'message.secret =', secret_field.get(message) +print('message.secret =', secret_field.get(message)) diff --git a/11-pythonic-obj/private/leakprivate.py b/11-pythonic-obj/private/leakprivate.py index bd20bf0..5e4ecbd 100644 --- a/11-pythonic-obj/private/leakprivate.py +++ b/11-pythonic-obj/private/leakprivate.py @@ -7,5 +7,5 @@ # list private fields only if Modifier.isPrivate(field.getModifiers()): field.setAccessible(True) # break the lock - print 'field:', field - print '\t', field.getName(), '=', field.get(message) + print('field:', field) + print('\t', field.getName(), '=', field.get(message)) diff --git a/11-pythonic-obj/private/no_respect.py b/11-pythonic-obj/private/no_respect.py index 4eb3476..291eed8 100644 --- a/11-pythonic-obj/private/no_respect.py +++ b/11-pythonic-obj/private/no_respect.py @@ -1,4 +1,3 @@ - """ In the Jython registry file there is this line: @@ -14,4 +13,4 @@ for name in dir(message): attr = getattr(message, name) if not callable(attr): # non-methods only - print name + '\t=', attr + print(name + '\t=', attr) diff --git a/11-pythonic-obj/vector2d_v3.py b/11-pythonic-obj/vector2d_v3.py index f2e31ee..376a8a4 100644 --- a/11-pythonic-obj/vector2d_v3.py +++ b/11-pythonic-obj/vector2d_v3.py @@ -81,7 +81,7 @@ >>> v2 = Vector2d(3.1, 4.2) >>> hash(v1), hash(v2) (7, 384307168202284039) - >>> len(set([v1, v2])) + >>> len({v1, v2}) 2 """ diff --git a/11-pythonic-obj/vector2d_v3_prophash.py b/11-pythonic-obj/vector2d_v3_prophash.py index 0f8830f..3552530 100644 --- a/11-pythonic-obj/vector2d_v3_prophash.py +++ b/11-pythonic-obj/vector2d_v3_prophash.py @@ -83,7 +83,7 @@ >>> v2 = Vector2d(3.1, 4.2) >>> hash(v1), hash(v2) (7, 384307168202284039) - >>> len(set([v1, v2])) + >>> len({v1, v2}) 2 # end::VECTOR2D_V3_DEMO[] diff --git a/11-pythonic-obj/vector2d_v3_slots.py b/11-pythonic-obj/vector2d_v3_slots.py index 0e56556..1d0536a 100644 --- a/11-pythonic-obj/vector2d_v3_slots.py +++ b/11-pythonic-obj/vector2d_v3_slots.py @@ -80,7 +80,7 @@ >>> v2 = Vector2d(3.1, 4.2) >>> hash(v1), hash(v2) (7, 384307168202284039) - >>> len(set([v1, v2])) + >>> len({v1, v2}) 2 # end::VECTOR2D_V3_DEMO[] diff --git a/20-concurrency/primes/primes.py b/20-concurrency/primes/primes.py index 63dd7a1..0884dcd 100755 --- a/20-concurrency/primes/primes.py +++ b/20-concurrency/primes/primes.py @@ -1,5 +1,4 @@ import math -import itertools PRIME_FIXTURE = [ @@ -36,7 +35,7 @@ def is_prime(n) -> bool: if n % 2 == 0: return False - root = int(math.floor(math.sqrt(n))) + root = math.floor(math.sqrt(n)) for i in range(3, root + 1, 2): if n % i == 0: return False diff --git a/20-concurrency/primes/procs_py37.py b/20-concurrency/primes/procs_py37.py index 2ab5d37..19d86e7 100644 --- a/20-concurrency/primes/procs_py37.py +++ b/20-concurrency/primes/procs_py37.py @@ -34,10 +34,10 @@ def main() -> None: label = 'P' if prime else ' ' print(f'{n:16} {label} {elapsed:9.6f}s') - time = perf_counter() - t0 print('Total time:', f'{time:0.2f}s') + if __name__ == '__main__': main() # end::PRIMES_PROC_MAIN[] diff --git a/20-concurrency/primes/spinner_async_nap.py b/20-concurrency/primes/spinner_async_nap.py index 30fd4f2..13fa066 100644 --- a/20-concurrency/primes/spinner_async_nap.py +++ b/20-concurrency/primes/spinner_async_nap.py @@ -18,7 +18,7 @@ async def is_prime(n): return False sleep = asyncio.sleep # <1> - root = int(math.floor(math.sqrt(n))) + root = math.floor(math.sqrt(n)) for i in range(3, root + 1, 2): if n % i == 0: return False diff --git a/20-concurrency/primes/spinner_prime_async_nap.py b/20-concurrency/primes/spinner_prime_async_nap.py index c1f82ad..48166ee 100644 --- a/20-concurrency/primes/spinner_prime_async_nap.py +++ b/20-concurrency/primes/spinner_prime_async_nap.py @@ -18,7 +18,7 @@ async def is_prime(n): return False sleep = asyncio.sleep # <1> - root = int(math.floor(math.sqrt(n))) + root = math.floor(math.sqrt(n)) for i in range(3, root + 1, 2): if n % i == 0: return False diff --git a/20-concurrency/primes/spinner_prime_proc.py b/20-concurrency/primes/spinner_prime_proc.py index e1454d1..8a532af 100644 --- a/20-concurrency/primes/spinner_prime_proc.py +++ b/20-concurrency/primes/spinner_prime_proc.py @@ -7,7 +7,6 @@ from multiprocessing import Process, Event from multiprocessing import synchronize import itertools -import time from primes import is_prime diff --git a/20-concurrency/primes/spinner_prime_thread.py b/20-concurrency/primes/spinner_prime_thread.py index 11db5d9..000743e 100644 --- a/20-concurrency/primes/spinner_prime_thread.py +++ b/20-concurrency/primes/spinner_prime_thread.py @@ -6,7 +6,6 @@ from threading import Thread, Event import itertools -import time from primes import is_prime diff --git a/20-concurrency/primes/spinner_thread.py b/20-concurrency/primes/spinner_thread.py index 795e116..c7e4c8b 100644 --- a/20-concurrency/primes/spinner_thread.py +++ b/20-concurrency/primes/spinner_thread.py @@ -7,7 +7,6 @@ # tag::SPINNER_THREAD_TOP[] from threading import Thread, Event import itertools -import time from primes import is_prime diff --git a/20-concurrency/primes/threads_py37.py b/20-concurrency/primes/threads_py37.py index 296d26c..d98aca0 100644 --- a/20-concurrency/primes/threads_py37.py +++ b/20-concurrency/primes/threads_py37.py @@ -1,5 +1,5 @@ from time import perf_counter -from typing import Tuple, List, NamedTuple +from typing import List, NamedTuple from threading import Thread from queue import SimpleQueue diff --git a/README.md b/README.md index ec0a6d0..defbde9 100644 --- a/README.md +++ b/README.md @@ -49,4 +49,4 @@ Part / Chapter #|Title|Directory|Notebook|1st ed. Chapter # **VI – Metaprogramming**| 22|Dynamic Attributes and Properties|[22-dyn-attr-prop](22-dyn-attr-prop)||19 23|Attribute Descriptors|[23-descriptor](23-descriptor)||20 -24|Class Metapgrogramming|[24-class-metaprog](24-class-metaprog)||21 +24|Class Metaprogramming|[24-class-metaprog](24-class-metaprog)||21 From ace44eeaf2545e67960b27105d1d22ae7a41a332 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Fri, 12 Feb 2021 22:53:10 -0300 Subject: [PATCH 010/166] ch21 examples --- 21-futures/demo_executor_map.py | 31 ++++ 21-futures/getflags/country_codes.txt | 8 + 21-futures/getflags/downloaded/.gitignore | 1 + 21-futures/getflags/flags.py | 59 +++++++ 21-futures/getflags/flags.zip | Bin 0 -> 2370199 bytes 21-futures/getflags/flags2_asyncio.py | 113 +++++++++++++ 21-futures/getflags/flags2_common.py | 155 ++++++++++++++++++ 21-futures/getflags/flags2_sequential.py | 92 +++++++++++ 21-futures/getflags/flags2_threadpool.py | 73 +++++++++ 21-futures/getflags/flags_asyncio.py | 47 ++++++ 21-futures/getflags/flags_threadpool.py | 34 ++++ .../getflags/flags_threadpool_futures.py | 36 ++++ 21-futures/getflags/requirements.txt | 11 ++ 21-futures/getflags/slow_server.py | 92 +++++++++++ 21-futures/primes/primes.py | 52 ++++++ 21-futures/primes/proc_pool.py | 50 ++++++ 16 files changed, 854 insertions(+) create mode 100644 21-futures/demo_executor_map.py create mode 100644 21-futures/getflags/country_codes.txt create mode 100644 21-futures/getflags/downloaded/.gitignore create mode 100755 21-futures/getflags/flags.py create mode 100644 21-futures/getflags/flags.zip create mode 100755 21-futures/getflags/flags2_asyncio.py create mode 100644 21-futures/getflags/flags2_common.py create mode 100755 21-futures/getflags/flags2_sequential.py create mode 100755 21-futures/getflags/flags2_threadpool.py create mode 100755 21-futures/getflags/flags_asyncio.py create mode 100755 21-futures/getflags/flags_threadpool.py create mode 100755 21-futures/getflags/flags_threadpool_futures.py create mode 100644 21-futures/getflags/requirements.txt create mode 100755 21-futures/getflags/slow_server.py create mode 100755 21-futures/primes/primes.py create mode 100755 21-futures/primes/proc_pool.py diff --git a/21-futures/demo_executor_map.py b/21-futures/demo_executor_map.py new file mode 100644 index 0000000..00d3bdb --- /dev/null +++ b/21-futures/demo_executor_map.py @@ -0,0 +1,31 @@ +""" +Experiment with ``ThreadPoolExecutor.map`` +""" +# tag::EXECUTOR_MAP[] +from time import sleep, strftime +from concurrent import futures + +def display(*args): # <1> + print(strftime('[%H:%M:%S]'), end=' ') + print(*args) + +def loiter(n): # <2> + msg = '{}loiter({}): doing nothing for {}s...' + display(msg.format('\t'*n, n, n)) + sleep(n) + msg = '{}loiter({}): done.' + display(msg.format('\t'*n, n)) + return n * 10 # <3> + +def main(): + display('Script starting.') + executor = futures.ThreadPoolExecutor(max_workers=3) # <4> + results = executor.map(loiter, range(5)) # <5> + display('results:', results) # <6> + display('Waiting for individual results:') + for i, result in enumerate(results): # <7> + display('result {}: {}'.format(i, result)) + +if __name__ == '__main__': + main() +# end::EXECUTOR_MAP[] diff --git a/21-futures/getflags/country_codes.txt b/21-futures/getflags/country_codes.txt new file mode 100644 index 0000000..72c37f0 --- /dev/null +++ b/21-futures/getflags/country_codes.txt @@ -0,0 +1,8 @@ +AD AE AF AG AL AM AO AR AT AU AZ BA BB BD BE BF BG BH BI BJ BN BO BR BS BT +BW BY BZ CA CD CF CG CH CI CL CM CN CO CR CU CV CY CZ DE DJ DK DM DZ EC EE +EG ER ES ET FI FJ FM FR GA GB GD GE GH GM GN GQ GR GT GW GY HN HR HT HU ID +IE IL IN IQ IR IS IT JM JO JP KE KG KH KI KM KN KP KR KW KZ LA LB LC LI LK +LR LS LT LU LV LY MA MC MD ME MG MH MK ML MM MN MR MT MU MV MW MX MY MZ NA +NE NG NI NL NO NP NR NZ OM PA PE PG PH PK PL PT PW PY QA RO RS RU RW SA SB +SC SD SE SG SI SK SL SM SN SO SR SS ST SV SY SZ TD TG TH TJ TL TM TN TO TR +TT TV TW TZ UA UG US UY UZ VA VC VE VN VU WS YE ZA ZM ZW diff --git a/21-futures/getflags/downloaded/.gitignore b/21-futures/getflags/downloaded/.gitignore new file mode 100644 index 0000000..d8c78e1 --- /dev/null +++ b/21-futures/getflags/downloaded/.gitignore @@ -0,0 +1 @@ +*.gif \ No newline at end of file diff --git a/21-futures/getflags/flags.py b/21-futures/getflags/flags.py new file mode 100755 index 0000000..4164937 --- /dev/null +++ b/21-futures/getflags/flags.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 + +"""Download flags of top 20 countries by population + +Sequential version + +Sample runs (first with new domain, so no caching ever):: + + $ ./flags.py + BD BR CD CN DE EG ET FR ID IN IR JP MX NG PH PK RU TR US VN + 20 downloads in 26.21s + $ ./flags.py + BD BR CD CN DE EG ET FR ID IN IR JP MX NG PH PK RU TR US VN + 20 downloads in 14.57s + + +""" + +# tag::FLAGS_PY[] +import os +import time +from typing import Callable + +import requests # <1> + +POP20_CC = ('CN IN US ID BR PK NG BD RU JP ' + 'MX PH VN ET EG DE IR TR CD FR').split() # <2> + +BASE_URL = 'http://fluentpython.com/data/flags' # <3> +DEST_DIR = 'downloaded/' # <4> + +def save_flag(img: bytes, filename: str) -> None: # <5> + path = os.path.join(DEST_DIR, filename) + with open(path, 'wb') as fp: + fp.write(img) + +def get_flag(cc: str) -> bytes: # <6> + cc = cc.lower() + url = f'{BASE_URL}/{cc}/{cc}.gif' + resp = requests.get(url) + return resp.content + +def download_many(cc_list: list[str]) -> int: # <7> + for cc in sorted(cc_list): # <8> + image = get_flag(cc) + print(cc, end=' ', flush=True) # <9> + save_flag(image, cc.lower() + '.gif') + + return len(cc_list) + +def main(downloader: Callable[[list[str]], int]) -> None: # <10> + t0 = time.perf_counter() # <11> + count = downloader(POP20_CC) + elapsed = time.perf_counter() - t0 + print(f'\n{count} downloads in {elapsed:.2f}s') + +if __name__ == '__main__': + main(download_many) # <12> +# end::FLAGS_PY[] diff --git a/21-futures/getflags/flags.zip b/21-futures/getflags/flags.zip new file mode 100644 index 0000000000000000000000000000000000000000..671b5a0e40419c71a53ef9e727e2daa2be43adc1 GIT binary patch literal 2370199 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/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 z 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%0AoL+s5LmC%DF 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�zhX5VVU3Ro2hs94p<`6TILGf5%hzcycY<# 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≦s{OY zKPrv7?urQ1oU7JzO_$yuZmynVYYMEc7bhjE$RXUN?UMQ#gI*`LKI|2v z{Z4(wmo{(|NqZi7qshVB+mG{UaEgqD)%6rBHooFAK2h_K)b#~f_A!k*FeH`)Ylh?R z^)+^j(veG#es*TCy*1XH(!jh9{h8me>#c2nD(N!bcoR6s6^wJ44xe1sXuF#_7gC10 za`RE1Uc@n48VY=8z5PPIe#*1xwHx-IChYls*nb+a{Qig&qv7sXT?u{fC7P++r9{V- zT6d8~bllQ@Rn+xyN+n~PAGj164i^8m$Y?OXRGs+|0G_f2 zZ}M89;B~Tax77M1XAKi}r0Z$sGPc_?B0nGHD-J*QkV-KMl?kR0G2C`WxmRyVssDj3)kLzG)S|AEo3-GPdCKL zdnUMw8(zN@JOE35rzoazp(kI;I!`2zRzHG+y)_AP^1{^^hEGSzpjG-H^@`k;wdTrcr}Gcu9)Zq0 zIR58Z=CqElOZJOOmRO`{!ubG9$uXN3;^Ro-1TVJ7QFK9U^jeG(gwSx>ds@#dtp5FU ztwu9t`fb}jIwuZtzVWSMADG?@sVkeQ@0n?sn`!zpbLY~`6u@r7h@=k>3s52XWB^Wv zb^m!!=a}tIb}Ifd4OjB;(i2~0fL-bDAB)evoO|D`!s5>Qq}pw^r3HGM_5{^At#^T*Q-A! zUE__L`pos6BfdI+KDaG$r*WQBRWzcP^!|7 zH#pbmAWNUcl}6MKpK`H>4%hM3j$Xi}Nt*kLl3uv^F7b>#^+#71o#(=< z4;Ma|Uh3tkjs4NEVg@dpOerg0dhnWhSMXzK@LGdqAm#RQ8@J2j37eA-h)PZKM?dY7 z4>tiHCyk`ddR6+p>U-u)7hadoujS+W3b0W7S?4OwJ`G)F)rA_cve)W@pXzd<>J#&w zr878e)DKMXd+PY{g;Pa3COqW1rgz6RwBBBT&yv1?4h;Hu+^&i;IFA_9JsT|)CJtH* z)8P{0<`#$x5Y~beQ8}&WJ-HV+0!g3?WUZ<%Qv2JcnM)jnI4C!fOY#T-(xuAhFR}j> zPDDc#61nCpz()7Lf|wt=gpA#aAA(8D+$7X2k&E!g>7FCAD}i}(3^q#O5Tfp!ezN27 zVaM}mhbX!0ZL;fov~!6J@*^tS6Cvnt?AqU0 z%RcW5%6`#cF$b^Jo&-hdZ-H^xVq1f1h7n35?T0FeW%A3 z@fqB*#~o_Sa?3AEkr1roua+&SQf4_aZm(CY3Kj6*Pd8WGe0E*YZgYQ)JKaW1Bj)SM zyB{)KtnURDmi$(dO>3;Ksm64E?ltD)@=%G2?cH>4a8r}XA}tPPCq6@6BrJZJm*79s z-sJawck_wpe7LFW?l&-ZGdF7b{->R@DUU2=Bi8 zJ-?wHu{QheN5B|O>Os~=-j$>X@!yx+G5Xsf(=C4KYvWu+M=KHL!r#`i3q@I5C#Kks zdCu;gTN$~g7BD~C73qC}P)8ko@N;J_$8T|X#HTulvH0|=*vWE)lo&oT?JQk9eem1z z>Z=bU#-?@><_$jUuYPYNvmIZ=mhAY}KbQ7Yyy{;qHP%l7|H6oPQ>0KAzKM=(Gnyn> zaTS0(D=~>ABWAF`tcV%|8cYQ8BA@poJT}*R-vzgAnZG+N!fnK=T5rq6b&fC6QfjC3 zMYU8`;5n;X>{mAnvSgwntQF@V+^`Cb)21aezsB(;VfB1bwi;dT7!6j%3Fe{*GMM#Q zu>tE=t?*O!%4ApuNfC^qZ5K!l!Gm(EFQ;WN2&QIzM;gXEuX%G}xD92rgEbe&6>yb2 z8r)!F7&cr2LrpL9dUW)f9djcY6r`i;$~l{r$g`CDVT~CrtH2?MUi>>cI7`085*aJnLiue zj;G>AaDiA!M}a(`*riR_J5Q5iE;VmA{yeovp~3-|$w3eW3&zcTIcFHBKj0B+;^}I$q?^3!fCfU8 zpJvF3Ux=*6Q8z02B|||zSIWk#iGp0V+(#>U7SOIiARDsb1qo9ODd<%>FEr4vpwlUr zljYY5F7~ve3<5GaRH+P$#}t0)0*5rV00H-oO#ePM-2+l?D6`S}xmRDdjJ3{9Qt|E& z+y@>ZY=e@nI)=8?+(1`l=7VuyBw>(ZCm+8NK!U`B?@?d%#cx08L&i-#DPs&Kl60>Led5I5#ziYvG- z{s}n~l8{+)nj0b`Lx-_+;-vO;j`9o4zD3M`vfJ^zWz2VrkSgOK#`5HCr|?!EzAZTS z)ixqKOfOs%lb5QHz+gK=sDxgprfDfJvMDW~jFZQbp>+rVG9U#KTXy9&T41v&tjI8% zO_JTNgi5;Ff!P+A2u5ujtbxnNg>Lk1I&niKC%$pZvMjoqZ{P-Lq|^&5(vt2OLjsgn z(h=6Y(y_Opo$5)MT-HU8p4= z#oiDV8rW`hn9Z%UUSoY3TOy#v#CbW1d&sa4zOkWu4b-a6f5SyG`~34ye;jBB+%ine zz3>J*DY($AEBn$ItNBsQ(&KWZzFb6}d?E69OLjOefqcxnu}f0VyQ(cloh=18cQv>+x!{o?nxuUeR}LyLw%~ zzIV+qc;*RH93oaf)x~2mk-4Hz+lY8o8c>JNht6sH%C?IH%Vaz1@m_zuonOyWrIRb2 z^4phTGR(Yi6rR*+#ocxtkn!+h6}~bX`^#Z4P)PA^oLGqILsplGTh}dSl~jDLZ&BDX zluWxj2l*2aP*MlkaGd&9+q>DwhP=osORZ6K8c&`QuN1~M_uYM6aBGCopwTWv^{rau z!9{0)%>sM1sdrnZ&V~9MeWEB)PwE$P9Xdxk9>bqFof>z#6& z>vcw4=z6}H)39ZbBaVx%G{2Q5*sWfH6LU@Ac;`R@U9HF~c|8m%nh>NT^urRfFRtVs z#0_&Db&++ib63<}?qioA;|1&Tn2rs~({!ZJyEi$e*26a8 zmU(#>KmJj?@tR%#3TrqclL@`V^=c0S2QcJVUx>tmUupc?EvX=hMLy%ezOXw)7VV-1 zk%Edonb32odh(0hVD}fk&vjD;HaJC2S*rPRkT~wL;KJ+xqHm1)8yHsx>?&y*G3G3B z$;KNdJlLKb4A+5n3VwD~I2nssqUiw-|C?`4&ZlMb+ZnZ(l`P5^*w9 zgHHIuGhz)a_-&@OhtJB+eyVg|_jG>n;#qyt2?CwfzcW(Dll(1|F11lufY&UfuBl+8 zf!lhxehvhbt*8Q8KpRllK9`N=omoi^pHnj^f3fSdaVDz*%;USTG#qZrQu5UDM0@WU zM^_b%&WeT3w_#t)VDq^R?dMHDgCcPB2ET|Cn>XrM|dW-RIG1!9#Lwa_DRP`zbF|K_!$vAekK;4tBz zw5oG-w&JT}k8NVvyJnVEUz?u&_2%~FoyNpVp99YRo?1tQywob!RH>-fp1r*PBJuKe ziR9Xsk1mUn-zjg`mUyLv40v0|;>{)uB^%?ZajjFVTw@Pa#?qx8CILcC$?83CG!-YW zJ`%d1Q`ehY>Pi+s(X;YZ2pN5gTd|chMp~Ybd6a5+wstMsPvNK%zV>K?cT6wIH)wQpASI z%IQ(^FgO52F97zw84sIb70ay-HtnJD5IH#05!b>UZe?k#CHYmdnQJK6ea}cW5Ug0$ zk~h)0^%|x)UAs%aJrgLTmiJHy0?8Xu&5}=2%LDFa9(xwgGD1vYjWSfM;D7RI@QS=e zA`%p%mrjzgtjn^#!)KYWoqim_WyDCoT}r7hwZ3=o_Zm#zPRCCVq#eLcbOZ6rl)ycx zd>}IygB+lj6f&Lq!-4DB>vUiNEXs?7Adr_BK>loZQyxfmA!+gx=*dGCcNPjZu#2gZ zU4jNi6MJwAIjMPB*WaYwyivP7oLQ2Q_UMO7ZDm84$789eEI}kCQYPJI&!RcY?p>ip zf-)>}!8(!-TK1nIcN zR8%uq7Dz_AcDxEndliy;0o&u=`A~X;(l2vQ^`K94vE@-ol1oA}qPffhtQNRHfpR}7 z=NV+OyvB5co#Q+r#tX`6I(Wul)GmrWN(;s<4~t^JxHAYWWdznn0$UG({RM$zj=)(< zfd3&RBGZHEL!@rIC@7iJ&Vm1ogMjmRGy-&cJ3V@VJV1E40Wwe?XXYX#>A}g>qev_@ z|9Szy6_}*L0Ofc!NsOfnP?=b0x4t?hDSuD3T1bKtz)!%exD8>6PN}fz)N`HShciq+ z&Z$WRGCibJy%0$`(gLx@1u0I^xObXa@QakNW^hg(2&-qu+W_Z5P(XoWQREkk$`kKa zEKHezn9flKh8&3)x>QQB&~~zBdiFOMW&nsqf;cfRVjcn0r38bAp_1__Gk9bmInCKSrA(2}Zi&;4m?BNQD}iPj(JN4J?SHJ8#3amZ z*D7vjUBEhWLToy<<9Dhw6*#V9#H0;kX25i4pje@o7Ix`TMzDl#r)ceT8*K|_;#(W- zx3ya7k)MYm_bj-PFaVuyH91;e^Y*e+x=GKeNHRHQcmmdQDth9Dxv>RKn*w6MqBBm# z$xx0i$-tuGtpOw~wG5PKWC6&-QrlewK zXC2!r9~&Q{XhI&G?53v@{RlbyNIV#(d2caD^pIo{9=3kZRr|Uc8NxpSyi9brf0ZikAhw z)&>dbK~Ko)o&uaJ@LJ`snJZC>Q`IOLG?{}+cHPX#Q{eil!nIN&FU-y2;YxPxO5OgI z>QmT|dtPwxv*%ZiQEAJb2R4pedrvRd)pH7E!7!k^8;&Z7R1r84BtZnL5+TQlQ1b;@ zvil`pfJf+5RE-69^E9)WTVf{^N=+8Z%cIfZ2S!CBLLgUpS7rodM#d>|&*Kez?!EY# zsEQ#_{6v%O5Ocemaz+wYQ1&Q^1?j`Gzmm#5Oo7!}G%3Fmw8@M1G*YCj$t64O1cw9zpj~B|5>23SiZsBHrRG z)JD%RD{y#$YiENz4ZbR}I(0=4K-K}cCg8HXuc^JSnWwM0&I(p%1+U|4WzH-(?Vex@ zg6qMqPr63RyIxwch#aPHFHlrjK5S>O)c;CDcDP>BqUgehUTRzL@3IImJ0$82XZAR~ z@Efu)n~lPxXwbdx^{|vuPvll)an~_NAt|mdWD)rl3+5!t<0}^7z9Em-f^}9%@qp+b zSleEDMKa&;K6Ch+_`9V>L)z@?$q*69kBnhdr$cKjUB0)fgE6vmO zBHx*)13O;JbPy4EzPbuGc7T6Lmbc8c?9KST!1;|HWE{6$-*} z2DN*J_+A!fKDx>aWT?N4j*AugC_h#COLzwn3Do^ zRzzrNHrdm!FD=gdL5gGM-tz}Ll)pLJ{gqMd3Ls0c`WUj{-cSr7J-QRhON3xlf?pYL z3g`^+(VckFFlN{EhjtbOiJ_$SxnR1NMJ4azt%5Il1@d%qiW!u9Zz`w`6qK{lmh)}0 z>08Iz21!25zMD0IW^z_G#f(aaj>AWXo64)!4MauM-IS459Qi<(<<@C#c1omqfiN!D zOqBO#C5e?vMw1IcE%!(Dguy>d%NqONtOe$GMEs8ZwBJOYK6I%{qeSkx#O}a&7c98P zhDaAF#Mq*l4S3_=^K3egJ{`igXAzA~=c6rv0CF@nNtPV$c0Jr(F13)^BjMs$%1dZA#QFA18GM6(mDcF)SbNRb&{I~nZKehUwGfelzmjfvT`|-yhep>Qz zLfUaUIhYP+rzai9M4O(7R+isBZn=&5maKu^wz#)#eTo?yu`Sk_o4;|xpn%CO1FS5x zgC7EHi=veYX-+25HW51xLrGQ<+YY%qPT!J)_da)#hgZT>hUgmxXU`c1d}>Qu5g`D! zcEQn4w|qWWi0OLGazvEh$eTb*t}GMP6vEju7M47$Z_A}eO;Zf(K#C{4l#V$u)d997 zO4P<&gr5bPR|8bwjVph6FS363>PRuBVeo-uBy3nVc#9 z>0^;Xz*RqwBTQ{jF2_4zA^|pO9d~hp)R3kp&XRte$=!{N$VkdpdAVHfbm`!YecEg^ zpX=c2DCzmzi#yZP#=`Aw>0OKu5W*H^Jw31K$KK08-1RkYlcA>V=}7h=lCRx#>KLP` zet0|?yOqOngWYI2+5VPWY&ucM6>KNudoI^6GGhAUZhDmNN;H?lx$cRp0p#>2P8mI` z!k*Leb-Xl8cIg7sxNuUrSJY*dcc4zEfV7p?@GtnU8*LKtmgVH|93Xre5)MzXfjdOG z!g%Wr*mB%EETD<5={C(E6EegMZ4uM?)u!T1Vqi*V&$(DMOnKu@KC**56CBkEeHD=G zK!O}4GRW%aWOYlZ8Iam(mhw6d;H^t$k_F5N$?A+`Nd}O}fFSlRep+AdstA<$9CMRv zx9V7sh0dov)!6qJA+Y1)jk8XUVh_Rsv09n*cBEf1JeQE(W@_C zpaU#;O_2PG6O2>#DNiyE(c50fC-Q#7X3&M)6c#3JJ}{{7#0u{6DYMg!6+24P z&0am<*?xXgo@ony+Zy(1SH+`a7xg(4F`S|w#r@VSbmbMhxKz?7sDMWJbDDc}%8`|@ z^C+lW{qi5r@9D!IYBl(@POqd^2RxNWG0eA@d;3{HOoSGLFnD36e{eCkggJ&o+@OxY zk0ABoHFYxx=ATK6<|$m6;5PT`BdtGFzGT+5TCYD1Q0}O%CO#AdBlq*@{PHAtC+47x> z^)eSTTgfwF4Sm3Gb%-YRiLSE~U+T-zEO@W3K1U`QOqy=JK;ZW$xTNsgZM;%u3Qqy3 zLI&X-aMCUA8t!yV5r3?mXYDn&S62G6*8oh)Z1BJaP zdwr%~{W%%R)=;b+iiluhGJ^UQ+T#%#PG&~%ji*f;NFnZaHV4`3&UKy^Q_yOI1BOZ9 zgkO=#Cu&rtNCYd$+OJ3+mtNJtN52A5pf$6KGx;(^HP`nhk9Zta8j~$0wRiX3njQ9* zi*7Wy{`xV>bWkZ@bqol*Gi>6tmV0|d1*SeRW@S;9&#HFdYv~T!D!YH!?XaZ3MxU5y zd|0J0>_I{^hz>v~j4V`_zABpn*;EKL%&-@ig&xs{$FAQ~H7l10s?jj^_;%M=S8-XDtT z9{DQVl~RH@j@QU7)bA&loc-J?$~0-H9bx{d~A}Tkkvz9URqY8nW(q* z#bNb(4>*|Q|LieCa!>YyKnuo2=OHXQfo0O;xnAE4z1>m7T&`(pgsrgoJ`~NISl1ia zUrA0f1llPlM1qw9leNgEF2#$0>PRg za-T1V@XQH5aU4IPG|3}~zqMG^o%vBr{+z@4?R=FeSAffFnoEjWJ90@X&=?+R0PL0ong>F6BnzDs^bB#cO07Gv40_MB~3&M1^7zigdk zPd&+fbHghPTM4M*=*gh=8tzS6FPq3#1?jx3S1P+lAy~ow0zwKLi-oe!F5KrGIZq0bOg1azrO;0yrtWd!Ye6N9=*~l2y7du%F()(_#(GGxD$eLa| z(opCsIH4yGMby!^Fh@!KsFNyEUs7 zib@yTJSWAn2hQ83X`qUZ2nRqs76s!pkGVO#qQ{J zZGljMBa)Xbv(YY#ln+>I_;d6Ka2fO1dCvzx5`>jpDo@yCa&x;*4+gM4d{Z;=ktgyy zUeon&H_P(!t?QB&H=@G5Ca@eRwj81@nmn4p*Ww`RyU^<2DCN>vQtXObS*liYzp-q* zMmVKE+@h4Y;Kcb;!KG)n7Unh(c2;^AyECj=Uf`swsEtH@@nCwEF|2V{@Lj#hEw7U^ z6z7I7s9ak(kK^|&1=PpR+3x7Rdxv$|VI20?1xwq{#A1jgyKn5LI=peMDQ`NzNw1r< zrNGVjDuBH<&{2)6SDWOX51$DMBY1+E;Q)v59g;9L3tNOk;YGEj0 zfGfYWg?D8<=V^bP$RRRIl7?YrbUNP7S=Qphb96N>$+^1LdLWG+$jYhL+SS@{#_vPG zGqPCy=XntQ?Y6Mr4*qacK~J$PKmZ}cU_VJjQKC0`BqQ@RFq>M=2PQ8 zlLeNfxY}m>3yVaC#n8TacBHCPuUTxOfALtuA8)Chsr40K9lIr07gmUIm=O2$|yq(0;(8b?UFaQD*%nqUBOa!D>#!PEubv)Dg ztdAVX6MFkO$K=T(eJL4U&39#=zSybj?k;10b+3f&e3R#Cu*%lJkhD%22PBVf$_~ezUel!XNb?WpI_j7&UQ2;I&DeMPv^%PEhD3`za=YD)l za~6xL)ZIAg9_#M{VqRCvYt~tdjW;x~l!Q8^#UMd&7Y}bQSB~<{hOlEFkS_JVrC>); zSERVyFhB5IaTaG2vwW)HoAXDu?^HvBC%=0*d+sMiB8{J!ts^aq)rQstUr?jjZv!W1 z{eMWM5Pt|ThS=If%Y}huPPZ*N*U0mq9$SQQDeloZz~@kjGC24#;#0lLLN8`(33cSFhk1Im=bhs8oT&w?81x^!AY?WeF%mDeX z%>4><`%RF%c5ud7+dhuKv{(s0SJ*E~g}51;CUmF6^-|$fpqKmEsxhH#%VvXs5n7-y zWdMdUH(4?;Pj93+Av6C3a@5Nd4^0XVXU=}ow_j-IF-f_0j1JSMncB7=A?z>%F^w?# z$ncp<=E-NuXI!N?he7NwM+5gDGM_O(xUZKgIx?<-41ANnN=*-n{_Q-mxj=m9H< zGO`rf?5FR$+W?QqaTt?#cBC^ss?pR2%4>@TBr^bg(&>{UDf%+JoPqhAi-4Mthf1&n zc!iSj9KgB`UfoM(BZ8+cPX5^@4%N;NHF}!CrAAbq*UMPtr(CNQ&Y)EEKkK@h(CKYQ zTYytJ%eO2{_SjUl3burY?_yJreWr`^Rn~CxG^H-pRrH!PA63510!g%_)hbtv>j9!9 zzFIiNgpQTCMyt}w720DGSOBndA9g+Zo`$jvZ<`Y*D14pRed_yf4Pf{Iy+*zSQjt8b z&GrU^lz&}4L9bg!DomzvjqVks6Z1tz5Bpo15M2)0BUu1eXC1? zVv;AZbUd2Q?g3_}g5?O#x=-z4QZpI?IhwI-YevP5z2%Kv`7W~*@n#SU?HSNHw8~z9 z7jjI<08j`qrW7Fg3)=~&0ei;4+G?*~Grp-b`Iy$(6q1KqT}>S+*?6{^({)g&;W?ji zvW!5kw9|Ey_&K3wybmkLzc@Gd23~kND>&@vmEY%^csK=Ch|`qE*)-q;&#>~lPE_&% z@MZ{#Ob(65h|{T(l{H=lL-L~{CJXMSXeo4`lPQ`m|1PD8nw+Ivz~O+=+Az(5+XQbR zE^aWLuSkaT`3QTv_9#ei2Ccg6LbNW+a+k9@`w88^zP-@Ese3&2^hefISHa%QYK^CM z1isb88XvdSD=cG4LOkM8Tm446w+d}-cjv4Wh)&p?Gvh*FqkY=jYhc5R0*V(;RKY=6 zUkdz)6lG!twq@#e9$tY=*UtbMdVoRQR-vp|kT008hP`SGYr}4B(124Y^nF^oa!h2y zS^}f1eac#e(nVeX8ZGpPQ{aI|w|Sa|^odwLE#APH5B6eD!ODGLcDUmoVI5T|b^F@w zOK(1IC0BV(#$UhvG4sxp(OX;V6lsnDC4&L^%we~2(@RZusytU8i>1D=vokPAoo^6i?iULc2dNyoB%ZoX$IyYw4MdMJ8Pa& z_tGQB^Y5QFid9JntZj;u*>fDP!xGQfpJVt}J!XnbWn)(D<$3JQH3GdLbIpJT>F$P` zvX5Ov+T;j{J#Kh`SioIg=p$DoH`WZT&q>4u(~1WLZyYYf=LdYsE0hbB)2X z`WC@rr_lCA@bA{12cwB8^ni__nGLr-voP%jH+$x>z*4-fYqHeh>nRZz7u6Y;w8m+j z%w7Piu1pzwNxni7L8ERr+7~JJ;d&=g{i_O^e+2AqnyJAACQ*6Nu`$RG;5u#b|P;DK3DM z*>`IO(aZS971khfc{k%*GtEdFGKK)2s068XjpL{0eoGw7ub9ebeiKSvT78OX(gY8u z^vFVK4)sfO$ypZ-tydaDjk4~3p-|uRb1t5S!6EkNd>+CeOjn~Ft~k&4qC-wwjh_T# z3M)F4aYpmm#$%9Nw7(JMyCB<2GK1r{K#*``Cne6yo*ppPX5)-qRV#UnS(ZXpFlE3a zsIu=t<>RHPkZmH>{vN>F{D?O*DNNvQOftK+!g5a!#~YN+qP}nwr!u-PA1=cH8XF`ubEx@ zZ&!8gs@`4QYdzO(r+Tvx>m4Lp1EfeOsQuS{25zt-^vzo(XNT=Nhl?|6MdSC+KT_1O z`in4)V`S$z(Hg+1a4S5v+Vpb6|C$Z205lq=c^T{MB!r4&v#)&`Q7a@^2wMAlG@8OZ zGnl$L&>cm&)%_?Ow2xJf6AXZbThBY}vfRzz<~QQ2dMn<_Nl?@U=D`x3c3E|UZlc5% z^)Maau$@(Dd8V?nt zjawd?m=|uZ)7kCP9?mGUx&9Q^>f@>GK%0$d=%{naD=S90up6j=rSsg-H~yYD;ghXU zg6$a8ICgM#3vCt-uk9AP+lpsrYdr#;r=<1iw?U#boJHW_DQEdSF14oFIe2ouStoPh zxI`zK{*acGaC{6f2nJHJJLTp#mmht^1kW*8ZS+-^)-?~3rTWzQv<09P>Xo2sb6Jb* z{XO_FozgINIPcEUxeEdLqC4&CxP~Ywgzl0Lhwrq&9@g%yP^pan8{M|ZBv~Cm5W^)s zlp!?C7bnFKhjqXxe0=3W&fCjwT!Da_Q^6c>q=;40GH*}m!R0B5S-6PjQlbqXrr#{L z(}L%f{Guyb)aQK_&F+ogV!I34ekozjF;-<33Ha~z6%F3$6{Fd>#kN+BAsBrZKmt&@ z_7F`&T>-nx3VoKtb#H{RmzUF)b$m^ZIZ?59mc#;eg&Ufgt0vRC?b$-*)Ei z+x!-NAvb_gA7yU*11cRd?+RmtL8j~iJ>@IwNn_bDXXs;VeR$Oar#Ts~%m|`=^Q6@m zXdQbRacdX&4vfAQS2PUb`v=4aFJ=iD#n1DS1MgVYM6t28E!`Mz&?%Rk|2>f$OjS9o=#Nc(gaO&d;z>kvYvp}g`cEZBVC$U2xx zua$h4#0x>kKgBiH&7}T$K#8H~(mfsFf~x`WBp~Dwv9DdkIdll?4Tlh2^s-or!QXSS=IJ{YtOJ6lO(Mif87NOyfmQkdrfHxi}|i_x%>&f7`xuy z>X+xk=Vd?}quZ{bFCF0J0xi_$K2Lk)nLjhDSBA*v8A-Yx>pe3 zFhj5YT30T0yztilSO9n;dhI;`Wn)aSX;r1LswXLozZAq;V*{tHI2x^4gM2^Z$x^54 z+yV+|LJ)LxB*%Au<*zZQ8VU*PM_3!JXy-4N{O%yD8A<3KvZmCY1$?t=P&O)+*IJz> zT@8>#WWzJ&wu*?W@hTE@+;gb{iuxf$3rnv=3o04!(|Uo$y@n5p?;JRhW!(<7x-xHj zJC0sxN}YxU-OTbv6ZyQfp|Gr{g-VuYmGIr1(PhcYR%qj_bRQJr`1X%Kn@Lh)BH|wo zE3SQF>@}K;^n25ExjYvpUqcb4RCaSb6{HGXmrdi$4cqOBPf_BggyL16#w>!>FNY zB-2FzYlyz-uE|60#OwAov9WOW-B;K*aos)HAdwpfopl+1z)@ zEO7Cz;SZ)My+MBM&`{qimuQEGKIr(urJy18sIa8EIEn~m_a@P7Z3fj2e=XZ%Dp`Pe z$9k8(6_d57Z;MT|KJ2E{7)#W&;zigIU<<0-t6R`Kaj^LzLFdIl$8@^NXPL_zD*$)# zG}x^Q=Sp6;!IgDY$p^QaSN_$zO==d)lt5>n?lA!z&#MRlixQEg{OPg@!L&Tq(+)Io9|Jpz}&$-vt1yAP1u@XW4uW6DP;Yk2`fA9}Erm#ylFX2rOid|@oD z=N}Ln1@1u&GF{uvpgWC0oD?1)O(OSUk`Ae1ex}N5(BH4}@k;{XS$wViQyglh3{oCX zRq&{v9gfx4{RI_Y-%q0Eo6eyx$C64sBuBa%mYA<6PI+QNCxDCxB>>QV)^TvI&lM6 zZjkIR(wA#SmQ|196TcT~s3eR{urT zvIm=N@`q0D9>z<0(7tWY z7ArZV8ChUh-!)k$m2SU`yrDtfl4Y(Xic|d6Fzj{)f_p3xw(ctr2AJzyyh7)+d%$2DVL04&6p5(EuK*zt3H zOXO%I6_U@zRt`^tn~cE9zQuOCt)~53t&0Z-Hx$K}kVG)Y+8J;uV9W{f^Zv z+rITer)scLq);O;v)@4<2R>s)60h&Wf}jUOYN_AW2>{wwDPx`kA>4iKKEtWBWIQ&G zV^TpFb(Zhq@=rn4-tmef>EvkwLOakbw)>ZVLIuNl40+)4dfPSwsI+3J@LizB%B@3~ zVNV%f)esE+NX!vaFB+*MpFbEim@x6&4Y@kV#9nmSe76l^OpBQVPlVUUO)>X`U>qr_ zfmghd?IG-tZccXC{;rp`S&57zVOt8JMB}y3{Yl5;$qrM8X@yrWnyj$na-T^(ZFw!p z$Eq6h1f!@8sbM#L=m2e7M~_ERncHAw6?=P85}8>Cim?5O)@4!43Z zgvKF2Iqr8J4r%)Hx~v&%8c%v^6$=)++il7Gc|0~8;p%04nJ1{)(XZz2B|f?N(PHD@ zIV&@-uPPy)plO>Fz3UYZlm00zcixjPM89Hc7NOh_034G@IR{j;C=!I8aWY>;+3TmT zF6lqp5khenKplN>m7DE=cocuB=F&WZiiN`ZPU@HdF*wsbZ_Q41mj8DCd{Yn7Sf~sC zqiUp#y~IF9Pbl51CBgRNe1|prlAzC*nd{uj2D~kctR}0$wUR=2oaL6}&dzyyk|W>T zr-(CV-^(ZwO}x7LT#Uq=^N)&rYdarz0v7?qNBM?zF2_7_ao&yx9!0XDUt!$R^AmJ9 zKBoOkc0q5Qj07~~^tZGn-9VxLGtyF%H+h~qAn6UC9G7NopAG=EVPg(l@6g65(O zD||_T11y)5gHMU?js!+|@;^>g+#G)ATY^goXIZ}LV388vfeRL=_}FSA9P6BT-=E)^ z4&fJ*HwwO@@!XsW810QgJ-+Hn-+vJZ-@?*lCQAoZVt?3$V*-95peMvZF)tbDU_nqr zyE-e8vpgHGW(hnGxeD6E8fGP^S+SNq=j*PLp}b5Hdn_{b)j>K|Pjk=lE~Kr>fv5nz ziB>*yCf;m^o5Dhj{@rdOF_#TzmI7E7>_GJ7F2nHf5{!x!yW%<{BC4w_?FTl=urty2 zv$Vki*A|Q-a1k$fwDE2lbwk?+$CZoiB<esc~gI@PySa}AbXgsZzSiBDYKzS2JzbrGZ;@|MH3W88JDK3E~n68|- z(Lip2`Z$P_0B6G<1qZo=_PIQd;M(SGTN6xNv3>6qX7_)c1SjnDM*=FIRo5#*|bCb0m!fks-6h8CbA7TVS&P!n8 zw{s&CFu5t8wu9V46##_(uA=b!UhR8x+ZZ;LN|*%n># zX$^zMXIYIr98Nz_WdF$h(37-_(8dJF2{?5^CUVQ3pa+998-D7c(Qwmif%n3h7J`{| zijB>&!P7^RA;5GRy-}58vNa8N0KRd#RLJE?d&v+ z{`|4a+Y8jxch{nq**5%JT}^>bK9dy&?O+eWO13U|F$Js5@0A%AYSv}o6fGF$8z3}h zNF>yQWXQZv{2t*sy8n*xW++&sZYkT20TM}-4jJC-4NFg9X>3IR5P0-**Ifn?{02 zKu$g{N-zY-1&$PBO~fkDl`d=-4f6Vj#MX}CQ%efp6blPpR%aonV9fpHj z2^#?+2yb{&Ah zBqI2-7b4E@^YjGsdKNQ-PvmF8C+=6 zEq~8=2=cbB6AYree05+k8$32`<4_(7qMJ$=ZzBoQUq_#uIP&HXB0|3Tw!uYcXiI+QB=iUmJWYwYiTQdM*C-^FhPE;Ks?Uiq5?j;w~VtBKto@qOGyDCL3A zAw`0_P~RDURgvJe{E1BgUd6=4kfL)(TzA*^_tx_V-C!R;DCvO)dK~s)2n`U1E;wyw zIMpCp2q(wT3-;odh*DmLTZoj(#%Mc-HIVsM&nY2s3O`meL-IN9{wC*T3H14g>F!HZ zXYnwr$iA~5NvI`pDHAfvlG19Sxj_%4u~x(ovLocHGf&$VNlDj~oJGcid1Yxy$Vt^G zAIU~7%L}p?TZcr32Qy2y5rd0q_*1oNYS(zT-QUWy#VS7;urSg_M&bkAS~oa!`x#DR zC4TzDS94}i#>PVb2o7mLrE{m;q8`|yHvS;DyM%`Y3mlXn)IK!zRXWY}3KMs_%AROi zf0)6qXZ?iGvj9@h`A3#zEe}$T?f6LR(RofLQS$0j+<$RixdPLvQQoBzql*5WsmtbQ z0xpPqHEFxoVcYS|^jl6&BjKxnM|M(DO~%b9#r9MQ<6s^eU{kzh)hg~uIO65BjZZ;l z?C7Nf`o%H6ZYSrT)63&PNe9ALcjv!RhqEid8N z8M;UjInk7e;mb(kKwMjytXc&21@1|OM}`)6XPfR<|R&o29s)G?dc!Lb?Qp zY?ks3!uhe4Z=s|)B?Z-h4?f_an9C(dfnhH^@^O2y^!#am*#gLdysaqyrGzkjv<6uK z*BX=>t^yq*#Jh8{^315yP(WZ=x$qBvDb|RC0jxfti3&2?+q+;I>qu~Hp4zw+o0CP>6h@Hti0K|=cej1e84}_0Igb1WagOE&+s2ueTau2xNmsKJ;E&W`0VOTF6x|J zAeU0uV_s2nDfdXGFm8JUt&kX7gS%sYw%7JE4lg6X_k^`mSvG3oc z5CirkLVdzn8gXp~c5g#T{OJo4n`O0?P|Cv6;Tu%(H%YqQ?$K3oxJx4@jyhA|mX`H2 z(EHe+YibkfOfExOVEMc;DnIOEMZs+He(tBAsc7VcX6#MD4nl|;zw{6rm`K+SyY?q- z+CLxP!3D7;os$5m<=gR&UZ%Q}KjP@?50&#~m*o0J8c$trI-}CN#WFP)@NNdtlV!v^ zx!jAq1T!+eWtVqTn5ueP6lDSTRc9MmIy5%scN9sTtHJ z?skgpqS|~n*(vT_`@JrY-bRzU{o<^TRfNno23+cEwaDe0PT{K9TTq}}lBrMd&J-U{ z1srY1k>bmq9~I6KdHXCFwsCJw;bYHjuAGteo>7l~MB*(>k?gSBrumT6UbvO@V_cG# znvFo$Tc9_C))6@{btQq-XW|^s;1TD841T6qt}@O?)%GGgUZDuGurC~zvBfTuL+ED< zpgViCau}L^P1%D#f=L0=iBnP@8&WDbYm2bT1It&GVEBYH&-3Cpgz2shf23=fGd;1e zbXTm7$K)-{x*IHrDQy3Q-*tU;L-fpzEQXLH>JlbARWt9f7O_`y!l;(++-`vMmVRLg z1XCD~?R!dwuu*lz5KQ2UGn&aM_{jJqfU~$smzkfGP($!L&5JYMn%-|GPyc&ryeK`0 zm*0}3TL}OOV+`^Y_eDTp_11Tl02Dg<$D()yWDGR|{RjzY(VUf7i_|ssbOXWx8g*I>Q4N@MlTbD`>tE*UR%aW;4S4^W&aEFK7B^q2hLJG z1OX&Dg0zBdet~kC!|u&Nf*#rw%pI5K5i*7{PcxXL0(JT3s=k@#LGyH|6_ zcW&@RsR`Kp{8i+D=E$9^T?`tyiiBNHaxcLy;~aYN?BtTapx3Zc#}P3AASqU~O7@Z7 zw=bOE4;7v^cJ0ucD`z4cRDy^jYlz8|9H)|KteL1;#VgBv9_AaJqOdO?I$Nunu(0=5 z=T~&D;R1pAPo6Gfkew$=QY6xUtBY`b@>qjjVmcM{?c(1=XLD(*J?G?gTXtx~J(RPR zjWKr6$~y}x_L?1zjR0RXXY`u)zbh+2(nV{D9eWdXPq}JwC~5IGRV&c)v!_*`B7Cf^ z`LuLr*Y2-8m(M>-vz5C7gN<44!M&2>>VnLRK7 zI_B0rdA&Fsj#hUn`hNc=kE}bkpX@iw?LV4VcbVWc3-Nwtc-}weEkz~0Paj5c{3EjQ zbUG_2JNKj*GT}}W8KhTe&AUhFEU_b3D6C}>75Ub)svKLHfK45UTYg`3e0CIkSuY}Q zf7IHTyD<+xL+mz=Z?8$I8D`!jrPF37*e!zi_sL~(6@20f# za=tM>t*+)zDALh>4gcm}mtLQDcoCd*3=hn2KT3E|E|e30jo^lbKp0%v?x^ET^#i7= z{guQswMM7GjrbKM^~5$sON~aJ^?$?b{eNXoK7ySIU06V(GFUWeryn!OLo>c$bwA#HCVxWJx7wqVZRv1>GIV)5yW3GXeg-35pq29H(gy&O zUWpYjNcJOUfP(FFssy|3XqG7aGSF{R^#N0bMM;P!DytoZ_w`w7r(;xHJWI`6^1K(E z!L7U)A4?Ow(?)hXy=yYB^1PX7>RNi(;3l-Y$o9HwnboP4!@|a7oa>%Cou`xn|G4Nm$NCD0NxlKs%dunAzv0PAfv-24AJIQW-)N3vbKDFOe3|0q}F zN&$FY5JHGArYJ18G#8m92OE}#0HGZ17Z!nu9B(66q(<+Dj7U!iMM;9pfXsqVC^3Hg zv-9$G?RwpQ+UfaxIm7hvG0ok`B>O>r-u;ZT)ud{HQrpc=hSD7E-y8Vzy_5_bo&2EnJPq>V39?V60 z995>4rjGiq`mm0#)yfkDiq|md$6AGzF#( z-iN}NuWSuAR9!Bge9!v4bjJ=G1GA&e#u@*W@!cC8-}LnFgvAfTlZ%(Nk6Z&vzQhVX z-}H}>V;?NvpSPvPH^!CQm-V;u{rdX;SPbo5hYV3&n1_H&6UEFDyxs$oC9j%)Ib5oI zE;T2PjE;ouIf+_)_AMrlR@-iW91o=sj9;5{Ix) zZLROvSlL=%#oz{^`b$i*w zo{+lmdK1$(Q$`K(q==WIOBnfFVPSSnqN8Md#H+rhzBt)_3@IWxT6*qH9n1?0tcwb- zrpLxgYgBJ+SR6an@p@opd_JFFtgh7V10i{Hb3gU8(qr#k>{S~cB40LrG_Qydzkp=G zhpFDcgM&n_93asVK(hMD}=Jt(LXZ?;A0a5)aKb|SoT^_qO+j$1xsH~2-)b>m(aJ(a3{-trz}WjD{(@JW!ovvdGv zmgL5l)OP34P4%tvD~*DfhA_}EG4`e&VeC^#O;Xr}ihXq{H+Kwx18Lyb*MBxwT`XfI z%)Geg7y9<5(wW{(UCu>EL(tJR)YQ=TqF|z;9$OGS?8W8pe2BZt*q49Q>YyW_!(fR< zLPbO=z80S!Vr9a13k&XZ5eUO+X@A9cN|_&Bz;ke6QOvl{FtCt!_P{Z*z@NOs19(u- zpV>3^Sz?2@{`|bQv!FG0|D!Su00qSI*15}2$;z5sI?*N zPw@72d$Fd~nhVeu;bj5vpYJ!1KATtxK|!^=;_z(5nXQ4bHG|aX?(FFN{NVih=Js&V z8JpC6$eFE`Pd>13F8kk`d)ss~Ju@5Awy~wYS%+n6{{FZ2Nr}nINy|!Sl=M_pw3Kx8 z&ma#13fc`C3c@3v&Dzg?#!sx4V~= z`v*EQRth?@86Fl+`C~1zTF4h4_vW6}Rn>HqwUo=c`Xw79bJNHbTm7!FS&LgnjGUf&g; z+HZ}w_O79iyRXfIflut0i_2#t4wmtBbUduD&-25v+t3%G8?fDmU6{w0?rZPm#qH&{ zhQjXXr-G-q#!^|0j7&^CHf|5KSuZ(NO+{^Gb^G2=o%L<5;;7A5o0P3KpXyhO`Q(9s z&&fwgG`io$osp^W0^>rtIr9D6k|Tv_VxpMoAEbjr1H-U+`FO-B#DwWgE%Js|p5&9pfe*qO*(h z>!taXg(W#vMP+#v1*J6tIx?!jnG+=Jt$bW_f8 zYBi((J84rDb|Lc^3RMX|l%L_Nzovo7;;-TkUrlr9}r zzNze{^LBgYih$+pAfg-5`scYbs;vv&gYCJk_R${BzU{f#UT$+qaen%_c2NS3L^putjuf!`Xv#Mj4r#w41)T*DI7TYKxa^j39zs`{*bx>+-u`>BEM?&M*x z_qMpp$BxtPCDOWL>g(fxcjXCmaxKP{#eu%gyLRI2`(^UEl?YC!bNdFM6)6-V5$r%N z77ZnXxsf!PZ*bX6x=7L`NXAshX&I1kWZ(z-2V?k=Dnjk9qj9!Yx_c(YVo&Dyprfhe zA&M_ZMwdRfI<3d7c_ODYYQihLzOHS8iDrO4;hkkMSgh!m`FrNh zGdU0G+KM_Id=YV< z7}8;=<@PwnY;LV}PPImvXA5WVtsiKrW>tmq-c_iz2a>iEn<|u`K2Hi;8?2B{X)g~h zWX<;qdq&%Bb-2*xTeo}3+2aeUjr-FJS!6WyZYCxK&Uv~v<#Im3mP z*}aSy|Ju}!hm_AzdACE{dl)AcI# z%Rg2c+TM}liJvtc|D*<*;OB9y9TZ=p=LcBa{MLb%#2792i!yh|Vp$SnSd;$Uyg8@0 zoXk`wyb0B6uUnpnt5tdeMO$ZK55noq@$e`Lw(x?Gazpmet0;juvsql^T?qlL* ziHuK;3zk(iB<8Q}Hzo&9ojuoasU6elaG++UC6j7l>$lx(>vcNTCRsuUI*?3;jxP{FT9~`#IE~iMjD9mvXYMsO7=LUYvNGhFrI%_y7erHI>i-T^*A}u49yM z%!A^HONABKJBruioK(z|Ck7d&!~?PnyddXyJO*Ot12QBFY!AiCJ|K8nZXUf zJ(x-glI6PR?2@uY_f_lT+@z9l^~Sm24n>QW-%&SJ0?^o`%b*-71;j_Ve9~e%Lb#2Y zfrsS?(a^FvN>b3S0+?m84a^YVCLU6ixRoVWS%G09N4iJah6tE4ZKy_Ab|wCC;`-;~ zVWVKTMx!YDCGQ%mvK>i&tG1G*qiq&c149*t#2s@nvq1Igbpv?I^()M){my?!OCnp$aS7o`vC6X(+yOO07ib7hQlHAq=n2rF#oEXREiv2OM z=TIng6&wv;k@$ao>_i3(=OB;(o0ERS)*(H;Gv+@Gc7D9|VHZSe3>1MFP@(;0J#o;WSz}c23P-9t9^>EiQ7{ z*Z<-Uin;f+iiM_m{%5(HZV$(uzDbKagBYQa>BHE4;P+&pNXSYH4aaEa@FweRikqPC z4dRwH7T>je2hTX0OV#1KguOvvwWaZ*bS1Q>xxG7yGbR10Ieg2QO#dT3Z1`6NtYtVZ8PRU3*g#tAZea#nuqUUBgCkw79Na# z(WZaqA%a&2?(aR-Zi-HIbEQGau_&iTR(amENkp+khMQ0J<}lAEsWBuhqhGyw4R)Yy5P` z?(U3=6p=5@>lCk2X*{oT)To7*k;LfO3_G!7Z1Up3F4A=D1SDOA(T0Nd>D>sz_r>SG ziRidp=x|8yirh|IBx@|=nqtVS!{uA|=rJ_599zayWu}vH(&2`kW<%y@LKn1_sMoA*v2tTXZe08d;vdhGzIL zivqIZBKil4B?{mZHXzXv<4_w~M0}^j*W4u6VER(>PHv5F+JVCp+{Ba|%7<|80V0wX zfG{KJ$-!)uatP(}CFJL#_(Dl`zKH~x;n@l!a8ClZ8py%p78XL-bqwvRRXHjQ=M0%( zZ4YGyw0-qe%u!%VNuO)wac0Dc9QdNt+eA3>N^ER{yaEae0x03yRoQRR2u(z$&FFWd zrSCx+w+X@R^=V4sQxK%;#9LKkTz_)WJ~9kp13wrL5KlhTXicwUc-52J{77bm(+k}a zf=!I{Epy1>Efl9x3y!D~K;Tfz09f6L>65;L92$5F?0C%%!)1s&b4&?9ngoWwRf~KD z3X?hi)nMuqMW-hex&=XkU6t84AyF}Wa3lcCq^6l*mI$ICjshIC$G7$fQF1ksUTl}= zh3yE107?ulMMRLrRUo2x64xpSbl)ej=$WC4mx7b%g77!-1HA*a4hle=2AS0#(uwK= z0k86b05O0jPK2&Md4mIwxQDyxU4tuF6kxf=2M)-~TTn-W*2{8p#vU6zYj-x~#-&1`?Il7ey~yg%M`N*{&r% zlA@EjrOmQzpz2jAvi2_s_wN~qfCCil)9A{?ICm^rb<)3|mpU_s8N+J(#sxkn^{c2| z2UXR%=`q>q_4-^GHQeOtK77SmUO4;fA4JdHlKvF~SR=hpEKdslgk7U>2ldw%oBAc*noT|y+khrtZ;RG2FQ~rJQ zH4IiG7;u?kPmx8LE+1>Ik+J&ERT>RXc&rf6!5M^F6}VhO27jX5JO-;+ zBr7!lu!O;o+;$h~WVmQU0jKjkIMlKZw&rJBSf;HyXax3vz~YX;@rFA@2?RygGy=Hn zDM7~Z0cG?d`>eW4vH(&zt3ZJ>CQDWo3tFCvwp`-`@3EkHplF~zL#e(4-1jM#Iyyyq zU_ZY!!8h%HGNZ z(|)oLc`*2x>t`}BW5@9L;=(2!xh@6-3*itPoNZj(CNS{*hy*j}aaX7B5p|C`xs0K+ z9Z^Npe6FwbQsh{EDUN>ZsAt^JG!xXRM#o%-a@jEC@xV!r_%^10M#ch4(**6OquWEV z4%$0o<)mW}5Pi_wK%lP$g)jBcXg7y*M#I7yrkVSB$!_bU!ImOG`y1nRZ|~O8?XYo* zB5m6nH(F_RUQ?cj1zng(z1H+LplYV8i)iLA>glHk6Xb_ zMMq5?H0GRuG_e~@XV;35AM`^?$-%+tivfstA?cTLFc?iW4;V%8dx%2#v1*P5)4r;7 zH``vD%eh?;MZHzR>q%ly1%gYP^0%j<@wpb&8hi{gqU%|5vjf9VDjFEC zw``zVG8oHC0qGsU@k|Oj>ErY2P~5<$@73`Bj+lZ2gqeYT68pV}+0Me4rOsHOMQ{2h zz%(56E6DD0{y8-x{fR>foza+8HSyme?=D*J(PA$=#wl&~z$Td$7&YKetxSAv%WhWy z{K{t(g3o6QuUTA(^RJj$ZK?Wt$d-@2$NR&-EfJ;}9ygakOL`|_1V#CdAJv}i3tw0> zlyp7Tn4T{yVc~mRzu6p+jKBqfzY9h{&ns9mVubJ+he+z^BqEH<-(AqT6cn2Y|3n_iPKZhoHQr^?>miQmWDuT{R$>WTDY zEmL{7p*evKNOs5SXnSMBZINaWOjIW{M=zALe*XNe1YADpeES4k@jLI4*x!QfEp&$Z zaZzNc-WeZjOV!Am|CR%{n?i6+Pm)g-1#KbG;k<3mm~MFHXUcu@2srR~C#Is=4Yi2Q zF|HarT|<}wvFeic#p>Ef5O(}*CiXHFGjI#GbDQhnSr`s;2hU0Yv%*x@OU%w=0%}W- z_uK^5c?wP%dy0;VYLd%{klgNTD5U&IThhhq#?u{aZ}mUn42VE0A~j*b9nNH3>goLGmxpIoN7l z(?s-cFZRmvmXRv9P-Sr51$_XD6**!k>k$8aZ^`pDJOAm$J_+C$FW~_-#&7ugU%MW? z3vKxgmS&Nz(e>B39%IG~z$C+-Szmu#=$r*cX;qYe9s+%i`J`X+WQOT3hcOoA!=d{g z8L0#axa*)_BZnZ#>fE$IUP-d6KifxP&a-W9_~?(F!6 zQ*a0)o(94oVPw}Dg1c^+Q#`WHHQdXSH$hSWb{tt3^P~c7sO->=f_hrS95<5_-E`u> zE|NxR06gxIFx#Pr66pm7+6s({*|-h!G7^g8<78l%AoEk0zPas{z>9ROScaGbxXnY& zOmm$8^fBPjdw|=UV&ex$QGEhA-T-fL?*vMes81MV{Qbx$<;;;jnb-SkslGrQ42YiJ z09BT?k6jgSMgZW)LH=%w+FnTjrXh2qHz)~Hw&2h_!MyJ5 z@gmQL035(U+=wKzWU|jVwP!}OtP}*?>19a5=rN!RL_+#ONa+h_y}*2|YMni{-*vTG z5JCTV_{3vIiEgj!(rJ$@36GLGBQ`vm%YGkV23I4(+5aL8HRkUR&DiX4MdK}ofF#nM z+@r%O^aoMh^D`43mH#!E1P7FDxWz&>ON6aUK3 zhI+5fiCr(>ylu+5>1+UmhtnJB+~Uhz9Dl1m(K?6+Q#j$T8kdtS(L<5hb8?b7!s-MR z2u)nTL9xoGz!nP&ETTt@Jr_*rm>qTtuw^e>8vgoHpyzlox?s}#YHptNiVwg$RHg0S z9K@?z{M7&;Lq)HZGm-Fh+|tm$jC426o$E_=x~`m|$Hk=NumI|^2EKjbs~ugU0YZGV z-Mv+FSBcy?KDc+hwdKv#HT#Je62EJQfPM1DKFn|Gh^vy z*hHF9pt>@hDeA>uc^~e!RoOzENA3?WG+F@s#MZMcGB{KcJK9@>lg0ON%kSK=pS*V7 z0`<%?_41dGioYWsKQ)$c* zu=^b+p|4fhtr?E@Ab5K_z0Zg=MG$DO6k>1kH3M%p)6jL3=jNVzblM)=EOUm56n*;4 zBnAC+8t%6A+BdaVH)Q8~4NY|3thIfPa?ibcw#8A6WC7bmWy$Asf0P{M6t|9(Mq%Kr zCU@>Yue4`NUioWzGTaRnnL3)s>5E2EX-J8U9V)4$9TgBv64-UgTK2sP>fwGmP&cBQaWtN=dI1z z|N2+L5JZY}>$ONIlmh8hH&8)6pf5+k+#A;$XxSD=gmrM3-`{Us+Q@@%73ax(U7;4I7||llTM;y+^4Ba=9PbtHzcGsx1?ojRPDP-S(6iGsA zOv%lW)q=w2%F4X*C>IW#`{i%LCuO8vD|7~``aD15GcM7dZIdenrer$KO|#cqs3dnx_tA|*naJ?iru(4HeiDS z;V!OH2e?eZ1gJJDYZSh77@AML1=(ihoZESND)3RqdW3~9j@0m8JY#IbZH7E`1$Vr` zeS)eV>X`-Lsd8H zt>3+B9C`;MuxrlbpXg}=KsJN>8N-``7(!7IKGS9JGU-S-_f7Pg`#ogq(=$9kQ0ueh zYp;+9e9%EgOXTTc6Y4fy%EzSU;|$MvxFOWZg`p)Fro9tr(yJ4zIwh+OSvi0(V%Mwv zY;(j?j1)P%Zb>%XBnjh^ep<1uxu+jBv>DqlWOPEoy?dhy0D?)-pQ8u2hXsGTUI%^2 zmcTen2tjJ3#0no&t;>&k_kHiI6eCc}qZkQ)&JJ2#icpx!h8{m78Sxz>McAM(MyIir zTZ6f7Z{uLN(wC)?M0VDK>bCbz+G}U@b@;ySSub|708%kosNy{4x`9ZFpy%;zvGrVw$jGv>IqWn#xV z_wt4epv;klMb8|}(SI#x^Eg%~5HP>q1qbMm*}ynjM=N{w)tT@GooVGj^z$*UV~>6g-aQV9-O(SQQFEkGU^{n~i{FT4$6xo4(AtmMxHTsziJagj zvg-Y;8~482zh&R!ix0(@mkJ~i1I(VZW&4=TcAC=}B{a;N^l<3HM1{z1Mq3TDvskeY zrV7-#U7S}N#;@I(5vxFh3hvrte^x*lAZ7kqyoI{1WjDS>8u9xMr7XV2TmHK}saBaG z+Fs9)@_m65;4#1qS$}e_eU-mlg9FEo%_=|33E7E7>9+iESP^|9VyU&@zI<8lv-HoE zCaY94(X(uW%m$1;ouhZrr5oRT>C7h&$#}UM5W*1{BjV8xaqB$j?b4;8MN=u{J=-zK z&J@OQyn1^oeHRpb7SqRz=pI?GsGNik1Zi?(ovs$067roipO37#=H->Q+tGxiRq*dM z3}#@ZmLxqAW$C#1C47r(H1yV(lT21&0Gjp>>5+`)sA4zLQ^f(fFf(GvmAMx zBUw+-wspxZxA27F69rs$em@hGRyhGf4`{kMtXnv;7?IjA1|=J|6ldW38o6xaxL<=n z`;d$kB-&ZeDYimNA@sz7JcS0t*+Aq?Xep?7)E{2{zBn};dU9bhfJ)iid@n~M#20Dya^YBI@xBm9dTGieu7q*RNH3GbGXsf!M>+64T_Ks1OJY2qJvJ#cHZQHgg?MheLwr$(aJZamuZQFKE z{k!Mwes9m6weHM`bt2A}y#gQN#CqblpAED-CSDS0a^f?+Oi__Boav!J{p?8HbX8zZ z<}b`Qo%i%z1U)w&6Ie{y3VH|LTzvpduVw~yq{_e07s!-Xo@44Sg;pW~G`4y6BhUVx z18WfYX$}BMqXHpev6|Ki#=>_n1BzyvoTrC)anFE*`YX4EX--QL6K>0F7>Whe?&IjH zsmx&B6@GE@&spd_afU@TehcxrqlFEKA-P<^80wT?`-a;{a&G4K#Io4VwrCU{T^hWUmQv!K;w&`bhy6TC_w zv!g3B^|!{R)`kMG7LEIR^j4!?)SB_K_W&M?O%Q1+u$-#$i4=U7l9iAzKPxMf9FYn4$F&oa?IHg&WxLJo`btU+%_w z<4%H$T*U6_uuwRKn|M7$P%U}h!H1n(^l}>E&0G^gku{5 z?)}g1zinQj!1f5%?bU-e$&((4E8Zw?3OqZE#bh+yeK|yaR`i^N9uVOq-PseOks|qT zvX`n)-#$MX4cj>)OzE?3Lqq z`uvf@INEvR%7^&wpv4}^mUbYGH_{01UE)=lu3%~~%IDmplFI!gktwTi`iwjj);9Tp zUZEtz>tA@Kw{4mz07%bZMd!jNEXpBJ{9G;+bNsp?9-jsDFqFI^6JgceO$PUK)qEk>1UITJ9 zxyQn=fMa_l=Av|v&IFAyP@-9;So`Y}6YA~9OU6p>2mntvLPMh0#TvzRy}9yDE{W@* z2gXOT+DbuW_gZWez{pIp-jj_Zk!N<@+s%iyC7^}-nIhBOdba+#uD*wnX8FV*%yF)w zXU7#T9oEOLz2x05dghT{avIyiaqwBdCwdw@$lW2>ezL%VwEp*s9vtyfC|D z0|L_{WQdMFVJua8dq9HNocFYZ$FK^XN7Adv8lLe8`~{kZi6@$R1dWP{_IdMh_d9-Pd-j!-wW@6z1*DLvTwe3 zb?^xu7EqDo$6=nSV?`)1$SGdhNEqQb$^(Hq2{h$+(c1n(+QVfFWmRl(vph9{+>e|{27^J=_SNJ` zMqoV79mNa7!&M4am>xA(48-?CK~!LX8=THf?>ucPL2_HU$M-_v1AyS6NE(`^6?6UfIV%n&D6;g z2=DNs^dm{3pwso{8E)c8mfl|F{wA1AnSl}IwTQ$=!zMKv%~Sz@~ASn2k1_uxSKiYQr~|9gp0nT&F=81{7a2pKg)zD zn0KmIj?qh4NB;-dPPm03)$$S|#CF~tySA^PKs>Ey}?)?6cjZkxx@~fh}o1JP)LQ!jQ#7^JUX zi|p&flZM13D%((nF*5u!#7_{WfM!*>MulgvjO)Az6x{Vgz`mtm<v&b zLq!iUW+pZHUS8gvio#+2gg*~|fHe{Y#Jh8Z#3kI3JBC;NWa_k-d#XLf&}|_oe@2h8 zLz{U$D+Rt&XN&=~0s(C4rHhiDQaT)Bq$9%Ofgcs!*s0(dL=cnO8`m1qIC3R=A;&Sdix)ST@qLAsee2e^-{)e zl{KO(reP0R$Bh1tYgbtnpn+8ochTF`g^o#e9cT%5ld=p5Jd(>E_MX&JbF{u1TFkzOiYl)s9$gMz`yae z-74+XXZYN^NMoP+(Wpd2vY@>%>W$*u$#XRiUF*6h^9IqpMQy&KyMTJZ=x& z{WO4}RJ87Iuz@A31e?ALxt_caqr7NepHO*hDmq2t=!5+`YzS{cBUK{AC7l{Nbwevv z@=dVulLL#D9jr7>9GdYCyi&V%)~d}xT}a}9ZOY_Lm*9M*7KC&F0jQgIo#`Arr3-Uj zy!U4$hRY+Bw5qmd7WcY^p31DP@d{JhuWxfxT{mUCfV#PGbp1GcdmkzhEamne;yYp- zwg^c61;Vj`7jfOYn?gVFw&!LfxKDo;t4V&zH>b4Fds}SLg-u?Tww98sSTZ6!@co+O;_{Cm$;X2sdY2Flf@E>H&W? z2-%LP0uKG$#`dO`SoFybaQ4UWTrl`}tC=^gswjkf_=Q$#38A6b2)y8@!_uWY+=pY) zCV(H_=43JeXoeC;J4N`1Hg3fV5J7UR&_E@L6pDsY4atE=0{2u}XB{f4$e37>#2J6m z6UOy2nG{BsgrzG3z6Kk%#|<+KCPt4Up~dkoCKj*PmTO-WVSdW1gBIYJT|4WDMIhK8!+yD9XYCi*#*i&mv)CL0N|Te5fdFd(YsgDr~C39ZVuhN1rD=o{)MDy>EqT;-Xnwr$=JO?|5@kJ!}l;$ z!m4{vBbIGa`~?MSW@9HDylJkW;GeuI7^}I*JHNPZq6+$|<`2U~%&l6|p$C24+#^S;>zeZ?-pId?`t z5AEp~rQ3edN;o0hhFtsm&Hs)7a4?BKnp~K{A8*Z{dfW2G9g*WWOnaLyXsJ*7vg}w9_ys-I=GZ{RZq7rN^>*Kx2hCGDP?f{S3X?kWlTracYheopL0vGcV%9m=aR`3t$|h^KISOZYKCiMq z!d%V|Zr(+dN(YM1wRI{%M6+3nG^a^(09K%qk4+Ny;f+cN#Zxx)H;lkqIEr6?CD)0q zx*4S1XS)BpHB5$O*Tq-&Y+M5GYkEjKT*vY&g4Zwqc1}R1&KZ73(ATRpp#0^wUruME zSIWlpBbt(ioBh6#gQsZu{Jq(U*}(2-mA1hMbeZ_Ehl}RzeJDqG80*dudFhj@Zb13o zqGNCrjv@AATP?-K?L*tSMZ`eG7t~>J+UC|hU3Ao1{%U)hUrs}Fargkh{+b;!8l5-* z|Cvq~KPQak-~6nx!~z6iq~JVgJwS2N`NS|Gs(|IHeEWhFjm>72NZD{b9nG555j9y* z@WqQ}o5K^~b~VFTy++q@;+OJ#FR~67?-1C*6NYOrwV^nGIL%vq^Y_0kHGtqfyV`~% zdI0_^#1C!q6>Hga`$FoY{nwZ{=v&ptEXC|+kS8wQaN9*FFPUgFdL|Q^sJhfztuCr7 z3{8rpD*nLl{WW)%*v*ry>f)CRWweL)FGMlCGN9hK1|96vP$K;2*)n=zej zIvz91P@jovZHXe1w_(5>YQ8G6eNz^D5Mhrv>FV!;3Bo98dbMo%xc59d-74HI0R$|nXmJj zn_4LARFU{V^4A?vngwoz>g_^TzQ+Vze`7T)>!T#*d&fNpMc3R|0<7T7d62+Xuxxw^ zy$*Cofy{Wq;uQh+;N@?Q$spT8@U5`^k7v zaD~~cu9ozr&a(e7d+cQ(X<_+QA;lV_aF(A-sQ&E^H%ux6;?Da(yoL~$;UcGf zm(GIXj=$SDp1;(lO0{Kj6$rTTJyKxlor-RoYr_OaYTyOIdM{(jIl{)ufXTF;;xYU~ zBh*D3OQ-$BxDxAfAubpro%OgePR&}%up-QRSnN}Nxqc-ih!-yqmE6Q`)vM8ZHBuLv z9P+B#Ri^{MyQu0Sx1z@;_{EH%Y1(Ki!hn|el+>+nc#U%E3( zSb@9@Z)|NpD2SIyo-H4fX+kjuU5;8&3PV*1{YFR15MhIxOgL-V4{dO5{f!we?5Qen z-~9L;I!r>FNAUF`l(q~9v^oAeu8u|le6j|NQp$IS{Us144X6L-mvg|5pFx(Flg4#* zri5#*gY!(6ifRus<&K4ktX;vxPtk(S4-E_t#1kYG{b9sY!!}vJrqZR=xtdj`S>e;`MGW8C?f|XMz z+q>4bEikEo`cG)7Kkjl0@++FQE!`WPc)Y-f7wE~2bwd`R47D)lsQWNirVp^oeD@!Z zP83|qkNIE#;e+^Zz++pz4f-yyT*~>O1=8xN4y6@Av0VB_kKNc_yB74gO^8G5=?RvjK3 z9_4HkA7&H`cD0ub&1kyMqv*!hA4utgM%l@)_7ert`8UI+VNZck8|U&Zq|s>wJw_eH zi>KdNt<~O=ilh85?J=m|k2H!C@F<0%h-^kb@${Q1$h=?N{$TR*_j*;&$AYKGpafkk zwSl%Fq|;qgH}BkEVF<0`B6#dQ|3F)Vxk$i9mUtdf-eU^nJ|}?xX-=$ni%Sg8#{r3m z_SaIO$+ZEHhOtKynp{OJJtzc1$t@1WH{!45u5t9`WH z6sf*4e`78e6HNP2+K6@1DDqMobq9G9UB_5kHGSL=peh>L0wMGgzkLYgFLzmAgkbWJ zl88zE*#A4zQp48t>-QM=)BSZAa`c>B>eF53C^}**VV%z{`TcNj%(CW`P$yi-~7rHD|J<6ukLpuCrT)sB8GtJx5llKYjA)>Wvze& zPUA(DUuOe5_j}g4cG~?xlozjpB-6E$EPnDfv`n((LZVY09!E}F40@qg%M5MZJx6X2 zk=85Pv%zMCrqwoMZ(`c^bBam6Lvp~!QT0SRN9}Zu?&=_Ad<>9K%=p9A|K*-m)vyq$yN_wQ*DUCqBKhJufQerGi zUntZt+!$x>lq9Vx%12^$F!6=78$_-C2i*BgvjOFA4e4Cq`nYutXY4w{UrZ5xl190T zgUr$#{vOQWtYq*qMMywF?E4) zIk`0Yh?MjeX@u6*1gk|flol2;B-rkCg%&r+3H^OIObCL5S1O4b$tf;eln~cwN-g^v z8?P(YOilUnTV{%O4a*}+qM_1NKM3qmX^1n2dreA=t0;oZ-^I%TrptmVG1~H0DDw4c zaVH=Pk5;TEK(%-wq3#w@iP-^$K3nT7mA}8%RK}na?l{I}I-}TxSwqXXMMDIQJ(L7D z=&-|1LHtg?WT6r|;mobf#AQ zits(s_d}pAG9%8};Ov9qMIC`g)|vf=)^KC;$w6&uS(T&gd;RAD2L`4$Z(1!PT~9w=-t# ztg7}IM2T-D@48g!N}*!Rb7;PExR-BSd8^aI4#sI)r*!2kSHou}hs3ELklQbOmiOnZ zq!5yB-{#lOAy~PRtZ8u0%3#9R{FfNRH}~n6M?ir4LcrL(LNkfCZW;_zI;rPfqs&1H zJ{-)JxFXCb6dcncWc9r3ZlztD%AZrMSH&jHfsmc}qq~vSyI5|JymFUm;ekn(Jma2bb8=PuSB}sXo-N(ZBwEEGz49mVS44JACB2#Ewd0k5)k| zQf(P^*?C)YnQH{A&^_{6?&{QZ^N`K}t~P)SQFPqm!F4a?m=1btY!FT*Pe!&pQhJS!JUh3*m`zM=m(RE zjwKlFy78muiVr`qn#a=VuPiCctXmuJCEXqq+Op)Z%X6BpJeO2HE%lD?;Xlrf_ql(Y zl>hF&_N;BYUlj&AOC!r2n2RRwcchg6Qwpf6qHF^#DYwv5yPs2Oo-Z%HFSJ&({iT*@ z?PBoDrOX<>viLavsyshkt=Z9I+`|3bQI~zmvDyEZtyOwK)14QJB#e3*T}gLoxvuNS zU_2eR&N$kV2erz(s-TU0fwR2sb(Yy>d#AhbQ=@Q|`*o-JcFw{(g4TQQ9kx*ZW?gC{ z1Q4hc5XbepMAufy-I|<}dk(e*fAd+C^I|mINt9P9F7`5ZP9^Sl6??5FcWu$I-?Cj` z0U6&)8L}mfy0W2Zx)`H|QaPL%I;%q#&5ru>Vd0mh?WSeo*YZGHHNgB$OB44w|4U0@ zbA$Ht^{(xe>oaJmElBlq@Z-9@9w>_SHxJOom+n6v0z<`WZXbRiAR@tk_YnNgUVgNH z@8!2La@04}chsjdcd)TmQHKFi|LX$yS4)4nD%GfDn4GL?oud4l^tj4@c?e$p|K%Yd z0N$QB$p7eA*qacE75PU;p#OjC2#TeRIDmkFr2c;8r9r`<|89YRV*lTD1or=>BS83n z=m`F2*}v%s?CFi||6ND$ui{G;6ZM4grY|9=MOrp_J!zOuvOEZpaK=(de_!9ER4_M<6Zm>8=rvjzV+MMhH0 z((QUUD6y%klo2fKO5p>qLgo2uz+g<<)YhJc=3`2$XnhU(+fE!cc{%y$1eEgarB~Wa z;AP2d|K|UqA3|*Mtpsp_1rh#n{t|!e z48yo^;=#0sy_T?@513M36d(l4#9I*SN3oXTiFP5%d?4MW^_r}x=~}Lpz)}9jI4Et7 zZl<0^l^YW1(AM7Iaq4)?#mIbT?UafJ7TBwzUsg884QV8Y&(1XTdv#LjuK5Sb7P2%E!YG&v`$!Dy zQ95@}1w_!7b8nKV`CGFAyIfKZ96)bVTYn3K2qq6Cq_wTjyMTA}_y+D9^U~~4c%Xl3 zmv@trgvJ|!?iF6?JM9fSwEruLSW$wG|8qcJ-xa-sdMoOpH9+)Z;3`w=m?o?SSs?P~ z7_Rhdtp{`wM3Fos2QF+Pl@>OjELxdas2~ynl0;}z{x>L;IDn94XKlFAhx&!wPCVa# zeHW_?Kg5C%HV*O^D=XMuVEU|GVO&!gqR`JM5lq6_((Mf;>ukeFY_#tT=@)dhcYb@P z=!6@LI_}Z2&CNrXvP^rsJlA+h{(;#2WGt#se8dkYF`h76&=}!_4Q)bx;auSt_T)x+ z^D}Nf3$y%2Ww}x!1Tn~8zfd+1D*cv(2v}9(W^}bSx^G{64*Q(p0{YKlp$ySKswbEn z3BS`@5xhlYhr#!cS_%{~LtJ{~wMJ(C9Fh2AzfxRu6LPDsbeq=rPy~F&@O;v)Nq*ta zcb8k6+`4S;Z90c1H7F$!JUz*x>*)U0*3i+^O3lg0(r3h+S4u$M|5HHxu$$NG_jT>n z$-BAT3r(dWOB;TKZN|`+a}plOd_qF^lMu4yi1uV^6j(GT%_JvcZdA}%Tx7!njLAMj86`t{pptJisPbxiXz zEc9x^fuN&{gH!93jIF(6&wkA^!;@OIR+rkAocj}gcHtxaHrbTHv;K~sU)reFS+>KKM(~MH`1Tl1Amw#|j1RfU!k*~&Qdy7}7 z{d{-zFiGpyYY=g}wB{$@yhO0wmmceQkT4dyp+qzs7n4;NmPz+Bc*<#^&#)XObG-hql^D17ABG9vFEAqAG@a# zn1G(gW-C+B?11Lyf(c+9AO?nMt}N#^mXiA-tJ?eLm?C;BXptabZ(`#92I0&c2Rino z!^dRgQ~Y_EvHNZ{Hy@XJiuiCz1z3awR9+Sd_K}&f@S@4E|7tz-aGwrId@-4PKCXRnz zED9W*cgZe8s|f^Q2sIoe?!nq7fq0BE|B||i4IL!(;Y2Amb~qzYwGpR|MVPps>{tgz ztBaU}{XrL}ruT#2qFdjIu+j~WL?X53xl|&r%9lnyD?JqW1_M2q!OLRUSM9(LHw|nx z@O=JiU!+VdZwohj9^v9@YIz+prTV;XZ7BW@}PyqatZtU@fQO{;ZockZC zEgSY_9TSQ7OEqNrY<`ET!=E)sWsN)*!>XxMUarG#T-64g-c;RhwWgQS^UmO6dA+I3 z0m)sN{SDIVypuHDdL<)eDvEP&lrv z>otU`tt(E9wehTEvAUhDG-z*FBjFUT{Z5|l#nxb%(lq-h`V&WYSh@yZjk>iiO(?r)gPlD;?=$+zKvivg&iirH zXI8&~Ej#-A>*B3Zv|{?VjB(L)P0Hkg{r#?j*+n;NO)C=ugY_A2Ax-Zc)>Cj}6`R=e zmC(~Bcg7W?RBM~p9~zDB)e?4I{gi7k7n$=YNi8wQ!;H+(qwYsu$HdhpRF+YplmoCAdTFW6?8T~c2k z86F&iWH5cOyTCqV@N5(um~?tOD6=C0JVfkg&fMeRf`g2WC0UV6OhF9LDR{S94^>DS zYK0&x#O*(79u4|~)EwU!*jKx@p-KL}1QnACd5+vu=-LRa8F!xXqTG6|6T=HXCK83~ zfbl9f{SlG-%;TNJZAbJ>2jT#0LOn;4QZ6Pc;uRnjei)Tky-b*2U1EGF%h|cC!Wj1C z)k9CnZH&Vh9;%I&K($BC!iA^MiR!Jaen;j~!1%V&`R=te=i$jGvj4m%WWB5C6t9yF z`-1cQk{sB<{q?Y@w-^GFB34iTa**mjJveU6`{^1q9cZJzSaml6dRU}_>``-^_NGiD zlw$vb)Qi82a!xjCX2x+4INOt4L#*F_#=x%f#WF@OM0383GEOtHm|jP?DsFP*I6}#g zo-_Da2F>Gh5s^oiwj#u*$65uaequ#{^nCKh=mm<}#6EHiV~eZ-SbPEBt9wTTIm`U# ztY^0V*T89Doe<#LjGBCQJ%6Z;(~8mBUM|-M31nxq6ykhH?zOrTp>2aWF7=yK+Bk8$ zVWpSJM}qfVhQ7C7ZC4&?{KKt3F0-EOLlq!SL04iUu8E*p8LY6x`BtU0#QBj$uYzlz zC~hCR$P4i^Vbb9|T4o*PSpA&mYQeq>$E>7#?eJa}boX`Y>n^r>avd?sHzR9F&4qr& zHXvmagnN+gNi}&6wyJP4K8byu5-^6W%RY~JP?F1+VN z4?B$B#@C^gB3{o=8`vKaOKaym11@m-B4M zS&q+9oZ6m9Mw^XkX4>lf#lR?Koy`F8(klAJ06@6tytd)X!L6pytg+}3!pWt=^hy$j zIw~sgssC7-IX*QyG3tPjIYTWcUcIXb+swgdrOTLnQa*)^Nf6xG$$Ye46iYnk;(E1dY^*O{C#rjUF|Qm@j*Yc`y}zO+&aAl1VPnb8dsm;+fQa?8 zUem)A{W9K`^^bP$1#Cuws&8sS=;nsvJj9xbOd6vk_Uib0xs&C4(QPViRebuSM(Tp5 z*w9m#iTC`YHw%hg4zYs7L67q*ZVz_9-5o|s6S&2ozUq=j&$^8oxbZW4CvViz4m6U@ z-e$YVcNCaU7}IwZ+;_hiaHcG9eEJvr^m_;^800T73Pn&pBXG|{Xioy<(kM6-7wAA% zu%k%uGQWU>Lw^{6UzCO))uW)(1A)fXj@0N5r?PySu)K~f!kya;l=)4DN$)DTe?)EX zPp95uBiQT+D(chY+457{=ds*QiiCKZ`VZN;C_Ejd6tx0feU- zgd4qiP#SsSt2V-aPV;EwOB=OLjBM3Y4Oeb+YkVteAg+ashlB$o`)_3RF&t0IgiCdV zQ~7ymItY1iuw{Y72PDN>6#ED3`sMP7k!leWgF|851L0YPn7I2ua9(AY=5nyoQcy8r z;I`?}=}d917!ffy5j6ax+Crk09U>bF#8iw#NdpSL<0EAu#0d}bT_X!TGxLAW&f=fU zlENxz?kl+3%48VIuyTnlGY$n{-Lq03(Xwh@yFCDBtMj_Ih{|73@E$n}(+D#)f#Wn2 zU@mVV6!7C=$3*`g9&%zI@DtnX8b;PlxkH%~0I}*JgoDSDh{T^lMxr5wSC}DMH4|=u zs{z*O)g;JrR+k53L3N4Q2EwWX8_57=a1ek5GILxg64oZ zm(`To5>CKa-w__j1pRsk`8pBh`h#k#htX(uZE7s;G_oa8gt1GH;;`J~JLc zc2JWJ-NQOSJ?`jgVX1MUN!Tor&>Dinx8~T9?Cu7w_|d84vAPHrPYNGRtT<++DCV_L zj-*KLw&*ufuWI840YdKke-J1-1H5{BL?9p(lz%5s{}bQ*8-cR_|6;gsl`nq6OsFo_Q^F-7qXU80}T5N%*H>ElO*jk^)rJJ_C2Xe7$F0Q4mv1F4iXdX zX-tlAI~%{>-K)PFw{5OVT)bXBm)w`U_@~{gj=h(-EQ~?KIlPbR4}OFDuDpo!B#Ba$ zfUK%2++qquqxxIP7B2sS4?#Y~xdMqzNPuifoK->wQpR*a3=NY8s;dA>K_ZIT)BMqr z2vT(jRK^k>CuuOhhw<#RdNf-L1EeCPCkk|8kJO1KkTN(xj&$NAaOGyhM$1O$CsIu; zvJAZ1Ej~;-gPAr!A&Wz-P_V&6M(f87v18UKq8bRFB^+V&Hn89!?oj7|2D@8Ki&Yl!<2Quy03GrPg0?=kOGc27;`dDY={Z!P+16toQtr(JyR?u zDhCT1l&R8lC64hH$`LM!`{xXDT}Oer`@2r@AwQw$QsFXxWYnKP@#2~Ieb0RJd*Bp?*$N{oW_=S*Bc`=-9*p18?| zQzDy9mqoCzpe`Ya3vNyC)elqlV#H`)5VKN&q^#U@hS=gZXc^padGSmzfRMgCqzo#^ zqGGUMBWpuL9>YvC@R2AnN4sebJKHN-MDrYQfh0)>8`$3YdHG&>u|IK(kRBu$2#88h zX&Kc$!q~uHfjY4UrZmsnW&mUOiAc|mBnA#zL%VZF^gY?BZ|}C!%>Z$n59bh)N*7t- zUf_LTEbc8rDm2R8Db#B;S^QLi))(fl4p-0^|4ea2yDczs6nO6ko4l{IH{6qp2XwgW zUpFu5CH2T)hkkb=`jR4eXiuo)+^tWXAi-(^;_!bm6P~ZC(rV#W*0J|1D)lR%gNT(E z5zT|ihJqO|K#Hw_mIVZ&7WJ^?TM$3m1GP zBEzOKf)tkl0C`{N%P`U;oCaC)+pVcS4y@mC+XDK6rZkd$r9N$m-ZiHHg- z78ji>McmcTBkXM_OZC=oUfgt01!YFjiXeDi65c$os4K&klX@sOEty&sc>PevuQM^|wEYB+Z zIZpOoaM9~YLiNk)TllC;m@#EP7(OzS*0YLz)I^3lc&?{q=G6zwtqxzkT2*Vkp|wv; zMx!X>+~0W&sOoc_=^f8o5=5eN+wTqeyy6V@^+)Y-IUt%v)`yl{TJD!qo$&2UJVPM^ zoEad=vb;EECsZzNOc*9!h2NlTwrKWpt|@(j0>u3FVDb_aBe`PVaD(kV38645Gj6Q>es zkJC#gnImLI_3&#eoKgT#=ifuE)^uMomg~(t%*_@FH^isWG@kLHDnH}Uj1KS2@Ojrz zlGdOuC!O*ukQT4>pJgXqb_eL5v>~+QMDCLz_k1 zO|-k%{I8mMw~lyiub=qvj~+`Am(}R*_4$OmM6;+VSKKj|_5B+-y$wwsvS#BtAzEQI z3%HRId3}8rUy9fhRZbD~Lq&y)`YY?H&Xw$zRvw$fszlG{DZ3obuTu%Bu$eWWR-&HW zT1Bz(@rqYN#(*0I&JL;1;YXYx?ka@5lhIf$QBf-FP1Q}~2drqD@aVTR&t}rMHLo~- zu6}*=VKsjm%{01t7_GQ?dpOk;59|;y$N-B7qol%$^Qlfp zs$6uVLq{+I5h{lL2tlLh8y*51Nb_ULWxUN9={fp~B@x$y1ORZwxvJ;LjNL))xg4C{ zxq%>ax{zE3adZZoZ_4@ZyLgH2hSE4FE$9cf(v8I06U~hZ&VL9+?rh)qs#0u`UuFzS z0ir|Rv_WKMNrMd3=0m+3Gi0xyv%74#m`F*L(=S%i#5f|%MVgv9f(B>+(})RS3dee} zME~nIPsWt zUiN0~Hr(GCq}4%g)`IHcMp!1e>`WHi{JW?rf8r`%m>Mn>9~F7d)Zc)`>z&tk=3c_1 zEt7m_p31SMp<-jYg@h+)ypi-tn>1T)#1CCsOz9yUHzpLOKMU}@ygAKo9Akf+bIzfT zOW|>IRxX+vyV1rcG2P$L=3l!l7ZEK51pTp*`<3oQ+^!C=&`{zUpKjeWIZ`+4saA3a zIpS?IIF!+UD(v-Z!lO;PbG&6v5EGyLZm{?b{jFw_4N|x>EkQ%6?5xXo(qdpUad;x* zMj|#;k(-Rd%RA~_=?-g?<>`W za8N+!1reC|=vJqm@4S|d=lXqMfPnpV!Y8UmFBe`h1T+M(3bm9^tJB_Xu%G?B(zFxLq|*sTpUxDrlFrs zMH$YTa{pcqCMKi4EWLZ=8*km)O-kk5d%2Rx)qidgDPim^SQ9lzwmwd-DaBlj+lQ1L z&3SSV=0NUjg8C74?VU`;UPeAPV+7e|X+4>fT9=_YnLX#5XO_SnZ_5z_=k0xJMo^F> zm);E*0gb8XCX7=)dgOOx#7ZS^(is)h(isN&y{-jVSW<9yW9mvy$5Nr52A1_EBcVe5 zlT5F;uKKp$_cHbAN3YDM1hJl?vqPn*Zbc-X0V8;_jt#379Tb!4&OW~74thWP#bJpY z7KRXgCRit$b>o zNyyum=iN{Q&p5q)x4ZGp^25Q%>y%ypO(1MGj#?wD69cB%=INa+wsV! z94+-25*I%nM=Z~beX-k)LPiu#iou7C%&5aOuXRKb-Qcoi=VCt*mlkabeABXQ zfBUkqt>`!)qft)I0sk4nh`&U7Tqb9>{Hindjq^Qm0);8}EvW7y#k2kLR{C-A^Ifr< z=kB&M=);qU`NP zY#Z%)ThbOZ^ESN?(Kad;+a}tkdUyk0cWwpzB^`OS#;AVlp5l0Beu)+wy#Tdt!FxHGxa3n4;-l)`JynB0nc)r9;*IS$oH;*1bIh_FrAY;|=kY&5CK_~i>D*BWG`Tz@k8NdyU zt84NvS2#`{dd7}is*hbg*I7;1Fl7-;p8O56{L-ZDN&RYkf`f^T2m@#ejZ1}Ph;+-+ zNkelBqQ=VbHAaa`&4MS1S@+!rbhZX>CM$+GHf2$4yuzre*&!q|#TQ;h89qL~odqA@ ze-byk686%E@IXKTNdITN<1aDz-zIMUm0I~<6E||o|CPAm{;$N1p5H*>2T!c}U)&)R zm7w^az+)t8Y=`A9>=6B5fXDw#{`~jw4-GM}HH z9~n<@4PM~m`i>0<_HFy{NZ0rF?%5Iui$x;*;OIIbt5--lzN&G?HK^9d$7cio*G*2{ zWlXW8tlrYvaV}uw)Fx6+FWuY~a97>^JiQVW*Loo5uuZSoe6vxAOJ!AsA~0gs6b zkIcl(K|;=e4fMm%#8#Y7$rhiPM@Zev#$HX+l#We-3!kjLZzVQ1_U7h>oG0M>`+EiG zpKJJ^AHr9#|M)@k&-$Np0{ZdYqogN6gB3GUXg_Mnl`~W7C{?Lhy;9*ke(B!5Q{#HU zgQx|AZ7^xwzJ1f;DH~G&_R`@!ef|FJ>*pU37!({5>OtXTO9c}X8yBCDn3S9nPZ?=L z6PBHmo0ngZmywZL3P_;?14H29frI64Xl!b3X>Duo=xl*UM&#-r7#tcN866v+n4FrP znVp*-faq!kuUT8)*xcIQ+1>oH+yc6Aa(Z@tae0b{bl8Q7vG?@64GqLxS-n+k0EtYcRocJSWBEcPH6U)i!x@ahY`(z(gV(F|Ko$;GV$&P_^Rtb!^rg0FT&pM0 zE>p6ej3oqwFnWVC-l*q0f-L|V_3W0kGL|PHxguz8u^EIfN{STHvUIT=)9%MYT0?nf z>7lOyuC%JQLV-kQxR!Z2pu0q`WUP~{S7^1}HODMzSfx_e+RiI%Qo5xE4&k=2_%kBr zlD7FNcPfjfx@vda^*Q-`{KaNRCx9^m&0Vi&VK8fua|rx3+ief?IJBQp>yQlbewrq>R-jXB;!zs1fz~SUP z|M67z+Jnwwf5bqQw8#7Vct-&^efPSZ&XHuG;Ez+(mVWma($oo$j)7%ek%vQr9DnGu zXC!m@zB^rdU|E*$T)(qb(cRERgaf@`T|^ebunmn9q+m>IbsZ^fFGwN>$hsB0fcDH# z0}jOWf&zd1)Thmu^%2)!A>6C3#FjWX3)b$NSSQC#nr$vzp(zFaXOv3lkIfk&Jd(TV~5lv7}x4dML%m|^gHqKpPUtaf| z7;YlX|0A4K9&-?ij$A-i(rFU>iW-hLoYj0#JijOyZIAqm(>+VpQ0dZoEz{6e;XO?N;`?4}Da77zu zk9Y7*;YW(3Fvbgwqq5+iMxi5##`@$H;t&y&3gX84@Xj2RaE`(&@FV!=%R*5DK>Oyi zFNDgy3>i3H$DjggG{uB>Ap*n3)%cB4XHE_e$f*JPYk$)k=qO=hEh?aLJ#Bu{N}YT2bOup!~2|be_@>=tYS{KDi93L>)n$p z^4+({(z81f92E`;kiE)yA*rNIc`XP{CjW+{XAMA%-WMBAS9B7^AV81CW$QQM|NEm+ z7`V1U!f`rUow}jSPj4fyx?w-(N-iEw+axb<_NYWNm=y6#)m%=;K{@3JT{}fqLSgbw znHj2yQ{*rSLQuaXI)Ucf)0@hoO4V9gZd86&t3_B-j^|xk9qVLB9^BbZTe~GuHGdDH zv23aq_JmI6MnSCQY}Cf@f?B*8c$j}NmfZ{1ln=nS7g+gGm;eoDD6u;!sVkC}VBlgRSUH`8)jV2f#Huhs#W*1f5il%enH?-hiT%g>xqG@e}D%9_`D2nEJ zZ8$0Lm_Zyxd)d!>Ia<~MxNA31^vbq2km1|)f@!U2%_q?G)|)$qhXvUOwO+iPdnn68 z-2x6bs`3akCoV%hwFb;`OqHnE%SuDLdG;PQbh!A_l>=(K%wp+n#+@kcWa3-F{x}5( z&#hD*^8+*4kj_i8dzFiGO0B!M|qq$7DmNV>7GYMLm=ra;QIB+ zI$kA3UnrxCj)nmiie^5{*eTpcBcSDPs9)W*lUezUYHQkw9LJ`c z_$fHTlI7zsL(ShAgzzOcdluS$9@+W2PGx$SrtU{voE1|S>Dkf-UV|BQ4M@^ecJ@{^ z>J4MYT+Y5q*(jR%X=B$sE*c4P8Txo=vxDwWk3Qq)5_rn?Q+LgRtW4H<-Q03(-S91! zl~>qgQX3Uyu75qEXY^Y7*V_H!BT%ugg*xJ-ANlXP94~de|733CKelj#AfbJUJ_SNf zk9XDy-x9EP(E75+^*2D>*5qd{!7_&S&T3nyIy~~@DLjXWwV%Y-h^*b^J+Y>AeXr51 zXHy)?PZVT3a0Yf%UFTRkk7p;&EGg{G?6~*R$E-d7aAp-@lo=MN1|}8mj~~tVy1O}D zA(Z{WUtjLvwyF0?ts>HCr)NB$D^k8|&&eV&9S)w}Byi1#;uEr-MkuO#c_Q*8C@1R; zTd5LxGCugk^gOoNZhCWtgyd&5IeJ^+CDsd2^VxGYyV^v)bJG$fJWa$(S}SmL|7YBN zlg(AVC~%!K6N_~=80Hu99QfolpCeqwIB{M_{@w`uqHONDKAA7yPRSy?B7*3cfz>J9 zB`bTW(E2=202q~`zg(QiU2a@rculAiHD?c>Ott|J4rTV=Jxbn!3Ix7{ll54Sff{c$ z?jMkKMAKK#2shB~cN~BBO89yld<)`z%NoopTHFSA^})?OfRG~&521dO`F@RkeykY0 zZwtN@hPLY%eq&7jO*dX4sU8-j-sJiIjm&--!|0BboQeTnq*Th}VF+Y9%qvR%0-=I* zBcu#rT9dBk>MVhd2M8#oc=e5DO@jRQ&qAvrtRgRXOfa6>NB%p)dNOIafd@g2Ai+>4 zCIQXNsxbI}+=4Vq=^7S;vlYekTfq&@{n?J#%_KF&B;71q%|j1Eq7;SgIYR|tY+NM0 z)e}P`l|T^8zCpLp98iA$Fb*qwH{yCL9P=>Uz%YRTJCOtGm=i21^zg10?i6);*A>Ey zFw(5FAV4W5?k@{DbRT0$-V(Pz9cBvZtrC$d8e})Vz|vw;0yFr45m$DVhaH z74i;F%Y|L1{6Xq|Y}CY}S@yola=<8fJr&>R^E(mzUz` zp6b^213x|0Yc(}CKE($oE#NgJmntnJJk{AfErKgG=rkhoHO(wNEsiVQU^T5nC_U{p zIw3th>ovx6H9hxLC=(~6@RdJh^~auiM!6JDL0d*O4s%I5M%imd<0@K}MrNx!S)DsU z+iE7ULuNNlR#IIi4|C=}_beOGw2`(f2cay!zO0#3v|gO-g>XE8M)nFU@mPBH#wzjh zYW9vh+B#0o0nV>&jhq#(>=TXbDK4UO*qpQ1oSD@e2U5TW4&W&~>wy||C>@X<4*0^! zdUt04#mxrM%x&Y!#qiIaS;e6In~Z{+2Snq}!_dsb^vJ``$ir>V!#@iQ+XunN%_rr~ zC)doU^vI`z1EHLupIOOgc*|$PEnwj;V12_v>CI=$DBx}{;9V==e=Cr(FOUZ<6xA#g z_b8OiD2yO2w96}$e=GcrTclK8D9c@>=24`cQKY%XrP^Mk`&OinTWlDCtixSw>QQW- zQLGYCV7XRo`&RrHPR0bc#7VQn#iL}2xx}r##A~g@=M2%~tt5cEG^o7TU$ZnUqcnoM zD73va=B+ettRNb*FC2Y|c0*rU934Y4SrymGBP zyS=;`x1#>7w3fS~*`vZ&v!b=V0@#UL(!N&Fi(6^*R`E}>a>%1-z@u`ky;3rxa^kIW zroCVaw`xJNih;XoDWhr)muIEDYU`~kC!=hKyZYdaaZ|JUB%`{JruuBH`f3dO_^tYm zyJj7w=E0-p%ChFAz2>mI=Hsnq>#PQZr*@I17Q(Z3!m<{oqjs>o7U8|N^Q;zyr>=pf z4#Tsq(y|V_qpqO54*$I_>#UBLr#_jcp4_uO#gi+EeU&S>sz zS*~_}%ZPif-+6{lCTSpTR)A+~kw)v@3?Kq8E&RPTceFK}tgRKNEhRHO2_7jXvn`A( zC+9sQ^PDSBCNoW|J(jC|JFvaNy}i;it(=ypMk}M5r^6qoW5lQqYYRSQ1Ft&* zw)`Zv)rXzchuhhQ^#KO`nmDR2ZRMCiMiRet>a=M^ zoC4(_^#>Fi=HG|YU#3bdu~bS#GD^x$oa=Q7+6`wK?4S(vBoVZziP?U?gyhM#{=X&z z?ggMkpaV{11LVR3-@id}m6_5AeN{)+k_ZBi(EUoYgBjGK4HBmTqyf{2%n2^L^Nqy2)Zt0k)p;`IkPrt$X8j|<##)E}G z;}PO6!-Eb{1V*DWbowU(_9pgnC!2^TVPS^ZlszV9K~^`~I2R`Cxp;Q0`_r>94rJkv zKByP8zcng&8U(Vc9m<+r8jWsbQwf+ZiV^(S&M zK10mJB1Ic_VkIV_{z$uixTg-lI~u`va{@%rD{vF?KDirPC+^2h49{+dO=+Oyz#*T1!t<2BdhP#PnU z!Yo|!zpH8@O9ohrM#tVmHX_|N$i3dN7eez@Y@07E6S}jrY2GLWa=DH5tQ*mSi}Xm# z^c$;k8?$}uj=P&Wpj)ebTbbaSx25YdAln=XTf4O@Xhz#Jd7G!b+wOByev~`U(d*4c z+rU{Srh=pG*ElEtB?<_BM5q|y_xYFzcC3}EpqI-yBnCt$L{hY^1^SDP7yI3J@2xS1 zz3tq2wEP_p)IB<`JsvZ2Z;1VkT#jZNY`QH399sy#};Hsl9>3-NXdbeXj8R)0+dx{6k)*!+!eZL7Q<^^cAI;(;b0o(rZ!D27G+TbksAzaidc^&%T$gSnw=H~+oV`nf!}0zjA@mb7q~jO5 zV{(aO!<3VY+LQB*=(_sRwwrx5&{GxqA1+G-T^ib!Y7}XLbdr-9x9)%t^4ON0m`?ZPiD3Bj>35=e(d7ZNwM35*MGQ7ci(3 zT{`Cy8$%P-_U*dpB~zXfBO73K$~MqPie;CJE|Wq97x_rSywTSCQ>cdwbfc4^G3z7i zQ&+~PS974(G-TI1F*rS0*98rsj=R^$p_iyXZ^T4yP>pU>6t6{YN8o&UE^%fMV{g@N z&qDCdbA@kT3~#}AZ+Z_eD4{MlH*+L>#EE@>kZ!liRa>H7UBY?a+__xC)ZPACj=A?i zq^WTOK~5#t)1yRYFx{FJvs2>VMiPvTp}c`~Aj z>72RRow(}_mhp!N<}CXnPxxP_1u9?!1Nr6jo`vi~H@X*M8PP!av8<-;lWQilrwQx@ za54dlX$-27K*?xiyd*;SlPT^*aHxpK!`17V9{NuuO>wq);J1dVqeY-|b z{l5US7LOTS4A=v=dpmJW+qdprSHv8cYhaMju=6|Lt+Y>=ICwRbgeVk0De&;&n4t-R zsJPDXYl_PS>zb()ej-YKf7_tY7DbA0^G9|sz3-Pd5aYc8F}+YiHYD~+MB(0(7zYelg7F~HxS?c;r8ZM> z$-2wu<3}*s3-_5P{2WfVJu-9czA@$)E9<9HA}aASrzIN+a@^`IHl$!CpMDz4~w_;8)}- zooAdGQ?A`U-O<`#>#0Ux?~w*>8LK^gOpxEb?-Rq8E`dfdQaY*EjJ*v#PG28Mr!ga_ zs=0qd-IHz)VnqQ{o7^lHyY_a+R~$rN(*m6i z@J214OE(NDu)z0FA(e^1Qb8`HDAGO(%#YuJucfqp=vF|&G-5EMrhdqNS=I|MUR)jN zTM;H7)SWXWoxGc$O#6!Axwk17-m{J38`VGMA(|QljJy^WIi9?xGQjG`y;D zg0zDvDQuI4`XPt%#(Dt`$o;zs(g1uaS?LJv@d<``>sx8A+`{|GkUeta87_p&8+lOo zb;ks7i8p1bq|!u;bYZ+~v?)Tm z!|1H4PQV#tqrp|3RFe0>Xw1WgRn$#rF;kGtFCVMn`dU#`bX<2-HG@lG*`o}m?J=W_ zU|WgRQ7icn{oIE(mS`<|Bst`*o18iFY`Z?ev~7T`LP6S&*Be;bsNvevx(cX+Y|C88 z#V9LYkDn|RZ2d3Kvi{Q~>au}&&B@Zizl>Y=Er>!^WVWcD{_PPl^M|t7tr4WtxOK&~ zNPCzJRT?Kon`@*dMXPi5Lh|;a2&)@Txqo|HYhT5)l80=}a>C7fPaXXTg-?$03$F() z6Jof37RR=F2$T-Gq@7t!=t7-TNu6i37bxNgwl$)5!x^*B3}qfP#p=5lI`6p1*e2C| zGSK&=c01d#zL{>&!jpq+!1Sd%)ZY}c=%d=Z3nzG)4ijCA8PEQ#*>E6b0*($&vcWw# z+6hZ^-Nwpr5Rk7k7I--?tshZ9(SRIvB3uEPBzaQ?-H9?E=E&@FXtsT~^B!Y}iML)p z2jt}5I-kb~?&f;{x{up6L{#q%a?-$(tDm=pA0m&Kde3@C*PkDYo6Dx3d*EoiHVC75 zXk*(QInS*p1b=B3L;xU=w>|C;3OY`8u{PM8RS;J;{x}oi;IGg^?2#x zF-Wd$=R0urEMO+VHzee;ApF1Au!e?4r#qdDi{S~)i8B*)drCc#2K^QE>X}?J5dkt zfKO7i0dBa^Fysv}y#BwOtF0$eHfz9w;gJ(zKEgW+i-~32Nf1d!n1o*%Jdh>WWqm|6 zf8)~|4T&tmV5M;V6TIulDVR>Bh$zv*I+Q7N@lWZ=ViI8L@e1!^;>P}nGaD#h95Bqn zNRL0prZgWQe4ymX=*QHhntU9x&>EKL7bzqRUBI(^=E|CL+a=EiYDRI zEjET^kMVa*o>VEMU3JH@=a>E~8C1xJkqhb`M4!V*vQBi9CgJupPkY~qUMcA|?SG1b z^FdxJ^Q8fS`ZO4Y9BrELF%k%*W;H_K3lX8Wj1NOI$hR*um+TLpMKMK&rq%D2F~C($ zpGqobYqEetiBrzLN-E)dw1D@7KvBs z{dleiil^R(lu{eac42^;sXoY%QWx!fVMKzbF)E)@pA2~c8vp{r^Ph3ECv)(U5djE@ z?)Uk>=e7TZ*MH}=|3}=M^WQSx|1EB&omBg;xcPtK^~C-&of8QQ7?9T!oexPL+HuC; z!Ly2eY0Ljeo>QTa9KMwkAoAbm{|U7>{{KVmjTz01|9kTMKNE8=S9fjM>-T3av!|+J z?=^)A3&0aKz1dn}aG9iJv1(})90|W!3VT9a5EDg^F%_(VX3@Vwlz&Mm8bfce%(2tY zuRgxh*N>e!$F4y8DQ~^!cW<5N$M))T-J=Oa15XMcP`Tb7P!LYshW24tUR*9nyslsD z7P@Ai{4$rv?oE1nID8y-KCHF=NWndgtk7qFKzU@;o>|}?xeZCSG)g4N7Y4i}GZQ-o4^S8Vf9zU4CeOR0S^$-PK$M#-Z+!RpU2i84y z>0f+L^lG*YRcrif02bwTtwgg+QQT87j+j*`@1N*5TQeWe@{zY(@Y~p#+pVkfpZ{*? z`;`}~N^lK6zJ%WSKtHd@%4}D=Kg&h9c;A;v04gZ;IAxKJ+1tLQ|mFRlq=fJB#Fn9NWt8CjVL`~%6^t`-d zz3@ge*@|8J+ChF-gXp5`b}p(=pYGQkyhoBcv>c><)v@2T#5ZS3yev4n6ea-I2JM7oixSp0uumAH@| z*m-CATV>BnN88iiUvJ;q@zm)F80!6gOfE(udOrF33Th(~IB$qQjNYA!B?_Offwg;d zs`hNp0svdTfW;py8otU7}ivIz~}eh0~vc(lkdv+P{} zjE=uopm33%-2dcAu%u@B4EAZL0Ju@+9Uo=q_;0-%*b}i*+i(C}kv6NaxW~_#0tqfl zdOUvHuK0KhDYeDn$7uNoT~s4KAXFCtzaT({+pZDlCsmSKB^mIO(lTF;ulE7N3bMM{ z8Sp=gs@2mEt-f~0oE_avB5GbqpC#4E1(?{pO-_n07SiPIG0E*TvoDejudbor58RJU z?tUpdqVIN2L{;Kn^XlCXB(X`AZTY?d!?GuKeT5wUyk|ImrTYQva9SyOez>?AbCEaj zW*H!JNSJY?q-Lh_UHo$}<>Kn#tm7rf(%8Dn*`hOh*`EA}ZMsNey*|l}^Zj-T-mxi6 zPab;maM`TX_Spwk>}Op3ke|}k)s?5i`iX`0Jtq&%iq>cgcSq9T!UWEuBxNLKbtM2g z9ON)Rra74{Cfl|39Pfdxaig(OAB=+WdNISLZv%`jD0Seh$e=*~ziV*Ffhl ztf%R*u4A*B5A@!Fs9d(u^RM)5yaTIl+s!cyyYHG&%=PiX$#FJfa;o0d)$!HnewSxM z$Tsedh86z{Al1wDfB@QXlE4PBnVA|05An!q|Kj2**UBPoSU(cN!$-krKRxHl_@lr^ zn+8`~xW;H1aJTwHV51ipe@>8=osrf->DiaTh69vJZo|PjcW`I`qW1eUU&TxTMQ^sP zWG8jo_}M7PJ>@7rYEay2K77@m?wnkm4LsXxb-)6E>MpP_2G9;!;9%O>T^ky$p0^JJ0&&%`FX#hWsK&S7s%h}}l=pRNsSzaO{WnRxWXVP%`AJZ9Kwu9;zP4 z_a#B!?a3`XzD=EMg3J2zel;Xh^8PD82%EfYmKs z>;GJ;_>(Ho5l|4=m0gACn9`J76x@bmZE0w2Y@RzkGru^yG`EmjQ88PJ!4fX{?f<}r zfdFZ^R^I`b0;i7wWJJW}eq}9qe7H#JH9(D_sO#hI#TOA=#gC&Y@#NuIhA_ zmc1NKAY2tfKf$hi67>_!P3>eZPod4+v)7~gwQvd9i;%N5m$n^_ZpFUtyG3@f@BrGQ z?;D}(4dBjt9j7w)8!pDaie_~wI{yUE+m1QL=>H*0XmbPed&Zm}=lx~mYOW-~S~7W8 zkuw^GdbrGLy4v1v&^ju&`s4p-Zt>Yprh9mtj}0PE8{T!;-maQ>H*^@TY2fgi&k>KBs?ixLT^zW%5K$JvQp3D z-5KQG>Eo5@J$~@wqK16$Ubg=EaLlv9cn(N?^~&_>PV{!QhPH|+5{0s!PJB8uG|N%A z*bQ0fELBJ8xEtJRZA@gO*)NbH?ND9JP1_+&>{$W$Yz)CUs*PP|Hc3V`o-1o4NGcgQ zs+{_N*!_&PF#jHvA8{wH1~=N4=Pzua`+T06yN6wlj`eE8f(4gS2SL!LET5w%{OkTR zd}$m(8n`L@GFdjra0p8NP2#{W>a#6Rk#sNtu#VWhv9l{-TJg#qu3{H$HybSHFczV zCt60JqWkF165TIUvEiSB`par0XE}%QXkFQ+0lXSQ+&pm@C%sB8zfC^;&5>~42fx-& z%wrep8dS1gOAxj&bK-i?=7eR0c+oB$gZZ$f%KcXE?SiGdT zkBb*=-bE;qBD|Ml__&yIUiT;S|CQw0H<~n#l##rWm;qN?M}(`;JV8U+tWq>~I>b8Y znFymNiYHmYxgYkGcEF5}#y>QhniN8blCs&St&bji4XD3{S(Ol7o^a7=xn+dpsBGtSqxb%jhNj+A6v9e$*vs2!V9BcK+TB7@w&@nn@d{htLZiQM(Qs zGmk=VZA|auOb1z6qFMO$4FzI|U_jVB5A-jH${?k|Pm^G)B)^=AvgAao|IV;-37Rop zCz2k>un*6r^`w*dt_a(cYF-~)K4CaC&|@8g#~N&@03cr0?%DULq~l;vLe^?itC2OL zH~jjnUhLFi#e|>zweNDeJP@)l7ox_iyw`>1B80;d{LC(ux@50`nh+C5<0r3>%3SXg z3{op8%<+J<7i-#Fp`0A78>)agTb7tX`QReQ(9RbIrzmXfCQDLI`eia-W+!ajVpQE5 z)Ixd6e?i|kstXe`0DF20hp?mwn|4g=oFgq75`FA3GH(CEf zm};p2|99xYg&sgvZ?$3DHQ^wSEu>V+$5c(S>(a(a&YtK_S}=YGmZ%8JhAKy%(MS3d zogbSAO+?;c2rjdjM%8-=nOvVKcQ7&tEUTn|#(xFI|Yv{GRLdc%??N|;qy!a+?)GpEvQc`A24@t+HU z#UHB!!Gm$S8qB7H!qLK>8ua112<^e%qRbC5tuf{LhNries41hV_-E^N$BGd`s?p7O ze^bg1>{@^h7z)?H*8OXdP9?z$sIWJNs*_$(d=e#uLTRcFkF12oOd#L8DC4fUC4MNp zQtHf9MYd9?U<(foI%Fk32iQpi;Y|E6x7yNz&I}C1QKCjhoLs8nVT2w_iVQWcUvgx9 z!kYH;O*>FkAa7rDZ6V=ilp)qs^K{lgjlv%|3Ef@tDr*yCp^BrFn|4)=V5fd&L{k)R zS(;&4A{t&iYy4h%axvt{OS&Q2=Vm+&hLV9&hRb3^6d(l5rBdd%OaCshP0&YMVyMT0 zmo!0OM&C_=Y+EdKm4%Mi8mq&p>RsCDO&%}+=_%*%!~ex4fdvLxxCe4+5((Ebq|*T6 zAFajj&B@=JU?I+Ou%-BC58}lFc9oqnv|1T7-6Z$A30mB~2f*N+&O#nGlDx}7MG|Na z%XMjkmPG0LE1sV8wO`9t&2+d6XJlMPkJol5AL1|gaoCp-%B7cC?Gg<}#{FP`k4QjK zm;l=L3WRz6KDYSNY@}2K?uS@$LvjE^ma^XGKN_U>y~AA@RQ~Iysq@Q)EfBM}M%$w6 zWETybO80rO)m!_Wem))>es^2if1Li!fs|v@_$n@hYUmY>zn#!HfOOHwk+-#?z2SlH zh+&+{Ts>4?x%=62b|}<)LnZzy!>R8WL6U_01dvEMAXtvfRZg-2L6#LAkZ#F6pbf!O zjoE%uu_(P!kQWCAXRYa7USmJF#DmCx+x~tzTuH5KAfg`q+`tCMY1S3LDVpD)Fs6>O zQmJeKYIU7%m>z3z4LN-q7Tk)m+^jY;KwgJhBUdnjis#RmBx>|qD4kLjv<4w3{T}&1 zPR0vZG&qB!eisqTYlAS3-!|EHuX$->6UpxHrX3DE^QmC5bH*a+?%1wO4?on^3SiZN zmjKb^(DFf*5F$qo67E56(%>l0$3~D~>@Tyx283(9#Y8FQp#nkFDQ>A#%R%?KA8t%O z#9-knK?&|H>=fN|Yxrapj}`F_%I<$8F;V>PmsU--?ZX$H) z{Jbb=59ah~2h2-BYceg@JPigopDp!Q^LsQDP zH%$)KJqeuwgv@b)7q z>xRi!NGUFhgAxY&A@1y5oeK6&cKBhPOGM4=N{@Z2x+K(BK6xtxpo{y1k{m24V_7Fi zmz$LcP6mF$l>-Z@E{zYDHj|GPG*?rNS`0_5VcE5XxMF9FC2n~rMBgAT$Ie&F!mf2O zdV&3IEB?d>~_R_mY$9stq+4Qi-EQvCe9&J?CQJ zV|`-DcL5`gYs*!1#B2@Up&ni*iJ9)?bxjrV)~1S-Xs_%Qr8O)0S6Fpu!r<`Xljfo= zyh7afNRm7!BXMUHOKhN4fufHNW=RV}Yk13(%e98KgM&388qO2XgvZUfAtkk|)TFov ziDUl*urb`vUnkOeSP<=cq89G4#QC@>B}@i*0_k0+U9_Ms)FvFd zo}BHKTLP6iE711)rEOjv(P0u`OIQjbQsqrFXBNN7Aw3gSZQO)TCygm+n*hqq0s?Pz z;kE(~hf9Ir6@+g(oBo4KmzUvSLA65x%x%WeaO4FQwn_c87OuD|7cXbst&nq~m}dG} z9I%Ls%IpBvBMhdJk~pG7bz9Ni9ORJ>d69@>(*HvTF!~e3%Yv&hEzF{_XcZi9iquU0 zuem9VKZVrxM*8RekHzXN73#jwB#F&n3$30w#@<31;WbEl>L71uXEJlhMD7jJrb}Bp5ceVaw1qVJBc4>iX-lO}P8KGcy$ogMz z(n#)JBrnIo5dD~27b)NTKXsP*<+tb7SOvYuRPOr1E!;s-rJYU@uTwN}Kcj@~%TKdeIM#4^q*RdZKzVl6G zv^|e&VaWFd$SIxtM}HSU9CqiK64-}I*xPm&I~r<`7`r@t?-%0({}Ac4algHs%>(}Y zpy{{TZ>W)@>w0oHvmBo8!QOx2iR~j(TJjNDzq~1uU^=BXcm3RXB>Gc5&D;I;e(c;U z3H3Tsd&}b09i>iB)-qaJz|sV@YBlMCSkSM^te>)%ai~!)G6}%VdUEQk8Aty__N_`Q{IcK$pfS<3L_7v<N>Wg-e9Yo$nc3!wcYX8y_Fi@dK6dub*X*iVcBkfc ztFm@&vy+?r9JFg5wCvjh_(h1LAn%NPLrkgy-8Qyt<3(9PGt^wQZ)^}&RM%Enr-`_a zfag=Ym}|SyYrCqeo{gn#_BCIdVm(vVncZJAKLlnD?u7n|xi4o~_%h+4c~0efk_V&z(&D1n}tXU3-o9 z{L=A0mRQeDkK$nh{8Nq7r!I~X^N@zL(yzoKP-pxZ0tuSD?^aL@5eU;4iPR_0zyt}d zczBkJMk=jHPi)~0^H>|85~}2NwO!}yuzb6$lX4puc^#K&xjGa1!RDXS3PZ?%rd~E! zfGtHFlVx0x% z@L5ZFKMlPC+9)9HR9wc>Eo((-5QDS5qR4@g+lPwy39<01E3cWPT{j~3^6r2&poE3| z_UQ)+me(0Au!c~wG+xI<=~W;79$zCPYkRAI9_%Ke%E5*Htd$x+>9wQ=NeZMAluh9s z@qONL5yu$Q1y_?PSSFkWxA z-OQ^c)q$95;FVD65>HP0d$bm1lrJ7Ju^F+S081oLMej@9k@mqn((qwfaGvBsLw$1;S-=4% zN@;Il$Mefe@|uHk3F~1JAGw1ab(aH4nm9}VEIF`v?ACNOEDHEf^|I~>C-_P=bh!hX z3nW(!>FcORcKdBkDbLmw5r)g_e73i$GX+ehZsOlw(koYw<2*q zu7_!x{ZW8xU3 zYkR?tz=4q{}uBJ z8O{7T5vi-~^S$cv@?E15y6PryV*d9nN zJVB4R%WN(mQh3Bi=v@%<_7PtqC-0pYA;I?T$wHMxY>Pa<`y1HjCVo8>j>h{?sX{9jPS zKLDjIU$e?BU~CK^&*%QOyR@ee%Kygu-nlo?fCGKQA$NOR**o>#$a0QOEu>+(C+OtS zQ)0~m=gzG0BpJ<93-IMI z#2s(^)^tLh+x}+GML@gXEel2ob|m+4Bu6LsGXa>v(CuT^Jqjsq4CXYmP5l1%lM${ z&m*-JI_nF=85($Lud3zvLJMMPF@FLgB809C#$Ak$98m6XFt$6Y_v66bW#&i4;Ufai zL_7~HnYrS-Vas->yx>g}wOGdjX~8U%oz$Q53{`c7!XPOA82>E!iii=wx#2hdD$hz+ZznS^NSCQelOtMI(A|{HRGh2%P{Qf*iT!PqjT?M>xRDc2< zs6#Ko*_Tt}arULw5Y!Z?Ih^Ovv$zfNvKEJgjqNR|0(Tjz<|=ckQyk zS;z(DozM-UqrqwIlWFQ8_kmkd`h(*YjX8hRL+zv4h>lc$NTt>NsvU zO0SuWzC!`Yh8WJ6O+_$BoWeQz@0uNndo=H-yv_@cfkfPp+vCP9%^rW?TfDo?nabYKm@$Tv>xX>EHA?h)V?lg$!q^niOVY zc6hGD5rT5{jks5Gz3p4H7K8G-%k@m^67mEq3+c_As#yl_r)<<};`d)ouQUIY7N1j( z=R_Kdr!=;D8p};T7dj%Rg!_ko^3*~0kK_albfhAKT}o! zT+debWoIKl1EYl*AHftc43_4@p`7(R9X+wub&T?4ba0Ub@mMEj?UoER|9tXfGN##OH4ka5 zuI9HN2(}NKX~U)SK(e0o363+Y6Kjr7Z$_;``f7D@lWF7yHY@j@u5h6ImfT9;6ze_c1fpBEfMrgJvJUwjpE9dM%6Q1 zJ;RE7Bp@MPNGYtFZM3CbOK@VWc=CwrSTNwUp*;g*E2wX5r&b~vM(7X1c z$)2KclU+Q!2LPoGH))0+S3|Q!8=dJb^rPNQNfym5Y#Q?WG#Ik zvw8}a{i552f4z`Q}AeN{GfDdTV3JWsV(6qBR_5 zFY@mh)1oB?xT1l*bLC>!$LAl9)@|wR!o`QHEPm~OP8Dmrt7^!{&+^9I8xw6awWGbI)U-3VV_pY7-xvzSOC z7r>m??*$&CTKjuWVnCUI4HsYQ9XCO$Tmr$J;T=02LEEFX3=-Sd^#0$4D?tmswF)8v?)hw*dkbo# zyHBc<>;B}K0xIX#Oke5`Pw(!vzh-^#mRe6wNWOod-W$aRV`I+MDlxbL`33Ov7kn>A8~oe zAuSTLA_aa{TE%KrbsAE!`(EZlZ!nloWXIBFN^3fqJXD0zJU>^6UfTb949+8MAOiGy zt04<>%^~9XsxkFL0Pnq&}8D{U3^mKC1Ap<=lqIp?$PZwYBxe3 zDK)Mx&zaU~hFddUx6CV#cltQ5@0~Y4GKHmOlzH*79FHkb)Sbo8X{&sn=retVycJCy z)*l1*2^n2fZQNL$AbP5OY%K1@GJm$6ajP~oUfk$rHk|nnA!OB7+ll=MTZBmDsc5|th~I-?aOO!H_KO( zH6izks)94MvS0oBhd%|_;x7T00Lb?7Cb}Y#dNOGj_xVwx4m{td?9a!c(-%`?z=##l z0?xX)kJhELXw`=~X|Wc#*O!RjHd7T zONB9o;Xw$V6Gpz{2No^lTo`HWz2A55i)wHLw>>LnxPrafVn+_otM*?wGS1ILN7D|o zeX~{QD$`u98S@kEQEJoqX~)U5M63j5-D=SMeT#Ia7fV5=y1{#09B7FX7gvJ~OuB@TqAjzOaj1?*8^YUu$w z(lJUXQlHC+;poAXv>UGUx}l+E;ZW(Tz|reP1c^1EwCc?d29M^Td7vERk#iP>cHL%W zJ#W&y>OTY4$@)0IZtUXsmGSgzJ$!H<-ck?M2@yf=?_5(0lhv-6DA~I4(zqfQON`Rg zV5U(lne#qAnAZ@R>{>idoIQ4OJF=9t zYU6VGIDDi@A#JDKQ*2?=3ML{Lx=l2*6GrhN#Orfq1Q~)xTA-Re0sN3;LmLKo=7oa5 z!l76^2`~r9iOf19# z52Ua&K+;K&W)QV`-g%OiI?N3B|}9N$M$Xc$BO|C zAyWy!{E^L4yy-M&Q)u#1n7rRQ`%@*n&rR{T*xOm3YYni}zTOq&2C0}8S{4H_s01BU z83Ga1kYs3c1d6F@Vozr_z|~EMew#-JL1dI&Y^30HOVY;>MDJvZxOF+w2^C|{a28eg zf|StXBDZ^i&6j0g?X%E&U$fhkF-6t4*p3x?Jukm0AlWXLJKoT}jzO6{mA{xMB>uBN zfKsn$w5&KVA&HU=vN{hv6loxYT0BECePA>w42tJH0Yix~n>j}(W+}oL9^q`e5?KaK zec?mxY_D|OTLStCj4l;C>+){{|184C?vri5N13VSoCCO)E|io?eqaW$KQscMXt*8_ zu_MYxQLkJr9@+|Zb6{Rblev&0LUf?A$K>`Q+*#|Mda@x*im(7Lo4BeC(1w2Xy@l;l z_bSK#WuoPe_C9!~I8E-b^xh@)5H1euysHRd5}i~zkZm;f6>5I$JrQ-JMpE>vNj`bU zTSud*AcYv}XgHb#VH8S!E_b2#iz{N|TpT-17`-k;L4K|Zz=NY;L1>^3h3wyyCiD!%CQ8J({-dust_A8|J^xjf z(~V8i;NAzZN8f&Tfh3jkUXkK~SmH1v#&e9Y>V&PRITJb=lOV#L^Lb#L-hVZ7E*Dk`0$mH0yc zmMP18;ycm^9=c_8++*z|gqILEMm*Q9*me^HHyo^Ft`+cxZ9~t1VD39~|sd z@7L`INopz1UP9t-hY$DxI7dTc+r&cbo=!i(HsRwOU-(U$HY78b`dj6me9vyImj)5g zaGl48^s$>-N2k0SBJ^SQIzpY>3t7)O1b)urh*_F=%H#Z@wd9e=?;Z|WEfa! zX)k@Xuzu!dp{zzw)8&F0{bE29dM6B*ZL!BBHN`Yx{afw=UWBS>SCqV8(0y?59x zD<@ihbWGgG8U+C|iUE2+sB~RgDvwi}Fmlnj9C7I-f$5`@exrm>0S8lt{1?GVG5{3x z#OheBMn{ivhfD}TB~THj2FD61*ipEn!(TE@c3Qv&&pmFt+a9&@*wNk8eXzGr<~?Zr z;d{{mN&#~|hx<@QRpeEF$YM~E4>**V$F zNs&$^j8IRAKEPZWa)Dfx&Gt|I}nvgtGa+%)VP&`UhjxTd?*SkUBnyACCi%of1RJaS?iN_n|VD%x51IrMf}h zcMFZ{b5>OB+lM&TwmEvljvg7tCJTYI^K&EGyYp_!=}zxGi$Y1j<4OIXorD3H$YL7_3Z zspHxiO2xI^VVM>-U8oj{O6~1|I&cz2ogA#%b+B`5AS9!ANXNMiejfu1OIRNS!H4M3 znc49gB&zSJ`fpr%{mwO4WaP&4`7`$Tx6v7pB~#|uhasgW2_ZK-2%hLL;#`yRy(1}%fXtou(a1E zqaX5=jr8*%fEzSUhJu7SYqT)%K3(EnQ5tjJncJJ+ceeLnWFk!(9S&XwqPle3XLHCo znmtxsqF3Lx?&aC|K0n_z&nGeV2s@3E{akcMXY*Ug(6LC2z!eC&N!%%}^Io-dxD1yOjD2B3sG$=ax>dxfU z$qiD@R?*?)6Vb#N5=I9eq)x}&=m#6RB`Xf4{x-RO=>GsnJ3$FCMcY*QVu~}qlwb}#uwUU1@(K-YodrZx4hhNrAI!a2)r0*fCOpo(uhZM6b9;H8 z-q#Gu(*_iDtn+eP3lAts#4w0zg+lacg|no>W{JV~HQJ36N3-*_~1hp@^u80Sg^&#ctcg?DRw`rQVbOO7B@rSNqlDI3-SSBw(Ie|(04V|-mY+OJniag60g|f7~i>j zRR}vP@{c>jr8H=h@)TIq#J>hY+$KMP#19FoNz~diQCM9|EJHT_o+P5}n|H=1Gd~}q zRl^0Q0}TAt+;4#A?1;Vw4A>Se66Zo59vhP0B;E7td(Q-bM%02Ipp3fF2a6kU-e4ut z8~dyDwL$9xdubybi09>%0a|}m#hb<_lL(KLu47YItUsT?qcf$*tlPBLTKRTT*=zCj zH+<{zxhz_UwA{@nkct;IF)9HhI1P`2ON2dT0TFp#9s$B9+XFh(5|>E=MKwR7-;z5c zU+7MI`fIh_sEtVJ0kQl4mfkPkb7qdr?BcKD_4+s+-D^(89YifE?>%yNty~;G#!6N9 zaEdu>t?TT)I((mVEmd-U+o3<4d9&C}C1AgN&IjO`hZ<#DpyTJquu~;jY%TAzi#rm@ zfcYi#UnAx@qC18$D~3)w*KF zW$F|1R^4CmT}rI|nI1dsv}v+gXI2^#l}ezAs*HPc;3ec65v+m5V-EGR)O zg1dWD41`p-C#Zy^PTM{G1FQ5vV!0-)1?u#R;~jMhtuvjf{tArjd)@V7Y=-Gdf4gQm z;E@Z&RjqE2ZsO`;*1Uw)Rer@_Hb-4)tR-b;8`Gu4@&3kw z7kdK?u4*ts#n`qh7M4)mD*@3L^?X%{1g%%l9jj^s!oAnMy+X}-MTo#K72%#^=H&Fq z5z6y@2rZ7pSl=n{O!=e5$yf0{o7z6mmAVuVpsGc}1q%e1f=;DIMY;NN&LMNVG3Wpq z-|ND^NT!B@4>?*zpE|j`m4K9H=4_qzpOwc1ZcrE0GIxI^(}%W%^-(n^nY4GlnY1NZ zP8De}^L=w0xv&)jo?UPfwe>hBYDc;xIhr|`X&O+<%$K>!*J`QZ0CO)FDn;zbx`xAM zgb0#aVy&h?l@AGgfTGeLtw9o1uM_Bln5)$yY7{Y_HFBq&1fnF1+$*2lnqY}iEpXQ7 zQ;Sz~*Tf%yqmXYixl`UW+>c6TooJn3RF;^Wb@8_EEfX{u1i=q z>LV<50n!P8Yc7%na5i;(L})+2b`QuUsGb!u=d`2Zd_7?&jooXi>TG9T=qj zQ1MHBRla%|JX_LT@ONzpDo8~hxSLnox<)Cd?_rfddi5K9zmWh>zLc`yXoy@!Gtc9a zp$SSGJ>ZxX^jWb4dSel`d-FelhXr))mf5Q4uUVK=x|eE=Wn#M318$OV9$#6xr?`|H zDrkU+Cpr|SV9At{*)fWS{}Bezr$$C`?!{jbdWk0vTgM8S1JUvv`8Z^mjj=7$f9{_Q zcP6lta`wo3;S;UoW{lAo4mWrs5Gh8v$cas;Bil*ZhEQ^@9#{YwVYuKmCP$Q|0AYGN za}K28TOl{3m(85(yA%^;jZs_^op2JU7>)oT*Ew+WZ(xK@VoxowJ^$b;FA0GWh~K{( zhpgUAJ5Wna&sdL))nI=t#h^w5Cl-A@B;ll&1>Qy>VIdt5EP>|F=fdvK;!d&zzQy~~ z4FG)rpsjnwRi|#k*}$S!mcL;@Nk;)4DFlkSaxzN&DMlAYxqP0?AFc$Lk)j*b86fA7 z>~EDo$yX%~RTV7XkR~n9DF_@*8^Xjf3dc)DFK}DE?wmp{>yyf#s{9;q-i-F9)XDav zi3iEK2z6;;1!0kleW{or%1OlD)Ibc92~MLU6oINNkP5MCPgp_!iEB0fqbUpZ2NMqk z17R)k;9;1}FW&-D0r*_sEs;cbBQYsh5IHJ%pFM! zyW5y+Zf4a$^3F|HR{$L=(V)qb5#Njlop zH6Bx(8KQP6?wv!~gf^aL6iNaL`&w%9AP8koe_?RkTI!vIZ8c|@bYcG0s>lIzvo5mW z{i85vXKWHgZB1AwO)qfpL{Sx#B2gOk)pi7m1(_?t$=k&^p%Fbl$a*aCFku(#|Yq3T<4MEU@X+hOovM~EYhZv$+1 z3f+$%2AO`IAllQsl%6J8N&G|S)z#Q+my;(fsUzT3453q{DJ)qWw!s0#-;UxGVQZLB z2Y;!#fpGEKSZyigcKgC1Gk_uwZ5Q|+(h$2Ti}1t2e%5NYHQq_z(b}6jzWvmGYMa7# zAPpj9Z25rvHXvkj@eLGQj3NQ>4483ITGaAZ5E5w)Jycw)q_Zc?>w94)BznBv0K$%f znl6|LW@n{6;Wr+*R-oZycOaeqq28qp2{SbMahv0Wzd2|H@q+j)vEhcp(H2_*MtWUl zj?o9CE9ch>omc3qaiQUS0?42$>=Prz(&;bv>N?+vLB&cC&JX;h;xbaDm<|WV;{jHI#KK{Q zkT#dV7Dn@ma0p!+w1(529Dgf10f0G0DpLjRa1jt25}+9|9XstmT-rp>L$A(xBjKTM z7A2DqH3NU31nIUgsl}Ix1++M4BPocA1x^@D_K97DfGwZAhff9=UIBQLM2Ltsn5i%w z0mnqJ@}Z{;Y{cOZT~a_aeh@NONjbqMrpZFx-V@#>;1h^QRLGOGLQ7v<@d`#ch{wr< zN@)=<)c0|!I4&<4HoD^`7zM9D#LECp7HB2x05`f4vm%+&NUh9~r&`6%dy)8K`k$Ze z5D#5n0KV2Cc)}FOjffzcSO)Z0?PZT>rUfkTiK+}Eh|m$d0Drm0mt_S8TlHD6H+21i zRkT3iiE}{#Wz_P$zK2Xe9AKeroPk-z0&LB2KMI7jz{WAo?@ewVV{TpQ0C+qChu`F? zK8kj528|fOGqzSpN3Z?v ztTmXcK}aSD2m6)9L0SUfH5tw`N6S@v=NX&d?4-)PQ7`(@{zM)cp28Vtv@qLUTRof9 z1qGbsBXIndgCerDpj*g;b*i||YX*Q)<;R8~VB!E3@v7+y7)B9DCmNUMTa$XZ(+Hqr z_e1dZa{NhP%@wJEcPolE@aqy3?jJ=-9uUKVFJWEnA<5>f?HjSte7=i=+=^eYv$x3~ zfC;AxmJ37M_bL2Z0^Cdm5W*9}5Y?^Z&c^x~&QDV5;hzSah_oA{D*OsJC3^#Ca8WOg zd>t+Bx=PJeu6i~yakDm?^9=IU$qGQ~s~N9jvS&(3M6@0pwc0dd#RMv3$E(1e-qC(I1FO_orfk!;a?^HEwAKcOgNm_6I4&2sTn42Oibpw4kW#2h zvH%~eeYu4BGGNxV503^~*o1bzLa6x{=VXw00d=mI+uq)8K;3>rQ&)_?Im)7z8DxdH z9|#a9{K6-3AB;gQ6-TynnisyM8Qx(UTmw~<8rW`s(Sz5TG=xbQx8WenHQhZcv7#9F zC{WD>bGtWK|6hylH)oiFRF2HJkioxkBHrRSQ zZ3)K2omhf-M}Gj7cZS6+?F~wf^;iTad2b}z?QznThUOS>kLTcs>-KsC++<#Y$!!#b zHio9Ro2tmF^!eYu!(DVW_}{wHnJ@gxk^A4%X{YI!zcW@_jKYN0sR_l*6ou4;gqC8O z$U;gLv+IzO6iCcU%e2YW6|>DmlHTf-8MT|epbwKv85 zhvF`?9tJsv#sc0c-0mxTon4f0EI{NcsETf|KoJw}yxXmH}(J-&3_zM_k9yT5SK-D8ydvZMa z=AQG`iPu`1dl1c`XP!jFBlc<8uFe9Qk6?Jxm62os_7Y#P)-sz-9qf~4!rvc8-p37w zf;0FY1kNrX>w_(_YQ7nJH}=f7ebJc>W&9ew-qdim)6$Ad>sM&Fxkl2x;hK zIlb7PJBo7Mul|FfN7^)ge+}1m;%2{vQ=+LrtSQ0QWA3@^z)DziQ{3E!%{fy0zT7K; zXD(<^pT$THX^XRjMI2p!%?im?EZn{a5F( z^+#Q8-$Ra5<*S^F?5>OO#jz#j@>)jAva0FT9!dWLl<1aSIPP`2iAGktHe{AldMo*c z_m1N=i*Rp)-|5bd`H2zP>k4zFG>fd)YPZx3xz0~7Pnxa-HkO0GKX9DAMY9CKb7-@o zysh@~+!+?VR(YJHV?3`?V&yY0{ie8^*iFHyXVu$zD}Eo_8{pAU^`FqI>W(vA(8{nv zxt9Ds?e{?pBs6XpoJS3XeJp`G|1vX2W6z~ogIzT2WpTLm`l_w7M($MhhZi+medxSy z+B!}Dn1)BqnfGeO_piy323)zL;yDzS-|Dj4C;5HxT`88%JPV&jaYs`wH+So;dpVoG zX>T9&{&BM;72YqW(^sC!y-%Tzh5I5zHLoUrl$}0BC_KU6?H;I#aOan1#@d(sTtRJ> zqFddW-S~8`o^`WH(wZ3gf8QXX`)rt(?T~Suknme?^|rD=ZZ~eP|EwP-XZ+4wMM{72 zjrDZCV5RcT60FRUp*DC}i+?;*IGdZ_(#0LRL~gpAt2oCkjpQEUc5tnF?jpw1vOoCH zFN0Vt#Ti|2eBt( z#KXCXCi}lB)LwcewN@3ndLNI7O3(Si{8FE7r0!e4@29O%|J=WWs6O*lZhAgSofNE& zB%+%W*W^{qg@!t@yx=6W%JBTvjuELH`aa4(sDG=UQ5s{5zCV z8>;8aXKmI7wSgqNd2`*K+*`!{F791(w?bXgiNMZ41PS?bQaFJ(rR>?=>#6TEHZxA0nMzy9ad zHTUZ)@0R&pOli&n#-;1;bI<3&t3RAhDjV&+MPK@9N^0NizUxx%Dsq!+QWp2+hflBG zldP|Q$<+IHTF;2~UEtRApES&=(K2aXKAq>zC&>x~@>&pGD0Ke`<8fZ)w^>#R>* zn<`5%Rd1j^nkBc8Ka#xYpHU~mB4`yiFy`^yFAP6N(wI-$UGvJJ6A65if2534zk640 zyEFv=crMj$qVL3}NbVf5E8T01I_QIFi)U+k5(d_r3q|TVd>_cu$NIkpE_T>NqaP}o zA+BYz8a~tX;8da;d*t5K#2W!f+GRo1& zs$V>XmdHZ>J*kc7RG*i^KqXZ=E;%b-8kBj+r{MxA&5w|r<~^?Txy>GbuY~JUHe)*?s#oa+^oZ{Wc~r8Se7Ph^ugCOnkK8Se^*hQ>|tG;@h=S zx;lGS4mV=*OFvERTmiYxr7r0-B?D#<6E8%^7cxLsg*MDb8Bx$Vr^*INFm%y3O-2ps zLmPhTUPX_LUw`A2Mi{!+{~67b^&{2$a9kpq@LL>PT-`5&Pjze>=T;-kv%Z;OCd7W)j}(tNTsE0k?E3RDkeAQe`7x1ggdi|VWJh&@d+0N4J!o$b~4 zAs3_F(Qmw%S7teK{;+*`p2AM?tnGq$e4-u2oxJV*w!cYG4H)Rch%|e-bX9^j*9)CZ z3DDQX*(1EkzTG3Zi9XHQhsVF&g?;?;Gd|9gg@+l!Q=gI~yi;bpa?K5xbBa_Rm$+M% zzH${)d(vrL*Ur}Vh4kxupXZTeqKht<$Q`cHbSpjR%v-t6SG__`-FNw#8)a|u^#GTc${~v@g-cl#Q|`I;*t=Hvym(bDnibVN{IC@pvp5o) zM{EDYva&VEYdyKyu-0MxdFD}QtYbIVmg`G$l!+l_C|ub$3Z69eA(>2hcC5o^U8POf zpE|`ZKX!kEh$NkGr$2P~+FtN}vypJl&CB#@*@V;7c&`cIDqH-mbGF;vla9K!agx4{ zllkujRe-(htzR^|il+6mp0!4~vpWF+r^19>on7X38ZtZBg+s>sI zl6qe^ZPO36*?#J>qYgO2epgW5jhq(Hf#rMwDPptNzMjHxMRbcwQAa^>>+}(Q^k%Wp z5runrL#Bg6zwM)bdbayJrdX?YfrmS>*S50Q=&X3AI5f^UANGE0lfd@L!U44 zETo+hP%#|yd*I2LMM<4e`PA&uX*neN-w z!$=Wsv&|^G$*k(w{UgKwn)wLzJy|!ha|KTbe^Zx_Pnbr>Vde8#^mjnHjxJlTP2A9# z^k$E#TqpZlix>|>*PWF5buV-K5pQPm?>@5qG3HY_;nzl1;K68(cH4~}{|nZiSH9n6 ztsb5op{a8?$LNYl`}nBkCqCYjMK`)@<*Nr8A_f=9KU$yLdo||DXUs`a7s{7;ry{dY zqp+!Zd&xc#pO?P>>Se92X?G#?QY;rPPqTO2vt<3V_jkTM=62jKxCU%bYZB0z;V%JO z|8BLfHR?-k?zmf@dRw@&oNA*JmdNBUOq=h=t`VbmUGaN=W0Def4gCd6A{unEbp}~| z`icbQbUbBS(^yTIPpXVFcEl}Eb5*Uv?2#`ph1GaK-a}=xzelb@kZsrFy&VnOs}n6S zPTZpI&T3WFk3Jk^htzwpqxJi0aXDQt-2;J9Uk;=5+XQq0^^Q_S=5n!?W#lj^R-cxh z-tN6o%-3W))TZeigQr;CQbd@enO=$-EWEyacRvW~Tl?ZgyLJiX@2m{dBP4dTcjFBbSM zKslTbW2@Uj!2hSXoT=Q!FBQxkuIVoL`_x{HWwgU;ul6nbFFwABN2_8AR@L>_17{tN zPoQcZ0VVL!qlwRI0vmZ?sTmCv9~SB*3#c{SHH@RB)nPndKFsLyWSc&13@>{PJh~6? zd;j|7wjW0AjjUDX`QEaTT>DXaa6Z1B9%q!GWg-M1U(F1gHq>{?rQao#VQlY(a*THc zKIS_ce$A2z<(C6Sut0aA(v#NtNeun1?0cFIZzq<``WezUzlP;oe)U4fke`7X% zvv@=Ofs0^%*;E?_O2$yq6f+g4neaDEHAHesBDKQS)_1J2w_LdzQbyeT-FO=dpC@mM zXKzOuBrkMHMd0CRYVG(9v~`XmZ;(qu2YEOm$ntX@ep+W$N>GD}8QDQWzDXp83V`%n zgkp6x9%U8YUmJ9Fu6v_rwxdP%GO089o@B6vl z?Phz_w@(kt3Lk^?C)cw>zKRv3{wKipW$#lYKn(o(OVAFpy#{i=IcW3;V~HsSly`sA zHl~MvwF1Uk{@*O&kmt)$&$e%S(xK}7O~C)_`jX?i6Y)!otJ(7CY1v951zc?W-Vdt> z4RKb6R-ib*oGq-Zm_w%CSD}{^id8DV&8ia1^tlS8Rw)i;G&d>Oszom(42R6T60uQh z=AZxw!V^_L6+x_wX95fug+q@>0Y*SYA&oqc=GX&I$Q7diBl{Snf7$@C7V*SR41$coToAUndAdI=T+8^}e zH(qaaO2Mr1ekeh)ybvYgm5GEy?O_gl3?~dk7pZ=yXB7D5XK`0@!*Zc7%+Q9%<9?jU zKF&eZ0TOQ1BRGK^h9TSq^s%$c+_uY*0tZkrim?!BhaaMC$7KSIK+MH6?zhJwk}oHX zek4OWfHx?RCtyuPsuu^$bZYanG-D42M+qqzWkyecBVYlL()Ci9jl{-mRDcH(AU>IA zag7!LLLE4`B9L&CZiUcW5lxDG=Aimes|%`xi^4qt1~z{Xc*^l_h+C5W98pVFeOT+8 zbUSZauTd69MFen_15l&`ZRO6ghz0<*7O4{yVi;S->1cI8GJv%sJ{3aZdK8WD(BWOC zd^Nco&~`AXh^PQFs|bQYX94RQ>nhXh$7n-|N;Bv_rE{x!)5IP%g{mWv56k?W^1O=Kx=jx2w#WdXa-9FT#lAyT+`?D3|w5lxbDD=sf zHH1a8Lh2FKO5^xZ)ghNxh#i zUA0G?6L?{P91y}PkixU1D&TUbA3_M4fT~b2Zs*$(V=0uwIUzAtu?R3pNUJ5z<%@_D z3}e7JYDwD)gIHl`065RA*0$(N*8mAy5N&HLw6p02-YPRx^)rwsW8XzcBV58#ude4Z z^sF%c@CX0p0BuKOqJXiCC!i&su`^iVcpXFh;4cFiEip zS|mca=oNFbiv+1)_{$C}vTD?Z2bvhSKxHEC1(zf(9l1~m#e_5vCGXRg`Jjc%5%_?= zS;7hg3Va(vUSbR31Xg?pB zrYn;USn&$R0mr072@8|RgR34{R?NyuQ7DI11UM!ERH<2JZCa9>MB7WdE+uEPk(S}l z*8uff0^rv%3|Bjc4S(1*6elUch>6)KC+V!nbb=^I28MisSu)wAvk=LN@D~n76c<-P zjqA_ue3EY>nW-1(6olEpHs%}bWn@;Q9!w}5Bpg6Gy%={N=+b9QptLT zFwy##n(*%+hYIai{vzx(Q(5K#ughdOH!K3&MYRXl8w4l^I?Svf$kU!B0bsKhGeDAp zn_vkdn(W5u}& z)jHw9B>h4BZa~us8AjwDERJ?ZTv0gI`lAqa56&nQ;H!E;FG4{SAf%drAI0EIRQL+Y zW*4GRr86mR3BNM!2>RnXT1Xa>PPd4g10-%hBpB(e&e2HPoish@o33(9m_W*IAxt2S zH!3)Mr;d@mC08tkk;WP~X43iHs8ci3!UnnkUMw?Y1Md35`UaqA(+$!vA9YVh#QV*} zpe)5wZj`5fwKxcs3fzC4)JV1x4(50Emjp)_y#l+mkmNVKfqYdd$Al|RQruFBk{~%? z07^#)9;3*}J{5O|Bxn@9fk!aw|VsW~_9Lf!7a%5Zr4q^ePYKZ$BzS?h@vJveFN>xOx(gBN1KLsI|Q0o#y9@k|93dc93Q(O8K zaZy%;3nP*esMH79q-I^ITs&UIm1h6zsZwGy*@C|sxWaz3dzP!&!V`aPMrSzJmKcUF z#d|N3lLGJ>Gus=L<_{GZrfDW2skEV>F{FpMWLC00|0k zVS~U9AfK>{IL^gSgn?odd^&RLs^no^hE}vtsvouNO*c(@5QInq3FUYG03Mn_lSy`t zz+^v4uWJ%3W~nKZg^YYa%+d-&!B7|lVhnKra$XrTON>K5pdu&+DZ~EdfF7h;B4;ZE zgakmT-P+%S8fEI6gSfMgfbC5M;Lb2j=3z342qMtS%69#eohF(DF;Pr(gGnCFb$UY* ziNMMcj7kxAkpUJY#I9RLXQ=^9NH&;JSSpTGf?**aDUo(N$aBg{Yck3RAsC`H06Zu- z-8gv<(F^r@qAn-te`-DFicGONSS!8)ZbKUD)W^+ebIw#P>6+xbScIP<*G~;lRp`IX z>v``iEQ)}RV~PJTCzq z#7Kn;q&nRp?JAX*!Hq#H#eemOA&y~dx}P=4Dp~7q5}jie1h-j+E}^J$aj_e_vxX zfevI*;_(Q*qcZVa0*xreOo$YK2qg4XdHy=jG}#Bu#cP^D#gv4d2LlM(+N1Y(qopI& z431Xi%PAtW2nDkqfq?MT8O{=_%oz(vfQoIF>5vM851Mw?N@zk(n^zPVC1;SGoQ^Zz zXcB-V(59@n0UTAnIrkm;F8;Y7dGh_)Qf&P*sMN$Y5K6wqcR|4rYt9Lc-^ezU_d?CT zPyRHvY~u`~|Cp#kII3Nl?g_vWg&{EB=EGW~5%~CC1FIk$xoA~Gd-Fx;GxDyJ#V6OE zyg;*nFUrt2Fm=^g)_1aV3keX-O__zt;RT;+-Vz)i0Ivv4DKLwy`dRt>L=Ltle@RO4 zs&@?%Adzm+-MofQ_}aq~mlPM>J9FQM=n;$KXs(RWckg|`@=7Ev_P{cep1 zpSngFwk<4SbSMxQ?TW{Hl>PU7fvdVh(`scBcu*jIZ?rX7z2}pjqa(ehlbpMXZg8Sd z*=9wN>@ph(-%olMB(J%V0mp#>DyFxnQn#@y6v3qRq}}~kuk8<(x6Ri6Kt7BP9vF5D zg5F*ppQB1TlLdP}G{Xntspiq)hBt%x)f-xT0%C>-LDsY_B!jlTAxgwrH6n}b?epGk zo7a760<6#>g!8`k?V}&9zM9JIxu;vn3LmYtB{dSCMyO_X`OBY4Lc>6oXp`+ah%MDm z+xd4o0KGM(jc}~_So*e?sfSQ+$V5=FlAXj*GFQ+<(G%M5#LpUfWH!`ISZ{kO$EzGV zjd>LB+)-@Uc$#c}h;=VK9YUHdof~7``uBu9Y{p8WetT0DQwI@46;+J`f#v;(p5Zg% zg1*@3oTysw`4|a{hsSsN&cO7I8xz?B10CgUJtZG!;R^Y$iX>Oj`!e}9n9<)Ar;O&~>373Ccz;@a?59s- z_lECEQPvr}sx+OF53=ko%B?&HzxfY0q0joPn^ox7Z=W_dp1AeC7H5|Gm(^NyM1R=5 zYxg2A7lVo4|Ix}Vw&ae5paTE^uK(Ye^aNjZvHvV{{;%`jm~@l>lS$Wby;S)xWBh+( z(xv4U{}+?q74SCkUgAkAnqm-GxZFLQERtaHpS|2EY5!A47xgI`Vf*((@qgd{la6ls z|EHsy(wm$9-*j}}c5n@3-8MEJQeMIkyv&dt#;5!UNZn$Qe+O1oAY>i|)m4dt$Vfm` zWtpHnB7Y#t2vSr8{|WMwtC`{$K+x8vvXb8ecoX=>|L%et;-jSjhf zI&UA58Yps)9w7aqoAUtaO_-BiiW!5Q;p_2yWh)k2f6eCV=H|xdP2ks-B-~Xq( zUgOQyuJPtM`j+dXY4Y!1^||CGXXeQc#$){B+X~8(hj1sam%W9K_ouPfzs%1|o1yqL zXnpJ~-<9^9o|n0eRo^`g6%S)u^FVF%V5>!7XvmWt^dDr%V^(4orBJ|GoX5*`W$@-v z@Y9sPF_&7tj0|}U4e85Fn2hymv$7QaF|$-DNgeOXDrs{csdZi2WH1(eAtU@jtJ4%^ zDSiGFW2kk{HQ{jhKb7WX^DS7g8s9tfH$4UWbg55dZV|yMIWtsu(4z481R`I{QtZzzE@+RH6OJlmy@p%azN@b z_1dfj*7uU^93{Cqi@#Lnr78TSVNg+Kur}sqr(s>2JL%XnpL?&}TXg3NN~>2}Pg{rK zP4K@Nxymw`^hz`g_-u=bE9`T!@YH4Ge3uI+WMI8>vGL2~`KcL7_m9DG`|-fXw(@us+}!rO{#>lQ@1B9r zxBQa}@b>&?iu}sxZf;N0Ke-upNpU(|&m~5`Aq9uFMS70`{WBxyx%Psh1)|AfdvtLSntqi#t-;B?Pr*j#Y$J?`rv^Dlk zOUNi632d%*c5Qu?;@P`Gt+tR+VAEcYyXnu%+C7$ zb2C5h?*}8$9c<_4J>-2q)7@3vyu8oX(X74>zFS9W`+2W;uhiz@&P=}nR=X`LW6v`u zq*&CEii9?q_BL`?J{KJ<@z~gXl#JnMY3+=pzL67at6gNx$swd^wEZ)9d;<|Pi!N=%EEzf@zRrbj0|&Mjp&Q zTVFYtayx#HnK|9OIyqlwd_O-wbS`V}g%f=pkiThYzgd?Lzf-y#_A)|Wh1tp3&BOlj zG-_J2ea@DYzZV^%mJR3Fr=lC=BxEBerN`+Wwp$i?t&EhkZbheqzvyi7`fvW_(7)mR zp}%3sP0nvaQ)RHr}FbKZSre&1h={425)y?*~<+PLpL zJWuC`;jh8K5ANUgyWM`zjkhC@{<*xqi`1W=_eHsVczkEQyRLOjMLn`!ud~lgH9Cf1=SdvF6ybtN6W7Ty(E1usqm93;6MvI3--r6;0!=5jVR>kTfwQxJA zj7;C4+%zmC?jG_@0|9^hdii!}PL%3MbEDBc8Md}{bhC3#%t67uxwWyasimQvnU#@^ zGZ%n_fqi*(E8|*IOT#*`L{qATh>-3$Xux8F2oV5c{EGtq$@{-v-_{f04pIWKUQ}>n z#!wDeY>eA~GUJG%EEQZ09x3|@msC49Q3Jw^k#@R<4gQvNf`eCoW$!%V@q`y~N3W^U zcln$wFou=1n&nGeS(OzphC|EaU)I3#7&P7Nr#Q>cHChM9f$QQzs#8`Ji7OA6N0a?r zlZH*zvG#UnZz)=)SVmUPT>Xl4V!3<|ZmxFU>bSQRb%8ZmwFt}y!`DVI66t0lee~up=FA*UL%>j{^3Gg^s@bO?TEUV zJ0`jbV^;!P7$+3dGLXJA%5sw1e&Zvl()D9{x^9JBf%{?mxVi0ePmwY(u};hV{b$rFJbvxWk$1pOErq*;Lbvwl zr|>8^purB6h3tdh08`g8 zOwDf+E6>^PRO)K|1`Hdbk1$}x|n8kDPxW6Z+O_69Ca5^aoiq0^O#JoL}LD?5ib#G9us z|A-Pl7tEA4PsO9{u)pmTy+5Zu9(to+IsIrc2|N7(MG&sl(Ld^?yOf+?%%z^wNvqX< z+p;_6TI;H6V%PV&>2ue8jD~IbSkt?}XK?X_UBl~hGgTH(I(klp>C(41nM)a~lYUH= zjr#d2BVId(lF^J-DL-W3s#kZ&b)?RQMg0%X?x{(zaLp2M+O{ff+qP}nwr$(CGb?S| zwr!hT9nll#W-g}ZSL`qLyPmbqVW8j<#xVuW;t6OSX$RM|1bWKj-gUY4(`#i#{Qwx5 zcG5t%J{I1qT29(NAs9oGt@_nSm;g{ zqTX-IcHE6CGO&c5{dZ#o*hx+gf;NSN+-QD`z;LDpWRX$J;Q+rdDFv1TbD}Fg%gGjo zwS#P%LqFWoo+xig8^^5hK@MJxZD2O{5wQATap)77qDZ0D zXevhIOnT$_SP1R+spq}E-J<=66a2?Id{xUt z!Qo+AjRL=w9F{&++1#WM2b0s?wYW&>zM;CviYAUcg`%uTtAEe1}~1VqwRO)mdoaX zD6dyHoB#w@p>~kav3LVc4xM8#b@yjSiwZX&ehz?^J+Wwo0~DN1Qaz%-R5aES$gO_C z35Dq)RaN~xqc)O2!Sj}nB|rtLu;wfj#D9d$)@IiQ8=_(JrxAFWM>bk5vxA!Rg`zaT zBbyeY=ldCxv!c5CAj!zOPL+Qp=sY}sACEr7X#nC9Q8aw_YBur>JKs7Baiwb&To51S zTfydn7vP%vD~X@hm#J8vk@o>3QJ&+w>84h>kkFcIY7c}pR=gHLN3B&vTX*ezM1om_ z93Na{#jGpFp||_L z)npp3?HsIEO;LKmYi}1FWmF-qFWAeA*hyDC-}$)s=k`QV_4P~k#GKOrqB`F#H$_S+ zT^-nfXE-oe?JQ+-#>|)V!SX9#hpOKpD*bSg&BM6RejskUpB5?h%0NTJq5)L=z6D%s zlKUCJ4$5obi8M!6-i~H@h&dhwo?~q0u$)pwVdu~~MM01Um$*l5A&Xu`uw#^pDqnmObR4Wqs(Nu73>Y&Yh^Jn~n$CXWJ zGZU886Tr?r74KQEzDH+|dYdh-wR8wej$2{*6}BseM3KE3@iqQVUeLt(mT($zx_@!O zi-ni6^8qyqn=@TGJbNUaqm}3YKl0d%Sqv}9vd655#8c6#au@b*K65D^#qWlBrqD5K zbu03lLB&hws#pnEMI#B51ekJ9sZ!;S`+nN+=!W%Mn57d6 z(Kqad?16{;CYpcXl5qxa3Wm(y>K~qulXk=v7N1W6_=PULDFZ=vmRp(cy2BJ2tQ2}! zEUq(zsKb$fZi}r;jIDE`74{peTE~^ojZ0Q~RSd|vbQbbgU7$$HQJ54I`%pF<-n;Fn zJToS`2B=y9RI9)UH%u9EXGs&V{EGw!5SDpkOxADIW|ch-P=E=Xi*xpMwAOcqhqbmG zR5W$=4!(z?n!|h+?Ihnpx|D8UO1s}Xy&g+l?HX6=|GN5(o#SZs&qF|L)@r?j+CyEN zhZ5UVLh=PenwDY^F+jWHPCDlYK>x&Dc{ z8uiu$)SvOi=6*N_JfMvsafh=p%Kf`UHDod2umR9gLS^*A;nB{OJ3(@3zX#2%B?Z8Y zDzfINz@rnM*f6cJ?JXYEMILqs74Tb{B<7#Pk`O`Xgp7`^Zt2t_1T`h;L2SV;MGKA( zLON;qOkA0Ue^vsrT?p`)-o~KOV=pKYV|!s8h^tCHFMuCDiKlNG=<&CZ_)-D$9UA@V zWIzky4s^I_3dW~!a6wm)wWwfPc<<+JT2$M-45+S*Tsr3An`@^vGuK`WRwUVLe3&xr z=6+x9OY}A;he~;d+^+(u{mzqiAH(L8XM+$HOCwAWx5PmTb*@AT1m#a`g2Acq9hrkd zA;J$foB_bk0G47T2t;>Yo}iY}JMrc=q~8yKDF^H#Dd?YKU)%5%KsZwtY#fk!gfPmI z^^VB<7ASh9HKF*mhVQJfEUJQoQx09$&$LW;&j1c_l1C$ki4rw zrSj6TqlB643_@j4o$Md2%7)~svxRm7n?9YDo6>(#{o?v7DWoP!+<(z z0D9O1;>I*_!Zn~2O=~;cBV9Srx~oQB#x>8VPlqqC*e4w}vES+8EJJcak_g-u4@LzX zTAi^wah@}PqUWJuKn(cUXymt@-;U?cQ}h9XD0O3t-7XUhkYG`3=(sg&^2 z)qSD;1&d)a1ZrX$c9M4I2n;wod*ZZDd7jx|x@>ve7vP8`7&%h7qj^Z0Fez^gsU~nJ z{Yj|Iv#K7Pnn%Fi9stQsXUQ5$f86D+bM;&@}{W$fA`RYU+v7ignt zEZDht00@wWNYL@Lu@XnpmmBMrK-gCX3k^iTwg&bLH}Nxj&Mv-aGd1Hsc`U#ZWieM& z5qeUTnkL2?W()o(a;haJ%exVai%`YvCmf}h;*z4?)7et48%b+6m|0gJC*|!{!FJ5S zqL9-E51|Z$VC*PO+(;T7hI-g+J8~}Y=hW4VI ztPX6C6zg09^#=xylQP7Ri9iY~n@ofT@eq#~ZJx5g$#E%$U<#38Cp-u)nV&|IA=3fZ zh9(dPwMu*a&J4vF8bH*7%My`3!_lC56!ewsEjLELBpDy6X!g86!AikbF))JWikPvX zp}7XJ2pJ%{0%%J(V0$JkOQ)MU5n(0*B9^h5D3IlJZxJ48ki(eAJE#Bl!={X11TdS3 z#5O)3Z!WG-*nOYGnHoap8y0K6fCK;L5cz6W+Z>teocs^cxC$uL6Bximl5TTq{&$^j zE-NXZ^MJn~33fXYa|eTa5=GMWu3Z1cpD6V$&-E)x;l(hT;r-Gk>}k1rQ7C61Y`tx$ zun7fhVVM=i>NyIk^gufFy65jnDja{71SU12nqkTLL}4svso0J2b|x~Ozg_-o0~8yK z(H+DhIY~L$N&KmV=E(L=3`!vb!E6%rgrJsOIJpB=m=N1nN@ZEQf>vO#I$kp09q_b?sV5gt9S3l0BVr@|D@ajF;9 zNdk>RB%n;Jj%zz;v*A59U{^FOxMTsVzr8A#ng4bu83Y`BWp*{_=2|%i1!ZJ8kB)4< zyUyJ|Rgk9Ad{aE_?XiPR7XVtTh)-ZboU} z#e#SXp?N;?ANu8(=ipQaJl>Y>CZdr7k)JutepmbaVVvcM$Nd??0R^aBV11;vOPk>{ zeSkyiz~M2{&@1hgD~dm}QCd}}hoCJ8yNoLNxddri?F&Lk2hZs~X|m0~Aj<5DPuJdK znC(Lb130uNI6ivN0PS=7I+#zAW0m)|D=(x>ZN@wdl3-izJ?Afo3G*C#k}(iEM5~Nx zKP+S;cKr1(?Q`db*5v#{jw*+!^yFmk;t?^-iqXv8kf*F6kktE?ktCbMyasm=#>H)(C=^N-)9=J`0x(V@Jd1yJSvOPH$*vh|DbU7Pe@B?o5#+1w=e zWE`VESYX6~pHbInkbeJI=ZlBVqxN&N(#E@b(fok}fKZJT;o*!^E#X~`eO!*$U%mF| zQ3yaA%SE?WkV17ALNxCK>{#RPIePO&loSRGN3w@*ot0TqBMzan7UBU5FM?MNM17+< zSRR$m!^Qg2y38^I1J~^nKo_?sBQI8m>WF}Tbs96oWG)w;iCt6)Jxh1?tcLc#-4=#0 z4(*&%kkJ<0Q)S$t@lqF7UHI#1jjtO0Ar%bHp-#ie(X&rNq~K^W@W_PDYBm50^#+36 z92r5P7`_2PU0SVCan>#dPB&ce2r$J2Sq(HswqoNKm)s4mUkXx13mRg*r2r)x2bSyz z&2-JVOH+$oRzw#WF>7(;OQ9kpSPFcHz$PY>Y54@|Br94eoF7D0ziCn4`p|`o_udTh ze*BPQhq#<&z5L*{B;YX<>Dm=5IQ$KPk{N7?4(wLr;#AU)=^R)St1nnb`&?ea)Ko5D z+wM_x{K2UuOEzKyJ(OS+OIb368Yy=S%ue`Nc)g$Yr|zwm;FXGMjOCs(B1uT38zCiz zW4Yg3s09IM?L2|WJxJySEYX}$(2Yg&PyZhr`SVlHvpE$x zOYYa6_fkIEYauP(KYgJp29YiNkW;lR)=y%kH$oq&^RKY(ud`m&w92)usZu*pbnwFo ze`OKgJGDh}yztY(+^`X3DIBRX?WZmDEmN!Bcj@|HSa*N^^e8 zcVomE5V*^d6odqd5ez{AaLCKzdd4AtzR^NpyI`Y;(nhN#DH33Mq)U)4{-qYNU;+W< z4l@A77(&Fu*y|Ruf5pMkzCz{yLJ;C%5MHJO4&uQFMBK~#}Gg?$zjt9?h%4 zTUQTH_6UZt)4=If=4;PiuQOc3GaQ0KxuH^Q*dH$X>3?NcP>#qLOnoY7+ zZ}Q;ev_&~2Xwg!#Qt38Vr@YGYEu?q**@^k06V{{ECAJ^DQbkML)#dCt8#o;F-u!UC z3b>5huEn`bn~b(sG*t4}YL6@>)ngMjR@>vl!=$_A=BhjkYs@ZOKJ?1#dG>5U`h5_k z>I(X`Pf~MBJ)h_R-d#j{$3=7ZS;#HqxPuN)uQwkQ&td&*t4sNicY;Hs<`n$JH_y5BNd&UMR)r&>%ZFAI({KE@RSM^VIJxk?&acwjVA-2=ANBmN1c%2$n!Wm zGe*zYHq$P`UyB9w{giFZt4$3+7fmFQK$%$m3$gS0(zZjTj;N#wIczv}iC0o2>cP@Q z!}px28n3#4x%=CYNG)>TDAgNca3^1yM6xj9Y z=2dx4s~6SsZu@INYY@%u4=5=%)jMVMZS}m5lPxWmm--f?oEXxus0~LQ+VC%6N%e7- zV1H4`4gJ0AxTNi9I-7D><%G6I>!@X(!r|Og&+)0)tvfl%Tk&06zy?RNDIp@ixKyP6 zZ3rhXkC1sR%>R2Psb=rmVMvDDF=)f%>I)>1LNvj4c;T6Jj_PP3XR>>U=vod8p&_Pk zf{mp zP*RLiROIzSa0r*RATr?r49%Yii7E%?B$Hz+qlbx3p(L9Yp^LJg#7Zxtin~pJ#HbZ0 z+aluF)kK5O;ZVgimRfr|ynnmNHPvM4>HT*FAyHMt&1)Nu168MzU9z%9WHiWb^q#_N z8&_;~b*uHjVO_9EKNYg?tY8fnm>l*Oj4bWmXMM8I7;fhg=S~iyih3VoUZ!A7zXgIm z%WH|qK{z?N9bmegQ}n-kqCV!OJA$bVce}o>RnxdVGt)~uS<>*~OkHGRQ2&-q8yrsx zrM}Lax*ivfJsrBf-Tk68*}WRC0e6XeWHV`Qw#nJ_K_}mcS-PPv`YZ1UAO)Lh_lfoE z*znWya|Vm*D1@InWs+t(YM;+Hnyy|Fs|clvFHe}nN@{ZNyE`c^#|F#K147%YftHoF z0(4&HVzdNo)#eDclE}E9D|O1BSK&*ZL_qOU#=&Pyhdf+TMfn0LpNR{oDbo9b>79Ur z8dZ{O+x;2c`gx`a9jC(WtaA~1K`J5OaOX>Z8#Ng+bYb5*p_x`Vy3h!i1a zLe5v^8YCc@;MIx6A;y?5S7z{B8s*~5@50-wd0WT978vyhQ?}{DyxDtmFdRA!4zsZS zgLJCliBsU5;coA$tcHL`awBpuHaORH{NLbSvGZtI8AYe3tXJ(L+~&LXA@Q4e(P`f9=)yRv;TZ3Zi%XPs)lM-6dHqA5 z=<5j>4_AeU-I~vS)y~vXI5ODTEbVGzCnc8Z3T#FR2yB=BuKMfI({slK(@|*T#As%O z=&yQjOxJt)$Ek62nJVR%*K~|g5IFqjc8j_Lv8JQrPz7)G-rR(fr_8gw>Y!y!P?ash z44bBL?ykPO{xfivX9RkygOSp$go{5vamWD4u&LE8YG1xO3}PZft)hbPaj}1~B8*i7 zqI#p9$`pcD_n9sVN z_a|ph+r?NHBZ(?4y}X`$$Ep1(ZCpbk2KsY?Fa*Rt!Clr5E?xr{q)%Tt+ z5T;2M_*AN-1~{BNnR+PoNBXsg$j7as3sB#P@P;68t zl<&f4rJLa#KURl&t4~hq7UtVcj+P%@*h9K~E0sxwgz-9as}KG#a!#BV}C$l{NrxZ{@{pH9-aZbp6&3 z?r(;>s~5ja*JIWV++UmztIfv125pJ?j=ygq=y|8SGc{qq(WKT}_aE0=sctH*d|?!J z+}LZ)&nt7A_tpM??DVwPD2Hp++F)pWy*4k!vW`C@x}}o9HR-I+oppb&`!r@1gOao~ z)#`qIt;zk`2UqpBwN~X;Y}5ZJ!fyUtUC|C_{5#KFCNHkidHJMeZ*%M%(L239f9TbO z@$|fr|LzcsVoBVbU1hGhIWElH%&HrV-X-TznIwJ@|4iGpi0plQph$AGg5)@v`NpIe zPEy(IUy8P%~(^B+d!)Cp?<6IxTzzsX9nKx4}Ex&`O*XSv_ zDQ&nB7O_)_xPw-5O)yP;DQZ`+w)rCI^+}pcq?j*aXP}@E@xd$W$~%+GOWV&|%fM@E z!7KUdjVD?W2My&Kzo;T6uz8fKj@3nl+3zgmiGvCctY^oihbO1E8w}S0cs8>)tOW9d z#B2st0|sFE4Eh*lJ%+tI$FN(kp~pII7ZRY?ma{7<*Bh>}3jx6H0k_c~@+T1!ZW~C4 zl}=N$jdIOy8@u<}$QRr6dmu<5(R7!Y%GnvW9#yT01+!2BF%XrteTlDRgRgnsZ;|G= z3HNu}?rU-Mi^;T8(T;#h3*nMCz=Y=_eOhFYw@GLjw-B`&62!U+Zfysn{!q2=L@^j2 z)Er5m_KICKSY;M@F43bM2LL2LFuiY}sju)~%fmt+&vF1sOn{diD5aEFM-*0bJK$}tcq?l6C` z?i@vDzUFLx$6o#-gMisW;LHj5avIq3JDSA9+=n#p7&)~!Z0Ys!t0^tJnC6ak{a51l z!=wegg9<$tR2C$^c2(f>b_)pD3x#^q$H{{h&!k=qN074fPJUVT@Dpc zlp#Pe#8V}IR1dzma$kMmU!;CjPkTAJyCSy!xKYny4lI0dQu(UYsCVH~dL@tm8h@6V zJrQ4VJ&Uo)i+Hsdf;zj9u5(C_PtxMvj|5gwubPZ9W7r^qaT-JjeF?w{=BVoZ_=2yw z{e`*N@|{`TZUasU({?{rd>l*xBk!9 zcQg*&VPLu5*S>_>s=c_Ll`b2Bdmqlp4T=&zm^UAYW}S}~ETjhf?3U>VF)a*t}09!q<+zuS&g-sfh+&=Our=+5k|Q$_^2sRe7i)xf+o6 z77>1Isie`+$rG{^Ek;$qtQadTYb?}Vz*LbD)!~a`WwSoM)HTKjEMEY9mDZ*e)UEXq zb)$Q^9Jci5doW08lAj=e>NA*}kG*CmR&zVXVCc7pHrsyM0n?EjPb_hwO z(+5Bz07aD*BCW0m7DrtR-zJ_a1tWp(a|JU_1CDIBMYMCXKu`(5Eqx#T$$2wr?a_g+ z^%ZjSVl=NW81SiuKrn#??Qr(yrx*MC?OW|Y(6x2pEntCnV8l}hqYF_@PG3yhZB6#(<%A_CbXUc>Lf#`Il5G|$5JCDP99u}5hNV4Ay9nevmbv(kI!rv z(f8{Z!57QrVRBmGwX518+YQw*CijOI2&5wCKslV&ux7YyF&3UEy|Vg>K;^Mfgfj+Y zA^a~z1(bv53D#^QsUfKG#|^3~MrkNTR4DfsvaG-Z2(_Ub5;RfPeJob#(FzG52f0o5 zl%qxP%^6^Xe7wDI)bGmffMKPoJ&zqV^AJ&iw2uKj%Dey6x1g+$gVC6LxdeFAI1pUZ zvC9NFv@DPSh(NMf8Byx`R;j;sfHo3T(lJ44P>+V%zqNQtvV6GX*Z1c}N@G0CLBUv5 zo;^dD+K5C;Ej}{k2Yt#ITR^YqYU;0z7AY!>iyK&@;1Gf>C*nh6PNc&bm5UJI0OBHc zv7_J~x*6|0hkd(CIgq>Ux%g72Pa&{_qk$+I0=IwRNef^oFCnWP1yqlc{LyHYzAHnA zfEKpn1K414*W3VkS}tSYi@NX)h_U)6btK)4hMlvEZE%FIrEdl+;mx|&Br3}=+8q~OOFV*S~- zL^(J4WqcSBFc>^oy=VsN6D;oPr8wLD>%>ob{;29!>atoJ~;CG#&@l5EjDBsx5KaXc5RvixVjH)Hz>(1DNjyB{%3BrGd&2dw+{t zHebKJ=@~V_Dn3chj5*Q)pF#n;#4#X!&T!p!F;&G+Ixtxc-j(hJe=Oh}+E)z)W8Gia z32mr(=QLU0wnenN6u*o^AV<3dYtvQlGexEJ_CdZ!BE+c}HMW0X2L4`vv=<6}B@#rL z0Hgu}8UYQA`Bl?YKUHxre-Y)!>47=8%{>#5f|G2@C@#Drtu5YePZrWFt#uF6w*s6w zmT=W421bex$zP@1&!q}@lFcLF?A*|14_IUm?q5nd$`n_0!zyPS6*3Vi%{sPnE>Wz} zpt$KELUoB)4u9j^uUHn_T@EbDV^z4OZ-RoMA8q{#WPU)`iAawQSTQ_yq1MC$A(s^+ z%s2<8sDCx4$re}O;+aJ=sW*!MHO7lEEn3qoZgtr_EeI6(C(4=KJ^uZOk{?>)p zsJ9a08grP*KuGnO-xYu^ROa(^(8p#NG@M{-U9qV&G*WKe#$$ofF`KPg??h0M!W^?!6>^lf`4Yb_emsNj%_x3VAV8R ze{0TIn;0t=zipe{;C`4ixW&mE!YL7LZHCU*~qS6HQBE3jho~MQhGgQ(OlxVnF3Uma)=2Y zA{%X6GHm&g0XiA9)M@^05y_hCDa(q zP;&q7lmJyy(OjugJT7A|JEUV3h%D0gMP38+=dLbyJ6@ieOc4~CSU+8AU zS4Iq!W@miP*!-d8hzlm+)|v}ok>c24Wtl5#Rptj9qn@TEb=*Vxz_Ju>Rl=*7o*w-tH|+?~ zAO%W7$ybw?3(0d{(cpAx+&1L58)KI4>r0`)J>dedo^eLyDX@r+{_RM7N3e~qQ#%7!4fFnl}?&d(q3Y=Of5hHTVeO zk)v}WQpeWy?aNKcDJ?(G>1jsY-SyTrA6q94mb|$3D#6843Q3$VB}Pk*LMW@Bo?~AqAt4|3Y;!-Avc-)xxSerl+(>wId-l*D@**+ zQq_4fD;O!CK7y!7ywIR=+iF@lk-JabeJqYKeoweX+gsqioZwDwJ!%BKzzD~VDpBb+ zW`);7vVi5Vmq!F)tzSad+ioNxPUMUf@Nh2VmA0WciI5c=l&B@LHsx+(ZjH$ElPlKj z#Q1nT-CQHrD!2RU;YiNKgyjT~&Fxp8WsCE1)z6bHsyo_qmN(k{?GK8x00Ajkb9*dH!(3K~G}mZoH+oUxsr4)tC&B z8U21_LB=0v&g3ssktrX}z|T*PxcdF&aIdZ07-ucg)<#;g&$8#_+X^LEql@1w;8y`$fGfK_@H??|_|qrTs{ z>|7ph4lI!|{aXF=@jTlfq4th_r%eLF=1-2aoOxD7@@8G@@Cc z=x6ax2-<;O(>cT9NHr~XLR)WmR@Z@J=O$^!*s4Xwzq{TUrbIFI!tI-ghG^ehfp{hJ zH>!xG$iE7t^5rD$MD5zid}x*`j&ihhBTb^RzV+HZ&-&Id6lzM3zqd`z&`G^Z*OCED z6uC%}sFe|uQif$9Q94@Y7c_mkNI^#UV(z%#Pb76hR&kF7xFvucB~OM)AYyo2maFE*)$y77($xtd>7{9# zijN2A#<{Du_3LLqkwZ)HTY)6B$ILOfnWa{G*cn{;MWOHrx=01+TO9I*0u-6ME>eKA zPW4=F0Ctlc7Kk%Q)Y4#7X9I%Y-s?3}oJJ(EvN1{R#59(JI0Iv=pAb*fuqB5WbG93-uCM_UgCzgWDW?hO(@SM-<=M;8F8 z)4?n33i>*b^kTr3RQFtOeo742^x5U*BcbnV=4tp~E{yKcjRi~1vL*^{W(><%>Ex)S z47p}z-=mt2BD+LcqUS|722^D9Ps`7_1H+e@yYtGB{a5(?I!e1>X75TVYLmTcmrKo} z44iq3bl4&q%uxT;G$<&(&ukzA1yRJlPy#r}56#@VStVhJsb^b)Vl6y{KDA3rp)-uh zB3s$WqFwlC1VmL7RnQi>l*=fL^@wvDcTenzElnSrCcr1Lh+LQFIqEjpWlxAoaM?sP z^=;y(9wm%wndL=jT@6(2>OUiJwE&&Bk&1Rc6C|IhFEn@>NeVEOE9{d)@DKN2l)l2| z%2aPWohdxPgNiC;g+??)5$WhZ@-sm`tV-opwpIH0CaIgpK+qNnSI^Ojs4t49w!~fK zCIF)oE3fMQOo?P{I2fji*57$0Ay&rAt~{00m^)OcZtl7<-u!Flp%9Qy>F$GdiC3KM zDGwfyAkqADL;1j+cShw1TV~7az?M{U=?}4hs1y{ayP`q2`FaD?}v{uJG$GX&4&^=`*pLBbjvjo?u3g4=LX(*qW2Wx=epiZyi|T5?tS zps4_shk&Ouf&_;KZa97R=jFQFxVL+j=}#>#nBfho8S1Ja40u0kjZh2NlNPWXH5T*5 z=8Hw?@L-9v*~%fS^n+&XewZP^^l|FGj(S^=HgWCVi&kkCp$TA=nV_P}{d2u3d8s%~9=rxKA%+v@1B3B$`cZTm zLozrM^LL*#6Y}R{GJO^o&e)m$b=>Fmdq}NRtLA85rgRm*VGy-gYgSOH`m~pod8vPc z6_(L8k)2)ufuh9=Vl~UQ-jiMZdZ=U?b*^c~#kl@sMqtgM=Km=^%lK$gn|>XC=L-Wwt1l-c>a)SMUqTFeH0-YD*IIw8_K9v?!Z8r z-%K*zGQB?-$Y-6D?GTat^_hYrEdOg#y^yYC6>TjP^Hkb(E%?4ipFeoHK}5aE1}wmo zo2tBS>sEg~;eI<(bvbRllNExM5Tp?Xb`qnHR6Unlp_99xph~$@YE97~+mmlGx2O5M zimJF&t99}ItO ziXyJ|Mo`b#Uz#aa3#Jm!+KN-*ng0l0*O~CyrC(>Kf*SAGN=5a>+@3hleKmI6b_hixYr35%x7w zc82Ajfr?Lhy;Y6wpfhsp1g}In8)}|cC9dWw%AUdTRHxJZF;BRnuRs*cmam`$nO%u`ox_ z-0r@=4?6X3rNrt6xM|uNh7|RPUU6zSZ%t?CVL$Ho#60){w)k>!cta(TRawNUbF!vu z*Xvhv=dH2h0cq%E^v$We(2=##(u{g)S(nqR`p=;Ly6?x31!v8Q5t*FcPN}d7n4^PI)DYzIJp_mp^}75(jgAdm!6@URSJ;Avk{JzCU|IHR_DjpDZ;E86Q@>diOAm&+$Y(}x3sOYFU7kYQ}_sdH7LE3oj! zsBb5HM)){;V$=0BERz9lDy~P%Iu;X(M!wRsw($Hr*;7|vQ8)EtVzM}i{8Be#ebWeV zrfg@PXy=Epkmw;ao4NC{`RZqR@oV^dlhWNdwb?Q2Wx#eNjo78RM{n+6FxS8DbNu!= zHse>w#ssK;{|hWH1rAYmL3I(s3jlD-|NltyP5xIjU*$hEKkz>^f9b36Y3l#b{LcTO z`Q8zSiT^8_Z~p&B^UdkZ%>S=6-_=ZAX>;X9Q{x;Yk!C>)096?Vuvlb1KU{KNot#jT zFqn{LAxg5C3SXeOoLr%}wLFV397H6O(4i_k9|;>jFnDKt*y)vb*W>1KyV19rKg0;OqkQ!|rmBb8Ks&{Fb%p^a3*;UQ%JZ2CEh z4|Px$IdsM5s(h240}22Xd}Cu{D9LJtN&>#J0@hcARu<^U%h#z&YM5D0jGPCsmmXF+ zCg{K;|5;`G3x%5&g-mzgnjr!07pT_6a2L&2Z6#SZ7Z9z~f8`~WPKD(F5@O5=O%AvY z4hKojAQ|Ncp`sd@F33gY;oC?39>vi_Q9;Hd$Q^|WcQjnYUmZjX1rV4r(ufj!sL?;Q zw7|6tLauxw;&dfEETBXp-97p4X`M@Ed3rGksb|JA za{}zZVtn2pWAGD!>^j+xTCg{njrN~F_dvQ;S&cYFe2p^prLaYSHxj=*HVq4C5jEcB z0!4L+alu-vWHh(*zCNHcUe6m#Vzwy=)x?(?7Kh&6Sf5`nrRN2x4Oie_|64+#udN{T z1#D{yLnB#-EZ46p>Q7BJP^E82tk0Gn29$+`vZh_8ju1w;L2!VfT{fjIVB5pfGMt`S@C*| zH%g6COHvgjwkKs&Pd9PNTRF+n%m?W73PVx7s<@(wk%IIa$hzu29aA8da?E1v0?|qx z;9Yeq$?4R%2Kz9SGQLcVi=(myG0FEKCGj0LA({~Q5ghnY-0%^edV>nx#2-oWK^j9b{&?v(N^d=xj2dpg;o5pSWKzvwfU%X zT;M~aS9aW7n!+~V6qoOqgD!?&e;_Yp)~SIAJ;q14!k9qL4Yc(a3~GDN?KQ=CGD$_H z<&3;mdcbf-;J|_Z3wBLHadNyhBTHi&?doePbJ%04e4!&{nIFph!VpqS3u`_QaY+5! zb{BKR@ZaBvpzyMETwPyNETiWbEMYn}CRlJGtp*w^w#7v&Jz;=H@u7gCTEv~&Qd=x8 zH-y$Og#mAko+*4VhN;z2x_Bhh(ti6#zTx8PSidM2mhj&|5RWX2^j}^4pO*GbK7R>j z;)K?=#F(dWQ7s8it?^PV8JF-6W1fcE+#cF>_bA*9Lvv|@OT=GaX4M+dwss{rE%hH8 zD;Lw!eN2?0!n2Fe(9lFdSn(e~+z!vu($WrNA03}>nyPS`fPG(q=z1F8fXrX>dVR1C zF!#TKU0>hfy{FMiKm7uxSXjTody@^#->rtd{0qH6gzX=n7oiO59iGGuwRLqjd}n?6 zgVL|Rv2fg2GlmzqreGi7;BAa*`HZEb|JG?w+yIagjq&Z3_x8^6l{D;*lCjg1@3p44eTEZA+`57N zz9eAop$<4V!rZ-*{7`|S)cp=OwEy5LK|gMTk$!Z(RBNuVvF(~z7xtP}RV1{Rl$5l$ zkL+r<{*oN@wrl=DR^V};#N_%wlQpD_VWs0 zadC6EzPY!3(sQ?5{^+ey>c0Awcl;K}zy4M%`w3UU-g&*<&S>R)b-HEvR>5M|Z)D`V zn|N3_82H+{ntEC~8fr7YSUDe^-o+L42i)%V_`XwL{XX6Qcfd2(ZTp|S|G7-bzce|G zI{kBPQ$sVGna68!z{YFquB`W3-%{FfF$?~BdrwIx(~+KzfQOEa>=W?Ej!#QU(V*%+fd|r+L?R0q&Q}lz!3hii z$mfLz_vPd4)$8?p2hfDsqRK-~eNp{%inHWuy|?9)!8g}DzUp*4)ky5>KZmz@eb7{j z=%l0g#qX-P_wjnkF5=9{1BrjYHhkqHKkG6;V*e`m7BEnwWdW-)d^Ve}>hd|yl697b z71t)}RAy%-ajX33HeXNQQC9h=P5gOEjC65X{{+vilal4GqrI&%@VRcY&=GPp_8s@# z{AB+5CNVn2WiHCf)45J2?|wgu+VIlT;>cWSwZ`4~{o&s1`&^6)g9qzu>x8B4ObY_D= z&*q{7N_fr3*jQ*b8Z4JIWC@PPmzjs-sdKUL+-BgQ`tMso%$GGLxPA5R~uU-8Z1TGM@c%I@)6ID0=qAe4t3es3e(E&{3v z=)@N&0T$P*NS1^zYA@4F;J(i#?*b2)O`QfC+$pf58_X_uK(aR; z+SRBYuZ9K{AdLJx6R8| z#DJVg%VE)8;@_B(Jw+UG3ulfVxyyD{wQK&7*>I7$q=myuX5wPXbi)qO>jv#?+6lUW zRQC-uHCx<|N0ReaXYr-|x%qQg{3)o4OkSv%RjxTwb**b=fe-t zVU1C5tc@qVF96$oZBBvdWQ z5Egm2;QXss%4R2;fM&ZT`&`wTo2P~7P}BB+PSV-sKw^00dmJz}KkzddCFEj6wQm&n zT{08Ncn3b~Yo%ZXlXWKV<>AU`O6J@CTNe~lu(98+$98zJur9)`x zw@Et;DO)(L1Ka+>Dm5XDMfQHfS@V6ZDvPYvkaBxahoqRUaz01{QFmlC@L;*r680K>jULG zacbnnekkE$)^ZFeK~yCvnLx`SBC>^copgmTtW;);tO!NZT(i65ki}WN$1&_(AV`yb z7SwpmDHe&u2d?Gvvu=%kFR&f)MCrh znXb62^Oh4Qb@5Vv5*BQIdj(gx%BypZNO+r{zE9zvgz29n~<(ZP;d zbuX?)D%Q5m=@<8%(%^KgoQVaWr#`Y zK?EcQR{muFJkFJMS{T+dMw6EjMQ%1{J~$Q`byrZ;@i<>M^@wPYd#X!Sw>1H5AOory z_gUjZtIqhJtSUxSz39hmT1(?x_av>MgSr3+)uo5HEr=cXHWSArMzvJN=}?LYCc0~- z0-Kv#NO-#m3B&flGnYyr-LKLnRK*;FC7*l6fr;@m|+~tCqI-po}D1KE~f; zif27$8}*3T{1iJ{8N+Z2qADSE$O2|opPsao$3pHl@Vn>laoB1!u;GXi5Y>2(%ODVQ z>uwq>>`?)V$~yQb=RhlQ9;Qu? z>+fppH9c$H2SS0OG$nHx zOHpjxGXD$#rE4G-p3nNB!pri%20CDU`{$!E3Y>Aaa(vBoVv8B-#uYo>8UK@II+^c8 z9T216S}GdUC;(CIkvG?O9dujHDe3sy#F#`EDSz~yU9RaK21=0sN)EFf=r)J4y2!w! zr=a;j^Pm^oiic7k;KxidG)4e;dWf=2{1ZoTJZ-ghHtOkJclQZu;&(SL()s2O<0tV? zHvl*saxQCO?WJxcm_u8EY#T=m?!ai#!mmvg+5r9#$$?C`IWsy;g0w^WUuPLbXi|2Y zji})LG}&eH8r@w4mfx;|;YH9ZvQCk=ISCg{f|$4mF`z7>lsHE7$l-udr8F5}p^Vuo zLZRHG2qpR&CPbeMVcEn0T7&&W)~RgsJeSQb786e{{0>>4T>WZVodhz|D+S&?utF)w zyvp0EtW6rL$ZY_CQ-R@G0d&p|{VL3!Du{*^mzWv^-qAi{7DwqihncuR21J4zlDb8h zf?CvF&x51bZW4e`0r_?;%bJ*U6uh*I zJx{uPDwjxb!>~}2r^X*=!!SHVn<-HeQ~j=-w9_!Z#4-)Y{d5NLkFa)V3PPD=cr+!& zR-XQdc4?kB5=}skSuwnV-*`#Fe^Zo}5KT662QJUM(`gvkq5mumYop>m7# z$lFbnZX`4KbXm;Rtn_iY#L27>HJCEO8ekGyesN?GLc%GfmzUq9=;Z#x`q7Y1EIyc} zmR$Ao88IK;0^hp3_wsg1LYRq76p&4kag6Y5wJx= z9QPiPz%frsC_bJ@dXWB>**R-!cucX87U&$L;GwBp!}CwF~TRgv1yS&rtd$mZgj(0iq0aUk%I#=>ZO z47^O{a<|UDY1v-U)uT|h7`G`U;&^mZPA5dQ3VSGojR_GO3CYe~R8}-7lleqZB7RV) zP^blDrxI>%Esr0UA0NyIM|t#QdgQYGB3uIs0BF`CT#yJG9AkxjPaC656xW=iFtspq zm{>Gpaba<21?C`SR%BoO&6(GZdz)({kl-YGfNFH6fnY%BcY=GgYMzH{4B0wnO0y#xS`YC2P6X( zO`Gq|mpH*;B#4GGC|>LU>;%MD!K)lx=qiU9iTxomm)@LVf+C&q`DQFpl(>c@AS#uBo#sY!%f95zY;QT~SVT`8%1!98-!WArd%yFkzB6Cwo z$C!}?e*3C1n5~&$^BZnRraECVz)@|LSX~7iw+iPbSVtqFkpN^stl}YPZb*wl8gS@; z4VVty^Y4h7I@?}S+LK+S!n6O;u!X_ZAOb~zxG|o5wnYD`#6D}4`#9LRGRNMCLaTO- zYVPDqG}k;`hQ9MbxgZ5p5iRLa`Q>g0OtPnWr_byu!ifbcstM1%x>ymF`E(N2e-P$47?Z5oHuzIEH7lX9SV+J_1)YsfuiYR83Q|Gw2khL8w77m@OXz ziGuPlsuBY6$ElJwF_yPI)G|W5hDL@b3YO8;&AVNF=9JW8f|Wrb7W9_vp>;B0@e2Bj z!cp7RPF5)T8rRaCfTm&~ata2{!^_;b;FCq{dfj%CVoDI0{)lQY0gR&wP#LjpW=4-U z@0%{h$!5-_criryF(Cz%y7kotebCKPCCw{T!nsKL*~?NxjdQD4AZS#?Qok#3m3{=1mA#mf=~Ph4X_g8uaxkZ6p7MXVV7Ue1OT zI4VqoG_@cc-jZ}lRn9+^_jos>0_VX(PY?)i7Z~{=Yaqs$HHUbcF)6)YS|=z`YqDZC zX$hlIw;mW~Al_5{Yu3b=i#y6pJ$%=>%U?0_!-T~b40&!mupmu86FXA|&M~9(BwzpPRG3 zdyY)C0O1IXVky`QNmJlx&sWXw@9B03o@zl8P~r0OC`qH_d5}QU6C6g@v2J3k0T+u< zJHP3z2_795YvO$A2$GnX7l}uSx<=l zWIRQQHQdS|&wk*r!yljka?KifE65Hx{=+sluyw}X3tJ#F-^_y5Aop!l0N|bsdI%7e zdlkKO7$hl_&xHO-rSC8lSol*2bsaoKHn?0aNxq#q;69wG57BoT&_B%2_*u(21K6WKLBil)IE(ru9 z88&uv8*{KDiRGS0Y93e8k7lu}O6L?4!C#?)C?3n05^C>sYs2p3yLV=avl9lJ5Dp<2 z>Doi26gd@cY`i&MC#?db=r^|mzDU!L^5A%dLT?mY+7ey|0gyq~v%4Q*Wt*2Xxet9H zuYRi!a%9k`=?2cGBnmKbpKl}N7KTtGWg3^2gT+l$?EXzcC6x1wq~YN*muN^iMCNV4 zX-CKWPmH=gk69#%Cwgx2-r~GLtoYX7fw5C* z7{fCuVD2FA1<`OLm;syaa5$tGuucszWk^(1DsNqngfUnG7SV%!GF8Rr=Q%VXz9KK* zV5(DMEEYLUqC0~nAepQWhO-Qz!~0PbX5Yv_08T9fmVY8tIUJL`wYN^GV&@*PaS+Ny zDU62RlUML~>hJDph~u@YA2s@-Fy!KXlJvhm;uc{S_F5Qk3WtJPJmWS&`VmSX8TqjD zrf{5-yfr77oG_-s!0VlUQmnEW%iKH%7m$o!blCnO z8wCVZ#z4qFL~#w)w-nInGb2Sd9PkSB$pSKBX3Z-FlX-6ZF+qdY#Vzld{c?$f{`4%r zwwT-b>wXMKA~$6SJ;jmIBw!A%kimfW78VWB9Gvey5@p1m0q#(C!p+q%6hR>{OmWjd zXKxo0su($MNuQu}6Jrl~Vk;~oQl)LeSbn)7AuEkj4kEk?VB{bfW41Oq2-}nG>7tEE zAWSEZntFhw0^;{yZvbFhYF_TupmxJU7Ng+td_1zs;}PJj9kwIu*QE{U=_=4z5`Mmv z==(xkxEFx@q@w5M%jl#w7|<&hU)3g(PukBX7C`Q?u@gsKni+;FU_~a-H7PtnE;QaO zP)pDdo*9Eym_6};ip8si0}&lq{OW{QL;J47@;BYX03naoHpmQKpOHyGKswo{L zq6A#}2a+UFKQL&UF<=+TZrdFYFvNYz%RLG@ql}R0d_(+4e@4|gWunU(02l-UI_MBw zl?dfv1}gU1FxbIHKj4BO4C*D)1mhrt?QiI3l)j|o$Fm%4$^1TS9yL2m8DOmKb`V8* zv#dFXIYYruV+>p)+sU^V*rR+`?>vDj>hRSP;``X`z+})}EF219h#XbC(-&-M&gs{4 zU{i>_4banQYYlAt?httFcxP+W07|DvNUDN@;{ycLlIf09i9VJG!t8Iz1h;@HwsQJy z&h~#M3lx?MB(4by9PQn{e?#38D%hH>PLhHrQ8c4ujfqR@ZoOKT1mTusULZRwb=6`6CA7YTvPbD%nfM$#j8?fKUIVjA}P0^Dwe zpBl|R%w{9a)+`<8xlfFAkM|3YksMFxSZ7FIW_8hK9hQ!3FSBATgTNgO*qo98@nK7) zCm<9a88$hAgfd$c5SkT|Iu#b$6qb7x2=#W$X}5RUUrQ&AoR^Gju8njL_6xs>KP9_pRJ7k3 z9FH<5Cj^!z6s{-gCjUah^NRKP9OUaal7zOcD&?yXt5?MPkph4K=~!kL3YgTmNtd-So5 zvb?UezSM%l|D8T4TITM?ouvUW{u+t;k{Gu~~{>5O2e1s<|hLoDT0|fKgPD78N*~t7e7BFG^F6 z*210(S@RBh#JK3CGx!N%0sitxtCaWqM^-ZAO`Q#{OsuATURraSh_wQ?&g0o&yCBxP zNK}{jxNNiFvN*ch2aL6IBiEyXYXf(wl=jW$ZhytZ6DB55m0Pe!@)8!PM|pg%l3}MZ zw(4PSw+?YpH$5^luYLhj(t5q#s7zMq-?B1yJHHp`Vs{b41%kc2*7*m6O>5dRr{2|8 zhRKOTQ_s}AnNNxAFg~*5Pgr4Qz1)*kD>jo49G-hv-^=owDs$4}H!PvIHeQwl_Y6}Q z<6k%g>LrfGj^`jecpr6DZz2}@nb*V*gPzU$F-A<{RL8v?E1j%i2=Nc-vN$S_s4YC? z5kyYc(LzW|W^H-hrO8^}X8l&u%U|vZZ9&&m*dl51KS=1(NjxWU zH}$1i8n=grPae8lYh$-D5C{R0n4sy`kW3#o_%k18{z~*OJXsg`N6NhL)xlZpoVFU8wC*bCN^C-kH=wy?Yq|p z`IG&;HJT?v9^FJo|5_?_ezlBNBhuMk7I%3}fm01*BC{!4@WHvfOAzYV>a&HpR) zzfv<(QQm0T_J7z;q$dO7ZS!E`|*}i7N?1qB2A!7 zo7tVKlOREg9O|<%E0+M+#giRy=DRor3JzV>*+$U;zopYhkt`;vSD`=)8#2mIo5jwW zTROgbwPuT*#K-ctdI$|^)4Ar5!S3TdYy_tn$QWU1fgoK7#chrRLJS?vTv|el3=$R= z4)p3#A=r1|IKWyjzWN80Z*J|CHHe>Jp_PR$V>@pM4L%B(<49u(uD^F!UQkcbm=NB4Rlvv zZ}c}|a!-E135;V9pvF3G_TuEWy{kVoyZIoqIaNg3B)i#9{@4G5Zf5V6mpgs(R}^UP z03O!+OQ&9giJf16U2)nNzV2<%G$|w0&Pv#EC)CWfo^$iVCfVB?hc4Aj(tG0QfmRq> z1PCy68r#5;MhF`{MMQXzSg79Zk6Te~EH?*F0|mkZ82CrKwZFu#LUSJ;R@t%R&rkOB z9&oGfI|om`01NArCRH+!-jQ+x3+gA_=g(_z)U=X`x;T1TGWWdh^LA%$ljHqyy~l{DcloN`u-o5GBz0ngsYOaF61{UP)#G)Dn< zlvn>OKY}z{aHl6&*07<;!;KLL&NHwOj~x{I&o|)nR*4A!AUN}a`1QR2R-wU2=aXV* zeA-r8HUlsTRJZ^pK^$49f<#q3BwBdD7mj^8Yd|Ubb6oP1ymmt}*s<&F!UqA>wb6-;CJ1P)k`CwpolWZ zr#b}sZA^?W88IR{9f40iaCoS_-cJ-94^qG=A>1AiZ~z1z z2d3BLj?5SH6Z+iZ0X+_1*zOCloIE_p47@Agi9Ae_p(1}B(`@|=h`uJ!)>jn}Pe6}v zb0bqQIOv;)QFIkdoOx8gZB%}6b22DYu;3M&IJRM&S_lO($vV=PbpK8`Q+Q8ll$C!2 z1EY|(k-|tw2*nbha8z@Ba5Lb<7*uo<+en^5Z=1`vzlf_Z{3dg_D@1YSbB!{aJ zz@CJF-%hPk{lfz^6bVvKq+v>E^rPYq^XsBGPCq-wiG?gg8^A|AP}PlZowZR=KZd3@ z&Z>>Ik#nB_8sj>YJ3u%STVLAbRHk;n?_b^@;h`UDbVA3GMPTVi()T?dwUl=JyOXKe z7tiB5pX={!rsPf@2fb@SACXr+^nhA^2m1Y9LMEF19hRY;B*Jg;>~Ey;h@X$g+L}b# z?{n>}-YQMVh8hq_(uP6dx~4V;e)bQW?oBvXN0S@emVmsMtwE_{W#0=eiDl zAGe*SYa281kIa^pYTD^qy&kvkOABRVVehTP^$bf>)uV%mdvW+lyrh&(`X1NTvO0pF znX9hNtjj8GcDp~O3=4}jr6oF@8Cgkgqi(+4x3M_^ZYS5e2<-xjfsVWg8#AoegUkOf_7~B z@sfVM6*!zH8x`$>jWy;1@}83_$f=Q7s+5kM5-MEw0Zr1JA5& zB_00NSUt&=R_45vbSR$aK44>n{FVt`Z6l5qb?LS63hdkCV@4oq1yOQyRt`fi-2T-V zZ8<~Z!{(f9opotPM!P)CQqD|hG@)?frdevS){OPUVk~KGPJ$bCD%5tKYg9#^_E2$oRy;%J5$H4?%yIp-c-B}W^ z%lURx9;4Dk(e#`7893#-m6?2-vb6;4LaScVSX&+7-fVLl_+G~=^PA&O7W&KMfT>+I z4MVjlNDW23^`PagfB;9WmupoybF|c+q@1~~pVNcC(+Lyt$eB`B$FmkZW$Fqm6rLS| z*}ezG*Lc;T)gwax?p-d7{zbTozgT;H&R;BsOlZC=pintSEu1C1tAZ~qs{kK91%ENbi+=1@k6jQl2mfT1?phMDqV#L`jp=Otc zi3@vr*@VY`>pb1V6zC_+J~H zkn9+Z>^NY!D_a#VM%loY{yy%A3^k<^V#CrV6k40yn(?fd3e}yiPmPB~Sk|%WNyo!f z&E4_{y6E}Smd)fa(Q`pCm^uHn>bUFN5-O!FBOBcNWj}&>@cvEI2~IVx_dJNVZ2hwu z8?Sz;v@BeVS6N6{zZbR%D*;-%4~|W>Nxi(91s7#Stio~)K*UOVhojlnt!16MYSwKl zzy(H6sJ@N}!F7VZu*T3WfJ^dy{QFJA1< zW#J^@NpC|I(onfLVY!;F0BsNmi|==l8yT!dTK(Lm`}ZaTvTns<08FbD7qu(w!nOQ9 zg6`sJ=nvY$MZ-UsXbN)+#}ik7lvap;qHgoOI{Q^AMS2^o$p83savm#`^_D)sR%OSr z#VfSAF4Y7se9&6e+o~!%kiyt0Yp24`ZzMj*=kDPwC zJ0mxJ$n!h`Z|n;C6L&IC&-xe#XC^4|gSuy}_-v{Mt4A zX06n4>@r*gNBJl7tOHAFM)lZF zT2EzG-pXxNb1k@P047dz+Cz!PpI4(`9Jqx3An-9^PDb87`5fn%JTOsUr0On3u6cHG zF=+(E>_$x|M6D(#Zm3Vp;{q4Zkw#zaTs9QE-!Ge0 zq5jc;Fdk3zeB4avy!>G*q;w7M&01!=7lc)$c7?I+Qd03`p5mj`UZ(G|RShOM%}&{+ zx=CTFbqr8w>e3pGu|g-gvx_`w7sC~0^pUbYes!b?ZBzIWB#pb2jLI1iV~lognf*Lg zDmAh=FIDR1H+@d4rHwm!rOXF>N5j0DA;;i>1yGw^a=gJIcexF&-OSpqxVTPsZNN;B zn)~|72dUJ{3fQ7KfAj|S1>tU6dIPgB$qG(QBQ|cJtxisYq2m(gE-d!AoaX01iD2J(S?&!qvb=5* zRQnmq*#NWI_*67=Vc-Wg$QEqE+TU`w3}t#8r<7l(E$IU;Y{D%`xdugYic2W4IaM2l z$V@Vnp@OmG(h<$M6ojNndcrMW{COTXrVSL4|XC&0?)u;QI=27I21))|^@ag;0VRws#S!Tn# z2SPx3^+QB|^Q>sx<3)^y^r(n*{|qaoqyMv^7i$}F&p$ZtIyg0EbeL)rDW@g%V!X!U zI;G;`iKnk-61qfGuS-3rq4-{-5h z>CF)9o5l>UFwmPd(5-w7u?r#BZ+1SlTJMRki?b?L=(Fb<+sJI%N`>2_qN*+_vH_ht!=zk2PwM8S{76xnQc~FOj;y# zl7JhV0}a?jOYo$jO_Wr;PI}R&?JbqT166)>0;KqNmg=!En6gk_0fQBS=$wU!JP@41hDT)Q1~cluoNp6KkbPo`Pb6x)@72X>bHn$xcU`!+KUE^1o7uV*KD60i*% zzL+ktUV>+4o<)87Tj;%WKSFx2)|cgSbJ0*WPdewgSLYXgKq;8?%e+C8-lCpele)B5 zcab#@Pi!$S%icD{euiuV10SY8wqv8CwRr}F=!s%o0Vl}nSPI+5X`Pw|fe>uud0Dd< zTRtY}zu%F=DfOY%a64uTyT-`tKak^mQC-_XGFMgvRgLaD?CRP(9d!gOmEr(ZP8-l& zLr8Z}^ulX0d*(P-S*;6YRSTq(?Zh-qO{p5TILSp*Zv@srO&cH4`U@s`tSd`e5}Ya4 zy|_n^sPpE+sAi!k>Psmb*7sA3s~Nzkvi*!Eaq%RfDoGR!z>+j@zCp~PU}IX$)!$xy zY?SBG*PNeTa;TaBe(y^P5KW#?eqTo601{!-=~GvGtEY32%t4ZM=DXEBLEHngS8P4+ z^azhb!`BiXRT;MJEqv;-)w?ORWOXE5mz%-^d(y>B{|pSCtzoH1EvwzF;`OA zo7>UXP2R(g)B5y9B0Va;mYAGsRdHHMud>#Hxv~_LR zPf2<=7}B{M^=vv5)@NLoE?gH-asrEi;T5oq0Yr+1HQYAPyW0$^2GMIDbY?scZU!N>&Wl{A?Lbe=KKBTW||HdRPJWu-Ew61 zS8s%4yC7?cC4Y)t4J`pnM57K8lnQ#3vWumS5N^Te^NjSMY1^ak37{P#s4EYzV7>f# z{WMY}w>C6I6$_#8=gtZmp@@M{hL(V<#%(3>hcsHDs_E|I*6);I#GTp!e1uo)A_lVXBa-@K>FWL>D#568sf@8CC_|;kmzq za7E5o?{tGuTrZ)UzBA$U4tCrQV>2TMiHIZ^)fJp-N+1RG#2BT}UW7em7>^1Z6*W0M zv}$}xU`x$bOlR+B)Hmk}yzrt%QIPRZ3Zn)s#LWb|SosLRuGKg9_lE+dI{;Kk`UYrl` ziBH|QDDDw?wk1?*YI5M@r-#I^K)4}oLf#){t~oN8$fRZ=Frxtb>oMfE+uq>!*(H7f z_!r;3Izf)d9cPcWNuqfP{vv+9TM+6O_Gc1DRY#ojBL5n5+$hhv8(m>s=lnc^q|l0l z7#*MfjR3@RgA7J*;=|eci9Ec;B|NpFOH?@tO)YxU-1SU26u6$J|1r zFfnk@eTUv@1RE<-%=R7JPjYb*IMk@exk1BE0^e2v)F)pNHO0ALN~(#;{=;S`d7??H zv;MtxEe{2EP!rL%hMI&qex2B#!e9O4{SUqRGYKZVt11svtk}0>1IzrYFA2-J>xs&I z&9@#oT`_N5PK}^>*PKbMfkjg)`TD<$giGH+x!Z;x;p8v&(1xFjs&B?@m_EBZ{Y2M- zPb-)BZ*t&B?sOTdiQ0q0LE!uE?b)sDtLo|Kr{;gjN5#XzZ|!U8XXWGJ_&cMcJ<%~R z@ZMkdZ;PYwH#^<9Ud(elD;p0TzgoA#?4{-Ph0Uc^1%368bCc=FlYgqos7EHnq(l1! z0|B3A?(M$3-uE28o<-(^`N7}%$d+txVoF9rR+5B7SZri;yEpZ_smfvW|7wR2coEP6 zMBcp!f4t{)b@RmIUE9{st<-YrJO6bOSlw7#*U;9~O3O^oz`(}DI=whMH@C9Tn{TV~ zk(5P+Q~sX~5dL=5KSjkP^ipAwx(KLP$i%>5 zIWZ~6D6FUe5D6B3JJRPbCohTceiQ&}a|8s{2WYQ!%|rjT&%0(o*bECt!De%v$*J8s zG!6@B620oRB)dZBq28|}wNv%3JQX?YbFcO-DGACT6@vFtM&2zWcD|fRL*i#oN$2lb zzUZzzXXMB|;#uM1h=}qB7up!jSjK{%*NFV(K4$)q?`0h}vp>z2dM*S<=cZU?uH=TgG{p^-aTo?9Tcc^> zxznTbBUVq=z_Rsd;Z?Zcmo*8wp~nLk2tEy zXZLf_Mt4pQvi9(Ax3FAQ%RAvIdAu0G$mcGm)GilD6;>B%cXwo(Y%hCwwe@y?9_VUg z2LmNRot0?{(4=Af$#@{MWu~T=8LB%Ammg`^aF-vc|5`Zbs?H{Il^${uvX3CRAM>PA z>=jFnZ8>6&R1X2W!BMTivL|G4 z@44HkB1A)_OGR6&t3_4mBir7lgM}|mM5*7BwqT09rKwdzG%s1B;_C2P!*kQE5K_X3 zn;G^G0e8~dK7^SiL(q+YtFF|A#?8IimPqe!wa;<NuYZOZrJtC`NjF^S&I~wk zeMELF8C4}WzJS$PwzVl&0HYT~ZNyfsy0DsSs18z*FP!Hek&-YDVZFIAVImelyc-mB zq}`^29(Ft~{rZ=$5j+#;8*!HIsqY{=iWXcLDukkgJc^nt`O=HYI5WJ}k3=)(ZmMx+ z^={oK4ju%RJ1kJlZ}7;W{fWTh`;O+oqw^eAN30X@%nqGrO82{+I^YsdWD<|H!sr^u z@49l5V72>Y?Bu7ytE@PEL+Cf)@6padJ=IZZ`rcyJYSx#s&?r|k1D|;GlI$09WD|LO zeA~w{%PIcoc+S^$etps3oErtTeiRf#LjK>ksZ%kp(7KBh zRXcKDYBJm3zryH*zJEiau-*MT$*yd~i7a2Y-T|T!8#+z?C23F=8stc+K3)cWYykb+^4p6~Wzk6uEv^w&LJT+=L+2bqWXA-c} zF+Ofsr1Miq2}wzUZ7ht6G$#wad3nOZbzD_J z;mtOdhm=%TwpHx~;B&VM27b>@H6%_(8dUZNo?gmR1~Q9M2@_*o=(pg>l)70whNgpl zcf1s3o9cNi`x_yBKk3Zqsn;*r7AP)lj`mWp<*1uSN7*{|PJyLIO#XfDWmno7_skYr za$x$58|rHG%fdUf={fSu@TIUMBet_E^g^dkQF?h%iEbuzbvWUmlhxjG0G z+9O{qLDXE9#{rIDi32Tx6yu$33xohGDF(v)V!N;CByFH9X5)wHNy4&8A^N<(JWuJM z9_%fmP3a`m*ma@*!D%=^K5i2F8%87ii{d6YZf8<>S>~ zyti-fxzt_p+K<1&na-7=dbPi=RQx&|B`(ML?bVN@ef?Hpc36ELdD4CY-Sw2H&1VWe ziX{70g$gD{N*T?sKaHDGNPZzE{;^_tQJni8ih~u6XP<-w#>--yB^EOKyD*^k(%N~$ z%|Oks^1!AvQeQJ<%XnsZAlBGBDG@SWp(eKIx+T>drZiOYSFABCp~}E#hBbRbc-Sib zV#xu$_Ti_>2G?pp#QGzF%y%7J$-G{Aegyd%7t`i3R)lDl-Dv+FJlh5S2pod#tk#0DR2lI`U8Rhf+ZK@et z`4UB>2r86^tjyg5Q|3CG(cOWb^X&taTqklsz8$=k!;IwK>iy~w=vsw^a3fF*i~>Pt zK!2(c3K1?z&}H_j^B^^jL2^htx*)@|a*8fB1mULX7>GD_a;rv$c$+?AACl(4mJ9J2 zpMw__`K>4$Rs{jSJ<`w7Ko(@geC3i(6&^7~(Yn^1ncTMQpgIk=7 z`0r(}DLNJCyDEqNzgBF^+%4FsN8vV^4OrK3FnxJmmq(3O?0#Y^Mfsxau1ah1TokYm zmD{!a>Z_ik8LExuCW5+^tC1;DRUI&OL^r&RJA}iWNw;8U^Tex{+W~|`Rpm}>-WL8L zvzN;l;;RB;YqBbli!03<&5?*TxpgALb+6tkNT?R&rC#*dP7yXggF zYtugz1%Ff*Sth|T*&26v$JS@6Q_=~S9i4(r1}PpImvYsAWKYSejzdwSzb=Vobq7FB97Nig zw}@vaxK38KsMY)OVrQSY#wXxt1-goQsawEVBvPa5A>u<%yLT|VXU7G zw>Z1rY{-o4_n2HIUgCJ({6g92o37hTN?}Kq5R4~z&l4BvYn|*=IilWjs4EY=dv8%A z#?R}eBVI?qr=u`#;G5aCYxFSPmbP&Z75yitR#S4KF+1;l4NYVGRD|MFE1yl6p0}#j zn)C(}*_bB%57n=D)W+0B&!#GP-U#$Ii*t_$x6b3ey?flvJq|D*jVU?84e?>VC>&g< z_#aFmxbW9?M`j)V3^o=g#yze@a_rHwcdek<5oY?1rahGXuJlF!pqhLgSM{P2oWmn9aSxRzrBZkG~_EOX0Ml zBOW9}TGYcVtD{TT+wVF!*d$1$wb<$xsOrqvh5it(Q!t$obHW&>Y5tE}-8QmqJl!6M zp}dUi&37*P2etfRuC3wvP!Zorh=V=&v7r6J;qNs%hW+^~+gHQMmgEE$2RV*Ne2O!p zTznBR2Me;=b5cNO#eYKhH(lk}Z;K{mWXh!v2gr@g6OD!ve0(Ik7_;(PauvAjgaEdn z$iybl)^YZa4NN1O=%SA%j)QI}R>cv{A`U%tN!3jeSW>WRtbh8gRYZt9*M*iE~)z5IuMgTd~D#tt0Z zt`yPEtl{Me%ubzSVhb=Bq_C?&p$sg^4r3xVYQWDMNKC;Wj!)2#kI+814lFkeF?Wn6 zH`8E8+kOY5L0%`#PAJ8$AkgkU%#Ld9MK170m*c$$_?-|VJ3e^EQs5*gKR0gP*Z=EY zZ0-%tC)m(u*uFObthWKNH-e^@s_w_J%@=mH^nzF(04J+7kaI?y1y4U=FF+j zDUgv@Gpz?~N&v$&F3KHz#0R6_>u;YA1UEjM+4N|17YQ|RtJCLpXpG`p4zN21aDUu8qc1_EJ;8!6$be`( zD46&R%~VvlCX>_v64k&WdNAtpZ3Q!!hyB;Nan+hvc zCh}y=^F@5~WpXrHmX*cC8KM~4#HBj67_l~zak<-^I$QYn2L&&!Ml^;5N}gCVHf4W13OrGcU5K#U=vK~YUq3)kYZoY)z6&Lqe87o zv(l7WMA|`4+Co{{+-S_;)imau9y65HJll4lj7W3cF$-NwOWn8S4wu!A{>5J_n5l%3 zzaAiF54fyQ`mA~&oi1naw2Ay`UTM2jL8l-PFBv0SV%n%gX{>~G*OjnF97wo>X+R91 z$9iu^wrf@PPZ{pO2BA~A@S){sFgEUf7w!@o%_Rw0hb)>HQuk-E+;C;~1Ur{uEVpVV zH+{6Tp59s4Fj$g219bynmvt$|+A2Wv572)Qc&#G{c4ssIfIRg7B7wL4ZxMLq|9!am z;s1Xon--?Jmj08#E2jM?fj``4c=>M;c!&Q#0`EX?=J5X{@UE^JiSmoriFiPefS~0_ z6%w%B!es$UM0o{G1l?F3sK`R#gat%Y1}Y*dad`xyLkcJ${$UXmDSib`0RetVIKDUT zJ2ze{?K5xBu_tTU-8bpkRxU2Ca2%_cSa0)!KzXbHWi!uOh^fV)tG&KIzXwzAkza

UN-Hn6VX};O%k{6!yI~yCFgWABlKEk*?)5%HgYE)1i_vW7$FEgp$+B|s21AF}Z zh8x`cqvs9M^eHXPP5zZ|vscM)cNSn}UeAfGj*q8|c~l>LU|HeNW2&em|`s459lhu3}>(|xix zcXqjM+FeZq;(5orQ=jLjKDY?PVSF zd^f(^4`nTIP9_ph3&7V4O3}$LSCc2ly)(Wa-?&SM^*xQ-%Wm`x3>alzq)y*&RY~^} zJB_oAJNP?2bBiA(j;6*Qwd0@nIIk1vTb{AtAF>RnzaOl!F$?It;Zy)o_c(!W%nrUXPqSNz3&eNDn7_|7qPd)8!e1_ zeYjRmSK+C^z{-jI9i1JO8(1@_i;WTY0d(X+W_o1D=V%= z&~{T2WwKtmAlX)vg*rxd_hcMdE4BKAm3#p+e4)wCRBA4;jbLo8Zdvo&mzBW)gVoP^ zv}K!m5#KoGE}v{qkUlnXKN$zhbfBmId=#=f37K)rxWL*S-8BEAR~MfaC&{M57Ntj zRA0&{wuam2a>i|di<*b}Rnoih;3>%J#Hqv`-Fb2#I3fiHTW%0%M{bVPW2L=j*S> z4+Ugb`c{?i7{C2}_wIkDmwsfv;2HXUdhHKi)53h;Fn4P)F%U5@F;LMz&?}$lFL#o$ zJBNb4Ve|BZrYV13eNAz#rw;cIgnxaU)=hUIV(T4ZTxu?CYFb)iS{*4N9%63TxU{@o z`OSO2D4(wT`r%@I{5n?B(%!(*w6xy1xU{s|xYSm<*xcT@T{Vb$__3o_KmUWXbN=lF z?7MXPt!>-3rnYUnopNg1wvDN6+qP}nZg-w{H*d1Z{;<3M!kv6_Cnx8+PUr<98qNvE z5y2~g&FRnH{*QQdowH1y{$?5Gi*gC^cY`V}G4a0ffzdb8LFLvXYvXtLZqEuPCQ1e( zrU8Z{HS$XhB4+3}KP`@?*4f3m#s$vm>Hd$_a`WOmdp&!yA#~zp(yaof+^MeIRr|4EX74pe5P=-in0$qHq{S@=@&bRV{}A1%De79~?`E2&oVU zg%EuO%e4HqUDfYA{>iue<_OOCS<(B7J+%dVE_~HYesI6mY{EsoXVx=LdpaMj%$Q!!@ArRhUQRy7-YD;M%gNbwdwoCJe-3=qxfc90I^nwy;p1a_anAny zt$BHC$hoeTTV9BvxQgA-(#{}YW8?Fh?XB$5SNFWR&d%6aYALtbZk=vb(^T|%8b7L+ zF2Z2QV&D{Kpy$%oh|5jQOw3M}oXAs}F3gpamX(x~lKD@DA5NYd5fKH600RdF4-I?E ze-gbd#2VRsv!oV?~30MA=zP~0>FX&F2PY>OUk|;-cH6L;&^uR z_b%(#MVV9z>g&}B9z0y()xHhdya%Gqz>K&I2|loGp3x5~ABujDVw@Xvkk}0AjGia( z7KvliIl5`^x-BufJRd}cwuIIonNywxMzht}tmmrVUH8<^+%1|Nq3wfY+V7>=V(lA$ z$-ZnaCAL2lA@G)a|1cgt>E8H@J0DCP)_kFbE?<|8xl{*jf9S4c8eV(Oat}e1DDPx< zNH3k3N)N2)wSVSIjVwbbgP3ydy{Us3Ew_46Wwmx6sbU@x=T#Jsmq}B9n9d zSLrMoV1>V5?=o!T2txp`(Yj&TbY-5wZTPmjp155o@wEkSWex?P!R;x;OR;6a0)JV+ z!&$9R7@Me9Tm_{vH~rl&Px^8&^&U-%)CiuB8{;`T&lvM+5?@FJdp#M~hjA7c?#^NR z)O%MXAmrI#675LcNvU ztytE@z+|L2fB`PPP$bQvR1s{_EO|2qWP=gVP<4QnFR*CzBx8bdx=kr z&CPFi`q4PmN-E`MYbr9_mE7oLz&bjP4S0*0ohOyFOcfAVzL$$hU`S&rP_LzE}4_AO>?Ah=jYUu6tVH zKfZekjOHkK$(3~Mb9H5S83wd0bjfmYW5iL+0QLs=sVUZ;lL4Cs;M*1SR48@d&Ijo` zmi5VN=gsQiP2Bx@xo)MLUA_0Rs=xKzQYO#8CU*$FBabi1>V97_AL*$1F~qtlZNGnh z^+DD3R+i>gCS;nQl|rCMt;>>neq&4Dk3zFT-IAAarIQS}?4Hh)8p!0=7aIPdO^33~ znEsd0XX5k_>A1eHjEJOmNCzWMQEc|-T(osyiPzZtylr+S`FQ^l|LxgTYq@>>3Bo+! z)7&A!4?KGCp^f0C-mio*1XYBSB<+#SP=_W&Y~h!Hrv|DSP*&xA;+wb7Vz z6Q}4Yg?oZ^j8IrV$TSzNKPIbf;+VQI*f_7%hgtfoh+w8zUc5C3ca7bBF0Mp(iw>h9 z$+Vfd&Iee##Q~exRNd`fJ)yphxLD)VNxOJ`OQzt9O6%^qi9^IZEQFl@X=_`XgER=9 z2L!FFU#vx^F)XVW(BH@2$AIEnCd$KzW*4HKWJZFx=#YdMvLLRkw8T1O+tdkiJ# z;HE7@17RipukVqi2f06lFD+aDu=;2_n(?G#GKeOWev+axbsAFfWach2^VAX4nEf|) z{OLoqTkb8lKXyO&2C#0s^}bmvE$AMqN9U1+ct*){u^7}LV<30%7OMK8Np4liC7gOE zZ@E_!O$5^tD^9u>?R3R0KVvi6!8S@4>1>jk%yHc-uywC1@B0u{)_;y&OuOiP%DnAF zZr#Xb6$jlFNh*dGq7|n_+|V?vyKsXri~1E z{Ot00?=ipobgmH-Pq&!jAFQiidkR(rWuN&Ga24^lj%u$8XE?NXF~Qi=<}p-ECt=u^ zrKxI)`zOg-lJYv(JAAE$82dFG>OXp$SKuI3_$qd{b5phRlZ`bH3e7T0s`!@%iLFlA zKf{VJl^kh2o)UCY7vp#Fn9@WHp7w7AJ54BW$l7Y#i*J$oGv4N`K}CZRo@MvGQs}x? z@7U57jlOUb66W*-{uVZnG&@@mx_^9oocTr7cut6O$qtNDil?nmcG{b*xA;9;7JPJ6 z<67n2K~F*yKlQ!VMj7HH=d{Ov{TW+;Ky&g<7gdD@XaeSJyML#0H9zJH;- z!t#b}Hp@)ND@|7ic2`XIYP8$z7!wS?M?5eZK*fc|WRsU>+atkKonrVZA9G8|Ug$YW z!S*XjwXY6CGpSc%zUejB6S}bY+3MWACx^$OxolbPO~7G1zu^JAh}2w0lKG}`MNu|! z58=rhJ6phWo*}&N*}kX4))MXTtOvni>*M7Sbm73=Le#IazhraP`Q zkr=)ro5F`+GIPTs;nZIC=XINr&$dT+d$r+_fo=bHkPcERy31Ta(_b!vhunMmJ`Z*- z_SU!YTKn^hq2gr4Vgb}eNmh%k;k-*{HK(wiQ?{(xv8MJSr&NCiZSE=BFqU-U^kG4B zY?rGx`DrfO=uhy^%WiOkbNbOpcg|)Wj2n$&(!94XlN zR<(kXNZ(HS2ApobMh|{x&lu8Iw@O$0qolOl)2%}Mz=FJ0*RSHqOC20eFmRQnq=vLr z>wEV<=2gayp5er+(}Jy)u97}_Qg!CJn=GB*nNCCJ+Xr3QFHWClDpI1)2Fl;jdD<~cAnNVsNJ>qlp~ zt^?dX1P{$^L-H*?us4OND9g`CkZJsItQNZbpb5aPM!o7Wio24iCRkugLP!dH4O79^ z4f$e&Nrv-huHiO%CWO&op0R;Z^@>X^eJGs0EyjdJg|J8Uu*oT~O?9x*Pq3YsvYxag zDLICMp$JuipKWm_JX2oTVu%xs%#)3Gql@EUb|Q!gvckv$eNqhn5wG?LvVC zxwj2FQpY-0_dHx7J=&q0sQEM2!=EyvGviC>!WBtdItd;{38u_nzf`v?K>S)KD441t z7;Cua7~9`n*#BMMpGp_34SDP8qDKl8YYk6T&B@mbI9Q!EprC zljM^A13;)#)a60`O@I?|MZ>i7t0oH~C}Cj^wpEFqGARhj7$n*aGa2Zq7bNdYMUvJW z;mwE)SU_3eqqS6t=Td{YGy&x6Qw~u!MBvO1PO~-%Qq2&}SrLc{IxnnaF{U-n=}k%M zqNp$(*$bc0kH04K5`7vOx1R(r32e4fIyjYq(_X4o(k#x)9ENKU38>mgQ2nmf!=;=H z15MJOX%u6t)?5@wcMn*tUwjn+3Hnr3P?G|>?? zo@hqjL^RGEHC{+>=~0*1lJ3sCj~3w$_A!b@nkRyU5Sq}dPKB|BxjUp2#}y0sPI8?~ zkNMRItT{BmH+B0K4`ytiDuQXi0l&j!A4dy(TcYzO*Hi0=)i{(RqCIKh}EGeYINyn`U;~w!u)ymsq95b#GdtCX9(l;ttus` z1QRCJ=@Amv8W#GY?1!HBs6ms(f#iVz|yB`(!g?@)0 zI(pRc5P^UP-dls&2Sgrb+v5j`R2>w`h6f@3Zz`T}F4NU-^?=iyeUU&r@HgSVrKb#s zf7^|ilAtTiTYvUY=lU7ZPzw=ra&iIefa#a%P4`6KibLV0(vfT$Xu_eLyqEA%1tZ;5 zBp_4OYEA((;0R)rLb*_BMkHd?*=C|4$70s^-9Ix-K-@0JU!Gcz*+wV;L|%t_px;9FD^CBC&~nbFvhzM_DUBMW@qK~uoB zMTN3zjoTsQvlG}u>I|e(5NVs@f{!?4Ew>hs!Gz>@U`z5p1)Q&E%z8B+TrTsX*056XsB7N zwE&k<^k-_l0ew2NXWl@Am>U8iqkfm$cb5acp}|Yv6*56J1p=lS{H-cptUEZS)&IF6 zD*W^V0L2)7S$DM^>Y%~!P0z48djS|81Bv>Z4R2wg-DXhmr5@uz3dVBrc|=z2WT7oJ>+!%A+~6#|fhhKr7Rt2% zEFyUf1&1JtSuoVwJY68zfI#8V+b+eNBjJ4T~roJd0uXND&G#6N}w(kEhRA;{skw1PhA@p`HAJ zh*{GhgAw;KG<BZg}6mhGFjKGhYS0PK+n7|0XBy0fs4;^bd20zp)F*FcE|-xd6|X9(#0` z)oM}V4qNx(Z33Sz#{$h<${wo-^!4KW6eG+E?yXJKCz7jRB>Dzw#h(!a2)`q3-8JdP zfUeR5WKvPur;S=@O?EZVA9{Sji@oW&O-fqzJdfB2#aOgE9 zj5%i9rUlwsrmtp5pdUFB7fcI9#R}A zty?1K`d~mVuml&3R9~QzBqB3<7UMUU;Rl=H#&!lQsOeU?VXw>bf?QA+a^$62Z;!X& zVGEfMO4ujmvH}IzU5Y;^F@<^9kuBNg4(YfhGvdr>ToswRx8m0TX`#yOg^q7;hqJpj%fPd_bq0l z9xQL%ZJ3HYKUUDBaR5A`TM9&jDUDj)x1h*1v)4p?3&#nbC9_YD= z7ntdaF9MCOg%6h=iO$Qgn1Sz1>T0r}xP1Z=;x5?=5kwuFWqr2tyT7d>eM&qg)+fv56 zn`1bC&}KuWB>)fBvPQ!+A5kZ{V6k@Ocz1c^IxESdh(0xb1`MAR7ACak2io;P-3Z5Y zAWLHzx@L~LX`g>lA(I}?K2L^3CzDWrTclRIeDjuKBcXEZ(y z$3iSxnT2WiDApr6)BuIv2`BM3ew5`AY2MPqG*Unq2A}+dtGY|;g!6tF+S>0V49h#> zBsFo7Mn_TkcqzgvXH||1CG=s?K=dDXIPnRIalQ61F?P#gR-^VHV|=>cTneHu7IOO- z_Cc8B=i1$HP|aq(y8ik+H?3JJKu>`Lzvq<$7o^`AQ6QD$x z(He}#Y$mI+i;uC{F93v^23PmnAEsLrE87aAGk8SjZxr_YTLUJf=JCble$J-V1xa16 z1=oBA2<_i|qE{>2(EUjIY~pPeNxL|HjtqbI1N>ulB2HFvlA^tByM4;YZ2L1ry z_}6??b50mbaM;N|{tPIyb+qOznhFOKtFAeYn8m2*C+TL1r9cAN_fD_kjDzaxTFE}7td33 zninRT^0J-2>g;Nw>Lj-VnOH3u11(t$$IR7TT@-}t%!acM$Tai~K5BE0PTy|EryC#s z2T)D2_00YIAX48q>374(Z289N4c)XvI}zA_cFBec@)5aS49b-5vQ(b>1^j-3d2sXK zf^Z?n1z_}C?X2g{9I8<)GvtltpEu%$u>qkKQ+j2$F+%c<#l0%x7;^Sp4H+ffVDP4;IqZb)$s7Kd%Gc;*QFuS0bdfYO}-$QezYmJkuFAFoxtU8VepuE2#*xR>gda zBQ}33yRQYTs-m}V1cGq_-kitI_l3n~C*}!+{5&`KZ#0`U*Zcv!pqn3mdH9H}07n)J zP$hxm@eXa%NYAtlO=GL4GEAPu#mORW1njaypw))Z+Sz zny4hE5&$18_s@^P&xj=X3K?`NbpzmD#sDx|s>GFldRYFLnL{naT&6*a3(*Yc0wdRKAlEu-yAh8)F2!=Xinp!|u(gqrqCis4iT-sL45~pJCiLoYmlpBW z3N5IxKsbJFxz#`^`1@ii86G0!a(K%sKC$8kJBs@NLU@JxB^Cl+l7hH?Oi&~KWbit- z2_bW4xLFTYP9MvEFX5*XD~Y*lZm>tK^XTg?`^7t|HW*@r0@$7xmZ_ zx?&buT(d>S*xdWmcU!WtNVS6$12^ z05NP5C@u;&p9$Fx#H_2G%~UXr*bkp-!Oi=Q{h5lM>|{f~0KXHN5yD(Z)Oywg{9Y$9 zH`$Fp-T)ZaB6U|c9GGSsv;Yr{63Uhw*XHUbfs;k|=k&Fj*R~kp^ln*$v;(4S?k!c& z-AgsVRL$-R@fSNV5QH=&5)I{Zgc_{&N|JhvtSM*}Gk~_Xa>WVw0~Sw6@#)i#FemNC zA4K!>{7*)*LD|i3Sz<_gFq*wtrir}|BKDU}{fbi(K}Ce^3U|)S&=C+&RctWyJqdqp zxMiKYHaO60^v$K!gjilXJ5tRLokpdt>aPnpKm>r}^?k^j^%1#`anlA7l;i8mAV<3{ zjAzq*5~*TptdUT`$~qUd;)4CO!Wgo{ud$e>aW*@Lf z`pGxhQEd<61a*7x@q`TRXI0q*P0_(E=vp(I888_5j_*urzJn*almP0Szt?f&w zG1zKX|24n}2y!q&B21siXcOyKqeJD6%oy-Wz%a=VH$Gxk|6>s@2jsgoQsLDgtD=BV zpi=&oGH3%BG7olvf0Cf8q-ts+{Yv11`N3r+mTscT1T})qHqLP*qKSpZSy~GU_dy;s zt$UcnUQpxyS_YBi0y7n$*6mZ=>rhvNKMR1|m)ML>Tn+Re2ZK8Si9+mPv4@1d0m);1 z|B&KJLDbF?!%tRzuquB-!9Z_F;2tjmXVW4&A1P&`mRWDufm9&L9D2zF?r&fry)GGs z26d-`@hpY&JaS#;)B}Q}=K@Qr7xSCpn!@2FmEm7d!Wqk3hDijEjiX#2R(YOAA{iDR zsvitlS6WyAbz>`e(ZK>yEp-i9-&L0NcI-%ZGPm*1?V1bBSOax(uGTa9R<*J848T7b#ap3m$;{T1Bbpqj0~y)1E=GVUS){|^o-F4?Q7m^m zxTH<#xXF4kj4hC|tc9QHL%RXnLgu_wv;(_!-5B<%e_T_rV9t%ecA=7hjM$@~xZ^?Z zO!@)onhx?`G`8OVe;#s(&ttOnv>mmhDc^)?`{|?1hRg( zk(^U`GpRb}fgJ%dxR3IVS&K%DYiRkT#;JrY7SUgEgj{qvR)ld$$*RjTQts7*0Rh^j zf1_xx5E;+@BkZTom@EO7#i$hl^X5qXfVN1Fp96=zKm*~D9WwB9B# zuwz{!*-|TXAUWm{ClC$$5q5)(hX|ovtrIJ4UY`SRfu`KK7Q%pEjMnbhf9I0(jB*S- zmmDNzZjyZ*((Ag(voI)Ad^y5X$gi(sE6r~!1Ph-# zE|;yb$-_#Gw+pbA>6Ej_6OZ})9SrD8BvdV4tl~(Ao*nVcP5ugA&k+G)2Uj?}%Ccmh zyO_cDVb6jb(?~nRmlrAyCfIF;q5G-&<4DxKNsEM_zy-9@d!&Uz80)Gj4G^K|jJDfx zlsvkJsJ7e3;ojCs?HyO%sIJ_5!zDAtw!SPghm>#tZU;LPqrv8^$mq%pDvZQi-ZPoN zR(c#o6&$dl=!EZ)szO8qaj*AZL-zcY63qabukCaf1-re>!wTE#;KB~Uo$cNWC(w|m z^KB#A=-;SzF&~XVjO&&J;k-CE`3WxymDgXlJkfa06EPJ$sdKxAKerhO7wwXJ65(3K z-?b5d|F--5FuQBM!Ixil30XIQO%zy9%A%@1+prd&JuT%m^JTTCZn_O*cNm9CBK$*c zuYlY5M|TLM%X!_@0IB9$o1K^x&Icu@>jPz}(N;iaeBX*f%KI=xE8LTw1!fsyy$Y5| zp-=?QZyT+4Xes-%c}t!h3s-N6H{1gSQ-0wguPoBEdWr~CBZ);-9KpJRc5}?>w>gh_ z$?*)aoSkmg<<{SddZJ}QNmB}wfd#im?V^QHw`_{i`e$1b9`ontbaCMG7AER)g!<-= z1^5u-4#}L`JkTm0G`F#L9k7b&R)Xq%rzbk@o5v&21~mxd2!qi)e4ra&u=N7nnnG9y zgi}0k=f;~7DT%0l%a9SA1BZ^j11M*FTj!gwRT9*X-Q1nPA}CZ;K8T{rx7o>txv_?^ zjR(a-vj>Zrl!lxtw46WAIkCBJ%?KWRZ-;BmaL7RB?AuWWb;o4JgxQc?zu=o|@|}V` zo^qbgxi6mn_E+>wTkVFgObFKW$?FNj=nZV>SvS5^{9!ob%kY8oBb#@T4Qy}Tnrvef zg_f;C=qwrb{0x5?5xkMl?c`^0^IdyBwx8ugndeJ9=hL6^>gIftasQCndM_LniORsc ziUO8+og|Evx!Z(2(it4H$SwLqK)>#)x&Bhpe#5}BgGRBVYP+)&-)&Xmy)yG|qw-q4 zMoe4WkT5Rb>DX6Bw4Lq1XfO9{gUf0`tBnsOkS)nbfXovSBtbzGDdCk&y6BDU3n6I` z7W6ICPn!VBpBT50WFl-g&ngph;n_lpf(%IR>x&P;iobxYW9AS4@JYb)Q4;o<&;5F) ze;>Pi-D9LMeaT5$_(f&wqty3SsAC!fLN(zp4dUPk3+Z#B%kqWU$Ra^F7w6>kUU)n$ z$uq2PRnyZP(i79xQ{BKXKQp4wKfXh_wNpE~Q+i*~2R{i&&6b(I>l0Lr8dEWjpre9e zB+aHRFoFfK&H}Oa23-CHz!yTSVu1Zp0mtnmuq!3$wWsVobM$-I=NK8HHQ=R4$R^WI zY3q4p5g{JPQMfvngd+_!9d{s(_wih5m~I61fCKaJg@4q9ecXZ~H4ONC2G3rEyyrlG zwu6U$go2KE$FGHKRW8jr#u{7rzV@pkp27)d_r zuYsd9Ii9yIF`gp|)D}}~Ag8+uWopo0eUtt41T`fMd3BVi5)-7Q2y`(=OAWrt4cAeO z>QX~e=|V{>=-b>#@@j0}YzV32#2P6xexDtpNh$vk=NMLCICZv1C%V`D-eBm`Fw?|! zg~b|b5pl2K!VKJO3t^~sxKfTZ_Xpqp%TvSBc@(WmMi6?^A4D)JyY~3^DsufakaFxL zaR@p|NV3odwJ@}eZpf|y@eWb2Y^tkcSug8QDqy_>|E`<)KG5;DLEP~g$B%}0{~R_N z;!eYkaq_lr#(OsG-4fp!8Rc#6dVj_ik%QSFX2P;&5_FX@Kf6+}qcnI%QJ;>rGg{x8zPp0TjeeCuWD8OY9KoNN`kpO@EU#CaRGZSZsB%ry+2{k>7C{a~-V0 z!0BKp)V}KzX9S{%-0(v2f{|G{+fS@o=e-URb57dNROv^wn(&*lw8AMektE9SIeuF4 z{T9M=AJc=RTnsDh*$l~{|HzYddikPyfY=U!Lr z{c-;XdqF6P!2;0g7u=umG2K8y+BV5ZRBk-`wx-8XYxHGj;6x!_;?s#V9I-#S9(-k-pcj94(0 z6+}Z>!YDfsPxR7+1h06_tX8pz%`80Dd}1JWx2iHmMn+L5u^OA8t7y3v;sNf74had! z3%T?Vk15az{>D1x2t*Jh?1|U)F_e0aM^IGrg2Ru5dblM2Sa5GMvngJNg=X53P$95! z0jSAB$L^B)*sl-~GqR)V>6rdo=CRas5*WH#;9)-od`sC7JZJgJNKZQ}I=k zWXrz0#<-f69Qph7SpEQV$$o*yGffiW{3f+0stUIlfDppBKo=Ie*^!cn9wk9r;IS$9gT*uTOcy80qtdb);X8fo_MviZs`lRwgvvGMJ>HiUz2;!RZv%3deb<%+iX1w|Bz)X;}M|5_*9;D z65lJ>{(M967q~?L&tB?pUI-r?Yc15p>}&lgDw-QEqD-Y{-Mu zWRE1sELqn>8f72k%7d@5YP$rNgffg0q)0GnfUxc27W+)b;W zK&N0U?IE5nJ?*^kM#R_$h_)4`MUXc+N6k!Y#-Yw?Sh8)`)8Ir>2OT?>dz|=6xL0m< zI~<(c>vy=TUnzCmgaSyNkcF;zGTll!G&Omr&8e4|d#Dp^^Ej?-)7%l@0;G#n6ZDN6 zNIbHUR>M}kR~iB=%V{L%O$0oAxQs_pNq=|#EXS5j93;O_z)7s(16+IWjk@o)(l!%I z3rZ({P5TcXX?Z5EhXg{g*mpMz%`jW#R}v>e_S{BU}!Xp@b;yYw#a3};@!Ecwg@IK4!ht7Hp1dI371c@uCUc9DdN7x<+DWP-Pwm}O?H&7?f zW+SWVUbNiMZ<&w8OCr8|N7OSSkvP-0zmyAhOupih3441Jti)(rkUY=IaB1<;#;wp- zzFSGLsUj^0rD8E=aS-5d(kV0_w1=Y70buCusQl4#_2` zR0(o4Py0+XwVoPx{W15c$WK|4bi^kyD5@b3`RUMzeCsffMxh^5)CX}m-E#R}_7?ew zTy%c%+vcB*^*9TX_9TVrs(PhM^(MX?_{4TC+8d~+@=O|CjM3X0DgQlbr{C!<{Sa#* zmq8^r;~SfGFTJ{x%fztxefc43?dIi^nO;l-fx--H*$e>>I)fmizH2NOR3P7dONz5Gww%j=(SO{J@%<$vK``F=o}LjPoI zivPF5HHZH{+{=N%%;Eotd%3y(Q}wr8%bf-l2S_CTC6s~z%ri-$SRf_E_&ws6<&)?F zM-zt;Le6^vozbCMC=s(gDuAL664D6}#yW0u_IQrqylj2_$It)d-4ON9&u`2iO9#j4 z{!d*G$^`_3oSwDRgskyRQfpM_zOOC;#)ljIQjrBimkC3i3q_Ynm@u|S=5~KJ7TNx$ z%e;orCBnD;RHk!t`jN8MJbhqo z>#goe!FbaWu7iW9%Y!e`2&>kV?4h;o1SeaYY;vE(K8 zA|`6}vq?zaYLX&D9Q(;bMvxy#2wwWrqwib0JfDZ|U6Z5T`N*-F-O=dfu+2#7 z+;R=b+>x^3QY0y|JND03l}U3A0(YGUw#NOH=9$pft?SNxcy0e%@BCPO^HA&g()Tv~ z{uxre8-1R&%cr)j8&q{i0UePqW4R4^mHlO!3^Y|%1T8K%=dabDr${%UZGK|$q{$I< zSx_~)cMJv;_)+|$3GtB<;HDjW9J7gkL(f*Zj{!SjaD$U3$d>AUts48`cD()%E#?0xDmDcM$ zMrV(Ytc70VJpcl&ZaWFSCmQW%;R6urV~`280h(gDvBTMVs{1pfw;G#(2q6tM&V|@>g&bi32VYr?dn9-1<+5M;B+|X5W8I?7qk=d2zzC8vIjh*wO z(-6^~^P*899qY0F-PCxqqQug7teS+Ibk#&h?ZLu?hVo3w5nA{B6%d$IPl)%rEj*uK z7^0!bfZKE2-U+ zvR-U2g{OLTB%hO8yUs5Am($Ub+sgGj;h+Y@Q9-naPm{24`YidW;gRFZ3MZWK+B)Z^ zmpxCTo72mD+S`sYS-+VzIn7=TF^uv^g(g>rZj4h?Tg=jzS@zi_wQafGX6RgGCE&9d zyOx)_J>m8acQ+csWH9SXp4?TMtmq$PX~^3zxI24Q^wx(Jz>YqJ zf>%r75Lr?F{rvr3FOK{K1*ox1WCcwo$LG1OH zr^oFVTZ{Yt*+l^@;jJy?HKgv=59RYM`VTjuy|%n7R3V&@kPc89ZSXFpc*aR3K}2VNlHm=ugG^%S5nebdXLG9;!%;6 zQUa$gp?;|e_>tpD4BN=IUpitV&383rBej*3l$0u$9@|pta8%co5))IE(v_1{`qHmX z32lju4z2p?(K-3qFaN300aIo0#37Kc3C+o|`ALaL$;_;2iCxh*;!_pIvLEYVevOTN z)|>WucEQ%mx%Toc5$JpKt)+!nQbqY|;U#=ZL5{svk4 z%OkvvldE;@7q#T4{wkKKm9jV`k6TVzLhmqKKhz^T7wLeGgH$t@_XbI|CvMRHVh2~hv{VS|E~1{4nw)})frfr~a&+u9KK-7Sen-T6<$d0f zbl4(kt5=}cB_k8&d&l!%%LmN+nr<5A5_Sp(K=V5@D6EvcoT86kmQ5vcro z@aKB;eDU6Z=&)K7J>;64xW@PT-*C{oPA{i5(4PfLV2<=Db9Y&`?7NaVd85>-a_u2y zCw{d#h76&5tGes&2>$utH&;DDD4xh+X(wKsLL6}@;%UEG)_p&8d`Ym;c=n~orke#M z>-u7PU(1F|HcjMVRiSUFZa}S@y;wD5l zLX;keB}B|cX4_+k;3F)Om|vUW_)j)8t_=++73rLC=o?ND^>&%G&)ts`6a$5lqS~BqGN&ImSIWIPNMh0_nFuEgXN%c$=sSL?LXj#i6b!QYJ6xzg8ziO{l z{Zz#@Xh|e8+i+&Zy{?Q+(SU1d#w9>Z>G4@aPzHub@G5`vNO9&~d(a=ODqw1;A2?K&U}4L{xRLx@p2Y{|M++FU{{DLUzY3zlXOv06#p&u{9o z{43{DV^Mgjyz}S)+vmuo(Yw6OMJT6slDzT~6rr|#YB&DElw!8`L^T6UnTgrZ5|1dl zYuQ2GU^Dr&q|$+Uvh*VNCR$-()5Nc~z)MezY2$CtX)p~_q!jLSQJEYc0Sp4T&{V|G zU2ZrCLe2W6Sd>e7S|eG&G?tpun})Np%nx@<1@rT%7>DL~U}k6&F){PE|8qZgsOZRo zmrzvgz9H4AplG|U^9{YmxjMMdnh_1tGG5kcMO0m}k@Qm8R|X2G=zfH2HmKr|-Ar(r zS6PGYYgLo3qy z8R>5%?^*o0Y~V9VvI-k|-}!~@z@_4dz8qK^Ku9^Uf5Cz706A&}$%mCIsvZW-k|XDQ zzvF9pWtw~sm-_w*JUCPiZ`KyKYY*Q5TuX)?+7NoWEq2TY8%a#!HH716fUD1eRLv-9 znc@ucb^FG49Tr5S{R;lg42{-Tc_tA{IFVVwhE({DGM@gj3x5*ceJ11z?)rfEQrl7u zI(iZfD1{2?HC?ay@ymPSfp(0~QcbXybWRC@q-r| z+3>`+ZN}o_zH(Bu`Rhvlnj5{~b3}`yyFW9F-vd9vW{ z@tUQv9h3W_BZLKi*62d?uRN3pT1HR9VC|eG@zi zm=px$0o;~%M&|<6an|~|db_!Wv>`l(vQ<8Tjr_JS&i6K$5RVOgH+r*TLjl|x#EXNx z`U9KQ*C9I|f@9gTb7HL|o*?MoL5>tn>J&f69o@hX!PzgXL0)HoDnX2fY*w9WkS(~? zUiTKf7%dCFyQqK~?t{OnUUBC}+U#{F$(4et_Fz(0S_A$I-06eq?g zL@`GKoMUkqfort-= zw*Gd}m*@|1C2ZFRo2UxqI6Yy}!}tAETXI&15c@lcJ#0_=M95@)q?*^rbiUpk-=@lO#Z+iUI+J-&I$r_fPua5P}>!m8` z9V)RqV5u{?O2_d!=Y?gwya~tZXb$y;`f9UZS7(D(*$49!@9QiJhi6~wpPfn=)S_iE zNf8Kgu_9Tk2WBb15Y9bEP>t&H;%Z@aeM*+|2M zhjVmdptg_fh$Cnymlz{IsT5gc4nj4R*NZc1D(fjvQZv4Afq2+1@t%cyc+`Waio6uM z{bhe0hdGA++#-Y00^Ex0L?@6Z9d<3IrPyUYEDFV$x$-}UM-vH&{1c_!Luv0OtVThWb-VI zUjw4%cR0(FvaR@CU*(U9vJ_v%XJh$a>&1!sVd}CCj@nNCr2f*LzYxKY<;R-V1o@@J z%hG^ITV zF513X@OZJ#IkkuSEY+Wt`ttBPw(Ik6)*lhLREq7J#S)e?2mMVi1cHr3qOT2Z%WW)| z;2Z3Q-a?Y3MyBvln!1WQe*8eEn>Lze@81+7f2K82)&(?BWCB#^XL4>h;^iT0)+Mao z4-2sFsq9;?9t~b^2#oVMtfBhs@TX`aitu^^tM}6*kqnyUcE8eQ_22)&**QdM61E98QJIytZB^R*(zb2ewr$(CZJU+0 zZL{m2&CFsJ-MxuLoD=87GU9pfb8px|;YcHWD?*RZtYGcrUrxG#_jwHPugwi-TmR3t3vNQiDkWL#t< zV|XP0$Z+vUcUi1tSmYm4!*q}V^Jd7$eEJYVX1^hBGq})NDZuOyn5`lR?cT0IS^&;= zZ~9TgTmOT?-kjqvJIIi5X=EW&*mB{KmC_Nm&&?Oj@=5a_4=ciaRCm{n@}F z;eE+$`QRI&gXX1Fe-=8^6=cY#TBDjSjcCvI8zZTqZj$so)*_vIiEgR_0sp2Ml5kC# zvImWX(ia^Dsr)V@IZ=n&b`Fv?EV=h)AbC0ipqW%Z23MWFP6-dmrOveSU28O5bU#c`P-Ubgd7PV_v0h)xcL3>bw>-Kd zW6~Q_(i?1!JT`oTfj+C+?@m2}Sp>ArowI)`Mk!GTp$w77OMl5+MKL{O=(@a-kjLZl z$K^pMn9)$UH+bz5{0>mSW6#gr&aJ-Qxu7m+F%!7d?_$l?5>J6Vs*nftP(@~qNK#wV zT@J?7vHSEtCF#(>dSKM3ABh*2We9;WzsDm9EAR1LrFmyi=XXFr2-HSk1S0f-{=%b3 z2eR;y$#M597WmRfn$AzkfJlj|Q)jK6pf;#aH>6Jy9Ydc^0uzL5w`^xr{JM+t4u30H zBhT-Xm0CoQ{B3w?E!i9#dwT>|PXe z1wenA1P82Scwh)(Ll?hGLt^Cbiuk*enY)`ryQBHLTRJ~Rt5rx}Txcrx%>u$GXlIKV zc4%N{to}IzfiN0?HYxh(>VVQ!5LVR?xU~^Hln`drG4kawj8!m@)i6?3Ft)^8*`aKQ_-jSJU(3)@W#KB)^FfQZaxQZ{v<-)MEP zIT6_W=Ai``(;z0LlgF_Ww^>J$P)CkgOUH6l-JBl8X#slrtiSl}!GP7}%T`$`)0Fj~ zpi5l>7Q2Ii3x=TlpkY;n6f+9T1LvB~%-+eE2VpYKFs28T#|y-cd?B=;euLG|-G(f- z^kaGqq)S!tgG4B+PFDs6R0J(n)}$?F^OXZNouYzq^b3Sx0>_aSmI*ahpk!1e3sr0a zjRaED|IU&@koAPUd((M8`H;?aHUV7V`i40E#i00yozyZwRWOLxGB{Sp_X*4x%S#Y! z;}%r?s;n0HWT)Vo2~J`sFRE4W>^2Su z|9Z)Py~gv}i;MDAxr%qR9CrVV$|)NDQH_lEg09q$G>aMZbdY;^yxUM09)ClM5x*)? z7*>H@Tz=TD)|reMB&yC_7uVEE_$4gr<%L@)pXtDdEvcZXSFL2F%xX!VV-N~&T2}_LMrfXW}Q=&^1JY&^qy#|b2 z2mJ`u-Vt%bDwV=HJ}cP_rlQ-z9}(*MQTB{P%tb}8h`K7>#>hVv0>$bVP{#_`$+tn? z<5B)FdZ8XD$bK*gbT$r_8)P*slP$!unaNSgI{&<*Mxe5uQotTTMYqi_*Vt5r*jVA% zovNOGL?8s9!CMKgcoX4l8yk5_OcYp7=%k>0N#en>sQPX?@otLi9ySSYTbg%;fDZ-U z$6}Tj@3gB)k|$%97k^Gac5Z2M=tG7iqN?_{HLFIRz$H)aVX7?m(TsE`8TqiEGUUD_ zvAk+N`Qie~MZmG?=tS zqaaM9Hq9^*v#}9*G5znOww3Ub$uwiOp)ivtF_i7_y(0@4gXJ^t=X-^j>^oW=$XlMl zB{gmtD(mRn9Wk;&r$#N)USJh=!Nfgl40>zgJ zgj1NG5>=#gTE-S?0Z|H4+Ry2vPs*gxJ&uW1+leu7&KkXBh1BkY?l`&Tt|G0w21z>} zWGy^2mYv*To_|KLC)ITC%Ko$)TO6D%A@JlKO{Q}Xwq$;-O)L%vZn|U6l&zPb z(<~}uZW>R}qD~AZPcTKNFwHh=&lxL58!$~{CRT?tVdShC9IXW;jJNcU>(sBMMhzYr z3?fP>4n{fNXEzXEGZ}_2A|J-J19CWW56Vvn8{H<$8;mC~*>?jCR!#GhMq|I^byl0T zR^inNsOXmJ=VE;eJ-ibo$Wz=mB`;aC?&*KYW`>vA4l_+1q|4k@9p8_j!>6ZT0mi-Tw`K6Y7 zsVl^^wXMWJ#_Xi9m{9fta$HCh)N^{^?A`F-WqvcIR3C<8Q5 zVDWYhcM0U6^w>gW*y>Y#qsuqplSpbc{?iUf1-WQSb`BgR}vz^TOlv1 zpQBR(sa6~7{LPErb9d6T__qKu%X(uIdLwmw!>h~pm(5SR#giz-Wo+F^t_vUoh$5Oi zBm)b6pZq{l-wq3*OX&I_MqndmVIW3g0}e*Qn-)hQI22}E86x{pKS*H^g3IGjoBbxI z|EfDw#ZyGzlMMf5YW%v~>j)S@7DbbqqqZ2R{yv!TJ3{`Kq**05NNuyx z^3t5=(=)t;|Kb0_m{ZxP$Q85tDyTA`xp9I&BTgup?a)YENSk>IbEr9UnYd+&?d4lL zGmYc6zfqW8%acP2&_0t)v4hEGFeo|LOJucdogP~$G!UjCz45=dv|>`t&Up z5V8^;I1-O{N8z8=&t*+n{ey>?oQ0XR)}D#G>li>(!1Qk)bfQM+)Y_oLyS>j!YOfpG zj3np4QO1vu&P6x3w#A^A-bN;sWn{8s`4I^D*Wa;|uP>mIu z#Av+VS67&tqCWUcjEDGMk78W!@ww%K9XB+Et^)cIzmP2mar+{7L)^#*%b2zTR3dL^c4(OfIhtkF}{gY=ES_4Affgz^nMrV`taM z7qP0-$uL1{MQy7|&}8L{X9DIt@?zJ~ASFLuGGsF&5I$WS{fj3+;Tfg9!Jfm(lXUCnk)Ccip8VESeCxoHS8GEc zu%#hXGh8$KvNgPy)P20aGX<$t@Y$3^Kmaaf=gtFh<$NqZEjp*JZm~bSh29Sf>s2N5 z~L zsVt zd|`~UF@D~fGU8sqC^yBvD@-UeE8}m6?iRS#v+O$O-J$&70KwR7Ry+vZOy}Yj>{KZdUFlik`>QPY zCa?gXs9_d;j&p+^JlzQrQ+Z^gbL~FKi^&axKLjOsryH&_(GJl&l@Vimcwhn@IEL{( zi}>`q3fqV^#a2nt;O5%G-74gQwW4~)(bW~6F;|S4mdwu3WT- z#0A3v))+X_ob4)z^%Er6DQbx|e*|Q6FTi>AY3SdG6k_t+!@^pX&)e*XuOpSTxWrKG zBL}Vkm&y?1DX}9>s>)**&IWZ`e=SSA*PG#V#4$;@^raVl2EA$z8Ev?nJ@a&Q$}&rn zcn3=U;3S~E9BuN|1v>ekV3NZB1(W_C^>o3~(xKA-i9ZU+)BH<4{)XVS zgh^rgM&eZAuMq z5J+nh(-46Uw3}Thm==Q2m!N_q7Kd0AkjD^`Ppcw?d1DN6|KPd#$E4*R@O3n**Sy7(+_vBC271hp?v)n_EoQe3&jkVjuN#U_#Xx|;jTle1&*W#s{qn|IAzL&1PB7o23?8=GvK%i{K&)c``_F%_tuX1QW##CJ+r89$#CPacK*y%wS0RtKD<I~AqjB=>s zICf?Pr|t9L-$|VwpZBki+;wy)gQtiW+YXlVP~RRDEYDL~E>a>D8fK1x#;&-WxBaDt zWo;8K^pPFjCDVWE<~c@%!HL2^ZC#x!uZ4}`)2@!EH(q7UM#r3Jnr_AT2L~MH8K?_{ z^Q-VvZ;zioYxljrP|MZ`-RSPuOZKgn1W|6x_M`rJ+1#byH{Yzh$t|Y0Y!6;k`9|MY z+ZUaoJ@{826{pHhtvWt)_D^O<^BkqYH@!Hk+j1&yCsT3Je}|VPZO@iVD-A=HX5rz# zx_xi>KjJ5#b7VmV*LoL{QuM>D_@#v!WW?D_XlrDTn zZEI#I+v3i~PVn{kqh5!nTL;sZ7Ec;5zz0|g{=GNzDyZeLEyypJQ2%F{g(1TvGcA1_ z-p+NW$vkm`jIOomvH2SB`}_M^O4e0Yb_FuXa^!ev3d7&hB4o>#BxuNVsHFD5v_?|0 z_{S2bk&Nb#V@CCruawxHsL*HY?h;Jv591gOE6uPr!l3Jmonc35QQOb&?D%$iz&~!u zRLJIdw39AsR%}utHagdD;|pH-AGgHY*S7jIa@V+!_%k`vruOoB_?q;(G!+F|HR`KR z?t(|p>%A2OYM3CnV9@y8%=yPC!&+szqYWq*L6&TCXK`ca4+S;J*x=yKVCLZcooR@h z`zyVAf7K7fYwl1uW@{Uy4aVZe%IGN5)ZohG$E74=r6eW4XXVqAv$B%o)4yxYJ#LBc*18-d=&+?EL&yavQ#m z2j6sPKbaRlKag(#!H7v=5pWxIc#zEGvpg{jQp_2guJJ-p-lDGc?7zur;Y z?sn{KJz1WFGB1bV-$PP5uwxbU8IUIUOG+EZ{WyY`v!0YK3OCo|c>*-{YVAFEQB# zFxTE+4|;7|Wnt+0Td5*atJXtpah5O~ABjf0JClpf;5u`uzQ;5uAQ%wHeu=qQ3MK=4 zYx?%~?&j$(V+6*gihe>V6Ez+K_w3Tb>SXyx68b^hg!vx@MJx#FU6Df@EJbvVaLY~& z?oSA0AVZWYiL!VF zL+IbWL>AVS?X|s@5~_E>e7+&2Us%2^?c%1 zZVG<6HH;%W^M}{hV50m* zT2#01`a$Y>;vDwaj-cx{%UrzeX;($+^O3=o;ZBC2?p12`hR^G6hiNzZY<~ef+w;|Oq@iu{ zXvs0pdG^qF&JT@~y!3rR$!tZ8K=spSWi{`p2ws+!_se<9os{+3ns97{5Y1plJ^bvA zPUkbnH{F9)d@g@_o6(>CdKprpkLCGp{N979CJ*d==Sg3m`wRoRhv= zo2Lu1r+sL&?5RJqkiupnOiBO-BD+GmX3Eih2xHJKH>%;!Gl1E(eeup4670s}*|v&T zUbEg?LP5Kk;>=t7swd#(>1q0A=k3R<7+f*eaIBaCdnP>d3ANtrhtgIBLY{SAc$V zUOx|kWusoeV!hmO=QT-x!F_Je&iQwsj;8q~>Ls%--rZ z_RTwux@_#c$ykQh`Nmhf4}Ld3_bpzc(%O}4{$_6P^Ac}tLAOE*?xF>i4%NZ4Z&Y__ z@7dA|`8YbDVDdlJg1ioe{I zB-7n(t_M!}baL_MU4?f?X)5ov+lig?(Xr@V>n1n?x4zW3`$v*y+=Kjvv-)fUNu1JW z?e#hA_-}?|Zb=WvYd_7$)%^EarU>hK_BdV@?hg}ytjp`^rPb?Y=`z(6E&J^u&$;|M z7oY#t(wF%{dXYA^tAZzk<5}wS&GzFf@p}DtEV$%|uO*EnW-)F{s69%U z#pEGDi^;3vuZggoUo0(SPj`SQ@$&n}kuPWRTU%<$#+X7XrPBtsTqhXWHIy5f-Zcgm zPB7V7waU8}g*kH#>)-H!!>lpC&O5kTT-7b{%}TCl?IFM}*5yFfSX^E!xBBhvTW%p6 z40}aP2MZt7shexB-d%HE|G>9H@YDV*plXNCs^mHsGw9Ej^6Jady37e9c^(5>E2quU z?oibSK-bk%a-VGWuCtxsYsN-hFUoMp@W(wm=S=j3b63!JyQ90+e&;C4(9qtbvAYE0 zk~e9uZ;2_paYD7Dme^>_c^0^LOx%!5X5=r7WR{ICD<0P^2YdgCq@%6G!4)WIdS!QQ z*MszWH$`#o=doB=;{NSS<8_q+?y1_7rgS-4eb3s+u84t>H)2sDS+5vSBKJY51 z>Grdu`w9;zjw)# zxog#8Mt}cJx?m|_Y=0CwhV%_jmK`7ej{Gd;a(Ph1$|x{W+j}YaD3*#1JJ*58Tdjv6 zuMqQlsV|nj?sL7GG<7ew^wl$4wb!%vkznQ|>Sp(y^kA}(89sXE9V4b@9uuc5$8__w zeayIiEn?JvQEFzo+b3Ws{oKRS@L~K54*xec*|e=%ZNW-P(`iW&A<5Vi0a-hYjDx1 zm@cHZTxxPiI}1K8VcB-FHn_9yY_wwI(cC8sOX58zh_;Qd4zeEr+3Pp_QuMT&Rn^fS z8*aPTb9FC)(XCI4BS~q1M3N}DV_WUCSzGKa%*;5RrL%I>SkRrGU<@>#39U6l(5-gR z$MhaX!2-AIeZJbUPJZ%1wWIeAoP_Flk+_cu$d$GKoTIolMEZL8c ztXC_rChMZW5#{4|1hOs7y^DGsHFgT0$62_J`w@qDx!;p$m=KWJEwB_(B(yu74~i4z zm>bKyzEY+?(MTF572)3wdC0U^dD8sO`+!fX2pxyK6d^CVhp^%Z6~ZCxifsOSj4xvD zF}DaPtL*g8vh{@%SKm5-p29p8hdc}erVbAa|- zBX9V=(kC0|1Xf^Hy2n8aQNuU3;1xH>>L=&7J_V8KvE&2)8(wyAw9!Tz>plp}?+;1f zOfjnRNo44+{v20x!*G*8iOuv5cj>Hwz@Dj-r1inng_*2Iz3|L0Z=cy|3G-9>{s#y3 zSiRRgHDD44!Q5lm8#Fkj^K8Oji^UW8mnxbH+&!md-!Yspe3K($ruOa-*;s1)y9v=Q-lZv1_ zl_@~Zcw*zqEMn!EB7!5irfAM&EI1KHhQ9-f5l#Vb7FiaHIIv~2Qtqcbb{hv2N9mI=Wzc?$l8IX zust;5MD^m2|Apr^QW9iG`RC2AkK{8Mat69Vvxnr>fOHK;9G~}&Ff(8*?g|np-oKlB zPLg!ds4q6+P7nD$MG9b(@tX9T$%qnlPg7fNACbkLIB`jErP`u?ri;&BNgw-lC=#fu z7f5zL9$*J8I0VTyXn>g*xIQC#lZyk=A07wNi#k_@!Z}Yxt;dh;UUcN-_6N1e{2qwH zduGEV^&K3*XpD#9_}z`0m0KHSEb z?Tw$ebdsz-wBkcZI82pTh=bM->fJ3Y2WO7O`!w*eA| zHsI(ShB04K;gAA(@Prs0H7*|tAt_CVmH|W9D+yDZt!i(p*tea;lhg+|BE5!;R*8^H z`J<#D4TICC0%;cPv-cCXoBI$rlBH^7?=UF|c9oeUCk&(uO11xw;iQ<%jbp#xcuY)` zA5G`+&BhaIV;7O-D1mHiU^t`-GK}&ZEtN^Sz$oOdDtmvW+v*dAS&4y!`|lgfB&5<+ z-a=$I3o|NYa-$NOI2&Kie?=(Lu5Le18CfNUVPA{t+%bxIsj>LZ^3;eO3NQ=GG6BS2 ze&Eeb&5;h$$&AvSyiOjk@7wFA(6%6wX=E}b~OJpLp= zZrDnF^(;SziP}$r2Jm*zPPm3t`c&wgX|d{kiUPz3xShK^x>hI)5ACW{!HNzp^<+t;rm2|uY27N`?wx3P5g#MmlgHVfV3U7>Ogw6 z{ddBMHrijUFvBAQj45E;j4eg0*sRuFvZ1a@1S*e$oGAM*#|ihrgnoD&+Tb4jqu#Wa zA^N8D0`&35J8)W*2QJ>kfYIyD6!49|z);$&1!cb!N^Lz1gtsd?B_%VOaUxJreUrA+ zroph=`fPC0OGQ$~=w%cPGN5R8@yX@JBR{b5-mDaX-Ne8y6!Te}HiLn*s3XSZ38meK z1T2c-bVQ|BuG}01KtJT$3%j(UJc9$fMwN?AsN^G)>jN;9{loi zC(?IJz;_3q+o<&;AX}yV?PD<%6rNsvv@dguNDdUzwi-r$+erHFs6Uo{8E=h)+(b;} zNlv<5J)B$ZE9A&;WEiKMI6bgGGpJtjqSJ1f%vU#H+eiI}|N()At{cZfX zUS8s-$Z`onjS12figeD!Hv zkpV3wB%TF{8t_+&en*lWJ#NozfWX*lb!&~xMuLl_;(K%V*+CU@ZFnXJcukKG^BU+{ z&471&prlnmC`_H3#}29*i}I{&L|jAozu&q8!za+n8EY|EoVEHvlWa6w4;x1Z?CO^A z8pB*~pNtgP+7w$x&j!k@?G7KL7O{1#Q}Q+pA@fLnxQQ@qcTw-tTXT->zzBIoXPHV! z<-j_4_@2B|DKLV97VG1K;B$_LZDSLd3QBdLB&{NDFgNlFohB4DhdUVBo*$l7{$aZ^ zyJ`?eIj)R?hDu9DTB!SPnVG_Rh703g?8=S z>K>5DJEKLRq$uA7LOCdOrbX+Zw3wkp3&VP0F6f9!$Nc|r#;f>wxxhD)Pd*4f+Ztug z38^ytO0KZutoU+51-li8p~Xn>52kSOPq4E^@zn?xJZvT!wVDGlh2L(o5@L(d>kHmP4f31tq(5+-G0jyG_&F$6qLP zmNd&wOVoF#>^4cVTZ%|FaxS(Is-OJDo@ zQ3XtV$dd|ckbPl~x?(0ohr*{uh9P2=k&Z}dNF*9vY;VFfU>RN2*XHsK36WSsVIr9* z@{C@UwY?kcM;I)KPjo=23ur4|yhWZ@hz(D}F~2P9qbykzN{>`#FOVBmNW@v{jW3or zq4X#gC25!9wC*qB+yq&C`=R6HPWHcf--!S;(0p3xw0tS+(cPebOz?dRUG37^3im!E zHw`H<85qE9drY2h^5|6zbzTG<%FPbcV>S5&tL_~Q6^v^?7vA7Fn=DVw!>|rZT+Ygq z80^7KkP*c^|D!f{$;YXtg4=i?PKvC(I6E&+6BfqR41;8x{MOpzo z6{>%Bg1^D*yC|&3ngOj&kDk{#7RQS(A?Q)?XBbSmPklej(MBu{tF5{CwPBTPOAV*M(O*3 z>B;f4W;R@u5QEP|sw;h;E9A$$5K2qA&jJr1BC*#`=6OHccS}bm8YO z<+@F4@@9gAIy8B=reF{OMZLEl1A?0XHNg*$Up&xrz9q?G`+QE_TTdl`nvU`mNbiff zFCqNwK&iVl0uh+7FQ-6(j7~L(Rk;_eG$4X$mg~s&L~hUHv5}st zUrx|GJJbqNk%r0f?9$Vo`fBSvQ(u=fI;_8;h(FQ=1<6H~Ho)1-*NpZrDDTFJuYW() zh2(K=0@<_e%~QHb{4Mj;@Y|M%>a!8h?cqhcw|R)VbGeea$@-vk(lJf*Exyw!>^zk{ ziSEs77@n**oB2pX$=XZ57-yXO8ccsP6d(KyX7Y9yX!X4m30tAg z!E+T#LqbJ`l-1fAHAP4a>q3W^{%O0oW zOR1cD$Hl}St+h?n^+4|fC*#0i{U$eMHYz>qPdC9YI1M?*Y05l~fXU%hKF{Bo@D$!| z$Bi#oO36|>U(~lJ9UI1+aN;7e7Uw%XJ(`x;$~Wq$&V7M$ia)?Fq+K1EH3@Osa>fFJx+8Y+ zKe#Fz(Hjqbm96C3ck7mL{&04LF|iL3+$!09Af*(}b+z3|h^kHR08RY>@#DWZ%qdaT zY?2J`q`YZDC0hiEPH?vbJLmC+p-v(Xoe>lFh1+ShQcrW{Tbe3A?f~pJ(a^hY0=)?2 zQM>x*jjg+o4|67&FMv{bBYnZzasR_i&A1_UaHFe&W2pNuYx^E+C%-pz%3;pO{qFYrF3Biw8*=?$5fQ7R5OEuKxx!?kn*0P&rd9!1m$K zS2^t-cfc?WIPPeL-cZ4eC57!)q1Y3fgZtq$;~1AmP*makWF=|aS`_Kc9hg$J*r~3= zH@cMB8{T``;W)z4I+T&X=D-lJU63GnGJS{+)HG}SWM#y3#%zWL1Wa#D@}G=cYU;sl ztW$~z-f4*G#!nW6s>G)|XV@4*BBkT&^rSl4=NTo-n~s3ees9c|(qiw*ou`A<=t4@G z-$6xkg|}JSz)_=f*S8TA!L-Ci*>)>l*HsdIX@E*t@vAR~u%mjo%+}d$37>3>#yLEi z3>NWv(h9;7RFp)LR$p`fOMD~X?mUbT+Xa|G820XD2C>Ak@8WwU^fmottCuWp<)(0koE$r*<6n$kP zXL_evEy~W+VaF=?o|<|}+OE$|8ZPu9r0{4W8;3N}1;}e!E_z`}X8m>jU)MH;A-Ced z?`grsbjNjR$w#kfe31LC`nYVnc{*VV*E=Tq!=3Yb+%Tc{22VE>)4S|06p7pXc^-X= zl5S$i)(NUcqzvVsq;VD1uoYz-m$m{AZME(`EL*^kTE7B`fodxuItrSRxSle_LaY5g z1%r|z&({b#n)qJW2m-Fn`fUq$%j$@1j+Ou?tJ97nR5B+qx-bF+Ex)4H)r!r6mKFGh zoLbNHM@T~~`DTEjnigf{9fmDZ9-~wq5_bE}g~p28Tm1d*8(cZ|$$Ix3l|}O8giV&6 zmRORty^@<%cs<+Z&>E316CuUl_>@NASwmC#g*33axqdCA(}q?HtVb#o&ZgVmn>9}2 z^gt>(u}P*cq220n(~0P+ZVBtWlJYL(NS{x@yR=MYE6PxhnOj6>WF|N<`+M@ZsA{E| zf1z(S3=1F;YnfR)(7|J!&ZW;(H%uS#E9#cpb-woJ6)*T>W%pwp2iKdKnc1aB@s(s# zD^&0=A|5CnO#)7x-gh{9!B3h~=521+rS9{q(-$Xoj)I^Es=}1iq@xEg(wdGuom#5T zg;U-rW>Th%dge0teg*45AQx6f5LPYN0V$Hg!N&H}#uz9n7O(R07sqcb=7%L~#!_3> zY`Gr0O?*y)rphFbX28mGG>$tJ$den89sB``E^I6sK(@`dDlH1+=3k3^KXI5rx`-5( zwIqo{b*}dB-b;qo&&5uNBL8hr9Lt)7fhW$XgJMk#E6jsfmZgz!r4!+N-jbf3xN~ha zG$pG~yJxF`aK2JGKz$YI|242d0q&4x`2?L<<3L>~5j>^L_ZoJpXK^8fdS@nn@(D_w zu=2Z#!2==H)7>nUE=D$|R+EJ?d6qRoP?)cn$c*)U@&@ zdG^=yaZ(zv56zuWpEORh?OtRv1DM>HaTV0_A{;ejfZ2|fK~KZF&BpE& zP5to`XnztZzKcI%a=qLdc+ud{SylLY%$&&zQD)RCaOQ?L(#?reo!**QW`sr^{U zWIl)Zs;G<08UgW8S`m_xRv|_`OhHH%C4u!ki|3o%AhthZ9_OlyZan~&JUS7H3qfLB zYCwxsSeKTWBClobSQi{MPHsn8`kt7VGh?XZ$Bck^2OShQv%&K0Vt8>2*#;t~9cSz2 z%=yuvP}GhVpiwNTND*d*X^+b0G9xHl=d6r;e{znsE+D>kzR5M*A*j5103JtKfpwn@ z+*OM6H&Nl?q%qVUZ4==>A%~m^HZe~2qjKPot((AIE@ZDp#yRTDhhcrYMA(!aevX1{qKutrtnU=8jL+gsE~Q2W9|xgS zh$@)c?>7kRhIaXCZCan2nda~5L0O|;9TVV+S~Uc+7K0p?a$lUwu$?4$+~;o}1kc>_Kpgq}Uq<$J%{|9%c}3!D=W**wsLQiDNE< zPB$ft!eP}vF{5J?xA6MIR7*V}>|$_Z!P21JM^bN%D>uXe1q)Ip3g8wb&M$Ol)`64D zVAIZ_jp+VdWi@L$5#sr%yEr(*yysa!)IvaAAPtK`DOh>CwF*WUD>ezrqV$4H1_zIw zUiC5v38K2Y?qM~iD(Y@VZP1sHI^z&SvC0TLcOJyPDYU^_@{4}0qj!-)jDatvE!qSh z4)G$4CF4(VoLUoLh0o9yTC=uH+reviBb(>e4S=1k=esKs%I_8nSfhL<5Ca)fGzl`~ zLfA7c4*p{6eNluC3Kf8NZ*WK&=CI(I)z8vr9}2tg|3P8;4JW0K({7ydk^PIH77qEu z%NFg}i)YQfB3rhNLM`0mVGZDwPnj7t8=@7Sz1s?*YHB^<*9|$`&AsGLiEIw1l?@id zbGy|b+C&XyI$0FS%*;a`pug2#eLPaZ6PJ#{ZzlGMLXR3&CKbmD5_{iW7}?eALO2AS zI%t0NufvKUVlAvbX&2%=;O{9=fdZ8jiDph>mpvy!j5g=s4QxT%A+KoG5lGn|&F&-J z5q1N>oD%rzlsyL?Zgp+hcFJPIwXEfKw@7xwhd>GI>7bQnX(Xa|TjMcMch56F8|P9aMWID9A|Ybf z`x`ax)3Ru#w!Z-{VHg{KV;*5wk%b*7bUs!*YE`w&i9Z5rH=L_usp_Ddej#O-mG5_q zC}a3Htdq=?lfNqr{*fiIHuGfL{M?3(Q*&vqh_(X9swze`>Gp!8++mSaL9Dj7xRW6RP?zTtuh$$S6#yWEAXygB4Z_ymLi8@OWxfdb5LErt|8 zQ=NX~sY$RI!+_F=^)k1&xg)xY{;@MbvjG;?Zl8Yhl@K)43{Qg-5SJTlGVFp%yQxtetv{Z@RM{zz23h4kLaG#*GJ4KMRz_6Efemiph z)Y(~z$7e1vMl(Oh?=O`SQSWCthB7OI5{jI5-u?nF-J$teQTw`IegoU?D&nZR1j&<{ zP6Q%F!7rppsJOaXOocc#CaxG?^O;1$c9;R$>j~QqSJu-y8yMFo?DAGjjur9P76uRh z>Nn1MX+qlFuBv!A8(Q*3>7*SuYmO+ZYDiQ_Pf702(W|&%-!WqMPku*D7?2KQ#0uJ2 z#B~Cxjl>I*awz!zhNSKD4bku_*Eaw!wiF1)-x*?$D66enV2}r1jCKn3Pi#dxKp~_7 z&tg$+@e&l|V%VOYCfp0h|EFxUOJS1^uS;w`uO8awI2Ogdk@0Cv;wF+^U20>0aiv9p$mC_}!pO4Qr2`BYe%LUvq!%xxO zbYkm^7fhF6tL7k8#W~5x;YFqcqHPK81?#!5MS4Q$jQBqh8dxb;{5cgTru=?IRks%% zcfnLX859lkz(1rpFT9Hm8dx<*c&|?=tiLE*JRgz8-M)(7I*C7Ng5}&!*%))HS9&+U zwmq|A=zacWCJAbdSxDEz5y`)O$YoYzYObzh4rX}&*Thz7#MqWm>(hLtsErfXuARhE z5@c=u$qW`nG#7a|(6_l?`Fs7=&bnU2FP!neu zEvmyqFga5Y?Vf-b=~m%(GaCb2b_>;nV{p&4LFiON>f(K<`R)WOWcH3N3Ms29;kSi8 z5UPW+fvU(+2ISD-M4W8x)4CbycKGD=SbNS(ojj8Mxbi_KVl;P7bD{off? z@-sNPfS++q-!R6!kXxDs#+LJtY6@Zb>f!=-fOA|Acm~nfoh2r#z(bjmnd@jWNi;13 zgX;*h{ZzH?$WAjD=5AJ+yjX$O=h{;ebxxKl@#}j+sa&5+@1Xy@FOE^@`@~Wor-4PAf`olPztPc ze-E`@qF`Mo=B*!{4i1=EWqk3`I zzETHoGz#6(+tXC&n4mFDC_&>;!oXmE$(IsQX|46`zp)QC*x|xW7pamusr#DDXO4Jp zY~~|+%g$c}js^pA$TV`%<)pEf$q=c38vYmu^#np+<6Tdgr-orNmL9b+A6+jBIg$Bc zdLihLn>^?jA{vReA(YuqQrT~0ULlL*09>^_Re5V1!sjRC&zf%9$+YVHz%OtIrH+Un zDIjU$*|L)jv%@yes@~M+da6cT^j#HOf#rzn{?86*x@@`G0r-GL4MI^s}Wvy~B6mh1)^pofV7F|imz z*BdmJHFv&^hKKBoVsH;YR^FjmIPo^}g^b0UlCDIq2@}}WuP=RWT;=SFGXv9# z(%~p_4QKvQ_fMl=pz`D95!L}cE!@Fvu+4GFL!J#ncfjxd_%F`xDL9j`YZq{A+qN~y z#I|kQHYc`i+vXeFw(VqM|MS&f^&Rbly?0l2^-&*o_w)2x_qt$>4-|Nj0vE#_9Hmop z^-PKeT3s}{p%#%Vq>_)n%KhQci)KCTlBA! zbz%T;Kr{N+EKl?ctN|f9)V*MU3I1f?$VEvvX6@sM(64%NHhBrR4<^&n6sh2kw; zAtt65t>I`ux?*4zmV9%Xp&+Lzb(i!nW(&1{QuH9Q5lp=-XbtRelL}<79={UdgT)Gv z)ff}y-BmT(8V>Q1Khw@h^-vTfpXrfx&Y)uG3RgRg@9;12=>{D*VA&!mqauo0{j+W@ z8I*p&^66Yhh zx|Di4lowwY2Wj^AS+6)EhFrpUXd#Ak0oeZUe;}q(M%Cr*oA~#0Z%+`_kQ}tdN}lra z9ws^7^1NKmTK>vOf1>C#q&|sQK9z*~p;o(Zz&t2ytA-_Y?gMv(79i<#I()mR*22@f zw?TLn(wa;%WtYgZP8O`B>^#iPT|YE3bNe;P+YoH3Flqc!Y^I*2%f(GXIP*~~$I+4l zGW@Kn#GDiji2vKRv6SW< ziMXM3F7*e#t9>a|Griid7rMXy!(%BS%9zAwV36yJf+0w%5`iNT%Wl5TM*7mSz@l5s zKHMxFtbwL?+3-QlfIb)JEbIthqVRUJTU_(+qn7mh=11AekAwG0=ke%j9HC$P0|Wo> zGd7K1p6eY$+$^>eLZ~sLRX5v7n9FDgc#l_XG`o9OtUQXszuE0^^__&$&aU31lFI2; z>grR?%wkq~8up&jpyAf2jI zJx?&`2R$I^a2JYB%2hg|k8>K%Nbu6?CbznhOuH5f4Dwf*R!c)qeEFG1TE`AC4n=*& z-<-|fg|UWZX%W#|wtdUfU}d5e6NUl<{+efDZPo`Zsl;C0R4V?%D7z$fwT)z*V_4EUZ15M-?;^{!GDFvcgETAUx7k+dIKlEd ztkC1nRvOMsThSNSfx!4}c|MOxuK?V0==zZ+g5#*#(xVBW`|_h%i-3K&BCC!ZrHI^? zQ^JYeC#Q1J(gTwWD;Ujq3%=BAw@X3wgbcg~UDXZJM^Wf7s9$Ubz0r zvSZqH^Rb)}d}V#PE69~Y$^*ZK^`yMJS7&tB0dO0rP59ew(5Ie5kU>c_z(Vj?I2`qC z99R#|s_o{nT)HIQHY7z-I{;K%(^9QZ) zw6H;UnNBff8fxjM*prU?1f8d$DveR_kHeD!2C7BrAn&@0& z(WK>M3)v`;2}v7{XSg!dXc6iu0WAGRxx~0yf~K5Hq;bVuJjD>dhI#lrJx4S{;yo40 zvyav1@Se9`2_({oO<-Ys5;XCu7?Sg7mV-$Zy@>hHicK4<6fv!fZ3SLqHurSqve{KJ z3aXgkPO(Wt$ES9Qw!@cX)?x&Kp_p7#POR(B3Y!RM%9vLtAbh4w+ct-a12Vk>Hb#NB zwo$k4SLumo>2|K)Eq8(JpwKN0Re+S}pMG#+fQ;B3N0G2FzBm{MflQR1WL{~L$G73f zcEFUFdvCsR4Y^Qt%QNmMmtl~Sh5gWt>CqYBHINR!hD}~k7=hl3o5w%>MBCl}r0|_; zV{qi6uyxh?3PM)>i(bl$1oOQ|puvuzT#qKpShFsmTGIv8-xE_=7PEwWQxnPeow2jz zvP`UsX$gZp(HHTFy)t-fa5Lemi4}aBn^O6}1h@5uoyXUeK23-HjjIK|!CC+tqcQ)G zLP*`(Ed}{|=`)4Z9<2nT^^yQwF!)`nTH1sHt#$Fb3c%5?jN_Qr7^GV8g!-NE*d zrL4v9x1y3FwIMU8uy5Ut<&crF+n8u)+tM4`F!R-clf-Uu{(gFj$xSgMt7h*e%opt( zN}EXQie($xA%0EaMZ;;3pv((J()VNEbY5%x*QJ9RxDdD3T^5rHrJy4$jmjcku})wD zY~Wj8HcIoC#^6GLTlg$|A=%yEwQhFW3cLb}d#Qs{uADD!dgG^JVc9y`X}dH-;$CU< zsM$piph{HD<|0wUE1K=&ZUgIY5#Q?H5Vg$fV6=2__WA4apU_Z0v{&1@+tHS$xMv7S zen?Ubk+%G%JTHIOyj)M%guMw_F$)j~Q;1pgw9gTRt6}rf!0oU-V*t#oE}oV=+Vcp{ z!1zg#zf3Zk;hi(B=~3oxHU0$YiN5SqdlPukH;;dPnxotzJl%hrOI2^9)r=0EGRiYX zF|yPu0ZPosQ@c3sO5G`}0J|4D1v6${XVp{$=oT5qmwuTS~1&$VMU|s!KXRMUdq%*tGQ3rMQF>EYmJ#7 zP5KmG(LoZYDm6gTTWRU4v5N4YzPwWXw+dW&3fmT zO5r)>)#*oXK8Zllf55j7RlG%lM~+eP+#pbI1RKvpDTPxx4S0wAzE%wp$5zC%<_&Jp zpQHx~1E_`oc!mNG*qJR8`ZXnZ!}K$j*uoLe_$uPatwa>~slq>{&DSME5K?ML$ykvP zE7fT5j8H6IroMx}$6_#QQDy~SA#8;BDy7w4VYnWv z37%mmIWvTmz=PYHHT^8xn&DV13@@8$T+ZC<$8E@t5N67!=`z2h#-C+c<`?_WNDG4K z#PS%N$_jTV=8E;}H@ET|BWMf8*ScXS#@AO8^NoEyy^CCNywFeT6j1vN6*dh!Qf*Pe z*|pu6(2Z>HT;3>00p4@iVx`iZ*ZS!wrHs%tOp&XeR5r+&pLR{2WWo9c(G=`UAgISM zr&a>EYUx!Wa1c^dtD4a3Hc82|<*1xyMHy_d!1QDJa)yhvOY#t2MRgA7whwM?(;pK% z3%0f^fj_PWaiIQ6r>@;QTWP49%T;!)@!{J5K?HO+h;dlAF|@<2@E-J+XO`jbPlH1~ zf{dvKLL@mmTMi=9(j19=E%#7lhm$B0aAEk2LW(@~O(9!%AI=-6+l(9AkX7#CV)gP0 zEhz7%icdEWAZDtAC6)loU8EF5x(RMM7IjK{+S7WncX}Ro&--lif$ zK<3z1O7tYn4pZ%TfoAR;zlOr9nDB%=4C4ZE2w|%q4@t>bU&Hk~gz8XhoDCt_9n0S$ z$8#*61hNS9%d3opWy_(9zUIvw4|@hw9$&pn8vM*~mm(u61qt@P?Qw(P5FA*nywPj= ztoF8C#A?kIcf2P}<5MpB<|QMj7y{Nr8ScN^D5ARLW9i~;FJJ-;2i=GQP0>e8}7#wS~1{nF33X>Ee zx=C{xw2kTIF0b*tun4`x?DDxb78p(q)a;u2zaxGLNufowH9bKR@E%h+2)~CnZU@xX zgLO^m4iC+5s%oRDR^X?u_o+h5^K6@@h9j4aDE}#QjFmYsfOIL7$fy3)dmz%t+gZM~ z_l@x0Y(o9@1*%!KR-EdVso3=dRZm#ydLuUeX49vURC%R7tIOeSU$ZdfdCT|S2Hef} z;+7)LG$vvTO!i&0b~Rhjv{|DHqebgNgRkM<$f;lIYL!DQsl{k z)~%Td4$aleaUk-Xv%+aYIE^3<8CefRJ3env6wgZqxB5(bZq%_aUn1aXeVr1g1|zu# zdpQV#Cu#VqU5<-*DK0KRapqfJ=yK|^6Rvfe0|APTRgr*F3&{s8p`6FaGz*Gj^;jZN zB5y`)ocg?n_x6>8PrFwgyx8BHWp~(VM|aW)TV}Dajaovc1$prx$SE_mww4U@~fgi zuh-7-Nc6Ha7G|7oZ)=(W8+c}lX#1Q56}Ev`P{i_B18l}mq}z`fXMmE10di@-lK8kd z)EtDU@@G%eh;V#SvbKt{#jh{yYUNP%b2?$5Xu_|o z+nDsXE2Gs8HXQVt@=0TnqbbII!awo3Gshx&#?{!q%-#NGx53B{7<49ihR+VHO32DX z%>)m@_v%C=uFyoXUUZ&o(k@vdYs{P^d^e#cNsSfB1ZNvT)j4))X&;q4PBQ)!gS7M3 zP6Xy%=8UTCnBe_)IQDc#!m!|>*J7mDVs}g1CyJuk^UvZy+K+I{>rWo*2VW3&Xak;f zw6A`miy0ksiUM`5867=}aAMR&G;fNE6Zx_R4JI+PB{wU@>V}&t&9a7w+=fg4heQ05}QpSu-;u(q#Xi#R^K!Y%Fx5c4Zi(6-m*;ogNlPdbc79D z#~ufj??#Fxkl3R^Eg7t^nayOKL@^Mq;`K9M_B4^^=6GTct%h`=6s6HcO>J49LpZ|` z&i4`jCV_dHT13=YQ2(}gb0|8cwmCOW_Gj6$sDglkuypm`s%vf#jAB~>tDndq5Ra;o zMak&T6hr^0c@QJxZjXXN&%~H))c4K&1ncU~oFmh8_DHg8oq??ocsLCS6X` zP_t?*@?4F%dV1!qH^qEU^v%=sD*eeO_f|;Klm*QCRvFZ6*s)A>o)&uCv4^1NWk=8O zffkt8zUa~7cCX?jBBKOV?u3II8?@=SrQt}o3Rh2&s(in`R;3#2lW5@+=$giz_npIY z&kPg8o&E&>42xQR(iJfUs|3xvfWlemK{TVdK9dM>>4c~owO9pb6Q>i#T7E9ro%a|mI1&1=8tH)h(&kT zRqmv^hr4VwK6lSaavu1eAVU>_${swvzToY^KMI3}lR;~_-G3l~rl54ZnC$tq&Lq#! z5ROo(6KJR+%=e&5*B5}L4atDfUapOLyX}42ikNSU$|w&fCS{s84wt5 z^pAol}*% zNz*>@@=Opz_U$e+ygz*}*}0n$%Zas#Kq*181TZJw=Weis*dcAPDjYaaP0rXR-H1=iFYcQhdS*&MK|s4e z`Cj62Eg?+x8FnAPg)^eC1vEjI&rSQ=I%oL)Ram}o51=&nN`3?br)iyb!ZU&_ zSYU`&jC`q+#~H1oI}h{^uKzBAZpq1@r`2TOk!gAYMQoG6RQpq^rm~X8Cu6p}Ys^qw?SAXe;V!+4o*!&GPq4t55Qb-c^T z^$8tR@wzWNP|`vuEw`DOo@&bsrzP-LBy5a|BJ`-?H9NuC9;=0c71+W;iohxl0_!l9B_>Q7xZ5v%p+dV&^bkbeAS~Yt5*GgJHOhlIRwrYo2wcD-XRGx9z66; zXt2RczFMA~&3QtL#ivA;n&Bqc0S*|(Te)%c1~d0o37gsgbjNWe^7&$qwfebl=77aD zm`&p1Wye(~k7rzU^#k5SRT!NSL_l?Rf)GnVd!I8W9gzzJzJx;b_pOUjSVtV}*HW79 zmt)#alw5wZPnT-R)?nHo4<4?a;sc4E+>Tn2XU!j9*e&*WW z!%r=oX}Rx}0&BqEA)^n0oUgvfr?_qAHPDAxEq1o8V-!Quia50kY&`t2dY>&;G3Xb5 z@>w^bLH3-zxj?>@&d!ABzgqxwb>gfbqSM0yG{4qN&4yQ3Xn=)^r$veT=%tSUr(?xS z@qx^nisJQHXo=brBX^liBHec+SN$J|eI0QLMQ$G#$@!uDP@}q}0I9&?sxri0KXgmB ziUzyYf%|0H+Xp5mmBxbnXkw3T&h7PDB=@xUss<}plu5WEs7ayO0 zPm8cx4B(6mU;4|}Utf}Cr+5d9d6idU)oYM#32HMF;K9+A2R0O!BjuP+?>Qf3h+?Ii zy?kgx2Ws4#&n@ekILVj_!X7kXcxZ*qf5;QyIvtfMSFN2T>wM8e}mTxnSjPB^4&QV;;Rftc~`2Nm# zYllF){a7jjC!aYyv_ERa(N8H?(4AoIF`qfEy&0;s3x9ndEd3e zRYaZr&06yIY-`9dUiHN?Mj#O+o+A2_yxZuAI!YY^>2O#%>Op6m|90!c!8Ka_7CVt< zwI22NTz#LxrD^6(zuq2Bysh2ey}7O8UuKiO6?#lsQ0zb#lk`qY+zLF35PQ^uCj`bR z8n|&(KtT!4dmmo;tWEX)XR=0~aZe@gR~rq#cb7RF7(%i&u9e__hRJ6|L}>mh)`S&< zmFWf(>+cP@7z0~-QFvb;^}3Oo1~iBs@21>OBXksnC8OfEb!~<2nff;}>x;bM_6Wu% z$0?VMm23#Me1>R6uh4UYwwOmma9yUDu|XRUB_8av^u_1Xgx?^c;QXNjMNbB|iF?Mj zDCJIC18r=|EZwG7ycV40Aar_fd-hixhKU%t%?&iDn?7hJ8NSg42=F3-thE-t-H?&? z(N=fW(owso*_YrS`exqZ#Z~FdV;0i7q z#4lzORucV&-Cg!u^fK#MROl{xwULcl*?%o+QHUlY2LiidvnTu>@s2}MZ1v-M0D$Tv zHjB|+goIpj-x^>&qHXzj(COl zYYf}#+Rt!Tro(jkB$%8Mg7Fw2jjtLuqvZj8AqQek!p z)*}_yF-J7d+-o*XPu*6x#W*7wq1cHqs)=O+O$t+uYw|~zt@-=uS@XTtsBx=qR_syD z3~}xZLwqDxji$cEiTbu3;JFesbIzv9yQ5raLP7?OGp0z}KyZxrQ6@gtA{=xbuxtRb z?MvHp)9cc zM>I)CQKpJF*``R&!%9vnSED5{1F`wiiwp_JLj(~Gt*Bu!fGT7tg`x;l{0k!E zDul|D*-Ez6TY_S+j`Y++_<23{bRb=eP63PjMd*qnvD{^KIO--hVy~6|VA+@O7>rCR zVHq5t=Xrt3;+BzqN=EuV&SVTEfE*M8l1W%yzz5?2yH=@qgQdYxed>xK+5(MN5QX(G znuso?2uUcLZxB`OjZ>f%kY5&gL3zkRL?bDXs|xIir14wIWol|a6%(s zy+8DQ5t9Zb)&sCxX$oSM3gcrZ;VCdfE2|yr66umZlW%!kR!@dYS&=e${Nu&6^sZO< zAw?qH%2%KGao6od?Qt|}T^X@7@u=Y&oVi01ki`=5@ly2uFS@mzo9HY zuROa50CfIquwJ;p2+V0YKtWeqQ@HQVX;T{33NiA@D%hv*czYp3QR{Jw z+1S>4dW0X%S1brc*H~{zH;%bUzXDZJjGYy)K`lL;g?!gfcRgM+n#7d9F z0mAfZbWpSWq7CzbM!&^=UgX(f7bC3Wd^a2tCohVF9BeW02WpNrv+1LUMYlek|K*}cB8p@6XO3QE>g>81 z;Ya~vAR8#X0Hnqa{ivEnUpazV$&hlU$w*|13f;T2D^9<;SD~-*4be|^43Op&KZ)>> zMMUOLQVi9sa`qt~vt@#PToTD-UCsx~grN#0{wI<50ClmXop~&Q?D$SRW0A=aghKq^ zqr0C~vt(Lv7}ixVhrq(h64D{3U`brmB{)R#X5USQE|BtDgH!m5=HJXxff&JzOeoe< z{=Kj5Sy#|$DUG0#b`=$i_}gqgB%B^+j<>nwN$Y(U$zPinjmvC-In5Lm!o}-S1|lV% zpatcyH@k_3Yc%wx@XN9@5m>I2g;UVTHOS1A+lFiI>tDgxTMPBe*ik@VG043Ri}|$zYGa~owZv&@Qc|fEFEo`h1iL~P9XnxUw%q<5X*W}z$#X7z zHjB*o?u0u=VOXg?@sUO*~nZiPI2LCpr|<}I+XJ^lb|DR4bN)WhB4V#K?@@R z9WP0rOBQ-OnbA=T&jOmAK;DD9QnOXIG+d*BQlm1lgUnHJl|Rn#_hxH<*TYvJOMXGDJLi&R^GJ=h~Vu%Hp~aFj`}- zSFm6!%u|z?L5rkA*0NQ#D<=O1L3;otGCmqop|V62dO;wb8PkZzV%qPfi7&BGiLXiLoIKlt}fKGss z;=yg>PLyFX-7X2X@TuF?D9z6El0CF9Awb>}h|o#%+avB0t)vUu^?MLNRatnzjaZTQ zJ$UMrho;cxjT8z)A}hfV=mgrsMj-tJFUUA&gvDGxwsIK`N5&gqgxh6hBxIcvLcv}5 zI2A1$JHHlR*c)PL_m>^*vBut-lw)y$u%HGKh$OAWInkJ|EpcR#2kwxV2q1T(FQs!M z7n+^SIBPiLvyDpQA#cHdV*q`GSqvUPAJ`B@8VXRldQ(%^Z_uM-RBo};fGqwLkT7Y2 zSBwg~l)M)wMQ57Zu5sX}Y<5e%ns0sYH!bUdUx#_b_*?FNn}09*rhyr612?qJA(cVz z!>oBo2&6;pk5Dd%DlMpSQb3i&-=IJv)(^irX^7nVO*#rU!yqY@WuLCbwcyfU2U7oH zSplmS6K*I0iA9Ig>g&VcWXC8_Cqe@w5h3n94(h-rF&)1f=Ev(5hWqzH?)D)p9c37% zC+&S3I_*%sD}863CqFvub|Tf-AF9@Y1Rrq7j=YQ+ik-mD40v`SFIhrK5P1Mmd}n(- zi^A|`hAK_{1_*~Ni(-!#6(6i3Ak6o_#m)>{%M0o58M{EMRz8>DCn-fBR;eup-*G`( zs*x-RPrvW&ZZ0KR6#Gu=XJV)yug2VLmFf0BK5-eiH1jv`Ig3cAwk4J@Ck%D2l% z#-uv9NkdLVro+vjny_eLfdC@J6G22FHUL`xi{mi|Ba#_p0}GZ#zE@tywZdzgn0K%J z96kso%C7D;7O7O#DdRPpp_UMUMFd`2eGi!@SPZ6V4T$6fRpy4NuRuKS2ioaPTn#^a z?Uo%;eNzHub)$c{rBcVks-vV7_blITf{Nb#yct}7tzp5=_Yl!J+Snt9diZ8wz$LqJ zH~(OEz%_XUz*GLXX9B<0)g`&}hcXZtJ2?Ri%T#38yUV1H98W4nGNoL%>@RLmWRRbX znA1T}N5M|DO&<*@1HtC!@Jm2hY~Hl=iW#?4>ktE9xYLOS$oL*bA)p7a=zo+^Gj_k08ViK$8$kT*! zlz+%hZ02y{TiHPj`cqDLY2-lTF=Ohvii?$m_s9zc6P%>xeo0dC%7Gnu_R8)~K$@)03>-N&QjgLS(>R`K-NB%GAJVZBHvBC?8c1WCp4TPbLI&bclx80#3tR=B(O zk%E83oYrIxY>?fl9(Q^|wg4P>jSi_zRVG@ZEDK}_0ZTkS1YBs4fC+&VLbH~vD(7~+ z=TqrUryy`wW3TeT>G1fm;euiKDQ#rfzuEmyO?87_Cl!-9lC;2izmXEi63w+CSbwll zykl<@5Jy6@hFfk~EQ=LVr@b@szjrkR!YD?~lIpM6!l6LISjl=V0-&Y-V3ZM38G5|0AX^u>C zp$B`QMy50RAG5ym+|pXT1J^6-`mqD&-2NHluiSFK_)$pkYTGx+72uf$ShsMf{Bc@u zHjX9&C)`x*Lk4p^vjRo@0io}fIG{{z!>_+a*_|}k>V4;Gm0k8BqB7Tw=fVG2y$+*r zyiq#$I#s7REC@zhaF4))Z7yXYM*%h`2lE-ykD5ZC5Ep~ zGX8JsFVbaN-#-u^Q*3UVo8}yd9qgidQtc)HZh=Z(vRTfWL25ic+oJ1rIM%g{6G~e^USAu0QVND_w9lv4Au3t%*8)`E4_-xm>k_YX@JOYqz)s>PE#z)1n*<*e+Fk;jfi7B;%MQK;0@Za7y^cMU1Q4;Q&P}0E^m4YoWPRf>I$})>XYtjw zt_vd-pZ2yVnqVB$s}FJz+Yy^fFx|NzQ!;3c+m4HQV7>tN!iRP%MP6NQQw#rS!u1tz z+s829PjCw5>MR}QXzCTGzpJpv0sd*P6g%bAvI)r%EVHSisC1ZaC>gPMpGq6Hl?Bxg zi@?8n+g%}d+s-J^7r8ND1RKQ@Q4G}Ge~$db!+vT9DvQr={F2pswoIP;)$+P${-i#|Po*tTVps{<6KpG!*!`Fohe~`?$Zhv43$^5T zx(ZL!E;c|3?U&^qAS-uCx?fmrjgEt)=FKwPFC!=UB)(!ye`)a;)T7lf1Iu;(*hhjz8~~`TJ3?gg{O{Ny=hl!{Hf@goqP6^WCOWK!b1YM z_RHi>OESpt@1f|wv{-&s=_mADzB}XV-SGR4?hl)p8IQ}^=MZ`e>0OMtb-aHP$N$cP zh#_=MNLXE5em^XFEBX`lI5U=?a^p?GXmZt2IkDaZtV1X}{@qUb zThe%Xt@QL`!93ZuVX+4$sOVDlsr7|LUjW#xHILxNMXvYBfS%RYis^m_pvIQjQBUG) zYWR7jyn0XR-(%6)rr>t-80o7Y)W)RFOD}opnK)>_7!q%ts(lQ4Vj(Ad==V$~iWD~I$U4cv27>#9qgvt0kh7FT zR=_q#(~I5{?S-W>@vW&sx}_*Ns<&XlsT1`}is`dFNJSZ@c>LD zviHm=ZnO{5mAZ0?t9V7T-^#6Fr}D_Op3`XSjNdv$+}Utys-%8aC1Pt(Kcfqu%NDz@ zvARm8ok<_}`K2JI9l)CJz1zw=ZZ^00U=a0>OH6N#8dMNy@jb=+Qc0B!9KKxjrcTq4 z+l>8mn{gPcN6M!m;RaFY^cm5T!w9t{E4W6H^Hgapz z^O$puOPyAnWZOPY5&i-{J-iPAlV<| z_|lgvYCLyLuD3XZTezC;R?HJqUNcuaqcEe+MsFdjd8Py|9wl87^M8BA&+2mF&>6|8`mtBYS~ci;>eU;>Ur_zz-;> zzc(@dxoO)!bd|65^-@XmT6A(2EVFL;&Mmd+iKK6%HhkmPV$5r?LDJ_2G^{?F2#?q1 z5V!KjJ+BovXLH)0QU7k1^z^wd5N14Z_xUDpYp9o1Yr0kX*?tTI6^*NF9kz?$zaU`n z-C99#x&FI}iuA4YCHdTub)VQ#LRx1_(7d!$Kz6z!-tG80$FP;Qr>XlhdZ2Ypxh(I5 zs?;^WL9g?^@cOq{b0a8vBQBwaLyx{~T1WS1R%PHVi;#u?D(U0v{&GJlYuC?r&DG0% zO=!8d4Z1DXEVHW$gN^&h-RFu$=pfadGnsluk<2^VzI?1}k*kbg-LK_AL$Iur=7}}4 z2MpP|Q#;|Hk+m7%BOXvOVocT6bezX`J739k1(rN$`@^i2CMOptwyvDNTos-G3*+%hH zpG}Q}RlGy9;yZ0EyQRHpcoSF| z^_IPEw__j4QL|4w1tPOygc)e#d&;5zX8pknpRXjQFrMtT?dlcJ67BlqRMQh_0FOpa zosPOfk^@T3SJ5-P&0{M#I{vM!UOj$uIqRGTZSvFZ_|whIWHUADZd`gj-?6tj{_VPc z`~5vJlqdKGxwG#2Qp@n}^IkLRPf%C8J=xynofW~%hIIQ$Wg z@+SE`KHClkx4y2v^eb)BdNs3Njmy1$R=H}9-FQL7PP)?oo%lR=cHPVjd|72Xi^Gf+ zCVZtk-y5cvZ}%1lpTYOnbzu+C3fW)85J)8%Aba_eN2`xBp#0)|Tkn2Z9Zb$=eV@VC z*B$-nDr>jwYVyaz3}F#XU$hv2?5?lN>-ph4dv&^*-p-!aqzJl9;(Wru?O31r4Rs6= zU3iN`z`&lrNCl35D0tyDK#NadOsvN*#G{^>{V$JS}r&&{{v z%#L;Mt=0#xb&YSc>%EjknS8$Q9xTvYTB_)5(5fD#l2($ru`j|7XmpG8=72&I9S<5^ zBXp=;xWKRo(Pg2L9E8>Zk|MNXd2#w$VD=D+ucbDW7jgs;jSPYtFro4_e*IcMcK|Rt zw+HMMR1`Bq_*ygnI1+N|Jrax=BH4xWF7{ zYHF(b*(13pmQ5}I&$>dEZ8ma&%_?APi*xBm}tU1_#e z$+1!REk|d2y#%6#tX`ll5RGi@g>_9tl&q|2Yeof=9wo$%G(Z)Tl$7C{Yi4v(#5)4O zhAO>fV1Prvg!kxJlDf7UHTn1^91#Tz3k$i~_v~5jZ|#(sqGqZ81tA>M5Sq~G&_6FQ zd6l|i)2(0Eu<)Cp!}SJ4NXtuA=qfWKwzM%ZwX*Ssd>Qq2M1CR_f@G{;H0+LOS~mLb>gH6|^!d08d0nl0DZ zWZEI!7P;IL!|x$`?V^%G!LD(p??_`mSz+wj*@#;0b=Lp00W{QZw$Hz5K}Rc{GUL1* z=b%e=p8ljV%H@4R=Ku$+k}fQ>j8RO(DoIO;qRN?+z9nzNpd|ey@rV$0DL#;gp{i+|qVO6MS_VpkZ>2VJfNF@{GCrQBhKkVFwImEo1NIyb8i|bsG}cyR%ZT8VwFPluR}-tV|9n!+^lZ zWFO_=I?yddM+ve@DW!ssj1XiPTIPxP{hd~MnMkhIZwBmm@J*f-59AwG=w2|hai5?!bF|J zlojv$RwIn|S2$#dkW%PiP+*{epc4~+BR5`zSkXQd8eT{uugKUtYKqt9%XoO7W32j`sfN%Kru+bPx6i!8|V9QZI$pjjVL`T4>9{fB#_h)syzlvkzJ%5V1e)z&~8{ zJA{{~qmg<2f$5K&NOqod->j}~HVzq4-=eA}aF`HPf#P2aPIw+U&_fq2MF97Ymk3lg1|EzS^L4b zAKzZDQvTIjuZY@0pRS0N|6C#xQFT!b!wO|}&KqTOWc0$jOCFKJ@xK80AKT!}r z9OidFieln#J~n~Z=SNZhpD@JtkDHfg7k5{; z=C*BXTT45u?oEd>Ky7PjXJunycXe}dd-+{k;3tokot&H6$+b52xn%|QxMT$My<-FO zykY|Kz9EOb4kA2sDFir#!sqy^gdc!U#GX+e(VZi(Dyk{zP&JJ9Kj_7ZT51~o)U=Fj zbPOE)$x34*dezfeP5vL?gqrPEh85VJV@|DY0RO8DF# z9?FQNbT1CltD=ggQ`I_q)hz$3{%Cl}K4@9zs%Iphf0eq|J)7pZMy+Ngc)KdQ<3pm% zp0_QB(XvouldxgD8E?#*cAi|eade-T`fd5JZuq$u$cIlJ+VHv=I1+saHxFuvFaAAr zQT!4LnmJjX*WtW)doZ;reX*k3qxI zZr&tcX_YK;EYr9tt?#=~)aS<*>)AV3-gf!0;u0l|2WK-gszQA3RsYmnW`0XLoTc18 zTHyUNdGpsoad*oZ!>_2o=Tk&~cLnGY%a1twxOvoQ=rKhM1OB0)I*>{4Gd^XJ4sx?Y z0=Uh}s~YLzkq)*GwschZRDS`LjHW85E^S1Ekz5d$EH&v^Uv`sBkU%Xhg!;PykR7Ud zp&_Ra=~-)X_9`X73a7wH+a`O5`_KkQANIgfvu&m3OJ~DDQ@b%NReQXHu%=zIJzcIj z`7j}^FmuikJCrbjQl^GlZusQDZansqv9Aj&}m? zUJn;tIn*l=yW>yRFMuaNck*Ajk(fcWq$D@1<;kq9$7lYhnq74SzbDG=x&`|#u(~u2d2H*=^;Px#y9XxGOG>D%4bt&Op&BxD6j7vd6N{hG@`M}^*d zOt+ zcB9RO%2$z>gQSy@^fnAy>Fcrja0&jXxc@FCL>{SY@1Guu*qwl$`&@3wEcacmvlLy2&0dnOv#BrP&>NRl z1+#HIEnTy9Ia58aVX2?e$I6mz#q(dRw?fpRmQ*rU+f3(xqfkLW!cnQ*cK1-KTU*^w z>&cm71pZS;)`2~@@K5*Zc9TM#(w#vEAj?uL^yv9}9{~Diszi(a30X6((ko!;;2aT zwfrU4e}zR)L7{Z@Z$Fjk_sk+;DEp=ij|JpduZLXvY-jHwl1r95JC+T@4*?!Dx3Yf~ z{Xd+YLy#z8v!>g&ZQHhO+qT_(+O}<+wr$(CZF|mtCnjbVv$!*hirQC2R%JYy-}`8H z6Z8|$lTX*ilM>+p%o(Biv-Aa<;Yysb04Y}N^MFrvB}^H3g!`8{IE{6jIP&SnTk)r7 z(DN9{yWNB3Q6Cj_bA-o~@JDcK3MM_AZ*wYR2MCk;A53F6GQUBMuM+Jg96Mj3T01^r+ng^@tx>G^nJQYrehkIHuIv1lRm+2t9>a-FRCH~i(Y61~clGj3Q5@`Cn7 zL2>0C-$@vEdacLCOX;*!bI9&YS_cB{k^NMeQ0%L4NA(`qM|%ZI`U3&C>&A1DJQS&e zkA>DI2vvEV9miye)4~x6GD2akcfJDtK`86wIc+StU_Pj7yX+pt3A0*MWgWo0I16ol znZz%Obn zlW=^<$GEpyY*Xj40bdr&*OW2v-IkHvd-RmMIGL9Fuq^w!#EiB5*~O0-Id;ZA@3#Eb z2vnbXG&kH$g&;eL$pd}enUAL{vZHZ4>rbvq5|KDa2|~#msY6+a96RY#PlIKFxJ&8l zXwL+VYa>=O>~oHc*7psJfm;SMyaH2o(u`UzJd!48gzf8xCbq#?CL@kE$K)6=zW&dg zuxKm+DX!U{wu{rKmM=x&RbPNnht!lwu)#)S{ z=9=O+(HrDC9{$E}--UrFwTtGhCx;bt+;{FcjXXa~Ogw@^f+-`w(sY>Fkr8|SNB0o$ z*$%1ly0}U(Md|$P#9n^$L-zZOXRd6`Xd+@pWev(;`||d^*TTsT>a<> za)9|C1=@e~iHZ#^PuGti2o~j_OW(qg5GM%c>yshv&qrOXvov40o#sqGwH)qU+3|zA z$B8HdS`BG#akL@Rlz?;Aue<$Tn@WUeOGWE{GSZ%H>0;;3SL)vL``1WG)ar@w_t(DTOh(&zBLLD{KG&)EDfnb-KSUoCL2TRmS=yMgbP z?!WQ}EWL+d0TUy9_W|yIU6H3ch;MC2eD0Q#*}1)k?8iFta!6tpm$jy-mTw-+kRxSS6h zBbGVPl}MDk9x?dmKPIo-MJY_n(}WJ|ovH0vgs>~(l?k?Uc2>PIh$9>zR{XF+J*K#r zj4kR1hd9b-b?TIPD1sMH+ZzHMM6Is!!eRu{9@B;_gB03w8@bVmJ*NJ}N*94tuo1vG zu|FZxN2R+D3ChG1<=9K_Ieb$seYXuY0;+KAWj69qES{q8gOo;t>J{q@#b60A1pM39B7>G?q<2tVR~-x`#!nW?gJ++8WZb zwi=o9d~rDuXVl2>3o1bCEJ3~yHdSmq2^E3%E-yNK4IknvIqEM6;JzO)>U4vO)%()g z2SmhSDz-Q_Qmqac5?@L_ul*Cp8@!I2k>d<_>Oxps1}4w*HjG6%BWz>x-gZee&aAKO zUU9?;Zy4mztsJwZqP+P5!%7+bs7j&SVLjnp;*x6k8@DJlb-;1|V_{eSmT8__)LDg9 zjRROpS@fi$rWKlh!}?-aaQP*WVr*nja91s?@UMJHbyl&iej9j!Tim1ksf?ch8|x!< zX?J=L3?$;8nR==F{4mq3rX96`bH=Ityh(tHQE^Tz9QW?o9}<4K&$a}$zd zt_ZYdWVJjj9A09&v(IL-5WT4s+1ev=*$-Y&khq)|U2Dr+CQ5P&Or1k_S`sFqN>Qgk z7GD(JnVS|2tPo*U17|A5z*X}VRhzm(V*)xbk8--Z)U*VY85)MkdZp7A{J_@NGj@AV zcQ+%>xRvj`fjs-b?kU;~U}W)jg5qXSWbAaZ^851&{Ig1Ry(2>9=l!zqXX57yXmRqP zy=PEP&e3YT!KxikY^P8fT+!6*Q2g#lW~cjh@}tOXBQcH&YX?DhXZm_(QNDZBSw_{T zhE)5Es3*~swnV9OE$H|P)BIWFL)Ghw0d<)S)SS2&MDy}N{hdRAw7b@_7g)l zQq92AR<@{84hqL5B>Q_($Z{zs#Z$&FkEt#ssg6^&uV|z*Xq4rr`aL8qDAM#BDdCCJ zE|<(ML&CAAQai6wlHRDMY#M2Lbn9R$=89E4Sk%{?x7{Gx_b46@=ueHvczv@arM>=Tdz+RR-x{-bIqnKccw5%Q5@zr$YB5}|v>2^?(p8`TMA%R( zylcANb}8u;xZaN5>x_=>`5Y#HagLpZ zWDH?LJ!Kh(V?WVTxE%ZVV(41hjwnpkZcZLIcsjSV_|1u zrSwz|Dow{__%iPs0L1>9j?od{7VL1QWsr|2b1-s9V~!dU1Z>nJ&S{$IHQ)4|^}7sS zr_0)2BzB9nF^sjDDrSrOVynGjJJ89x^}{~5;npn4F znuMSi2kT=q9B?Jd-oyu};Xjt%zTKNPfIsI4;xP@pWjd~RJ#lqMn{q|FaYxI$mQn}g z1#k`&2$=8({0|z|pH=XNMFjvTL;G){VfX(hG_3x=(QwfJfrh8LmPB;RrVb8*MEx;d;b3sVo!Q=&;J`C4t6(H(YbCgJA`Gq*=Z9# zM1t68>+A$~hgkS`vnJ>V?$-?hmxhodkhl|w&-F2%X?I?&(sX}`>%Odh^^|f`wM|e@ zMnya=g9XS|VrsI7>#tSm-b9-1gAw-nGDcPgr}ly7Uq#DwlJjf6&THP`-?_cI z(bYexm7TPCU`9W}TwDkMge|?J_Ihg`J+2igPpA0W!1Nn-7n{CIN<{R7Vz`MjvwQ98 zs;b8dKSbZFw5S4Q;m+2nz0QHp)uH#dng4diRC_5oKI%I8dL3SdBmIi>35z?APn_S6 zxj`f~-q3lTIqtv48=tRjIn4{;pPgNOJzY?!Xx!|sJ>87+_si4s=i|8L?&c2jgW@VQ z%jVWP$Hn$)gD}f4vHe-OWlV(YB?{1wppppx_VeQFEzdYpetls!DK6^l>iv5B5A@=p$nIo;LX{q9!}UcSK5 z!^z9bU0K0$wTL7kA#G}ApfIn%0mLkZJ;u+&yT$wSN8b`xA0{`4&u(E4@Q0# zJ`Y^Ax2k$R$Wq?U4UkpqX^F+yxfursNBNeihra9Mu9+Rf424tg?eS!9|NQ)f=zMk& zm}cPZhKNT=erHPQF+Jh_WdGpho^wUwPeiVviOs?13mY;n?_Rg)C3yEPdV#z=JbUlJ zOO2n-7hmTN7(dTnL9ifW-!HBy_Yt{+{lYLVqo)-}d!3Ki@Bx{QEa>g;B|KdhGq(DLfut_^QQ7 zEiKm@DS#iKho7gP-yeQn{{H>hJNwur=7ojsY~Z%Gb}8(?+Mitd zxxamRFM4tjO3E=wNipq&@qvX(UR)o>TA}k1kOI|m*Bhg?VcTUY(X+Gie#YzK%%PZZCYqRI{&5tnAFIX5Xm0|AQa%V%c)~0;q)-bjFu^8i)3V?{=tG||963w; z*n9py6K$RwfuL;!*((Twcy$E)=Yvo>dE<0j+cCZ`v3v&2P2g0TlCrza=h(fiL)_bzPD zXU{K0fAOIkTJ%8#n&?fj+P z?eORzTBD#}{pa*{v|8MLnKz`#`?gcHCs!t?M;A9<=(B%%+duLuegQW$e$i*PM;G7q zJvTI*H@P-D);herzlR^bpg-B26kX*z7ASNMxNBjKAf0iBAzm6xyp~A<6ibFg=-rqd_$%q#g zoQRlwd=`Wn7MKEkh~j@8P<6YzXmzV?e+ON^_;vb!6R4>UEv?V5Pbv;iPQLQg6kXh2 z?jK&h#9>qC(e@7B%L0F6=T{czS69Sl7NcF@OErxBP?Z>(QRwp4?i#-OVLAy??cx+`h`33TIMl zUi@X9Hr6&YKRCP>yI1a9T%OSme|F=0HEw=|4}a`O+J0;KzT+82H&gJZhZzFD)ynC#CSW*w{PEtII1Y>Z)qV zX(_4y@i!lx9-o+)o0=7um6Vc@ladXM4v&b4i;4w@_5a7oyaNXI_W55nX0Nu+_709- zM<dLB$YDy~cX+abJg`&IA(7j2ih-gSC z`esKzUnBbd7knNX6Ob2&E-ff6EG8f)CM6^zBIz3)9PS?hGBnBuo>x$ePe4Ef0N4OG z>Ej0<qC%_K1LYdl_AdqNsTB| zv8l+OGMKMn&#;?~SPpWGwP&A4Zgx~&mv6SmEfda<$X5gS{y4Kju? zsYg_{(tl!o_JXMSaL9(Yrjzr+?mEOx#fdG{11aHA9=Uh#7aFR_8qyn^d)RM%4sHpG z_Y7XiF=m5sjkUsZr}b|H7r@7*+j#g^*XK)fD_!~n)q$C?I81c@FU}65K`3IY%}eCE1?oV}eB0XXq8c*R2NAp zo)^7mW^m`pN_NEVdi76P;|b@*kM;|<)$?LbHUK`AxdVZZQ;w9?BrQP=L{=hs{ll)HB3O*LFKiXxm2y%TNNTOv1g z$*J>zQETVOEw<{nl4L1WZi#Z00M4b{jp4q%nU>GbdF{&w-;dY$opDpTnCu)xL1{~2 zdO3yDyDDK~6(b)?*sj=$`&!NqH|Mi4e|! z1;7o22tiJ6Je4#~0-9}fe~I4&E2YgWJ9d(lUtutCkOn=N@Gh&JwnYh+403j=;yxo( z13w@oz|l3vj5lQyWw6Mjc~L{vYBl9laMJ@oBu_vK{q4h1m%A${fc<9Rc%sUP?CrD) z*12pwpOB8OJ+1>gjaN%rZ#0y z+c1{A&${^IvGSm?d^XEg6h}LA^O4frpZR&-mhvFbmqj<2zZu12uS)2L=p@f3LsL+! zLRwoFNlr+vW*HaET*Yb*Yv?U0UZ~ZxESdF{ybVdrZttpyt`RawfxrzU1K|T;pO#^T z5Q{dh+#JaWi}szyh7#_mHF(nC%1~`KE|Iduhv*ij%)u8vcuB?G2qFeAj6P&XacNko zK+(O0)a$iOyiq0qw^*JHqpV+H@q`L+iM*)-`!&LAy2P9+%EZ{T^-h!1MOZFLAj6WTj6_j!I1Z*=>kx;;Az82F;xi`(|v6)LV=nF#6;+`F|X_^Pj zhV@4DjRsaBHKm@_)&KGy8*7iwWE{Jv?(&9qm#|t3V@z$89F4OYHbTR?K<8k&@1)fk zE!$+TJ^S-481&YUl2EHas~q`~Eo!1TKG={b8OJ%(g-f0)98ayFp@Hz0>+um|LP{28dRg==J3 zp_Pvwe-6adKWqKBT~GK2aaByDqKLMJ^mnrGUst8r7#}kd0?0ZsnyM1qEi}W}LbDVr zAB!0qE4P`oo-MAFB*ox3yE3Mek@L4ATJgl0Wa*0)ML{xta<=kGfXoQskD%NS7PcAW zpGY{+QDHJ@0K|6_wKaEP*B$e{ONDLrfW9zjkpKthFTCv2?7Mg3Zm>jVQs?S*1Gyhe z78AlUId^~{Sr6?Kwyme_W4KUd=DgRevZ52*U&jQo&Sanr->@LZi_uTV`Np*%BM@Mo zBY^ywtyWIK@9*KtFIbc)!^|_f;0Dj23=h{Cc1NbX9!GVL!?sAXg2KLsc|yiL&)cez zDL}XF8MqgRcNMtaoaNS}*NDztF{=Wl1MDZIleR{4?Y}p9WJ>jdjkBwgu5q$}RDTP> zKtMYtWo~xI{v0LC9xsEMWwKgi6_LK~1UDye9A(A-gw5UvCzi*18)Z)3rFqkLof5OK zwIbBLJ^-LkEMpgB?+6Rcbx>k z@WL|X;Q7#Ep4ywMK4vV?N!rs4u898W0A_EQPDp#NqzYtL&uLM6M`aCwF_;5H8Z%kl z@7$@3=bf6dsZJv+ZiME{7Ou8FnWOAgsO!qrP2RESOovW@d&X^$NfnZ}bodlc_fHMn z6W1%XmGlOiJL3t9AV;DIIxNh10PfkY|5yKhZ}w(`GAQTSRLNbyr=&{3SwO|0lX|L+qGJR8T)9=y@59s*6}_9FG1BwsTK-`qonl;MG`roHk~^99 z%TGraTj%kPj94_>6=v=A4X)l5#JuiOIRkVBNGAMN_x3?MOqt(azK~CF^aLxADUJws z`b*-*D6YT~$vC)RmItJJl?ePup|r`*Cnlh|o1*B*iNwe_J_W6ZeIYiR z=z&mf9YCsHpX#)OtJUb?EQeaL<_uh_oSSz)(heVa^cU# zNL}PSWw7~zy69$a5b9T`JJr*WDLtX#!k&@RW}t|8WSGBl(PcXjyd%_Wk@GrL2uB2I_c9B=Hc8p>RFU}O|(REFKuOM-<9fdv=z}3%3mq&k$3jcVNu&7Cw!5zP@&K*Tmd}^ z5^q!H&k&cqD*j(MBu)6&`iD3&$Ns!mK|H~|M|ioY$rD(Yex|<|@gO^dix_VXS4|%) z*9Jt%MmV~43iYZzsTILLE~t0&XqKyb955+1rzW$-rwxKq_t0>2%?okvl7xtV!SZ1* zi$J~V8Ct?{t}J^(vFl)9j-Ee|I3p&w>{T9R4(i@6Ia@}%%wX)WDp>vwfrv#~e^)qi z(WxfxJFN-;hfK!Ij+zzB@6jkWvSV^Yt|(frn*K&l5k)T4JTJWle^81Jk3mtMSwV3u z8G~UJ&Iv1*OU5gij5 zR@Bjc{;l`|V)1;n8}FL&)>|K5qc*`SfI$;3{+j0Z$z%HqZ9P2_J2io8Wp+zZUMXq< zXY&HVFEOflPb=KrDYtW{_dZo&)E#QQ~#jhMWiu(RZ7tr2^oB`2eShRe}-69wp! z9}rGqYD?(j;a>58P7aM-`vKF`rYjVw^&krh|3fNMX$$?~KB9eetEvZzH$o=;2_*#o z$Qe&;(&(YCeH}u=poqMynQHI062zLoq-LWuT$ofTo;T?N6o~n~{12!*$JEp2-jrd9NqNZY-B$ z6K0?T7TCDcp$qJd30%TzPC7>sOK8frCIOI6v==jPD`A^gindlv#;0Jm_7i>mdwD1) z?k2dx@%L#dy+ku77%rq;0@~VD#6e<@d{JY%8A`Gy9N}3o{whT=JGHMJMJ&&Li`?xb z_q@g6O?aj`+fXE1+XdG9e^P3;^Vi;L91?q4@r$Yyl)`#3E{*66vN&nnN;!M6b4A9r{y6R zeB92bUs|Oe>hYfrQ+p?ggwps~Ep&Hyz5Z50_L3{G0djn#!(tZET|7w;qq@kp*a@{{ zKo^KANaWN;`~?{Nja({99vV-qJLvcQKNJ^n4T6G|)pevXbPv_1_1({E8rd-JBqz{W zQ+AIU#SW`a5}z{=h3U+~`Bcij1k{^#bkY8NQc8r8>JNrXPr3R_Ilz`7Mee!6BLO}z zeD=-9q+2~Pdrn*{!3;Ny#*KxZ&>@UO9>+C4J&oWmv}-Qf<2l2r zP1)=Z_11P+E)o9TnFLd6_sg5tdL)X`jP*l=m{ zgG#x=cBH{$n0bSR{8lLD4wFwx|Sx*Tty8v64aA|6}<#AONA-RgEl}*8ifUyhQehisNO`=@2cT&x^?gn+o$I=Z4|jR+m)8Nb;x#q$cw9E8s}6eqYaqt^@v^#7GhHg9gp4-Gqo? zW0gu4jkSN0nz=Vru#=Y~Wy^wbn9!)t$c(Cvbuw0nlp&ItC@5=AnUTDp0{1fSOKE<+ zBJP0;T=aHl)@iSw_zXK5eCt&!wF*^C-}j2m{F~8s&X5cRao#T%?-KsTWf4Y(mq34x z9BRXJ6+oXzpjxp|u!mfHAKhGXF^8&!V{po_Q2M%oP^v}W()#X zkS!+Iv=mR%dQjvW3`*Wr_8uS*`4txzj?ivmmex;BR$H!XkP2mZNp}q}4A-{RSl$SM{>VDpJo6)g92q;xB$6;LoEbI|+f0V?v3k=EJRnoIHc750ejg2IUETI>=#v5{UzIGlpwi#6V#4+%I*6dXHUj;rkLzH=n-Bo3Zp%+SObXQd9D;&jxaYtXeZ zR7wYkY3|0EL7zcOVvN#YCsOJZo!Mq7_aiOse9DYB>1>4KPm_sGNo3ab-)7M>a@zjP z4EL*T@LNtXEK9%`hC5K!zO9bR>ik&)a!;J0n26WJ)=d z3vn-G)_y9K1OYI#cU0!E77ZAej)ucvX=t~r$QxkH1jBqci4e4DOBFZMWRz|%!%_cu`fux(YnB}JC|L<_HuPf({q0)ZKO#UZDz zrROjnosM4UOA|Zxs-&Iu?OLO46FPI=Qi=6ZpLhN?G=r~M1*b8aZaX_#&Cj02le_N zISTAwlUORvSQL4744`RN>=_$ZxPihvM?Yju)Q&d#y?P9%W$+Kh!Y=Q+jhT9=Q>KCC zj6i^a)hyNTVPh-IgXs1eFB2I`t znu9IYs(n5!SZk;M+Yny5jH4oK_LCBsx%n%(F1Uw&CMnFPo48B+Z_!-C-G~ z^#^_`3??6m-JQ22oXGAFE;y{T?3YCl;rkk|P=}LRvp13mD2iY@8AM#I(NT$8df0T!l{jy!jQl~c&?-C9 zjl$r^TWtWZ6iY%O!Q9*CUGg%2aPyC;mYKEfs%RROHgmZyOK~$qLnf7Pb#a-}OQtb= zA~L{@^z6hD!jBXWoOl$9<}c#);pCGea9>W0Pr^YRL5^tY%krEJ%B`)Uu{x=-02rqB z7>oQMU#Vbl?d=*P)Z69u6utSUe|g92{MtBsIL5qr{TXwDF95sKmdd zXnwv}-&zjYH7-%Wur?Wrj}@e~G=oB2c95(`SsGpEyvG>KFqIx)=Ccnxf)4Jb+J zbZRO*!Y{l$tum(B-eSJrAuuLQ3Ck9n=I0}*jeTkX0qzYvLqae&Eq-y&Z^6h{(!`?H zphLHp^sXZT^&q-V`8y|an<0GU-f9o`gLDfGO0Veiy!2EKNm$wiie2Mj)oWv+O34sJ zd@uMyizRo-soC{H0h1A_+dy!azq+rg4noe%jS4ShYJ4s@icAAf=-!mUS)$g=^gKh6 z7Vo~HeN6-VB()@kWJ7k)7#E0!M}s!%2-(;%$zM@kc_&Q-(G2sHE8*KeHuOSscw)<- z5jb{@y0(D*kW$o?e_afFWTh|{i@52DWZJG_C-b^iL zd71sL3(vz4F;fwB?Hx|!8By?$nSm1`Zaatb_@u5*8LZIuKgk_N(V|fh=#CE?PLUV! z0gr;-P?5ydsxx`OsJm%0d{(k>0q&E5tDbx?YA2l){MalbV3DMWx^lpk?~)qskHn6i z?W|pZgU7qe3Q9x1ZqN8|SpdK#4Dc?=rPMjR;D94Lwj!$Xo_hzLBe|UYO zi6ryM`m~SE96$=pByJ}2D9~ai(x_QdwNTWt<=9a}`kees`B>ga_*Db{y~K|%kOTDv zG;3VIUwdTwkqp8BKuU)|S=#+pQYIpxz56^8x-AVX0e#snBRs=qQeGSO05|=l4|(^M zPac;$B`A&HlI#L=dfqY$eCWDphnRR7g@o8@5}lB8givfo)P6Kh=H=&;!z98?|Iqkp z;tpSeZyP$f2uZ!hQh}%g7Qw-=+JSo^C`Fw%p6OSL*0ilq-in5(iPftnSoVp?sGOk% z)Z$J?liUP2Y|Jk-AC+Ur_v165ilqU2hD0LO4Ft@M!$WE?0E$-#hc42ALLiIkt4dfN zkufY>zV-_vGT{>(=@xP%8ZpRE{qAp$y_nh zl&8vc-YLVZbqiLar(*SFf4XJ8tV0%Jd=N#D+}tQS%BV z#Xz9b)Zr9}gdiUo15{8M5M*b=P~UQ1@TT&a4vGM!Kl8+%oQ!jA)Sm9eH9G-v7x<*I zQCb~cL#4_!#n2+|hgfmFH~b|(mef*6~ofZBz`x56X~@GIX%oL&F2h&N;i zcn02$x#+GFRr0mVjLpUibsa+4wXr?>_JCx?gtrSWa*WgUawIkAOI8%iavz-&3guQv z0<=RCwN<0N?OK5l*FFBJz>p>tI#p&k8D_t z(IZ<#E@SxELNVwMK3I(21N8`_-@$wJ?LMu4}0#N5ETRG_Z4ICKkKkUXM4t~@`x6eDgH zFT+*Zg=--R7;#j~p_$OJM9^G^N`n%6YfRnC=*ey{owmCSb}|D7qZGGwW9$%h)OW~8 z=&)Q66o=GP97UX%HvGqUqDtoMsPYPbePtiRK?(S5!2DAX=LEJf4O z&_(nLpBKrp*d6PY2T=mBl};Z6C5293)sT}!OcOto^_j~D`1*&f&P~)(3Rj2DgG+3j zRJoZJgMuv!!<9F@Jse(LmnV*LlVIJFg|-maRxF}eUzCA~L9~?S zbO~4*U5GCYdf?G&77~vg@xuv1IIVEjrsXi#N)6vH!{>kqoH!1YsIod6ZR(fk`U>48 zUM6%BaQ z$tX9Rl}#xow)P|Kz;BB$#R0GQdZxQ4q;}>g21IO&^vf5t>P(PXq<=CNTL)OCrmw4n zZ^+`@*x)P&DgZ$fRZ2m# zV3fDU02stVgYh>X2~Gfdw6*4nTbl#Nv+=b#;PDyT91|^c0XDhZX%x(Nx!>3{Zrt{8 zk+B(h9zaX4qc<~ebJFd!v8Hh+c+>B*u1v&ujyttmW?_y^ z3Eg6ca=ZVZ*xs>Y@(S=CFJ)X>n98PT&%pL7N_=yYzz3*XN#LMrr*IvMC&oOP0?hk< zROT3^177o|7BDKE3st%lVHiBWk&E)>h)nZUhlpKKNk{Vx2Wd@1WI1j;^|94TLQ|0` z0T~X@$C7%bPJbXLq#GYnO5xa5Hyofc62wApqTP62M%pBC%#KWRd{y<-QRY)?Cw0lk zzx?V?82k)y3(BfMEw5e+hz$I%HSLsVK`sjdr#7%6TK}qr)C##VZOC(^+(``w129d$ z$**3IKd0){O_BW=G|K?aeGWBO{8>x%o1&XF5!~@atS-nu7O-8|!18lDWWe7iU9nE* z3Cl#HK;XokjsN1KZ6@5B82HbT=k+wmx>lI%?y6ijcZh|zp;+lBX0tBF8 z*?Ez~1D94?lzn};^Gq=E!P?@H!N9GA-q-E!0q^t)^%_HaP=T|&MtL!m5PlWdi7)Zg z{3V)>bHIM2pCx|kAxLVr@8iy1Nbsj!35nPTsl94el);fWX`KjpKGVV){%5m56aGx( zfG|E1k3fJesc1o-$ah`zt)*x@yo&pjM{*aJGE(gr@TBzEQz>x_L?-_SdHOL_TWiB; zc5qgyzrEbUU-h*9DxbSos7)VyKH}^_n&`*D`Ey<`R4Drei*naZ578pvp27mux%g9R zZ6WdQT=n9gI?C| zTvh!VVd#E%jvNMW=H-1J;cCk`3(FUPl1wTQBv*@m=l4IM-YF0(g!e1KBo?amn?(S^ z`a*bBt?h^41JztexBiu7Frx>|)~p;l3o%3n`})~9x(o8f3*%+Z9D0m0rVZz<9H*XW zuB6!8odJMN(7sb~J?dqidM}p4Z}(7GJ9W#igi?rUTKx7JY1YkFO;|a0>uOlhd|tV> zZRgg@8hnL3c6%}n2cP*ribnmk%y8g6-Q7m1NlB70U%t!QS!#^D2(e;)$3C}wbO0QI z>=?hLz(7I@O!B+UZ57OE;q-*y+Mwyy=`15qXL6)V)pVu$05Io!Z-x%ud7xr^RKDfVq?r(%?-asP!ajYOF`N?A4wt^%H!CzqFNKs?T zqKKI9X_VOxFw^sZ@vPtqJCV&(?~EkS8HoGjNH7=5P~!7l-5DsQ@<<9ubHa!Kbfiw5 zICF2gBSL(E}Elqb_2g-PXOB7lIMAk9dYJ z?9VGrhJT?*c}n_#NDtjuCpBWKqt^9+!*}4ZiUu~*(Berw;z^?XBbyz;Q3YSd- zq+=(t8&7 zsOV^a5~28aawq|ou9)s$)=UEQ5R-<5;8o%{(>t55hT2#^KYnYG5qDy*-c5odECrG- zO93k4P5!AR+TV)pp&R%5F3jv*hVm8s{0#?&m=MvqkDC$=y0}EYP|VaW8q>z1nS}rz z$9XC;35S8K1N6sGQE5=7E5qi#|H0Wg1Ze_v3$|?Awr%SxcUfJwZQHhO+g-M8+paEG zz5d@sOw3{yZ)TCp%*aJX;@+F*B<$BD+&R;&vKr&Hc$BL_I<&s%TSr?3QNS<*4IhXQ z@c9jI;D_aN-iubC6E#qMfkXS-d&_ET5X^{){I?fa?XZ#JvQ_ zT6-PfW;eyOJbiTC%O{Z#sHBF2I&GNSih(SDXD8Y^BBS* zU1NY5nTQmreo$#q8lM&8bIfb5@iH&DyI9>P7Jxp``@5 z@C?v9A};Rz0>{=91A$Bi)A!g^k{vbx?GPB5%?UR~!BYXNnEZ<=#qq!of{Yb5V4)5A zd$LtXk5c{S(m`UPDqUT^`Gzr*}(=zxfAKG>{#GDa@NVH1t>0jk2u!`8IuIIO{!u9djs-0goQ#tKAMMdm^`r_RR zP$%ve%U=Clb+s8yqP3+9D6~Rgn$cz8znaN;7w$1}jMb;0g7CLE4I}Y4_26_y)`?vY z)I(w4hESdbVrI-zM}im6*db7pJTcsSZ1JA&S`r+qqUXWCDx&U#cL=EiQC}F^Bn-}z ze{GsSK!_?^TLm3QXA|SWr;dF?kje%QM_9CYzv2u^)}n%dt;&V;R!gq23?L5?mP)qk zFyn>=|24id4l&que-PAPZOz4vw%qZq(ej1nW=8MD^Ni~kNTxAzbC z>A$E5?iXG|L!iXRfD!MO+#Hif7gMWszOqV!&gO3J_`HIs3SWbGCx!!Q@KF?+5C~BpkH|=XV!TohonsPjZwO7zQjk~qonOZ;ZE!G(##MR5KN@+#K^MH~CHYnlqoY7*x~5_shzA?|7AizU9& zolHZm*8Z4xvm_>T6ivAI7G|kKi{(+hZEh}XBRVY=+_dE6?kli22zl~Pot_3ikEt!G z?-8yJW(UdoRlc%w<3c;noLpeFBHerQi~iVT4lK)ktlhhEKfk$W6yaZa4yaf+IeHud z>Wo^j);CV!Isn!;(5tjyf zW@Lv`iLn>+6}c#r)^@#@QW0^-XiqO;w1R$g1K$1x>t`3$2j zFniXH%A@9Zkh=hUxhw-%B~1B+_5>&={JoQpa@cLdxk>sZ2C<@@%{=J5iq}afaBz@J z6x^L&_3hH@b^J!4B+G*!nbt;HcNJ_Vq;yYE%lJg3>dk>&Nyi8rm>N5t zu^XvK44wrd%Wh(iUFubjeV>htS(KqbnQ7gvYEk% zn%<)e3JPLtoKkB+aJP;siv#N33^E||EK%22)-UaN=?4Iz*iPYQ`X9P4py=&j|4n>T zOb$#=RWzu7HGraI_d+WF*owYSA;@l}QSC=1Li-N*B6iRP;kq zNVu+ptBK|vn)LbE!i-{gw$_+LgJ0G|*r|#LqAMtvgA>HGCUiYxiX`bMiRqg-(AOBy z-#Z-;I>}`M<*f{P+@VK2=tZGjr5zhUf*TSB0Hf`q^WIeyG75pXqFdpGY4?i+WeYQ< z2snoj;nqYN9HI6Q9$OV4M;J%z=DU+tAtGn`@W|mZ60s1anTDm8M;y1Ziz#X$2^N&z zo&)UJ%v02L5bBkK?)^k8H3vAoT8|N>5F2aMnVam18xZJa+a`P#fJKTaIy23o+_;#nNhEL<($-zf zVaUr1*)E03KoS(IJa)cYii$Owij`%JnPo3pG^2CRJ0$gbpn@8f1ZV>L#E?x+FtuVD zd4`JXeH_M`+rwmE!cDSyU!J1nJ!F)HDwtrezz$ZDkh-<95hUHTV5Ags+6bk8QJ^c9 zW|R;Q=v~DF-z16%xhI62GSPe0_|TJbIr`>4#V>Z@d(HzA_)Q4uFnRd4T9Noy9u%yQ zGK``b;_)7W7F!}bBeC}{C)x4tld#2*#@6yEx$(-$c}`K;3;LD136nI%F-d_cKSkM3 zb5T`43}QYM%E`*ZgjhWSxJzQ4#fS7HQH6IjQ!4P%XZ~ttx7U4KDw)xW7V=v0u)CZB zy1WLenN;wuQsQ2L`RrWJTy!nf5h{~J(;B0X>z8Pj{jpWYq|Er26~;C}G?*xli}JS2 zo;GYH$rwwb;JEAKiG=Pp#z%t%lqL1a6ws2%pC5gc%Yu31XS}#SmtCD-Xl3kp&RN zxTZ*L)$wo;I0^zw5|K0SUc#3mRJl!P&$?>sHIfetlA?izW>P+D_JzV+1`M;-`0Yii zsTOH{d9N(}AuLGysa zaGKQga(x%NL}7SR+rcF4i;WP{xe{!x?K5i~wy^F!PQ_FhssmziO2~}CnBXwwYAO_F z(MLfo4frtufB0GTP60uP_hsqY8V7EA21oG1rbFfM!x<~8GZI*bBj-8H<~byt;4&|` zsG;=ZC*>{`4pM}ecbrE9#>JQ9@hRf#TxAEl$eJu@@;D5RYgsW0; zymf?x4tpn3)-_@5 zdoM^iiu(16I4OaUrm}*36+Pl-B7W`?5W-V}#5^TaclvketV4ZZkdGC#-x<)y&y*ia zBI%dT9z0`iqGY%%W8FiX|TjUF=+@;F$fx$mbxuL4;plA#uaj1LqcwS zT!Q%p`CY6u5?W(R#_Pe2vySXclGepn5+&_@F%nwJy$q# z6LUMo{VY|5pyqC$MJQpQ1nhMXQ6^=!IqJLAZA>MZdQoV8dSm=EmhE0S;_EZ_xc z7W%e^oj(;ne^l7U(b~L6NaudRMMSYrU@Al1;A+-mX35eU+D(?jYs1+pc=(fNk;=lFnuGPioh8p5elf53)GiEJsx9P(PVJ{}K3g}B~RpJ{U= z(Uk}F6?J8~;HeO5zO)o!n$%-8chQB*+c+kiRD3&1r@@}TSU~Xwn_3wiQ;ju??ixwQ zRVcsF6oUR=`0O(Gya zG^kmDXi*W(3ejs_Fc22il|Go)2$t|_(E&Krc@$KKs=)OYQ>$m<`JWnz8SI@6?C|TF z3?g%y5h6w^JZjd~Y@|kXvcaCf*!l0X-$c;Zd=HYxPo{o#Hy7|H5G5zET8f!C6c!L= zeF{+7ebXimT1Ae;tKzW~K?fi~*k;Y##VsVk?Z7^e16c#N+U7vaSbilB4od== zfHRcSxG3BQaz+MDHQzAzC4#;U)Ll;idP^R_L-P0sA>k1w;W47(3oa7QSRZJH$_GO~pbQ?74O5Q4 zfd*!E9YLe%C+D@p$NY&F9wP+Res+cB!A*KY9RYi0u834uT0tQC8msSYhsqa09ItL^ zs#RiS1M#!ACS8=t+Z?_~=V@@_xY}!!0p82Th^ugEx{3gBxi=)E*Vb53)0Bab0_8Bb z>)>OBZ}y1?LJc`E={2NvL8~ir_@TSoH%*>{y%`Q?@5S| zIR~6W(9w>eX7Kj^wte4gGCY4QrO-`B118EW>z2r016F9MlR-gyz~f_Kmu8O<_^E>e%m;(rKwU8h4FS(w~C zm-hkr=3IJoGBB7czt>OP4l>MWXd$`Ys)-OXks=7B_q%&n*3;VQ!f>kQv`k|*6CY-6usc7Ndq$>5)^_dZPq<8^?x>NT%(Y&d zJ-H90qP;}UNH+5#6nBBf_AV$%vcD}{aRSv265kHay0XI`_5j3nHQjr6y*5O)f~_Eba^N>ADA3V()ZQ5d*02?2h0Hpx7Iw z&7CBj!8s|`v$&V?!K^tydMVf=yS3}R6-2qtHgV_0ZGM|+hMx84x?@B|1DQ}?=@y8Y z^Pu7WyZj>sdfyeju9PtM{P$LH9u!P402CU7%^k@=1n3h8X%AC$a*zm|0WF~(akhv5 zgUovC#aK@=fjBDIyZIIwPW{XE`T(g~k174je5Kg~)l5Ez*W$)dDruELL)H)-6=F&c z-*3?OZS!b0UC`jWT;hIpWH(Za;QMQF5&g$nqrqrAmE8-&MythYz18Id!&bY)>3qe9 zH(xj~*z4^tdpkm3ClPX9hh%ddOfElB6yJF>qnui?Wve^0ZVOT{H^;TBYfQs z1dbQq2LjJ`SKFQW`;HNwLnADf2#iD@AO5vq5S)YWpcW>v@zI_oT4Z22T4(A|i2vDp zz+yO+t7*Ip&Xl+SyV6z&hT6}!qV6Z{{j=eMrV>ce1g5@61ZkWsMLThv;zugBp9*}V zFBKk7n=+LBRnrXAe5PGL4xs~;nq`8gd8*L|?X8IL!90^%d_K_7VmR;Ws=CCto+|{* zG@4ri@cr<3k4fZKe47TuL$xq2f$xL1Ah{l>Bu%}_49E}XAOZ>;6v0DfNx*r$A%)Ie zD@8Ztu%gD|JXD!4#nclkMB&=uL!V1nYTcTmbYe zizhIJDhe4D4ExiI*8|}NuzvPCazj!(i0Fc;AfU?EL<0e2?NCbz((mPD@gm_s;w}*$ zPCBle;FaQYObS3SZ^76Q=|;i*oISXAa-kemu0TeC!9L&EXZe_>?R72~`)0^M6d;eq zk=zsMOAh_d2f)evxEU1Hay!(&^4}}+AtZ_e<9hvyEJxD5>`SHznOm{?t4T)_dCxkp zdp_C=B6eh=h@jShKmf9GhlOJ-Jk&<<6r_BxYU8k#-RTNj@5Y%slHf4<>A(`|m9YW8 z(iy+ODXz;mQh{x|RrPXQz@B$-3tXDr0h)aj|K>;UD3 zb*1-LV&W2;O4l68q&BYLhBt>|=0BvNE3yzBnM0S9_774hFvO9%&Ir^4uc)ft!6{o{ zSAyth2S^cAsjw^nSf_RneIOuzZmwuTb@u=a7PekWZL#3|P7+C36B=9a0rX)b^;O@6 z78eH!Xm1#N){{X#BRC@(UYwQsTEk#*bmTaX>^b1iIuxp7c3ZGx-)KcR zp|?VW;j?*Fq`L?s;;58;w9|g=^|TPSBVy!;{slUc!Wd^UZ6iy{cE`Ek(LYpJlhH|C zQ|+*DONW@>i96nRP045YK{0)2;tie{Z4Lrxg$Xof^B}BPzGx>*5t_2Dc&L?e<_*5` zb0O~T%q%`VG$vS3S%Ms=i1Q^if|k6YjK2MR95+OmU{PoC9-Y+@H^&B2gq0E zltws{vOjmO1vG9sVU7Zd78Dbut)LG88?K9#O{G=kI^ShQ!R>_<61D;u9V*e@=A1Br zr}_B30%oiLV1aK=?%EV)lkXs8v&7R}-|%$Z_C5exX@6gZ z>!+-P@I9#hxkAEyd{ZA1640g)oId32(Xco0VlhIWCMAGnq{JdW8 z2WxF@0~j~Ee&(s`em!TB)7Aq?+|j_)RUlHj6bnJT8$n*1msfY-Umn??uiUOB3h`lq zA=%0VlY7*ai-@#GQQcS^q>oEYuO-dHf2aO6jgl&#y(ZdBJBJ{r+35MN0gs| zNNq>@Eut8yVjP6tkj^`yaRN>05h%VT*OJDBs>^68b?5sKim39UCr^#rqa9T)It|od z!tFj_(W9XSIg*MDeMAK75g@9CMUJ9DW)i8dZ4doLvQGrzk!*F#yFX4LrJx&QOBIbf z4=4aR+3)piz*Tb&v1P4$TeHc<44X5MX>KM5v-ik|yjM!ML?7=GeG1-r`w7Nk%5X9t zPX`tNj@J8zvd?c3fUAPL^n{a-S{vVa4PVG4Mr%Cdsc=e&?p`F#Tfj0RPz-kTcVJ~* z_tIt62Syn(!+NlgW09oP-yj1u{W>28#k_UjjC$D&$RH8a)yEp53T4K}HyxP(PITmJ zloQ(TquH}g(x0n@?5>>kIS@tH$2!m(Y`;NCH5|3$dHTE0=mpX~)DxvtR=#>a=Sqj0 zINK}2$I{>R$8A1P`gM=0IYP6pk}=wUk_|{+>JuV@ z@hG##1ji_rFN!gKz))>OMg%6GFIPXp7as;)osTVau4hZsmmVgNY(T>` zPJ>WY1%iwgRSzttPe^dcyl^+b1J=&t)dyA50Efi&;Go+bTzD)_5hEAgWMa(+xV78B-}ygBe?h`AM$tKMa5a2JYMY zFju1Pk0k0CBt4uYN>YG-1%jm8C$KO=<%PWSzSGm9he|`C%Sa^c00m(@0VZVvnE3;e~*uzZQYHHcFJ6WIKc0wWVh=g3H@n&3WxKR6!!5q|A`0>e}S3;8Sa z5GLVb&229z;SA_s*4trM2Li+OV}q1nj5+X3vKEf)Ns$DgRpmpC0K*seV`mp&W*cDl zJrei@a6nE%F)i?YBw^JBptTjSco7qPht#JQ2&C>~X7`h*0^`pu3X(n*jOYWA6yVP7 z;S>vtP6~;Z^y3!?hzhIVkQfjU?c;Iv0}%nEc*FUN%dv@TqDdI3NSGx{SS?A|p#hv! z0B*?uuO)yVnq-iQWLUCf)RJTznpBdCR2rK&cLA{ZBGOF)aG^gE(i6e)q7X6~H+nN@ z+YD$gn(Uy8>}ay=Hc4Gws z0{V|}>VEM=L+tGDpC=QBmO{6)?WM6Z_u=hT9nnLAQ@b!d z-@cEgMCBLjjJA6dOIxFmw$7)~2iZYA<;Rd|#uGorHUZz~f7iOh8sr%^Hs|i2j&6>7 z6GsaVkCV;Mv)8zKnHQbemOZMLn7B|gU;3ZdmRB>6VaDFC)TXcM*7;j@9Kv}EYK)eznd4oznycTf{XSSI947$i&rYoqYdlKGr{8(nLBBylasWj zXd`(kEdgzd6cNv+Qt+i_I2dUj+jXVBKU&d@9%6s)$r|&UUY3hJ3`Nyk2+h1f(iGh4 z+>SUdT^v0aZ=C6g3uf-ENo)4s=AEVrR#8O4L(}8xu#l<~D)q{#~2_AZnkrVK9aLUFEugDF#$0$$?0uv?P;B8lC0(|Tgs$lxI{sYNNk_iHle1H5rRmvdU1gYY5q|IZV3npshMV8=}_v0%+#3meqErc zi2X9~{0H1r6Q+CM$nDL8rarUw8?m_bT>Lqa`N^tyTkjB|@G=N_xxTDJ^qcm^%JKPm zqs*KAH&)VN(bjbYR`;*$*_Fk)V3EJ~u(08SuMl7%VmRZoE_|t@!LPXEUHFZo8%fik zfuhwYMj+X!__1%~p>O9OlUZ0saMTdVP>y0k*@&Si$OPC{Ld=OT_i+#KOlucEH7m)Z zpRg#Hret>SFF+@Dc_)g@!0Dnm8ZLs>wHOlW08tsps=6XSASa}%kmV(w@(BLw+7Naw z=QW_JkT~%FoT!RAVwiJWK$`+!CM9d-I5eO-y+@ksQ5`kB{7y@N^L5 zavRb`19ZZ_C(-kmMu;EXKjre5p~#p6v0_4cuLIML?jj|^++$s z!d+D4T=8o>dkb5ACNgs7PrlJ%HVDX%oAXrzn4^EGvXHfkceGv~hW7qH?C5UA575`w zcM$35Xz&lMyr1B-|DAuh|5*DPef3Ij{cl!IE*#Xx6rntO?_|Bt-J%tD(zn-FB)=fh z*B}l4uOOcH4O6i0jMUVg)Yyt0Vjnu)-gv)Z?Wpg%e7CG&T?2bSk3cco!Zh!*FT zm*y6l=2---bFA#_%MEiZ%gb$ZABd|wEF3eG3ygb&wqt${F5l|40UomczqD1R8p_M> z{G^9@`1k}k7*6ciN4DPLLsxdOKVR@b4MpU^Ur`MUNtk}51BAzZ_B3Zf{4^#9L_XsG z8gvi$*MvXcX~0(hO9}cY*ND0+cYpSFeE*0J{pi)6tQdcO!5xSk41j*?XqN6f{M>|u zedcfn+2@Vr(jt<45i19afwdZ)_53x`vU$7xXK}^z1EZ;=uu^iQ`$@T;I_6t%*W+cVb$~ zg=$1PNDA@p8Rmj`ZcezxgUq&sSYC0XzFt;cTu#Iow%%p-!mysh+dBClUzRm0ASxmf zswh4`o(KdX2u27<$QB*qa~t8)=i|c#5$YZ|E^yDNM_zwT>iCQDJp=t0%RCInYK^Vx z-xKRq1pM<})#oRY5k9Z9=j)4rC`z-;si4l-w6*CQo>aN2I((Yts2qH3bCYhm(Hzm6 zR*NbxRTCHS`XbVYK+pTVPzSi)*i+j(Xwu@z6 zM~t%**0=0&t_m2YfV|x(71Zv-%2%3Ni~RZSvHn4)`mdF=GB)r{T-O)7U;K>o2)MZ2 zyYJ8B+k9qmUqP*Qc4r^_hUpgEh}ySYvL23&j6a`C1Rp-u<%c_4W-HuFx?wYa-x$Bt zm*jiSx_j?ZbWFS+tnuZfNM(i(0vXSgHWNR|!g^oMie{EWGB{%>^>(1@xD@xKBl{8l zVAPM3?Yw%tkt&X(FEMbRz|#E7 zli%>wX`t~Xlqg;H06bpSuTe2oK2&;+F1pLcj<2MYD^wPAa^D=0!^RTFS6@q5JjSIv zc78~F)y^hONDO{dfPdyHKZlJ1o|VNZM?fqYJaTpgbq@SoRqrkBZN8pLgmym`OOATz zb{EoLi1jA(>zaCYxSG6oIY;;V+v=X2EsXDD9w*>gQv@7mO)JfNd%)E(+c{*_F~K>O zFjK^4e2p}6L}pf0d)1(8>Z)2Y%I(^DaXo`BruB0EtgragT%6>bL$nlU^_up!xxWyn zWbiK-*wXt<#6a@Bv-gDU60oR!tUpW;(-ofQxEYBZj*i`4JQtZQ`!2_xhFL7hZkvCq zzSX>SNes^dEa{iGIiF4m7Tz6C2tuX>^1QA_MIAIp6I{0#%5plkY^|1sZ&0%X?!0G- zal77hRcE)}1WM-m-^!FC26{gX)?IrcwRfs2_;~kRSx>lI52xHpS0D%LW?gzi)e*2h zJE$KQH0ESNZ77ne9|G%dPj}Qg!jejMxil(edR-b@;`~nSS#D>nX3N9d5qGE(lm|yJ zuWx&x=;Fe^XrIpaDt=wWi6aO`c~}s#dR}PXoHywBe(nZAcET)JCwJGn{a_>7cU#w} z9mrpgv>xcLk8d|U=;Y8qTk7WY_`a`RsC$+|*p+s2CBD?e&QE3i@wmmj<$bIe*|@SX zjjrj=G>mS!h{?sesFh1U;b{XPy*=d^CQaADhO2uCH$Dw?ATvSKufG^ba{(cAM z1g+vKwa(5Ap&SnyW_7Qn;_%{)%Zfm)6?uBGxdZ=n&+n6sQjC4 z1*yE#IK!89zv(c8QsFbGSQ)6|Vs9-lIkx8paV*Yv?Y^y|J^Y z(SNcWoW}Qr3|Yu+B&QI;JW1zTx4?@Pqhd6YVf8$JOC*5RzY^~pYcOgo1l9X}V^vwnlcesdGQaF?)b%H% zf55okxrE~B;)ZV_>%Ag2; zqL@sxy-+Pbij9Ka<6dxK3s=zQJu83#Cn39R7g?{h<-BRu&6QPWUJ@Yybn`EUtL_30 zjuH-(z+XZ+r@~fy+m5SLOdP8`QwC`jawF@a2&(&B$+Qb|myD!34AJ|B>@e}EQ&7kg zd_+xnc6vdlyYozIEWT@y%Wp5pZWj`z3w1ll(_k{jT*x!CAqJ(Tx`LO zbJHUN@b7z@6x2^Ki_3|DH7&oYp8b=^(Lk>-=FFRrYXYvkhJGsmzwu|H)4lTdKpFf^ z?e6{Z(A)B4*IUnrii1jqe&y>SKhw#QcP5jvyhyTXfzJUtQDNKPgg=moQetkM{C3D8 zHn)dET)qxYb;%4Q7v<7=z~nCWUBgls7m&l7K6fg}+;lRGpGxV+LqW`+M^an|sy=^aC&gw3zS6X_q9|=vNtp%%TLx%C^0;f-1}4cl1b`S7kt4O-O8o9* z(%{Dgwf$KQs)|_BB7~*F-R9q)(b>J`DZ1d*S8qNHuGN?uj11V)kQ+sKP1&#%p*&ms zTdK;7e3anD-sKfoQ<@0u3vGXg%bw_JmgkTUsUilamTbD_kP!KKGS5sayoLCdEtGT* zke6lGyI?K6_P|9d#cq#LOm2t?X;nE`Yp-StJqPj^@xScX#ZO7V8}l6*=C!{i!7cmW zwcg;j%)TPMRyI{;`dxG607(Enj-%v=lag@vskVk=Vk}2v$Q^|L`2#!ANbjUG zW06KSY%?k-P8!9nJHkb~mfVLx)5s{v6K3(J4q|=f61|v;?qgXYERLf(KT-vG5CTI8 zb!%Q!IyJzZC=mg-Fkt_vU9)x!6~T}aKnKT&hg>$8@!NV>r749W`?_3FOlol3=Zze)%i3 zr4bC1$@&4S5r?dXAhw+Y29VtIfg6YjpGx%?5Fs7W7#qcVousbE0v!+Rm{FqZJ=ft# zLIVk}2dNYyMyKxRq)Y@eT`V^>Zo*AJqWXG2|v0@Cz4} z0CGnV?B-D)&8tc{6E;N{29l9*0bM<&lgC(G{9jes1aNc31YmOwa@OrSDuJRfAd^6@ zW`E4DLuz$88 z^mf_cjG@6i`^jWRXvud7kPcQ1IyU&;I#97C^3)~gR3>s$9OQI|P#bw6e2gKzMru>O zSwlHmbGZPe34OcBp+?3c`ymJ16CLI`0E~}b(L^H-OI#8Sa72vG9!Om5!^X$s0ZlYG zd@jyz>`ougS&U?pA-_f@lG#CpM7wgoG7)P`cLg$ULYx~YTu?ainUEVQYX|A!YI zcT6D96Uy!I9%SuO+&H-r2B-lFMz6Vgc&!aA(fCGlH#OL1LCkHB$g6xk>*}Fw!;lEQ z6&g!`p}oWTrc@|>9z;&xeE9ESix4V{aW2&KdIU)mm`w!|V)UFVp7u$HJ{0=RErVho zxIR`oqzqGx^9L0=Nd{;_{zo)gDnK@H(481cjwppOW`f=trVb;?tOL-&iaC|hPAY+w zsd}-!-Z71Cq%W#yK4gZ#l8x|=6f(d~ z!l4p?Z{3j7Lf{tBTtL0wXEcDr4Q3}J`XL$!dWBkI?)F%mrNp~=+MM8UJ1ux4hJis^ zWp22+!Ox*-C<$j74Igf(xO!ID)=@6PvlatqQTA8gR_umZ3{*A9m0mWz-Vg` zb%Ns=b>8sUjWM2qoh_LBFzV-~FsIX)ud}fo5Lrh-mNP!0!&Ds%d3_bqt5oF-INE4b# zk4is?6^H?c&ErX%A9ihwuW@!iofzcY+Q6ZyL;z*sG7CBnL~8XIp!OFVo6fb4F9h=w z2*zXQs%5(tKX%1yWnH+wx?E)lHS~TbQ`qD(M2p=VBpZCSXfW0+gi|G2(g~ORMQcc! zH9yeikWWh3e)h{2cJS0(LsLyj26OXjXX%YkWP?c+-pZj|nnU;luzQx(Ga)>f+-tZqyUDLImVm)%X0n2+=!vPa@jlrz`ox#Rx~r-fIJ&0H(M%ryt2WsqZY z5YxCZM6xbP*gQr`A#_CKqv_1eLblIPoN49NFwdl!;Ecvt?%z;7%+l=y**lEz&1FCp zj+$cJIUS12J{SC}sNldnfJ94_7WFwkhHn;FyJEd9eGio7yyXH`qGeE+j7=iJ-tl~R zwtqBHPJ)H5FWt&B=lbItI$S@c;id?tfy4;Qkpe??6hlGxug@P#jp?)Cp!Dhf=5SG^x$sBpS8|$#2pENkITnqIsYnCOP6u3g z#}w$aF*0{=Zzr;Yl)66&U=a5(s9^TC-OB!vM&fto0U$P5foChiki+#<>Kw$+2*QHv z@$aK-j0p2H;yT40z*lp@hqt@@9!BwS&@PaVz(>-bD+W*f5e!e5Kq57YQri8!;ZzO? zbHYKc#@V!`f?jIJ^uNIh!R+eEqMVXq9+LEkrScgO_+swiuGv&5pe>L?HZ=Q$TaO*w z#>6`nD3ITfK*^Z4!RkhT$+U}F#7|D`>7=i~tURvF8~A;nnUnK2V_u(Rj5CyMs%wSE z3Ku52c3ndgt59ny+6Vu}ny^tBe&f0$0vti()=py3mk`WWN%W2FuX2eWB6D0s{6&r| z#(pdcSe~=uLLvu}J2#K6ys0%#%TsX0pJweg+V%Ch)RO?z!%-@UqY5aI%2JA&W>7g; z<{~JzBiQ|m;td`EU7pLxHO?cuhhzO#NQ@=2qoja=woxiz!P_HN7sHO~Zrm=>14(Uz z!ryB}H;Uigq=>4gVr(u)!>x9sUK@hurV}or?^cok97neSjc%-U5Z}^AIY?gH?=eGG zP?3;;b%%A^T#Q059(rRSGu+X^0{7JJQIi-7(h7DYP)xRZs!TCa4hFBCxYqjctCm+` zkGxRrrI;F!$y`w2o=4zAKPx9jh z*V2wrMv^twrOe0sDjR}v2&YGjcyC2ClSIDe7pPn4O=pns-1hlmq|fE9i(V2`pW8libYD)>g+kK+71C;p zp(sNgkrvj2Ae#qM;eV3Qe`Zfj7dWAtKav2D%N#%|{Hpf}`wKv-RA|6J*08&Ql0bq9 z_rh(S4&kxRlm%#yBVW|k`(_pQGs*J3c<9n-<0ZokI6-08s8*cQDctrGYUkwffC|1I z{tVh~4x+~&TRS4x%208$G-8A5ijYQz>jNW)L_CgByU#z3(dlac@qQ-xJ9Vh1D(pr~{_FXs3wN=M zQ}6usgOI=`tU1#YcISt0hk*aN5CWaw=Co&BxrimiW6@ka-*Vasmm)4t_P)-(@Iy+0 zFc|A?Y(Os;ZTQ;+6fsMvqhgbZ!ww!TIJr4kdwV$f&^ZUM%aeM~q-M3O%X_x#<>0gw z5LX2ZaMSetwBa^z+mRAyKNSCjX&Sk1(0vY+9ZcysG5EpCs_4KSfJy+gJ(YCI;;Qy| zqT?77Z7(QPt_?ju%}*aapT)U_3HY6qv7P&5bzeCe1SAmKfzoJG&iwY*=}r!A#TCUbllNa+v;aySy$JTe~)8lOav2L)ANZ%WZ!f8GCELGtTOhMz=% z*V5iMKu$EdWG4+Ix#atW;~JEcUF{TJDg7DK1j$)>k%au)cSTq4k4H~oanbEdIc;me z^0PY8Wo){$_0gwF!=>gTL&hs=MTI(^0jWE1{=RT^loi0FD(d5fFiAa@Uj8ptF_oPW z`b^SGuQ(PHEqkzWP7x$a9c`19MKNkxX*NI22P@39{J9{tTfQ^6Ona9py$_Egitqc7 ze1pMzQ8kwCEPA;#ul5WEc0cz|R3=XLxH?{A1$sfXP?XQnhZl@g*1=brjmjvux;?^^ z&{jZz2>;}nk6Rd_F#L$uNG$mMr_;4%|GfySjbuqfP`Luce&AL=4sQE_(6mVxH*u+qi*y2n$Cq6tE^INn@TEur+iFc=+XP$@DA0U2h5paHld~0 z*EgRMuT}M9G?Rtu$71Kjn=&2sUz>ueH~;AFbK(8U=L;SolTu+-ZwqG5+w3%4N@~0z z-mQe)-_t+cH}%rbCRf<^&A-~xLfOdq*?oTvoI$%_ch#V)qwi63)sp+)fmsc2Wp)U! z=Kx%57++xdwyS`UF6;Nv=)i!>iPjq5Ez|yqD%)B0E14m(eF>4We&y zS7{&R>gJErmaK!;lPzQ)9ardKw08eGQrqf08X6@cna0VcyK3p&kYGjq#gMXNUhppk zuPonD*s>{E^JD4M9h)%~srBJ?j;6hqI;YBWs=Vm9Ke)b1U*DDdZ{4kz>BjNj3W1>Z zQiWGtesc-&G+~QAGn^Q~J|t!+vAG$p0)0NX#*O>?_O6Q=2-n;1SCV9fhJ2?EAnMQUpN(wKXNm`@#}dgpe<x%etZN@Gbs=vKRUz48;$SU696 zmCRln5brrES!7=*s*`(U#R_&dxS}9An+d0kSCE)V*82=D{}*TX6r4!_rVBW>ZQHhO z+nCrkC(gv2*tTukcK+CQva_|j=iHo&ty5in)3@DK-SzeJy{~+m0oe+26FQ0QCKk-3 zlHegJ9^hNdJtM2VRbFSTrN}4F*){bG+fX{pr`}A+(etkc1ArDN(k!XFCRO) zIq}5bcN-pS+Cfh0CWyc3>G}WN-d?ThoKN{tyOYd?vz7wULgFF0-$Bl2uihWO1WTP~ zJz_#mA>v|`Uh?)mTCEQ=d$2?Bf6m{Czvx&?t62Y#QEhH9_?>GQEHdwQj|vKSX!Yoq ze3Q-rUJYL!(l#^1dct$~z9a8U-mXOx*2j_$NrwvpHp2a9hLQ;hyI(I9zQbdrFvBj_ z20m719L8O|bAJiewzmlAY^N{kBpnF}?jASxHEw-1bdD$w*W+vd2EgX>jSOQaM}Kq` zy6jfTUA}H8Y<9o#3qHKNX#WiX_UlZi{qtmx-Wqb^GHVi*n`1eGK?&!!K9uUuzgYG{ zynpoYT+xvv(XyU-YCI+;dz^G&YV#Zi!(OO8%=Q*D8u`BDEC&dfoqIuvYm zQ$S*OKtrbMRFSn!@4#<-Qlzj)CyZ;0(fI*=u^&2 z_11|g==56t;!I2WW+5iCFs6BPlr$ya2OI(92>AIYp&+4mPBl zGjlcmW$bFqVC~{yudW3Lq~&&@hV<`z0!9O>)Swxcj#O08Xi!mFm>g66AE4#=8uWjG z)@N7pLcx$pyyHKjCHJ@YzgwVVkcy-^{{bzj|1HvT{{KT-&Wx7M|1YHF>j9~zD!+O? zE=@@cM!f(n_3!A0LJ?s!5keB{QvxpngOXQPlsX+Qiztf#1{PK-EHF_7LPlpMqA`Ju ziipa1K}Sa)d-}S5`Fb(**#4N!aGOa!S@$u&>b97Def8qy8J-!$2qFZ!9pZc(92r_y zH?M7;HaJbVBd08F)}Y2%`AMcn+&M$EIe&YD;@zmO%codga#|gDb1^iVoP=d9NhlX) z_DN~UeMM?frub+-IlcN@Tr4(;^%%Qy^c|$=*L|NARzEA-u-gDonAd}_F-`v2>KQCV z-Ss92$Z2`tG3vP9r)h5DVp;DgIZNLwZdph5TO8~dahTcG_5Bc|_?Tz^e68*{Hid7- z)RCCIk528S*L)H1wFelf`dGgmcJ@{xJg?AJ@Tz|&e!c7HtN*RG0q6<4)3y78c)ZMH zlg5~GRB-LPxD@#mt}nwRzdY<;UJ zcDnVW5-w}fAW>>$<_TUM<^>ME`AVx8z2sTpZFdQtF`GlRBt?5e0}g8)r>)Lxa)CI= z=n3xmqM~hS6`g^~4TtaM`Ld+jLv;@8f`7F6Hk5fBdwR zzoSoblcbBhgo#i7-#Y%`tI1PS!(;^Ms}0tb%NXmO-AeOk_>0*~99cT@+-@$`GTXNw zNx_b|oAzGHsaEbh_Cwjm+Ak8rmz}YOC37 zOJC0yqYs#j8M^Y+hw${Q1H-1bAndQ9~~de9i2B^$U1O^qyuWWob^*tIgJO z!+NV;r46E!w4(ZgnOhE6xP#3{ipwzpUj}u}fbrB{(G7xTPSP!pwIoUO|2`}%y)KRi zA>#t1dh`I)`HTFPfGQVS&wr#=lny&v)cN8PQ9g{ubVIA6rL+$pEyj>s3M zWV2ctm$|*sy1=X){<CSLjbhhtMy3S7Rb;M zhh2=Dj|%O8q>V9~tc&otYJz;{CgJW%wZ% z(JCh=E2lVRboAM;^9B21aWr$$sQQh?#4*jvD)5r4EvTsZwY8NDX2Gjy6IGbhT%80z zy_sZgcJ#~a;@a{=?5?T4p^(d)>s#_a3!|~?rCtY{@0%K*?^~K18(Qm|8}1vN8lLN% ziq^l&S8@=pbKHOS-^~DZCaedjKZEjbf_j@j2^W_M*;tbkQwY;javyP(uf@ej$8`-q z*c4v2b&YlJqrpE0rY7z(1}4EC?&dsah7^tpZvmDio;;qq+Pt2fi?;aRmMtaRJUrf9 z?VG$Ey6wE&DtwPW)AFwYm#+aX_|*US)=};-*${7|ik4O6&okW%+e)vl74?tS3^zT8 z=>=iJF?6JKeY+LiSM&J#v6XJpf=O$K*0;kNVb{@5zRoviQ0h-w?YG6LBGBjJXYJU> z;#=*S{M0LI-Fg5CDuu3r&ga*~$w1uA?s4z;SK8y`iJ}XkU{|gm=Y#wE^ab^JWg26S zV5Tq69pJ9;F5I;60Fv}2{2$n2b@jPj5emqa@_ZP1veB&7yY6iMF$UVR2 zSzmW5=&pY9lx6+VTAUvt4M$|hs}fXd4cIl<$=S#dd@C#Hs%i?Eq&64r^thtisPF#Q zV$PVBl9W9>H6^9EnVON<#ZJvkEWnUjPfo#O?E)x?xQ#K+#}x@ayZF5V{(s7*=eEl2 zZS?h5xOln$OLzTnqti`IYh-4`$Hc(FYn)vqxVkkpv#~ENYgANF(N-&w(I5ftxVY&c zVnj&xh~R)8{Ye4?qSgg-15^Y86MzHsU&P&QpjzB&h$P{mGd?CGDnD`kW?Qx@R2rrI z1YDnAulUL0WaxWcsy&{eG@E*21;Igt&<-UEci)~rB=KAX_w z?S7Tad-(7Wwurk~+?gxT5EO(LQ7de9&piR)3VT&}_e@?EAI1?CC3ZVK&)3P1Gdm?g z8M6JIQrfKy0tQ_H%F*>Jir#4cXKDfM3+L(kJB<$Z4;tWX;06LO&j!DzTha#8W_x}5 zUdp`BP_))f$eNyga8m2<JC0V)?Fcui6|`U1?Oz~)O~wJdE@kv1m(Z#%`16}40j2QrsY+8IltYNc-MPBAJZMi z!4-XB9DGWh3spf^_oiGvujCs>P9GH}D%P*H?FTNFb{h|=^U=lCo`6@+l85H4r7)LYo_C@ei<01pYbeyA3lsSasr2r%MqKHW$ZEOVKUZT9chE(Y{ErXegOV} z{&YCG?cEVPM`O|V(`R!^(%4R)zkgiw9YLss$r1W?Nn#JGmUA_>E8FC+SGLc-8pCwq zX;4!LlTNY*RnZzUsnKvpUnp1XKRDsq_)?T3w%?D0^k~YQX0o>{k~NEY43${%0GrP` zech9>FS=%LVkIyWbLh@VCfdu%f;8Q;n-%GLjYesBQcKcudUf=}@)5z~YDCwzgGB25 zq3Dph-fQ$eEYlgpzb3$V656dJTU&)u&^fw&LfBg(f+iyMe%_g158D3_=ya+vm;B)J zTdrnw+%*bbZv_9RXOr4r5XjRTV9E`od;BrX!y*04@DFK_rIaIVV^yg1XHDTV&kQ=~ z(|z@KE?FF05rJ~KG$CYfpZ5B)C?c3X1p+0Sfg%INCyUvcZxlr#pYt!%Nko$&(6KaV z*?~&YZmp)p6)E|WWuNkFz4?GDyo!v8@UoVJdC3CHNBW}ruUCOophN-8Tsnu-I=H$r z@EfI$dtK`j%>P0**O6+Y#v+#8(>=ulvxlwr_|Yc$uu7qRF*=A>=q*X7gNL2|HOGad zO#DEuqV~<{C-`*}Oj&10#20#|B!hpyG)**aWuv?=C5dDO8<+z-ik^kH?+#@p&X!ZS zKTcjwN$GVERkpk`wa{vAG3n3Gum8s1&@& z3F;tkeo;yd6WVU17u*DZ{I@NNGN7b<3@QUApl7TTx3<(N0?VPJV&JT|3Jp3KXV$-( z_)p}(4>ae#nJLz(y_te%#vFFro1OWt5k}UoXwHb7$wl9KiDe8%b*F2knSd$JK|&5U zG#k+2*mz=e-#bv~*?i;WA@76RAZ(>bR!bi(I zgRzy?C7;0IS#cyZNCnq+P0kq{sYFs0m|5fYW@G>0f`ZGt_n=)kGGDz-xvd>IwXMF!T-AP>G(@& zIbZix=tCANJX~}f&|m&b0*o)4@U-qThbdE|Dk`4~F`97XcAQG65yz$P@s9|~aiMct zO8gfCv$GX#3Q{AZeU#d!#iUG?Tq@)<&~$8;9#NP#8k>9C;8)?36AfIPzrJxxmG2CW zN~B8_hQ#fZ?%``n39F^;>%fO&llLSHp-FruHfu zLAEzJFfSpLLm}%C)X-swrW7#vn(z^IVMGr3u@(Ek!R0ZMu6%+#VP`Mb`y)wUvm(D@ z>0B6jXr&);;2s^{wk)`U>MxSLymk?>+CJGNE)a%*Fxp z15`37LrxD`8aD0J(QqeoVjGql_dl^pUD5S;1fmOK@UCbiEYm#4Ske;qef0qH58evN zD{&>CwFx~K5i8SM3lLZ#G~@&ucj!GZDh+mlc0vJ_ad@piop2Pg<1;!0iREpDlOK?`ZJ|zU>5HK zW*Zn9miqJ=C`n|_EBTsU;UHM<+;GUO;G~tqoK&+o_8%e^oeP@PfHy3U=F#71ZA~pvj}Gz4iJ%=mkqn=aNY-ZQm+I%OaLBnAbmnF9fUEyxzEQy<3x;5 z#U~at_Us@_grg`izN$!o&Cre~NE^wWa-j)#V3r402C-_s%yIpchMFL_!!{&=X+ceN zLN$;xIJO+>;0uhhCk*Cv+Nd}B{;;E>P)|6R%{?&p)f$??F&{FzEZx4W681t9rfWMd zkPa9z3(zAs@InwYqs1ixH&U1o_%)Ug7z+~?iRW*aS+}OL?yDEI>9Vwa43XsBQ6c=j zK~8Lmdk3dmOkWQDh^Exw5wy@rwAZ6*GF*rl+Z&l`$s!c!@KbGhm97?`dQG_8Qq2z8 zsDZNt;6Psv#7pEs`4B2*)=ss+6IJLgCaX$E#o46<#s zVDld~^Jo#$yDK5T^k>-hVB{{&3BACJap6#4CIUl)B^VbBP+&O#S!g~Juqf=};M*+} z3||IF5v+PJI8EAvvM{E4!836NF-``=4*Hmz+R7tP0V0B75shfh03~T3~8|6>$P578(C-tK<*Kc}NEeRva?l zt;Jg}!bHgKG~0i{ShCn|0|R<>ts zdBaB*ooBh&JQqVT-=vD3rB54ykQYD_&g8Y2fYLlBRZ360E_$$u;u`c=&~gg$WZ{xj<`=5 zvZLD1F;!Coafu^j1Dfk%hFXS4NUXGjfW&U%b$vrwz#OMuL&N>>5f#JpIGB6iFF59}Wcf zw@JnboUc*z4L_tCVoq_le^+`BJyVDk|7N=`DnD~}z5Yo8q&EXug^E#VS`NIJN4bohv+UxWKmp6EfbqDUJ3 zmk?g`zsi^hf)M9s={<5Gg2%8g*JH4FBQR7foxsxr&`R=KtFSG;W|lCma>X>86I1Q3 zZZniNM$$M&^|E+m^m|Aw-V#I~)fwMzkRc%|&_&UG6}HGIrjj}HH+(E)^e+UU-8zs$c!Z zXjKB+m7BCrIpHYu;9NDWLivfh<~CX$U-)JY~h$H|Nzo>yRNH-l^`7>;S;$8&w6DDaRs_wZ##yhmL^2KtR9J zjCqc==~`@Do~@T)QE0*=c0v1(&S$&EMGOAfL}}zFhQdRUZCtcxw7{LlWAhc&wo~hi zQC^`(oGK0eFlk8Gt3fKa$Bq}0A(uY0dVA2lyDTt4(jh9Wu|i*QW@1eX2q73ST|=rR zjIpcZL0O`91QeXnAtx#9phwiUDA6P_y-YBMR&V~8klT3W`9fZXZOQy694%#(A$#fk zdHpvcw-7$$1d09{Bu;?b%6Bl5VO<(Ob#*NRXj}lvZ=8NEntgf=`=CfmZ)X*~y+V2G zI}C$cbBSlK6)RX|zqlVsh zn=6XFEH9@hh^>?P6+MV1IjA{Y9|TA)vyct*RQOJ`&1kQhAPofxNEs*z!xtv1F0t@7 z7=X7K!GCM%-~a7iLXoW^vK!(909G~w;G@8%RN;e|I{OtJ83;O1JzBYwYj9IfteoNX zHfGN|sSm%U-6dAa%iz~9p|xioLZKA|G}bv16KC8=N8&B^Eb&gnpz@+S1DJ`(S|SFN zZ(k7Bd=i{+M`3XA>0~2=Zpoasl@L^o95}J`+j=uHHBS7e$bEaNlI^-L z7ja2}Qlgk^XB~zjhdjVJ6BLa}a%XGiH*9F<>=#NA*zf9QS>TsHh}SvEB@dOp0y7-E z4~SlbA^MNnpeWG4pt4AaODUaJsmm_Vw!DDtX(Q7NPf)K_+X?r~zs2t4o@=!{_lScK zflnDE{Y{BP?oMLtSsnLjda@%_0o6Z!Ar~sbiD*%6jsYCC&cn9N;2^xD5`qBYSmmI_ z0Dj}u*C*5?AZ%ob(qFDU^m?zM-`E6iBUFbJ2mZPyEyVbk^h%MXc_KqnEU0EPVqYwV zZoh+d3V0LGPRa5p@VJWPwtQHS(&WT1F^M)9U#Vc{z0klQLnZzO>Pmt6RSa}|r06%M z!%%Si2I-_2!pk>#psu$V3URQ!weTMaYfyG&xPOww0td1MmeUq=bKXwBubX5V6in9c z7~pia5e3-X+{;%I?#Yfyi6PBI3%D*Z5%i5|6^(w>mlEH0t|6ltaq`3Ly%9IQ$s?-9 zR`}DAQMjbI8-GX{#VO5HV=g1?KM^PMLQkQAd|*4<6u0rK45{Y)&Ov%fiXxt5i5G*O z>LR(%x$uVtTceT~1i#nzt>ui{0X&*hLxQ+F_n8AxXXLigPHpQD$!TR9`@GL?mnZ}U zZwdn^jta+1G@4$YZKETRkgv`Do>7uG;)%tIk4wqpFXme~|C(5+;X2KOinqA0-&7>E z!2dVq(_p=iCGxY79)Q7u8FAHPz%Qi?M8Ai66I+8*y#@y2dGM>sWx)Z;gIIG<35PFX zyD$tu9;xP<=yMBAd*(wtJ^Oai{xfAaN=mqd{xD}p1Jf;5)Gk!aN!WZgLt84<)XJQH z1zM6GB-sgmeLQ&*Z}f05ku&IK=brH5-Y=&=H=@60g^jp$;+A>a$3fDQ{m(E?;-?GvsK?H7FX&EYLwpHSfp`yTGDRU%#9gB3q|9m+bPIoR1fI+lG zhI0VWyCuAc$me3u29{X=y(tYO_fW|&2#$z_tkAMN@bu)lpsGzloV|jlefx{AKurZ5 zNK0&)taCv`6zs{RnJI!!8_S*LU^q~Pd{~^qYY+YgcJ-jW03z{3vAT1ibm8+ogdfs| zwOG{cONp=sLr}rNY|cu6fLREE+-(fG8uT3fapy*ylmd->1obgjLhXR8`P@J{u{_zK zb9@7mg$H<;-HYr~KQ?+;#Ycuv$mH38D{wcuP=NRm-(I!ER?v=f=&1FDN3se8X5x)D z^12aHJxvKbB|&C^A|5_LX%S2$04t*bT75L+Uge!vQ8Zdfi~dNm1z_D;fkY&!RT}Z% z%)Yi8wKHUwt^F^~gQvCbb_(>pdYxSlj}*Th8dIWx9x4hQ;Vq zbI8$|&HJ!@tjJZEkf5|P3DG7+r)bFuU$pk_s zxGhlpr_HP9lB&b4{#2U&@f)lvChTfZeQFk4wPIG7L~h`A66^P+x@e2iXink8t@*cz zP*(MeL?>D%e{hWN*lD^d!Kfwk5Jt z*DekgIh|>MG-zET+#IlZS zVu;d=PgV=MBuNEjGFu=Hop7#k`ka#PuB`T@qON2wYQtMCa9YPzl2AQDTG-~s;FB8F z`UrV4v?Bt`5FU;Qg>!$AI&Xv8dH29rb09KAnnn+SGbChLLnTQ-!>&V z^ukw4-8P!l84aR8Sl;na`7Ijjgzj=>k?&bs{$#Ua3^2tKDdC6-zsO}qo|Nw9Z+qXM z9NCrMuhH$A2j*3+0JLVOSlb~)jPvfxTuIT?jbZP>{odWoLyKM%ZV`*L*gso z!!u+H6DbZ}wA?=NwAzQoZ!jh{4T@?5U*)=E;#&4nknq~!320;dhy}J$b%kjRHrWy) z79wya_%A)@BS8@#<&n=WE!DQ09UXTif_E>z?33az-1!5s>TjKKYOdt|awj)->fNMo zwDAJ%t`nyw*OJuKK#V6)+>N5SM>fuZEe{BQP06{!LJ8e&%Bqo4eX79kninI%6m1dF zw4CkJ;urf+5o*v$%iaQ&VDLH77it|vs-M=1UQ6XGX()sOn(<*E5`++3VL@CKndS$v zyEe9(%)wLgg^8~8|FUWlu2E4tQ8ffZ#;>OB)?4vt>g=t@h9tuU@jov!Rn_Y-4=9a6 z;HJ-2TyvoxX`kwnB4f&-1m7~?Q0OWOSZCEnr;F>NkxuASf6*3_Af>)f$!serX~ynC zk}d+9WQ&QXX_pL4>?=`*@U)EQR91<%;eka8jcjS0pugg4j>}l3b8zVa$`C1gT{82O>WCFd%>Xuo3M`BZ? zaiwSkzG}TYJ?sL05`^RsKHh9tFi$lHe7a;SPeh4C_G_$gA?I-`0OR7t*>&B&TVla# z858wmt%Fh(&*AZ9X;$&gh}zn-aGjRyD!!plwc9PU@Dc<9@ZU80myWLh8h71T-3sg@ z$+6}Ry$jqPXzD1V0XB1gSsmMb41LNDt#UXcNi1D9TT3l%SoNm?beo>nXbm~a6@r}P zl40nTwK}aJE9VI4H>l;0X)lK1j$rH!Hl)rBiv6tM>RfqB(!Ys4Vw zBsZW}#<3&Dn;e?mHARX*Op?saM%*GXW4jg85L_eal;jY}{waYt8P2rmOv;^*bBLau zcEygD+Nuq7XaVsl{Gr8A(i_%j3}=sEA38@iuT>6zKjVv5A&3Q*i|Jdv7H`JGLhDnI zhfqYe%hFmx-bcqs?sI9)7r^9WO_L-b(B^@yk>+yY;wtNYvr$!om1%=nNjWOIPsrH6 zMRdXgRS%#k>9)jnm@;3YR1*j=*gUK2UEZYCKKZKbII?=w2x_+2h!E!UHX+I(bD`y@C)FPeZ$%mCknKJ91)fy|~OXCQZ4OjyA=8|#=bfs~J)5?HwRthKBkHaS9; zj0d>4VWsC*G{0Qg^LHetUN(B&8+ZCmHaa80g;xGe$>r?IBxl*P_x~QD`nmtg#;!um zH1w65=zTY#F5G)gz;utr1_%L}?bDX^CO?nt@*qWg*0`#aK#z%WNdkeKm2-IGeeu>hhPX>g zp&Z*hY~TYl`vaY(4Apg`cP^Vx$vMyaL%{PQZ?u8?Lj3seYfiH(tCaLGSOc~^Pd?l_ zTjO%Ia^gscH5a7r9RO0Z)hh4Q)fR%5Q1ZOUtg;`qlX~8Qv7Qt1XUR1=XiG(?xVNLH zITOYG51k~I_W*DP&H#sLU>M25L`@lo!+nAjJ8fUnu8)(i?a4xW^FU0ic%hxjy_?q* zqv74|N2{0XVGrG3y}MVb^yBWJo-}gNFbq3O=+3!l!2t%v&+zCNXjHld(aYhaogvQ3 zDdQ-psDZK@n#RH;-OJ%djpTkFjF+IVkQDK^(8(hM(95BfZt&z0Y8;fEVzF||IPR+hBGS|h0Ekv8qV0T)}`;%xW)5-qAdZ|8PcTFO_?q4IMcT2=HFejOL+?U zX%7|ofUaI`(Ga1^yfrS7t2>TWpG^K=yIj`9d|(|P&MiV0o5x`)v@NNmdMVeZEBc8n z{q=hftFUkgaQSLU>p_hf6(~rQ`EMFN@9f_19zGCF&+J8ctqJ{MFIX6bm-9ZeM)b8v zthMkA2Khk-#>xh`i(LcJT_jqqY~^OwD03ZMSnA>Gsi|0sb)4~S8KP!_?2wF#PHx~r zMvNAsjFO$LmvfiungpwV&QfOu8^YqC=qxCofv{b6u=UbaxF@`Y+bZ{M@ptP~5j(b_ z#f#nmm~UumA7~u!AzlS{utFV>McB$C2c)0i@wh8#)knIv$CFNXM-cC+VsEJ>Z>i{S zsg$qBQRvhzoO7bCYc2#o@mF8*?bE0%_zsu;8if zowP}{PFcKJKGAW6x8PU4)GI*LU@k>^lfA=JKW)(gHhF@rkbO#%NPq)9L$lenhCutD z8k_I!L633qaA#okIn1V-ktjzX05m}I5t~tj4mi2eT}D2}52}o;5|{_X=+Zuyp}jS7 zwOgP70u<7u+RgEsj1{vGl z0f}oGu44qIV=-l3Y3Ys5sqyN1AgEXZ@H$-16vi^Rf_tqG23OT=@1mF8_GJ4Z_~&_Lk#Z;NQAhtB5%qUJ z{*d3e5K&gmPf5lzkt0B5Y3%4a&|}rI@nP-7>Y^>{gD_>4k>8LI3Fp;>*%DhZJY|l| z^YqXC*oDHg&8Ck72I#JJb>_Z+=i2rQYw`na9f-E(r!cI}QUNX9{P?2q?<%2!x3VQU zxK>@kT%n(1?8nyouSl8q!Jt6>6yZ3?q282Q2!ko0XBl~0VO1OJF(o4lU1*O0~c59oeC86>R>;S}3~ zS;@AE-e$VS^jB##@l5IUMGwA9&rcoh&n*)57*Y0jHtv>?PPiSc{}NV#7vHJrA;jwA zUC)UrqgKAZB>yTw85V-CQ4()d4KzoMwx9ui@B_^DtX^w6)Ah-_rI#oEIIZnrwZIQf z)n@fw&Ye!G`Qeum_4K;bB-Kg`2n7|8B`iN|wjni|*|{}vklK@Y0zCiXq*-Przs z_UMIaeJMwVp3kYzzEj1UsaVt6p1-BZnyAir`eq_z6B`jjjAIL`M!9@ptb6?n9t^1-LK04ww^AD4)FQMCa4)2DELO*`4YFdzwVOX}>#v@xPZ9KEkzIAAJ2aAP;d{jJwxjn8 zbCrtlA(^pZPKmF!ro;m=ei2n>dG6q0XQ5|P!FKZrS7%8W@U|37_;ds_xaWg#XXP3$ zI4-1{433lg*}^Eddkaarpop^s@Z0O4*bl}N_+vo>)7|Wukp3;hZA8CAB8194%x8@X zWFw#HN>NbB7yJ#%N1*XU-sbI0ND3ep3+*WdBp>3qDM?kNlnY#2x=Af|S$_rIEI!m2 zKglW!7??qt&SRibEr9WQ1btEQzu(QAQesBwhY1P#x3Aa=TH}hVI}26*K2#q~0Y>dh zcAv6&NV`XkI1m9kwcH)r{Iox5l#;IR0(%=_pnw?ce!{5JuJ>YlKWqPp6Kv)G{Kn+L z1oe_6QV1*P6?b^c`94>+s8{#0LTUVE4*GmZcTgBu2_9%@60R|l((Veh@;LHYOI{*k ze}!h^ChSuWZ1Qxu%`Q}*3Mgi~OL#>)tc5k20RiK3o=UtS7#OADcpKiOpUDxTp2cv8 zDwA>ujC@Y9mkH}+alpp|3YH>YC5-x@Fm)49Ou#NSC#&&s!dJXlk%4!~2+5|18OYsH zN+GbYis*v^8u>sGH+1zxMkx)0qdH;Zmo&KO)v*O#yHf^^#bX2}`yjyo`NC*5j)INf zqY%C9QCkapa2Ol#Tv%UC3xKm<`uM+Xho=i9snlHHN9?i?7zVqAwf&usuM@oL~uFtdaM zoIWY`QAymLVcz9}+J@2JKG5b14F$5dZ{x?J!M*th*rgVf6r_$M^WpPeyRkqtC62Ue zf!0WrC^_)^-gSfvDci8l&Euv@Q?6klsAzEVV8M(?6C~r-e7eJ;GDFRdLo@G$@BMJi zJpE<)LPC;XRiPCkQTyhBGxcw|aJYS!CY0>820CYtuVQLRI4Szg5lrp zOG*xl9ETlCm8F7uAD>QEd`~&&zcVKZX&4q}ILcSBihKCS1qd4H`QAli+$|lxTHZI> zX*mUU@c$+$IQT?)J3c&oBrG;HR)9`pwvO#3Bk~3d@J+3)82FqkpJgfQZ;VG1Qdk6s zv$7oL%tH6hMoJ`pT+z-z7V~!r(<)T0<)%Hir9QU^n5~d_E*wlw+T`vCev_$u-OhvX z8a+y*B)Ljk+=4)fa*0muU#8x`HNxeL4YCTBd52H zV*$;f2jGuq=z#^Nn{{9bv4iBd!|5KS+SPsE%rn1YG%v*GnP`^hkLTE)i=9PE=U3=J zcU|I71}>eW%V@7QwR{blioh z^USv@q(ZXnkEL=g>~5i_^aJN7bQj4k<*f`nM^RWwTsyTc9#NJvfO%_yLn((@2j+!8 zi34Zcj=H0~F{SMymN4WUT5bpE`htDug^~3@?_1|6iCLB^n;QCl`Pt`iB@E5V)=$NG zjL#<2Yl}LnFb>OVHI&k90wgnOdzah_ezgktYJc<6Lv?rHf#6ub;Egjj(VbR}?Qjpw z(^<}3gx34|C-d!%p@9Kewfs7Gc=4azI;Zs5MnKoPHDMO@<1Ja&NKcesSS7&STZye4 z2$w>}7VQx*W5C)QG;WzqP!Wgf!z3DgXNfsZ!D(8v8tApo@l7k{w__$iz#OSg4P@Fq zy-deP_3WMULd|Je!KuPGeuat_U%*>aM`yanZA zh$WAF1t?&DL@{gf21mnhd|P+z9b*UF`D#Fg6uBqI_hK2u(R4JQRQLf~j_e8-7F^+f zYh=b3X^0++#K!a~EZ5s$w><8R76^kj)Pa_ZzV!H%oZb=i{o^Gx*!B%ZT%Jmx*JJs! zsakhPEJ!rvDq5H4;|W=Jy}VhLjXcFYU0fMz2N)dg%;yBN3`g5{fls=G8FT|t5(DnX zHG!2WJq!8>LTJ*8A+bJ;gi4*9zx^Bd{cIbJUrRR&d3U3FPp_Xfcb+CaUoP?#u>%N* z#+*Zs+RoRjRa6K)ppUeT;ZumUZ!mv%CjD5qm%};?&$f{9GcgDoFMHC$(XLg{B)eGM z{BYpe*X{7EWZy4a7R^^Se0aV^TrW%LtiwDky#b8Cu;PgB_-Mm9SKbH|?ja908Mc%cu=DniBK|aFX6B7;0w(z1 zH@^T9-BXIa&e+JaP7VmYj@&=&Dtb@Wb6cOSi=R~!afWmc?pK4^yJv2t2wJMHy2 zrMQjr<_#rhR^{UPPn(@C0lhw`_}#zSn_g)J4pkm&mg;VYBr_rhiW}s$Z+Ls{Hux^t zzI@XPdNWk6%c&u|7jkzIB+z=Cn@=!_H0z|a8?~>P*U!@YNTQc&+1PH>y7${?6j^I8 zr65lExl$b)DMU)wq)O#-7ca#SY8%+yHdGoWKTCx(zV18MK+-m<$#RTUuP+X`AEMvI zA7xOrUEP%Ij8RC7zKuSCn;m-OvW}35LfTEvGHKz{!Ac< z(o^ML;D3w2X?>Hbv+VvI^p}a$-_QD=k|nh^8KGspp0PEa(fL{V{l>Fa{;V3}a3iCz zn+?aUO!&u+J1>rgcR`CcLW(=G{IzD$&A_x-Q^mR>Rh&y@BxnfDL222`#*O zGHISRC6;uYcI?e9;?)gp6KfXcWhrPx`I_#utRNI#nQPIJ79MDKLhtbHevBkU@0H1=(eCwRe;!A>`-|wY+Re-a^FBuC zTHN8cGDcM=d^_`G;}W@!2iO71&V{LncBMQFJK>JKiT(DJ>NGW&rqdmS-}(pKCz4Kt z1xhIVje<;uJ*xt}pJd_U;2++Z-zaOcKb(i&rXyI9>u+MOLW6mRG<#!vY1kw{T|Ex6 zVETktO9{nq2^pQYghJ()`54tJCYKIAKTt=n%N(*(y}$Vk_KsTsAXGJ^w$(FeS+gi5HH{)bGe8WO=_0*$W-_bi1!Wr}QTA0m z=_T|?P(I-tO?gb%7cq7%lh3G=6*FxvMB+2lW%aq#3s}XCb1zP>#NKPQG~0k&VwFRb z!H^U@i1){JP=Aa>cn;3Qdi@Mpqt5A7u1Z~O5p|tbFn>*+GZ@&oKro_6c|z@Wzhc}X zjF@0>DW^5M8%Hv{_$K}G?!#su(|*VBcr+a#;*O9XGX>f}2=D^n44oJrH;&1CO&fY zCfzLbTs^?n=d=ngKn8lV=jP9i59|lY|3e`%KbL&ZRz0=;$FuZ62;X^>uC1nIX6@(O zRd?-cX{1Sd)1Mlm1^HCdMLFcCf*(dXu{fY=B!Zw3i{5#a*B<%LIW!wYCHtj!MZj90 z*4EQmi$s*PSpL3gsJ(&*1UwT083{G$r&P&vn9(HZGz>i6flq}J@D^?Dxd_i|&RL@w zD*dlsh&i)&s^!8}wX;9pFR=Biss}EDSlw@cnlXbw?e%S5CuxSPlFq)J^PMB`LzWDi z%YPXdl$dvyW>INgy5g5D)3`aj0jrEfD$JhJp|Ia-4)@#F1B55|pLXiT5xIJFqHQ%X zINruDr3b_@Idwg>ThSbLrKrlmktdk#L+r5x8I;&b4^EiO;uA-sKyS3SW>@u* zn$Y>f{kGB64dFv_UBhEj@AJ@uNpp*~uNX6^%s4~B4A5FmivQ4YStOfSJ!{VDGQwB2 zrk*Pc_>p@%yZ(# zC*gWzp5I&U5AvJwaIPhY{dj!6*Kbw~;8i86Dv56)G3npd#*gEja3DV$l#8rdr8pYL$wo!38IAC-68>{LC1dBP?~$MV4#Hex0n$<%R? z(tEdn<##M%_*&CILkh2cFAUHhGsjvHEd#pT2HTGUbnX!dEUxIlXMaRYoL>@g&$);Z!iK5cp}nHfHl$%uhgjcF9+SOKbRGab8M9FZX@DrBmR`2j@rfA%90MD$`3Hn z<-4}p%vnP%Ne%nKCd&vy?osq)ZPZazy&*m{j)TL!6 zcGUvip_=DHxy*mfxhY{D@8=30H#sp3>nl3di>!+UN|5&L&7xO1t><6D`ys?;M}QTN zp2*bja;O__FF7-BSf8JWJlbr4{j?ItB3EmbcTEqoY46_qL?1N>fV*XhXqgpzZ^>AsG|xNPL?2>EPgiV{#29-dNmvFmKIVIF zOx3e5h0J=0TyG%8AR@uRC&Lx1jSe$!E1ypoc!8VLi{8c>NH7HJ+S)!vG2bd!Kg}@Rj@vw~ zo9*dtoa@@`X*w9yZ(01^I_Wz=isrg?jPg~>G%7x0GEieoqp}h&thn+nT7=Gc@R6d1@cK^#@-mT#0fxZ@7Bxg$ z+hf+bMJklYzSu@g%31sR0<)#E@<7oA=W44teS^?NoDMZvlnCr1a{V{CSG=CwT~kI8 zWspConwDZH58>uk=_mf8m)L`mlxoJ)Sl?0h%FuJGFFc26_pU4S4$*sPD?BD|cMsDm zpH+6V)i`x8mFvSJ#;Hgv2_>#5?LQ)8ar`;u_HvF#W*U}Mw(Pld z45+!x=Uis>?_DEoaAFn6cg9Xij*&jA!D-7Fi5+w$C6-~^PZ%xcv+dY0K}nWrgut_( zs0_SU3N21m+!O@m`3qLxe;P|^PA!Y(iksGwDTavN>FH|G3p#@1@n^kiS8UN()jISD z(w;WmUJ4h8B0rkkzZ&GO|M2=FNwjGqTTlCTIbbZmzqfdL3ixYAqkwqxKT7AE+44v) z%KGEve}5x2e#B1GRk*(VntqbW;Cf=+%<*%kZiLpf0d_)IXFbpN3x2pWeS$q=y z_^ch~by@>k4_YCjWQjD`<5limsXG-sD-V}^K+3*GT0}s(DkWoX;iP#+xwBsW-IgLB zRPD-dwI36QM-lm(D;59hxtAR^32*M9fATA~Qc0yW5*KSD3?>e0iT%vS)6-*fF|Vd4 zMQ%}|)NEgoK063H%S5Qv!0iH*sz0TLNg1+N^|R#wl9Sir)9&YhcWaSlFIY~9R06*T zX->%f6UvU`(GuK6G$ZLie+z54 zz$|@E2TQZSl8O9#z*6Y&T*4C*D1XQ>QuhG&Fj*B>dRJgg} zE(yGxCAW+fN;xHH?PnXBzNLMaPAnpyEYVwq>0XzbsqQQa62jYqB{z{7j>WUE` zGNs58s?Y+>^rsge{FNm@Id#Ta1|WHHoHo2WdT(H`y2v6EVz{kE)XztU9Av2o zB(is;64=9 zm4RTb2Y3N}E5Jf@EM>%N{fK1%7G2=CZDt8QYK9wYiPFd7h5pTgL;ALWEME|a4}Szm zAY4di81-nt;{Yx&FoClWDl7=5$Zz690rDw-z~KP7yAwtkaD789g7Y8fEa80LLH40) zNwelgA?kcAJ6VYEOTn9hLw5f`tTbTU&jB3g3;L!*c2pZ&NPnu+*W;s*UI~e4s*i-5OV-ifkNzS1<-LIl7WF_Vg2;#PxPOEeo3?0iBa(u zI@>|8mqg%4!1sUm2XK(II+C~Av%58jwm6Bg7pt~p=inn*GPuU(I@__|IX16zF}a1l z@D~YU(VQE`;xk0FdVaLHY0=r&zPLvAzZpE6tg?IWzI=MNs`aw_ioMuc31Z2+`5U?T zRkAx{wAuGwA#zlxu)F^uZVR}0ffVMjqvj}7W&2QYcgJrF;cp3!Wv6rH@cn5qj%*9h z<&bdVh^Xa={9p=i<%k-6MXzj&T5XG_zKPszi@9-!Y<&rO;E02Gn>}ue!)TA^|DlP- zZ%?3qo0jBoXXZ>2dvp4DO_XX+)_W^P=1j5UOy##saN|tVYflN~OqY5~P36qMeoHIm z%!GMMZ{^I|e9ai;%$|JBT;;D&~GJRO2d1eJnELD&2i3cH=60xi1OjD(Am1P35Xcy)7%{s{C_X-pW;V z(3a@uQnlJqp~P9e*-^83Q*qN#D|TJ^(@}@;R`u{whySqw&fP%YIrz-g$nP~s#oZ*= z*}}!$tmoAt$=zc2k%WEeo66A|`q5(9*_O(Eg3jHR+1Wu&-Co<->BZU6``Fp#<~5`G z*0uZ5zFQ2UJ;1Kr0^;5j2+`Gtz|)V>)sN3JK;AV#&ojv0HOS91B-S+~$1|+fHLS-o zV%9Zc*EQ@Xj1?vbd>cTW0D{g{`<=i(QQ9?8%QM;9HQCEEHQF^b%QL;&HNDF-bJ8_) z!!!HRHT%Oe2hlx;@HOMdLUSKLBQ3BX2Uuk9UgYOp66;=)<6TzkUe@DXG3#Ej<6U*@ zUiIT$3+-NubI z3(@lzfo~6^XAhrmpS)+Ep6`IY=YZUoHOLzn6!gCgTq?R5Oba3a!1F)%)qm7ir~t@+ zcbt#fe_UAq>p}-0{`(Rem^uApqx|0hUMLj*7vKf-e*s`c!;8OW7 z2iIaHpo{h23&sAe|CWQx`Tq^?;!JPu{C~o``1}X&vf1)1D<>{QL;xHCDlZ@c*$tT? za7s%=QKo-NKr2)h7@{izOQpl`xW>RjNfbmjjSPl{#25yx&On9EA&Iy1ll5`^I*RQU zUH@Xa%RZUrRMXtrT%8O-jBp;=(-ND4$$nXC8n{$01kzLB0Fv?jyUOp}}tJ*f* zQOecFrxxSn$bHzl+`QZFOm+2eLT@8Wm%qb@u4a^%b6JpgySgH))|}mu>+=f>i--l> z4=%q`1k|GR`I{rL7TbG=ZQv~x@$vk^%RGR$occ`pL0Q$Q{WR6wPxnMHlOr!CVydp@ z#&%=+-sa*4Is6-|ow>>u<+1Q{AviF$;If_2yZ&ljGx+@;l>XbUNZM zH2K!k;OLj4VWn6Yd*Nw1c)65TQf01-U07*Xs#>5n$LsJF;@{QJi^t+c?WIi({wtEM6ld<)#^#JPkuVHAtik{KoA{jokDJKoDZCS`_-2Ct88#ZbQk*!aH zTIeUc;8)iDm{tV1*qU&W0{+>X<|nduTFsBKrxvVCq8z1S;dK@3 z?DJ#dOSLK9V~8R=n7O0u4+sqwO7Bl4 zb$t@XFZm7S!j|gA0b1 z$t*3b%oAZb+ zOKGXEuSZu9lM*vqx*_W4@f zoA}8;?^XH%dVl-I7K@F6{YuUC?a}#Z@9^B%Na;8RAK@Aa0XJ@-`^RmyD*r)PW#ft1!@a)vL12INYxn#~wlYLI zyO|CPbB+#!{32H=6GVy%E6aoA>No7|DPh~ww{O!Ev0lpM__itcGq#WF%~dfL`L(AP zzyS5l|ENTv8scdTLQK4mf3>k;Y!i{wp3_ns5Z@Bt(i7aiu&dqsGiXO0fwZoz7HV#O z$DZ0?+g6iP%(yF|XiGrw|B;P&bH7^EbXVZtYXAa`g{l+0&27CBM{#$*tMz&AV3aXJ_Eo;OZos zV>h4Rh1hg$ud15w_v^RK+ePZ^fSlAnzKdUL8^g~(DObB%V_+&XeMznyZ>IPA;oZ<{ zVdy8s%gr_}HZCS!2Q$aq?&{>lSWg1@{zKHtAHvof+Vkzi;bOLnzwWZJPgv^F*;peLth z_GLY{Ra~#+6Pi&m@oY08837txeXqqfIX+>XutPygSq~J8GHN(yNI~+?(k0YK0Fc)X z0QSom$z9Ej#21JFJjQ2Fbt*CWic35Z4{l9-OaCJ*|IQq#=wD>&7%d6Kt_+<)rkQGe zHI#DyOAf&yCYoV68WGl0h+&g?ah;6$yE_xlqQ=iYJc){opG$YcvSEZV#w)sWOG&P} zg}T+M`~J1U>!BW_uv#s5C-3QGY;(8kxr(z)mGX@BVeT7IJ-OaKXIslO@az71SbZ}( zl~)?lvr?Gbt%YC8v2=C&d8C~%rQvH~&tr56{;ncgqqYgYIAbk&b)4I^g(k-7S?{TX z97EHWxyJLzxoBqzMl=3%V5(coCRg`faUNP$=PLIkWcG%~v$yi#f~R-mdtrl?$?b8E zTbK5M(j45k`=?2~zrEAy=pwUr+X_CJCmpMEH%Il~POJCl{jIs9|Jy>{}6Hd-FiE)`qyX`cLXL5DP!F6JxgOebo zfm!Q@cFhi_#V)g`9p^FxY%2QkOoq2ukEue&(&xR6vZ(396)d`yBJT60yAzPL$wx_F zH_=@0t@zYYxlnBst{tzrN57pE4@|>uN(k37c+xz!Dli?BV-(2b0q{NuHS!e~Fg>G+ zD)736{z2YERqH>UKzrep;Br+*t{^LhV^pA3m^mI^5el;a|-NW+{Kj(BO#oQk_J4xd&U*vRshb|+{j)>a$HGgc6R>s=lt?q zN;qX=-O9KdlmUCGS}9-4cxYkj#?9dxy-Sp;&ayMUr8}OZ)mgQ=h}wnEN0`;FSq}b% zZZ&rFX}ON@BB%bAvpqczL+FlLo=2)KagQ69xq#5}^{ak|+Og?My+D9>7Zm|71-o+PuC|t=gZR;7F+~>h|p4BMfFhO<=0s?M4+R zd$^Vyp9{$-yq*)0H}?y|T6xycES;4b`BZI&=fApLJ;3?VHdp3A3})Qdw)%$k;&*;5 z)}6yGx~W>im!P>dHTlxVV&~|13)MM(fBAUQ@s$7cFHTDE>tgJw`f1;Cc6b|?T`rE} z!=P(DC(S;IWWBro{3V>f(VKuK+N-!xK=RH%S?w_@4SCs}=VZ5nRg#mT;}=|{^ftG~ zE~WH}v21Q_O2BBdj^_~yCjKUR*`e1b4YS29?{D3O&Z{#X1!Xbj!$(OPk;H7qT#C;b z0wqCaKDvhPEAVCm$4(@VoWEVaNU(;t$Ya9CZBguv`>mkxvTf}r0-$@1>K82$k74Z3 z{01)0Jw!2~XI>8rT=(@$aGaRcV(!(2OY0^0BY$coiTykh&yFcD^!x*CfpFWP!2Ae1 z87Y~7VB>i-yQegA+0E~dG2^@YO&~=`3ucr18%lXIl?PlM(^Ms}Jouwd%i3}o1I&j! znt&3AMB+X=y7~)4=C#;RK(gc&o+O??mrY_l4&<-?kZ3TH>v2r;jxv7%*eSq3pg-Eo0NJlO8P)?o{Oc_x~Mo`!DQDj>s-gttM+U=hkI4%zJ=$snrKIM$e6i52F2ecK}4(?9Wv>Tin9JWI}Smje6%N89zW=^=?WKH&Ql{3>=A-+juGu0ass zz37{fV=>_I7H2tE_C9XJilKv0{9b32e31}8yb-AjP6QE=&mIQ;k} zy!AH0Dt20EB+6Z%4j-du3nPS-P`}~|WG+u_Je%BARfgbk`TV-nwz#ZsBF0SQqkQ5F z<}YOaoNZ;$9}Hqy{xvwEodgz zygqk6XwaO8fBya#(siAPvbof@51&5 zK^tXi@9DlRTVCMZyhrVCU^#cS2NZ3U zoacnxg|BUPHCv#FpM``j7$qPmZPA=Mrml}YMMs0T3!lb(T8KoAs)k`^0|h1sUYd;Q z*c2-JzXu5tmnHAIq&`e`6>cs&ei$~{uUpiC79?x0p#ayC*AJdIfbc_KVDjhj6wzK? z_0HAPC2vlITtayh#CjES6=O+Rvo0}3CGLIT!)S3pn-VsxX&f8dJaRwsXQN$B;BXD% zG!prXN$!27N?qePl+Fb@zR#a%>hruXSW=B=N}Km6D@e7Rp2}uT90k{)ow{~=WVea0 z>KdcZ)3rA-X?pJpY5aLvCCHY7L3GeK12$+f|Izz^RGK<8xMJyGUPE9!=`bVuG{syN z!nhH|`n&El1wP3T7kRpA&3MCxSK-cl8B))6%PO=#`-B5wd(q>H@#CdrLG@NbO=M`n zs6@K*23*=(g6QrD*rX~!;l+j3Y5V1%VVQ{JhQJLphgk81!_bY&Dz^_{z9>Vs2lcA{ z?FO34V_FDGZR2486n2BIKN0QIp?Z7c%xnz%XlQvtYi@k9i7Cora?tsM$55y0DT@@? z!LfgIi1pt^mLA1;4i6Cs;7MMd@~c)*7?Ba8NDaOsm97=5mpDcAJoN{Jc}*iO`C#Ph zn>^~2)9P|)nxk}x1BSw*LcZ4B-0xFSfshD%@5zBBf}c7IKW|TuB_UPuorA%i3k%N0 ze`x@_?weWC0a4WQtT51F(?%^JVr?}R@YDCne~x*P*>kIpux~FXfH-;I)g9J0pc%iJ zyfp4|Y9XT<+2ZV0D_f(5HPuvZ9#&0<5uB6Hf{1yH@c^HL10_z5MnI|~P)#I9%ma6A z8yY|;dLupVL7vWU1aUyE`e0o>8h(>;vu`cI{fJHwK zHNcNkKI`!}z5y%(#+cjnpeVn>73Z&C(}7|1Mhw2B`E18(%E;DOu^LleqDu83&SS86 zB~mCb^n#vOVIpW1~c9F`oHxrEJ+1xB*%*^H}k2IgQ%JpsGer1qN!C7d+s2S5Pw8X zWsG4S{msXr z^7q8bCuvUE2q^L^L~`PQM3o5#T5uc0{F!jbzq{I1TtJG%Q5ICP0lQJA)KoztXSq7D z`Ea7ZfImTP*651&j?tjl`Qf2gD1D^j3?P64ZQzH0jyn`!0wPG_p#Y0@?HieBV{7A( z!Oy^9C%~nIznPN3TVHv%(AJ=#IvpW5`QE$n+0#F?t(=sBEzit7v~8xLVU6cE<<3*^ zLWo;7oJDcn@!ukoB@-7yqS!yb@--PDa9s8o?WBID8d^*2T1&H=wf<+y$iu+)aR zAUm)FspWTrxqAN9m30NDHfAZB-9O8eY~Xtelh4e!+HRSbMv0A)!#B_hM*KKN;Q}9Ee~EA> z2?I%98E`1w1i}`P2wD#efUK+FWiGcE8rBfA0C|#kIvOGZC(xinwn9i1*dm3hjx{X3 z<*rgAaGflt!S(NfaWloe9da@*+Fa4)uog%;r*HM0(kY>r2mZ-Hya}u%5?JD1^J}T{ zJQ1PE!20mmCPD(us6Yg4;6n(SsQO1ihF~=EheT^hfE^Hjx2+TXlttpZfykU+vX*Ad*6` z!AL~_Fewl+Hz0dTJfGB<;gU#+M#OuXp|0FlSl?~@FesF>c%AbGXh=wXH*+mq{7&9h zT@g^JIzbV1eaYWvi30D^O~Z)$oItKrh)@O*4kGRi7Z45lp-TT|jk?`nwmCq8+i8>Z zI4X&0m&rw`I*prnKYO_Ph6M_i;%^FmqZ3kMkAmF=`s*mi^O;&;sA#%4jqd*6glgd*#UM)*CmXJ z(GCS53lD<_Cqf745ec?J=P3Z2+rcOYdn~0xmMsqVSch?F1f>xJoq0nhM)>}LfTSUi z3JN`~;06=EF`{Tgm>rEg%*|%cq@c*9yuXy+*IE;j>o$C?G3E|1SJEoyZ~*RzCS4g8 za=RU_ZLlA=<_LmtF&vDY7nJ2Wr2`tk5@`L0H35)hhklR%u8{p@AMyaW5r#%FNW;pn z99GCnIS|b!#Po>r;qCx^;es|pR4(U^&jk0%6Y$=(m*+DWd@~xmS?wiv8@lfX?etm( z`=)h7fq4NJv$}XdTMj&zun6u!KG1b09h+=au%!cli-+%6Mnvgy8@R6XfrHmKV> zXsTr?tK+Y6gDR+@y>1ZHycUB(AdHDPOzk00nyNV}y!oV{;%yEueuZR zhiWA$=w-lZZLl{5)z$*)ayzrBMR|_|{NpgHy2E&OT^6+>RbjxXJm9B_5>voe5~ zE6gz^NvILY#MA1rXKK&2#QXc*du(i4s-p!e>=JONYo^f8ewUAiELM0)|5iGL{X!`@ z`PPK_&FW9)ggOTq4A^adXAp3ED*?36(Khy+Cfud0Yxus^L&~E3vDT0w^&`v6A7um< zWRCkxyK=84BZsrlt8M)4+>vx%)J18EaGTkdTlhT5{s^t38lo9_hBvI;@j2#Bq|JDKO2heNU-IqUbG# zIZcns_Gflyb2_+lLKW>tL-*A)R`d>LBDLO@@ccUWmxfC*U2EfpiHiw65tTUUB=9ZN zV9qI8<;#2(Pv`qIr6s9R+49LF_)M!%wms1CeQT)ZW0H~h=JB2-Y31@oL9PYe`wHGWKdUcSH~qWoAQ)b!@aLK{tKy;#&)%POho z-LB4zw`p&``Ape&`qsYvkE=a@q?KN(!DE7X3k`$6i)ic^Oc2EDWky0<8cF6Rw;Dh- zVHJ%FtfR`qwtWJ{QOKu)*uS9ds(ObW@^ zlo66fRw=C?JlTyWPWHv*yqv#^YzCA0A}GpCsjP%$yp16Nzd_OxNzmCAnB=j%?X-2|jYq_K$1%BSpY z+d4bt){00s2s1~z-Vu(!S!~zwetQmR8pFx*SF)3q)-cVbxL5WREE{_nfzhqAAC{|5 zF)1#^Xar*GY_1GA-7h_nHtf0@Z@)&%vctQ}MYy+jd%sxu%(NOM)A=W~v!KVHA6WYH zXCylZ@cSndd-x%@DScfhhzdw;uM>lYXRi*5{IkJ=-v55A(yQ?#ig25sTJ_R+!n<^4 znG21VE3{{`rdoblzt9l$2zY71(KFF)J+abLdTm&nc=Vd;HUo{gJwyY~pLgzLHm}cm zD=umpKg{+rropepSW;QY=CmYdGrYPJHqQn6?9hx%7TYS$w|hN$C^$4s(DW}B%cg89 z+%l&vxf3XyO}L1S-0fw%wm<#0UVw7@Ziijno9a(Y^!!R59t{DR^6v3ty^zy@-&nK5 zyP0WwVk}|zzrFjg?lrSDv~J02PI{8A5DId9wis+~TRlOj@w+J+Rh92#Tvg+rE$xj8 z|C#%IJcK%Hz}9u3e_AzF-EsRBft-p!m;3ot&|A%7VLJ7;G`?I=2yOL;{=78@%iBrV z89K}%=8M(U`E87?GcHteh3kXp=vJ+Vu`BtrC zW7s0O1JM``@-5-`x*YTtYVb?SdzOp3QRR>XA@KVo_ZMns1?kKe{CY0Ui2;a;?qujy z#8zslf=RV9hli=uGGApMlwQl!AhVZ_8QOWnaf`mx5ON;2z&bx|lIrHiau$)|%<*+( zym-s)9kI1L$*mHrR(-K)n|9gGaql3b?-Hkx@X9d4KX(!Cgja`4RT?UGF%8p*Q3z!I zHfw5*D)uH5g#t}oaUV=H#m71wsgW;G2Prkj6+lHC_Xcj>EmJs*Lf;S5_iuIoRkGK^ zd;-QvOF19rt-aw=^vN-ENh`0eLxdBTPRtY>%Lj!QJKu@YuwJc7;SIl&=>R>BUc375 z0kYdhUkTL$101Jym2)VRaw>^d&xx3z&`|^l6+2&TGn1~4Sz^Ho(KmSD!a zZoRrI9#x$cuA*~iKQ#_e@+g=biRGJ}-;w1W=gL5K)xkY&BD_xKekvzwF<+UEr3or( z4R<5dA9}AH+OqE2cc9}AeonBSJ`>SieR~lv&oAxUq3}&nF{{L6zzE&qZL3>XD+F;q zeuoDXl};m9qMrwqIn+uJ+$(I3gY3R}ygY|HezrzNY?s}!muZ`GIX4B=Z(#V^ZaBWG zH>BIvKd-`l>6Eb$x6!o8^yq)A)0z?;-claC=m#tAM)&ZhXL%1~ zZO=DeO)0NaI#ySO#y2Bh0{3&*`M+1bnl7CLgPtM>#V+t{&d+jRofnAq%EYUhA)N17 zQCx<=ODdJ|J&a*G?gG7r~V&#Q*`0~ls7S?O3ASNYxey9 z@3wnf{@>a?F7)Ou|EG4(w2GN};(E)s5Mn%5zr(K#LT3m?$T&ox0D=WzNIVB?Ba(b^ zaVErwBlEhsOhdun!Ge?vk^cF_aRR_)QHFvBfOss=({VW6Z01>?8Rp)b?>ir_>aU(k z9;@2s7j8~{+AvB2jX6lb?Xt^y7CgbGkt8l#evOf$2=>m+$ewdpP=N%TPQLii>fCXi zhmxhIH|ttc2VL9&XFj}HcmAuFm>ZmO&|q&^&lW8ed@r$i$uHL0#$c zLlEx0$3)Wp{Qbp(g&8T-sLgsI6V5 zLU6Rz=~UUdO_=KZu(L8FoQ?>#gMFsrB2WYGZz81IA4iLDV+0hQyk`+`h0oS4Y(esO z&;-mmCF5Q|1)6n72@p6OfkuDVG^98%uG}+%JrB0@GuMUd+=+EwEErVFDwZW?KqO=V zL_DU5xd>t~R$ydZAd*Crl5v)MZ@Fk+o*rS80Cn2cF_7~jc@DaCNJfAB2%oqU>e3c! z4AXDTG|gXvGI9S7tW5$145JjpvoOI!MFe|k)7G>Y#xYJTZf>Vj)f)oen14Cr03+ey z!6S$bTJ-xuCIttB0b2wzFfyVOM+wi!V-7(YS%y8k!8{)#=Kck65&Me<+fEEAHOYsd z!9Mc_6L1CGrW#m6ep(6H<7*-k1&WL1{S_CHN8IP;3fhx+{xWkO>t}Xm8c4(uf`NM` zjNIGsb9$Oxx)Dg2)#LCqXE?ITA;AYt7lJ2J!Xz}LIj2^|;hf>+5YQGN8qPUJ>G*fn zZwCeg2S>0sbtx044uhfu)0F+E8yDInfT?*nLxbRIo`RVKJ@(7??~O=3xGb`h%$zzm z49^OlNetWX{O5d|E19c0*XIwMxL;B2lsnJdtKnD!1d2XmyX`e@Z$uFKcQr@kj49i=c9s8ZSXIp?Vm^y zzH1PDV~~vye+&QqoF>@+*;>1ya=^B>{W<5azg2@sEZ=W|Lw_L`C)rZh)6&tArxo^Y0!g1BIntV7e z83}ps*uYw8Jg=B2MLYVR9gH`*dq<}ReEx}dUgm?nKObi=*Eok-d`S=l%*+?S1`o9i2w%`FXEe;WG?r>3Rn$@bR)>}>BIt@!~x zIW;kC(l8xTO2RraPvpt?Zr^pn8W!9l3WB<_s-~Hbs^zo^`cQH1W7XomxUQm^xznK# zYgC340WF%9e|!PMBg5n*WTd6qojINd$JLt_Zr3|fy+3~qM2~OA8P8X=1RX4JJ~=s9 zK0OX6PuE`miM9MZOXr?5RSj*u0Z=pZl>O3Db|A~KHjHI-xQ3=5jS^-?U#U&Gjo@J6jN-&2vz;RWKc8jU|>H_2;WzLP9oA)|N_RP8T&B zW)CNu^NSc;=jxG!^up^;IxegaiCZ17I@jkHkL~k0-VfcQ7b__>-G#q9*_ZHjkf!9^ zNA;``!=2Gv;TcHxxX6i%pOia@O{T>14Q`MdK4vKJ?q_+LvrS}qUX0Y~9ve(i(Xl`} zlRK$Z>9su7wyr0BO}o2qYDI%4vd3VB9}^>;OK|j;X*M(`1#3oZ&-fQAEX$^a7FgBb z!WStca08u<2!@}EIX?+|#7gKcF>XihES>Ld7A>zHa(}n}>T#olN9%bxC4=cXi;^?% z{XjC|Zmi8k;^^-36FjdG8Y#}6flgQObP#Dc*WNK-;ZY`|Kj_$O+da@Ct_NDrUyAX6 zguvMOIR4JLa^{NtB(VK_P9#F->^+@p=?etWnF%OQ&XrA!P-nUd7+w)d4=geb7RX&_ ziTNdoi8DhRcF_ROqjE~1)6#sel?$STxJa|a%#UWnrnkONf9}Be6;Pd)0l2sTXponeL6w_GUW~0* zbQL#7r@sH{7L@WyaBN8}GBF~v4xsH|W{ZnpsO2-#!aKX{uCd;0ro_B-2 zOM|6mTS{&%g{IzVF)QTpd*ttdC(;)up?qubI+bB?`3wWeP9e1;N9aCz@iY0;TL9#^)K;{{6)j=81Qu-rV{3AV(Dz#O$i4Zyz(On(*eT zR@%4yn;F>Oke{}*iL2Cjysa$GvQu6zN(gXc?kv6muAm%SB#=A{s zw&eAmedlcJ6Z9EdL^mydSd);c{TpM!jI=ER>FqTI17bf^QAU?bAHxMdXg;t))6%c(Lh}dT=#!)=TpdoV z6$k$NS{TWEX#uKg(4=JC2}?rM+^ZvBuDk4%BgvN>h`&LyO5mh-N{+Y zd2~n~hW7_0)yxUs`;l)Z7w^orr&``Z^#>?a@) zi#exwMcra77Swq$e>ZZv51eI>SMfWtnmu%J$xUK3>ZXcJAATT`E&0k?HBfqLHjO&& zzh+npXWv?kLo`biY;>QiUQ9H=&9@7zXPLmWkAE6g+%28unBC+hCnv{;EBQ7xBb2@w zK*@lf7nsTHn!8H4h;#8vtImxg3@*WMpX8pEpP?)nRh+<-{yDaK--Tq1j*T`WLSh9` z^qS60t|qT7SoK1qqrdg#hEG{B+KfS;Av>-MVL>v*={kOGUfRIx?lx=ajyTSm zwBpz6)bCef&gm_j#bOjoa}73+*~zWFYC*5&X@k{UE7WGkGK}^j)f?pxd0mqWLw{t! zArF*=_m`?7`~D05yF~}khpY~`JnmoG>EGHnHiGb6BrU62Pb(l_ee`kAGW1#dE9SiF zt$D6lp|_c48le|deLX3vvZ1*d1v?Y4mB&~yhZL42QyGx+AvvnBr>*IB1zF-1j96HY zn`B5#4-^mn{E6_NDBP>Q`sW7@0BAz^Z$@F4|Enmh^1q{S;Qx-o3tf*Q1Sk|4IT=y% zDlq>;sf?@%JocX`{NEjPu`vFBio*XMvoQWYMqyPCj!cWySjCP!Tvl39*63y-Hm{Hs zvf`zU6~*6|k|^B8DFn|81<&V(6|S0S9))_--2a(L z^8WFC!Jl>7J^B8cX`glaw~Q=I^6mUJ&>B!YK>*S+vsL8aGi@0NJmWS;&QU>cQ`8i4 zvNC>gY8enzb~ZM5?p+>n_m5K->8p)}>^>!>C1SSkP0en;^70vJSs96HSnYzB^s_!M z*SvmKiL(bXGSWO|N7IxPHok96K2WTztWQKo?_)9xw{SP{^0na&Q}Nh4`2G{LHdliN ze1!%>1$X^~#rjJ!;^fjJE9p}beYUj0z zIP(@Z+3hqnITzI*4_{x+Iof+^ym$k8;!%gr{@Pl5XXIpEdJSZys_j3#`Tx8eko^3e zZ8`C$fQ2J=$Ii+BFs$2g$C`t?5hTD2K)1va5CEQmbQT;%H@&b5c_GRBL*YJ3(D|@N zU{?hM?|jTM7Z+3X@%sMP;upbosCWUoe^f9*~1>`H5&;uKA`lU6%%{n8V z(mf9zv?M(EY#^Xx1F?;5N?=m}B+S3xK%N5_dT}vmFpkSp{1?uvjX&rL1&3P^$i#jo zZT|%1p+w;uQU092KS10cJxKb_e*g{^Hje=mHDC^vfTcWu1S_OQqJZk!QedxK9lAeN zlpxWc+bRlsJ=PQy_^RXYftA%qsv$Dk{W2kB$|Xp#k$1j5B<%SC$o6j~3wZYzKT)Mw zi1+<`cz3Gx*uY_!zY%`S|B|o{0sIHX%xbLA=Q%k%%rMFwpF46iRo_6bAsD&Xh7x2T zz%Y!0X~oWL71{*!{(<;!92W(s^e!OZCeOq&fGKzo?(YWKI~O=Z61p}-iCszWKQgXJ z0B9hJeKV+@3W8|?*y{2^$l!FdwDXgTjDaZyOj1jFPH@T!-D-qLv4ObyCtd0-KHWZk zyErp6o-nb}Oyc;^wsJiY@>dzlM9eSq<1&zd8h%sbRTempZl!Vf@i2teYMJ_YVC z1#eq>drC|S-UJTetvWs!b7;Q{5;&PXZW0vqV|3&PJ>E~QcG+JB4w?kfJfyo_rpX}j z4>n?CSE5i7LvcLdYy1AdZJU=DyFFlVAYGjh1naA1+ z6q@e}EglK^DpWxg=(|=AEj>1Vzm5OOx|-Ur96x)r?JqYwJ?TEY%)G3ejC{|Q&c?4t zk@@{6AYVS+yxTfkGcm5RT2@jIjU^+ca8EAHtjj9NXx1x<_YI@}HEQtYF}0O-DW-wI z%)GFOx7C}Pk&u;?X3x%dEo&|9ymU1dcB{ocXseCT?V5ZiCVrc|`v>?8=-Zct>ZpM} z{ZRPvnaJI^BkS8`XMV5L>Dje~&8)h5E(QIVf>{aWfVngp z)=vcM^y2K?+=>vC9auyOq{>%PM&-AL1d=ndyUroBSays%Yd4h5Y$4AUTEKpq`qlvcl>=AvcGOjn~T7!sejK&DpWD zgA?&pf|1dM<(J&AWSXHMLfeEH%D7sylQe(tIqZ?Oj1FQwZu-cS;Uc2E91gKL*}$Es zzZN5gHgGlYF$BN%_&zJ=v=}XnfYVwc({NNQ3`?C@h{cVUGuhy)-_1r1+yoG3=2i!z zm()(}fiONrg-`W@55oOfE;nx)Naa@wseDtrQ-||0;i=3kFfIs~uOl%~(%^a`c|=?b z&b;K@#;SC8{X*%sZef1QQ(|we*c!aqNf!sxDti6^Dmmuutg&2K(r0XGeENHH`KBOu zlYUp@^@cy18v^~nVnv;5m}%6P+F}4sZRh2(AL(0GsoS}uv(JkJ_vT<^&_H(^=d+P2 zH+ciSaOEY$XIDeNV;)xjV`OCYfZ|(FdNQ@~%tm1Ng)BUzv-)(-XXUqQwU5|hb!t%V zvU;&_!Ig&1MC_Z!@MejAOvc~>=Oi_*H%hBLy)jEGB%E-B4t1gH(pjxj9UZ-ZkZ({3 z*PWwp6y>5;Ur!Oob`7tY5OOfO{+N2OKf7G}_d2Jy0jx59I%;(&+Knj$Md^tdPj2FjIfcMeBImk*kW{I}eTfiao9e?-YNoY=N)XZrtUs%9?c;-9(LRp);1I#p{u>s>1jQGvs9jt#9usv}xG%+nLH_+Pd@ z>)${a^2_!7RYaa^BfnGmaOs=Qjh=E3Yjv$8QRMR~4vy&OQVjL=%V%&mUDScg8T&+> zGsemd_$wl`YpM2><9XdjpY0EFcLKdDd%5A?_ef$JTm~~XWYsy$rdxkDE^ z3_`LB6LG|nFV|GVUz*4c=KTJO83w8>8;Q6 zg~6@g)CK{qjNZ4MrQVKN3RMwFn_rK|07P`y4SkG_ywB<-OcTDFg$W(}AhkaITdI#;$HA?IzEEu$zP4fu5g!5Qv z)%tNBhkyl_CeOPOx3a{2f>U!Rk|@LX5bS-GY32~XaOOyC390ZLfUvz-+oE`U@Gi>? z{ZNVHoLGVjbF1L`bKy_edYCv32OW~4Mp2Y|icDq(IIZ1TODVisMbuh_b~l=7g`?0n zT?f1-2JX=eKxwtj)TXTqZ4k*1u%UF2Ho-Zh$L6wa3)1Fvbc9l&} za#-YfhqqdXg-L>$yJ(aabSts9A0Gp)tM1f9XW9uh9dj4d%-Io1rOPK6<2KUb^cZ5= z`MZ>BWD4h)c!%8WLzP<$pg+!PlXwP=7ZIyd3E!zmgv93j4 zNXpps0xib}^bCklO}d71OPBiTk2bw#&10gIW;`UbUj#9g+Nao?PJ9s*yivl1sDZLl+!TtpMGysccYRHjYD%M;7YAxBeI9xgmYk>C#e`I3KqS3oXiOUX48ZHCT%aLZ^df*%wDs*)*H&Nm&9Rrh9Z~ zHYmTaboSXmb1bfE%VMj8Ld!5@ZnM_lH*|5}46o~ESEN~L*Y(d3>i`#+ zeaau!tFq#Stbsbor9>?tgkT-F&QlcODLWX4XzTo}Hi9&2NkiiehI)YMpFB$bo^( zyv)74PPl~midtO`%26A;GihMq$6OCD9e_BeajEVv;MVXFO0(Aha>)k{J&xu#I7dL~ za+_@T-*} zrVB$hI+Lj8Hy37dPsTskhGi2szAXaBbyt!#=D2!h4g#BS)+?K6uXna6qp@Mb;F`wx zG<4g@t{zE4dQYy20%X5>J5iaDIL_A%iL9&>-kNs%(i)fBPqQhBsgZ7P38%1ht$L&JL6Q4YAy>^c&2G(eI$E zjCA0IqaF(?#{Z$uLuss2W7X~Qdc1i`;<G0Gg6Gw4>EJcpgHz+`TFVM+!W0Rwcl? z;{3eO#gIW3O9wK)1X{FwVDaC;?0#vGABr2@#42wM7Yukv;iKVsMFm4z4mC(kMU=vV z;zEek`RUnNzn@by&p28W1;k5Y)DNQijefh91PX#IQ9=!JL~yS9@Mg3GQNmWZ9^s)X zt0~qP%1s2>d)&+Y>p>9*$~JD;5(P4^zz%AD$j|S0N?jlLwiqrD;4S5_eisAEHhR30 z(indt11kyLTSWTj-l6JPk<-(P3IWzQ{x(YZKlrL`IVA{6nECmEK*FwwLBke`w`d^O zqhIqx3cgcNuic-P$g?>$o%G3d9GEQ00>HV0d&y8W^k7XFnqxl$u6J=i3Pe8+P;dO~ z&!v{FaL*-vD?8WwH!vd}*C}+Ct$ghq+&e9;Z0yTxRn1GuN5!>K`WVM2X67aTSSFtz z&d;l%jf;uE#QH_vNA_iEs39w zhK|bj{#8-YP|#QJdNAvUr+bF^ME!yR z|D;_H%P)KC5SflM(Z@%^ww=Dt_Kw{;Ec2$s!l3T#>geX=;^F^XuCuXsnpio_{pX$> zF=TP6-Qhdyd5u{s0}B2RYg@&HoxOsZl1dyu9R&>u9T_wqIG4wM+t5D)6xpIYBIWOMIj=N|5!*&YPlJqQ*sZ5I&0_GeI^y4txJ8X4aC zA|Ya=;tYw00jkJj86pE-<3x0RxQB&_BH;B^(oxbD+&~w7i?&0rp(cs)}`xZiNO?^seb1~98M9L8tb@ct*wc`j1cUCd?3a?GI5GW z&~)XVv5^eYtWIU?iH`F<*`L@rh=24}%LG;gE=c!yt8WtXjq-b4QE@dDu_X*M!f)D>o?xBS+4 z0^Yu-AYd^Wej1-79z*;UO>~I*QDc^7Dq!qy6_!_(1A&kt4MhlZd-4_CyW=O@u^S}X zKED-gpy=~G6z79srwQ>n0se~weQ}xjEN{$p4(iUN)v=kRDDW-~JY)0K_Ed54mF^V? z)Ek$P@9OhojScL08XOXb9OFncI)WKad(>fH7J<0V4FKs~=ci+=YKU5OxV9?7Z49rI zfI5kr`ZwA;RNB#NWPlM$eNk7bTaJPJ=84aP-{Y6Qo5?xwYtNiYDBwd|IsL?ptE3^u z9!kbs80>GUvq;R43_o7NMb@+_5t#WsSoB}>d&nvyGnkZ3E_0ZGp1?-Y?zxf1*kf$Q z##rEx?EBbAeKde2pAV|?V-Yp2ZOtpEwHi^cOQN6L4VtejTljmVGV8Xa`5S8iG!W&4rq zR+HiI^a5t3YKO0Ej($u4(~I9_XjEr)S5lg{PL4~kkL&RgxLfjZwRNw$LD^}06j!)nXbqMPltIOoYyH&KS_!qkrkE-4V8qBVn4Q9~NP^u`7 zX9>xLL~0wLkMGX)LcTbRfh4@>aHekI$8~Y(;jM_~h`#0H+ z$-_GN+sa$n6>GZ@PjF0(h3G@QUA_pM7)B zFgJha8IF0Kf@ZNxgWB%ffbgXX0Tl@{%wZ8vaNnZU2+l_GSIVbTo*QR?ZJTpJd z3qc&m`?39={-Z;k15exd!q0~8fsBHSfZ~H;l!6}eec?*}y#YSOe=_OT>$!Bo2Tos7 zKYzP~TY|c7L;r$q0j17$o*_4e5ijB(nZSVJqMxlI%Kac&xQ_p8yt9$-1#yBTsd@me z^1<|?!K*#HI-yBkeQpR{C^`b4H2KdRqrz0B<^V*>-j*(j&r&n2T_M-Nx!GxQ=QGuM zh3*rZ>|p#>n0qyAm1ao@GR<@@X%#DKmsf#PtV7kq{e-6|cn#Mrt;SiC62PYjH9JI4i?DNfisT7lEnHUP^v>hO$N??W_$vh>z&h0t z*#UBV^FnNIk)6@Hwh*Pte7&oZ8P)6WLq((cHW$>hs&Af-`g@8E4x9~ev@|9hUN#+E zf_m3)V~tgMeHTuWOWO%0QC}vC?y=f2jru<35(Z=MmbT|Ir9F7+r+E=A_cQqBdq~Oc zR~_Vw#6DmhYyN%|cJW`^cv$$7^TaPd$8CQKTl#NYB68=oDFWIX0Tf-io9FD!v6)R3 zONxHymWAO|^-TnaPi*pI%??Kq4d>s(RXq3{$7a3v-)1SEM|ovl>GhqmXxXOUEc+eY znWoHG9)(OLp}TYPIQNGrVM{;@5yX6h_}$w}B+js;^@5#d%5N&RV}l=_c9ycnPM2@ei_? z{icslj_s2!ki22$C{^tHq0td6_5t~9T6_7DiI@+sj3WT=?_$CAk0|gOri>MRchL-# zECiz8&1^;~fmdXCVVNG<6GIc;(H%#mapY4y5t%dfz1#$Ut}Hu2$-y^0;F6gbF0h8n zhZbHYvvn9R(Mb4BO|M(kFQcw?2=t#TV1a}`xVR8gy-ZaW_c_#;Y3xL(i}q43?g+<);$BJ}$^g^5;@~4?4o-xHWfJc(Wr0Kdw7D;b$W%@$AP~X37!p$~F#(B@ zp0xX+Y`KV++V?3P(_>G9vs)Ag~BCfNE)sm!BOqN~k63hW#z1Vp1 z5S6+=)$#yDA?-}-YzABTCC|i>jb^^^Z~JqVf;xwHy*t zx1lXjp^>t)jd8YRBQN=eg9+`lz4WG6*f^;z(^D&5kjEjMe^66)Mdk&cp!XaE$Q%oi zFw?F>I43vhsRE@eiOZoi5F^EGCV`Y6memMcbSvDE6RdO;%tZ=(lwb&_e8})aXxL;Z zMd1Ldj?duD++gOzV59cHN$CKr``Mj`q39QpzUkR8rDq!~Z=&Q&P7!}>f!}1Q`mGp} zDRD|1@&^{A=YIPu-{p@o4^vy}%b%1~HYkU@MWg1(U|Q6D5a@U$MYKvvdo7iJffL@G zA2Sz~Sx=R*y;BAemMvy{#!e0m#alCV7`#ys|OV@GHGJdRlq9d)wKWah1OU9vAU$ zo;Q4se9l^TR=PgowU!fgKEKDRUFF{T#xr_?T6?F)Dc(yDS+9JI{miag{Jgdlo?e$q zzuf%DQ+uL%pRK&D?EKzN*7yrq@ej&dcZRmk54ICzCQHpmN|hr!4;LEQHz@2|qDwGZ zW~KmBvXk@nKAgIDOx+DPH#=v)_{E+hiY$}$^$`BDvazJ_USwv)%gaYoBq2O`zojTH zr@K}@9Ngvx4ckZMNF+vWnC4ajm6wq-!|1{LVZIapZhbyjoXoj_tQ2Ru@vF7{wW&|sJ%3Nq zc=^>|kZM`{i{Q_<49!_gJ8zQW{4`a{e-zh8g%+w(+X|7C+ZUGL$-eJ`Y4PsJz4 z_wG{cp>~7yOfO!?l1F(+&Q?|Fuv+-G)9TrA)_TcT$(LwzezX(?Cr z`|M`%x18n4;^pN|ud`*y&4P3>;tqmoYE$&w;5>;3kdz^eKuXLkX`!*Lj? zqWF^ZS$}4(afP@tjTcj;r_!We@s6F>qc35*LWb7-$M-|<>(dJ;`Y#mw7xPWruAl3Z zw~lL{Wc`z4Kvrf>cH+#$$-I4c>ZJM4G-RYVb#s?B9};59AA+tY#_pyQ{hgWVztd9$ zT}xj|^UoEw z33Ay6io1?tifVJ#7JQ9tjgvDJA2l9JN-icMM%F>jl@$&?b_02jXXo2(bv^D=KW>P( zCiMEpm++@@ftj8!@X|oE8Lp6PJr4h>+UBxc?pY4QM7sihX? z`)1NXPuu%d?nL0gMX|Bc1^! zA~H5cmNh1}{!@OGE@G5{{le7zmI(G{R9+Uc=#XY5G+@Lz8|O<*}C^yXw8t} zVGW9~hHBfYvSmib&snJ z>0-{cb?!BfS9g!qRgSgQ&IP=zGCfx|1eFYmD}In~`Tfsu)mNTWu_s5jqIJ(Tj`g*T zRrig#&8thEGQP``*B8n?OzBKw%ge=j8Ww)WT0#(el|nGf1l;g3_W^Wu-}PlLyn zO6Oaq*h5Qm{oSpD*0G6rF&D?W*BTe+8um9k`{xgny*wlZg}ocM*Ls7;f^Crza2O|r7@3&(r<-@XIK{Uc4cEE;52tw8$aMy$B{o4lGYb{h zoP(%JV6o|AP4--Vr%u%m|0YlWyPtFQ4`RgfPjB4U%&a%T=f-C)Sk}#_JdF6z^oi}K zcVOUs`&xSMf^s72fuC>e7f$jQ-rL>R#_dXe|LE>ltB)*Xlc!9om|XCoN@Nmui-hcYp%ikd<29ZYNFm zv;C03hm+~!T+XkV@n#9qgJnS>b1tr8H(wm*sq%@@7RD4Wa z)NXS(mBX5BVA+Ij(6`xWvG5~&VRY8hg(2!670vtPN zgoFhG+-Cp-{M}9a?ep3B0P4t3bela-j z?j|8Se+MJ?iO=kh-Q5O?+3@xCawi%7QB~-mXJv)dj=D$>J%T4&&j&!3GnE)C(RV^E z1u@po+qL#{fdoxWr>jG2aU-sFT=_q)-)8U49sxUuJpKTuyz&*PH5bN*C|; z-!At>K`JCY*W`J$T?I!ZCdOd!{@+yd_QD&mvpY zi=UFi&2+APHo6yI^AcaytH-5w?B_tn>lUtFkGn3)eZ#eZ4P8*~?n7gZ-SfXXz#7_% zXy=H91*}LkQ9hOsQtHE-odHEByo7t=$56iFI5yE|v-r=4VoR-$P|r6KMNyL^Z?1-$ zS%5GAB8;vg6s|RWM)ncE-fyM-ZY&T{^lS9;qhj2{g?3LWr(S5W&++eiegJAEdgOpS zled(e({>bQZ{-!`Ins>*Z zJu}6-_hIvH;{_&SX8u>Q{c#rZ1tpt+j$A^`=x$71?o#b5LY>$(B6`YSo>#pm+r#`S z%d?q`otx*+s_ajt`$jvn&y5-JK{sj@hmL|!yy9r77I}sYDGOAA`(`X#ojOj>$B5;c z`8o2fuO0E+gg1=djOZDWAFKRLiO%RK8FIs}6>Rs%`{6BkI<8P%F9TJozC%IbS|mqbzK6EX%7)!{_=}oyZ-2->c!{DPsXD z3EPpguY~4AYG7M@lAvMeQb4}7M?GW@hAuTXgN_!T({AQzyz2SOL~4CGe?{vOZ#{}e z9%a`UUr&J911^B*F*l*-^T~T{CY>>&_UZD5J@dfq$K{$j7yoAc$JteAqBSQI&jHp$ zhMBDQUgi9zpd^})6*fKdZ3~$qHye*Obp}vmRwVDY@o;v7+%tK?&#(AtMO3s#JCZTU z6go)%1-L7g=Nxa-nh9Ao1w8p}m6v&%LA7n()UR`Dl9TB~GUrE7U@A72iPO_u-9O-U z3=3eAhEm_y+iCCsj%k%)hR1}-ap9W9oj-FR6Ixei zW2#y?7&0cE;;n9*Gi+v}3C>p9rEKi&oV@F<`>yBtvIYM|Y)dxUC1+~sdy+4$IhZoS zri0H1X)<}Y>%rG5Yq7Th<%2KKP7AI2%xsw`9}eJQ3AMV+x~|Irn(r0K{9!WYndIt# zddE^2b$#~U3Hz;A!tY#d%N05`de4?4;m`3-)|G0O89vwyKBBVhcDb*@Cmt2A%Tv@; zp^enH8Ir6~r>*3n^%kQVN#Lqg`eV**YY${EgUc?JGl;9RiI!JqV{qY0dyaqgl~-O2 zu7a+Ip_98SnDMl37U+}sA2FojK55-ng0Do*CkhDaAFW8g6b|#(sK;HuebQZ;#wMqF zziyXz)SL9s-e|3D`Ca?I@4-u2kK>=KRHr5@8i?Ueg~0R9rF75dGArHQdih@V+=#A* z0jwX|`ubVlK0!4)Q@bC%?FC<@W54@&2u}rf)7@tGhtS+wNB2fA&*qJeqxNNAw02VP zUzG=x=neUv{M=49bLYqW6RPp6Zz>2r+lQ~WtG~Q;6+NSoj5~%>Q|NKW?x4Uv>Yj$F+-uT8$Nk;4oxlOY??%*_&MbRMquaM_mE{o8Fn{{PA(j zY5UEomEvx!`f|G*0h#@K;`zuIRM-rsVsp&s=%#SOh6|QoVDF#;*K4F*_SXDf83mgI zV+H%z64ySP0KtKUf`TPhhD}r2?qnFCGfWaV^*EQf!uay)nPQ?>vWd@tgk8>9_IWyr z2fjIq`xM1q{YUBsz8>9kS04)ZuSFyte370FQe^__a~Dw7-szTf4J1>na~&yF8BJV# z@Z&cbF#9ICeiirkIx66FJ>Vv@DCnTT1|}jTHwrj5m#VFe(lD|GVTiMFXd)o`G!WjR zTbqQJJA0kN@7ZXvg|HvN@FW=6i^{0_hSpRU1<@Vt?3zs+|8vt@U&-`AnP2&@w!b)L zN`rup@r$uEbqF0NpKa>Z<20w*b71WByK#nKjw3KOV?kS`bzQwn0vd_A*3PQ_-Ji*B z+O2cWqv%>Jk!)}iUQqtNBr4T84Z;v&QH0W1eB=uHH|wA7x0!1j>Npc0u#QSN6VA{ClaM+KjB_N-p= z^Ql)N-vpjtlepU$FdA3|AV&Zm2eP$N4{E*8tUD;-7reCod6E;8-av6?b$4CWy;mX;I@fs`v#Z!EuWqTp;oI#S zBT@Yn3MRTfQTGVOt8=|b6UOayQq{9b-?rjXn+6sD)Q4%O~-i;XMMX-P7bS{-|Y|k-*V;xh2_as6X-Y6E7uil=)mIida@4 zi2E)V0nc(XJFKIQt0v_!H8w`RJ~FAMnPba*25eTH?Dol->{i&Q!X2Eb= z0V5Ee60;sej55L4Nt8KYyI-X0B@*ee1J6v!JZgcq4T^*5am|UaE~eD6Kzg6SMwHl8 zseM5z=;1ioBr4CweGyDh$Oj3EyHwCp$&}w{MzRvGswm*H^~GrLBm97=W&_%Onl0d=N=m=aS#V2~465&6JW#QtVc(h;Q$p(89%nLqH+WE{W7r#Vwsu`mU% zN4}5q0LANm-at0J9U0vj8{2*?@l4G%n=#OZNgJG{g0j6H*HPKG*)U(i83lwRKIpU19i7uX5x@Re zRDNGFaPH76aQgkfvU$*oS43C_v3Q1fCq8uR{!mvb&2 z{8RjjP$xW$_DQJRwWZIR(E>>&=p(V9lA3^5Q3#Lg6D3UoD@fwV`$Y@ESj%A&qf_Gq zPy>|6uyd;=vx-q0vl^kYTo zcAS1pG`(*f4KJjQud6?5-|3rB69_p(Nvuf(215s1kHZ`Rb)7KA^8v??txBLkkSMe< zqQ@?T-VzId`lDhGkpRuzZnFNIO*;D#;-?#(A9aaB-m9nU74ZT?uAT`owv-G`!U1E@Axa7t9I4-CeaRi~XIpn9frnR(>qg7Kn zTPr>X6v)`_yQ0h~%83$T7!U@WoDyn-ktzg4HcCD(_?MF2+@KxGLAJrI3V5pS)iD71 z)5GSYQntmxoLHo8u4z4Tk3T|nnq(e4RhuKOs_Rx{(j!}^^10HZ`}vpAcJ*ObN$^e?Oq@rL2OE7Q`1v88anW6T# zkbGVv3qRU&+KO#;LqruX6UfE#lMlq#)3nov%HZ!ZzPpQA+TmU_ce{Ki!evUM#iYW? ze`QR%RVaW<0D>fP*LC_Afy&QRw9L`sB#}tMAenI=Q%+Zc!tR60cTxg>_|$!{tqVdr zM>1kTdv>=*^XMYhNhHUPA`(s5;lnNHvd|fUZV2#DD_&gX~I`o(Jj=S8sZk^TE1q*n%SQOW@KO z+WPzbJOT-{{GO!E6&3x|beGLsl;tp#e{L*HAAFDut6JaH7#*LG*VUt3l!xgBZ;SUo_rOzX+UhA*Iu2>8#lg1 z35`hv+MF^6D~j)5uoS?}z&yD1W<;z=W`yNSH#@TV`09;`5KeNNW+Zc>KVot4H!sI8 zYQglX0vRJ0DF(8#hWua$LI0|v1NQiQG7xZz5oAVu98@YV=sQak1Mm;%#@+Gd42r3f ziBQ_V4PcaphBJ*wegg5b5K~18;b145;92oV(-QvdCQ~rb{c(3L?NP+pkv@cKQowmT zMwF^R+SbNw$KoNAFIZuPYhIc#U|f&NGFTXUGg-jP;k%Oj{!Kk~a+?Lm5rvVvti(g% zV0xrfM^Ow7x)fqXc@i&c_^IZ}9W!WZdz&U48Xcu6V9!92YDQ!FFjIL_3=_n<@EEQU z22A~mK-H$mBf+lcN~a<)%ZVO*`4#ED#N!z;SKzx~9Ro{83YVPuI2CDxWKyP>aP}H% zPxu5{LPbnTN=1EaD5s<&4U)4A()~#O$jcaRTz3RM<=$lZDYBcigH;LsK}rJgzEh}C zZ(WKC3Cfd52j#>e=9EO@84q!?-cpdTSU~8q^cJ3bAjtdxf!v~9C1f@^dLRzszwpov zC<7X3+tUBMY&b`vFoNuWzwF2~Lt_JiruryYf&04aw$fk>&027|mj^WGj4} zz}ci0$-A@Ok_}R2izI#4QG}d-oFV>@2xq=Zl%x#?|CZi)o*j1g9JNxcNCIBB|59+j z6;_;r42lQ_Mwyo$%?H}&0V{{0*D(_G8+9~A+n))`J4u=_r@G^kG-SsP=a<-|Bb~;~ zSUK~L!h+kxNq3wK?_cc@hGRNydr~^&iHt~uO@^ivi;X_ZH{x|Orz1BMRtzC_`Yt}1 zfHx{jhIc3e(l3Jp20J4+p&j7yQkc^YNF588G$Ros_U^W{rMVC+cs=M-nKh@*qk_-? znbN`zG(Aua(I5fVp$tTl921tf~-Er(*&X(@igd7nC!&qIf+#R#z8l!6pFX!cqkz2=+; ziUCzR8ES z-^ltPpaKy{j0yj0rQ2{YE~3EFkXlG-WL`!Ux_%3$@L#oE;n=DzNhpl8K=OeTU{Z16 z8ht{M#d-K&>t4p&btLYYKVTT+85REH;|GLrSgO!B;*8|rz5jyh|0v2;!8@>He+dtvKfv4{4o zMA^J}^kKY{gycXhXwofefkwt)YW@mI_T?a;=|zDUGX=y$B@P4OiBc=N6qE=ss)zGZ zC@=}!g`MtlBj;Dm zlI7872L8ABk_jTJ(zo-M2ws9znp{t6iy0cWT8-IJH$`C}^W8a!It=ZgWvS06c^bH*@<&}{B z&VBY4qU-Uw-*>ODAu;lWli@OJVjVZLd~j$$*Z<$>bCo9^OcU6X7*B9NlC4 zcq?viP@Nmgp2+oGrlN#bo<|6?UjKtXFn)hWCY0GtqMovQb(O^zD~d+?5)#7PDEmpK z0zkUL5%AR?Fvs;hj(5C6yNt#Zh2;u&4E=1a36Eoa9INs#82+@es49a=azN* zM*mu^V_mnU+JVfKy$5Uw+n=e2TYgAQEAu{^tJj>rDv)%!X?%)%n=+cdCJ3m=2nGQ3D-xpQdBvL$hjdF|mKL&H`FDPidW{hB z^5xWJ@LsoE{Lw1raClVpcUnPTcS9oL)nvf+z2bV6CL3s_-VwS9QS$toL?zAG={|5H z2DCylQpRmdv~X7Qzxp2b3i`zCwuob%Pt#jVa+2i9PpZ*gI} zP;c6P!83=2P`QH-(&9Q*SItm5Ht-oAPbL7EoCk#kC6-!ideOSf1~KqW7QVI%o5D(h+%nq>TjXzW2MxB!GD%*RuwaJly{i;C--H|w)!%4s z_pH%WF5c*czsY!21%~A%A}F_0qX(qANX}SIOvRkK{}ghc{}^K`%#R1f#&epDlrX-dl0JJPfuV!T`|*qNoF_L2 z8`gb~pv87cV&;WNBSK z-w8agADgqR4fC;byDEKj>+YO)fC2jd)zEjLdEGKw!7Nra9Al zc`tFGt&7^)K7L4qpwf+lOKIW0VbPZ~0Pz>1URUK-8yz7yb(k%rysG8OfR?BGp|=ms z(_Y&dmh9l{CS2TX1!VO`Sfw1dGCAv;dY59k6BWhVBYsO_9<{`<72+t)CA8a#NTim57LUn>?pYIaZAM3W2A~(9U<` zNR9}%Y@MqGu{&%!y-Iuq`?%(P3o1hWgG{~_aU3jz`;2>hQOX9i!~Jt#0$4}TX7z+# zXO1}~N%v@B4ap$Bu+ZdXjDSvp+o?n97X`M1N(mlRt=~KXt=#R0c5UY1&u#QlrL3xV z0H0y3e}nH}f|M5j4$w8Rw}H+ZkW^9dqjIm+uI>H}+Ip)d%B)#RBkv_f7R8Yynj=P= zk+u1si!3g9fTCx!x$0#XG7@O6cwxv~s;<+aGN%05OEBr4z4oUSxBB?}R!8KMeQu&Y zgnjUP01_@)H50Z)kW4MMka=kGHVdi|63!RXb<3P=lUcvv&aS;dlPX$ryjfjC#HA|R zw1KRgsA7`OraJmPG701dw7ANKRvSCU02NipAY@J%6U<8{ibFJh`Cg%iJnix)*SDi% zG+|+Y#C@+QN0l$na0X(oKK2=(`D3Fide?_J^zvN7qPzxGWGvrOJP!EZ^@?^jgcVN| z5T-C58g%M_JQ{CS&n)83-xrPGV%9a4EwTJ7N*jtYNU$qpjOelsg3Na{Rb97$&JBCy6sx{x1j8vj!~;pf!<6!709Um0aUT_k1xgaz&A4 z+#-?=-NKE75?61&vf9rxvvAMb4z>pTf71}Zk17!?D?2anOAj}X+u-5PW1-rgI|`~y zbL z+2WG5jI_Tp-ux)6x|UU^wyH7|VSabiHoXwj^q#8XR=ULL4-oxO=aEn{6&b{yQHjXMBqX1;5J zN(5Cq54&a8;bGt^f>BC&G%-)`Ll_pxeGTgSQI1{l7^ue zCs66aRmYyJ&O*-acV)8Pq_xo?)r(xYl)!?(v)!qMQeiG@YBs=Fzd`Nd6i1tcaS~U* z0O=o8>00Amr$h#Hw&V2u`|y-NvyDQ;oDfR>OhY?DCmFL7SqS+ZCUKL%C>mqxwz(Ii zxr0(L_sg|y4z4L3J4{CW#3gq*c7N_AA@(a9zsYwtgeDq{3$D0#m#Ua+&^p1hP$e-w zIUZ+62kRKoRZ$D5T<8^(Y;j`e%+L16>tbEb2IhH$Va#~bx$E&1WRLW`F43m;e^pra z5L!+Dx!XG;JbSfKZy8@24Hrd7nYt%!U^;N-S=sv62XwEt;9dhTfQV}Sdv+4&Ch>@Z zkq{vAoS$#0q7C#!m<7a2P9KyLF)prB zB%NJcy<$>5z4KcN&H%j%>OlPUX`xa>gKuqqIM6m8Rd`yrbLRjlv4_Rh;d4g zC(MRwx_|~Nj=XWxoSfMqA+sLl{I48#r~!T?-8u7z?9b`2r+Zs&PHL2#3DP`eG>bb@ zg|Lk7Y(RkNjBL6$E?o>bE)0cnb0#Q$;PNECJ@`2Tr!?{G67BSHl6n$sz%gTOs!LD~ zDNCqhM>!L&CBy=n$4gV&RoFDHF5y&1TjumGSm+uYKcS^*2>_{16t*)40tfhZeGT54 zofw#96NWx^sIR&tUz(yln;wtMfaKwHgwAgpOd%gY3YVUatp<2P+=s9>4O3!N!7-nN z6-b5A?jxpf{a5?<5)!cpG^c>k)o~MMCMsSZ(~oj^6LHP*6cKhskxLOiU>+a{{niJ| zFw=yZ!9SSM0Q)2w>C~xz64+v=VTb(M*qz9pzC{jNz|AZR!diWnV+FSj0l8}+5=Is+ zO_(1~8^aD7Sl-th34rWuEovta3ce7)u<4&|%H!uhvYPgq&nFi%U~5YRue7{d)Z~$d zc0~vBqP!Egy~US)yw`DlUR)j?zvTjGk_)k9xv4koxo0T|Sw|s~zR*$Qf-{GOM zjhzPT61S3lPjuCGb?dd$1OT&k9i1ZN+sc(P3d?r5+XU%h>OswN)yJ^#MF5ve_Q5?% zGn3QDO+=Y@iiw>i&)E5FoFyqXSeijt$2z&z37TxPr7l4Kyl=BZ5EG4N**-_l3=}*Qbz4iuSn&B`%cld7brZc=ywS=mD5pSCl@C6#@-K2FdaaSNZO;>UXQsXP zz++WwY2ar=0qBVVFJ;D=vgKi# zI+L8_Do>{e<2iUxqzXVI>hSDJ9nob4uT@QPjCbE(*R8`KhzLsD)G&g+vXly zdu-dbZQHhO+qP}n_DuGlbCFymYn|i?`nG#@RefqL=(uYn0f$D2_Kvy%6?@BqaVj&E ztk+j3^io-(uVmG(LbO?ke(JM8@JZJa)tkvHE!56lJzsm=Ww07tMV0ne=;=GTtOJmE z6Hs~fVE}EW3C0wG1!-RGl*kK-SDXv1Nf~g~_yZQFxe z50>w=O~XmARS#2BMomE&i#>0lB$B2> zU5S;DT$z-!{sO6xo7PSPwxauweiZuR6@nk?tAYVZ?uO8gtDq3{GfIpqRe90b7~z;T z43I{^AdjZE#(Gof9bP>$bOaEvptVJUR2lC@6|13|XKNna*DlY-Z|C{rn_@F;CIoGW zGZ|7?7==tRIUgCUops_ge9n=9Qstexn%ENX#X%mNzq$1Xvv=L^5E^CI{Y5`cuVZJm zO8Ns5R9Kh~>A7qC?E$%WMah2kMY##KHV<^_J1S%ec#CeoSw!_2p>+gigbPX=V*|&Y z>%-1Ut_EcZ>8oC4M8BJ?*a5)ygXofVG{Ms*M!p&tP$3qZB|dUHc!-wh&m@D;NU=fR!*9eI#Z9bwRs6%6M!mBZ9zHZJIA4$0fl`UjFMy{7a zRHWQ1Qqypvh!lG=S$J3P7KIL52G#bN7Hyvl*f?F^|07+K^l6eX*rAd4_h&LVKFirq z{2{*x!Tn`dPKz5-VMbsXqYWTI0w)?jZdQ{5xB@f?!Go0ne+=R{E$@zq+D@Bem!x1iWH8RFnU~@R`B>5 z(>?iwFAx4fD1^SNzKR=TQMdu|&|I$W*Y)xlFCV)a^c@PZ-Xqp0otQv~_TT{>`5}@J zG5KMPgAW<6hi|u)74M0>;`}Gb(xgyQ_01@4&^r~1UNhP#vM*;bkjmMV))6Rps!wlC zx)&o|bKSd``)0EnRo6`H?R6^dqPk5<@Z$8o2il`{x~4S7y7@_7@RYqVV+So}*}<II)L*l9;C5 z_6_D7Oi4xAAmzf$jOp8#<++%~!)QQtA;>NVq!})8b{__9H*W6-%KWa;ffpFFdy4hR zTkk({hOD|2zS?W2y3JO!%lqCdi_0?Mg43;vqLX|)oCW{*$j`Yl`NqDv*C62~`ok4P zkB`k<*P3H~QJPkiRFUZP7F2fdHz`-U=ak0c`cZrd6bE+=YoFZtAmX%0lTQzoLRD#= ziPqgR#|)c@Cr_icuXD&rp2OmvQft8sq|yf|KBZCko{y=h>Poh>!`Qk=!1dVN|({r)It zw1N|ya#F&N-*=v??t>`H0LzWLzCYb&-QSnUe5VvX%rb$oi7eSgv+pjghuTylU5cLD zw;CeY_4;OfP+B9K^UBrpP>jFnIVuRdeTdMU*59zL{oyaFf)G!jS03M}no+Q^IlN+< zE=%W7A!V)X)mdrwLX}^)Tt}#)cAM$T^qLvI}xisj8 zP7>&=hbb#{h7c}HOC!WG9jvlwbhi*uBf8-Y?iJ_?j-1Nm%fl_hUo|E>4i!d9VV#~9 zQZ}DkC+U&f(4n#BF4i);7FRkQKiTz(ZPXnbt)7=SO}@sf^QSRn5yh5TDaUpEqQ~On z5{;1}Oh>1m}W5Y%O7+4m7d-dpLLp?CnsV$XW3Gr7@ z!x*KtVNh1kd34-FlZ=y1@=!#~)t)4huGI|rr6?@sK1mN0ciaf6$(WYQQk7$&a?Eb@ zrto_Ji_cL8K~*VbQ$TijyUIu83JcD{jlZt9_Amwiy_gbFoI_jWw~{Z$+L1-0>uR2) z{w{LXOjmA>zAR(V;!%5Cd|bNV4Ho3IU`?xUjV&yP->jaOzIUweaQM(nudSJhM?#}e zj5XoQks*3zuWWI++mECjv==3JQ0l}TJ4l4khTWLEFtN3*7g@r2* zOq&Yy`lze638mHauH#gx#P1$8DDoZ7O#l>pthX>LepbrtAl?G~x+@!As3A6cu)RE= zC$N<4~P+n>MY0_#}ODHb!c~@i~o;2IYz14LWHp~xib+-SUM{QAcHl_3_6ub^VPF@Xg zwyn|9#WC|Cz3*7=r&v)5UNwzmL)lmrdEfSmWs|4`kN)@l_HFiJ+}tV5TXW)19=WU4 z+U@B$E$|4dI=#CC)1}!}zT%}Qe7~uA?;9}_f63xiROVbD*PP(L_RY$URsD%!OD3z+ z(Y@Ps_*2dmvnv3~d_}RgMEE9Mp z2TD6jmVPRn$ zH}Cked6FY-ZO;d-F;MEzgP5UxSk##x(t5aJPak+D-Q3423P*}d*|!$&EC0tqMDX~_ z0DZu-t#xK1%GplT>cX$rE*tgjwd!2(Q9PfFOii+oY9pLyEXtMY4wG>V zdXg>Zu8EU#3F>i@;iocdoKsE_}x19ea^q*Pb% zv}BZ4pjWK%KLfsH4ZXR||549$1y@p(Z2ir8N(6yWmP-;# zBqIq!Kp+hwA&KLspwf{SM246Ml0f}<9%p|v{?oi+~)%*Kk(4Z6kvE~EYPI0sEj2qg$?a?5G$Y%rQR9}fV^`CB`|_jmzT6pGOND2=l8cJQTt#o@#qeXV zYiau1N9!SB?=kpJt{ndTy&9U+Q*n!dfF>rO6IXTmGS{f;b3oM>BWueI&48>M<-71A z`~CEMnVZ`SR-}5(=Y1?Ju(!6eS2r-yA^Dw@_O&T1nhXjkI9Pbp+)3Ue6)!kEFfgc! z0*_0bFtqTP9(^icGq)evjo<72@2~n?&F9Y%YHw+ewV-^V?nl4hQ{ckOj(5XMsV!d! zx;P-0MqMdm7@_1xKs46gU@GmtRdPh^jN1G6nLU8I8g z(?0^4EM1T*XA0N=H2*D9`31U?MzBQI8}aW3^h1SCt4E;5t2;1V>NPPjAy9!yXg4=8 z!E&?B&`!%Coq{rPF87_8a_L>l?eY3I$N{cqf03>$rUDbd!N^gHhJKgswB!q_>^pkG+apncz{oTQp?ytMuE&^93{>*2(;& zwe{2~L#!v90Mvp$enbwA(uuwC zvefEXf|7OBt-0l3%*?09OfOcgBIpT>UI9ty1(=SRg2npj^14O`6&V&OK^iJ5JW58# z_U&|DLCL}D(0+bV>;IKU3rTUXgbMc>4xl{!+ohDzD zFWG~u2=5~rSIFr7qlpIth3r&z`SL2#94o9rXUggE^~J^DgM+g#_|(j_1gsPc#AkT6 zm)>@re-7p}H3t<93nd3TCFM`99O4j3F@^Nvnf{x3lPb{rMZXUwGC?*EidW!C$$vyI43)ZNlN~&A?Whoi^UN-PKLU>P|_Y=ZWJPNpyOrrd+;NmzP(jhQ;bq z4VfJF9j~ji_g()G(u;7RyNDnBIbY4*x4kuc$=Q(EeLVLQjxJn_|mhTOG5D`LCHI zaY@BMvA#ie8nEcUL<9h6Km7i^ncKVDyZAnSih;&$IfS#yYG^X6WKR4lzSi{PQ>7t} zCHbv$cH<@c{!G!|pW`|xF@#W|X%fwYfYlr7=0d zxwnfe+)Hgn+wM9vvYLv&8@gYuwhh2ovLRX9Pgf)DiSV+}wWw3CJi8BXR#V-`mo_SScxu5US zSGuC*{yc~jvH6QuPM8 zIjhEK86~!jKW}drGnE!BeQK1$6oDA#3$^v#*xWLvyMf#?t~Y+%JU|UCTa{}tSBWmQ?HGHWJH9_tQu#eKt z*U?usUN7$c0m!Y@x5$-zI`z(FS#7wk9bcD^iQ>h4KVG+T50)b;P528DsmTqJ54$di zyNhemWB{0|czGSMtf|Kbg)uHHQAI6%|<-jMwD>2V#qhb5rtk>C(wi~abv%SY} zajA@A-ttKfi`R{wKas+(s^2tQ4|}yk z{f?=trSvi$C_dw^mxr#Qk+ZsMP9F9Li$mH$)`PM(kDIgB-u3MJ?=!|1lB0K;_~ySa z2J7hGLwYt49_P|R+KN_ zi&h{=#Q>A>#3vO)>^Ue5ru;GXipFsfMHY{UTe^ZjV4z5pBgT1>j-@n?3otx}+y%$9 zh~}~xv`WyN++Eb2jTFMqLudA}R>4%ME|wtgEx0e(T-Buf!8(wb}Ehs)XBOtBs`y0esXl#sKU!SdP}`!{G+e zOMljk38$tF&!l>$LbN+EmNs*rpvH-dUfX;|wmJ_$Uda|YMr%9PO;2W>mi8MTK46dr zn+H9ycvj$H-qyMB*rm7`XZeEkKQ{US2FF9fNQ{#5F@CSB5O^o=A>EMFXs)>pbWMkk zQ=Bb#i`}u!q3XW&Y_>V3DI{BKV0Oppf+{26ZWH(0B7oCVZMs*aIe63D1dF^wT%v^% z(f%pg)gIgHfz2>ur_xLo)F>3tpG*L3l=NOcml*d0_RrZo7y{#foqxWvroS_5!UWP4 zVv5D}r}yl#E8tp;V_^p2rz^g-77g{)V_(hBE7(-|S;3Ismj?IOFE^#;s7G&cgv2hc zBCAcOfY&w(0YzV0pE+A49^D7PvKfVA+q-F2|6Odf2L0(Cm5Xp_0spc!29nyXN$r)g zPR%!rExgrL$3A*$nn*h$*W&x$PU)I5(Q05-N0ZxJJQ?R#_(cbM3iQ~Tp~b6(H`HbM z-5D2xMUrl-jWPiWb>P_7a7b?YFkLm`=6g!Ss2S+ode87W`r_7@M^A2m2i68UVR4OA zp>Xl!VH|JS1z$HT3W5r0b=%}kAwP5CXOyNy5tUKdMtwqh1sI{&>W4Mi74DpSo z+BO~7vroF>SwTuzK5I|it8r1zOOJ7xGkF*`6G)rafusheE1U@6V=>c^Qdo}uVE`x2 zYE(;rtzv|73Wm@=yw6(yAmghs1|cxxF!3=}DV{v~xM~pEH6~BKypg$TBxs0BY1qnA z-fyk}UXR2HeE_V+=AgO||6y_3rB|!jrr@)XN%4QeQxw7(pZuGcGmh%Rqa!o>Vcano zc<@yg!_HK{reEF47Dw#zQecHUNRX9X-XoCXM!1OZ#tzx6i7#(WQ!X~rLx>Cn8qW9E zAH?{XpTe{N#%oWxKiz`pL%wCrcfTTdGX=)#+vJ90J{O)s1~%871HoH}``2{ECmIfA z-n5UaJG3U#kEzkC85%CrqukQQv_(uY88n#U@^sl|q`XDf*|V|4;4`AuG2ZfSBL&wJ zKHUGf5VtPf4y9cs;4htA!wMDKCDuaUhU_(akr?}FSjFxIOG}z%8S~dXMU#3yz?xpj z%_$m6N(1JXi4SG1hr7k-gz0LaWG0IbOEJ_pWX|Gi0~6)|d) z{?}=orf6nM3eE@g%8UTOQx>Yex=(!JSh!45ayQj0zd)-CaJcdEfyZ+Gjp_MZZF%r8 zZrx4ALLqlEka0Tr9k)q(3~TkLD&sjcxvCJ2w@|N53)sVX+-*7X0u7Av>Q`w4fwf01 z%O>*M;{Z$3IS+Lz?N(@*m)teNMAqPfludJYB?XF0_73egO}97f8MX=HpNa3Btbb2v z{#tFnWH4Q@>LBufr-9k9``i|Ssw8^f(V z*_~REv1*<$REeUOS(@nwe;8VNsylNy@aWXbKdhz@`)s+HRjC7U02pNu+Emi4{3@qI zJx)8grahX8hsu9_mm!asXD)s?+mH5R7+(7@`W?P!j6E9L&JQ6!KnT_5t z(WaxH*hOAnR0SN*@{^s`__J_dBrN@bR>%S;33GDK>%u{N6NWgd{O%o2tnRQDFsYBQ z|2Vus060GPr=q~)V-5&`;0ZHkp$MQ-4J2woP?}6V(m}aU0o9d0vfFTQv`GQgD*JT; z-NhUuvV=3Lph{;4XDXFxpf;(F*(DFuA8OP%l%)&_L#nV++Or*Jpt@U0B9Pu0ZjHKWw^BIW5?+m!Gx6Riad;2;%99GCq~7io{kBM*%p3bMeIAqYr+u#Oxf z1)&5+D>Qg$cz(WHrNpzA2qNs0PsR#Wo-?K_A!;xvtz3=44YiYsO{Gd&$s|w<0ZwX; zmq1`GaSk2IPD5%Fdv%9~KsAWgM9EvPR43xc&KlGP2$JvKlOk^kp z8KX@cr^9Lzv6H_-BO9P@szY7(LQ);P+ zAn{mI5~bxjx)Na%CERTN3)YPwG6cX#fGS7>weNY3l?DRKnrb0;FcI9Riv}Qs36Hwh z0HslGu|T8iDGut5BN)n&jEkOZA$f-YZCe!d9YeZ$YU}8IAfD+7gEe^EElrnPbJHQW zQ594Hq6X|h@EM&_D+?hXH5%#W2uM&)UHACN@&}497$r#3RM`wRoY_c$eQWfSg8!E2 z6I;HoMXakSOYVu~EfTLH&6}pDs^%H@8v~RZ#XM`j01g^zJ#uZA$d98BJ2GF6(t;i= zdk3+KQbbv7uanVg(yCn~G};IR7&?iSKt+td2tm0zfMQ_DTLBhGt&n!0?rPCoYR~YJI-sj zF=8F*P_7}nA}DXXDv!w#Hpt1{8xXPyJx9_8p_qyU{UZQ{aHJE;za8fkT_736qL>lP zQ9>A$*otC6z&n4C_W-sXhgYN;kn!dOC5G$3Ff}CZoHABxXeG*0^ec}tdK{L5iM28r zRVq=&MOv#!2y-R$%28s_{24jP_(SZG2SqtKR@{!b5$FO;iwepbB)MoM{KzwfgA;J#1D8n()3c%2Pvlsl{Z`j zgb|i93;Y*kl>!mOJ6v;ZXksckCtkeZA7F3H7c)yY(x=(;Kdhdt7L&Cr$XNcs10aE0GiCG z)DELuGFO%t$PS4FeYf$Kwg6+(0T~}EO_!(-3F@Mq(P)RGjz24V9_(77?>2*AH?C5x z)MYgDjAQpjg*u;BT8o5VPdJEboWYP&LK2b#0x?73=qCmM2{((#5-g0l?upEi0fj%{ zX#qf(tW=NCcDyQ8PHmiF;dKvygDMo4#~FS+LU4H^K|7)*KeI;q17JFou=z=8vC!XM z5|Bi28qvvxB2cF693tgy!I3hSLeN>vg~K;6YQ~?DvZxp;yalkF@sBg3egU{Gi6|lH zRgM;~BoU?Z0IQ>mHGRP?Gp}_K(lvJ|15x$Exj&m)LpzXA7tn31@L>gnK}27!LlQ@~ zT51f{*>y?-;_YTXDaV9L%<5P_uNQ0~NLX$#o&>f`Ac*~6nAc)Z_5_=sna&TJwLs^s zS_PQ%$uypwfZ}D%jASw0{*_P#uV9G3;t(1Y-5;Pql_+VHA)Bao5|~i*3v(T}t$PfJ z5I(IGl`j|idYT6_l!4K-EWAkcv|6IA_5GN@MNH+Px~xX?A#BhuHz)zjm{9*J!9LCC z8JHo94tgzOhOO*sjbFDOel&h_6@!A6FS0#DZAqS2#D(-t-YoTv#bx#d3I5@$!{Z@_0uYd_oyjA>^v_TZ@@@Xi2kXTT>@ z2a>t=B+MZ~0zc{%9+UQtQj&uvCECumiXq8;t98tVy}uyk{le4q%rc$QVkFp;;=3BI zy^qE<_75X;@sz&|;fw8{aeHoqM4|GUu`nRG>rxAOmXIiNa~llF)2RKgD8<+yb13{b zP~FPI;#z`V*D=v5K9orOWqtD|Hz8`K@tqJ_2NV7OFEP^2EYr7O2!iTr+h-1 zIT-jn8W@$Uo%s4`*=u<2{yA?M&3+$c)H?L)A$-biyY&EON&|t`PvxZDt3$v_016;7 z+WCGA1)se{BGitkQliXA0Y&pl+dvs6(^-B(7-w=aedbI0{ zwle`J9t1*?^N<7=pUNyX0E0bd9MCq&nmU=FxMIi6<2M(nODSw%fj|c4H_qO^$!f8-boznKIbwdsN~3hFIWE^byN9y-N^`E-cP=aRmo)FIFXRO>Q7x z$XAdvLJ;Vbx%ur=-%}ze1tzg;Yd1G)qyplYEicVtpG`ZU6yTUjB2CcW2P)oUp;UQb zYHbnkR`XIq=*+<*p#^8H5<%-6&Kxck9R|UkA{|197UW-dv0^w)9`z}zD*ZLU54 zVozqrAL6JGfoDiagaC4pP#8jPpvKQBU-Zvj_yH0m8SCSDtqoD^M^eoi-D11^>KIj4 zj5JHEH|2>+=dg_eT-RQTdaYHom)C2uEh)=|%VLISt+yIzxNi(k&+CZpMcCAh6>%*} z;|u0#Y7;DT1}MC`2ussEy!Nr6`@jwBiLGdJSiZS?!$6<+Oa!)!dvFR1zpb|}8|R{6T^zHd@1*_^d3mSvX^=*1qT~^Ys2=l!tWN z_#s*ewIfpdQAk}K#Lswfxsai35ooqKf+bTahRHN6q~ zcT+`&fZp0yS~a@{xd#;lv+#>n_I#Tqco&7@gaxwW+I zxX9gB^h0#F;_(TD26S|#qD9;<(%YO}pVaD4hkJ?oEuQ8fj?g3JQH=mtjaT?%#7pEEM%% z=8+TZ(O4TNWtB%xkr6BdGl1v^9a&1E60VRgAp=*cVRfNEDrk_{1axP9zfwp^FJMYD z36C>C=N7y_aR#SI*>rhuV4ol)He0@ndcH&Jp4fcsRXMyn$BqLLJI5uXuAMsj2J58g zK!@AFTw@Tdt|$IJ~km%;E%`f(mK zP9P0Z3vzUoy2sdPWAYgRuRI>D5_GBMYC3{j9U5m^JJB8f%v&PmUkJ#FG~)@aTic${56DCL<>~7v z?7^tf`9Pu(yd>qr)}jXF(|%>+>7O zl6arSyI-zs^`N^c8F ztQJ@FE@Yt)HSx?`q_i;IqHYJ(AK*+A zabB#BzX>03<&f8`xfQFs5rrf%M!dO3Z*Znq#Gg4mhrn(C4fUAwC)RM{lC|u;@Ww+y z^62f;JauX?m3MX%@Q$`CoN=JbX+W?OOdh*VT4}lnZn=Fbaj;2M+}!GfNAa;-^9cx; zD?=$fktTee4f&(YX|SfSG@UEIIQIrVz`Yg~MpqjI^))aIm?dTQGK+z15jekzrSEN! zrua3$We_fy)iV!xq8J6Uujrzv`sop~?l*?9xMZ6QQCl8aY@Klj*>xKPuLy4D&9z24C1w`5Qr<%17ZTzYOPS!3Vf{*u5}2clF2J z{d}ZI0XcJd2Y4rPnBuRQg{x*dLg&=E6y0kqj2s0rSH942{r)~yO_DcoK3j!V$(RXo z7qlqccF$M@GZarJ^8F~;ELydaP`@qu{5*QCI|w&MI+H1mHR)!vgNZS^{^Z8&>&lh1 zjc;IKAD?Z;45;UPzQcCz?Kj#rSBhb@8K?}&W;odj(=F?hfr^XB> z?pn%XaSYWC3lC7b>l4S9zf0u<0Wq;3&Qo9Drv}iJ)UB3}RqOuhDfg>uyUXPPv(J&P zvuG~sR#XL)*`%ao#8UDs$oa?3Cu;YMk%^OZ?8fNgahUrI-f<`BMx@LoEiJSG+>2b%Mqg zjP$^cDK5sh5kxB?<|%Pwa7-qlm-VV^&o${ISkQ8;(p3apE>ehGEg{|I!QGravnOac zxg#;3eKHv)kzqUggI6GrI`BNVN;$77a<|p}Q%7Ph6KcCFShv}yt}ULvHi+cCAxzZLC-zIAt?a{tzyju3xP~~8 z@t%Fz?Yn837c;cYhbh4h@GEu`hwaJ2t4s@R%CuX8EIZ5DTO0eA;;n6=rR@q_F1Yih z{Wl{PhPfg+v+r7TY)PZxiI7wMg#U*5BGL zT{&YvmfSkII=;5twtIrQzKFLX>buWqJG6&AnXPWEw}-n*RFwQeDTwtR;RnCH+7=PKgAzQVg8sYq3}!#prp3_L_$7ed(+-Sbl2YjH|Go=c^Aq zA?l8EVJtvP2XIFQe%jnDL;aw1S@>oh>~mJ`c^rIm2HJVXfAL95>|j_#?$dXDj&?|G(8V4y>RCx4*-gIfxQ*0LH>001yj{Woz-oBt==QgK0E zZh~ee6w2+x|84xe*qu~3SEGn0+J7cDrT=Skb9ZGdobexMDg57sE$#pRU`u;? zbNl}*Y&pI1B3{d{q_ii@o(H5Lm`Ef>vCu!4N$T{Evn&q7h;SlAD}n-#i@~tUQwx9! zvXE1{0z?GWOwxJW|K5G|as9s3bY0;zeVKC4={BwHe$B#!rgqfku_cQF$OQMJ9X`?5 zKJBfMx{ixSZ8zG{_k>V1Hn+59s*?`fNMP({oj$+NL4xog%METaxlKwTJh_dhw)1WO z2#2ol()^rGR?yMX?6Rx2v2k(GAy|{+2Q4mARaM0nxv5Zn=lS4XW4<@DcF9mOe!UZ* zKYe}V=+Ds82UNYL&^dmxwl=qR);D$^uHFw?d`8Zc%&KvAsjh)|LHP*F*=%iVY9dSP zWZz(=+arfp-Th%++x)!ikKNaWrTOVdSZA3e>d-T{7Oo@E(KFRXPhQdOB~shWFh_j- zQaG=?e2=|^6qfN#=A3~KF$!Jbi z-j{U2dGXT|kb5m;^3E%e6;VD2NVJOIPS7)UzkW?2`c&?>eG@J$9#-xjn7BH)*t!jl zE@sX4jYsZ~0GWKmmZTw}&wvn2nG7i2x!!=btQbwEP0C|C^YcT+Gq$V53{8XYOu&kIp%2CsNna(G^15 zx;)N%cQOi+za-VxM^xH~lx)k;-C_V0d=O#P?`Wg>&v;E;wHrQ(EqZSKCT(wM>f&bl zbt`o^rJJTbcIib+&2f6BovI#7>M(U)>dN+7ZWQFZ2W;wUcqy%h00}Vm*DD8K@}jDK z22nacetMcTb70RpRlw4s+$J=~osk)CX7Sf^wpOm&p@&ofy+qU`r>zxrwYGOwzBAIZ zGdA|ozDQhj-{H8+>-~i^)8b@Zp)UP-pr2+1Zp`iVg?oOfC@bp%idF%o&f>kPte*Jh z>7LEu>7*{5U!BCK z0|yJmp7^oyC4dI|#GL3b-;BRLEBO)VPq~l(k-2}F>A5HD@$hL7@e~MD`(FFwk>=aP zhJlTSg;$JkNpX3NzJFNTOZS_uk_h?>8V<*h#Pah9_+_}rOtgT%q(WNxmf%_$*x9VK z+&t!?;W3d1rk-6@Qj;&TA}>#VX?b~;xZK~HdDYP@)(hY*%JkT4T^$Q!9Qf~+`C(zf zz`#(!kWqmc?{9J8;o)In1j_;oo9rAUp`_*3S{WQ%{v8+-;qnCkc6&x~%HGkN zQRu5QR4=Cc>-_fe`{#ML{1@Qc>o>X(KN99UHP@%d;wS${|7U+*{!;DPSASKtrk9V= zjmK{%-vqH|^vA22ktvDCg=S4wcT*~`s@wrx zr*Mxi{F14{=TZw9mlm0piHGTVJ455*;*3_`AE1m?)c==nl@i6a?@uD#Z8R?_f_&OGr7Vy5sl!@^)!{jg3J;X^6H+o?Ty{d?e%?ZTueS*>3?SNcYD3FGrk_&4;J4> z>*aSsUvGDM+`lc3jw4S8XLI1}b7A0~UY}p=IyyP=`8?M8ee8aBwqLkfH#KaxyX?O; zpf4>RlmB6rAOA)#E++p$m2&Ru#R~}s1p@(*@8~nRT$59iJc<9|D&uopI9W(J!tJT< zjRwNs+SbwzNB>Q@#x4*K4E`020RaaEd;5Ig=DVH-?Wf{j{xDnh?t$uBeuXttQ@XYW zoX3)V&Gm93Q{`iHN0POA`gWsxb_|8lZA$a)R62L;obTbWkbDiP;cQjfRT@aSb3YV+)_+OkbKr~3T3&Y-zNz)+t51ed zY;)K8^Euv8=lpJOdvh_jogs*7Fkzr_oLIZk&V5p{Mr^0h%Kz!-(-pn6m)ny2>*%NZ z*!^d;=iwXvrk0Lg>J(GEh5Y=(r|2E758ELN%qZhh0fWH)6d5OjBsY#80 z+brYgp1$y0FJnM$Bqnlge$GUE6mT2N_#pD3y+4RGzAqy$S{J7CrM8IjHIqW0_Zo(&neS*E_PQPo(>Ir~E1>Kn~n{oVh;={KBi z^Bi#dX0J>j@I5HJzdOw}Lo2b`B}4G!;g&f^<8sZMyRo&cfcNUGq~K?ZWiIAxfXRga z`Z(W-s~IsjAhDtr$fCEhi+91I^ntAFGvZ-$EOj?|2lv-Y{M`GiYp?SlR4A%~h~C}u zW;P~Nom@l2(I+Roo3Z%ANB=8H9`o`ixKGfYrbJ=!z?8Ih>kq5ao#CpP^mWFTlAvYd z*6PWPpu=uWcN5=z_p#tjc&)2X%J6TOEc3%2bD|Ghrz5j!=b7~STXs~SAwr$(CZQHhO^OkMfyk*^YT9|${jEH12E)%3{0ETvZ^GB@nx!X0?{K}pmMXld~6Ximzg z*3|a4IpEv2_VdV2RSVHP)LQ9L}pAm zb8_)M-*!(4jaq_2RqKrJZ=qdVm!s@>durYNPMe%6Q(rxf&h^3)fNdeBM=yp5%7rfS zyE+OW**C?FIunewixlS;@$>n0ho&p$D_gKQ4mfpr7WA9CTFeP}{FD*Vp_MNWa^u1c zkm5Hw`zQYBXb)c_tvXx5@@ex%`t$0x&gplp3hNLY-J*BcMhvdtoe%@5dteL_q@3Kt z*=7F7fS$|bWOP3({5h2=SrhoTs&T7e_a%e+Z|c+1@cSuy9Q2-ApDQL5Yr$erb)gD| zS<)23)xxAy%sqdHuk=-OeL;p+v^+4=j#r10QQO>dbx&iCTc?O$yY80D)?rpm2J`ctB*jDe^YettE2Lg6&$QX3p7n%Xw0N@{ zH+jt~$Cr$Sr0(`Q2ar)|g6>-Mu@rPrMw%)O02+cxgH)pIus$N4jP zd-*L*kG^b_Zs#zsBh#Vs_L;!zvgbUoDzdJ(S6?^N|L(pE3)oot^JU=L>~=hYqzXC1 zkC_sQk|m4^U4oH-Z19%WMWWlF*cNVD9AsYeh60K#!lpA7B6E*S@3ft*1mfI)`!Bk} z3?W@8L2lsA<&+e0T#Qki89_4&K!HH0i6d~sjO-EGhOLjbd<(8t#T18XS`BF~DBsc> z&_J=PEE@=;n=J+RM+InJ$kKoubvwRnAM7hygn1Hv!&JTqvR@Si*Uo5Vcb(+Y~J#vQutR zcko_$>`2Cg%k7B-(y#(hKQ(p0Krl}VPH?JQePr8xv1!tmj$<5f12AYXo}ChJrrn9n z1It&;3ao4vb5vGL+lxb-b-)ONIH++mQzVgQSscXj`p&I4XkgtDV7mpzN~waRbPQ$T zKms6~mRqC@>0gpDgojS=sA4{1VjQe(ij6-cQtJtgz9B+u8S|cpsRyqzb%(`8P+YBE zM<|}s5UTQwWDS-TdQqFZERb(LuU1rM)NM({Q%wNmcYuKkq(ELNpNK z$^u7$&@6;SouecIWSV)pEfugvCgn%HV@_P2v2VjhpM&;guJiy9sh#2+0$|tuKM|P) z4ct;;4>*mw4p1`4kyvavqvkB|iV6|fTO^1^B@*R9k$`F9L)iV1pBpibx#o{S(y{Z$ zg6BqYuTCZKTUi5JP|>QJw~nOwAdV_9p-CXhY>I0fp)dlS?^ie#ROc0SQR8oXn8ikM z^5#cW#;3Jth!4hL2xH(Xg#|FWXrSVSE)iMo2*BIx*r*pu2AV1$cu(6Zm5Xm*Ub&yr zp88L65wrs5WTe2j+FJ$6+tA9~9Rt_M~o#P_5t{?fl8DV zkrEnlM_WvY0HX*12{@%Qw5*fdW|6~3bc}ZsywvX{*`y<-afl4U2vHachyaIdCL3%Z zBuF+aKuOKlNJrj?EUsx7vT2mgySFdpGCseGMNc zc$>XhJ^}E4!5x_mk>Zu~8}Pg>iL%7T@j$$(%p1qG7oVY&Ud_7_FCbLd4x9?$WJGO9 z0NFX+FQ5o$8HozsujBm);?#u`#&NZ((M6){)Jreg) zL(WymKo=kg3`qioXd&QB2s<+*iFOvw055ut`BoM+H1^P1n%n>qMCv zcb(cN)q#!14GI1pAb1$B=t%^U8E=;jL2j#mzAGCY4?qo^$MQ0f{9`ni#o8bx7>cK}nWmF*b;a zKLYE@-X;l;E+E((HW0Ph60C)&1x!YcwkT6UKB$66RmHcf+F5||Ulv8}1TDK=kB zp*i3LOB^G>5)3Kk9{47y#>KxDmJZZtk>M-MBP;(luP`Zkk7(l#@f8XZn8_Ki7Tm9OPtb-$Pr`kAo~Vd zJ4Rzb28`&8389FkbAh%M5wt00odn7tYU4o%ygL|%sgUsbu7*wdR`{Hg6*UfK;1)2B4uRU|hzeD2VE7E^%&Jj>H$njr5EFoLtlwq}Xx9UVQ#{Ji#IVOx zLL4hWN{B=F4ueXkVqVoRx;~HjfazL8J$j0R7x2EwI&3YBm^D5tbcU=JObB8<1vd{Xp4K=dn4-?s2Z@F zCZI<~RJK&5vjqaSkmsMzn#G1SipPTzG`*Bm zdhx#8BSd+nN0-;uzBhMLdxm=}>_ll^iC`dTCZhQ7*es+F0M_)-q%eaoU(i^H`g}s! zTl*DY26Kga;B2e&FQU zcyv%8cc!~A1*N7RMMnt2;lU}!dToq1a_LAymUK3Fy~&ZBlfk36h)tS-ZuH)in2e&s zD$VjwYG0diziSPSevyy603EQ2(N^kwmg4L%*wTI33=MR)2G@~$%;)-9=c7ROfn%Ox zaQJ^qRy-18eqea>0rz{Qop*+CG&VQv93t2B#lyS>{SV_W=H(7V(7I-K5>|v2#9(@I z$dLJjVI6C3YS5{*z|&_FU%23jO<@*|6ZenONXnA9~JurIBQ4WJ~3 zj%-spTg%u&6F?qZkQyg|BU7$`=Q6?pC_%`$bppaCjq^|dH06MMp_bXy_MH$4W=xVN9& zm@Oq(ZgA^r3xP$i?MrA_4h=k%55{=G6Jw z;Gcewd47c^DKRua5bHcgdQg0Wu<%Zd&^CJ%B|>*EJ3SxSItZLg)6FM;i&}}U&pqI^ z5^J#aQo+*<3SL4C$U`d=pW^SQ;y+&bv93f#6B!*qMTlS{4h}z%JCy*tfgtM43Jc3U zSRh-2X!TIm$^{0 z5OT_XZy&dipicTjcgeAh3yLGj5B<`lp4Tk6i35y?9n_Q(Xa5Ewvmvukk# zMHCUTG2D-im2)C|Z$uf$2x$s3^fvFcB^sR68_Y;3Op9nGuE?K|C_Y^=Q>B?*G57}D zPY8|RNp00HZuR?GR5N?#rORxMm{s7K8F{6hol8;Jt`l^a^{LDAucK6c!t1@X_cHU| z$6cii*=_d!71?Ik&&eIs52F%lKPtSY4ETFvbjPwuv;9LA=|udsczLS7tpOg?p`RP} zdBY@z@C}gIp-C&C#9~oDb%w!r z)nB!n7iF|h0U(L}18F`__|LDteYu{HSgM5{9%T|DfSn_ctdP4Nm8Pkl zsO1c|!~3V94Swg`p-TQMi$^_!MM?@y{Hd?AVx>j;w$1l7Yo_~KHlyPFlb7x)z%D%^ z?~md=PAlJLgPW77$KK624@Ty>uCkxQfkY3Yo7Z|*uwOwaNweFQ z6rDP|7rNuDJ@S0hGJhOBB=u7!H~w~-<=%FK^*5Wnm#`}STJ9|cT6Q)PKL6t+kPKpM zbIQt80`&{^3mECwf?a^jFBq5?@tMqQvx=P3pYi8D407rlapY(oV{)=qWep8gGSA%` zwGvpYW1B2AZr}U(AI_W9WTqtSe}Q$vODZNRY0`>-qxt`-1A?gOIVW!@6MM+NpLtPO2kuA@sO7NbM*stg4Du#mWgc zJq9^WbRS2hUY!dqzbWr%>h}q-`wQ@6r(tMv)8dw~=%VwD(Vj)9qn2>K;|-M7bhIrOT*3beMm?HYlJNKG(%`$L|5 zpqoWF)FN299leUb>sy$}D=+s(pPvWy$>amKa(x!~U5~ZYbV0FenFQ*)jkcjb9?E*S zeZJ096LoBkfAtf4q&)EweKYAveLr1K)Jx-OcG2VgtQLKVFEWxh$9)Iw7^_Ba`Vu$X zzFZ}0Ms?iQCX*RSbMw@VJs$dCjYo}&Z%5<2^`~bgU5=k<{)pwt#^hPq=tlp#eVZOc z;$J^nlfT8c=1RS7mk~>(d!D#ixmtCVHGG}og>E2G5CkXl^5yi(CSdf}(np%a{g7f9 z0$Zr5gBU?qH3M5I6j*VA}dGW z-h4s`*=h<=_f}-4^@I)>$&!+npL>sp&vQM6*Q0)%h~L=szf20Jp)Sf`^XJK?p-rJo z;WlRfbLI9Tb;)z~6K+~>nP2vV`W~@_W~+l?mrfSv4mRLriRk^?1*M~K^S2<4DKi;Wvb(-Da{k_BnJCw*fK+Ou@p#L!6)VVp$hlTa# zZu$wRu^wCO0?LmL@bv*qOGmeUvT>QZWsKWaj;x150cA_=#H`wMTA8}{KYERi;s$yF zi)KeS=8Zi7*m@h~MYx3{j9JC%E^L#Iv#BA% z(qjEz6qu)SsC^4BU5*cNoq(Df*@`ZvVAH~wh()RUbN;Gfh>HIjom!PJKdkh5;XrqZ zM5{gqUk~VQqV>7gZXe#7e|zHqzu{HJv`UB-$uIgs&~IjjS1wOR+XS*y0fv1uP@`K$GA)T^t@k0 zer*UxOWP?*XK7OASy{9r6N_SDONqXOet5CA<7-DF>CDu?u_i&T=UrcTeLwY(@t1Mq z>ExMLN9Cv&({%HMht}zjR)^tnuC2aa^?J!yW%ZRK0q=Ud*k$RL%P1xO+L@oetBwi--1(zBGHw zq_yZ}xpZ87UQ8E^zP9biPyc2BJaTn^c|QgZ&(uXmv)Ki;)J)gYn$$gezr#{C!11H| zIgMIlky$+s!s;qlblK=agb!m4>DOmJ?Rcq^NAbSQ!L`*x__jIzZXK$-i3@Li>^|pU zn;JNos07Y*cYp>&)<6(kFqJ>&|n1J0{%ss+=~>hW`uy-IY3ivYcR!T$&N`SowuIwb-C za3c9H0zZ!b9pHyX<^KXdU_t*6_$d^rPx_BtEs5g)*wutPqaObQ{D}Nlfgh*;8Svvo zZ|U^^1Actk{@K-Z+vvL8xd>rFV3q->B2q$jmK0#iF;GxZSqg$v6qYn3AwtSi0wN-M zvHscBuo?;|!tkOH>H(s#Dp^<~KvBn!d4Jz$dJgg3w^&DZyi-pzoLe+syzuSC$xql7 zu)iPy7AN%k8lgvEWs8%2AKfcUT_eUGf0Vt%9%GmF-9;Mp)!X^GI`4|tKN;z-MM)jD z{nsWw|FO&Vj+DDCf2jOvp1yOwYQGpw%Re(LK0aj>r=P3`V(U8=k0-_>8IMo-gR1)$ zzx`;}p`n#0@cB=Gf=;x($=j}f+P6Lo-7n>Zk;^WG1>SFB;;Q%~*_t-YK6BCK^Ef{} z4&IC7rUT-}g_MaMjEf!~XKCY^&@yxBDR()KkAb%~Yn?o;&HR@K1nRDzzl%DH$#*}? znr4Gi1N-z^eYM|YD)^i4x~*s2U)^1k9zM$yx0e@fX`R8DnYig^s_1^rt{m+0Kg9K3 z%19@(^NMBjIG%STUT={WGUme<%i#2V-;a7;7U)e2#|Myi)2ctJj9=8VLg+TX+luZ* zPPao}2fvjp^LAT9`-qc!xpgxaek-#uw<9#(*fw@{eh8T)xt6%^D{oIsJ_G+oRj6MJ zbgaI{$+?_|&53v5U+a`{dHYvb$yOru@!NAP{t)zCQ+hwK>?r*fT#v?wdeT0fAq6`( z#hwiB+e1T3YvTc=>ai+6ocB!qr|3U@ZrUZc_xFDnUDo(|7yYT)Bs=!@FGwg-t14}A z`1i_YN&1`R2Qv4?rH51NwY$JKpaZ=9s#7%AYD# zKeG7Gi`y~&arMpv{m?P97g#-Y7FOp*AT^(V3jZ&~ujpT?HeXe`KXnuQ(YXBnbWoJ* zET6ZV_!OEcO>6wX5f;(G{LgA%B((v$_u;8_LAvXV;w%BFAHUScv$LGO-~9eR{k3mj zUyq-!d36r+AM2tgdHS^1jelxu&24RKwigoX^ieq;W)13091$;~X34jY`%ym%jf6|A z_Q<%#lQRZ%zB+4-o6-wD2b3iU_Y>#utz&wZc$kSq!)q8f4BH~RBaadBP-VHZQF@>2 z^##uMeGB9a#V%Kpbk=9CIMj4Jppu8;qGRRV%<348!bn3xxan7ZuUnajzR^iccd>}u z{J|xDFP^IYDkCwT$x;&$TgBFkEx+pQ#S^KhF;m6?#YxU{B&W^ zT1i7iJM0{sfA4mpXDk*8qsHuU&<2CbG7-uA)Sc*D2IW-b#Yinw2(-6@l!4H=) ztfSqJkNhU?VdmmwqD)Ig%0-ZWZ<>XXT*+%1n>^)_4mGgZe&GMTAOG{)LNGZO!a>cJ zY%1D7qCGsU`3;_v-AKxF@Ea=!$1w!ewtmB#YyLeZ82E1>$BMIqP{L2M#diERbCoXOc54CP~ z@96T>SgNqKor9BYGiJi=tu1d&PL5sldlL4ozjD2Qynk(YYk7HA&*2)C){>l779Nhg zKDaruJvlizGd*VI$YfYBLpe{`u>b4Zw`HXK(}jlpef!Qi%5+GKE~*t0D$ejaT8dE< zE7w>b5gQX>D%Pp>?cXjMwETOb^^K2I^W(4n!mk_cc4z^SGChI4TBoYhMISpU$?Nw|$u=0i1`dBEFDWNwLOL`$eDg1* zjEaGPhlVv|dbxpw+vu@2-;2B61ZuUrFSCEyY<3K;gSYK@JrL{aQe3UkJ-j%$Iyy73 zH@7zKIm7wqS5wcbVPe(nFzRSXB(%)<-+nd6ia(_zNUp5^VSvgDRTch20fhzt7`#D) zeQ8hq`hH`-1EwI&#bsKkjMu+>a$dKpPEc9yA^&+dn@5t)l0&L7;wp|brR6MkC1>Q5 zJg#{!&8oAvk?q{lli1n(Q{oeOYG*vLnb+jBV@Ua(eg z<5QScJaWX=BpUn@^ljM>_lkRPT5(pt!4w%?FfA&5jW`#cir+1gM-J1*%g6peba(~Z zJD0zgf0-K2wy(Y7!96aam($^b}znj$iqW$%}KC?0X#7pT4AMcm*hp_j}iD`?!|93W)nBVz^Z@3TP zzgMvpkjZD}s}eNH{}Rj5W1gV4bA!&2m-4?bMk2!m`nUaW24s=;TqGKL7_2FfhiMRh zOCR=SIZCVMZc}W{RV-Q$KcnVwR%Z_jI+j2e84AehxM_dx;u z=J(BkUUi`9n`wRSw>${9^;1>-YYPGi97@blM|UzKIA$w#Q9TJun7JPCUp}o9|6K=j zBf5t)HBd>VMu$aGAz*67Q_VEx#8M$wGey%zMT_Tg-i9i@EQ|#y!1@yG*fbUOFql1S z4kn(*FH_;k$n7JtQq7c8U-d?1wyW{F_*^f?;|D(}(24Qt3SDrwQ(d(?A4XhM!@rlF zbTeKrwAtQLmQ;fkhj;BnGFo)l*EzdO8+NIH`i%6jdI9iRR#onnbTUeL?uUUB(f z{%vc(*0(zKm)WlU@Gx!jj8&pmQw(RC@A_`$IGAnm8t{6|5Q8m|vl%S*nv-eT`Fozh z1@p6%_UG8#xUbbuz+_rN24BwKmi(5_YTjlGiTiWw-u76%;eGUcoz5yqPjygRWUI|? zBMmF7%36lc*IV$kqgZFSF8=d}3c)L7ffe=s+?&2=+w<4?WW62#8=ENp#{=2hWI9mY zkrs`=s|a`H%V*RsgMZHVtn49K`0^*%-+yAUjgOOqg5j^PZ;#^h%2s{-C4_geLAZXm zj`?eizA>*~P9I;?i>bfe}gvSH)HzIjuFHtip}ReHDKRhel1t;&qf-{Ypq zH{z0#%h9qokk!Oa)3!VqR!v{ehXU7}<%ZW;cJgY^&7RknO!gw&6MgbsGdk`&<1?F! zpIc*f`t_eopLB!IPiN`c>Abq0Hu#@zKI}4`w~WmWe;d8jc{=t)fw}caMm8dd^p4u} ztbS4*f*;nxj{Bt{%OL>Vv9bZKt=kF~U>V72sYS16q2;GkY-zj&s*9E+6=s?ysT;6N zwlj^YhimS1`D@^uXMgIqwfAV~i9?pzo^2yQSz~sWD*o97+v*N_NV}!P(Z%zs#m97O zirlhT8H6OTrAG~$z%($(_= z>UKI?*Ah4`)pa24ko9NGxPmH)+kkF3^7dd@3JB3qePBv3nYg$h;j2HvZ^@CI_fU~` zCnFAJmyw*C(2t3_hoI_BTa7R66V6&4`)mC_SM5MM6=b_k4=XvEY}>&b9V~qiT9;I& zOR37xM7j6WvvC+TOje5?$TDc zFe-#x%N|V1`epi(&hrdx5LGVPoO{A;llDML<72J^Ih=o}lNF(z*v0uU%>=N4>muvc z-kCYEU4#^?sJnBFd>F|6AOGYC)BcS|c1ofq715X~&yezLt1KrPrcDtintF($~$5p z1`a7)_n>Q-J~gvGPq&ob8|2Y(@{&B)ZU|3a;>^ib3ad9Jj@kW2yIbCT###^Pb!R~c z*w1xyR2jx5aSKIdKXZL%MFznvB^J~M){&iDKtsVn1FSq}abK?}>pFK$$`Ay#hKhok z45awJMdW{FEuvo$oL#YZlroK7Q;+YHEA<0aryfYl_Sip0vW~YTlg`eN^MYUIt&V=) z!aXr(($=k#>qtnP!~^}f_3Qq%0VSguvYV*7aKLj?*|%k<rme?|5revN1&J^nC9 z(1S8z}nlJQuOwy38kQqTKp zbj}-g{NkQzsYkOZj!?ASx3R-zwIA!lko9j}57o7O9oBX7DJm(qqn>mh{zjnfinA<5 zsyYp%FJd}Intg}>Lw{D%s5{y(jTTpBiz|Vb`2=pccF8LYCVg$_<9?HOD3c4`2s+}-O%Z+Ck9ifOxR zm!2H&GLK1Le@-u|1qLQ=zZhj}o_B{OBpJs}m%e>9i`v4GYOj>An2LUH;tzAd>_@jL98)M=PGl^kqs*iCodwxGK;Z=lsp+|sPjkkr5NdS|zu0Hg4$JWRz~ zVwaCoy=oTN>E^@c>eo@PEagaOH>8l2fdZ_mQ2464(we5+agzT0K>PLZSS}=ICb%#n zP-lfm@r%4s?aMIro9BL{uVl3|IO0N731+pjppt~c)#-**SyEl`tj$aDtv|SW8`Otu zsLx}Uk?dn;-c7`(unhu1A`5=p8Cm0+2Nd8fXZ7ebF=zpgM27kkf0=;zRN=4^RPvY% zLaZ1>Az1SVCq!JRIoFEzk|Hd{;w&c%`|+5LNFfWygNzab00(~oan1S#;iClOaAcGS z@*oxQ%Pp-fEww_P;E!B@A&V7&WK9C#Lku&KsxhfGmEj6en}xX60QR=lEH5oAseHTf zz%2R|F;NO(&i-|Zw}1}w*}9}AJmL_d3@S+H^?XdFf`Oa5DIM-`-eQ5BVggoC0r4<8 zsJO)t1+^d+-2ki-VtI-Sf&qYR3=43x_j~{X1PL8s!OQz0A+f=LFeXR<*@pXMAqven zAY0a+M<27)~RTNj@YK^0b&Kns&dES3T+n0vXs$tvP9c_GQvz${e_75$LS zK*6bH>$Jm|6CJUK-F|RtazPklhVB>u0%|oz07C+IfT+r%r`S{s&~6l{Mqc&CD~5|M zv*k~8m`eg+rc(Ll0R2*dPCyFBqI0uG-K9WP3MTo!je1d>tVk?!Ao<4u9WY+mAtLl* zlAJfytgV6|nh1hkN~)7K}z>lB3Pa4Hvuf zBT!eb5(42dg1}w!337`wsCy;~vDN}f2o0Hl0^Ks%~|w&WGPrU2kF^WcL4B%ZyT zn1+5e{hjz$bd%!94A3z7x*@|8_qP&je4I9C+(N+s*UiD0PEhfQh|-17i++Mi66Ri_ z{TP7i;v;tuZ;YS=8X$Z$0a}Pq-x6ods-h<#5*EnCfiT^u#&wB=-$n*9a26CQwSUl4 zqXO=nPkpoqPe_hZz`ziUa>~=KUDl+5PEaA=17W^+f)|uHZ!c5ab--NxL2(cg?v|P? zID6$HR29A_P==?&p^EAN0iCMDF%~C-;HV%Y(qQh`0P7~kDwQq#R0Pgb)!>eZ5NgcXev1P6S*?y~-nP_H4UD1kN{nDg?UA3Z}XMHQH7-> zG@RzNKxe#5%|9>#Z>C1Dkmv_FZ)LgvaYJ9j0Ms|^#0Z)@9ZFa=kpaLBMl)vmwD77U zGJ4(ixA3-40QHpxjb=pz0M;fKAx~sPA;h9r*^gst|E!FABPeQV)J6%6t;Xg6RR{)K zGY&j`<=1KeJa2#}m))8x9Ls75?k*juYcnWO;mEDue&)Gkd2KkiE7$Z+6#z<%3MjOv zx^^lKBkRlo@D|150G0M&%vf2OrI|wz8 zH!qJ?Ug=Jr!2iPooeErHG_)(Ztc_e*P-vdVd4{Q$n+H*jdYt15ge6f)-fO%Y5lBbq z6%aD4$QYGkUWzV)_C3Z5lL}R%HE5at2g)8#SUd89o#S{p>5g6mfnQr98pP>kSrr$} zZaG7&1^Kz+^uYm2A_t2^QUl`y`oRyxnpE6Nr(Bsc(mo9s{1y-x zG=qy`;+-Z1=JayN+x+8~pCFY)mQKn*r)oplyopG|LAK}EwjUu7Avl;w{ugVj6d^4U z%64V|DI&pX`I!09iAC9lJJgXj=#VZwy%o?oJ!c)BUt4H#RNB9cWppo;;yt!|pV-v` zwD_*dDn?$f0z;+~o=%2vnH+$h^9rouq^MkUygy9p4lu+8R<^qqCWA!NC3|*t%&*Rew)ze~looU1?*t4P) z?`V#C`6hor;lk2zE|puopD!tZ5~u|w;??Mkd8Em3n+G=amUI}+++jbPIvfJyFBA+i@NHr*!~A~RmtnYwda&wj zb(gh?3#$N{@Rj!a*~XN(CgkPs$k$r@??wR)YLj z#Cmni4110A>FZp)By#=p`c;$ey7IOh7)@gBcvQoJCkb}AKLZq!qyooMNUi!Yz~y_= zOlD{R-NSR-AkDI{a>9SFjR>42E4$)*&Wr=r#OXN)AWAV7<&|)g1yVpPl7UjGz*#iY z@u=DXpsiSsstt_+tH}rG9P=Vkar9l#yU3v00#ZpR5TNL2a?Ff zF+XX3-DA>tUD78AtF2g1fT5!Zif)q#@Qu;EtHp9Pblu$awwILvW^ z7`Rjz0s*hoSMpgyw)6m2sdN8U_qfHyJY)~HQLCs)7lt6`v*Cqffq!za57DC+kv5O^ z3W(=EMdc$!9B^se$wXj;Ai%z}e$BvF+kY&{g<~c%>Y1q0i=l~%xep$6qXJ-gxKQJ9 z%#5+*>#Zlnj+X`TC>;nQffF3=BE+IYP*vppbq#N%Dbnk-(Vx<|cWohmIP(G@3iyLj zgtE`&7)^)Cbq?d45ZtCqK=&OWc|Yui2Bxsb2uAF_YXH4rpT1bYYPM#5fU63l5FApu z^?3qY08enrlN@Xfl2-|TNatb1kB<*2EKkA%RM_H2i}Srj0Q?fNU+W|lllq4uYdgQ8 z`jXa{?;u(V7&D3dcU1UiogA(T#VDEIF<2d);<(-d)&k$KH9Fva1W8Zjy?Il<&l6|I zzd%tPnRTz;Q|jSq1D?}^zZ>?j$O_->hXvV6p!=Rcq`zemMH``tgUd|;o$vhVGHcqx zBz_%&sw4T4LExDbqXdDLRl2k`&>CJ7R=_AQLD(M;GZ%3CgRwSn;EG-7=o6vG68G<_69;;jR=~AvrS00s1%RDrvX5Dwio5gE3&3_Wh6hW)i7_1Ouw=+M%A(i{{68@tDy|dq zzYjDQqEBPr%a@Ef8T$;5X=a#Yo-R1+*&@w4nfsH|f#)c(!>qa31cD_Jl?6%Jdnt3l zf^gnU%iaZmYL)@)nUQ@bmu{bwrvpr_>ipS}Ckb}U31rU}D^RAq^RP6uqmq_#0sXBD zHGKBAvPu>TSd#PKIrc0q1+=V=&0 znv@fsmVSY%ti{ZGcBFjzfTUM={?2wa^XReCOhj^C%Hsy;Sj^6vN^^{|R0Z8f@!;*c z4c>VBO1 zh)Q}y`RP!!(}~%m^0yZtILNm92$L;t{Yu511%nAmI{!a1=9A=#3kPeIJ4&# zkc>7*K*l?NuPSb3o$I2gN4qK_(Zft+jDr8D@f`HmC|*Vhm(2a3RuK!hNdTB$JTTggILiR zcI9^)Nij#aixG+puEjIW7C7_5#h=nkce}&T1&_03lMu5G!j*QPH6?;)l3l5(ff2Y% z%fyWW%by7svl0tg04Y0q0M~5YMTg55#YixtB!428)o*@<)np>L1!fdV0@h9X0Gw@? z>X&GWO5JXv@QTF1p(f8(tm^F8TwU=rxuU1j@Wyeaf73_7g7>9m*eVE1Q?}(ElKO0o ziIP57je2!S+MVWj0ALt8WnOe2#ENmLO&77S9AGEU$z8dxC|F#btWbYJ0HTE~MwlNC zber}y*etFmCPMfF&)DWNZECR#<1OJYJJ4+N`)dtas4xrwriIPr_1M-{3(KTL5NzP? zSH{Vn`s%X?uo|l($e*(e<^Gwyc_93R8gadIr)Cf?uFnuRvH92D}l0LV- zrVbdVIJKacIWHcglIi5J5KhX?zBT@tGc02|$dWWJAv|d+60sf?f^GHr>ld>D*-$+C zF7HMSf<5jwDeQ5*Ma=}z2KSzL+l%T!a)XkQDVGb}ZihKTEFl%hts48}_Lnvj>(@Bh zEqL~UA|;^08k^Y8q6i3yVFcuA7FQDmUY^r}6b>}gTDz0sT;T7pw2d-F`%5x`yew3T zx0TT1LB=8_u=8!Zu*~vEC2wh=9VCqJU2H`uXx+B07&)UgP{JM#tI3y{8KwXsXNaSM zMHtv6ciOAq=G{+19O^aWm1me0A*_`nQwqRUt+Z8FGgy@XfQ5i>oGA^CY<;4xsUY33 z`)uea#VnY3+AAoXtd(`AW`Y_~fdNJj{y5(d&i<`Z7Tp;lnT2VY@FKoVK|2#=zlo%E zEe9Amv$7XWG*kqhQx19B$r^hMAHD>Lkywh*h8C6~eFBlnxABS-_9_hrzA&?V_8eOC zes}%tB@{W0F`!-ib2eulw2JVzafcaVq|uA!SJ6G@vfiw#IAe_b2*F6Svw80sE5Yce z^;&DAC;2;#@du(`r}%Q{U`-v%jQM=}!f^vAsf9gl9Ib$bnl89j4gvs}wT9|pVf+G& zMv$t4+`$+wWxT_dFmOv<2w*fqMkg0!Kzu zQw%7nM~Lmbnz*aRc*fhHa+udEz#&MJ7yT|(B@`zk3^#@dpvR@Qw~ha-4r62#Wr`za z+CUW;Q{*iZEi2iIT$tK}s|V_W>5@Ak70nc3vt=5tcA%vpHsGX#k9{D0Kf!+#fv05v zd(a*DF#h2JD@v;xTDm`61pW+plI0T$!F7;Xwk+In)#RNG!;epe8*sT0iwb=IHNv|joLV7y!e^U zZ?*lTmId~bo;x+$HIX4EU-!eLjEzQs$o3~5fH_D^m^h~P6EITqrzZ%sS%jx(x~Pu2 z@Q^3&%_{X9TG;9V`Pn^Nw~9kPp-kMg-CpboDgE15e}@F9g3`hXSbV3k5xvtnq=*FA zn-zJouB0?r`E<&M{N5w03eIsnd$$i5zWP%uHAxoME(SZ1l*0>mUOJCr>KPi`DBha~uMV_4d7>|O@6syc&7$s)lGPUZzYW$x!2d$9+yL^-S+Pdtn z;C4Hs@xqRu`VKo=y3E%~=#;ZtgA(`>Zm;nZx)T~zKCk3CBF8~-b{axrF_cWH(=DW?Pohm1TN%UReEe>aA0Q^0W{ zYFNt!NTe4DCo7@>g+#iI5{6xc|2C9He*#G+40Km%L#V6?s&r0zh#hgP%8G5*>oht7 zBuX9hwMfFN-8~O0oZiPE5Q$+Dm6&c~g(WZ(mn`V;9xAZW1VEX)F2Xf3HHj62@h*%A z`^ZwJ(VpFtA3EU0FOn35ul%6s(jzL9Sy1YilN{1uLDg zFGG8e;h2K5cNa&{25~j{guHI7s^>+ewB!Lkf2bs~;x15dUUy%IH3I%b`J{$<*PMd; z&}DvxB_I+49aYQoWz{#_NmBFosEQ96XFUiXbr2r4Mg!L>;R(}4v0wXawKJ;T=rJEW&a!+73a$+4TI@cwZjsxXPQGKyGRsff zzoD&t=oJ+?7-MI(PYHnV;)3|WTCsAB+heXD5urKEd}1Li&YTNMbxA&0ZXCm9GH2zH ztjuQ7%s^7*q)%`#A0$^MG>-1)GPUrybGYE-PRhlb5ORwbCq@B%L)jrxnvbNo7i&El zC}uebX)~|sUnaYaWltax2;<5@Z`Aw(=7{tp1(EXOF>VPXv^w%V6vh)SqeTa*m75% z6HoRDAm3I7C=g;XN@w)aQ&7(?e5=FXvJmnXEjtdlttU>hbxm-$COdskAGfAT;EH{D zkphHJVdq4-dGr9Y6^JXYnb|5xpHJFv-EtYNc^mEcI%I@F1wXkcZ`t#gNEGyR-}519 zK!9VWT089cVtz0euzXw3vhbHlRO6y={B^<}LpDs!E8~}5T9Tuw>CNt|?OWShv-PW= z8&(KH1TVFMUx$boGVUTQ1If|w`j??`;go%+qQ*|a(-c*j2X8@eREe&4eF z;X9JBCi_Yu=PSU&!M%*}9lK3eg=!+2Ag1Sc?J%qE)un^i%Av-Lazr2?4(a8^0qeK) zDVO`eUTj&xE$!vJxs8xZD8A|Uu3tG3+?%s$weTjGcP#MayB$gQWpN_-y~!PC^?TPv z<;ICK7}d9mHG5s;Tpijg#$U5 zm>R>nNJD7n(yg2ASH6uU)umnLd#G&3%mpH|sjy<2M;MgfeXC}mG6`Je32k1e#6Sz7ktOQA|!2?yyp?J z=fYwrav(aiRw3eO!&xr;P5HvOK#)r0s|hP%>ceXnhO?cJ$i6+6tq96R{+Zo;Cdqj} zCi8;O-UAov6JPC%;B4p&^O2?x4aJ7Nj(`Lxy0QWZmyNdz#vjiYwUdO z9B*10A2s698UFb@{(~EZ@%k~7&egfaXG%tcYDXC)6DVmkXir4Y)8NU)KQ;W-uS9nC7$cYMCfL%E}oc(&7 ze`K%IR*uUtXc#N&Fm57zE;FYrOa(-rxM7m$h)BzcI(zFXQ!o+30HY;{L{4~KVS&wV z{>S3XflB^`>hQwCD!$8P`JTzcF_YN~#tC!)2z1Ifh{?8xv^r*9-pNlai{T-3&Jl>` z$0oC1BZ7YG@2!cxA77m+2I$yrs+@gQ1_*C*k*4HQb@K%Ok8Yl?W7UDky&id|W!LKs zq7&zk?8h(G7G;Ll-e3KG{F?nKPlhZkJaAgVEaB{D92 z4qJADP6|%#*3low6I$~V-e3FjM{CLW@z2Ba`ab(6fOYX^6?`>aLabZ_y&d<{4-S3I zO_e-CZk{6T1E&3fpAYz|w?B!tzDh>%lK-&Y`kg1wL9ga%Ea@6fW!zpfqZsDLAJ<*F zo}#(rYTU%F+?a8mp3P_Nb%;cE)X(=hr>o+p`SD3xBX6G1)GHzrCTqeC9{#9}zum>149TpUhMuD7lf`uw=>J_SyBh-+Xoe9j%DH zFD4uxB8ngIW2+Hx1_&3eI`Ue}Y3cWH0&i#{B>_~p=H72Z1RlQ$x~ zeKoXNgS__0qf18~fB9Xy^vIptN1lE-Qk(E=b)h}c<%w|FD8FU2cHPm&&GUK4Be`8i zno^HS&mOrMnlL+x$V)}Wjn{BNh4jnOE~`H^3G-dcj`c+T{uq*E`DjY`5fLlq?z;Z5 zC-+!C@^JGPcFUz>16_afO*l$p!fET?UqUO0HHovUKjhuI<0B@&WDoz2BrJ?MKn>{- zK_@d0iNpn_@0Q+CV;%pzdEw3a-=FcR(#?OuuY|?Qcw0!o2CF@~LIA>Hyt>Rc7J@;J zY2Ub%nJ#6|-s1|SAQggCH;+<~>eJCntz*X(hK&MslPxLL8|6)E4=wdL5^%u5{-p0C z$Li(x=OZ*Y|lIsnu~0qS#PwLlI~*cJ{-Nr5>dCKJ<4~@GWRr_MeqiyR z!orj8h3pfZ$igaV#@Tq1`veqrp4ANk+uyK;DB*QZF_DzVK4#Y=N4>z}A>sWFiZQFC zLH!qj%H7z{GW3L3@41%+$1Eo`EZO_=L93e9MjMFrTu?s@^c9q4s|U#;paCpP_}sQ6 zhp_MZVCSoqdxwhKx3cFghK+&O8u^l?hpQz`2Rjn;+AT`*SLE1vQH5O?I&cT>Qcw+c zqS{%Z!<4Y1x~6q$oEJgrL{A}y*!yi&*W-_ej}}}TJ?@(1)kCs*Zk-=U-iTaAl__&= z)NK75$x@mJ2cs>JOrIGdd1OaR%+=hiSJ|Eh?4;Gm_bzDxgK7m|02#~%(!om8xL6ds zyfxUSubI_wXw3un7oL?YwY4-+vjH;jj#S&kcdPV1sq#sd-B3EZbLC$Rl0$Xwk)9%(bu|vEgQsGl4FT+#MoyIrp$8@Tlr$ao^TStZ(&g63d zxW+O-;a5raaR==qI#zN3Q8@|F=`}BVe&aQ}v=doCm4Q2zLJ*uBW>C|JJ#>Qe4@};l zLq9@WmUpdl9GBD3o4u&iHh|9H>kv5NdvGVC)0-fBP%9-%pL>#&b>=OM7sj_LnNYWa zAr%_XVk@7)!puvS70|#HDH2Xw%I!4x%mw(mv2}qb;)93ed_0E&F8(s>Z9q|z3c;L} zfrBdBgw2l!hRcevJlNmy7BD}Ndz?W6dT0Vz1PiWm)Of|4IgYV}1+Kmaph@&)_V?5@ zVBx~#w>O3!DIiOC_;YkgG%8G5mt{e4N1hpKCN~0H2%8JM7M#G8e6vy46D`|7YQY5) zI0?&PU^)RXXp~$!I0s-`Wq#^@5>;!7FF_edJS85t!}wYU%^0FmADe>iSsuVJqge$d zyBe-x2o{W^;=gXWtNkVm2B|K=o$GE`W;CMIp^G6amdjNFH}L>5vw5i>aB?N50lKsm zYj5WCV6~Y|kY-i4HHv!AszVM;_rV<5_b^_zWk=P4mf@rK%8LDYFe;;2f3~_Mon;J~ zm`c=1TBrBL4yhmk@9r=vz}E9fpvOe?nySzp;iDE`WC&|#0Bi&6RMi0Y@0D(#+BgHf zR04p)8Aqq8EjlTlk>-)-2w?;{Y$=_GOK-VuES0NnFREAH#X;JCi_-og8l|pj?znJk zmDRhX%^M-C7Rfz^ih6Z3<)s_wH9mOIM39v!D)&vwXQ+_2nJiPQ!Yng)BgZf}M_?4Y zBi6>+;~EgVuSr`kPJT0)P%>{6A8iz;(m9yRoO%P_L{0?NTSw=yH)M-2U3 zahdcD+(Ru~TOe~#kO1Fm5z86E!G|eak~Ac6q0h;Fx!J9MPQLb9Iz!*+EE235Pe8z{ z>y4@?7+rvaQ8T@S5z?2VB{1Jg^N&t1`tfTbzLSK$RBK-*T$}ub_=29MlCQ>H5d@%2 z*v#CO0lT)Sq|~ULJn_Du8DWY7_K!38#$$wQp@19;sko(@PTGxID0}#2PG&@wwH`;2 zv>_a~7Mu|_h9z3I)N~S=_F7UwIK6JoK+|hKoxQxSI)1*tlqW8_ZPu2g##`vIH+e$ zN-O^5&f{zv1Ue*c&kro|sHM(A_q%dJ`Y1e?4FZITxPIv_GL&xB1o32n1hV4&aw@>j zi@kU1I%fK75Ue;%7HbU*LtF)SKEuSt>j(POW(SZng zmO!bCpa%0%LQcbyayj5gdvklwXXDJ8pJ#}iehg8v?B*^W;;-q42i-zjt{xBiq`^>d zOpsJ)B7Gn9G5d63V%5q@HPl~XrYaE#)f^fGxIW(NsGFYbzuDm9_DguA>(IBNZc)T8 zIDjjCj?oShY2;KrcyU&irK-V5-KzYZwjKAYYi=`tC4-+GESgl}=zuS}c5oVJTD5 zk~%*`JHBxC+LK%@f6j4o1AMn>6aJ~kc?)iLAor0EXwSIRH`7$ViKoFD__@Mk`nkc% zRc=c!P;HL7)u>Rd>(H4OsQTd-RRo;*{!0n$e^lOhjh<*;`Lf774m7B4alN zhy$zSBfQ@M9uw5^o+3@49=x6si6_qXS z=1^U^8^~s+dP#1x2h)oVUHoEN5z)~mBG zHU9%(1@AHi%|Dz-dS8XGwu|#v=(2lM8%JpBcc_6hlPc%-e;?4G0V;2XRJ8V5oE#34 zmqyugLl{l)4ywI*1Af&eV+~Pr(-P}?rSGm^jGxCom=u!iP3xDCd3F$Q;$-$RZ^>#q z=%g(dRl#%m+N`;@++U!d#G zfFRf14%&Q@&Wn8tRWFys!1(@uQElc8b{9CRfgm}yPK71eNE?aNGuoow6i?_P>$$-k z&!t*$@qv&T1t;X_%eQexyQ0 zqqxeHc59AAo$Xc<%rU0F-E!F56R~o$qmQ*&or-7)5k)m^6+Rb$CeMR*J_kV44KDNO zoj9JamZak{b)6JI36;g;VVx9~TM0M{cZ51|UXfEaxM)BSgfx^J7h?))s$B^;T@gRo zm?O|<>{mDVM|fnO7nQ=nEN9km>(oh`!Wj|1ZIYcmE4GW>HV!<0ENZnDQqh5KX!1F` z0{{jK)u{uV?(J4T>INaV-u`qY2($9YkY}YxP-zaXSjcXffme=8u#XM)NjdnrGs%Uc zzK{PIZv|8!r*5~V&z4I%)F7!89u6?h*WGlG_dV6KX7KQy zKuSCpAc5S*K1u+1Iyj)}5LbiK;3AKVC|AXt%Eb#1@NYDIYTdbaB0v;?oYVJ3 zSN#0WBU*sDzftSo6u>pMywU^q`qilr>N4kA$wIErPA<%s>a+0TObo*5qHpQv#>W8A z##jNe>)_>Aj1}CmEY9yB4@=hA0Y|8_lw;(Y@3Z!*&Anw#S-cBl-sdeEY&E!6aigHv z+j^v4ku^b5tgWH+y3S4#Pbvn-T!hSpMm1Y5j$*np*=gEArm*j4F-JMbCwez%9!oArj+B(BS1Xag#)&(} zH}#F&@MnQ`dn5)$pra|aGEkyMrK(Go1m}Ceq)QX88&+z#t4<4>`}2;`Hq@F$Hy=ESXMpJ0bCmn%U=1T;1*q`zwSV?4%-vmvtY5hGO2&U^g3c5Vl-Z z&jM<%00-X*tGCcv2z4q!Tun~k{y4WKq%$8acyT*H7dBT{0Jyt#wm+CIJ-M;>b0bBg zUS*u%B;j0|E4C$o2JAY=<-DtxB^Vl@OH0eFRP7$?#>DW^9-06*rnIi8vSCZ5vcVz9eQl82xVct6UvE|hLQm; zEZ755&%=m$-S1^oI#oYFg8($JCh#y(mmp?{BJo&4tk^R`F*wu}8 zw3ifuL=#0_RiXO7DmW9jnA$gvKj+Lj)9f=%Q_W11PNQX_GAbD)XWERkjF9xEX4-@> z2qBzlx7A1pVIp2jGWK}sJuO-Y*>@&uh?3>aIzPX^;QPI<`+lD9=f1A{y32mjd~ygT zZb&v)4L)gsY4Z90Z6wm%^`yQ4s~~ME@3S<7J!r5`g#r|Gv@_!M%SO8#2x@C$9ltyL z9$E3o6sh)hunB3+iklSxL7lg>8);>J5VaeUhu|NQ(KXX?YcuSuA=J6KqV*tvzz3f% z0~#=@14k5%R9*@-dL;z&7ZsxERO0fXwXGXO1Q3y)PP?Uui~*G7kAw>bX1uF)Jh3QE#)MQ7(C`IxpA0 zTCr@SuS*lXn?QI3SJp71dh>xJuZx2%&8u5a2N>-x%Nku(K%5ooL;i=lP)8k5GX3Cd z)1kL66jijnFhR-pOR~7~`6}EtGdx_fEHvj#+^3=uA=0wVI5mfKa{+V%LGKH)Nrmh? z8l=;@Z54SQc!SiWcYibYV*-OdQ3($G`d>?%Y8b zoXJw}yp^vO~C$H zdEtgjQpAPB=}qG#bV%~cT18o0$^;kIL^2QhS_cq|p_^)}dR~?v;%a#*erDNZZ`Nb)^F!W~q!(o&MHpF&z zaNxWPPb6DLloS1z$F8{k>&`!i(0d)$l9lCm;Gv!r7$^E~)f6@V`}5krMQwLzAtb$$ zY`!_pLP31=xAr@xfHZFYd|2e4CF4Dm>*V2~ijQ5|u)?O=4RK-A#`QMK!dTD!Gw;hY z#iN!Z9yi!Kw5^`3ALI2e$IZKASWNJob9ZpsY8EstS??OND^MTQ7gGN4U#)nf*IrY^ zYnt~&Y35s8YY~p$AgGR4_}Ap^p!s5kQU1>!OgY3&({08URh&G_J#4lHRZSgLD;!oa zF}{36Wxhxp_DDHArO;B2D6}Y-U0t?uf(xihLXd^7Gaaa$3sqhASYKG+y= z3KO4ctrLg5ar@!(If^eU!(1ST`n^Ww57p1kUhwdR5Z^>f7I(2>)JW{qBFCWiJK zH#$()z)M97ik$yji_~_#saRT56+1muW(uoJtDXJ~mPN?kcxKj2u(QOi{Pty9A=6s{ z4LV%YK<`RArR90dtG3>=+sXbECd-l^v<@7y&GfWGmqaH1|e9^qUb{YR~V_NU+$=gHYbzzCi-#`97 zZH{!d4zclOnw`?3ChfWAp|h@Xo7MmQ(Wg#j^eiMiChpmg>r<_MeM-N+N=lzKJ!!_` z!w0T^rHcZME5Bab>hWf&sB%&s-L?8`p?KDTH;+F(og`KkuBw-JS=LL*vsB4ka?j4~ znZVqVJ3T?}DzYJuk-789t{KaE8+N04^oaX6U;x_EmpwSzPW?8gR&PaZCqv=tk2^np zE`b-oR*JP4Y;&N>$)~SP9@fYG`sFG!kp%Q|;u!PL2eDIzGB94_ymF;saT|>gmsJhz zHLA0Qq*gJvLQU6xHBliR?h{WErh95}%^p3Afz!md49jQ%Eq=mIc!WGS7O&yP$#v+k zVyO7qip0;g81PPOq6|4p?TvfsnCXJEUl@0NG5tOE)dY& zx(&tRx@^!q{z*&Lp6Ds0tl?Iwx$gyHRm2;+5@aQOESC8EmzBS7{N41Eu=o}jpa6LjlTO3E;-G?6pJy_BIx(soqtvdltx5Wl%Ss0m<=G=@ zX0eCz>do-b%r3Z_2c}&=6l)rtCwUgph`O1Oe;%AdJwu8;ZIh8n=NaW7gs*78A5`&|Q3EKo6Y;OprdL5V(Kb9GPJzmMS&VWWGbWXoo zJC~k1RxiHy;JC-DSV}E zXpz^t=~oU-r5>~;m}?85&tY3^c48spV$_5n!VEuN9J6$%GIj8nc2N>}5iMH)IoL02 zn^f3eJ}Z{k?wk$B1ooHzvjSS8h8eNNIO9B-<~8yDkseipZHx7NDaCnPw{X1A=|_+K z{>JD%#WkSD9D*sDMCgG}kf0}RL>tbAHAAUwlL%%+6`GeGHu(O6?hp!OpRcBj@z$VA zK0ohhu_XodAm7WBLiZ3B*^=h<9@S(=sM{uA!dPfmZhNk$E+}^b^Q|TB?tSL_>OSwh zoL5O({jdMU!w(kxoa3?E=oAZrn6gPjQn=y=H})?%xV9(L`=1l27s$=MFk|MD%qpir ze3F%_d+(csp;t^n43)#1h2y6KhBxjZxgI-nV0q`{`bE124}E2)5Iaubf%1JF>tQ*C z6-(>m5A@8$nemseW;7%+b!ga$%~1wTC|ySq!R0?`%M>Bb#UOt|ww}8z1M$r=6b1BX zCAduKSf*wKsf-+j4B-?fW=Z9M6QO`91Ni4)xe;+yjN)*PEpjshcA~2A%56{>)&X&% zWm0=H206VmzbKG3TXPoB+)g@L4M`f!77}^7`7+D}EYi7uYtnM(R~U7ZuZEMO0qo+apQBkj--Z|=b{#+#rVeCanjJ5NT(JsJ> zb5hf=9pl!{`!+iI{ab?jsD#T<_IGmM?wL0b{^3J0!M|5!%_FEiL+Sm1Bi8e`6A;Y^ z!Y5djnBVqsKJTjKw3PN+T!wWYsO{!Bf7Su0zs9=R{HibFqYL4fMbL{0UT%bys`h<) z--mGGc&`9T(^wbgjHi+&yf1uger!%AMjofD9ZR53p2OnPa zK=57#9}jmZ2Xur$!hnP3Ju^(>SZ@RgW=c!9XXCRXU~~ydP4YC- zvR$MRDJw&)z3d>FNsF{Kh*bFSdJK&rAQmN4g)0)SFEVLepJ+vy8sY39hYaf#mIGZ5 zg?e!b>=g)PJm8?xP|M5?jlo(j*!FzZQ!k-p12uwh4Ah8tIQ2A!!Z_UoN&25T_9qWMp@*f($lHtJng*EfqYp6BNYIE*G81{)c*N(P6trkR}% z8HEc7m`Cp78!4t_;riD@43M1%DPU#@X|B@!jefXs5X%`-5r~0g@ijJ`Br;$_iNUFO z8B(LMsm{Gt14JhwO5u3hc!;eck?aP<9Bm3m&-V6a^a_knTC^oUeI_sp{Cv!VM#K@%`&tzU2?pv$wY?X)%v zyBJXbD$&}cni;hXl*V>6z#Z%g;YI*9-)I|ja{SUPn}Bjv@X6p_n+dLdP<=VF&{>p@ z%OVL;8Sef%1Y~H0&EP@;VB6_~*PV=r%l0hTly}7SE3Of!fyL( zg(YS{;H(nJ>Y0~F+@R_`F>a#Yq%|j}tPn)5JEXDe|X5j!hkXpuLS#+2bGpj zvH@yzaftiLpvAZTTIaWvPpr-e1N~A^Lm>O1K@!MO(82DviL>8~&=!wx%8D4}7rf?V z5Yv`R09H&RQse2ZDnQh^NEt-yTci4TLjA*L+`}xJ`rEs&8Wg2D>SkuvYnVeCMcehhXHtX_ zIPjb*Ippa{Q(lk4sTI&I8Rvx09Elr-w(0q@F6MSf@a)R<8p2dh@U}bOi@D`yG=d1T zLrImg3THjhI&LCM2gS}nhoXe$_MIcB#akoU4=82E+hzFGKd2?Eaw3d^{)VE6Keljj zoilW@nouv<>IpMW8|&(EcEEo~;hozRT};0W`uB$Gv(uSTfU(6WsKJ)XaB(Llst2yG zULYkZ*3bNgbdph{;S+?YQbm<1uLni~#a%R{QLCOyucT4V6Soln9Z=>ckcdf7@3}lr zr=dzQ(Lg36#*d!iiPE(6z}bstf_>@auJh|0Nwa#%o7)jxaG(a!5eZw&b0f*vZ3nbO zjiBd@lg#_Xo#c)fuurCy)NkRfe+72ky|dN@{sV&9U1P!ZIZueHq{ca^K{QJX18zf}ELp=R z&5U|NbQ-`6=UFHrVYIE@_tNS3r)|T4pq>=OXqeKg44Ia2SK1R=W`(mzY834Hw)y@7 zMw^zw!8m4fCs#TlBdoe-lQqyIiDcoG4^s{~np{9&E(Dv>6xEsd-m3m>UnS8O_L)iCr#!fGRyGU^e)63 z8WB&3)@C5_t3(M>unJCGq%_AjZKL^Bj8mZo>+C~>e9t|@D$!wQDgGN3Ap&U z-scE$pjF@U9io&7uhyFPQ&uoY1ze`#lY{`nACy&z?hO}}VV+@Y<`+6c_{M3KqxX@T zt(97dWcmWD0a(?rtAUUNQ5aeJn+(v|(q+W>aJ~ldAw=slVtVOTx~bdTl{=3D$M_?A z{#mlyU4Q2BL8tExdxkHElWSOhdX@`m9R;YPgQl$22=7$+y@VVLMv>1V(RZz+mI#yM z3&xpQ%}Pi0vru8k$hrF|CC+KJUA|udEx%Em+&r>W$-fF$a0%C~4JZevX5eC2L$3#P z7ya1ZkkgD%szquhubEk%Mv8xxAl-;PHddkl*5OHdlo#9q|2tP-AFy@f8*TWlr zk8vsn24zS*GjUoF4AaOVG0DrW0z`8i(Yb~NlBC_}RjTDk*-X(3Xi!#+B$I&pFt}f{ z9{K4jHMh^j#vX(~bpy2y0_TkFD&U|qioWYhMOe-R@u1%gVt`A`dx#s!^!jh&lMUhU z9P0TXhg$u_&ljoH5D*$gWe`R1TyrpnRc0?#;34~!4pEJR=3?ACVY2w)Q9&?6hezUR zvqx)?Iz3!_+{qxVgh}yRg8#eD7B`C;Sclou-Ze7s*pux1C*fsuB|rhU@QfSdblH)5jWx3Eom#PCw1 z*D(6=c^|I31Qfvu=z8v>)d%$Kz~^^f%ozTl$}n`z#Xay(boZCvl;BiWf>vM z)HiLy13<}7p1EpBj_(N1XAbp{ox<6{-+5_10J>x(A~-J0tWg=AROXRn82Al|V%{^W zNlH)aqHw9_*L-^$qf5G0CIk=aG03DGWPaq=_{>Fq-ifd2^i81hG-CdCk&0Y}RK4=D z8T1MQw;=kIb!>nmndZa@P3taFaja@(wf3>)tR9(tfBci>c&US(4^D|zQbX!#GUTUth5pNXU^5da3MFuI%wfz2R!?DK+bDnpx`b2ABO1{su+ z&(qi(tv(Y>7vj`@Ej5E2@0KJEx$!mHj>XWtooEzsh#UpG)`Giav}-R4=^tZ=|-cD2xo73jIQ!86j|LzGleRkKIg|LsL@iw2vJS zk*65}(rsIVT1{)(!WvqJGnz{76APgx`5c$;X>oX#%ry*6~o?ey?5Ve+BV%X?-@@hV;BPeD2JH6ZRH%Jn(VximG`u ziw!um*)7v;X8S*sXPe>Gb*7N@r@-^?)`I7IE=p&CSCcla-ylX)dwot;=*?m<;V^Tj zdT-c{5pgx?AA{~n0r1%xNTc)cHdpI5zDB;Fr-Fceg$%&Y5!1Y<8mx$hv~7I8W?{pD zM*TW>u`eRQo7j@Ay)y!H2zVZaG3NkEX+li-z>T;G*$1VnL#Z=(c*97y5KXv+ z+PXk@ndTx;UqYK)WcfUauJ5r|t3kIDy=>@YQCol7kkA!R=UfyiuBcp?TC53TB&xJd zR)NJ>D59ZZh3<$BP!>k&G!)bgOu96@O$Ft%3b4sEsz-+|#2S&yE^d+-AP#ITz#uE9 z%7bh;cD;>WnCa=!Yz3*D5m74w3L@Ugua|q7tJ9_|zim+la!d&QLJ3 zK1IpjCom9fSDm^*FoQHeHlb!yGX$$nbh!PMqhnFLBz>VkI?(B|Mv@SOTNNb8mEP?O zh+zM_3ZE!`PH|oyZ=0JvA(g6w+fZ-S)azD4M_qx1nV~jDZFg}R_?kqpQ%u{5S)xeQO`Z-HEv%<)$=mXCb+R#B$Mk}ee z1j*=_=K%{EP#?8!J$-*)4C3QsqU6(>PHGBlMfU^^b|g{OXzP-#3!AD**p)wcCe|74 zBblyiVa<-~>Oj=Q!u7O?2adH_Mo3@yw$uHJ)dkFn2F(@Eo5N2a!dMq$GJHkQcTup( zQ`sbPQ%gzvw)Mnm+hrp^U6g!`>SQhY-fgY)AC#jbY^^gdu6w>B!5r&&t3pkpn-Yvg zJ<=R=?qQnxM4Qd85cv$I9g&yss?pdr8FD?gD=DDUD6lEUox{`~PSYXQ3k?hgO!-Xp zGZ-xf?6m}d>R$4iD$0D_HnNt)=`klzU~4e30wcPZ3KNx6qeFveGVo_qqMud+dzIEs zRT1P<{knkm2SV)P8tpr^xxS>)X%i2BoPgy^iy=yTF;Ju{b6v8uLO;-mezykmBaF*%P&nuXji7ZafgJ#J0xQr8k|mo= zF#W&+){@MI@gDMvZYDh;a#7|_t7!adt*Ht&7->yr)HmMHNcT)enkpJZN40FBk;0ZL zf~lL7Fpy(#yX>M(j3)fj^sA+beYsuv28BqKvdSx?Ff0ZjvwjYSCd9$fcEcflF)=<# zQ)n*)9HHM{LA6S68znEbjU*(|rbettl`qO^V6=kqwo$s~QTxpNyEMYy8z<&xY0Hy& zZq$3%8M-)(>G+_DUbz64w9SSl6sl;EM49qMxR&c- zfP7>IAT!`}e}WWX#{9ICV5O6~&3u}$y8iK5@DAb)OR9s7YrBvFpN9W$vnI6p{Xv-q z9~?lUx(2Fd28;-e2u{bK%RI=rr3BY4D~+#%e}C(%30IMd8UjlF?B`TMIB0Vjlfq5YfH^yTWR^=&2KOTu}0r4$iIMw=I9$3yi z;F`ads!k()gaEO~6>^P`2b>oUFR;tOdD)vA=mVa2a+pB^ z$~<242*xeeIe`@Rz~9;>e5pc0u>I-+5LmGVSz=BW+L-|&sY+#4;$lZ)4KdeTcq#^S zp!ZcW5ri^K9b{!x5ca&|Yho%1o;7Hs=i|QDr#FZP(+F1of?QFyMZeF{3>I4x>$%Na zp;p;J559c;QeoDK(+M!8f(lifwu8OXaVl50K21ikyR5>O7wA~aaWR5= z$0prbh|yF3jCtkef)NLx*I_)|in@mi!U=!~a%n6L@F_V)W5rd8Kqr-=1M}z|MpU6_ zu_oC>nxU#Ih>nkI+sI{NDb{VF1aj|KelR}gvEl%w{gg8*Ql!uoc-SDJy2s7$IRZ>A z)*pW9d}IlH|D0jYqw9-ZDhbFEDeJfuM*cxq$s>Ogbw!wB(Qq?HQMbv!JZPR2qYR?A z7L6#z2p5HkhP*98ssS3L(+FL&mmG75x|}A#L{@S6!5k$;oJyW@s!%m0ZCF1TBUo*` zJV4^bem5{b5eCBTHf}!h+}O2hblBd%)K<$L!O%6TDZ>-jw_3fTvHB)EZZYT5jAGHk zEdbJSziS5^A1O$^B0OWWXjqfg-ti}247imqXs0f$|+QYz=(F|hY+fj406nfMI4j=BT0 z!83ThB>N^A-VZo#07XeoMiS$4d5X6>_FdiN2qI!10W$lb4jt%@Vfc3D z-2xm70MX9u2ZlKv1ZR%zly_?6>7VybKu&TPVC+znO+NrPbu3`cxq<=6h#+`;vW_Z0 z^+%n3M{YM;>)J+49Ti4-q=EW0(3OmhPw_v$X~`W8`)MbyNj{Zxc5)K|Sz~CX3CYKx zC>i~-SzytzTJKI15|9f%(QJl{Hjt1&!ZE({Ot!R+*F;j)n(=faLeqiC27uvHaf7Ac zvZ>$-mHSCU5oyqpbjAJuU$=$N?4#bmxP3Iv1Cl*c$7ar=jC#rPAfh1oUzmXzUBE^> z;72-^of>(Eaqg2T-G5T6OK?I*9pw&U+Ss?uc@z&$w0^O*4sy}4-%N?ecx(6JV72q4 zPx4Z;*T3{C3flAV=_MoLSUn@c~0 z^LO&(9Nf&hzDCljM`G0sJ`adGk*8hWB8@XdSfrCuoU)087!#eijlAmL!OfGzXyBKN zA->srdH)~xU?PS-zV0Y7d$%d2d=lShriu(qjhw<;4(vOT4~_0^8zJssly8c@BCoP% zQ0`9H`|FM4^)-X-2UbfiNnsNkGpra27-5 zW*8-zGtBVC^Ox_TeRrI(eBTTVy=43opdq`S z2kV25L=&9l;3)Si+C^a%KvQ>20S;XW){_|Ra>&B~U+Ls}=zx_e#=S3bzX7JMfWN6| zJ^)SBp|J+;hcBsJV(1D$j@T~k=*-xs106baA9l>fFzv`QnpYb>Qa1j`NDsY23OFngQLwS)qgQ28xZa*^cO%U(zP3? zjM*5aLyjIn)eELEma9RqJ>+eM6U$OYeXU`?L*3Ph&oN4p0lCUcur_czKq`)NUSP+= zx4xsfJ0Csf%#1P7;~kv6MN}kV zm}ZT;OW|I)yF=mb?i5hCySqc-QiZ#_ySofmy86e$n*h;Ct*IM06ai8u8;|BJ6q2-7qOKfm8tVi=OvJ;Bm+|XMDy@ggbEVLQ z2RF$syAY+A@%^0LIIvkwf+`yhCCgMb-)!i;&zZ~(c={>RmbjP@tB z8`7TljND;7i`A0HKHYc&cVpV7S@&t?b8x~2fhb3YoTQ0Gr`Ia>e3L7Wg~HM8IWH&{ z`@15$YHdZPp!70;=tgH&t}SapF{_RnN@-*;;;HY#Ie$oTnFHA1hje9N$>E-iSvt-hV zmhS~q?L_;%h&!t8bqVEWZ8o5xFt9f|RN9#LvSbXSf~VU3Pyy;$qJE7pfIP zTkKXZU1@r%gO%T62MY48rQoPHXWsjkM?wK!xdK$N~O0v{f^rcfvSM zGDCBY8yJr@s>2Px>msEk6c^kGk|<+d$~WqDm6En{0rU;l8Gf~QS7__42*udc;T%Go z4G(zT=ekx>mU7gqev%wD_Ro32td?v}C(t$i5}n6Zw%+3V3z6qD`*OA{$UeC6Z>B52 z$*~G=o2c^V!dSM_KI9ST77< z{eiwMb?nGtHz_eydHr8@v#eiEchuzh1fDu|`~1O?R5^7{e=lQHn&SR(T&_YfdJD7p zTkpnv-&SQ-d*#@4TlQ0X8a;V^rS6K79>LIl4c%Bs&v<@@2We?-I_KF#Vz{w;nk{Cd zEbwl=7_XG9MDgp$WQ^%N=bBZax^oN4zx!#vL_b!UZbHmt*k4-qi{9uAJ|63gR)+z5 zXZY{-u2kZVwpalk)CJqw8PC`9wyRlkh3>_mrq84H`C0872}!JpY(k@ zGXwH%>+b7TpWa@)$Iqv(XAimUomxN8&H4Li5P(nUfl%J|4e;@velOI;^m`1IGIigwLjuS<9doJw!7fgnXd;m(pCUv>w*tTnZmyDgH;; z-xq390e|^Gh*zf;txIWjRx3SIryqAS?=h=)l$38ax)1MXQotm?LOg0!^>#^eNvBgq40?#?8CjVg`@EC3<=HoM^PhC%h%30CosAu(9@j<#W|{{d4D;_@j>E?R}?shh+BM{bv1{V&yGO@>!|t zshaC;ly@*&6PFU{*R7U^d<@NlWW+PlH5#Vt6 z+0p*Kv2<$WQP^uh_wjhM-j{9Dn}b5o*8lMVJPlAP2z>M9R}p; zZ`MB=Y!Y{T)NG(Q`DKg*{~Jjq&m3G&LykMpWy(F1vEH~$UA&WaynI@-Gu2WfI;b?d zq($8#YH@i;e~8gh12MX8lI1=EUYbf7`L#T!*WB%k!`_O34}SN<$coih^x@3DZof{* z$GZSK4cpu9r=SGhgHf&f$M%@2EQsE1=VqlxdWT5TbiJOQ0H8yn+WeQX(P&%PZY$eN z=ka}oIVP1bqDo($CoBfHrJelzXcMR(ovdgb>bpiF)hlnZ+;OGzhowvNs(+}xZHLJ~li?tWoX+S_ZBTx)oWg5y@= zj&Q%JMe}SiSwQZ?-&3z+;BAy4j^Tq6;r5{k=H3RrBl!T}It5cJ9Qfy-apbx~u$2)W z0)zKu0JEZad?Ts9b#dy2j2jF0qJwIg-}<=BK{f)=gsKg|kH=07JsU~041sDfrX3Nc zfDiDto;W=HhptwtD96v4YK1w-sAyru%w{pc{uHyWjpRJkRJ2qUkkvBw=&CPkRR(%! zs~@<5O$w}SO!JjxasMFO067!v#rtK7$6eyn%-_Tv9qTMG2;H^ zL6hLXmwYN0tJY+)*UM;H52oYrrf=m_Am*0OXRK4IUHHa}O_#LN2B?)Yq3;S=eDJR5=d4lRCUY0)hn_Fci5cq$1Bb!l3tC*a?z~)^4o8Cz0 z|B*LR^}oE4LH|E*Z)oyOu z$#y#+NqCIlXpT5=1l2+@s3L+PQVHur%YWe8Y~Yl|60+iaFtNm2rXj4d=;%?27|~@9 ztNehSt9zkQfM@*4$B7`Qt%hAF*CA_%=Ml*@HH9buOfc{r46Lu(pP2$Ggor#h$N!@R z(y`iRu-Yo(S)FVDTa?`JW1*ec1toG}ks2GA?#lLN`!IdeFg22+4QZnE22k|(mFFEi z{O-1+udEPxq72q1Ym}=zF9uB8B1B#@Sp0}x9n0(K;clqBkPwd$)lV2F$kqH|338Ya zvRp!<+$>^}Y{CzPZ6EkiV@EKY#ZPz%A_V?W=5i7p?e8u7%f~qB$KTRXz5+9&uqRZM zpD^`rk=y&>b8-1@^K&vX2dh6o##ThcTfJ;7kr$)8PwZkF5i6A$;V@2vKGdbfkA)Jy zO{JsNQdnD8d7G9wo0eC@NpSnHSJ0p4Nw$#yygWjTHAGg!HO(m1IE%Y_;mdLtPCjSx zPw`3{Fj`;VY5WEwmoX|l%X}X8oFGk2G>EkXOW91}^Ys(63wK#e8o12Re~2O7Ta?PvtG+$24vf@#dX_yi{@K^@Vc zk?aM$6l??}?&ZLM$*%>^SuppDhMTib^rK^Zeo5&F>R-r(dJqHaO7BmN#9Ca&&uqKX zko1rrtMeUu(EE2Ax&mh}Weq4kuA=x^o7O`$sa&xY;m=syv5!CDCEP+w+WXxJzrn`iyEpWCcJ>yaGmgMpTt~)i;XWov0Kd85{(y zw@wS?r?LCza^Bjs7NA9e_SR$3;SfsQe4-`2tlg`ymVmP5-#VQ67Lv^$0pT%l)b; z0p)yFp3}qxEHN%ds8)feR6r#u$xx3&%4 zkwOg#fq(>l`1*DIG|VBswy#mfv7r81+SBuutzA_$17NG$Wn(|B{cm|xePaH9S`}N; zjunvInh7daG*VKrun&*Se$fz#sEX=eW|WOB(GQ3Q8-xG*azm8({Y45b(E8Lsw115;aGB*4HKBslS9E;vOSg`#h(TIS5 ze7^DxX{d36`r+vPae*?hY5%fBgf)XX?G)qE(QSCJmUKp}UH?((KFHrqhs|yBo5`j+ zy2UFJ!NdsS<3i0Ai8W2Hfh%_+@pm}cSjOgxXET&vrTnn!jCXtN)}sDZ9=TO*xi0W7 z#AShpK=85Wp9q1V)fa(HefFKOJo7_tNq~ScSQ)wajNTy=G8H2J5uVGq@u?@NJPq;! z+t8Z-wO?&oZ0?94i&=tSN&Ca1Mua8306Jt*>K049m(_g{L z5}(pYY)r#A@$iMrwd9%7Rbuk^(B5~xz3q>uPxEb{22?3&3Mp&76DthQqfX47o_{9- zVQJ@*T%8wmv3AbgM;i5GPOUJ#EbRm|QTFzrjaSe&;JV%Byze(YqV*DkhaGh&SqS0j zQ;vkvhXg84iq1p5*&lP72v|^5{74?QkD%AIYPO|SaVtzrk0%R904Mka`qTb`I>nJf zJlAHvgB%vcu+l(7*;S`sxl4_OgKwod_6-#xL%ISu4f}bj7)Iz0y`4BKW|+SH!N5r# zIbnFQ_OhBf+BAdGz|G!s1v6daceIz+{|HqI9T@TcDKt2^vX^Pp1bc; zv0GygItWNKpB}|3#PnC>x?_diAeeaK{-H2k;qRsGxdke=DzCY5nHaX^xhd>FU4DL^ z3ES{Nz0OR;=On6Cy7CKlXrS*wlqcV@XR0$y=BYzf19b_5PB+g6B|avf;v=2HC~j_t zERPd@;jk~{VXyujN@&0I>$*FvbZp*rOa14l+0Ym zh=bMZJ%aX6Caru0Jhb4o91&#@`Xi;loPR)fT?-1uCBSZucv#iCxG%CxOTJ==AcghXhj)i*!>@M_uWBzj zdPc)fm-K9L&Zr-b-#2`)?T!6drq}F{NAsyU8yE-TRS+?e63Lm$*-p=d26EWsfAip3 zl`w!E!Wce?Vpsgfe*xK|)cd6X_UmF{DCk#8@dRIox|bGMe7tHux=z(w?O-)bSkbtT*;ramo%E zZ{b!Rw*M@S7=o-IHzQRPxe@*9@}&24U1HFE4_{7VXW4oF>dc_0Eo|(!isRYe(VA<> zm($;8KMT~*%Fb3_9z{5jGtnVj);g@_{e4bI-Ot{rMbq6e_nf25q(UPm^vxPSo%y|E z%N&`rFzc@DIpX6^$)2~CwxQuD%Xa(c8+G%#k!Acslk4sT_3ypT<2@h%1NYE^GkrUA zqYU~8gP7ThWSht%Yn{=U2pwG+NrKYwT?MYfdFgnrF>*R-5}ox`wLTUTk@Ipk`8J0O z>w_h6+lf`n`%tdjr$sxCNum6>F{Txm?_u)e?kI|jKx0j6yg1`>X7`=RTCozOcLP-_fOGOf+!^;gv_Bbc@4c@2Itp;_xgY4wKHk5U0Eex4dVqewSj3{ zrvIGG?<%Da2nZZ%-+6-<4wVN>xQ91rS@h=Ce#@o)6>jkxx2P`dF7-8n^?4K5LYUFP zEw~Nn*Uu+&|Il}?#{5i@c)fk56nt`+w%6iMvyZdYx2nN4fA+&AasPNdMRiT8bl2x56TEPt1f&<2XsaG4l35Sv2r*H?JW4q~8VQ;q;r#XVL1lZf7agVQ6EUF1RF zIJe>ko*QAiRB(us%U;g2#ZKOW^tGaAN|YVyTinp<_K-pjsnX=y_;<5^s%-8Ee-HoR z!g7qQvSFH|gQ!B|y{AUGbp|_^(}p5?`j%&OZON8fTrWRsHb{PQYVg;}RK$yPNdg_2 zF3cu*cf0`VsXnxIr^00Zh&=Z*>lF_ChkZlfH*}L+WV&>+t@bct)P4Pq^4NCREesh$ z!0;o3ZPI%X)Z*GCN=zVhn)yTC_)1{b!J6B`@iHof5y|ll%iG&oq<~>0^aL4|C*LF9 zq5EH;@#0BOLtf|XpKI}(EQWuX`98dGLN&kCU0cP=pxf^$J_?~iS$Q}l6K@1rFU{ax zYoIGFIx_LO!qfkXm4daXZ9cOa1tCH1O@n1jvD7{6ablZB_CXv3cLb zOv<0+OsEafgH>Y+aD(@;&PnQ^*xtx2B?NU#GvIqG2Bk9z4DpVR;!D=~pkvSpzWhf4ORsn(? zV3OiXPnO1DV|S!?TozHI&JA1!IZJ(n7p0A+Ye<;ChE`55_?iO>{X3Z5T!CLDDs%ok zsXk-n6AxS^$j^0dm5+s zo2QWRO?dK6JdjTO_%4g}W3s95H8M3?3DPpo-<T~xm%+m>X_M3LZ?izE$S~&DS4>ZNd2@L}z7bCh znjexZ6j-xpI(Wf#tavx8Q&X##ZZkjpc3oU;g)H=gR5wG;R=MAte*JaAKIKVLBfKL; z8OAdzoz1l;Usj(RlYT+}ICiHsx_Wt9Cr)-JNaD^oMWA=WizlCx6zv)H z+iQ)+yoh-cqd*i!qnhX9xCln7^IaI?I0xDI!%=~&&bT~DvX4!&PMhk?m#Rqr*SMPO z-jwVac!^_xJnwMHjZ5)offBS{ssEBvcT#ExMzZ&n={W{)jo7Jrwc$3RmsdL3e8o}m z)@b@u1T#?^+Yd|YNuss{qtm<*wZcjzL1FXN0O|Bui^r)_6T{hrt8vdikFqnu2QYz} z3(SLkNvtV*5c<6Luj%(I%4DLZV66%A<9dIi^mX!58^d9r8T*5HaOwrtO0nf z-@=Q1M|RVy5ai;Ms%&p>9}X*jV#6FOysx<}+cwGa&XE{@**~gUopl=;bj2Fh1)cd& zT~yngFH&9J#n@O2cvq`7$I>=4;@d;3v`mlIFehsL#K9-vBy*fvESUgN_GGMVvU@Z5 z@*L1@hmQVlaxk@!sbeUd>`;C17;#U#zql;60#4l$1tY4Zq+y0L)>2Nq#<=bi!!7z# z5y7iIlWx1Se&Y}5cYIT}8Xt*)xHdvJ>q=8fhwqw@-ASIczC3&vxcKeCego%m5gK&0 z?(t{L6U>8af1Ob`oK=!U*jfTifiW-~MWc*Qt;}7$tlyxhT(!tft%yLkXx3n{b?^sl zf#2+efb6sMe5xI>a-Er?>70ViKV4gHHQNC7%RYnsBGr8pwE`SjyUfeurD+-mGF-#` zvrz+l;{zeFv!*SJh}tg3Msv)apX`!>knNM?>JtUF6FC+>10Ut~;HT8%lwH|1Y^*V0 z@!1i@Iz5+yyKX7~kLNCWs6dKwFt-TR$t||JZ#x8}2TB*t0o+`Bzp)biCRgkB)pTA``=~4( zi7VZ(Z`oDK%xL+Cud0jeBKG@U{r9~6bS+PcGPt!DTys%W$ICT%U}CZc(v-9QFxklH zFZJQU!BLOcE?)+(o-3*?7WJDyT77V|9pm)w`m~7@-x*50=317+e4!;Mw3zYLExgJ< z%kqewOAUYi5u1*Ys%TIhO_QocE?+FkM=u#>-nro_&d+bzTaX`o%|$*JL1n;A)a3oz zG~UE1%sJq|btf;YXV%F>z}ep7v}050-&c0lR(|zZKDRN+|4(ytrtIvg*nIrKyfgfG z)Ua8}wi#{3O=CDO-nq@s%Q^?yMoQ}(CWdo_UAFY*R6<{h9-o&;f>Wam%R-~YlBCc9 zwz=;}x5-vdc65$9yqb%tdJjhrkq*1k>&tfZ=C-nt*J5`qV#i#di%q9@!M#3jwZ1mI z9^<1si_|K(Xg@3CVDsC7*?WVDZS!-**{8pAS)cX3YhoVhdPLJ%Z*uM(Mco!9v^#mM z5O}Tjp&r-oin4wxEA`XPVzYE6DK{Hea`C$465LeEPj6*uye8~rCUrTkkuTeFMG-ms zTD121ezqfivhxbI!%Bm{b_6?XRL_vZ-n3t@j_RQ*)b*~Z^>)v}sHG9D`V{?80-jpi zaH|1rSTAbNhZ*n00IoUV*SR~a2YvH%1xvsBun)A-Th7+y+x1dd*XnHBQt7OXZP~i1 zzy`s6rPl9mz|o{oGL$L1S*_-EUqm=K-ZuE_Ce?i`)5vrXBYtc#k9i?R^0RGU@X8y? z=XEzG(v;!`tkp?rw3phNE8TJhBNIVn6Otr~kO>=-OdTr4tON~bDMiXb!l=vSEYH>Ww?S8n;ehPwi3V;Rth2E+|McOH*de7Ds)#@n{vq*mT z156HSo9O8<>f&MCIY*o%OuX}GJRVLwHfrpxL)=ygkrYCF$nTNt{DW-x!s-!J%g^%# zUjg^E4Us@E`F};FLFtXxLprg4)e0tbqKC*7dH@Wa(`2)kRWG`GbmfqiY;)Ng3h&<1 z2SPbroV0xNetw~1ug%FSun|$A$!jg7fEcIS38&dkfJ{8RcW%UDB~{f6gWZc{xkGG8 zjFQ=EH$jeHZZGelV?eNt_59}}KfaTS8dhyEbi}j)N(ig>>Jw?_4{!&zc>a>i<@gH5 zT=$dC&YCdMXtJ1WNnU4n>!tOP(K=W#2EiQe$vOynL3XG3da5hWMSTK$Bob%0MS{?a3`2BMHFy^UrUmiF7C3U42L! zuV7*IQErV<@8GXw-46DuqdZyT-B3?k2#k8WnK9Z&!ORc^qytP{HJ|C*b?JA`fyeGI z#~uz-pcF@sw0wAyLg-gNlpany0=O)bl8L~&Cb=$pvU=UDpTve)MjZP#V652g?5^Zs z=4fyBP=V|YiJdNq9`=bKSM(tzs7my(%P8Oy2kM>Go5xIA{M;5RtmsR+*<#wN2N;4< zTFPCTI$ydvX8NIGdePP2>)32s!#q@Jb2b-Je0L^-S;ov9rp)_V?Xv1kR!S5b7G*1z z1xv6JW%{R5dK6%#qID(HQ)P{k=GMKYAivg6mr9}1N`IgYxV4SQlMQNaRsa36=&KI+ z3JCE(IFENnB7+$o7}z=fe-k#j{EuLh=6}JakpF^Bhl^Q5U04a0U%W{^wNdqd6DQ5A zMM|2e^fp@Es({ zR=a3X`~giH4V%hD1_yxoBO-sC{ zl!rxij)b@$D$~eVv{03;I8f+=3>M4OevxPtXdLVf^R)MWgJmvbQLHS0DJU zC}Q)=blvzpcLc;pmAsn1x06e~$!;wXlw@DkrZdA$BZdg$t-7N&m8ZXf)7h0k)u24M zKe?mKoSz!iA{au$6WF_jAQp*yYrpXBA zw&pH+9)|!j(p%6VTb(H}J0+rWa{#PBV^#ST{E9kn)i$EV#nrWGU{p%WtZf9*5W)Zg z?Ki(S&7^=9H!l=T3Nf8qvTU&`4ORy^W*A$q8s*!C??9hPp&L^WG80RnW&MoD-_0!v zvgh8bLWY}rIpJprEW8+D82fk`kr#~qO4DP2_=Y@6-b`3_wM8v)aM>(4HTYG4J8-knnHMgc7 zBnD9;B<%tCP`ZcM1y^%r7VYIGTY-n@4JQ&_t2YZs_DQ-5?VFE8}lsT>t!bWoh?rIKh_tK*jxHr$n76 z-`(9?oqU%T)QTF)!8QA956h!vfb(j;P=jmfL)!8?Xh01!q*0Nyc;)J z*q+#Dh_|l{ep;BgT?d*FjF4|6#4mYu6FGFyy0m-MzQ0*ET_O8NF)Ge^5M{G1^{CE5 zyj3fdxHW-`7xGddEUg>G@_K=t4R~?f9rnP8+I&1SOKevKM^UzUX#qXLI_ojF+kkG2yFz@$ z3#Y4@AYvYIxXX-~Z^30Bv^<$0Wn-;4GQfVXt@q&;#r09d<;rry^FYlLg~9UX>$4iJ z^xG7z7^986@~z6wfSClUiRH7diwMy)l8U*QCGcY$BlUPe7KsO93OMD^2tbJ6Jlx!c zVb+A8&uCL)0kuE78R20*ko!B0U@L(_U>{HA;g2Fghu=x}r6aqrqzyGiWxqirhZ#W1 zh!v40sm>>%K|9gH=_`VxBtm>N_Kj})W*O|3oM(Vu_?Bxz<~#*Zm}v3P&`8lqDS{rI zQd5Hajg!!~jstIdwITfhsy9P; zxWLJ}^xB~vcp1|AoFphHMO9e{^0PDASJSKgP_cKDHG$H0KvG zq4mPkp`}H}!xdLVs34;*ts$qVEQeQnA6&Y<8>Z+=C==G7>TS0Mr6OwGi;&%DlejP} zA1GDg7S?-KG2_QTmj4}>kq)Y2F`QWi+u3mgQmdw*j*uKxorOF?eHg&;9D{CAQ`0q* zgU1Msl*_`KQc^1D1A=!l7JMy+w-NZWzzPaxUaWKBw z-zTnNB8JnV90v^(VSWcqrvg!9l0c=uK(<7){SLQAke*dQn@|IbXd@d~IFLJWm&PM}trSJCDzgPp)cX z2H8Ycb`CpT{*Mi*i}Ra{tID8)nkO#>x2493=(`406B#F!pCbN-_A?7#)GPL(i8`L7 zSjg8AWH_;PSTq#+!tYeiI;WC|zV`*a`bC05!2*Q4YU8<4E{@LjuERw-JMyv;72!rV zP+BklH}_(knwD3H{f&hD@aXE+)Ewudpr9e~di?uBHrf$aUBpmPQ}XTQh?LC#s^U-C z*q6I{@{4+*fhhkE^@_onX#}~&U0vd7elsswh(#(@%*iW-gaCu+qz3C?~~z7)GBQ$9HJHb*gLV_B%B>sxoJAWH@)+8DeHvppNnP5 zDs7m?#eQt%PIhk!9PGV6Z7|*9k|un8q`A9ePcD%uZJN^=^(G75t``7?Ni8ep>(!R7 z=UTXZe0K-%S$VuD;W07)r0^tbjfk!RwoFRB0UsHXke;vj`l>b>F=&-dGr3VFiAGsb z<_lP_7%tlwACzf!Jj}Or0hB+U5onZ*xjyeOA8doAWzKB(o5P3KitvBE(NNV$jr286 zL0P$d`vvhQYU@4pbmjdVht-J05;5J+uZ$V=L0>xokIiRQz;~S!-z>KV#Pp$-kNAN^ zrlIg12-`ppX`uSVxKOzF-pZh#ek#i4-}M7o2sL`1?-gaYj2y6Go%OvzFIsyl9*4<@ zK|0Mym!Yec_Nn{y?!&EPp`P50*0mLw-h+AnrwQYfPuFke22Yf$V}hO_Esf5)!_s6j zl{tJXEtfO3k+tE6(8|AY1a29LO$KA%8gf5AUXYYsF7PhC12scS%h0#M)u_BT+}`G^ zIo>CRHan`bfx$Na@tB$(EtD@`;WF@lf4{k$)5k2ZG_+-_Bafdj8-xkBgN*nKl}vb0I_PFTDG4L)#`Q>naEG zNJ_E`RXyYv%PaT|L!YRPmQJs%IJQfl)~!29isi@ByThnJI?e5l>GSMO^y z%40~?o99D9jkE>$DL4JOA%^r{R;MCLivvsQKO3ih{w3EjT|2CWX zzu!4|G?8oxZ3xaI1j~hex((bCvi4{z|H)_@9_(~VoOgPH;iOH4^$n8W?LHS*V5+A# z_LMsdwiJqVQk2|(+wa^R7?EkbhI@iXXs7G{PPtD_zZ2c^RM~KnnEz%J5zj+AVPOr} zamYr4V=g|`-S%^Qp3BNJ_VD=AGuutgFw9Mc$2V`-T>gw2&BI~qDxWJL|GfecUfcCT zx5ZXqS(>S*rE1HDp!K$>oXv=wv~Kce?Gq~(0XUiHV>Tho{Pi7Uq zoCuO?V_M$RC<=&MymPVWy&T)!SKJKEd4TnF=%usrfr=Awd?>`j_J(7PRo^FL`8Q`n^OBqaU6!>e-Wa8gVxhINY$xapLpZ0o9h+hiqNdN4n*Z4^)rp`6- zOKm>GY%0|`ixypzfaOJId8A{8W1DKWWmDxHo2r)cRZg|dG?p|>)QEP1Dd5k~0m`&( z4Q7K`yjg~BWcYZUz(Q0Za^iLk?6vC>Z6l!und;h>wa#Mz|3*|0u19#K#g#w*!~O@=&pEBr`JyJVxoGVsq<~RENyyJrabn) zE2XM>E-q-B>NgsDKf~AqyItD*lxB^o%?A%Eg>YTZ_6Y@KZTnW-Rh=-6czLN@TWdit zQ@e3dR)B}6J-L&woTg}VgD7s zPTy-Q0e^fEr=6`h2SlBi2c!`tezZ2FtuYj2xD57HGJPKQ$07dTSPiKS3z>(FtPhXI z=X}+zwa-W4qeW^Iqjn%vO zQtC#l(>>dRvopRg!TEOcP<+?BGxyVa`7|D0A7K-=XXZGBnJ%cq?~PN~>TUBouwBkz ze)KyJDiGzF{XT6HVH@CTe)^V?;Ny8t7%;OBXD@R>_1h96bpI4jAwc`}^E&}iiC3;X zw~6H)U$?2SBvP|_WL9%5R&_iYK@_fiJdlm#6-Nw9NRv^huhf@dvfag}mNknU?Mp^r zbxJ6K5o>N4%O{9&^dlB(L}Fu?BK6;AQtX^T-yeqoR@qa_w_Nnnrh>EMLJw2Z^)u5{ zev_ADQxts@GS>RP*7fs4mUAPxYBL*gS+Y<36)0Gl{8lRYuW|Ly(dY{Bdk%2NPtg?WZ-Tb1o?}Rgt;AW2w-O1X?@hX#a?zg?=9nbj z-SMfpD7F}4oZqdwv}z(lFKCu!HncYRoKz_GoH|5BI}|V|KYx3eu=>m^#JnKlU@DDK zD|%2qO;c;eoOYQ!_%4`juOF_CpNUBBoF9un#PPLokZoh4K`IZCmM;9GG5Jo!D(RRJ z$fs~kS|XL*Q^Y^5!#^#Sh{&(FlbMyakVwBg#;N$-4YqwPbd#L4wL05_H~U#CG2&~= zHkcjlSbjE)kN0>gVb7NjZ5T+HgDYsvWNRakTgl;2CPij z3x{|0h-mk@?)W*n+0DM`XSey`DgfcW<(7#NGASU+N*pwOoyanKY9HUA0(_BnuKC&| zC&4!?>~|^imf$!NX+ENq9vc^^YnuaFkugPgYrez(%kxEm+d~EqVJxl&=O_DRh)TJOw$#A!AIM# z8p>X2PK`K~XoryiO)m7COt`rOy>TO7X$S6z$NEUU&_%hPH)tqpG==b>b`8*;6J2aW zD{30fa<0bWd$0X=ZZ$wQ74Ob>Zz1|rizKS(fksIRknko>6r%PcWB$u(G)@P=lQPur zTTsb1o4M_>*;w_YiFwy=vmNhKu-7>P<75s>qkQ*TCCM@C=8XDFy zA`B2FaSbg68wQ;Q3$w8MneUTW-?Ha1B#ODahnhyNYBxx15x!$icXmB<(l|Y35-p8@ zFt8qeI$-Lkc_ava`pD^?x)Exo7ga9;+Hb_z_as>Qp3$_X?iH14a;br7Xw5A# za0r7ow58gnjdfKtfD6Gk5UkgQsUm- zL^zmzQnvC z8dcn_m90oM+=ei9zQ*V;U|Fta*!|#HSOi&D`1-*F|8$V;i(ulTU}`^L5>flONdi@P z`ZkdcQn{y`Mv%@%knqrU?OIA`hD&feOWJlzUaCsORn_jq)uOj`^4J>Z3Et4mjb$BO zyfs~@%6T(OqcFkDKz+Udx@uK%N8Tjl=?w!;JeE7(r@s?|-<{*N&Vh5sFD z@50$Quzf|>5?{xEC)BpK`+o|xzaF!;`@eILPdPl=bdHtzTl3$_0mg_st`pBv zG7>Vgxkvp)H5m${W+UB0z4H$w#5fCHi+l$Yi$GO{Qa|3({V0WeEXAK5PawA6`T>xEVxgGd$aT1jo5dP?b_h_5Q((6K^#F$`O@q77EdTt$*JppcBLj&)C=QxPg4}VJ{ll9JZve~z ze0w{&{k8D{33X2titosukI2Bc*!K^q7YNWBTqpf9Ungz{SCL1BcRSMz7eal*>-H@A z^$=WLqj`|2F99PZQ^qWU!kFyKJu2K3Hs$wf*>Mxf5(cA0uBE5@Bb$~g>~S@_lXr2K z0tqtO0c`XOcGcsmT)fOCe+>-gHLe?G%^x)Z*Vc3ADOFIK*XN5(@}SM~7WnbADB90} z!vMF=jfPLeq-N_1yMMhf+Mou!q@2WoJEwy-Dkon-?VfFW;%&kl?%A-T@!Q=tAI}aK zpuQl_r;z_AA?k83$%gPUTT=S#EDAEHwOY8kyk($@A+fj9M^Q0OFD_lrd&JoTl{o%n z=GOK3!Sy4=aaun;Ej8gHBhBiW1UaAfuM8f&xDNgNf;PyS`g@6mg+g=lM?7Ms#M~UH znul@Ws+Z7NPS)ruCzI6q+3}#Q*6`|+#MONHGUXSDpO4)=n`YS0UDn*&T~c*|q-_ht z^4O~ef|OT$bI79Mr+DlI$}2i6$tdV#C#9vn!d~1OtqQY0=cd;0D}iCEiY+8JfH?8- z_?Ec%eIy!smCC3iChs4Py`Uo^V0=7p1iC`ngaj1J(j!nK2nIglgF!ODg@ZKU*UkOS z!R=Rrpl^4Vpuj*^aQmJJnZJ;KzmR`V4%yE=(4J(;FLTNw%3@$d=RU}J@KdWMKw45( zRMT71*4z6$KsAh6epYrCt0YF7%*A(8K>J>+h_dv=?2|qYlw&1365UKYzK3^KsRP1> z17U+u-#3+K(NN=7a?>Y~?;md9P>x@3yLbpmi~!0?#Iuu6JDZ1~h#mj@e8SlTWt0`b z?4sa+Kzmj~p0Z(GEe>}mCgH?=iyMgj@8Ne)6}C<}Eoi1TZ?P&suAKf8TNtz$+H@44 z{`I0U$c-sZH+0@{S3om!7{yI-{9GEwls#d6=6qm9}~?LWKP>s`!8Z!D%`<1$zU*-Ugj zwJoI`(ePhZ76WNN1!{6@<6F~Da)&_kW+xDDzq6I1zR~ zg8hBP$%sv>u+%c@%z9@WVQnN%E?(_>yvX*2a0$Oue*=1$MP{r{ZFpZ z)|uaOw~AJqd{(N34kNWsH5IV0J?%=k?NHrzA`;YLvz;^D0f(P92e|J43qwG>zmIHQ zyxwQwo`Y7^DWaaD zlv1o`Ha86qbnKqI^iK1|(Lq)9jKedpT>A39_slB4s|nJ|L@};j8)LxE-xs5uzK^}k zK7!!1>&^A;wP|7Rf1a3MOj+*yK$`JJWN^n%H%oO#OmeJ~+xE+L!+s*XxRY|dj4!c1 zRKv$Hm}`gn3Rwm0{v!KwVgFY4vAX_kF6W2(w{vc=2a3HP3GKF;mz-AO4AiqFHtuC3 z1$rCnyeO=9uVih0{n)?`3Ez3>4*bcxR43q#Y@lYPI_vH0y#k4yj!`Tg>|~Cq3<~v* zj(gmo7{5CG{6N;D6@v$J-_#Et+W2){i}Q>q0Y*q~K6T6OQ0)CBfiIeN!-n2$ImUZ) zwk@clV*NUr*?^33%TgVHO%LD~+r6!?JLWt|Z?wpOz6JMXEUikR+AJI+MZ_lzGum1Ktckslw7aPB6KYn|OA#U7f%LGZm&Rg5p zbohS|?Ji)x7;B53@~R+n=9KC4PkebZZ(3h8_prCe%)pET26xC2wI|mrw=regAOpiz zJR?)!MK(BO=-I`*GBG+UCt=*}m!{n${nv1Z)8D^Fy8S-?8dXhm|2@_-KmGTY-sOK^ zvCi+9-4S@9S(HFFX7S7*1L-K_E{1g?!0h#TijV&w<}O&mgxd{Ece-|5+iOx;mio&5{=Lb%?q|fCn#9Aas)bf+<3nRM| z%||QdF$t!A9CAQ+3+TmAJ5)@gHp-bSZoADgkL?B()l5aK<>a>&*F+6>EQdfSfvpth z3R&O`8V0e{Tb~)WGW7pJKg&9`cQ^kSJq%nAK^hfcDH3qbsFKz%!{?T@S-d3y_A`&^ zA~{S}a-8np*w;3HrJ=x(Kh3|R3R`>BhUq{+5OYXOXzn{?!k=XTPk&JPbnMt1W{8a& zRv3)RL7i38 zQplva|ERzlGFa2~==`NGC+^<5j=v$m8&`w~w#QB5HjBx&P1VetArv{#fEhijYS8XV za`BhJSshi@edADKU&jv1vq1zC0qnL`uOL2EK(pwTSt~rmyl!#cc;4}q^KUJr?GhO| zu=5GMO8gRSwBUS!2DUI)5zX?dnPrrx$#cGe?j1>1qj6BcX|RnJ+d`bl2ULD1)mC?J zL2PazVxEBal8MaS*?ksK8xDn%N39&3g1B?v|k;B~MvJFtbNVT2dZ)?M>bL zxEA_pO#ape7(KT`r&@T2WO~>|a!*^iB{&B}C-f9vW`l%IoRhBmm zyXUyHvW-j;7%P>U+nQ|-B`N*NVrkA5RA@@Wb$swD2_-M+x=?zm@^SN$K*Is;#!OC| zH!%f^Wukh+;HC~&Ce7|>(F+wKQ%?SJH}4KvG5wlem1u$fBBQ{ck|9<`#qIkXBuOL3%%6%Bnz3OAQGXeLtx)M2Wee@6I?sYDC*<%y-EX)BaRm@UbxG9AL8ti}vZSFK0eu+> zw(5wRvaGT7p{C?Q;AFi5f_$|OgNt(OWa5yD?Km|_lG{<- zl;E%81nP23tY-wE);ZI}VWDE;61%tlpBB0~SHznKYdLs}nVTZlUHFc@@q%>eev=2C%wk%;Y$`Ke7L;!*Kn*6wb!ZBJk%j~YR=wl*gxF9A z;Y!nQGG=gJU1CKFAvv&lD*ws3yP?k`DZcOZ9y8%7h)lrw87M;`PY!*3M}k#&+ge_S z4Q>8ZIjjLUxto<3E<_B?(_mU6!-Ih=R2vCLkSoIMgA;kPZV(`X* z?o^r7BFAUFRzEpV2GtfOrDI4ghrGxeO7uk%7z+b&iC5-}R}Uhnf_z*+TqeL7Yb-3} z7O5&?DFZQ6gIAt3D`j#o5yw+P{VIBG51qvz3@afkK(|ywG!gl>j2@tb^)&d>K{^py zS84(UZ~#E5Ju02Ma@|+0fUIp~pa~4t6VPq=RDJ}}iiO;gBi^zV3x?bc`T2g_Nj&9Z zYKJaGj@!&2_F~iod4w@2Y#&;A>>T-Y3Urx+ILL9UIe34j4b|?oW%;Df_6jWG2tYqN044fW6@BQP=QAL|&8vHB;5$1p@6>biOi)7J- zO2B7qpV);TRuSmeY2TOAgv!}TBB(ZHr7?yy`JvY@2jyG8L1YMp8U2v@*6BOoUw zl_#Q&3J4er`G`&;4fYkvI=ZfB0Fu7&nz0GgkCElb2${Nf(GQ9NQ2O@2=@Xbu3+gHp@h3C+(Qv{ixSR8 z^8nD9rUT;9vKvZx$YkA2Gjf$|vklPElj~%wD9=?meHQMCg0xVFv>uQLIFtrJkOTS( z839tc?NAXfvGBQ^_|q)PQ{3LJ`X$Z+5=8@TW&J5Z`XFoiYZXLe(LM@d=b=RndVsW? zV6H89$YK$vb96qKk^?k&fWd<%JL4ILXQ5@|VB6crWuHu;&ld=>en7@r_mEG$%7Vh= zdq6*xFN3AVaXG-ZPvHET0f%!UFQ0b3wXW*;If}Iic*^#Ktkp5nM1NH041n^WZ}Bf# zA-&=GF9EHK1zpupWSr^`Z_9Q)-lwM^uF~KSZr%JIS$7~V?*O;v*w)Qea-B-inn-L5 zSp%JNy9$mk^Cy*^sw4J6k`GIs4D7^ z8W|_x^VP^6KGBmy`K{rTS^Im9X~S~BYrey$DDs z!Y$z71st7+4Dwe6vFuX)yKVJB4JSjNmH`#@=aBAB8DfPI-zaD=KKb*G4^ zVbORPaUM!sFUuxAiRCLukc>z}VK!@J$_;!zwzRD@(J21BL9Ff~Z)AoFByd(SFg#AS zYlNjZnl<+gE3WW6ZM~etml5Xy@*5ysC__v&n`3Ja*C+@!oWtmb`a>1LF==O~)Ag4hY1WW*6UvYt~kD1+IUF3uZCmoGp$#M2DPrRC?(ZkXy^PEo*K zhuU$Wtm!WlB(^+r#|ZVUI%a-2en4e^1W$Wdi7(cW__cXoHTbf4#D)X-CKp4U&%Uj_ zQe{Tor)?rU=U)uK;EH&nAuC~alI&1hGM_Jd!9w!5w4f}w01ZFOfh=%;o%`onPaDx) zuq;Y*zC=Om7a@>>HprPYq)c9Qc5iF;iex1iV(}(3fC$Td#6Jnk4}aRab5sXhQC|t^ zx>7m4#_7Q38V#jJuA_%;>#@LHW8iMHNL?th@VnRhBQ$T+twatDN@z|jn1F%%0#frF zT$Bn-{L`+oqa>j737FdpCA7hTeue=)sR%`v+$=RT=X}vi6s~;_<|}keMbt;~!?OhR zN(I4-TsFP;iv5S`o&TKNPp7*4NCK@ zAolZV=A2n}hECT8+bf#`&Z?~V9AW6*%mo7L<~J9%)Jk6}so$ict?Oyiompk5STnOvB!00lD2b-wP#=wk1AIPPNd1qGPuh^zE9|D-V2 z9_fuZhAiGq3;hC?qBN!ghtnzvb{a~hvUQ)?&NmiM4{y2|vi(Ild1t^vMDq>VEyvq3 zK!y^85op@S?)P`BmZ}3^bLfRC=(U2hLIrIU;1k!P_E`|B!5y9ZOf;lJVNv$TDCU)< zay~Q!$aH|iWDY++y7z2K_$-q4+?1)&KRXD%9i`gvk$BFP_v<#pDEY&clkcK$^8Tv1 z-1tPMz5x`iy8+P+Q06t7YX`gYi&p0@=P)&J@&!`89&OnI9ipOm&3)Gy_I<|O`5nI@ zFBYI-#6b;AdL|cYwpcChhkd6%Hv>6+JTWC0&qpkmL3+j$som-JEaj$}E5X2rpqs&#i?toCw#c)#RIm8zvu{^YjR(E9EH zgTJo?sC1Wbx-IX3YA@Q5JaxT*=G&3Hp|gTCh(Bxx5Ek`hP9lwl*VE>Oex4b=axPBu zX`V43N9B{OxX`LExO4{bv@CsL{p^<_+z^XyCEC%NeQ!`nm2QWA6dg9v-jZ+ng8oyo zMGmJhz%GSumm0FYMRgL<_zYGc$N!zIj`WxSaY$w=$czJ%uvw?iL8tlg&9_5S`Q*=s zMpLt)<}Q4&f^6eWme}g-)v|F0dyy(853m`Eh=iuR!dvOt6gb`*fO*^r}hN|T(L z5ZWl&IVTCTI!0VrEx+TZBE3+~tWTEBI{maus{}MYO(^fmQRA0m&=v-{M@9KZ@o<9E zTATjxn0DgbrRoU<;h~z|&hKC|bhiV>=Z;ewG$j^(8S0s$0oC2UkRm;YnKh)CJ$^aA zxul}O7P65&skQr*Kz*AXG6_qq;PkzmQQ?ttr)I%}H_-=p*`HoLc`!Ij9<==A!r4Oj zkVm?%sm}dbi?KYM^UR|Q=i2Ve9Bf*UpgMY(OIO?(GR0D66!~ZWntCtRsWPx>&Fecy z;~#%od?}igN7B3CzCAkw-2ChJ24x(>sbH*qi6vMS9x&%k zOGp`8g%0dbzY)Xo$J^Z>z-RWqWm%@r8$zvO%XN^04_|Qbbl9cBPS{AbKnHZZfBenuqb^p0`egOjRE!33>Il_CPKOI!0Yw zp-ho*ZgAJ|gDE~)p*FK4wJ1Kjxr)W49*KaB9<*+q79C}M1lk79ijueK1+Tc}5p03)7i zxM2V0rx(v2hP_anggB89az3QZC}&YdN@Wm{(GBJA&v71iWab>oH$Ir`kur^9+azo; zKNGbn5sSTm*&D_prt-N97w?$cm1lAFxJjZtqpyS}=O7H!e+c9u&%QezTw@Aswq+hW zvG)dbzJ`aE)vH)o;T1ZheXPRS^kB355gm+`HmmpSL(k z;@?J!Z39S{m{zg4z=FzcgQwombr0`JOjq$y)2)Pz((UElL8cLM5YWD+_G?(7E)xxz za-&a6O=ruQd{vuEC<|JeCo%j-8C?HJO?QdAo7S^xr>^A<>G?5R+?FDxuTovG0Ix;@ zxyGcKpk(vLHs&)lk?7RFBa>MQUTx&Agnbu==u^~*I?p`?X1D7M4jxT| z&vJr1wyhBpB4CKXfTnoQhNz2FTMq=m0Re&%CMJREhEY9kQ1MbV&>w4o#)1_DiyJ!% z&TM;C)-$`pKNUja>uy+El@_{)Sj=Z~6zQ%3-XZ~W!vT_?A)_)RFC%;~qR>HKBX;Oc z)6Ew~ns(%d3y7oEr=Or6?woBp<1Gdj${Qb@IVaY31kvUR7?V%fmgH))#8#gpq}-l9 zNyx%B>(%vi`}_Sn=gd8wuse3}_#s8Uzp(#cb`hYxV{G#oQj=oTsJXi8)b)GHO1&T{ zZzc*ItECwFU{jHC)lLJ ztT68bRdD!5G0kcSAmNXoO8<7q5~cpIeMOoZT4g;fFfm%j(qCqHgb_L*TpT@kFJ=*1 zlO6}~<}oypQo`g775cqmm=hYSnF%LIymFhu1)(@C++G(xHJ*!|SIg#4#2M`^Z%};U zd^n?NfhKJva3uN4>DLlxld?o)fiNiPmEgm{WBH}ocofERV-TFEFbuR=I&}cgP<`T} z-nkcpRy`+%#}xTBOXz0u7QYNpi4h(n^>wuP&6Go#ji=A+n-#(%pdc^~Mr?}ET*L2q zPJm@tM?gt(zo(NX8C}b7)kGi~IC|I<^Uyk7zAViS(pd?Jsh6LWbzCB zXR=CwOMlU=t-&|@?;)0rV!s;R&K;-%TBLfMV?blljmv0HXISD8Rl?pu8# z^`Tw#@_Y0t$82DiA~C1N246a{0+&BtSlp(O+%1i}>$Q&LRZ zjOoLP^jocTbbOl4JP*XHPGESM5o|Fo7eI38amnTmMH56oitPRzI@`!zc^PPaUc)R-?kI4@f%w$+nH7DYx;hB_d)Jc34GV_S;}HcwxJ zq7WvI`7$o=l6gX2OdOz_#7WHNtr;wz|I_$_QhHlZeTMg;f2}QgJqT1myk@pt@aJh9 z;w!O1fLh*Z3LQt3(l8=(!}fbW9=%9ljm58c~a*7aF-J5U2;g zN-Z{4g6S+=B)=ZX1#CfMSj8j;8Ci5WFCy`52z=FEGw-3(5ppFy) z9Kyh@7{}l`AR5Bb<>%1GOC%+&>_OW{~Pwx$Eprd1FKstO~S^ph=O z%#rvBwi?=dga{5UVna44W~Cv=homcTV-IjCGLWb!fFgk;>VoOq-3TD$6iK4L7fgvLn7)KbqlpoXEW>7^)6-dzt$R-ZAutMsg!Pj0iTL2{b4{$3%t6fgSh^a}ig4NE(7-*3K^Kz9rt@EIAG&yR&g8Ho#TG8|TBiu3L{qUB z19H`XsRE2$bhvMF$aQhMf?R0873T^HGdM^#2hoebVnuEXhh#iZZ8pcU#9nXJq8X_b z7&6;@R-IRBFGdi;_NMsUp+cJ@pu(Q9uNcQBAsIuMeG#5hfuGIMXSV9Q@Vbr!Ox0F* zPnx6~Cb=SvlTKs9R1J1StR0)@BQr&7{vU#Z7v zFy?8Lx&n3#25jQ;pXf;a1pGfnDeU}3O`P3A*aS>QVhXG^*qU*oITx>`qJjCr1XPYY z^aI{NraQIcEz=nJ{fUnhMloUp*+w<{rE=e^vwtE9RzzUo+`I)sgv}LC!H^#xuqhbf zZ16Rk{6bG5arqFToq^74f!eX{(D?Qr#-50kb{JOJiFeC$o{D$xwn%ci|uk1Jxg+|9; z?t?beT~mZW3PsRlmb5ylaE<&J%@m;v4k&C7(EEu9Kc6ei^NquF_)eT#x+;QU})7Mam}}K>-29U^PsQfPI4ki2xC)V1WS3V<6r|h${mW93@6; z3WuBY%~Iko-iv#2beHx=FgKdvBfAYdkwDmxDw7lE%?Ah{o1N(LK{lyxPN6*?KUuhs zsFA+%gwwq89O5LeHqJOQe0YZoS`L_HMi& zZvT>@e1C@XSO0%*{d&G^aXfyesg>Z0t0K6;9E$`}n~nk_wgF!u@y2jrqQ!K9bbZT6_elMV%!B(B2mf$<}Ie8q-K0~%RSRf%Ni3w;y(72dAE;f<_YMftJ z=KOs<(%B=xmM@uESJ2f^+YH^k%2gBA>c70w|4rnd+pzN|z?N$4J!E;~_(IW;l46U~ z-dI>7#Nk)^|8aS5nCLc32ph=xz8vg!jQD~#W)cB8a$wzC`6x%cm{DMcinF=B*_snM zxrH}F0yFtwJs(e(;s4~P&8dRHi|)x+^j+6hxX@mYvhw+u`>IWsKASqN5)nCaJePzS z$xYIVBztv_uTlQF%i*kE0TF|m9+x+xf?zNczan?X3Jw&Idy5xmUVCqV7awWJ$LKCk zLRiP&ubk603dlmlRRrnunuKKpyDVru6Xx{O?scG%Yj&D`UiHqXg~h*2cOD)=EPL_( z3d#Ehw;NSOz~o3isQi(piKorV(T zjuX?y#Z$(K)AAyn)t#baMCd$nU0_r20RQma_8Er{k5oJ{Q(y$4z25XvxP z@PXAT4mq_fLs+;#CicwDcM#wzFMr^36u5cFDXoC z6{;)tabzTsb()Qy9b2P5`f zU@RV*tQv9C5Iz5b?Qhn$)?IQc^sFu(n{Pw-IiuL29(JY^2)gBI6U5jeNsgL@2pWwd z3ezGZ;>5rofP4rwCXy;v9TI6M~( z^ya$|vBKyF;#H>Sq7wh!ess*NGAs4)t}hEILppT(zL_gUTnHuD#uXSfvGyLVPiGY7 zhzd_T-zHW=*82--O?j)$;C&m!ix`!U>dfH^Dc&_dbO@Jo4wu~lO;%UZsC4RO#i^LgTVOx&exAZZ^Vy-h!c)7iW$uU81B;EfnI(F5 zn?@Z=;*m9Kd8k>fG9-VJ-R9DfFtU7s~&4BkJn>b#DaVRPrxx%c1P)k+EUZRA_~%kxWmg0;Lh(T&c7Yo4iIiIk`r z37K1!@yR9o-31!+*hr)%;f};4DsOz9M@#$meWw#DNlC(o`LnfefX*oHF>@Iskp#*d z7tzu_8)v628SWoUuY!IT-J~%uiQX`q`1k7<)~$*o?siX~2w2y={`-;i&l6YF!ffxU zIx)2+*IeU>LtOZll>0lY1(4KDnKZLIlT7c$(l@=K!J%UYB_y4i#0Z^>-QsCF4&5IA zq+Zte7FZV;p1z4s?H-os1-VGb+>d-!5chR%b&G`eHHj8JXSkOhus#3;^MrgBhIi#Z zpxVpE&k#4pOy{L6e)r||-951@FTP$^*{BzU5Afz`9m^jLDYg=yVKCjV`e;j83E9`w zi1(@jCXwh(qcaKR_9HEBD|>9)ap@TQAGWbPR^qF5;oPub7^-Q0eT^Xm=g$Y^P|54| zNwu+Ak_U8ZOfF0(Pmk&^_5nRmr-5E-i^GX?SC>0%IREa_zWq4afP7B`tP&j zu)i=5E@4{Az2+hr2N<+-x5Z~ld1jGEi?~)zN*#}DJszOK#=8>+%?mf4TGUjz$Ki(M zV{+#vI1i^Zm<7No$2ft>7Bc$o|7ds!>+e^t~a7d82yA6hES9 z8el%l1-P)Ah9ry2F87F(dASWGOstOg(8fD=AR6(mN%}ZoxVk4O5U#H3uxsbTU{hh3 zrrr3TI4^YbhADqfe!&b3oO&YqQB1UtoqaW#N;Y&k7tZ2ZU55wrDlbdCPyxy7>3GX0 zVs5iSY~xy0fZBR?7-(k&%#*(?v*%(Z25b}><`;6WiVOBpWyC%}n5yo}v%&gIdM>M< zk7PK!O;AWb+dMHRYPK)^)ncO04%WwsBnAp8bY$x^WS)TFDN=8_(}`J`+vw)3RvRCZ z5%^0~7*PnDd6?!IDNh{(y)s*7T!DXg9~E9N#d$|a0v?s>5k;u}8Losjp`lz_rzk$R z?U31nUaMal>uJt**WIJok_AIz=(nm(M<}>V>z2}@KSUYrT~oqB)wu46Qfu-s&d5H| z%ur4A?<$2epS9?0DI@u-f=Y6QF`oTso2O7vA`%YjOGwpT5kcD))SucjJGgpsl1_zd zYM(au#x^4mtf3@|j*uqDL4G4Oh2Aw)uwj#ZEFN`cuBiqLc9_j<`F68B*vd>{h>QEt!e((VUhA4_mgHvCd*FGOCD@)Yy&dm#4BmzP=>mlPsmNO?EioU za786t$=x_-CwYw& z^vq)wGMN&q7ibufsTOZ;YO{PP>*AqM@v*Pf*fU02(y)=iEcp9s^sOaZrrh-j zNB}mhXMhNfx1mSMf9Bz7wsNVRP)*x)ki-K)c=L@(XJ3NC0B^oHN7QENBnk$nMYPOf z*y_>h2LI+NM_Oo*nWSSHp0%vp5n>r$sdHF!(0)-LF-O^Y9XIKarZD%NobD||42jEV zgwO=ebcNci?WofG&Z@rP z+#L*!K;K`}>Vi{9*c@Q!gi1H+w9~w^n)b&GIs5mB^)k-d>9C6A$el`>YZL3nbJKS_ z+8T{5wtqjyc)#5H{3*i9#suneTM09YOg85#Erg6|=5OvLCm(`?*Ebg-LX2o07o=ix zgYlx4W8UkXOV0|(Mom&~`L7)(j`Z1Xn5gFB+AnXe z3xNz~1L~Vnya#4u-I;v@h?Gf@g}nRCB@VQ#=c$JstvH9p$Ce~W7?a-qY zx4|QE5aDXVlDJSDyoD9Eh@jmrb$vGY*!%0biw{Z1209Ia-Pza1#oP0Z*$`o-$arh@ z^LA@S2Q(nZxb`hvzuXPfuSGY8+sB+ej{c(@O3O7%@_Bp)?KeMjKkrn8&GE(4QM^Ld zZZfvI&07WVPI3@%zZ22r!iOe6!=|p;p1Xq%D9yX)-X4scan0BbckiPUi16syy}uTWN*A<1ma284HgP0CL|>^92air>tvLX zme(A)Hv&;6(ALX#LhFfzA!b!ls4V6Eu1A+%4)Pu|WS0+r9fwVA`@}hz1acB0{#=x{ zMud8&@9cn?GE`enm_rNINN7(#?7U38+KstianUwx<(zwBb|*K~Y9^~>ZP_;XO-;Dfza8uklVRkIgyN!Aa!x?I3P{m6F zbCsSokX})KdrwejGc&97-Y;s!uP_$z_5HEi(U!8-O-y5Oc7fM{Ad|E3tNPBJ*x@O; z7{Y8J74a?Az!=4?32QT6WJ#EKgny(S>%F;T{PyNPV3B~C%5mnuuy9f6D?LGAfYKxY zv&U;OlYmzDP}uGHyqA6Q^yhM;Pq)D=Usl!*YAi#|Oqj*0ZR?BVQ7bges>;(*s(JjzXw+uk>%h0iUygXxIYV2g#?R-?@p) zfz)2uV%D?JZ!8$kTDpl*{?c^sg1kufdIsBgpu922vSV6i5N^?lLq4Vl?UTj%YXBzz z<2Ayr8R1i64;gcVtT3_HhiUj%`V~D;Mf>!|4nxmGmB;ykva{+YhnIgn402~^LvF59 z>gYza$fOIx9{Ktc%mOg~;!FI-|66Zx#!2 zDh6wQ+jMe{-E?tGJ$8M}WCWkkXJXwQoeHh}sH$9{NSxzavz>$qO+= zTk`ZG?0gsa3bFXUg^gU8{>+iy{qXK;`-CemQ(tshW$<&!;S~WbK8;K8W5JEOiTy*U z*+$5ci!nvY0H)c37O@dvQ9%(;E?G4RaL0>}hyBBAmm(x!QXD2pNlbgY>-v@GwA<0^ zwhgZ5F3qn*O#6{V(4Pd1XIFIUg|-AJ+s>bDqsIjqMi?7mm{CWY<)WFiSf$G|2x;zf zX?$9L5WHBYwB)yVWd%2zJ#zo`z@13}YBZx%yS#u{kUCFjsd;oOp{2=T#uj;Sz=3%n z`TCSP>9>96A(1@p(5TwFMk*xvahx}DadLuuUt+${QeuG-X?t#afLTGty^zoRp zo22_rCkGZuxAK&Zc`>~^I`2`W1FmA8T3-PXHHD$(I zdtuo_%Z?UX4!UEbbY3iAGF#dER-W|$erKh)I5Eyc$&cxz zQKYGMS5@BGo$0Ua(a+;7iY%jUk^S%l*_xb6q94MsPoJm*21b zwZAakU!Az2_P{8mn+pnaQn^6ljK}7$fvJtDf#{x|8tK*59~im&DgA}*ro=Lh?URLz zoz`nzYfeobx}S8s|EBQ>_D@#GUlEuCJA{Km=0&@CL(qbhpgV@eeic%lro`||8~qo? zvu~l<1KhJ#Tw|-%Gl@$F&MgjDk;L8+T?5CAseEic0bLITPucjUo&dAk+iY~j_~wUy zm9}=uT79CPwd*apBysC~)0e;-_`9QwUM|D4r3mBo!abEiM(mJIsl|_CLl4V0c+4&t zBg%tpS(wXE-sKA5S}buI51J8$Sr-R6peO_A`gKUH*r+ya+UytYYcwbk#XRv&uH_o^ySoeBrUH z?+D*Lp7?KrCAXzyA0qS0Tn1!<347HqRpHKKFPvi^1q4nHi+jTyIFH>jF%}BBHp!r_ zwyo6|Hxs(`q7Baprak>w^ezwX3s6_isG9@?M|s6ojR`ht}3 zprd8kjlA*NJB-zeE$OnkSsOiT!fd8sQir2+6SQYNKClKpsXg+G4(AOoVIqw4@mHyF zC|GvYzpRa*k#X}12vhA|>mYxP%GD3%_NoX5l`9M^xTq%99F=0~nd3Z36GY8v*IXS7 z_EN{i(iTeM!Z=iu7iuRfcd>fe7y?nHF6~?N*v~= zd_!_CAm_pSiIZdcU-h%FAOK?2L%~BbTOBrQvdulEeQw8c$TxR3*kU8s+$P@- z-O+AbF73DVe7e%hYIQF|d0uUC`&UHC8}k+MQaqYdvLQr{iBQa~0Sd|hY=?mVOa8QwS7iGYNu~$WV!hv1@XEqjd zaeHn|hIWNDz7_Ft!n}Bo zV``u}YF>U0l!v=D1=+oj?)e!b@Pm1NbN@ryeO?$D96&soP2?90{nT=spd;0V_c%QK zJ*4f%%;)rfA0GLq+C$@6|IcGPzk>@kxCI@v?E?YL!f5~+gLgmAF1#j9y>nUW_3lCe zWRI!=_qN$}ANbWWu31IlsQo!@2D@+jvM@p`Jgegud>l$Ow|Q{mg7N)j6FgonUXV86;uK+iblSJ!kZ`IvLypq}{96*~qY@=ws|N7L|<~ULf z+_3vQw|XeR(C&UPT$%*qBnx~r;9KW5VjMO{tP;1PO9Jz69>&cY4VxvLk{8LcTUCj# z3)oF$`;5Q6p7W$>5rdlW>t|Hz6KQ(uHOg5bmG9ANtsNJpb+{qg(9d zki65RDQrgMj?j*F3g=DXX;^B2yy??nbaxz2B7?YL@%7oPSaX?=bq?g`R;K zQ%j0}a(qp6JPDsQ8@+`vNXA&=mltjHw)gi+OVz;BF!KieD;RXxjeT~rlAH2u)u3vDHO}07X(XU@}+<)h%rl;SL zm*r*6SRf43fTD?{YyM%=6cSVLY5V-1t?O3($j#N-{WjMHn(f@Bf2phB2T7&H_<8;4 z@dJ+2WJmve?)LOMrA`62t<^#wcVFi5*>|_z5fEqn^yhYs_uW-}XXoYw7cmcLVjhGo zd_$rD=$w>2fbhqe11Kop^Woo@x5oSGem>|JPcmduZ7==Jn~pC^#pn_fQW1?~%3-K$9)u10Y7wu6eAZ4cf= zouc|#(G?*w;KJ!`It1k@9rPodu>F-=z4^6<#h2DdP6hPGZYrr9&OZoY505wWS;&lr z;xqi*)`M-+)TWz9v}9$cg2Z5$--PYEeA*z*VlOH(8~%NH5Elr(8;334{{+H(DF>t zFGeTD(djK;MPqE0s-kIfu}O(x+aZCHG1_HkD@0hmXi``H;gzV4}DLeP*l!=qAu{FQdDB2E^sJ!|x_*~9bJ z7}H)BNOQ=GhCxwA;sY&bZ+}NW1XU&wbb^)Ah_@JqePomXB+;8B!wx~pkw2Z!STgV| zCRpScj=XYY!&s#0)Mb<(h!I6LlvpL7^EbNh>D^;=dx@;=hzj1FR5rsMMYA2x8d9N2Ct+V;f{jwPzlNHCf1UWy zJavQ!edbsXc(#8mU_gy*r3R6Ca~KzmBY6+yQ3$d_)~&iEIxdu6Ly0v`2X4cNje;3? zh`SUQT70EE&B$~IHg?BF7Sng0><>u!js4oZ2*=Iy!e_^PT4R1a^RMA9qr>=j@Dnio zi7Jw>hVEJ*gVp&zMD~>rms3?CsXuZslPHB5paz2xcPf>;iR5dHaL9`n+AE zHhc$D3e7}fye1+Hhbz8=DVkn9+t9FGuY$Y8I>*BJkM7d|$)c)+K>BGstjV_uHC z!StY`E9ePuv>08QKgz*Bcq>JRPAVAWS4=het5yOV9I1SB3C6;B$}O-s?aYo zm4MqR!po4@{TOFQms=QQgt)d1IQ)32APf(4)V}Gqz zPONP*q;`|nWW6jYbjHvU)S#w;=7+`9ysJ>h$Yc8Bo+le731vm`PcFgcN?MabWJ_U1 zR$zEdo`ZN3I2Bfp!^F7O(`{=m4buI>652aGlf%HU%kOi-DuUy-!RZH^*m;NEP0qh# zwpig>NJVQ)d#>IoF&N zW8vZ2Z?x-W*d;w#b@O`0W)8e;zP7pVcQa@U|3w89O_xI$z2~j-5dmQ~tnz<-!;Xx_ z#6$6r9s}L4L}pw30boV(oB*ZzUV2_=*36)FWXmg9B}0+zkrHu`MgTRnM|n0H)ynJ; z&r8vDtMQ1m;`84Em2Wt>MEUDe+XYVEcl z*F(FRU#PGMV7}sFB?~$S^&1}q+NG~VY@g_l+#5CUlK`#8Z>2}Ddb-kILKC=9uPNeP z(u5=vteq$*m%PRB_>4a!&qhl~@`x^`Duj-)ZU2=pLNmGaru}yfgVzhV>Z)_)H&_rP%3%(to$oUyfGs0cdSlW;IT#8tYr%dGqYp;3fL;4~Mzl=L zA&7&Tf$W{A7G?)5`?s(vM1{@Z;m9-jzFH(;+I#)P5@%43acM=wtK`M$ER&DS9_amd z1d7Ybu}uo#BW|c3xG#gB0)(X-z&kKlz=yhVT7BAkWrfAp1jwnl`&ghLyM|kfGTfD< z!n%<>a4hUB$@ukpe8Kd8*ii!dTWIo;ehE&v-Nb>qa>MB5sg}AiMl40A|D`Ywkp_SIu~MRgBEe z8;%fVmNzr_#JLHBxyhTolE{@M6&rd^T<{lHmdsw)hCNs_gRU43Q)<_stH^LCJ|QfK z2oF|NJ$#0%MX|pgICcghs8|d4awTaBa-SmnbW*@AtD9buFX=KK^q^gw#ryi{6^rqk z%Q0_2HB;6p^^IOJ$7X1~X8p~pW%~xtU0`z*GRD5sWXpnqyGH1mn87?Z{hwh2R{KfD z{IuSZ=yWi9M-Xbv6eE-oh}*BXC47#q$J-JHF}1{~cz@Y8m3HGq?Ka0W(5wvJ1>+>) z0Xl>l>IJ(H-N4*0<&&`SZ+M5=JQEtL#f0iCav+T-+)mJ#|ch5Y_zN?PVsftBrXnnry`!IHFD~>Z8 z<$F=(BjyNwNxd&>*nxi8TSp+efG5aDghl>)@}Ze>E)>?4`16tV(zgA*>2cw^8-_iw zJ%Y>p+NqRE$iUxQYXKvGpTNkIDDlq*_`B;(GjNr4!qK$A#CE5v(twR2k&mPq^%782 z&V3kdkwtwZ1x9;~NdB8ks4haPG*KY|X~)@tu>6I`ltV7@{vD0BOsC&m!U@rBqXC5( z@V<-Go!7JLG((f%?;7aoHhQ`uiG1OV%NL<@+6BqbS_Gs|Ol*`NsRlw~BQ3wmS}ixK z#0>lXR|xW~M{LKRwvNcN8ix3wEatAcJ^Bo;0d)t^ilFcn(8))^jT((Momz)~y6#2^ z`1AG$_l>WVE4m`%ba!;R+=41!Eo1;Rsg6l=z= zkwOv%#HY+7c2oR4Q@hS4-aK2va^@@h*aoJl6^^;KD&+3!X0E+s2d-o0SOZi+;WxBK zB3L%I7Fj*vXITGIW`hyD@LYTK#3B?KOXMX(&C6B9Z z8t?EUSe}G_@}`Oj(DX&)jkP@%a7CQB!uXLBnHG5b8 zPK!4JD1~;Yug+zL>dd=t7PY72Cww*OlV73!o0QRd27I$8|Jj=EzGHcj?`mNt(MGr)RJCK-2shbkD~jH?LFQ|H5@4@`0VStW+a0n# zK;WAM4OEnRA@zR&w52;kO`vvw<~vW#DO#2M((>dm|IuG{dk?JnN_o>cZzEP=Rw|eJ z!qG*hqrQ3n*h9kE%9j%pv}78&^bTaNq(Gd??dsnn;TR&`_We?Ja8}bnFNb!q zXuLB(JL3LO5zrwMb(c8j{)v)EO260jmkJ0OL7Puq*Q@E)L$a?q7W~dfyJ<*^rdIM4 z1DRRzI}!aOnu*We=;l4-hWEY%4b5bz5rqE5Co+eyCWj_p@i(^fqBIAXhCoMc=(hAn z?Fx}pGIhR>2~&@zXeiT%2?=LYZNXK1&L4B*%$=`mgSgSh`aFfOB@u1mBe1!dcG>oD zIc!B@7=^v99>^KEdv5AV8BB&+1H-Q`Cj5;H({c!u6Egqt_F2qD$qLY!t^BVymiNke zpDrQ=HgVU+$_vrWxlt@S`BK^7hm`1;( zO{&ObVXgAC_;m2!CqL(;Ye6sCLL$&n!;QzHseU$s9K2)Jt)L?j=O(NL!-_L#LMl{^ zq9$HL@6LbeZ}s0|mH=Z2P?|IH?+nTK)We}dsc*n19CWJcceWhk*FNw)d3xWwm>?N6 z84M+!U3TFprfYC$h!~rdk@BiwM%02u=zw6@y^uWFI{LQQQ3}eE z`~7uaPO?qGv-Qq&r_m?xG-y`1PPIJ^V*@VY==5HN-fBB9_-tlh?Yrk36shTe?au8A zjQo#+irfpS*^=#j#U3jLyZa!vY*1hV{h&t#g)SFGuVKfpW=51owqe1IR>#8nG^5C{ zy{wxw__RZs@sFyx`^>a|(;V`g?DphL@1Luov#sTs#dVU;Z#SgT;P#zj zRI|4M>Wx;pq|kl1to`E2BGzu%>nBShZgkz$YI;`zjy7uC-t}N>f8tU7Q<>1A=i8(s znk31-{=6BsF`y&+Y4@Jkt5`?7iH;UnaO?r9O2`x2DD&X7$&HeZ>KgJ=uz-2?`iiLgg|$FYKY~o?u$h{a7QE%h~rn$ z_LRP>)`oR2%Hh2DMz@C%=j9jHof%z}+wYL?nr2A3tYf55n~v)fRHCA*#dO8$I8oy{ zBL!Z#D9B&h3RKKwe>IU!vx?A9xVp?$=Mnwbv zO$zKU-J(`r#QC$6{c7*69`;0Y*QyxLdJmNA$cs~bm{QP|Yei#&kHX7?^wt>hO6cLG zFI1v5$eIl^%o!b}-k_VchC{(@rVq(72qgAOHzrNg8!?rw@ z*g`R#05R+*`=7fw>XDf%U#DS&qUFnLOBLl`n3^q_FWJJq-IVxNtlioMwn@vxxsNj< zeNVCL%U$|FQcjNX2bURBb9yGJIcYCT(mjD1|%#~Or3(RS*8d(Ty1wM^~DHVc8!GY-PrtdH_GJW$Ehe+4$L>E4984N`k)ayH$zNH_9kruUxjI}j=%79Xh89I zGA{GS50bxsfBEnq-Ya_XM!?z@ZEK;W@nZ`vq-L5$~?`+ZLbDLF<81nt?!vAV!|X-a0gYq^KR$&;voPWAjWh;92M*?AauOO$g9+I<6;RW9g=D%%> zJjjP98dOit*MbMcgX5fZW=`&a(p9>hFTZXp{{*k4HDc@IP z@)%66v)`?#+$p1$1V3R*X+t~wcq_ab_3m;7*Rb)L#mPh5KRWzWS_u)@wjnAsjR?AF zL%CQdu8d&kPYqInUheo|)GvPTsQBWJ3iey(`*1eShN}T@c#njcAIx8~WCCvz{ae}j zlciIgUfO5H&0(~JlZ1qOAr)^V;Vo0vUxy-n#j7R7L;@w@?XxAu)hpA>|Eh_7RLB#- z9rInkk>3Vl|MK>0dPYJ+G{0>3`04GhWd=SH(NlcjHg=OxQk%viDB}L(yx3dNqrn!j zzPm91Fv}b=6|`c!^G8&sbMQ>J@=>a-q)EsDvo=vFY;H*7PD^b)$}z2)l%wTu^R5ey zI1iHu#89V{gT!3~TuZWFcFP+q+sA5Xs5Whncb1AC%Li?wsr$dnP`j{2i%oSN2l ztBrF;I~^HZE8&BqDHL!V#G5_guK>%kVk%b*+DgGUY|rr1f51>QoI)F^I|g$V@=aVS zJHv8 z5?oa6YtPT-pU0K8{Eis(B`syy`w|w2ir`Vcu2-x5Cu?4h6fRr zp_tE~B~{z}QPDB-^XgQSBECEm6axNlgNV15>AYMJ@u4Dr8+lqm!b8D4!h<8`vMk;& zO412o4y=O~1h!{9aWdGmAHemqWB!H1LUC4~NOjIz^$0D#z$-TWYBbsZ%Cs7E5=4`Y zc&HG{fdGwFyZRn^Ap`27hMPwwtC(r}@th?8F4(Q-UcWRK^Bwl{14AHT`w}%e*9R>A zdOAR45GdU!FIJqMHazq981|YdDkM{|&GrhympY!wb(zDa1LK_*IO1rUa;L{DVj|Y6 zoQ`#aHI6dw_R+MYf*Y!L#lq;+{0pML+0`H;@jjem*2*%`ZuN}kxJ;0}&IEdRKa4W+ ztq$%AM))^Mdh4H=yL1ya_%3FEI|F1@j<@w!Pbl<6j6wG_#H}8$G9WM>uQ+%X z;XWiCGlELF^ujd0C!MBgQnMlk_iBGKY7-dZZ#5B-fvX@r`@F9WuJ)q7bA5W(VPt>u z7+Q-vkym9?s#ADG6~L(c=?c2iXm+|>uZ8U#^Y7H4 z!|p);&^VlmA5>JwGuM*rIxPcTy8U6=q|?QwAbYJnfnN~^5tsp0+jUz4!em$4ua}oQ zX$V|&5;DRwNY#ThL48(No*K}LnAqLtZjp{6hQBW*Qd2@ zo~tXO{TI#e&~nqM`Uv9%brKba9xX>(9GDKiX?1Pql{UM&ORH%ATF9K6rA83j9y&)C ztdT;!MM>$uHnmR02*e{me_XfuN;r%j3_6f>6v?ISk^3|P+a%=3?KJwiaH zW*#n)yQF%#=^V8DG1mnvlv3%RdX`TmQWgRt4l!i5L4vo8$Rq|~yOLknMXO-{?tcu1 z;D@F);SnT|NCR5>J%3@^DHh@F^c9;$^pcrw>Qh7GyE~v;WjA!uLq4clbXbslq^nlB zN~*b?rU`c|wxx8lI4k~Ky$td$%zQ$bd_w?w3t+@phMrf`D|Z-jK&p6O{!?pCf`Krg z_hK8&B-W==t-n-3#|G{;lk0a*2BdUI@i(()8ul$fN7VtcTBQ*davp;WmtfqtIBe9H zeLrN#H4yqLP1}<+SPOBd2Yt| z6W)YYsF;5+plVPG@z4|{rKj=+<;xt`m8g>>6^5@!jxooa8!vWWqZQ~s;v_fVK&w<# zr;-;(XPK+!cUg~8vQ+mP5a0e~eSH@#YJl{Ko?8ar!Rlv_;wKTLRiZjgbLDB7=MI(V zuoii0raPv23a_Ju2DyK{4Pt0H&KYB%rg z)mNEX;*-Jo9zOlKItJRi{3YeegKa>z7rOqX*s?Qi+&L5HLV47K5r&M{YEIsjAv6U$ z!wiCFR)DI+59R8QPD7h=h-L(uTS7jAZt#qtv)dHgGP-ut;|g~z+s^SK7mIX6Nb_d^ z{4QdK0PQWu=w~5&(~yVdFUvjzC9&J5gEK8od{r4S+0GrVHx(nFm=T?0WwtY!D0I8n z!Ao988vhKE(cl^^K)4HY4Y-mb>gRE#$@+G=Z(Lv0`~6e0s@@aAqXaU^8A$k+)-N;8 zXm*cIFo(LTFp9lM>y&DT3uGM;;JZyC^kNg)o^t!>n+4>LNvk#l z;L{=!4$(`L=w5R-e%wo>qz_4rZZjOWV3wWYN{C`|WwDqEL8$gQy{@f>C|UjK{v*F1 z0rjXF${ZieAQr+Ai6%WC&!!&13|DVK`V7N&FazWs!laf2f$V>5cQqWA%p#j&5|x;~(S!QZXvxs{B{($T^! z;8DYxX&QjYIpmAtz%A`wm&Qs$0tM;9bqf=lc{$Nz)U%Yphiv=@oI+A6+lovEM)>-u ztzDp~_;cZLG=$D(!AW&1i7>ZPEkKiV2Hplz6cP!2#7;K#x!%R45?!V5ZQU+?6)_SM zpp$|IM*9XQS&GVk!B#pJu~vy65N(ytq5G!|0qB9@i;LvtAABk)5n2OU9J{&B6@6Aj zH|~q9N2jANYN(w;Ad7O-8@A2AEbl2pH-LCcDJ$N)y)9kKA333ruQK*vTiNe*v*som z=JAro4X+oI`$U+ONA=ndZwPTT+CjK)oJP zQEi`GzZK>+!GcOcphR_A1D@c?Lzm^Fbw_EK2r7547gAXc{)BS@zxwyyC_o_Pa#MRr zk1Td3GBA?d-QC6CaY-H<=sTNE?654{L_R(5`V=Em^E+cpLVnb8+M3@+f(-oMl<|m_ zZ2J1iuvxXio+;y0v~;rq+*X>~_pjHvMNrJ^M_O2Y2$FqpMn%vPMB96IytZ(DEUFs} zdSz*L*y%YB7IXgc;;M7~0-Ywj7*Cst97I3Q=!cbEbI)S-jYn-x;1`L%yM)nRRAvWN zh)Tr5azD-HX6wA#N>wKNhxC%OR9<7?4ShJSeg+LiM_`d}jc*3~$>4{$#Y*xO4TSz5ZV1 zA-MuNcLXPWWR=0LTfPOE5h_I}An*K^)KK}{WBr8KgaK zN3BK#E6oQe4mDZ=dPuY{2|Tl)@(f$QL#ee?P}Bww!<0hxEuRljHw2a)^vSGAvuR}n zfDtdwrG+`C%GT3T3|{_4ub6|Km$n^ljc&I|&s)}~m#3EUb1veL$Q7j`Ulv7eSZzy?%MtoMkc3c%Y^ME;;&I#qY7v zE&Xrz+cEPt3oToKl+2teO8%Behjqag`@C)jW;EYQ7GGwukE_<_)#N8G{VV78?P2%T z;Xi(NreM2abAnQQ_Z`1G)5wKBQ;_~&2ze<8NFDIOeh^Clpx@^I4#WD-ld>^!HZV4D zHlVX~va?lDhXts+Tq+~}x1NDe0hOv$|4AKkvZ@V=@(Ys_D*q3L^&a^DVpv04LsLg0 z@#g=-VX6Ed4r|mKzm(-a3QO$&mcz39|Hon3(OcU6e{onoZV<{!n=LQ0Z1l~!p*2B$f%}71VK(0h>Dk@735RhLH%J{EP!mT|+T8-D zhy`pEenY6JB8c0waa1Zo<8*eWY0-6BaTlb@_*WR6ddHMh@$gMfNJvN+4G)oT5Zqo_ zTSJM{-q`+>S7~nR=)lLM|NEzVcy)t{dV50yj8?;ZpxCKYh(o-Q)t-L6ovrt^U^5s* z4N>gow(6#~s{8U3NTp+X1nVlcODj2kb4Oh{9?`(riIWAFm&`y~-EegiMnO&^S$_56 z3^Nu1%ZaxcIjenWt<7V~K~C4|E$@5ju=y}Ep{8)Z&5t8+e4o2&OBa55ZR@0NNM#u5 zn5oh3)6JWu2UFHLGifN<*-5Phkz#MNoB;bXR2BmbU|vy&>&ETyKV790K)?T?vw!ZKb9x}-U7sInqb1PdA>u&kRG zYGlOqMhh>dEN!eRB=J|#Hng$3yLo%}@xx2<;=k17$;!#c%1A|cR%F^5_~~g@9j=*G z(qp}!gopz%)kSF;s=O^+aXW%5-yzj=)_k86o9~$TgVV)Ll*B|;b?vP#?Yhp+4`s~^ zmM=(!i_UoAz%9NWGUwbfFGD#Y2)$Flj?)J%Sv>`PtrK8!-y6^A5o|u|-@Az4_l{yR zVg}ODw%?}9d)`M?FqMFt=H{9LMqpVE6dBYC36^=veQyF zO#OWP*AU@l1ft`Csv`N7ZwXE1q_g|RI$PP|LiCzh_feagZQb;771qC#-xKf?kRKpu zrLaz_u+D2E046AkOg${<#f4n=uV=o;OYd;7-dOK<5olh$*FP_w0p*-PE=S5pSe$GD zzay4Itl_VKeYlOcvAn4wIOkHo$& zP}s|dpPZGr(Jxp8pAE6i%Pa7UT{ilD0+70nOI8NItQ<;ZX<*aeQb= zettfRJdh$mvuHq{Ke<9MP)XJRls6`fM;XMKu)dnZw-LI%v`$z%^OBP7MfD!RG$jctn zukiA(%r}Ji!!JTCE(+EMJ-@^cHc&p08Sp?s;Y(e{@4$_Q@0q#rm@dFan0D~n=tsA? zktHEE0*VN4p?PTl3)Au((=t;_E5phX+wvj}>kRvVrkT~n0URCt7xFE4s2j^=0e@9- z6w%Da!nC@)w7Rg|qS(gVrrfNgo86>3tZ{C&`76u{10C%e=Y-%6f&HkrUG$)MiOUx~i% zem_MHLTh@vKd;EBO>Pi2b~d(O@7+BuAD5@lpQ8^82LoSQSFd)=i%NRr8hT`Waeml1 zc<*of-<_>?y3BfT2ypOEug^VK=QkHu?)~mRR~Da1qyJG@(=u`ReC9>Fc4f7d)#Wu6 zRTT8p-EOI_r!Uosnd~(fdYzii#}CC(OECZAUbUY(H8e6YFDxoj{%_;z-l6_;7o819~v?)DyG20>m%up%R_Kn>#Bnsf*ZRU#;H+op=>Pti`bR5O;t4w?bP&L zn2bzx3_KS0+2#3#xz#lu{Dox&)r4du^n+sq(}JS91e62gd=T*fL@+4W+w1M&;qm@R zo`N_XC@y%2zaW6H83^phw!*LXr{^7r4t`J6G&RGGor9`VZX#M8P2K;^I5v5WZRc_Z zWrdG*XH)&-0eFR2ZR$o)W7p(wDt2xUMDbbCBY$O3b-16-3uf%=6pFF~?O^(l8!v;O z@^i=1OT$CNkJz#uHB_;fEuKY%hu-D;-O}k#$wu63gj@F+2{Ym3`6e2lXvae5Li}rM zCjo=hwP?=JPsT6Loa67L6EtYOSKcXd-la;h$Ey{(eEh*pDGocYdlyHs`sdS-{^sCq ztvR{}a%>#Iba%C5IWOl^o5lX0ZNI&i!z2~VkAa^w4AYi#xGqt%cM1Y7CxXzf3U@!p zw(jdyH`DTOywcKptC$;kMtTlH@d$ZMzfn$;@rF~l^tS!?)k-tDR^P@_@Ee}pGYX%0 zZ_;P#w9wvb=lEN})Bc%NZ?WL^qe^uP%x(=;uH5DW<4}LEJL3|2{bl2SYwPRd*4F^ajK0^P6D2+Q#cfMeoc<(al z^z9OxRLrTRa{4KAkK!^Yb6%4|gP8r*81>k7IceC`XuMdlV}*gcHZVJtn#Jc-hD($# zS`TXSZu%HMQB{adkgHbthu_`V>hQ5L9Qp&32VEly{>Ir?mzvgt@2qRKQ|HxE}K=g;{uJ7T+nfTkl$R^;~fgdxp~yKOgb8fHn1>qI?j&+TX|LZ#p+?Bt@gF z?e<&qpQV7WywJALfk7U>uC6Ctw{zGyh;I3Idi0Ql-t~qrj(gR>4txaY$Irkmv23H* z0ihiCvA|iUXPcKz+h@=cg#DTz>6@#nKedRAHY%O5{N83Z0wgA00um{Gh&A%Ywv}nt zzm9D$o3B4Nrov;7MX8DJG5P|?XH$sdq?Ax&dTkGusUTzM)-p{?ps1;Vl{}3~nig{@ z<4YpQk>Dz4-S8W;ZY*#xNHqn*u%_H?2j&)%b4xUEQs>{m@JaH`YZwzqidzlAEF|DC zXcC|RG%_$sf$@i0Lo=r@pLtw)SM&qLzWzAQbf@5n(Z{@$M}sfD_YUg3&!p)qt%U53 z``+9l0S~8$D0Wb)5SJ$itgSMQwwXU=<6DxVU3NWFkgk%9O|<+3n!pNtWB&USbOX02 zx;C&70k8;skPo+1J|5_)U^(BO0`wWh?LYVJA!^2b9;*!}4%XlkXn-80jy^TA`~R}6 zsTCbO$1ZRwFR1~b65bY{D*=z*Ay2SiVU8~U$qa7v+>lk!dk%fYmuPY(Sh1fEIhRg( zRo(5bg2$DT1`es(f7Pev^#3l~9LZN|8HJh|hRWll-lyZxjgDY|?82R>U-HdG`{6t< z-+gNTb%yiP|8g!rLO~o|x!Uo$pc1`5G2Ei2?#{)ahxbi*_H#%J((dl0LbER6-*980 zevoWm(;_YZG4iu6yj>;-F9XMG-90V#kl9kO&}SJf0$(m ze~!M#d9F4-G<^f_wTou$Jr>cEZv)hU(qBKP9X)v_Xj5t9srJ>6s^!w+mxeu$=js3} z!31a*JPNy@J+;47H?kqn%O1lP0S|YrUubrtAnQZ^upS>XYENtnOGnJgGG7v=> zxS#)pptyAF0QdBGd8~025O&Ylt!@`rY+RhOPwYJ<<_Yd7P=BE5WMUl$9Aoeoe5+_w z1MxBzf?#ktgz4d3=+H(Uihyc}#bl-;y3)n61k1UnPBkH%vW+fyFcaI_u|lM~8tP7% zhK~Hf4La`WGNp^M;>6A&)A+40Q_M9$kh~S1Zg&a9Y2U-S501=3qSj7PQi>U!qiluQ z-he5NP9Q!m?~&o1nAtS2$~P zh1{*kwH0gWZOY!`v`_klz>qLVbNChEfsSB0U$J+q+rNyjn7JBAeY%Yj+~>BC{lK>J zeDXg1@vypt>D!zTxC(_qq-Ih4 za7zCN=;mk=mtyAkaDMxKS(tIYEGI?m4)*5STz}==wKO7%{W^!=G&seQ`C52PU#_{F zp?tiWvJGJnkzG;e1-5#+B95&LGsI^MMhS|w zj__UAwDx#*cpvyC|Ixa7FaG3bEtp;y+I6w7gcL4XDd#z<5V=|BH}zDzZ+8r)&y>^U z)w1(wZp`XiH@tV`h|)?rO;SnVCmqB@rm<{UYC@$iXBCDL77)gypVQPXX(cCPei2(d zc>~15fmjcNMlkr}0S`5X1Y+giedXUd6#M>Sk`{$c7~PHRABzMciK?4Z`uEKYumv4~ zYB(r@*5PCI3qxGbwNIWg@W70dW>p-f*I;DYGDz*8N*=kUz6S`1I}&xN*V$S?vZE12 zNjWVBI(f@7~ zgQ3w)Whd>g{g09Ix0LTJc*F_+EA<70E#~iBO|b-J!Or3i1kiZ#5~s)BjS59`sEcEw zNt+aQn=|;4@ie4j^a;7f0%3_TBw*TTVd)HsPex8xQ_WOE$?_?jr}phF4{S^-y5qjJ zdEaUG=bT;snV5|&&nXI=m7i@%?5~(=Y_5Or9)9=a+46uYN$!yx|^nF7KjMo|Fw|68XoOvVG$cV?->csWJbzyukY zUelkhJ)9mKQ)yhh^np|WQVbA}*Y8ZON-fus-Q}fbkP*`3MJS9$ScO({# zf)O^%f{TC`0R<3-0}ygThveIA>=}fyiGxCC1T1%PIsL>qR!=ma0`j(C3fZw^#dNBE zDxq=}CQclH`d*qYOVlxDaN1(}RN9NH_Y>kcyVK@LnLCE!U<5E0ctRXZop(4QBEap&1 z1H!@`)5KAj0DKs_e4>GH|Kie$i?jxD0%vT5&2rVGU2&}ALIEDia6{--bxJ+fnBGlk z;HZQ_VT8uo(L;H7XK~y|SOF2xJHbGTt$Zf{0?3|$PGxDCMXb>yne6{iz0`?O&5GeTrzG?mqKwoEr z_AfjstW>Z_g8(o)A{s13FjyjS0j0V!37%_QgnO0}(xn@#I?3WN^P|*HE1!1xHGlZINMB-kg)>4Ihio zs|9ybD1CwG)e)^AsL{wN5=ARY$;+z5$`QvhtLu!1PZ|{rK)=u?cDB`8PG|?Njuu4- zU_gR)Pe`+n3vPP>F#@=U!=vQ~V%syQK)15Pc*Fw3mlAbHoGH8Bkkhw69QN7NYWIz%bqVh*K0op;mgKwY4iEAJ-_WchF0_z9KV`NfB@&~_X-Ngm$3Ja@X z*|5>{<-isn%GZe4PXOpgW1-0v+bhcNMyv5W&cQ&$;F;zcl!pXy5&2*Hazd1g0=lp3QsfEfe)%M;MW~+q{gXAa;~*kQ|e( zcno8vgG%y7_<4C%HS^cB9E=MG7>}^Fh%8%HRFMS|@31PB#YsA2e1f$8;378>XWpN7X71u^vVg#=LPPxaLG4qz>dcz>Slk1{MtL{^nlzK|De35lFuY zK6psv9z@`*L#Yf?g2O?R>R7IFi8mnPNnr|v4K)fJF3+M8X99TlEOyvS0aSC?k`t1Cjop90j91MK4|4-fO!G}p$&q*u02OzT1#?! zvQt(w*mvYu+D>#q5&q`?Arv>uj_WfG@65x9sC(@J`s|)fP@T)5(w|tpRWL|1H}q0Y1)Kz;#6WH zNgwq6x%3b+LOLI^Rb?LJy?Gcuo6_ zfr6<)HX_Bu7UCDF7Y31fFkY*Z5bMj@}~@R4a@aCRjkAm^IKrZ$d)Q73Lq z_#8|oBnRYK+HsL}P(l27=Eg+Pv%}aK5pz(SCsfeX(IA$Hc3 zpoBhmcN*a0*nm<7f*S@~lZ+vEPeE0?Eh;GiapqTen_y}r=(G!m#`*50aq+7^al+0D zWM!bt8l^nvG)^RlaZ{je+U8?-sjFLZE^!NOQD-*8FbVFanCNR#MkN1W5e!Sat8sf} z{mT;j5$?wp*A}2vWRTIMjm)pmgM2RLRg{B~U~c+;r1SiTDeYpCc3bVabqfc|MTWS6 zw`vI%ir>~iz_9oC68V(fR-VKz82vY$5kp_*Twd30gc|73D5w2#M}QtcqiI04d2>ju zVdy6WHJB3?`@*vfiqciO1PJ-7+pfx8mbeD|rx@T7aU6mv-2oUp$DH~oINHqO`4s7U;|OP4W)gJ@gsDjiK*O~1SEhblfqNWB_-+uVKsyjDk}LlM3|gYSp*ym zt!NEfE)?s)Ju`ie5ExV#SIrLSnY#dvFM!hBe0+ye67-S}qUmg$%Ds&SiE2!JeWYLb zuGd_-jt7fvQbZi(*4q1IxzHa0E?^joKNHXIYbD^94dPOk+;+{rok`!v?i7y#(TY2n z&O@0K(h=knbXG{_FhPXa9uA>q;2v@XwTl7Al7rJdrnp1|CU`-K725c<8?IEaS}B*&=GcOQ}R zSt6FTg@m?W9fKMsl0ks5ORMn1nSUCw8aV-G${hJ1=d*ia3~u4z(7UPrduGBT|L`vG zkNX>%PckQ%;!KQr%wKFo0cEN<5+uGDut$Kmcji_@j`V@kpJ+^5^h`f|I>!bd%_MQ0 z0|mV|iDBI)Q7KX>4YUb-KV}=ZzvI;bEFY$t^Atw$CBVlM7JLJRU2OhS;@2EJt|^ca z`ZM^{jns+=m|pCsiY`@KxhPwxL{#ftQrATBvnbcWzv|YJl^=$nJ#MIAo+|V7`nsUWi z>g0b#)Gx=oE!$#vT1Hy(o}Gf+aJ<1RJB=*WjT>NO8h67S;p_N_5hXnw38OK?-(x}4 zh)wsj#1ULC?V%|=5{FN@}sZ^T6X*VTDH<=hUu| zv8=}~UBYc%<-SoW$Di`i$`;`i-)-+EebI9Bny1xP$%mhPV%XD>^t*R09Px@8Y%{u? z<%SGKe<|VI>*!U1e){HME$)_68aGa##Y9T_(qKp+4(>XGaI>``CiK_N2lC3 zx+*OcGgCh)PfOJBmH_RI&ooaN5uL@pL|39^!)&7AC>Up?+um&rqqqLglFD__KYtz7 zFphzpoHq0xaib5}XRrG%2n|fSg7De#CIR0@(#{jFN}s%}@sYpb zUY!|Hvw@8`@FXC+LXG)+l@{c-$!j`q8usOd@zabPZy0%4&QL8wtD-|4Y!5#E6Vw_r zGD)n~2oD7^VTM|eg%oy$4TK|{fP76kU=9$UWvgV`ko2?6C*He{`UW+gSNj2cT|q-3 zlN3`LdRHsK7Lr>!5`l(L@uuGW`T9uW(Kmggc15jrpJxDdv1|d z#;h8nF4j$KHd#qsFV*jf*RE;^wf9wI$}AVOb$WnWISg37RFnL&8s-`mXRYjl_Zwcs z)0B8Vlr&I$V0X34zwJ|Z-n~}s`da)r@J4`syP)rjh;h({00R@U_ZK3Jqc-wpS*eo1Ik$&?l~o8DUgJDomCgo6 zE4^Z$x~wO$zEPioAM^S%`WOhWC!CGNk8`@FoagN?!|7rEy}FwuUjJ1OGvhcwZl3O%Mnj7KGi96maFnq?2$~S}5X)Fa@sc|&)WrRzIuP|O zlFpUB7?BFFR?f75)AE zyv|8>N_(ThpwXduQh}vH4r81MapIuGBGxdD;UMBruX_@*zlEdJx|9Qp=IfJ=%Fp8= zlY3pgxkaMlb*Mp0EkL%DGJ-u^{87I^=S}!JHJumbusyZ5vM8 zqhn963#e56>GyFr(74oj!_lxbgP6W`tjUG zlJHvubdqhIrEB8WC^Hr`@+Zvu+qvoSyJf_D;ChiVYj$PxR{0dr2uPGX4vbkgFqB6Z zwe_DTvUtfVgg!!;Ea(jCDX)F|D|6h$a6>?ke`Zre)Nm_sE9VDDB&16=1s#1 zh_H5dlazcT+xCt8UpKvgSh945O(Gsj{RF?Qm$rT&D>=(#pmGVGH*g?#l!%ph-Ml=l zk&puNd}}HqFmxZDZ}L5j#jLC=T+aT8G-@2L6b`agd)>!gc}neeH{0O!z>{{$9QR#e zg?5w$Uih)o*r!O}!qNSmr123&4&p5Psz-Mn3^WA~{gcEKi5fUK3jOmL!mQ^v?^~}x z!Qes=7l@&&U_;fqv~`>UMWujfuaME6#XyodGF69LY6>labWdW&(T{}^8xxJ}>r6Zo zj~JP;919F_sEF#v?@oBv>#-AmB90J`->A$d?8;-l#|W~`E-o{9PeaGlA{rbT>|Id< z$-X+zY+XlL%aT`Wwvpiz3LhiO7Ik|;@7S5d)0fp0;*P(ImN{mQg9gQ}6IZE&44c8A>v@p6yvwwH(q!yh=VLN-oR@uga0re0 z)Li7Y)<{$&kmHXr74~1=(l|^aJDM|Zj`q{@1=D=g|?vrs2O_sKiB|j%((5U z6!6@NaEa{H_h37?kIqKq=BlPu)iUL`Mhi+^tfNpkp{9rL|R=3}?``7kt(NIEqDom?|@Vty3Kf$r%Ix~Q& zdYxGVRGBB;YXC|I3O4T0z!hd6r*6n!hnJHFWvI#`~(^F z{G6|;&9o0bD8sm_G!1CGrPu1Hsf5tS@nhZpfi+Avb~3)fa!-EHN%ai za3XVVx#HHy5ysrR?VX`MG5|*wrKKKqWMpxczqU{VWK)R<8ZSWU~Zo^IzH6d>jc?(a`$}{962aom= zOrTvyDEl0_xW`V?02zxZypzEPEQ0X%BES=Ny;97vtCj?i>Sk@4gL#-ZALRY6W--T2 zsL3||M%IO$>IVjzlaBu5>fF2=-4LaD6_eOb2=&ZhU5XxkcAu(uu5vcWQu?zs^sK^S zMVhMZxHTaI$3DVNHO%9bUY%b1Dy4vE<)8>x61AeH-&nPgV~4`SrLpav++ga__qz5r z%U*I-y>ohPKs3XW!@09v-}P_ZWJ#I7mA9UgMH)(K7C-<%Q1TA3)Zu(7-`p- z|Jcrz;uUb_O{;cHxV~ISY+a?mNnGNjaJePorF@^3-Q|XzHHx7$UeFoe>UDG}NYyM6 zC}dD8Y`Ane1HsblV4aQ9bfI~@!u2c#hbzSiyiC}IaJ<3Nvs`v!KlOosw2I#j3`e)9 zfVaKi+^Kl+-Jd}=I$~}$d*zU-F_|iJeeF*ceBUCB(4co2R0Cyxl*=GdMI{PYDI(~2 z-=R~Fz+hnFh%#PdO18*AHoY!$dosDN=FV^*WDT9@0EZM0VrKGe871r} z$r`^g+$l9rQv_YW27V4q!+KG3JNpdl3~8~EL)iyH!nnbyZ_V3nG7$?_cJsf%UNJNu zyIheGJte9k$cio3+MkzDU(8e(>s4?OHU0McwsxF+69dm6^Yb%X1MEf|*&o!_DPsMj ziEWK;lS6`qh}^ z<}RbPs0E2%HLUKbi$NnxkE0qmS+JK@zG;oUaissWh(HXE=UUwD`RBBuuliLI4ZS`G z(4Nv|{=tq*y{DzT#5$wfsr-)-^x6*8kdhI;6$1lZO*Bg9NC(28>Kb$OneK)qFfk}H zj2w9}A%`JV8|zQ^{_)QmrZNc10kA{EJX8dpej}qLENFN@@Z2`czm6Pq`j9?ddBxfs zW+BX!s4Gwn5R)9D^ESOSo8pccLN|yG`ab|@erM#JD)0yN#0c_Ef_d$9%XORCap=yi z6r2B$LQx2q=II_PZH3PfW!-^Bt&`O#cE`pn`WJ?^noqPWI{}{ljjw9#t|!ElyW$$5 z7_XhHF&<C;tO@zSc&0poC!{T;XxU^|;-O=!;Ym?h&0iR1#;oH>w)Tgow0yzC>xQ zp>Ay#w~NgRoNXe1S%)LW!T?J6-PxHq=k-X&tcZcn`?>Agq!QwsDa z*AkFdfVE4{B$69sI{v}i^k{4G5tZ?FP?H5hS6Rt|slL2uoxA5;OAos3pK+hb5&w-v#0;JD^(a%7~w<~sh8pxPt zmZ6?_y(7L_7NJ^5oMpUN^YQ`|7VeOe-h3r z|0SFQ{!2K2<|htv#_IkDaF+W|;Cx35{K4>xn}z?Uz}fcy4>;S>nA`rhz}XdC5mBS# zS(3Pm_g6PVD2bl}l0ixdiUa&rob|=UrIJUAEfC;G1T}2FH zT<4xfV4#?q=a6S4(#&C{MtY*8qrKhxTy4s->^0wncEj81`)T3qfW)Uf&69pPV@rc& zXXQ&E53N9*ocQEuL1CeY$-Yfr81F{wAM-gTtEFVp#7K{Ms-zC*gZ#{XMP8aF9Sd1y zhN7g{Q&x$^^SBA|$jHbEiO+X2`#8zSaDIYzMn_k^I+^jIfIHX&)LUqDo#*LQWbjz4 z{(Dt)H$V4-MjqMDelc=F4^p@d)LU!bn~B%D;&?I(HJ{ER%*kWr zQN4@#_L^ufF<^(G%Nw1B1!(*eBnE?X<%I>r@Uxo%L9HRU zH@Fht8K^gxp~uRfd70$yoF__RpP6nZ=G4rz93`grA_1?*)4RzVvZ2Wl$f3*3{S_6g z2_5Ji&-<3ok;upOtQwu3XteYg>tK?mscur^;^o81smW+9)^DvHkMEUFI4;DY$;;a8 zW)@x_4epDRmV>d}bTzILEh$nz z**Q5nx3Y9Swqqy9$4Cv~;BeT}GZfz6pKKgF94s#%XA9jQFE>6P#2&A;Co7wqF(X>x zZErRIq7{0$|CE|Xl~!~$KcwfR%NdqPEiK3|y7`P8-?oP2H? z-e|bV)Kq?@YYKgY6;xcV)P!X*(M#C0X?`ok6w8uEA_u0zGY`>lvv}`a?PbkG>7*nl zN{ESyOHVx(diS`ch>J-*77BBO%nG!2i;3vijwz)796G9^*aGPJ9~9`|d9gJ-$8{>ZWe;3ib|d z*UruvEH=$sm!=s>S(YX@$H&GZF0~G}wQ9Vphe(FU!zUYt$ElwLeKceyTF&GwEKC#$ z_bwdVcoZ}%3F2QPK`_>$qdp)e$E)8HcpFY?=)cTQlfy%!T}wE4i{~_RGqmvOhlSn5 z4UlDw8lQ}Wg2qE@9%%E>r}Vw2><0jGoQ9f#)PFkr=W`= zYj5IJr1yuA`f-(*+Scn+)9VY<%j;4LGwbr|3rnqA`kPY4L&O9J2W)F7?HkU;%vjpm z^SHgxr?u>7%IGUc(`Dzf@O88D@<>hX>?aV4gNTBKf`lkS;x5Xuxv503#F*IFYWmFs z`^U;m-ub+^aM1U%5@x)IxS#9EXKv=!>9(nagL)GtxF?{iD<&qRB7-37`f~s9WcT=3 z^oxb%&gX7p$qf%5>y#R11$5!}nKn^io?r4fJ{UefFSkyg6+1u;5LVhwijRv)zBsy7 z&)R;fi%Pz^e6G*#(#D|itq>BHM}9+opR6Lj*g1 zXbgYO)JFq)Dt?)B(C{yFCUd~EX+T9lQXD-wF*YeK@%~BAe0?*}eE!;4siVme5#jd2 z#D2f^mVIC8XaTm-?m72)+jrY(DCvqPiNSetP!;jk#La8|Ug`IXRA zdk^7zRvJH~>+-6xJ#Grx*3!?y*2>1h?(4yeiHA>OVHXJdY!)knL)F?%^u+E$i}$0U zw$7@iqPO+AMSc=MpI@F{s**M|GBHEf)-WolCL|&uK|_7Z!|riOML}(8WK)6Wl63Tp zvnvZrbIbFKvMLHncoYF5qt%)@IW8{2WEt~E_L4@cQ4a$y2!mxiD@w$$!)pU zr5c0IVgJ@A#)$cz{HCI^I2y57U>uonx_T1!HOhFzV_fRG97vRyXiFD}_NVTv--}YD zIm-0 zHc?Y4IWRrH_HA9Lj$a11-*pC2gHeT;#`W}Glan;oGJrF#3P@JI(MikjASXQYN81Z)y|~5YV6(fu(XQq z+s_1|MAXhg=w%v@WTReKR`Rs`qUaj0B(#LhK8>`K2R!TmB|?58-7+&9X`B}-{Jbp2 zDW~RHnaXhBTCG*_D{W0Nzf@hm7a5DDtEiq9@&b8UeAqP~-}*`jHqkdjZMl9CKAv=o zeW6|3(m-4R-u3I80XH9C!N``1Qgn9h#;xsRp&Q<}Ljs@C?1{oTI0a8mw7YA$-pbm) zp(N$U3*sgDlM^z>JGU0ioo48-QK2(@7QbE<$Z-+evGBx#-|!w0D&JMlr`MAOmU*G4 z>2|IXwP|EBk&3LFYmKid4Hh9!SS+@i?XYL{X=-Ago>UWu_T9fR2IiW%24}^4j&`n_KnCrsf1#z z#VQUo_g1?WTQn*+g`sEGSvO=rR7AG6(y0|j?68G9F|*)93nzztsh$hjq^K&eQz6_ zK=XjRJtn(;050;*>Pq{L7;xz(Pj_b(SwE@Q#n)QgESy~>iuP=WkY3^O1v$Ids)ROR zZ$1Oi7sY(cCtU3CyirlLB*=&@sEWpqX@ewmZ=3;F8Y@cFxU!?T6Uoje+oolb2-2~MD`!HAKG03Yo z8p;R(YvL4MpQMoxpQMajLP?m2ouvN3>7qq3m?YnH-?QYGXXDv~- ztZYJgWG~=9vc2R)t#aD0g?XA*d3EWIzWq%JYSL#lRJEgIel@trScwLdRLJoM0mEex ze}!Bke6R#pqR=Iopq-kxM5o`%|5Q8Xh8LEhFDK*Cg;HP#X!rk+RvA{5S0ujHC9 zKIOVo?D#oZfMfqdQV4!QoJ_i6OW{WrnrSK*P7X;@ZIcI-VjS^@1%QK!rlqXXmxFVd zV4$2U0K{D?OL1x0-a|MbnlBs&fls8mULT8K*yc) z29RufcmPdKJeoy7^sb0yU9`KY2n45BW66fLV0X0yaG3UIytAf{un0H~2w-dwY;zw? z$4lxO7TI|tEy3^1IDcI_Ba|nC7obg_8xzBeIlHmryCI!P6WdQS8HD3U+Xy-N+~e{8q|ytgg39L`{a9kf@1_JT7>RJO7E$dQ_|`)PqW#KUOqPqu4Yc!3IcJ^O+U=k75hO>QuvS*g@Po8& ziJ?JHo#}qL)OHV9vMXdnyHDpRv763KNtleG&1t*3>_q_d{sFY=*8bhN?@B%R1DN6^ zxhENeRYj6O1wgn1^u$OTw^j2jI2HYXlP_^`XLXXJbi=>@bu{XzjAldOvMB%dn6$316$Qki1T-xjAgs5M$F!D$qeZuxyUB8TrYM2nUJl|a^HQ@c zhRg-WRQIqx!ROKV(Yf)rD#=9>IYBt(&;emjUT58AC;u~3y7djf#i7V29inyJUZ!_6 zTK;@dw?IPS(e58LA{Y4bC5f5tX;koejPWQA5&0MXR7>j$2~9u)%=ut|x|ntUcb;h6 zio!rQixE2U{2$1iI_xAVxQ;+%z$<~ z8WUUE?Zq+rPH_VT{sD)x?~;tGzz=cDQ>bJ{PZt?Mw)KUUoQFo|*3!>9M@xw$wNnmK zmA`y&&g8+cMMzAOMUZM`(^D`C;EAiEZw%)x^Mz%Vtb`~bBxNPMDcd&;sV_muj6*^M z$NdzFxj{X(_}MZFQ!DaTi)PW0o}tVYRtR7Y-->geqslI$nWV*^kN?IPxAlNaxRxtD8HoW z(@x1NqZdsJ;1cAkCmc7D9Lx*nIi8w29vdIWpRDFJeVi$0!9w05AOgT@Lti!KCoT(( zt2pxoz+ID=HI6@T+u1;*_7_F(3j->zuOsg*Z)z+JrC`ci=S4x6)jPlmg+znmmx|mX z0%;mV+o{H~4Km{dt@3ptU(M}6!=MUQmg9{CX(vXRrRs`~ZA>V1J#1}2 zZa3OG0aSzrIPHPB5es>B!*N@L)1S&u>szNsZd4N)X%Tr5;rmIXDjTZKdQcNjeq)eN zb9>RQfeRnZOASvVXpwLcS6$3XzX-TH21Umm3nX7?3WV+;&&yZ{pRHYUjwTGuUkHVD zLdkuoXJ&p?5s0MM&)S;R16v|VfRWHA_@2IHSu3h|H4avD*;GnKYgGu$4iNa4?DQ4bJ_CWuyjbM=xZ>Yo^6^t~FIb6jsb+{-eN|J>267F2Ix;$}&~HWFib zYsT_euL8g2j6G$H=i)c=<1AdhIS5R{i)$U^9*`yatnIy`wJ1=2Y=kV19QQOA3Yy|Z z#syP7tm5Cl^l+?H`qp84O#kEosR5GYb8kLm@A#U^2u}Mo4y*C~g1oQ0&v(%b7Q z8Xx)xNOgf>9V39IVAHYLq}1^ENaSuQeGiHGnUt_O$o_2e1D*#;f39$3P@1*tKRPp; z@nitl-MTo%xN8NukA)J_8A}bKXPP^M)#l8AT%Mjd2E=a2!mEH0k21Ry3&-)Ye@G*Dnk$qY@L7?!lOx6lQ8cY2^+PJJU zusdsqlLyKXs0e*3#`1&lxAsZ{+FSg*#Rr(EbqaYonubPUVp~~@am6)2K~n(G9^+c& z#@{<4z9#u&8z1z@QDdoxsl;KmiT1rmIICZ>UN{d2l5Mp90nkVl6*xBPh8+%sR<#jX z6NAu3AaMaqCKh^{FpYR3wdSQI?0q10qT_hZDkPXE&Ba3(AR(~T$4JKLrD`kB6OjPF zeWp*u0(acgUMp=enGx~C2PFhp#boD24)^}h;*^|WWF8tAq5ij1uWI0IEZZ={ReMikyGX31Ac#F&CrcewD*%@S z_#w^xh$Ge68Sw*sZ*-@{d}Z<+fo(iKY(y`rW2i zcyvANzYpE5l!v?yJSHgTx<=eLK9VnXcIjflgIHTl>P%;vL-5lXB1b6OFioKEiLPrj zeN6PdQDP!FdS;4qn&6@|)$pWSlt8SS3%8bXcU}Kzfc9SDoUTn|b_s@k{OHix4uiW# zCAjhqh$_oNWV+(*_JxHKhiY^qYCh)UJT2DNVhtrUC~D1{b16#^Z!W2@2-&!68=g6P zhg^Tce#JQq9nt9KC&!>S&9xsOT!5AKu%m8--A(#N2D7jnJ1pUfG zNGwRGSxRn^g}lgGT+ofo{?4)w}p( zcrr=Fp=O$$(GX|Ed>etAn6S)iV#L*1t6e3JYbhzb=`bdJ%{koL84ThU$lTrzZfE5O zUK88TK}s92ilUd>^MfzR=GZ>fi%Y2D?%m4|b8&R)wh_8tI5he*U7@vQa7R|i*gVbZ zKS#~?sCHm8Y&JT9jaU_y?rKQ#txShTcURgKEW{(AO5T^KN7+FeJ=`-M9-|^nrr-xk zK_?SlEgu9s^WkHuokpY2R98Ne(oOy@KkHS0a*Vb0kT6UKI{` zB7VYo1R0EhL830~?{pr(%0M6=Qws>ILJ$^;It3UjVf;D)Q=`R_1Xd~x5#S9+BfM{_ zqee;i%v6D#!$R`tJLI+WoPib6jiLj#&_p8l9BIchR}1M~juBDHr*g+G@h%(Fpg zkA$3y5F63=r)`)Pl;&|54_Y|hQYnlj$)mhiPq?ulQFgYlpmq@%q*{VC+lE%y<5D$e z1TyZXNiiOAMsWwQ!}=h3f;7#GQ4q3H-E;7SaF0OD6EoWi+(EIEU6OJ3Ov~gd5`pNH z<FO}tSrQo;B{sr(Yf{m>F2yIF7GmDc%%{U}lr$LBNggCY z1@qOi)fJP{`5LUm%?G zkGaIAlJ+6<;YS<1v9TWr0=bp0T>g;jsx0`o=y`UE3&z(SL+ z3jZtui6IF3La25dkB>s~( zf@u;I!=CPrBzlW6;tH?MjveBgjoAv5F*sJ0veD`=4^!8Xc^<0-?U$_kIT+w*0{DW? zbpnbSL+%{6_qvnZfnm#qC7O90csDYXsy{?ZMIj=Q_dnY3XIz~JUxt@uS@iMozS%1DcNGhqr-Y;T=(|kUB-O7Hs zNyww{Wrj|8WPPq5;7F7Kr_re-*)Ve;-E(Qp*9>N2Pb)vut6y;w>vfgKe0qCU7IM@O z)>{1%$LsVHwgYI${+xjabrqakaTlg{Jf}iY81t-seYfiK_4UD>?2gTL=&91k_&z;2 z2VO`VCc1@3Eo_kKtl_3R;NGbr*>~(N{INuO(_>J9;pEFwSmF@|hs1fB=#uU6xFBny zn_5uuqE`dUuRnZ5BO}xCtYm(*Mg5}}ZiBYCSjn-`m2npp!3|Ogr_(aH{*yP3Y%$Vc z1nyKXBoXUQgwYV&9<&ZgH2l;SeUjJx$h@@sTUoL3p3eOi29%73c$Iv^-Zn8JKpaE> z5T0MPeyDY=J#qSO7yi#T#Er0K%4=pl;KYYGzCoyC96*A&C9ALlKB2Zwpl|R2i)qsr zb(O96MAvkDn5{%snl)m{A$HiN>op-b+E5ERRp_fqLr4%0VAsjO}Uho zfQ4|>JGehnFRkR+rXLzcY4~a!cRV4P#V|`uJBGwVBI%c*WloE)IC?*pBq&}G<~9fc z0!M(WGNz~$%9^SrUrE_34c0s7_XTN;R>~6Q`ht3JKNgMh&;9wL;iEXKY z7fa2~9n%qRTb}M>?~pAJ3u)3$z;$i3#t^bze)IPlf}~}kcZiEr(twkYAEsoC#XIbF^d<+1Rx0EB8nA7BfQ%GB`Qy-OGyN8 z2(t^MJ#_{qDp?LLqu^Xg%Yg&!SRSuX3?)f337Xp{IclxXB3Y+-BPYPm4)XP|Huy%V z*=IihvJ(JxR)SAdDuCH#TmH#Ha_$u?t7{fjFIVre18=s8ok87Zm_qi$zP=O>c#$)P z!pZLoq_eL{H8GdEv7eW1stg@MG!G<#+-FvD_YNt3rdH)h4s$}jVbWQvU(|gM$?~A$ zpD94oUY{#Zzb-JYoPWD*T}0c1{=m=y8($$!W{~ru0$2*uYygRmfzTahWn*;*`5-;nKu+0ZQ{3Yvx6;%AHLfiw`=Ks|%IFR0o< z>=Ys`sz&v6*vB=aZ;f}2Q>awl2%Xo(*P^t4vsA0Q7mOSluU3)yDeN1)g_LsP@v zqhDUXE~FocS@9$SR50`#CYqF_g=yse0#z^a+4rE2a+|GXt{@1QLia_$4mvv;Zu-6s z40)Vj^V~{=xwL4Z7l#iv2Lf1{HvCLS5f1s<=7i}I2Y9g;Wb_Y9KT_EWU`9L)eM1V^LVvLz7#ElvFfUl8N&~ynB?@5cYs$)3au7sbYfQMXkuiTN5)b~1zHrdD! zd+8`rez!>u-MxqzWLua} zeFUh#Q6$0%1U~}&{k`>fc+oMnB+9D@c}sv9PetD~3&gYfNSooj%M1AStZ z;2IPLT$`9fk~(|Bi~H&09Bd=o)T)72V~qY3)%a<;L6Y%Plwmxxjoi0zr<51nGb<=i zqVE@Mh0HF*1*6hw&fG?^GXkQ)YlLb20EGi!qJxkKqT{i`>N!<0`diU;#FgGw9N#1g z{X{$qTDU|Q3E(aIq5-wLM%4w;OqqfhYMO;*@OEr9-D`>491-O6h1 z`|zgizEV{BmiODAAnh+PVQqY*ceKJz6%f^#AykbMU|&SkBK7*#LFqC)!<|N*mobT2 z0?E0%3SImz=9Dzk5d!`*05>VSA@-R3KEyJr_$kQR=~_# zVa4-TrqgaMclow4G$~4ie0g9FhYy;Ryfcv|JMs}m7NgVDXuwo2KciHBIzUthNLD-~ zU_s)Uoti%f%ksv^w9RXtS=%*2m9Y5{zvn#q{F`3Mtoi&xX{r>9>$pb#ETfts;rN82 zI+F7Fg*7sgtv%wu;c%>`FfD&*GyF4^G!(pM!Mt1!dE zV<&$XpNbvuX}%7qIwj%V1fXC~MyaOnK{6(x6O^)zf49Zi@#*2m_Yw_@Z@r9$2<6bR zd5t9U8hhibsG@l~ux9R3>YzK6aHu7wY?O-q21>?7369Jh3Hb+S5OE*U zr<&5P-2Phvf6NS{)j%#-dHN0~aH7bF3 z?T-`B1z)I{K#>sax*rix`6$Y8q_I0>^maxV#;I+eq?vLTfPoi+P8-+jGwUYa*Ke1} zy#G0S^i_R?BfnQNSZRd->-F#!o}bj`*L@^Al+#?^lt6WucU+q|BfoHgG6&<3RnVYV z>z!?ItUQ8H=Ql0C>^UR;wajHqgl!L_>_x~@Z}bCU><9ZN z7Or-9r6B|d>)|Rg1B!)^qBaUdJZ#at^>ntxOoL__*JZ72anjPbvh|9I|#BPN5l;vXg z#vx{Cu-5L$h^I0K^BE!o4JtFztDcDK^5w|4tej--NsmFWXH7$InM?0snet6_b!Lq= zcM*|McSn0Lm~k@>?^50$7kah3TKR=P67Cx_!!|?8bC9^ph{DT=Vy0oN&=xtsRw=`l zX~y;`);6luc4{O3n2f3qi9OJNUmx{(j=q^xvw&yw)}nRJlQjrQ&k~-mDU9|mGB-#t zbyz6XNHF#1ubv#1o*QYudA-UGPL-dK9a$+n8NS50#0~j@bDmWYtH_LKNv!BetEfXt zr05C{KKfFebqM~}j1YA<*4Z8D{DW2D%8KYstchALgU5o2Ga+T|YY)JGa7)y8fJ{SD z003I@{~@=u{eR(>CW6f~{+Qc6yDItwlde?;y$> z)s2tD$DbG>4{<(w#VLcXb6z{SP>kgrRb;YXoYO3>P>hY4p{{ww%E|FgbS2AWCn}9k zkFqV!bI}1)Ic$FRZ|~VA&feD;Cbphu^;KDm--mM?C}7N|j+L!pO-~>15j@*N1=XW9 zVy??cf6pui`Y|u}O@BY^5m*vzPQOO-e#OPiLv0`?^pISCWv7ERwN6x!v9ED>Cdx1N z2!9WMk25oWSVjV~>^>dNA}Sc*93t^iRrgS91vKuPF{W2HPnYIR&%8q$-$U(i`mdh5 z7ZDNl5Dz;Y9tkth_h1imtl_L}ekPOD&=N+lk=2xv#B9eZE5{|N$SN%-iBOc1i>_ag zk5A;*k%!xhCnzweVrk|6`4Izq@X$YRh`e7MJOoE4n#MjUsJzDxc~Db7YpBFOsZn`; z<{l^aj+6VQjC#W_cokCzZ4FgP9Pr(^^~l_csV~Q<7l*n>%bj{ki0lVvJDRCa?dr;! znTP5jAz@}=d3k2Vs85ZtPnEG3?;$SXt)f;)IKU}NEFmf39xMAy%iO!Xx|EjuLC;W3 z5^#9_?QTd0h`L2v57mL_qI87hBQ^brp0ORP@QNSsI}uhfdOj>X5gg~fW?orUGCIO3 zk1JAcR%&$CDG zirhCF8(T>gLrvL_ktc%bNPv1)(qR&+p`wJuOtn}Z8|E~L8By+()ZI;+T`afAt?9+? z;ckzQ!ULndttlfNIu?IqXe7gcXPX;FKr{@)fa?v9KNo*9emwq_G$F#4468?Ab|@?p z>_93Ge@1ay;ZVFm*jV3b<7#RvVFDwAq^+ta#N46HPmY-u%)3tPp(#V;5D)aJkE8k! z3BufmQ|)@5ssLAGo?mn};PPs(Ds)xM(!lxo*@Zrw>4l!uGY)OvdEUgRtaJbT3&^TrDbKr(#q1x%6G)9G&^<*W@a%~O5rOG z6I@kjaqW+jj^k>R$D`A!odLiOJoQez-$>pVmzVqSX z^KbC1#7kkApPe}|?2$1+Ef#%wPjP+HI~vWqUuwBi_5&Ck^o<&w8ISfr!!hv7b?63F z^)>D1bt<=eYJFU+_v6vtR{Z+tmhbY&ek#{BGW6u-@dqTIH7*RIBA@<5zNoFMYn-2< zU!bd^U9k+S-z;pzFxAt)$N2t;eR32aKBrh%>FJv3S{0U?5&Rw5rK~cA^Z$PecDhz`y8dK5+MX693XP-9~gWilDo=#+Iu39Wk)vK z@6;58JV!@DN61LMkHq|JlhM)LqtQ(=({ZDG(TJo9e*ZjTY+%#Y(lWkx1oW&wJyJe? zo3qR|&@s+4%x~Bjg}t$`T>coaD9?<}+vjjU`!4mgErY{QJ)^IpX{f6G!1+or#bT$%Z7g7##)?0~t-b^wm?_ zC53_k4&4T_Zi?-x=z^^NZ=6{}VFHAwfY!MomsR zIx{hqXKZ3*EIVD8!|ilGc-OdS@V~QV4_{kXQ%_4paS;;gQgGeHLd{Mm8oE z2DYYgpSN({Sh+xJ>me9eZti`nbP(n)pOCBoqX+q?Clz#Qqle zCZ{AM#U;ll!lNQ0|3yI%guH#Y!~e@^i^hV~4+;k8+fM^=E3@!^@-Q<5*UQ%Hm@*@( z@D@tiQXBk(RB$IvEk2hwU0q?;DXXuu_)u1TJSN|ml*benDNGE8xX!>kMI;~6#BXwb z>9KyD#Uj4$%Dv@q*9WSJ!oQr?@}<|H^_Fm$%>3Pv)TjNJzN2#pP5t&L*p=Gm?RNKX zPuj22K<8uud;UNI(B-3=atC(@s?%CNOxQPpg+zO^%kFB)3JO(znoJ7Akqp(jx*6W` z%RRpMbaj_8R#XsqOTdMlQd8Z7w}m{B13$=%LH35h;+V(vYXF~s(c<;m?!9B;X%LR) zW~=kB&k})O!{*z=@Nq`j{d}0IvuyvmX2U^HSNTEt&9O(m&K65Y=KbAJ7Ju|-0FO_6 zcQsG9Cm!+Fa!$_WR=0f0#z09&kJsDnZ(%uG2(YF*s-RzJQ%|mKMPmcPF8f6#<*@hb zg=L&$l5V+R>lT92`Ko(uk7WD0xHia=HIdF08eJi8MHNpWyhbgSydk_blDef~8j8Ap z`8XqMb@!#%kXZ_vRKpQvz26Zhj`xnHWP?ucwB?s-_i@GnskCOAsH=lyP z%cUSo3)|?gnsEjyIm|2sAA}72JQO*q!WrB(^6jcUEi>f@nqlRVrMU6kKW8`5wE z)6EsVeSVGQg0imDD||M74jkd0p%^n0l_1uawtTx=3Q`Arai3wod%nHW)O{v0CaPBV zp;O_B3_erioj;%di?MSI)&z#q^mlFBwr$(CZQHhO+qP}1``YZjHn(SLcXn#$&nA^r zDpjXW@-I1cp7Rz!_+2?uYYUZNQNp+l5gkclCwT6v1o%AXEz46MW|jnIU@`Z+e8bG{ z;P2y^rD`0Ja~11LVPZ-0WKDt3T6AIvK+uOvXhgeJ^gTndJ-juZ7bNenfTa1R9kPH+ z6$Hx#Y&`*9bkJ{>+;Q?` zYJqR?fDFIOHb?mA0p?+V=$Rb#Mah_E!MGJggOYOUWI$Z zy<1^$uiSOr*JlRnGdokh^k^A)0ZK6~iA5#0jRSOopyQMSB+%^(UZiJ$73I~ zXU|> zDKE4%A7_|AHr&LdPA>~1C>am{($fr@Ui(ocbNQZr=LtEK^L;B6?H!Epa8I&;+v_%} zHg^;RCFZdCForOMM&GVjxwtn(DC(0@sWIExDPj^-1qst0Q*6!6=H2=uJj`K%s_2Ad z-GPj0RdGY9t|hSq;Yn9AQ84fe8$6`M^cVKa0V-RMVJHWj3mNv0>dt&{R=%B7s;+?T z!KNnz;hpUW zst!qm`@goN?O;dzCZpq9blQ6e~|2xC%*H+r$d zSs89^3Oi{2v7y|KwT)2&cd8Kmq}9s`vEJ`#?sB6!=7Bt#3iyQC9uZ^?A1w&k6eKb% z&3ue-k)QeOt{R|X_*J!EF1qpcwdT;iy1XTv;81Rq5s$9da8_8yigejqAB(nR;@%z< zAo!UtMvJ5qIX7A`ewg|!kK~_vH@6SVU%ErNocVVrbDbd7ANsD{ep6s7+XE;CDI=X` zTboRvqVB2)t4tl_V8z(f0AUFw3|MRGxqk5rjwnw3g&_w04fm9+)Bo3)62!uO=L;9w zMd6a%ON>gykE9)_o921jCAcbe8w|Y2x1r+SV2$}n#LIsn8Fph+oncf^ggzAw0n2MJ*Ul21iCRRYKQV znNfCSZ0y0rjcc4lB=eYPZpE3W|L)RvU5s|x3Q?fU-fLFiNich z5H{tGT8P^Xja}fH@G(V^Hk_aWth9%6qu02?I-pK;6t)oi6+4pGq|R-O2MR&3D!jAD z6p6#YC8W$JewF%Gss-591_9$ns;5T44lF=vvaTvxF;EIKL~F;QPiLdFC!=;}+b_bU zMqSvJ`*-G!q(LvlEPWU=elUKEHn2m4DD}l4lxU2<$3V9jUs%?A>u^~)n8Ap`7Rp(W zG%_PArVNp~{aIoxbU{-`|pmF4N9*7@FYa~EGgB?l$^8WNHX zYJ@`n<$bN#pfpvjFr`CKVPi#x^F>DR;j-Rbcnw+-W2?^=b*=}nCUIJH91=$+V1|>R zO@UJ@bYucP1YJ7yW-G=^u(crM0jdN|p^2tK90qS*{652wDFvVRuck_Rb3?b&_1pt# zLYQ%5u0sU7wfUEMRZBekJCcUB&`sJfJ2cY_bk31-UV(t5*6#37D^id5ehr&AAw8xc zTyrU;b95#*U%j=Vz%9MjHD?~l@Qzhw*cB3xk6c&gUk>J$7^CKGC=@8lzCA>QmW9Yo zlJ3K8OM$G>N+oUxmwuKbTSH(R*|oMRG_XxN`s;T7qL0SQ#(64O841p@Q0y6-7$3?S(hAO-4rj1daMl@ z0X~j^-tWBZ5N_;uB#e#V`bI5^iJvxtsQODNFcg&`ES>&vm8Nn9L_6DSc-8?Tq@b*j zygvOVR>uRlD*v;Mu;=U+9@<-v!L177`3CNND=|>dC9~{f4`zlx%sfEyqL}k<(sH3` zQ3bvH5xwjbj;MQpOx8ZV!zEG-Cfgj={FBmlJXsFV^Mgni`x~sjj$gr) z?(YJxi!SDTEfG;OQnYUxIbFEIr+gn^UNd?D~2EJSa?1Z4Rd+2eu845^=UlexQGlQmF*5=9A z*FS`6$>1JK5Gh@;BH`SqD4L6f^Y#?l>U5N-G!1;Glf3E<92de!uM+IV>Co`Mjz*sJEe>3ymu|?`_R?78Ao#gKy8TPj=^{m^uPL@ z4vPh5G|25`4CBlS^OFaLoWAyIPKvu@Xm{hdH0oSfc1%y%w>|7&bM&;O_qY`onSeI_ zA!sCvw+It?;J-VLo>TSND5Jsls2{b}?Gkr_FzTA56h*{|?%%Fy*}nR$z+mv(scZDf z1o#bwR~x>CD;1wbu~T$CNSD3~!#{`N{e=pBrm%&G5FOkb?iOTG)mI0boJG&$=W6 z7Jiiuf%OxsPzHe=54ZN0IPHs&plc-@$lC`;umjVTc)tn3e%HeTkRFrJ4P&BM} z%))lz&TW~m1m?b&5a%cz_8}F<@TU9icR>-?g-$|Z2wyY=$FNdch7bN;@00J_y3Aa= ze>?{A9sa#ORRpVj$jzMsxGn71vS8*a#BX zfW6RX);5m_Oh9&qe0KrQ)bXBC@#Q&GK<5P8O9oSfJ67WF5)Dsqlf6ok{t^2Z)(7G= z2KQgjJZ8)}D&8)}yzpc$RoBPFZJFt_@4`c)2TuYB+Q0WG3Me zk&)tVi%HD?5@r`CCWr@HH;c=;^pkox*eKh8Z|J}1TYrlRS%Me0qgqhXUvpc>s&Y6T z+~?_mqjWh@NSiY&opOc*I*vLC;IH}lJWK{oydlYz+Ypd){zPkFzGf0}>cOJVy}9wp zHxp4JLkW+$Ksvg(pRfh%Det%*{t?}FIqk}`%3Z(X$JIDFH`0+dX>w>MTKoFq+5f8& zgu4Q)i?e-fm7e9${>pM($F=Vt&WSx?_Kv?k#KVH_QepoL?{HRkSEOYh(Bla#3bznK zo*9wkg+ULeM=i`6i*SL2y?K6XqNY^m>8S^8hsv+z`5j3z)0i`cg8_Y^-1g4J^|2Gh z_4YZR&<(-BhI9%F>OSfAJ9*|Q6HAxGN$+BR)@$&AgU^CpFQn#GDelEvHs_va_vxzJ zpVMBd4z+$B!r8aOe|--6hF|rabuko2n|j4BRw&pp-T;HZUzKVZ|3U2pK=rcxt^OT_ zG@Lrp3P51QgcD0uawV9j$wSCYt#eY&P;y2Wn}bPKFxm|ZaQQU?zpxs(B>`6!~8u<|D^Ko1HB?T6%HW*jy{5}xLWRMd$R;O!$7~-#8XJ8 zD0e@_F`#vC?wZybBJ=hpi$+X}X(1p-gyt9?T|9ADKz>f(83pbQ`5S2wOM)>k0MZm| zxYd2ang15n6w?edUr>HCMz3w`r{XR@!)g~xhp z;}r;hdsoyJr$H8O5?*GiUwX}d!+$T$W%y@`1S-YZ#Dd)zR1J4t2;bqDl`f98oEXK5mv z*)9f0^nql`N{6M14%Cz++eb7_2kq&-8~m6{WAYF1K5;woM&mDMo2B(PC77&=WIEQBM+N@0c$iuyl!;Sm$ z{Ey>+(d%&J)tYX7DV@uCqOQnvN~nx&z3cQ}&E6`;3gq9fL-q%(WQoT1fq&(;eKH-- z`TFSho0ibdbsd`uXPW!*K|8EJEe3VNYJo&?e}HzlA<~F>IK)Q+GA_#hJV^1)fRPMT zJLlKgzB^}GwSixi|X+zFu>LlKxWw|%*g6x~3v&QpPV z6M#ZQ+pmEJ3fXOUe zM*0iefX?oUd8TpuwX-;g3GMhk)C8b5a`X}XK+7Mgzf3*x{Sr{KXtR*@E%;eBlz5S z*iqA?&kcpH+tFiu`(aHU_xdQD|fbIi7+9Yl`LtLCEB)vjbFXk56WJRIjqRLp4{Z0*5 zVa-F+^A#`7rX8`11`M{F)*WEt=KITb= zirI=UXaJ3xpHr4Y%@*)nhtU%rHbIk`CG*Lh;#Rm+%o1g4SEL6K5>Yp>+;5ctNx)Y| zbBO|B-#=)UsvC(Cr7}pQq#d?GsB{Ko!J}Xy6^3ipW5{F1&?8xinZL-X{CXTJYWmWu z=RB-u^fK>>lgESTuhhX!Mx0RW25a!F%0mjObtMZyx-8XigYJl7kQ-@}n%~}RQ&H9xg?R#;?Sy*U+d&= zk;6*bm5HoV=4f5W>|zaMw2qcK|)VVJczvb&s^!jzUM(7cqr1 z=KL)+Zr6DKLVR0>dg>&F+NL=mjV~nVNoAFvE=S1eOGdK4(mRAri`n`j2UIF0)e0A( zIMNr62(eB~NGSx`xxm~AqK3#jS%O2WiRYOn;EsDnnErxM+2&Pwv&IlN75_090y8sQ@j%?igmJv;V4?(v9%nRS@X#JqZ}bq*r^eGzi4l#KUA>` zlv>?_)e5$6>#}tSUE>7J?M+Et(ZuzH!3!xmMN zdNvz@k#V(#VK|`LUllyJ4MVvbsW%L=Pn*MAx-|~2u_!v-DcI-X)Tt5^&fw6Hb>X`PVvYF6Lo`dPVPDSb z$gTz(IAt~wz!`JxM^x?9jIKlXx2h@^R!oFb`SH(?B(V(t{r!nj5$g>0Ta^z~7_BQhrn@;0>>meGM53)C>?$<;k41vm$U)j|?4b76VBG;bz=< z`^!oqqah7kj)rlwo4)sNO`z`GrJF^wcz*9a4DieHs+4@`QSUa7$(~y9L0qqG^9ue~F!wRD5blA9NnMGLqclx15nu4?}0o^w`PY8K&iO_Am^j3_e z(IgiE>kkM?Wzj8%^=bkKTz13OYju7qex?Wt6ZBbucEs^7xH$RJ$(@E7OE?#F3hyE- z3GI~B&_}Xuwau-GwH0wt{2N74lPqZWJ2hb-{y?sNlu+rVf4hiVe`5LWUvS69c09J#zo5yDe`hHN-B2)-Jtmy6p*R2UP+Fvl1E zWd*5T)?2#u=2|NOxgCf|Ac*%TtBtb@!=)^DCU}nh{qRSlDDawJfR@T2DLRKL=Hxi{ zim8m%Bk~|*gv4wy+G-FiWo0PS%EZi}wOCn{ot}QptB78kO;Eb;6_to)eDu(Hr1~^4 zimHigSRs?H=dc( z*rZ#V&?M>-G4$U;!|Z?+=hTKRCD3wy;pOL~(R*)q&U?YST42|4@YwY%qC$*2h8<~dt&dx?fDCk8QQf%m@Rp3@j1Ovg4Myy~qYA8Be-dFxZugFgfEf2Kv zd~_$<&zN7A)9G9^uy&=*lqK-!$6teZ-D2PWhYd|ZjHY31jLYW zV4q5*dn#ToYdWNo(UwZfGB11PZ+vq}uQomP!g=4Ov$J277iL_AN3>@t>O1 zJmQ!#h+nptF}#MHRJEK^pCzINwklfMmE08U9|<|l`%Y!;tH)PES)Hi_-&Z?FcZ>lb z?GMpLN{A|PFKFNrF>+dN#fpA}T^Z9$>UjmD$?j;tQFy`@00E8ag zHbbHpu1XJVwg^)U7Kf|({cx>GafTwB*y)B9mOo9uySy|LST8-sq84InWuv72pnBZ0 zPevr2j0NJ$tz1>G<`vN<}hw76H zQj`4r69W?J;Wv&?E`*>`^-`6G&3E3F414_gbB_4ggs0}Ochv;iG2&g%ao~KC+lxa& z(~-yH;XfQBn1hf`hOu_#01X-%PYCu_-j;g^Q7tsANQnu_e-c!mza@vRcT`7R#Ujt4o_W$2J1?6tfkB( zwrD$y*8nOTB1l&Ob>rFTIy1tvOQHV0r{0pEBa>p4v}D~657e~y!38aXGH$qVH*RUw}wSFC+;*6s+NmcgJ&a8tM z=T1%p!!7RH@xk(Ce2wix#gXy45fp8Q?bX`@|3ky%0|6y|ulD6BK-72R=$uZWjSb-k zBR{ClGuJXNN9j3;?A)S+3ZaEs!7jtO*@D}wDBSppDdQxa1KD$orW>EYafaqO>ZNMD@-UDU_ELIyxy|J3vpqx8}%zpd@MtyhT_8hIA9xv1)# zlv_~&!Q{?Q_B`c6%jQb0k$2&L*}AZFwd?gH6Ctm{iT;I3iR;S_+O3wF;d(q3I8iJ) zonjqbNJF$mOsCVnR&kl)<6r9lSD>RRQKt+?xdU&SL9X+>MUk?=A+z3*?m7~kMxzDt zo_MyU{8srMX|Tn~FO6J1e|#@uF^$}rsIus?EHl-kfB^A_qXB4n1@PoAo%5{pDEw`Q z|B@egQm&S13oQz-phgh_+*C-r8X64ww8K&@hv4A)+gR3tYWKPbXOn~X^3Wp0mw`EO zN(Y~@X5n&w!1eK5mR4@)HHlJ$+T7HY+%RjhHuwYtleaYna@t?=PN%rqE`5_hH#1`& zrY)l0?%g?obsk5czVWC>L-BO3Ftb{TE8^F0p)JK&yz`MM?HLQTm$rUxrWcXw=ZnXl6VQOpe-KM=^}HFe7Sgj>T_lZ5DVGIwYC9~k~by1&D!Yk4g-M6^-Z2;lBo?eA{ke=H7ZI5Kmn{-ZlF|NgRQ4W_8IG^=yr@YEj z(2rkOmL7IA-T9jIZt^hxdytQuB|qzy;nujQ{EEeP&B8g&2e;3QzF7n+C&(bZUvRwE zH@kZ(K`Gqzt83i)MwvgvY-_MWtLySncKJ-UEhA|Iq~xQw7!PMxhgET-i0BMKY!^2k zUs>xwBW5v4=R^Sn1U1n@fN&W>UPJ4StrQpidg=xw=^7n3@EkR%jU zd6Ixkcs`j`2x@qM)33x=>~kPiQiwi;>IDhl=FllhFeydL4Ctg_?$^ZPUN~sY3S}%^ zTw62(9MvcG33r%u(x%IiQt97j-CIGq>CLp^MhxLc9`JSa261kmpJo;e_??(^qmyJ< zcH8nl&fj375ntFU$spfZLlj0`rGy2AkYD`zU`rMEa2b%&j%lE|56+A-V&;ZcgaOD~W#QCw zz`_PF3}4ifQYDw`y{j0gZ#&c%9bYeFmX7CL)d)ew)JUhSLTg&@g(K4kiHb(j=d$DW z_2r_awpPnA83v*kP~vM+N!X+XE040@Pja(c8}wJGc(`OKNf?W<&LqFb zszu&>`*<(ZCSoNG5(v73!A!eKzMcv56^2h%hR8Lj)&U%Rd@Be3$s2zyQ*icrrB!UD zonIdc1N8KmlfYCyCJjK=I1Hd-A&RZa4@bQDRzkv6FZgIC>eZ0xpZb-S_aGkw zK6cji)17=w|9oxqZ)sI~ZS(%IW%H~Di!)YAOIH4x{}`vJDqLl~Ni!Z9tAIbRi+%`fL_HuQW|QA#{&`<_UR8|O53 zjqMv3VL2~DM)d`y(t~={R5!$tXN@Lc%i!>vzV7QbIyL@E<%@n&~Q_q~mZv^8_cgI?_=+viOA`(=d^ zu3qftv#3&TnC8=X=bfo!I%xjygH!l#Da>m;W){QQoe4e$Ww~C0$qn39xgzWeN7Ci2 zqC9H_jTPP|LN>vvw^{6@0pL)QYJJ`1E8pvJxkPE{Q&y3XG6PL2UU#ZwwhSkFw^;)e z+2UNhFY^ri`VwcQ%WJ5waER!C&&Pbv<&AI8``jK5?h`o?r&ExY5v<`6ydJSQsqR=P z;RIhS-XB9uDdcO<@dNUDUTPjCL>9BVMZ-xGDad@zcf-F=XkCiBK;B%vi z^axsdHZ zioExQgO70sG2>>uAZC4$>tR9>lqqx=XQBAfy3z3KxENDg=A9~Qe=C%{hoA`MPR!SS zgZb%Km4R)+osHTJm8mZx_$Y%>Ii(NYWJiJq`3#qcXIxlBO$^`7XWO?ep4V7L_6zB& zR7JJOd1u+^4&m$5w=4u)ee`ULbqF|Lpf&{y%FtTLsyGXOBHlsKn!+0uvHps%#4T{@ zDvx%7^@?!s?)S7cVHAg*N%SLda77U%MH9*7wzRTiB#42SrN0Jyouc1{;p$j!y{T`g z2C+%1ac_pK(BaW4&1nk#>;THW72R|z2Ye;c3FSI6oDO5yD^T+E+xo`4$6L2(zud&? zJhL7neje2*WEf^{$Z4M!qi{`_;8X~=xe3f2UA?y|+=tim7E6=AQl?8B-ozTi`W|u@ z&8%5@Vo|pFv_IkId0Uaa2!&A|s|S{uY*D-Ir>(S2;4o)~pf>Ru=X<7CIng6EX`Kgzb1>x#F`NoB&X*g@K2GX9*! zM>}WvGEnmSb@OEQWVFsPbMlFttUGuVrPA!G&pz|dXgryyrOhA1;kmeV_}R{5{*Yu4 z;X@BhZx1K=tc{&{uP{P6Mf)(t3p@6R3Q?=IiG@O#!I9a5g1*|9!l4cozu{T4=lm=f zb_yNRzek$9vR1T9)XdVWjf&ho0qbz%(=UGHKaS`lWc$_`NqiE$w^uqEoj$QKuir=v zG+G=#;=NBv4_qD=W*<0phRr54c@-R+*42de574a^2OJLK^^Gs=4+JI-`*p9y3NZ$- zR#5c=#`J8sr!gKh0asN1gs>teXg0gDIX%aR0S4b)%6La0$_F2%ye4-i{RlV($NiHS zyxToobCmFWom8JK2csLmMkmVmN?cDoAQXB8GEEq&@q63a3O$D&S5{Uvs0H?N1J>HV z=qDXsJv?&;uL~4XR7QB1UfxzXI4}&Hj@^Gg4;=kJ!=lV=W5ju+hy}?UNw9r8!)!gK7ugXol(M6^76|(zGlM@coh#fd@bI%bs z7B(`BNqDtui$b*)R(h2{n1cA*9$p7E=JdOMxnULlW^5TbIb1kG{hoYo+Ezy4Z)o_< zUy1>I{V%?6?S8Gix~Mj?WtSYEdiIgd^>gJuJA1PWhvSQ7CR+2j{IQ^U%EOr zCu5E{UDqpm6^-c@G=!gYhU)R?{R|7rtIU-faNoc6_`6Eu=2Z*eFi)$pnbqeR9<#_# zKGrt{%1oB(0~l{Q1KoQd6_+8?PV)1$%cfPBJ4youit8#b>#A$VP7`0L*ZJ=Y6{>Q3 zl>(gKl@&%dl240|A0$p(Sc~4AxSX#6{|U#fw=LKpu>$}O9RGiC+&{X^&dk-w)X3F{ z-rB{%UQH7gpy_s{iui9l0ignF)TyVWq7>xSo0S!pXXMrX3y$jv{=abCYVWk@f5LIX z|0EFp1JPSXVCiQ5A81^XpT>V^6Z}_$0w4m+ZH+8k7;G&53ynki-;Nb!KtNFekpKGs zYliUO&1@|GI~w@boaq;C5svkh{Unl>c z3!*pB|JXek0nU$@Zk&|CgD3MoGT2{_dQc+z~OI%gg*p$=I+}0k~+R@z; z(A7IInC><-I_A+oJ~gd5IWxZ?JGZ#9D!jb*>v#0X=FTqf#@^vk{r2(MIn}|%pT(=c zw^KKFkAn|SuN^OMpN$`10EI6YNyOE`U`Q4`*-6Fa;c!G0zF0}6#nEWY7v0&(rTOuA z!aZMDDdf4yWXh4BT-mAQl&*9}pt@Komg(7S&a~~>sg}w4d_kX2SZUOW#bQaNKG|v4 znWb_?HQqSsw$asU%?9JLYB!qIdi|{66HCHB>@AGof`&@ybWu%j$dZmrg~CB-AlKb= z5$i+17-*2KVnjv4!F}lar!k5`LNU4=QUceb|l-2s73RBs;Ls1xuSy7YrfQq9BO065}`24OQl_DQUB* z5n~t@2-zPjQz(^q zD${f#J4oaB#5u{~tiCuzO3xfRYXf(;;0>aj!h^M?#L+)egGS`9S|^Ye*i_|(==)Gw zzGT~xc+d&Wmw08EPTtbMV3gg#nX=i224G!zT-A`_#nYL7aoUO*e&0!kfP`*qNgJ{D zd}gvAATU?X;Oe>fTbpJJd7GgI*XTeh&%$^`+H7)M9L%^wan-suC~=$MsDflJgR;D* zH<~)dXBpsJfpBYs3^H4-p+?j>pXf{&%MVL1NFU3B=+bZK^8wOa>82LmdvjqBB8hDC zTwJbCMo(DRNYd`QZ|ml<=P_FXMn1<)Ss4y3e1q^D09ecS_+{t^;5q*n) znj-uZ5_nlgJUrx_zh(C8teyha{a@Gy2_@8B+ zB!5u|ws0^5a^OV)Zz%)fGH%;j3Aa4s#d{V*-LB=kI=jCGvK~%ILAhBU{VGfXqktP1 zp2xb{@Q!B~kNmnJYgu-;<3~E3{p^j%Aq#42Q>j8lZ;*0)3=aI+Xb;`2^d7Tv0MG!S zFCz)S6*tnII0pYoADlh%ijFY;Yb4NZBwz;{=zXw+W*o~hG$Lzv+kH}mno7{-8E9>9 zaHVb)Y2TaJGZhMCcvyn9&<@E4`I{81b$@Oygm1POy#`fSd(I)k#|5<+b9wYfLUNv; zj%5h8NxW@5me4{wwgh*_5@@|jKAZ?w54fgt_;u+hcAu*lN|S^2>qI8PMb;D-3NeU1 z9zHRcCLs!#M2P%72_G7U^!QPt=9UQ%6AfF2i^rJBs}Acx0$V~xBFrX~To_lSRYGaO zHM3g^??{pez%`Kw1|Ma_Gb1xaZIp`MO!l`Th~=@?;sz7LF383KVUoOS_4LFl9w z1Oiu-LNQG?E5#yal9P}Dh6J!dV+L{~sPMQ^7Xn}q0k~(Gbvmau!92WXP}wVTHu}lf z;FN%bPvZfkRwjG5TpS>9#$it8!lW-%Az*yOAYOB){CZY=))KA!m-8Q#e@WOOK#^yG z*4l=dxNtel80CSHp#gY;ySb%r@c97o5V?5um>Hr6+fWC7~P=;FA099E^dW zf>~l(ajg;LaCiy!mkYkDsBs%0#n2Lqi?sBGh5U*##8hd381qNae}5ItxORY<7L6M&fGS{K&?sF&D*_77EjC$nHMTwF2C5oKTxI#C_G7`em^X%ON! z8i6gC==~ou6>v#+dBe0yZiw4JU`fN)A~_)gk+&J3^QY!avOk=M)e^m0sxe4{)h*IA zfbwq}pbEbrpvO%S>u~E5m=!ukGoCG9{HYENS-W-WZe?J>!m=Ma&A(zP<$%3!=61-g zGQcp`0$zML7*r&{)xt}!olqmi+nZte+?m79v>ma-ba1xSS_K_;zmG;vv0eT4H{q8? z42U_534eCnN4^j}5@KMe(gR>;vqh^F&Quivcc07n${j0Qb_SVnMT%$%6uNMpZ|cC1 zWuH0$fv*XSSy!Mp%_A3u+9`s;dKvZQwmq*2qjnxm&9)U24o=a51#Phikzv2xLT*3K zqjqc@-C~+|G##efhtVBZ3l1UhG=eD9EG++5ohrQr+gp!V9=TQk?DcrY#^8ZL+;5Ak zFb3%S%u@hEBWV0~5ePK&isy3&o{Rek;2v_71jx%Kkp9P|;Dl3ObBw9O+IJAxm!`y% zbQ5mmq$o{hag9madzmNPjy`6K7-Up6{_Kg>r71|34&-v20FJ5JqRJZSypyheovEwz zBTivNsK>=Rm9{6mQm=x$k~cQchU{-Haf|2r?^`PbJjK8fSGO_#lJ*vqDg~!#Wx#qp zwIHS0sQ5P2= zU4Y(Bn7~fB(6~HTirVIVTK55)AQ};yBmkGfQ1MSjl`eLvZW00o@X?@(38t^FUSGZV zOvnBVm@L+X^zU5*ATv8AR zf1Ghc2avZ%ex1e`=pp|42HPT71wwKkwsQ1h?+IAAK*yElKz`I0i{AgC` zW7ns*9*kxlmxgTbByySdLjC}QD|DF#`2Q#pV%(*&zytiiZbfJlB?eIWqg44;AV3<_ zL<|cAb?h#JbMKLWeNct98_SAd|nV#5Xgqe6Cf9D0lcOR-5kP&Ene$@Ng{^u5y;1Rdzeh7=9tAY^*ik5H@1VH1F z@Qi*)gOLG~61yWJP!*2wq~QyhvFghJ2vcmeDtG;Y8^6b!YRaH3B!Q>#uEsjLqn1XGo`J|MAtqp{FrMw_M4phZzu zh;j8IG8rv6Q!Yh`i7loJ9sfORtwAo|#n} z3lsKu(gP7o+VDPkRg!OkjZV!%YzLWAyxEUWH5nozjynAV}_7pf7yTq#)>PPH#ALF`CQI!sQdOp3`Ru`5Y0f)Zx$;pb>7 zpJ$OFADJ<_C@(~*ugofO=uA8JfHy6|cA|{Af=r30;mzBcS^i4`E-Cw0isVfQdr+64 zFqw!3GjQfGqh+N>ho>0XFGRX1@cIh|7geTVrcm@m0&)?ZlVV)RCrY~JP;Ay@t#P08 z3VTo>6|!;uMycF3FdQck={_VP(qR6><{V8BK^IrO4K9z@MftKef3466wMhn%6oPLg z;}ha!yc7zO!0ht%f*(bSi2XEpI7LSmd?E=`s1Yb{aKxNeAjEkWQS6hyaleI9 zFN%#zfhZ1x&Co`(uiK;xD6dyoZo z(W^o%BwRWqV^Z$b)WR}Nj0;P$G@XZ7Fm>!mAdMQ>F*sN6mk6v&aLuwD+^sv7R91Im zsmLupf|?PBVd>A~l*o+_B2$`U+Pl=8DQzWjd&Z~t1UN;nip(;uw z0gjtA);e$XGNt7TI_IWYigtx`3Ojh3*;%F-2bW`IS>;194(NYRVj6b_A!JXrBkh#=LPD(R@emAdh$MFbf%NQ>0FCKLf& zPzf>1PvwRG`Of0#fZ*PeFcTQ}GX$T+RT^qKz!W-nzyyDOlo2!F(w zvW$xZnY1G2vEnfr{t?5_uvvWMkXqz{vb{>O`H*nZi&~eZi9&=P=-h3FwPD4uT5+<~ zQ4^rqD*AI-MNSgPkd&Q1m6db_%}wH+L(C|)`=-vU`mgp?3$YBm0$NZn1T|KB%Na7= znQ;o(N}Ho+wDA?ijxpsxtI}3>t|VsXr#SrNx-61lq)?3_Rx<@%aUm%HUxY?FDd(Sf zj2N+W%WV=)r?W`~M)N&mZCRkXdwJkRDKIal9i<~}=}^ZVHQTl(%Pw(Mr&ZCbE8kU_ zfiHkF#v5PR(En(|I-f0BTRuk#H3~&{ zAwWtA1H_`b&WD^|!-hS`C8ueIf;B|ZY=s)JCm@Zz9-6Styt8jNkT90j&yq)C4~?$g znB_+(xj*=x2q4=(olL}xrHwKsbeG;akQ1^)sA%{L*y}0c_GKU@n;uxU|GHV>xkv{x z#`UJG{Z|wm=sU3Agb2K$l4fRGdkuSV^H9uYcj9_ZASatF+Q97y0u`-gd{d~CILp$N z%_YqU<0pkbu)1rTNUw^1chAVvp!a4qO__L%ux$s^XYa{OFPmNuylRp7RazRYArWY{@Xop*70;T8P$3^a+UUi21ywCSW)2>uU& zGiNj?uCXw2Q8jQ5t>(P3M7#UMnIpA%ECzv%E<@w=ONwM1@GN~`Xhm9WpqorwxMh(q zbUpLX)TfCIgc`%`bbN{=={?b{1>i=EYO+*e$iiarv+fK)R>lO^2wFQ!4;&$ugF(tIc zw7A@0hb0Wu-dHGN#B#0xj;~epqmf_~#pph)s_voN{j3Yq55c4{2J*I_+YMetC>xA@CQs=Gr0s;~^34yhAJCXoAls1V!PW?P@A$Zk# z7>Vkfj%xCM4Fai|#}9?mC3DA5PJRxWGxt?uv|y!P#>?KWede$wc_o5~uG!2q=9WsA z)>we$iCNLPAvFf1n%KjZ=+W`|V8g+T$aH$H%3d@tk6x{$sRR|1(zLd5Xy9Qc=PkVv zk^|z@A09Q9y6vPgKsK=qv1=96ScOS-{|Rz2Be5+hr4XLE`!YEe=BWRkn3-}G5!wxM zL&%Ma>VtM&TT0^OI9C$xSB)lk3^~9I1-v!EXVfRG{JLN|QF{2QpIUFyi(w9l+>$KPM=g;V0Xr^5_aJ zGc`&;sV(g=d9>)kaK>OEY*)v+XztUV$Nm$uxV+1&2&s$RD!6!9B;g zYAX(b&?H} z@$Kb zaPQUreHSJgbmpv8T=5P{A9090^Z$Oy=F)xp%7qwZoG*d-ErRLKPY6(^W9DF%v{+L&<(op zBfwc^`6I?8vS@gtT>RHex5Luf2wxko9t#PDK3h1O;fGcC!ceO|Ui0zNa(lt6O@S(e&hWwerGnQL{|3MjvwHb+NQITz*NtlR>;lSV|Bnxq;U!SbewReH27dhRk z_elYt;f{&1fK^fc>c}((aR*W1z=0ikkU=I1n~;8JCHB#Re?KD*pZ@Cz5inv{uwFd& znnt!Jy^i@=M~nRF&TW9$wYMI_0H6(-P`mui?BA)8)U^)2Ffy|1AASm!J%Z)tM-0wc z{J`wc8)Ej{s&-96!yg#v3R*JV$E#1m#l)c*qjyZ*4~n}-R|ZV{m1*oGYptqZREH@eVDQDb00kCJD-)H%RglBK&c#wB_4$posBgg19uTPjXnn* ze;;Z@%E3}12}m)}3prSF z1ynf7G0K_37gh>=){oDx830NoV3u=eV2%<<7@kU4%GoaYgeP~!l+@BhND3I(N~Q`j z5u660sx`8-FX|TN^d^gQY8UNb0bLCq^CGv?FgHFVelh` z#O8v5^(au3{mU8C24nExMbpj6iz2~i_DAS)Z;_yWalmE8Ou#?QS327Yt8qYQVvGFrsnC6-%Q5b2y$#AEzGe z;iNsC&0qBdrdEWg%lchMZ<^&-91?dSIh>-{RcHxzfn z9P8b|c{G*BUJc{(u70*u*W?`Q^FemC*^7P;=i7~ccQ~8G80YJQ{&cx>VGr;3m*wK| zCMYq^ub2Ge^9Q;R-yax?W7iK9t33=q5OTYXAn1Yfb=M0~vK=o3HP?+W^uVHh&kfhJ z9XFg%mz^kLF~fb|iL$aCCyEyDg(!M(9>G6`{kJM6mizu+Q5-)IW^ueQl16cYI8jD% zq7>6wagv-6CTRjP4uF4(nqfvss+QwgNt#|DW@)-nqDEZ}y`YUq-0FeA$|8Xl&|G`WE;{SI#Wd8r?(0^j7|9XP|x$VEvA#-{w z^Z!nV{&TZWyPLX_-XdMMnJ_H~lAmZZbFhMy*IbbnF zxx()yBpxXyDq;<&fT$HuM3jDT^Ok$Ud-IgzwEZ=@UA6f#m1$-AoY}%=I*FA7ql zk7HMfiHZGxxA7DC^;DMp(?1+Cv4%xOnKX2|g_sJ)~67~ERq#ARdV9@a;HNGB-2=pg6o94;o@_fJUO=N{{ z476UB69e<^CtA7HQ&3TF$jH}dTx~VRXNN@z67w!k9&|*jgy$Ughaz_LPE^r*Yd$r7 zX{v4=y>Y^A$0sMfI6m#K7tpaj#bgxM`Km+W@(iczH6mB z=NG^8Z~EO|zDRkM1a~M9ewE5_Zv_Q|&6!trCRgmlcA|FM+ZA1NL)^^tbx((buk0yL zMfBhghbd;t60uxd8RD_pNlJQ&pUD!c!WPZTEm%6jS7AcLZPwe5ndU^^kFSdtcVR&< zz2la&W7xi`Y_Bzyy^SlUlbR_`e#6Q`1etw;!;%aDo4cfPL%UBb#!tRRX=DG zvbh}YvpTQ8$OpsVudB?>KTFHM6{B9U>v~paJ|{oDT0>Q3Rb2^#I5`;X1N6TB=zP51 z9UPTS^g1sCIJ9B0I{zKR1Q8W4EX>+pP@H)K&E?TibnRw|$ffZ7Jk05yXs#S-8?P|> zaGj+TcSMq_@1sM{#il1CQ#Bn$6?>wzrqBwtd&Hre5rSS06sB zW4fqs?yRU=oTN_NNj#e3VGByxSXp$t2Wfj1xnr3(7X#$KwZC0{NO-=cvvtrN4~^MG zCMRD;eTZ}D@_OH)#zIFy`FVQ~zZVCJ=v-grg^B81-(r6hC!(aGxb$J@eisicyi4%X z0$69Q7<(|5Wp|wQAgDmY(}We{1q=o(m7Im+0!f@(Yz= ziTDX5xp7)@em4tzjgw6aE|w@PVO*ogwl5-8VZ z7S>~}B|l$^-qf@dMUr!Rt3G;NzOFbeM@YyQucW2naJh`V$2K_#Dc6UVR#sMmtxXM0 zO&`$>oC#?8I9Zk{zWrDHNZQ5lAmk!T$VbVBkblm?LP>F?U6+s zlaP{uknoznppldAdN<&(*e^7K4ls43^vC^n$0}MnQYIqWUQY6#+-Dl}rQr{KrcSoC z_O!OPPECzY^|vLPlOt2pn^k#6RZCP>MMzD_ac>9W4|>$kw9AK|qs%uQ8PO&kCmSc- z{Z7IWO4{d*s`e#+Zc`L=Ox!p10tFEh)uX<4<1rYzSyB6GdHb9>c+wC2GrHYe^4;@0Ld=2Ls>h(dyTawmLn& zS?|p6*S)W$u5rlu4fZ;{9vnZ$XYa?gu|@K@kT^Uzcvv`|)0>N{v+MIqgyx;zRo9o9 zs}(JFyJqcP*A%~_`;z0*ktomIsNEy~pMyVM_2McoNbDyWL#8CL_9RfjV3R(v6)?5!HqT> zSeVE+?Ih(?E?qM)9f-}=jxlHr|t!YmAIOswA~r?azjb1Mr= zgym&rmE;r@)RpnNlq4Nu;rJ1$y?P85s0jbTd;|ml7Ta^IZV#y#vverr+ZMFFp88b5Gxp zm6W=3m!`Mjr}VOzE-*V?PY-Ggu3*%fvVUd9tuFd+H~Ui80SMKZzj>uXYPA*p7x{~2 zHy>@VG4V}Y9;Gb>uY`J^#o!#Fqnv*Ru>yIXWD(#P1_*|{_XFNxczn$aWO)QxX*x3&Ka z9NxDWp$vUQ0LNZ#T~QYMuG6qy=;;eEa~KqW`|09e98V`Mrw>uf)cg74@#ks-bI6 zORo-1dWiv3n$5K=Z%bFEeo3IXt8b26 z@w-aexb(LoQ#MBX+ur$gMWn#xpk^D$>VO5$OzQJ06WNPy=a>}z^5ZSw$yX`2ab0w z^~O?v_GxvObOi~{$8Q~{r{i5gW%H=i+_UZLD`YpBpa(1?GXrOq2`r$`07j&q;g?IG zq4Vx8dT(}g^3sbKegG%|4=D0yO2FfR;Nl&$*Juz0+x75NR#gy&)=5r|J` zifq49K8uHIHG9vNOX-?gP8C2>oUBPYGo#%+j;_SPp>1^#{O$~V~_ zn%?}v#&;L<^T7RW4pip?7mr(4rGR-d|Dtp#ob^%mS3m?O!Veo{@#PSxr*4Ju8y9u| z935XN&^!=^2XmwU;UMi>PHzNz(5Oh+78j8JH1CEDYrlkz|uG5Sn^iYfw zUb!w5@Rt(vS^%{?DT>Zh+>4KwnNQD**?azFu5Zz4^5F@kLd-JuM51LuC@3uM@2scT zj3*Xw84U;=h$oSKMWE*F-iS>bSgww&o_XOPR?s6D5J_%zHn7Y0W|z3F(`jz#v{O+T zLVU*Ax}1&mcHgT0Ryp9=8+v+e1BqdqpUAcK`tLBO2lR5jUG3^BWVEI&un#|6&G<1PAyQSlp^?cKVOYaKlV7OFFy?la6a z*sIbb+~=!!PLUh!k1+62QsxvbpZ%|*hJ%B5e!DJSW+CS&z*RLBCJq##x{%PQ?9^W3 z#07qWo|}-@sfzcgiiVMtOSrYKN1O zj`4K_^&w!bN6j!?-PM{S2{(y97q^FTc2YJi9aB%$l{9f42p=2}rlzQx<@Nb;(O{aQzAi}O9@4M zDp|vl0V3we3d!ZIo0$O{dqMzZsOrROgOA0tx~(z)-7-T( zd31iKgg&+~#u_rDVG6JG?RLnX7om`d#I~1;oHn-JTr}L4MqcEx{S*0f#C)a4IW7*s zn|`K|Y6l(OC1j*A*bc!@M4=*ST&1;Ost$&^I-eNRHV6+5zM`b6-<}Q^0!zAAMaYu@96_nBY$eQ9K=9|9B0FA`$=?K^_!96cbBR_~!bggF&oWwdoq-<)9hX z7Do?GyMf)0z;CV|{k#yD^P)}Cjs~{EIP%957LYPaFsKq5Wb6+R(y9c_bCD|$Gvsck zEupti-qsRfqO2F39CH7AQ!K}6agjC9Kzn+WP&+7a_aBZ%C2+0Oph^$|DrZVFK z0T2dmyg5i5lSslD?YS(#@!i3v_AwehCYgGd;Lzj)Lnl!RB`#nsJbz51q`ZD+G5LPu z*R(9Hl8&(=7?fDBi6jSR`~qpPZ4DSjr-E%5iev!Ud-W1Sl!Q^X56agubG9fSyqP0!QpiZzpB!C4fVdALkT$-zNhv*6w2+2Gl57mM{{LA?c zm5g|$LWy$4LZh<<1iCswVK-$xp6;;;RksucEaAQlhB*oCO*}??Q9%}aNc00Ercix+ zo?S;@sk?-VJRmF{TV%L0f_0C?(D8jg2>_>ba6@poI+JN4_*RKMip&r`0fCK3-zBYj z1cIuFzZi5YS0sX`aX1(zBB*g}5|D07>5{_(PRK10QDJzfG9f{&y~1d?1g3H|7GNfI zti4}oBzl2207M>u20=l(vW2KFAs7V<;l8%0v|R*7Q)oaH%7C0B-TvatV91OgO{GA{ZeN{k0S z3dV?dCQ^bJPksVa#vu^3aWO2Wh72bZ;Q-foy9LzxBVcv5zw|yau7%gWoRO@eVX-I! zC?Hh|xe0mbDs((5kTVwSffOBMNnva05&L=YN~%%RkU%q0vkF91ArTGx-~-?oA<)c= z&)Xk@b>7C}L%>97aFYmkHLmc2)OCmqXpz3i=!lYh@?WmbSQ{6{fGSkw26By*vPx)( z>Tb^qW*8pclz3|}8eESUsaz{inG#C(ncPE5-{Fg*LLI4CApGu8PE6!Z1DFg>;7Pe3Yu3*9QiZ2<#12|n~^b9c{N0AQm z+pDckl>_)}d(cr6LZ!s`7hokSjF2&3n(LINk1jI9EBuuW3QeY85mSW(F_zvxD-!U$ zwGR4 z9Vf6>IPY3jltc+A-VCS`Xg)F~Ny0<*_^9cVzlh{=A|9S3hi8aCJ5s@JIaAw9Jx!$_ zA?_MAKyD(Si@-UoD3(y#40@5PVm=y5eSCP0MsHDpiyTvgR}9=NY9STH?Ewf{XjEU+ zp4JcrP6C}Z0Cga6+VI$s?OZgBEh6L9=_uLntef$sFVVNV)qyx53@wGp8G1*}aLP6q zE|@wOi07-~|4LqkTxaYLVx%&AUDAipE{!j$aLL$KM-Q!8V zV$eKKIM)~mn67&BjO*3 z@)$r1DgHY718U!BlPuwQxO{y{a0eyUQ$l!X16r6Ib^u_62b<#_YG*IUBgKe77SFO4 zYLNJj{$Z3pkX9vPZ)e1@ikQSO(ah)pdw9C}e}q96Mk)7dST2=kd@eGM*SqUqeU*Cw z$wP@`3ir`MAgma@LH!Le6-_~$3vSCZ5w3Ni!Juo!?ikmI+&gaHNkBJ5gE^r~uG;mX z0edCE=e?`JFfY;VBvH#cwgqg_`}Z;bkpSWR0l&hKr((6rjTK9BU=*557T|^1l%bi$ zXX4nO9KJ3zcvjp!2SAwsU$XJ11B8(a8$pU+(3XZ0wfs5m(`S_TgWeYSJ(Zlp!c&e( zcGCz~KMH(L(I@W#{{nz1GV;UeR*~v%$q79L$!9}ZqWhzTnh_OGN>C5-=#Sv5pZ_tx z%{&f!8_FU9is=v*-aw3Zu_VljRF6^lNYBTW3ulKKc%6VzW>c;M(u@q-E+)un~ApcH&dL8#s@K7Q|iz|)u2SBa=k=)XjwrkN6FO-k0@CD;b-Z-Tdw2lj&W zY>tiBBPsUm)u$RVpu!X@5}ct_9LlV@ndk6m&H>E`B`8?=0Kh^IO%E1_>_7rV6A7M% z9spZ(rN-~JTKG=^W>Yo-AQHg{(b$+A@JMDv0K#1hK$R+06Ab{CCHTW@NDC1hT%eVa z0DEQGTg25{xEGcVR7@d(ynNw9(Rgkaim>n-qI|TDd{$8L8G*m3{@O&FywDY+ z0xyt#P*5Uh{%w$mIEeKGhP8_F*m{3%V*k=jhEN>y4y=Kh@`V*c6p->r;H#`>c)kLl zD<1-hgCKwdZU%ru6Lu&2y=jO4nFW#^Qz%{tD_P$Hi(|Ae1(gRump>swgK)==0I-tr zM-ItP#ZV=JiZ5D9m+{h)FQ#c%Tx+)V;rg2ioywR`iFpu4)qn6gp#mU_BoNGUMhi0a zs{;na;mV2P;=zsYz`ZTw6OAHd1;UVeTnf%=$pDKh2=F>ReVS+|!J1h`UiYKVP(@Hm z0h=Wn{0w0TfLM_dz+NaZ8jDaz6hIXQ<0;Mnspj*?L-(iA+y-M8p-pp3jtwWk+YuF@ zf+Fbh5J2~@+o6xiciY#KN)qj>+L6aalph=Y`g97XoLK-t3j>H%ZqVMwR z!}{puYA8Vj3$`a1uPj3G(l@#aNNl@n5V<6aR~R12WI{mU4H6OSAu{yGK@bEsz7u5m zNv!49gt98}i=EYrN&fQI-)LwaiOxG#F=1{Op;3IWJ=MHNhWt1&|-P@lX$^CmtCP-PUD4Jt64Hwr9)d+u9e`P#=yM38P5&TqK8 z?bJ(2LGXK2wsn83euWv4ND}%H=}0VE+BG%XVGq7zw}JeoXF`S;U-zGb4*GSz*zw<8 zMgw!oBj3#Qgu(lc`nO<*!$ys|eO*274k*5;%r@u56F& zL?_u3HZUuy=d@k@RFuRA4d9GPFSg3+>(H2)+zAsix;`SC*YIkjoC_t!DaD#=v=1Ez zUhfU)VXU(p9t7_bps{n=)CbGKKcewNPr2UP&jU0Og*7Z>Y_6Y2vmgo6{rC^30*QJt zyTN~0bW>|AHCTj(lf40rP5$789tA*}?5bG0ZQGrvHi(sZR!;xTSv84_wxY-Q*0i6b zxA9un5H<1<9wX&Zc-05`Ln2ogXm;-@lfjm?RZ zizz667&R?&lMn&?EqZ?&9f~h+GqRwZ3rFl$IcG+pLV#MqHX>{&(OfpDC`m8kt|jA4 zeomnCh*-E6$yVO%57}IjorWJ?+wRYbl#_JJrA4B>mQ)>}#?84UHRr(rmdxFoL2pEe zJ{k^jftUaxW-gnvWS?;y#4bDv`TLoXt(QsFl=&r+;)BT|F;CVZFIJ-(aAaQdI}9&g zHPR3iMUR3b5?IZPC`4W&f)f-H+NpY`L3T`KeDqdeOx9p;`xeH-E5`>Awfpy!W)<~8 z=aBi>Q#Pj|&#rwYVwtoL@hpewrXZ}z#1jK0;u|7fNtB>T$V^gOP$w=CPF5L-7RI!j zgExY|@%AY>@#<^T#l9QX4XEbhishr*v2{v(>^Xf}CtBklTtKGfAVPlhF~jyarZN7X zHx$**_kUtVEIdASm9r|4SvAQ0E}ooKHSM5d!7W@W?;4YLCkeFQQ z@`IbUINw6HA_HG}s&p9rM#}1<$&Ax$w-e&FA{rlIq?g8IvC@Lq+cID6rVq_iBzu@G zD)ydk9vxnRyKT#K(Qo_UupI=#L@_NRFO|rHM6tnRnDg3>s(*ANVSfUzYAR^U^sF2c z9Bux&&b0mo=o(RdSgQC~Ne}OasAw0!2ArzQ)*lF!VXDM|OQSvw)XbhvZk&Fx0kCTk zh`aR8CTGobxj>mdGj~Sdt&{*S4e&%9N-S82-Gky6nnOdua?lbc1IJ2D5f|n`v_GYg zN1LCWOYfix7*CytK*>aYck}6rH3-`mc6_m9LM@Dh)=DEh1w}tePgWx;=Bja`$&I%v zU;@lg;~+c0k8f$|u*5CHhjby}PMoq_1pDbAm3HA4E-}dUveAqp7e*?Jh#x0KE2s3Q zY~~;|f`nLOC1Puy(}+DF1aKhW3u2?x%Gt0`H=_~_cyr3ALtq;LkuDb^C(^Y=CecMN zDr9PY{&xdp7#&?x*;*#VnI;S+HPlre4q(O?{-;pTS+jDrL8WweRWefIh-je5qTT3$ zh8$VH@+!^%qd1kJw)w7DT?k<(2STN!VJ=eewq^u+?G-vcuq*}suo5BGi&Vjh%dzkp z6kgK7d;5cxgTB?ZyL=U1rG_EGSNzM9UgjShQqzyX6MfBoyzP7OiWBOwl$tEnb8 z@LBN?Hj|o*3L45F)Jm42WP?ItD!wn(5nF8kF1g1kLL8mm_AO{ZAVDJEmMjDmN^%}Y z5`9e?VDXbF!XH6Hka2da1?qy4mknlO(@36*EFaM6X>b#BvJd)OLbkVdFU8gOC+MjJF<^|cB?F^bIbrhai|^`Pi(UGw%S(D5ouA1(Em zMh$t83=>%Z6$_+6(s=x{V?-j)O|X>)a@UdvkR4~tR_fKGFGLU~6qC&w@n*L-v9%(^nw6^eSkDaaT zF+4J$S!Qlwg%daj*>SmjPCwlpW*{aecp*jb_}S!aS5E91b#M(aqcf@;R1smKm-){3$lcI>=4ZBJ2;S=#vD+_s$(%kfW5HQ#O{m&a}JCnTw%R{ z4IjHjr4kvsXjX9NPr$z#5?23a%O~~@x>yD}*v3A(Z!L@6sB!=CfW;tM9t^pRs8z)| z0{uS$D(yQ6uM+6-PeYJ}BIFhw&H#2uThq)L2tmf7(Sw8K`($xFUV|bNq=GY<%n|Ny zY>|Pmd7%OUvRBkMd%o8`#;8R31Hgz`@(e}r$re61N5i|VC`w2ewHo1Z?O9h+$~NF_%a6pqn^sH+nq7m65uamur~n(CToCY>GB^#$6=Nqfjbf4Pj@uM97i zO4_K|I>7)IL=ICIj?~igh1sON+ihl22P8q`m-Hvv{*jR(-AaL5rrfF z@w$2Hq`I0iV)2de`93l(&P1PPT0Iga@6yUgAm?>b{RFI|r-M!+zZ4AIDA{?645y4So4#z z3|BHxhy-V&{Xp@*vuRZoSlPg)fu}|Zs*L=F=k7hZyz)7(5*74RiqEHRd}m(Jnj%T5 zd}N+9XqI#>3S!4xILR48f>SWBAy5CnwhzelTfmY*ab;P)z&eYAd`4;!a%2m_&1DJo>zs3Uly1?ldm~JQaV-t)LtQ{FR|yi?IY%y z*tw25mG9Wf+zhJSxnfOL29;iP?}3WD+H9?kW@0-7sCuX-VRKYEMG%_CyugWWeq()K zh&|Fqu#8DHLlVqaHx$s9+j**~cI5e6$EBHmAQy(HxJFe$G682cY7C05#N4Zmy0Ji) zW1F1Jj#x+JGgEikr6)`_yZ)pUo0fi7Mj)WDK}z@ghh}Cx@hg4^QrR z%3z+ilL}6_2Fle8K3vffH1613?9(LD--a5F2F!zgFU-iA;m%e*a9Vl5aMxYACismg zfVyA}$xVq!?^w^87=zt8Q+dP?UR}BQA8PMTPX_dN*&wJD;}?}bI>G((?0;gLdK2K+ zvA&3$0kl!)?QMuKPW7ZSLdV~<9<&&IG+5`*ZH^gd%T8;A`G*x=8x{q29?6jh!AbkC ze(=GXYojQ;gbkL;2z)?rHVkfb+4`Tb9)=b#*!rq*fp~53ktdfbOnn2k=i`yB0W^sm zWysh4F zW7Fl?f7S5DYwFp#0VtXz@@{w^LpprNxxH7szEvXXH$Ea>{J8=^A8rX*)%89Dt+UT< zhH?rH2U+S_%J5yIS&$vWxyqZ zKkVU836*4c)Q`J9g6=E{;&WXFik+jb#t>mEQwx4^KZru6N0yC#bI z3kvq)#WQ`inxE03RYLPH?oA^CyQbq6;2Io4*48G*jXAv9fKKKtiz!vMFxa=gqe}Zv z;3tW`AZT9bVnJS3T^eBQ(5znKk7fkvy-$jWi~n8`A9osc=UVC8$G0i{cPI!UHUdRDw?*R2rIp=^#`(O>gAysjsM_%)7sF-@``~$f68%E{8v`s&&pHt8 zsqj7In?3o$u`&q!pD-W5o_>21I`+vy|FztUC`kedEi3|2W9<)B=!O zKG_B*37dCI>hHnpaLUDP#|v65m`0y|!<2G}ouBO%vtMEFhJx*BgNNGRwOaI<1nZEj z(2eY2P~C38%yVd*qbrN*@dK}DVq<;Z3$4zWuYrMQiHhvWI=T$y$k46pgIDQsa#Hdl zmPS5&13F`yL%_YmIabw?gf^EOJwLaAu-h&u(Z>@G1t+A|Yl+|V0POllmhh~4W%Gv| zo9taGjTH;*Iw=llViYgiB5`%9FgG__;?e4i*D?~*QZrbD&QN8PTp;UhtqJ#{A*$ke zey&FeUvJaVlWKU4w!}V1ZERBjrIN*%0>L2Y%^8vqh= z;sDutD{oVHFooCY%Aj_V(RK4iUnFz7gW}j&bY>~r>-(=3Ht^EK>-XzcKp}#QCSl{ zpU$G=!51S$3D5-IIY~lM>g@_D=<8D#OjUmG{A;TYKp-0qN?(KExCN!LhCI9H%V?tJ zw4h`xKA`^zlf3foCb+meH9{sd#fgxe$;|6KCYuxikJv6zl1~@LdN&}L-o$$2(Cw~J z&BvaNW*!Xp&T`hdA1{Jirh>l6_%jSpL?5LVV7pJmo=g4a5h$W%f4$``=cOY*dwm8< ze3KM0?2vk&4egq;?6Qqw|#GCJuy77ezM+d z1+ep4_p5T7(~9Nw^JEF*X{VnYdimKKOskV`ZT zuT)|D;tKOE6EZeosyT^eRRQs%f}YtPCe*Zh7=&r8g^(3ao}Sjpmy|0Wirk4zTJjeC zilVF0P-JLT2jK4g1Xz-Ykmu)K}W(am2HN7oi$} zM>_CeaXQj3St*V$kqAznm<+FDb33w2X{^F_oRZ*ESpv>UGET5L1PId**H3L*qL+Fc@Db89Wlj)?oagMcLMJXe-90_eQvH zCQpLi&{g(m-d^#BB$Uxsl;B0nNm{t^<=nkBywn;QvG8Yrtu6zOooVvSO>fl3&%fuM3CWYr1swTFmP#_VB=@G~->naZ&^|_qJWJ@M-@sD%)9Fxab@W z-`xHd#~i`ozskm?>U-*LYQs^%Sn&=Qs8vipWox%qzj`?m8zX`5_D=85XpOquS-Ig+-y%({$|dEtf? z97?!%%obMiohnRmHMLtbh`C*3@%|jAYsCBhj2IU5&GB@H78dL@|pazg!L%#c`1gE1%Rcv%^f(w^}F+HJW+; zogqXWDd2y6IV3p3Xv=!}iuThfx6X@b>S1f?hzS|$Y;%5n*rNR;#R7el`_BG8@YID* z@pX|5Yo*X>FGHa+moL-Z*n%?7n2u(7KYG+gcuY}VulHJ&DADlsT2>bA;B|Vbc|3Ns zL(d56Z`&Sz;x8^q^kTcvaykcY)#FiVim2*%i@u0e`6$Uo3>>+u2CFW~R6AJ?zVVA~ z)@w|0q`mPps8MKvT=h15niT{4*Ku7lLx#hO5TIVr(9eHLN(BCy&@<}h8BINQ>z1`0 zgFF5-sfJ6bU6xwb({-afF(0(S3Z2_CCtzOUBn4LU=CEjqwYtadD)Q4vqV#XWTB$`+ z$W;NVxK3|re518eV4m(HOC8YnLMWGqwnWzpW?qUIe}}}Y=DtDXf#t*cZH>2Xcf*+) z&KHVSJeTArp6CkDE7wLzyMTuSDV+1w^&&0AyoXA18(CoVMNY(6MWOWQBRB>M*JPNI z{fyJH1&)Y}sO@Ogv+i^lyRw;m>aoYx@v#hVq0|8dpN4hZk=%OFdmWNWxtX{=`W-Np zbPEXu(!9M(u?=-+5ns7$w{_xnTA+_ju4d1yUeIdso?-S0_UxGy_5$0;%(jOeJ3vcH zF6?Z6u*dQfz$I%`0DNkAR~zh!lJ~35!@N5loW`cPfllO0C)1a^IFO zswi^IkxNy5^sQhIejI)mQ+4QKp|(U=$HyxPFjY}AcyKuvI!y_FyVm%#X5e1z6C;(b zEKWV9%R6KSw`vbPw~Pt8=OK6KFOT6DS!XFNRU^*j?1RutBACh zIohRKJQ&KFArWR^l!ZIEj%^m<;Oki34E(9zbJp8lhzzcSKOYAdN+*_V+ z&)bbK4-##3w5ujcv^EucvoRn)Yuz@iTLl!bj^)V)bmAe>{?~d43?`c96~(GjipS!c zqIgOgG<1!#SMe2f04SdzYtURBU^~w}`ebr-DzKO0w)wCUW(znpy$(4}{OBog(us$T zT_|nIN)WL8JG@BnD9Zb^u;d7_DKOS!NI;T=jqGe_MK{6>dXd8~ zhp$qWoH; zf}8wC4m`&4wFr#Mup;i}20WA83=oSG?T-F)di%H=86ciT|14QU>~(5W6dMOlBJD!j z$qDI3W2*^}me2gN?VtA&I^iF20+1URq~`0c-aEa%dTvRCuGa~nG^3#A+YYd}xp3;S ze-AGkxr&nw1v*M^;nw&WfpQ=w5W~{|as#_VPF;7BviHXK5qYVpSO3YsA?vDS+HhBS zSvF10Aj?NYUs1ud6GlL?L1WCI&B%jJD!Tpc_^!X&6g>df30E7!@vsA^6*NM+4dj#Z z9Szwi$<<;@hyO&wKDkV3hZ{hAj0y1jd-4zOj)iODwtL=luut@(aO4{9pP*fCAy@@} za`VgDR{UWV<*9X!17Jt6qBm|gb9YLe-4OHS>uUn(Jkk6&

(wrl$BuEh5< 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%0D$cki-7 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¤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%0A_i~l!P1w;D5Ky(Lw5H+ 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 {idteS*s_v*+&s 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 z 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 i8DGYItsNB3&8#(;p@1!!`S%>z6 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

Oqw3}em?9UzHVP{4=yah@Mid)AJ=T* z%?mV7AvTnyrfAh#W3oDuB^9R~JRg6*1|`!r&=+i#*a>>x{klo*{M~HDPhmf=7rnZ9 z(0H-gv=GJ^NxZXyJp-b180GM;TO$&Fu_u0(Zp8K*Pqw4xrVE&5pzD!$`!ahMJ%uk4 zvexDP(dkW|y~j9f=}@fWh^d&$Coy^X@LKQsk&$k%?#D(qY|r#WuA*eJwE3OxFXzdu z_hDR49(9KzXm+Sg@x`{6=VhTsw)hr^>Z4c}&S7*;?v-z%`GhHJ<<{$G<{|l+M)b-ri=5ojVq#GBPLuN*?VZcE@V@BtXX}=Q2~f}OrWw3 zul@aP8U_cAQbMlNteln)#ivdmWd6SjM1lWez>pLlI?1}A3ZM-q;?Ts2^zt>})_cc4 z1%+YWgnIYq;cZ5agUs|C#C?BNXEUZ96lFx~|LYEKsu$DCM7lrP@#e{tq}KntC?GK4yP)EUEywR3jq11Ah$} zilk&TYA@IKr?Vp{$TUEh!9FTzIyklj!R`K9{t&8j!is3r7K^=?roXCm2G>v?5Rxg7 zFWXk?;!8+g@vNIcA!-` z%5@$rw<=G*=YZ4Szj@ajNB!EQRTFDeQ9bf?@fedaOF;W&VS;A-_0p82Od{g+byu=4u0DmwR z`Z`1`FePFGK9vVGA(+!$;iJ9D6yePmwGnSJ7(fJcs{oE73lh5kXpslX!UnER@CPO; z4hLr=?7v}_U;i~G2FG&d zl{^h&Cir?bW({p05WenqKGRdiAO-DChasCSj-v0;a3-O}rFb)i8=#~QgO!*EvNc4w zM-t2dKqMO=#HCpC^u5ynzr8+I8B2MzB6=eU#`TXbx)o;x>fsah#i7apH0S@DodP+- zAxsO+z1P&%p)PCK^FnjP!AV{RMH~_i_{8_x3OSz*bE&X@tRgk+^AL#$U;;J`9l%(m ziD2+#fKKKLI0mid+}gnl7vN=5jp74VoUn3?2NNo(RUx<3l0bv+!(S!Q%ml~ z5c9iO{<8W1H9M_DCWPk<4jicNP`n65L15cleE(m;L@89%6p)f;|6${!IrN()Lpn}b2I z$ASGq35+&PyLAF$NC&Gdeo!sz-=t`cdpL#7d2E&VG3zHh_=OMmB!d94!WHwjz+P9;slz(jlpy{UO*qo5D$azAjr%y1{IqgA4S+bp%eI>T4GJ+VSG8 zf#E}83p+dOtD=T;Gn!4v+d<^%0nz#XMz{sC$!V)V%6N!{qkOxfLfZ49)?1bsR@UJ# zL>ERtVSM3e$mROXM&NU$#c8b;kyteDtYF##p#6{%(>-eKsk9q zCdBMAb8a|^>_GX!qAQ782zNYREUpmnV{(BN4GJSMt()nrG%M*%ZGRFr&8k|e_+?6A zAakFtjbFZqWEe6PI0lB!Yo8cXITzNtz+gUkQ+p0KjxXeK7D7oD@{>KeSuDorh(jjA zWH82_4p z4)vA65eEP5v-ucgWFdhQq}?k6{5tPXkzMOkdU+eq7?LG|2au4Q(Eq|TE?psx*Yo{* z__&L&5vy>IHbTbJJ6P}o#GiC{dlE6;mi0!5cz1MkI`3GLW5rGHXS$*WzU&3u5 zhvekRyW1#xLtGGp)f1bHn;aDe<57@MZ>zDo6U|h1$?c)c@f8Jd?tpPh;+6G%e93*Z7~a%Hn%mo z?TH;WYTFgEHiLu7i+jpQ+ed%ItxwW%7kJyQE>h2;_d!aNnP#&LkN;@1*Wi1ecrt8b1Wbj0M-41Yc(Z<1?=6+5>NQ zHwj^O{+3+bqTb9klii294XG+osPW(|;Q%E-29*R8lMpFT8t@eDcTpyM78!KLid^Ew zYS*!IO{-W;@fwePPm^?7PWP1b0ic!Vp5SI5<0wx znoc&qN`}<&t^78BOqhopl&HMR!dN6uIOdyAo!G60Cbr2ex;pMzcOo9l3Z2<%c9JRJ zSQ+Nn^qJkC?6B0|>!1GQd2k`PE};KuTcsGz`O)I|Dc(Jb_MbOm+*$nkw%f2uo9QwNryG&P$bSCK$DMYazcj^;obh<@CL7C!1Y2DVfEc zrrsvK!Hr?ic|6|hdUb&HHsE=Cy1LL&P~Oq3#Rt@SRnBT>%lEi0!OFlpYG;2UbEmH*@KYlk`u-E0E-yV&%w$>7X ztj9)rdg8bzyLj1JJ~u`?T$(&etxTh=w#T{Zw;8$(PT$jU+oAWk_7_IjrG>6K4c_w& z@UVdz)@Yqieyr0d3*oPPOFz@uJ$i>xn`(HuTu;dJQoi`Rv06u!ppU6-@X~94t;Df* zIyLD4`B+7zL6kJYx^kPml0bRjWw_Lrc9mkpS5Y*UTlk{UdR+>J9$yGgiB10^Dz00f z68pU3;3l||xuRcEJ=>_dr8Y^O96ukz+UKS5JDFx-Za8~t>D;HkHI=f?shN78E0!LN zl2@72P;ZBBMWId-`~qZ=;dTy9+y;@L8YYQQdxxjGEZKo^Y2BL?md<7t{`x zXxh5NT7h{x@)qDc4!7^vH1$4WRlDtep2V)k5g9!oRVdG-*8~KAkC}R3bE|!`cN1%2 z<9ndPLA^$kWzERZPPbDls%=xkmW`U+kbJ~%c}Qi!{7wG4!cQZ*eg6YZQr zP<9Cu2L3H;S5-SOyP-J-7wpP^^i|EhtKrc=wxu|ZQtgRyL+-JxUNEWIB5Wbhw9xQl z!qO~VqMEbe)iG<)@9JdkJ~ATEr_ckmqaZ|j{_Wc`XL3uFbg^c+R>ZJY)-!1;S5_hM zTG&0DbTMz|a35E*saeG-&ba|jOUISTPgRf2qAS^TV?05t!0M7Yv}KRh#M9b3%y{|M z6;*boA5BeTaFzphW=%PK^}5lVenOPin!P)%^V0FoSf@9AwAN!4hle}{&qBmY^oX9u z%cgtnvK(>FHF_6q>k|EhrQ?>|QMz_DGUMhOopW0t_&l62bBm)(T5|0gfz7@8Q-1{P z{Sb=?%aMX-6`^&~XFd*Fwm2jkqHUI=H2Jcv7f-S-FLF}RwwQ|OeF;U$K!16ssn7T1 z<1H^ZaIc9_wDc{Y%ix)Y&r7lXtt_$rC+UI^b$Go@sHzgbtTs$F7^d`^v!G7{-l*kL zieUGKt4q+AV=s|m(3(bndW;?#X{c#UWC)LTy(f6AH0CHq0!@S<;XYB^DJh6pyALYc zungZniNp!1i0kNry{KcEk3`YSMJ==lIOK}3-L##&=dN&elYF@AC44&)^M3L-*BfQ^ z_ge!xEuJHkTlR4pF-M3HaK{hCe=vapruukHd;lPl;D5*j?Ee3lfZhL!38?)y6A1cm zCh)m6lszT#pG-hV<-c<6aFBYeO@?*D}Zrrpexm2_IKcen`)ej~~w^v_ix za0$-Maf=mGNyN`-6&HmUycD7ciVCCF2ju7HgO^ZAp6UqK0uy!P?Y#U;wCiyg#XR}$ zz8KxHnttMOs%baVFuK4#uQ0?>69lmb0W7?;Cw;Ifd9K>QTerAAAGuAFzEp2xu1VkQ zN}p~^Uunr*>fqyI$KB|9Epw{MmduNdK0m{|KEo%zvmt-5wZ6cyxxw?zUK_(%+g{=g zz1WUC+g9IPo59+SJX#yW+lswdS6^N3;%-HsufH~S=P@_6cQpF>xnK0(s$S2n+^_f= zzliPJt}Lv4PdxixeA-TK&4iuh%rAFWJaubrV=pIdGGBb`qPa%-T7RjpU+8Z5uyt%V zH+8l(|F{^m+8DUm8u>Ze-0Y93?#KQ4#Mgq!`e04k>}7lLN^{ebv+l*%q$ZfxT2 zt>0~L`!Ml!GqkC7^zO7Y{2;e!vh#3rHTt=m{d4xzoo2LI()4FPrQ1!3d)kS+cBJ+9 zHuf-oD_)GXf~o7dnX6@)E0&qth}z3}sjHakv+wu17hlJht7DnBbrw&9=ttvAaElIG z2k)a-t%Ysb*XniIdQRk>&3Wy1{c<;c)BH`Z>tH7vRe96wE?4g}9J}Am=jWd)BZt;P zBbGQWW=u|(rm46qi$cnuXLm?b-Q=k6L!0AjjZp*R>dS=@}|Cx!3Sy;Y zW`|sBw$5~X7CPm37};LE=zE=7GB&t5d6m{!)gFA}Hx_@4tcmitmT&(o_8N7cdAUj6 zXll+{^0{zzn>~%5x=X&Ck7nek=^XNljV^Sk?a1AD7e2>C%Px6iV-&cO4lai!O5HzXWf$G?G2THN)BG zH#z$dQ4g+;4r|d02~Q<%yU(j#Iew&QsKei}k@hMG%imVP&*`%^dVFUnNHHHR$eS8) zBsCPoWD#Q&h9rvq&O4fGaC&GoO;~U?WhO>qX~I3#61tmD$L$rG))c+aYlTt63by(K z3Nx#%?d|KN>jI-|^Qx+Gz=IM*CnqN+^Y}!vpW&aN?0<1y9nwF>f#aI_k@LG5`ghjC zpVu}Ys_gawUK&EnZzTKL7 zMJeVrR<_o6+2>}*-w@~?@k_tm&iMrb7keFs1o&%vzeqU7xx6^NxIaK&@ePl@NAPmB zue$O+3;?*@&fYn|KO3w|^9a9wIg^)Do{bf$Fy2REfYI{e(sFV}XBU=MB7y%YA}9-1 zT~_@P@ApH3CuIs2u8B;Htn6y3Da~#OY{<^82`sHF4V)qTt*fGhSVT3&p%HKCX zI{t=V^Wo>j_(QEGvpKuY*ta@6u)2DYr_A_x5B&&#`ymy*&{IxA{GtZ><059p|3Y9E zn44G2etv^c-u?rh%9^6w^yHj!l+xtP&~fkRm}#y<$_vJc3I>KN8eLkNy4Twl{L_E< zL$3CG%K%E=40r+1pZJ-ima6sjn(AKriONn-&W}FHA6W!8Mj7R+`R{3D=XJ{YcU=Ai z;24_xWRZQ~PZl4+;l?eED?qZtLXWE^UzZ-q;WCT;G#wVOvp4 zMMWK>+n-EJ%S6Zgf_;2)c4=y6sXv*qEYDLIEU%=XrlcCfcLmK@Tu@9zIxs9G8W0Ku zE?4^WCijrLwa4cl86I~?&WTAmf@DS}2H`~#ga{MdGqBnW{MkMz{_*^D*#>!p@E5sL zPqW$}?zZ-H=uEx;oOQ4k5`+Im1&x)|hRPGFMFpL;D8CUw4y%XST0*q%CLz(2P)AUYWaEM z=Uw$j_x*Q-A(x4&+JjdYsqnSA9)8ZxtAU-_!|dnmbKHlq2o~BlMLNZ_Sb)BP8fC)N1alJw8}Hq` zxh!?-Jce!B!u(p2AajvJx%yqb`@VH+J!rBcp=uA8Z?!0aW;RkBNBucZZXuTnS8inw zA9HS~=$a;w3UhD9UiISmnu*hIb()}!4Y4E$>F^*(pdNxsv%Wa{1soy zh3dWWd9E582$Z2?W*DuV(;Qc(<>+{6?#8eQjm8geghm!OTZPG(@+w1QkG;KBHR?|rUZ0n@wU}0;?ian6pri1+<%#({ z+S|Tv-^4R=%3fx3*56XL(>y)vZ96WdKfL~OzJ$n);;N>V%5_zHZ6kdeyvV&OtaLBi zfuFp5KXP^b(u#u`5hFOg0|<=5j9TCynbF+wNPY`yhC0}(*v(7^1z(SGEkLiMo0&i z6Y3T@L+wmCV$S1tZ!&|8zieg}iGOS)V;UV76|S8*qYFt6i2ea3f?b2`l>k|J6eKLz z!@wZm9T{fk{x@J?XoB7BmPj`s1jFJ=P}u8`Xp0I~QcY7I${irPx{!q#O0hW6EhobLIr(o#Nj7

#$DMS=LI85+0BLB@%*I2m(pRS*3Ie#YFRkvggvWHpuTlur7YiULz7y7C3R05k)s!oRoTkFu>`KZfMR`fg- zf3A*eX`uE;Thy`)o5G;7locY$yDAg!JKTOWjpI(kTMRtJfKAVG){=p=sUhj;KvsU& zN~71{rWUMUu036@GWSzgOba`gcZnx)NmTcOdz6(kYn6A_TR-PA0>w@DO1@vRt>bT= zdOplITZZC$QEUD-)#G!Aoj;f;J`7ABKfTGE$tDH(s+WNS@ea%{5#zk6xjDF&h+fIsjWMhyz2;jO;yTHf z#N^4Og?jdQ?e09?I0{5KH{`c@wcqMwbai(03ITlIYwxzhbE6I68SmiE`k7@R^**X* z_-98QFP)}0?q2J_zT!dVk5H*@`Mh?gA{~7%sO}p#J$#b6XQv5d-!XsYyk1Yi`1Sof zozK929!LolpQ7;6am#F7Kjv(vTfEZYX_9T%98833z&E)-0jo7HjAaoq*@@s_L~^`Q z9n9;9oLGTKN6o7JagI6DbbGR~_nO7?vcut(50`_P6ijd${wKr&;hBmi0vKT3s3c;K z;H+)>esaN4Fh}uNp=;jYdfLbz-M5b;hZ9jt5y|I8R@+2ZcOrUPkvu$AjWatZ{xvsy z0z8;4JAd_WW|*orBB6$Y=Ku-3@KH$gOS3~>a5~whbLs`#M|M@5iiBQ^GYf54yQuZl zpt~}{c9`Jqbe4ZQZa=FPUf3pGP@fgiw~g*~BknXO`S_?h!**WisagQ4$ms2ugK^1w zwoqxXSNjC;3Fe3Tn_^2KWMfK5xkbZ*8i5yu>4(b9g#Kv2YNkTT)f8EwKlfX zApbJV7aNVskHW2p#O-PJ;s~rXNA+s9Rs85C0*yuBb|5@W=z&3N&d!{kmp?Tvo~Z!1 zg>-c^XvJVK3NxMuxH~9tWlC^}SuO+CnG#rG zX0VCbt`{~8jA%tUrWCR!_N~+mhFS??_XkK&Xk28tFbUDYM~w_RlVeh$cBgK!SHH!* zZzK5ITK`tP&HTz3AVm?yPiiPF&2-ubA1f;iGC@qGe-?P9Zkv0;|8=3YHnO$UBnkjKq7H4Ml?>|{?q$=k)o*{+8 zdl!UBD+6^v=Bq24d!3xqUUH+EeS(zFvgw8=E88L$tL`uSn%wCC$a?sjCX-4UDf@sc z1zA+)k#$b@Up~*xw4-Zpe~q+R<&9JPru9V=PZ)k_Q%ut_(at+(^-_YKf+K?S6oc3l zgu|5i+S}U9i5qMy-fsXA4 zsf{41m-U>T#XLW{JkJe}^ZCT1{@Nqm!wK)sfS#Un+)+&QYvqeEf4nH27z*7O8~qr+ zdXEOKLo4sF+u;0gmN?RX{;F~MQoj9$YJc>WIOgrYHjSq0@J5U?Jt=K;%WyL@ugAJI zefR7>?S}^RH@M+Y+!&+BGD&0Wl~gu(V}s)>;kUf*KCAq*TJ?kEtbopkU&Two$=Pw* zcSX@<>-xn`EeAarDDblBwJWW^6}l-_ z*mF`yzgyqWV>`&MZ%UXXB`2Upg=NW*jUIA3bxxL9L^n?;qECYjjm{*xhrb6SZdHB$MNe!N3UHk~oCb~B4`S0@L?ZS8{|4ms zZsy0+9NWB*9|iy`CW%lu(mN8f>{C6*QMC}ESH69|xXjz}Tx~X619FVH-AY4|;k9{; zdz;kT7X-F)STdcB<^oYgL=#e_vfS3QHtcGi9s;(KKjlgZsn6{KEidEXXBl2?de(B( zQCE#0)T$>T%4L=+Xjr#|O4kC*~H3zT9i+c;)i1zgkcGzmj>jvv5-T z8zy5=Y=N*PZHP=HE41tC_``Usvb)$8Cs$?Lev<0K9RphQoJ-`6KW4yoHuG)ZBYYo* z>waom{4_`MUT@w0%^tvnh5@WMSwA0WLp53EK`Usrb< zef?)R-=*ibfdf41>jWW%2Yv@zwU-IAkSn+>i0K!0AM5whL$->9`@tZ- zSUcDbzduGZ_Y~2iy;PovE!s=!47adiT96GIn6 zIxA;;J5>!B0F8h3|MiS2HL3~8f53rSqmsfRHJ$SR1*ii53!owZT*V398_sDQPWdAG z&s0C_`u|Mz+q2m+LLk=B|UR+jb*EhPo&skcFXu zn1H0fSRiBpMT7xDDFsRDk~A-jC?swLBB~OiAR-bN0ISG|axR4e{XjJu2TMd`M4bNG zwfmX-*wyDe_hW2!+mdfy(_@y(+jZrxB=dbTfV<}o;81DO>&tGNuJL+elIm%MJiO7} zOro10RVv*_&QE_OW7Xa?HL~uxyISX5)GV!NtL`=Fqw? zHrDd+v2rrd8Wn|HEggPswKHV&5^-G%? zz)3w+U(hl-ijTCT~ z+iu*s25VNI@j2=i$Is>3GT!wdQ}jGOo|!owJlqCpF2BzuHfy7HE<#B4sztVs#N%s( z@UZ*oHA&XUTJQBd!P|)@1wZ%yxZBS=D09R^L(+DWZ-&$o0oXV zsNwA>b#!Nw_*LW;#mA-zN`7{h9&ftS;m>DT?~>r{$x{8J5#FR1ldQe7y{RU-X_J@;WVeFFyp z?eTOje|It8;g#t8_IBj-!_1%VCuKJI0GrDD;t)zMznA0Tvdu-?=dd$4-0$bhQ`e=* z+~jJ$;p!!iyOpgqc5vWsvB?W%^S~%i-7bLmvf5PXl8$TjL!l|NV!dt`8f~k|1}%Hd zAs|VmiL>nIWxn3l==eq$eKIa(qxAk8?r)a)KZ_#wp2u2^NZ*BwZ7!EnJkrjE6mPoL z_UqB{+{6XM&y+A-$XLsug@?ygc~)K%6*89XUcc*=^kbYhNvq|ANiU8?2{yk5zp2}6 zG@Mp_0S@A{qd5pT)0_F69zW@16s&gPCEsY`^+ekl2*?)cD6@7F&W?YXx8NCIR$3X! ztYz!MRY}+=K;!I@NiLkLTuoK$PvEydityzh&62Ev^Q!7Ql!P776S6^*4 z$A95w64o?YGYbucgn|SI$$-^d>I)MqDk=!q*XGwW{4ophlgDG856_s5SAL2I!Vr-W za3|wnO&KNq>%PA<9UHh=SbrS}ldjL+@JFUGg8ey75c;E7j@}wzV`0X_5);FI3yh6k z8H0Y@U1F?>vJKng5r?9p;S6YoR)5%)f4QIW4GjAI9j@BXBK+SmF$~5d3`CeR8W^WE ziv11KV35+}-J#l%{(X=r1st7&7~vNp8rm>Rdc zkMV1HnaNh-G5g=bu|M>6l7CaJ&2VS#qD@7+8Z{7qOQL*5M08|qo_39rTBgu|i~ES* zZkd^igby(Txr;RuOZ-FoX`cM+W!5cCY`Z5W-Ku8H9vV%Jqy9RjCDz2sv~DEOP8`)_ z{homRz~lX9cRl`2vFh;27zAkASlECZ?q-@|B7WX#ik$5xGDqJTgV9$l!<&G-kBAOi zWicDIV*l)Zn-=W*JHq^ClMVfnOa3C#n*Z!B{ibX^9Po=y8PKU)Jvm7REtc~4`G0>k zy!q~Leyw)UC#`4B<@5P@@PFT2XRp@j^&;@+d%Zl>O^_qUgf z%lg~=kF38zZC!SIo!jme$LGmoY4pwXPgCi~&_~%*tU0!vin^+reRcKRS{~O2k&*ic zWAQ1e_&cenXej6h`2USjnVOY2FDb#7l8}=+A^jhKYK_LsM8reGLc#%on4y0+{^s)V z+Vw8rPk7g+pL)9QY;%uXoHvf!=H~T=rM0oGsbmYoZi8=jCZ%cBMA}StO!{VEVDFBY zF77p~*5zu=-(`XtMg5wx%A)FKDMdlQ(k&<=!1$rbNrQO`5+Jw%0f6~8K=2Dbqct4WuhaQa+qerf6@+V&T# z64o=?REn6)8ow>6+Zp|9hvUKIXu?C;9b)yY-_A)tR-cW1tEr~ToH~5DaP&>pkhfVI zHwlY@55fmEyC=+R3vbqb?MsE0Q?1YJ#3FPkUhG3{^m1-DKh>SV@~>^$y5}ph zcZExX)Wc`${NIa+cMjLnkH71ugEH%^a_n5a-B>i6XWp-)Ep>HCVJ#CmIt8MvGH+ML zzcZJ+<|93zi>%&Vc3*E?ynZ|G)t4uAw@K>6f4%Kpzu#Nlq&n4-snwqhf1eVU>>S<^ z9J#cr6mtSuGttbE+^zb?liZ;_6KhbG--yI%{XRtLDE}0hiGQAYzeGW6(tkK+pM8=+ zkAH8}L9z2gd8&$dZek$psdMVJGl9;8skyXUIjAPD=6maQjvakpGHuA^vWx3RqcR!z z<*Rj{tXF#Xa>~sucxoeR;C;M=)V*@rb)@Q?buK3O<+`Uf#0%}_%5kulxaK@Re0N#V zDY<9$eGauS|M5A>Dzk=`<+I42?RYTpHI39>`(#EHq`G;g)RMiOdvJE)Uskia@!MTn zn|~^tXxoxG?Jm%-{Ja;MZkd3c!L`q3Yt@mw_*$b9K?`ZkONIPhW_w%YEAm}FD&C1i ze2mSf=T(@_FSl~w{(YZZk_#QLI>?D-R@YApZ`1M8b@eN2>M}Gr=`8sLejNQGUWaE# z+sQNkOFQe6)05gsK#e$9<+`?g2J5}!GG+5k(vDO6^C(=`{Tb`p6E*Np2&HJ?^TjL} zGdu-Tg?gEWMU+FGg)N?opN*B^ZqOG;4}10LVdmC+d%oF(FV-K+`yQT+#vA9D-R&w? zV)5_Of1Uk$b7kIY14nn~_Vf1m5m;ywZ!vW5<>gBZ(axZlaM$rj@^7>-OxkoY->-X< z+j{+@I25;=AL+y9XL;h2e$&pN%Jur*)$XzAcpsW9b$=l3z1}z_`^yH={$7kuGHH|3&H)<+1Ez$vsv6&Bn<7cvKBX{Y)R@EO|Ofi>_1;A zJw3+XoHqQEr5P#xDT}*l&DeU^O=5O_`t}bQ`?7}Js~`ojrguoZOfjThxP~xR1mFwh zYqkdlUAk$0eKZKGK;?H=ra9URuNAy)yiDY#oLJmZCO+g_D_*0VAVXg<2)_1GB_rib zS-|ocG^qO^4y3Y~x5{oVq_qxROy}w1ZE+d{|7@P_#VJEwnapqLs`1{F?j5tJIruxU z2_~rzb+*P;8W>lt(4TL?lTp#AMvQ#%rZh}ft?X^iZou|ln!&8~=f~Hf|LZgziLNy- zq!GVaDf967x?*+s%tQ;QMWmU;ef!p>e-{pbTI{l=6%Lk@bSCw_!Mxdh9sZV^)4kbo z4paBF(6DW`!#HS=*=yj1*v?5IQ;2x=;5Etxrl#wOsWmXin*XBip~c8SKKTB=P?4Ox zfX0>)c?yl=O70qvOblD57H->+%$JvRG_Mq9Z8w(hy~W9EPP1uD*!_^ZFE0%xZ~JG~ zj|MNZ!e$y*VGOEN zdL)#S!}-=@6>4>l1X!zYb9CVm9xHwRo9?e6{Csp>N*g6hhc2$4#?Q)I_vI;BeBgFG zmi0*N?A*>3_mAP1%#d*Z&n72B-T8tjs^Jon=&HHNo8#&nNAGR+Jn-T+GOsNBEFaEy z0jY|o$LN*Q-97p{hi|sGB>Y9f%5|iuSFLSit@q?1$ztISl0UI@*4_}AU(5&RA^lfIg8!tG{35yurdiD0H`D>dR z?V}>icu}_^!VY|+iu1V2#%aZ=s^Hwt$_f;`cS7%VQJp8*hKl}b4!D4_Eh6^q>t+r5 z8zAMKLAKESB=kDMArd2EH5fR&t3%OHqG3-|r6JehS9ok;K6%tw7(I1LFUBfAO+S3- zs_TCptSKjV+|qX>@pOK|G_tq8KF98?LWG^m;u< zW}~06hjupbud4UAehd&K^Bz{N6*JHR83eJh+B7|0u;i2=WI}h|$VZ;I>>y6J5kn6@SJK!Ee@eiHh+~ z(YHPq?O5--Or7ZZa{_)^8*PYp+3NfvXSs;d-}{t;(Ie7vXWKu=#Q_Mw?|jP)-`_|c zty6*)Vg4B@I(j-3SFOC=anIy~it{*&@GII_-nCcXzINlJ{IQu;$%ji!|MmVec)EEx zG?{CTXQQdT^CM(Kp|Slu!~b=1b$F#ei@g+$JMu?0F4BMb+X3mF3(}DKy=R^zd*zk+ z-EgO=_gMR8-P7XSFY8IHx{Yeh-498jUK^*uWYqiycmO@f#YcX5EQd0*Y&(5`I83_Q zyK?iStCmeXrz5a+8^(`_(byZANF>9R-awn{Vl})T%X-zqW%^b2GCihZbcwjg~Joz zrm-E9`aGGD5CC`kEuk?}V5?F9BN-u*M}(mr7F8VPCxXJ>Cw+q{N(Vzig#7udsG?9J zE+#of9^9CZ`kl$>fj|yKsYHru0iz<+%e4?raO{RK*n;*JHhmXWy(z@j+U28;I3$h! zxRq~mpoPnX0)%S|_gVSg`%`1EE}dK`Mb~A9`h&0>34A%C5f$K@A{*^HHrkkYA3`Xv zzqElLI1tF0d^w1`T)>Zr-34}f@j9Znowq;nK|?o)U@Q3n;i8aKxYh6)1eV*Ji`?0V z38-C}ros`W->|I8PFbi;Y=zM{yO}%)KaFmgv)><}FH6vnEQq)Yz2TJ|Eugb6#o>X|Qvz&s-BdidV z&h;F~-($~JFBI*K6WoK1(<3gWh>UtqoWQ80QX*A=H#9Vp>+|;E zjm~U{z(RJA(>A(N9Rb}CJ{hp;5}iF@e0_=#`#r|Ed5g`)LP$m|VIgHQ0MyE2*&J%o zMsg#(H6k&Rc|l`DrC9hsO$)q2R|w=C0S*3Ugu3Em+M7*V1e>-N(oljB9PR~eX285E zAykNyrp4eQ0)G&238|D3@x4FhMYEwZ5CQFxT4h5P(2E6;uE%{E*pU=vglvqERXiwM z84D)G*yr~}*yiPhiiSgs;un=Yk8>T5V}(Cw`-pLjg56*Q6j8QUB040)`$~ced1PuO z*rH>9iH1F(0wEo(2K>a|oi(lTi%{MS7%{^?19>0%U9sOqKw;k%V-^vCX&7x_bx%NG zO-WM)sMi6MfN+hPpdA2#=EggpzY}#Ma(IH##^IBZg z4LM5}xxCwZA!11N)lm&1c`&%w8IwS?(h7Niu}XuVteu=y`)!X`-N%yzA*z5dCo&O8 zR|M;i1(?l<5^y2}r^F9{)=F8UL-}yhDvC!O@F3a~5sw3Kpu$I+2?Iq8grrN6tkWYh zN|=cF`!B;0NsSQyjC}Ius1ObUQP&4#JlIz`g%;tkYdw&eHZqk?r;0cdUO%X_kM7Z1JixSxvtvoQ8er^Q42H zD#yLMZOLOGqE0~lNEloKDYRsT#ytK_-&$)C3h|=u2d$SQB6Y|5Oea$W7Zw`*-Rm1RfWg0^8Ai)k z&P4^1U3};Q#}GocA4_g7Q8vn9!{?<1y8|eo8E$HafG6||0DqG((NBa39X{d(pa}wr zNg(j&a!3<{Akl*$m>O0_tXg;Qr8+pa@835}dEB&a3munIN^8?73ybKU7QIHKWv_SB zER?}R;n)XXpbkkbp^3*!Kr~ zYg3UaaPYu~7d+nJE)Ae=M~H*QEh>BpEda-yM-PD6vMMoqT;vr4MupVN8)Sk4OCP)Uc&h+9kukQpq6O4agynJSKgknSbswj~& zO&E|M7#t=V;a1r*jJ+Tz6tmPoL}YUEWO68PLe%4m9gs)H1T&*sMl_ZP;3Y-#7=RN* zBG~bzE+!OG0m6bb^&%-+e$6s$%I24`qCz!HwLnk_z$FGgk2GELuBXn1QxyGo{0{p$ zycRX^aerKf=tT`}6e!+bN^CSS@!?B*DW9>m*u($j9gWa{5+;(_L9)D)dlY(Sro6SE zi1~&{Nc8zqq%o01Bm_bli5N)|VIAf^1s>RwDY5&N*GOTwN-WwSJumR@@JlCErZ7;m z0??ZQR%7Dvb||(%Vxn6MWK)A9@rk`pT<) ziYF16I*T##+qe{AmKd*Ra>zO{;D*Pw1=a<7fFydM=%A1})0WoJ#bH`KGsHR(>N3g| zcv};s5z`TE;MYU}Gc4W-$PEI~N@ArD-(Z!y7a=5_#d!iU(06PUXX0wBmO#B?a1_9u z091gcU7<}kIXVhIur!{rfEKi9j1;;c1Xco_NDlx551`;Uzc9ksTsxwkSFUeD=%Iwk zzEH&d>T0%11}$vDpP)*kAb$K};&M-CfMW(WdxIZi)7G(Z>~uA_mof~hqQR@D2CgJg zVZ0wE0BS>EE)s^Ym>sfEFn}#pE+QkF0omsI;J7oJ?~!8S#q)CZ-1;FTfjE$&@kTbC zx9<2oStPYIcz5ExBr42J4>%~6LAw?Bu+4Z$%7OLgR`nu~MkE3gPzEkUJYqpC6QRj=yE421LMJ}mD;1;gQX?OV>Dm4-zYyOZbSdw1`5>CRdcNAHLL zd;jSG#&-f2p0$FTKhKG^5UputbZSwg(xOENRO&{^A zM?Po3OR}WL_-5B}3RcQ?B7ycbrB>Efe+7O1yVp$5!_@US)sd)8}$2Dm(k*`r~Yg z)h`95m1*Y0QEdb3SKbu^kg$tVVR`yIS={LNMrl{bQ9Muh-26OzdA1nqYkzWdrKSE) zz)TEbSkN|MN`KXgG!UP@DU*@3!&jfezoJ{OT~iA${+M4;Tutr3vVS|gPc*I5eWf&l zp2norIUH-#*Di63N+CE@MWiRevaEl_dV zLZ)Qam8eQ+jgOmoE{TUj(EH<15c*W+qOE=Nd0^|F8hlA@;Mt|)2@#S%5fvcg)K5f- z&dVp5q^#Vaoge7;u6`wW5;E;u)9iAMhZ^ zC#9JIoKHWX)%Y^wU#wwFOQ>_g>*BT(sQ9T{S)%1-%awwvrB0%c)w0*A?wv@5&0wjn z9g-GVzk_pp;8ql&%E-gMi$AYi&2 z0s*-i0<93TbwO2Ox=@DMx9Xu_TehFV*aSQ+g$LH(sH;`B&Q#86$bPmvPCKR0SW!4x zXDh;PvUU-+7ZmcDS$2hYK8kTfyz6x~8aB^}*^;oR|LkV?|4+k+LZk4Q20UQk_CUl^U8*mOvKY&u#<;}sk^8l?CF zW3%7mDq8KlbFJKZkXXc~Wuk7y@0qxN z@71Y>c53exX2Dbb%_Z9^&rIvx2~Y2S>#)cr8*1zcQ9_O1T9t^Ex@{4xYgfw@?V<@! zWRx1I0cCN8SXZL3RQK#NF@JF>xsyTqx^3KWO4FzCijixj>`3C?g^Ry0II%%fFc`DQ zdJhHwgfGF>y(lN#TQsEAkVjSgttCvSjNRet$lq*VkCt@k*x=+-y|2vQG6@&4CLp9n zWlb=%u40pqC z(3+rMi=h}aG+ho6BMVCvW#tTa3%&$j7(5w^z>^xz1y{FJ%K*1!WN3>{2aB~DGzV*O z^LUsg4{b<6iv4#+Op{wVBH*3CV=Efs+_-C1LByf>>|u)`5A~n|q*b*7tT8X3K&F4v zNNw%U*Rql;H>EUZYOA`sBt(o4kHnc2`sfrbUp$9g` z=~Zvv2;rZ124lU>^BQ!WNo&HvoX>wNJ}YqJ0!fOR402Kt9>}I9q4;%3$eIhx3p;=( z5avn5lS{C?kqiC^@`r7R8XEF3?07`kXOhbd_IcW6idX9aY34x5v(&8k7Y?6%ZA`|7 z|3wJm-&8V=%=A^*6{~P#?loEam%}C?&$j)`GcoZT<`SoNC@QIFIE_ealVc$_N<7f; zE3or5XE!#8R!J_g>xarUG3&$SHgq;;I-Pn`?b@IIV?1~p2%isv98}edpRJkW%D`FU znHd*T)7sH>)RxK!5;s7&DnQ_IUhKx{k4Ttz<;K1MxA zqVt9U6?(4%{x)@1agge))5%HsX}v4$Kp|NU0@&WOn^%oXzu1=qilnQ2}O|8mvH(#B_ZdFudq znr8k!l@{WTJhsQWaVM7Q!zP4>JeZfH_ze;y_+FlB-ICfmnKkV!6r0_Kd!yV4u)`e* zdC^0n;iv|LZyWR%0)zATsFc0!U_E9<*_KS#Tp$V9c44?x12e4CjJrG|1&6QS#r~X+ z;AKlf_(B!HI&8zBEfJF;F*$Mc!3vG4M@qf|8oO|oK@ zqWzlyHhf1aydZ6@6$HhlIk#UX6hj4b@Rs^toV^2+W>L6inMS2inU%I}o0Yb0+qP}n zwrv|<+P0n9b*HCq-{_f%=$;>NBKC>cCt{y>?e#p0SRYOhBxTDeTJ2-QHUXSC!NieB zib=3Twm`uO2NSnsZCD;457}ovajKy-{Q%^aXY;pL=2}?_m4*xPWOY}ew-xkSgfS-L zzGa%o^gs=(DqkL)xqIa_vzbKIez2mrlKF_~WADElNv7 zl`z@V)xyb~1Uq?Br`PR5JbWSv-P-sXUVSID62G8Y3_`RqlWY}2kj+XiYD?w|n&p|X zqZ)dB^cv+rBOWvw-(CMH@}JD)m9jb|ei$t+J5 z9J7w_3|tBpt*#g)T`vZ1%wv&vqqaxZxPb z7LgzSFry-wC9Y1?yRzBZ@R>4;4HdA0MSeB{P$PA|R9f`N@*-t9{MzgyCAEA-66S`e z8)myBD*upV!^%xibnlcG~16a3uhh{NrG&0ZH2XC-ZIlj7ByL=Qz4P(34C% z;gAo?K5^%4#TYbWPm86N+mZ4?SdT#R05 z4GR%}7f{uev>7I82P1*jsk2Pb*abGV6bEEx0DoiN0SQk3FUulN-U6q1)Lp}QDL#Tf z`d2NIrzCCPjA*>WI!vQY?;jl|p1C&|VN0R{uvGH(I)qN(*#1knKq#tvvm|=e&_Fe` zYwRs4d;%6Q>wBXj2V6wqDZ3K<9F5}1Ig7c)phBN#RZy|VbIg&W*wtfuT~4F9axGT8 z_Q;uJG81BDAPe_fe8xVnWce(acn>If(&#WSf#;Bjs4JJgy;W8Qr?`P>YO|e7Enq4$ zDlMU@+d5EZgNA0Rbv}4?vdy+HL>+e#+u9ipJ!hyo8V<-?zs?6wlL>O&dbb zK>Q6!ET`Q(kT(pzyv=B@pi0DLtW2y?u_6h4m}0507KuROMh}px)fVWBxXP2847~2I z-F_CY=WyqN%$g;yX~;8N_f2OdIgGYb_``R3rWUz2Wsfuar6?8&440*-ynfyl19&Yi zZ%{H&czh6Y{ywlQPBl!T|}%_}1m*OQOIE8(Kee zhy)H}$LR)^3NtNsH7iaf<6x)oMr-%{Z|1a-ux0Y?e+)>CAl)1qq)Qs5FWcmx{0Ih? z{7kqJzURLu5&?rHaw#j1F{J%p**g`Fyf>){olO}0)bj2ucetsw#ekJ!w`(1YXs ziS39KqnBh0xfwA8M@=aK9RW@>yo@7KpcJ&~C-KT}9z^wKRNB1@YztrL7;&{`vDYiA zB8hQHDE+6$cuIPIT6W~21X0YU2(_kRL}BSh?0vsUx+)< zInij`H$5|waNUpsgzd@#F8(N-MOuHGfssG_e!)F>hZ-Shy0*u+w3dcQ$W_Dz*({vs zqfS0LLx|v|LaWZfM%VBiPZDbwXCRZD3Ffy$;Q!&-n!JT9u#&? zmf>?(V|Z=5N_Dnn=q4uBr-#Q9SC+D0-rN>X7IAWCXT3~#Kc>F#;&;7ztDdtLU%om9 zUAO07GFxv1i*8f3 zPQ^)ak|o5@7a5eD($PAs&~di%P?pA;^ZXZ?Yu*LcO9wxG4C5%XFHwU%Dk2`Vawc3}p zibu5@`@Qv(x;Hhw-$}jwQN4Q!d>D9oMYwuah;`=ueCklC0f87Hiyrg1-@&epAg(3gnyBELhK0B&7Y@*w zT*4U-=J3O0c7h~h7Aup(d zrNRDr{PX zwka;9-aarN;tX=@M)vC*mGz}x&zxH48mC^Q74>PD^sA5YFarc->4KZFf^?Y?==y!D zwlI8xF??8_3>!8^8#(r}Y4%pJ25NcU|4l8sERPS`JMlKq20E9CJTwG@A&|UfkXn~_ zUfzXai$r1HrC!j*U|WTwPs1Jp?a8 zyiy+<6Hyr@5t(aP&jsYSM;jwsRW)Pfw#PMvqPFudUAJS&z;rL?V=A)a%=<8k>beu& zsWStUXPM&w%CDiJH#(c=LqHJ1eK=Ljw-Y zW3Wr;=ct8;)ICb+!s8#J-Fh=xePZM(Ugq|Z3O3P{)?o@b z9|{Y2%^uH_d#&=8Q1k6$W^jY28LwrmiKkDVg)479TDfRjeOaEVDAk=TN{uWUwtnRg zuZ7!}o5kg3#W(Y!B#T(9LskjL645jUQO_e*>CvqE!>7f=axeuEw->9SR+XgshU(bb9GgQ$>U!NPT>Fl~N zD-K(}^061TMCMS+;`QR0ISx{)!24YF|9tAj@i?vd9Daf@YTDGN|CqIMaW12gm6TjWp~U zo*f#zIXI|gY%s}cgC1_}Sjc*l740Gw*+(cmW+-xwP_bvD_m6m2#}-Hm!@6F1fZV3o z>a&vFxM}JOi0_1*_s3U;6kCz0Mv-OtWUDfyt5Qhsq5ra2CRo5Fo)?AVQxKI9QYmF! zi9r5_MB*hBHnd!L)BKA*l8}*0);vYpERhHnblAc&eibr~PLn`)CEatG=@p#p#X{3Q z$g`%|C09 z4*%<8|HXH3|34{=sZsh*-$5+kfA|i1{u2dL!f|RI7L0<@H2+sS3>9am4C6n`;XfK; z{-<^EzpmoHSPt%Vj_&`}a`+#w;Oz>jgrd3nOq~7*R4*qImPd|H@8^^(o=6y1*#z@T z2)bK1BkY$vVjDRoRzMzD`9f5%1wjBIp+%PtQk8sC34aj;$W-i&@6AW|h3?Kzj=JS> zmUoV6>Y8V+dgEgM%+geDpkTfUaNp!$Vo3p|>Tm97YGCk~9rFooL)(RGrB7kfyY?u7 z!;flLS$_x7h1}nYV_w^-$A90P`QpG?ek&W<_`D=0@+B_~jcxAl@4poP6%L@5k?t|G z1g^QNxIU5<7Zr)f6ufpANn%DcsyE0ZYI-OT*ReECpM{DJnIagQ+LSedZ`-Rs(@2tb z4-j)i3lKj(8BR*zseItsri&(ZP0{jM?DE6kNg^xn2`Q%rx7cmJX$OxM(U zCFpF|b>F(&Oae>SdrARPz49j<4Ij_V5XrYC>y?=ehg^a~KSr{OrwRz0?e38w*Qf5J zR)1Dj!BIz~Vh&N;=K2=W(XJ{8KJb{Do|&edA>bS458v2UnF-rZfgT^2zyd}d|HK3z zZWlQmFNCifX!iIVzyJ7ZH=*en(I!ShsHCJoeI*=QB4mu5iUnPE!92g95aAMAt?>pd zzj;SLr!|l^1aV(qF32hB%4)XyNx{`_G54g9@;bYio0m5>Hda22BL`5^5EMFCiv3P{ z)3&DL%z4zc)Jmd`p|VA^D2t)2#**9kRFrlfh2g9M1ybxth*oW`(aU?S6k)UR|z?Pc-eJvenT_OT^y~ zy{cU@yAUw;K#`rr8_F#WRlZ(cD3D_5SWQvN!q2JsTS+ zIy?z5vJhx?!|U*v{pxuxoUK%Tc%GS+t*V4!t7bQFBRX$(yWNX?I#HPGKEefP{56G!S}xoSvF`#0TX81oLyL4 z4Sp;rc>ER^9lkMv_;Gd0+7Lifv}I$WB_&{uYll_+K~Vh3en+4uzujS15wtq&-G}|P zWGKp5m^OcBKdJWh&t%|XbB%`6l*#igl9tjNsMUtdw6=hT))zPyWrc_O-#HAxsB`06=JAs+ck@%e>?=G8cWe26Goz{V+3A`- zKM9A`xtUIGWNt6O$VZlwot~4C^-6(>hxK*mZRGZ^@?~T7n=Uu_Kh%bwYir$`%$!x< zf8R&(?(l=@mTYH^6YsU#V_o+ZaPQN1cc<%rFM+Y$>2MEp?XGDl={VOg7I9ZMA73`G znNOY!22Z_zpgq=M_r1Qvm^oAAAd@xviBC?q=jOQr$h%lf#1DohAdupSONGToZp}u7 z$3#RyA;7>vn?Qg=ybk*Gd_2GZTgTDa-gS%Vi!H1w`iZto4E!_Km6df>waXk$tL=ux zzd{%b1FNRtIlVWfx#hL0>f(0u!jX`tbBhbJ3QEi5RdNc-vB~ioon{AcWl@a?K~f}W zH*B<_Jb9ox8+b6E=;>aYUfwq#U|?LDg5EeTCbJiA<`y$#}hEd@4WH{mnPWKFC@rbx`LQ#Vv7cRePxG z`AVB>FItWr$F$68IBZS~d%&3{dwth;j!vE1cs#2Wg`{_?=TwzHa#)voERCLAwOuT# zKJ3WO?jAUJ-KXIKL4%^49U4YEcp6m9KU~##ihf!PXP`W5&hHzQbBV$|A- zRy+9m5vv(+dYN${`=f+eeFIm6+x=KU zQj3ZWmF8!c)WGdirP9V?8Shcs8u`VmYoF#t1fN)pYTJz-F~)CF@h@*d-P0$ba9bj# z8{!^Aa0%p{QB5`M*EnLw)4tKcFY!r#*~^hP(1AzfhoV_^8B0t|3arr#3nm(81Rb6O zOluXd{@R*$dOnj&z5XL5?FY%>(!vwltW#e)+!}K_X7nXqTuIvI&mtO(tz6IZ8N10g zoBP)KKcV(AyDW9;<55$^R@gQH66p!;;K zS&GMkkutV7Sna$y_!yCK656+Jd+YD3p=SINrMWZbBf4W;@Vi%UW+X_Nb&-)NH-`sP zB4Xua?Yp8Mki@q048b2}Fed%+r)($iU2Xo^F5@Ft* z3!3sF=C1XR{xdGg?((GB4#^Ql>wHR<2UX^5Tjkp%gc?9%%KJmCMF$rR>cIn6wbzvn z(b=(1iM~006ePc5uee}ozqm0vDuvIaO+)P`XR%j4fD|*G$yQ6j#ASh1eo?tiKlg%7Er&`24G#bFXJ@P@}Kb?1E zC3g=~#b%gPIUl;ZH%V#jW>arq@82$cTnXDKx~5s4?NZnJ^&;WgM~UNr*^pAa{!F) z>P`&?7ukG_0T?Gq+x<8p4q9N%p(~`7%u6ktW}&-j3#h5PTIM7>BxCfslqJVX>sC%) z=~q0?%6sa?*?(6j6)oZ3?tAV?Q{jhF8yP`c|Ri~WnU?l?TN zP^(zKs5hLQmRw}XpIP3v?eM_-SNNpPvFwSb<*;+|q)`*k)nI!!b#eT5c41YH`R8V7 zql9@d(gW?<=W8KT47h=for6UHEBn2p%hSeWClV^l`{UUBeu}0>$@)|#a@z(p_9ADe zX^=a&=gK_2yw|Mg;3IRW65X9^aqh8-wL|-2tMNWW)3xYaEnVg&cj}N;>1=6O$LrlE z&!*!n$x&M?crg?S^etuJy<=#k^uq(x1dC5xTh`-piETU4({=34!@;-h#%}A9M?Pk| zhbMF!KA-0%GFMyH$92rs-lyg1ij9ZWC)F-3Y1H-@_}-JRvz)m(zh%L`Ga_g$_AtXn zLNQh&=g3eYbKMSbMc6`vSD8s%!WtTKX49RSbF$&-NcTiefIQMC4IInQ;oX8KrNU_x zk)QN1@O9GbboQRO#6CERg7$e25%af_q`BH^A6t8CsiY$N)y(+0hPS%Y>Yj49BnFs8 z@7sOW`z@&jR&$2o?FC3-<|TrAzNx+VpMO^n_^HjrKn2GxwyIbwu~vthr@Ad!YNbtf(-(oTpkI`pF;LV^ z{cNwK4X+%-Wq&Wg-yf3CjxWwlP6@R~3b3@;gpGsce>78+Fu~3LzzS$74 zlM>t;Z*tn&g27KGYNt(Y0!<|&E`J;l#sIf##(}fmoJ7YRH)|8CApm5w6_V_3yl1H%U`Mh9K}Ig72w*>Mb*XLvC7G#697eFeIvYjOG$RWjrol`DV;mU0gu zRW(=Z#v2Z5R-#ge+b-I>)bC-qc!dC^lz=x#?DB|BSl;#dcuko9GG=RD+(XHr$J&Gg zbtst}4OD%2o&11Qc>bNsFOYlpmIC0V4yt@DVDC@J2C>4Y9x6OX~bkMHj@Mh{=_~=RQ5aDN9iR_>4Ut%HxOmrylg5ofkK)@&=lSFC>C2{TkpT zBtg0;0^qb{z&MF6$WvK>rA-%GG=%zzEm5d?*?`% z$j8!a_zl`CflmTixS+4l3gK^nK-$PqB6vxRH(Xp;%<(61(=el?kAbx#CgR`yP0I%F z3#?}h&>$0DR!RJ3F~q7-giRPRj7CM?2zn-1xlEs_=vY}Kg9Y=y_m6!v7)eJ#E+A=_ zum#*$R|9aayMEB0ep~Hj6S!>Bxa4Gg$#8y|YJYGz^(F?@>+12N;12z1vZfBZrtTr; zk^{Geg)HY673@MkjQ=L#c7b5s7A3+H7EsF|r}WJdhWkO|ie`qY9_Q1TF*0iPB}U&D zp`c#ejs_p)Q^|mUtmz)4Up_+1(y4V9}@n-&awN15FbMm*sO1OkbWY0wg=Ep zoi)ySY<~;1Ehc;$XpbN?|4t}~83hkgr{h#5w39wa7y#@i&F#j(0Naf}QVmWY0rFFO zZElOF?||`24s}5>AUt>~CT+$>-e`O~g%=F~H3_o5{hMs!1dhNn{w;(>7;(n9j^5`+ zAO~ACG#TIGFm`Vx;Yh-nPSqGGPI-ufZ9Txm1e5gEM~nx6I0(?crN0>y3KisHJudP) z1(MTpL+7;}yND;j!2z3H2+Kg6z#8Fv@Yh3XiHl|2mG~gIb;oo0CVuk5{AvP5xEclu zd;r!)GU*Da8t=Ql8hShSPkjYmP%oQwN`j!$O&7TbzD0d40{4?XD5MSo=k+2Cj#Xfw zQej9W_yvTV9Vh*;L(KjGj`zNn-Tv2U?+qA40Bk4>>I6Rk0Z(t9u{l@`g6eO&^8`FZ zvw8i5msbc7Y(XI$hnXQfZ4~ZlpGYZ*{m7l?p}QU^B+4?3I1~Ws58QpY{XgcyZ6gOI zkQ*AEP6U>g3P~JD>NL=gt5RH2+c)K!2*nIGhmXTGPC&tSK!{%u&NjBIKYItvB|g%{ z;hTxrn-byz5=IBP6^(Sx!9mGd0-e1B%?|3;A;kT#&r-sDtW7kM3a|EHJXAZ`YX#KW z0F5qKZ%@-^?Zxl@YN7ef@e6cwgx490P%V<4>Qyc5!na`Zs|;Eq7S5`)5Dx4Q7v@Y1 z@cMnY&!%N6c0KSUBq1Ezv}m z4msGfLb&H!c(wAizE@lIFC0StX-6PLh`_h9!J_p~G%0$>+g&|q!pE~{pJIs~PSP?& zPwuI>J;sVe@#JGjSsY}8)_Gt!h>vurj z0K66jOshuICxQblNYBxvkfzX|1reply4%jnXasbAc*G_W^yYApEw3TepGQ(Z_2%oCb535SM zZ6-4s+`4Grh_giYL*>wKojyNca7rXbgD?55r2%;QW5d7gPKBQgMdBt$|mR{@r-o!RHfk1PLi@DaZf{`HVBy2J1I{$4Lr_{{!6 z2wsRe!ZTV*3D+>EABFDU`mr0X3D|xydWN}11~bsJfO~o^kAe`y3(|ot)S?FUKf(&1 zrh&Gsc#&5_m-&-j7kzlh`pNFE_`vHK%7t)&ureDq9a5awDx-n|Ra_M=R*6~-C_(c` zerVi1-nhGyK#O@6-kf;|=m+eF;%QQPO{HTGBBRhY7%!7RPV{(qedJIf^V@=`;V}X* zGUdX%UG2j&Qd1cjnTQ5T7Hj_9QiG`6BaZosnczb^=0H$-wkM!ET4zS^?)jYlRPDnW zx61)B;PR%&;)})&ZC#i4wVNoR}tsO9cGSyz)|!i1UH1y`7patLTf+ArJMU^fg4Nra*a$?Wau4`=oy4Vxp=1 z;r&)o@le?t{_)sdC`I$tfB%I}&$TdTA>jGO!&S&B{{B;B53v^n#Jv&h5aOTT4-syS zUOl|(FD)N*lN;L44AGV@z&ypsP3%Rkuf0J5cwcrGktUyNPwmp>doToxB=?7lhiE^fzQMPwHno?-VWYUJ0(+!h_noU&f% zM_y_R`i*S`4cD6+k${VTB(Hmw?IXKy5QxazAD_7>wl}%*ZXy6sCj4a9^#HdEQb(`= zW+y@;rNY{IMZJuVB(kgT;7;d@!tyOVyh^i$UtP=as)GLQ9O8@b zrja%&Y8+iR9DG=9)#XI@3+=s=vCgUezz3b51S%68MRBdHGH{r|lMfy%KTFswP_H53 zH1g)|`$_V0y@>1jaRW}!1=+WiYinna+2E#7(p!ikq6(daWfoTE%_mQnb5MY4z$jV| zAw-KF%+LT~r=Ip!mj*Z)Ak}(9os8l7WLHoR#f*Y`9&5q2b8VTO1VWrgIK@NRAz7UX zp@{=pA=dbN55*@re;O)tf0Ve9pf4g)u_z?&h7XHF=C59{0Qd2RHSS8uJZf~-Na=D2 z?ltyEp^1#fa<{&%rXU)mYXuNEbn%S!&+`)&#SCE)^)L~4W<&Vw^Q5s2^@^o?)d@4NTeF+5`IQmZn^e)t39;af(S4=&A14ef zhj2A-BdgZ~faFd;L-O4ooFPijL9O+uX-!mbIUdS#G@ZyGjO2k?5u!20!0;tXX_$C! zS9mp{Igc%caFfC^*|3^LIM)&gkHhA-qWk6sPNY2@mIq0ZTN0c*BiRa?^b&4qmYxjv zi8zk`))7)#mNJk2=KvQ1DV}@u4T?ezh+i?Ro-v2<0h@6ur?D>c5rDJ#VlfGZaab;U z+nVF*nC&W_^GccdYK!wKN{aWlw3A(!ExRykW{|;j5ZZK5>x{5ZM!2YnT18`hO5>eR zV|aPvUy;V~>c&vM=@R_LOIaq_GJf8F14qSoH(_DoMbJ1QqcQB|_Qn*)ldoe3#gqGp zW7_7K-#!_g0aNzMC$$v^97>bpwa${4+O9a}WzrTh9? z_H)cnA5PYeuv?3PRS0;5xake?p>k&D(N}a&p=di*J61UU@;G$}VC=z3C)!&rmAVX5<34!r zmDbqb?Da4eEsH?+XBZY$2J`c&3-H`Dq19*u&|Tfc+jt(n@UgBo>4}SEWaBT`X0z8i;H`ALZorsXN7edpv#28%Pcg zRHVvP4(bx;a1h`8IkO)=Bcc#P(}_WVFTs_O21h3LaAJ0Zi-qS@*H=s{^e;>~F3ya}RMv#ea*FTCl*!cWcZ_+D zU9`Ps?9wwlt@fkYemv!}Q zN7FiOM|SsIA0*$uWs_x>W}Td<0iym! z_z#}_SoEuR1P2Jn6z_kNXM6nr09{~ge7XV`%WT7Qk11pw- z1SLZ}+e43ZV?~C=1Z6>O7{~B*MukOWL)ta2ZPJ^apQYk)-m^r3Q79%^77Q?NYHuCS^hoSs;`g6 zPxrT>wY&QpU^-kDkBh@Fs*be{5`hU z2PA66wO5$X-MQKob0nTk$3%%I%q)W=8_}cSTl%j3z!_a|^e|Z`Dwe`#9aY?A0w<8x9LZ~vSu7V`3oraDFG;n>OkXV6@A z{{0*sBY7usS4@Oy{@j6o)Jbb=GH8UZ_xGcPV?hwzi0^nMPA4zgY6l?zXCVXUU3-f? z-=4X_n6}xTy~4PSGLfOMh-+>GOZ#PdeW797`(4jANEt z{-=Ltz}(rJrQMyP>Do~IjmKuXDj?zbqNhHp0^HJBP+|J8qv?7vwsy5HL!ON{yBgCK zVbk&cd~9!}ekg*@x5w+_3rj0Gu2)OJ>+@MjVesPy%NZ9A&ijkew!qZu{(R{k&u{tr zSZ!kf9b=1VJ5+W%o6p&De^plNB=I}Zc~PX)lIW(3X7=p$+3_hmDxxJuz}fyXdf$<% z@&XjJ+qT3sMm5UJdM#dy;UTYz%qr|9NSy z@I>9I{Hh};=^S~So|(Ji+m|i%XlThB!|U^Illb`FF!UDEb9%8jMY(sV5*aiUG>fe1 z>nh&sZn(0#@#2VE)k_jV@db!S?&<*x6D|mPSJg#jRuWN@RmJR|A)7e4S+x+uUL3tq z<45gh!zX8-(zv?F{;&wr^R;aL($o&Gk=g(frGF9@_WKCe#`PhKc~gI_yaLG7MKngi z7>7Uzzaj1m3Jnm_98@iPeGYnqQvbC4AYqjweS?Q{npBIvnfl$vN^jhT0S%}K75L-L zPo?RZSeRK!G|@NDk)I7${U?Cr#^>slgx`=@wbUjA5L*v@K1 zBmEw3VrGnpj%JD4GWdgwWMpKeudgr6%)rRN@ES=U7KIRbAI=07sq_;I(dj4N8>VNX zWasv0FSjSw*3BuYIb+>71#!a3`KxxnPJGapW^9g*dx?A|)ZEBQocR|V%R2%mqlGO0 z?oUk2W0)z#pDXAv*w~m`m{%CpAH0%p*K=RLZ=auGhC(92?`9cazCyn^zd66Uvaw#Z z17H20nN0^*;agqE_aH#}kaz<j@(ALz@%1Yl#g21{N zf8Cm4TY`8k;k$6x7x_m5_Up$X`s%NXX0&zKh>7M@bhN8YJ;8eVhE*{D@ya#9ih{WF z#|~$Zu+VGZ6Xbgk?wc;v;HTGt#+nGkt6+eLdtgxLyHOciNU(sYknkG`#cHb+6Z7Yw z-*=V)pOBrjZ-|^82_7WoK3l0$0 z8xM_CUHkRc{cd@55_>bblY)lcGvk~7@p*P|8TxXw-IJb<*5mW!VBuikb@`8d7s}>x zkFGpfWF$=PI*no?v9Fk4UxCy28He9 zg#Chl6#UISl~#v6*E1&5@mcEmtZFLiv1uvE2tL!9^32F!ApErrkak)&7IyIOO8i`$ zp;#b>fIslJPY>@SQ7D2t-XrI3yYG$;!V8a0(^jXJc2+ia`G1GthOeX*DzQc z+ULFJR;M8pujVehvEq1h02Ceya<-F;4>vWqwujt2zUBf~uo9aW?{5wz%ek=F&ehl3 z|N5r#XBi08PtaO_p4CLbd4z_pV{Sj%FK0_?*|79@#`PKt-3XE#lPH?!{!r_5vjw_K zWL$H2bsjyy^y~y8uX0uEl5P<4^z9uDp({U|- zx#x~%#B}?(z0Azk4%93XCU3p0`+8@3Yd9XxcX#&s(r$jrwIE)G!F#r`M5vrs7KNwl z%%L^tF~SmH6)Te*?oOEmkGpwhpP`<4N1Z9P_f;r7GsiOOVJHvJ3OmWKp>As4=V*&_ z;OtGA8`REZS}mSLv6+hBRBmTAzcYN0hDHHCQ!L3)KA*Gf(`p;XH4C0)mZi5oZD^!1 zaVzBi$bDTWxK53_x<58e-+aBeELzQ_K9N`>*>vvj=ssR-edd#&yWfWHn_=lz4-`gc zvd~%lV&uD3Fp8N;F&R?wC5NiPTHUvAa^g$T1-Lpn-klZ4HmVMIAMmN%FF5fj7lTVF zX+sNnt!rs-yq_sb8|9nr4=(S|6z1ACb*Ivmb$dzobjbM4E!JAo4SIEL7b`QZrcoVe zIeL%j+(6ZRG3O_Go^(Q7Bv4b`+obXZ2`Q z_Gp%x4PQk-U%yjty3(aq8Op1x!H>RIz90=S{G#yj_!ZT-pm`BL^{ zmiXk>!`#Dk`l_Z@rg#tFbatxEo?pK|{l0C!F%)>RcXuc?p_EkkcdFx`P!{^OG}0f zz7jCZKMZk$diaLFLxC9w)1sL0U#C!Riz_%#h*;a%p>XshNKUJcSSl2J{#7H<;-S#8 z@u22Y#dvz8&aaasdZm)6JC)aw^!Mn=)l!|-WT9cQh^m~YLnl%_N646w?Zb1)&~5I@ z(Fn%VCdRiVgen<3s zd@YY4;#@*Z1}GiKK0npcj0>LKo#yBmA=xq=0rrx!-YF-va?aikdsDH^DSx6^aMeu< zmPBs)i)d9c=|jADg+|)~tAaiX$IZ^onL<{tqFvyZ9wZ{sq0jwx`G*zeucl8G`ULOk zCWpnk*eUnlV67m8zLQ6%;kK#4Z4wGQVkd_8C(1qw%~L`+cq5zNDqV43{Z8zQEKS#5 z{R?tO&$*xlVt+#&B~5q&VA1$^_s8fSht=Ej@`2-m%1} zVbP(3$Zg&J_6c+^8TklYjC>blfy#~8r1}?YQD3711ulL9cUUj;hW81(s-e3EY1o~@ zhuLOo@ztC?3fb+$e2lOPk-_)Jx9qvlBe;I;Ektv|I}yG(QxMXWp?~OMgHPkt)Fudw zLslk?)TgNhZU-vToBqw>X;j)@4TovV-QsvVHX>6BQt}pGYD>w@@=!`!srX@1R)^0v z7H)NT)9cpZduWzRu>QS`_e51Pm~X>$cpy6oYF|MJ8vxrYiAUU{<9+|I9jjkhl*>}P zn(DKyi%!%K_bzBR7y(otITm02o3N$IDJ++y#Oy-Wa8s4j7nUq>>DF&)1OD$MQp$T) z-0z-!P}~_)<14(3`1ZyHA?}ShoWpxjABW=9$A2c`8UrK%eMGnc}A)12|!Z+rl7r^c8c#dl@NEhLBU;53P_C}ia2h-rvg3^oOar-hR z_!Ky114@-BQ0-fcygC_H>tFN7$Bk~R+Ws_m(M45sz~`;Ky+Hhd;7kj`T`YGCTdHNJ zj}vFxJ8>^0z!?wskq2!`G$r&_y~# zcX3Dos@LtyIgGy>RWHd0ARi~CP`gtD>mfua%ZMzuCA67UwZn(Q<8Add`ikL+Eu{C< z8l_^W3aEmcATNlN%D^NTe5GgzEopX~7B|VFIn5 zWev9bM4K520drYr9Z`$Algau~#jx0{JnwiR{^^2C_}BKl7-G33OFC4I>~-uk%taHn zVZ6-9S|WBt){U$KEadTy1r;dZB=Aj02~&B1VCU4x26Hp<7`w^ zsTf@D8Or{ODnPawGgTp}_EEJ0@#v!|4vPG*MH@EMn%wv~U9l@wO@7Bs+Ind@5V{ei z+Ers^#X;tVHYD449j*&oXUGHPP~U;Ny6~}S+qxF*cO}3!o@fVy(>CQ3y@RwlJ@Qz& z0@*h)G-`2>e*0v9yiHLP%e5E2+)V>$&(gJz{y`iC$N8c6g_*P!vcsnU5`lj4B zirH*@pjV`TQ1Wp(fV$v4wIV1QFgYAI+~sa;XX~>m0;v>f1;+h+7>sBe%}ra;+_X#%^Zr~ZB(w@eGx;xe%RgCO2S}@<|UPH zNKg-vpb8fP#QNo$$^FrpN(_4uTdHas`bf50uD=X4T=R5h_hv)+wJ*mBrd2PaLO~(fo7*@dqW&;MSviCUPJok+ zXixBY8`hQq8pb@T!H$1@1lgMC@T*27yUJh*3YZo7m`=D^nY65x#7hn6dJ*{nY)w~( zc+)3-t`ZIdQ?d-CK_|Y!sA4v$N#F=lZpBHh4yYkILIDW`_RxGZ81*;7yVxef$EV>^ z{i4Sm>@OAr?pTozU<}lHKn2F8ZM_*G3bScIcRcGe(9^jLMg&@gyIuqw z&hEYjyMiMer2GYejm+2y(1g%XggEG9hBws{*FSay8+Tgi*o0OTBB%+# zEI>Mfp;^V*QRs)*jZoh*l+14=@lV{aAwg{Q$ROjT4u8UbIha`!Dn!y9hlJuGOXR7| z>1VtOfE1+WI_2{TNch=f%%*<1K@jIjn;;>1#URjFW1(}?gJ_)ljRuQHBi)Ti3^lun z2Ae7V6*nQMp&LyE5)6jsVM9?;trL5_T|ojcV?4iZ9tj!!35go28;S>FcBL*%0fJRR z?=CUP&vF}5SEB{a+tvTw6uBm-hKTnEFE-wp{4Wz#e5z^Cb?mEm`Jv=?Fqc3W0=F9u zic*1ocDvaT(k4B|vkX+bHryr%v6K3~F^rw_tQ0h_+_sB}U7q+mx~(m6o3x)t8}#13 zvpp7`TP0{hxY`AJ@xLl0?_mGCpt{B?@?ivWF>b^_F(1s>jnx$IoFIOXG)*n)kl2wp z0`Hx;5$Yj~3;+%0C#Md!5b8s*F|N_BMff!&}85i z5fhp2&>XGuYpF6SE?m=0;9uIIsf)1T#d!JtFuF0v!MF78PfG_kXtw+yMvU^AA$=eQ zHeS{Y@b+O)6w(@QLc+Ye^ldQw#B`N_jr1})Grcyv*; zNk}y5&D%&0S|ag&q=6mf6zn3>XO5yA-EsaHehqhVSAVr7D9UJR9ey-d(gEX3z1G{? zBkCb8?#Eq_OWrprTsax_?l_lD)(@hA9>l4VNcV@toeY6vxv4S)Kx7){do0!jps&fx zh#TDn&2KnSo*tJoE;V~gr4$LW2StKD%#IBSPDzLV{x&&-2ocxMJuFmokHkkIuEfJm zmsG=!soaKuWh{{_M=m*1Ok7L$gCx>(60wv4Yw#^?u+?m52$xv7EBjj-9*druFS z0Iu-}k@P-DwG6(*?2-hTwRLa6qdnx>M-fZ@s z^0Yap1;i={kIoo!eNxzRFdVa3Vdy6G*v;=hQ!Ui%P-_G82Vv&!^UoU_<-Lwh2{DTI zA-CPI_7xUP!Hjq+5~&r~7#%NdW|R>Uktp{rf1kpf=p4I!FzuJuX24UyCQ`j8Qe zaKE-ec``tT0(iK0u~D>}gSuV_Emb6t?&kd}GJi;jnWhoyJ1z%f-CLl(2lP|ZXpI}6 z8bYfX1W_}r?l?YxEMQ0o7<^DXi?af>Y5+Ho<+818o8U3`toaCe?gpewTPv;sKnJ$q zdL)iV4!%9nwtC?{Tf?kfNa;5f`D9ezjjlKK?|M&VxjakX@M`0kpE~{v2(r_Q5SMqf6dPD- zyan?zk-7xsJRypG7#RF{Vn+K+a}*3ymM^zagT+%1y-}Z%zK-KF4hq&e*^VV9xxPD4 z=iQ4i18NV2EZHA!ep^EsstkRrGxd66#Bw(xlFasx1D?3w;rm3@l=@I81fWU*hRua! z-HIL|uDpDvz=d@im328EkNCXqSW4B_BE#G?tkS)(RtQ5Y4ij{*G$_f|#1|DG}D0k(GeSdFRwLyuWgGX`ahXak|+5Ld=OEL@5 zJr*%9Du^cw*O8ayLlZo2pNMLpo)`$*Y0TRkiB&bQWr|=5ijPkuC`;Qa$6`%QnR)6i7~4T|jb8;Jrv)o?ryVtp{+U%-| z&qTg>Cs6z->{;03#XP7{^js-!Elqt8>@EH2Z1ENL$(S9GGkX|Cd!qK<3 zWoSHW26{1QYxu*%4Wyr>!f4 zl45y;UMGimIe+dVqwgZ`mQ&N8 z8Ej8_qjnSP^z-wEYKAe$g5P+qExlD&>y2R+D0wdkVYnxULlEO@fb020&+PkmiE((w ztE~15!<~NGGpz@6nsHbJvbpSPe1#1TwM@_uu0?ZwKP~J%eD1ej+>6CIaUK@l)RPt= zleiHa+u-0xb{h)NltgKpAn;TRNrVRf4Hlh5@do|vZ=J88umN8t1FwIp1%Pm$e;Ot) zkF^VoS63xCv2MEM%TAzvahG`PyIWjB;R)#TX;m=+)yRszArD^xlA7_ivi;VDgT|2a zOV8H$ZMt8C^Xq@oA8FR$GSQ2BbSRT)#(rUZ&J31V=;6t@!K0W%dkN6o^L;)`(PAJ? z29`ss@J9J{f)+s?s>JFKVt=o++fQ+5fXIX;kgD>g|1}BINzofxctieO8n;fZH&ed5 zgsTR?N8Rhk{gHylPwjnQG%a1K3W6!u$7nd1H={ME&*O>>(`!Uj0O(#r6-99@&m{6a zzJ7TQ)n4)^tGQqr%+fVHYHYoQ*`7Mxe09QqC&WUYhXSYslj_@4!WiKt8{U5FktwR!|h7B zDdP*4rdtvHE5du$&7}Fy$nIe}s^g3cnWv!_#nytF2E?1YLKXwSm91ZEh$FKG@Ly_|=lrlRtWTbu!9W z;k#I7ta`mts^A#uJ&`^wVm783z-KnLEGZ^4rOMVI8qJwYLm26ho#*P$5jJluQ#pW_ zt5t{ccaCZdb8V1FJ9swL7&=kGUwuE89yWV+sbk73q1@Ye*eTsbKZ!;d_yu}?(&o=| zBnGrq=6SB;Wv<`M+IKdE+sMpHe)G%0XolrN(=AjpqeTxXku-1NU+bPCvhu@lr1PTy zd<}jfGq=&i`NguVH7!s3IZ)5%2#1@DBfr^O+1b%Q%r-{59=HRLXTCZ?9!edT`>bv= z`g;cr9M9CEWAzMnmm=wzHhGm??1enT3y}|*3xpw4H@M;6aC5!mR+nct#&`1Ybn5+9 z@4~RL>vWNSc;i0YgS=R`+$6M+k~Oo$^1!GP{}@&KDMo1v9WyFSqz=r%G_SmqwV& zyfIY1B*Jb9~WhGuE2XI<>SP4sn#1UJI%Tef&@^DkL5hEeMj zjM8-@*NMkq#SO~^unJJhDtuCgK)A{J6zV-jbHK@=^Q_*9!h}>5N zA-tyxH$EE$w8RI+V<*zG>JrQ7&gXGcjrMlpHvL>K(V)U$)SbS|oWxUQLX3cSe8EG6 z(f?%`%Jn;+FiX%^cuWk_?I8(wLJp==#pg(T`J`Td(Gum5V*%kW%2DgccqT4CKyFCC z(s{>Huk9b8^eh?2rMrewGd_&7@F@xPHLd=UK+(6emhbPz$=k6emvk3M^%@Khnv_A7Ut6)iR`ihPcYh5a?&qyX3>}TCu0}~2GHoA^5snZ zKEq5s$=y7!nv$JK+?2OC43xP*Bxk(1iERr0rPkm(Mu|AOn3|6H%(^~_FYA7!GwIs@ zN?$^l?lgwYqMjdCxzPrjTs9%WgbuOLR6MyS{^3e|y>?I#$*fY#4?423 zCvZaY(gSi6K{2?Hk+2LiDnx^{3x2%?Z)HKDM!3O4I;+T|e?71i+~&xggKzj~t=xGq z(Lole0$Aqa-Rnd1W2WhgX};gCa(3O(Kw|Vv$|4 z+)x1Y1PTVE2&7z`BGz%fY$0X+ zjlJ-yeIxuwPXc2pz3@sjs)j;n@ptf1SO*c@F$^vj2>m<11-5e+wa@FqZFCU_?GM&twTq6v5;0vOzH+vZhjP|60NPM(?^(Y z1QV4oS1bb8?SVI{3b?OI0u1isNO$_i@80$i%RAWd7n;|Fd0RL~KJ-bj%M|HxJABcq z7=~-dMI5-&bw*=IvuiM+5G=i=BDO?kLGQ>RWKVy~USZX=#Nj0+uF#(Q1y(@*(KiUk z?Uox^b*k`Aydp;j(G5@4&&0l^W4E&&X(w6zOFvGts)EG-@gRZts-doDf-Su;G~5_i2uM=-xu#$3?5C3ot>9tc#`P2!hfag zo>CdohjMCVSOl)yx1MbMW6Vk{VFizUe&)1z*P7pL=`B{r(~2p4uQ}blb>LmYefAt$Cpx>IYnE0`(l;!{rt>oTWsYC7jx0PQ0xv?^~ zq--z}on+NMdOYu4Cas%V(Khi5Q61QR%Hb+|P+we6Cta|*(a$iCWxU+k+=n*F?4Mq5Q}y=aJ8mFhqV zv^!J3a6&kP+LVba3mFc3IuEd-a1m=4EtLUb9-Ml%CQK=P3F7_6p#Stw^88Ut46H{@ za&!>@o(-E^jNqQaPO_Qu)FWMneq~a`>uCxWIg@df0m^=~YE?0GE6A>b!SozJACm>&r~$?G3< zuH{0bw?`h@ge|><8$#QednBtn1tj%|ap1dX1!;@mNt0IrWo$N}TW;Xa7lJK zZNzWm*{d|C#5Mh@9hlaBNKidD%*@MtTZZ{v%Jyx96_%4ilhr=IdY1^>^fT6^Gu|*V zhOlLEXTpI5C#~|Rp*w?rf93te$w~Q-@`b?Qy}^cU=?qBE96l7nI}DP(C)(U8M_FAM zYT}z4f{n)=zi8dH)nHK!Iwa%1D*eS5xcPQK&ZwI( z+4Cn^D**Qk0*>eUVd5s}8{s2E19`)dv$s6wQ`)fQv4G9i5+S*ipCYH>mtJF1oQrL7 zey-zTwZOTkXW1C^xh<=~9x?+eS=4onH_*5>r*?Ej`j)!;8iS?}9mAgywht2Nx+c6n^Hq?X{#hdm=%_u z8681SN{NflxwjS~Su_Ds4g4`!cn1{ua98}Sc{MM)2W!!{Td7ga5cSggW88^84OgQI zw4alY{JEWSw_6rFJ?=IqBpPh#b-ZYAxvOEFxwlP%=_oCwc}H%`D|WP^JlAx;jbsuS zgFV3|!oP5Dd4C;$__T9dWM&MV=)IF%8YhoFd3Z9bpD2e!s0#{i{d9@M%$Mg)1LIz% zM>NbZ<+8vy%dwx95X6Birz7r+K;b}Vpzq}8cLiZ7qb17_EJEWF7hM!$7rwC%V4?~14^8BY8 zv`=8OPk=%)^SEDuPZ=n61K)8i?DkOXuGkagjWpbG(VJwlPBKVDLmRpfne=bUnB9q; zG1}iuAj+gH7r!_E9lOj*K)#Yyi09jOe{&vk^F3}boABGlRp7lUA1aco_0`F54KkO~ z!hYJDIQ8@5cn#4xP#L$3BSps>yHRYd1}U|no6hv5c&KuBCl+4cVl)omBi%Iq12NC2 zH>6ohFi770c+{(}d`K?NMj+gD z)*Om2l6aG?71#@f<|5{QvUOCn{>Lq3=|P*j=7@dfM`8;7hMh2)I5hyNNI&h$+V7l< zHi$X>P;fG%sGtNCQ+erZHL|I{`$d3QjYpbDEK}X;tNV{ryU0%?oS&8`pa_wl9`2AX(;N+v4K~2 zfRO~O1Ni3DzT{2a>CXnc&kQ^NX#$T)1+=3f$mw}j=Rk31@T7+tP#g8wG*3wQOQ$nj zY(N2mG62%OJxN-PGb71C6!JomX@DIbj1k9;Fm2CRdonj4QHPtQ1t$xR^Uls>7JWx9 zEw@$X+;I+4_;++tAXfy0SnkWK?BnZVhb!Fk!Y=IYZ^LVfdJ=bBf_rlm)B`fO!d77h z42fx+w@d$8Ra&KOaW9YIE)wG|Pwl`z<>Mnws9;_VbSOJ&(+y<(u;zL6^Eh#tXImH3 zn^2Bq@?*h3xP$6}D(7Mj6|edsxt0^EyH4&7QmM{yl$9|^>dc-DdKMH&LgoML1SR8h z3$8Q2O?ETCJMTUA0SUasyF4*HOwGUnWK9@He_}qeV^d$zjJGS=l^|E+L2|@PYyf(m zV0|XYn`U6FF`Z`mA!~93>z3)@@^ofF+T!4b4*(Yq-fo)b{A< z4a$ss&NS92JbwY3i-LoKn0btFhXt~(34xo*24FsZ4Y4!{0*yt;bQw$9SMG2N`(`qd@&3bstyFBJ6o~weB>@p)eL10yxe@N*aMbU`-K>SSmj^J- zw1q^|#Hf$)t^jNAl}wihOKL-%Y2UB)P|(uBB{#;y(fVx$?Pu2DrXti{t2l2i>vFed zd@RRZOG?LsDB_yyYWb5<@~^A8vA=a}nU5zdCY)4C_D2Xjjo_#?^Z@*MT^l)ssV*%Z zWO7)r>@@W>>lR$&h<}xEBZh+=QCVETb3qz#XvKrtPYX^>m|+B67-EejD#jgtjE?i1 zJufb}!iQOl_7Fkt9bkpw4M>1qVm?17fH%MBmvrE!HgXZTIDn0e`DGRKv(pl0k0iih znJaRi|NfrZc8`B%!)(5zLer zD8aD_Rdy}ws2D3&p(*NME6f?AzBbSDhLykz(oyY|F+XouPPSYxM(Z9W^^DcJ&k^cB z^t6=(U*Xv`M)bjz+aW&kQZr3w5R612Yhl(0HQ*NUb6(`qPl@S+RMRw7SMNj$+BOfV zVg=E$c%L;gkWDR@?$XwfD0_Sp9#NtpmmW_zE+0&plG~gTMC}#3IuepY;!McF_;o-d zFU;5Um%_me0*4q7eWr_XM-BoJF`!+cv1xLl?t({o-5#w= z-EJ#t1Ctn1xwexvY%iY`m{ag~dBFxfCmDK4iLY*}IH#TX*ifZgQ)0l{nPlmqyJfSo)q5_aq*7 zk)~tU*Kz5yc!3DI2tzbkweDgyl(jc+ZJ4B(SMI@zsUR(3I?TR;? zXO0L(CnQ{&4G$QV1js+ca@@lxV#YBdAcY!ifkj2$otWI*8wD|Y6rNBd_y2H9Xoo`4 z#(jcyX(hox-FzP;y2%<^iT5dnjwAu6Af)T!z8yE4I6Rc z3FbbDHc~VK7nwb?r3QyTgIDO^=&jIPW8~4TryW4nw~&oK@HW>_4VuSR;dk#v-BkGrW~90EHPe$TC=N4U9^kn|BA7;_WU zQ%kAUP0v&nISn_0rMi*E5IPwR+~msX8uUHwimb>B4kTjzddxdQOfz%ZFF0VAj#O_! zauKtJm(?yUSPch7#o`c(8{4pR`iJaP+`KuOZ7YiccRt6n_}T_iQVL%(E-J0qzzGHO zKxUoSjytPG28A4pxcu&vsiM-TS_#$Ls^oK;OZnMg&h{ZUXP0i0Z38Jp=wdg?@U=I% z;Jb4mOs|9uI_(;^#3l)uG;l25s#m1Xt-jMha;)CG1wX z#u>4xm>91=(Bk7pq=UKQbwp7MWfQY)eeb`d56>(FKLKf}Kql%S6M- z%8uj(mgltT4~lB2HdbuK{-v`U3*%Hi;3Po|R@;XtE5B<+Y9^uHvVOeR6D%?B3`M-&;XdCpFf>V8@y z-`D1a8h-M}e`^EVMO`{86z;P7ABA z=cD0~U4U7H+d?xN(>Nzjj5xr^-XW6kG5rI!0ZW!-!06-6L`{lwp-@eM(m4R$119*M zxPXPM=6;ud?q|R+rrMWmp6vnLXVD7}XA-}eV^ihBpo~Go?vP`c^PaBCOnS&g(wP*8 zBMWc66Jq^_nx#Ro`o|Ax;H=YtlNlfOLtS z{hx8hsX*)?CwW5DeBh{*F)NF!C_!oe1aMouee72 zH5)~*g+&0TThC??m(K}@ub~I5$A0IWJ)sH)!EK0;b+6ygkZdgc%#+6;fmdN+W5$uH z=tSdd+C+e1d5w_VD|<6CyBFVy@-45~#@=BpvqklN{xOZ&H(JdEvj;sKgPcda%sgRz zEzmQ}#pC_m9D8l@PMm>h5nCWJT2tR*nPq&7g`*R($?Hp4+0;TzDxvK2`c|T8%#-CR zZD)pQU4R`SELY!(LL58iDO||}?BPxCz&Iq{;XmYIB_7z3?y@&~x~Z<7KtyfG64qFn z9uW*@HmoaOe_pTi^aycWiVTL(Q(H^{j@xFAQv>2q+4x7o>n*2#MzdoRNHP!Ak&+%O z%B0Be!$vP`HA-o(&YnDDOdKu$tk?PP2BHD3=fRY zX!k>_dMZY;ca-9)^++Sc#&xx`%4yLmk6-Wn7A^hqs`mIdQUk|OnAzzItn)dgHFSz_ zI~E}y6tb!DLSswTB!5Y)SaT<#%p{n3LJ2>#|4MgRmF^e`St&vG($!Xl7!YY@r%@(m zhAN%|R;w$r=~1dj#p*gomiB<1yb#4tgi(cW2m>^a^iCY4(Rtd(js_ZJ(Kp$?r+6uc zRSoGUB8DQCs35|?&>Q8wCY4T5NVWBL#(HBeTb`@eqXw_AiVYs4LN^Kx(AJo}d>wP1 zE1M-*SCk@p5WRbkmHWzb;V6N6v3Ika6$XRh;3m>tMK<7aOfJQ?3s0;JEPH?jv5hsF zbxf0>&lXfl4TG??@#n*s(K@n+i5GrPqOr}4ObkmJHNq22(m;&rGS^s(;#@nSrk8({ zg@vZNDTqDZlH)y4oO{PLK^vW1IyB6X(O0V23d}O%&%}DQ5zZSC(LhragiYS&05!Mc zR^kbkw`fOAw|nX_+K&V8n`U$4jN4O_ZkS@gI{GYO<3OEJ6yGoX8;@Z1o6xtpDN71X z(~91;i#PB!1iXNFC_G|WcHZ5>R@2bsyxcM9l=D#RtG zmqXKlNFs5}*%<*ep0)$EFF#Mw{5|JG4IkXVY7QgI$W13F-p4KNT!EO`zfV_|-wQkn zS9S(cVgV~i?~!!9Ux4@1h;l7`lj`Usym!)YkPv6gef1O<_K|g3*k+L0d(cud%M!@t zD{$pITr}?#P=M3}p_G##)jbqxP{AaCBc3BKlyhw>p*$bMn9nJeC~IW%3b$DxFY3TCU7FGNe5>-F z4_Y(@dWSsk;{4oA!S=iHQJ^|9@;kS-t@{dHCJ{nv+}EnE4u125W;)c)7aD$D$ZM6J z*jB0{TMa;(VqNcG@KzCnSMnnG1NQ8)j!r(x5D#X16HgZaP8o2pMSm`U*A^eWPz5io zVcH#6r8cZGssOn~->sF=qmFSTcZv=^#5ee|?4ZsauG!KWW|j+mtpWkxXmm%bh(t;E zDs>`swDjeVuUgU z1y2H3-RkOsoaTkhhoPw&TPr|P8G>FmRN7NBzg;$Ib%Vdbu2yz0*(K?I@ z@d}#xoDnoNjp$nnl-DFTNb?6c?`O?zCuD|bH;o%)A-ngGw0 zWS-1tO&tYc3XwnFJoNHK*4My-VExp_+Di<(S|x@c&9>me$N``({GT@A%93rfIDb%q zq;VOYh`R*WDU;PJiWp2j=^bwaUz;)&dD>;#FUM zM5;wMsu)9o>#hd9Sbx~ujj%Nwm1F{icAPY}q8t1;vVZWlYFd6TH*A#g<$ zH#Zg!&p8?=P4#DJi(cCBF5p%RdWNAYX5ox>8BtOqiui$qSCqiL@uS^iv>yf=k7tDY zY5b})9go@#(#zklxPK)A{VXa#ib$Kf=n>ASMIY7u+%5GF>iV1TDpFv<$WV4}K`e42 zKi5jYf+$cFr5V{pI-CNmg|)FI6}j6B4!Y)IlgJN(=&5xnQT3uCh=nnPzv-IV98e{U zHX`-<6P-`ujIt8=#oGp1n^rUw{#nySTbQ_5>tTT^qUzHpYAX1H0vACgspbnT{9}{M z#uWqmMKChGYMfG$fYe6x#%@mz7l(!K{3YNAL7hUE2r5s0e3p4`7~ywa^3O;Z-l$$m zz}1S|BP?s~B`aPt6Z|>rkw}r1C>|#|j=Mi?w}bj`q_jKmhl=I(%bpwKiNMsWv-|Dk zoTF9P2phVkfGpS#$CgK)p)vVJWps@_%YaAs{Q-sR)_AfcKl&x_;5k`RM1hPP(rdEk zl<$SoQ_sNDb@x%;jZ?_;*we0=aXh<5f8|NA((bhSS*rTpGO!+cPp|XDPieMb;hFsA zommWQGPE8MZZC!C$g>ys1*a__%PcqmE57LemN_nUpCfxfqCe4TOCr zk%a4Y1QxO;C=JHdKTnfTW4pp^^n1CiPA70`;8SQWrGIlnE{Ai+gMs$m9NfmZFyCSW_okQ2r5j>abT-KvnSE z6*zeP_VQ6t+~!4PV_A6R%;TtPNS(a$c(+CPB1rPO!nM+?m~K6&*!FrfB#u|91OveV zI!tRk8C_;p&g$t`E{;Z*9Fvo6j%jQvGy-kY8N}SJ2po{nhu3TlRP zVtQRCr{9Xp;~h?kOX%bmY#UtDg_uF(yirOW5#|9as!yu#B@k01X-Xw^#+i|hIS`J z#9o+$;$kuDt3<#anj})HEw0u%qiOn}!Bi4tT6GXR!MzCBW>7`65`vg08iR`Fv26+~ ztPO%X*~{h>3-RrEdwN9YJQU&B`#&5J^_!7fHLOJgbT|9o_@rLpfjHpt>RaPOfV)y9 zf2nR){&knF@h#&1yIw9Lmh&EYzd^*&;m-%9=0d4ds@Khp)>v!*!b)M!*2K%dTT!#p zEP8N|Y0CzF=uM=pd$b_BZr`wQ&1uxGA-@`0fRBm3{p#YvjC^=w-JrW5V5tuPG z0i+q7(5c;C0D2b&srUIHa!9y{Y!tNmDC@oCe^7y>i(ToLB27_M#Px)^6g^ zu2Os((}+b(B%9%TH6cWGQ40f_SU zSVF)y7=JemNLxh>-oZg3o?p`=cyj%REB7Kbg=e%M6;^pWR!V}SLi~FpZN9o|{FwPK zy?Qomz`91+p6iY|#p&s~;UZKB%jNX!ke@c_4f~N-^`7~g=4ix}1^uf2ZsF>Tnm697 zw{O$Qz_a2cIda&>{|anL44ZW5vGMQl3(7<9+IMQo4i~S5XZXdwqBe@QeRkz_B`KHe zC6m#B@19=y275enbXyFV9D<~7P2{27-u(Wjo7x9j0D&&sCf#LMXdwlD*ZKmWN?HZi zb=MyG;r|nVX};}OJRQAvD*N~04z#JFp~r_H``<(RX(R;l!3WBQYZdxw8za6lutktk zw%6z~Gk9H*L89xmZb_64y{Kw#5Scj%X3jkPc}3bfX+_P1Hb9z>M&%ixySnrg zslxCCE;8DwpQQY-T3H?>SuLvCO21X-frVKq-8a|U>59j6Q0ofe=H2f%K8whPRE+Mx zq3G6wQKOpHZML?KtpTVYim;_}NGO3c zbRsc!j#}&QoN*{)8SDs@#0_)?cBE8o4?%J>9Dy3B@Ui+LVY`*qpp>r6u~~F}9b}w~ ziJ-`1%1v&GBv?- zch4STEE~oue4K2OHR+|a@m^WHhFR7qNYl3V-xf0eC&?|tE`F}w=={? zstU)^#}^@gCu^+bYrZHiUe-&KlT=*~M5|-RbP**1O10y&*?r-?9$YT}aj3c1XQ-p3 z8YlYJyWBb#yEQ*_zTe`mIYEc1i;ae4io#lQTXlOiykHq=0^!kJYs>}aFl zJMFjEF_vo+#ZBUWd%_FG$*pX9a<2^=HCQN1jk1=t#!o*xw=Q>S;#~FjkNw)gAl)g?Fxaz5Mz?ph*`&|e0g@_G z84B;QzlFYI=Imrzag!<*A2A+0u57CvL1m92yl=&^DpU&+(X!IMzj9f4pIgF!vJ&Rj z@XBaEBxF!1Kv~9cX%ag4x<|dMnLG2cSFY0r+X*v@W#VV}#99(+0GR8}Y0UK)2Ss}G^8sp*J;NQ z%?+!4a6cUBj468?2jbs$h(4pxPG^b<-g{nGi8B7dZ7L?rn?qBJsrh%$T>6xbRU0OR zlRkIaaTkfy_XHwHqa=yOOUJcDqu2c|9JG|!Be zHy@@fY+|0JOvB#ChBokL8_F^G5*Lwbtc?~JepI~PCkYL6q2Xh-2c3{u+@IAAywokob{tZPa z<8*_JXSYP5>e9!fZ-tFY-zPJ-x{g|P9&vr=A7vpK+GE$sH4bcf^x(ndo-VCT=q zWs_4WxbOb{X-c3*k;$z8$>MTS>BDNpiCI6xRCa$Z7r7&joCsH^(pxl zqDuY8JnCysT*672z)PQo5tSw44p{*@cXSkbz}LWN5j@d@(%*O??cl?EPkV2A{Iw?M z>=gOvz9X>Ob?lf~NSHHcby53Me1h?J#Y!@9uKC5;eK5Sm9tsEH_A`*=LvqYY24QPp zPLx>Z3uv*XBNH4Q)N?uMR4zn7O1E}hpp zCgYpMg_P_gWqj|Lr18{ux6b;zY<$l7Qhm$`yKp${Ldcc`wnkL7Wz+BHN@uCHWh4Gt z^5^N)Tf9fxe5cM(llObLvENp;Hug$l^3mOA^`6?jHzz0zkTduEQxP|CzYfq{RnO8U1ehdgl2U4ojKiGX z!mCX^M{_T+oicyqBf?>+_?_OPjr}h>`o&o6nQqaQe)+L`68?AU{Jv_SLym5=S%(B1 zG2w=dY`N|U7PcWVrH8BU_4JgO+=%aGg{y%1%jPoj^cx~yljq&Xw03F(X5xL**1l&9 z3ndh?khJhy|1*y}Ui%mX?<@}rP9wK?bJQ%C2n@riuF3WSb4gLtFq4;8ONKeSHg@-0 zt^F^R`(g}|gB3K)%|t+qWkjDP`mTHQ3GWO`W_js1E<*S&pWTF23)jb~Z+&5Bs7<Wet!?;Ic_atrqnbB-Bb(o(zdE>QO@Wqoy$qB97ab znYbshil7uAj@}Hhzo-@xUd`2fP5GNBUn>rqu zAng?5Hzx$^Q>Fife%_gU<~Mud|7Q{i9=dfn2nZ=7i;v4EwIY)&jTI$Lq{=@+ApM?p z?#Ri!%T!;HJL(`I<-<-tK$p6&DtdcT_!LU1!$zEmL;=V}_`0-qthAm?I;b z#tz#aWe|LU5C@AVT&|wnw8gK1zdlx)&|$dI$G`N?Wu}E@p`~q}wIsf?Vj8z&dgQd; zRy-@}87^acyGY~0q<&$rhAX8Snz2*O8eLsf`aJKd&5l=L)OBV^SSmYNa$cBhLhv!A z)0UPk%kil-u61UJ@1hScgJVvk&i!3whOsi^gs!0f`<7$XnaMVby;VVNtrMA!B@JFb zCFlEuULTgh+eHw^Q9b1S!RMueeG$=nQ9gM2!zXdfr!t7q@-EWeoN`fM(8Dxsn!LeT zrB^nmQmCfKx_%%9-9odZw@GT(N)uX+q1jj{pNvrasAg6t4E@#aq_`?&t9cDjM4BW z_7;`>s_v^I5qo#`YngI~_Us$s(l(t8m-;G)>ff%-rEQX>+S?Ud4O&wLmvM$(a{y*s z(6}ByPq#Ktr{vEMpjWpDap5*`#pKZzpjg(3(;2(jD(cxSpjlpM2H(<8IYca?18iY4 zTcivazf2)Sh1*{tT{x9pn&4dw3SJw7-ZjeaPVjGA1#cCD-nT06{Fry%GV1JzJyu}{ zjsXYReWP~jYYvd-)XX2ZmFb8Ne|3&~8TV?71;-#EJs}s|fsx?ek|6o75p#cnW?2!{ z#2~F|-f$AWwh4{6U?K$p{C`3IClnLmk8ua6A^>oy^xvVFasS_tM<=8I4aG$Ke?lJr z@3Q|2dHh#qZS16Pr0=9pZ{cWbqpSuCQ2ST^pEW8~sK+J4<$kL)D9SI;NGkjXiW$`A z_n(kQLZH(q(Yl1EC8IKAQ;cGb{}uB%c#mhz`j3Sv`hV~+|L;EjE9UWEA5KR9orn3K z2E3I`|0G^E{Fnim8x&&zoyW~}KFJU5$RkFR`p*@kAjTng6g=&SBo{0QhsMnb{Ss0T z^_MRWL6@Sx>D)f8>YU-bjlIoGe|0aZN>{I~r}`s{hb}%S+z1DNFxo4o;9T(ju<1YdRU7^}k!W~D0)zEpb;$+DKj`qa$0RjUMlW97wBKV4XLqv_EG8h0wg z4<*4!)VS&S(IJ467IzG|pfi3))!VG291_n5Y&PmWP^Is;`2TvuX@lq0(*z;Z=z*Ge zdVOl_`=>Y2I=6Co{T+$y0p3RxEqyAwDhJaF_fiSblYNMxgMrQl=_75Z3)j{(eR&CZ zfdf=v80mtnEzvtfEUYC>Ifet3311K&HjQ6%;rjqyOwz?+L2Chs%Dr#zKtq?t{NsBM zp>I1kzjVy|YRrWMy^=KpG<>bXRRMBW5b|iDIDJ3h*E`sdb>^l1tGG5Gm0Jl*aDE-& zxK#^u3=`F}<4UXMlty`>jH1jPtTdEix7T2Zw&sY--lD*C4cK*-+QU4jL zgxHHx!U*2xcN_8wKbhwWCuDWf$$;YHH9zpQ4gF-v!edQ0zf2tEW3?J#8Xk zs~{(mWPdO6Z~(B?#9nw{6=t&$eybwr$({Y}>YN+qP}9&+VS> zxzpVi^ zM!*PmzW0%MHNdB3G-#tXNUu6$5pgl3u{y~!GTuLy9zxZ z?MIgWbwcTR+X!rPsdgKAxYkSi1Dxo=P2MctS=!kMcnpO2zPB>_>`g5nx*L{`8G3FwLniGS4s%c#pIAt?Ee2=zTc8H80- z@e1;*DgYDEKRzMwMVx|&S!=Kbe`8&WETA_%+}kYu`*(JB9Csoe>1*CM_oW3dJDJhK01i{RuOOLa%}&E6Ss}^hJ4&E@$t7! z^RcVRv5od#1aJBy#{R?EjbWdNsz3Bs3MW`MC!5ml}YwkZitgK6DFHge#tVyt_ z1eO<2k{6L5@q z9>gLps2(B2ISKhWi48qDJwNz0<&B--Q|n;&VNoCGYS+DvtT0;$4Djf!nRIlqexk!m z^Ye?_gV(JZV|$?uSzotsKiR|su*V_(LfhI|!9SJ(U>AJCf8Vd0a)h!1YZ7o48nv>b)MZ?Zy( zr`BJ_ifM2Hf;~7rA@mp+l3bcZI0vko`+lLh27-P?Kz}oZ?task`AP~w-)MgtJ;WyX zI-~4)HPu@Sr;GCO@UCtZT-{n)|BPc_T2)d{_WmogJUKO$U|v+>^ON??eS2~Fnmnza zm%|t1?eb&eW?`S(UEN%iA#vX4;@a5M)XK=ja)5nyX=b(C?W(D%sB%})Rn=6~4yL6U zo1L54U_j@P0d2jr0ZI$-Y>gM8ii+O$iW0fCEqw_zR)i9WTszH z@wR$eSASbkQ$rjdDkA;l1vf=o!gP?_=;-b7jKm;!WSso6jfaAT?Y8}ipnkfa1EE)< zJRCcH#E-N~(tzKyL=&sD{%BZBvCY|^)5p@l>0hb#wu3f6?AlW9{j-cqPwU=qg5cN+ zC^I85)o()=+wJhiySCNV&9sgw14`};FX^DxeR_(-^ovHna?Gw)ySw37*Vuj0zbdhZu1-BFmPr#$1FZ?n7O;cPc9lkrr{Hl zGJy66lT?Z8l^IJhe^#2l?HG+_ROKgLLy*;ITbwLj*O7DPj>hsMBtm`sNy;gC7PLT6 zKmajA^TbA|3-c~9orJpfJ^d&QLqZXX8&X7)H`j^wEGb{piw_!to+ESr9+EZlT!<=t z`e8StwYYoZg)?Q@lSw>I!+&d?yvDIHLzMuIid8S(e$%&y0aSxCZu&0r!bB^@gPTA1 z_r<|Wg0aOpzp2aBAipY-KUtH;PixalI&+^&rc8Ci0;9eGmZV!bgzvIWd7DK(MAWn0mvh{p-2hIjCAw2pW!ZOO%`j(9_~)o@ zp-YHL%VK?3Q`-U*P2)@*b0h~YQlY{ZflFN_Rh5e)+GORf^p?HG#lhy61sK-z2%vPG zIMhf^^QpIl!^4EetXr+b)}k9;Flqfv4a;Q<$%;*fvovRUw7jdhA@-*No^V z@@q$XdHitg-Qv@pme>;uAv@%{a#~Jxr}vK-e(0oaO#h(~SNhYEZo_1q>?P977zbDg zBQJ$7+tak!%koR`@1N(@msbMO_U!SwBXoIAOh+?xHb*~8GpZSNTQ|=A*vA{`NSk3g zO2S^OK@;Sq*!=EpBcXJ9x{5rSq*Yoaw90TtFDW-4ebML?860_8^n#YxR(a98AF7=(DwNGf151|n2V zrP~Po3*Pn5IX5~!kAppXKQ9rp{F3{hhSjnY&X`GLvPYbaAUWE_hGak-MvkY6k&Vw+ zHK6tnC;pVIj!JUvZ!Of`MXby-JE~gVbGccA9^&~K?^{U%J88PheseCFrTEuqXS&W- z&BbzP2ZN3(5910INouxP&7oALH8|$f9{&+dlFEDlr!X{S%-v+ zd=G6MConFzRl_U6cUk|=PUWo{5vT*dIRu&TFNp_(GT#K|LTRvyBsatwnRXUYG-03i zWi#_fpxA9(B4-4oR|ewWEcZcBq4MWc#XBIxWo4ef`r^_+mmZPQ#*KrraxLGHrOpO7alnaJS6|?fc7TY2dOas-2TP2^mx} ziS!@V!o;m;zeuqv>m=!$`FtSW*4(wy)_wg!+Y6@|G%8wNU=bzvn3a+(RQQr$+*XIo zYJz6VrYGg5<{|JJo5n{WGS5U>pZ7>`aosfYYHOnEOe zmoMVFkUMZ6!VlYxP0!x>cA#V65@vc6&l`o9H z4X@~eXeK1Gsna>+Th_uUW{qo$T~8nKuMn#4)kjo*Khp(XI>oOuLmF&^#k*^M;ot8C z(p@Lqc2>0(?~~KwQpDh~Cp&W5ZPRL+j3%mXGNKMIm8iV zXJxoGk!>LjX%{z_fxa|;WKr$}j`fXU@1B+mVw6P%db9qSYKE#b+M@(MPuo(-=;}75 z*NZ-{XXR8{tl0ClIG53*y+yFAOS`FoURtry+>W&t0%L8V+Fx&nN9K?LyZy9_qlqBh zifm2k#Wdp©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∨^e{e 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

VJ=p12WF;<4*F0(78kM{bdpMqFy4u$$dkH{{>m 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%0A!Ke3phBdVLvz!OB|tC>(i6ax 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 literal 0 HcmV?d00001 diff --git a/21-futures/getflags/flags2_asyncio.py b/21-futures/getflags/flags2_asyncio.py new file mode 100755 index 0000000..dab7db6 --- /dev/null +++ b/21-futures/getflags/flags2_asyncio.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python3 + +"""Download flags of countries (with error handling). + +asyncio async/await version + +""" +# BEGIN FLAGS2_ASYNCIO_TOP +import asyncio +from collections import Counter + +import aiohttp +from aiohttp import web +from aiohttp.http_exceptions import HttpProcessingError +import tqdm # type: ignore + +from flags2_common import main, HTTPStatus, Result, save_flag + +# default set low to avoid errors from remote site, such as +# 503 - Service Temporarily Unavailable +DEFAULT_CONCUR_REQ = 5 +MAX_CONCUR_REQ = 1000 + + +class FetchError(Exception): # <1> + def __init__(self, country_code): + self.country_code = country_code + + +async def get_flag(session, base_url, cc): # <2> + url = '{}/{cc}/{cc}.gif'.format(base_url, cc=cc.lower()) + async with session.get(url) as resp: + if resp.status == 200: + return await resp.read() + elif resp.status == 404: + raise web.HTTPNotFound() + else: + raise HttpProcessingError( + code=resp.status, message=resp.reason, + headers=resp.headers) + + +async def download_one(session, cc, base_url, semaphore, verbose): # <3> + try: + async with semaphore: # <4> + image = await get_flag(session, base_url, cc) # <5> + except web.HTTPNotFound: # <6> + status = HTTPStatus.not_found + msg = 'not found' + except Exception as exc: + raise FetchError(cc) from exc # <7> + else: + save_flag(image, cc.lower() + '.gif') # <8> + status = HTTPStatus.ok + msg = 'OK' + + if verbose and msg: + print(cc, msg) + + return Result(status, cc) +# END FLAGS2_ASYNCIO_TOP + +# BEGIN FLAGS2_ASYNCIO_DOWNLOAD_MANY +async def downloader_coro(cc_list: list[str], + base_url: str, + verbose: bool, + concur_req: int) -> Counter[HTTPStatus]: # <1> + counter: Counter[HTTPStatus] = Counter() + semaphore = asyncio.Semaphore(concur_req) # <2> + async with aiohttp.ClientSession() as session: # <8> + to_do = [download_one(session, cc, base_url, semaphore, verbose) + for cc in sorted(cc_list)] # <3> + + to_do_iter = asyncio.as_completed(to_do) # <4> + if not verbose: + to_do_iter = tqdm.tqdm(to_do_iter, total=len(cc_list)) # <5> + for future in to_do_iter: # <6> + try: + res = await future # <7> + except FetchError as exc: # <8> + country_code = exc.country_code # <9> + try: + if exc.__cause__ is None: + error_msg = 'Unknown cause' + else: + error_msg = exc.__cause__.args[0] # <10> + except IndexError: + error_msg = exc.__cause__.__class__.__name__ # <11> + if verbose and error_msg: + msg = '*** Error for {}: {}' + print(msg.format(country_code, error_msg)) + status = HTTPStatus.error + else: + status = res.status + + counter[status] += 1 # <12> + + return counter # <13> + + +def download_many(cc_list: list[str], + base_url: str, + verbose: bool, + concur_req: int) -> Counter[HTTPStatus]: + coro = downloader_coro(cc_list, base_url, verbose, concur_req) + counts = asyncio.run(coro) # <14> + + return counts + + +if __name__ == '__main__': + main(download_many, DEFAULT_CONCUR_REQ, MAX_CONCUR_REQ) +# END FLAGS2_ASYNCIO_DOWNLOAD_MANY diff --git a/21-futures/getflags/flags2_common.py b/21-futures/getflags/flags2_common.py new file mode 100644 index 0000000..76cf5d3 --- /dev/null +++ b/21-futures/getflags/flags2_common.py @@ -0,0 +1,155 @@ +"""Utilities for second set of flag examples. +""" + +import os +import time +import sys +import string +import argparse +from collections import namedtuple, Counter +from enum import Enum + + +Result = namedtuple('Result', 'status data') + +HTTPStatus = Enum('HTTPStatus', 'ok not_found error') + +POP20_CC = ('CN IN US ID BR PK NG BD RU JP ' + 'MX PH VN ET EG DE IR TR CD FR').split() + +DEFAULT_CONCUR_REQ = 1 +MAX_CONCUR_REQ = 1 + +SERVERS = { + 'REMOTE': 'http://fluentpython.com/data/flags', + 'LOCAL': 'http://localhost:8000/flags', + 'DELAY': 'http://localhost:8001/flags', + 'ERROR': 'http://localhost:8002/flags', +} +DEFAULT_SERVER = 'LOCAL' + +DEST_DIR = 'downloaded/' +COUNTRY_CODES_FILE = 'country_codes.txt' + + +def save_flag(img: bytes, filename: str) -> None: + path = os.path.join(DEST_DIR, filename) + with open(path, 'wb') as fp: + fp.write(img) + + +def initial_report(cc_list: list[str], + actual_req: int, + server_label: str) -> None: + if len(cc_list) <= 10: + cc_msg = ', '.join(cc_list) + else: + cc_msg = 'from {} to {}'.format(cc_list[0], cc_list[-1]) + print('{} site: {}'.format(server_label, SERVERS[server_label])) + msg = 'Searching for {} flag{}: {}' + plural = 's' if len(cc_list) != 1 else '' + print(msg.format(len(cc_list), plural, cc_msg)) + plural = 's' if actual_req != 1 else '' + msg = '{} concurrent connection{} will be used.' + print(msg.format(actual_req, plural)) + + +def final_report(cc_list: list[str], + counter: Counter[HTTPStatus], + start_time: float) -> None: + elapsed = time.time() - start_time + print('-' * 20) + msg = '{} flag{} downloaded.' + plural = 's' if counter[HTTPStatus.ok] != 1 else '' + print(msg.format(counter[HTTPStatus.ok], plural)) + if counter[HTTPStatus.not_found]: + print(counter[HTTPStatus.not_found], 'not found.') + if counter[HTTPStatus.error]: + plural = 's' if counter[HTTPStatus.error] != 1 else '' + print('{} error{}.'.format(counter[HTTPStatus.error], plural)) + print('Elapsed time: {:.2f}s'.format(elapsed)) + + +def expand_cc_args(every_cc: bool, + all_cc: bool, + cc_args: list[str], + limit: int) -> list[str]: + codes: set[str] = set() + A_Z = string.ascii_uppercase + if every_cc: + codes.update(a+b for a in A_Z for b in A_Z) + elif all_cc: + with open(COUNTRY_CODES_FILE) as fp: + text = fp.read() + codes.update(text.split()) + else: + for cc in (c.upper() for c in cc_args): + if len(cc) == 1 and cc in A_Z: + codes.update(cc+c for c in A_Z) + elif len(cc) == 2 and all(c in A_Z for c in cc): + codes.add(cc) + else: + msg = 'each CC argument must be A to Z or AA to ZZ.' + raise ValueError('*** Usage error: '+msg) + return sorted(codes)[:limit] + + +def process_args(default_concur_req): + server_options = ', '.join(sorted(SERVERS)) + parser = argparse.ArgumentParser( + description='Download flags for country codes. ' + 'Default: top 20 countries by population.') + parser.add_argument('cc', metavar='CC', nargs='*', + help='country code or 1st letter (eg. B for BA...BZ)') + parser.add_argument('-a', '--all', action='store_true', + help='get all available flags (AD to ZW)') + parser.add_argument('-e', '--every', action='store_true', + help='get flags for every possible code (AA...ZZ)') + parser.add_argument('-l', '--limit', metavar='N', type=int, + help='limit to N first codes', default=sys.maxsize) + parser.add_argument('-m', '--max_req', metavar='CONCURRENT', type=int, + default=default_concur_req, + help=f'maximum concurrent requests (default={default_concur_req})') + parser.add_argument('-s', '--server', metavar='LABEL', + default=DEFAULT_SERVER, + help=('Server to hit; one of ' + + f'{server_options} (default={DEFAULT_SERVER})')) + parser.add_argument('-v', '--verbose', action='store_true', + help='output detailed progress info') + args = parser.parse_args() + if args.max_req < 1: + print('*** Usage error: --max_req CONCURRENT must be >= 1') + parser.print_usage() + sys.exit(1) + if args.limit < 1: + print('*** Usage error: --limit N must be >= 1') + parser.print_usage() + sys.exit(1) + args.server = args.server.upper() + if args.server not in SERVERS: + print('*** Usage error: --server LABEL must be one of', + server_options) + parser.print_usage() + sys.exit(1) + try: + cc_list = expand_cc_args(args.every, args.all, args.cc, args.limit) + except ValueError as exc: + print(exc.args[0]) + parser.print_usage() + sys.exit(1) + + if not cc_list: + cc_list = sorted(POP20_CC) + return args, cc_list + + +def main(download_many, default_concur_req, max_concur_req): + args, cc_list = process_args(default_concur_req) + actual_req = min(args.max_req, max_concur_req, len(cc_list)) + initial_report(cc_list, actual_req, args.server) + base_url = SERVERS[args.server] + t0 = time.time() + counter = download_many(cc_list, base_url, args.verbose, actual_req) + assert sum(counter.values()) == len(cc_list), \ + 'some downloads are unaccounted for' + final_report(cc_list, counter, t0) diff --git a/21-futures/getflags/flags2_sequential.py b/21-futures/getflags/flags2_sequential.py new file mode 100755 index 0000000..48a5498 --- /dev/null +++ b/21-futures/getflags/flags2_sequential.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python3 + +"""Download flags of countries (with error handling). + +Sequential version + +Sample run:: + + $ python3 flags2_sequential.py -s DELAY b + DELAY site: http://localhost:8002/flags + Searching for 26 flags: from BA to BZ + 1 concurrent connection will be used. + -------------------- + 17 flags downloaded. + 9 not found. + Elapsed time: 13.36s + +""" + +from collections import Counter + +import requests +import tqdm # type: ignore + +from flags2_common import main, save_flag, HTTPStatus, Result + + +DEFAULT_CONCUR_REQ = 1 +MAX_CONCUR_REQ = 1 + +# tag::FLAGS2_BASIC_HTTP_FUNCTIONS[] +def get_flag(base_url: str, cc: str) -> bytes: + cc = cc.lower() + url = f'{base_url}/{cc}/{cc}.gif' + resp = requests.get(url) + if resp.status_code != 200: # <1> + resp.raise_for_status() + return resp.content + +def download_one(cc: str, base_url: str, verbose: bool = False): + try: + image = get_flag(base_url, cc) + except requests.exceptions.HTTPError as exc: # <2> + res = exc.response + if res.status_code == 404: + status = HTTPStatus.not_found # <3> + msg = 'not found' + else: # <4> + raise + else: + save_flag(image, cc.lower() + '.gif') + status = HTTPStatus.ok + msg = 'OK' + + if verbose: # <5> + print(cc, msg) + + return Result(status, cc) # <6> +# end::FLAGS2_BASIC_HTTP_FUNCTIONS[] + +# tag::FLAGS2_DOWNLOAD_MANY_SEQUENTIAL[] +def download_many(cc_list: list[str], + base_url: str, + verbose: bool, + _unused_concur_req: int) -> Counter[int]: + counter: Counter[int] = Counter() # <1> + cc_iter = sorted(cc_list) # <2> + if not verbose: + cc_iter = tqdm.tqdm(cc_iter) # <3> + for cc in cc_iter: # <4> + try: + res = download_one(cc, base_url, verbose) # <5> + except requests.exceptions.HTTPError as exc: # <6> + error_msg = 'HTTP error {res.status_code} - {res.reason}' + error_msg = error_msg.format(res=exc.response) + except requests.exceptions.ConnectionError: # <7> + error_msg = 'Connection error' + else: # <8> + error_msg = '' + status = res.status + + if error_msg: + status = HTTPStatus.error # <9> + counter[status] += 1 # <10> + if verbose and error_msg: # <11> + print(f'*** Error for {cc}: {error_msg}') + + return counter # <12> +# end::FLAGS2_DOWNLOAD_MANY_SEQUENTIAL[] + +if __name__ == '__main__': + main(download_many, DEFAULT_CONCUR_REQ, MAX_CONCUR_REQ) diff --git a/21-futures/getflags/flags2_threadpool.py b/21-futures/getflags/flags2_threadpool.py new file mode 100755 index 0000000..b2c37c1 --- /dev/null +++ b/21-futures/getflags/flags2_threadpool.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python3 + +"""Download flags of countries (with error handling). + +ThreadPool version + +Sample run:: + + $ python3 flags2_threadpool.py -s ERROR -e + ERROR site: http://localhost:8003/flags + Searching for 676 flags: from AA to ZZ + 30 concurrent connections will be used. + -------------------- + 150 flags downloaded. + 361 not found. + 165 errors. + Elapsed time: 7.46s + +""" + +# tag::FLAGS2_THREADPOOL[] +from collections import Counter +from concurrent import futures + +import requests +import tqdm # type: ignore # <1> + +from flags2_common import main, HTTPStatus # <2> +from flags2_sequential import download_one # <3> + +DEFAULT_CONCUR_REQ = 30 # <4> +MAX_CONCUR_REQ = 1000 # <5> + + +def download_many(cc_list: list[str], + base_url: str, + verbose: bool, + concur_req: int) -> Counter[int]: + counter: Counter[int] = Counter() + with futures.ThreadPoolExecutor(max_workers=concur_req) as executor: # <6> + to_do_map = {} # <7> + for cc in sorted(cc_list): # <8> + future = executor.submit(download_one, + cc, base_url, verbose) # <9> + to_do_map[future] = cc # <10> + done_iter = futures.as_completed(to_do_map) # <11> + if not verbose: + done_iter = tqdm.tqdm(done_iter, total=len(cc_list)) # <12> + for future in done_iter: # <13> + try: + res = future.result() # <14> + except requests.exceptions.HTTPError as exc: # <15> + error_msg = 'HTTP {res.status_code} - {res.reason}' + error_msg = error_msg.format(res=exc.response) + except requests.exceptions.ConnectionError: + error_msg = 'Connection error' + else: + error_msg = '' + status = res.status + + if error_msg: + status = HTTPStatus.error + counter[status] += 1 + if verbose and error_msg: + cc = to_do_map[future] # <16> + print('*** Error for {}: {}'.format(cc, error_msg)) + + return counter + + +if __name__ == '__main__': + main(download_many, DEFAULT_CONCUR_REQ, MAX_CONCUR_REQ) +# end::FLAGS2_THREADPOOL[] diff --git a/21-futures/getflags/flags_asyncio.py b/21-futures/getflags/flags_asyncio.py new file mode 100755 index 0000000..697307d --- /dev/null +++ b/21-futures/getflags/flags_asyncio.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 + +"""Download flags of top 20 countries by population + +asyncio + aiottp version + +Sample run:: + + $ python3 flags_asyncio.py + EG VN IN TR RU ID US DE CN MX JP BD NG ET FR BR PH PK CD IR + 20 flags downloaded in 1.07s + +""" +# tag::FLAGS_ASYNCIO[] +import asyncio + +from aiohttp import ClientSession # <1> + +from flags import BASE_URL, save_flag, main # <2> + +async def get_flag(session: ClientSession ,cc: str) -> bytes: # <3> + cc = cc.lower() + url = f'{BASE_URL}/{cc}/{cc}.gif' + async with session.get(url) as resp: # <4> + print(resp) + return await resp.read() # <5> + +async def download_one(session: ClientSession, cc: str): # <6> + image = await get_flag(session, cc) + print(cc, end=' ', flush=True) + save_flag(image, cc.lower() + '.gif') + return cc + +async def supervisor(cc_list): + async with ClientSession() as session: # <7> + to_do = [download_one(session, cc) + for cc in sorted(cc_list)] # <8> + res = await asyncio.gather(*to_do) # <9> + + return len(res) + +def download_many(cc_list): + return asyncio.run(supervisor(cc_list)) # <10> + +if __name__ == '__main__': + main(download_many) +# end::FLAGS_ASYNCIO[] diff --git a/21-futures/getflags/flags_threadpool.py b/21-futures/getflags/flags_threadpool.py new file mode 100755 index 0000000..1d01491 --- /dev/null +++ b/21-futures/getflags/flags_threadpool.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 + +"""Download flags of top 20 countries by population + +ThreadPoolExecutor version + +Sample run:: + + $ python3 flags_threadpool.py + DE FR BD CN EG RU IN TR VN ID JP BR NG MX PK ET PH CD US IR + 20 downloads in 0.35s + +""" + +# tag::FLAGS_THREADPOOL[] +from concurrent import futures + +from flags import save_flag, get_flag, main # <1> + +def download_one(cc: str): # <2> + image = get_flag(cc) + print(cc, end=' ', flush=True) + save_flag(image, cc.lower() + '.gif') + return cc + +def download_many(cc_list: list[str]) -> int: + with futures.ThreadPoolExecutor() as executor: # <3> + res = executor.map(download_one, sorted(cc_list)) # <4> + + return len(list(res)) # <5> + +if __name__ == '__main__': + main(download_many) # <6> +# end::FLAGS_THREADPOOL[] \ No newline at end of file diff --git a/21-futures/getflags/flags_threadpool_futures.py b/21-futures/getflags/flags_threadpool_futures.py new file mode 100755 index 0000000..49ee1e5 --- /dev/null +++ b/21-futures/getflags/flags_threadpool_futures.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 + +"""Download flags of top 20 countries by population + +ThreadPoolExecutor example with ``as_completed``. +""" +from concurrent import futures + +from flags import save_flag, main +from flags_threadpool import download_one + + +# tag::FLAGS_THREADPOOL_AS_COMPLETED[] +def download_many(cc_list: list[str]) -> int: + cc_list = cc_list[:5] # <1> + with futures.ThreadPoolExecutor(max_workers=3) as executor: # <2> + to_do: list[futures.Future] = [] + for cc in sorted(cc_list): # <3> + future = executor.submit(download_one, cc) # <4> + to_do.append(future) # <5> + msg = 'Scheduled for {}: {}' + print(msg.format(cc, future)) # <6> + + count = 0 + for future in futures.as_completed(to_do): # <7> + res: str = future.result() # <8> + msg = '{} result: {!r}' + print(msg.format(future, res)) # <9> + count += 1 + + return count +# end::FLAGS_THREADPOOL_AS_COMPLETED[] + +if __name__ == '__main__': + main(download_many) + diff --git a/21-futures/getflags/requirements.txt b/21-futures/getflags/requirements.txt new file mode 100644 index 0000000..a09b073 --- /dev/null +++ b/21-futures/getflags/requirements.txt @@ -0,0 +1,11 @@ +aiohttp==3.7.3 +async-timeout==3.0.1 +attrs==20.3.0 +certifi==2020.12.5 +chardet==4.0.0 +idna==2.10 +requests==2.25.1 +urllib3==1.26.3 +tqdm==4.56.2 +multidict==5.1.0 +yarl==1.6.3 diff --git a/21-futures/getflags/slow_server.py b/21-futures/getflags/slow_server.py new file mode 100755 index 0000000..9c8b95c --- /dev/null +++ b/21-futures/getflags/slow_server.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python3 + +"""Slow HTTP server class. + +This module implements a ThreadingHTTPServer using a custom +SimpleHTTPRequestHandler subclass that introduces delays to all +GET responses, and optionally returns errors to a fraction of +the requests if given the --error_rate command-line argument. +""" + +import time +import os +import socket +import contextlib +from functools import partial +from random import random +from http import server, HTTPStatus +from http.server import ThreadingHTTPServer, SimpleHTTPRequestHandler + + +class SlowHTTPRequestHandler(SimpleHTTPRequestHandler): + """SlowHTTPRequestHandler adds delays and errors to test HTTP clients. + + The optional error_rate argument determines how often GET requests + receive a 418 status code, "I'm a teapot". + If error_rate is .15, there's a 15% probability of each GET request + getting that error. + When the server believes it is a teapot, it refuses requests to serve files. + + See: https://tools.ietf.org/html/rfc2324#section-2.3.2 + """ + + def __init__(self, *args, error_rate=0.0, **kwargs): + self.error_rate = error_rate + super().__init__(*args, **kwargs) + + def do_GET(self): + """Serve a GET request.""" + time.sleep(.5) + if random() < self.error_rate: + self.send_error(HTTPStatus.IM_A_TEAPOT, "I'm a Teapot") + else: + f = self.send_head() + if f: + try: + self.copyfile(f, self.wfile) + finally: + f.close() + +# The code in the `if` block below, including comments, was copied +# and adapted from the `http.server` module of Python 3.9 +# https://github.com/python/cpython/blob/master/Lib/http/server.py + +if __name__ == '__main__': + + import argparse + + parser = argparse.ArgumentParser() + parser.add_argument('--bind', '-b', metavar='ADDRESS', + help='Specify alternate bind address ' + '[default: all interfaces]') + parser.add_argument('--directory', '-d', default=os.getcwd(), + help='Specify alternative directory ' + '[default:current directory]') + parser.add_argument('--error-rate', '-e', metavar='PROBABILITY', + default=0.0, type=float, + help='Error rate; e.g. use .25 for 25%% probability ' + '[default:0.0]') + parser.add_argument('port', action='store', + default=8000, type=int, + nargs='?', + help='Specify alternate port [default: 8000]') + args = parser.parse_args() + handler_class = partial(SlowHTTPRequestHandler, + directory=args.directory, + error_rate=args.error_rate) + + # ensure dual-stack is not disabled; ref #38907 + class DualStackServer(ThreadingHTTPServer): + def server_bind(self): + # suppress exception when protocol is IPv4 + with contextlib.suppress(Exception): + self.socket.setsockopt( + socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0) + return super().server_bind() + + server.test( + HandlerClass=handler_class, + ServerClass=DualStackServer, + port=args.port, + bind=args.bind, + ) diff --git a/21-futures/primes/primes.py b/21-futures/primes/primes.py new file mode 100755 index 0000000..989989d --- /dev/null +++ b/21-futures/primes/primes.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 + +import math + + +PRIME_FIXTURE = [ + (2, True), + (142702110479723, True), + (299593572317531, True), + (3333333333333301, True), + (3333333333333333, False), + (3333335652092209, False), + (4444444444444423, True), + (4444444444444444, False), + (4444444488888889, False), + (5555553133149889, False), + (5555555555555503, True), + (5555555555555555, False), + (6666666666666666, False), + (6666666666666719, True), + (6666667141414921, False), + (7777777536340681, False), + (7777777777777753, True), + (7777777777777777, False), + (9999999999999917, True), + (9999999999999999, False), +] + +NUMBERS = [n for n, _ in PRIME_FIXTURE] + +# tag::IS_PRIME[] +def is_prime(n: int) -> bool: + if n < 2: + return False + if n == 2: + return True + if n % 2 == 0: + return False + + root = math.isqrt(n) + for i in range(3, root + 1, 2): + if n % i == 0: + return False + return True +# end::IS_PRIME[] + +if __name__ == '__main__': + + for n, prime in PRIME_FIXTURE: + prime_res = is_prime(n) + assert prime_res == prime + print(n, prime) diff --git a/21-futures/primes/proc_pool.py b/21-futures/primes/proc_pool.py new file mode 100755 index 0000000..be9fa85 --- /dev/null +++ b/21-futures/primes/proc_pool.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 + +""" +proc_pool.py: a version of the proc.py example from chapter 20, +but using `concurrent.futures.ProcessPoolExecutor`. +""" + +# tag::PRIMES_POOL[] +from time import perf_counter +from typing import NamedTuple +from concurrent import futures # <1> +import sys + +from primes import is_prime, NUMBERS + +class PrimeResult(NamedTuple): # <2> + n: int + flag: bool + elapsed: float + +def check(n: int) -> PrimeResult: + t0 = perf_counter() + res = is_prime(n) + return PrimeResult(n, res, perf_counter() - t0) + +def main() -> None: + if len(sys.argv) < 2: + workers = None # <3> + else: + workers = int(sys.argv[1]) + + executor = futures.ProcessPoolExecutor(workers) # <4> + actual_workers = executor._max_workers # type: ignore # <5> + + print(f'Checking {len(NUMBERS)} numbers with {actual_workers} processes:') + + t0 = perf_counter() + + numbers = sorted(NUMBERS, reverse=True) # <6> + with executor: # <7> + for n, prime, elapsed in executor.map(check, numbers): # <8> + label = 'P' if prime else ' ' + print(f'{n:16} {label} {elapsed:9.6f}s') + + time = perf_counter() - t0 + print('Total time:', f'{time:0.2f}s') + +if __name__ == '__main__': + main() +# end::PRIMES_POOL[] \ No newline at end of file From 916ceaa88f7d5e6d585a9fbf9160262145bb3e38 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Fri, 12 Feb 2021 23:23:26 -0300 Subject: [PATCH 011/166] ch20: minor edits --- 20-concurrency/primes/primes.py | 6 ++-- 20-concurrency/primes/procs.py | 31 ++++++++++++------- 20-concurrency/primes/sequential.py | 14 +++++++-- .../primes/spinner_prime_async_nap.py | 11 +++---- 20-concurrency/primes/threads.py | 31 ++++++++++++------- 5 files changed, 58 insertions(+), 35 deletions(-) diff --git a/20-concurrency/primes/primes.py b/20-concurrency/primes/primes.py index 0884dcd..989989d 100755 --- a/20-concurrency/primes/primes.py +++ b/20-concurrency/primes/primes.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python3 + import math @@ -27,7 +29,7 @@ NUMBERS = [n for n, _ in PRIME_FIXTURE] # tag::IS_PRIME[] -def is_prime(n) -> bool: +def is_prime(n: int) -> bool: if n < 2: return False if n == 2: @@ -35,7 +37,7 @@ def is_prime(n) -> bool: if n % 2 == 0: return False - root = math.floor(math.sqrt(n)) + root = math.isqrt(n) for i in range(3, root + 1, 2): if n % i == 0: return False diff --git a/20-concurrency/primes/procs.py b/20-concurrency/primes/procs.py index 27263d7..9c470d8 100644 --- a/20-concurrency/primes/procs.py +++ b/20-concurrency/primes/procs.py @@ -1,28 +1,35 @@ +#!/usr/bin/env python3 + +""" +procs.py: shows that multiprocessing on a multicore machine +can be faster than sequential code for CPU-intensive work. +""" + # tag::PRIMES_PROC_TOP[] from time import perf_counter -from typing import Tuple, NamedTuple +from typing import NamedTuple from multiprocessing import Process, SimpleQueue, cpu_count # <1> from multiprocessing import queues # <2> import sys from primes import is_prime, NUMBERS -class Result(NamedTuple): # <3> - flag: bool +class PrimeResult(NamedTuple): # <3> + n: int + prime: bool elapsed: float JobQueue = queues.SimpleQueue[int] # <4> -ResultQueue = queues.SimpleQueue[Tuple[int, Result]] # <5> +ResultQueue = queues.SimpleQueue[PrimeResult] # <5> -def check(n: int) -> Result: # <6> +def check(n: int) -> PrimeResult: # <6> t0 = perf_counter() res = is_prime(n) - return Result(res, perf_counter() - t0) + return PrimeResult(n, res, perf_counter() - t0) def worker(jobs: JobQueue, results: ResultQueue) -> None: # <7> while n := jobs.get(): # <8> - result = check(n) # <9> - results.put((n, result)) # <10> + results.put(check(n)) # <9> # end::PRIMES_PROC_TOP[] # tag::PRIMES_PROC_MAIN[] @@ -32,11 +39,11 @@ def main() -> None: else: workers = int(sys.argv[1]) - t0 = perf_counter() + print(f'Checking {len(NUMBERS)} numbers with {workers} processes:') + jobs: JobQueue = SimpleQueue() # <2> results: ResultQueue = SimpleQueue() - - print(f'Checking {len(NUMBERS)} numbers with {workers} processes:') + t0 = perf_counter() for n in NUMBERS: # <3> jobs.put(n) @@ -47,7 +54,7 @@ def main() -> None: jobs.put(0) # <6> while True: - n, (prime, elapsed) = results.get() # <7> + n, prime, elapsed = results.get() # <7> label = 'P' if prime else ' ' print(f'{n:16} {label} {elapsed:9.6f}s') # <8> if jobs.empty(): # <9> diff --git a/20-concurrency/primes/sequential.py b/20-concurrency/primes/sequential.py index a1b9ae7..4467528 100644 --- a/20-concurrency/primes/sequential.py +++ b/20-concurrency/primes/sequential.py @@ -1,18 +1,26 @@ +#!/usr/bin/env python3 + +""" +sequential.py: baseline for comparing sequential, multiprocessing, +and threading code for CPU-intensive work. +""" + from time import perf_counter from typing import NamedTuple from primes import is_prime, NUMBERS class Result(NamedTuple): # <1> - flag: bool + prime: bool elapsed: float def check(n: int) -> Result: # <2> t0 = perf_counter() - flag = is_prime(n) - return Result(flag, perf_counter() - t0) + prime = is_prime(n) + return Result(prime, perf_counter() - t0) def main() -> None: + print(f'Checking {len(NUMBERS)} numbers sequentially:') t0 = perf_counter() for n in NUMBERS: # <3> prime, elapsed = check(n) diff --git a/20-concurrency/primes/spinner_prime_async_nap.py b/20-concurrency/primes/spinner_prime_async_nap.py index 48166ee..4d6fa47 100644 --- a/20-concurrency/primes/spinner_prime_async_nap.py +++ b/20-concurrency/primes/spinner_prime_async_nap.py @@ -1,4 +1,4 @@ -# spinner_async_experiment.py +# spinner_prime_async_nap.py # credits: Example by Luciano Ramalho inspired by # Michele Simionato's multiprocessing example in the python-list: @@ -8,7 +8,7 @@ import itertools import math -# tag::SPINNER_ASYNC_NAP[] +# tag::PRIME_NAP[] async def is_prime(n): if n < 2: return False @@ -17,15 +17,14 @@ async def is_prime(n): if n % 2 == 0: return False - sleep = asyncio.sleep # <1> - root = math.floor(math.sqrt(n)) + root = math.isqrt(n) for i in range(3, root + 1, 2): if n % i == 0: return False if i % 100_000 == 1: # <2> - await sleep(0) + await asyncio.sleep(0) return True -# end::SPINNER_ASYNC_NAP[] +# end::PRIME_NAP[] async def spin(msg: str) -> None: diff --git a/20-concurrency/primes/threads.py b/20-concurrency/primes/threads.py index 7ac275a..ff7ac50 100644 --- a/20-concurrency/primes/threads.py +++ b/20-concurrency/primes/threads.py @@ -1,5 +1,12 @@ +#!/usr/bin/env python3 + +""" +threads.py: shows that Python threads are slower than +sequential code for CPU-intensive work. +""" + from time import perf_counter -from typing import Tuple, NamedTuple +from typing import NamedTuple from threading import Thread from queue import SimpleQueue import sys @@ -7,22 +14,22 @@ from primes import is_prime, NUMBERS -class Result(NamedTuple): - flag: bool +class PrimeResult(NamedTuple): # <3> + n: int + prime: bool elapsed: float JobQueue = SimpleQueue[int] -ResultQueue = SimpleQueue[Tuple[int, Result]] +ResultQueue = SimpleQueue[PrimeResult] -def check(n: int) -> Result: +def check(n: int) -> PrimeResult: t0 = perf_counter() res = is_prime(n) - return Result(res, perf_counter() - t0) + return PrimeResult(n, res, perf_counter() - t0) def worker(jobs: JobQueue, results: ResultQueue) -> None: while n := jobs.get(): - result = check(n) - results.put((n, result)) + results.put(check(n)) def main() -> None: if len(sys.argv) < 2: # <1> @@ -30,11 +37,11 @@ def main() -> None: else: workers = int(sys.argv[1]) - t0 = perf_counter() + print(f'Checking {len(NUMBERS)} numbers with {workers} threads:') + jobs: JobQueue = SimpleQueue() # <2> results: ResultQueue = SimpleQueue() - - print(f'Checking {len(NUMBERS)} numbers with {workers} threads:') + t0 = perf_counter() for n in NUMBERS: # <3> jobs.put(n) @@ -45,7 +52,7 @@ def main() -> None: jobs.put(0) # <6> while True: - n, (prime, elapsed) = results.get() # <7> + n, prime, elapsed = results.get() # <7> label = 'P' if prime else ' ' print(f'{n:16} {label} {elapsed:9.6f}s') if jobs.empty(): # <8> From e1b8edbe6a706df3f117d1ba2cf3c655559fc938 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Fri, 12 Feb 2021 23:33:02 -0300 Subject: [PATCH 012/166] Update README.md --- README.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index defbde9..27f5740 100644 --- a/README.md +++ b/README.md @@ -38,15 +38,16 @@ Part / Chapter #|Title|Directory|Notebook|1st ed. Chapter # 12|Sequence Hacking, Hashing, and Slicing|[12-seq-hacking](12-seq-hacking)||10 13|Interfaces: From Protocols to ABCs|[13-iface-abc](13-iface-abc)||11 14|Inheritance: For Good or For Worse|[14-inheritance](14-inheritance)||12 -🆕 15|Type Hints Distilled|[15-type-hints](15-type-hints)||– +🆕 15|More About Type Hints|[15-type-hints](15-type-hints)||– 16|Operator Overloading: Doing It Right|[16-op-overloading](16-op-overloading)||13 **V – Control Flow**| 17|Iterables, Iterators, and Generators|[17-it-generator](17-it-generator)||14 18|Context Managers and else Blocks|[18-context-mngr](18-context-mngr)||15 19|Coroutines|[19-coroutine](19-coroutine)||16 -20|Concurrency with Futures|[20-futures](20-futures)||17 -21|Concurrency with asyncio|[21-asyncio](21-asyncio)||18 +🆕 20|Concurrency with Futures|[20-futures](20-futures)||- +21|Concurrency with Futures|[21-futures](21-futures)||17 +22|Concurrency with asyncio|[22-asyncio](22-asyncio)||18 **VI – Metaprogramming**| -22|Dynamic Attributes and Properties|[22-dyn-attr-prop](22-dyn-attr-prop)||19 -23|Attribute Descriptors|[23-descriptor](23-descriptor)||20 -24|Class Metaprogramming|[24-class-metaprog](24-class-metaprog)||21 +23|Dynamic Attributes and Properties|[22-dyn-attr-prop](22-dyn-attr-prop)||19 +24|Attribute Descriptors|[23-descriptor](23-descriptor)||20 +25|Class Metaprogramming|[24-class-metaprog](24-class-metaprog)||21 From 626ea744055a63b6ce938ed7721d744fdf78383f Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Fri, 12 Feb 2021 23:34:26 -0300 Subject: [PATCH 013/166] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 27f5740..3835e54 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ Part / Chapter #|Title|Directory|Notebook|1st ed. Chapter # 17|Iterables, Iterators, and Generators|[17-it-generator](17-it-generator)||14 18|Context Managers and else Blocks|[18-context-mngr](18-context-mngr)||15 19|Coroutines|[19-coroutine](19-coroutine)||16 -🆕 20|Concurrency with Futures|[20-futures](20-futures)||- +🆕 20|Concurrency Models in Python|[20-concurrency](20-concurrency)||- 21|Concurrency with Futures|[21-futures](21-futures)||17 22|Concurrency with asyncio|[22-asyncio](22-asyncio)||18 **VI – Metaprogramming**| From 7b193e0c13d71519c04fe4f5a36c8af0ef7bb829 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Fri, 12 Feb 2021 23:41:11 -0300 Subject: [PATCH 014/166] ch20 examples --- 20-concurrency/primes/spinner_async_nap.py | 58 ------------------- 20-concurrency/spinner_async.py | 41 +++++++++++++ ...no_spin.py => spinner_async_experiment.py} | 5 +- 20-concurrency/spinner_proc.py | 47 +++++++++++++++ 20-concurrency/{primes => }/spinner_thread.py | 5 +- 5 files changed, 92 insertions(+), 64 deletions(-) delete mode 100644 20-concurrency/primes/spinner_async_nap.py create mode 100644 20-concurrency/spinner_async.py rename 20-concurrency/{primes/spinner_async_prime_no_spin.py => spinner_async_experiment.py} (93%) create mode 100644 20-concurrency/spinner_proc.py rename 20-concurrency/{primes => }/spinner_thread.py (94%) diff --git a/20-concurrency/primes/spinner_async_nap.py b/20-concurrency/primes/spinner_async_nap.py deleted file mode 100644 index 13fa066..0000000 --- a/20-concurrency/primes/spinner_async_nap.py +++ /dev/null @@ -1,58 +0,0 @@ -# spinner_async_experiment.py - -# credits: Example by Luciano Ramalho inspired by -# Michele Simionato's multiprocessing example in the python-list: -# https://mail.python.org/pipermail/python-list/2009-February/675659.html - -import asyncio -import itertools -import math - -# tag::SPINNER_ASYNC_NAP[] -async def is_prime(n): - if n < 2: - return False - if n == 2: - return True - if n % 2 == 0: - return False - - sleep = asyncio.sleep # <1> - root = math.floor(math.sqrt(n)) - for i in range(3, root + 1, 2): - if n % i == 0: - return False - if i % 100_000 == 1: # <2> - await sleep(0) - return True -# end::SPINNER_ASYNC_NAP[] - - -async def spin(msg: str) -> None: - for char in itertools.cycle(r'\|/-'): - status = f'\r{char} {msg}' - print(status, flush=True, end='') - try: - await asyncio.sleep(.1) - except asyncio.CancelledError: - break - blanks = ' ' * len(status) - print(f'\r{blanks}\r', end='') - -async def slow() -> int: - await is_prime(5_000_111_000_222_021) # <4> - return 42 - -async def supervisor() -> int: - spinner = asyncio.create_task(spin('thinking!')) # <1> - print('spinner object:', spinner) # <2> - result = await slow() # <3> - spinner.cancel() # <5> - return result - -def main() -> None: - result = asyncio.run(supervisor()) - print('Answer:', result) - -if __name__ == '__main__': - main() diff --git a/20-concurrency/spinner_async.py b/20-concurrency/spinner_async.py new file mode 100644 index 0000000..5bc5028 --- /dev/null +++ b/20-concurrency/spinner_async.py @@ -0,0 +1,41 @@ +# spinner_async.py + +# credits: Example by Luciano Ramalho inspired by +# Michele Simionato's multiprocessing example in the python-list: +# https://mail.python.org/pipermail/python-list/2009-February/675659.html + +# tag::SPINNER_ASYNC_TOP[] +import asyncio +import itertools + +async def spin(msg: str) -> None: # <1> + for char in itertools.cycle(r'\|/-'): + status = f'\r{char} {msg}' + print(status, flush=True, end='') + try: + await asyncio.sleep(.1) # <2> + except asyncio.CancelledError: # <3> + break + blanks = ' ' * len(status) + print(f'\r{blanks}\r', end='') + +async def slow() -> int: + await asyncio.sleep(3) # <4> + return 42 +# end::SPINNER_ASYNC_TOP[] + +# tag::SPINNER_ASYNC_START[] +def main() -> None: # <1> + result = asyncio.run(supervisor()) # <2> + print('Answer:', result) + +async def supervisor() -> int: # <3> + spinner = asyncio.create_task(spin('thinking!')) # <4> + print('spinner object:', spinner) # <5> + result = await slow() # <6> + spinner.cancel() # <7> + return result + +if __name__ == '__main__': + main() +# end::SPINNER_ASYNC_START[] diff --git a/20-concurrency/primes/spinner_async_prime_no_spin.py b/20-concurrency/spinner_async_experiment.py similarity index 93% rename from 20-concurrency/primes/spinner_async_prime_no_spin.py rename to 20-concurrency/spinner_async_experiment.py index 4905e25..6d8bd3f 100644 --- a/20-concurrency/primes/spinner_async_prime_no_spin.py +++ b/20-concurrency/spinner_async_experiment.py @@ -6,8 +6,7 @@ import asyncio import itertools - -import primes +import time async def spin(msg: str) -> None: for char in itertools.cycle(r'\|/-'): @@ -21,7 +20,7 @@ async def spin(msg: str) -> None: # tag::SPINNER_ASYNC_EXPERIMENT[] async def slow() -> int: - primes.is_prime(5_000_111_000_222_021) # <4> + time.sleep(3) # <4> return 42 async def supervisor() -> int: diff --git a/20-concurrency/spinner_proc.py b/20-concurrency/spinner_proc.py new file mode 100644 index 0000000..5cc3067 --- /dev/null +++ b/20-concurrency/spinner_proc.py @@ -0,0 +1,47 @@ +# spinner_proc.py + +# credits: Adapted from Michele Simionato's +# multiprocessing example in the python-list: +# https://mail.python.org/pipermail/python-list/2009-February/675659.html + +# tag::SPINNER_PROC_IMPORTS[] +from multiprocessing import Process, Event # <1> +from multiprocessing import synchronize # <2> +import itertools +import time + +def spin(msg: str, done: synchronize.Event) -> None: # <3> +# end::SPINNER_PROC_IMPORTS[] + for char in itertools.cycle(r'\|/-'): + status = f'\r{char} {msg}' + print(status, end='', flush=True) + if done.wait(.1): + break + blanks = ' ' * len(status) + print(f'\r{blanks}\r', end='') + +def slow() -> int: + time.sleep(3) + return 42 + +# tag::SPINNER_PROC_SUPER[] +def supervisor() -> int: + done = Event() + spinner = Process(target=spin, # <4> + args=('thinking!', done)) + print('spinner object:', spinner) # <5> + spinner.start() + result = slow() + done.set() + spinner.join() + return result +# end::SPINNER_PROC_SUPER[] + +def main() -> None: + result = supervisor() + print('Answer:', result) + + +if __name__ == '__main__': + main() + diff --git a/20-concurrency/primes/spinner_thread.py b/20-concurrency/spinner_thread.py similarity index 94% rename from 20-concurrency/primes/spinner_thread.py rename to 20-concurrency/spinner_thread.py index c7e4c8b..8722c51 100644 --- a/20-concurrency/primes/spinner_thread.py +++ b/20-concurrency/spinner_thread.py @@ -7,8 +7,7 @@ # tag::SPINNER_THREAD_TOP[] from threading import Thread, Event import itertools - -from primes import is_prime +import time def spin(msg: str, done: Event) -> None: # <1> for char in itertools.cycle(r'\|/-'): # <2> @@ -20,7 +19,7 @@ def spin(msg: str, done: Event) -> None: # <1> print(f'\r{blanks}\r', end='') # <6> def slow() -> int: - is_prime(5_000_111_000_222_021) # <7> + time.sleep(3) # <7> return 42 # end::SPINNER_THREAD_TOP[] From 93dfeaeb80de8cb7fe668211a098526c586c5972 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= <6774676+eumiro@users.noreply.github.com> Date: Sat, 13 Feb 2021 19:11:54 +0100 Subject: [PATCH 015/166] ch21 cleanup, f-strings, and pathlib --- 21-futures/demo_executor_map.py | 2 +- 21-futures/getflags/flags.py | 9 +- 21-futures/getflags/flags2_asyncio.py | 6 +- 21-futures/getflags/flags2_common.py | 90 +++++++++---------- 21-futures/getflags/flags2_sequential.py | 1 - 21-futures/getflags/flags2_threadpool.py | 6 +- 21-futures/getflags/flags_asyncio.py | 3 +- 21-futures/getflags/flags_threadpool.py | 2 +- .../getflags/flags_threadpool_futures.py | 13 +-- 21-futures/getflags/slow_server.py | 11 ++- 21-futures/primes/proc_pool.py | 8 +- 11 files changed, 70 insertions(+), 81 deletions(-) diff --git a/21-futures/demo_executor_map.py b/21-futures/demo_executor_map.py index 00d3bdb..5ceee29 100644 --- a/21-futures/demo_executor_map.py +++ b/21-futures/demo_executor_map.py @@ -24,7 +24,7 @@ def main(): display('results:', results) # <6> display('Waiting for individual results:') for i, result in enumerate(results): # <7> - display('result {}: {}'.format(i, result)) + display(f'result {i}: {result}') if __name__ == '__main__': main() diff --git a/21-futures/getflags/flags.py b/21-futures/getflags/flags.py index 4164937..1411041 100755 --- a/21-futures/getflags/flags.py +++ b/21-futures/getflags/flags.py @@ -17,8 +17,8 @@ """ # tag::FLAGS_PY[] -import os import time +from pathlib import Path from typing import Callable import requests # <1> @@ -27,12 +27,10 @@ 'MX PH VN ET EG DE IR TR CD FR').split() # <2> BASE_URL = 'http://fluentpython.com/data/flags' # <3> -DEST_DIR = 'downloaded/' # <4> +DEST_DIR = Path('downloaded') # <4> def save_flag(img: bytes, filename: str) -> None: # <5> - path = os.path.join(DEST_DIR, filename) - with open(path, 'wb') as fp: - fp.write(img) + (DEST_DIR / filename).write_bytes(img) def get_flag(cc: str) -> bytes: # <6> cc = cc.lower() @@ -45,7 +43,6 @@ def download_many(cc_list: list[str]) -> int: # <7> image = get_flag(cc) print(cc, end=' ', flush=True) # <9> save_flag(image, cc.lower() + '.gif') - return len(cc_list) def main(downloader: Callable[[list[str]], int]) -> None: # <10> diff --git a/21-futures/getflags/flags2_asyncio.py b/21-futures/getflags/flags2_asyncio.py index dab7db6..739c531 100755 --- a/21-futures/getflags/flags2_asyncio.py +++ b/21-futures/getflags/flags2_asyncio.py @@ -28,7 +28,8 @@ def __init__(self, country_code): async def get_flag(session, base_url, cc): # <2> - url = '{}/{cc}/{cc}.gif'.format(base_url, cc=cc.lower()) + cc = cc.lower() + url = f'{base_url}/{cc}/{cc}.gif' async with session.get(url) as resp: if resp.status == 200: return await resp.read() @@ -87,8 +88,7 @@ async def downloader_coro(cc_list: list[str], except IndexError: error_msg = exc.__cause__.__class__.__name__ # <11> if verbose and error_msg: - msg = '*** Error for {}: {}' - print(msg.format(country_code, error_msg)) + print(f'*** Error for {country_code}: {error_msg}') status = HTTPStatus.error else: status = res.status diff --git a/21-futures/getflags/flags2_common.py b/21-futures/getflags/flags2_common.py index 76cf5d3..f79a993 100644 --- a/21-futures/getflags/flags2_common.py +++ b/21-futures/getflags/flags2_common.py @@ -1,14 +1,13 @@ """Utilities for second set of flag examples. """ -import os -import time -import sys -import string import argparse +import string +import sys +import time from collections import namedtuple, Counter from enum import Enum - +from pathlib import Path Result = namedtuple('Result', 'status data') @@ -28,14 +27,12 @@ } DEFAULT_SERVER = 'LOCAL' -DEST_DIR = 'downloaded/' -COUNTRY_CODES_FILE = 'country_codes.txt' +DEST_DIR = Path('downloaded') +COUNTRY_CODES_FILE = Path('country_codes.txt') def save_flag(img: bytes, filename: str) -> None: - path = os.path.join(DEST_DIR, filename) - with open(path, 'wb') as fp: - fp.write(img) + (DEST_DIR / filename).write_bytes(img) def initial_report(cc_list: list[str], @@ -44,30 +41,27 @@ def initial_report(cc_list: list[str], if len(cc_list) <= 10: cc_msg = ', '.join(cc_list) else: - cc_msg = 'from {} to {}'.format(cc_list[0], cc_list[-1]) - print('{} site: {}'.format(server_label, SERVERS[server_label])) - msg = 'Searching for {} flag{}: {}' + cc_msg = f'from {cc_list[0]} to {cc_list[-1]}' + print(f'{server_label} site: {SERVERS[server_label]}') plural = 's' if len(cc_list) != 1 else '' - print(msg.format(len(cc_list), plural, cc_msg)) + print(f'Searching for {len(cc_list)} flag{plural}: {cc_msg}') plural = 's' if actual_req != 1 else '' - msg = '{} concurrent connection{} will be used.' - print(msg.format(actual_req, plural)) + print(f'{actual_req} concurrent connection{plural} will be used.') def final_report(cc_list: list[str], counter: Counter[HTTPStatus], start_time: float) -> None: - elapsed = time.time() - start_time + elapsed = time.perf_counter() - start_time print('-' * 20) - msg = '{} flag{} downloaded.' plural = 's' if counter[HTTPStatus.ok] != 1 else '' - print(msg.format(counter[HTTPStatus.ok], plural)) + print(f'{counter[HTTPStatus.ok]} flag{plural} downloaded.') if counter[HTTPStatus.not_found]: - print(counter[HTTPStatus.not_found], 'not found.') + print(f'{counter[HTTPStatus.not_found]} not found.') if counter[HTTPStatus.error]: plural = 's' if counter[HTTPStatus.error] != 1 else '' - print('{} error{}.'.format(counter[HTTPStatus.error], plural)) - print('Elapsed time: {:.2f}s'.format(elapsed)) + print(f'{counter[HTTPStatus.error]} error{plural}') + print(f'Elapsed time: {elapsed:.2f}s') def expand_cc_args(every_cc: bool, @@ -75,22 +69,21 @@ def expand_cc_args(every_cc: bool, cc_args: list[str], limit: int) -> list[str]: codes: set[str] = set() - A_Z = string.ascii_uppercase + A_Z = set(string.ascii_uppercase) if every_cc: - codes.update(a+b for a in A_Z for b in A_Z) + codes.update(f'{a}{b}' for a in A_Z for b in A_Z) elif all_cc: - with open(COUNTRY_CODES_FILE) as fp: - text = fp.read() + text = COUNTRY_CODES_FILE.read_text() codes.update(text.split()) else: for cc in (c.upper() for c in cc_args): if len(cc) == 1 and cc in A_Z: - codes.update(cc+c for c in A_Z) + codes.update(cc + c for c in A_Z) elif len(cc) == 2 and all(c in A_Z for c in cc): codes.add(cc) else: - msg = 'each CC argument must be A to Z or AA to ZZ.' - raise ValueError('*** Usage error: '+msg) + raise ValueError('*** Usage error: each CC argument ' + 'must be A to Z or AA to ZZ.') return sorted(codes)[:limit] @@ -98,23 +91,29 @@ def process_args(default_concur_req): server_options = ', '.join(sorted(SERVERS)) parser = argparse.ArgumentParser( description='Download flags for country codes. ' - 'Default: top 20 countries by population.') - parser.add_argument('cc', metavar='CC', nargs='*', + 'Default: top 20 countries by population.') + parser.add_argument( + 'cc', metavar='CC', nargs='*', help='country code or 1st letter (eg. B for BA...BZ)') - parser.add_argument('-a', '--all', action='store_true', + parser.add_argument( + '-a', '--all', action='store_true', help='get all available flags (AD to ZW)') - parser.add_argument('-e', '--every', action='store_true', + parser.add_argument( + '-e', '--every', action='store_true', help='get flags for every possible code (AA...ZZ)') - parser.add_argument('-l', '--limit', metavar='N', type=int, - help='limit to N first codes', default=sys.maxsize) - parser.add_argument('-m', '--max_req', metavar='CONCURRENT', type=int, + parser.add_argument( + '-l', '--limit', metavar='N', type=int, help='limit to N first codes', + default=sys.maxsize) + parser.add_argument( + '-m', '--max_req', metavar='CONCURRENT', type=int, default=default_concur_req, help=f'maximum concurrent requests (default={default_concur_req})') - parser.add_argument('-s', '--server', metavar='LABEL', - default=DEFAULT_SERVER, - help=('Server to hit; one of ' + - f'{server_options} (default={DEFAULT_SERVER})')) - parser.add_argument('-v', '--verbose', action='store_true', + parser.add_argument( + '-s', '--server', metavar='LABEL', default=DEFAULT_SERVER, + help=f'Server to hit; one of {server_options} ' + f'(default={DEFAULT_SERVER})') + parser.add_argument( + '-v', '--verbose', action='store_true', help='output detailed progress info') args = parser.parse_args() if args.max_req < 1: @@ -127,8 +126,8 @@ def process_args(default_concur_req): sys.exit(1) args.server = args.server.upper() if args.server not in SERVERS: - print('*** Usage error: --server LABEL must be one of', - server_options) + print(f'*** Usage error: --server LABEL ' + f'must be one of {server_options}') parser.print_usage() sys.exit(1) try: @@ -148,8 +147,9 @@ def main(download_many, default_concur_req, max_concur_req): actual_req = min(args.max_req, max_concur_req, len(cc_list)) initial_report(cc_list, actual_req, args.server) base_url = SERVERS[args.server] - t0 = time.time() + t0 = time.perf_counter() counter = download_many(cc_list, base_url, args.verbose, actual_req) - assert sum(counter.values()) == len(cc_list), \ + assert sum(counter.values()) == len(cc_list), ( 'some downloads are unaccounted for' + ) final_report(cc_list, counter, t0) diff --git a/21-futures/getflags/flags2_sequential.py b/21-futures/getflags/flags2_sequential.py index 48a5498..d08bd50 100755 --- a/21-futures/getflags/flags2_sequential.py +++ b/21-futures/getflags/flags2_sequential.py @@ -24,7 +24,6 @@ from flags2_common import main, save_flag, HTTPStatus, Result - DEFAULT_CONCUR_REQ = 1 MAX_CONCUR_REQ = 1 diff --git a/21-futures/getflags/flags2_threadpool.py b/21-futures/getflags/flags2_threadpool.py index b2c37c1..8454a80 100755 --- a/21-futures/getflags/flags2_threadpool.py +++ b/21-futures/getflags/flags2_threadpool.py @@ -50,8 +50,8 @@ def download_many(cc_list: list[str], try: res = future.result() # <14> except requests.exceptions.HTTPError as exc: # <15> - error_msg = 'HTTP {res.status_code} - {res.reason}' - error_msg = error_msg.format(res=exc.response) + error_fmt = 'HTTP {res.status_code} - {res.reason}' + error_msg = error_fmt.format(res=exc.response) except requests.exceptions.ConnectionError: error_msg = 'Connection error' else: @@ -63,7 +63,7 @@ def download_many(cc_list: list[str], counter[status] += 1 if verbose and error_msg: cc = to_do_map[future] # <16> - print('*** Error for {}: {}'.format(cc, error_msg)) + print(f'*** Error for {cc}: {error_msg}') return counter diff --git a/21-futures/getflags/flags_asyncio.py b/21-futures/getflags/flags_asyncio.py index 697307d..60bb755 100755 --- a/21-futures/getflags/flags_asyncio.py +++ b/21-futures/getflags/flags_asyncio.py @@ -18,7 +18,7 @@ from flags import BASE_URL, save_flag, main # <2> -async def get_flag(session: ClientSession ,cc: str) -> bytes: # <3> +async def get_flag(session: ClientSession, cc: str) -> bytes: # <3> cc = cc.lower() url = f'{BASE_URL}/{cc}/{cc}.gif' async with session.get(url) as resp: # <4> @@ -36,7 +36,6 @@ async def supervisor(cc_list): to_do = [download_one(session, cc) for cc in sorted(cc_list)] # <8> res = await asyncio.gather(*to_do) # <9> - return len(res) def download_many(cc_list): diff --git a/21-futures/getflags/flags_threadpool.py b/21-futures/getflags/flags_threadpool.py index 1d01491..4fdef0e 100755 --- a/21-futures/getflags/flags_threadpool.py +++ b/21-futures/getflags/flags_threadpool.py @@ -31,4 +31,4 @@ def download_many(cc_list: list[str]) -> int: if __name__ == '__main__': main(download_many) # <6> -# end::FLAGS_THREADPOOL[] \ No newline at end of file +# end::FLAGS_THREADPOOL[] diff --git a/21-futures/getflags/flags_threadpool_futures.py b/21-futures/getflags/flags_threadpool_futures.py index 49ee1e5..c06f260 100755 --- a/21-futures/getflags/flags_threadpool_futures.py +++ b/21-futures/getflags/flags_threadpool_futures.py @@ -6,7 +6,7 @@ """ from concurrent import futures -from flags import save_flag, main +from flags import main from flags_threadpool import download_one @@ -18,19 +18,14 @@ def download_many(cc_list: list[str]) -> int: for cc in sorted(cc_list): # <3> future = executor.submit(download_one, cc) # <4> to_do.append(future) # <5> - msg = 'Scheduled for {}: {}' - print(msg.format(cc, future)) # <6> + print(f'Scheduled for {cc}: {future}') # <6> - count = 0 - for future in futures.as_completed(to_do): # <7> + for count, future in enumerate(futures.as_completed(to_do)): # <7> res: str = future.result() # <8> - msg = '{} result: {!r}' - print(msg.format(future, res)) # <9> - count += 1 + print(f'{future} result: {res!r}') # <9> return count # end::FLAGS_THREADPOOL_AS_COMPLETED[] if __name__ == '__main__': main(download_many) - diff --git a/21-futures/getflags/slow_server.py b/21-futures/getflags/slow_server.py index 9c8b95c..25587a0 100755 --- a/21-futures/getflags/slow_server.py +++ b/21-futures/getflags/slow_server.py @@ -8,14 +8,14 @@ the requests if given the --error_rate command-line argument. """ -import time +import contextlib import os import socket -import contextlib +import time from functools import partial -from random import random from http import server, HTTPStatus from http.server import ThreadingHTTPServer, SimpleHTTPRequestHandler +from random import random class SlowHTTPRequestHandler(SimpleHTTPRequestHandler): @@ -52,7 +52,6 @@ def do_GET(self): # https://github.com/python/cpython/blob/master/Lib/http/server.py if __name__ == '__main__': - import argparse parser = argparse.ArgumentParser() @@ -61,11 +60,11 @@ def do_GET(self): '[default: all interfaces]') parser.add_argument('--directory', '-d', default=os.getcwd(), help='Specify alternative directory ' - '[default:current directory]') + '[default:current directory]') parser.add_argument('--error-rate', '-e', metavar='PROBABILITY', default=0.0, type=float, help='Error rate; e.g. use .25 for 25%% probability ' - '[default:0.0]') + '[default:0.0]') parser.add_argument('port', action='store', default=8000, type=int, nargs='?', diff --git a/21-futures/primes/proc_pool.py b/21-futures/primes/proc_pool.py index be9fa85..b184ede 100755 --- a/21-futures/primes/proc_pool.py +++ b/21-futures/primes/proc_pool.py @@ -6,10 +6,10 @@ """ # tag::PRIMES_POOL[] +import sys +from concurrent import futures # <1> from time import perf_counter from typing import NamedTuple -from concurrent import futures # <1> -import sys from primes import is_prime, NUMBERS @@ -43,8 +43,8 @@ def main() -> None: print(f'{n:16} {label} {elapsed:9.6f}s') time = perf_counter() - t0 - print('Total time:', f'{time:0.2f}s') + print(f'Total time: {time:.2f}s') if __name__ == '__main__': main() -# end::PRIMES_POOL[] \ No newline at end of file +# end::PRIMES_POOL[] From 1a0da424e2ceb2e8b5fc195e80643439be61bff5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= <6774676+eumiro@users.noreply.github.com> Date: Sat, 13 Feb 2021 19:35:34 +0100 Subject: [PATCH 016/166] ch20 cleanup and f-strings --- 20-concurrency/primes/primes.py | 1 - 20-concurrency/primes/procs.py | 6 +++--- 20-concurrency/primes/procs_py37.py | 6 +++--- 20-concurrency/primes/sequential.py | 2 +- 20-concurrency/primes/spinner_prime_async_broken.py | 2 +- 20-concurrency/primes/spinner_prime_async_nap.py | 2 +- 20-concurrency/primes/spinner_prime_proc.py | 4 ++-- 20-concurrency/primes/spinner_prime_thread.py | 4 ++-- 20-concurrency/primes/threads.py | 10 +++++----- 20-concurrency/primes/threads_py37.py | 8 ++++---- 20-concurrency/spinner_async.py | 4 ++-- 20-concurrency/spinner_async_experiment.py | 4 ++-- 20-concurrency/spinner_proc.py | 8 ++++---- 20-concurrency/spinner_thread.py | 9 ++++----- 14 files changed, 34 insertions(+), 36 deletions(-) diff --git a/20-concurrency/primes/primes.py b/20-concurrency/primes/primes.py index 989989d..bb91771 100755 --- a/20-concurrency/primes/primes.py +++ b/20-concurrency/primes/primes.py @@ -2,7 +2,6 @@ import math - PRIME_FIXTURE = [ (2, True), (142702110479723, True), diff --git a/20-concurrency/primes/procs.py b/20-concurrency/primes/procs.py index 9c470d8..fc84049 100644 --- a/20-concurrency/primes/procs.py +++ b/20-concurrency/primes/procs.py @@ -6,11 +6,11 @@ """ # tag::PRIMES_PROC_TOP[] +import sys from time import perf_counter from typing import NamedTuple from multiprocessing import Process, SimpleQueue, cpu_count # <1> from multiprocessing import queues # <2> -import sys from primes import is_prime, NUMBERS @@ -60,8 +60,8 @@ def main() -> None: if jobs.empty(): # <9> break - time = perf_counter() - t0 - print('Total time:', f'{time:0.2f}s') + elapsed = perf_counter() - t0 + print(f'Total time: {elapsed:.2f}s') if __name__ == '__main__': main() diff --git a/20-concurrency/primes/procs_py37.py b/20-concurrency/primes/procs_py37.py index 19d86e7..1810aec 100644 --- a/20-concurrency/primes/procs_py37.py +++ b/20-concurrency/primes/procs_py37.py @@ -1,6 +1,6 @@ # tag::PRIMES_PROC_TOP[] from time import perf_counter -from typing import Tuple, List, NamedTuple +from typing import List, NamedTuple from multiprocessing import Process, SimpleQueue # <1> from primes import is_prime, NUMBERS @@ -34,8 +34,8 @@ def main() -> None: label = 'P' if prime else ' ' print(f'{n:16} {label} {elapsed:9.6f}s') - time = perf_counter() - t0 - print('Total time:', f'{time:0.2f}s') + elapsed = perf_counter() - t0 + print(f'Total time: {elapsed:.2f}s') if __name__ == '__main__': diff --git a/20-concurrency/primes/sequential.py b/20-concurrency/primes/sequential.py index 4467528..6d14fb8 100644 --- a/20-concurrency/primes/sequential.py +++ b/20-concurrency/primes/sequential.py @@ -28,7 +28,7 @@ def main() -> None: print(f'{n:16} {label} {elapsed:9.6f}s') elapsed = perf_counter() - t0 # <4> - print('Total time:', f'{elapsed:0.2f}s') + print(f'Total time: {elapsed:.2f}s') if __name__ == '__main__': main() diff --git a/20-concurrency/primes/spinner_prime_async_broken.py b/20-concurrency/primes/spinner_prime_async_broken.py index 2290ece..4f6ad06 100644 --- a/20-concurrency/primes/spinner_prime_async_broken.py +++ b/20-concurrency/primes/spinner_prime_async_broken.py @@ -24,7 +24,7 @@ async def check(n: int) -> int: async def supervisor(n: int) -> int: spinner = asyncio.create_task(spin('thinking!')) # <1> - print('spinner object:', spinner) # <2> + print(f'spinner object: {spinner}') # <2> result = await check(n) # <3> spinner.cancel() # <5> return result diff --git a/20-concurrency/primes/spinner_prime_async_nap.py b/20-concurrency/primes/spinner_prime_async_nap.py index 4d6fa47..55b8574 100644 --- a/20-concurrency/primes/spinner_prime_async_nap.py +++ b/20-concurrency/primes/spinner_prime_async_nap.py @@ -43,7 +43,7 @@ async def check(n: int) -> int: async def supervisor(n: int) -> int: spinner = asyncio.create_task(spin('thinking!')) # <1> - print('spinner object:', spinner) # <2> + print(f'spinner object: {spinner}') # <2> result = await check(n) # <3> spinner.cancel() # <5> return result diff --git a/20-concurrency/primes/spinner_prime_proc.py b/20-concurrency/primes/spinner_prime_proc.py index 8a532af..799078c 100644 --- a/20-concurrency/primes/spinner_prime_proc.py +++ b/20-concurrency/primes/spinner_prime_proc.py @@ -4,9 +4,9 @@ # multiprocessing example in the python-list: # https://mail.python.org/pipermail/python-list/2009-February/675659.html +import itertools from multiprocessing import Process, Event from multiprocessing import synchronize -import itertools from primes import is_prime @@ -26,7 +26,7 @@ def supervisor(n: int) -> int: # <1> done = Event() # <2> spinner = Process(target=spin, args=('thinking!', done)) # <3> - print('spinner object:', spinner) # <4> + print(f'spinner object: {spinner}') # <4> spinner.start() # <5> result = check(n) # <6> done.set() # <7> diff --git a/20-concurrency/primes/spinner_prime_thread.py b/20-concurrency/primes/spinner_prime_thread.py index 000743e..6ff9c1a 100644 --- a/20-concurrency/primes/spinner_prime_thread.py +++ b/20-concurrency/primes/spinner_prime_thread.py @@ -4,8 +4,8 @@ # multiprocessing example in the python-list: # https://mail.python.org/pipermail/python-list/2009-February/675659.html -from threading import Thread, Event import itertools +from threading import Thread, Event from primes import is_prime @@ -25,7 +25,7 @@ def supervisor(n: int) -> int: # <1> done = Event() # <2> spinner = Thread(target=spin, args=('thinking!', done)) # <3> - print('spinner object:', spinner) # <4> + print(f'spinner object: {spinner}') # <4> spinner.start() # <5> result = check(n) # <6> done.set() # <7> diff --git a/20-concurrency/primes/threads.py b/20-concurrency/primes/threads.py index ff7ac50..0161dd7 100644 --- a/20-concurrency/primes/threads.py +++ b/20-concurrency/primes/threads.py @@ -5,12 +5,12 @@ sequential code for CPU-intensive work. """ +import os +import sys +from queue import SimpleQueue from time import perf_counter from typing import NamedTuple from threading import Thread -from queue import SimpleQueue -import sys -import os from primes import is_prime, NUMBERS @@ -58,8 +58,8 @@ def main() -> None: if jobs.empty(): # <8> break - time = perf_counter() - t0 - print('Total time:', f'{time:0.2f}s') + elapsed = perf_counter() - t0 + print(f'Total time: {elapsed:.2f}s') if __name__ == '__main__': main() diff --git a/20-concurrency/primes/threads_py37.py b/20-concurrency/primes/threads_py37.py index d98aca0..61d70c9 100644 --- a/20-concurrency/primes/threads_py37.py +++ b/20-concurrency/primes/threads_py37.py @@ -1,7 +1,7 @@ +from queue import SimpleQueue from time import perf_counter -from typing import List, NamedTuple from threading import Thread -from queue import SimpleQueue +from typing import List, NamedTuple from primes import is_prime, NUMBERS @@ -32,8 +32,8 @@ def main() -> None: label = 'P' if prime else ' ' print(f'{n:16} {label} {elapsed:9.6f}s') - time = perf_counter() - t0 - print('Total time:', f'{time:0.2f}s') + elapsed = perf_counter() - t0 + print(f'Total time: {elapsed:.2f}s') if __name__ == '__main__': main() diff --git a/20-concurrency/spinner_async.py b/20-concurrency/spinner_async.py index 5bc5028..bf1f923 100644 --- a/20-concurrency/spinner_async.py +++ b/20-concurrency/spinner_async.py @@ -27,11 +27,11 @@ async def slow() -> int: # tag::SPINNER_ASYNC_START[] def main() -> None: # <1> result = asyncio.run(supervisor()) # <2> - print('Answer:', result) + print(f'Answer: {result}') async def supervisor() -> int: # <3> spinner = asyncio.create_task(spin('thinking!')) # <4> - print('spinner object:', spinner) # <5> + print(f'spinner object: {spinner}') # <5> result = await slow() # <6> spinner.cancel() # <7> return result diff --git a/20-concurrency/spinner_async_experiment.py b/20-concurrency/spinner_async_experiment.py index 6d8bd3f..d55e21f 100644 --- a/20-concurrency/spinner_async_experiment.py +++ b/20-concurrency/spinner_async_experiment.py @@ -25,7 +25,7 @@ async def slow() -> int: async def supervisor() -> int: spinner = asyncio.create_task(spin('thinking!')) # <1> - print('spinner object:', spinner) # <2> + print(f'spinner object: {spinner}') # <2> result = await slow() # <3> spinner.cancel() # <5> return result @@ -33,7 +33,7 @@ async def supervisor() -> int: def main() -> None: result = asyncio.run(supervisor()) - print('Answer:', result) + print(f'Answer: {result}') if __name__ == '__main__': main() diff --git a/20-concurrency/spinner_proc.py b/20-concurrency/spinner_proc.py index 5cc3067..b6f9334 100644 --- a/20-concurrency/spinner_proc.py +++ b/20-concurrency/spinner_proc.py @@ -5,10 +5,10 @@ # https://mail.python.org/pipermail/python-list/2009-February/675659.html # tag::SPINNER_PROC_IMPORTS[] -from multiprocessing import Process, Event # <1> -from multiprocessing import synchronize # <2> import itertools import time +from multiprocessing import Process, Event # <1> +from multiprocessing import synchronize # <2> def spin(msg: str, done: synchronize.Event) -> None: # <3> # end::SPINNER_PROC_IMPORTS[] @@ -29,7 +29,7 @@ def supervisor() -> int: done = Event() spinner = Process(target=spin, # <4> args=('thinking!', done)) - print('spinner object:', spinner) # <5> + print(f'spinner object: {spinner}') # <5> spinner.start() result = slow() done.set() @@ -39,7 +39,7 @@ def supervisor() -> int: def main() -> None: result = supervisor() - print('Answer:', result) + print(f'Answer: {result}') if __name__ == '__main__': diff --git a/20-concurrency/spinner_thread.py b/20-concurrency/spinner_thread.py index 8722c51..b642194 100644 --- a/20-concurrency/spinner_thread.py +++ b/20-concurrency/spinner_thread.py @@ -5,9 +5,9 @@ # https://mail.python.org/pipermail/python-list/2009-February/675659.html # tag::SPINNER_THREAD_TOP[] -from threading import Thread, Event import itertools import time +from threading import Thread, Event def spin(msg: str, done: Event) -> None: # <1> for char in itertools.cycle(r'\|/-'): # <2> @@ -26,9 +26,8 @@ def slow() -> int: # tag::SPINNER_THREAD_REST[] def supervisor() -> int: # <1> done = Event() # <2> - spinner = Thread(target=spin, - args=('thinking!', done)) # <3> - print('spinner object:', spinner) # <4> + spinner = Thread(target=spin, args=('thinking!', done)) # <3> + print(f'spinner object: {spinner}') # <4> spinner.start() # <5> result = slow() # <6> done.set() # <7> @@ -37,7 +36,7 @@ def supervisor() -> int: # <1> def main() -> None: result = supervisor() # <9> - print('Answer:', result) + print(f'Answer: {result}') if __name__ == '__main__': main() From 03ace4f4ae86f4b661a6ab227bef9319588ba07c Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Sun, 14 Feb 2021 20:28:07 -0300 Subject: [PATCH 017/166] ch01-12: clean up by @eumiro --- 01-data-model/vector2d.doctest | 12 + 01-data-model/vector2d.py | 4 +- 02-array-seq/.gitignore | 3 - 02-array-seq/README.md | 21 -- 02-array-seq/README.rst | 4 + 02-array-seq/bisect_demo.py | 4 +- 02-array-seq/bisect_insort.py | 2 +- 02-array-seq/memoryviews.ipynb | 238 +++++++++++++++ 02-array-seq/slice-assign.rst | 11 - 03-dict-set/index0.py | 2 +- 03-dict-set/index_default.py | 2 +- 03-dict-set/transformdict.py | 2 +- 04-text-byte/categories.py | 45 +++ 04-text-byte/{sanitize.py => simplify.py} | 22 +- 05-record-like/dataclass/club_generic.py | 4 +- .../dataclass/hackerclub_annotated.py | 4 +- 05-record-like/dataclass/resource.py | 10 +- 05-record-like/dataclass/resource_repr.py | 10 +- 08-def-type-hints/clip_annot_1ed.py | 2 +- 08-def-type-hints/comparable/top.py | 5 +- 08-def-type-hints/coordinates/coordinates.py | 6 +- 08-def-type-hints/mode/mode_T.py | 4 +- .../{fibo_demo_lru.py => fibo_demo_cache.py} | 4 +- 10-dp-1class-func/pytypes/classic_strategy.py | 3 +- 11-pythonic-obj/private/expose.py | 5 +- 11-pythonic-obj/private/leakprivate.py | 7 +- 11-pythonic-obj/private/no_respect.py | 5 +- 11-pythonic-obj/vector2d_v3_slots.py | 2 - 12-seq-hacking/vector_v1.py | 126 ++++++++ 12-seq-hacking/vector_v2.py | 164 ++++++++++ 12-seq-hacking/vector_v3.py | 235 +++++++++++++++ 12-seq-hacking/vector_v4.py | 217 +++++++++++++ 12-seq-hacking/vector_v5.py | 284 ++++++++++++++++++ 33 files changed, 1383 insertions(+), 86 deletions(-) create mode 100644 01-data-model/vector2d.doctest delete mode 100644 02-array-seq/.gitignore delete mode 100644 02-array-seq/README.md create mode 100644 02-array-seq/README.rst create mode 100644 02-array-seq/memoryviews.ipynb delete mode 100644 02-array-seq/slice-assign.rst create mode 100644 04-text-byte/categories.py rename 04-text-byte/{sanitize.py => simplify.py} (86%) rename 09-closure-deco/{fibo_demo_lru.py => fibo_demo_cache.py} (68%) create mode 100644 12-seq-hacking/vector_v1.py create mode 100644 12-seq-hacking/vector_v2.py create mode 100644 12-seq-hacking/vector_v3.py create mode 100644 12-seq-hacking/vector_v4.py create mode 100644 12-seq-hacking/vector_v5.py diff --git a/01-data-model/vector2d.doctest b/01-data-model/vector2d.doctest new file mode 100644 index 0000000..ec506b2 --- /dev/null +++ b/01-data-model/vector2d.doctest @@ -0,0 +1,12 @@ +>>> from vector2d import Vector +>>> v1 = Vector(2, 4) +>>> v2 = Vector(2, 1) +>>> v1 + v2 +Vector(4, 5) +>>> v = Vector(3, 4) +>>> abs(v) +5.0 +>>> v * 3 +Vector(9, 12) +>>> abs(v * 3) +15.0 diff --git a/01-data-model/vector2d.py b/01-data-model/vector2d.py index 557772b..32b2a9f 100644 --- a/01-data-model/vector2d.py +++ b/01-data-model/vector2d.py @@ -29,7 +29,7 @@ """ -from math import hypot +import math class Vector: @@ -41,7 +41,7 @@ def __repr__(self): return f'Vector({self.x!r}, {self.y!r})' def __abs__(self): - return hypot(self.x, self.y) + return math.hypot(self.x, self.y) def __bool__(self): return bool(abs(self)) diff --git a/02-array-seq/.gitignore b/02-array-seq/.gitignore deleted file mode 100644 index ab5fce6..0000000 --- a/02-array-seq/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -floats-*.txt -floats-*.npy -floats.bin diff --git a/02-array-seq/README.md b/02-array-seq/README.md deleted file mode 100644 index f2dc9f7..0000000 --- a/02-array-seq/README.md +++ /dev/null @@ -1,21 +0,0 @@ -# An Array of Sequences - -Sample code for Chapter 2 of _Fluent Python 2e_ by Luciano Ramalho (O'Reilly, 2020) - -## Running the tests - -### Doctests - -Use Python's standard ``doctest`` module, for example: - - $ python3 -m doctest bisect_demo.py -v - -### Jupyter Notebook - -Install ``pytest`` and the ``nbval`` plugin: - - $ pip install pytest nbval - -Run: - - $ pytest --nbval \ No newline at end of file diff --git a/02-array-seq/README.rst b/02-array-seq/README.rst new file mode 100644 index 0000000..788195a --- /dev/null +++ b/02-array-seq/README.rst @@ -0,0 +1,4 @@ +Sample code for Chapter 2 - "An array of sequences" + +From the book "Fluent Python 2e" by Luciano Ramalho (O'Reilly) +http://shop.oreilly.com/product/0636920032519.do diff --git a/02-array-seq/bisect_demo.py b/02-array-seq/bisect_demo.py index d0ea6c2..40f68ea 100644 --- a/02-array-seq/bisect_demo.py +++ b/02-array-seq/bisect_demo.py @@ -36,7 +36,7 @@ """ -# BEGIN BISECT_DEMO +# tag::BISECT_DEMO[] import bisect import sys @@ -62,4 +62,4 @@ def demo(bisect_fn): print('haystack ->', ' '.join(f'{n:2}' for n in HAYSTACK)) demo(bisect_fn) -# END BISECT_DEMO +# end::BISECT_DEMO[] diff --git a/02-array-seq/bisect_insort.py b/02-array-seq/bisect_insort.py index 12277c7..177dca3 100644 --- a/02-array-seq/bisect_insort.py +++ b/02-array-seq/bisect_insort.py @@ -9,4 +9,4 @@ for i in range(SIZE): new_item = random.randrange(SIZE * 2) bisect.insort(my_list, new_item) - print(f'{new_item:2} ->', my_list) + print(f'{new_item:2d} -> {my_list}') diff --git a/02-array-seq/memoryviews.ipynb b/02-array-seq/memoryviews.ipynb new file mode 100644 index 0000000..860d474 --- /dev/null +++ b/02-array-seq/memoryviews.ipynb @@ -0,0 +1,238 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# memoryviews\n", + "\n", + "## References\n", + "\n", + "* [PEP-3118](https://www.python.org/dev/peps/pep-3118/) Revising the buffer protocol (2006)\n", + "* [issue14130](https://bugs.python.org/issue14130) memoryview: add multi-dimensional indexing and slicing (2012)" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "3.7.1 (default, Dec 14 2018, 13:28:58) \n", + "[Clang 4.0.1 (tags/RELEASE_401/final)]\n" + ] + } + ], + "source": [ + "import sys\n", + "print(sys.version)" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "mv1d = memoryview(bytes(range(35, 50)))\n", + "mv1d" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49]" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "list(mv1d)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "mv2d = mv1d.cast('B', [3, 5])\n", + "mv2d" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(3, 5)" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "mv2d.shape" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "3" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "len(mv2d)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[[35, 36, 37, 38, 39], [40, 41, 42, 43, 44], [45, 46, 47, 48, 49]]" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "mv2d.tolist()" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[35, 36, 37, 38, 39]\n", + "[40, 41, 42, 43, 44]\n", + "[45, 46, 47, 48, 49]\n" + ] + } + ], + "source": [ + "for row in mv2d.tolist():\n", + " print(row)" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "42" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "mv2d[1, 2]" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "42" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "mv2d.tolist()[1][2]" + ] + } + ], + "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.7.1" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/02-array-seq/slice-assign.rst b/02-array-seq/slice-assign.rst deleted file mode 100644 index 2d5f681..0000000 --- a/02-array-seq/slice-assign.rst +++ /dev/null @@ -1,11 +0,0 @@ -Assigning to Slices -=================== - -An example that raises an error:: - ->>> l = list(range(10)) ->>> l[2:5] = 100 -Traceback (most recent call last): - ... -TypeError: can only assign an iterable - diff --git a/03-dict-set/index0.py b/03-dict-set/index0.py index 8c49915..0bc9bf4 100644 --- a/03-dict-set/index0.py +++ b/03-dict-set/index0.py @@ -15,7 +15,7 @@ for line_no, line in enumerate(fp, 1): for match in WORD_RE.finditer(line): word = match.group() - column_no = match.start()+1 + column_no = match.start() + 1 location = (line_no, column_no) # this is ugly; coded like this to make a point occurrences = index.get(word, []) # <1> diff --git a/03-dict-set/index_default.py b/03-dict-set/index_default.py index 839ffd7..60279a9 100644 --- a/03-dict-set/index_default.py +++ b/03-dict-set/index_default.py @@ -16,7 +16,7 @@ for line_no, line in enumerate(fp, 1): for match in WORD_RE.finditer(line): word = match.group() - column_no = match.start() + 1 + column_no = match.start()+1 location = (line_no, column_no) index[word].append(location) # <2> diff --git a/03-dict-set/transformdict.py b/03-dict-set/transformdict.py index b0cc002..626b64b 100644 --- a/03-dict-set/transformdict.py +++ b/03-dict-set/transformdict.py @@ -56,7 +56,7 @@ def getitem(self, key): @property def transform_func(self): - """This TransformDict's transformation function""" + """This is TransformDict's transformation function""" return self._transform # Minimum set of methods required for MutableMapping diff --git a/04-text-byte/categories.py b/04-text-byte/categories.py new file mode 100644 index 0000000..a4cecbe --- /dev/null +++ b/04-text-byte/categories.py @@ -0,0 +1,45 @@ +import sys +import collections +from unicodedata import name, category + + +def category_stats(): + counts = collections.Counter() + firsts = {} + for code in range(sys.maxunicode + 1): + char = chr(code) + cat = category(char) + if cat not in counts: + firsts[cat] = char + counts[cat] += 1 + return counts, firsts + + +def category_scan(desired): + for code in range(sys.maxunicode + 1): + char = chr(code) + if category(char) == desired: + yield char + + +def main(args): + count = 0 + if len(args) == 2: + for char in category_scan(args[1]): + print(char, end=' ') + count += 1 + if count > 200: + break + print() + print(count, 'characters shown') + else: + counts, firsts = category_stats() + for cat, count in counts.most_common(): + first = firsts[cat] + if cat == 'Cs': + first = f'(surrogate U+{ord(first):04X})' + print(f'{count:6} {cat} {first}') + + +if __name__ == '__main__': + main(sys.argv) diff --git a/04-text-byte/sanitize.py b/04-text-byte/simplify.py similarity index 86% rename from 04-text-byte/sanitize.py rename to 04-text-byte/simplify.py index 15eb09a..ebbdcf8 100644 --- a/04-text-byte/sanitize.py +++ b/04-text-byte/simplify.py @@ -1,5 +1,6 @@ + """ -Radical folding and text sanitizing. +Radical folding and diacritic mark removal. Handling a string with `cp1252` symbols: @@ -45,30 +46,33 @@ def shave_marks_latin(txt): """Remove all diacritic marks from Latin base characters""" norm_txt = unicodedata.normalize('NFD', txt) # <1> latin_base = False - keepers = [] + preserve = [] for c in norm_txt: if unicodedata.combining(c) and latin_base: # <2> continue # ignore diacritic on Latin base char - keepers.append(c) # <3> + preserve.append(c) # <3> # if it isn't combining char, it's a new base char if not unicodedata.combining(c): # <4> latin_base = c in string.ascii_letters - shaved = ''.join(keepers) + shaved = ''.join(preserve) return unicodedata.normalize('NFC', shaved) # <5> # end::SHAVE_MARKS_LATIN[] # tag::ASCIIZE[] -single_map = str.maketrans("""‚ƒ„†ˆ‹‘’“”•–—˜›""", # <1> - """'f"*^<''""---~>""") +single_map = str.maketrans("""‚ƒ„ˆ‹‘’“”•–—˜›""", # <1> + """'f"^<''""---~>""") multi_map = str.maketrans({ # <2> - '€': '', + '€': 'EUR', '…': '...', + 'Æ': 'AE', + 'æ': 'ae', 'Œ': 'OE', - '™': '(TM)', 'œ': 'oe', + '™': '(TM)', '‰': '', - '‡': '**', + '†': '**', + '‡': '***', }) multi_map.update(single_map) # <3> diff --git a/05-record-like/dataclass/club_generic.py b/05-record-like/dataclass/club_generic.py index 41523f0..bc9456d 100644 --- a/05-record-like/dataclass/club_generic.py +++ b/05-record-like/dataclass/club_generic.py @@ -1,8 +1,6 @@ from dataclasses import dataclass, field -from typing import List # <1> @dataclass class ClubMember: - name: str - guests: List[str] = field(default_factory=list) # <2> + guests: list[str] = field(default_factory=list) # <1> diff --git a/05-record-like/dataclass/hackerclub_annotated.py b/05-record-like/dataclass/hackerclub_annotated.py index a760a55..5cf90fc 100644 --- a/05-record-like/dataclass/hackerclub_annotated.py +++ b/05-record-like/dataclass/hackerclub_annotated.py @@ -30,13 +30,13 @@ # tag::HACKERCLUB[] from dataclasses import dataclass -from typing import ClassVar, Set +from typing import ClassVar from club import ClubMember @dataclass class HackerClubMember(ClubMember): - all_handles: ClassVar[Set[str]] = set() + all_handles: ClassVar[set[str]] = set() handle: str = '' diff --git a/05-record-like/dataclass/resource.py b/05-record-like/dataclass/resource.py index 90bfaf0..5190055 100644 --- a/05-record-like/dataclass/resource.py +++ b/05-record-like/dataclass/resource.py @@ -27,7 +27,7 @@ # tag::DATACLASS[] from dataclasses import dataclass, field -from typing import List, Optional +from typing import Optional from enum import Enum, auto from datetime import date @@ -43,12 +43,12 @@ class Resource: """Media resource description.""" identifier: str # <2> title: str = '' # <3> - creators: List[str] = field(default_factory=list) + creators: list[str] = field(default_factory=list) date: Optional[date] = None # <4> type: ResourceType = ResourceType.BOOK # <5> description: str = '' language: str = '' - subjects: List[str] = field(default_factory=list) + subjects: list[str] = field(default_factory=list) # end::DATACLASS[] @@ -58,12 +58,12 @@ class Resource: class ResourceDict(TypedDict): identifier: str title: str - creators: List[str] + creators: list[str] date: Optional[date] type: ResourceType description: str language: str - subjects: List[str] + subjects: list[str] if __name__ == '__main__': diff --git a/05-record-like/dataclass/resource_repr.py b/05-record-like/dataclass/resource_repr.py index e32c88d..dcbfaea 100644 --- a/05-record-like/dataclass/resource_repr.py +++ b/05-record-like/dataclass/resource_repr.py @@ -41,7 +41,7 @@ """ from dataclasses import dataclass, field, fields -from typing import List, Optional, TypedDict +from typing import Optional, TypedDict from enum import Enum, auto from datetime import date @@ -57,12 +57,12 @@ class Resource: """Media resource description.""" identifier: str title: str = '' - creators: List[str] = field(default_factory=list) + creators: list[str] = field(default_factory=list) date: Optional[date] = None type: ResourceType = ResourceType.BOOK description: str = '' language: str = '' - subjects: List[str] = field(default_factory=list) + subjects: list[str] = field(default_factory=list) # tag::REPR[] def __repr__(self): @@ -82,12 +82,12 @@ def __repr__(self): class ResourceDict(TypedDict): identifier: str title: str - creators: List[str] + creators: list[str] date: Optional[date] type: ResourceType description: str language: str - subjects: List[str] + subjects: list[str] if __name__ == '__main__': diff --git a/08-def-type-hints/clip_annot_1ed.py b/08-def-type-hints/clip_annot_1ed.py index 4c49239..eedd954 100644 --- a/08-def-type-hints/clip_annot_1ed.py +++ b/08-def-type-hints/clip_annot_1ed.py @@ -19,7 +19,7 @@ # tag::CLIP_ANNOT[] -def clip(text: str, max_len: 'int > 0'=80) -> str: # <1> +def clip(text: str, max_len: 'int > 0' = 80) -> str: # <1> """Return text clipped at the last space before or after max_len """ end = None diff --git a/08-def-type-hints/comparable/top.py b/08-def-type-hints/comparable/top.py index c751635..320b696 100644 --- a/08-def-type-hints/comparable/top.py +++ b/08-def-type-hints/comparable/top.py @@ -26,6 +26,5 @@ CT = TypeVar('CT', bound=Comparable) def top(series: Iterable[CT], length: int) -> List[CT]: - ordered = sorted(series, reverse=True) - return ordered[:length] -# end::TOP[] \ No newline at end of file + return sorted(series, reverse=True)[:length] +# end::TOP[] diff --git a/08-def-type-hints/coordinates/coordinates.py b/08-def-type-hints/coordinates/coordinates.py index ca64d79..d48b390 100644 --- a/08-def-type-hints/coordinates/coordinates.py +++ b/08-def-type-hints/coordinates/coordinates.py @@ -8,12 +8,10 @@ """ # tag::GEOHASH[] -from typing import Tuple - from geolib import geohash as gh # type: ignore PRECISION = 9 -def geohash(lat_lon: Tuple[float, float]) -> str: +def geohash(lat_lon: tuple[float, float]) -> str: return gh.encode(*lat_lon, PRECISION) -# end::GEOHASH[] \ No newline at end of file +# end::GEOHASH[] diff --git a/08-def-type-hints/mode/mode_T.py b/08-def-type-hints/mode/mode_T.py index 46953ee..ca26d7b 100644 --- a/08-def-type-hints/mode/mode_T.py +++ b/08-def-type-hints/mode/mode_T.py @@ -13,7 +13,7 @@ def mode(data: Iterable[T]) -> T: def demo() -> None: from typing import List, Set, TYPE_CHECKING - pop: List[Set] = [set(), set()] + pop: list[set] = [set(), set()] m = mode(pop) if TYPE_CHECKING: reveal_type(pop) @@ -22,4 +22,4 @@ def demo() -> None: print(repr(m), type(m)) if __name__ == '__main__': - demo() \ No newline at end of file + demo() diff --git a/09-closure-deco/fibo_demo_lru.py b/09-closure-deco/fibo_demo_cache.py similarity index 68% rename from 09-closure-deco/fibo_demo_lru.py rename to 09-closure-deco/fibo_demo_cache.py index 90936f9..5e92ec6 100644 --- a/09-closure-deco/fibo_demo_lru.py +++ b/09-closure-deco/fibo_demo_cache.py @@ -3,12 +3,12 @@ from clockdeco import clock -@functools.lru_cache # <1> +@functools.cache # <1> @clock # <2> def fibonacci(n): if n < 2: return n - return fibonacci(n - 2) + fibonacci(n - 1) + return fibonacci(n-2) + fibonacci(n-1) if __name__ == '__main__': diff --git a/10-dp-1class-func/pytypes/classic_strategy.py b/10-dp-1class-func/pytypes/classic_strategy.py index 4cc833f..d929ffa 100644 --- a/10-dp-1class-func/pytypes/classic_strategy.py +++ b/10-dp-1class-func/pytypes/classic_strategy.py @@ -70,7 +70,8 @@ def due(self): return self.total() - discount def __repr__(self): - return f'' + fmt = '' + return fmt.format(self.total(), self.due()) @typelogged diff --git a/11-pythonic-obj/private/expose.py b/11-pythonic-obj/private/expose.py index 094acbd..1db9612 100644 --- a/11-pythonic-obj/private/expose.py +++ b/11-pythonic-obj/private/expose.py @@ -1,6 +1,9 @@ +#!/usr/bin/env jython +# NOTE: Jython is still Python 2.7 in late2020 + import Confidential message = Confidential('top secret text') secret_field = Confidential.getDeclaredField('secret') secret_field.setAccessible(True) # break the lock! -print('message.secret =', secret_field.get(message)) +print 'message.secret =', secret_field.get(message) diff --git a/11-pythonic-obj/private/leakprivate.py b/11-pythonic-obj/private/leakprivate.py index 5e4ecbd..572ef77 100644 --- a/11-pythonic-obj/private/leakprivate.py +++ b/11-pythonic-obj/private/leakprivate.py @@ -1,3 +1,6 @@ +#!/usr/bin/env jython +# NOTE: Jython is still Python 2.7 in late2020 + from java.lang.reflect import Modifier import Confidential @@ -7,5 +10,5 @@ # list private fields only if Modifier.isPrivate(field.getModifiers()): field.setAccessible(True) # break the lock - print('field:', field) - print('\t', field.getName(), '=', field.get(message)) + print 'field:', field + print '\t', field.getName(), '=', field.get(message) diff --git a/11-pythonic-obj/private/no_respect.py b/11-pythonic-obj/private/no_respect.py index 291eed8..39ae7eb 100644 --- a/11-pythonic-obj/private/no_respect.py +++ b/11-pythonic-obj/private/no_respect.py @@ -1,3 +1,6 @@ +#!/usr/bin/env jython +# NOTE: Jython is still Python 2.7 in late2020 + """ In the Jython registry file there is this line: @@ -13,4 +16,4 @@ for name in dir(message): attr = getattr(message, name) if not callable(attr): # non-methods only - print(name + '\t=', attr) + print name + '\t=', attr diff --git a/11-pythonic-obj/vector2d_v3_slots.py b/11-pythonic-obj/vector2d_v3_slots.py index 1d0536a..20b6da4 100644 --- a/11-pythonic-obj/vector2d_v3_slots.py +++ b/11-pythonic-obj/vector2d_v3_slots.py @@ -83,8 +83,6 @@ >>> len({v1, v2}) 2 -# end::VECTOR2D_V3_DEMO[] - """ from array import array diff --git a/12-seq-hacking/vector_v1.py b/12-seq-hacking/vector_v1.py new file mode 100644 index 0000000..5f75c04 --- /dev/null +++ b/12-seq-hacking/vector_v1.py @@ -0,0 +1,126 @@ +""" +A multi-dimensional ``Vector`` class, take 1 + +A ``Vector`` is built from an iterable of numbers:: + + >>> Vector([3.1, 4.2]) + Vector([3.1, 4.2]) + >>> Vector((3, 4, 5)) + Vector([3.0, 4.0, 5.0]) + >>> Vector(range(10)) + Vector([0.0, 1.0, 2.0, 3.0, 4.0, ...]) + + +Tests with 2-dimensions (same results as ``vector2d_v1.py``):: + + >>> v1 = Vector([3, 4]) + >>> x, y = v1 + >>> x, y + (3.0, 4.0) + >>> v1 + Vector([3.0, 4.0]) + >>> v1_clone = eval(repr(v1)) + >>> v1 == v1_clone + True + >>> print(v1) + (3.0, 4.0) + >>> octets = bytes(v1) + >>> octets + b'd\\x00\\x00\\x00\\x00\\x00\\x00\\x08@\\x00\\x00\\x00\\x00\\x00\\x00\\x10@' + >>> abs(v1) + 5.0 + >>> bool(v1), bool(Vector([0, 0])) + (True, False) + + +Test of ``.frombytes()`` class method: + + >>> v1_clone = Vector.frombytes(bytes(v1)) + >>> v1_clone + Vector([3.0, 4.0]) + >>> v1 == v1_clone + True + + +Tests with 3-dimensions:: + + >>> v1 = Vector([3, 4, 5]) + >>> x, y, z = v1 + >>> x, y, z + (3.0, 4.0, 5.0) + >>> v1 + Vector([3.0, 4.0, 5.0]) + >>> v1_clone = eval(repr(v1)) + >>> v1 == v1_clone + True + >>> print(v1) + (3.0, 4.0, 5.0) + >>> abs(v1) # doctest:+ELLIPSIS + 7.071067811... + >>> bool(v1), bool(Vector([0, 0, 0])) + (True, False) + + +Tests with many dimensions:: + + >>> v7 = Vector(range(7)) + >>> v7 + Vector([0.0, 1.0, 2.0, 3.0, 4.0, ...]) + >>> abs(v7) # doctest:+ELLIPSIS + 9.53939201... + + +Test of ``.__bytes__`` and ``.frombytes()`` methods:: + + >>> v1 = Vector([3, 4, 5]) + >>> v1_clone = Vector.frombytes(bytes(v1)) + >>> v1_clone + Vector([3.0, 4.0, 5.0]) + >>> v1 == v1_clone + True + + +""" + +# tag::VECTOR_V1[] +from array import array +import reprlib +import math + + +class Vector: + typecode = 'd' + + def __init__(self, components): + self._components = array(self.typecode, components) # <1> + + def __iter__(self): + return iter(self._components) # <2> + + def __repr__(self): + components = reprlib.repr(self._components) # <3> + components = components[components.find('['):-1] # <4> + return 'Vector({})'.format(components) + + def __str__(self): + return str(tuple(self)) + + def __bytes__(self): + return (bytes([ord(self.typecode)]) + + bytes(self._components)) # <5> + + def __eq__(self, other): + return tuple(self) == tuple(other) + + def __abs__(self): + return math.sqrt(sum(x * x for x in self)) # <6> + + def __bool__(self): + return bool(abs(self)) + + @classmethod + def frombytes(cls, octets): + typecode = chr(octets[0]) + memv = memoryview(octets[1:]).cast(typecode) + return cls(memv) # <7> +# end::VECTOR_V1[] diff --git a/12-seq-hacking/vector_v2.py b/12-seq-hacking/vector_v2.py new file mode 100644 index 0000000..23d98ee --- /dev/null +++ b/12-seq-hacking/vector_v2.py @@ -0,0 +1,164 @@ +""" +A multi-dimensional ``Vector`` class, take 2 + +A ``Vector`` is built from an iterable of numbers:: + + >>> Vector([3.1, 4.2]) + Vector([3.1, 4.2]) + >>> Vector((3, 4, 5)) + Vector([3.0, 4.0, 5.0]) + >>> Vector(range(10)) + Vector([0.0, 1.0, 2.0, 3.0, 4.0, ...]) + + +Tests with 2-dimensions (same results as ``vector2d_v1.py``):: + + >>> v1 = Vector([3, 4]) + >>> x, y = v1 + >>> x, y + (3.0, 4.0) + >>> v1 + Vector([3.0, 4.0]) + >>> v1_clone = eval(repr(v1)) + >>> v1 == v1_clone + True + >>> print(v1) + (3.0, 4.0) + >>> octets = bytes(v1) + >>> octets + b'd\\x00\\x00\\x00\\x00\\x00\\x00\\x08@\\x00\\x00\\x00\\x00\\x00\\x00\\x10@' + >>> abs(v1) + 5.0 + >>> bool(v1), bool(Vector([0, 0])) + (True, False) + + +Test of ``.frombytes()`` class method: + + >>> v1_clone = Vector.frombytes(bytes(v1)) + >>> v1_clone + Vector([3.0, 4.0]) + >>> v1 == v1_clone + True + + +Tests with 3-dimensions:: + + >>> v1 = Vector([3, 4, 5]) + >>> x, y, z = v1 + >>> x, y, z + (3.0, 4.0, 5.0) + >>> v1 + Vector([3.0, 4.0, 5.0]) + >>> v1_clone = eval(repr(v1)) + >>> v1 == v1_clone + True + >>> print(v1) + (3.0, 4.0, 5.0) + >>> abs(v1) # doctest:+ELLIPSIS + 7.071067811... + >>> bool(v1), bool(Vector([0, 0, 0])) + (True, False) + + +Tests with many dimensions:: + + >>> v7 = Vector(range(7)) + >>> v7 + Vector([0.0, 1.0, 2.0, 3.0, 4.0, ...]) + >>> abs(v7) # doctest:+ELLIPSIS + 9.53939201... + + +Test of ``.__bytes__`` and ``.frombytes()`` methods:: + + >>> v1 = Vector([3, 4, 5]) + >>> v1_clone = Vector.frombytes(bytes(v1)) + >>> v1_clone + Vector([3.0, 4.0, 5.0]) + >>> v1 == v1_clone + True + + +Tests of sequence behavior:: + + >>> v1 = Vector([3, 4, 5]) + >>> len(v1) + 3 + >>> v1[0], v1[len(v1)-1], v1[-1] + (3.0, 5.0, 5.0) + + +Test of slicing:: + +# tag::VECTOR_V2_DEMO[] + + >>> v7 = Vector(range(7)) + >>> v7[-1] # <1> + 6.0 + >>> v7[1:4] # <2> + Vector([1.0, 2.0, 3.0]) + >>> v7[-1:] # <3> + Vector([6.0]) + >>> v7[1,2] # <4> + Traceback (most recent call last): + ... + TypeError: 'tuple' object cannot be interpreted as an integer + +# end::VECTOR_V2_DEMO[] + +""" + +from array import array +import reprlib +import math +import operator + + +class Vector: + typecode = 'd' + + def __init__(self, components): + self._components = array(self.typecode, components) + + def __iter__(self): + return iter(self._components) + + def __repr__(self): + components = reprlib.repr(self._components) + components = components[components.find('['):-1] + return 'Vector({})'.format(components) + + def __str__(self): + return str(tuple(self)) + + def __bytes__(self): + return (bytes([ord(self.typecode)]) + + bytes(self._components)) + + def __eq__(self, other): + return tuple(self) == tuple(other) + + def __abs__(self): + return math.sqrt(sum(x * x for x in self)) + + def __bool__(self): + return bool(abs(self)) + +# tag::VECTOR_V2[] + def __len__(self): + return len(self._components) + + def __getitem__(self, key): + if isinstance(key, slice): # <1> + cls = type(self) # <2> + return cls(self._components[key]) # <3> + index = operator.index(key) # <4> + return self._components[index] # <5> +# end::VECTOR_V2[] + + @classmethod + def frombytes(cls, octets): + typecode = chr(octets[0]) + memv = memoryview(octets[1:]).cast(typecode) + return cls(memv) diff --git a/12-seq-hacking/vector_v3.py b/12-seq-hacking/vector_v3.py new file mode 100644 index 0000000..ea4ec52 --- /dev/null +++ b/12-seq-hacking/vector_v3.py @@ -0,0 +1,235 @@ +""" +A multi-dimensional ``Vector`` class, take 3 + +A ``Vector`` is built from an iterable of numbers:: + + >>> Vector([3.1, 4.2]) + Vector([3.1, 4.2]) + >>> Vector((3, 4, 5)) + Vector([3.0, 4.0, 5.0]) + >>> Vector(range(10)) + Vector([0.0, 1.0, 2.0, 3.0, 4.0, ...]) + + +Tests with 2-dimensions (same results as ``vector2d_v1.py``):: + + >>> v1 = Vector([3, 4]) + >>> x, y = v1 + >>> x, y + (3.0, 4.0) + >>> v1 + Vector([3.0, 4.0]) + >>> v1_clone = eval(repr(v1)) + >>> v1 == v1_clone + True + >>> print(v1) + (3.0, 4.0) + >>> octets = bytes(v1) + >>> octets + b'd\\x00\\x00\\x00\\x00\\x00\\x00\\x08@\\x00\\x00\\x00\\x00\\x00\\x00\\x10@' + >>> abs(v1) + 5.0 + >>> bool(v1), bool(Vector([0, 0])) + (True, False) + + +Test of ``.frombytes()`` class method: + + >>> v1_clone = Vector.frombytes(bytes(v1)) + >>> v1_clone + Vector([3.0, 4.0]) + >>> v1 == v1_clone + True + + +Tests with 3-dimensions:: + + >>> v1 = Vector([3, 4, 5]) + >>> x, y, z = v1 + >>> x, y, z + (3.0, 4.0, 5.0) + >>> v1 + Vector([3.0, 4.0, 5.0]) + >>> v1_clone = eval(repr(v1)) + >>> v1 == v1_clone + True + >>> print(v1) + (3.0, 4.0, 5.0) + >>> abs(v1) # doctest:+ELLIPSIS + 7.071067811... + >>> bool(v1), bool(Vector([0, 0, 0])) + (True, False) + + +Tests with many dimensions:: + + >>> v7 = Vector(range(7)) + >>> v7 + Vector([0.0, 1.0, 2.0, 3.0, 4.0, ...]) + >>> abs(v7) # doctest:+ELLIPSIS + 9.53939201... + + +Test of ``.__bytes__`` and ``.frombytes()`` methods:: + + >>> v1 = Vector([3, 4, 5]) + >>> v1_clone = Vector.frombytes(bytes(v1)) + >>> v1_clone + Vector([3.0, 4.0, 5.0]) + >>> v1 == v1_clone + True + + +Tests of sequence behavior:: + + >>> v1 = Vector([3, 4, 5]) + >>> len(v1) + 3 + >>> v1[0], v1[len(v1)-1], v1[-1] + (3.0, 5.0, 5.0) + + +Test of slicing:: + + >>> v7 = Vector(range(7)) + >>> v7[-1] + 6.0 + >>> v7[1:4] + Vector([1.0, 2.0, 3.0]) + >>> v7[-1:] + Vector([6.0]) + >>> v7[1,2] + Traceback (most recent call last): + ... + TypeError: 'tuple' object cannot be interpreted as an integer + + +Tests of dynamic attribute access:: + + >>> v7 = Vector(range(10)) + >>> v7.x + 0.0 + >>> v7.y, v7.z, v7.t + (1.0, 2.0, 3.0) + + +Dynamic attribute lookup failures:: + + >>> v7.k + Traceback (most recent call last): + ... + AttributeError: 'Vector' object has no attribute 'k' + >>> v3 = Vector(range(3)) + >>> v3.t + Traceback (most recent call last): + ... + AttributeError: 'Vector' object has no attribute 't' + >>> v3.spam + Traceback (most recent call last): + ... + AttributeError: 'Vector' object has no attribute 'spam' + + +Tests of preventing attributes from 'a' to 'z':: + + >>> v1.x = 7 + Traceback (most recent call last): + ... + AttributeError: readonly attribute 'x' + >>> v1.w = 7 + Traceback (most recent call last): + ... + AttributeError: can't set attributes 'a' to 'z' in 'Vector' + +Other attributes can be set:: + + >>> v1.X = 'albatross' + >>> v1.X + 'albatross' + >>> v1.ni = 'Ni!' + >>> v1.ni + 'Ni!' + +""" + +from array import array +import reprlib +import math +import operator + + +class Vector: + typecode = 'd' + + def __init__(self, components): + self._components = array(self.typecode, components) + + def __iter__(self): + return iter(self._components) + + def __repr__(self): + components = reprlib.repr(self._components) + components = components[components.find('['):-1] + return 'Vector({})'.format(components) + + def __str__(self): + return str(tuple(self)) + + def __bytes__(self): + return (bytes([ord(self.typecode)]) + + bytes(self._components)) + + def __eq__(self, other): + return tuple(self) == tuple(other) + + def __abs__(self): + return math.sqrt(sum(x * x for x in self)) + + def __bool__(self): + return bool(abs(self)) + + def __len__(self): + return len(self._components) + + def __getitem__(self, key): + if isinstance(key, slice): + cls = type(self) + return cls(self._components[key]) + index = operator.index(key) + return self._components[index] + +# tag::VECTOR_V3_GETATTR[] + shortcut_names = 'xyzt' + + def __getattr__(self, name): + cls = type(self) # <1> + if len(name) == 1: # <2> + pos = cls.shortcut_names.find(name) # <3> + if 0 <= pos < len(self._components): # <4> + return self._components[pos] + msg = '{.__name__!r} object has no attribute {!r}' # <5> + raise AttributeError(msg.format(cls, name)) +# end::VECTOR_V3_GETATTR[] + +# tag::VECTOR_V3_SETATTR[] + def __setattr__(self, name, value): + cls = type(self) + if len(name) == 1: # <1> + if name in cls.shortcut_names: # <2> + error = 'readonly attribute {attr_name!r}' + elif name.islower(): # <3> + error = "can't set attributes 'a' to 'z' in {cls_name!r}" + else: + error = '' # <4> + if error: # <5> + msg = error.format(cls_name=cls.__name__, attr_name=name) + raise AttributeError(msg) + super().__setattr__(name, value) # <6> + +# end::VECTOR_V3_SETATTR[] + + @classmethod + def frombytes(cls, octets): + typecode = chr(octets[0]) + memv = memoryview(octets[1:]).cast(typecode) + return cls(memv) diff --git a/12-seq-hacking/vector_v4.py b/12-seq-hacking/vector_v4.py new file mode 100644 index 0000000..211a076 --- /dev/null +++ b/12-seq-hacking/vector_v4.py @@ -0,0 +1,217 @@ +""" +A multi-dimensional ``Vector`` class, take 4 + +A ``Vector`` is built from an iterable of numbers:: + + >>> Vector([3.1, 4.2]) + Vector([3.1, 4.2]) + >>> Vector((3, 4, 5)) + Vector([3.0, 4.0, 5.0]) + >>> Vector(range(10)) + Vector([0.0, 1.0, 2.0, 3.0, 4.0, ...]) + + +Tests with 2-dimensions (same results as ``vector2d_v1.py``):: + + >>> v1 = Vector([3, 4]) + >>> x, y = v1 + >>> x, y + (3.0, 4.0) + >>> v1 + Vector([3.0, 4.0]) + >>> v1_clone = eval(repr(v1)) + >>> v1 == v1_clone + True + >>> print(v1) + (3.0, 4.0) + >>> octets = bytes(v1) + >>> octets + b'd\\x00\\x00\\x00\\x00\\x00\\x00\\x08@\\x00\\x00\\x00\\x00\\x00\\x00\\x10@' + >>> abs(v1) + 5.0 + >>> bool(v1), bool(Vector([0, 0])) + (True, False) + + +Test of ``.frombytes()`` class method: + + >>> v1_clone = Vector.frombytes(bytes(v1)) + >>> v1_clone + Vector([3.0, 4.0]) + >>> v1 == v1_clone + True + + +Tests with 3-dimensions:: + + >>> v1 = Vector([3, 4, 5]) + >>> x, y, z = v1 + >>> x, y, z + (3.0, 4.0, 5.0) + >>> v1 + Vector([3.0, 4.0, 5.0]) + >>> v1_clone = eval(repr(v1)) + >>> v1 == v1_clone + True + >>> print(v1) + (3.0, 4.0, 5.0) + >>> abs(v1) # doctest:+ELLIPSIS + 7.071067811... + >>> bool(v1), bool(Vector([0, 0, 0])) + (True, False) + + +Tests with many dimensions:: + + >>> v7 = Vector(range(7)) + >>> v7 + Vector([0.0, 1.0, 2.0, 3.0, 4.0, ...]) + >>> abs(v7) # doctest:+ELLIPSIS + 9.53939201... + + +Test of ``.__bytes__`` and ``.frombytes()`` methods:: + + >>> v1 = Vector([3, 4, 5]) + >>> v1_clone = Vector.frombytes(bytes(v1)) + >>> v1_clone + Vector([3.0, 4.0, 5.0]) + >>> v1 == v1_clone + True + + +Tests of sequence behavior:: + + >>> v1 = Vector([3, 4, 5]) + >>> len(v1) + 3 + >>> v1[0], v1[len(v1)-1], v1[-1] + (3.0, 5.0, 5.0) + + +Test of slicing:: + + >>> v7 = Vector(range(7)) + >>> v7[-1] + 6.0 + >>> v7[1:4] + Vector([1.0, 2.0, 3.0]) + >>> v7[-1:] + Vector([6.0]) + >>> v7[1,2] + Traceback (most recent call last): + ... + TypeError: 'tuple' object cannot be interpreted as an integer + + +Tests of dynamic attribute access:: + + >>> v7 = Vector(range(10)) + >>> v7.x + 0.0 + >>> v7.y, v7.z, v7.t + (1.0, 2.0, 3.0) + +Dynamic attribute lookup failures:: + + >>> v7.k + Traceback (most recent call last): + ... + AttributeError: 'Vector' object has no attribute 'k' + >>> v3 = Vector(range(3)) + >>> v3.t + Traceback (most recent call last): + ... + AttributeError: 'Vector' object has no attribute 't' + >>> v3.spam + Traceback (most recent call last): + ... + AttributeError: 'Vector' object has no attribute 'spam' + + +Tests of hashing:: + + >>> v1 = Vector([3, 4]) + >>> v2 = Vector([3.1, 4.2]) + >>> v3 = Vector([3, 4, 5]) + >>> v6 = Vector(range(6)) + >>> hash(v1), hash(v3), hash(v6) + (7, 2, 1) + + +Most hash codes of non-integers vary from a 32-bit to 64-bit CPython build:: + + >>> import sys + >>> hash(v2) == (384307168202284039 if sys.maxsize > 2**32 else 357915986) + True + +""" + +from array import array +import reprlib +import math +import functools +import operator + + +class Vector: + typecode = 'd' + + def __init__(self, components): + self._components = array(self.typecode, components) + + def __iter__(self): + return iter(self._components) + + def __repr__(self): + components = reprlib.repr(self._components) + components = components[components.find('['):-1] + return 'Vector({})'.format(components) + + def __str__(self): + return str(tuple(self)) + + def __bytes__(self): + return (bytes([ord(self.typecode)]) + + bytes(self._components)) + + def __eq__(self, other): + return (len(self) == len(other) and + all(a == b for a, b in zip(self, other))) + + def __hash__(self): + hashes = (hash(x) for x in self) + return functools.reduce(operator.xor, hashes, 0) + + def __abs__(self): + return math.sqrt(sum(x * x for x in self)) + + def __bool__(self): + return bool(abs(self)) + + def __len__(self): + return len(self._components) + + def __getitem__(self, key): + if isinstance(key, slice): + cls = type(self) + return cls(self._components[key]) + index = operator.index(key) + return self._components[index] + + shortcut_names = 'xyzt' + + def __getattr__(self, name): + cls = type(self) + if len(name) == 1: + pos = cls.shortcut_names.find(name) + if 0 <= pos < len(self._components): + return self._components[pos] + msg = '{.__name__!r} object has no attribute {!r}' + raise AttributeError(msg.format(cls, name)) + + @classmethod + def frombytes(cls, octets): + typecode = chr(octets[0]) + memv = memoryview(octets[1:]).cast(typecode) + return cls(memv) diff --git a/12-seq-hacking/vector_v5.py b/12-seq-hacking/vector_v5.py new file mode 100644 index 0000000..c5c7344 --- /dev/null +++ b/12-seq-hacking/vector_v5.py @@ -0,0 +1,284 @@ +# tag::VECTOR_V5[] +""" +A multidimensional ``Vector`` class, take 5 + +A ``Vector`` is built from an iterable of numbers:: + + >>> Vector([3.1, 4.2]) + Vector([3.1, 4.2]) + >>> Vector((3, 4, 5)) + Vector([3.0, 4.0, 5.0]) + >>> Vector(range(10)) + Vector([0.0, 1.0, 2.0, 3.0, 4.0, ...]) + + +Tests with two dimensions (same results as ``vector2d_v1.py``):: + + >>> v1 = Vector([3, 4]) + >>> x, y = v1 + >>> x, y + (3.0, 4.0) + >>> v1 + Vector([3.0, 4.0]) + >>> v1_clone = eval(repr(v1)) + >>> v1 == v1_clone + True + >>> print(v1) + (3.0, 4.0) + >>> octets = bytes(v1) + >>> octets + b'd\\x00\\x00\\x00\\x00\\x00\\x00\\x08@\\x00\\x00\\x00\\x00\\x00\\x00\\x10@' + >>> abs(v1) + 5.0 + >>> bool(v1), bool(Vector([0, 0])) + (True, False) + + +Test of ``.frombytes()`` class method: + + >>> v1_clone = Vector.frombytes(bytes(v1)) + >>> v1_clone + Vector([3.0, 4.0]) + >>> v1 == v1_clone + True + + +Tests with three dimensions:: + + >>> v1 = Vector([3, 4, 5]) + >>> x, y, z = v1 + >>> x, y, z + (3.0, 4.0, 5.0) + >>> v1 + Vector([3.0, 4.0, 5.0]) + >>> v1_clone = eval(repr(v1)) + >>> v1 == v1_clone + True + >>> print(v1) + (3.0, 4.0, 5.0) + >>> abs(v1) # doctest:+ELLIPSIS + 7.071067811... + >>> bool(v1), bool(Vector([0, 0, 0])) + (True, False) + + +Tests with many dimensions:: + + >>> v7 = Vector(range(7)) + >>> v7 + Vector([0.0, 1.0, 2.0, 3.0, 4.0, ...]) + >>> abs(v7) # doctest:+ELLIPSIS + 9.53939201... + + +Test of ``.__bytes__`` and ``.frombytes()`` methods:: + + >>> v1 = Vector([3, 4, 5]) + >>> v1_clone = Vector.frombytes(bytes(v1)) + >>> v1_clone + Vector([3.0, 4.0, 5.0]) + >>> v1 == v1_clone + True + + +Tests of sequence behavior:: + + >>> v1 = Vector([3, 4, 5]) + >>> len(v1) + 3 + >>> v1[0], v1[len(v1)-1], v1[-1] + (3.0, 5.0, 5.0) + + +Test of slicing:: + + >>> v7 = Vector(range(7)) + >>> v7[-1] + 6.0 + >>> v7[1:4] + Vector([1.0, 2.0, 3.0]) + >>> v7[-1:] + Vector([6.0]) + >>> v7[1,2] + Traceback (most recent call last): + ... + TypeError: 'tuple' object cannot be interpreted as an integer + + +Tests of dynamic attribute access:: + + >>> v7 = Vector(range(10)) + >>> v7.x + 0.0 + >>> v7.y, v7.z, v7.t + (1.0, 2.0, 3.0) + +Dynamic attribute lookup failures:: + + >>> v7.k + Traceback (most recent call last): + ... + AttributeError: 'Vector' object has no attribute 'k' + >>> v3 = Vector(range(3)) + >>> v3.t + Traceback (most recent call last): + ... + AttributeError: 'Vector' object has no attribute 't' + >>> v3.spam + Traceback (most recent call last): + ... + AttributeError: 'Vector' object has no attribute 'spam' + + +Tests of hashing:: + + >>> v1 = Vector([3, 4]) + >>> v2 = Vector([3.1, 4.2]) + >>> v3 = Vector([3, 4, 5]) + >>> v6 = Vector(range(6)) + >>> hash(v1), hash(v3), hash(v6) + (7, 2, 1) + + +Most hash codes of non-integers vary from a 32-bit to 64-bit CPython build:: + + >>> import sys + >>> hash(v2) == (384307168202284039 if sys.maxsize > 2**32 else 357915986) + True + + +Tests of ``format()`` with Cartesian coordinates in 2D:: + + >>> v1 = Vector([3, 4]) + >>> format(v1) + '(3.0, 4.0)' + >>> format(v1, '.2f') + '(3.00, 4.00)' + >>> format(v1, '.3e') + '(3.000e+00, 4.000e+00)' + + +Tests of ``format()`` with Cartesian coordinates in 3D and 7D:: + + >>> v3 = Vector([3, 4, 5]) + >>> format(v3) + '(3.0, 4.0, 5.0)' + >>> format(Vector(range(7))) + '(0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0)' + + +Tests of ``format()`` with spherical coordinates in 2D, 3D and 4D:: + + >>> format(Vector([1, 1]), 'h') # doctest:+ELLIPSIS + '<1.414213..., 0.785398...>' + >>> format(Vector([1, 1]), '.3eh') + '<1.414e+00, 7.854e-01>' + >>> format(Vector([1, 1]), '0.5fh') + '<1.41421, 0.78540>' + >>> format(Vector([1, 1, 1]), 'h') # doctest:+ELLIPSIS + '<1.73205..., 0.95531..., 0.78539...>' + >>> format(Vector([2, 2, 2]), '.3eh') + '<3.464e+00, 9.553e-01, 7.854e-01>' + >>> format(Vector([0, 0, 0]), '0.5fh') + '<0.00000, 0.00000, 0.00000>' + >>> format(Vector([-1, -1, -1, -1]), 'h') # doctest:+ELLIPSIS + '<2.0, 2.09439..., 2.18627..., 3.92699...>' + >>> format(Vector([2, 2, 2, 2]), '.3eh') + '<4.000e+00, 1.047e+00, 9.553e-01, 7.854e-01>' + >>> format(Vector([0, 1, 0, 0]), '0.5fh') + '<1.00000, 1.57080, 0.00000, 0.00000>' +""" + +from array import array +import reprlib +import math +import functools +import operator +import itertools # <1> + + +class Vector: + typecode = 'd' + + def __init__(self, components): + self._components = array(self.typecode, components) + + def __iter__(self): + return iter(self._components) + + def __repr__(self): + components = reprlib.repr(self._components) + components = components[components.find('['):-1] + return 'Vector({})'.format(components) + + def __str__(self): + return str(tuple(self)) + + def __bytes__(self): + return (bytes([ord(self.typecode)]) + + bytes(self._components)) + + def __eq__(self, other): + return (len(self) == len(other) and + all(a == b for a, b in zip(self, other))) + + def __hash__(self): + hashes = (hash(x) for x in self) + return functools.reduce(operator.xor, hashes, 0) + + def __abs__(self): + return math.sqrt(sum(x * x for x in self)) + + def __bool__(self): + return bool(abs(self)) + + def __len__(self): + return len(self._components) + + def __getitem__(self, key): + if isinstance(key, slice): + cls = type(self) + return cls(self._components[key]) + index = operator.index(key) + return self._components[index] + + shortcut_names = 'xyzt' + + def __getattr__(self, name): + cls = type(self) + if len(name) == 1: + pos = cls.shortcut_names.find(name) + if 0 <= pos < len(self._components): + return self._components[pos] + msg = '{.__name__!r} object has no attribute {!r}' + raise AttributeError(msg.format(cls, name)) + + def angle(self, n): # <2> + r = math.sqrt(sum(x * x for x in self[n:])) + a = math.atan2(r, self[n-1]) + if (n == len(self) - 1) and (self[-1] < 0): + return math.pi * 2 - a + else: + return a + + def angles(self): # <3> + return (self.angle(n) for n in range(1, len(self))) + + def __format__(self, fmt_spec=''): + if fmt_spec.endswith('h'): # hyperspherical coordinates + fmt_spec = fmt_spec[:-1] + coords = itertools.chain([abs(self)], + self.angles()) # <4> + outer_fmt = '<{}>' # <5> + else: + coords = self + outer_fmt = '({})' # <6> + components = (format(c, fmt_spec) for c in coords) # <7> + return outer_fmt.format(', '.join(components)) # <8> + + @classmethod + def frombytes(cls, octets): + typecode = chr(octets[0]) + memv = memoryview(octets[1:]).cast(typecode) + return cls(memv) +# end::VECTOR_V5[] From 47cafc801a8e811b7cd7e7a5821f1ad4a65efa52 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Sun, 14 Feb 2021 20:58:46 -0300 Subject: [PATCH 018/166] ch11-24: clean up by @eumiro & sync with Atlas --- 11-pythonic-obj/private/.gitignore | 2 + 13-protocol-abc/README.rst | 4 + 13-protocol-abc/bingo.py | 28 + 13-protocol-abc/double/double_object.py | 2 + 13-protocol-abc/double/double_protocol.py | 11 + 13-protocol-abc/double/double_sequence.py | 6 + 13-protocol-abc/double/double_test.py | 56 + 13-protocol-abc/drum.py | 17 + 13-protocol-abc/frenchdeck2.py | 26 + 13-protocol-abc/lotto.py | 30 + 13-protocol-abc/tombola.py | 35 + 13-protocol-abc/tombola_runner.py | 36 + 13-protocol-abc/tombola_subhook.py | 64 + 13-protocol-abc/tombola_tests.rst | 82 + 13-protocol-abc/tombolist.py | 23 + 13-protocol-abc/typing/randompick.py | 5 + 13-protocol-abc/typing/randompick_test.py | 25 + 13-protocol-abc/typing/randompickload.py | 6 + 13-protocol-abc/typing/randompickload_test.py | 32 + 13-protocol-abc/typing/vector2d_v4.py | 172 + 13-protocol-abc/typing/vector2d_v4_test.py | 56 + 13-protocol-abc/typing/vector2d_v5.py | 174 + 13-protocol-abc/typing/vector2d_v5_test.py | 35 + 14-inheritance/README.rst | 4 + 14-inheritance/diamond.py | 27 + 16-op-overloading/README.rst | 4 + 16-op-overloading/bingo.py | 28 + 16-op-overloading/bingoaddable.py | 86 + 16-op-overloading/tombola.py | 35 + 16-op-overloading/unary_plus_decimal.py | 35 + 16-op-overloading/vector2d_v3.py | 151 + 16-op-overloading/vector_py3_5.py | 431 + 16-op-overloading/vector_v6.py | 358 + 16-op-overloading/vector_v7.py | 429 + 16-op-overloading/vector_v8.py | 421 + 17-it-generator/README.rst | 4 + 17-it-generator/aritprog.rst | 31 + 17-it-generator/aritprog_float_error.py | 26 + 17-it-generator/aritprog_runner.py | 37 + 17-it-generator/aritprog_v0.py | 25 + 17-it-generator/aritprog_v1.py | 45 + 17-it-generator/aritprog_v2.py | 31 + 17-it-generator/aritprog_v3.py | 11 + 17-it-generator/columnize_iter.py | 26 + 17-it-generator/fibo_by_hand.py | 51 + 17-it-generator/isis2json/README.rst | 12 + 17-it-generator/isis2json/isis2json.py | 263 + 17-it-generator/isis2json/iso2709.py | 167 + 17-it-generator/isis2json/subfield.py | 142 + 17-it-generator/sentence.py | 37 + 17-it-generator/sentence.rst | 54 + 17-it-generator/sentence_gen.py | 28 + 17-it-generator/sentence_gen2.py | 24 + 17-it-generator/sentence_genexp.py | 44 + 17-it-generator/sentence_iter.py | 65 + 17-it-generator/sentence_iter2.py | 37 + 17-it-generator/sentence_runner.py | 36 + 17-it-generator/tree/4steps/tree_step0.py | 7 + 17-it-generator/tree/4steps/tree_step1.py | 10 + 17-it-generator/tree/4steps/tree_step2.py | 12 + 17-it-generator/tree/4steps/tree_step3.py | 10 + 17-it-generator/tree/extra/pretty_tree.py | 35 + .../tree/extra/test_pretty_tree.py | 101 + 17-it-generator/tree/extra/test_tree.py | 90 + 17-it-generator/tree/extra/tree.py | 17 + 17-it-generator/tree/step0/test_tree.py | 8 + 17-it-generator/tree/step0/tree.py | 11 + 17-it-generator/tree/step1/test_tree.py | 21 + 17-it-generator/tree/step1/tree.py | 14 + 17-it-generator/tree/step2/test_tree.py | 21 + 17-it-generator/tree/step2/tree.py | 18 + 17-it-generator/tree/step3/test_tree.py | 34 + 17-it-generator/tree/step3/tree.py | 20 + 17-it-generator/tree/step4/test_tree.py | 72 + 17-it-generator/tree/step4/tree.py | 24 + 17-it-generator/tree/step5/test_tree.py | 90 + 17-it-generator/tree/step5/tree.py | 19 + 17-it-generator/tree/step6/test_tree.py | 90 + 17-it-generator/tree/step6/tree.py | 14 + 17-it-generator/yield_delegate_fail.py | 29 + 17-it-generator/yield_delegate_fix.py | 24 + 18-context-mngr/README.rst | 4 + 18-context-mngr/mirror.py | 92 + 18-context-mngr/mirror_gen.py | 64 + 18-context-mngr/mirror_gen_exc.py | 101 + 19-coroutine/README.rst | 4 + 19-coroutine/coro_exc_demo.py | 66 + 19-coroutine/coro_finally_demo.py | 61 + 19-coroutine/coroaverager0.py | 28 + 19-coroutine/coroaverager1.py | 30 + 19-coroutine/coroaverager2.py | 61 + 19-coroutine/coroaverager3.py | 107 + 19-coroutine/coroutil.py | 12 + 19-coroutine/taxi_sim.py | 203 + 19-coroutine/taxi_sim0.py | 257 + 19-coroutine/taxi_sim_delay.py | 215 + 19-coroutine/yield_from_expansion.py | 52 + .../yield_from_expansion_simplified.py | 32 + .../primes/spinner_prime_async_broken.py | 2 +- .../primes/spinner_prime_async_nap.py | 2 +- 20-concurrency/primes/threads_py37.py | 39 - 21-futures/getflags/.gitignore | 1 + 21-futures/getflags/flags2_common.py | 4 +- 21-futures/getflags/flags_asyncio.py | 37 +- .../getflags/flags_threadpool_futures.py | 2 +- 23-dyn-attr-prop/README.rst | 4 + 23-dyn-attr-prop/blackknight.py | 44 + 23-dyn-attr-prop/bulkfood/bulkfood_v1.py | 37 + 23-dyn-attr-prop/bulkfood/bulkfood_v2.py | 63 + 23-dyn-attr-prop/bulkfood/bulkfood_v2b.py | 64 + 23-dyn-attr-prop/bulkfood/bulkfood_v2prop.py | 69 + 23-dyn-attr-prop/doc_property.py | 23 + 23-dyn-attr-prop/oscon/data/osconfeed.json | 13307 ++++++++++++++++ 23-dyn-attr-prop/oscon/demo_schedule2.py | 25 + 23-dyn-attr-prop/oscon/explore0.py | 65 + 23-dyn-attr-prop/oscon/explore1.py | 78 + 23-dyn-attr-prop/oscon/explore2.py | 54 + 23-dyn-attr-prop/oscon/osconfeed-sample.json | 32 + 23-dyn-attr-prop/oscon/osconfeed_explore.rst | 20 + 23-dyn-attr-prop/oscon/runtests.sh | 2 + 23-dyn-attr-prop/oscon/schedule_v1.py | 39 + 23-dyn-attr-prop/oscon/schedule_v2.py | 76 + 23-dyn-attr-prop/oscon/schedule_v3.py | 86 + 23-dyn-attr-prop/oscon/schedule_v4.py | 94 + 23-dyn-attr-prop/oscon/schedule_v4_hasattr.py | 86 + 23-dyn-attr-prop/oscon/schedule_v5.py | 89 + 23-dyn-attr-prop/oscon/test_schedule_v1.py | 24 + 23-dyn-attr-prop/oscon/test_schedule_v2.py | 48 + 23-dyn-attr-prop/oscon/test_schedule_v3.py | 59 + 23-dyn-attr-prop/oscon/test_schedule_v4.py | 59 + 23-dyn-attr-prop/oscon/test_schedule_v5.py | 59 + 23-dyn-attr-prop/pseudo_construction.py | 10 + 24-descriptor/README.rst | 4 + 24-descriptor/bulkfood/bulkfood_v3.py | 67 + 24-descriptor/bulkfood/bulkfood_v4.py | 70 + 24-descriptor/bulkfood/bulkfood_v4c.py | 58 + 24-descriptor/bulkfood/bulkfood_v5.py | 72 + 24-descriptor/bulkfood/model_v4c.py | 13 + 24-descriptor/bulkfood/model_v5.py | 36 + 24-descriptor/descriptorkinds.py | 204 + 24-descriptor/descriptorkinds_dump.py | 169 + 24-descriptor/method_is_descriptor.py | 41 + LICENSE | 2 +- 143 files changed, 21690 insertions(+), 61 deletions(-) create mode 100644 11-pythonic-obj/private/.gitignore create mode 100644 13-protocol-abc/README.rst create mode 100644 13-protocol-abc/bingo.py create mode 100644 13-protocol-abc/double/double_object.py create mode 100644 13-protocol-abc/double/double_protocol.py create mode 100644 13-protocol-abc/double/double_sequence.py create mode 100644 13-protocol-abc/double/double_test.py create mode 100644 13-protocol-abc/drum.py create mode 100644 13-protocol-abc/frenchdeck2.py create mode 100644 13-protocol-abc/lotto.py create mode 100644 13-protocol-abc/tombola.py create mode 100644 13-protocol-abc/tombola_runner.py create mode 100644 13-protocol-abc/tombola_subhook.py create mode 100644 13-protocol-abc/tombola_tests.rst create mode 100644 13-protocol-abc/tombolist.py create mode 100644 13-protocol-abc/typing/randompick.py create mode 100644 13-protocol-abc/typing/randompick_test.py create mode 100644 13-protocol-abc/typing/randompickload.py create mode 100644 13-protocol-abc/typing/randompickload_test.py create mode 100644 13-protocol-abc/typing/vector2d_v4.py create mode 100644 13-protocol-abc/typing/vector2d_v4_test.py create mode 100644 13-protocol-abc/typing/vector2d_v5.py create mode 100644 13-protocol-abc/typing/vector2d_v5_test.py create mode 100644 14-inheritance/README.rst create mode 100644 14-inheritance/diamond.py create mode 100644 16-op-overloading/README.rst create mode 100644 16-op-overloading/bingo.py create mode 100644 16-op-overloading/bingoaddable.py create mode 100644 16-op-overloading/tombola.py create mode 100644 16-op-overloading/unary_plus_decimal.py create mode 100644 16-op-overloading/vector2d_v3.py create mode 100644 16-op-overloading/vector_py3_5.py create mode 100644 16-op-overloading/vector_v6.py create mode 100644 16-op-overloading/vector_v7.py create mode 100644 16-op-overloading/vector_v8.py create mode 100644 17-it-generator/README.rst create mode 100644 17-it-generator/aritprog.rst create mode 100644 17-it-generator/aritprog_float_error.py create mode 100644 17-it-generator/aritprog_runner.py create mode 100644 17-it-generator/aritprog_v0.py create mode 100644 17-it-generator/aritprog_v1.py create mode 100644 17-it-generator/aritprog_v2.py create mode 100644 17-it-generator/aritprog_v3.py create mode 100644 17-it-generator/columnize_iter.py create mode 100644 17-it-generator/fibo_by_hand.py create mode 100644 17-it-generator/isis2json/README.rst create mode 100755 17-it-generator/isis2json/isis2json.py create mode 100644 17-it-generator/isis2json/iso2709.py create mode 100644 17-it-generator/isis2json/subfield.py create mode 100644 17-it-generator/sentence.py create mode 100644 17-it-generator/sentence.rst create mode 100644 17-it-generator/sentence_gen.py create mode 100644 17-it-generator/sentence_gen2.py create mode 100644 17-it-generator/sentence_genexp.py create mode 100644 17-it-generator/sentence_iter.py create mode 100644 17-it-generator/sentence_iter2.py create mode 100644 17-it-generator/sentence_runner.py create mode 100644 17-it-generator/tree/4steps/tree_step0.py create mode 100644 17-it-generator/tree/4steps/tree_step1.py create mode 100644 17-it-generator/tree/4steps/tree_step2.py create mode 100644 17-it-generator/tree/4steps/tree_step3.py create mode 100644 17-it-generator/tree/extra/pretty_tree.py create mode 100644 17-it-generator/tree/extra/test_pretty_tree.py create mode 100644 17-it-generator/tree/extra/test_tree.py create mode 100644 17-it-generator/tree/extra/tree.py create mode 100644 17-it-generator/tree/step0/test_tree.py create mode 100644 17-it-generator/tree/step0/tree.py create mode 100644 17-it-generator/tree/step1/test_tree.py create mode 100644 17-it-generator/tree/step1/tree.py create mode 100644 17-it-generator/tree/step2/test_tree.py create mode 100644 17-it-generator/tree/step2/tree.py create mode 100644 17-it-generator/tree/step3/test_tree.py create mode 100644 17-it-generator/tree/step3/tree.py create mode 100644 17-it-generator/tree/step4/test_tree.py create mode 100644 17-it-generator/tree/step4/tree.py create mode 100644 17-it-generator/tree/step5/test_tree.py create mode 100644 17-it-generator/tree/step5/tree.py create mode 100644 17-it-generator/tree/step6/test_tree.py create mode 100644 17-it-generator/tree/step6/tree.py create mode 100644 17-it-generator/yield_delegate_fail.py create mode 100644 17-it-generator/yield_delegate_fix.py create mode 100644 18-context-mngr/README.rst create mode 100644 18-context-mngr/mirror.py create mode 100644 18-context-mngr/mirror_gen.py create mode 100644 18-context-mngr/mirror_gen_exc.py create mode 100644 19-coroutine/README.rst create mode 100644 19-coroutine/coro_exc_demo.py create mode 100644 19-coroutine/coro_finally_demo.py create mode 100644 19-coroutine/coroaverager0.py create mode 100644 19-coroutine/coroaverager1.py create mode 100644 19-coroutine/coroaverager2.py create mode 100644 19-coroutine/coroaverager3.py create mode 100644 19-coroutine/coroutil.py create mode 100644 19-coroutine/taxi_sim.py create mode 100644 19-coroutine/taxi_sim0.py create mode 100644 19-coroutine/taxi_sim_delay.py create mode 100644 19-coroutine/yield_from_expansion.py create mode 100644 19-coroutine/yield_from_expansion_simplified.py delete mode 100644 20-concurrency/primes/threads_py37.py create mode 100644 21-futures/getflags/.gitignore create mode 100644 23-dyn-attr-prop/README.rst create mode 100644 23-dyn-attr-prop/blackknight.py create mode 100644 23-dyn-attr-prop/bulkfood/bulkfood_v1.py create mode 100644 23-dyn-attr-prop/bulkfood/bulkfood_v2.py create mode 100644 23-dyn-attr-prop/bulkfood/bulkfood_v2b.py create mode 100644 23-dyn-attr-prop/bulkfood/bulkfood_v2prop.py create mode 100644 23-dyn-attr-prop/doc_property.py create mode 100644 23-dyn-attr-prop/oscon/data/osconfeed.json create mode 100755 23-dyn-attr-prop/oscon/demo_schedule2.py create mode 100644 23-dyn-attr-prop/oscon/explore0.py create mode 100644 23-dyn-attr-prop/oscon/explore1.py create mode 100644 23-dyn-attr-prop/oscon/explore2.py create mode 100644 23-dyn-attr-prop/oscon/osconfeed-sample.json create mode 100644 23-dyn-attr-prop/oscon/osconfeed_explore.rst create mode 100755 23-dyn-attr-prop/oscon/runtests.sh create mode 100644 23-dyn-attr-prop/oscon/schedule_v1.py create mode 100644 23-dyn-attr-prop/oscon/schedule_v2.py create mode 100644 23-dyn-attr-prop/oscon/schedule_v3.py create mode 100644 23-dyn-attr-prop/oscon/schedule_v4.py create mode 100644 23-dyn-attr-prop/oscon/schedule_v4_hasattr.py create mode 100644 23-dyn-attr-prop/oscon/schedule_v5.py create mode 100644 23-dyn-attr-prop/oscon/test_schedule_v1.py create mode 100644 23-dyn-attr-prop/oscon/test_schedule_v2.py create mode 100644 23-dyn-attr-prop/oscon/test_schedule_v3.py create mode 100644 23-dyn-attr-prop/oscon/test_schedule_v4.py create mode 100644 23-dyn-attr-prop/oscon/test_schedule_v5.py create mode 100644 23-dyn-attr-prop/pseudo_construction.py create mode 100644 24-descriptor/README.rst create mode 100644 24-descriptor/bulkfood/bulkfood_v3.py create mode 100644 24-descriptor/bulkfood/bulkfood_v4.py create mode 100644 24-descriptor/bulkfood/bulkfood_v4c.py create mode 100644 24-descriptor/bulkfood/bulkfood_v5.py create mode 100644 24-descriptor/bulkfood/model_v4c.py create mode 100644 24-descriptor/bulkfood/model_v5.py create mode 100644 24-descriptor/descriptorkinds.py create mode 100644 24-descriptor/descriptorkinds_dump.py create mode 100644 24-descriptor/method_is_descriptor.py diff --git a/11-pythonic-obj/private/.gitignore b/11-pythonic-obj/private/.gitignore new file mode 100644 index 0000000..2b7a045 --- /dev/null +++ b/11-pythonic-obj/private/.gitignore @@ -0,0 +1,2 @@ +*.class +.jython_cache/ diff --git a/13-protocol-abc/README.rst b/13-protocol-abc/README.rst new file mode 100644 index 0000000..b3a649b --- /dev/null +++ b/13-protocol-abc/README.rst @@ -0,0 +1,4 @@ +Sample code for Chapter 11 - "Interfaces, protocols and ABCs" + +From the book "Fluent Python" by Luciano Ramalho (O'Reilly, 2015) +http://shop.oreilly.com/product/0636920032519.do diff --git a/13-protocol-abc/bingo.py b/13-protocol-abc/bingo.py new file mode 100644 index 0000000..9afb777 --- /dev/null +++ b/13-protocol-abc/bingo.py @@ -0,0 +1,28 @@ +# tag::TOMBOLA_BINGO[] + +import random + +from tombola import Tombola + + +class BingoCage(Tombola): # <1> + + def __init__(self, items): + self._randomizer = random.SystemRandom() # <2> + self._items = [] + self.load(items) # <3> + + def load(self, items): + self._items.extend(items) + self._randomizer.shuffle(self._items) # <4> + + def pick(self): # <5> + try: + return self._items.pop() + except IndexError: + raise LookupError('pick from empty BingoCage') + + def __call__(self): # <6> + self.pick() + +# end::TOMBOLA_BINGO[] diff --git a/13-protocol-abc/double/double_object.py b/13-protocol-abc/double/double_object.py new file mode 100644 index 0000000..c8c062e --- /dev/null +++ b/13-protocol-abc/double/double_object.py @@ -0,0 +1,2 @@ +def double(x: object) -> object: + return x * 2 diff --git a/13-protocol-abc/double/double_protocol.py b/13-protocol-abc/double/double_protocol.py new file mode 100644 index 0000000..d4df3a7 --- /dev/null +++ b/13-protocol-abc/double/double_protocol.py @@ -0,0 +1,11 @@ +from typing import TypeVar, Protocol + +T = TypeVar('T') # <1> + +class Repeatable(Protocol): + def __mul__(self: T, repeat_count: int) -> T: ... # <2> + +RT = TypeVar('RT', bound=Repeatable) # <3> + +def double(x: RT) -> RT: # <4> + return x * 2 diff --git a/13-protocol-abc/double/double_sequence.py b/13-protocol-abc/double/double_sequence.py new file mode 100644 index 0000000..2f15bbe --- /dev/null +++ b/13-protocol-abc/double/double_sequence.py @@ -0,0 +1,6 @@ +from collections import abc +from typing import Any + +def double(x: abc.Sequence) -> Any: + return x * 2 + diff --git a/13-protocol-abc/double/double_test.py b/13-protocol-abc/double/double_test.py new file mode 100644 index 0000000..d37aba5 --- /dev/null +++ b/13-protocol-abc/double/double_test.py @@ -0,0 +1,56 @@ +from typing import TYPE_CHECKING +import pytest +from double_protocol import double + +def test_double_int() -> None: + given = 2 + result = double(given) + assert result == given * 2 + if TYPE_CHECKING: + reveal_type(given) + reveal_type(result) + + +def test_double_str() -> None: + given = 'A' + result = double(given) + assert result == given * 2 + if TYPE_CHECKING: + reveal_type(given) + reveal_type(result) + + +def test_double_fraction() -> None: + from fractions import Fraction + given = Fraction(2, 5) + result = double(given) + assert result == given * 2 + if TYPE_CHECKING: + reveal_type(given) + reveal_type(result) + + +def test_double_array() -> None: + from array import array + given = array('d', [1.0, 2.0, 3.14]) + result = double(given) + if TYPE_CHECKING: + reveal_type(given) + reveal_type(result) + + +def test_double_nparray() -> None: + import numpy as np # type: ignore + given = np.array([[1, 2], [3, 4]]) + result = double(given) + comparison = result == given * 2 + assert comparison.all() + if TYPE_CHECKING: + reveal_type(given) + reveal_type(result) + + +def test_double_none() -> None: + given = None + with pytest.raises(TypeError): + result = double(given) diff --git a/13-protocol-abc/drum.py b/13-protocol-abc/drum.py new file mode 100644 index 0000000..65df0f3 --- /dev/null +++ b/13-protocol-abc/drum.py @@ -0,0 +1,17 @@ +from random import shuffle + +from tombola import Tombola + + +class TumblingDrum(Tombola): + + def __init__(self, iterable): + self._balls = [] + self.load(iterable) + + def load(self, iterable): + self._balls.extend(iterable) + shuffle(self._balls) + + def pick(self): + return self._balls.pop() diff --git a/13-protocol-abc/frenchdeck2.py b/13-protocol-abc/frenchdeck2.py new file mode 100644 index 0000000..e3ccc2c --- /dev/null +++ b/13-protocol-abc/frenchdeck2.py @@ -0,0 +1,26 @@ +import collections + +Card = collections.namedtuple('Card', ['rank', 'suit']) + +class FrenchDeck2(collections.MutableSequence): + ranks = [str(n) for n in range(2, 11)] + list('JQKA') + suits = 'spades diamonds clubs hearts'.split() + + def __init__(self): + self._cards = [Card(rank, suit) for suit in self.suits + for rank in self.ranks] + + def __len__(self): + return len(self._cards) + + def __getitem__(self, position): + return self._cards[position] + + def __setitem__(self, position, value): # <1> + self._cards[position] = value + + def __delitem__(self, position): # <2> + del self._cards[position] + + def insert(self, position, value): # <3> + self._cards.insert(position, value) diff --git a/13-protocol-abc/lotto.py b/13-protocol-abc/lotto.py new file mode 100644 index 0000000..5026041 --- /dev/null +++ b/13-protocol-abc/lotto.py @@ -0,0 +1,30 @@ +# tag::LOTTERY_BLOWER[] + +import random + +from tombola import Tombola + + +class LotteryBlower(Tombola): + + def __init__(self, iterable): + self._balls = list(iterable) # <1> + + def load(self, iterable): + self._balls.extend(iterable) + + def pick(self): + try: + position = random.randrange(len(self._balls)) # <2> + except ValueError: + raise LookupError('pick from empty BingoCage') + return self._balls.pop(position) # <3> + + def loaded(self): # <4> + return bool(self._balls) + + def inspect(self): # <5> + return tuple(sorted(self._balls)) + + +# end::LOTTERY_BLOWER[] diff --git a/13-protocol-abc/tombola.py b/13-protocol-abc/tombola.py new file mode 100644 index 0000000..ea6cb78 --- /dev/null +++ b/13-protocol-abc/tombola.py @@ -0,0 +1,35 @@ +# tag::TOMBOLA_ABC[] + +import abc + +class Tombola(abc.ABC): # <1> + + @abc.abstractmethod + def load(self, iterable): # <2> + """Add items from an iterable.""" + + @abc.abstractmethod + def pick(self): # <3> + """Remove item at random, returning it. + + This method should raise `LookupError` when the instance is empty. + """ + + def loaded(self): # <4> + """Return `True` if there's at least 1 item, `False` otherwise.""" + return bool(self.inspect()) # <5> + + + def inspect(self): + """Return a sorted tuple with the items currently inside.""" + items = [] + while True: # <6> + try: + items.append(self.pick()) + except LookupError: + break + self.load(items) # <7> + return tuple(sorted(items)) + + +# end::TOMBOLA_ABC[] diff --git a/13-protocol-abc/tombola_runner.py b/13-protocol-abc/tombola_runner.py new file mode 100644 index 0000000..bf41046 --- /dev/null +++ b/13-protocol-abc/tombola_runner.py @@ -0,0 +1,36 @@ +# tag::TOMBOLA_RUNNER[] +import doctest + +from tombola import Tombola + +# modules to test +import bingo, lotto, tombolist, drum # <1> + +TEST_FILE = 'tombola_tests.rst' +TEST_MSG = '{0:16} {1.attempted:2} tests, {1.failed:2} failed - {2}' + + +def main(argv): + verbose = '-v' in argv + real_subclasses = Tombola.__subclasses__() # <2> + virtual_subclasses = list(Tombola._abc_registry) # <3> + + for cls in real_subclasses + virtual_subclasses: # <4> + test(cls, verbose) + + +def test(cls, verbose=False): + + res = doctest.testfile( + TEST_FILE, + globs={'ConcreteTombola': cls}, # <5> + verbose=verbose, + optionflags=doctest.REPORT_ONLY_FIRST_FAILURE) + tag = 'FAIL' if res.failed else 'OK' + print(TEST_MSG.format(cls.__name__, res, tag)) # <6> + + +if __name__ == '__main__': + import sys + main(sys.argv) +# end::TOMBOLA_RUNNER[] diff --git a/13-protocol-abc/tombola_subhook.py b/13-protocol-abc/tombola_subhook.py new file mode 100644 index 0000000..b624ae0 --- /dev/null +++ b/13-protocol-abc/tombola_subhook.py @@ -0,0 +1,64 @@ +""" +Variation of ``tombola.Tombola`` implementing ``__subclasshook__``. + +Tests with simple classes:: + + >>> Tombola.__subclasshook__(object) + NotImplemented + >>> class Complete: + ... def __init__(): pass + ... def load(): pass + ... def pick(): pass + ... def loaded(): pass + ... + >>> Tombola.__subclasshook__(Complete) + True + >>> issubclass(Complete, Tombola) + +""" + + +from abc import ABC, abstractmethod +from inspect import getmembers, isfunction + + +class Tombola(ABC): # <1> + + @abstractmethod + def __init__(self, iterable): # <2> + """New instance is loaded from an iterable.""" + + @abstractmethod + def load(self, iterable): + """Add items from an iterable.""" + + @abstractmethod + def pick(self): # <3> + """Remove item at random, returning it. + + This method should raise `LookupError` when the instance is empty. + """ + + def loaded(self): # <4> + try: + item = self.pick() + except LookupError: + return False + else: + self.load([item]) # put it back + return True + + @classmethod + def __subclasshook__(cls, other_cls): + if cls is Tombola: + interface_names = function_names(cls) + found_names = set() + for a_cls in other_cls.__mro__: + found_names |= function_names(a_cls) + if found_names >= interface_names: + return True + return NotImplemented + + +def function_names(obj): + return {name for name, _ in getmembers(obj, isfunction)} diff --git a/13-protocol-abc/tombola_tests.rst b/13-protocol-abc/tombola_tests.rst new file mode 100644 index 0000000..1489903 --- /dev/null +++ b/13-protocol-abc/tombola_tests.rst @@ -0,0 +1,82 @@ +============== +Tombola tests +============== + +Every concrete subclass of Tombola should pass these tests. + + +Create and load instance from iterable:: + + >>> balls = list(range(3)) + >>> globe = ConcreteTombola(balls) + >>> globe.loaded() + True + >>> globe.inspect() + (0, 1, 2) + + +Pick and collect balls:: + + >>> picks = [] + >>> picks.append(globe.pick()) + >>> picks.append(globe.pick()) + >>> picks.append(globe.pick()) + + +Check state and results:: + + >>> globe.loaded() + False + >>> sorted(picks) == balls + True + + +Reload:: + + >>> globe.load(balls) + >>> globe.loaded() + True + >>> picks = [globe.pick() for i in balls] + >>> globe.loaded() + False + + +Check that `LookupError` (or a subclass) is the exception +thrown when the device is empty:: + + >>> globe = ConcreteTombola([]) + >>> try: + ... globe.pick() + ... except LookupError as exc: + ... print('OK') + OK + + +Load and pick 100 balls to verify that they all come out:: + + >>> balls = list(range(100)) + >>> globe = ConcreteTombola(balls) + >>> picks = [] + >>> while globe.inspect(): + ... picks.append(globe.pick()) + >>> len(picks) == len(balls) + True + >>> set(picks) == set(balls) + True + + +Check that the order has changed and is not simply reversed:: + + >>> picks != balls + True + >>> picks[::-1] != balls + True + +Note: the previous 2 tests have a *very* small chance of failing +even if the implementation is OK. The probability of the 100 +balls coming out, by chance, in the order they were inspect is +1/100!, or approximately 1.07e-158. It's much easier to win the +Lotto or to become a billionaire working as a programmer. + +THE END + diff --git a/13-protocol-abc/tombolist.py b/13-protocol-abc/tombolist.py new file mode 100644 index 0000000..b3ca2a6 --- /dev/null +++ b/13-protocol-abc/tombolist.py @@ -0,0 +1,23 @@ +from random import randrange + +from tombola import Tombola + +@Tombola.register # <1> +class TomboList(list): # <2> + + def pick(self): + if self: # <3> + position = randrange(len(self)) + return self.pop(position) # <4> + else: + raise LookupError('pop from empty TomboList') + + load = list.extend # <5> + + def loaded(self): + return bool(self) # <6> + + def inspect(self): + return tuple(sorted(self)) + +# Tombola.register(TomboList) # <7> diff --git a/13-protocol-abc/typing/randompick.py b/13-protocol-abc/typing/randompick.py new file mode 100644 index 0000000..721d540 --- /dev/null +++ b/13-protocol-abc/typing/randompick.py @@ -0,0 +1,5 @@ +from typing import Protocol, runtime_checkable, Any + +@runtime_checkable +class RandomPicker(Protocol): + def pick(self) -> Any: ... diff --git a/13-protocol-abc/typing/randompick_test.py b/13-protocol-abc/typing/randompick_test.py new file mode 100644 index 0000000..957441c --- /dev/null +++ b/13-protocol-abc/typing/randompick_test.py @@ -0,0 +1,25 @@ +import random +from typing import Any, Iterable, TYPE_CHECKING + +from randompick import RandomPicker # <1> + +class SimplePicker(): # <2> + def __init__(self, items: Iterable) -> None: + self._items = list(items) + random.shuffle(self._items) + + def pick(self) -> Any: # <3> + return self._items.pop() + +def test_isinstance() -> None: # <4> + popper = SimplePicker([1]) + assert isinstance(popper, RandomPicker) + +def test_item_type() -> None: # <5> + items = [1, 2] + popper = SimplePicker(items) + item = popper.pick() + assert item in items + if TYPE_CHECKING: + reveal_type(item) # <6> + assert isinstance(item, int) diff --git a/13-protocol-abc/typing/randompickload.py b/13-protocol-abc/typing/randompickload.py new file mode 100644 index 0000000..7357915 --- /dev/null +++ b/13-protocol-abc/typing/randompickload.py @@ -0,0 +1,6 @@ +from typing import Protocol, runtime_checkable, Any, Iterable +from randompick import RandomPicker + +@runtime_checkable # <1> +class LoadableRandomPicker(RandomPicker, Protocol): # <2> + def load(self, Iterable) -> None: ... # <3> diff --git a/13-protocol-abc/typing/randompickload_test.py b/13-protocol-abc/typing/randompickload_test.py new file mode 100644 index 0000000..0bef0db --- /dev/null +++ b/13-protocol-abc/typing/randompickload_test.py @@ -0,0 +1,32 @@ +import random +from typing import Any, Iterable, TYPE_CHECKING + +from randompickload import LoadableRandomPicker + +class SimplePicker(): + def __init__(self, items: Iterable) -> None: + self._items = list(items) + random.shuffle(self._items) + + def pick(self) -> Any: + return self._items.pop() + +class LoadablePicker(): # <1> + def __init__(self, items: Iterable) -> None: + self.load(items) + + def pick(self) -> Any: # <2> + return self._items.pop() + + def load(self, items: Iterable) -> Any: # <3> + self._items = list(items) + random.shuffle(self._items) + +def test_isinstance() -> None: # <4> + popper = LoadablePicker([1]) + assert isinstance(popper, LoadableRandomPicker) + +def test_isinstance_not() -> None: # <5> + popper = SimplePicker([1]) + assert not isinstance(popper, LoadableRandomPicker) + diff --git a/13-protocol-abc/typing/vector2d_v4.py b/13-protocol-abc/typing/vector2d_v4.py new file mode 100644 index 0000000..1e9d4ac --- /dev/null +++ b/13-protocol-abc/typing/vector2d_v4.py @@ -0,0 +1,172 @@ +""" +A two-dimensional vector class + + >>> v1 = Vector2d(3, 4) + >>> print(v1.x, v1.y) + 3.0 4.0 + >>> x, y = v1 + >>> x, y + (3.0, 4.0) + >>> v1 + Vector2d(3.0, 4.0) + >>> v1_clone = eval(repr(v1)) + >>> v1 == v1_clone + True + >>> print(v1) + (3.0, 4.0) + >>> octets = bytes(v1) + >>> octets + b'd\\x00\\x00\\x00\\x00\\x00\\x00\\x08@\\x00\\x00\\x00\\x00\\x00\\x00\\x10@' + >>> abs(v1) + 5.0 + >>> bool(v1), bool(Vector2d(0, 0)) + (True, False) + + +Test of ``.frombytes()`` class method: + + >>> v1_clone = Vector2d.frombytes(bytes(v1)) + >>> v1_clone + Vector2d(3.0, 4.0) + >>> v1 == v1_clone + True + + +Tests of ``format()`` with Cartesian coordinates: + + >>> format(v1) + '(3.0, 4.0)' + >>> format(v1, '.2f') + '(3.00, 4.00)' + >>> format(v1, '.3e') + '(3.000e+00, 4.000e+00)' + + +Tests of the ``angle`` method:: + + >>> Vector2d(0, 0).angle() + 0.0 + >>> Vector2d(1, 0).angle() + 0.0 + >>> epsilon = 10**-8 + >>> abs(Vector2d(0, 1).angle() - math.pi/2) < epsilon + True + >>> abs(Vector2d(1, 1).angle() - math.pi/4) < epsilon + True + + +Tests of ``format()`` with polar coordinates: + + >>> format(Vector2d(1, 1), 'p') # doctest:+ELLIPSIS + '<1.414213..., 0.785398...>' + >>> format(Vector2d(1, 1), '.3ep') + '<1.414e+00, 7.854e-01>' + >>> format(Vector2d(1, 1), '0.5fp') + '<1.41421, 0.78540>' + + +Tests of ``x`` and ``y`` read-only properties: + + >>> v1.x, v1.y + (3.0, 4.0) + >>> v1.x = 123 + Traceback (most recent call last): + ... + AttributeError: can't set attribute + + +Tests of hashing: + + >>> v1 = Vector2d(3, 4) + >>> v2 = Vector2d(3.1, 4.2) + >>> hash(v1), hash(v2) + (7, 384307168202284039) + >>> len(set([v1, v2])) + 2 + +Converting to/from a ``complex``: +# tag::VECTOR2D_V4_DEMO[] + >>> from typing import SupportsComplex + >>> v3 = Vector2d(1.5, 2.5) + >>> isinstance(v3, SupportsComplex) # <1> + True + >>> complex(v3) # <2> + (1.5+2.5j) + >>> Vector2d.fromcomplex(4+5j) # <3> + Vector2d(4.0, 5.0) + +# end::VECTOR2D_V4_DEMO[] +""" + +from array import array +import math + +class Vector2d: + typecode = 'd' + + def __init__(self, x, y): + self.__x = float(x) + self.__y = float(y) + + @property + def x(self): + return self.__x + + @property + def y(self): + return self.__y + + def __iter__(self): + return (i for i in (self.x, self.y)) + + def __repr__(self): + class_name = type(self).__name__ + return '{}({!r}, {!r})'.format(class_name, *self) + + def __str__(self): + return str(tuple(self)) + + def __bytes__(self): + return (bytes([ord(self.typecode)]) + + bytes(array(self.typecode, self))) + + def __eq__(self, other): + return tuple(self) == tuple(other) + + def __hash__(self): + return hash(self.x) ^ hash(self.y) + + def __abs__(self): + return math.hypot(self.x, self.y) + + def __bool__(self): + return bool(abs(self)) + + def angle(self): + return math.atan2(self.y, self.x) + + def __format__(self, fmt_spec=''): + if fmt_spec.endswith('p'): + fmt_spec = fmt_spec[:-1] + coords = (abs(self), self.angle()) + outer_fmt = '<{}, {}>' + else: + coords = self + outer_fmt = '({}, {})' + components = (format(c, fmt_spec) for c in coords) + return outer_fmt.format(*components) + + @classmethod + def frombytes(cls, octets): + typecode = chr(octets[0]) + memv = memoryview(octets[1:]).cast(typecode) + return cls(*memv) + +# tag::VECTOR2D_V4_COMPLEX[] + def __complex__(self): + return complex(self.x, self.y) + + @classmethod + def fromcomplex(cls, datum): + return Vector2d(datum.real, datum.imag) # <1> +# end::VECTOR2D_V4_COMPLEX[] diff --git a/13-protocol-abc/typing/vector2d_v4_test.py b/13-protocol-abc/typing/vector2d_v4_test.py new file mode 100644 index 0000000..eaacd9e --- /dev/null +++ b/13-protocol-abc/typing/vector2d_v4_test.py @@ -0,0 +1,56 @@ +from typing import SupportsComplex, SupportsAbs, Tuple +from typing import TYPE_CHECKING +import math +import pytest + +from vector2d_v4 import Vector2d + +def test_SupportsComplex_subclass() -> None: + assert issubclass(Vector2d, SupportsComplex) + +def test_SupportsComplex_isinstance() -> None: + v = Vector2d(3, 4) + assert isinstance(v, SupportsComplex) + c = complex(v) + assert c == 3 + 4j + +def test_SupportsAbs_subclass() -> None: + assert issubclass(Vector2d, SupportsAbs) + +def test_SupportsAbs_isinstance() -> None: + v = Vector2d(3, 4) + assert isinstance(v, SupportsAbs) + r = abs(v) + assert r == 5.0 + if TYPE_CHECKING: + reveal_type(r) # Revealed type is 'Any' + +def magnitude(v: SupportsAbs) -> float: + return abs(v) + +def test_SupportsAbs_Vector2d_argument() -> None: + assert magnitude(Vector2d(3, 4)) == 5.0 + +def test_SupportsAbs_object_argument() -> None: + with pytest.raises(TypeError): + magnitude(object()) + # mypy error: + # Argument 1 to "magnitude" has incompatible type "object"; expected "SupportsAbs[Any]" + +def polar(datum: SupportsComplex) -> Tuple[float, float]: + c = complex(datum) + return abs(c), math.atan2(c.imag, c.real) + +def test_SupportsComplex_Vector2d_argument() -> None: + assert polar(Vector2d(2, 0)) == (2, 0) + expected = (2, math.pi / 2) + result = polar(Vector2d(0, 2)) + assert math.isclose(result[0], expected[0]) + assert math.isclose(result[1], expected[1]) + +def test_SupportsComplex_complex_argument() -> None: + assert polar(complex(2, 0)) == (2, 0) + expected = (2, math.pi / 2) + result = polar(complex(0, 2)) + assert math.isclose(result[0], expected[0]) + assert math.isclose(result[1], expected[1]) diff --git a/13-protocol-abc/typing/vector2d_v5.py b/13-protocol-abc/typing/vector2d_v5.py new file mode 100644 index 0000000..ceda21c --- /dev/null +++ b/13-protocol-abc/typing/vector2d_v5.py @@ -0,0 +1,174 @@ +from __future__ import annotations + +""" +A two-dimensional vector class + + >>> v1 = Vector2d(3, 4) + >>> print(v1.x, v1.y) + 3.0 4.0 + >>> x, y = v1 + >>> x, y + (3.0, 4.0) + >>> v1 + Vector2d(3.0, 4.0) + >>> v1_clone = eval(repr(v1)) + >>> v1 == v1_clone + True + >>> print(v1) + (3.0, 4.0) + >>> octets = bytes(v1) + >>> octets + b'd\\x00\\x00\\x00\\x00\\x00\\x00\\x08@\\x00\\x00\\x00\\x00\\x00\\x00\\x10@' + >>> abs(v1) + 5.0 + >>> bool(v1), bool(Vector2d(0, 0)) + (True, False) + + +Test of ``.frombytes()`` class method: + + >>> v1_clone = Vector2d.frombytes(bytes(v1)) + >>> v1_clone + Vector2d(3.0, 4.0) + >>> v1 == v1_clone + True + + +Tests of ``format()`` with Cartesian coordinates: + + >>> format(v1) + '(3.0, 4.0)' + >>> format(v1, '.2f') + '(3.00, 4.00)' + >>> format(v1, '.3e') + '(3.000e+00, 4.000e+00)' + + +Tests of the ``angle`` method:: + + >>> Vector2d(0, 0).angle() + 0.0 + >>> Vector2d(1, 0).angle() + 0.0 + >>> epsilon = 10**-8 + >>> abs(Vector2d(0, 1).angle() - math.pi/2) < epsilon + True + >>> abs(Vector2d(1, 1).angle() - math.pi/4) < epsilon + True + + +Tests of ``format()`` with polar coordinates: + + >>> format(Vector2d(1, 1), 'p') # doctest:+ELLIPSIS + '<1.414213..., 0.785398...>' + >>> format(Vector2d(1, 1), '.3ep') + '<1.414e+00, 7.854e-01>' + >>> format(Vector2d(1, 1), '0.5fp') + '<1.41421, 0.78540>' + + +Tests of ``x`` and ``y`` read-only properties: + + >>> v1.x, v1.y + (3.0, 4.0) + >>> v1.x = 123 + Traceback (most recent call last): + ... + AttributeError: can't set attribute + + +Tests of hashing: + + >>> v1 = Vector2d(3, 4) + >>> v2 = Vector2d(3.1, 4.2) + >>> hash(v1), hash(v2) + (7, 384307168202284039) + >>> len(set([v1, v2])) + 2 + +Converting to/from a ``complex``: + + >>> from typing import SupportsComplex + >>> v3 = Vector2d(1.5, 2.5) + >>> isinstance(v3, SupportsComplex) # <1> + True + >>> complex(v3) # <2> + (1.5+2.5j) + >>> Vector2d.fromcomplex(4+5j) # <3> + Vector2d(4.0, 5.0) +""" + +from array import array +import math +from typing import SupportsComplex, Iterator + +class Vector2d: + typecode = 'd' + + def __init__(self, x, y) -> None: + self.__x = float(x) + self.__y = float(y) + + @property + def x(self) -> float: + return self.__x + + @property + def y(self) -> float: + return self.__y + + def __iter__(self) -> Iterator[float]: + return (i for i in (self.x, self.y)) + + def __repr__(self) -> str: + class_name = type(self).__name__ + return '{}({!r}, {!r})'.format(class_name, *self) + + def __str__(self) -> str: + return str(tuple(self)) + + def __bytes__(self) -> bytes: + return (bytes([ord(self.typecode)]) + + bytes(array(self.typecode, self))) + + def __eq__(self, other) -> bool: + return tuple(self) == tuple(other) + + def __hash__(self) -> int: + return hash(self.x) ^ hash(self.y) + + def __bool__(self) -> bool: + return bool(abs(self)) + + def angle(self) -> float: + return math.atan2(self.y, self.x) + + def __format__(self, fmt_spec='') -> str: + if fmt_spec.endswith('p'): + fmt_spec = fmt_spec[:-1] + coords = (abs(self), self.angle()) + outer_fmt = '<{}, {}>' + else: + coords = self + outer_fmt = '({}, {})' + components = (format(c, fmt_spec) for c in coords) + return outer_fmt.format(*components) + + @classmethod + def frombytes(cls, octets) -> Vector2d: + typecode = chr(octets[0]) + memv = memoryview(octets[1:]).cast(typecode) + return cls(*memv) + +# tag::VECTOR2D_V5_COMPLEX[] + def __abs__(self) -> float: # <1> + return math.hypot(self.x, self.y) + + def __complex__(self) -> complex: # <2> + return complex(self.x, self.y) + + @classmethod + def fromcomplex(cls, datum: SupportsComplex) -> Vector2d: # <3> + c = complex(datum) # <4> + return Vector2d(c.real, c.imag) +# end::VECTOR2D_V5_COMPLEX[] diff --git a/13-protocol-abc/typing/vector2d_v5_test.py b/13-protocol-abc/typing/vector2d_v5_test.py new file mode 100644 index 0000000..39bf956 --- /dev/null +++ b/13-protocol-abc/typing/vector2d_v5_test.py @@ -0,0 +1,35 @@ +from vector2d_v5 import Vector2d +from typing import SupportsComplex, SupportsAbs, TYPE_CHECKING + +import pytest + + +def test_SupportsComplex_subclass() -> None: + assert issubclass(Vector2d, SupportsComplex) + +def test_SupportsComplex_isinstance() -> None: + v = Vector2d(3, 4) + assert isinstance(v, SupportsComplex) + c = complex(v) + assert c == 3 + 4j + +def test_SupportsAbs_subclass() -> None: + assert issubclass(Vector2d, SupportsAbs) + +def test_SupportsAbs_isinstance() -> None: + v = Vector2d(3, 4) + assert isinstance(v, SupportsAbs) + r = abs(v) + assert r == 5.0 + if TYPE_CHECKING: + reveal_type(r) # Revealed type is 'builtins.float*' + +def magnitude(v: SupportsAbs) -> float: + return abs(v) + +def test_SupportsAbs_Vector2d_argument() -> None: + assert 5.0 == magnitude(Vector2d(3, 4)) + +def test_SupportsAbs_object_argument() -> None: + with pytest.raises(TypeError): + assert 5.0 == magnitude(object()) diff --git a/14-inheritance/README.rst b/14-inheritance/README.rst new file mode 100644 index 0000000..2f7426c --- /dev/null +++ b/14-inheritance/README.rst @@ -0,0 +1,4 @@ +Sample code for Chapter 14 - "Inheritance: for good or for worse" + +From the book "Fluent Python, Second Edition" by Luciano Ramalho (O'Reilly, 2021) +https://learning.oreilly.com/library/view/fluent-python-2nd/9781492056348/ diff --git a/14-inheritance/diamond.py b/14-inheritance/diamond.py new file mode 100644 index 0000000..5aaac18 --- /dev/null +++ b/14-inheritance/diamond.py @@ -0,0 +1,27 @@ +class A: + def ping(self): + print('ping:', self) + + +class B(A): + def pong(self): + print('pong:', self) + + +class C(A): + def pong(self): + print('PONG:', self) + + +class D(B, C): + + def ping(self): + super().ping() + print('post-ping:', self) + + def pingpong(self): + self.ping() + super().ping() + self.pong() + super().pong() + C.pong(self) diff --git a/16-op-overloading/README.rst b/16-op-overloading/README.rst new file mode 100644 index 0000000..f872435 --- /dev/null +++ b/16-op-overloading/README.rst @@ -0,0 +1,4 @@ +Sample code for Chapter 13 - "Operator overloading: doing it right" + +From the book "Fluent Python" by Luciano Ramalho (O'Reilly, 2015) +http://shop.oreilly.com/product/0636920032519.do diff --git a/16-op-overloading/bingo.py b/16-op-overloading/bingo.py new file mode 100644 index 0000000..56ed288 --- /dev/null +++ b/16-op-overloading/bingo.py @@ -0,0 +1,28 @@ +# BEGIN TOMBOLA_BINGO + +import random + +from tombola import Tombola + + +class BingoCage(Tombola): # <1> + + def __init__(self, items): + self._randomizer = random.SystemRandom() # <2> + self._items = [] + self.load(items) # <3> + + def load(self, items): + self._items.extend(items) + self._randomizer.shuffle(self._items) # <4> + + def pick(self): # <5> + try: + return self._items.pop() + except IndexError: + raise LookupError('pick from empty BingoCage') + + def __call__(self): # <7> + self.pick() + +# END TOMBOLA_BINGO diff --git a/16-op-overloading/bingoaddable.py b/16-op-overloading/bingoaddable.py new file mode 100644 index 0000000..20294a4 --- /dev/null +++ b/16-op-overloading/bingoaddable.py @@ -0,0 +1,86 @@ +""" +====================== +AddableBingoCage tests +====================== + + +Tests for __add__: + +# tag::ADDABLE_BINGO_ADD_DEMO[] + + >>> vowels = 'AEIOU' + >>> globe = AddableBingoCage(vowels) # <1> + >>> globe.inspect() + ('A', 'E', 'I', 'O', 'U') + >>> globe.pick() in vowels # <2> + True + >>> len(globe.inspect()) # <3> + 4 + >>> globe2 = AddableBingoCage('XYZ') # <4> + >>> globe3 = globe + globe2 + >>> len(globe3.inspect()) # <5> + 7 + >>> void = globe + [10, 20] # <6> + Traceback (most recent call last): + ... + TypeError: unsupported operand type(s) for +: 'AddableBingoCage' and 'list' + + +# end::ADDABLE_BINGO_ADD_DEMO[] + +Tests for __iadd__: + +# tag::ADDABLE_BINGO_IADD_DEMO[] + + >>> globe_orig = globe # <1> + >>> len(globe.inspect()) # <2> + 4 + >>> globe += globe2 # <3> + >>> len(globe.inspect()) + 7 + >>> globe += ['M', 'N'] # <4> + >>> len(globe.inspect()) + 9 + >>> globe is globe_orig # <5> + True + >>> globe += 1 # <6> + Traceback (most recent call last): + ... + TypeError: right operand in += must be 'AddableBingoCage' or an iterable + +# end::ADDABLE_BINGO_IADD_DEMO[] + +""" + +# tag::ADDABLE_BINGO[] +import itertools # <1> + +from tombola import Tombola +from bingo import BingoCage + + +class AddableBingoCage(BingoCage): # <2> + + def __add__(self, other): + if isinstance(other, Tombola): # <3> + return AddableBingoCage(self.inspect() + other.inspect()) + else: + return NotImplemented + + def __iadd__(self, other): + if isinstance(other, Tombola): + other_iterable = other.inspect() # <4> + else: + try: + other_iterable = iter(other) # <5> + except TypeError: # <6> + self_cls = type(self).__name__ + msg = "right operand in += must be {!r} or an iterable" + raise TypeError(msg.format(self_cls)) + self.load(other_iterable) # <7> + return self # <8> + + + + +# end::ADDABLE_BINGO[] diff --git a/16-op-overloading/tombola.py b/16-op-overloading/tombola.py new file mode 100644 index 0000000..5ed0f85 --- /dev/null +++ b/16-op-overloading/tombola.py @@ -0,0 +1,35 @@ +# BEGIN TOMBOLA_ABC + +import abc + +class Tombola(abc.ABC): # <1> + + @abc.abstractmethod + def load(self, iterable): # <2> + """Add items from an iterable.""" + + @abc.abstractmethod + def pick(self): # <3> + """Remove item at random, returning it. + + This method should raise `LookupError` when the instance is empty. + """ + + def loaded(self): # <4> + """Return `True` if there's at least 1 item, `False` otherwise.""" + return bool(self.inspect()) # <5> + + + def inspect(self): + """Return a sorted tuple with the items currently inside.""" + items = [] + while True: # <6> + try: + items.append(self.pick()) + except LookupError: + break + self.load(items) # <7> + return tuple(sorted(items)) + + +# END TOMBOLA_ABC diff --git a/16-op-overloading/unary_plus_decimal.py b/16-op-overloading/unary_plus_decimal.py new file mode 100644 index 0000000..35e5acf --- /dev/null +++ b/16-op-overloading/unary_plus_decimal.py @@ -0,0 +1,35 @@ +""" +# tag::UNARY_PLUS_DECIMAL[] + +>>> import decimal +>>> ctx = decimal.getcontext() # <1> +>>> ctx.prec = 40 # <2> +>>> one_third = decimal.Decimal('1') / decimal.Decimal('3') # <3> +>>> one_third # <4> +Decimal('0.3333333333333333333333333333333333333333') +>>> one_third == +one_third # <5> +True +>>> ctx.prec = 28 # <6> +>>> one_third == +one_third # <7> +False +>>> +one_third # <8> +Decimal('0.3333333333333333333333333333') + +# end::UNARY_PLUS_DECIMAL[] + +""" + +import decimal + +if __name__ == '__main__': + + with decimal.localcontext() as ctx: + ctx.prec = 40 + print('precision:', ctx.prec) + one_third = decimal.Decimal('1') / decimal.Decimal('3') + print(' one_third:', one_third) + print(' +one_third:', +one_third) + + print('precision:', decimal.getcontext().prec) + print(' one_third:', one_third) + print(' +one_third:', +one_third) diff --git a/16-op-overloading/vector2d_v3.py b/16-op-overloading/vector2d_v3.py new file mode 100644 index 0000000..5812dcf --- /dev/null +++ b/16-op-overloading/vector2d_v3.py @@ -0,0 +1,151 @@ +""" +A 2-dimensional vector class + + >>> v1 = Vector2d(3, 4) + >>> print(v1.x, v1.y) + 3.0 4.0 + >>> x, y = v1 + >>> x, y + (3.0, 4.0) + >>> v1 + Vector2d(3.0, 4.0) + >>> v1_clone = eval(repr(v1)) + >>> v1 == v1_clone + True + >>> print(v1) + (3.0, 4.0) + >>> octets = bytes(v1) + >>> octets + b'd\\x00\\x00\\x00\\x00\\x00\\x00\\x08@\\x00\\x00\\x00\\x00\\x00\\x00\\x10@' + >>> abs(v1) + 5.0 + >>> bool(v1), bool(Vector2d(0, 0)) + (True, False) + + +Test of ``.frombytes()`` class method: + + >>> v1_clone = Vector2d.frombytes(bytes(v1)) + >>> v1_clone + Vector2d(3.0, 4.0) + >>> v1 == v1_clone + True + + +Tests of ``format()`` with Cartesian coordinates: + + >>> format(v1) + '(3.0, 4.0)' + >>> format(v1, '.2f') + '(3.00, 4.00)' + >>> format(v1, '.3e') + '(3.000e+00, 4.000e+00)' + + +Tests of the ``angle`` method:: + + >>> Vector2d(0, 0).angle() + 0.0 + >>> Vector2d(1, 0).angle() + 0.0 + >>> epsilon = 10**-8 + >>> abs(Vector2d(0, 1).angle() - math.pi/2) < epsilon + True + >>> abs(Vector2d(1, 1).angle() - math.pi/4) < epsilon + True + + +Tests of ``format()`` with polar coordinates: + + >>> format(Vector2d(1, 1), 'p') # doctest:+ELLIPSIS + '<1.414213..., 0.785398...>' + >>> format(Vector2d(1, 1), '.3ep') + '<1.414e+00, 7.854e-01>' + >>> format(Vector2d(1, 1), '0.5fp') + '<1.41421, 0.78540>' + + +Tests of `x` and `y` read-only properties: + + >>> v1.x, v1.y + (3.0, 4.0) + >>> v1.x = 123 + Traceback (most recent call last): + ... + AttributeError: can't set attribute + + +Tests of hashing: + + >>> v1 = Vector2d(3, 4) + >>> v2 = Vector2d(3.1, 4.2) + >>> hash(v1), hash(v2) + (7, 384307168202284039) + >>> len(set([v1, v2])) + 2 + +""" + +from array import array +import math + +class Vector2d: + typecode = 'd' + + def __init__(self, x, y): + self.__x = float(x) + self.__y = float(y) + + @property + def x(self): + return self.__x + + @property + def y(self): + return self.__y + + def __iter__(self): + return (i for i in (self.x, self.y)) + + def __repr__(self): + class_name = type(self).__name__ + return '{}({!r}, {!r})'.format(class_name, *self) + + def __str__(self): + return str(tuple(self)) + + def __bytes__(self): + return (bytes([ord(self.typecode)]) + + bytes(array(self.typecode, self))) + + def __eq__(self, other): + return tuple(self) == tuple(other) + + def __hash__(self): + return hash(self.x) ^ hash(self.y) + + def __abs__(self): + return math.hypot(self.x, self.y) + + def __bool__(self): + return bool(abs(self)) + + def angle(self): + return math.atan2(self.y, self.x) + + def __format__(self, fmt_spec=''): + if fmt_spec.endswith('p'): + fmt_spec = fmt_spec[:-1] + coords = (abs(self), self.angle()) + outer_fmt = '<{}, {}>' + else: + coords = self + outer_fmt = '({}, {})' + components = (format(c, fmt_spec) for c in coords) + return outer_fmt.format(*components) + + @classmethod + def frombytes(cls, octets): + typecode = chr(octets[0]) + memv = memoryview(octets[1:]).cast(typecode) + return cls(*memv) diff --git a/16-op-overloading/vector_py3_5.py b/16-op-overloading/vector_py3_5.py new file mode 100644 index 0000000..ad04864 --- /dev/null +++ b/16-op-overloading/vector_py3_5.py @@ -0,0 +1,431 @@ +""" +A multi-dimensional ``Vector`` class, take 9: operator ``@`` + +WARNING: This example requires Python 3.5 or later. + +A ``Vector`` is built from an iterable of numbers:: + + >>> Vector([3.1, 4.2]) + Vector([3.1, 4.2]) + >>> Vector((3, 4, 5)) + Vector([3.0, 4.0, 5.0]) + >>> Vector(range(10)) + Vector([0.0, 1.0, 2.0, 3.0, 4.0, ...]) + + +Tests with 2-dimensions (same results as ``vector2d_v1.py``):: + + >>> v1 = Vector([3, 4]) + >>> x, y = v1 + >>> x, y + (3.0, 4.0) + >>> v1 + Vector([3.0, 4.0]) + >>> v1_clone = eval(repr(v1)) + >>> v1 == v1_clone + True + >>> print(v1) + (3.0, 4.0) + >>> octets = bytes(v1) + >>> octets + b'd\\x00\\x00\\x00\\x00\\x00\\x00\\x08@\\x00\\x00\\x00\\x00\\x00\\x00\\x10@' + >>> abs(v1) + 5.0 + >>> bool(v1), bool(Vector([0, 0])) + (True, False) + + +Test of ``.frombytes()`` class method: + + >>> v1_clone = Vector.frombytes(bytes(v1)) + >>> v1_clone + Vector([3.0, 4.0]) + >>> v1 == v1_clone + True + + +Tests with 3-dimensions:: + + >>> v1 = Vector([3, 4, 5]) + >>> x, y, z = v1 + >>> x, y, z + (3.0, 4.0, 5.0) + >>> v1 + Vector([3.0, 4.0, 5.0]) + >>> v1_clone = eval(repr(v1)) + >>> v1 == v1_clone + True + >>> print(v1) + (3.0, 4.0, 5.0) + >>> abs(v1) # doctest:+ELLIPSIS + 7.071067811... + >>> bool(v1), bool(Vector([0, 0, 0])) + (True, False) + + +Tests with many dimensions:: + + >>> v7 = Vector(range(7)) + >>> v7 + Vector([0.0, 1.0, 2.0, 3.0, 4.0, ...]) + >>> abs(v7) # doctest:+ELLIPSIS + 9.53939201... + + +Test of ``.__bytes__`` and ``.frombytes()`` methods:: + + >>> v1 = Vector([3, 4, 5]) + >>> v1_clone = Vector.frombytes(bytes(v1)) + >>> v1_clone + Vector([3.0, 4.0, 5.0]) + >>> v1 == v1_clone + True + + +Tests of sequence behavior:: + + >>> v1 = Vector([3, 4, 5]) + >>> len(v1) + 3 + >>> v1[0], v1[len(v1)-1], v1[-1] + (3.0, 5.0, 5.0) + + +Test of slicing:: + + >>> v7 = Vector(range(7)) + >>> v7[-1] + 6.0 + >>> v7[1:4] + Vector([1.0, 2.0, 3.0]) + >>> v7[-1:] + Vector([6.0]) + >>> v7[1,2] + Traceback (most recent call last): + ... + TypeError: Vector indices must be integers + + +Tests of dynamic attribute access:: + + >>> v7 = Vector(range(10)) + >>> v7.x + 0.0 + >>> v7.y, v7.z, v7.t + (1.0, 2.0, 3.0) + +Dynamic attribute lookup failures:: + + >>> v7.k + Traceback (most recent call last): + ... + AttributeError: 'Vector' object has no attribute 'k' + >>> v3 = Vector(range(3)) + >>> v3.t + Traceback (most recent call last): + ... + AttributeError: 'Vector' object has no attribute 't' + >>> v3.spam + Traceback (most recent call last): + ... + AttributeError: 'Vector' object has no attribute 'spam' + + +Tests of hashing:: + + >>> v1 = Vector([3, 4]) + >>> v2 = Vector([3.1, 4.2]) + >>> v3 = Vector([3, 4, 5]) + >>> v6 = Vector(range(6)) + >>> hash(v1), hash(v3), hash(v6) + (7, 2, 1) + + +Most hash codes of non-integers vary from a 32-bit to 64-bit Python build:: + + >>> import sys + >>> hash(v2) == (384307168202284039 if sys.maxsize > 2**32 else 357915986) + True + + +Tests of ``format()`` with Cartesian coordinates in 2D:: + + >>> v1 = Vector([3, 4]) + >>> format(v1) + '(3.0, 4.0)' + >>> format(v1, '.2f') + '(3.00, 4.00)' + >>> format(v1, '.3e') + '(3.000e+00, 4.000e+00)' + + +Tests of ``format()`` with Cartesian coordinates in 3D and 7D:: + + >>> v3 = Vector([3, 4, 5]) + >>> format(v3) + '(3.0, 4.0, 5.0)' + >>> format(Vector(range(7))) + '(0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0)' + + +Tests of ``format()`` with spherical coordinates in 2D, 3D and 4D:: + + >>> format(Vector([1, 1]), 'h') # doctest:+ELLIPSIS + '<1.414213..., 0.785398...>' + >>> format(Vector([1, 1]), '.3eh') + '<1.414e+00, 7.854e-01>' + >>> format(Vector([1, 1]), '0.5fh') + '<1.41421, 0.78540>' + >>> format(Vector([1, 1, 1]), 'h') # doctest:+ELLIPSIS + '<1.73205..., 0.95531..., 0.78539...>' + >>> format(Vector([2, 2, 2]), '.3eh') + '<3.464e+00, 9.553e-01, 7.854e-01>' + >>> format(Vector([0, 0, 0]), '0.5fh') + '<0.00000, 0.00000, 0.00000>' + >>> format(Vector([-1, -1, -1, -1]), 'h') # doctest:+ELLIPSIS + '<2.0, 2.09439..., 2.18627..., 3.92699...>' + >>> format(Vector([2, 2, 2, 2]), '.3eh') + '<4.000e+00, 1.047e+00, 9.553e-01, 7.854e-01>' + >>> format(Vector([0, 1, 0, 0]), '0.5fh') + '<1.00000, 1.57080, 0.00000, 0.00000>' + + +Basic tests of operator ``+``:: + + >>> v1 = Vector([3, 4, 5]) + >>> v2 = Vector([6, 7, 8]) + >>> v1 + v2 + Vector([9.0, 11.0, 13.0]) + >>> v1 + v2 == Vector([3+6, 4+7, 5+8]) + True + >>> v3 = Vector([1, 2]) + >>> v1 + v3 # short vectors are filled with 0.0 on addition + Vector([4.0, 6.0, 5.0]) + + +Tests of ``+`` with mixed types:: + + >>> v1 + (10, 20, 30) + Vector([13.0, 24.0, 35.0]) + >>> from vector2d_v3 import Vector2d + >>> v2d = Vector2d(1, 2) + >>> v1 + v2d + Vector([4.0, 6.0, 5.0]) + + +Tests of ``+`` with mixed types, swapped operands:: + + >>> (10, 20, 30) + v1 + Vector([13.0, 24.0, 35.0]) + >>> from vector2d_v3 import Vector2d + >>> v2d = Vector2d(1, 2) + >>> v2d + v1 + Vector([4.0, 6.0, 5.0]) + + +Tests of ``+`` with an unsuitable operand: + + >>> v1 + 1 + Traceback (most recent call last): + ... + TypeError: unsupported operand type(s) for +: 'Vector' and 'int' + >>> v1 + 'ABC' + Traceback (most recent call last): + ... + TypeError: unsupported operand type(s) for +: 'Vector' and 'str' + + +Basic tests of operator ``*``:: + + >>> v1 = Vector([1, 2, 3]) + >>> v1 * 10 + Vector([10.0, 20.0, 30.0]) + >>> 10 * v1 + Vector([10.0, 20.0, 30.0]) + + +Tests of ``*`` with unusual but valid operands:: + + >>> v1 * True + Vector([1.0, 2.0, 3.0]) + >>> from fractions import Fraction + >>> v1 * Fraction(1, 3) # doctest:+ELLIPSIS + Vector([0.3333..., 0.6666..., 1.0]) + + +Tests of ``*`` with unsuitable operands:: + + >>> v1 * (1, 2) + Traceback (most recent call last): + ... + TypeError: can't multiply sequence by non-int of type 'Vector' + + +Tests of operator `==`:: + + >>> va = Vector(range(1, 4)) + >>> vb = Vector([1.0, 2.0, 3.0]) + >>> va == vb + True + >>> vc = Vector([1, 2]) + >>> from vector2d_v3 import Vector2d + >>> v2d = Vector2d(1, 2) + >>> vc == v2d + True + >>> va == (1, 2, 3) + False + + +Tests of operator `!=`:: + + >>> va != vb + False + >>> vc != v2d + False + >>> va != (1, 2, 3) + True + + +Tests for operator `@` (Python >= 3.5), computing the dot product:: + + >>> va = Vector([1, 2, 3]) + >>> vz = Vector([5, 6, 7]) + >>> va @ vz == 38.0 # 1*5 + 2*6 + 3*7 + True + >>> [10, 20, 30] @ vz + 380.0 + >>> va @ 3 + Traceback (most recent call last): + ... + TypeError: unsupported operand type(s) for @: 'Vector' and 'int' + + +""" + +from array import array +import reprlib +import math +import functools +import operator +import itertools +import numbers + + +class Vector: + typecode = 'd' + + def __init__(self, components): + self._components = array(self.typecode, components) + + def __iter__(self): + return iter(self._components) + + def __repr__(self): + components = reprlib.repr(self._components) + components = components[components.find('['):-1] + return 'Vector({})'.format(components) + + def __str__(self): + return str(tuple(self)) + + def __bytes__(self): + return (bytes([ord(self.typecode)]) + + bytes(self._components)) + + def __eq__(self, other): + if isinstance(other, Vector): + return (len(self) == len(other) and + all(a == b for a, b in zip(self, other))) + else: + return NotImplemented + + def __hash__(self): + hashes = (hash(x) for x in self) + return functools.reduce(operator.xor, hashes, 0) + + def __abs__(self): + return math.sqrt(sum(x * x for x in self)) + + def __bool__(self): + return bool(abs(self)) + + def __len__(self): + return len(self._components) + + def __getitem__(self, index): + cls = type(self) + if isinstance(index, slice): + return cls(self._components[index]) + elif isinstance(index, int): + return self._components[index] + else: + msg = '{.__name__} indices must be integers' + raise TypeError(msg.format(cls)) + + shortcut_names = 'xyzt' + + def __getattr__(self, name): + cls = type(self) + if len(name) == 1: + pos = cls.shortcut_names.find(name) + if 0 <= pos < len(self._components): + return self._components[pos] + msg = '{.__name__!r} object has no attribute {!r}' + raise AttributeError(msg.format(cls, name)) + + def angle(self, n): + r = math.sqrt(sum(x * x for x in self[n:])) + a = math.atan2(r, self[n-1]) + if (n == len(self) - 1) and (self[-1] < 0): + return math.pi * 2 - a + else: + return a + + def angles(self): + return (self.angle(n) for n in range(1, len(self))) + + def __format__(self, fmt_spec=''): + if fmt_spec.endswith('h'): # hyperspherical coordinates + fmt_spec = fmt_spec[:-1] + coords = itertools.chain([abs(self)], + self.angles()) + outer_fmt = '<{}>' + else: + coords = self + outer_fmt = '({})' + components = (format(c, fmt_spec) for c in coords) + return outer_fmt.format(', '.join(components)) + + @classmethod + def frombytes(cls, octets): + typecode = chr(octets[0]) + memv = memoryview(octets[1:]).cast(typecode) + return cls(memv) + + def __add__(self, other): + try: + pairs = itertools.zip_longest(self, other, fillvalue=0.0) + return Vector(a + b for a, b in pairs) + except TypeError: + return NotImplemented + + def __radd__(self, other): + return self + other + + def __mul__(self, scalar): + if isinstance(scalar, numbers.Real): + return Vector(n * scalar for n in self) + else: + return NotImplemented + + def __rmul__(self, scalar): + return self * scalar + + def __matmul__(self, other): + try: + return sum(a * b for a, b in zip(self, other)) + except TypeError: + return NotImplemented + + def __rmatmul__(self, other): + return self @ other # this only works in Python 3.5 diff --git a/16-op-overloading/vector_v6.py b/16-op-overloading/vector_v6.py new file mode 100644 index 0000000..a272a9b --- /dev/null +++ b/16-op-overloading/vector_v6.py @@ -0,0 +1,358 @@ +""" +A multi-dimensional ``Vector`` class, take 6: operator ``+`` + +A ``Vector`` is built from an iterable of numbers:: + + >>> Vector([3.1, 4.2]) + Vector([3.1, 4.2]) + >>> Vector((3, 4, 5)) + Vector([3.0, 4.0, 5.0]) + >>> Vector(range(10)) + Vector([0.0, 1.0, 2.0, 3.0, 4.0, ...]) + + +Tests with 2-dimensions (same results as ``vector2d_v1.py``):: + + >>> v1 = Vector([3, 4]) + >>> x, y = v1 + >>> x, y + (3.0, 4.0) + >>> v1 + Vector([3.0, 4.0]) + >>> v1_clone = eval(repr(v1)) + >>> v1 == v1_clone + True + >>> print(v1) + (3.0, 4.0) + >>> octets = bytes(v1) + >>> octets + b'd\\x00\\x00\\x00\\x00\\x00\\x00\\x08@\\x00\\x00\\x00\\x00\\x00\\x00\\x10@' + >>> abs(v1) + 5.0 + >>> bool(v1), bool(Vector([0, 0])) + (True, False) + + +Test of ``.frombytes()`` class method: + + >>> v1_clone = Vector.frombytes(bytes(v1)) + >>> v1_clone + Vector([3.0, 4.0]) + >>> v1 == v1_clone + True + + +Tests with 3-dimensions:: + + >>> v1 = Vector([3, 4, 5]) + >>> x, y, z = v1 + >>> x, y, z + (3.0, 4.0, 5.0) + >>> v1 + Vector([3.0, 4.0, 5.0]) + >>> v1_clone = eval(repr(v1)) + >>> v1 == v1_clone + True + >>> print(v1) + (3.0, 4.0, 5.0) + >>> abs(v1) # doctest:+ELLIPSIS + 7.071067811... + >>> bool(v1), bool(Vector([0, 0, 0])) + (True, False) + + +Tests with many dimensions:: + + >>> v7 = Vector(range(7)) + >>> v7 + Vector([0.0, 1.0, 2.0, 3.0, 4.0, ...]) + >>> abs(v7) # doctest:+ELLIPSIS + 9.53939201... + + +Test of ``.__bytes__`` and ``.frombytes()`` methods:: + + >>> v1 = Vector([3, 4, 5]) + >>> v1_clone = Vector.frombytes(bytes(v1)) + >>> v1_clone + Vector([3.0, 4.0, 5.0]) + >>> v1 == v1_clone + True + + +Tests of sequence behavior:: + + >>> v1 = Vector([3, 4, 5]) + >>> len(v1) + 3 + >>> v1[0], v1[len(v1)-1], v1[-1] + (3.0, 5.0, 5.0) + + +Test of slicing:: + + >>> v7 = Vector(range(7)) + >>> v7[-1] + 6.0 + >>> v7[1:4] + Vector([1.0, 2.0, 3.0]) + >>> v7[-1:] + Vector([6.0]) + >>> v7[1,2] + Traceback (most recent call last): + ... + TypeError: 'tuple' object cannot be interpreted as an integer + + +Tests of dynamic attribute access:: + + >>> v7 = Vector(range(10)) + >>> v7.x + 0.0 + >>> v7.y, v7.z, v7.t + (1.0, 2.0, 3.0) + +Dynamic attribute lookup failures:: + + >>> v7.k + Traceback (most recent call last): + ... + AttributeError: 'Vector' object has no attribute 'k' + >>> v3 = Vector(range(3)) + >>> v3.t + Traceback (most recent call last): + ... + AttributeError: 'Vector' object has no attribute 't' + >>> v3.spam + Traceback (most recent call last): + ... + AttributeError: 'Vector' object has no attribute 'spam' + + +Tests of hashing:: + + >>> v1 = Vector([3, 4]) + >>> v2 = Vector([3.1, 4.2]) + >>> v3 = Vector([3, 4, 5]) + >>> v6 = Vector(range(6)) + >>> hash(v1), hash(v3), hash(v6) + (7, 2, 1) + + +Most hash codes of non-integers vary from a 32-bit to 64-bit Python build:: + + >>> import sys + >>> hash(v2) == (384307168202284039 if sys.maxsize > 2**32 else 357915986) + True + + +Tests of ``format()`` with Cartesian coordinates in 2D:: + + >>> v1 = Vector([3, 4]) + >>> format(v1) + '(3.0, 4.0)' + >>> format(v1, '.2f') + '(3.00, 4.00)' + >>> format(v1, '.3e') + '(3.000e+00, 4.000e+00)' + + +Tests of ``format()`` with Cartesian coordinates in 3D and 7D:: + + >>> v3 = Vector([3, 4, 5]) + >>> format(v3) + '(3.0, 4.0, 5.0)' + >>> format(Vector(range(7))) + '(0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0)' + + +Tests of ``format()`` with spherical coordinates in 2D, 3D and 4D:: + + >>> format(Vector([1, 1]), 'h') # doctest:+ELLIPSIS + '<1.414213..., 0.785398...>' + >>> format(Vector([1, 1]), '.3eh') + '<1.414e+00, 7.854e-01>' + >>> format(Vector([1, 1]), '0.5fh') + '<1.41421, 0.78540>' + >>> format(Vector([1, 1, 1]), 'h') # doctest:+ELLIPSIS + '<1.73205..., 0.95531..., 0.78539...>' + >>> format(Vector([2, 2, 2]), '.3eh') + '<3.464e+00, 9.553e-01, 7.854e-01>' + >>> format(Vector([0, 0, 0]), '0.5fh') + '<0.00000, 0.00000, 0.00000>' + >>> format(Vector([-1, -1, -1, -1]), 'h') # doctest:+ELLIPSIS + '<2.0, 2.09439..., 2.18627..., 3.92699...>' + >>> format(Vector([2, 2, 2, 2]), '.3eh') + '<4.000e+00, 1.047e+00, 9.553e-01, 7.854e-01>' + >>> format(Vector([0, 1, 0, 0]), '0.5fh') + '<1.00000, 1.57080, 0.00000, 0.00000>' + + +Unary operator tests:: + + >>> v1 = Vector([3, 4]) + >>> abs(v1) + 5.0 + >>> -v1 + Vector([-3.0, -4.0]) + >>> +v1 + Vector([3.0, 4.0]) + + +Basic tests of operator ``+``:: + + >>> v1 = Vector([3, 4, 5]) + >>> v2 = Vector([6, 7, 8]) + >>> v1 + v2 + Vector([9.0, 11.0, 13.0]) + >>> v1 + v2 == Vector([3+6, 4+7, 5+8]) + True + >>> v3 = Vector([1, 2]) + >>> v1 + v3 # short vectors are filled with 0.0 on addition + Vector([4.0, 6.0, 5.0]) + + +Tests of ``+`` with mixed types:: + + >>> v1 + (10, 20, 30) + Vector([13.0, 24.0, 35.0]) + >>> from vector2d_v3 import Vector2d + >>> v2d = Vector2d(1, 2) + >>> v1 + v2d + Vector([4.0, 6.0, 5.0]) + + +Tests of ``+`` with mixed types, swapped operands:: + + >>> (10, 20, 30) + v1 + Vector([13.0, 24.0, 35.0]) + >>> from vector2d_v3 import Vector2d + >>> v2d = Vector2d(1, 2) + >>> v2d + v1 + Vector([4.0, 6.0, 5.0]) + + +Tests of ``+`` with an unsuitable operand: + + >>> v1 + 1 + Traceback (most recent call last): + ... + TypeError: unsupported operand type(s) for +: 'Vector' and 'int' + >>> v1 + 'ABC' + Traceback (most recent call last): + ... + TypeError: unsupported operand type(s) for +: 'Vector' and 'str' +""" + +from array import array +import reprlib +import math +import functools +import operator +import itertools + + +class Vector: + typecode = 'd' + + def __init__(self, components): + self._components = array(self.typecode, components) + + def __iter__(self): + return iter(self._components) + + def __repr__(self): + components = reprlib.repr(self._components) + components = components[components.find('['):-1] + return 'Vector({})'.format(components) + + def __str__(self): + return str(tuple(self)) + + def __bytes__(self): + return (bytes([ord(self.typecode)]) + + bytes(self._components)) + + def __eq__(self, other): + return (len(self) == len(other) and + all(a == b for a, b in zip(self, other))) + + def __hash__(self): + hashes = (hash(x) for x in self) + return functools.reduce(operator.xor, hashes, 0) + +# tag::VECTOR_V6_UNARY[] + def __abs__(self): + return math.sqrt(sum(x * x for x in self)) + + def __neg__(self): + return Vector(-x for x in self) # <1> + + def __pos__(self): + return Vector(self) # <2> +# end::VECTOR_V6_UNARY[] + + def __bool__(self): + return bool(abs(self)) + + def __len__(self): + return len(self._components) + + def __getitem__(self, key): + if isinstance(key, slice): + cls = type(self) + return cls(self._components[key]) + index = operator.index(key) + return self._components[index] + + shortcut_names = 'xyzt' + + def __getattr__(self, name): + cls = type(self) + if len(name) == 1: + pos = cls.shortcut_names.find(name) + if 0 <= pos < len(self._components): + return self._components[pos] + msg = '{.__name__!r} object has no attribute {!r}' + raise AttributeError(msg.format(cls, name)) + + def angle(self, n): + r = math.sqrt(sum(x * x for x in self[n:])) + a = math.atan2(r, self[n-1]) + if (n == len(self) - 1) and (self[-1] < 0): + return math.pi * 2 - a + else: + return a + + def angles(self): + return (self.angle(n) for n in range(1, len(self))) + + def __format__(self, fmt_spec=''): + if fmt_spec.endswith('h'): # hyperspherical coordinates + fmt_spec = fmt_spec[:-1] + coords = itertools.chain([abs(self)], + self.angles()) + outer_fmt = '<{}>' + else: + coords = self + outer_fmt = '({})' + components = (format(c, fmt_spec) for c in coords) + return outer_fmt.format(', '.join(components)) + + @classmethod + def frombytes(cls, octets): + typecode = chr(octets[0]) + memv = memoryview(octets[1:]).cast(typecode) + return cls(memv) + +# tag::VECTOR_V6_ADD[] + def __add__(self, other): + try: + pairs = itertools.zip_longest(self, other, fillvalue=0.0) + return Vector(a + b for a, b in pairs) + except TypeError: + return NotImplemented + + def __radd__(self, other): + return self + other +# end::VECTOR_V6_ADD[] diff --git a/16-op-overloading/vector_v7.py b/16-op-overloading/vector_v7.py new file mode 100644 index 0000000..241956e --- /dev/null +++ b/16-op-overloading/vector_v7.py @@ -0,0 +1,429 @@ +""" +A multi-dimensional ``Vector`` class, take 7: operator ``*`` + +A ``Vector`` is built from an iterable of numbers:: + + >>> Vector([3.1, 4.2]) + Vector([3.1, 4.2]) + >>> Vector((3, 4, 5)) + Vector([3.0, 4.0, 5.0]) + >>> Vector(range(10)) + Vector([0.0, 1.0, 2.0, 3.0, 4.0, ...]) + + +Tests with 2-dimensions (same results as ``vector2d_v1.py``):: + + >>> v1 = Vector([3, 4]) + >>> x, y = v1 + >>> x, y + (3.0, 4.0) + >>> v1 + Vector([3.0, 4.0]) + >>> v1_clone = eval(repr(v1)) + >>> v1 == v1_clone + True + >>> print(v1) + (3.0, 4.0) + >>> octets = bytes(v1) + >>> octets + b'd\\x00\\x00\\x00\\x00\\x00\\x00\\x08@\\x00\\x00\\x00\\x00\\x00\\x00\\x10@' + >>> abs(v1) + 5.0 + >>> bool(v1), bool(Vector([0, 0])) + (True, False) + + +Test of ``.frombytes()`` class method: + + >>> v1_clone = Vector.frombytes(bytes(v1)) + >>> v1_clone + Vector([3.0, 4.0]) + >>> v1 == v1_clone + True + + +Tests with 3-dimensions:: + + >>> v1 = Vector([3, 4, 5]) + >>> x, y, z = v1 + >>> x, y, z + (3.0, 4.0, 5.0) + >>> v1 + Vector([3.0, 4.0, 5.0]) + >>> v1_clone = eval(repr(v1)) + >>> v1 == v1_clone + True + >>> print(v1) + (3.0, 4.0, 5.0) + >>> abs(v1) # doctest:+ELLIPSIS + 7.071067811... + >>> bool(v1), bool(Vector([0, 0, 0])) + (True, False) + + +Tests with many dimensions:: + + >>> v7 = Vector(range(7)) + >>> v7 + Vector([0.0, 1.0, 2.0, 3.0, 4.0, ...]) + >>> abs(v7) # doctest:+ELLIPSIS + 9.53939201... + + +Test of ``.__bytes__`` and ``.frombytes()`` methods:: + + >>> v1 = Vector([3, 4, 5]) + >>> v1_clone = Vector.frombytes(bytes(v1)) + >>> v1_clone + Vector([3.0, 4.0, 5.0]) + >>> v1 == v1_clone + True + + +Tests of sequence behavior:: + + >>> v1 = Vector([3, 4, 5]) + >>> len(v1) + 3 + >>> v1[0], v1[len(v1)-1], v1[-1] + (3.0, 5.0, 5.0) + + +Test of slicing:: + + >>> v7 = Vector(range(7)) + >>> v7[-1] + 6.0 + >>> v7[1:4] + Vector([1.0, 2.0, 3.0]) + >>> v7[-1:] + Vector([6.0]) + >>> v7[1,2] + Traceback (most recent call last): + ... + TypeError: 'tuple' object cannot be interpreted as an integer + + +Tests of dynamic attribute access:: + + >>> v7 = Vector(range(10)) + >>> v7.x + 0.0 + >>> v7.y, v7.z, v7.t + (1.0, 2.0, 3.0) + +Dynamic attribute lookup failures:: + + >>> v7.k + Traceback (most recent call last): + ... + AttributeError: 'Vector' object has no attribute 'k' + >>> v3 = Vector(range(3)) + >>> v3.t + Traceback (most recent call last): + ... + AttributeError: 'Vector' object has no attribute 't' + >>> v3.spam + Traceback (most recent call last): + ... + AttributeError: 'Vector' object has no attribute 'spam' + + +Tests of hashing:: + + >>> v1 = Vector([3, 4]) + >>> v2 = Vector([3.1, 4.2]) + >>> v3 = Vector([3, 4, 5]) + >>> v6 = Vector(range(6)) + >>> hash(v1), hash(v3), hash(v6) + (7, 2, 1) + + +Most hash codes of non-integers vary from a 32-bit to 64-bit Python build:: + + >>> import sys + >>> hash(v2) == (384307168202284039 if sys.maxsize > 2**32 else 357915986) + True + + +Tests of ``format()`` with Cartesian coordinates in 2D:: + + >>> v1 = Vector([3, 4]) + >>> format(v1) + '(3.0, 4.0)' + >>> format(v1, '.2f') + '(3.00, 4.00)' + >>> format(v1, '.3e') + '(3.000e+00, 4.000e+00)' + + +Tests of ``format()`` with Cartesian coordinates in 3D and 7D:: + + >>> v3 = Vector([3, 4, 5]) + >>> format(v3) + '(3.0, 4.0, 5.0)' + >>> format(Vector(range(7))) + '(0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0)' + + +Tests of ``format()`` with spherical coordinates in 2D, 3D and 4D:: + + >>> format(Vector([1, 1]), 'h') # doctest:+ELLIPSIS + '<1.414213..., 0.785398...>' + >>> format(Vector([1, 1]), '.3eh') + '<1.414e+00, 7.854e-01>' + >>> format(Vector([1, 1]), '0.5fh') + '<1.41421, 0.78540>' + >>> format(Vector([1, 1, 1]), 'h') # doctest:+ELLIPSIS + '<1.73205..., 0.95531..., 0.78539...>' + >>> format(Vector([2, 2, 2]), '.3eh') + '<3.464e+00, 9.553e-01, 7.854e-01>' + >>> format(Vector([0, 0, 0]), '0.5fh') + '<0.00000, 0.00000, 0.00000>' + >>> format(Vector([-1, -1, -1, -1]), 'h') # doctest:+ELLIPSIS + '<2.0, 2.09439..., 2.18627..., 3.92699...>' + >>> format(Vector([2, 2, 2, 2]), '.3eh') + '<4.000e+00, 1.047e+00, 9.553e-01, 7.854e-01>' + >>> format(Vector([0, 1, 0, 0]), '0.5fh') + '<1.00000, 1.57080, 0.00000, 0.00000>' + + +Unary operator tests:: + + >>> v1 = Vector([3, 4]) + >>> abs(v1) + 5.0 + >>> -v1 + Vector([-3.0, -4.0]) + >>> +v1 + Vector([3.0, 4.0]) + + +Basic tests of operator ``+``:: + + >>> v1 = Vector([3, 4, 5]) + >>> v2 = Vector([6, 7, 8]) + >>> v1 + v2 + Vector([9.0, 11.0, 13.0]) + >>> v1 + v2 == Vector([3+6, 4+7, 5+8]) + True + >>> v3 = Vector([1, 2]) + >>> v1 + v3 # short vectors are filled with 0.0 on addition + Vector([4.0, 6.0, 5.0]) + + +Tests of ``+`` with mixed types:: + + >>> v1 + (10, 20, 30) + Vector([13.0, 24.0, 35.0]) + >>> from vector2d_v3 import Vector2d + >>> v2d = Vector2d(1, 2) + >>> v1 + v2d + Vector([4.0, 6.0, 5.0]) + + +Tests of ``+`` with mixed types, swapped operands:: + + >>> (10, 20, 30) + v1 + Vector([13.0, 24.0, 35.0]) + >>> from vector2d_v3 import Vector2d + >>> v2d = Vector2d(1, 2) + >>> v2d + v1 + Vector([4.0, 6.0, 5.0]) + + +Tests of ``+`` with an unsuitable operand: + + >>> v1 + 1 + Traceback (most recent call last): + ... + TypeError: unsupported operand type(s) for +: 'Vector' and 'int' + >>> v1 + 'ABC' + Traceback (most recent call last): + ... + TypeError: unsupported operand type(s) for +: 'Vector' and 'str' + + +Basic tests of operator ``*``:: + + >>> v1 = Vector([1, 2, 3]) + >>> v1 * 10 + Vector([10.0, 20.0, 30.0]) + >>> 10 * v1 + Vector([10.0, 20.0, 30.0]) + + +Tests of ``*`` with unusual but valid operands:: + + >>> v1 * True + Vector([1.0, 2.0, 3.0]) + >>> from fractions import Fraction + >>> v1 * Fraction(1, 3) # doctest:+ELLIPSIS + Vector([0.3333..., 0.6666..., 1.0]) + + +Tests of ``*`` with unsuitable operands:: + + >>> v1 * (1, 2) + Traceback (most recent call last): + ... + TypeError: can't multiply sequence by non-int of type 'Vector' + + +Tests of ``@``:: + + >>> va = Vector([1, 2, 3]) + >>> vz = Vector([5, 6, 7]) + >>> va @ vz == 38.0 # 1*5 + 2*6 + 3*7 + True + >>> [10, 20, 30] @ vz + 380.0 + >>> va @ 3 + Traceback (most recent call last): + ... + TypeError: unsupported operand type(s) for @: 'Vector' and 'int' + + +For ``@`` to work, both operands need to have the same length:: + + >>> va = Vector([1, 2, 3]) + >>> vb = Vector([1, 2]) + >>> va @ vb + Traceback (most recent call last): + ... + ValueError: @ requires vectors of equal length. + +""" + +from array import array +import reprlib +import math +import functools +import operator +import itertools +from collections import abc + + +class Vector: + typecode = 'd' + + def __init__(self, components): + self._components = array(self.typecode, components) + + def __iter__(self): + return iter(self._components) + + def __repr__(self): + components = reprlib.repr(self._components) + components = components[components.find('['):-1] + return 'Vector({})'.format(components) + + def __str__(self): + return str(tuple(self)) + + def __bytes__(self): + return (bytes([ord(self.typecode)]) + + bytes(self._components)) + + def __eq__(self, other): + return (len(self) == len(other) and + all(a == b for a, b in zip(self, other))) + + def __hash__(self): + hashes = (hash(x) for x in self) + return functools.reduce(operator.xor, hashes, 0) + + def __abs__(self): + return math.sqrt(sum(x * x for x in self)) + + def __neg__(self): + return Vector(-x for x in self) + + def __pos__(self): + return Vector(self) + + def __bool__(self): + return bool(abs(self)) + + def __len__(self): + return len(self._components) + + def __getitem__(self, key): + if isinstance(key, slice): + cls = type(self) + return cls(self._components[key]) + index = operator.index(key) + return self._components[index] + + shortcut_names = 'xyzt' + + def __getattr__(self, name): + cls = type(self) + if len(name) == 1: + pos = cls.shortcut_names.find(name) + if 0 <= pos < len(self._components): + return self._components[pos] + msg = '{.__name__!r} object has no attribute {!r}' + raise AttributeError(msg.format(cls, name)) + + def angle(self, n): + r = math.sqrt(sum(x * x for x in self[n:])) + a = math.atan2(r, self[n-1]) + if (n == len(self) - 1) and (self[-1] < 0): + return math.pi * 2 - a + else: + return a + + def angles(self): + return (self.angle(n) for n in range(1, len(self))) + + def __format__(self, fmt_spec=''): + if fmt_spec.endswith('h'): # hyperspherical coordinates + fmt_spec = fmt_spec[:-1] + coords = itertools.chain([abs(self)], + self.angles()) + outer_fmt = '<{}>' + else: + coords = self + outer_fmt = '({})' + components = (format(c, fmt_spec) for c in coords) + return outer_fmt.format(', '.join(components)) + + @classmethod + def frombytes(cls, octets): + typecode = chr(octets[0]) + memv = memoryview(octets[1:]).cast(typecode) + return cls(memv) + + def __add__(self, other): + try: + pairs = itertools.zip_longest(self, other, fillvalue=0.0) + return Vector(a + b for a, b in pairs) + except TypeError: + return NotImplemented + + def __radd__(self, other): + return self + other + + def __mul__(self, scalar): + try: + factor = float(scalar) + except TypeError: + return NotImplemented + return Vector(n * factor for n in self) + + def __rmul__(self, scalar): + return self * scalar + + def __matmul__(self, other): + if (isinstance(other, abc.Sized) and + isinstance(other, abc.Iterable)): + if len(self) == len(other): + return sum(a * b for a, b in zip(self, other)) + else: + raise ValueError('@ requires vectors of equal length.') + else: + return NotImplemented + + def __rmatmul__(self, other): + return self @ other diff --git a/16-op-overloading/vector_v8.py b/16-op-overloading/vector_v8.py new file mode 100644 index 0000000..48b4cc5 --- /dev/null +++ b/16-op-overloading/vector_v8.py @@ -0,0 +1,421 @@ +""" +A multi-dimensional ``Vector`` class, take 8: operator ``==`` + +A ``Vector`` is built from an iterable of numbers:: + + >>> Vector([3.1, 4.2]) + Vector([3.1, 4.2]) + >>> Vector((3, 4, 5)) + Vector([3.0, 4.0, 5.0]) + >>> Vector(range(10)) + Vector([0.0, 1.0, 2.0, 3.0, 4.0, ...]) + + +Tests with 2-dimensions (same results as ``vector2d_v1.py``):: + + >>> v1 = Vector([3, 4]) + >>> x, y = v1 + >>> x, y + (3.0, 4.0) + >>> v1 + Vector([3.0, 4.0]) + >>> v1_clone = eval(repr(v1)) + >>> v1 == v1_clone + True + >>> print(v1) + (3.0, 4.0) + >>> octets = bytes(v1) + >>> octets + b'd\\x00\\x00\\x00\\x00\\x00\\x00\\x08@\\x00\\x00\\x00\\x00\\x00\\x00\\x10@' + >>> abs(v1) + 5.0 + >>> bool(v1), bool(Vector([0, 0])) + (True, False) + + +Test of ``.frombytes()`` class method: + + >>> v1_clone = Vector.frombytes(bytes(v1)) + >>> v1_clone + Vector([3.0, 4.0]) + >>> v1 == v1_clone + True + + +Tests with 3-dimensions:: + + >>> v1 = Vector([3, 4, 5]) + >>> x, y, z = v1 + >>> x, y, z + (3.0, 4.0, 5.0) + >>> v1 + Vector([3.0, 4.0, 5.0]) + >>> v1_clone = eval(repr(v1)) + >>> v1 == v1_clone + True + >>> print(v1) + (3.0, 4.0, 5.0) + >>> abs(v1) # doctest:+ELLIPSIS + 7.071067811... + >>> bool(v1), bool(Vector([0, 0, 0])) + (True, False) + + +Tests with many dimensions:: + + >>> v7 = Vector(range(7)) + >>> v7 + Vector([0.0, 1.0, 2.0, 3.0, 4.0, ...]) + >>> abs(v7) # doctest:+ELLIPSIS + 9.53939201... + + +Test of ``.__bytes__`` and ``.frombytes()`` methods:: + + >>> v1 = Vector([3, 4, 5]) + >>> v1_clone = Vector.frombytes(bytes(v1)) + >>> v1_clone + Vector([3.0, 4.0, 5.0]) + >>> v1 == v1_clone + True + + +Tests of sequence behavior:: + + >>> v1 = Vector([3, 4, 5]) + >>> len(v1) + 3 + >>> v1[0], v1[len(v1)-1], v1[-1] + (3.0, 5.0, 5.0) + + +Test of slicing:: + + >>> v7 = Vector(range(7)) + >>> v7[-1] + 6.0 + >>> v7[1:4] + Vector([1.0, 2.0, 3.0]) + >>> v7[-1:] + Vector([6.0]) + >>> v7[1,2] + Traceback (most recent call last): + ... + TypeError: 'tuple' object cannot be interpreted as an integer + + +Tests of dynamic attribute access:: + + >>> v7 = Vector(range(10)) + >>> v7.x + 0.0 + >>> v7.y, v7.z, v7.t + (1.0, 2.0, 3.0) + +Dynamic attribute lookup failures:: + + >>> v7.k + Traceback (most recent call last): + ... + AttributeError: 'Vector' object has no attribute 'k' + >>> v3 = Vector(range(3)) + >>> v3.t + Traceback (most recent call last): + ... + AttributeError: 'Vector' object has no attribute 't' + >>> v3.spam + Traceback (most recent call last): + ... + AttributeError: 'Vector' object has no attribute 'spam' + + +Tests of hashing:: + + >>> v1 = Vector([3, 4]) + >>> v2 = Vector([3.1, 4.2]) + >>> v3 = Vector([3, 4, 5]) + >>> v6 = Vector(range(6)) + >>> hash(v1), hash(v3), hash(v6) + (7, 2, 1) + + +Most hash codes of non-integers vary from a 32-bit to 64-bit Python build:: + + >>> import sys + >>> hash(v2) == (384307168202284039 if sys.maxsize > 2**32 else 357915986) + True + + +Tests of ``format()`` with Cartesian coordinates in 2D:: + + >>> v1 = Vector([3, 4]) + >>> format(v1) + '(3.0, 4.0)' + >>> format(v1, '.2f') + '(3.00, 4.00)' + >>> format(v1, '.3e') + '(3.000e+00, 4.000e+00)' + + +Tests of ``format()`` with Cartesian coordinates in 3D and 7D:: + + >>> v3 = Vector([3, 4, 5]) + >>> format(v3) + '(3.0, 4.0, 5.0)' + >>> format(Vector(range(7))) + '(0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0)' + + +Tests of ``format()`` with spherical coordinates in 2D, 3D and 4D:: + + >>> format(Vector([1, 1]), 'h') # doctest:+ELLIPSIS + '<1.414213..., 0.785398...>' + >>> format(Vector([1, 1]), '.3eh') + '<1.414e+00, 7.854e-01>' + >>> format(Vector([1, 1]), '0.5fh') + '<1.41421, 0.78540>' + >>> format(Vector([1, 1, 1]), 'h') # doctest:+ELLIPSIS + '<1.73205..., 0.95531..., 0.78539...>' + >>> format(Vector([2, 2, 2]), '.3eh') + '<3.464e+00, 9.553e-01, 7.854e-01>' + >>> format(Vector([0, 0, 0]), '0.5fh') + '<0.00000, 0.00000, 0.00000>' + >>> format(Vector([-1, -1, -1, -1]), 'h') # doctest:+ELLIPSIS + '<2.0, 2.09439..., 2.18627..., 3.92699...>' + >>> format(Vector([2, 2, 2, 2]), '.3eh') + '<4.000e+00, 1.047e+00, 9.553e-01, 7.854e-01>' + >>> format(Vector([0, 1, 0, 0]), '0.5fh') + '<1.00000, 1.57080, 0.00000, 0.00000>' + + +Unary operator tests:: + + >>> v1 = Vector([3, 4]) + >>> abs(v1) + 5.0 + >>> -v1 + Vector([-3.0, -4.0]) + >>> +v1 + Vector([3.0, 4.0]) + + +Basic tests of operator ``+``:: + + >>> v1 = Vector([3, 4, 5]) + >>> v2 = Vector([6, 7, 8]) + >>> v1 + v2 + Vector([9.0, 11.0, 13.0]) + >>> v1 + v2 == Vector([3+6, 4+7, 5+8]) + True + >>> v3 = Vector([1, 2]) + >>> v1 + v3 # short vectors are filled with 0.0 on addition + Vector([4.0, 6.0, 5.0]) + + +Tests of ``+`` with mixed types:: + + >>> v1 + (10, 20, 30) + Vector([13.0, 24.0, 35.0]) + >>> from vector2d_v3 import Vector2d + >>> v2d = Vector2d(1, 2) + >>> v1 + v2d + Vector([4.0, 6.0, 5.0]) + + +Tests of ``+`` with mixed types, swapped operands:: + + >>> (10, 20, 30) + v1 + Vector([13.0, 24.0, 35.0]) + >>> from vector2d_v3 import Vector2d + >>> v2d = Vector2d(1, 2) + >>> v2d + v1 + Vector([4.0, 6.0, 5.0]) + + +Tests of ``+`` with an unsuitable operand: + + >>> v1 + 1 + Traceback (most recent call last): + ... + TypeError: unsupported operand type(s) for +: 'Vector' and 'int' + >>> v1 + 'ABC' + Traceback (most recent call last): + ... + TypeError: unsupported operand type(s) for +: 'Vector' and 'str' + + +Basic tests of operator ``*``:: + + >>> v1 = Vector([1, 2, 3]) + >>> v1 * 10 + Vector([10.0, 20.0, 30.0]) + >>> 10 * v1 + Vector([10.0, 20.0, 30.0]) + + +Tests of ``*`` with unusual but valid operands:: + + >>> v1 * True + Vector([1.0, 2.0, 3.0]) + >>> from fractions import Fraction + >>> v1 * Fraction(1, 3) # doctest:+ELLIPSIS + Vector([0.3333..., 0.6666..., 1.0]) + + +Tests of ``*`` with unsuitable operands:: + + >>> v1 * (1, 2) + Traceback (most recent call last): + ... + TypeError: can't multiply sequence by non-int of type 'Vector' + + +Tests of operator `==`:: + + >>> va = Vector(range(1, 4)) + >>> vb = Vector([1.0, 2.0, 3.0]) + >>> va == vb + True + >>> vc = Vector([1, 2]) + >>> from vector2d_v3 import Vector2d + >>> v2d = Vector2d(1, 2) + >>> vc == v2d + True + >>> va == (1, 2, 3) + False + + +Tests of operator `!=`:: + + >>> va != vb + False + >>> vc != v2d + False + >>> va != (1, 2, 3) + True + +""" + +from array import array +import reprlib +import math +import numbers +import functools +import operator +import itertools + + +class Vector: + typecode = 'd' + + def __init__(self, components): + self._components = array(self.typecode, components) + + def __iter__(self): + return iter(self._components) + + def __repr__(self): + components = reprlib.repr(self._components) + components = components[components.find('['):-1] + return 'Vector({})'.format(components) + + def __str__(self): + return str(tuple(self)) + + def __bytes__(self): + return (bytes([ord(self.typecode)]) + + bytes(self._components)) + +# tag::VECTOR_V8_EQ[] + def __eq__(self, other): + if isinstance(other, Vector): # <1> + return (len(self) == len(other) and + all(a == b for a, b in zip(self, other))) + else: + return NotImplemented # <2> +# end::VECTOR_V8_EQ[] + + def __hash__(self): + hashes = (hash(x) for x in self) + return functools.reduce(operator.xor, hashes, 0) + + def __abs__(self): + return math.sqrt(sum(x * x for x in self)) + + def __neg__(self): + return Vector(-x for x in self) + + def __pos__(self): + return Vector(self) + + def __bool__(self): + return bool(abs(self)) + + def __len__(self): + return len(self._components) + + def __getitem__(self, key): + if isinstance(key, slice): + cls = type(self) + return cls(self._components[key]) + index = operator.index(key) + return self._components[index] + + shortcut_names = 'xyzt' + + def __getattr__(self, name): + cls = type(self) + if len(name) == 1: + pos = cls.shortcut_names.find(name) + if 0 <= pos < len(self._components): + return self._components[pos] + msg = '{.__name__!r} object has no attribute {!r}' + raise AttributeError(msg.format(cls, name)) + + def angle(self, n): + r = math.sqrt(sum(x * x for x in self[n:])) + a = math.atan2(r, self[n-1]) + if (n == len(self) - 1) and (self[-1] < 0): + return math.pi * 2 - a + else: + return a + + def angles(self): + return (self.angle(n) for n in range(1, len(self))) + + def __format__(self, fmt_spec=''): + if fmt_spec.endswith('h'): # hyperspherical coordinates + fmt_spec = fmt_spec[:-1] + coords = itertools.chain([abs(self)], + self.angles()) + outer_fmt = '<{}>' + else: + coords = self + outer_fmt = '({})' + components = (format(c, fmt_spec) for c in coords) + return outer_fmt.format(', '.join(components)) + + @classmethod + def frombytes(cls, octets): + typecode = chr(octets[0]) + memv = memoryview(octets[1:]).cast(typecode) + return cls(memv) + + def __add__(self, other): + try: + pairs = itertools.zip_longest(self, other, fillvalue=0.0) + return Vector(a + b for a, b in pairs) + except TypeError: + return NotImplemented + + def __radd__(self, other): + return self + other + + def __mul__(self, scalar): + if isinstance(scalar, numbers.Real): + return Vector(n * scalar for n in self) + else: + return NotImplemented + + def __rmul__(self, scalar): + return self * scalar diff --git a/17-it-generator/README.rst b/17-it-generator/README.rst new file mode 100644 index 0000000..26eb7b2 --- /dev/null +++ b/17-it-generator/README.rst @@ -0,0 +1,4 @@ +Sample code for Chapter 14 - "Iterables, iterators and generators" + +From the book "Fluent Python" by Luciano Ramalho (O'Reilly, 2015) +http://shop.oreilly.com/product/0636920032519.do diff --git a/17-it-generator/aritprog.rst b/17-it-generator/aritprog.rst new file mode 100644 index 0000000..cd56e80 --- /dev/null +++ b/17-it-generator/aritprog.rst @@ -0,0 +1,31 @@ +=========================================== +Tests for arithmetic progression generators +=========================================== + +Tests with built-in numeric types:: + + >>> ap = aritprog_gen(1, .5, 3) + >>> list(ap) + [1.0, 1.5, 2.0, 2.5] + >>> ap = aritprog_gen(0, 1/3, 1) + >>> list(ap) + [0.0, 0.3333333333333333, 0.6666666666666666] + + +Tests with standard library numeric types:: + + >>> from fractions import Fraction + >>> ap = aritprog_gen(0, Fraction(1, 3), 1) + >>> list(ap) + [Fraction(0, 1), Fraction(1, 3), Fraction(2, 3)] + >>> from decimal import Decimal + >>> ap = aritprog_gen(0, Decimal('.1'), .3) + >>> list(ap) + [Decimal('0'), Decimal('0.1'), Decimal('0.2')] + + +Test producing an empty series:: + + >>> ap = aritprog_gen(0, 1, 0) + >>> list(ap) + [] diff --git a/17-it-generator/aritprog_float_error.py b/17-it-generator/aritprog_float_error.py new file mode 100644 index 0000000..b871ad8 --- /dev/null +++ b/17-it-generator/aritprog_float_error.py @@ -0,0 +1,26 @@ +""" +Demonstrate difference between Arithmetic Progression calculated +as a series of increments accumulating errors versus one addition +and one multiplication. +""" + +from fractions import Fraction +from aritprog_v0 import ArithmeticProgression as APv0 +from aritprog_v1 import ArithmeticProgression as APv1 + +if __name__ == '__main__': + + ap0 = iter(APv0(1, .1)) + ap1 = iter(APv1(1, .1)) + ap_frac = iter(APv1(Fraction(1, 1), Fraction(1, 10))) + epsilon = 10**-10 + iteration = 0 + delta = next(ap0) - next(ap1) + frac = next(ap_frac) + while abs(delta) <= epsilon: + delta = next(ap0) - next(ap1) + frac = next(ap_frac) + iteration +=1 + + print('iteration: {}\tfraction: {}\tepsilon: {}\tdelta: {}'. + format(iteration, frac, epsilon, delta)) diff --git a/17-it-generator/aritprog_runner.py b/17-it-generator/aritprog_runner.py new file mode 100644 index 0000000..9061889 --- /dev/null +++ b/17-it-generator/aritprog_runner.py @@ -0,0 +1,37 @@ +import doctest +import importlib +import glob + + +TARGET_GLOB = 'aritprog*.py' +TEST_FILE = 'aritprog.rst' +TEST_MSG = '{0:16} {1.attempted:2} tests, {1.failed:2} failed - {2}' + + +def main(argv): + verbose = '-v' in argv + for module_file_name in sorted(glob.glob(TARGET_GLOB)): + module_name = module_file_name.replace('.py', '') + module = importlib.import_module(module_name) + gen_factory = getattr(module, 'ArithmeticProgression', None) + if gen_factory is None: + gen_factory = getattr(module, 'aritprog_gen', None) + if gen_factory is None: + continue + + test(gen_factory, verbose) + + +def test(gen_factory, verbose=False): + res = doctest.testfile( + TEST_FILE, + globs={'aritprog_gen': gen_factory}, + verbose=verbose, + optionflags=doctest.REPORT_ONLY_FIRST_FAILURE) + tag = 'FAIL' if res.failed else 'OK' + print(TEST_MSG.format(gen_factory.__module__, res, tag)) + + +if __name__ == '__main__': + import sys + main(sys.argv) diff --git a/17-it-generator/aritprog_v0.py b/17-it-generator/aritprog_v0.py new file mode 100644 index 0000000..dbbaa20 --- /dev/null +++ b/17-it-generator/aritprog_v0.py @@ -0,0 +1,25 @@ +""" +Arithmetic progression class + + >>> ap = ArithmeticProgression(1, .5, 3) + >>> list(ap) + [1.0, 1.5, 2.0, 2.5] + + +""" + + +class ArithmeticProgression: + + def __init__(self, begin, step, end=None): + self.begin = begin + self.step = step + self.end = end # None -> "infinite" series + + def __iter__(self): + result_type = type(self.begin + self.step) + result = result_type(self.begin) + forever = self.end is None + while forever or result < self.end: + yield result + result += self.step diff --git a/17-it-generator/aritprog_v1.py b/17-it-generator/aritprog_v1.py new file mode 100644 index 0000000..2ae66c4 --- /dev/null +++ b/17-it-generator/aritprog_v1.py @@ -0,0 +1,45 @@ +""" +Arithmetic progression class + +# tag::ARITPROG_CLASS_DEMO[] + + >>> ap = ArithmeticProgression(0, 1, 3) + >>> list(ap) + [0, 1, 2] + >>> ap = ArithmeticProgression(1, .5, 3) + >>> list(ap) + [1.0, 1.5, 2.0, 2.5] + >>> ap = ArithmeticProgression(0, 1/3, 1) + >>> list(ap) + [0.0, 0.3333333333333333, 0.6666666666666666] + >>> from fractions import Fraction + >>> ap = ArithmeticProgression(0, Fraction(1, 3), 1) + >>> list(ap) + [Fraction(0, 1), Fraction(1, 3), Fraction(2, 3)] + >>> from decimal import Decimal + >>> ap = ArithmeticProgression(0, Decimal('.1'), .3) + >>> list(ap) + [Decimal('0.0'), Decimal('0.1'), Decimal('0.2')] + +# end::ARITPROG_CLASS_DEMO[] +""" + + +# tag::ARITPROG_CLASS[] +class ArithmeticProgression: + + def __init__(self, begin, step, end=None): # <1> + self.begin = begin + self.step = step + self.end = end # None -> "infinite" series + + def __iter__(self): + result_type = type(self.begin + self.step) # <2> + result = result_type(self.begin) # <3> + forever = self.end is None # <4> + index = 0 + while forever or result < self.end: # <5> + yield result # <6> + index += 1 + result = self.begin + self.step * index # <7> +# end::ARITPROG_CLASS[] diff --git a/17-it-generator/aritprog_v2.py b/17-it-generator/aritprog_v2.py new file mode 100644 index 0000000..be211d8 --- /dev/null +++ b/17-it-generator/aritprog_v2.py @@ -0,0 +1,31 @@ +""" +Arithmetic progression generator function:: + + >>> ap = aritprog_gen(1, .5, 3) + >>> list(ap) + [1.0, 1.5, 2.0, 2.5] + >>> ap = aritprog_gen(0, 1/3, 1) + >>> list(ap) + [0.0, 0.3333333333333333, 0.6666666666666666] + >>> from fractions import Fraction + >>> ap = aritprog_gen(0, Fraction(1, 3), 1) + >>> list(ap) + [Fraction(0, 1), Fraction(1, 3), Fraction(2, 3)] + >>> from decimal import Decimal + >>> ap = aritprog_gen(0, Decimal('.1'), .3) + >>> list(ap) + [Decimal('0.0'), Decimal('0.1'), Decimal('0.2')] + +""" + + +# tag::ARITPROG_GENFUNC[] +def aritprog_gen(begin, step, end=None): + result = type(begin + step)(begin) + forever = end is None + index = 0 + while forever or result < end: + yield result + index += 1 + result = begin + step * index +# end::ARITPROG_GENFUNC[] diff --git a/17-it-generator/aritprog_v3.py b/17-it-generator/aritprog_v3.py new file mode 100644 index 0000000..3dd8e18 --- /dev/null +++ b/17-it-generator/aritprog_v3.py @@ -0,0 +1,11 @@ +# tag::ARITPROG_ITERTOOLS[] +import itertools + + +def aritprog_gen(begin, step, end=None): + first = type(begin + step)(begin) + ap_gen = itertools.count(first, step) + if end is not None: + ap_gen = itertools.takewhile(lambda n: n < end, ap_gen) + return ap_gen +# end::ARITPROG_ITERTOOLS[] diff --git a/17-it-generator/columnize_iter.py b/17-it-generator/columnize_iter.py new file mode 100644 index 0000000..867b677 --- /dev/null +++ b/17-it-generator/columnize_iter.py @@ -0,0 +1,26 @@ +# tag::COLUMNIZE[] +from typing import Sequence, Tuple, Iterator + +def columnize(sequence: Sequence[str], num_columns: int = 0) -> Iterator[Tuple[str, ...]]: + if num_columns == 0: + num_columns = round(len(sequence) ** .5) + num_rows, reminder = divmod(len(sequence), num_columns) + num_rows += bool(reminder) + return (tuple(sequence[i::num_rows]) for i in range(num_rows)) +# end::COLUMNIZE[] + + +def demo() -> None: + nato = ('Alfa Bravo Charlie Delta Echo Foxtrot Golf Hotel India' + ' Juliett Kilo Lima Mike November Oscar Papa Quebec Romeo' + ' Sierra Tango Uniform Victor Whiskey X-ray Yankee Zulu' + ).split() + + for row in columnize(nato, 4): + for word in row: + print(f'{word:15}', end='') + print() + + +if __name__ == '__main__': + demo() diff --git a/17-it-generator/fibo_by_hand.py b/17-it-generator/fibo_by_hand.py new file mode 100644 index 0000000..9bf8ab4 --- /dev/null +++ b/17-it-generator/fibo_by_hand.py @@ -0,0 +1,51 @@ +""" +Fibonacci generator implemented "by hand" without generator objects + + >>> from itertools import islice + >>> list(islice(Fibonacci(), 15)) + [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377] + +""" + + +# tag::FIBO_BY_HAND[] +class Fibonacci: + + def __iter__(self): + return FibonacciGenerator() + + +class FibonacciGenerator: + + def __init__(self): + self.a = 0 + self.b = 1 + + def __next__(self): + result = self.a + self.a, self.b = self.b, self.a + self.b + return result + + def __iter__(self): + return self +# end::FIBO_BY_HAND[] + +# for comparison, this is the usual implementation of a Fibonacci +# generator in Python: + + +def fibonacci(): + a, b = 0, 1 + while True: + yield a + a, b = b, a + b + + +if __name__ == '__main__': + + for x, y in zip(Fibonacci(), fibonacci()): + assert x == y, '%s != %s' % (x, y) + print(x) + if x > 10**10: + break + print('etc...') diff --git a/17-it-generator/isis2json/README.rst b/17-it-generator/isis2json/README.rst new file mode 100644 index 0000000..072d26a --- /dev/null +++ b/17-it-generator/isis2json/README.rst @@ -0,0 +1,12 @@ +isis2json.py +============ + +This directory contains a copy of the ``isis2json.py`` script, with +minimal dependencies, just to allow the O'Reilly Atlas toolchain to +render the listing of the script in appendix A of the book. + +If you want to use or contribute to this script, please get the full +source code with all dependencies from the main ``isis2json`` +repository: + +https://github.com/fluentpython/isis2json diff --git a/17-it-generator/isis2json/isis2json.py b/17-it-generator/isis2json/isis2json.py new file mode 100755 index 0000000..065bccd --- /dev/null +++ b/17-it-generator/isis2json/isis2json.py @@ -0,0 +1,263 @@ +#!/usr/bin/env python +# -*- encoding: utf-8 -*- + +# isis2json.py: convert ISIS and ISO-2709 files to JSON +# +# Copyright (C) 2010 BIREME/PAHO/WHO +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 2.1 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. + +# You should have received a copy of the GNU Lesser General Public License +# along with this program. If not, see . + +############################ +# BEGIN ISIS2JSON +# this script works with Python or Jython (versions >=2.5 and <3) + +import sys +import argparse +from uuid import uuid4 +import os + +try: + import json +except ImportError: + if os.name == 'java': # running Jython + from com.xhaus.jyson import JysonCodec as json + else: + import simplejson as json + +SKIP_INACTIVE = True +DEFAULT_QTY = 2**31 +ISIS_MFN_KEY = 'mfn' +ISIS_ACTIVE_KEY = 'active' +SUBFIELD_DELIMITER = '^' +INPUT_ENCODING = 'cp1252' + + +def iter_iso_records(iso_file_name, isis_json_type): # <1> + from iso2709 import IsoFile + from subfield import expand + + iso = IsoFile(iso_file_name) + for record in iso: + fields = {} + for field in record.directory: + field_key = str(int(field.tag)) # remove leading zeroes + field_occurrences = fields.setdefault(field_key, []) + content = field.value.decode(INPUT_ENCODING, 'replace') + if isis_json_type == 1: + field_occurrences.append(content) + elif isis_json_type == 2: + field_occurrences.append(expand(content)) + elif isis_json_type == 3: + field_occurrences.append(dict(expand(content))) + else: + raise NotImplementedError('ISIS-JSON type %s conversion ' + 'not yet implemented for .iso input' % isis_json_type) + + yield fields + iso.close() + + +def iter_mst_records(master_file_name, isis_json_type): # <2> + try: + from bruma.master import MasterFactory, Record + except ImportError: + print('IMPORT ERROR: Jython 2.5 and Bruma.jar ' + 'are required to read .mst files') + raise SystemExit + mst = MasterFactory.getInstance(master_file_name).open() + for record in mst: + fields = {} + if SKIP_INACTIVE: + if record.getStatus() != Record.Status.ACTIVE: + continue + else: # save status only there are non-active records + fields[ISIS_ACTIVE_KEY] = (record.getStatus() == + Record.Status.ACTIVE) + fields[ISIS_MFN_KEY] = record.getMfn() + for field in record.getFields(): + field_key = str(field.getId()) + field_occurrences = fields.setdefault(field_key, []) + if isis_json_type == 3: + content = {} + for subfield in field.getSubfields(): + subfield_key = subfield.getId() + if subfield_key == '*': + content['_'] = subfield.getContent() + else: + subfield_occurrences = content.setdefault(subfield_key, []) + subfield_occurrences.append(subfield.getContent()) + field_occurrences.append(content) + elif isis_json_type == 1: + content = [] + for subfield in field.getSubfields(): + subfield_key = subfield.getId() + if subfield_key == '*': + content.insert(0, subfield.getContent()) + else: + content.append(SUBFIELD_DELIMITER + subfield_key + + subfield.getContent()) + field_occurrences.append(''.join(content)) + else: + raise NotImplementedError('ISIS-JSON type %s conversion ' + 'not yet implemented for .mst input' % isis_json_type) + yield fields + mst.close() + + +def write_json(input_gen, file_name, output, qty, skip, id_tag, # <3> + gen_uuid, mongo, mfn, isis_json_type, prefix, + constant): + start = skip + end = start + qty + if id_tag: + id_tag = str(id_tag) + ids = set() + else: + id_tag = '' + for i, record in enumerate(input_gen): + if i >= end: + break + if not mongo: + if i == 0: + output.write('[') + elif i > start: + output.write(',') + if start <= i < end: + if id_tag: + occurrences = record.get(id_tag, None) + if occurrences is None: + msg = 'id tag #%s not found in record %s' + if ISIS_MFN_KEY in record: + msg = msg + (' (mfn=%s)' % record[ISIS_MFN_KEY]) + raise KeyError(msg % (id_tag, i)) + if len(occurrences) > 1: + msg = 'multiple id tags #%s found in record %s' + if ISIS_MFN_KEY in record: + msg = msg + (' (mfn=%s)' % record[ISIS_MFN_KEY]) + raise TypeError(msg % (id_tag, i)) + else: # ok, we have one and only one id field + if isis_json_type == 1: + id = occurrences[0] + elif isis_json_type == 2: + id = occurrences[0][0][1] + elif isis_json_type == 3: + id = occurrences[0]['_'] + if id in ids: + msg = 'duplicate id %s in tag #%s, record %s' + if ISIS_MFN_KEY in record: + msg = msg + (' (mfn=%s)' % record[ISIS_MFN_KEY]) + raise TypeError(msg % (id, id_tag, i)) + record['_id'] = id + ids.add(id) + elif gen_uuid: + record['_id'] = unicode(uuid4()) + elif mfn: + record['_id'] = record[ISIS_MFN_KEY] + if prefix: + # iterate over a fixed sequence of tags + for tag in tuple(record): + if str(tag).isdigit(): + record[prefix+tag] = record[tag] + del record[tag] # this is why we iterate over a tuple + # with the tags, and not directly on the record dict + if constant: + constant_key, constant_value = constant.split(':') + record[constant_key] = constant_value + output.write(json.dumps(record).encode('utf-8')) + output.write('\n') + if not mongo: + output.write(']\n') + + +def main(): # <4> + # create the parser + parser = argparse.ArgumentParser( + description='Convert an ISIS .mst or .iso file to a JSON array') + + # add the arguments + parser.add_argument( + 'file_name', metavar='INPUT.(mst|iso)', + help='.mst or .iso file to read') + parser.add_argument( + '-o', '--out', type=argparse.FileType('w'), default=sys.stdout, + metavar='OUTPUT.json', + help='the file where the JSON output should be written' + ' (default: write to stdout)') + parser.add_argument( + '-c', '--couch', action='store_true', + help='output array within a "docs" item in a JSON document' + ' for bulk insert to CouchDB via POST to db/_bulk_docs') + parser.add_argument( + '-m', '--mongo', action='store_true', + help='output individual records as separate JSON dictionaries, one' + ' per line for bulk insert to MongoDB via mongoimport utility') + parser.add_argument( + '-t', '--type', type=int, metavar='ISIS_JSON_TYPE', default=1, + help='ISIS-JSON type, sets field structure: 1=string, 2=alist,' + ' 3=dict (default=1)') + parser.add_argument( + '-q', '--qty', type=int, default=DEFAULT_QTY, + help='maximum quantity of records to read (default=ALL)') + parser.add_argument( + '-s', '--skip', type=int, default=0, + help='records to skip from start of .mst (default=0)') + parser.add_argument( + '-i', '--id', type=int, metavar='TAG_NUMBER', default=0, + help='generate an "_id" from the given unique TAG field number' + ' for each record') + parser.add_argument( + '-u', '--uuid', action='store_true', + help='generate an "_id" with a random UUID for each record') + parser.add_argument( + '-p', '--prefix', type=str, metavar='PREFIX', default='', + help='concatenate prefix to every numeric field tag' + ' (ex. 99 becomes "v99")') + parser.add_argument( + '-n', '--mfn', action='store_true', + help='generate an "_id" from the MFN of each record' + ' (available only for .mst input)') + parser.add_argument( + '-k', '--constant', type=str, metavar='TAG:VALUE', default='', + help='Include a constant tag:value in every record (ex. -k type:AS)') + + ''' + # TODO: implement this to export large quantities of records to CouchDB + parser.add_argument( + '-r', '--repeat', type=int, default=1, + help='repeat operation, saving multiple JSON files' + ' (default=1, use -r 0 to repeat until end of input)') + ''' + # parse the command line + args = parser.parse_args() + if args.file_name.lower().endswith('.mst'): + input_gen_func = iter_mst_records # <5> + else: + if args.mfn: + print('UNSUPORTED: -n/--mfn option only available for .mst input.') + raise SystemExit + input_gen_func = iter_iso_records # <6> + input_gen = input_gen_func(args.file_name, args.type) # <7> + if args.couch: + args.out.write('{ "docs" : ') + write_json(input_gen, args.file_name, args.out, args.qty, # <8> + args.skip, args.id, args.uuid, args.mongo, args.mfn, + args.type, args.prefix, args.constant) + if args.couch: + args.out.write('}\n') + args.out.close() + + +if __name__ == '__main__': + main() +# END ISIS2JSON diff --git a/17-it-generator/isis2json/iso2709.py b/17-it-generator/isis2json/iso2709.py new file mode 100644 index 0000000..d93b09f --- /dev/null +++ b/17-it-generator/isis2json/iso2709.py @@ -0,0 +1,167 @@ +#!/usr/bin/env python +# -*- encoding: utf-8 -*- + +# ISO-2709 file reader +# +# Copyright (C) 2010 BIREME/PAHO/WHO +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 2.1 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. + +# You should have received a copy of the GNU Lesser General Public License +# along with this program. If not, see . + +from struct import unpack + +CR = '\x0D' # \r +LF = '\x0A' # \n +IS1 = '\x1F' # ECMA-48 Unit Separator +IS2 = '\x1E' # ECMA-48 Record Separator / ISO-2709 field separator +IS3 = '\x1D' # ECMA-48 Group Separator / ISO-2709 record separator +LABEL_LEN = 24 +LABEL_FORMAT = '5s c 4s c c 5s 3s c c c c' +TAG_LEN = 3 +DEFAULT_ENCODING = 'ASCII' +SUBFIELD_DELIMITER = '^' + +class IsoFile(object): + + def __init__(self, filename, encoding = DEFAULT_ENCODING): + self.file = open(filename, 'rb') + self.encoding = encoding + + def __iter__(self): + return self + + def next(self): + return IsoRecord(self) + + __next__ = next # Python 3 compatibility + + def read(self, size): + ''' read and drop all CR and LF characters ''' + # TODO: this is inneficient but works, patches accepted! + # NOTE: our fixtures include files which have no linebreaks, + # files with CR-LF linebreaks and files with LF linebreaks + chunks = [] + count = 0 + while count < size: + chunk = self.file.read(size-count) + if len(chunk) == 0: + break + chunk = chunk.replace(CR+LF,'') + if CR in chunk: + chunk = chunk.replace(CR,'') + if LF in chunk: + chunk = chunk.replace(LF,'') + count += len(chunk) + chunks.append(chunk) + return ''.join(chunks) + + def close(self): + self.file.close() + +class IsoRecord(object): + label_part_names = ('rec_len rec_status impl_codes indicator_len identifier_len' + ' base_addr user_defined' + # directory map: + ' fld_len_len start_len impl_len reserved').split() + rec_len = 0 + + def __init__(self, iso_file=None): + self.iso_file = iso_file + self.load_label() + self.load_directory() + self.load_fields() + + def __len__(self): + return self.rec_len + + def load_label(self): + label = self.iso_file.read(LABEL_LEN) + if len(label) == 0: + raise StopIteration + elif len(label) != 24: + raise ValueError('Invalid record label: "%s"' % label) + parts = unpack(LABEL_FORMAT, label) + for name, part in zip(self.label_part_names, parts): + if name.endswith('_len') or name.endswith('_addr'): + part = int(part) + setattr(self, name, part) + + def show_label(self): + for name in self.label_part_names: + print('%15s : %r' % (name, getattr(self, name))) + + def load_directory(self): + fmt_dir = '3s %ss %ss %ss' % (self.fld_len_len, self.start_len, self.impl_len) + entry_len = TAG_LEN + self.fld_len_len + self.start_len + self.impl_len + self.directory = [] + while True: + char = self.iso_file.read(1) + if char.isdigit(): + entry = char + self.iso_file.read(entry_len-1) + entry = Field(* unpack(fmt_dir, entry)) + self.directory.append(entry) + else: + break + + def load_fields(self): + for field in self.directory: + if self.indicator_len > 0: + field.indicator = self.iso_file.read(self.indicator_len) + # XXX: lilacs30.iso has an identifier_len == 2, + # but we need to ignore it to succesfully read the field contents + # TODO: find out when to ignore the idenfier_len, + # or fix the lilacs30.iso fixture + # + ##if self.identifier_len > 0: # + ## field.identifier = self.iso_file.read(self.identifier_len) + value = self.iso_file.read(len(field)) + assert len(value) == len(field) + field.value = value[:-1] # remove trailing field separator + self.iso_file.read(1) # discard record separator + + def __iter__(self): + return self + + def next(self): + for field in self.directory: + yield(field) + + __next__ = next # Python 3 compatibility + + def dump(self): + for field in self.directory: + print('%3s %r' % (field.tag, field.value)) + +class Field(object): + + def __init__(self, tag, len, start, impl): + self.tag = tag + self.len = int(len) + self.start = int(start) + self.impl = impl + + def show(self): + for name in 'tag len start impl'.split(): + print('%15s : %r' % (name, getattr(self, name))) + + def __len__(self): + return self.len + +def test(): + import doctest + doctest.testfile('iso2709_test.txt') + + +if __name__=='__main__': + test() + diff --git a/17-it-generator/isis2json/subfield.py b/17-it-generator/isis2json/subfield.py new file mode 100644 index 0000000..05bb649 --- /dev/null +++ b/17-it-generator/isis2json/subfield.py @@ -0,0 +1,142 @@ +#!/usr/bin/env python +# -*- encoding: utf-8 -*- + +# ISIS-DM: the ISIS Data Model API +# +# Copyright (C) 2010 BIREME/PAHO/WHO +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 2.1 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. + +# You should have received a copy of the GNU Lesser General Public License +# along with this program. If not, see . + +from collections import namedtuple +import re + + +MAIN_SUBFIELD_KEY = '_' +SUBFIELD_MARKER_RE = re.compile(r'\^([a-z0-9])', re.IGNORECASE) +DEFAULT_ENCODING = u'utf-8' + +def expand(content, subkeys=None): + ''' Parse a field into an association list of keys and subfields + + >>> expand('zero^1one^2two^3three') + [('_', 'zero'), ('1', 'one'), ('2', 'two'), ('3', 'three')] + + ''' + if subkeys is None: + regex = SUBFIELD_MARKER_RE + elif subkeys == '': + return [(MAIN_SUBFIELD_KEY, content)] + else: + regex = re.compile(r'\^(['+subkeys+'])', re.IGNORECASE) + content = content.replace('^^', '^^ ') + parts = [] + start = 0 + key = MAIN_SUBFIELD_KEY + while True: + found = regex.search(content, start) + if found is None: break + parts.append((key, content[start:found.start()].rstrip())) + key = found.group(1).lower() + start = found.end() + parts.append((key, content[start:].rstrip())) + return parts + + +class CompositeString(object): + ''' Represent an Isis field, with subfields, using + Python native datastructures + + >>> author = CompositeString('John Tenniel^xillustrator', + ... subkeys='x') + >>> unicode(author) + u'John Tenniel^xillustrator' + ''' + + def __init__(self, isis_raw, subkeys=None, encoding=DEFAULT_ENCODING): + if not isinstance(isis_raw, basestring): + raise TypeError('%r value must be unicode or str instance' % isis_raw) + + self.__isis_raw = isis_raw.decode(encoding) + self.__expanded = expand(self.__isis_raw, subkeys) + + def __getitem__(self, key): + for subfield in self.__expanded: + if subfield[0] == key: + return subfield[1] + else: + raise KeyError(key) + + def __iter__(self): + return (subfield[0] for subfield in self.__expanded) + + def items(self): + return self.__expanded + + def __unicode__(self): + return self.__isis_raw + + def __str__(self): + return str(self.__isis_raw) + + +class CompositeField(object): + ''' Represent an Isis field, with subfields, using + Python native datastructures + + >>> author = CompositeField( [('name','Braz, Marcelo'),('role','writer')] ) + >>> print author['name'] + Braz, Marcelo + >>> print author['role'] + writer + >>> author + CompositeField((('name', 'Braz, Marcelo'), ('role', 'writer'))) + + ''' + + def __init__(self, value, subkeys=None): + if subkeys is None: + subkeys = [item[0] for item in value] + try: + value_as_dict = dict(value) + except TypeError: + raise TypeError('%r value must be a key-value structure' % self) + + for key in value_as_dict: + if key not in subkeys: + raise TypeError('Unexpected keyword %r' % key) + + self.value = tuple([(key, value_as_dict.get(key,None)) for key in subkeys]) + + def __getitem__(self, key): + return dict(self.value)[key] + + def __repr__(self): + return "CompositeField(%s)" % str(self.items()) + + def items(self): + return self.value + + def __unicode__(self): + unicode(self.items()) + + def __str__(self): + str(self.items()) + + +def test(): + import doctest + doctest.testmod() + +if __name__=='__main__': + test() diff --git a/17-it-generator/sentence.py b/17-it-generator/sentence.py new file mode 100644 index 0000000..5c0c5d4 --- /dev/null +++ b/17-it-generator/sentence.py @@ -0,0 +1,37 @@ +""" +Sentence: access words by index + + >>> text = 'To be, or not to be, that is the question' + >>> s = Sentence(text) + >>> len(s) + 10 + >>> s[1], s[5] + ('be', 'be') + >>> s + Sentence('To be, or no... the question') + +""" + +# tag::SENTENCE_SEQ[] +import re +import reprlib + +RE_WORD = re.compile(r'\w+') + + +class Sentence: + + def __init__(self, text): + self.text = text + self.words = RE_WORD.findall(text) # <1> + + def __getitem__(self, index): + return self.words[index] # <2> + + def __len__(self): # <3> + return len(self.words) + + def __repr__(self): + return 'Sentence(%s)' % reprlib.repr(self.text) # <4> + +# end::SENTENCE_SEQ[] diff --git a/17-it-generator/sentence.rst b/17-it-generator/sentence.rst new file mode 100644 index 0000000..03386a7 --- /dev/null +++ b/17-it-generator/sentence.rst @@ -0,0 +1,54 @@ +============================== +Tests for a ``Sentence`` class +============================== + +A ``Sentence`` is built from a ``str`` and allows iteration +word-by-word. + +:: + >>> s = Sentence('The time has come') + >>> s + Sentence('The time has come') + >>> list(s) + ['The', 'time', 'has', 'come'] + >>> it = iter(s) + >>> next(it) + 'The' + >>> next(it) + 'time' + >>> next(it) + 'has' + >>> next(it) + 'come' + >>> next(it) + Traceback (most recent call last): + ... + StopIteration + + +Any punctuation is skipped while iterating:: + + >>> s = Sentence('"The time has come," the Walrus said,') + >>> s + Sentence('"The time ha... Walrus said,') + >>> list(s) + ['The', 'time', 'has', 'come', 'the', 'Walrus', 'said'] + + +White space including line breaks are also ignored:: + + >>> s = Sentence('''"The time has come," the Walrus said, + ... "To talk of many things:"''') + >>> s + Sentence('"The time ha...many things:"') + >>> list(s) + ['The', 'time', 'has', 'come', 'the', 'Walrus', 'said', 'To', 'talk', 'of', 'many', 'things'] + + +Accented Latin characters are also recognized as word characters:: + + >>> s = Sentence('Agora vou-me. Ou me vão?') + >>> s + Sentence('Agora vou-me. Ou me vão?') + >>> list(s) + ['Agora', 'vou', 'me', 'Ou', 'me', 'vão'] diff --git a/17-it-generator/sentence_gen.py b/17-it-generator/sentence_gen.py new file mode 100644 index 0000000..3dbc606 --- /dev/null +++ b/17-it-generator/sentence_gen.py @@ -0,0 +1,28 @@ +""" +Sentence: iterate over words using a generator function +""" + +# tag::SENTENCE_GEN[] +import re +import reprlib + +RE_WORD = re.compile(r'\w+') + + +class Sentence: + + def __init__(self, text): + self.text = text + self.words = RE_WORD.findall(text) + + def __repr__(self): + return 'Sentence(%s)' % reprlib.repr(self.text) + + def __iter__(self): + for word in self.words: # <1> + yield word # <2> + return # <3> + +# done! <4> + +# end::SENTENCE_GEN[] diff --git a/17-it-generator/sentence_gen2.py b/17-it-generator/sentence_gen2.py new file mode 100644 index 0000000..503fecd --- /dev/null +++ b/17-it-generator/sentence_gen2.py @@ -0,0 +1,24 @@ +""" +Sentence: iterate over words using a generator function +""" + +# tag::SENTENCE_GEN2[] +import re +import reprlib + +RE_WORD = re.compile('r\w+') + + +class Sentence: + + def __init__(self, text): + self.text = text # <1> + + def __repr__(self): + return 'Sentence(%s)' % reprlib.repr(self.text) + + def __iter__(self): + for match in RE_WORD.finditer(self.text): # <2> + yield match.group() # <3> + +# end::SENTENCE_GEN2[] diff --git a/17-it-generator/sentence_genexp.py b/17-it-generator/sentence_genexp.py new file mode 100644 index 0000000..a5ff584 --- /dev/null +++ b/17-it-generator/sentence_genexp.py @@ -0,0 +1,44 @@ +""" +Sentence: iterate over words using a generator expression +""" + +# tag::SENTENCE_GENEXP[] +import re +import reprlib + +RE_WORD = re.compile(r'\w+') + + +class Sentence: + + def __init__(self, text): + self.text = text + + def __repr__(self): + return 'Sentence(%s)' % reprlib.repr(self.text) + + def __iter__(self): + return (match.group() for match in RE_WORD.finditer(self.text)) +# end::SENTENCE_GENEXP[] + + +def main(): + import sys + import warnings + try: + filename = sys.argv[1] + word_number = int(sys.argv[2]) + except (IndexError, ValueError): + print('Usage: %s ' % sys.argv[0]) + sys.exit(1) + with open(filename, 'rt', encoding='utf-8') as text_file: + s = Sentence(text_file.read()) + for n, word in enumerate(s, 1): + if n == word_number: + print(word) + break + else: + warnings.warn('last word is #%d, "%s"' % (n, word)) + +if __name__ == '__main__': + main() diff --git a/17-it-generator/sentence_iter.py b/17-it-generator/sentence_iter.py new file mode 100644 index 0000000..71ee3f3 --- /dev/null +++ b/17-it-generator/sentence_iter.py @@ -0,0 +1,65 @@ +""" +Sentence: iterate over words using the Iterator Pattern, take #1 + +WARNING: the Iterator Pattern is much simpler in idiomatic Python; +see: sentence_gen*.py. +""" + +# tag::SENTENCE_ITER[] +import re +import reprlib + +RE_WORD = re.compile(r'\w+') + + +class Sentence: + + def __init__(self, text): + self.text = text + self.words = RE_WORD.findall(text) + + def __repr__(self): + return 'Sentence(%s)' % reprlib.repr(self.text) + + def __iter__(self): # <1> + return SentenceIterator(self.words) # <2> + + +class SentenceIterator: + + def __init__(self, words): + self.words = words # <3> + self.index = 0 # <4> + + def __next__(self): + try: + word = self.words[self.index] # <5> + except IndexError: + raise StopIteration() # <6> + self.index += 1 # <7> + return word # <8> + + def __iter__(self): # <9> + return self +# end::SENTENCE_ITER[] + +def main(): + import sys + import warnings + try: + filename = sys.argv[1] + word_number = int(sys.argv[2]) + except (IndexError, ValueError): + print('Usage: %s ' % sys.argv[0]) + sys.exit(1) + with open(filename, 'rt', encoding='utf-8') as text_file: + s = Sentence(text_file.read()) + for n, word in enumerate(s, 1): + if n == word_number: + print(word) + break + else: + warnings.warn('last word is #%d, "%s"' % (n, word)) + +if __name__ == '__main__': + main() diff --git a/17-it-generator/sentence_iter2.py b/17-it-generator/sentence_iter2.py new file mode 100644 index 0000000..2663f3f --- /dev/null +++ b/17-it-generator/sentence_iter2.py @@ -0,0 +1,37 @@ +""" +Sentence: iterate over words using the Iterator Pattern, take #2 + +WARNING: the Iterator Pattern is much simpler in idiomatic Python; +see: sentence_gen*.py. +""" + +import re +import reprlib + +RE_WORD = re.compile(r'\w+') + + +class Sentence: + + def __init__(self, text): + self.text = text + + def __repr__(self): + return 'Sentence(%s)' % reprlib.repr(self.text) + + def __iter__(self): + word_iter = RE_WORD.finditer(self.text) # <1> + return SentenceIter(word_iter) # <2> + + +class SentenceIter(): + + def __init__(self, word_iter): + self.word_iter = word_iter # <3> + + def __next__(self): + match = next(self.word_iter) # <4> + return match.group() # <5> + + def __iter__(self): + return self diff --git a/17-it-generator/sentence_runner.py b/17-it-generator/sentence_runner.py new file mode 100644 index 0000000..c84f982 --- /dev/null +++ b/17-it-generator/sentence_runner.py @@ -0,0 +1,36 @@ +import doctest +import importlib +import glob + + +TARGET_GLOB = 'sentence*.py' +TEST_FILE = 'sentence.rst' +TEST_MSG = '{0:16} {1.attempted:2} tests, {1.failed:2} failed - {2}' + + +def main(argv): + verbose = '-v' in argv + for module_file_name in sorted(glob.glob(TARGET_GLOB)): + module_name = module_file_name.replace('.py', '') + module = importlib.import_module(module_name) + try: + cls = getattr(module, 'Sentence') + except AttributeError: + continue + test(cls, verbose) + + +def test(cls, verbose=False): + + res = doctest.testfile( + TEST_FILE, + globs={'Sentence': cls}, + verbose=verbose, + optionflags=doctest.REPORT_ONLY_FIRST_FAILURE) + tag = 'FAIL' if res.failed else 'OK' + print(TEST_MSG.format(cls.__module__, res, tag)) + + +if __name__ == '__main__': + import sys + main(sys.argv) diff --git a/17-it-generator/tree/4steps/tree_step0.py b/17-it-generator/tree/4steps/tree_step0.py new file mode 100644 index 0000000..4236a2e --- /dev/null +++ b/17-it-generator/tree/4steps/tree_step0.py @@ -0,0 +1,7 @@ +def tree(cls): + yield cls.__name__ + + +if __name__ == '__main__': + for cls_name in tree(BaseException): + print(cls_name) diff --git a/17-it-generator/tree/4steps/tree_step1.py b/17-it-generator/tree/4steps/tree_step1.py new file mode 100644 index 0000000..54f6ae8 --- /dev/null +++ b/17-it-generator/tree/4steps/tree_step1.py @@ -0,0 +1,10 @@ +def tree(cls): + yield cls.__name__, 0 + for sub_cls in cls.__subclasses__(): + yield sub_cls.__name__, 1 + + +if __name__ == '__main__': + for cls_name, level in tree(BaseException): + indent = ' ' * 4 * level + print(f'{indent}{cls_name}') diff --git a/17-it-generator/tree/4steps/tree_step2.py b/17-it-generator/tree/4steps/tree_step2.py new file mode 100644 index 0000000..10c3e9b --- /dev/null +++ b/17-it-generator/tree/4steps/tree_step2.py @@ -0,0 +1,12 @@ +def tree(cls): + yield cls.__name__, 0 + for sub_cls in cls.__subclasses__(): + yield sub_cls.__name__, 1 + for sub_sub_cls in sub_cls.__subclasses__(): + yield sub_sub_cls.__name__, 2 + + +if __name__ == '__main__': + for cls_name, level in tree(BaseException): + indent = ' ' * 4 * level + print(f'{indent}{cls_name}') diff --git a/17-it-generator/tree/4steps/tree_step3.py b/17-it-generator/tree/4steps/tree_step3.py new file mode 100644 index 0000000..7dfea68 --- /dev/null +++ b/17-it-generator/tree/4steps/tree_step3.py @@ -0,0 +1,10 @@ +def tree(cls, level=0): + yield cls.__name__, level + for sub_cls in cls.__subclasses__(): + yield from tree(sub_cls, level + 1) + + +if __name__ == '__main__': + for cls_name, level in tree(BaseException): + indent = ' ' * 4 * level + print(f'{indent}{cls_name}') diff --git a/17-it-generator/tree/extra/pretty_tree.py b/17-it-generator/tree/extra/pretty_tree.py new file mode 100644 index 0000000..668df54 --- /dev/null +++ b/17-it-generator/tree/extra/pretty_tree.py @@ -0,0 +1,35 @@ +from tree import tree + +SPACES = ' ' * 4 +HLINE = '\u2500' # ─ BOX DRAWINGS LIGHT HORIZONTAL +HLINE2 = HLINE * 2 +ELBOW = f'\u2514{HLINE2} ' # └ BOX DRAWINGS LIGHT UP AND RIGHT +TEE = f'\u251C{HLINE2} ' # ├ BOX DRAWINGS LIGHT VERTICAL AND RIGHT +PIPE = f'\u2502 ' # │ BOX DRAWINGS LIGHT VERTICAL + + +def render_lines(tree_iter): + name, _, _ = next(tree_iter) + yield name + prefix = '' + + for name, level, last in tree_iter: + if last: + connector = ELBOW + else: + connector = TEE + + prefix = prefix[:4 * (level-1)] + prefix = prefix.replace(TEE, PIPE).replace(ELBOW, SPACES) + prefix += connector + + yield prefix + name + + +def display(cls): + for line in render_lines(tree(cls)): + print(line) + + +if __name__ == '__main__': + display(BaseException) diff --git a/17-it-generator/tree/extra/test_pretty_tree.py b/17-it-generator/tree/extra/test_pretty_tree.py new file mode 100644 index 0000000..943f0f1 --- /dev/null +++ b/17-it-generator/tree/extra/test_pretty_tree.py @@ -0,0 +1,101 @@ +import pytest + +from pretty_tree import tree, render_lines + +def test_1_level(): + result = list(render_lines(tree(BrokenPipeError))) + expected = [ + 'BrokenPipeError', + ] + assert expected == result + + +def test_2_levels_1_leaf(): + result = list(render_lines(tree(IndentationError))) + expected = [ + 'IndentationError', + '└── TabError', + ] + assert expected == result + + +def test_3_levels_1_leaf(): + class X: pass + class Y(X): pass + class Z(Y): pass + result = list(render_lines(tree(X))) + expected = [ + 'X', + '└── Y', + ' └── Z', + ] + assert expected == result + + +def test_4_levels_1_leaf(): + class Level0: pass + class Level1(Level0): pass + class Level2(Level1): pass + class Level3(Level2): pass + + result = list(render_lines(tree(Level0))) + expected = [ + 'Level0', + '└── Level1', + ' └── Level2', + ' └── Level3', + ] + assert expected == result + + +def test_2_levels_2_leaves(): + class Branch: pass + class Leaf1(Branch): pass + class Leaf2(Branch): pass + result = list(render_lines(tree(Branch))) + expected = [ + 'Branch', + '├── Leaf1', + '└── Leaf2', + ] + assert expected == result + + +def test_3_levels_2_leaves(): + class A: pass + class B(A): pass + class C(B): pass + class D(A): pass + class E(D): pass + + result = list(render_lines(tree(A))) + expected = [ + 'A', + '├── B', + '│ └── C', + '└── D', + ' └── E', + ] + assert expected == result + + +def test_4_levels_4_leaves(): + class A: pass + class B1(A): pass + class C1(B1): pass + class D1(C1): pass + class D2(C1): pass + class C2(B1): pass + class B2(A): pass + expected = [ + 'A', + '├── B1', + '│ ├── C1', + '│ │ ├── D1', + '│ │ └── D2', + '│ └── C2', + '└── B2', + ] + + result = list(render_lines(tree(A))) + assert expected == result diff --git a/17-it-generator/tree/extra/test_tree.py b/17-it-generator/tree/extra/test_tree.py new file mode 100644 index 0000000..da878bc --- /dev/null +++ b/17-it-generator/tree/extra/test_tree.py @@ -0,0 +1,90 @@ +from tree import tree + + +def test_1_level(): + class One: pass + expected = [('One', 0, True)] + result = list(tree(One)) + assert expected == result + + +def test_2_levels_2_leaves(): + class Branch: pass + class Leaf1(Branch): pass + class Leaf2(Branch): pass + expected = [ + ('Branch', 0, True), + ('Leaf1', 1, False), + ('Leaf2', 1, True), + ] + result = list(tree(Branch)) + assert expected == result + + +def test_3_levels_1_leaf(): + class X: pass + class Y(X): pass + class Z(Y): pass + expected = [ + ('X', 0, True), + ('Y', 1, True), + ('Z', 2, True), + ] + result = list(tree(X)) + assert expected == result + + +def test_4_levels_1_leaf(): + class Level0: pass + class Level1(Level0): pass + class Level2(Level1): pass + class Level3(Level2): pass + expected = [ + ('Level0', 0, True), + ('Level1', 1, True), + ('Level2', 2, True), + ('Level3', 3, True), + ] + + result = list(tree(Level0)) + assert expected == result + + +def test_4_levels_3_leaves(): + class A: pass + class B1(A): pass + class B2(A): pass + class C1(B1): pass + class C2(B2): pass + class D1(C1): pass + class D2(C1): pass + expected = [ + ('A', 0, True), + ('B1', 1, False), + ('C1', 2, True), + ('D1', 3, False), + ('D2', 3, True), + ('B2', 1, True), + ('C2', 2, True), + ] + + result = list(tree(A)) + assert expected == result + + +def test_many_levels_1_leaf(): + class Root: pass + level_count = 100 + expected = [('Root', 0, True)] + parent = Root + for level in range(1, level_count): + name = f'Sub{level}' + cls = type(name, (parent,), {}) + expected.append((name, level, True)) + parent = cls + + result = list(tree(Root)) + assert len(result) == level_count + assert result[0] == ('Root', 0, True) + assert result[-1] == ('Sub99', 99, True) + assert expected == result diff --git a/17-it-generator/tree/extra/tree.py b/17-it-generator/tree/extra/tree.py new file mode 100644 index 0000000..1a6ec6d --- /dev/null +++ b/17-it-generator/tree/extra/tree.py @@ -0,0 +1,17 @@ +def tree(cls, level=0, last_in_level=True): + yield cls.__name__, level, last_in_level + subclasses = cls.__subclasses__() + if subclasses: + last = subclasses[-1] + for sub_cls in subclasses: + yield from tree(sub_cls, level+1, sub_cls is last) + + +def display(cls): + for cls_name, level, _ in tree(cls): + indent = ' ' * 4 * level + print(f'{indent}{cls_name}') + + +if __name__ == '__main__': + display(BaseException) diff --git a/17-it-generator/tree/step0/test_tree.py b/17-it-generator/tree/step0/test_tree.py new file mode 100644 index 0000000..1ab22ae --- /dev/null +++ b/17-it-generator/tree/step0/test_tree.py @@ -0,0 +1,8 @@ +from tree import tree + + +def test_1_level(): + class One: pass + expected = ['One'] + result = list(tree(One)) + assert expected == result diff --git a/17-it-generator/tree/step0/tree.py b/17-it-generator/tree/step0/tree.py new file mode 100644 index 0000000..5466949 --- /dev/null +++ b/17-it-generator/tree/step0/tree.py @@ -0,0 +1,11 @@ +def tree(cls): + yield cls.__name__ + + +def display(cls): + for cls_name in tree(cls): + print(cls_name) + + +if __name__ == '__main__': + display(BaseException) \ No newline at end of file diff --git a/17-it-generator/tree/step1/test_tree.py b/17-it-generator/tree/step1/test_tree.py new file mode 100644 index 0000000..9896bb3 --- /dev/null +++ b/17-it-generator/tree/step1/test_tree.py @@ -0,0 +1,21 @@ +from tree import tree + + +def test_1_level(): + class One: pass + expected = [('One', 0)] + result = list(tree(One)) + assert expected == result + + +def test_2_levels_2_leaves(): + class Branch: pass + class Leaf1(Branch): pass + class Leaf2(Branch): pass + expected = [ + ('Branch', 0), + ('Leaf1', 1), + ('Leaf2', 1), + ] + result = list(tree(Branch)) + assert expected == result diff --git a/17-it-generator/tree/step1/tree.py b/17-it-generator/tree/step1/tree.py new file mode 100644 index 0000000..6638880 --- /dev/null +++ b/17-it-generator/tree/step1/tree.py @@ -0,0 +1,14 @@ +def tree(cls): + yield cls.__name__, 0 # <1> + for sub_cls in cls.__subclasses__(): # <2> + yield sub_cls.__name__, 1 # <3> + + +def display(cls): + for cls_name, level in tree(cls): + indent = ' ' * 4 * level # <4> + print(f'{indent}{cls_name}') + + +if __name__ == '__main__': + display(BaseException) \ No newline at end of file diff --git a/17-it-generator/tree/step2/test_tree.py b/17-it-generator/tree/step2/test_tree.py new file mode 100644 index 0000000..9896bb3 --- /dev/null +++ b/17-it-generator/tree/step2/test_tree.py @@ -0,0 +1,21 @@ +from tree import tree + + +def test_1_level(): + class One: pass + expected = [('One', 0)] + result = list(tree(One)) + assert expected == result + + +def test_2_levels_2_leaves(): + class Branch: pass + class Leaf1(Branch): pass + class Leaf2(Branch): pass + expected = [ + ('Branch', 0), + ('Leaf1', 1), + ('Leaf2', 1), + ] + result = list(tree(Branch)) + assert expected == result diff --git a/17-it-generator/tree/step2/tree.py b/17-it-generator/tree/step2/tree.py new file mode 100644 index 0000000..d4078b4 --- /dev/null +++ b/17-it-generator/tree/step2/tree.py @@ -0,0 +1,18 @@ +def tree(cls): + yield cls.__name__, 0 + yield from sub_tree(cls) # <1> + + +def sub_tree(cls): + for sub_cls in cls.__subclasses__(): + yield sub_cls.__name__, 1 # <2> + + +def display(cls): + for cls_name, level in tree(cls): + indent = ' ' * 4 * level + print(f'{indent}{cls_name}') + + +if __name__ == '__main__': + display(BaseException) diff --git a/17-it-generator/tree/step3/test_tree.py b/17-it-generator/tree/step3/test_tree.py new file mode 100644 index 0000000..37a685c --- /dev/null +++ b/17-it-generator/tree/step3/test_tree.py @@ -0,0 +1,34 @@ +from tree import tree + + +def test_1_level(): + class One: pass + expected = [('One', 0)] + result = list(tree(One)) + assert expected == result + + +def test_2_levels_2_leaves(): + class Branch: pass + class Leaf1(Branch): pass + class Leaf2(Branch): pass + expected = [ + ('Branch', 0), + ('Leaf1', 1), + ('Leaf2', 1), + ] + result = list(tree(Branch)) + assert expected == result + + +def test_3_levels_1_leaf(): + class X: pass + class Y(X): pass + class Z(Y): pass + expected = [ + ('X', 0), + ('Y', 1), + ('Z', 2), + ] + result = list(tree(X)) + assert expected == result diff --git a/17-it-generator/tree/step3/tree.py b/17-it-generator/tree/step3/tree.py new file mode 100644 index 0000000..88da397 --- /dev/null +++ b/17-it-generator/tree/step3/tree.py @@ -0,0 +1,20 @@ +def tree(cls): + yield cls.__name__, 0 + yield from sub_tree(cls) + + +def sub_tree(cls): + for sub_cls in cls.__subclasses__(): + yield sub_cls.__name__, 1 + for sub_sub_cls in sub_cls.__subclasses__(): + yield sub_sub_cls.__name__, 2 + + +def display(cls): + for cls_name, level in tree(cls): + indent = ' ' * 4 * level + print(f'{indent}{cls_name}') + + +if __name__ == '__main__': + display(BaseException) diff --git a/17-it-generator/tree/step4/test_tree.py b/17-it-generator/tree/step4/test_tree.py new file mode 100644 index 0000000..602941f --- /dev/null +++ b/17-it-generator/tree/step4/test_tree.py @@ -0,0 +1,72 @@ +from tree import tree + + +def test_1_level(): + class One: pass + expected = [('One', 0)] + result = list(tree(One)) + assert expected == result + + +def test_2_levels_2_leaves(): + class Branch: pass + class Leaf1(Branch): pass + class Leaf2(Branch): pass + expected = [ + ('Branch', 0), + ('Leaf1', 1), + ('Leaf2', 1), + ] + result = list(tree(Branch)) + assert expected == result + + +def test_3_levels_1_leaf(): + class X: pass + class Y(X): pass + class Z(Y): pass + expected = [ + ('X', 0), + ('Y', 1), + ('Z', 2), + ] + result = list(tree(X)) + assert expected == result + + +def test_4_levels_1_leaf(): + class Level0: pass + class Level1(Level0): pass + class Level2(Level1): pass + class Level3(Level2): pass + expected = [ + ('Level0', 0), + ('Level1', 1), + ('Level2', 2), + ('Level3', 3), + ] + + result = list(tree(Level0)) + assert expected == result + + +def test_4_levels_3_leaves(): + class A: pass + class B1(A): pass + class C1(B1): pass + class D1(C1): pass + class B2(A): pass + class D2(C1): pass + class C2(B2): pass + expected = [ + ('A', 0), + ('B1', 1), + ('C1', 2), + ('D1', 3), + ('D2', 3), + ('B2', 1), + ('C2', 2), + ] + + result = list(tree(A)) + assert expected == result diff --git a/17-it-generator/tree/step4/tree.py b/17-it-generator/tree/step4/tree.py new file mode 100644 index 0000000..dc7bbf0 --- /dev/null +++ b/17-it-generator/tree/step4/tree.py @@ -0,0 +1,24 @@ +def tree(cls): + yield cls.__name__, 0 + yield from sub_tree(cls) + + +# tag::SUB_TREE[] +def sub_tree(cls): + for sub_cls in cls.__subclasses__(): + yield sub_cls.__name__, 1 + for sub_sub_cls in sub_cls.__subclasses__(): + yield sub_sub_cls.__name__, 2 + for sub_sub_sub_cls in sub_sub_cls.__subclasses__(): + yield sub_sub_sub_cls.__name__, 3 +# end::SUB_TREE[] + + +def display(cls): + for cls_name, level in tree(cls): + indent = ' ' * 4 * level + print(f'{indent}{cls_name}') + + +if __name__ == '__main__': + display(BaseException) diff --git a/17-it-generator/tree/step5/test_tree.py b/17-it-generator/tree/step5/test_tree.py new file mode 100644 index 0000000..d1621b5 --- /dev/null +++ b/17-it-generator/tree/step5/test_tree.py @@ -0,0 +1,90 @@ +from tree import tree + + +def test_1_level(): + class One: pass + expected = [('One', 0)] + result = list(tree(One)) + assert expected == result + + +def test_2_levels_2_leaves(): + class Branch: pass + class Leaf1(Branch): pass + class Leaf2(Branch): pass + expected = [ + ('Branch', 0), + ('Leaf1', 1), + ('Leaf2', 1), + ] + result = list(tree(Branch)) + assert expected == result + + +def test_3_levels_1_leaf(): + class X: pass + class Y(X): pass + class Z(Y): pass + expected = [ + ('X', 0), + ('Y', 1), + ('Z', 2), + ] + result = list(tree(X)) + assert expected == result + + +def test_4_levels_1_leaf(): + class Level0: pass + class Level1(Level0): pass + class Level2(Level1): pass + class Level3(Level2): pass + expected = [ + ('Level0', 0), + ('Level1', 1), + ('Level2', 2), + ('Level3', 3), + ] + + result = list(tree(Level0)) + assert expected == result + + +def test_4_levels_3_leaves(): + class A: pass + class B1(A): pass + class B2(A): pass + class C1(B1): pass + class C2(B2): pass + class D1(C1): pass + class D2(C1): pass + expected = [ + ('A', 0), + ('B1', 1), + ('C1', 2), + ('D1', 3), + ('D2', 3), + ('B2', 1), + ('C2', 2), + ] + + result = list(tree(A)) + assert expected == result + + +def test_many_levels_1_leaf(): + class Root: pass + level_count = 100 + expected = [('Root', 0)] + parent = Root + for level in range(1, level_count): + name = f'Sub{level}' + cls = type(name, (parent,), {}) + expected.append((name, level)) + parent = cls + + result = list(tree(Root)) + assert len(result) == level_count + assert result[0] == ('Root', 0) + assert result[-1] == ('Sub99', 99) + assert expected == result diff --git a/17-it-generator/tree/step5/tree.py b/17-it-generator/tree/step5/tree.py new file mode 100644 index 0000000..df5f5af --- /dev/null +++ b/17-it-generator/tree/step5/tree.py @@ -0,0 +1,19 @@ +def tree(cls): + yield cls.__name__, 0 + yield from sub_tree(cls, 1) + + +def sub_tree(cls, level): + for sub_cls in cls.__subclasses__(): + yield sub_cls.__name__, level + yield from sub_tree(sub_cls, level+1) + + +def display(cls): + for cls_name, level in tree(cls): + indent = ' ' * 4 * level + print(f'{indent}{cls_name}') + + +if __name__ == '__main__': + display(BaseException) diff --git a/17-it-generator/tree/step6/test_tree.py b/17-it-generator/tree/step6/test_tree.py new file mode 100644 index 0000000..d1621b5 --- /dev/null +++ b/17-it-generator/tree/step6/test_tree.py @@ -0,0 +1,90 @@ +from tree import tree + + +def test_1_level(): + class One: pass + expected = [('One', 0)] + result = list(tree(One)) + assert expected == result + + +def test_2_levels_2_leaves(): + class Branch: pass + class Leaf1(Branch): pass + class Leaf2(Branch): pass + expected = [ + ('Branch', 0), + ('Leaf1', 1), + ('Leaf2', 1), + ] + result = list(tree(Branch)) + assert expected == result + + +def test_3_levels_1_leaf(): + class X: pass + class Y(X): pass + class Z(Y): pass + expected = [ + ('X', 0), + ('Y', 1), + ('Z', 2), + ] + result = list(tree(X)) + assert expected == result + + +def test_4_levels_1_leaf(): + class Level0: pass + class Level1(Level0): pass + class Level2(Level1): pass + class Level3(Level2): pass + expected = [ + ('Level0', 0), + ('Level1', 1), + ('Level2', 2), + ('Level3', 3), + ] + + result = list(tree(Level0)) + assert expected == result + + +def test_4_levels_3_leaves(): + class A: pass + class B1(A): pass + class B2(A): pass + class C1(B1): pass + class C2(B2): pass + class D1(C1): pass + class D2(C1): pass + expected = [ + ('A', 0), + ('B1', 1), + ('C1', 2), + ('D1', 3), + ('D2', 3), + ('B2', 1), + ('C2', 2), + ] + + result = list(tree(A)) + assert expected == result + + +def test_many_levels_1_leaf(): + class Root: pass + level_count = 100 + expected = [('Root', 0)] + parent = Root + for level in range(1, level_count): + name = f'Sub{level}' + cls = type(name, (parent,), {}) + expected.append((name, level)) + parent = cls + + result = list(tree(Root)) + assert len(result) == level_count + assert result[0] == ('Root', 0) + assert result[-1] == ('Sub99', 99) + assert expected == result diff --git a/17-it-generator/tree/step6/tree.py b/17-it-generator/tree/step6/tree.py new file mode 100644 index 0000000..45557e4 --- /dev/null +++ b/17-it-generator/tree/step6/tree.py @@ -0,0 +1,14 @@ +def tree(cls, level=0): + yield cls.__name__, level + for sub_cls in cls.__subclasses__(): + yield from tree(sub_cls, level+1) + + +def display(cls): + for cls_name, level in tree(cls): + indent = ' ' * 4 * level + print(f'{indent}{cls_name}') + + +if __name__ == '__main__': + display(BaseException) diff --git a/17-it-generator/yield_delegate_fail.py b/17-it-generator/yield_delegate_fail.py new file mode 100644 index 0000000..3fd110a --- /dev/null +++ b/17-it-generator/yield_delegate_fail.py @@ -0,0 +1,29 @@ +""" Example from `Python: The Full Monty`__ -- A Tested Semantics for the +Python Programming Language + +__ http://cs.brown.edu/~sk/Publications/Papers/Published/pmmwplck-python-full-monty/ + +"The following program, [...] seems to perform a simple abstraction over the +process of yielding:" + +Citation: + +Joe Gibbs Politz, Alejandro Martinez, Matthew Milano, Sumner Warren, +Daniel Patterson, Junsong Li, Anand Chitipothu, and Shriram Krishnamurthi. +2013. Python: the full monty. SIGPLAN Not. 48, 10 (October 2013), 217-232. +DOI=10.1145/2544173.2509536 http://doi.acm.org/10.1145/2544173.2509536 +""" + +# tag::YIELD_DELEGATE_FAIL[] +def f(): + def do_yield(n): + yield n + x = 0 + while True: + x += 1 + do_yield(x) +# end::YIELD_DELEGATE_FAIL[] + +if __name__ == '__main__': + print('Invoking f() results in an infinite loop') + f() diff --git a/17-it-generator/yield_delegate_fix.py b/17-it-generator/yield_delegate_fix.py new file mode 100644 index 0000000..9c5ce71 --- /dev/null +++ b/17-it-generator/yield_delegate_fix.py @@ -0,0 +1,24 @@ +""" Example adapted from ``yield_delegate_fail.py`` + +The following program performs a simple abstraction over the process of +yielding. + +""" + +# tag::YIELD_DELEGATE_FIX[] +def f(): + def do_yield(n): + yield n + x = 0 + while True: + x += 1 + yield from do_yield(x) +# end::YIELD_DELEGATE_FIX[] + +if __name__ == '__main__': + print('Invoking f() now produces a generator') + g = f() + print(next(g)) + print(next(g)) + print(next(g)) + diff --git a/18-context-mngr/README.rst b/18-context-mngr/README.rst new file mode 100644 index 0000000..7fbc0b4 --- /dev/null +++ b/18-context-mngr/README.rst @@ -0,0 +1,4 @@ +Sample code for Chapter 15 - "Context managers and something else" + +From the book "Fluent Python" by Luciano Ramalho (O'Reilly, 2015) +http://shop.oreilly.com/product/0636920032519.do diff --git a/18-context-mngr/mirror.py b/18-context-mngr/mirror.py new file mode 100644 index 0000000..ba31944 --- /dev/null +++ b/18-context-mngr/mirror.py @@ -0,0 +1,92 @@ +""" +A "mirroring" ``stdout`` context. + +While active, the context manager reverses text output to +``stdout``:: + +# tag::MIRROR_DEMO_1[] + + >>> from mirror import LookingGlass + >>> with LookingGlass() as what: # <1> + ... print('Alice, Kitty and Snowdrop') # <2> + ... print(what) + ... + pordwonS dna yttiK ,ecilA # <3> + YKCOWREBBAJ + >>> what # <4> + 'JABBERWOCKY' + >>> print('Back to normal.') # <5> + Back to normal. + +# end::MIRROR_DEMO_1[] + + +This exposes the context manager operation:: + +# tag::MIRROR_DEMO_2[] + + >>> from mirror import LookingGlass + >>> manager = LookingGlass() # <1> + >>> manager + + >>> monster = manager.__enter__() # <2> + >>> monster == 'JABBERWOCKY' # <3> + eurT + >>> monster + 'YKCOWREBBAJ' + >>> manager + >ca875a2x0 ta tcejbo ssalGgnikooL.rorrim< + >>> manager.__exit__(None, None, None) # <4> + >>> monster + 'JABBERWOCKY' + +# end::MIRROR_DEMO_2[] + +The context manager can handle and "swallow" exceptions. + +# tag::MIRROR_DEMO_3[] + + >>> from mirror import LookingGlass + >>> with LookingGlass(): + ... print('Humpty Dumpty') + ... x = 1/0 # <1> + ... print('END') # <2> + ... + ytpmuD ytpmuH + Please DO NOT divide by zero! + >>> with LookingGlass(): + ... print('Humpty Dumpty') + ... x = no_such_name # <1> + ... print('END') # <2> + ... + Traceback (most recent call last): + ... + NameError: name 'no_such_name' is not defined + +# end::MIRROR_DEMO_3[] + +""" + + +# tag::MIRROR_EX[] +class LookingGlass: + + def __enter__(self): # <1> + import sys + self.original_write = sys.stdout.write # <2> + sys.stdout.write = self.reverse_write # <3> + return 'JABBERWOCKY' # <4> + + def reverse_write(self, text): # <5> + self.original_write(text[::-1]) + + def __exit__(self, exc_type, exc_value, traceback): # <6> + import sys # <7> + sys.stdout.write = self.original_write # <8> + if exc_type is ZeroDivisionError: # <9> + print('Please DO NOT divide by zero!') + return True # <10> + # <11> + + +# end::MIRROR_EX[] diff --git a/18-context-mngr/mirror_gen.py b/18-context-mngr/mirror_gen.py new file mode 100644 index 0000000..457955a --- /dev/null +++ b/18-context-mngr/mirror_gen.py @@ -0,0 +1,64 @@ +""" +A "mirroring" ``stdout`` context manager. + +While active, the context manager reverses text output to +``stdout``:: + +# tag::MIRROR_GEN_DEMO_1[] + + >>> from mirror_gen import looking_glass + >>> with looking_glass() as what: # <1> + ... print('Alice, Kitty and Snowdrop') + ... print(what) + ... + pordwonS dna yttiK ,ecilA + YKCOWREBBAJ + >>> what + 'JABBERWOCKY' + +# end::MIRROR_GEN_DEMO_1[] + + +This exposes the context manager operation:: + +# tag::MIRROR_GEN_DEMO_2[] + + >>> from mirror_gen import looking_glass + >>> manager = looking_glass() # <1> + >>> manager # doctest: +ELLIPSIS + + >>> monster = manager.__enter__() # <2> + >>> monster == 'JABBERWOCKY' # <3> + eurT + >>> monster + 'YKCOWREBBAJ' + >>> manager # doctest: +ELLIPSIS + >...x0 ta tcejbo reganaMtxetnoCrotareneG_.biltxetnoc< + >>> manager.__exit__(None, None, None) # <4> + >>> monster + 'JABBERWOCKY' + +# end::MIRROR_GEN_DEMO_2[] + +""" + + +# tag::MIRROR_GEN_EX[] + +import contextlib + + +@contextlib.contextmanager # <1> +def looking_glass(): + import sys + original_write = sys.stdout.write # <2> + + def reverse_write(text): # <3> + original_write(text[::-1]) + + sys.stdout.write = reverse_write # <4> + yield 'JABBERWOCKY' # <5> + sys.stdout.write = original_write # <6> + + +# end::MIRROR_GEN_EX[] diff --git a/18-context-mngr/mirror_gen_exc.py b/18-context-mngr/mirror_gen_exc.py new file mode 100644 index 0000000..c446918 --- /dev/null +++ b/18-context-mngr/mirror_gen_exc.py @@ -0,0 +1,101 @@ +""" +A "mirroring" ``stdout`` context manager. + +While active, the context manager reverses text output to +``stdout``:: + +# tag::MIRROR_GEN_DEMO_1[] + + >>> from mirror_gen import looking_glass + >>> with looking_glass() as what: # <1> + ... print('Alice, Kitty and Snowdrop') + ... print(what) + ... + pordwonS dna yttiK ,ecilA + YKCOWREBBAJ + >>> what + 'JABBERWOCKY' + +# end::MIRROR_GEN_DEMO_1[] + + +This exposes the context manager operation:: + +# tag::MIRROR_GEN_DEMO_2[] + + >>> from mirror_gen import looking_glass + >>> manager = looking_glass() # <1> + >>> manager # doctest: +ELLIPSIS + + >>> monster = manager.__enter__() # <2> + >>> monster == 'JABBERWOCKY' # <3> + eurT + >>> monster + 'YKCOWREBBAJ' + >>> manager # doctest: +ELLIPSIS + >...x0 ta tcejbo reganaMtxetnoCrotareneG_.biltxetnoc< + >>> manager.__exit__(None, None, None) # <4> + >>> monster + 'JABBERWOCKY' + +# end::MIRROR_GEN_DEMO_2[] + +The context manager can handle and "swallow" exceptions. +The following test does not pass under doctest (a +ZeroDivisionError is reported by doctest) but passes +if executed by hand in the Python 3 console (the exception +is handled by the context manager): + +# tag::MIRROR_GEN_DEMO_3[] + + >>> from mirror_gen import looking_glass + >>> with looking_glass(): + ... print('Humpty Dumpty') + ... x = 1/0 # <1> + ... print('END') # <2> + ... + ytpmuD ytpmuH + Please DO NOT divide by zero! + +# end::MIRROR_GEN_DEMO_3[] + + >>> with looking_glass(): + ... print('Humpty Dumpty') + ... x = no_such_name # <1> + ... print('END') # <2> + ... + Traceback (most recent call last): + ... + NameError: name 'no_such_name' is not defined + + + +""" + + +# tag::MIRROR_GEN_EXC[] + +import contextlib + + +@contextlib.contextmanager +def looking_glass(): + import sys + original_write = sys.stdout.write + + def reverse_write(text): + original_write(text[::-1]) + + sys.stdout.write = reverse_write + msg = '' # <1> + try: + yield 'JABBERWOCKY' + except ZeroDivisionError: # <2> + msg = 'Please DO NOT divide by zero!' + finally: + sys.stdout.write = original_write # <3> + if msg: + print(msg) # <4> + + +# end::MIRROR_GEN_EXC[] diff --git a/19-coroutine/README.rst b/19-coroutine/README.rst new file mode 100644 index 0000000..2771530 --- /dev/null +++ b/19-coroutine/README.rst @@ -0,0 +1,4 @@ +Sample code for Chapter 16 - "Coroutines" + +From the book "Fluent Python" by Luciano Ramalho (O'Reilly, 2015) +http://shop.oreilly.com/product/0636920032519.do diff --git a/19-coroutine/coro_exc_demo.py b/19-coroutine/coro_exc_demo.py new file mode 100644 index 0000000..a68684f --- /dev/null +++ b/19-coroutine/coro_exc_demo.py @@ -0,0 +1,66 @@ +""" +Coroutine closing demonstration:: + +# tag::DEMO_CORO_EXC_1[] + >>> exc_coro = demo_exc_handling() + >>> next(exc_coro) + -> coroutine started + >>> exc_coro.send(11) + -> coroutine received: 11 + >>> exc_coro.send(22) + -> coroutine received: 22 + >>> exc_coro.close() + >>> from inspect import getgeneratorstate + >>> getgeneratorstate(exc_coro) + 'GEN_CLOSED' + +# end::DEMO_CORO_EXC_1[] + +Coroutine handling exception:: + +# tag::DEMO_CORO_EXC_2[] + >>> exc_coro = demo_exc_handling() + >>> next(exc_coro) + -> coroutine started + >>> exc_coro.send(11) + -> coroutine received: 11 + >>> exc_coro.throw(DemoException) + *** DemoException handled. Continuing... + >>> getgeneratorstate(exc_coro) + 'GEN_SUSPENDED' + +# end::DEMO_CORO_EXC_2[] + +Coroutine not handling exception:: + +# tag::DEMO_CORO_EXC_3[] + >>> exc_coro = demo_exc_handling() + >>> next(exc_coro) + -> coroutine started + >>> exc_coro.send(11) + -> coroutine received: 11 + >>> exc_coro.throw(ZeroDivisionError) + Traceback (most recent call last): + ... + ZeroDivisionError + >>> getgeneratorstate(exc_coro) + 'GEN_CLOSED' + +# end::DEMO_CORO_EXC_3[] +""" + +# tag::EX_CORO_EXC[] +class DemoException(Exception): + """An exception type for the demonstration.""" + +def demo_exc_handling(): + print('-> coroutine started') + while True: + try: + x = yield + except DemoException: # <1> + print('*** DemoException handled. Continuing...') + else: # <2> + print(f'-> coroutine received: {x!r}') + raise RuntimeError('This line should never run.') # <3> +# end::EX_CORO_EXC[] diff --git a/19-coroutine/coro_finally_demo.py b/19-coroutine/coro_finally_demo.py new file mode 100644 index 0000000..fd4545d --- /dev/null +++ b/19-coroutine/coro_finally_demo.py @@ -0,0 +1,61 @@ +""" +Second coroutine closing demonstration:: + + >>> fin_coro = demo_finally() + >>> next(fin_coro) + -> coroutine started + >>> fin_coro.send(11) + -> coroutine received: 11 + >>> fin_coro.send(22) + -> coroutine received: 22 + >>> fin_coro.close() + -> coroutine ending + + +Second coroutine not handling exception:: + + >>> fin_coro = demo_finally() + >>> next(fin_coro) + -> coroutine started + >>> fin_coro.send(11) + -> coroutine received: 11 + >>> fin_coro.throw(ZeroDivisionError) # doctest: +SKIP + -> coroutine ending + Traceback (most recent call last): + File "", line 1, in + File "coro_exception_demos.py", line 109, in demo_finally + print(f'-> coroutine received: {x!r}') + ZeroDivisionError + + +The last test above must be skipped because the output '-> coroutine ending' +is not detected by doctest, which raises a false error. However, if you +run this file as shown below, you'll see that output "leak" into standard +output:: + + + $ python3 -m doctest coro_exception_demo.py + -> coroutine ending + +""" + + +# tag::EX_CORO_FINALLY[] +class DemoException(Exception): + """An exception type for the demonstration.""" + + +def demo_finally(): + print('-> coroutine started') + try: + while True: + try: + x = yield + except DemoException: + print('*** DemoException handled. Continuing...') + else: + print(f'-> coroutine received: {x!r}') + finally: + print('-> coroutine ending') + +# end::EX_CORO_FINALLY[] diff --git a/19-coroutine/coroaverager0.py b/19-coroutine/coroaverager0.py new file mode 100644 index 0000000..10ccfdc --- /dev/null +++ b/19-coroutine/coroaverager0.py @@ -0,0 +1,28 @@ +""" +A coroutine to compute a running average + +# tag::CORO_AVERAGER_TEST[] + >>> coro_avg = averager() # <1> + >>> next(coro_avg) # <2> + >>> coro_avg.send(10) # <3> + 10.0 + >>> coro_avg.send(30) + 20.0 + >>> coro_avg.send(5) + 15.0 + +# end::CORO_AVERAGER_TEST[] + +""" + +# tag::CORO_AVERAGER[] +def averager(): + total = 0.0 + count = 0 + average = None + while True: # <1> + term = yield average # <2> + total += term + count += 1 + average = total/count +# end::CORO_AVERAGER[] diff --git a/19-coroutine/coroaverager1.py b/19-coroutine/coroaverager1.py new file mode 100644 index 0000000..3b3c5e9 --- /dev/null +++ b/19-coroutine/coroaverager1.py @@ -0,0 +1,30 @@ +# tag::DECORATED_AVERAGER[] +""" +A coroutine to compute a running average + + >>> coro_avg = averager() # <1> + >>> from inspect import getgeneratorstate + >>> getgeneratorstate(coro_avg) # <2> + 'GEN_SUSPENDED' + >>> coro_avg.send(10) # <3> + 10.0 + >>> coro_avg.send(30) + 20.0 + >>> coro_avg.send(5) + 15.0 + +""" + +from coroutil import coroutine # <4> + +@coroutine # <5> +def averager(): # <6> + total = 0.0 + count = 0 + average = None + while True: + term = yield average + total += term + count += 1 + average = total/count +# end::DECORATED_AVERAGER[] diff --git a/19-coroutine/coroaverager2.py b/19-coroutine/coroaverager2.py new file mode 100644 index 0000000..9d724c0 --- /dev/null +++ b/19-coroutine/coroaverager2.py @@ -0,0 +1,61 @@ +""" +A coroutine to compute a running average. + +Testing ``averager`` by itself:: + +# tag::RETURNING_AVERAGER_DEMO1[] + + >>> coro_avg = averager() + >>> next(coro_avg) + >>> coro_avg.send(10) # <1> + >>> coro_avg.send(30) + >>> coro_avg.send(6.5) + >>> coro_avg.send(None) # <2> + Traceback (most recent call last): + ... + StopIteration: Result(count=3, average=15.5) + +# end::RETURNING_AVERAGER_DEMO1[] + +Catching `StopIteration` to extract the value returned by +the coroutine:: + +# tag::RETURNING_AVERAGER_DEMO2[] + + >>> coro_avg = averager() + >>> next(coro_avg) + >>> coro_avg.send(10) + >>> coro_avg.send(30) + >>> coro_avg.send(6.5) + >>> try: + ... coro_avg.send(None) + ... except StopIteration as exc: + ... result = exc.value + ... + >>> result + Result(count=3, average=15.5) + +# end::RETURNING_AVERAGER_DEMO2[] + + +""" + +# tag::RETURNING_AVERAGER[] +from collections import namedtuple + +Result = namedtuple('Result', 'count average') + + +def averager(): + total = 0.0 + count = 0 + average = None + while True: + term = yield + if term is None: + break # <1> + total += term + count += 1 + average = total/count + return Result(count, average) # <2> +# end::RETURNING_AVERAGER[] diff --git a/19-coroutine/coroaverager3.py b/19-coroutine/coroaverager3.py new file mode 100644 index 0000000..2b24df9 --- /dev/null +++ b/19-coroutine/coroaverager3.py @@ -0,0 +1,107 @@ +""" +A coroutine to compute a running average. + +Testing ``averager`` by itself:: + + >>> coro_avg = averager() + >>> next(coro_avg) + >>> coro_avg.send(10) + >>> coro_avg.send(30) + >>> coro_avg.send(6.5) + >>> coro_avg.send(None) + Traceback (most recent call last): + ... + StopIteration: Result(count=3, average=15.5) + + +Driving it with ``yield from``:: + + >>> def summarize(results): + ... while True: + ... result = yield from averager() + ... results.append(result) + ... + >>> results = [] + >>> summary = summarize(results) + >>> next(summary) + >>> for height in data['girls;m']: + ... summary.send(height) + ... + >>> summary.send(None) + >>> for height in data['boys;m']: + ... summary.send(height) + ... + >>> summary.send(None) + >>> results == [ + ... Result(count=10, average=1.4279999999999997), + ... Result(count=9, average=1.3888888888888888) + ... ] + True + +""" + +# tag::YIELD_FROM_AVERAGER[] +from collections import namedtuple + +Result = namedtuple('Result', 'count average') + + +# the subgenerator +def averager(): # <1> + total = 0.0 + count = 0 + average = None + while True: + term = yield # <2> + if term is None: # <3> + break + total += term + count += 1 + average = total/count + return Result(count, average) # <4> + + +# the delegating generator +def grouper(results, key): # <5> + while True: # <6> + results[key] = yield from averager() # <7> + + +# the client code, a.k.a. the caller +def main(data): # <8> + results = {} + for key, values in data.items(): + group = grouper(results, key) # <9> + next(group) # <10> + for value in values: + group.send(value) # <11> + group.send(None) # important! <12> + + # print(results) # uncomment to debug + report(results) + + +# output report +def report(results): + for key, result in sorted(results.items()): + group, unit = key.split(';') + print(f'{result.count:2} {group:5}', + f'averaging {result.average:.2f}{unit}') + + +data = { + 'girls;kg': + [40.9, 38.5, 44.3, 42.2, 45.2, 41.7, 44.5, 38.0, 40.6, 44.5], + 'girls;m': + [1.6, 1.51, 1.4, 1.3, 1.41, 1.39, 1.33, 1.46, 1.45, 1.43], + 'boys;kg': + [39.0, 40.8, 43.2, 40.8, 43.1, 38.6, 41.4, 40.6, 36.3], + 'boys;m': + [1.38, 1.5, 1.32, 1.25, 1.37, 1.48, 1.25, 1.49, 1.46], +} + + +if __name__ == '__main__': + main(data) + +# end::YIELD_FROM_AVERAGER[] diff --git a/19-coroutine/coroutil.py b/19-coroutine/coroutil.py new file mode 100644 index 0000000..ecd5de7 --- /dev/null +++ b/19-coroutine/coroutil.py @@ -0,0 +1,12 @@ +# tag::CORO_DECO[] +from functools import wraps + +def coroutine(func): + """Decorator: primes `func` by advancing to first `yield`""" + @wraps(func) + def primer(*args,**kwargs): # <1> + gen = func(*args,**kwargs) # <2> + next(gen) # <3> + return gen # <4> + return primer +# end::CORO_DECO[] diff --git a/19-coroutine/taxi_sim.py b/19-coroutine/taxi_sim.py new file mode 100644 index 0000000..97933bb --- /dev/null +++ b/19-coroutine/taxi_sim.py @@ -0,0 +1,203 @@ + +""" +Taxi simulator +============== + +Driving a taxi from the console:: + + >>> from taxi_sim import taxi_process + >>> taxi = taxi_process(ident=13, trips=2, start_time=0) + >>> next(taxi) + Event(time=0, proc=13, action='leave garage') + >>> taxi.send(_.time + 7) + Event(time=7, proc=13, action='pick up passenger') + >>> taxi.send(_.time + 23) + Event(time=30, proc=13, action='drop off passenger') + >>> taxi.send(_.time + 5) + Event(time=35, proc=13, action='pick up passenger') + >>> taxi.send(_.time + 48) + Event(time=83, proc=13, action='drop off passenger') + >>> taxi.send(_.time + 1) + Event(time=84, proc=13, action='going home') + >>> taxi.send(_.time + 10) + Traceback (most recent call last): + File "", line 1, in + StopIteration + +Sample run with two cars, random seed 10. This is a valid doctest:: + + >>> main(num_taxis=2, seed=10) + taxi: 0 Event(time=0, proc=0, action='leave garage') + taxi: 0 Event(time=5, proc=0, action='pick up passenger') + taxi: 1 Event(time=5, proc=1, action='leave garage') + taxi: 1 Event(time=10, proc=1, action='pick up passenger') + taxi: 1 Event(time=15, proc=1, action='drop off passenger') + taxi: 0 Event(time=17, proc=0, action='drop off passenger') + taxi: 1 Event(time=24, proc=1, action='pick up passenger') + taxi: 0 Event(time=26, proc=0, action='pick up passenger') + taxi: 0 Event(time=30, proc=0, action='drop off passenger') + taxi: 0 Event(time=34, proc=0, action='going home') + taxi: 1 Event(time=46, proc=1, action='drop off passenger') + taxi: 1 Event(time=48, proc=1, action='pick up passenger') + taxi: 1 Event(time=110, proc=1, action='drop off passenger') + taxi: 1 Event(time=139, proc=1, action='pick up passenger') + taxi: 1 Event(time=140, proc=1, action='drop off passenger') + taxi: 1 Event(time=150, proc=1, action='going home') + *** end of events *** + +See longer sample run at the end of this module. + +""" + +import random +import collections +import queue +import argparse +import time + +DEFAULT_NUMBER_OF_TAXIS = 3 +DEFAULT_END_TIME = 180 +SEARCH_DURATION = 5 +TRIP_DURATION = 20 +DEPARTURE_INTERVAL = 5 + +Event = collections.namedtuple('Event', 'time proc action') + + +# tag::TAXI_PROCESS[] +def taxi_process(ident, trips, start_time=0): # <1> + """Yield to simulator issuing event at each state change""" + time = yield Event(start_time, ident, 'leave garage') # <2> + for i in range(trips): # <3> + time = yield Event(time, ident, 'pick up passenger') # <4> + time = yield Event(time, ident, 'drop off passenger') # <5> + + yield Event(time, ident, 'going home') # <6> + # end of taxi process # <7> +# end::TAXI_PROCESS[] + + +# tag::TAXI_SIMULATOR[] +class Simulator: + + def __init__(self, procs_map): + self.events = queue.PriorityQueue() + self.procs = dict(procs_map) + + def run(self, end_time): # <1> + """Schedule and display events until time is up""" + # schedule the first event for each cab + for _, proc in sorted(self.procs.items()): # <2> + first_event = next(proc) # <3> + self.events.put(first_event) # <4> + + # main loop of the simulation + sim_time = 0 # <5> + while sim_time < end_time: # <6> + if self.events.empty(): # <7> + print('*** end of events ***') + break + + current_event = self.events.get() # <8> + sim_time, proc_id, previous_action = current_event # <9> + print('taxi:', proc_id, proc_id * ' ', current_event) # <10> + active_proc = self.procs[proc_id] # <11> + next_time = sim_time + compute_duration(previous_action) # <12> + try: + next_event = active_proc.send(next_time) # <13> + except StopIteration: + del self.procs[proc_id] # <14> + else: + self.events.put(next_event) # <15> + else: # <16> + msg = '*** end of simulation time: {} events pending ***' + print(msg.format(self.events.qsize())) +# end::TAXI_SIMULATOR[] + + +def compute_duration(previous_action): + """Compute action duration using exponential distribution""" + if previous_action in ['leave garage', 'drop off passenger']: + # new state is prowling + interval = SEARCH_DURATION + elif previous_action == 'pick up passenger': + # new state is trip + interval = TRIP_DURATION + elif previous_action == 'going home': + interval = 1 + else: + raise ValueError('Unknown previous_action: %s' % previous_action) + return int(random.expovariate(1/interval)) + 1 + + +def main(end_time=DEFAULT_END_TIME, num_taxis=DEFAULT_NUMBER_OF_TAXIS, + seed=None): + """Initialize random generator, build procs and run simulation""" + if seed is not None: + random.seed(seed) # get reproducible results + + taxis = {i: taxi_process(i, (i+1)*2, i*DEPARTURE_INTERVAL) + for i in range(num_taxis)} + sim = Simulator(taxis) + sim.run(end_time) + + +if __name__ == '__main__': + + parser = argparse.ArgumentParser( + description='Taxi fleet simulator.') + parser.add_argument('-e', '--end-time', type=int, + default=DEFAULT_END_TIME, + help='simulation end time; default = %s' + % DEFAULT_END_TIME) + parser.add_argument('-t', '--taxis', type=int, + default=DEFAULT_NUMBER_OF_TAXIS, + help='number of taxis running; default = %s' + % DEFAULT_NUMBER_OF_TAXIS) + parser.add_argument('-s', '--seed', type=int, default=None, + help='random generator seed (for testing)') + + args = parser.parse_args() + main(args.end_time, args.taxis, args.seed) + + +""" + +Sample run from the command line, seed=3, maximum elapsed time=120:: + +# tag::TAXI_SAMPLE_RUN[] +$ python3 taxi_sim.py -s 3 -e 120 +taxi: 0 Event(time=0, proc=0, action='leave garage') +taxi: 0 Event(time=2, proc=0, action='pick up passenger') +taxi: 1 Event(time=5, proc=1, action='leave garage') +taxi: 1 Event(time=8, proc=1, action='pick up passenger') +taxi: 2 Event(time=10, proc=2, action='leave garage') +taxi: 2 Event(time=15, proc=2, action='pick up passenger') +taxi: 2 Event(time=17, proc=2, action='drop off passenger') +taxi: 0 Event(time=18, proc=0, action='drop off passenger') +taxi: 2 Event(time=18, proc=2, action='pick up passenger') +taxi: 2 Event(time=25, proc=2, action='drop off passenger') +taxi: 1 Event(time=27, proc=1, action='drop off passenger') +taxi: 2 Event(time=27, proc=2, action='pick up passenger') +taxi: 0 Event(time=28, proc=0, action='pick up passenger') +taxi: 2 Event(time=40, proc=2, action='drop off passenger') +taxi: 2 Event(time=44, proc=2, action='pick up passenger') +taxi: 1 Event(time=55, proc=1, action='pick up passenger') +taxi: 1 Event(time=59, proc=1, action='drop off passenger') +taxi: 0 Event(time=65, proc=0, action='drop off passenger') +taxi: 1 Event(time=65, proc=1, action='pick up passenger') +taxi: 2 Event(time=65, proc=2, action='drop off passenger') +taxi: 2 Event(time=72, proc=2, action='pick up passenger') +taxi: 0 Event(time=76, proc=0, action='going home') +taxi: 1 Event(time=80, proc=1, action='drop off passenger') +taxi: 1 Event(time=88, proc=1, action='pick up passenger') +taxi: 2 Event(time=95, proc=2, action='drop off passenger') +taxi: 2 Event(time=97, proc=2, action='pick up passenger') +taxi: 2 Event(time=98, proc=2, action='drop off passenger') +taxi: 1 Event(time=106, proc=1, action='drop off passenger') +taxi: 2 Event(time=109, proc=2, action='going home') +taxi: 1 Event(time=110, proc=1, action='going home') +*** end of events *** +# end::TAXI_SAMPLE_RUN[] + +""" diff --git a/19-coroutine/taxi_sim0.py b/19-coroutine/taxi_sim0.py new file mode 100644 index 0000000..4078fdd --- /dev/null +++ b/19-coroutine/taxi_sim0.py @@ -0,0 +1,257 @@ + +""" +Taxi simulator + +Sample run with two cars, random seed 10. This is a valid doctest. + + >>> main(num_taxis=2, seed=10) + taxi: 0 Event(time=0, proc=0, action='leave garage') + taxi: 0 Event(time=4, proc=0, action='pick up passenger') + taxi: 1 Event(time=5, proc=1, action='leave garage') + taxi: 1 Event(time=9, proc=1, action='pick up passenger') + taxi: 0 Event(time=10, proc=0, action='drop off passenger') + taxi: 1 Event(time=12, proc=1, action='drop off passenger') + taxi: 0 Event(time=17, proc=0, action='pick up passenger') + taxi: 1 Event(time=19, proc=1, action='pick up passenger') + taxi: 1 Event(time=21, proc=1, action='drop off passenger') + taxi: 1 Event(time=24, proc=1, action='pick up passenger') + taxi: 0 Event(time=28, proc=0, action='drop off passenger') + taxi: 1 Event(time=28, proc=1, action='drop off passenger') + taxi: 0 Event(time=29, proc=0, action='going home') + taxi: 1 Event(time=30, proc=1, action='pick up passenger') + taxi: 1 Event(time=61, proc=1, action='drop off passenger') + taxi: 1 Event(time=62, proc=1, action='going home') + *** end of events *** + +See explanation and longer sample run at the end of this module. + +""" + +import sys +import random +import collections +import queue +import argparse + +DEFAULT_NUMBER_OF_TAXIS = 3 +DEFAULT_END_TIME = 80 +SEARCH_DURATION = 4 +TRIP_DURATION = 10 +DEPARTURE_INTERVAL = 5 + +Event = collections.namedtuple('Event', 'time proc action') + + +def compute_delay(interval): + """Compute action delay using exponential distribution""" + return int(random.expovariate(1/interval)) + 1 + +# BEGIN TAXI_PROCESS +def taxi_process(ident, trips, start_time=0): # <1> + """Yield to simulator issuing event at each state change""" + time = yield Event(start_time, ident, 'leave garage') # <2> + for i in range(trips): # <3> + prowling_ends = time + compute_delay(SEARCH_DURATION) # <4> + time = yield Event(prowling_ends, ident, 'pick up passenger') # <5> + + trip_ends = time + compute_delay(TRIP_DURATION) # <6> + time = yield Event(trip_ends, ident, 'drop off passenger') # <7> + + yield Event(time + 1, ident, 'going home') # <8> + # end of taxi process # <9> +# END TAXI_PROCESS + +# BEGIN TAXI_SIMULATOR +class Simulator: + + def __init__(self, procs_map): + self.events = queue.PriorityQueue() + self.procs = dict(procs_map) + + + def run(self, end_time): # <1> + """Schedule and display events until time is up""" + # schedule the first event for each cab + for _, proc in sorted(self.procs.items()): # <2> + first_event = next(proc) # <3> + self.events.put(first_event) # <4> + + # main loop of the simulation + time = 0 + while time < end_time: # <5> + if self.events.empty(): # <6> + print('*** end of events ***') + break + + # get and display current event + current_event = self.events.get() # <7> + print('taxi:', current_event.proc, # <8> + current_event.proc * ' ', current_event) + + # schedule next action for current proc + time = current_event.time # <9> + proc = self.procs[current_event.proc] # <10> + try: + next_event = proc.send(time) # <11> + except StopIteration: + del self.procs[current_event.proc] # <12> + else: + self.events.put(next_event) # <13> + else: # <14> + msg = '*** end of simulation time: {} events pending ***' + print(msg.format(self.events.qsize())) +# END TAXI_SIMULATOR + +def main(end_time=DEFAULT_END_TIME, num_taxis=DEFAULT_NUMBER_OF_TAXIS, + seed=None): + """Initialize random generator, build procs and run simulation""" + if seed is not None: + random.seed(seed) # get reproducible results + + taxis = {i: taxi_process(i, (i+1)*2, i*DEPARTURE_INTERVAL) + for i in range(num_taxis)} + sim = Simulator(taxis) + sim.run(end_time) + + +if __name__ == '__main__': + + parser = argparse.ArgumentParser( + description='Taxi fleet simulator.') + parser.add_argument('-e', '--end-time', type=int, + default=DEFAULT_END_TIME, + help='simulation end time; default = %s' + % DEFAULT_END_TIME) + parser.add_argument('-t', '--taxis', type=int, + default=DEFAULT_NUMBER_OF_TAXIS, + help='number of taxis running; default = %s' + % DEFAULT_NUMBER_OF_TAXIS) + parser.add_argument('-s', '--seed', type=int, default=None, + help='random generator seed (for testing)') + + args = parser.parse_args() + main(args.end_time, args.taxis, args.seed) + + +""" +Notes for the ``taxi_process`` coroutine:: + +<1> `taxi_process` will be called once per taxi, creating a generator + object to represent its operations. `ident` is the number of the taxi + (eg. 0, 1, 2 in the sample run); `trips` is the number of trips this + taxi will make before going home; `start_time` is when the taxi + leaves the garage. + +<2> The first `Event` yielded is `'leave garage'`. This suspends the + coroutine, and lets the simulation main loop proceed to the next + scheduled event. When it's time to reactivate this process, the main + loop will `send` the current simulation time, which is assigned to + `time`. + +<3> This block will be repeated once for each trip. + +<4> The ending time of the search for a passenger is computed. + +<5> An `Event` signaling passenger pick up is yielded. The coroutine + pauses here. When the time comes to reactivate this coroutine, + the main loop will again `send` the current time. + +<6> The ending time of the trip is computed, taking into account the + current `time`. + +<7> An `Event` signaling passenger drop off is yielded. Coroutine + suspended again, waiting for the main loop to send the time of when + it's time to continue. + +<8> The `for` loop ends after the given number of trips, and a final + `'going home'` event is yielded, to happen 1 minute after the current + time. The coroutine will suspend for the last time. When reactivated, + it will be sent the time from the simulation main loop, but here I + don't assign it to any variable because it will not be useful. + +<9> When the coroutine falls off the end, the coroutine object raises + `StopIteration`. + + +Notes for the ``Simulator.run`` method:: + +<1> The simulation `end_time` is the only required argument for `run`. + +<2> Use `sorted` to retrieve the `self.procs` items ordered by the + integer key; we don't care about the key, so assign it to `_`. + +<3> `next(proc)` primes each coroutine by advancing it to the first + yield, so it's ready to be sent data. An `Event` is yielded. + +<4> Add each event to the `self.events` `PriorityQueue`. The first + event for each taxi is `'leave garage'`, as seen in the sample run + (ex_taxi_process>>). + +<5> Main loop of the simulation: run until the current `time` equals + or exceeds the `end_time`. + +<6> The main loop may also exit if there are no pending events in the + queue. + +<7> Get `Event` with the smallest `time` in the queue; this is the + `current_event`. + +<8> Display the `Event`, identifying the taxi and adding indentation + according to the taxi id. + +<9> Update the simulation time with the time of the `current_event`. + +<10> Retrieve the coroutine for this taxi from the `self.procs` + dictionary. + +<11> Send the `time` to the coroutine. The coroutine will yield the + `next_event` or raise `StopIteration` it's finished. + +<12> If `StopIteration` was raised, delete the coroutine from the + `self.procs` dictionary. + +<13> Otherwise, put the `next_event` in the queue. + +<14> If the loop exits because the simulation time passed, display the + number of events pending (which may be zero by coincidence, + sometimes). + + +Sample run from the command line, seed=24, total elapsed time=160:: + +# BEGIN TAXI_SAMPLE_RUN +$ python3 taxi_sim.py -s 24 -e 160 +taxi: 0 Event(time=0, proc=0, action='leave garage') +taxi: 0 Event(time=5, proc=0, action='pick up passenger') +taxi: 1 Event(time=5, proc=1, action='leave garage') +taxi: 1 Event(time=6, proc=1, action='pick up passenger') +taxi: 2 Event(time=10, proc=2, action='leave garage') +taxi: 2 Event(time=11, proc=2, action='pick up passenger') +taxi: 2 Event(time=23, proc=2, action='drop off passenger') +taxi: 0 Event(time=24, proc=0, action='drop off passenger') +taxi: 2 Event(time=24, proc=2, action='pick up passenger') +taxi: 2 Event(time=26, proc=2, action='drop off passenger') +taxi: 0 Event(time=30, proc=0, action='pick up passenger') +taxi: 2 Event(time=31, proc=2, action='pick up passenger') +taxi: 0 Event(time=43, proc=0, action='drop off passenger') +taxi: 0 Event(time=44, proc=0, action='going home') +taxi: 2 Event(time=46, proc=2, action='drop off passenger') +taxi: 2 Event(time=49, proc=2, action='pick up passenger') +taxi: 1 Event(time=70, proc=1, action='drop off passenger') +taxi: 2 Event(time=70, proc=2, action='drop off passenger') +taxi: 2 Event(time=71, proc=2, action='pick up passenger') +taxi: 2 Event(time=79, proc=2, action='drop off passenger') +taxi: 1 Event(time=88, proc=1, action='pick up passenger') +taxi: 2 Event(time=92, proc=2, action='pick up passenger') +taxi: 2 Event(time=98, proc=2, action='drop off passenger') +taxi: 2 Event(time=99, proc=2, action='going home') +taxi: 1 Event(time=102, proc=1, action='drop off passenger') +taxi: 1 Event(time=104, proc=1, action='pick up passenger') +taxi: 1 Event(time=135, proc=1, action='drop off passenger') +taxi: 1 Event(time=136, proc=1, action='pick up passenger') +taxi: 1 Event(time=151, proc=1, action='drop off passenger') +taxi: 1 Event(time=152, proc=1, action='going home') +*** end of events *** +# END TAXI_SAMPLE_RUN + +""" diff --git a/19-coroutine/taxi_sim_delay.py b/19-coroutine/taxi_sim_delay.py new file mode 100644 index 0000000..8f38e4f --- /dev/null +++ b/19-coroutine/taxi_sim_delay.py @@ -0,0 +1,215 @@ + +""" +Taxi simulator with delay on output +=================================== + +This is a variation of ``taxi_sim.py`` which adds a ``-d`` comand-line +option. When given, that option adds a delay in the main loop, pausing +the simulation for .5s for each "minute" of simulation time. + + +Driving a taxi from the console:: + + >>> from taxi_sim import taxi_process + >>> taxi = taxi_process(ident=13, trips=2, start_time=0) + >>> next(taxi) + Event(time=0, proc=13, action='leave garage') + >>> taxi.send(_.time + 7) + Event(time=7, proc=13, action='pick up passenger') + >>> taxi.send(_.time + 23) + Event(time=30, proc=13, action='drop off passenger') + >>> taxi.send(_.time + 5) + Event(time=35, proc=13, action='pick up passenger') + >>> taxi.send(_.time + 48) + Event(time=83, proc=13, action='drop off passenger') + >>> taxi.send(_.time + 1) + Event(time=84, proc=13, action='going home') + >>> taxi.send(_.time + 10) + Traceback (most recent call last): + File "", line 1, in + StopIteration + +Sample run with two cars, random seed 10. This is a valid doctest:: + + >>> main(num_taxis=2, seed=10) + taxi: 0 Event(time=0, proc=0, action='leave garage') + taxi: 0 Event(time=5, proc=0, action='pick up passenger') + taxi: 1 Event(time=5, proc=1, action='leave garage') + taxi: 1 Event(time=10, proc=1, action='pick up passenger') + taxi: 1 Event(time=15, proc=1, action='drop off passenger') + taxi: 0 Event(time=17, proc=0, action='drop off passenger') + taxi: 1 Event(time=24, proc=1, action='pick up passenger') + taxi: 0 Event(time=26, proc=0, action='pick up passenger') + taxi: 0 Event(time=30, proc=0, action='drop off passenger') + taxi: 0 Event(time=34, proc=0, action='going home') + taxi: 1 Event(time=46, proc=1, action='drop off passenger') + taxi: 1 Event(time=48, proc=1, action='pick up passenger') + taxi: 1 Event(time=110, proc=1, action='drop off passenger') + taxi: 1 Event(time=139, proc=1, action='pick up passenger') + taxi: 1 Event(time=140, proc=1, action='drop off passenger') + taxi: 1 Event(time=150, proc=1, action='going home') + *** end of events *** + +See longer sample run at the end of this module. + +""" + +import random +import collections +import queue +import argparse +import time + +DEFAULT_NUMBER_OF_TAXIS = 3 +DEFAULT_END_TIME = 180 +SEARCH_DURATION = 5 +TRIP_DURATION = 20 +DEPARTURE_INTERVAL = 5 + +Event = collections.namedtuple('Event', 'time proc action') + + +# BEGIN TAXI_PROCESS +def taxi_process(ident, trips, start_time=0): # <1> + """Yield to simulator issuing event at each state change""" + time = yield Event(start_time, ident, 'leave garage') # <2> + for i in range(trips): # <3> + time = yield Event(time, ident, 'pick up passenger') # <4> + time = yield Event(time, ident, 'drop off passenger') # <5> + + yield Event(time, ident, 'going home') # <6> + # end of taxi process # <7> +# END TAXI_PROCESS + + +# BEGIN TAXI_SIMULATOR +class Simulator: + + def __init__(self, procs_map): + self.events = queue.PriorityQueue() + self.procs = dict(procs_map) + + def run(self, end_time, delay=False): # <1> + """Schedule and display events until time is up""" + # schedule the first event for each cab + for _, proc in sorted(self.procs.items()): # <2> + first_event = next(proc) # <3> + self.events.put(first_event) # <4> + + # main loop of the simulation + sim_time = 0 # <5> + while sim_time < end_time: # <6> + if self.events.empty(): # <7> + print('*** end of events ***') + break + + # get and display current event + current_event = self.events.get() # <8> + if delay: + time.sleep((current_event.time - sim_time) / 2) + # update the simulation time + sim_time, proc_id, previous_action = current_event + print('taxi:', proc_id, proc_id * ' ', current_event) + active_proc = self.procs[proc_id] + # schedule next action for current proc + next_time = sim_time + compute_duration(previous_action) + try: + next_event = active_proc.send(next_time) # <12> + except StopIteration: + del self.procs[proc_id] # <13> + else: + self.events.put(next_event) # <14> + else: # <15> + msg = '*** end of simulation time: {} events pending ***' + print(msg.format(self.events.qsize())) +# END TAXI_SIMULATOR + + +def compute_duration(previous_action): + """Compute action duration using exponential distribution""" + if previous_action in ['leave garage', 'drop off passenger']: + # new state is prowling + interval = SEARCH_DURATION + elif previous_action == 'pick up passenger': + # new state is trip + interval = TRIP_DURATION + elif previous_action == 'going home': + interval = 1 + else: + raise ValueError('Unknown previous_action: %s' % previous_action) + return int(random.expovariate(1/interval)) + 1 + + +def main(end_time=DEFAULT_END_TIME, num_taxis=DEFAULT_NUMBER_OF_TAXIS, + seed=None, delay=False): + """Initialize random generator, build procs and run simulation""" + if seed is not None: + random.seed(seed) # get reproducible results + + taxis = {i: taxi_process(i, (i+1)*2, i*DEPARTURE_INTERVAL) + for i in range(num_taxis)} + sim = Simulator(taxis) + sim.run(end_time, delay) + + +if __name__ == '__main__': + + parser = argparse.ArgumentParser( + description='Taxi fleet simulator.') + parser.add_argument('-e', '--end-time', type=int, + default=DEFAULT_END_TIME, + help='simulation end time; default = %s' + % DEFAULT_END_TIME) + parser.add_argument('-t', '--taxis', type=int, + default=DEFAULT_NUMBER_OF_TAXIS, + help='number of taxis running; default = %s' + % DEFAULT_NUMBER_OF_TAXIS) + parser.add_argument('-s', '--seed', type=int, default=None, + help='random generator seed (for testing)') + parser.add_argument('-d', '--delay', action='store_true', + help='introduce delay proportional to simulation time') + + args = parser.parse_args() + main(args.end_time, args.taxis, args.seed, args.delay) + + +""" + +Sample run from the command line, seed=3, maximum elapsed time=120:: + +# BEGIN TAXI_SAMPLE_RUN +$ python3 taxi_sim.py -s 3 -e 120 +taxi: 0 Event(time=0, proc=0, action='leave garage') +taxi: 0 Event(time=2, proc=0, action='pick up passenger') +taxi: 1 Event(time=5, proc=1, action='leave garage') +taxi: 1 Event(time=8, proc=1, action='pick up passenger') +taxi: 2 Event(time=10, proc=2, action='leave garage') +taxi: 2 Event(time=15, proc=2, action='pick up passenger') +taxi: 2 Event(time=17, proc=2, action='drop off passenger') +taxi: 0 Event(time=18, proc=0, action='drop off passenger') +taxi: 2 Event(time=18, proc=2, action='pick up passenger') +taxi: 2 Event(time=25, proc=2, action='drop off passenger') +taxi: 1 Event(time=27, proc=1, action='drop off passenger') +taxi: 2 Event(time=27, proc=2, action='pick up passenger') +taxi: 0 Event(time=28, proc=0, action='pick up passenger') +taxi: 2 Event(time=40, proc=2, action='drop off passenger') +taxi: 2 Event(time=44, proc=2, action='pick up passenger') +taxi: 1 Event(time=55, proc=1, action='pick up passenger') +taxi: 1 Event(time=59, proc=1, action='drop off passenger') +taxi: 0 Event(time=65, proc=0, action='drop off passenger') +taxi: 1 Event(time=65, proc=1, action='pick up passenger') +taxi: 2 Event(time=65, proc=2, action='drop off passenger') +taxi: 2 Event(time=72, proc=2, action='pick up passenger') +taxi: 0 Event(time=76, proc=0, action='going home') +taxi: 1 Event(time=80, proc=1, action='drop off passenger') +taxi: 1 Event(time=88, proc=1, action='pick up passenger') +taxi: 2 Event(time=95, proc=2, action='drop off passenger') +taxi: 2 Event(time=97, proc=2, action='pick up passenger') +taxi: 2 Event(time=98, proc=2, action='drop off passenger') +taxi: 1 Event(time=106, proc=1, action='drop off passenger') +taxi: 2 Event(time=109, proc=2, action='going home') +taxi: 1 Event(time=110, proc=1, action='going home') +*** end of events *** +# END TAXI_SAMPLE_RUN + +""" diff --git a/19-coroutine/yield_from_expansion.py b/19-coroutine/yield_from_expansion.py new file mode 100644 index 0000000..837925f --- /dev/null +++ b/19-coroutine/yield_from_expansion.py @@ -0,0 +1,52 @@ +# Code below is the expansion of the statement: +# +# RESULT = yield from EXPR +# +# Copied verbatim from the Formal Semantics section of +# PEP 380 -- Syntax for Delegating to a Subgenerator +# +# https://www.python.org/dev/peps/pep-0380/#formal-semantics + + +# tag::YIELD_FROM_EXPANSION[] +_i = iter(EXPR) # <1> +try: + _y = next(_i) # <2> +except StopIteration as _e: + _r = _e.value # <3> +else: + while 1: # <4> + try: + _s = yield _y # <5> + except GeneratorExit as _e: # <6> + try: + _m = _i.close + except AttributeError: + pass + else: + _m() + raise _e + except BaseException as _e: # <7> + _x = sys.exc_info() + try: + _m = _i.throw + except AttributeError: + raise _e + else: # <8> + try: + _y = _m(*_x) + except StopIteration as _e: + _r = _e.value + break + else: # <9> + try: # <10> + if _s is None: # <11> + _y = next(_i) + else: + _y = _i.send(_s) + except StopIteration as _e: # <12> + _r = _e.value + break + +RESULT = _r # <13> +# end::YIELD_FROM_EXPANSION[] diff --git a/19-coroutine/yield_from_expansion_simplified.py b/19-coroutine/yield_from_expansion_simplified.py new file mode 100644 index 0000000..7648da5 --- /dev/null +++ b/19-coroutine/yield_from_expansion_simplified.py @@ -0,0 +1,32 @@ +# Code below is a very simplified expansion of the statement: +# +# RESULT = yield from EXPR +# +# This code assumes that the subgenerator will run to completion, +# without the client ever calling ``.throw()`` or ``.close()``. +# Also, this code makes no distinction between the client +# calling ``next(subgen)`` or ``subgen.send(...)`` +# +# The full expansion is in: +# PEP 380 -- Syntax for Delegating to a Subgenerator +# +# https://www.python.org/dev/peps/pep-0380/#formal-semantics + + +# tag::YIELD_FROM_EXPANSION_SIMPLIFIED[] +_i = iter(EXPR) # <1> +try: + _y = next(_i) # <2> +except StopIteration as _e: + _r = _e.value # <3> +else: + while 1: # <4> + _s = yield _y # <5> + try: + _y = _i.send(_s) # <6> + except StopIteration as _e: # <7> + _r = _e.value + break + +RESULT = _r # <8> +# end::YIELD_FROM_EXPANSION_SIMPLIFIED[] diff --git a/20-concurrency/primes/spinner_prime_async_broken.py b/20-concurrency/primes/spinner_prime_async_broken.py index 4f6ad06..2290ece 100644 --- a/20-concurrency/primes/spinner_prime_async_broken.py +++ b/20-concurrency/primes/spinner_prime_async_broken.py @@ -24,7 +24,7 @@ async def check(n: int) -> int: async def supervisor(n: int) -> int: spinner = asyncio.create_task(spin('thinking!')) # <1> - print(f'spinner object: {spinner}') # <2> + print('spinner object:', spinner) # <2> result = await check(n) # <3> spinner.cancel() # <5> return result diff --git a/20-concurrency/primes/spinner_prime_async_nap.py b/20-concurrency/primes/spinner_prime_async_nap.py index 55b8574..4d6fa47 100644 --- a/20-concurrency/primes/spinner_prime_async_nap.py +++ b/20-concurrency/primes/spinner_prime_async_nap.py @@ -43,7 +43,7 @@ async def check(n: int) -> int: async def supervisor(n: int) -> int: spinner = asyncio.create_task(spin('thinking!')) # <1> - print(f'spinner object: {spinner}') # <2> + print('spinner object:', spinner) # <2> result = await check(n) # <3> spinner.cancel() # <5> return result diff --git a/20-concurrency/primes/threads_py37.py b/20-concurrency/primes/threads_py37.py deleted file mode 100644 index 61d70c9..0000000 --- a/20-concurrency/primes/threads_py37.py +++ /dev/null @@ -1,39 +0,0 @@ -from queue import SimpleQueue -from time import perf_counter -from threading import Thread -from typing import List, NamedTuple - -from primes import is_prime, NUMBERS - -class Result(NamedTuple): # <3> - flag: bool - elapsed: float - -def check(n: int) -> Result: # <5> - t0 = perf_counter() - res = is_prime(n) - return Result(res, perf_counter() - t0) - -def job(n: int, results: SimpleQueue) -> None: # <6> - results.put((n, check(n))) # <7> - -def main() -> None: - t0 = perf_counter() - results = SimpleQueue() # type: ignore - workers: List[Thread] = [] # <2> - - for n in NUMBERS: - worker = Thread(target=job, args=(n, results)) # <3> - worker.start() # <4> - workers.append(worker) # <5> - - for _ in workers: # <6> - n, (prime, elapsed) = results.get() # <7> - label = 'P' if prime else ' ' - print(f'{n:16} {label} {elapsed:9.6f}s') - - elapsed = perf_counter() - t0 - print(f'Total time: {elapsed:.2f}s') - -if __name__ == '__main__': - main() diff --git a/21-futures/getflags/.gitignore b/21-futures/getflags/.gitignore new file mode 100644 index 0000000..8484300 --- /dev/null +++ b/21-futures/getflags/.gitignore @@ -0,0 +1 @@ +flags/ \ No newline at end of file diff --git a/21-futures/getflags/flags2_common.py b/21-futures/getflags/flags2_common.py index f79a993..7c76498 100644 --- a/21-futures/getflags/flags2_common.py +++ b/21-futures/getflags/flags2_common.py @@ -69,9 +69,9 @@ def expand_cc_args(every_cc: bool, cc_args: list[str], limit: int) -> list[str]: codes: set[str] = set() - A_Z = set(string.ascii_uppercase) + A_Z = string.ascii_uppercase if every_cc: - codes.update(f'{a}{b}' for a in A_Z for b in A_Z) + codes.update(a+b for a in A_Z for b in A_Z) elif all_cc: text = COUNTRY_CODES_FILE.read_text() codes.update(text.split()) diff --git a/21-futures/getflags/flags_asyncio.py b/21-futures/getflags/flags_asyncio.py index 60bb755..b5dad24 100755 --- a/21-futures/getflags/flags_asyncio.py +++ b/21-futures/getflags/flags_asyncio.py @@ -11,36 +11,41 @@ 20 flags downloaded in 1.07s """ -# tag::FLAGS_ASYNCIO[] +# tag::FLAGS_ASYNCIO_TOP[] import asyncio from aiohttp import ClientSession # <1> from flags import BASE_URL, save_flag, main # <2> -async def get_flag(session: ClientSession, cc: str) -> bytes: # <3> - cc = cc.lower() - url = f'{BASE_URL}/{cc}/{cc}.gif' - async with session.get(url) as resp: # <4> - print(resp) - return await resp.read() # <5> - -async def download_one(session: ClientSession, cc: str): # <6> +async def download_one(session: ClientSession, cc: str): # <3> image = await get_flag(session, cc) print(cc, end=' ', flush=True) save_flag(image, cc.lower() + '.gif') return cc +async def get_flag(session: ClientSession, cc: str) -> bytes: # <4> + cc = cc.lower() + url = f'{BASE_URL}/{cc}/{cc}.gif' + async with session.get(url) as resp: # <5> + print(resp) + return await resp.read() # <6> + + +# end::FLAGS_ASYNCIO_TOP[] + +# end::FLAGS_ASYNCIO_START[] +def download_many(cc_list): # <1> + return asyncio.run(supervisor(cc_list)) # <2> + async def supervisor(cc_list): - async with ClientSession() as session: # <7> + async with ClientSession() as session: # <3> to_do = [download_one(session, cc) - for cc in sorted(cc_list)] # <8> - res = await asyncio.gather(*to_do) # <9> - return len(res) + for cc in sorted(cc_list)] # <4> + res = await asyncio.gather(*to_do) # <5> -def download_many(cc_list): - return asyncio.run(supervisor(cc_list)) # <10> + return len(res) # <6> if __name__ == '__main__': main(download_many) -# end::FLAGS_ASYNCIO[] +# end::FLAGS_ASYNCIO_START[] diff --git a/21-futures/getflags/flags_threadpool_futures.py b/21-futures/getflags/flags_threadpool_futures.py index c06f260..b17a698 100755 --- a/21-futures/getflags/flags_threadpool_futures.py +++ b/21-futures/getflags/flags_threadpool_futures.py @@ -20,7 +20,7 @@ def download_many(cc_list: list[str]) -> int: to_do.append(future) # <5> print(f'Scheduled for {cc}: {future}') # <6> - for count, future in enumerate(futures.as_completed(to_do)): # <7> + for count, future in enumerate(futures.as_completed(to_do), 1): # <7> res: str = future.result() # <8> print(f'{future} result: {res!r}') # <9> diff --git a/23-dyn-attr-prop/README.rst b/23-dyn-attr-prop/README.rst new file mode 100644 index 0000000..3a87af1 --- /dev/null +++ b/23-dyn-attr-prop/README.rst @@ -0,0 +1,4 @@ +Sample code for Chapter 19 - "Dynamic attributes and properties" + +From the book "Fluent Python" by Luciano Ramalho (O'Reilly, 2015) +http://shop.oreilly.com/product/0636920032519.do diff --git a/23-dyn-attr-prop/blackknight.py b/23-dyn-attr-prop/blackknight.py new file mode 100644 index 0000000..014b458 --- /dev/null +++ b/23-dyn-attr-prop/blackknight.py @@ -0,0 +1,44 @@ +""" +This class is inspired by the Black Knight scene in the movie +"Monty Python and the Holy Grail", where King Arthur fights the +Black Knight, slicing off his arms and legs, but the knight +refuses to concede defeat. + +# tag::BLACK_KNIGHT_DEMO[] + >>> knight = BlackKnight() + >>> knight.member + next member is: + 'an arm' + >>> del knight.member + BLACK KNIGHT (loses an arm) -- 'Tis but a scratch. + >>> del knight.member + BLACK KNIGHT (loses another arm) -- It's just a flesh wound. + >>> del knight.member + BLACK KNIGHT (loses a leg) -- I'm invincible! + >>> del knight.member + BLACK KNIGHT (loses another leg) -- All right, we'll call it a draw. + +# end::BLACK_KNIGHT_DEMO[] +""" + +# tag::BLACK_KNIGHT[] +class BlackKnight: + + def __init__(self): + self.phrases = [ + ('an arm', "'Tis but a scratch."), + ('another arm', "It's just a flesh wound."), + ('a leg', "I'm invincible!"), + ('another leg', "All right, we'll call it a draw.") + ] + + @property + def member(self): + print('next member is:') + return self.phrases[0][0] + + @member.deleter + def member(self): + member, text = self.phrases.pop(0) + print(f'BLACK KNIGHT (loses {member}) -- {text}') +# end::BLACK_KNIGHT[] diff --git a/23-dyn-attr-prop/bulkfood/bulkfood_v1.py b/23-dyn-attr-prop/bulkfood/bulkfood_v1.py new file mode 100644 index 0000000..abb19a0 --- /dev/null +++ b/23-dyn-attr-prop/bulkfood/bulkfood_v1.py @@ -0,0 +1,37 @@ +""" +A line item for a bulk food order has description, weight and price fields. +A ``subtotal`` method gives the total price for that line item:: + + >>> raisins = LineItem('Golden raisins', 10, 6.95) + >>> raisins.weight, raisins.description, raisins.price + (10, 'Golden raisins', 6.95) + >>> raisins.subtotal() + 69.5 + +But, without validation, these public attributes can cause trouble:: + +# tag::LINEITEM_PROBLEM_V1[] + + >>> raisins = LineItem('Golden raisins', 10, 6.95) + >>> raisins.subtotal() + 69.5 + >>> raisins.weight = -20 # garbage in... + >>> raisins.subtotal() # garbage out... + -139.0 + +# end::LINEITEM_PROBLEM_V1[] + +""" + + +# tag::LINEITEM_V1[] +class LineItem: + + def __init__(self, description, weight, price): + self.description = description + self.weight = weight + self.price = price + + def subtotal(self): + return self.weight * self.price +# end::LINEITEM_V1[] diff --git a/23-dyn-attr-prop/bulkfood/bulkfood_v2.py b/23-dyn-attr-prop/bulkfood/bulkfood_v2.py new file mode 100644 index 0000000..97fb677 --- /dev/null +++ b/23-dyn-attr-prop/bulkfood/bulkfood_v2.py @@ -0,0 +1,63 @@ +""" + +A line item for a bulk food order has description, weight and price fields:: + + >>> raisins = LineItem('Golden raisins', 10, 6.95) + >>> raisins.weight, raisins.description, raisins.price + (10, 'Golden raisins', 6.95) + +A ``subtotal`` method gives the total price for that line item:: + + >>> raisins.subtotal() + 69.5 + +The weight of a ``LineItem`` must be greater than 0:: + + >>> raisins.weight = -20 + Traceback (most recent call last): + ... + ValueError: value must be > 0 + +No change was made:: + + >>> raisins.weight + 10 + +The check is also performed on instantiation:: + + >>> walnuts = LineItem('walnuts', 0, 10.00) + Traceback (most recent call last): + ... + ValueError: value must be > 0 + +The proteced attribute can still be accessed if needed for some reason, such as +white box testing):: + + >>> raisins._LineItem__weight + 10 + +""" + + +# tag::LINEITEM_V2[] +class LineItem: + + def __init__(self, description, weight, price): + self.description = description + self.weight = weight # <1> + self.price = price + + def subtotal(self): + return self.weight * self.price + + @property # <2> + def weight(self): # <3> + return self.__weight # <4> + + @weight.setter # <5> + def weight(self, value): + if value > 0: + self.__weight = value # <6> + else: + raise ValueError('value must be > 0') # <7> +# end::LINEITEM_V2[] diff --git a/23-dyn-attr-prop/bulkfood/bulkfood_v2b.py b/23-dyn-attr-prop/bulkfood/bulkfood_v2b.py new file mode 100644 index 0000000..d6deebc --- /dev/null +++ b/23-dyn-attr-prop/bulkfood/bulkfood_v2b.py @@ -0,0 +1,64 @@ +""" + +A line item for a bulk food order has description, weight and price fields:: + + >>> raisins = LineItem('Golden raisins', 10, 6.95) + >>> raisins.weight, raisins.description, raisins.price + (10, 'Golden raisins', 6.95) + +A ``subtotal`` method gives the total price for that line item:: + + >>> raisins.subtotal() + 69.5 + +The weight of a ``LineItem`` must be greater than 0:: + + >>> raisins.weight = -20 + Traceback (most recent call last): + ... + ValueError: value must be > 0 + +No change was made:: + + >>> raisins.weight + 10 + +The check is also performed on instantiation:: + + >>> walnuts = LineItem('walnuts', 0, 10.00) + Traceback (most recent call last): + ... + ValueError: value must be > 0 + +The proteced attribute can still be accessed if needed for some reason, such as +white box testing):: + + >>> raisins._LineItem__weight + 10 + +""" + + +# tag::LINEITEM_V2B[] +class LineItem: + + def __init__(self, description, weight, price): + self.description = description + self.weight = weight + self.price = price + + def subtotal(self): + return self.weight * self.price + + def get_weight(self): # <1> + return self.__weight + + def set_weight(self, value): # <2> + if value > 0: + self.__weight = value + else: + raise ValueError('value must be > 0') + + weight = property(get_weight, set_weight) # <3> + +# end::LINEITEM_V2B[] diff --git a/23-dyn-attr-prop/bulkfood/bulkfood_v2prop.py b/23-dyn-attr-prop/bulkfood/bulkfood_v2prop.py new file mode 100644 index 0000000..e34a7c3 --- /dev/null +++ b/23-dyn-attr-prop/bulkfood/bulkfood_v2prop.py @@ -0,0 +1,69 @@ +""" + +A line item for a bulk food order has description, weight and price fields:: + + >>> raisins = LineItem('Golden raisins', 10, 6.95) + >>> raisins.weight, raisins.description, raisins.price + (10, 'Golden raisins', 6.95) + +A ``subtotal`` method gives the total price for that line item:: + + >>> raisins.subtotal() + 69.5 + +The weight of a ``LineItem`` must be greater than 0:: + + >>> raisins.weight = -20 + Traceback (most recent call last): + ... + ValueError: value must be > 0 + +No change was made:: + + >>> raisins.weight + 10 + +The value of the attributes managed by the properties are stored in +instance attributes, created in each ``LineItem`` instance:: + +# tag::LINEITEM_V2_PROP_DEMO[] + >>> nutmeg = LineItem('Moluccan nutmeg', 8, 13.95) + >>> nutmeg.weight, nutmeg.price # <1> + (8, 13.95) + >>> sorted(vars(nutmeg).items()) # <2> + [('description', 'Moluccan nutmeg'), ('price', 13.95), ('weight', 8)] + +# end::LINEITEM_V2_PROP_DEMO[] + +""" + + +# tag::LINEITEM_V2_PROP_FACTORY_FUNCTION[] +def quantity(storage_name): # <1> + + def qty_getter(instance): # <2> + return instance.__dict__[storage_name] # <3> + + def qty_setter(instance, value): # <4> + if value > 0: + instance.__dict__[storage_name] = value # <5> + else: + raise ValueError('value must be > 0') + + return property(qty_getter, qty_setter) # <6> +# end::LINEITEM_V2_PROP_FACTORY_FUNCTION[] + + +# tag::LINEITEM_V2_PROP_CLASS[] +class LineItem: + weight = quantity('weight') # <1> + price = quantity('price') # <2> + + def __init__(self, description, weight, price): + self.description = description + self.weight = weight # <3> + self.price = price + + def subtotal(self): + return self.weight * self.price # <4> +# end::LINEITEM_V2_PROP_CLASS[] diff --git a/23-dyn-attr-prop/doc_property.py b/23-dyn-attr-prop/doc_property.py new file mode 100644 index 0000000..b1ba351 --- /dev/null +++ b/23-dyn-attr-prop/doc_property.py @@ -0,0 +1,23 @@ +""" +Example of property documentation + + >>> f = Foo() + >>> f.bar = 77 + >>> f.bar + 77 + >>> Foo.bar.__doc__ + 'The bar attribute' +""" + +# tag::DOC_PROPERTY[] +class Foo: + + @property + def bar(self): + '''The bar attribute''' + return self.__dict__['bar'] + + @bar.setter + def bar(self, value): + self.__dict__['bar'] = value +# end::DOC_PROPERTY[] diff --git a/23-dyn-attr-prop/oscon/data/osconfeed.json b/23-dyn-attr-prop/oscon/data/osconfeed.json new file mode 100644 index 0000000..9961768 --- /dev/null +++ b/23-dyn-attr-prop/oscon/data/osconfeed.json @@ -0,0 +1,13307 @@ + + + + + + { "Schedule": { + "conferences": [{"serial": 115 }], + "events": [ + + { + "serial": 33451, + "name": "Migrating to the Web Using Dart and Polymer - A Guide for Legacy OOP Developers", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 17:00:00", + "time_stop": "2014-07-23 17:40:00", + "venue_serial": 1458, + "description": "The web development platform is massive. With tons of libraries, frameworks and concepts out there, it might be daunting for the 'legacy' developer to jump into it.\r\n\r\nIn this presentation we will introduce Google Dart & Polymer. Two hot technologies that work in harmony to create powerful web applications using concepts familiar to OOP developers.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33451", + "speakers": [149868], + "categories": [ + + "Emerging Languages" + + ] + }, + + { + "serial": 33457, + "name": "Refactoring 101", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 11:30:00", + "time_stop": "2014-07-23 12:10:00", + "venue_serial": 1449, + "description": "Refactoring code (altering code to make it cleaner, simpler, and often faster, while not sacrificing functionality) We hate to do it, so learn how to do it better. Covers: When to refactor. How to refactor. Why refactor. How refactor can help us write better code. Common methodology for refactoring.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33457", + "speakers": [169862], + "categories": [ + + "PHP" + + ] + }, + + { + "serial": 33463, + "name": "Open Source and Mobile Development: Where Does it go From Here?", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 10:40:00", + "time_stop": "2014-07-23 11:20:00", + "venue_serial": 1459, + "description": "Until iOS and Android came along, the opportunities for open source to flourish in the mobile space were limited, because platforms were totally proprietary. Now you can find countless FL/OSS projects that help mobile developers get their job done. So what's on the horizon, and what are the best open source tools today to deliver the next great app?", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33463", + "speakers": [169870,2216,96208,150073], + "categories": [ + + "Mobile Platforms" + + ] + }, + + { + "serial": 33464, + "name": "Open Source Protocols and Architectures to Fix the Internet of Things", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 16:10:00", + "time_stop": "2014-07-23 16:50:00", + "venue_serial": 1451, + "description": "Everyday things are becoming smarter. The problem? The things are becoming smarter, but they\u2019re also becoming selfish and you\u2019ve ended up as a mechanical turk inside your own software. How can we fix the Internet of Things? The things have to become not just smarter, but more co-operative, and the Internet of things needs to become anticipatory rather than reactive.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33464", + "speakers": [2216], + "categories": [ + + "Open Hardware" + + ] + }, + + { + "serial": 33476, + "name": "Scaling PHP in the Real World!", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 14:30:00", + "time_stop": "2014-07-23 15:10:00", + "venue_serial": 1458, + "description": "PHP is used by the likes of Facebook, Yahoo, Zynga, Tumblr, Etsy, and Wikipedia. How do the largest internet companies scale PHP to meet their demand? Join this session and find out how to use the latest tools in PHP for developing high performance applications. We\u2019ll take a look at common techniques for scaling PHP applications and best practices for profiling and optimizing performance.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33476", + "speakers": [54107], + "categories": [ + + "PHP" + + ] + }, + + { + "serial": 33481, + "name": "API Ecosystem with Scala, Scalatra, and Swagger at Netflix", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 17:00:00", + "time_stop": "2014-07-23 17:40:00", + "venue_serial": 1456, + "description": "At Netflix Engineering's Partner Product Innovation group, we underwent a revamp of the tech stack to make it API-driven. This was to not only help with the expanding list of API consumers, but also to address the evolving streaming business. With Scala, Scalatra, and Swagger, we achieved one of the best architecture for the scale, agility and robustness needed.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33481", + "speakers": [113667], + "categories": [ + + "Emerging Languages" + + ] + }, + + { + "serial": 33485, + "name": "XSS and SQL Injections: The Tip of the Web Security Iceberg ", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 16:10:00", + "time_stop": "2014-07-23 16:50:00", + "venue_serial": 1458, + "description": "You might know about XSS and usual SQL injection, but time has changed and we have to keep up-to-date with the latest attack scenarios.\r\nDo you also know what a clickjacking is? If not I'll show you how to protect against it.\r\nI'll also present techniques like Perfect Pixel Timing and a combination of xss/time-based-sql-injection to access intranet sites, which are not even compromised.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33485", + "speakers": [169932], + "categories": [ + + "PHP" + + ] + }, + + { + "serial": 33503, + "name": "Scalable Analytics with R, Hadoop and RHadoop", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 14:30:00", + "time_stop": "2014-07-23 15:10:00", + "venue_serial": 1475, + "description": "Do you use Hadoop for large scale data analysis? Do your data scientists love R? This presentation will discuss the challenges of scaling R to multi-terabyte data sets and how RHadoop can be used to solve them.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33503", + "speakers": [126882], + "categories": [ + + "Databases & Datastores" + + ] + }, + + { + "serial": 33520, + "name": "HA 101 with OpenStack", + "event_type": "40-minute conference session", + + "time_start": "2014-07-24 10:00:00", + "time_stop": "2014-07-24 10:40:00", + "venue_serial": 1466, + "description": "There are a number of interrelated concepts which make the understanding and implementation of HA complex. The potential for not implementing HA correctly would be disastrous. This session will use demos to reinforce the concepts and how to connect the dots using OpenStack infrastructure as an example although the lessons learned can be used for implementing HA in general.\r\n\r\n", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33520", + "speakers": [131499], + "categories": [ + + "Cloud" + + ] + }, + + { + "serial": 33549, + "name": "Eyes on IZON: Surveilling IP Camera Security", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 14:30:00", + "time_stop": "2014-07-23 15:10:00", + "venue_serial": 1454, + "description": "This presentation will provide insight into the security mechanisms being used by the IZON IP camera, some of the weaknesses found during research, and a few recommendations for them (or anyone else developing these sorts of cameras) to benefit from. Attention will be paid to topics such as network protocols, iOS app security, APIs, and other aspects of the camera's attack surface.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33549", + "speakers": [170134], + "categories": [ + + "Security" + + ] + }, + + { + "serial": 33557, + "name": "How to Fake a Database Design", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 17:00:00", + "time_stop": "2014-07-22 17:40:00", + "venue_serial": 1475, + "description": "Many expert programmers who write complex SQL without a second thought still struggle with database design. Unfortunately, many introductory topics cause eyes to glaze over when we read 'transitive dependencies' and 'Boyce-Codd normal form'. When you're done with this talk, you'll understand the basics of creating a database that won't make a DBA yell at you. We won't even use (many) big words.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33557", + "speakers": [170158], + "categories": [ + + "Databases & Datastores" + + ] + }, + + { + "serial": 33564, + "name": "Testing with Test::Class::Moose", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 14:30:00", + "time_stop": "2014-07-22 15:10:00", + "venue_serial": 1465, + "description": "Perl is known for its testing culture. Unfortunately it's often focused on quantity over quality. Perl's Test::Class::Moose project started out as an experiment but morphed into a way of having higher quality testing. With this module, you can get fine-grained control over your test suite, better understand your *real* code coverage and get an quick boost to test suite performance.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33564", + "speakers": [170158], + "categories": [ + + "Perl" + + ] + }, + + { + "serial": 33571, + "name": "An Elasticsearch Crash Course", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 10:40:00", + "time_stop": "2014-07-22 11:20:00", + "venue_serial": 1475, + "description": "Elasticsearch is about more than just search. It\u2019s currently being used in production for everything from traditional text search, to big data analytics, to distributed document storage. This talk will introduce you to Elasticsearch\u2019s REST API, and discuss the basics of full text search and analytics with Elasticsearch.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33571", + "speakers": [170054], + "categories": [ + + "Databases & Datastores" + + ] + }, + + { + "serial": 33581, + "name": "Building a Massively Scalable Cloud Service from the Grounds Up", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 16:10:00", + "time_stop": "2014-07-22 16:50:00", + "venue_serial": 1459, + "description": "In this talk, we'll go into excruciating technical detail about building a greenfield, massively scalable cloud service. Along the path to constructing a scalable cloud service, there are many options and critical decisions to take, and we'll share our choices that brought both success and frustrations.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33581", + "speakers": [116050], + "categories": [ + + "Cloud" + + ] + }, + + { + "serial": 33585, + "name": "Open/Closed Software - Approaches to Developing Freemium Applications", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 10:40:00", + "time_stop": "2014-07-22 11:20:00", + "venue_serial": 1457, + "description": "Developing freemium which involves OSS is not a trivial task. In this talk we\u2019ll showcase Artifactory, which successfully combines open-source and Pro versions. We will talk about developing, building, testing, and releasing hybrid freemium application and will review the existing approaches, discussing pros and cons of each of them.\r\n", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33585", + "speakers": [114822], + "categories": [ + + "Business" + + ] + }, + + { + "serial": 33590, + "name": "How to Build Reactive Applications", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 10:40:00", + "time_stop": "2014-07-23 11:20:00", + "venue_serial": 1456, + "description": "An introduction to building Reactive Applications and what tools you can use to do so.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33590", + "speakers": [170293], + "categories": [ + + "Java & JVM" + + ] + }, + + { + "serial": 33596, + "name": "Designing Irresistible APIs", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 16:10:00", + "time_stop": "2014-07-23 16:50:00", + "venue_serial": 1452, + "description": "So you want to create a platform for your product? Creating a fantastic open API (or even a closed one) is not the same as creating other products. I'll talk about how what you need to know to design, plan and execute a successful, engaging API and how to avoid common pitfalls.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33596", + "speakers": [4265], + "categories": [ + + "Computational Thinking" + + ] + }, + + { + "serial": 33597, + "name": "Quantifying your Fitness", + "event_type": "40-minute conference session", + + "time_start": "2014-07-24 11:50:00", + "time_stop": "2014-07-24 12:30:00", + "venue_serial": 1475, + "description": "The Quantified Self movement is all about keeping measurements about your life in order to track progress in various ways. As geeks we all enjoy playing with new toys, and there are a variety of devices and applications out there to help measure steps, activity and fitness. Combining the data from these devices can help you build tools to track your fitness in a way that makes sense for you.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33597", + "speakers": [4265,182808], + "categories": [ + + "Geek Lifestyle" + + ] + }, + + { + "serial": 33627, + "name": "Introduction to Docker: Containerization is the New Virtualization", + "event_type": "tutorial", + + "time_start": "2014-07-20 13:30:00", + "time_stop": "2014-07-20 17:00:00", + "venue_serial": 1450, + "description": "You've heard the hype about Docker and container virtualization now see it in action. This tutorial will introduce you to Docker and take you through installing it, running it and integrating it into your development and operational workflow.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33627", + "speakers": [5060], + "categories": [ + + "Operations & System Administration", + + "Tools & Techniques" + + ] + }, + + { + "serial": 33631, + "name": "Mind the Gap: Architecting UIs in the Era of Diverse Devices", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 17:00:00", + "time_stop": "2014-07-23 17:40:00", + "venue_serial": 1449, + "description": "Architecting and developing user interfaces used to be relatively easy, pick a server side framework, define a standard monitor resolution and spend your days dealing with browser quirks. But today, the landscape presents us with a plethora of screen sizes and resolutions. How does a team embrace this brave new world knowing that the future will introduce even more volatility to the client space?", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33631", + "speakers": [108125], + "categories": [ + + "User Experience" + + ] + }, + + { + "serial": 33648, + "name": "Devoxx4Kids: So Your Kid Interested in Programming, Robotics, Engineering?", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 11:30:00", + "time_stop": "2014-07-22 12:10:00", + "venue_serial": 1462, + "description": "Devoxx4Kids is a worldwide initiative that introduces programming, robotics, and engineering to kids by organizing events and workshops. This session will share how Devoxx4Kids is giving Scratch, Greenfoot, Minecraft, Raspberry Pi, Arduino, NAO, Tynker workshops. The session will show a path that can be followed by parents to keep their kids engaged and build, instead of just play games. ", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33648", + "speakers": [143377], + "categories": [ + + "Education" + + ] + }, + + { + "serial": 33687, + "name": "Debugging LAMP Apps on Linux/UNIX Using Open Source Tools", + "event_type": "tutorial", + + "time_start": "2014-07-20 09:00:00", + "time_stop": "2014-07-20 12:30:00", + "venue_serial": 1451, + "description": "The purpose of this tutorial is to train web developers working on a Linux/UNIX ENV on production, development ENVs, or both.\r\nOften, these developers, while proficient in say, PHP, lack UNIX system knowledge and therefore come across a brick wall when debugging production issues.\r\nOften times, because the development ENV is different than production.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33687", + "speakers": [171078], + "categories": [ + + "PHP", + + "Tools & Techniques" + + ] + }, + + { + "serial": 33689, + "name": "Forty New Features of Java EE 7 in 40 Minutes", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 10:40:00", + "time_stop": "2014-07-22 11:20:00", + "venue_serial": 1456, + "description": "The Java EE 7 platform has 4 new components (WebSocket, JSON-P, batch, and concurrency), 3 that are significantly updated (JAX-RS, JMS, and EL), and several others that bring significant changes to the platform. This session explains each feature with a code snippet and provides details on where and how you can use it in your applications.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33689", + "speakers": [143377], + "categories": [ + + "Java & JVM" + + ] + }, + + { + "serial": 33704, + "name": "Building a Recommendation Engine with Spring and Hadoop", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 11:30:00", + "time_stop": "2014-07-23 12:10:00", + "venue_serial": 1456, + "description": "Recommendation engines are the mainstay of e-commerce sites. What if you could build one with only a few lines of code using open source tools. Come to this talk to find out how as we build one using the data from StackOverflow!", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33704", + "speakers": [171147], + "categories": [ + + "Java & JVM" + + ] + }, + + { + "serial": 33709, + "name": "Introduction to Parallel Iterative Deep Learning on Hadoop\u2019s Next\u200b-Generation YARN Framework", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 13:40:00", + "time_stop": "2014-07-23 14:20:00", + "venue_serial": 1452, + "description": "In this session, we will take a look at how we parallelize Deep Belief Networks in Deep Learning on the next\u200b-generation YARN framework Iterative Reduce and the parallel machine learning library Metronome. We\u2019ll also take a look at some real world applications of Deep Learning on Hadoop such as image classification and NLP.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33709", + "speakers": [171197,171201], + "categories": [ + + "Computational Thinking" + + ] + }, + + { + "serial": 33727, + "name": "Open Sourcing Mental Illness", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 13:40:00", + "time_stop": "2014-07-22 14:20:00", + "venue_serial": 1451, + "description": "I was diagnosed with depression and anxiety when I was thirteen, and I've been struggling with it my whole life. In this talk, I'll discuss how it has impacted my work as a developer, husband, and father. By speaking openly about my experiences, I hope those struggling with mental illness will know the are not alone, and others can better understand how to be helpful and supportive.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33727", + "speakers": [1639], + "categories": [ + + "Geek Lifestyle" + + ] + }, + + { + "serial": 33733, + "name": "When PostgreSQL Can't, You Can", + "event_type": "40-minute conference session", + + "time_start": "2014-07-24 10:00:00", + "time_stop": "2014-07-24 10:40:00", + "venue_serial": 1475, + "description": "After using PostgreSQL for a while, you realize that there are missing features that would make it significantly easier to use in large production environments. Thankfully, it's extremely easy to make add-ons to enable some of those features right now, even without knowing C! This talk will discuss projects I've worked on and show how easy it is to make an impact in the PostgreSQL community.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33733", + "speakers": [152242], + "categories": [ + + "Databases & Datastores" + + ] + }, + + { + "serial": 33741, + "name": "How to Achieve Enterprise Storage Functionality with OpenStack Block Storage", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 16:10:00", + "time_stop": "2014-07-22 16:50:00", + "venue_serial": 1475, + "description": "In this session, SolidFire's John Griffith will review some of the key features included within OpenStack Block Storage to help achieve the enterprise storage functionality they require to host production applications in their cloud infrastructure. ", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33741", + "speakers": [171381], + "categories": [ + + "Databases & Datastores" + + ] + }, + + { + "serial": 33800, + "name": "Building Native iOS and Android Apps in Java", + "event_type": "tutorial", + + "time_start": "2014-07-21 09:00:00", + "time_stop": "2014-07-21 12:30:00", + "venue_serial": 1456, + "description": "This tutorial will demonstrate the use of Codename One to develop a cross-platform mobile application in Java. In it you will build a non-trivial application and deploy it to your mobile device.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33800", + "speakers": [171418], + "categories": [ + + "Java & JVM", + + "Mobile Platforms" + + ] + }, + + { + "serial": 33834, + "name": "Presentation Aikido", + "event_type": "tutorial", + + "time_start": "2014-07-20 13:30:00", + "time_stop": "2014-07-20 17:00:00", + "venue_serial": 1475, + "description": "This tutorial explores a set of simple and practical techniques for giving better, more effective, more entertaining technical presentations. Discover how to capture an audience, hold their interest, convey your message to them clearly\u2026and maybe even inspire them.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33834", + "speakers": [4710], + "categories": [ + + "Education", + + "Geek Lifestyle" + + ] + }, + + { + "serial": 33839, + "name": "Everyday Perl 6", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 10:40:00", + "time_stop": "2014-07-23 11:20:00", + "venue_serial": 1465, + "description": "Perl 6's many advanced features (junctions, multiple dispatch, generics, grammars, lazy evaluation, coroutines, etc.) may well offer awesome cosmic power, but for most of us the real and immediate benefits of switching to Perl 6 are the numerous minor Perl annoyances it fixes. This talk offers a dozen practical reasons why Perl 6 might now be a better choice as your everyday go-to problem-solver.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33839", + "speakers": [4710], + "categories": [ + + "Perl" + + ] + }, + + { + "serial": 33841, + "name": "The Conway Channel", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 17:00:00", + "time_stop": "2014-07-22 17:40:00", + "venue_serial": 1465, + "description": "Join Damian for his annual kaleidoscopic tour of the strange and wonderful new Perl modules he's been developing over the past twelve months.\r\n ", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33841", + "speakers": [4710], + "categories": [ + + "Perl" + + ] + }, + + { + "serial": 33863, + "name": "Building a Resilient API with Open Source", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 10:40:00", + "time_stop": "2014-07-23 11:20:00", + "venue_serial": 1452, + "description": "How do you build and maintain a stable API while rapidly iterating and innovating in your business? Change can never be eliminated, but its impact can be minimized. GitHub takes a pragmatic approach to Hypermedia that emphasizes workflows over data retrieval and employs open source to ensure a consistent experience for API consumers.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33863", + "speakers": [109297], + "categories": [ + + "Computational Thinking" + + ] + }, + + { + "serial": 33875, + "name": "The Full Stack Java Developer", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 11:30:00", + "time_stop": "2014-07-22 12:10:00", + "venue_serial": 1456, + "description": "Today's Java developer is a rare bird: SQL and JPA on the backend, or MongoDB or Hadoop? HTTP, REST and websockets on the web? What about security? JavaScript, HTML, CSS, (not to mention LESS, SASS, and CoffeeScript!) on the client? Today's Java developer is a _full stack_ developer. Join Josh Long and Phillip Webb for a look at how Spring Boot simplifies full-stack development for everyone.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33875", + "speakers": [171564,171565], + "categories": [ + + "Java & JVM", + + "JavaScript - HTML5 - Web" + + ] + }, + + { + "serial": 33880, + "name": "How Instagram.com Works", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 16:10:00", + "time_stop": "2014-07-22 16:50:00", + "venue_serial": 1448, + "description": "Instagram.com renders almost all of its UI in JavaScript. I'll talk about how our packaging and push systems work in great detail, which are clever combinations of existing open-source tools. Anyone building a large site using lots of JavaScript would find what we've learned interesting!", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33880", + "speakers": [164756], + "categories": [ + + "Main Stage" + + ] + }, + + { + "serial": 33913, + "name": "Trolls Aren't the Only Threat Under the Bridge", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 13:40:00", + "time_stop": "2014-07-23 14:20:00", + "venue_serial": 1457, + "description": "There's been a lot of talk about patent trolls, but how can the free and open source software community address the more complicated (and potentially more damaging) problem of anti-competitive litigation? ", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33913", + "speakers": [130731], + "categories": [ + + "Business", + + "Community" + + ] + }, + + { + "serial": 33937, + "name": "Kraken.js - Bringing Open Source to the Enterprise", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 10:40:00", + "time_stop": "2014-07-23 11:20:00", + "venue_serial": 1457, + "description": "PayPal has recently moved their web application stack from a proprietary framework, resulting in weeks of training per developer and large maintenance costs, to an open source-based stack that allows our engineers to come in the door coding. This is the story of how we changed our enterprise culture and started giving back to the open source community.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33937", + "speakers": [183835,183908], + "categories": [ + + "Business" + + ] + }, + + { + "serial": 33943, + "name": "You Don't Know How Your Computer Works", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 16:10:00", + "time_stop": "2014-07-22 16:50:00", + "venue_serial": 1456, + "description": "Software development is easy. You tell a computer to do something. It does it. Someone sends you a packet. The OS receives it. Things don't happen unless you ask them to. Simple.\r\n\r\nBut what if that wasn't true? What if your computer is full of hidden magic? What if your hardware makes assumptions about your software? Vendors wouldn't do that, would they?\r\n\r\n(Spoiler: Yes, they would)", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33943", + "speakers": [6852], + "categories": [ + + "Operations & System Administration" + + ] + }, + + { + "serial": 33945, + "name": "Leaflet, Node.JS, and MongoDB for an Easy and Fun Web Mapping Experience", + "event_type": "40-minute conference session", + + "time_start": "2014-07-24 10:00:00", + "time_stop": "2014-07-24 10:40:00", + "venue_serial": 1450, + "description": "You have seen the stuff that FourSquare has done with spatial and you want some of that hotness for your app. We will load some data into MongoDB, show you how to handle spatial and finally plug in in some Node.JS JavaScript code to build simple REST services to query your data. Finally we will show how to use the REST service with OpenStreetMap and Leaflet for a fully interactive map.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33945", + "speakers": [142320], + "categories": [ + + "JavaScript - HTML5 - Web" + + ] + }, + + { + "serial": 33947, + "name": "The Simplicity of Clojure", + "event_type": "tutorial", + + "time_start": "2014-07-20 09:00:00", + "time_stop": "2014-07-20 12:30:00", + "venue_serial": 1456, + "description": "Clojure: it's a Lisp that runs on the JVM and it's gotten a lot of buzz in the last few years. What is it actually good for? In this tutorial, you'll learn about Clojure's radically simple approach to data and state and how it can help you build real-world projects from web applications to servers to mobile apps.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33947", + "speakers": [137149,171822], + "categories": [ + + "Emerging Languages", + + "Java & JVM" + + ] + }, + + { + "serial": 33950, + "name": "There *Will* Be Bugs", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 14:30:00", + "time_stop": "2014-07-23 15:10:00", + "venue_serial": 1449, + "description": "If you're pushing the envelope of programming (or of your own skills)... and even when you\u2019re not... there *will* be bugs in your code. Don't panic! We cover the attitudes and skills (not taught in most schools) to minimize your bugs, track them, find them, fix them, ensure they never recur, and deploy fixes to your users.\r\n", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33950", + "speakers": [3471,5199], + "categories": [ + + "Python" + + ] + }, + + { + "serial": 33973, + "name": "Apache Cordova: Past, Present and Future", + "event_type": "40-minute conference session", + + "time_start": "2014-07-24 10:00:00", + "time_stop": "2014-07-24 10:40:00", + "venue_serial": 1449, + "description": "A review of the past six years of Apache Cordova development, starting from its origins as PhoneGap, to its donation to the Apache Software Foundation, told from the point of view of its longest running contributor. This will include a simple introduction to cross-platform hybrid applications on iOS and Android, and their evolution.\r\n", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33973", + "speakers": [96208], + "categories": [ + + "Mobile Platforms" + + ] + }, + + { + "serial": 33978, + "name": "WITH What? CTEs For Fun And Profit", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 13:40:00", + "time_stop": "2014-07-23 14:20:00", + "venue_serial": 1466, + "description": "Have you tried some recursion in your SQL? In this session, we will go over the concept of Common Table Expressions (CTE), also known as WITH queries. We will explore syntax, features, and use cases for this powerful SQL construct.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33978", + "speakers": [25862], + "categories": [ + + "Databases & Datastores" + + ] + }, + + { + "serial": 33994, + "name": "Rebooting Open Source at Facebook", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 14:30:00", + "time_stop": "2014-07-23 15:10:00", + "venue_serial": 1448, + "description": "Open source has always been a huge part of Facebook's culture. But in 2013, we rebooted our portfolio and launched a unique suite of internal tools & instrumentation to support hundreds of repos, thousands of engineers, and tens of thousands of contributors. The result? Better-than-ever community adoption - and an open & responsible stewardship, attuned to our ethos of hacking & moving fast.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33994", + "speakers": [118233], + "categories": [ + + "Main Stage" + + ] + }, + + { + "serial": 33997, + "name": "CERN's Approach to Mass and Agility", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 17:00:00", + "time_stop": "2014-07-23 17:40:00", + "venue_serial": 1459, + "description": "As part of a large-scale adoption of cloud computing to support the increasing computing needs of the Large Hadron Collider processing over 35 PB/year, the infrastructure of CERN IT is undergoing major changes in both technology and culture. This session will describe the steps taken, the challenges encountered and our outlook for the future.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33997", + "speakers": [170052], + "categories": [ + + "Cloud" + + ] + }, + + { + "serial": 34005, + "name": "Let Them Be Your Heroes", + "event_type": "40-minute conference session", + + "time_start": "2014-07-24 10:00:00", + "time_stop": "2014-07-24 10:40:00", + "venue_serial": 1464, + "description": "How can you encourage involvement and participation in Open Source? They key is through empowerment. We'll discuss how to empower and encourage more people to participate in your open source project by enabling Heroism. This talk will also discuss issues of diversity and inclusion. ", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34005", + "speakers": [144736], + "categories": [ + + "Community" + + ] + }, + + { + "serial": 34012, + "name": "A Presentation Toolbox that Just Might Blow Your Audience Away", + "event_type": "40-minute conference session", + + "time_start": "2014-07-24 11:50:00", + "time_stop": "2014-07-24 12:30:00", + "venue_serial": 1450, + "description": "Still building presentations in an office suite? That's so 2013! Today, you can build awesome, engaging presentations that run in your browser or on your phone, using nothing but HTML5 and a few clever JavaScript libraries. And it's super simple! This talk shows you how.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34012", + "speakers": [131884], + "categories": [ + + "Geek Lifestyle" + + ] + }, + + { + "serial": 34018, + "name": "Raspberry Pi Hacks", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 13:40:00", + "time_stop": "2014-07-22 14:20:00", + "venue_serial": 1465, + "description": "Ruth Suehle, one of the authors of Raspberry Pi Hacks (O\u2019Reilly, December 2013) will offer technical tips for hardware and software hackers who want to build around the Raspberry Pi computer. She\u2019ll start with some tips like how to add a power switch and go on to share fun projects, from costume builds to radios and light displays.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34018", + "speakers": [108840], + "categories": [ + + "Open Hardware" + + ] + }, + + { + "serial": 34019, + "name": "From Cloud to Fog Computing and the Internet of Things", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 13:40:00", + "time_stop": "2014-07-23 14:20:00", + "venue_serial": 1449, + "description": "Open Source is ubiquitous in Cloud compute. Just as we became familiar with Cloud computing, a new model has emerged, an extension of the cloud to the edge of the network, some call it Fog computing, some call it the Internet of Things. This talk describes how the compute model is changing as the new generation of devices stretched what we previously knew as Cloud compute.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34019", + "speakers": [172370], + "categories": [ + + "Mobile Platforms" + + ] + }, + + { + "serial": 34026, + "name": "The Accidental DBA", + "event_type": "tutorial", + + "time_start": "2014-07-21 13:30:00", + "time_stop": "2014-07-21 17:00:00", + "venue_serial": 1475, + "description": "So, you\u2019ve inherited a PostgreSQL server. Congratulations? This tutorial will cover the essential care and feeding of a Postgres server so that you can get back to your real job.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34026", + "speakers": [3397], + "categories": [ + + "Databases & Datastores", + + "Operations & System Administration" + + ] + }, + + { + "serial": 34037, + "name": "Distributed Robots with Elixir", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 10:40:00", + "time_stop": "2014-07-23 11:20:00", + "venue_serial": 1451, + "description": "This talk will take you on a journey from making an LED blink through to building your own Elixir-powered robot using a RaspberryPi and Android.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34037", + "speakers": [172532,172534], + "categories": [ + + "Open Hardware" + + ] + }, + + { + "serial": 34040, + "name": "What is Async, How Does it Work, and When Should I Use it?", + "event_type": "40-minute conference session", + + "time_start": "2014-07-24 11:00:00", + "time_stop": "2014-07-24 11:40:00", + "venue_serial": 1458, + "description": "Asynchronous frameworks like Tornado, Twisted, and Node are increasingly important for writing high-performance web applications. Even if you\u2019re an experienced web programmer, you may lack a rigorous understanding of how these frameworks work and when to use them. See how Tornado's event loop works, and learn how to efficiently handle very large numbers of concurrent connections.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34040", + "speakers": [172536], + "categories": [ + + "Python" + + ] + }, + + { + "serial": 34047, + "name": "Go for Object Oriented Programmers (or OO Programming without Objects) ", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 16:10:00", + "time_stop": "2014-07-23 16:50:00", + "venue_serial": 1465, + "description": "Object Oriented programming has dominated software engineering for the last two decades. Although Go is not OO in the strict sense, we can continue to leverage the skills we've honed as OO engineers. This talk will cover how to use our OO programming fundamentals in go, common mistakes made by those coming to go from other OO languages (Ruby, Python, JS, etc.), and principles of good design in go.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34047", + "speakers": [142995], + "categories": [ + + "Emerging Languages" + + ] + }, + + { + "serial": 34056, + "name": "Unicorns, Dragons, Open Source Business Models And Other Mythical Creatures", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 14:30:00", + "time_stop": "2014-07-22 15:10:00", + "venue_serial": 1448, + "description": "No one size fits all formula can be applied to build a business around open source, and attempting to do so may end in humiliation and disaster.\r\n\r\nThere is no doubt that 'Open Source' has impacted the dynamics of all manner of business, but building a business on 'Open Source' is not a solved problem.\r\n\r\nA guided tour of open source business models, real and imaginary.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34056", + "speakers": [24052], + "categories": [ + + "Main Stage" + + ] + }, + + { + "serial": 34063, + "name": "Tutorial: Node.js Three Ways", + "event_type": "tutorial", + + "time_start": "2014-07-21 13:30:00", + "time_stop": "2014-07-21 17:00:00", + "venue_serial": 1452, + "description": "In this tutorial, we\u2019ll explore three unique technologies, and accompanying use cases, for Node.js development. We\u2019ll divide the tutorial into three one-hour segments, in which you will develop three different Node.js-powered applications.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34063", + "speakers": [141235,147649], + "categories": [ + + "Emerging Languages", + + "JavaScript - HTML5 - Web" + + ] + }, + + { + "serial": 34064, + "name": "Node.js Patterns for the Discerning Developer", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 10:40:00", + "time_stop": "2014-07-22 11:20:00", + "venue_serial": 1450, + "description": "In this session, I\u2019ll presenting high-quality Node.js design patterns. I\u2019ll bring to the table design patterns I\u2019ve stumbled across in my own Node projects, as well as patterns observed from experts in the Node.js community.\r\n\r\nTopics include: Mastering Modules, Object Inheritance in Node.js, Patterns to avoid callback hell, Batch and Queuing patterns for massively concurrent asynchronous I/O", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34064", + "speakers": [141235], + "categories": [ + + "JavaScript - HTML5 - Web" + + ] + }, + + { + "serial": 34075, + "name": "Druid: Interactive Queries Meet Real-time Data", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 11:30:00", + "time_stop": "2014-07-23 12:10:00", + "venue_serial": 1475, + "description": "This talk will focus on the motivation, design, and architecture of Druid (druid.io), an open-source, real-time analytical data store. Druid is used in production at several organizations to facilitate rapid exploration of high dimensional spaces. Druid can maintain a 95% query latency under 1 second on data sets with >50 billion rows and 2 trillion impressions in tables with 30+ dimensions.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34075", + "speakers": [172607], + "categories": [ + + "Databases & Datastores" + + ] + }, + + { + "serial": 34076, + "name": "Real-time Analytics with Open Source Technologies", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 10:40:00", + "time_stop": "2014-07-23 11:20:00", + "venue_serial": 1475, + "description": "The maturation and development of open source technologies has made it easier than ever for companies to derive insights from vast quantities of data. In this session, we will cover how to build a real-time analytics stack using Kafka, Storm, and Druid. This combination of technologies can power a robust data pipeline that supports real-time ingestion and flexible, low-latency queries.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34076", + "speakers": [153565,153566], + "categories": [ + + "Databases & Datastores" + + ] + }, + + { + "serial": 34078, + "name": "Moose is Perl: A Guide to the New Revolution", + "event_type": "tutorial", + + "time_start": "2014-07-21 09:00:00", + "time_stop": "2014-07-21 12:30:00", + "venue_serial": 1457, + "description": "Moose continues to emerge as the new standard for writing OO libraries in Perl. It provides a powerful, consistent API for building classes with a minimum of code. It can be customized with reusable components, making it easier to refactor your code as you go. This tutorial will explain what Moose is, how its parts work together, and how to start using Moose today to get more done with less.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34078", + "speakers": [3189], + "categories": [ + + "Perl" + + ] + }, + + { + "serial": 34081, + "name": "Debugging HTTP", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 11:30:00", + "time_stop": "2014-07-22 12:10:00", + "venue_serial": 1449, + "description": "Show how tools including cURL, Wireshark and Charles can be used to inspect and change HTTP traffic when debugging applications which consume APIs and other remote sources.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34081", + "speakers": [45968], + "categories": [ + + "Tools & Techniques" + + ] + }, + + { + "serial": 34083, + "name": "Creating Models", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 11:30:00", + "time_stop": "2014-07-22 12:10:00", + "venue_serial": 1458, + "description": "How should you organise your models in a PHP MVC application? What is a service class, a mapper or an entity? This talk will look at the components of the model layer and the options you have when creating your models. We\u2019ll look at the different schools of thought in this area and compare and contrast their strengths and weaknesses with an eye to flexibility and testability.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34083", + "speakers": [46440], + "categories": [ + + "PHP" + + ] + }, + + { + "serial": 34087, + "name": "Incorporating Your Passions into Open Source Hardware ", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 13:40:00", + "time_stop": "2014-07-23 14:20:00", + "venue_serial": 1451, + "description": "It doesn't matter if your passion boating, fashion, kids, carpentry, architecture, race cars or rockets. Building OS hardware can be incorporated into all of these things. We will learn how to bring what you learn in your software day job into your weekend fun time. The result will be better at what you love and making work more fun. ", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34087", + "speakers": [133377], + "categories": [ + + "Open Hardware" + + ] + }, + + { + "serial": 34093, + "name": "Tsuru: Open Source Cloud Application Platform", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 17:00:00", + "time_stop": "2014-07-22 17:40:00", + "venue_serial": 1459, + "description": "Tsuru is an open source, component oriented PaaS. It allows developers to focus on writing, testing and deploying applications in an easier way, without worrying how they get deployed in a server. Its key features include easy extensibility, a fully open source stack and graceful handling of failures. This talk aims to introduce Tsuru and its components, showing how they work together.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34093", + "speakers": [151991], + "categories": [ + + "Cloud" + + ] + }, + + { + "serial": 34094, + "name": "Tracing and Profiling Java (and Native) Applications in Production", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 14:30:00", + "time_stop": "2014-07-22 15:10:00", + "venue_serial": 1456, + "description": "This talk describes the implementation and use of a full stack, low overhead tracing and profiling tool based on the Linux kernel profiler (perf) and extensions to the OpenJDK Hotspot JVM, that we've built at Twitter to help understand the behavior of the kernel, native and managed applications in production.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34094", + "speakers": [172240], + "categories": [ + + "Java & JVM" + + ] + }, + + { + "serial": 34095, + "name": "Get Started Developing with Scala", + "event_type": "tutorial", + + "time_start": "2014-07-20 13:30:00", + "time_stop": "2014-07-20 17:00:00", + "venue_serial": 1449, + "description": "Scala powers some of the biggest companies in the world, including Twitter, Intel, and LinkedIn. Come learn what led them to choose this powerful JVM language and try it out yourself. You\u2019ll get a hands-on intro to Scala and functional programming concepts by building your own performant REST API. No FP experience needed--if you can build apps in Java, Python or Ruby you\u2019ll do great in this class.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34095", + "speakers": [171226,150440], + "categories": [ + + "Computational Thinking", + + "Java & JVM" + + ] + }, + + { + "serial": 34103, + "name": "Reactive All The Way Down", + "event_type": "tutorial", + + "time_start": "2014-07-21 09:00:00", + "time_stop": "2014-07-21 12:30:00", + "venue_serial": 1471, + "description": "In this tutorial you will build a Reactive application with Play Framework, Scala, WebSockets, and AngularJS. We will get started with a template app in Typesafe Activator. Then we will add a Reactive RESTful JSON service and a WebSocket in Scala. We will then build the UI with AngularJS.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34103", + "speakers": [1158], + "categories": [ + + "Java & JVM", + + "JavaScript - HTML5 - Web" + + ] + }, + + { + "serial": 34104, + "name": "Building Modern Web Apps with Play Framework and Scala", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 14:30:00", + "time_stop": "2014-07-23 15:10:00", + "venue_serial": 1456, + "description": "Play Framework is the High Velocity Web Framework For Java and Scala. It is lightweight, stateless, RESTful, and developer friendly. This is an introduction to building web applications with Play. You will learn about: routing, Scala controllers & templates, database access, asset compilation for LESS & CoffeeScript, and JSON services.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34104", + "speakers": [1158], + "categories": [ + + "Java & JVM" + + ] + }, + + { + "serial": 34107, + "name": "Full Monty: Intro to Python Metaprogramming", + "event_type": "tutorial", + + "time_start": "2014-07-21 13:30:00", + "time_stop": "2014-07-21 17:00:00", + "venue_serial": 1451, + "description": "Metaprograming in Python is fun and profitable thanks to its rich Data Model \u2013 APIs that let you handle functions, modules and even classes as objects that you can create, inspect and modify at runtime. The Data Model also enables your own objects to support infix operators, become iterable and emulate collections. This workshop shows how, through a diverse selection of examples and exercises.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34107", + "speakers": [150170], + "categories": [ + + "Python" + + ] + }, + + { + "serial": 34108, + "name": "Idiomatic APIs with the Python Data Model", + "event_type": "40-minute conference session", + + "time_start": "2014-07-24 10:00:00", + "time_stop": "2014-07-24 10:40:00", + "venue_serial": 1465, + "description": "The key to writing Pythonic classes, APIs and frameworks is leveraging the Python Data Model: a set of interfaces which, when implemented in your classes, enables them to leverage fundamental language features such as iteration, context managers, infix operators, attribute access control etc. This talk shows how, through a diverse selection of examples.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34108", + "speakers": [150170], + "categories": [ + + "Python" + + ] + }, + + { + "serial": 34109, + "name": "Data.gov: Open Government as Open Source", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 13:40:00", + "time_stop": "2014-07-23 14:20:00", + "venue_serial": 1454, + "description": "The underpinnings of open government are transparency and citizen participation. In re-imagining a new Data.gov (the open data, open government initiative for the White House), this was taken to heart. This system was created using open source and with comments, issues, and commits worked with the public all along the way.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34109", + "speakers": [62631], + "categories": [ + + "Databases & Datastores" + + ] + }, + + { + "serial": 34112, + "name": "Python: Encapsulation with Descriptors", + "event_type": "40-minute conference session", + + "time_start": "2014-07-24 11:00:00", + "time_stop": "2014-07-24 11:40:00", + "venue_serial": 1465, + "description": "Python has no private fields, but the property decorator lets you replace public attributes with getters and setters without breaking client code. And the descriptor mechanism, used in Django for model field declarations, enables wide reuse of getter/setter logic via composition instead of inheritance. This talk explains how properties and descriptors work by refactoring a practical example.\r\n", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34112", + "speakers": [150170], + "categories": [ + + "Python" + + ] + }, + + { + "serial": 34116, + "name": "Choosing a caching HTTP Proxy", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 16:10:00", + "time_stop": "2014-07-23 16:50:00", + "venue_serial": 1450, + "description": "With Web performance and scalability becoming more and more important,\r\nchoosing advanced HTTP intermediaries is a vital skill. This presentation\r\nwill give the audience a thorough walkthrough of the most popular and\r\nadvanced solutions available today. The audience will gain a solid\r\nbackground to be able to make the right choices when it comes to HTTP\r\nintermediaries and proxy caches.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34116", + "speakers": [63576], + "categories": [ + + "Operations & System Administration" + + ] + }, + + { + "serial": 34117, + "name": "Git for Grown-ups", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 10:40:00", + "time_stop": "2014-07-22 11:20:00", + "venue_serial": 1452, + "description": "You are a clever and talented person. You have architected a system that even my cat could use; your spreadsheet-fu is legendary. Your peers adore you. Your clients love you. But, until now, you haven\u2019t *&^#^! been able to make Git work. It makes you angry inside that you have to ask for help, again, to figure out that *&^#^! command to upload your work. It's not you. It's Git. Promise.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34117", + "speakers": [4146], + "categories": [ + + "Tools & Techniques" + + ] + }, + + { + "serial": 34125, + "name": "How Open Source Powers Facebook on Android", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 11:30:00", + "time_stop": "2014-07-23 12:10:00", + "venue_serial": 1454, + "description": "The Facebook Android app is large and developed by hundreds of software engineers. This talk will cover how OSS helps us build Facebook for Android - and how we are good OSS citizens - by looking at the full life cycle of a release, from how we organize our git repo, do code reviews in Phabricator, through building using Buck, to how we've improved the quality of our releases using Selendroid.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34125", + "speakers": [172656], + "categories": [ + + "Tools & Techniques" + + ] + }, + + { + "serial": 34135, + "name": "Design for Life", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 13:40:00", + "time_stop": "2014-07-22 14:20:00", + "venue_serial": 1462, + "description": "It's time to design products to capture life and physiologic signs invisibly\u2026 usually through non-invasive sensors that don't require a single drop of blood, but just whiffs and sniffs. And when it is visible, it must be designed to feel wonderful.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34135", + "speakers": [44753], + "categories": [ + + "User Experience" + + ] + }, + + { + "serial": 34136, + "name": "Shipping Applications to Production in Containers with Docker", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 13:40:00", + "time_stop": "2014-07-22 14:20:00", + "venue_serial": 1449, + "description": "Since its first release in early 2013, Docker has been deployed successfully to implement continuous integration and testing environments, where the very fast lifecycle of containers gives them an edge over virtual machines. We will see how to extend the workflow from development to testing and all the way to production. We'll also address challenges like reliability and scaling with Docker.\r\n", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34136", + "speakers": [151611], + "categories": [ + + "Operations & System Administration" + + ] + }, + + { + "serial": 34137, + "name": "Is it Safe to Run Applications in Linux Containers?", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 11:30:00", + "time_stop": "2014-07-22 12:10:00", + "venue_serial": 1475, + "description": "Linux Containers (or LXC) is now a popular choice for development and testing environments. As more and more people use them in production deployments, they face a common question: are Linux Containers secure enough? It is often claimed that containers have weaker isolation than virtual machines. We will explore whether this is true, if it matters, and what can be done about it.\r\n", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34137", + "speakers": [151611], + "categories": [ + + "Operations & System Administration" + + ] + }, + + { + "serial": 34145, + "name": "DevOps for University Students", + "event_type": "40-minute conference session", + + "time_start": "2014-07-24 10:00:00", + "time_stop": "2014-07-24 10:40:00", + "venue_serial": 1462, + "description": "University students rarely get a chance to fully embrace the Devops or FOSS development culture while in school. This year, we\u2019ve started a program called Devops Bootcamp, which is a hands-on, informal workshop open to any student at OSU. Devops Bootcamp immerses college students in the basics of Linux, Linux system administration and FOSS development practices. ", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34145", + "speakers": [29558,123516], + "categories": [ + + "Operations & System Administration" + + ] + }, + + { + "serial": 34148, + "name": "Protocols for the Internet of Things", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 11:30:00", + "time_stop": "2014-07-22 12:10:00", + "venue_serial": 1465, + "description": "A large part of the internet of things will be made up of small constrained devices. The IETF is standardising protocols which are memory, energy and network efficient. Come and get an overview of these and of some Open Source implementations. See devices including Arduinos and Raspberry Pis with several sensors talking with one another and be inspired to build/connect your own devices.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34148", + "speakers": [172751], + "categories": [ + + "Open Hardware" + + ] + }, + + { + "serial": 34156, + "name": "REST: It's not just for servers", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 10:40:00", + "time_stop": "2014-07-22 11:20:00", + "venue_serial": 1462, + "description": "Have you ever written or used an API wrapper for a webservice? REST is a client-server architecture model and building the server is only half of the challenge. This talk will walk through some of the challenges of building a REST client, describe some best practices and some patterns to avoid, and discuss how we can all work to build better APIs for an open web.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34156", + "speakers": [151665], + "categories": [ + + "Tools & Techniques" + + ] + }, + + { + "serial": 34159, + "name": "Big Data Pipeline and Analytics Platform Using NetflixOSS and Other Open Source Libraries", + "event_type": "40-minute conference session", + + "time_start": "2014-07-24 11:00:00", + "time_stop": "2014-07-24 11:40:00", + "venue_serial": 1452, + "description": "This session presents the data platform used at Netflix for event collection, aggregation, and analysis. The platform helps Netflix process and analyze billions of events every day. Attendees will learn how to assemble their own large-scale data pipeline/analytics platform using open source software from NetflixOSS and others, such as Kafka, ElasticSearch, Druid from Metamarkets, and Hive. ", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34159", + "speakers": [172661,171450], + "categories": [ + + "Computational Thinking" + + ] + }, + + { + "serial": 34162, + "name": "Application Deployment and Auto-scaling On OpenStack using Heat ", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 11:30:00", + "time_stop": "2014-07-23 12:10:00", + "venue_serial": 1466, + "description": "This session will show how devops can use Heat to orchestrate the deployment &scaling of complex applications on top of OpenStack. Starting with a walk-thru of OpenStack example deployment Heat Templates for OpenShift Origin (available in openstack github repository) and enhance them to provide additional functions such as positioning alarms, responding to alarms, adding instances, &autoscaling. ", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34162", + "speakers": [33987], + "categories": [ + + "Operations & System Administration" + + ] + }, + + { + "serial": 34164, + "name": "Timeseries Data Superpowers: Intuitive Understanding of FIR Filtering and Fourier Transforms", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 17:00:00", + "time_stop": "2014-07-22 17:40:00", + "venue_serial": 1452, + "description": "In this talk we'll explore the Fourier transform and FIR filters in an intuitive way to make it accessible. You'll come out with the ability to look at your time-series data in a new way and explore new uses for otherwise useless data.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34164", + "speakers": [172201], + "categories": [ + + "Computational Thinking" + + ] + }, + + { + "serial": 34176, + "name": "Functional Vaadin", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 16:10:00", + "time_stop": "2014-07-22 16:50:00", + "venue_serial": 1466, + "description": "Exploring how the functional language features of Java 8 and Scala combine with Vaadin to allow you to write clearer UI code.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34176", + "speakers": [120866], + "categories": [ + + "Java & JVM" + + ] + }, + + { + "serial": 34187, + "name": "Continuous Delivery", + "event_type": "tutorial", + + "time_start": "2014-07-21 13:30:00", + "time_stop": "2014-07-21 17:00:00", + "venue_serial": 1450, + "description": "Getting software released to users is often a painful, risky, and time-consuming process. This tutorial sets out the principles and technical practices that enable rapid, incremental delivery of high quality and valuable new functionality to users.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34187", + "speakers": [2650], + "categories": [ + + "Business", + + "Tools & Techniques" + + ] + }, + + { + "serial": 34188, + "name": "Community War Stories : Squaring the Circle between Business and Community", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 17:00:00", + "time_stop": "2014-07-22 17:40:00", + "venue_serial": 1464, + "description": "In this talk we will look at some of the basic dynamics playing out in open source communities and introduce some mental models explaining them. We will look at the Open Source Flywheel (inspired by Walton's Productivity Loop and the Bezos Flywheel) and the Open Source Community Funnel (inspired by Sales Funnels) to explain them.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34188", + "speakers": [62981], + "categories": [ + + "Business", + + "Community" + + ] + }, + + { + "serial": 34192, + "name": "Functional Thinking", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 11:30:00", + "time_stop": "2014-07-22 12:10:00", + "venue_serial": 1452, + "description": "Learning the syntax of a new language is easy, but learning to think under a different paradigm is hard. This session helps you transition from a Java writing imperative programmer to a functional programmer, using Java, Clojure and Scala for examples.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34192", + "speakers": [2650], + "categories": [ + + "Computational Thinking" + + ] + }, + + { + "serial": 34194, + "name": "The Curious Clojureist", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 13:40:00", + "time_stop": "2014-07-23 14:20:00", + "venue_serial": 1456, + "description": "Clojure is the most interesting new language on the horizon, but many developers suffer from the Blub Paradox when they see the Lisp syntax. This talk introduces Clojure to developers who haven\u2019t been exposed to it yet, focusing on the things that truly set it apart from other languages.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34194", + "speakers": [2650], + "categories": [ + + "Emerging Languages" + + ] + }, + + { + "serial": 34198, + "name": "Syncing Async", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 16:10:00", + "time_stop": "2014-07-22 16:50:00", + "venue_serial": 1450, + "description": "'Callback hell' has very little to do with callbacks. Are promises delivering on the promise of better async flow control, or muddying the waters? Generating general generators, WAT? Let's wade through the world of async in JS to find order in the chaos.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34198", + "speakers": [74368], + "categories": [ + + "JavaScript - HTML5 - Web" + + ] + }, + + { + "serial": 34201, + "name": "Map, Flatmap and Reduce are Your New Best Friends: Simpler Collections, Concurrency, and Big Data", + "event_type": "40-minute conference session", + + "time_start": "2014-07-24 11:50:00", + "time_stop": "2014-07-24 12:30:00", + "venue_serial": 1452, + "description": "Higher-order functions such as map(), flatmap(), filter() and reduce() have their origins in mathematics and ancient functional programming languages such as Lisp. But today they have become mainstream and are available in languages such as JavaScript, Scala and Java 8. Learn how to they can be used to simplify code in a variety of domains including collection processing, concurrency and big data.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34201", + "speakers": [11886], + "categories": [ + + "Computational Thinking" + + ] + }, + + { + "serial": 34203, + "name": "Creating Awesome Web APIs is a Breeze", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 13:40:00", + "time_stop": "2014-07-23 14:20:00", + "venue_serial": 1458, + "description": "Web APIs are increasingly important but their creation is still more an art than a science. This talk will demonstrate how Web APIs consumable by generic clients can be implemented in considerably less time. It will also give a brief introduction to JSON-LD and Hydra.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34203", + "speakers": [172864], + "categories": [ + + "PHP" + + ] + }, + + { + "serial": 34208, + "name": "Git for Teams of One or More", + "event_type": "tutorial", + + "time_start": "2014-07-21 09:00:00", + "time_stop": "2014-07-21 12:30:00", + "venue_serial": 1452, + "description": "You've dabbled a little in version control using Git. You can follow along with the various tutorials you've found online. But now you've been asked to implement a work flow strategy and you're not really sure how (or where) to start. You have a lot of choices, we'll help you pick the right one for your project.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34208", + "speakers": [4146], + "categories": [ + + "Community", + + "Tools & Techniques" + + ] + }, + + { + "serial": 34212, + "name": "Telling Technology Stories with IPython Notebook", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 13:40:00", + "time_stop": "2014-07-23 14:20:00", + "venue_serial": 1465, + "description": "As technologists, sometimes it\u2019s as important to be able to share information with others as to be able to actually build something. IPython notebook is a powerful tool to both experiment with code (and data) and share the results with others, technical and non-technical alike. This session introduces the notebook and gives examples and techniques for using it effectively.\r\n", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34212", + "speakers": [59574], + "categories": [ + + "Python" + + ] + }, + + { + "serial": 34224, + "name": "Accessibility and Security - For Everyone. Gotchas to Avoid.", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 13:40:00", + "time_stop": "2014-07-23 14:20:00", + "venue_serial": 1462, + "description": "Did you hear about the double arm amputee who was refused service at a bank because he could not provide a thumbprint? Or the online petition to increase services for blind folks, that they couldn\u2019t sign because of CAPTCHA? These are examples of security practices that cause barriers to people with disabilities. Security can create barriers, but it doesn\u2019t have to reduce accessibility!", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34224", + "speakers": [172899], + "categories": [ + + "Security", + + "User Experience" + + ] + }, + + { + "serial": 34232, + "name": "AngularJS Tutorial", + "event_type": "tutorial", + + "time_start": "2014-07-20 13:30:00", + "time_stop": "2014-07-20 17:00:00", + "venue_serial": 1452, + "description": "AngularJS is relatively new, meteorically popular, and functionally powerful. However, a lot of AngularJS\u2019s workings are very opaque and confusing. In this tutorial, my goal is to walk you through building a basic app, and introduce you to concepts, patterns, and ways of thinking that will allow you to comfortably dive further into using AngularJS for future projects.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34232", + "speakers": [171372], + "categories": [ + + "JavaScript - HTML5 - Web", + + "Tools & Techniques" + + ] + }, + + { + "serial": 34236, + "name": "JavaScript and Internet Controlled Hardware Prototyping", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 17:00:00", + "time_stop": "2014-07-22 17:40:00", + "venue_serial": 1451, + "description": "In this session we'll be exploring how to build rapid hardware prototypes using wifi and bluetooth low energy enabled Arduino boards, all controlled through JavaScript and API data, to allow for innovative, web enabled, software to hardware development techniques.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34236", + "speakers": [74565], + "categories": [ + + "Open Hardware" + + ] + }, + + { + "serial": 34238, + "name": "No More Whiteboard: Hiring Processes that Don't Waste Time", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 16:10:00", + "time_stop": "2014-07-22 16:50:00", + "venue_serial": 1457, + "description": "Learn how someone writes code by writing code with them. Using katas and pair programming on actual code allows you to get an honest look at the candidate's thought process and capabilities, while exposing them to your team's culture and key players. Help your evaluators be focused on what kind of people you actually want on your project by creating key prompts for them to check.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34238", + "speakers": [155107], + "categories": [ + + "Business" + + ] + }, + + { + "serial": 34243, + "name": "Monitoring Your Drone Project with Elasticsearch", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 11:30:00", + "time_stop": "2014-07-22 12:10:00", + "venue_serial": 1466, + "description": "Elasticsearch is an open-source document store known for enabling search and real-time analytics on large data sets. In this presentation we will walk through the development of an application that monitors the Parrot AR.Drone. This application will collect metrics from the drone and then transform them to JSON for storage and real-time analysis in Elasticsearch.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34243", + "speakers": [172929,142336], + "categories": [ + + "Databases & Datastores" + + ] + }, + + { + "serial": 34244, + "name": "Grow Your Community with Events", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 14:30:00", + "time_stop": "2014-07-22 15:10:00", + "venue_serial": 1464, + "description": "Events are an attractive way to grow a community, but how do you choose which types of events work best for your users? We'll cover a variety of community event types, as well as building and scaling user groups remotely, simultaneous in-person and online participation, and getting other departments at your company invested in what you're planning.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34244", + "speakers": [142767], + "categories": [ + + "Community" + + ] + }, + + { + "serial": 34247, + "name": "Community Management Training", + "event_type": "tutorial", + + "time_start": "2014-07-21 09:00:00", + "time_stop": "2014-07-21 17:00:00", + "venue_serial": 1454, + "description": "This full day of community management training is delivered by Jono Bacon, author of The Art of Community, and covers a wide range of topics for community managers and leaders to build fun, productive, and rewarding communities.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34247", + "speakers": [108813], + "categories": [ + + "Community" + + ] + }, + + { + "serial": 34248, + "name": "Dealing With Disrespect", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 14:30:00", + "time_stop": "2014-07-23 15:10:00", + "venue_serial": 1464, + "description": "In this new presentation from Jono Bacon, author of The Art of Community, founder of the Community Leadership Summit, and Ubuntu Community Manager, he discusses how to process, interpret, and manage rude, disrespectful, and non-constructive feedback in communities so the constructive criticism gets through but the hate doesn't.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34248", + "speakers": [108813], + "categories": [ + + "Community" + + ] + }, + + { + "serial": 34252, + "name": "Zero to Cloud with @NetflixOSS", + "event_type": "tutorial", + + "time_start": "2014-07-21 09:00:00", + "time_stop": "2014-07-21 12:30:00", + "venue_serial": 1449, + "description": "We want you to leave OSCON with a working cloud account, including supporting infrastructure that Amazon DOESN\u2019T provide but that will make your cloud life way more manageable! Once your account is bootstrapped with Asgard and Aminator, we\u2019ll be baking some of the myriad of @NetflixOSS apps. This tutorial will be meaningful for anyone getting started with or currently using AWS. ", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34252", + "speakers": [172610], + "categories": [ + + "Cloud", + + "Operations & System Administration" + + ] + }, + + { + "serial": 34254, + "name": "Hands-On Data Analysis with Python", + "event_type": "tutorial", + + "time_start": "2014-07-21 13:30:00", + "time_stop": "2014-07-21 17:00:00", + "venue_serial": 1449, + "description": "Python is quickly becoming the go-to language for data analysis. However, it can be difficult to figure out which tools are good to use. In this workshop, we\u2019ll work through in-depth examples of tools for data wrangling, machine learning, and data visualization. I\u2019ll show you how to work through a data analysis workflow, and how to deal with different kinds of data. ", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34254", + "speakers": [170237], + "categories": [ + + "Python", + + "Tools & Techniques" + + ] + }, + + { + "serial": 34255, + "name": "Analyzing Data with Python", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 14:30:00", + "time_stop": "2014-07-23 15:10:00", + "venue_serial": 1459, + "description": "Python is quickly becoming the go-to language for data analysis, but it can be difficult to figure out which tools to use. In this presentation, I\u2019ll give a bird\u2019s eye overview of some of the best tools for data analysis and how you can apply them to your own workflow. I\u2019ll introduce you to how you can use Pandas, Scikit-Learn, NLTK, MRJob, and matplotlib for data analysis.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34255", + "speakers": [170237], + "categories": [ + + "Python" + + ] + }, + + { + "serial": 34258, + "name": "Crash Course in Tech Management", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 14:30:00", + "time_stop": "2014-07-23 15:10:00", + "venue_serial": 1452, + "description": "'Programmer' and 'Manager' are two different titles for a reason: they're two different jobs and skill sets. If you have managerial aspirations (or have had them foisted upon you), come to this session to learn some of the tricks of the managerial trade.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34258", + "speakers": [131404], + "categories": [ + + "Business", + + "Community" + + ] + }, + + { + "serial": 34260, + "name": "How We Went Remote", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 17:00:00", + "time_stop": "2014-07-22 17:40:00", + "venue_serial": 1448, + "description": "Hiring remote workers is great for filling those holes on the team...but if you don't have the correct infrastructure in place you're just setting yourself--and your team members--up for a world of hurt. This session will detail how our engineering department went remote and thrived because of it.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34260", + "speakers": [131404], + "categories": [ + + "Main Stage" + + ] + }, + + { + "serial": 34267, + "name": "A Quick Introduction to System Tools Programming with Go", + "event_type": "tutorial", + + "time_start": "2014-07-21 13:30:00", + "time_stop": "2014-07-21 17:00:00", + "venue_serial": 1457, + "description": "This tutorial provides an introduction to Go with a focus on using it for everyday sysadmins tooling. A example of working from iostat is used to show a practical approach to learning the language.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34267", + "speakers": [172994], + "categories": [ + + "Operations & System Administration", + + "Tools & Techniques" + + ] + }, + + { + "serial": 34273, + "name": "SWI-Prolog for the Real World", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 17:00:00", + "time_stop": "2014-07-22 17:40:00", + "venue_serial": 1454, + "description": "Savvy functional programmers are discovering logic programming, and SWI-Prolog's vast niftiness. Come watch Annie run her debugger in reverse, directly execute syntax specifications, and lets the computer figure out it's own darn execution strategy. Be amazed as Annie constrains variables and shrinks her APIs. Ooh and Aah at the many libraries, nifty web framework and clean environment.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34273", + "speakers": [172986], + "categories": [ + + "Emerging Languages" + + ] + }, + + { + "serial": 34274, + "name": "Arduino + Furby Broken Build Notification - Oh, You'll Want to Fix it Quick!", + "event_type": "40-minute conference session", + + "time_start": "2014-07-24 11:50:00", + "time_stop": "2014-07-24 12:30:00", + "venue_serial": 1448, + "description": "Furby's are back and more annoying than ever. Forget about a traffic light flashing or an email. When that Furby starts jabbering, you'll do ANYTHING to fix that build quickly. This talk will connect an Arduino board with Jenkins continuous integration framework and out to the Furby to let it annoy your development team, rather than you!", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34274", + "speakers": [99280], + "categories": [ + + "Main Stage", + + "Open Hardware" + + ] + }, + + { + "serial": 34275, + "name": "PHP Development for Google Glass using Phass", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 16:10:00", + "time_stop": "2014-07-22 16:50:00", + "venue_serial": 1458, + "description": "Phass is a ZF2-based framework (implemented as a Module) designed to make building Google GlassWare applications in PHP as easy as possible. In this talk we\u2019ll show you how Phass works, complete with a live Google Glass demo of an application in action! \r\n", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34275", + "speakers": [6894], + "categories": [ + + "Mobile Platforms", + + "PHP" + + ] + }, + + { + "serial": 34280, + "name": "Portable Logic/Native UI", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 16:10:00", + "time_stop": "2014-07-23 16:50:00", + "venue_serial": 1449, + "description": "This talk shows how to design mobile apps whose complex internal logic runs on many mobile operating systems, but with native UI on those platforms. This ensures that the best possible user experience on each platform.\r\n\r\nThis talk focuses on design patterns for structuring your app for dealing with a mix of cross\u2013platform code and platform-specific UI code.\r\n", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34280", + "speakers": [109468], + "categories": [ + + "Mobile Platforms" + + ] + }, + + { + "serial": 34281, + "name": "Erlang, LFE, Joxa and Elixir: Established and Emerging Languages in the Erlang Ecosystem", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 16:10:00", + "time_stop": "2014-07-23 16:50:00", + "venue_serial": 1456, + "description": "Erlang is a concurrent programming language with a small, active community and many high-uptime, critical deployments. It's syntax is a bit odd, being inspired by Prolog. Other languages--Elixir, notably--have begun to reap the benefits of Erlang's VM, BEAM, modifying syntax and semantics. This talk will provide a view of the BEAM languages, their history, motivations and benefits. ", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34281", + "speakers": [172990], + "categories": [ + + "Emerging Languages" + + ] + }, + + { + "serial": 34283, + "name": "Obey the Testing Goat! TDD for Web Development with Python", + "event_type": "tutorial", + + "time_start": "2014-07-21 09:00:00", + "time_stop": "2014-07-21 12:30:00", + "venue_serial": 1450, + "description": "Learn Test-Driven-Development and how it applies to web applications by building a simple web app from scratch using Python and Django. We'll cover unit testing, Django models, views and templates, as well as using Selenium to open up a real web browser for functional tests.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34283", + "speakers": [180056], + "categories": [ + + "JavaScript - HTML5 - Web", + + "Python", + + "Tools & Techniques" + + ] + }, + + { + "serial": 34285, + "name": "Idioms for Building Distributed Fault-tolerant Applications with Elixir", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 17:00:00", + "time_stop": "2014-07-23 17:40:00", + "venue_serial": 1454, + "description": "This talk will introduce developers to the Elixir programming language and the Erlang VM and show how they introduce a completely new vocabulary which shapes how developers design and build distributed, fault-tolerant applications. This talk also discusses Elixir goals and what it brings to the Erlang VM.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34285", + "speakers": [76735], + "categories": [ + + "Emerging Languages" + + ] + }, + + { + "serial": 34289, + "name": "Lessons from Girl Develop It: Getting More Women Involved in Open Source", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 10:40:00", + "time_stop": "2014-07-23 11:20:00", + "venue_serial": 1462, + "description": "Women make up only 11% of open source developers. As Girl Develop It leaders in Philadelphia, we\u2019ve learned about what works to get women involved in open source projects at the grassroots level. We\u2019ll share our experience encouraging women to make open source contributions, using concrete methods that can be replicated in your own communities.\r\n", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34289", + "speakers": [169992,173025], + "categories": [ + + "Community", + + "Education" + + ] + }, + + { + "serial": 34293, + "name": "Performing High-Performance Parallel Data Fetching in PHP", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 13:40:00", + "time_stop": "2014-07-22 14:20:00", + "venue_serial": 1458, + "description": "While Node.js and other asynchronous technologies have been receiving quite a bit of attention, in this session, we'll discuss a technology stack we've written which permits PHP developers to perform complex database, cache, and API requests asynchronously, in parallel, resulting in excellent response times. This can be done natively in PHP with NO gearman and NO custom PECL extensions.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34293", + "speakers": [86090], + "categories": [ + + "PHP" + + ] + }, + + { + "serial": 34299, + "name": "Painless Data Storage with MongoDB and Go", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 13:40:00", + "time_stop": "2014-07-22 14:20:00", + "venue_serial": 1475, + "description": "Find out why some people claim Go and MongoDB are a 'pair made in heaven' and 'the best database driver they've ever used' in this talk by Gustavo Niemeyer, the author of the mgo driver, and Steve Francia, the drivers team lead at MongoDB Inc.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34299", + "speakers": [142995], + "categories": [ + + "Databases & Datastores" + + ] + }, + + { + "serial": 34314, + "name": "Developing Micro-services with Java and Spring", + "event_type": "40-minute conference session", + + "time_start": "2014-07-24 10:00:00", + "time_stop": "2014-07-24 10:40:00", + "venue_serial": 1458, + "description": "With plenty of live code and demos, this talk will show you how incredibly easy it is to write Java micro-services with modern Spring. We will walk though the process of creating a simple REST service, discuss deployment options and talk about how self-contained, stand-alone applications work in production.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34314", + "speakers": [171565], + "categories": [ + + "Java & JVM", + + "Tools & Techniques" + + ] + }, + + { + "serial": 34327, + "name": "Embedding Node.js into a High-performance Network Datapath", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 11:30:00", + "time_stop": "2014-07-23 12:10:00", + "venue_serial": 1450, + "description": "This talk will discuss why LineRate, a high-performance Layer 7 app proxy for developers, chose to embed Node.js as the programming language for the data path. The talk will focus on the challenges of building an embeddable\r\nNode.js and conclude with how the open source Node.js code base could evolve to better support embeddable use cases.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34327", + "speakers": [173056], + "categories": [ + + "JavaScript - HTML5 - Web" + + ] + }, + + { + "serial": 34329, + "name": "Static Web Rising", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 11:30:00", + "time_stop": "2014-07-22 12:10:00", + "venue_serial": 1450, + "description": "A combination of open standards, open source projects, and evolving browser technologies have made static web apps an increasingly appealing target even for complex applications. Learn how you can \u201cgo static\u201d and why you might want to do so.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34329", + "speakers": [2593], + "categories": [ + + "JavaScript - HTML5 - Web" + + ] + }, + + { + "serial": 34332, + "name": "Global Scaling at the New York Times using RabbitMQ ", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 10:40:00", + "time_stop": "2014-07-22 11:20:00", + "venue_serial": 1459, + "description": "Learn about the 'nyt\u2a0da\u0431rik' platform which sits behind The New York Times website. Learn how it scales across many continents and AWS availability zones using RabbitMQ as the backbone of communication for exchanging messages in near real-time. nyt\u2a0da\u0431rik is built on open-source and most of it will be open sourced.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34332", + "speakers": [112672,172658], + "categories": [ + + "Cloud" + + ] + }, + + { + "serial": 34336, + "name": "One Hat, Two Hats - How to Handle Open Source and Work", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 11:30:00", + "time_stop": "2014-07-22 12:10:00", + "venue_serial": 1464, + "description": "Working in open source is living the dream, right? What happens when that dream clashes with the real world deliveries associated with those paying you to work in the open. Speaking from 3 years of experience working on a new tools project and through interviews with others in the industry, this talk should help show you through the ups and downs.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34336", + "speakers": [39928], + "categories": [ + + "Community" + + ] + }, + + { + "serial": 34356, + "name": "Making Money at Open Source without Losing Your Soul - A Practical Guide", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 13:40:00", + "time_stop": "2014-07-22 14:20:00", + "venue_serial": 1457, + "description": "One of the tenets of Open Source is \u201cFree as in beer\u201d but there is still a viable commercial model. There are many successful open source companies that, despite the fact they give away their product, still manage to make money. A lot of money. How can this be possible? Let\u2019s explore some of the time tested strategies without compromising your core values.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34356", + "speakers": [152376], + "categories": [ + + "Business" + + ] + }, + + { + "serial": 34362, + "name": "Money for Nothing and Your Downloads for Free", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 13:40:00", + "time_stop": "2014-07-23 14:20:00", + "venue_serial": 1464, + "description": "Not all projects benefit from a deep-pocketed corporate sponsor to fund their community activities. There are bills that need paying for server hosting, download bandwidth and the like, and maybe for trademark registration and other legal costs for larger projects. What's the best way to fund your project? These speakers know!", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34362", + "speakers": [29591,28902,173340], + "categories": [ + + "Community" + + ] + }, + + { + "serial": 34371, + "name": "A Recovering Java Developer Learns to Go", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 13:40:00", + "time_stop": "2014-07-22 14:20:00", + "venue_serial": 1461, + "description": "The Go programming language has emerged as a favorite tool of DevOps and cloud practitioners alike. In many ways, Go is more famous for what it doesn't include than what it does, and co-author Rob Pike has said that Go represents a 'less is more' approach to language design. This talk will introduce Go and its distinctives to Java developers looking to add Go to their toolkits.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34371", + "speakers": [173088], + "categories": [ + + "Emerging Languages", + + "Java & JVM" + + ] + }, + + { + "serial": 34374, + "name": "CSS: Declarative Nonsense Made Sensible", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 13:40:00", + "time_stop": "2014-07-22 14:20:00", + "venue_serial": 1450, + "description": "Unless you're working full time as a front-end engineer, odds are that CSS frustrates you from time to time. This session offers advice on how to see past the obtuse corners of CSS, backed by fifteen years' hands-on experience.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34374", + "speakers": [173093], + "categories": [ + + "JavaScript - HTML5 - Web" + + ] + }, + + { + "serial": 34377, + "name": "NASA Open Source Projects for Science and Exploration", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 11:30:00", + "time_stop": "2014-07-23 12:10:00", + "venue_serial": 1459, + "description": "The Jet Propulsion Laboratory has been busy lately open sourcing its software, such as mobile apps for viewing the latest Mars images, communicating between robots, and sharing scientific analysis software in using app containers and cloud computing. Come and listen to stories and anecdotes about working on NASA projects and our journey into open source.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34377", + "speakers": [173111], + "categories": [ + + "Cloud" + + ] + }, + + { + "serial": 34378, + "name": "WeIO Platform for Internet of Things", + "event_type": "40-minute conference session", + + "time_start": "2014-07-24 11:00:00", + "time_stop": "2014-07-24 11:40:00", + "venue_serial": 1451, + "description": "WeIO is an innovative Open Source hardware and software platform for Internet of Things that allows the creation of wirelessly connected objects using popular web languages such as HTML5 or Python.\r\nAll further details can be found on project's web-site: http://we-io.net.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34378", + "speakers": [173105], + "categories": [ + + "Open Hardware" + + ] + }, + + { + "serial": 34383, + "name": "Getting Started with Scalding, Twitter's High-level Scala API for Hadoop MapReduce", + "event_type": "tutorial", + + "time_start": "2014-07-21 13:30:00", + "time_stop": "2014-07-21 17:00:00", + "venue_serial": 1471, + "description": "Scalding is an open source framework developed at Twitter that provides a high level abstraction over Hadoop MapReduce, letting you concisely specify complex data analysis pipelines using simple Scala operations like map, filter, join, group, and sum. This introductory tutorial does not require experience with either Hadoop or Scala.\r\n", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34383", + "speakers": [90628], + "categories": [ + + "Databases & Datastores", + + "Java & JVM" + + ] + }, + + { + "serial": 34394, + "name": "Just My Type", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 10:40:00", + "time_stop": "2014-07-22 11:20:00", + "venue_serial": 1465, + "description": "We perl programmers aren't known as fans of formal types. Types are for straitjacketed languages like Java.\r\n\r\nBut... the Moose revolution's changing all that. Types are a great way of encapsulating the messy business of data conversion and parameter validation, and can help you think more clearly about what's going on in complex code.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34394", + "speakers": [75349], + "categories": [ + + "Perl" + + ] + }, + + { + "serial": 34395, + "name": "Getting Started with Go", + "event_type": "tutorial", + + "time_start": "2014-07-21 09:00:00", + "time_stop": "2014-07-21 12:30:00", + "venue_serial": 1475, + "description": "This tutorial will give developers an introduction and practical experience in building applications with the go language. Go expert Steve Francia will lead the class to build a working go web and cli application together teaching fundamentals, key features and best practices along the way. ", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34395", + "speakers": [142995], + "categories": [ + + "Emerging Languages" + + ] + }, + + { + "serial": 34407, + "name": "Big Data Analysis 0-60 in 90 days", + "event_type": "40-minute conference session", + + "time_start": "2014-07-24 11:00:00", + "time_stop": "2014-07-24 11:40:00", + "venue_serial": 1457, + "description": "Do you know how long could it take to your team start producing value in the Big Data and Machine Learning area? This talk shows a real team experience starting from scratch to a functional Big Data and Machine Learning platform using several open source tools such as Apache Hadoop, Apache Hive and Python frameworks SciPy/Numpy/scikit-learn", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34407", + "speakers": [173146,94695], + "categories": [ + + "Databases & Datastores" + + ] + }, + + { + "serial": 34414, + "name": "Train Spotting with Raspberry Pi and Data Science", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 14:30:00", + "time_stop": "2014-07-23 15:10:00", + "venue_serial": 1451, + "description": "Can computers tell if trains run on time? Using microphones, IP cameras, Arduino and Raspberry Pi we set up sensors to detect commuter trains as they passed by. Together with signal processing in Python, streaming data aggregation with Flume and storing in Hadoop, we\u2019ll show you how you can do this too.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34414", + "speakers": [171621,133624], + "categories": [ + + "Open Hardware" + + ] + }, + + { + "serial": 34422, + "name": "Mesos: Elastically Scalable Operations, Simplified", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 14:30:00", + "time_stop": "2014-07-23 15:10:00", + "venue_serial": 1466, + "description": "Apache Mesos is a cluster manager that provides efficient resource isolation and sharing across distributed applications. Mesos is not only a resource scheduler but also a library for rapidly developing scalable and fault-tolerant distributed systems. This talk will take the audience through the key aspects contributing to the growing adoption of Mesos in companies with large-scale data centers.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34422", + "speakers": [172898,171598], + "categories": [ + + "Cloud", + + "Operations & System Administration" + + ] + }, + + { + "serial": 34430, + "name": "How Does Raleigh Use Open Source?", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 14:30:00", + "time_stop": "2014-07-22 15:10:00", + "venue_serial": 1457, + "description": "Open source, open data, and open access, that's what the City of Raleigh is all about. But how did Raleigh go from open government resolution to an open data portal and a preference for open source software for IT procurement? Come to this session to learn how city government and citizens are working together to create an open source city. ", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34430", + "speakers": [156534,173173], + "categories": [ + + "Business", + + "Community" + + ] + }, + + { + "serial": 34431, + "name": "Neo4j 2.0 Intro Training", + "event_type": "tutorial", + + "time_start": "2014-07-21 13:30:00", + "time_stop": "2014-07-21 17:00:00", + "venue_serial": 1470, + "description": "This training offers the first step in building a good knowledge of graph databases, and covers the core functionality of the open source Neo4j graph database. With a mixture of theory and hands-on practice sessions, you will quickly learn how easy it is to work with a powerful graph database using Cypher as the query language. ", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34431", + "speakers": [159719], + "categories": [ + + "Databases & Datastores" + + ] + }, + + { + "serial": 34432, + "name": "Mesos: An SDK for Distributed Systems Developers", + "event_type": "tutorial", + + "time_start": "2014-07-20 13:30:00", + "time_stop": "2014-07-20 17:00:00", + "venue_serial": 1456, + "description": "The shift to the cloud is old news. Unfortunately, the pain of developing distributed architectures is not. Apache Mesos handles the hard parts of building distributed systems and lets developers focus on what makes their application special. In this workshop, we will illustrate how to write applications on Mesos by walking through the implementation of an example framework.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34432", + "speakers": [172973,171598,172898], + "categories": [ + + "Cloud", + + "Tools & Techniques" + + ] + }, + + { + "serial": 34434, + "name": "Hacking Lessons: Which Micro-Controller Board Do I Use?", + "event_type": "40-minute conference session", + + "time_start": "2014-07-24 10:00:00", + "time_stop": "2014-07-24 10:40:00", + "venue_serial": 1451, + "description": "It's a great time to be a hardware hacker. What started with the Arduino has now evolved to the Raspberry Pi, the BeagleBone Black, the Spark Core, the new Arduino Yun, and a host of other boards. How do you know which one is right for your project? This talk will compare the mainstream boards, how they are applied and help you decide which one best fits your needs. ", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34434", + "speakers": [77350], + "categories": [ + + "Open Hardware" + + ] + }, + + { + "serial": 34442, + "name": "Perl 5.20: Perl 5 at 20", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 11:30:00", + "time_stop": "2014-07-22 12:10:00", + "venue_serial": 1451, + "description": "This year brings the release of Perl 5.20.0, and the 20th anniversary of the Perl 5 programming language. In this session, Ricardo Signes, the Perl 5 project manager, covers the latest developments in the language, the development process, and changes we're hoping for in the near future.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34442", + "speakers": [3189], + "categories": [ + + "Perl" + + ] + }, + + { + "serial": 34447, + "name": "HTML Canvas Deep Dive", + "event_type": "tutorial", + + "time_start": "2014-07-20 09:00:00", + "time_stop": "2014-07-20 12:30:00", + "venue_serial": 1449, + "description": "In the fourth edition of this popular tutorial, we will focus on data visualization. Finding, parsing, drawing, and animating interesting data sets to promote understanding.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34447", + "speakers": [6931,183609], + "categories": [ + + "JavaScript - HTML5 - Web" + + ] + }, + + { + "serial": 34450, + "name": "Bluetooth Low Energy: Big Progress for Small Consumption!", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 14:30:00", + "time_stop": "2014-07-22 15:10:00", + "venue_serial": 1451, + "description": "The last year has been great for Bluetooth LE. Supported on all smartphone OSes, hackable with Arduino and Raspberry PI, and ready for wearable computing. Review the year of BLE, and build your own smart watch.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34450", + "speakers": [6931], + "categories": [ + + "Open Hardware" + + ] + }, + + { + "serial": 34451, + "name": "Netflix API : Top 10 Lessons Learned", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 10:40:00", + "time_stop": "2014-07-23 11:20:00", + "venue_serial": 1449, + "description": "Operating a massive-scale system, such as the Netflix API, is no trivial task. It supports over 44M members in 40+ countries and sees billions of requests a day. Along the way, there have been many mistakes, yet it is still at the center of the Netflix streaming ecosystem. In this session, I will go into detail on the top ten lessons learned in operating this complex and critical system.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34451", + "speakers": [173189], + "categories": [ + + "Cloud" + + ] + }, + + { + "serial": 34458, + "name": "Start a Free Coding Club for Kids", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 14:30:00", + "time_stop": "2014-07-22 15:10:00", + "venue_serial": 1462, + "description": "Most Saturday mornings, Greg Bulmash brings together 70-80 boys and girls, dozens of parents and volunteers, and they teach the kids to code at a free club called CoderDojo. Come learn how to start a CoderDojo in your city and join the hundreds of cities around the world where kids are learning everything from 'hello world' to NodeCopters to building apps.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34458", + "speakers": [171495], + "categories": [ + + "Education" + + ] + }, + + { + "serial": 34459, + "name": "\u201cUnfortunately, Design Tutorial Has Stopped\u201d, and Other Ways to Infuriate People With Mobile Apps", + "event_type": "tutorial", + + "time_start": "2014-07-21 09:00:00", + "time_stop": "2014-07-21 12:30:00", + "venue_serial": 1451, + "description": "In this tutorial you'll learn why you can't consider UX + design an optional extra when creating mobile apps, and how to tell an awesome app from a bad app. This highly interactive platform-agnostic design-heavy workshop is for programmers of any background. Learn how mobile apps work from a UI perspective, and how + why to build wireframes, and how to evaluate your designs for future improvement.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34459", + "speakers": [108884,118998,109468], + "categories": [ + + "Mobile Platforms", + + "User Experience" + + ] + }, + + { + "serial": 34461, + "name": "How Do I Game Design?", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 14:30:00", + "time_stop": "2014-07-22 15:10:00", + "venue_serial": 1450, + "description": "Understanding games means understanding user engagement and interaction. In this session, you'll learn a fresh perspective on user experience design by understanding how users engage with the fastest-growing form of entertainment in the world.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34461", + "speakers": [108884,118998], + "categories": [ + + "User Experience" + + ] + }, + + { + "serial": 34462, + "name": "The Case for Haskell", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 17:00:00", + "time_stop": "2014-07-22 17:40:00", + "venue_serial": 1462, + "description": "We're building ever larger and more complex systems. Coupled with changing requirements and demands for scaling concurrency and parallelism, taming this complexity is no small order.\r\n\r\nAllow me to share my excitement with you! I'll show you how Haskell helps tame this complexity, allows you to overcome the challenges of modern software, and make predictions about what the near future holds.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34462", + "speakers": [155881], + "categories": [ + + "Emerging Languages" + + ] + }, + + { + "serial": 34463, + "name": "GitGot: The Swiss Army Chainsaw of Git Repo Management", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 13:40:00", + "time_stop": "2014-07-22 14:20:00", + "venue_serial": 1454, + "description": "GitGot is a Perl-based tool for batch management of collections of git repos. It has a number of interesting features and acts as a force multiplier when dealing with a large varied collection of repositories. My talk will cover why you would want to use GitGot as well as how to use it effectively. ", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34463", + "speakers": [173201], + "categories": [ + + "Tools & Techniques" + + ] + }, + + { + "serial": 34471, + "name": "My Journey as a Community Manager (Literally)", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 17:00:00", + "time_stop": "2014-07-23 17:40:00", + "venue_serial": 1464, + "description": "I am going to expand on the experiences of setting up a worldwide community around our product, the Neo4j graph database. I will be presenting through the prism of being a woman in the technology world and how that has affected the way i had to work.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34471", + "speakers": [161517], + "categories": [ + + "Community" + + ] + }, + + { + "serial": 34473, + "name": "Get Started With the Arduino - A Hands-On Introductory Workshop", + "event_type": "tutorial", + + "time_start": "2014-07-20 09:00:00", + "time_stop": "2014-07-20 12:30:00", + "venue_serial": 1470, + "description": "Have you always wanted to create hardware devices to interact with the real world? Heard about the Arduino electronics prototyping platform but not sure how to get started? When you attend this workshop you will: set up an Arduino board & software; learn how the Arduino fits into the field of physical computing; and make your Arduino respond to button presses and blink lights. Hardware is fun!\r\n", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34473", + "speakers": [77469], + "categories": [ + + "Geek Lifestyle", + + "Open Hardware" + + ] + }, + + { + "serial": 34498, + "name": "Best Practices for MySQL High Availability", + "event_type": "tutorial", + + "time_start": "2014-07-21 09:00:00", + "time_stop": "2014-07-21 12:30:00", + "venue_serial": 1470, + "description": "The MySQL world is full of tradeoffs and choosing a High Availability (HA) solution is no exception. We demystify all the alternatives in an unbiased nature. Preference is of course only given to opensource solutions. ", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34498", + "speakers": [147], + "categories": [ + + "Databases & Datastores", + + "Operations & System Administration" + + ] + }, + + { + "serial": 34505, + "name": "Why Schools Don't Use Open Source to Teach Programming", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 11:30:00", + "time_stop": "2014-07-23 12:10:00", + "venue_serial": 1462, + "description": "Aside from the fact that high school programming curricula often require proprietary IDEs, they also don't involve examining any source code from Open Source software projects. What changes would be required in programming curricula to incorporate Open Source? And is that a desirable objective?", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34505", + "speakers": [157509], + "categories": [ + + "Education" + + ] + }, + + { + "serial": 34506, + "name": "How to Deploy PHP Apps Safely, Efficiently, and Frequently without Losing Your Sanity (Completely)", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 10:40:00", + "time_stop": "2014-07-23 11:20:00", + "venue_serial": 1458, + "description": "Sane and safe continuous deployment (and testing) can be achieved without much effort using a set of freely-available open-source tools, such as a good source control system, Phing, PHPUnit, some security tools, phpDocumentor and others.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34506", + "speakers": [173235], + "categories": [ + + "PHP" + + ] + }, + + { + "serial": 34509, + "name": "Inside the Go Tour", + "event_type": "40-minute conference session", + + "time_start": "2014-07-24 11:50:00", + "time_stop": "2014-07-24 12:30:00", + "venue_serial": 1458, + "description": "One of the most important tools created to help people learn Go is the Go tour (http://tour.golang.org)\r\n\r\nIt allows the user to learn the basics of Go and put them in practice directly on their browsers, running code without installing any compiler.\r\n\r\nImplementing this in a safe way is not an easy task! In this talk I present some techniques used to make sure everything goes as expected.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34509", + "speakers": [155088], + "categories": [ + + "Security" + + ] + }, + + { + "serial": 34522, + "name": "Internet ALL the Things - a walking tour of MQTT", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 14:30:00", + "time_stop": "2014-07-23 15:10:00", + "venue_serial": 1465, + "description": "As the internet grows, there are more and more interesting devices to connect to it - some of which are mobile, sensor platforms, or healthcare devices. This is all part of the 'Internet of Things' that has been an emerging area of excitement for the last few years. MQTT is a lightweight, messaging system for connected devices, the Industrial Internet, mobile, and the IoT.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34522", + "speakers": [141661], + "categories": [ + + "Mobile Platforms" + + ] + }, + + { + "serial": 34530, + "name": "Thinking in a Highly Concurrent, Mostly-functional Language", + "event_type": "40-minute conference session", + + "time_start": "2014-07-24 10:00:00", + "time_stop": "2014-07-24 10:40:00", + "venue_serial": 1452, + "description": "The actor model has received much attention because of its scalable and intuitive approach to concurrency. But the notion of concurrency is as fundamental to certain languages as object-orientation is to Java. In this talk, we will describe the evolution of concurrent thinking in Erlang, providing valuable lessons for Go, Rust, Elixir and AKKA developers who will undertake a similar journey.. ", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34530", + "speakers": [10595], + "categories": [ + + "Computational Thinking" + + ] + }, + + { + "serial": 34535, + "name": "Cheap Data Dashboards with Node, Amino and the Raspberry PI", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 11:30:00", + "time_stop": "2014-07-23 12:10:00", + "venue_serial": 1458, + "description": "Cheap LCD TV + Raspberry Pi = instant data dashboard. Learn how to use NodeJS and Amino for full screen GPU accelerated graphics (no X) to quickly build data dashboards. Show feeds, chart tweets, or visualize your build server with a particle fountain. Unleash gratuitous graphics for all to see.\r\n", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34535", + "speakers": [6931], + "categories": [ + + "Open Hardware" + + ] + }, + + { + "serial": 34536, + "name": "HTML5 JavaScript Storage for Structured Data", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 17:00:00", + "time_stop": "2014-07-23 17:40:00", + "venue_serial": 1450, + "description": "Learn about going beyond simple cookies and busting the 5MB limit imposed by Web Storage. We'll dive into the IndexedDB API and open your world to reading and writing not just strings from within browser storage, but also blobs, Arrays and Objects too.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34536", + "speakers": [2397], + "categories": [ + + "JavaScript - HTML5 - Web" + + ] + }, + + { + "serial": 34542, + "name": "Multiple Datastores Working Together: Will It Blend?", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 16:10:00", + "time_stop": "2014-07-23 16:50:00", + "venue_serial": 1475, + "description": "There has been an explosion in datastore technologies. There are five main types of datastores: Relational, Column Family, Graph, Key-Value and Document. Polyglot Persistence, or the ability to have many different types of datastores interacting with one application, is becoming more prominent and beginning to take center stage.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34542", + "speakers": [159586], + "categories": [ + + "Databases & Datastores" + + ] + }, + + { + "serial": 34551, + "name": "Marketing Your Tech Talent ", + "event_type": "40-minute conference session", + + "time_start": "2014-07-24 11:50:00", + "time_stop": "2014-07-24 12:30:00", + "venue_serial": 1454, + "description": "Today's tech job descriptions want 'superstars', but most companies \u2013 and employees! \u2013 still treat employee talent as a replaceable commodity. How can you market yourself and your talents, to benefit your own career as well as the company or project you work for? This talk will provide practical ideas and real-life case studies, based on years of experience helping geeks communicate what they do.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34551", + "speakers": [122293], + "categories": [ + + "Geek Lifestyle" + + ] + }, + + { + "serial": 34552, + "name": "Improv: Think, React, Go!", + "event_type": "tutorial", + + "time_start": "2014-07-21 13:30:00", + "time_stop": "2014-07-21 17:00:00", + "venue_serial": 1456, + "description": "Getting everyone in your company or development team on the same page can be a challenge. This on-your-feet workshop will teach fast, fun improv techniques for helping your group to bond, generate quality ideas and make quick decisions. Learn the secrets of applied improv from two professionals who have decades of experience working in open source, Internet startups and corporate training.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34552", + "speakers": [4378,106355,123894], + "categories": [ + + "Business", + + "Community" + + ] + }, + + { + "serial": 34555, + "name": "Build your Own Android App using Open Source Libraries - A Hands On Tutorial", + "event_type": "tutorial", + + "time_start": "2014-07-20 13:30:00", + "time_stop": "2014-07-20 17:00:00", + "venue_serial": 1471, + "description": "In this tutorial, we will develop a working Android application using open source libraries for key platform components: HTTP client, JSON parsing, Async image download and caching.\r\n\r\nYou will learn how to manage dependencies using Gradle and best practices for building Android apps using open source libraries.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34555", + "speakers": [173281,182019], + "categories": [ + + "Mobile Platforms" + + ] + }, + + { + "serial": 34568, + "name": "React's Architecture", + "event_type": "40-minute conference session", + + "time_start": "2014-07-24 11:50:00", + "time_stop": "2014-07-24 12:30:00", + "venue_serial": 1449, + "description": "React is a JavaScript library for building user interfaces developed by Facebook and Instagram. It has a novel rendering architecture that we're going to explore in this talk.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34568", + "speakers": [133198], + "categories": [ + + "JavaScript - HTML5 - Web" + + ] + }, + + { + "serial": 34570, + "name": "Satisfying Business and Engineering Requirements: Client-server JavaScript, SEO, and Optimized Page Load", + "event_type": "40-minute conference session", + + "time_start": "2014-07-24 10:00:00", + "time_stop": "2014-07-24 10:40:00", + "venue_serial": 1457, + "description": "Often business and developer needs are at odds when developing public facing websites that need to be indexed. Business is concerned with factors such as SEO, visitor retention and bounce rates, while engineering is concerned with developer ergonomics, re-usage, separation of concerns, and maintenance. This talk will describe a solution that satisfies both business and engineering requirements.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34570", + "speakers": [135908], + "categories": [ + + "Business", + + "JavaScript - HTML5 - Web" + + ] + }, + + { + "serial": 34575, + "name": "Open-Source DoS Testing and Defense", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 16:10:00", + "time_stop": "2014-07-23 16:50:00", + "venue_serial": 1466, + "description": "Denial of Service (DoS) attacks have been making the news lately -- can your site hold up? In this talk, we'll look at a number of open-source tools for testing your site and walk through ways to guard yourself against web attackers.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34575", + "speakers": [173285], + "categories": [ + + "Security" + + ] + }, + + { + "serial": 34578, + "name": "ETL: The Dirty Little Secret of Data Science", + "event_type": "40-minute conference session", + + "time_start": "2014-07-24 11:00:00", + "time_stop": "2014-07-24 11:40:00", + "venue_serial": 1475, + "description": "There is an adage that given enough data, a data scientist can answer the world's questions. The untold truth is that the majority of work happens during the ETL and data preprocessing phase. In this talk I discuss Origins, an open source Python library for extracting and mapping structural metadata across heterogenous data stores.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34578", + "speakers": [152026], + "categories": [ + + "Databases & Datastores" + + ] + }, + + { + "serial": 34587, + "name": "Monitoring Distributed Systems in Real-time with Riemann and Cassandra", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 17:00:00", + "time_stop": "2014-07-22 17:40:00", + "venue_serial": 1449, + "description": "Computing is spreading outwards: clusters of 1000s of nodes serve a single database, and hundreds of machines analyze the same KPIs.\r\n\r\nHow do we monitor a cluster with many nodes?\r\n\r\nThis talk presents how to effectively monitor a multi-node Cassandra cluster using Riemann and other graphing solutions.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34587", + "speakers": [156989], + "categories": [ + + "Operations & System Administration" + + ] + }, + + { + "serial": 34588, + "name": "Getting Started Contributing to Firefox OS", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 17:00:00", + "time_stop": "2014-07-22 17:40:00", + "venue_serial": 1466, + "description": "Firefox OS is a new mobile operating system, developed by Mozilla, which lets users install and run open web applications created using HTML, CSS, and JavaScript.\r\nThe session will introduce people to Firefox OS, the overview, branding and distribution and will explain the governance behind it. ", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34588", + "speakers": [120025,173308], + "categories": [ + + "Mobile Platforms" + + ] + }, + + { + "serial": 34589, + "name": "Make your Open Source More Open \u2013 Conquering the Accessibility Challenge", + "event_type": "tutorial", + + "time_start": "2014-07-20 09:00:00", + "time_stop": "2014-07-20 12:30:00", + "venue_serial": 1457, + "description": "How accessible are your development projects? This session puts development to the ultimate accessibility test. The presenters will guide you through an experience of accessibility for people who are blind and then go on to cover best practices, testing, and pitfalls in implementing accessible web and program design. You will walk away with actionable tips to use in your development projects.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34589", + "speakers": [2699,173303], + "categories": [ + + "Tools & Techniques", + + "User Experience" + + ] + }, + + { + "serial": 34595, + "name": "Robots in Finland: How a Small Open Hardware Project Sparked International Collaboration Across 10 Timezones and 5,000 Miles ", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 13:40:00", + "time_stop": "2014-07-22 14:20:00", + "venue_serial": 1464, + "description": "This talk shares the success story of how a small open hardware project used an Arduino/Python archival digitization robot to spark an international collaboration spanning cultures and continents. The talk focuses on how the collaboration came to be, how the teams used tools like 3D printing and video to work together across 5000+ miles, and how other OS projects can create similar partnerships.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34595", + "speakers": [165643], + "categories": [ + + "Community" + + ] + }, + + { + "serial": 34610, + "name": "DNSSEC Via a New Stub Resolver", + "event_type": "40-minute conference session", + + "time_start": "2014-07-24 11:00:00", + "time_stop": "2014-07-24 11:40:00", + "venue_serial": 1466, + "description": "The need for secure DNS is more pressing than ever but the current standard API for using the DNS can't take advantage of modern DNS features. We will give an application developers view of DNSSEC and describe the independently written getDNS API specification. We will showcase the open source implementation of the specification built by our team of developers from NLNet Labs and Verisign.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34610", + "speakers": [173324,173326,173325,172895], + "categories": [ + + "Security" + + ] + }, + + { + "serial": 34612, + "name": "Sepia: How LinkedIn Mobile Made Integration Testing Fast and Reliable in Node.js", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 16:10:00", + "time_stop": "2014-07-22 16:50:00", + "venue_serial": 1449, + "description": "LinkedIn runs a node.js server to power its phone clients. Because the server makes HTTP requests to other services, network latencies make for slow, and potentially unreliable end-to-end tests. This presentation walks through how LinkedIn built an open-source tool, sepia, to address the challenge of scaling a complex test infrastructure in order to release high quality code with high confidence.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34612", + "speakers": [172988], + "categories": [ + + "Mobile Platforms" + + ] + }, + + { + "serial": 34627, + "name": "Creating an SDK - Lessons Learned", + "event_type": "40-minute conference session", + + "time_start": "2014-07-24 11:50:00", + "time_stop": "2014-07-24 12:30:00", + "venue_serial": 1459, + "description": "Taking a complex API and wrapping it to create a coherent SDK for a programming language is a huge undertaking, and even harder when it has to be done by a single developer. I created pyrax, the Python SDK for OpenStack, at the request of my company. It has been a success, but it didn't come easy. In this talk I'll share some of the many lessons learned, both technical and political.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34627", + "speakers": [152106], + "categories": [ + + "Cloud" + + ] + }, + + { + "serial": 34630, + "name": "Working with Design in Open Source", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 11:30:00", + "time_stop": "2014-07-23 12:10:00", + "venue_serial": 1464, + "description": "In this session we'll look at how design effects an open source project and how to encourage designers to contribute. We'll also cover the fundamentals of design, in case a developer finds themselves in the role of designer.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34630", + "speakers": [173248], + "categories": [ + + "Community", + + "User Experience" + + ] + }, + + { + "serial": 34632, + "name": "OpenStack :: Where Continuous Delivery and Distros Collide", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 13:40:00", + "time_stop": "2014-07-22 14:20:00", + "venue_serial": 1459, + "description": "Mark McLoughlin and Monty Taylor - both members of the OpenStack Technical Committee and Foundation Board - gives their perspectives on how OpenStack caters to two distinct audiences with its time-based release process and its support for continuous deployment. They will also talk to this a case study for how DevOps is influencing the way open-source projects are managed and consumed.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34632", + "speakers": [172824,109289], + "categories": [ + + "Cloud" + + ] + }, + + { + "serial": 34635, + "name": "Move Fast and Ship Things ", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 10:40:00", + "time_stop": "2014-07-22 11:20:00", + "venue_serial": 1449, + "description": "This talk will explore the 'move fast' side of Facebook\u2019s software engineering culture: development process, organizational structure, and the vast amounts of tooling we create and use to make sure we don\u2019t screw up. We\u2019ll also dig into how we 'ship things': release process, A/B testing, gate keepers, test infrastructure, and more.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34635", + "speakers": [109270], + "categories": [ + + "Cloud", + + "Operations & System Administration" + + ] + }, + + { + "serial": 34637, + "name": "Chef and OpenStack", + "event_type": "40-minute conference session", + + "time_start": "2014-07-24 10:00:00", + "time_stop": "2014-07-24 10:40:00", + "venue_serial": 1459, + "description": "The open source configuration management and automation framework Chef is used to configure, deploy and manage many large public and private installations of OpenStack and supports a wide variety of integration opportunities. OpenStack is a large and complex ecosystem, this session will highlight the Chef resources available for developers and operators.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34637", + "speakers": [141169], + "categories": [ + + "Operations & System Administration" + + ] + }, + + { + "serial": 34640, + "name": "Include Hack - HHVM - PHP++", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 10:40:00", + "time_stop": "2014-07-22 11:20:00", + "venue_serial": 1458, + "description": "Did you know that one of the biggest PHP sites on the internet isn't running PHP? Did you know that HHVM clocks in at anywhere between 2x and 10x faster than standard PHP with an Opcode Cache? Come take a look at \u201cThe other PHP engine\u201d, how to get a server up and running, what pitfalls to watch out for in migrating over, and what exciting extras are waiting. ", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34640", + "speakers": [173262,173336], + "categories": [ + + "PHP" + + ] + }, + + { + "serial": 34642, + "name": "Playing Chess with Companies", + "event_type": "tutorial", + + "time_start": "2014-07-21 09:00:00", + "time_stop": "2014-07-21 12:30:00", + "venue_serial": 1458, + "description": "Most organisations have strategy documents full of implementation, purchasing, tactical and operational choices. Remove this and you're often left with a vague 'why' which normally boils down to copying everyone else. In this tutorial I'll demonstrate how a large number of companies are playing a game of chess in which they can't see the board and how you can exploit this.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34642", + "speakers": [6219], + "categories": [ + + "Business" + + ] + }, + + { + "serial": 34646, + "name": "Predicting Global Unrest with GDELT and SQL on Hadoop ", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 10:40:00", + "time_stop": "2014-07-23 11:20:00", + "venue_serial": 1454, + "description": "The Global Database of Events, Language, and Tone (GDELT) is an initiative to construct a catalog of human societal-scale behavior and beliefs across all countries of the world. Analysis of this data set requires addressing typical data quality and data skew issues. \r\n\r\nUse a combined Hadoop + SQL on Hadoop stack to cleanse the data and deliver insights into the state of the world. ", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34646", + "speakers": [53442], + "categories": [ + + "Databases & Datastores" + + ] + }, + + { + "serial": 34650, + "name": "Arduino Yun for Intermediate Arduino Users: Using the Onboard Linux Computer to Communicate with Other Computers and the Internet", + "event_type": "tutorial", + + "time_start": "2014-07-20 13:30:00", + "time_stop": "2014-07-20 17:00:00", + "venue_serial": 1470, + "description": "The new Arduino Yun contains both an Arduino Leonardo and a full Linux system on a chip with built-in Ethernet and Wifi. This intermediate level hands-on tutorial will teach you how to use the Yun to communicate between Yun and Yun, Yun and laptop, and Yun and internet services, such Gmail, Twitter, and other services with APIs", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34650", + "speakers": [141561], + "categories": [ + + "Education", + + "Open Hardware" + + ] + }, + + { + "serial": 34654, + "name": "Red October: Implementing the Two-man Rule for Keeping Secrets", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 14:30:00", + "time_stop": "2014-07-23 15:10:00", + "venue_serial": 1462, + "description": "Red October is an open source encryption server with a twist -- it can encrypt secrets, requiring more than one person to decrypt them. This talk will describe what goes into building an open source security product and using it in the real world. From motivation, design decisions, pitfalls of using a young programming language like Go, through deployment and opening the work up to the community.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34654", + "speakers": [164229], + "categories": [ + + "Security" + + ] + }, + + { + "serial": 34668, + "name": "OpenUI5 - The New Responsive Web UI Library", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 17:00:00", + "time_stop": "2014-07-22 17:40:00", + "venue_serial": 1450, + "description": "OpenUI5 is a comprehensive enterprise-grade HTML5 UI library (developed by SAP) which has been open-sourced recently. Explore its power through concrete code examples and demos for key features like declarative UIs, data binding, and responsiveness: write ONE app and it will adapt to any device, from desktop screen to smartphones.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34668", + "speakers": [170822,173233], + "categories": [ + + "User Experience" + + ] + }, + + { + "serial": 34677, + "name": "Elasticsearch: The Missing Tutorial", + "event_type": "tutorial", + + "time_start": "2014-07-20 09:00:00", + "time_stop": "2014-07-20 12:30:00", + "venue_serial": 1452, + "description": "Elasticsearch provides a powerful combination of clustered full-text search, synonyms, faceting, and geographic math, but there's a big gap between its documentation and real life. We'll tell hard-won war stories, work through hands-on examples, and show what happens behind the scenes, leaving you equipped to get the best use out of Elasticseach in your projects.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34677", + "speakers": [173247,150], + "categories": [ + + "Cloud", + + "Databases & Datastores" + + ] + }, + + { + "serial": 34678, + "name": "Open HeARTware with ChickTech", + "event_type": "tutorial", + + "time_start": "2014-07-20 13:30:00", + "time_stop": "2014-07-20 17:00:00", + "venue_serial": 1451, + "description": "Are you a software person? An artsy type? Never thought you would like hardware? Or perhaps you love hardware? No matter what your skill level, this workshop is for you. Get in on the open hardware movement and join ChickTech to create your own \u201csoft circuit\u201d using conductive thread, fabric, inputs/outputs, and a microcontroller! ", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34678", + "speakers": [131890,124700], + "categories": [ + + "Education" + + ] + }, + + { + "serial": 34687, + "name": "Identity Crisis: Are We Really Who We Say We Are?", + "event_type": "40-minute conference session", + + "time_start": "2014-07-24 10:00:00", + "time_stop": "2014-07-24 10:40:00", + "venue_serial": 1456, + "description": "Karen Sandler, Executive Director of the GNOME Foundation, will discuss the peculiar tension in the intersection of free and open source software and corporate interest. ", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34687", + "speakers": [173364], + "categories": [ + + "Business", + + "Community" + + ] + }, + + { + "serial": 34688, + "name": "Healthcare for Geeks", + "event_type": "40-minute conference session", + + "time_start": "2014-07-24 11:00:00", + "time_stop": "2014-07-24 11:40:00", + "venue_serial": 1448, + "description": "Hacking Healthcare author David Uhlman will show you how to 3D print your body parts, order your own lab work, build a DNA analyzer, tour an array of personal monitoring devices for fitness, health and open biology projects, stop eating altogether by switching to soylent. Also tips on what insurance to get, navigating hospitals and finding the right doctor.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34688", + "speakers": [86111], + "categories": [ + + "Main Stage" + + ] + }, + + { + "serial": 34690, + "name": "Contributing to Contributors: Breaking Down the Barriers to First-commit", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 16:10:00", + "time_stop": "2014-07-23 16:50:00", + "venue_serial": 1448, + "description": "Every open source project is a unique snowflake of technology choices, coding style, and communication channels. Learning not only how, but the 'correct' way to contribute to each new project can be a blocker for would-be contributors. This talk will give practical examples of how you can reduce the learning curve for new contributors and improve the quality of first-commits.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34690", + "speakers": [104522], + "categories": [ + + "Main Stage" + + ] + }, + + { + "serial": 34695, + "name": "Open edX: an Open-source MOOC Platform", + "event_type": "40-minute conference session", + + "time_start": "2014-07-24 11:00:00", + "time_stop": "2014-07-24 11:40:00", + "venue_serial": 1462, + "description": "Open edX is an open-source platform for delivering online courses. It's in use by the 31 member universities of edx.org (Harvard, MIT, Berkeley, etc), as well as Stanford, Google, and many other colleges and universities. This talk will describe the platform and show you ways to participate, as a course author, tool developer, or offering institution.\r\n ", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34695", + "speakers": [41059,179963], + "categories": [ + + "Education" + + ] + }, + + { + "serial": 34700, + "name": "PHP 5.6 and Beyond: Because Incrementing Major Versions is for Suckers", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 14:30:00", + "time_stop": "2014-07-22 15:10:00", + "venue_serial": 1458, + "description": "PHP 5.6 is out, and comes with useful new features and internal cleanups, as the last few 5.x releases have. In this talk, I'll discuss those features, but also where PHP is going: will there be a PHP 6 or 7 in the near future? What might it contain? How can we learn from Python 3 and Perl 6?", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34700", + "speakers": [152118], + "categories": [ + + "PHP" + + ] + }, + + { + "serial": 34702, + "name": "HTML5 Video Part Deux; New Opportunities and New Challenges", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 14:30:00", + "time_stop": "2014-07-23 15:10:00", + "venue_serial": 1450, + "description": "This talk gives a close look at second wave HTML5 features around video delivery \u2014 specifically, mediaSource API / adaptive streaming, encrypted media extension and WebRTC. We look at open tools and techniques for transcending platform limitations and delivery these experiences across increasingly diverse set of devices with real world examples from Kaltura, Wikimedia and others.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34702", + "speakers": [108736], + "categories": [ + + "JavaScript - HTML5 - Web" + + ] + }, + + { + "serial": 34705, + "name": "What is Happening at the CentOS Project?", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 17:00:00", + "time_stop": "2014-07-23 17:40:00", + "venue_serial": 1457, + "description": "What is the future of CentOS Linux? Hear the true story from the project leaders behind the surprise announcement that the CentOS Project and Red Hat are joining forces.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34705", + "speakers": [46737,173380,173381], + "categories": [ + + "Community" + + ] + }, + + { + "serial": 34711, + "name": "Schemas for the Real World", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 14:30:00", + "time_stop": "2014-07-22 15:10:00", + "venue_serial": 1475, + "description": "Development challenges us to code for users\u2019 personal world. Users give push-back to ill-fitted assumptions about their own name, gender, sexual orientation, important relationships, & other attributes that are individually meaningful. We'll explore how to develop software that brings real world into focus & that allows individuals to authentically reflect their personhood & physical world.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34711", + "speakers": [141590], + "categories": [ + + "Databases & Datastores" + + ] + }, + + { + "serial": 34713, + "name": "Functionally Mobile (Automation)", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 11:30:00", + "time_stop": "2014-07-23 12:10:00", + "venue_serial": 1451, + "description": "Mobile's here to stay! This talk will showcase how Open Source tools can power your test automation for mobile apps. It entirely relies on Open Source components such as Appium, Cordova/PhoneGap an Topcoat.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34713", + "speakers": [173378], + "categories": [ + + "Mobile Platforms" + + ] + }, + + { + "serial": 34717, + "name": "Developing High Performance Websites and Modern Apps with JavaScript and HTML5", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 10:40:00", + "time_stop": "2014-07-23 11:20:00", + "venue_serial": 1450, + "description": "Creating high performance sites and apps is crucial for every developer. In this session, we will explore the best practices and performance tricks, to make your apps running faster and fluid. Come learn the tips, tricks, and tools for maximizing the performance of your sites and apps with JavaScript and HTML5.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34717", + "speakers": [133360], + "categories": [ + + "JavaScript - HTML5 - Web" + + ] + }, + + { + "serial": 34718, + "name": "Android Developer Tools Essentials", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 13:40:00", + "time_stop": "2014-07-22 14:20:00", + "venue_serial": 1466, + "description": "This session is an overview of the Android Developer Tools (ADT and Android Studio), including many useful techniques, tips and tricks for getting the most out of them.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34718", + "speakers": [150073], + "categories": [ + + "Mobile Platforms" + + ] + }, + + { + "serial": 34731, + "name": "How We Built a Cloud Platform Using Netflix OSS", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 13:40:00", + "time_stop": "2014-07-23 14:20:00", + "venue_serial": 1459, + "description": "\r\nThe Netflix OSS Cloud stack is clearly a great set of components for building a cloud infrastructure and platform\u2014if you are Netflix. But how does that architecture work for other businesses? Learn how at Riot we leveraged Netflix OSS cloud tools and platform components to create an infrastructure for our global game platform\u2014maybe it can work for you too.\r\n", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34731", + "speakers": [161577], + "categories": [ + + "Cloud" + + ] + }, + + { + "serial": 34737, + "name": "Making Federal Regulations Readable with Python", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 17:00:00", + "time_stop": "2014-07-23 17:40:00", + "venue_serial": 1465, + "description": "The Consumer Financial Protection Bureau (http://cfpb.gov) has\r\ndeveloped an open source web-based tool to make regulations easy to\r\nread, access and understand. We talk about the unique parsing and\r\nother challenges we encountered working with these legal documents,\r\nand how we used Python, pyParsing, Django and other open source tools\r\nto solve them.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34737", + "speakers": [157931], + "categories": [ + + "Python" + + ] + }, + + { + "serial": 34744, + "name": "Machine Learning for Rubyists", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 17:00:00", + "time_stop": "2014-07-23 17:40:00", + "venue_serial": 1466, + "description": "In this presentation we'll cover five important machine learning techniques that can be used in a wide range of applications. It will be a wide and shallow introduction, for Rubyists, not mathematicians - we'll have plenty of simple code examples. \r\n", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34744", + "speakers": [173396], + "categories": [ + + "Computational Thinking" + + ] + }, + + { + "serial": 34745, + "name": "Secure Development is Much Easier Than You Think", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 17:00:00", + "time_stop": "2014-07-23 17:40:00", + "venue_serial": 1462, + "description": "Secure software development is something absolutely critical to helping create safer more trusted computing experiences for everyone.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34745", + "speakers": [173399,173403], + "categories": [ + + "Security" + + ] + }, + + { + "serial": 34746, + "name": "Making maps with OpenStreetMap and Koop", + "event_type": "40-minute conference session", + + "time_start": "2014-07-24 11:00:00", + "time_stop": "2014-07-24 11:40:00", + "venue_serial": 1450, + "description": "Like maps and open data? Koop has created a new way of accessing open data and making cool maps with wide variety of data \r\n", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34746", + "speakers": [108520,173406], + "categories": [ + + "JavaScript - HTML5 - Web" + + ] + }, + + { + "serial": 34753, + "name": "International Community Building: Bridging Japan and the Rest of the World", + "event_type": "40-minute conference session", + + "time_start": "2014-07-24 11:00:00", + "time_stop": "2014-07-24 11:40:00", + "venue_serial": 1464, + "description": "Japan has a thriving open source technology community. It\u2019s also the third largest IT market in the world, with more engineers per capita than the US, and a history of game-changing open source projects like Ruby and Jenkins. Hear a first hand account of managing and cultivating open source communities in Japan, the US and other countries and discuss international community building.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34753", + "speakers": [153857], + "categories": [ + + "Community" + + ] + }, + + { + "serial": 34756, + "name": "Graph Theory You Need to Know", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 14:30:00", + "time_stop": "2014-07-22 15:10:00", + "venue_serial": 1452, + "description": "A brief and friendly tour of the basics of graph theory, including a description and classification of the kinds of graphs and some interesting problems they can be employed to solve.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34756", + "speakers": [137697], + "categories": [ + + "Computational Thinking" + + ] + }, + + { + "serial": 34757, + "name": "The Data Structures (You Think) You Need to Know", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 13:40:00", + "time_stop": "2014-07-23 14:20:00", + "venue_serial": 1448, + "description": "A fun and approachable tour of some otherwise intimidating data structures. Learn how to solve difficult problems efficiently through the clever organization and linking of data.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34757", + "speakers": [137697], + "categories": [ + + "Computational Thinking" + + ] + }, + + { + "serial": 34769, + "name": "Tapping into Ruby from the JVM", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 13:40:00", + "time_stop": "2014-07-22 14:20:00", + "venue_serial": 1456, + "description": "With the diversity and innovation in the Ruby ecosystem and the popularity of polyglot programming on the robust and efficient JVM, Ruby and the JVM make a great fit. We\u2019ll cover numerous ways to invoke Ruby from Java and other JVM languages and how to package and deploy this style of application. We\u2019ll study examples from AsciidoctorJ, a Java API to the Ruby-based text processor, Asciidoctor.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34769", + "speakers": [117513], + "categories": [ + + "Ruby" + + ] + }, + + { + "serial": 34772, + "name": "Introduction to Ceph", + "event_type": "tutorial", + + "time_start": "2014-07-20 13:30:00", + "time_stop": "2014-07-20 17:00:00", + "venue_serial": 1458, + "description": "This Introduction to Ceph tutorial will include a mix of lecture and instructor-led demonstrations that will introduce students to the Ceph distributed storage system, the challenges it addresses, its architecture, and solutions it offers.\r\n\r\nStudents will leave understanding how Ceph works, how it can be integrated with your services and applications, and how it works alongside OpenStack.\r\n", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34772", + "speakers": [183246], + "categories": [ + + "Databases & Datastores", + + "Operations & System Administration" + + ] + }, + + { + "serial": 34773, + "name": "Demystifying SELinux Part II: Who\u2019s Policy Is It Anyway?", + "event_type": "tutorial", + + "time_start": "2014-07-20 09:00:00", + "time_stop": "2014-07-20 12:30:00", + "venue_serial": 1458, + "description": "Building on last year\u2019s critically acclaimed \u2018Demystifying SELinux: WTF is it saying?\u2019 talk Demystifying \u2018SELinux Part II: Who\u2019s policy is it anyway?\u2019 is an extended tutorial which has attendees work through real life examples of SELinux configuration and policy construction.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34773", + "speakers": [151833], + "categories": [ + + "Operations & System Administration", + + "Security" + + ] + }, + + { + "serial": 34788, + "name": "Painlessly Functional and Concurrent: An Introduction to Elixir", + "event_type": "tutorial", + + "time_start": "2014-07-20 13:30:00", + "time_stop": "2014-07-20 17:00:00", + "venue_serial": 1457, + "description": "This tutorial is a quick introduction to the Elixir programming language. We\u2019ll explore the basics of the language, meta programming, and explore why you want to use Elixir to write concurrent, scalable, and robust programs.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34788", + "speakers": [173421], + "categories": [ + + "Emerging Languages", + + "Tools & Techniques" + + ] + }, + + { + "serial": 34797, + "name": "Open Mobile Accessibility with GitHub and Cordova", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 14:30:00", + "time_stop": "2014-07-22 15:10:00", + "venue_serial": 1454, + "description": "The story of an open-source project that brings mobile accessibility APIs together with native webviews to make mobile app development more responsive to users with disabilities.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34797", + "speakers": [17378], + "categories": [ + + "Mobile Platforms" + + ] + }, + + { + "serial": 34808, + "name": "Writing English ", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 13:40:00", + "time_stop": "2014-07-22 14:20:00", + "venue_serial": 1448, + "description": "So you know Java, Scala, Python, and Perl, but do you know the correct usage of a semicolon when it comes to the English language? Writers and engineers alike often fall victim to grammatical blunders that can obscure their intended message. Fortunately, there are some simple ways of spotting and correcting these errors. Once learned, your writing will improve and your readers will thank you. ", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34808", + "speakers": [173393], + "categories": [ + + "Main Stage" + + ] + }, + + { + "serial": 34809, + "name": "Streaming Predictions of User Behavior in Real-Time", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 16:10:00", + "time_stop": "2014-07-22 16:50:00", + "venue_serial": 1452, + "description": "Visitors to an online store rarely make their intention explicit. A valuable goal in digital marketing is to infer this intention so to influence the visitor's behavior in-situ. We describe a data-driven approach to identifying and predicting online user behavior. The talk focuses on the construction of real-time machine learning tools for inference to sites with thousands of concurrent visitors.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34809", + "speakers": [173414,173431], + "categories": [ + + "Computational Thinking" + + ] + }, + + { + "serial": 34811, + "name": "Digital Dancing", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 10:40:00", + "time_stop": "2014-07-22 11:20:00", + "venue_serial": 1448, + "description": "You check out the schedule, and you note with excitement that there's a presentation called DIGITAL DANCING. You grab your stuff and head for that conference room.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34811", + "speakers": [161486,182741], + "categories": [ + + "Main Stage" + + ] + }, + + { + "serial": 34814, + "name": "Why You Should Be Looking at Functional Web Development", + "event_type": "40-minute conference session", + + "time_start": "2014-07-24 11:50:00", + "time_stop": "2014-07-24 12:30:00", + "venue_serial": 1466, + "description": "Web combined with functional programming gives pure awesomeness. Come and learn about WebSharper, an open source web development framework for F#, and how it makes programmers happier and more productive.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34814", + "speakers": [132323], + "categories": [ + + "Emerging Languages" + + ] + }, + + { + "serial": 34820, + "name": "The State of Crypto in Python", + "event_type": "40-minute conference session", + + "time_start": "2014-07-24 11:50:00", + "time_stop": "2014-07-24 12:30:00", + "venue_serial": 1465, + "description": "Python has a complex past with crypto. There are half a dozen frameworks built on at least three separate C implementations, each with their own strengths and weaknesses and in various states of maintenance. This presentation will review the current state of the art and discuss the future of crypto in Python including a new library aimed at fixing modern crypto support in Python.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34820", + "speakers": [173432,173435], + "categories": [ + + "Python" + + ] + }, + + { + "serial": 34823, + "name": "Building a Culture of Continuous Learning", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 16:10:00", + "time_stop": "2014-07-22 16:50:00", + "venue_serial": 1462, + "description": "Technology moves too quickly for us to ever really stop learning - but how can we establish and maintain a culture of continuous learning in our business teams? And how can we ensure that continuous learning is effective?", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34823", + "speakers": [152299], + "categories": [ + + "Business", + + "Education" + + ] + }, + + { + "serial": 34824, + "name": "Getting Started with OpenStack: Hands on Tutorial", + "event_type": "tutorial", + + "time_start": "2014-07-20 09:00:00", + "time_stop": "2014-07-20 12:30:00", + "venue_serial": 1475, + "description": "Curious about OpenStack, but don't know where to start? In this hands on tutorial we will walk you through the basics of OpenStack, the OpenSource cloud computing platform that is used to build private and public clouds. ", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34824", + "speakers": [169647,169673], + "categories": [ + + "Cloud" + + ] + }, + + { + "serial": 34829, + "name": "Building Reliable Systems: Lessons from Erlang", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 13:40:00", + "time_stop": "2014-07-22 14:20:00", + "venue_serial": 1452, + "description": "Erlang is famous for building systems that are incredibly reliable, having virtually no down time! What are the principles that Erlang uses? Can we apply them in other languages? In this presentation, you'll learn how Erlang's design enables reliability and how you can use similar patterns to improve your own software and software systems.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34829", + "speakers": [131729], + "categories": [ + + "Computational Thinking" + + ] + }, + + { + "serial": 34831, + "name": "Kinect for Creative Development with Cinder, openFrameworks", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 17:00:00", + "time_stop": "2014-07-22 17:40:00", + "venue_serial": 1458, + "description": "Want to integrate some body, face, voice recognition into your 3D application? You can do this fairly easily using the Kinect for Windows sensor along with the Kinect Common Bridge, an open source library that makes it simple to integrate Kinect experiences into your C++ code/library. OpenFrameworks, Cinder and other creative development communities have adopted it already! Cool creative demos!", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34831", + "speakers": [143232,23017], + "categories": [ + + "User Experience" + + ] + }, + + { + "serial": 34836, + "name": "Perl Lightning Talks", + "event_type": "Event", + + "time_start": "2014-07-23 19:30:00", + "time_stop": "2014-07-23 20:30:00", + "venue_serial": 1450, + "description": "Join us for the ever popular Perl Lightning Talks.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34836", + "speakers": [4429], + "categories": [ + + "Events", + + "Perl" + + ] + }, + + { + "serial": 34839, + "name": "Moving Java into the Open", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 17:00:00", + "time_stop": "2014-07-22 17:40:00", + "venue_serial": 1456, + "description": "This session will explore how Java development has been brought into the open over the past several years. Several Java developer community efforts have brought open source development processes and new levels of transparency and participation into their communities. ", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34839", + "speakers": [65329,116276], + "categories": [ + + "Java & JVM" + + ] + }, + + { + "serial": 34840, + "name": "Open Community Infrastructure How-to", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 10:40:00", + "time_stop": "2014-07-23 11:20:00", + "venue_serial": 1466, + "description": "Does every open source project need an open infrastructure? Should root be potentially available to any community member? If you think, 'Maybe, yes,' come learn how-to and why-to with lessons-learned from Fedora, oVirt, CentOS Project, and other projects.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34840", + "speakers": [46737], + "categories": [ + + "Operations & System Administration" + + ] + }, + + { + "serial": 34844, + "name": "Pro Puppet", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 16:10:00", + "time_stop": "2014-07-23 16:50:00", + "venue_serial": 1454, + "description": "Learn to use Puppet like a Pro! We will take you through several examples of how to bring your Puppet deployment to the next level. We will cover Hiera, deploying puppet code, code architecture best practices, and integrating external tools.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34844", + "speakers": [125113,99817], + "categories": [ + + "Operations & System Administration", + + "Tools & Techniques" + + ] + }, + + { + "serial": 34845, + "name": "Transit Appliance at Three", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 17:00:00", + "time_stop": "2014-07-23 17:40:00", + "venue_serial": 1451, + "description": "At OSCON 2011 we introduced the Transit Appliance, a project to use open hardware, open source software and open APIs to create a low-cost display for transit arrivals. Three years later we have two dozen displays deployed in the community, have seen the retirement of the Chumby, the rise of the Raspberry Pi and many new web services enriching the display. Progress and lessons learned.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34845", + "speakers": [109116], + "categories": [ + + "Open Hardware" + + ] + }, + + { + "serial": 34849, + "name": "tmux - A Multiplexer's Multiplexer", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 14:30:00", + "time_stop": "2014-07-22 15:10:00", + "venue_serial": 1466, + "description": "Many developers, system/network admins, and designers spend good portions of their careers avoiding any interaction with their systems' command line interface(s) (CLI's). Unfortunately, the CLI is viewed as an archaic and inefficient means of being productive. In tmux, a powerful terminal multiplexer, developers and admins have a tool for more fully exploiting the power of the CLI.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34849", + "speakers": [138530], + "categories": [ + + "Operations & System Administration" + + ] + }, + + { + "serial": 34853, + "name": "Build Your Own Exobrain", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 10:40:00", + "time_stop": "2014-07-23 11:20:00", + "venue_serial": 1448, + "description": "Online services like 'If This Then That' (IFTTT) are great for automating your life. However they provide limited ways for the end-user to add their own services, and often require credentials that one may normally wish to keep secret.\r\n\r\nThe 'exobrain' project allows for service integration and extension on a machine *you* control.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34853", + "speakers": [6631], + "categories": [ + + "Main Stage" + + ] + }, + + { + "serial": 34855, + "name": "A Platform for Open Science", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 10:40:00", + "time_stop": "2014-07-22 11:20:00", + "venue_serial": 1466, + "description": "This talk will introduce a new open source platform for citizen science data. It allows anyone anywhere to create online data sets by uploading data from their own environmental sensors, mobile devices, do-it-yourself science equipment, and other measurement tools. The talk will describe the design and use of the platform, covering multiple applications and alternatives.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34855", + "speakers": [169557], + "categories": [ + + "Education" + + ] + }, + + { + "serial": 34856, + "name": "Grow Developers. Grow Diversity. ", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 10:40:00", + "time_stop": "2014-07-22 11:20:00", + "venue_serial": 1464, + "description": "The United States needs more tech talent. Period.\r\n\r\nAnd yet there is a solution \u2014 Grow Developers. My talk will cover all the many ways this community can actively solve the lack of talent problem, and at the same time give solutions for also growing the female and minority tech populations.\r\n\r\nPeople will walk away with a 3-tiered approach for growing developers and growing diversity. ", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34856", + "speakers": [173388], + "categories": [ + + "Community" + + ] + }, + + { + "serial": 34860, + "name": "From Madison Avenue to git Checkout -- A Journey", + "event_type": "40-minute conference session", + + "time_start": "2014-07-24 11:50:00", + "time_stop": "2014-07-24 12:30:00", + "venue_serial": 1457, + "description": "This talk is going to provide insight into what\u2019s it\u2019s like to view Software Development as an outsider, who happens to be an experienced successful professional. I will also tackle the issues that are implicit with imposter syndrome, such as how to grow developers, how to grow diversity, fixing broken hiring practices, and what it\u2019s like to be afraid of open source. ", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34860", + "speakers": [173388,180122], + "categories": [ + + "Community", + + "Education" + + ] + }, + + { + "serial": 34863, + "name": "Keeping Open Source Open", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 10:40:00", + "time_stop": "2014-07-23 11:20:00", + "venue_serial": 1464, + "description": "What happens to an open source community full of hobbyists when the project cleans up its pile of spaghetti and chooses to adopt widely held programming paradigms and systems?", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34863", + "speakers": [173433], + "categories": [ + + "Community" + + ] + }, + + { + "serial": 34865, + "name": "Installing OpenStack using SaltStack", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 11:30:00", + "time_stop": "2014-07-22 12:10:00", + "venue_serial": 1459, + "description": "OpenStack is an open source implementation of cloud computing, potentially at very large scale. However, it has many moving parts and is complex to operate. \r\nSaltStack appears to provide scalable and secure orchestration for OpenStack.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34865", + "speakers": [143135], + "categories": [ + + "Cloud" + + ] + }, + + { + "serial": 34870, + "name": "Open Source: Emerging in Asia / The Asian Open Source Report Card ", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 16:10:00", + "time_stop": "2014-07-22 16:50:00", + "venue_serial": 1464, + "description": "Asia is a huge untapped market for open source expansion, but it is very unlike North America or Europe. Learn differences within Asia, see open source proliferation in all markets, and most importantly get into thinking Asia expansion is prime.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34870", + "speakers": [147], + "categories": [ + + "Business", + + "Community" + + ] + }, + + { + "serial": 34873, + "name": "Just Enough Math", + "event_type": "tutorial", + + "time_start": "2014-07-20 09:00:00", + "time_stop": "2014-07-20 12:30:00", + "venue_serial": 1471, + "description": "Advanced math for business people: \u201cjust enough math\u201d to take advantage of new classes of open source frameworks. Many take college math up to calculus, but never learn how to approach sparse matrices, complex graphs, or supply chain optimizations. This tutorial ties these pieces together into a conceptual whole, with use cases and simple Python code, as a new approach to computational thinking.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34873", + "speakers": [146540], + "categories": [ + + "Business", + + "Computational Thinking" + + ] + }, + + { + "serial": 34875, + "name": "Money Machines", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 17:00:00", + "time_stop": "2014-07-22 17:40:00", + "venue_serial": 1457, + "description": "Money Machines are small scale highly technical or craft based entrepreneurial excursions. The purpose of the Money Machine is to empower individuals with tools which will allow her/him/them to follow their geeky and nerdy passion of passions while enabling them to address the various financial necessities of life. Money Machines allow people to work in the manner of their choosing.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34875", + "speakers": [138530], + "categories": [ + + "Business" + + ] + }, + + { + "serial": 34881, + "name": "Building a Massively Scalable Web Server In Erlang", + "event_type": "tutorial", + + "time_start": "2014-07-21 13:30:00", + "time_stop": "2014-07-21 17:00:00", + "venue_serial": 1458, + "description": "Learn the fundamentals of Erlang - a high productivity, functional programming language used to build scalable, highly concurrent systems. In this tutorial, we'll introduce Erlang by way of a fun problem: building an HTTP server! You'll learn the basic of networking programming in Erlang along with key techniques for performance and scalability.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34881", + "speakers": [131729], + "categories": [ + + "JavaScript - HTML5 - Web", + + "Tools & Techniques" + + ] + }, + + { + "serial": 34882, + "name": "Highly Functional Programming in Perl", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 11:30:00", + "time_stop": "2014-07-23 12:10:00", + "venue_serial": 1465, + "description": "Functional programming is everywhere, hiding between imperative procedures. Stateless code with no side-effects may seem academic, but practical application of functional techniques leads to fewer bugs and cleaner code. Functional thinking is useful whether you're wrestling with a mess of copy-pasta or doing test-first development on some new object library.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34882", + "speakers": [6574], + "categories": [ + + "Perl" + + ] + }, + + { + "serial": 34888, + "name": "Cathedrals in the Cloud: Musings on APIs for the Web", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 14:30:00", + "time_stop": "2014-07-22 15:10:00", + "venue_serial": 1459, + "description": "With the rise of cloud-based services and Web APIs, it may be time to re-visit Raymond's 19 'lessons' from his book 'The Cathedral and the Bazaar' to see how they can be applied (and/or modified) to fit a world where much of the software we use is no longer installed locally and is often kept out of reach from most developers and users. \r\n", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34888", + "speakers": [108272], + "categories": [ + + "Cloud" + + ] + }, + + { + "serial": 34894, + "name": "Mobile and Multi-Device Web Development with Tritium", + "event_type": "40-minute conference session", + + "time_start": "2014-07-24 11:00:00", + "time_stop": "2014-07-24 11:40:00", + "venue_serial": 1449, + "description": "Tritium is a new open source language from the creator of the popular Sass and HAML languages that brings a modern approach to web development with transforms. In this talk, we'll introduce the Tritium language and the power of transform based approaches for separating content from presentation in the building of multi-device websites for desktops, smartphones, tablets, TVs, wearables and beyond.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34894", + "speakers": [122599], + "categories": [ + + "Emerging Languages" + + ] + }, + + { + "serial": 34897, + "name": "My Friends Keep Leaving and it is Ruining Board Games Day", + "event_type": "40-minute conference session", + + "time_start": "2014-07-24 11:00:00", + "time_stop": "2014-07-24 11:40:00", + "venue_serial": 1454, + "description": "These days, moving away doesn\u2019t put too much of a dampener on staying in touch with your friends. Unfortunately, it has had a severe effect on my regular board games day.\r\nSo, I thought, why not solve this problem with telepresence board gaming? Can\u2019t be too hard! This session will cover what's been done, and what problems are still out there I have no idea about how to solve?", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34897", + "speakers": [173452], + "categories": [ + + "Geek Lifestyle" + + ] + }, + + { + "serial": 34905, + "name": "OAuth2: The Swiss-Army Framework", + "event_type": "40-minute conference session", + + "time_start": "2014-07-24 11:50:00", + "time_stop": "2014-07-24 12:30:00", + "venue_serial": 1456, + "description": "If your application doesn't have APIs, it's probably written in Cold Fusion. Every application has APIs, and APIs need authentication. See how OAuth2 is robust enough to satisfy the demands of the enterprise, while still serving the smallest of side projects. ", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34905", + "speakers": [173314], + "categories": [ + + "Security" + + ] + }, + + { + "serial": 34906, + "name": "Mapbox: Building the Future of Open Mapping", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 11:30:00", + "time_stop": "2014-07-22 12:10:00", + "venue_serial": 1457, + "description": "Mapbox is leading the way in open mapping. Large companies are switching to OpenStreetMap and open source software for mapping. Learn how Mapbox is running a business like you would run an open source project and how it is succeeding in a field dominated by large, well-funded players by being open. ", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34906", + "speakers": [104652], + "categories": [ + + "Business" + + ] + }, + + { + "serial": 34913, + "name": "Data Workflows for Machine Learning", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 11:30:00", + "time_stop": "2014-07-23 12:10:00", + "venue_serial": 1452, + "description": "Several frameworks have emerged for handling data workflows. Meanwhile, business use of Machine Learning is less about algorithms and more about leveraging workflows. This talk compares/contrasts different workflow approaches with focus on use cases, plus how some leverage the PMML open standard. Summary points build a scorecard for evaluating frameworks based on your use case needs.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34913", + "speakers": [146540], + "categories": [ + + "Computational Thinking" + + ] + }, + + { + "serial": 34920, + "name": "Bringing Banking to the Poor with the Help of AngularJS", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 11:30:00", + "time_stop": "2014-07-23 12:10:00", + "venue_serial": 1457, + "description": "AngularJS is one of the most widely adopted open source Javascript frameworks in recent times. We use it for a not-so-typical use case: web apps to deliver financial services to the poor. In this case-study session, we analyze the pros/cons of AngularJS, establish why it was right for us, and go over our experiences using this powerful lightweight framework which adds value to our community daily.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34920", + "speakers": [173455], + "categories": [ + + "Business", + + "JavaScript - HTML5 - Web" + + ] + }, + + { + "serial": 34921, + "name": "Hacking Radio for Under $10", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 16:10:00", + "time_stop": "2014-07-22 16:50:00", + "venue_serial": 1465, + "description": "We'll explore how to use an $8 dollar DVB-T TV dongle to monitor and capture various radio frequencies. The RTL-SDR library turns a cheap Realtek DVB-T into a very powerful Software Defined Radio receiver which can be used to inspect and hack various wireless protocols. All of the code is Free and Open Source so it runs on all platforms.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34921", + "speakers": [77757], + "categories": [ + + "Open Hardware" + + ] + }, + + { + "serial": 34922, + "name": "Designing for Reuse: Creating APIs for the Future", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 16:10:00", + "time_stop": "2014-07-23 16:50:00", + "venue_serial": 1459, + "description": "Sometimes your API is meant for a small group and will live for only a short time. Other times, your aim is to create an interface that will have wide appeal and should last years into the future. \r\n\r\nThis talk shows you how to create and maintain an API that it can be both stable and vital well into the future.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34922", + "speakers": [108272], + "categories": [ + + "Cloud" + + ] + }, + + { + "serial": 34941, + "name": "LoopBack: Open Source mBaaS", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 11:30:00", + "time_stop": "2014-07-22 12:10:00", + "venue_serial": 1454, + "description": "Parse is a popular mobile Backend-as-a-Service allowing mobile developers to use backend APIs in conjunction with mobile apps. LoopBack is an open source mBaaS implementation that offers all the same functionality, is written in Node.js, and can be extended with Node.js' community of over 50,000 modules.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34941", + "speakers": [173465], + "categories": [ + + "Mobile Platforms" + + ] + }, + + { + "serial": 34942, + "name": "Forking Culture and Committing Ops in Government", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 16:10:00", + "time_stop": "2014-07-23 16:50:00", + "venue_serial": 1464, + "description": "Culture shift is a huge challenge in the public sector. I will walk through how the Consumer Financial Protection Bureau is able to successfully open source data, platforms, and standards.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34942", + "speakers": [156591], + "categories": [ + + "Community" + + ] + }, + + { + "serial": 34944, + "name": "Open Source Tools for the Polyglot Developer", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 14:30:00", + "time_stop": "2014-07-22 15:10:00", + "venue_serial": 1449, + "description": "Developers, increasingly, need to work in several different development languages. It is hard enough to remember all the bits and pieces of the languages themselves, do you really need to know all the unique toolchains to make them work?", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34944", + "speakers": [141524], + "categories": [ + + "Emerging Languages" + + ] + }, + + { + "serial": 34952, + "name": "Writing Documentation that Satisfies Your Users", + "event_type": "40-minute conference session", + + "time_start": "2014-07-24 11:50:00", + "time_stop": "2014-07-24 12:30:00", + "venue_serial": 1462, + "description": "Documentation is paramount to increasing an open source project's adoption and growth. But writing good documentation is hard. Using examples from new and mature projects, we'll explore detailed tactics for selecting, prioritizing, outlining, and writing documentation targeted at multiple audiences.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34952", + "speakers": [142111], + "categories": [ + + "User Experience" + + ] + }, + + { + "serial": 34953, + "name": "Adventures in the WebRTC Garden--or is it Wilderness?", + "event_type": "40-minute conference session", + + "time_start": "2014-07-24 10:00:00", + "time_stop": "2014-07-24 10:40:00", + "venue_serial": 1454, + "description": "The prospects and promise of webRTC--direct browser-to-browser multimedia communications--have led to an explosion of tools, both proprietary and Open Source. In this session we present an overview of a variety of tools vying for attention, along with a demonstration of the sipML Javascript toolkit, using webRTC-enabled browsers and the latest version of Asterisk.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34953", + "speakers": [6921,173464,173466,173467,173468], + "categories": [ + + "Emerging Languages" + + ] + }, + + { + "serial": 34954, + "name": "Introduction to Advanced Bash Usage", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 13:40:00", + "time_stop": "2014-07-23 14:20:00", + "venue_serial": 1475, + "description": "Broad introduction to Bash features for users who want to go beyond simple command execution. Covered topics include builtins, keywords, functions, parameters (arguments, variables, arrays, special parameters), parameter expansion and manipulation, compound commands (loops, groups, conditionals), and brace expansion.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34954", + "speakers": [173223], + "categories": [ + + "Tools & Techniques" + + ] + }, + + { + "serial": 34955, + "name": "Hacking the Kernel, Hacking Myself", + "event_type": "40-minute conference session", + + "time_start": "2014-07-24 11:50:00", + "time_stop": "2014-07-24 12:30:00", + "venue_serial": 1451, + "description": "I was chosen, out of eighteen successful applicants, to be one of four Linux kernel interns through the Gnome Outreach Program for Women. This is the story of my journey from a frustrated retail worker, dreaming of writing code for a living, to a full fledged kernel developer. ", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34955", + "speakers": [140811], + "categories": [ + + "Geek Lifestyle" + + ] + }, + + { + "serial": 35024, + "name": "Nymote: Git Your Own Cloud Here", + "event_type": "40-minute conference session", + + "time_start": "2014-07-24 11:00:00", + "time_stop": "2014-07-24 11:40:00", + "venue_serial": 1459, + "description": "If you want to run your own Internet node, it requires gluing together an awful lot of software, and maintaining it. We'll show you a fresh approach: use the Mirage operating system to easily compile the protocols you need (DNS, HTTP, XMPP and IMAP) for your Internet presence into a type-safe unikernel, and deploy the whole thing using just Travis CI and Git directly on the cloud or on ARM.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35024", + "speakers": [109140,159772], + "categories": [ + + "Cloud" + + ] + }, + + { + "serial": 35027, + "name": "Git and GitHub Essentials", + "event_type": "tutorial", + + "time_start": "2014-07-20 09:00:00", + "time_stop": "2014-07-20 12:30:00", + "venue_serial": 1450, + "description": "Learn everything you need to know from Git and GitHub to be the most effective member of your team, save yourself from any jam, and work with the rest of your team flawlessly.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35027", + "speakers": [152215], + "categories": [ + + "Tools & Techniques" + + ] + }, + + { + "serial": 35038, + "name": "A Reactive Game Stack: Using Erlang, Lua and VoltDB to Enable a Non-Sharded Game World ", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 17:00:00", + "time_stop": "2014-07-23 17:40:00", + "venue_serial": 1475, + "description": "Discover why Electronic Arts goes Erlang and hear about a powerful, reactive server architecture that supports a highly concurrent, analyzable and secure simulation stack for gaming. Learn how to easily script composable entities using a server environment purpose-built for event-driven programming, which is scalable under load, resilient and enables evaluation of huge data sets in real-time.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35038", + "speakers": [174072,174073], + "categories": [ + + "Databases & Datastores" + + ] + }, + + { + "serial": 35367, + "name": "Apache HTTP Server; SSL from End-to-End", + "event_type": "40-minute conference session", + + "time_start": "2014-07-24 11:00:00", + "time_stop": "2014-07-24 11:40:00", + "venue_serial": 1456, + "description": "This presentation covers all aspects of configuring Apache HTTP Server for https/TLS, including ECC, RSA and DH keys and key strength, cipher suites, SSL session caches vs. session tickets, OCSP stapling and TLS virtual hostnames. These elements are integrated to provide perfect forward secrecy and meet modern best practices for both client and proxied connections.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35367", + "speakers": [124630], + "categories": [ + + "Security" + + ] + }, + + { + "serial": 35390, + "name": "Building an Open Source Learning Thermostat", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 10:40:00", + "time_stop": "2014-07-22 11:20:00", + "venue_serial": 1451, + "description": "The Nest learning thermostat has won the hearts and minds of consumers everywhere by completely re-thinking how a thermostat works. In this session, we'll explore the Internet of Things by discussing how to build an amazing connected device using open source technology, with Spark's open source thermostat project acting as a case study.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35390", + "speakers": [175040], + "categories": [ + + "Open Hardware" + + ] + }, + + { + "serial": 35406, + "name": "Pinto: Hassle-Free Dependency Management for Perl", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 16:10:00", + "time_stop": "2014-07-22 16:50:00", + "venue_serial": 1451, + "description": "Managing CPAN dependencies can be a major frustration for Perl developers. In this session, you'll discover how to easily manage those dependencies by creating a private CPAN repository with Pinto.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35406", + "speakers": [173472], + "categories": [ + + "Perl" + + ] + }, + + { + "serial": 35437, + "name": "Open Source and the Enterprise", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 16:10:00", + "time_stop": "2014-07-23 16:50:00", + "venue_serial": 1457, + "description": "How can businesses take the best ideas from the open source community to improve their end product and the happiness of their developers? In this fireside-chat-styled session, Derek Sorkin from GitHub will talk with Tim Tyler about his experiences setting up a community inside Qualcomm that mimics an open source project. ", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35437", + "speakers": [175353,175354], + "categories": [ + + "Business" + + ] + }, + + { + "serial": 35444, + "name": "HTML5/Angular.js/Groovy/Java/MongoDB all together - what could possibly go wrong?", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 17:00:00", + "time_stop": "2014-07-23 17:40:00", + "venue_serial": 1448, + "description": "It seems to have been a common theme amongst startups to create the MVP\r\n(Minimum Viable Product) in a language that facilitates rapid\r\nprototyping (for example Ruby), and then migrate to the JVM when the\r\napplication has proved itself and requires more in terms of stability\r\nand performance.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35444", + "speakers": [132564], + "categories": [ + + "JavaScript - HTML5 - Web" + + ] + }, + + { + "serial": 35455, + "name": "CANCELLED: Using Multi-key AVL Trees to Simplify and Speed Up Complex Searches", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 14:30:00", + "time_stop": "2014-07-23 15:10:00", + "venue_serial": 1457, + "description": "Multiple indexes into data structures add complexity and slow down processing. A single multi-keyed AVL tree can allow complex searches to be constructed more easily and performed quickly, with a single O(lg N) lookup.\r\n\r\nIn this talk we will discuss how these trees work and how to implement them. Examples will be shown using python version 3, with C++ libraries for optimization of key routines.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35455", + "speakers": [31044], + "categories": [ + + "Computational Thinking" + + ] + }, + + { + "serial": 35464, + "name": "Real-time Engineering at Uber and the Evolution of an Event-Driven Architecture", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 17:00:00", + "time_stop": "2014-07-23 17:40:00", + "venue_serial": 1452, + "description": "Uber is one of the fastest growing companies in the world and the real-time engineering team are responsible for their mission critical Node.js-powered systems. Learn how they are adapting their services to be autonomous, loosely-coupled and highly-available by applying the principles of event-driven architecture.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35464", + "speakers": [175481], + "categories": [ + + "Computational Thinking" + + ] + }, + + { + "serial": 35493, + "name": "Coder Decoder: Functional Programmer Lingo Explained, with Pictures", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 10:40:00", + "time_stop": "2014-07-22 11:20:00", + "venue_serial": 1454, + "description": "For the uninitiated, a conversation with functional programmers can feel like ground zero of a jargon explosion. In this talk Lambda Ladies Co-Founder Katie Miller will help you to defend against the blah-blah blast by demystifying several terms commonly used by FP fans with bite-sized Haskell examples and friendly pictures. Expect appearances by Curry, Lens, and the infamous M-word, among others.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35493", + "speakers": [175488], + "categories": [ + + "Computational Thinking" + + ] + }, + + { + "serial": 35511, + "name": "Checking Your Privilege: A How-To for Hard Things", + "event_type": "Keynote", + + "time_start": "2014-07-23 09:45:00", + "time_stop": "2014-07-23 10:00:00", + "venue_serial": 1525, + "description": "The purpose of this talk is to reexamine the topic through the lens of concrete things individuals can do to check their privilege \u2013 and to put it to work serving themselves and others.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35511", + "speakers": [8837], + "categories": [ + + "Keynotes" + + ] + }, + + { + "serial": 35544, + "name": "A Multi-Platform Microsoft: Azure, ASP.NET, Open Source, Git and How We Build Things Now", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 11:30:00", + "time_stop": "2014-07-22 12:10:00", + "venue_serial": 1448, + "description": "A lot has changed at Microsoft. Azure has 1000 Linux VMs to choose from, there's RESTful APIs abound, and more OSS than ever before. What are Microsoft's web folks thinking and how are they developing software today? Is is a good thing?", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35544", + "speakers": [132865], + "categories": [ + + "Main Stage" + + ] + }, + + { + "serial": 35684, + "name": "Open Source Your Data Design Process", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 11:30:00", + "time_stop": "2014-07-23 12:10:00", + "venue_serial": 1448, + "description": "Design is a process, not a product. What processes do successful data designers follow, and how can we all benefit by open-sourcing our processes (to make better products)?", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35684", + "speakers": [147840], + "categories": [ + + "Main Stage" + + ] + }, + + { + "serial": 35743, + "name": "Anticipating the Future - An Introduction to Value Chain Mapping", + "event_type": "Keynote", + + "time_start": "2014-07-23 09:30:00", + "time_stop": "2014-07-23 09:45:00", + "venue_serial": 1525, + "description": "In this keynote, Simon will present the general principles of industry change and describe what can and cannot be predicted. He will then examine how companies can better understand the environment around them and by anticipating the nature of change then manipulate the market in their favor through open techniques.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35743", + "speakers": [6219], + "categories": [ + + "Keynotes" + + ] + }, + + { + "serial": 35744, + "name": "Threats", + "event_type": "Keynote", + + "time_start": "2014-07-23 09:10:00", + "time_stop": "2014-07-23 09:20:00", + "venue_serial": 1525, + "description": "What do you care about most in the worlds of software, the Net, and Life Online? Are you worried about it? Now is the time for sensible, reasonable, extreme paranoia.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35744", + "speakers": [24978], + "categories": [ + + "Keynotes" + + ] + }, + + { + "serial": 35847, + "name": "OSCON Kids Day (Sold Out)", + "event_type": "Event", + + "time_start": "2014-07-20 09:00:00", + "time_stop": "2014-07-20 17:00:00", + "venue_serial": 1587, + "description": "If you have a school aged children interested in learning more about computer programming, bring them to OSCON. We'll be hosting an entire day of workshops for kids about Java, Python, Scratch, Minecraft Modding, Arduino and more. ", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35847", + "speakers": [], + "categories": [ + + "Events" + + ] + }, + + { + "serial": 35848, + "name": "Ignite OSCON (sponsored by Orly Atomics)", + "event_type": "Event", + + "time_start": "2014-07-20 17:30:00", + "time_stop": "2014-07-20 19:00:00", + "venue_serial": 1525, + "description": "If you had five minutes on stage what would you say? What if you only got 20 slides and they rotated automatically after 15 seconds? Would you pitch a project? Launch a web site? Teach a hack? We\u2019ll find out at our annual Ignite event at OSCON.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35848", + "speakers": [], + "categories": [ + + "Events" + + ] + }, + + { + "serial": 35853, + "name": "OSCON 5K Glow Run and After Party", + "event_type": "Event", + + "time_start": "2014-07-20 20:30:00", + "time_stop": "2014-07-20 22:00:00", + "venue_serial": 1606, + "description": "Don't forget to pack your running shoes and your glow-in-the-dark gear, because the OSCON 5K fun run is back. Whether you are an avid runner or just starting out, you are invited to join other OSCON attendees Sunday evening for a run/jog/walk through some of the most scenic and emblematic sites of Portland. ", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35853", + "speakers": [], + "categories": [ + + "Events" + + ] + }, + + { + "serial": 35854, + "name": "Expo Hall Opening Reception (sponsored by Bluehost)", + "event_type": "Event", + + "time_start": "2014-07-21 17:00:00", + "time_stop": "2014-07-21 18:00:00", + "venue_serial": 1474, + "description": "Grab a drink and kick off OSCON by meeting and mingling with exhibitors and fellow attendees.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35854", + "speakers": [], + "categories": [ + + "Events" + + ] + }, + + { + "serial": 35855, + "name": "OSCON Elements Attendee Party", + "event_type": "Event", + + "time_start": "2014-07-21 18:00:00", + "time_stop": "2014-07-21 20:00:00", + "venue_serial": 1585, + "description": "This year's attendee party focuses on the four classical elements--fire, earth, air, and water. Wait till you see how each of these essential ideas transforms Hall B into new areas to explore and savor. Trust us, this is one party you don't want to miss! ", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35855", + "speakers": [], + "categories": [ + + "Events" + + ] + }, + + { + "serial": 35856, + "name": "Puppet Labs Party", + "event_type": "Event", + + "time_start": "2014-07-21 20:00:00", + "time_stop": "2014-07-21 22:00:00", + "venue_serial": 1626, + "description": "Join Puppet Labs for our OSCON \u201cOpen\u201d House Party! We are excited to open our doors to all our OSCON and Puppet Labs Friends.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35856", + "speakers": [], + "categories": [ + + "Events" + + ] + }, + + { + "serial": 35857, + "name": "Booth Crawl", + "event_type": "Event", + + "time_start": "2014-07-22 17:40:00", + "time_stop": "2014-07-22 19:00:00", + "venue_serial": 1474, + "description": "Quench your thirst with vendor-hosted libations and snacks while you check out all the cool stuff in the expo hall. ", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35857", + "speakers": [], + "categories": [ + + "Events" + + ] + }, + + { + "serial": 35858, + "name": "Citrix Open Cloud Poker Party", + "event_type": "Event", + + "time_start": "2014-07-22 20:30:00", + "time_stop": "2014-07-22 23:30:00", + "venue_serial": 1473, + "description": "*8:30pm - 12:00am*\r\n\r\nCitrix is sponsoring a night of poker, cocktails and hors d'oeuvres. For one night only, OSCON\u2019s Foyer will be transformed into Portland\u2019s only poker room complete with professional dealers. You'll be playing poker above the city lights with a perfect view of the city.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35858", + "speakers": [], + "categories": [ + + "Events" + + ] + }, + + { + "serial": 35859, + "name": "Closing Get Together", + "event_type": "Event", + + "time_start": "2014-07-24 13:15:00", + "time_stop": "2014-07-24 14:00:00", + "venue_serial": 1473, + "description": "Take the opportunity to network one last time and exchange contact information with one another. Drinks and snacks provided. ", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35859", + "speakers": [], + "categories": [ + + "Events" + + ] + }, + + { + "serial": 35864, + "name": "Something To Remember", + "event_type": "Keynote", + + "time_start": "2014-07-23 09:05:00", + "time_stop": "2014-07-23 09:10:00", + "venue_serial": 1525, + "description": "Keynote by Piers Cawley, Perl programmer, singer and balloon modeller.\r\n", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35864", + "speakers": [75349], + "categories": [ + + "Keynotes" + + ] + }, + + { + "serial": 35883, + "name": "Open Source and Social Change \u2014 Making the World a Better Place", + "event_type": "Keynote", + + "time_start": "2014-07-24 12:45:00", + "time_stop": "2014-07-24 13:10:00", + "venue_serial": 1525, + "description": "Keynote by Paul Fenwick, managing director of Perl Training Australia.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35883", + "speakers": [6631], + "categories": [ + + "Keynotes" + + ] + }, + + { + "serial": 35913, + "name": "Introvert? Extrovert? Klingon? We've Got You Covered.", + "event_type": "Keynote", + + "time_start": "2014-07-22 09:30:00", + "time_stop": "2014-07-22 09:45:00", + "venue_serial": 1525, + "description": "Keynote by Wendy Chisholm, Senior Accessibility Strategist and Universal Design Evangelist, Microsoft.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35913", + "speakers": [17417], + "categories": [ + + "Keynotes" + + ] + }, + + { + "serial": 35915, + "name": "Opening Welcome and Keynotes", + "event_type": "Keynote", + + "time_start": "2014-07-22 09:00:00", + "time_stop": "2014-07-22 09:05:00", + "venue_serial": 1525, + "description": "Opening remarks by OSCON program chairs, Matthew McCullough, Sarah Novotny and Simon St. Laurent. ", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35915", + "speakers": [74852,76338,3476], + "categories": [ + + ] + }, + + { + "serial": 35950, + "name": "Announcements & Keynotes", + "event_type": "Keynote", + + "time_start": "2014-07-23 09:00:00", + "time_stop": "2014-07-23 09:05:00", + "venue_serial": 1525, + "description": "Wednesday announcements and remarks by OSCON program chairs, Matthew McCullough, Sarah Novotny and Simon St. Laurent. We'll be announcing more keynote speakers here soon.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35950", + "speakers": [76338,74852,3476], + "categories": [ + + ] + }, + + { + "serial": 35951, + "name": "Announcements & Keynotes", + "event_type": "Keynote", + + "time_start": "2014-07-24 09:00:00", + "time_stop": "2014-07-24 09:05:00", + "venue_serial": 1525, + "description": "Thursday announcements and remarks by OSCON program chairs, Matthew McCullough, Sarah Novotny and Simon St. Laurent. We'll be announcing more keynote speakers here soon.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35951", + "speakers": [74852,76338,3476], + "categories": [ + + ] + }, + + { + "serial": 35956, + "name": "The Wonders of Programming", + "event_type": "Keynote", + + "time_start": "2014-07-22 09:05:00", + "time_stop": "2014-07-22 09:20:00", + "venue_serial": 1525, + "description": "Kids can start to learn to program at any age; I started at six. All I needed was tools, guidance, and encouragement. Once I got hooked, a whole new world of possibilities opened up for me. I could create my own video games instead of being restricted by the rules of games made by others. ", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35956", + "speakers": [177245], + "categories": [ + + "Keynotes" + + ] + }, + + { + "serial": 35988, + "name": "Discover OpenUI5 \u2013 The New Web UI library from SAP", + "event_type": "Event", + + "time_start": "2014-07-21 09:00:00", + "time_stop": "2014-07-21 12:30:00", + "venue_serial": 1462, + "description": "OpenUI5 is a powerful web UI library from SAP that has recently entered the open source world. With OpenUI5 you can easily develop enterprise-grade responsive web applications that run on multiple platforms. It is based on many open source libraries. Start from scratch and learn how to build OpenUI5 applications in this tutorial.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35988", + "speakers": [173233,104828,170822], + "categories": [ + + "Sponsored Tutorials" + + ] + }, + + { + "serial": 36005, + "name": "Migrating Data from MySQL and Oracle into Hadoop", + "event_type": "BoF", + + "time_start": "2014-07-22 20:00:00", + "time_stop": "2014-07-22 21:00:00", + "venue_serial": 1464, + "description": "Open discussion to talk about the methods, tricks and different tools available for copying and replicating data from traditional RDBMS into Hadoop, covering best practices and customer stories.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/36005", + "speakers": [], + "categories": [ + + ] + }, + + { + "serial": 36014, + "name": "Hadoop Get-together", + "event_type": "BoF", + + "time_start": "2014-07-22 19:00:00", + "time_stop": "2014-07-22 20:00:00", + "venue_serial": 1464, + "description": "Do you Hadoop? Or at least interested in stories from people who do?\r\nWe will meet to share our experience and trade tips about working with\r\nthe open source data storage and processing framework - Hadoop.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/36014", + "speakers": [], + "categories": [ + + ] + }, + + { + "serial": 36065, + "name": "Docker BoF", + "event_type": "BoF", + + "time_start": "2014-07-20 19:00:00", + "time_stop": "2014-07-20 20:00:00", + "venue_serial": 1607, + "description": "Come join some of the team from Docker and get your questions answered and your problems resolved!", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/36065", + "speakers": [], + "categories": [ + + ] + }, + + { + "serial": 36174, + "name": "Distributed Caching", + "event_type": "BoF", + + "time_start": "2014-07-22 20:00:00", + "time_stop": "2014-07-22 21:00:00", + "venue_serial": 1454, + "description": "Caching at scale might be troublesome and sometimes require adapting your usage patterns to really take advantage of the employed solutions.\r\nMoving the business logic to the caching subsystem and allowing horizontal scaling might be a key factor to minimise the impact of introducing a caching layer in your infrastructure.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/36174", + "speakers": [], + "categories": [ + + ] + }, + + { + "serial": 36180, + "name": "Hands-on CloudStack Ecosystem Tutorial", + "event_type": "Event", + + "time_start": "2014-07-21 13:30:00", + "time_stop": "2014-07-21 17:00:00", + "venue_serial": 1462, + "description": "Building a cloud is one part of the equation. To get work done with a cloud you need a solid ecosystem that goes with it. In this hands-on tutorial we go through some of the tools in the CloudStack ecosystem: Cloudmonkey, Libcloud, Vagrant, Ansible, and Ec2stack. Come ready to learn and use a cloud.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/36180", + "speakers": [152123], + "categories": [ + + "Sponsored Tutorials" + + ] + }, + + { + "serial": 36202, + "name": "Building an API for the Planet with a New Approach to Satellites", + "event_type": "Keynote", + + "time_start": "2014-07-22 09:55:00", + "time_stop": "2014-07-22 10:10:00", + "venue_serial": 1525, + "description": "Keynote by Will Marshall, CEO of Planet Labs. ", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/36202", + "speakers": [164144], + "categories": [ + + "Keynotes" + + ] + }, + + { + "serial": 36203, + "name": "Google Summer of Code BOF", + "event_type": "BoF", + + "time_start": "2014-07-20 19:00:00", + "time_stop": "2014-07-20 20:00:00", + "venue_serial": 1456, + "description": "Meetup for students, mentors, and org admins who have participated or are participating in Google Summer of Code. Also come to learn more about Google Summer of Code if you think you might be interested in participating in a future year!", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/36203", + "speakers": [], + "categories": [ + + ] + }, + + { + "serial": 36257, + "name": "Yes, Your Refrigerator Is Trying To Kill You: Bad Actors and the Internet of Things", + "event_type": "Keynote", + + "time_start": "2014-07-24 09:20:00", + "time_stop": "2014-07-24 09:30:00", + "venue_serial": 1525, + "description": "As more and more atypical devices are internet enabled, operating system providers need to look at the longer term impacts and plan accordingly. How can CE manufactures keep devices up to date and secure over the lifetime of the device. What does it look like when we fail to plan to do so? How can the open source way solve some of these problems.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/36257", + "speakers": [178139], + "categories": [ + + "Keynotes" + + ] + }, + + { + "serial": 36404, + "name": "ELK Stack BoF", + "event_type": "BoF", + + "time_start": "2014-07-23 19:00:00", + "time_stop": "2014-07-23 20:00:00", + "venue_serial": 1460, + "description": "If you use and love the ELK stack (that's Elasticsearch for search & analytics, Logstash for centralized logging & Kibana for beautiful visualizations), join us for an evening of discussion about these three open source tools and how they make developers and sysadmins lives way better. And your business humans, too. ", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/36404", + "speakers": [], + "categories": [ + + ] + }, + + { + "serial": 36494, + "name": "O'Reilly Open Source Awards", + "event_type": "Keynote", + + "time_start": "2014-07-24 12:40:00", + "time_stop": "2014-07-24 12:45:00", + "venue_serial": 1525, + "description": "The 10th Annual O\u2019Reilly Open Source Award winners will be announced. ", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/36494", + "speakers": [], + "categories": [ + + "Keynotes" + + ] + }, + + { + "serial": 36818, + "name": "The Concert Programmer", + "event_type": "Keynote", + + "time_start": "2014-07-24 09:05:00", + "time_stop": "2014-07-24 09:20:00", + "venue_serial": 1525, + "description": "In this presentation Andrew will be live-coding the generative algorithms that will be producing the music that the audience will be listening too. As Andrew is typing he will also attempt to narrate the journey, discussing the various computational and musical choices made along the way. A must see for anyone interested in creative computing.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/36818", + "speakers": [178738], + "categories": [ + + "Keynotes" + + ] + }, + + { + "serial": 36819, + "name": "Programmer, Program, Machine and Environment", + "event_type": "40-minute conference session", + + "time_start": "2014-07-24 10:00:00", + "time_stop": "2014-07-24 10:40:00", + "venue_serial": 1448, + "description": "In this talk, Andrew will delve deeper into the technical and philosophical underpinnings of live-coding as an artistic performance practice. Using his own Extempore language as a reference, Andrew will demonstrate, in a very hands on way, how live-coding works in practice, from both an end-user perspective, as well as a systems design perspective. ", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/36819", + "speakers": [178738], + "categories": [ + + "Main Stage" + + ] + }, + + { + "serial": 36848, + "name": "Mozilla Webmaker ", + "event_type": "BoF", + + "time_start": "2014-07-20 19:00:00", + "time_stop": "2014-07-20 20:00:00", + "venue_serial": 1457, + "description": "Webmaker is a collection of innovative tools and curricula for a global community that is teaching the web. ", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/36848", + "speakers": [], + "categories": [ + + ] + }, + + { + "serial": 36849, + "name": "Designing Projects for Particpation", + "event_type": "BoF", + + "time_start": "2014-07-22 19:00:00", + "time_stop": "2014-07-22 20:00:00", + "venue_serial": 1461, + "description": "Designing for Participation was created in order to get Mozilla community members to think about how they can structure the work Mozilla does to better enable contributions from anywhere. This BOF will lead discussion through a workshop designed by Mozilla to help project leaders Design projects for participation", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/36849", + "speakers": [], + "categories": [ + + ] + }, + + { + "serial": 36890, + "name": "Understanding Hypervisor Selection in Apache CloudStack", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 14:30:00", + "time_stop": "2014-07-23 15:10:00", + "venue_serial": 1460, + "description": "Apache CloudStack enables cloud operators to quickly create scalable clouds with support for multiple hypervisors. Choice is wonderful, but also requires an understanding of how hypervisor features integrate with CloudStack. In this session we'll look at the options and provide a template for deployment success.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/36890", + "speakers": [141574], + "categories": [ + + "Sponsored Sessions" + + ] + }, + + { + "serial": 36923, + "name": "OSCON Town Hall", + "event_type": "Keynote", + + "time_start": "2014-07-24 11:50:00", + "time_stop": "2014-07-24 12:30:00", + "venue_serial": 1464, + "description": "OSCON belongs to its attendees, and we want to hear what you think of this year\u2019s show. Join the organizers to talk about what you loved and hated about OSCON, and what you\u2019d like to see next year.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/36923", + "speakers": [10,74852,76338,3476], + "categories": [ + + "Keynotes" + + ] + }, + + { + "serial": 36934, + "name": "Open Cloud Day", + "event_type": "Event", + + "time_start": "2014-07-21 09:00:00", + "time_stop": "2014-07-21 17:00:00", + "venue_serial": 1459, + "description": "Open Cloud Day at OSCON will cover the latest innovations in public and private clouds, IaaS, and PaaS platforms. You'll learn from industry practitioners from a variety of platforms, who will share their expertise, and provide you with a vision of where open source in the cloud is heading. ", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/36934", + "speakers": [], + "categories": [ + + "Events", + + "Sponsored Tutorials" + + ] + }, + + { + "serial": 37002, + "name": "Crash Course in Open Source Cloud Computing", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 11:30:00", + "time_stop": "2014-07-22 12:10:00", + "venue_serial": 1461, + "description": "The open source mantra is to release early and release often. That means software velocity can be difficult to keep up with. This discussion will expand on the latest open source software used to deliver and manage cloud computing infrastructure. Topics covered include virtualization (KVM, Xen Project, LXC), orchestration (OpenStack, CloudStack, Eucalyptus), and other complimentary technology.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37002", + "speakers": [6653], + "categories": [ + + "Sponsored Sessions" + + ] + }, + + { + "serial": 37014, + "name": "OpenStack Redefining 'Core' Using Community, Tests and Selected Upstream Code", + "event_type": "Open Cloud Day", + + "time_start": "2014-07-21 11:30:00", + "time_stop": "2014-07-21 12:00:00", + "venue_serial": 1584, + "description": "Discuss how OpenStack works towards an interoperable open IaaS using a process we call DefCore. We'll review how we're creating a process that is open, collaborative, technically relevant and principles driven.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37014", + "speakers": [122917], + "categories": [ + + "Cloud" + + ] + }, + + { + "serial": 37016, + "name": "The quantitative state of the Open Cloud", + "event_type": "Open Cloud Day", + + "time_start": "2014-07-21 15:45:00", + "time_stop": "2014-07-21 16:15:00", + "venue_serial": 1584, + "description": "The talk will present a quantitative analysis of the projects producing the main free, open source software cloud platforms: OpenStack, Apache CloudStack, OpenNebula and Eucalyptus. The analysis will focus on the communities behind those projects, their main development parameters, and the trends that can be observed.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37016", + "speakers": [173116], + "categories": [ + + "Cloud" + + ] + }, + + { + "serial": 37020, + "name": "Building an Open Cloud From the Network Perspective ", + "event_type": "Open Cloud Day", + + "time_start": "2014-07-21 16:15:00", + "time_stop": "2014-07-21 16:45:00", + "venue_serial": 1584, + "description": "When designing and building a private cloud often times the network is not treated as a first class citizen and even sometimes dealt with as an after thought. This presentation will look at the process of building a cloud from the network view. We will look at some best practices for configuring servers and switches in a rack as the basis for the cloud deployment and on going management. ", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37020", + "speakers": [122424], + "categories": [ + + "Cloud" + + ] + }, + + { + "serial": 37021, + "name": "Behind the Scenes: How We Produce OpenStack", + "event_type": "Open Cloud Day", + + "time_start": "2014-07-21 14:00:00", + "time_stop": "2014-07-21 14:30:00", + "venue_serial": 1584, + "description": "The last OpenStack release was the result of the combined work of more than 1200 contributors. How can all those people be coordinated and deliver results on schedule, with no classic management structure ?", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37021", + "speakers": [109289], + "categories": [ + + "Cloud" + + ] + }, + + { + "serial": 37030, + "name": "Open Source in Enterprise Software", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 10:40:00", + "time_stop": "2014-07-22 11:20:00", + "venue_serial": 1461, + "description": "This session is aimed at providing an understanding of why, where, and how SAP is engaged in adopting and leading Open Source projects in the enterprise software space. ", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37030", + "speakers": [180019], + "categories": [ + + "Sponsored Sessions" + + ] + }, + + { + "serial": 37039, + "name": "Office Hour with Gwen Shapira (Cloudera) ", + "event_type": "Office Hours", + + "time_start": "2014-07-23 15:20:00", + "time_stop": "2014-07-23 16:00:00", + "venue_serial": 1546, + "description": "Gwen\u2019s got answers when it comes to Hadoop, R, analytics and more. She\u2019s happy to talk to you about getting started with Hadoop; R, Python and data analysis with Hadoop; architecture, design, and implementation of Hadoop applications; and anything else you\u2019d like to bring up.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37039", + "speakers": [126882], + "categories": [ + + "Office Hours" + + ] + }, + + { + "serial": 37042, + "name": "Office Hour with Jono Bacon (XPRIZE Foundation)", + "event_type": "Office Hours", + + "time_start": "2014-07-22 10:40:00", + "time_stop": "2014-07-22 11:20:00", + "venue_serial": 1546, + "description": "If you have questions about community management and leadership, hiring community managers, or managing community relationships, come by and talk to Jono. He\u2019ll discuss building collaborative workflow and tooling, conflict resolution and managing complex personalities, and building buzz and excitement around your community.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37042", + "speakers": [108813], + "categories": [ + + "Office Hours" + + ] + }, + + { + "serial": 37043, + "name": "Office Hour with Stephen OSullivan (Silicon Valley Data Science) ", + "event_type": "Office Hours", + + "time_start": "2014-07-23 15:20:00", + "time_stop": "2014-07-23 16:00:00", + "venue_serial": 1547, + "description": "Come by and talk to Stephen about anything related to big data, Hadoop, and creating scalable, high-availability, data and applications solutions\u2014including data pipelines, data platforms, and Fourier analysis. ", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37043", + "speakers": [133624], + "categories": [ + + "Office Hours" + + ] + }, + + { + "serial": 37044, + "name": "Office Hour with Garth Braithwaite (Adobe)", + "event_type": "Office Hours", + + "time_start": "2014-07-23 13:40:00", + "time_stop": "2014-07-23 14:20:00", + "venue_serial": 1546, + "description": "Garth has found that the topic of open source is a bit controversial when people start talk about applying it to the visual and experience design process. He\u2019d like to talk with open source contributors about how the design process could be worked into specific projects, and the proposal (as well its rebuttals) that design should be done in the open.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37044", + "speakers": [173248], + "categories": [ + + "Office Hours" + + ] + }, + + { + "serial": 37045, + "name": "Office Hour with Constantine Aaron Cois (Carnegie Mellon University, Software Engineering Institute) ", + "event_type": "Office Hours", + + "time_start": "2014-07-22 11:30:00", + "time_stop": "2014-07-22 12:10:00", + "venue_serial": 1546, + "description": "Constantine will be more than happy to discuss Node.js or anything else related to programming, software architecture, web apps, and other topics. Let\u2019s have some fun! Dive into Node.js programming and design patterns, event loops and high concurrency apps, reactive apps, and the Meteor framework.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37045", + "speakers": [141235], + "categories": [ + + "Office Hours" + + ] + }, + + { + "serial": 37046, + "name": "Office Hour with Harrison Mebane (Silicon Valley Data Science) and Stephen OSullivan (Silicon Valley Data Science)", + "event_type": "Office Hours", + + "time_start": "2014-07-23 16:10:00", + "time_stop": "2014-07-23 16:50:00", + "venue_serial": 1546, + "description": "Can computers tell if trains run on time? Harrison and Stephen are ready to discuss this topic and other matters related to the Internet of Things and data\u2014including data pipelines, data platforms, and Fourier analysis.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37046", + "speakers": [171621,133624], + "categories": [ + + "Office Hours" + + ] + }, + + { + "serial": 37047, + "name": "Office Hour with J\u00e9r\u00f4me Petazzoni (Docker Inc.) ", + "event_type": "Office Hours", + + "time_start": "2014-07-23 10:40:00", + "time_stop": "2014-07-23 11:20:00", + "venue_serial": 1546, + "description": "Jerome has worked with Docker since its inception, and before that, built the dotCloud PAAS. He\u2019ll be glad to share his experiences in those domains, and discuss everything about Docker and containers, including how to make containers secure; service discovery, network integration, scaling, and failover; and containers for desktop applications.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37047", + "speakers": [151611], + "categories": [ + + "Office Hours" + + ] + }, + + { + "serial": 37048, + "name": "Office Hour with Heather VanCura (Java Community Process JCP) ", + "event_type": "Office Hours", + + "time_start": "2014-07-22 14:30:00", + "time_stop": "2014-07-22 15:10:00", + "venue_serial": 1546, + "description": "The Adopt-a-JSR and Adopt OpenJDK programs have gained worldwide community participation in the past two years. Come discuss the details with Heather and find out how you can contribute to better, more practical standards. She\u2019ll talk about upcoming changes aimed at broadening participation in the standards process. ", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37048", + "speakers": [65329], + "categories": [ + + "Office Hours" + + ] + }, + + { + "serial": 37049, + "name": "Office Hour with Kara Sowles (Puppet Labs) ", + "event_type": "Office Hours", + + "time_start": "2014-07-22 15:20:00", + "time_stop": "2014-07-22 16:00:00", + "venue_serial": 1546, + "description": "Kara is always eager to swap user group tips with you. Come by and pick her brain about ways to remove common obstacles that user groups encounter, and how to craft welcoming, friendly meetings and events. Find out how to plan the right events for your community.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37049", + "speakers": [142767], + "categories": [ + + "Office Hours" + + ] + }, + + { + "serial": 37051, + "name": "Office Hour with Michael Bleigh (Divshot) ", + "event_type": "Office Hours", + + "time_start": "2014-07-22 13:40:00", + "time_stop": "2014-07-22 14:20:00", + "venue_serial": 1546, + "description": "Come by and talk to Michael about single-page web applications, static site generators, and static content hosting. He\u2019s also willing to tackle other issues such as CORS web services, browser technology, and web components.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37051", + "speakers": [2593], + "categories": [ + + "Office Hours" + + ] + }, + + { + "serial": 37052, + "name": "Office Hour with Michael Hunger (Neo Technology) ", + "event_type": "Office Hours", + + "time_start": "2014-07-22 10:40:00", + "time_stop": "2014-07-22 11:20:00", + "venue_serial": 1547, + "description": "Graph Databases take a different approach than relational and aggregate-oriented NoSQL databases. They make connections cheap and fast by making them an explicit part and first-level citizen of your database. Michael will discuss graph-enabled applications, answer questions about their pros and cons, and help you model your domain and use-cases in an interactive way. ", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37052", + "speakers": [159719], + "categories": [ + + "Office Hours" + + ] + }, + + { + "serial": 37053, + "name": "Office Hour with James Turnbull (Docker) ", + "event_type": "Office Hours", + + "time_start": "2014-07-22 10:40:00", + "time_stop": "2014-07-22 11:20:00", + "venue_serial": 1548, + "description": "If you\u2019re ready to learn about Docker for building, shipping, and running distributed applications, James is ready to talk Docker with you. You\u2019ll not only find out how to get started, but also learn about Docker use cases, the Docker roadmap, and how to integrate Docker with Puppet, Chef, SaltStack, or Ansible.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37053", + "speakers": [5060], + "categories": [ + + "Office Hours" + + ] + }, + + { + "serial": 37054, + "name": "Office Hour with Catherine Farman (Happy Cog) and Corinne Warnshuis (Girl Develop It Philadelphia) ", + "event_type": "Office Hours", + + "time_start": "2014-07-23 11:30:00", + "time_stop": "2014-07-23 12:10:00", + "venue_serial": 1546, + "description": "You want to get more women involved in open source projects and communities. And Catherine and Corinne want to help. Come by find out how you can organize communities that are welcoming to women, and how you can be an ally to underrepresented minorities in tech. Start or help a Girl Develop It chapter, and learn about other nonprofits you can and should partner with both nationally and locally.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37054", + "speakers": [169992,173025], + "categories": [ + + "Office Hours" + + ] + }, + + { + "serial": 37055, + "name": "Open Cloud Standards In The Real World", + "event_type": "Open Cloud Day", + + "time_start": "2014-07-21 15:15:00", + "time_stop": "2014-07-21 15:45:00", + "venue_serial": 1584, + "description": "In the range of API and protocol development from free-form to totally static, there is room in open clouds for patterns that allow both for dynamic discovery and predictable reuse. This talk highlights open cloud standards-based methods that have stood up to testing in the National Science Foundation's Cloud and Autonomic Computing Center Standards Testing Lab under real-world conditions.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37055", + "speakers": [180050], + "categories": [ + + "Cloud" + + ] + }, + + { + "serial": 37056, + "name": "Office Hour with Shadaj Laddad (School) ", + "event_type": "Office Hours", + + "time_start": "2014-07-22 10:40:00", + "time_stop": "2014-07-22 11:20:00", + "venue_serial": 1579, + "description": "Shadaj is a 14 year old student who loves to program. He\u2019s happy to chat about anything from Scala programming to technology in schools today. He\u2019ll answer your questions about kids programming, including how to get started and what tools are available. And he\u2019ll engage you on topics such bioinformatics algorithms, Android, web development, and game programming.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37056", + "speakers": [177245], + "categories": [ + + "Office Hours" + + ] + }, + + { + "serial": 37058, + "name": "Open Is As Open Does", + "event_type": "Open Cloud Day", + + "time_start": "2014-07-21 13:30:00", + "time_stop": "2014-07-21 14:00:00", + "venue_serial": 1584, + "description": "'The Cloud' was built on Open Source. In no way does anything resembling IaaS 'cloud' exist without the foundation of freely available open source operating systems and virtualization, but what does 'Open' mean to the user of a service? Does it matter if the code behind a service is open? How would one even know? Do other aspects and definitions of openness become more important than source code?", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37058", + "speakers": [24052], + "categories": [ + + "Cloud" + + ] + }, + + { + "serial": 37059, + "name": "Office Hour with Harry Percival (Harry Percival) ", + "event_type": "Office Hours", + + "time_start": "2014-07-22 11:30:00", + "time_stop": "2014-07-22 12:10:00", + "venue_serial": 1547, + "description": "Whether the testing goat is steering you right or wrong, you\u2019ll want to join Harry for an informal discussion of testing and TDD. Find out how to get started with testing and how to get the most value from it. Learn the relative merits of pure isolated unit tests, integrated tests, and functional tests. And explore how to adapt your approach for different project types.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37059", + "speakers": [180056], + "categories": [ + + "Office Hours" + + ] + }, + + { + "serial": 37061, + "name": "Office Hour with Florian Haas (hastexo) ", + "event_type": "Office Hours", + + "time_start": "2014-07-22 16:10:00", + "time_stop": "2014-07-22 16:50:00", + "venue_serial": 1546, + "description": "Tap Florian\u2019s brain about all things OpenStack, and take advantage of his broad experience in real-life production OpenStack deployments. Whether it\u2019s OpenStack strategy, organizational concerns, or getting down into the nitty-gritty aspects of OpenStack technology, Florian is happy to talk to you about it. ", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37061", + "speakers": [131884], + "categories": [ + + "Office Hours" + + ] + }, + + { + "serial": 37063, + "name": "Office Hour with A. Jesse Jiryu Davis (MongoDB) ", + "event_type": "Office Hours", + + "time_start": "2014-07-23 16:10:00", + "time_stop": "2014-07-23 16:50:00", + "venue_serial": 1547, + "description": "Come by and have an informal chat with Jesse about MongoDB and Python-related topics, such as Python\u2019s async frameworks, using MongoDB with Python, and MongoDB in general.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37063", + "speakers": [172536], + "categories": [ + + "Office Hours" + + ] + }, + + { + "serial": 37066, + "name": "Office Hour with Matt Stine (Pivotal) ", + "event_type": "Office Hours", + + "time_start": "2014-07-23 14:30:00", + "time_stop": "2014-07-23 15:10:00", + "venue_serial": 1546, + "description": "Why would a Java developer want to learn the Go programming language? Matt is ready to have an informal chat with you about it. Find out how the idiomatic use of Go interfaces encourage you to structure your programs differently (as opposed to Java\u2019s object-oriented constructs), and how Go\u2019s built-in concurrency features make writing concurrent software accessible to mere mortals. ", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37066", + "speakers": [173088], + "categories": [ + + "Office Hours" + + ] + }, + + { + "serial": 37067, + "name": "Office Hour with Anne Ogborn (Robokind) ", + "event_type": "Office Hours", + + "time_start": "2014-07-23 10:40:00", + "time_stop": "2014-07-23 11:20:00", + "venue_serial": 1547, + "description": "Ann is ready to talk SWI-Prolog with you\u2014and help you get it installed. She\u2019ll chat with you about your planned uses for Prolog, talk you out of the \u201cwrap my rules engine in a real language\u201d mentality, and help you get through the \u201chey, 2+2 = 4 fails, wtf?\u201d stage. And she\u2019ll laugh evilly when you mention Tomcat.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37067", + "speakers": [172986], + "categories": [ + + "Office Hours" + + ] + }, + + { + "serial": 37068, + "name": "Office Hour with Matt Ray (Chef Software, Inc.) ", + "event_type": "Office Hours", + + "time_start": "2014-07-23 16:10:00", + "time_stop": "2014-07-23 16:50:00", + "venue_serial": 1548, + "description": "Chef questions? Bring any and all directly to Matt. He\u2019s ready to discuss how Chef is used to manage the deployment of OpenStack as well as the infrastructure on top of OpenStack, and how to get involved with the Chef and OpenStack community. Beyond that, the sky\u2019s the limit.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37068", + "speakers": [141169], + "categories": [ + + "Office Hours" + + ] + }, + + { + "serial": 37069, + "name": "Office Hour with Phil Webb (Pivotal) ", + "event_type": "Office Hours", + + "time_start": "2014-07-22 13:40:00", + "time_stop": "2014-07-22 14:20:00", + "venue_serial": 1547, + "description": "If there\u2019s anything you want to know about Spring Boot or the Spring Framework in general, Phil is ready to help. He\u2019ll talk to you about an array of issues, such as developing micro-services with Spring, how Pivotal developed the new \u201cspring.io\u201d website, and how to become a full-stack Java developer.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37069", + "speakers": [171565], + "categories": [ + + "Office Hours" + + ] + }, + + { + "serial": 37071, + "name": "Office Hour with Jamie Allen (Typesafe) ", + "event_type": "Office Hours", + + "time_start": "2014-07-23 11:30:00", + "time_stop": "2014-07-23 12:10:00", + "venue_serial": 1547, + "description": "Reactive applications are taking over the enterprise world. Join Jamie to chat about various Reactive topics. He\u2019ll answer questions including: How well do various technologies support the core Reactive tenets of event-driven, scalable, fault tolerant and responsive? How do you use open source technologies to deploy an elastically Reactive application? What is the role of big data in Reactive?", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37071", + "speakers": [170293], + "categories": [ + + "Office Hours" + + ] + }, + + { + "serial": 37073, + "name": "Office Hour with Scott Murray (University of San Francisco)", + "event_type": "Office Hours", + + "time_start": "2014-07-23 14:30:00", + "time_stop": "2014-07-23 15:10:00", + "venue_serial": 1580, + "description": "Scott\u2019s here to answer your questions about data and how to successfully conveying meaning through well-designed graphics. He\u2019ll chat with you about the data visualization design process; related technologies, including d3.js and Processing; and how we can all benefit by open-sourcing our processes. ", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37073", + "speakers": [147840], + "categories": [ + + "Office Hours" + + ] + }, + + { + "serial": 37075, + "name": "Office Hour with Francesc Campoy (Google Inc.) ", + "event_type": "Office Hours", + + "time_start": "2014-07-23 15:20:00", + "time_stop": "2014-07-23 16:00:00", + "venue_serial": 1548, + "description": "If you\u2019ve got questions about Go, you\u2019re in luck. Francesc will answer questions about Go basics, programming, organizing your code, and more. Find out about Go concurrency and concurrency primitives, and how Go interfaces simplify your code and break fictitious dependencies.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37075", + "speakers": [155088], + "categories": [ + + "Office Hours" + + ] + }, + + { + "serial": 37077, + "name": "Office Hour with Kirsten Hunter (Akamai)", + "event_type": "Office Hours", + + "time_start": "2014-07-23 14:30:00", + "time_stop": "2014-07-23 15:10:00", + "venue_serial": 1547, + "description": "If you want your APIs to amaze and delight your developer partners, bring Kristen all of your API design questions. She\u2019ll chat with you about the API design process (how to plan and create an API that will be successful), developer engagement and support (aka the care and feeding of your developer partners), and how you can help your customers learn and troubleshoot your API.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37077", + "speakers": [4265], + "categories": [ + + "Office Hours" + + ] + }, + + { + "serial": 37078, + "name": "Office Hour with Benjamin Kerensa (Mozilla) ", + "event_type": "Office Hours", + + "time_start": "2014-07-23 10:40:00", + "time_stop": "2014-07-23 11:20:00", + "venue_serial": 1548, + "description": "If you\u2019ve ever toyed with the idea of contributing to Firefox, Benjamin would love to talk to you about ways to make it happen. Find out why Firefox OS matters, how Mozilla builds community, and how you can contribute to Firefox.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37078", + "speakers": [120025], + "categories": [ + + "Office Hours" + + ] + }, + + { + "serial": 37085, + "name": "Morning Yoga", + "event_type": "Event", + + "time_start": "2014-07-22 07:30:00", + "time_stop": "2014-07-22 08:15:00", + "venue_serial": 1574, + "description": "Programmers do a lot of sitting, so come refresh your body, mind, and spirit before you head into the day\u2019s sessions. This will be an easy beginner\u2019s yoga session \u2013 so don\u2019t be shy about coming out even if this will be your first yoga experience. ", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37085", + "speakers": [], + "categories": [ + + "Events" + + ] + }, + + { + "serial": 37086, + "name": "Morning Yoga", + "event_type": "Event", + + "time_start": "2014-07-23 07:30:00", + "time_stop": "2014-07-23 08:15:00", + "venue_serial": 1574, + "description": "Programmers do a lot of sitting, so come refresh your body, mind, and spirit before you head into the day\u2019s sessions. This will be an easy beginner\u2019s yoga session \u2013 so don\u2019t be shy about coming out even if this will be your first yoga experience. ", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37086", + "speakers": [], + "categories": [ + + "Events" + + ] + }, + + { + "serial": 37087, + "name": "Office Hour with Emma Jane Westby (Freelance)", + "event_type": "Office Hours", + + "time_start": "2014-07-22 11:30:00", + "time_stop": "2014-07-22 12:10:00", + "venue_serial": 1548, + "description": "Git can be frustrating and opaque. Don\u2019t worry, it\u2019s not you. It\u2019s Git. Come ask questions about making Git work for you instead of feeling stuck in a detached HEAD state. Emma Jane will talk about upgrading from a centralized system to a distributed workflow and improving team efficiencies with the right branching strategy. She\u2019ll also provide tips for teaching anyone how to use Git.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37087", + "speakers": [4146], + "categories": [ + + "Office Hours" + + ] + }, + + { + "serial": 37090, + "name": "Office Hour with Ken Walker (IBM Canada) ", + "event_type": "Office Hours", + + "time_start": "2014-07-22 13:40:00", + "time_stop": "2014-07-22 14:20:00", + "venue_serial": 1548, + "description": "Need some advice on your next great open source project? Ken can steer you in the right direction. Talk to him about the benefits of the Eclipse Foundation as a home for your project, and discuss what prevents you from trying out cloud IDEs. Ken will also point out which open source components from Orion\u2019s JavaScript libraries you can leverage in your own project. ", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37090", + "speakers": [39928], + "categories": [ + + "Office Hours" + + ] + }, + + { + "serial": 37091, + "name": "Office Hour with John Griffith (SolidFire) ", + "event_type": "Office Hours", + + "time_start": "2014-07-23 10:40:00", + "time_stop": "2014-07-23 11:20:00", + "venue_serial": 1579, + "description": "If you\u2019re looking for answers about OpenStack, John is ready to field your questions. He\u2019ll chat with you about block storage in OpenStack with Cinder, core OpenStack features and functionality, and general queries about OpenStack in general.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37091", + "speakers": [171381], + "categories": [ + + "Office Hours" + + ] + }, + + { + "serial": 37095, + "name": "Office Hour with Damian Conway (Thoughtstream) ", + "event_type": "Office Hours", + + "time_start": "2014-07-23 11:30:00", + "time_stop": "2014-07-23 12:10:00", + "venue_serial": 1548, + "description": "The professor is in. Perl\u2019s own Dr. Evil emerges from his secret lair to talk about Perl 6, Perl 5, Vim, and presentation skills. Damian knows a lot about all of the above as the widely sought-after speaker who runs Thoughtstream, an international provider of programmer training. He devotes much of his spare time to Larry Wall and the design of Perl 6.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37095", + "speakers": [4710], + "categories": [ + + "Office Hours" + + ] + }, + + { + "serial": 37097, + "name": "Office Hour with Adam Culp (Zend Technologies)", + "event_type": "Office Hours", + + "time_start": "2014-07-23 13:40:00", + "time_stop": "2014-07-23 14:20:00", + "venue_serial": 1579, + "description": "Organizer of the Sunshine PHP Developer Conference, Adam is ready to hold an open discussion on efficient code refactoring. Find out when to refactor, the key indicators for triggering a refactor, how to get started with code refactoring, and anything else you think is related.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37097", + "speakers": [169862], + "categories": [ + + "Office Hours" + + ] + }, + + { + "serial": 37098, + "name": "Office Hour with Paco Nathan (Liber 118)", + "event_type": "Office Hours", + + "time_start": "2014-07-22 14:30:00", + "time_stop": "2014-07-22 15:10:00", + "venue_serial": 1547, + "description": ".", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37098", + "speakers": [146540], + "categories": [ + + "Office Hours" + + ] + }, + + { + "serial": 37099, + "name": "Office Hour with Luciano Ramalho (Python.pro.br) ", + "event_type": "Office Hours", + + "time_start": "2014-07-22 14:30:00", + "time_stop": "2014-07-22 15:10:00", + "venue_serial": 1548, + "description": "Luciano is busy at OSCON with two sessions and a tutorial, but he\u2019s happy to answer any questions you may have about those talks: \u201cIntroduction to Python Metaprogramming\u201d, \u201cIdiomatic APIs with the Python Data Model\u201d, and \u201cPython: Encapsulation with Descriptors\u201d. The common theme for all three is how to use the special methods defined in the Python Data Model. ", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37099", + "speakers": [150170], + "categories": [ + + "Office Hours" + + ] + }, + + { + "serial": 37101, + "name": "Office Hour with Jess Portnoy (Kaltura Inc) ", + "event_type": "Office Hours", + + "time_start": "2014-07-22 11:30:00", + "time_stop": "2014-07-22 12:10:00", + "venue_serial": 1579, + "description": "Join Jess to talk about useful debugging tools, PHP-based apps, and some of her other favorite open source topics. She\u2019ll hold forth on managing open source projects as a commercial company, deploying and maintaining large-scale video applications in the cloud, and open source development in general.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37101", + "speakers": [171078], + "categories": [ + + "Office Hours" + + ] + }, + + { + "serial": 37105, + "name": "Office Hour with Michael Minella (Pivotal) ", + "event_type": "Office Hours", + + "time_start": "2014-07-23 13:40:00", + "time_stop": "2014-07-23 14:20:00", + "venue_serial": 1547, + "description": ".", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37105", + "speakers": [171147], + "categories": [ + + "Office Hours" + + ] + }, + + { + "serial": 37108, + "name": "Office Hour with Wade Minter (TeamSnap) and Andrew Berkowitz (TeamSnap)", + "event_type": "Office Hours", + + "time_start": "2014-07-22 14:30:00", + "time_stop": "2014-07-22 15:10:00", + "venue_serial": 1579, + "description": "If you have questions about using applied improvisation for better communication, idea generation, and decision making in your company or team, come to Wade and Andrew. They\u2019ll talk to you about growing a distributed engineering team, and building and maintaining culture in a distributed organization. They\u2019ll also share tools and tips for improving communication.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37108", + "speakers": [4378,106355], + "categories": [ + + "Office Hours" + + ] + }, + + { + "serial": 37114, + "name": "Office Hour with Tim Berglund (DataStax) ", + "event_type": "Office Hours", + + "time_start": "2014-07-22 13:40:00", + "time_stop": "2014-07-22 14:20:00", + "venue_serial": 1579, + "description": "Do you have questions on Apache Cassandra? Bring any and all of them to Tim during Office Hours. He\u2019s also ready to chat about technical training, the technologies formerly known as Big Data, and any other topics of mutual interest.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37114", + "speakers": [137697], + "categories": [ + + "Office Hours" + + ] + }, + + { + "serial": 37122, + "name": "Office Hour with Joshua Marinacci (Nokia) ", + "event_type": "Office Hours", + + "time_start": "2014-07-23 13:40:00", + "time_stop": "2014-07-23 14:20:00", + "venue_serial": 1548, + "description": "Joshua is available to talk to you about any of his favorite topics, such as HTML Canvas, Bluetooth Low Energy, wearable computing, and the Internet of Things. And he\u2019s happy just to rant about Java.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37122", + "speakers": [6931], + "categories": [ + + "Office Hours" + + ] + }, + + { + "serial": 37129, + "name": "Office Hour with Zach Supalla (Spark Labs) ", + "event_type": "Office Hours", + + "time_start": "2014-07-22 15:20:00", + "time_stop": "2014-07-22 16:00:00", + "venue_serial": 1547, + "description": "Now that it is possible for startups to build products as powerful and game changing as Nest\u2014without millions of dollars\u2014how can you get in on the action? Zach Supalla from the Spark Labs startup accelerator will share details and answer questions about developing an IoT product, building a business on open source hardware, and growing from a Kickstarter campaign to a business.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37129", + "speakers": [175040], + "categories": [ + + "Office Hours" + + ] + }, + + { + "serial": 37134, + "name": "Open Health Data/Content", + "event_type": "BoF", + + "time_start": "2014-07-20 19:00:00", + "time_stop": "2014-07-20 20:00:00", + "venue_serial": 1458, + "description": "Explore thoughts and ideas around public health and open data.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37134", + "speakers": [], + "categories": [ + + ] + }, + + { + "serial": 37144, + "name": "Office Hour with Josh Berkus (PostgreSQL Experts, Inc.) ", + "event_type": "Office Hours", + + "time_start": "2014-07-22 15:20:00", + "time_stop": "2014-07-22 16:00:00", + "venue_serial": 1548, + "description": "Josh Berkus is happy to chat with you about PostgreSQL high-availability and scale-out techniques, especially on cloud hosting. He\u2019ll discuss things like connection pooling and load-balancing tools, automated failover, RDS vs. roll-your-own on AWS, and scaling out multi-tenant apps.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37144", + "speakers": [3397], + "categories": [ + + "Office Hours" + + ] + }, + + { + "serial": 37148, + "name": "Modernizing CS Education with Open Source", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 13:40:00", + "time_stop": "2014-07-23 14:20:00", + "venue_serial": 1450, + "description": "Scott Chacon, co-founder of GitHub, and Jay Borenstein, CS professor at Stanford and founder of Facebook's Open Academy, a program designed to match university students with open source projects for academic credit, will discuss how to bring the best of the open source community's learning frameworks into formal computer science education. ", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37148", + "speakers": [837,180268], + "categories": [ + + "Education" + + ] + }, + + { + "serial": 37153, + "name": "Mandrill Party", + "event_type": "Event", + + "time_start": "2014-07-23 20:30:00", + "time_stop": "2014-07-23 22:00:00", + "venue_serial": 1578, + "description": "Join Mandrill for an OSCON Party at the Jupiter Hotel!", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37153", + "speakers": [], + "categories": [ + + "Events" + + ] + }, + + { + "serial": 37173, + "name": "Office Hour with Andrei Alexandrescu (Facebook) ", + "event_type": "Office Hours", + + "time_start": "2014-07-22 16:10:00", + "time_stop": "2014-07-22 16:50:00", + "venue_serial": 1547, + "description": "Andrei will be happy to answer questions about the \u201cmove fast\u201d side of Facebook, including large-scale design, the D programming language, and Facebook\u2019s software engineering culture.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37173", + "speakers": [109270], + "categories": [ + + "Office Hours" + + ] + }, + + { + "serial": 37201, + "name": "Office Hour with Brian Troutwine (AdRoll) ", + "event_type": "Office Hours", + + "time_start": "2014-07-23 14:30:00", + "time_stop": "2014-07-23 15:10:00", + "venue_serial": 1579, + "description": "In his talk (\u201cErlang, LFE, Joxa and Elixir: Established and Emerging Languages in the Erlang Ecosystem\u201d), Brian talks about the differences between these BEAM languages, and how each one is applicable to a distinct engineering task. During Office Hours, he\u2019ll tell you how to get started with each of these languages, and talk about the domains in which they succeed and fail.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37201", + "speakers": [172990], + "categories": [ + + "Office Hours" + + ] + }, + + { + "serial": 37253, + "name": "Juju BoF", + "event_type": "BoF", + + "time_start": "2014-07-22 19:00:00", + "time_stop": "2014-07-22 20:00:00", + "venue_serial": 1456, + "description": "Deploying workloads in the cloud can be challenging, in this BoF we'll discuss how to use Ubuntu and Juju to get up and running in the cloud quickly ", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37253", + "speakers": [], + "categories": [ + + ] + }, + + { + "serial": 37256, + "name": "Office Hour with Michael Enescu (Cisco) ", + "event_type": "Office Hours", + + "time_start": "2014-07-23 15:20:00", + "time_stop": "2014-07-23 16:00:00", + "venue_serial": 1579, + "description": "Now that you\u2019re familiar with cloud computing, along comes the next new thing: Fog computing. Michael will tell you what exactly Fog computing is, how it evolved from cloud compute, and where it\u2019s going. He\u2019ll also talk about network challenges in cloud computing with the Internet of Things, as well as IoT open source projects.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37256", + "speakers": [172370], + "categories": [ + + "Office Hours" + + ] + }, + + { + "serial": 37259, + "name": "Modernizing your Cloud with Software Collections", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 14:30:00", + "time_stop": "2014-07-23 15:10:00", + "venue_serial": 1461, + "description": "Software Collections is a new way to run newer packages on Enterprise Linux (RHEL/CentOS) such as Python, Ruby, PHP, MySQL/MariaDB, and others. Learn how this enables us to use Perl 5.16 and PostgreSQL 9.2 alongside distribution-provided versions (on EL6, Perl 5.10, and PostgreSQL 8.4.). Also learn how we've extended the Perl 5.16 collection with additional packages.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37259", + "speakers": [180437], + "categories": [ + + "Sponsored Sessions" + + ] + }, + + { + "serial": 37292, + "name": "Office Hour with Deb Nicholson (Open Invention Network)", + "event_type": "Office Hours", + + "time_start": "2014-07-22 16:10:00", + "time_stop": "2014-07-22 16:50:00", + "venue_serial": 1580, + "description": "Wondering how patents and copyright and trademark laws work with free and open source software projects? Deb is ready to share her knowledge. Find out if legislation and recent court decisions about patent trolls will keep you from being sued, and how the landscape likely to change. None of this is legal advice, just good solid background! ", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37292", + "speakers": [130731], + "categories": [ + + "Office Hours" + + ] + }, + + { + "serial": 37306, + "name": "Office Hour with Gian Merlino (Metamarkets) and Fangjin Yang (Metamarkets)", + "event_type": "Office Hours", + + "time_start": "2014-07-22 16:10:00", + "time_stop": "2014-07-22 16:50:00", + "venue_serial": 1548, + "description": "Gian and Fangjin will talk to you about real-time analytics with open source technologies, including the motivation for the Kafka, Storm, Hadoop, and Druid real-time analytics stack. They\u2019ll discuss implementation details (if you\u2019re interested in trying the stack out at home) and show you how to scale the stack and make it highly available. ", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37306", + "speakers": [153566,153565], + "categories": [ + + "Office Hours" + + ] + }, + + { + "serial": 37310, + "name": "acilos, your Personal Social Valet", + "event_type": "BoF", + + "time_start": "2014-07-22 19:00:00", + "time_stop": "2014-07-22 20:00:00", + "venue_serial": 1465, + "description": "In this session, you will discover a new Open Source Personal Social Media Aggregation system called acilos. ", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37310", + "speakers": [], + "categories": [ + + ] + }, + + { + "serial": 37311, + "name": "Open Standards vs. Open Source", + "event_type": "BoF", + + "time_start": "2014-07-22 19:00:00", + "time_stop": "2014-07-22 20:00:00", + "venue_serial": 1463, + "description": "There is a debate on the relevance of Open Standards when faced with Open Source efforts. Open Source is an industry force, yet government bodies and others still rely on and give preference to ANSI and ISO standards. Standards representatives from across the industry will be on hand to discuss possible paths forward that are complementary rather than competitive.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37311", + "speakers": [], + "categories": [ + + ] + }, + + { + "serial": 37316, + "name": "Office Hour with Spencer Krum (HP) and William Van Hevelingen (Portland State University)", + "event_type": "Office Hours", + + "time_start": "2014-07-23 11:30:00", + "time_stop": "2014-07-23 12:10:00", + "venue_serial": 1579, + "description": "Spencer and William are ready to talk about all things Puppet related. They\u2019ll answer questions about module best practices and testing, using Hiera to manage data, and interfacing with the community. ", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37316", + "speakers": [125113,99817], + "categories": [ + + "Office Hours" + + ] + }, + + { + "serial": 37338, + "name": "Office Hour with Drasko DRASKOVIC (DEVIALET) ", + "event_type": "Office Hours", + + "time_start": "2014-07-23 16:10:00", + "time_stop": "2014-07-23 16:50:00", + "venue_serial": 1579, + "description": "If you want to explore that thin line where hardware meets software, stop by and chat with Drasko. He\u2019ll talk to you about electronic prototyping of connected objects for the Internet of Things from a designer\u2019s point of view; discuss open source HW schematic modifications, component sourcing, and production; and weigh in on monitoring, data harvesting, remote control, sensors, and actuators.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37338", + "speakers": [173105], + "categories": [ + + "Office Hours" + + ] + }, + + { + "serial": 37345, + "name": "Office Hour with Rob Reilly (Rob Reilly Consulting) ", + "event_type": "Office Hours", + + "time_start": "2014-07-23 16:10:00", + "time_stop": "2014-07-23 16:50:00", + "venue_serial": 1580, + "description": "When it comes to hacking micro-controller and nano-computing projects, Rob is your man. He\u2019s ready to discuss all your hacker questions, such as the latest micro-controller trends, the hacker cross-pollination with artists, scientists, engineers, students, and suits, and\u2014of course\u2014how to get started hacking and making.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37345", + "speakers": [77350], + "categories": [ + + "Office Hours" + + ] + }, + + { + "serial": 37363, + "name": "Office Hour with Yoav Landman (JFrog) and Baruch Sadogursky (JFrog)", + "event_type": "Office Hours", + + "time_start": "2014-07-23 10:40:00", + "time_stop": "2014-07-23 11:20:00", + "venue_serial": 1580, + "description": "Talk to Yoav and Baruch about all things CI/CD, such as choosing the right build tools, and automating your build, deployment, and distribution process. They\u2019ll discuss the issues of moving to a cloud-based development stack, how to speed up development by dealing with bottlenecks in your CI/CD flow, and distributing software releases all the way to end users.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37363", + "speakers": [116050,114822], + "categories": [ + + "Office Hours" + + ] + }, + + { + "serial": 37401, + "name": "Office Hour with Glen Wiley (Verisign, Inc.), Neel Goyal (Verisign, Inc.), Willem Toorop (NLNet Labs), and Allison Mankin (Verisign, Inc.) ", + "event_type": "Office Hours", + + "time_start": "2014-07-23 15:20:00", + "time_stop": "2014-07-23 16:00:00", + "venue_serial": 1580, + "description": "Learn more about getdns for accessing DNS security and other powerful features. Targeted to application developers, getdns provides both basic and advanced DNS capabilities, while requiring little DNS expertise. Glen, Neel, Willem, and Allison will explain how to leverage DNS to create authenticated services with getdns modes. ", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37401", + "speakers": [173324,172895,173325,173326], + "categories": [ + + "Office Hours" + + ] + }, + + { + "serial": 37405, + "name": "Office Hour with Dr. Doris Chen (Microsoft) ", + "event_type": "Office Hours", + + "time_start": "2014-07-23 11:30:00", + "time_stop": "2014-07-23 12:10:00", + "venue_serial": 1580, + "description": "Doris is ready to answer any questions you have about developing high performance websites and apps with JavaScript and HTML5. She\u2019ll share tips and tricks for tackling real-world web platform performance problems, including network requests, speed and responsiveness, and optimizing media usage.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37405", + "speakers": [133360], + "categories": [ + + "Office Hours" + + ] + }, + + { + "serial": 37408, + "name": "Office Hour with Boyd Stephens (Netelysis, LLC)", + "event_type": "Office Hours", + + "time_start": "2014-07-22 16:10:00", + "time_stop": "2014-07-22 16:50:00", + "venue_serial": 1579, + "description": "Want to learn the intricacies of the money machine and where and when you can hack it? Boyd can help. Find out why staying in total control of your money machine/cash contraption can rival accepting venture capital and angel funding when creating a business. And learn how the Lean Business Model can open up unique opportunities for money machines. ", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37408", + "speakers": [138530], + "categories": [ + + "Office Hours" + + ] + }, + + { + "serial": 37409, + "name": "Office Hour with Benjamin Curtis (Honeybadger Industries) ", + "event_type": "Office Hours", + + "time_start": "2014-07-23 13:40:00", + "time_stop": "2014-07-23 14:20:00", + "venue_serial": 1580, + "description": "If you want to find out more about machine learning, Benjamin is available to discuss several techniques, especially those available as Ruby gems. He\u2019ll talk to you about machine learning with Ruby, scaling Rails with PostgreSQL, and other questions you have on your mind. ", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37409", + "speakers": [173396], + "categories": [ + + "Office Hours" + + ] + }, + + { + "serial": 37410, + "name": "Office Hour with David Quigley (KEYW Corporation) ", + "event_type": "Office Hours", + + "time_start": "2014-07-22 10:40:00", + "time_stop": "2014-07-22 11:20:00", + "venue_serial": 1580, + "description": "In his ongoing campaign to demystify SELinux, David is ready to tackle any questions you have\u2014including what it was like to work on the SELinux team at the NSA. He\u2019ll gladly talk to you about SELinux Internals and anything he covered (or didn\u2019t cover) in his tutorial, \u201cDemystifying SELinux Part II: Who\u2019s Policy Is It Anyway?\u201d", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37410", + "speakers": [151833], + "categories": [ + + "Office Hours" + + ] + }, + + { + "serial": 37411, + "name": "Office Hour with Niklas Nielsen (Mesosphere, Inc.), Connor Doyle (Mesosphere, Inc.), and Adam Bordelon (Mesosphere, Inc.)", + "event_type": "Office Hours", + + "time_start": "2014-07-22 15:20:00", + "time_stop": "2014-07-22 16:00:00", + "venue_serial": 1579, + "description": "Niklas, Connor, and Adam are ready to talk to you about the Apache Mesos ecosystem for combining your datacenter servers and cloud instances into one shared pool. Find out how to take control of the datacenter with multi-tenancy and fault-tolerance, how to deploy and manage distributed applications in many languages, and how to migrate existing applications to Mesos using Marathon and Chronos.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37411", + "speakers": [171598,172973,172898], + "categories": [ + + "Office Hours" + + ] + }, + + { + "serial": 37412, + "name": "Office Hour with Kelley Nielsen (Linux Foundation, Gnome Foundation) ", + "event_type": "Office Hours", + + "time_start": "2014-07-23 14:30:00", + "time_stop": "2014-07-23 15:10:00", + "venue_serial": 1548, + "description": "Learn how Kelley evolved from frustrated retail worker to Linux kernel developer through the Gnome Outreach Program for Women. She\u2019ll talk about the personal ways that outreach and mentoring affect new contributors\u2014and how important introspection and self-knowledge are in the process. ", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37412", + "speakers": [140811], + "categories": [ + + "Office Hours" + + ] + }, + + { + "serial": 37476, + "name": "OpenStack Community Celebrates Four Years", + "event_type": "Event", + + "time_start": "2014-07-22 19:00:00", + "time_stop": "2014-07-22 20:30:00", + "venue_serial": 1583, + "description": "Celebrate OpenStack's Birthday\r\n\r\n ", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37476", + "speakers": [], + "categories": [ + + "Events" + + ] + }, + + { + "serial": 37488, + "name": "Current Best Practices for Building Enterprise Mobile Apps", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 10:40:00", + "time_stop": "2014-07-23 11:20:00", + "venue_serial": 1460, + "description": "With current best practices for mobile development, you can create great enterprise applications faster, iterate more often, and future-proof against a fast-changing mobile OS and hardware landscape. This session will look at key considerations for developers building enterprise apps for any device and OS.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37488", + "speakers": [182198], + "categories": [ + + "Sponsored Sessions" + + ] + }, + + { + "serial": 37566, + "name": "Office Hour with Jason Swartz (Netflix, Inc.) ", + "event_type": "Office Hours", + + "time_start": "2014-07-22 13:40:00", + "time_stop": "2014-07-22 14:20:00", + "venue_serial": 1580, + "description": "Looking to get started with Scala? Jason is on hand to answer questions about this object-functional programming language. Find out how Netflix is using Scala for rapid API development, and why Scala may be useful to you. ", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37566", + "speakers": [171226], + "categories": [ + + "Office Hours" + + ] + }, + + { + "serial": 37567, + "name": "Office Hour with Brian Bulkowski (Aerospike)", + "event_type": "Office Hours", + + "time_start": "2014-07-22 15:20:00", + "time_stop": "2014-07-22 16:00:00", + "venue_serial": 1580, + "description": "Stop by and chat with Brian about using the Aerospike database in real-time big data-driven applications. He\u2019ll talk to you about subjects including low latency, high throughput applications and cacheless architectures, what does and what doesn\u2019t work when using flash as a storage option, and big data management with Hadoop, Storm, Spark, and NoSQL.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37567", + "speakers": [148534], + "categories": [ + + "Office Hours" + + ] + }, + + { + "serial": 37578, + "name": "Office Hour with Francesco Cesarini (Erlang Solutions Ltd), Robert Virding (Erlang Solutions Ltd.), and Marc Sugiyama (Erlang Solutions, Inc) ", + "event_type": "Office Hours", + + "time_start": "2014-07-22 11:30:00", + "time_stop": "2014-07-22 12:10:00", + "venue_serial": 1580, + "description": "Think you have the right use case to adopt Erlang or Elixir? Concerned about selling this new technology to management and operations? The Erlang Solutions crew will explain the advantages of the Erlang stack\u2014including when and when not to use it. Find out how to run an Erlang project, support and maintain Erlang clusters, and learn how to introduce Erlang to your organization.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37578", + "speakers": [10595,174073,173421], + "categories": [ + + "Office Hours" + + ] + }, + + { + "serial": 37581, + "name": "Open Source Multiplies New Relic Awesomeness", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 10:40:00", + "time_stop": "2014-07-22 11:20:00", + "venue_serial": 1463, + "description": "New Relic believes in the principals of open source. So much so, that we built the New Relic Platform according those principals: by making the SDKs and our plugins open source, we ignited a rapidly growing and rich community of plugin authors. Come hear about our open source strategy, efforts in building community, and see how easy it is to participate.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37581", + "speakers": [77939], + "categories": [ + + "Sponsored Sessions" + + ] + }, + + { + "serial": 37583, + "name": "Perl Web Development with CGI::Ex::App", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 13:40:00", + "time_stop": "2014-07-23 14:20:00", + "venue_serial": 1460, + "description": "CGI::Ex::App is a lightweight, high performance framework that has been quietly driving million-dollar websites since 2004. Come see why this application framework might be the perfect fit for you.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37583", + "speakers": [137347], + "categories": [ + + "Sponsored Sessions" + + ] + }, + + { + "serial": 37584, + "name": "Welcome and Introduction", + "event_type": "Open Cloud Day", + + "time_start": "2014-07-21 09:00:00", + "time_stop": "2014-07-21 09:15:00", + "venue_serial": 1584, + "description": "Sarah Novotny, OSCON Program Chair, technical evangelist and community manager for NGINX will kick off Open Cloud Day. ", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37584", + "speakers": [76338], + "categories": [ + + "Cloud" + + ] + }, + + { + "serial": 37585, + "name": "How to Build Your Applications to Scale in the Cloud", + "event_type": "Open Cloud Day", + + "time_start": "2014-07-21 09:15:00", + "time_stop": "2014-07-21 09:45:00", + "venue_serial": 1584, + "description": "Whether you have one or a million visitors accessing your web app, they are all going to demand a great user experience regardless of what it takes for you to deliver it. This invariably means quick page loads and fast response times every single time.I am about different ways to start scaling your application with the new 'Cloud' technology.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37585", + "speakers": [142320], + "categories": [ + + "Cloud" + + ] + }, + + { + "serial": 37586, + "name": "Open Cloud APIs", + "event_type": "Open Cloud Day", + + "time_start": "2014-07-21 11:00:00", + "time_stop": "2014-07-21 11:30:00", + "venue_serial": 1584, + "description": "The success of development and deployment of anything is often unrelated to the technical superiority of a cloud platform but a product of what the platform enables you to do. We need robust but more importantly enjoyable to use APIs at every stage in an application's lifecycle. ", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37586", + "speakers": [161475], + "categories": [ + + "Cloud" + + ] + }, + + { + "serial": 37588, + "name": "Full Stack Development with Node, Angular, and Neo4j", + "event_type": "BoF", + + "time_start": "2014-07-23 19:00:00", + "time_stop": "2014-07-23 20:00:00", + "venue_serial": 1454, + "description": "Full stack is becoming a popular developer specialization for web and mobile application development. We'll be talking about the role of the full stack developer using open source platforms for building recommendation-based apps. We'll walk through full stack development on node.js, AngularJS, and Neo4j graph database.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37588", + "speakers": [], + "categories": [ + + ] + }, + + { + "serial": 37589, + "name": "Office Hour with Steven Pousty (Red Hat) and Katie Miller (Red Hat)", + "event_type": "Office Hours", + + "time_start": "2014-07-22 14:30:00", + "time_stop": "2014-07-22 15:10:00", + "venue_serial": 1580, + "description": "Ready to write your components in the language of your choice, and put those components wherever you want on your network? Then come talk to Steve and Katie about the event-driven application framework Vert.X. They\u2019ll show you what the framework looks like and how you can get started with it.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37589", + "speakers": [142320,175488], + "categories": [ + + "Office Hours" + + ] + }, + + { + "serial": 37593, + "name": "A Technical Exploration of Atom\u2019s Text Editor Component", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 11:30:00", + "time_stop": "2014-07-23 12:10:00", + "venue_serial": 1461, + "description": "Atom is an open-source desktop text editor built with web technology. This talk will be a deep, technical exploration of how Atom manipulates and renders text. Nathan will share lessons we\u2019ve learned about efficiently rendering text via the DOM, and then explore the key components involved in Atom\u2019s text editing system and how the concepts they model are surfaced in the API.\r\n", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37593", + "speakers": [2732], + "categories": [ + + "Sponsored Sessions" + + ] + }, + + { + "serial": 37603, + "name": "Dive into Cloud Foundry PaaS", + "event_type": "BoF", + + "time_start": "2014-07-20 19:00:00", + "time_stop": "2014-07-20 20:00:00", + "venue_serial": 1471, + "description": "Cloud Foundry is an open source Platform as a Service (PaaS) that features a range of components which provide a faster and easier way to build, test, deploy and scale applications. We will go through the most important elements and capabilities that make Cloud Foundry a first class citizen PaaS.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37603", + "speakers": [], + "categories": [ + + ] + }, + + { + "serial": 37604, + "name": "Morning Break", + "event_type": "Open Cloud Day", + + "time_start": "2014-07-21 10:45:00", + "time_stop": "2014-07-21 11:00:00", + "venue_serial": 1584, + "description": "Located outside of F150 in the Lobby Foyer.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37604", + "speakers": [], + "categories": [ + + ] + }, + + { + "serial": 37605, + "name": "Afternoon Break", + "event_type": "Open Cloud Day", + + "time_start": "2014-07-21 15:00:00", + "time_stop": "2014-07-21 15:15:00", + "venue_serial": 1584, + "description": "Located outside of F150 in the Lobby Foyer.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37605", + "speakers": [], + "categories": [ + + ] + }, + + { + "serial": 37606, + "name": "Lunch", + "event_type": "Open Cloud Day", + + "time_start": "2014-07-21 12:30:00", + "time_stop": "2014-07-21 13:30:00", + "venue_serial": 1584, + "description": "Located in Exhibit Hall E. ", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37606", + "speakers": [], + "categories": [ + + ] + }, + + { + "serial": 37622, + "name": "The Enterprise Challenge for Cloud Computing", + "event_type": "Open Cloud Day", + + "time_start": "2014-07-21 09:45:00", + "time_stop": "2014-07-21 10:15:00", + "venue_serial": 1584, + "description": "Clouds - they started as nice tools, became a favorite of\r\nstartups and Web 2.0 companies. But the enterprise, why hasn't cloud\r\ncomputing dominated in the enterprise already? Well, it's complicated...", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37622", + "speakers": [141881], + "categories": [ + + "Cloud" + + ] + }, + + { + "serial": 37623, + "name": "Real Time Backend for the Internet of Things", + "event_type": "BoF", + + "time_start": "2014-07-22 20:00:00", + "time_stop": "2014-07-22 21:00:00", + "venue_serial": 1457, + "description": "We will discuss the advantages and disadvantages of using SQL over NoSQL datastores, optimal solutions to the Blob problems, what \u201creal-time\u201d really means, dealing with sharding, spatial data types and the expected throughputs of such systems and more. ", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37623", + "speakers": [], + "categories": [ + + ] + }, + + { + "serial": 37624, + "name": "Modern API Design via RAML, Swagger & Blueprints", + "event_type": "BoF", + + "time_start": "2014-07-20 19:00:00", + "time_stop": "2014-07-20 20:00:00", + "venue_serial": 1453, + "description": "Lets discuss how to iterate on APIs with our users, using their feedback to help us get the API right from the start. ", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37624", + "speakers": [], + "categories": [ + + ] + }, + + { + "serial": 37625, + "name": "A Path to Achieving True Cloud Portability", + "event_type": "Open Cloud Day", + + "time_start": "2014-07-21 14:30:00", + "time_stop": "2014-07-21 15:00:00", + "venue_serial": 1584, + "description": "Moving applications from a traditional data center to a cloud\r\nprovides immediate and real advantages in the way services are delivered\r\nto customers. What if you could also easily move from one cloud to\r\nanother? ", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37625", + "speakers": [181484], + "categories": [ + + "Cloud" + + ] + }, + + { + "serial": 37626, + "name": "The Art of Tizen UI Theme Technology in Various Profiles", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 11:30:00", + "time_stop": "2014-07-23 12:10:00", + "venue_serial": 1460, + "description": "Tizen is aimed at various profiles, not only mobile. The UI must be scalable and themeable to support these diverse profiles. This presentation will share the technology behind the scalable and themeable Tizen UI which is called EFL (Enlightenment Foundation Libraries). This will reduce development time tremendously to support multiple products and applications.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37626", + "speakers": [181502], + "categories": [ + + "Sponsored Sessions" + + ] + }, + + { + "serial": 37627, + "name": "A New Community is Born in Internet of Things: Standard Specification to Open Source", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 10:40:00", + "time_stop": "2014-07-22 11:20:00", + "venue_serial": 1460, + "description": "This talk includes an introduction to IoT and background, lessons learned from standard specification and its limitation, reason for open source in IoT, Samsung's efforts on IoT open source, and the future of IoT.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37627", + "speakers": [172630], + "categories": [ + + "Sponsored Sessions" + + ] + }, + + { + "serial": 37629, + "name": "Why Open Platforms Matter to Enterprises and Developers", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 11:30:00", + "time_stop": "2014-07-22 12:10:00", + "venue_serial": 1460, + "description": "The industry needs cloud solutions built on an open, extensible architecture that delivers consistent access to infrastructure, runtimes, and application resources. As customers continue to adopt cloud service-based solutions, they need to avoid vendor lock-in, simplify building of complex cloud environments, and quickly develop cloud-ready applications that drive massively scalable cloud models.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37629", + "speakers": [181598], + "categories": [ + + "Sponsored Sessions" + + ] + }, + + { + "serial": 37631, + "name": "A Deployment Architecture for OpenStack in the Enterprise", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 11:30:00", + "time_stop": "2014-07-23 12:10:00", + "venue_serial": 1463, + "description": "Enterprise developers want flexible, open architectures to develop cloud-native applications and bring new ideas to market faster. Yet enterprise IT needs to quickly deliver services, applications, and infrastructures in a consistent, secure, repeatable manner. How do you get the agility and flexibility while maintaining control?", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37631", + "speakers": [26426], + "categories": [ + + "Sponsored Sessions" + + ] + }, + + { + "serial": 37646, + "name": "Storytelling on the Shoulders of Giants", + "event_type": "Keynote", + + "time_start": "2014-07-24 09:40:00", + "time_stop": "2014-07-24 09:50:00", + "venue_serial": 1525, + "description": "You may not feel like you\u2019re a \u201ccreative person,\u201d but never underestimate where your code could turn up or what stories it might tell. The most unassuming repo can be remixed into something magnificent. ", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37646", + "speakers": [143674], + "categories": [ + + "Keynotes" + + ] + }, + + { + "serial": 37647, + "name": "Open Platform for Family History", + "event_type": "BoF", + + "time_start": "2014-07-22 20:00:00", + "time_stop": "2014-07-22 21:00:00", + "venue_serial": 1460, + "description": "Meet to Learn, Define and Deliver Winning Products that Shape Tomorrow\r\n\r\nBillion of genealogical records available to read and write through open restful APIs. Learn what's available, how to use them, and get profiles of many application opportunities.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37647", + "speakers": [], + "categories": [ + + ] + }, + + { + "serial": 37648, + "name": "Github RootsDev Javascript Open Source API Project", + "event_type": "BoF", + + "time_start": "2014-07-23 19:00:00", + "time_stop": "2014-07-23 20:00:00", + "venue_serial": 1456, + "description": "RootsDev has created a full function Javascript API that can be used to authenticate and read historical person data with in 30 minutes. Learn how and why this SDK was built and what open source apps are already talking to it.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37648", + "speakers": [], + "categories": [ + + ] + }, + + { + "serial": 37649, + "name": "O'Reilly Author Book Signings - Tuesday", + "event_type": "Event", + + "time_start": "2014-07-22 10:10:00", + "time_stop": "2014-07-22 16:30:00", + "venue_serial": 1597, + "description": "Author book signings will be held in the O'Reilly Authors' booth on Tuesday and Wednesday. This is a great opportunity for you to meet O'Reilly authors and to get a free copy of their book. Complimentary copies will be provided for the first 25 attendees. Limit one free book per attendee.\r\n", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37649", + "speakers": [], + "categories": [ + + ] + }, + + { + "serial": 37650, + "name": "O'Reilly Author Book Signings - Wednesday", + "event_type": "Event", + + "time_start": "2014-07-23 10:10:00", + "time_stop": "2014-07-23 17:00:00", + "venue_serial": 1597, + "description": "Author book signings will be held in the O'Reilly Authors' booth on Tuesday and Wednesday. This is a great opportunity for you to meet O'Reilly authors and to get a free copy of their book. Complimentary copies will be provided for the first 25 attendees. Limit one free book per attendee.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37650", + "speakers": [], + "categories": [ + + ] + }, + + { + "serial": 37651, + "name": "Office Hours - Tuesday", + "event_type": "Event", + + "time_start": "2014-07-22 10:40:00", + "time_stop": "2014-07-22 16:30:00", + "venue_serial": 1596, + "description": "Office Hours are your chance to meet face-to-face with OSCON presenters in a small-group setting. Drop in to discuss their sessions, ask questions, or make suggestions.\r\n", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37651", + "speakers": [], + "categories": [ + + ] + }, + + { + "serial": 37652, + "name": "Office Hours - Wednesday", + "event_type": "Event", + + "time_start": "2014-07-23 10:40:00", + "time_stop": "2014-07-23 17:00:00", + "venue_serial": 1596, + "description": "Office Hours are your chance to meet face-to-face with OSCON presenters in a small-group setting. Drop in to discuss their sessions, ask questions, or make suggestions.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37652", + "speakers": [], + "categories": [ + + ] + }, + + { + "serial": 37657, + "name": "Ubuntu Server Deep Dive", + "event_type": "BoF", + + "time_start": "2014-07-22 20:00:00", + "time_stop": "2014-07-22 21:00:00", + "venue_serial": 1461, + "description": "The Ubuntu Server BoF - come to tell the Ubuntu Server Product and Project Managers what would you like to see next - and hear from us what's new for Cloud users and a comprehensive tour of our security features. ", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37657", + "speakers": [], + "categories": [ + + ] + }, + + { + "serial": 37658, + "name": "Adventures in Hackademia: Bridging Open Source and Higher Education", + "event_type": "BoF", + + "time_start": "2014-07-23 19:00:00", + "time_stop": "2014-07-23 20:00:00", + "venue_serial": 1457, + "description": "RIT recently announced the first Academic Minor in Free/Open Source Software and Free Culture in the United States (http://boingboing.net/2014/03/06/get-a-wee-degree-in-free-from.html) This session will detail the engagement strategies, metrics of success and failure, and educational resources that made it possible. Patches welcome. Forks encouraged. Teacher, Learner, and Hacker friendly session.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37658", + "speakers": [], + "categories": [ + + ] + }, + + { + "serial": 37659, + "name": "Evolution of the Apache CouchDB Development Community", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 16:10:00", + "time_stop": "2014-07-23 16:50:00", + "venue_serial": 1460, + "description": "The Apache CouchDB project and world of open source development has seen a lot of change since 2005. In this talk, Joan Touzet will discuss the change journey the CouchDB community has taken as it has matured, covering advocacy efforts, Bylaws and Code of Conduct, GitHub, marketing efforts, mailing lists, growth of the committer base, and operating within the larger ASF community.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37659", + "speakers": [181972], + "categories": [ + + "Sponsored Sessions" + + ] + }, + + { + "serial": 37661, + "name": "Driving Innovation and Next Generation Application Architectures with Open Source", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 10:40:00", + "time_stop": "2014-07-23 11:20:00", + "venue_serial": 1461, + "description": "Join Rackspace and CoreOS as they discuss/examine that developers are beginning to see a new set of disruptive technologies come into play - beyond virtual machine, beyond just configuration management.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37661", + "speakers": [173503,30412], + "categories": [ + + "Sponsored Sessions" + + ] + }, + + { + "serial": 37662, + "name": "Author Book Signing with Paris Buttfield-Addison, Jonathon Manning, and Tim Nugent", + "event_type": "Author Signing", + + "time_start": "2014-07-22 15:10:00", + "time_stop": "2014-07-22 15:40:00", + "venue_serial": 1598, + "description": "Paris, Jon, and Tim will be at the O'Reilly Authors booth #719, signing copies of their book, Learning Cocoa with Objective-C, 4th Edition.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37662", + "speakers": [], + "categories": [ + + "Author Signings" + + ] + }, + + { + "serial": 37663, + "name": "Author Book Signing with Paco Nathan", + "event_type": "Author Signing", + + "time_start": "2014-07-22 18:30:00", + "time_stop": "2014-07-22 19:00:00", + "venue_serial": 1549, + "description": "Paco will be at the O'Reilly Authors booth #719, signing copies of his book, Enterprise Data Workflows with Cascading.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37663", + "speakers": [], + "categories": [ + + "Author Signings" + + ] + }, + + { + "serial": 37664, + "name": "Author Book Signing with Harry Percival", + "event_type": "Author Signing", + + "time_start": "2014-07-22 10:10:00", + "time_stop": "2014-07-22 10:40:00", + "venue_serial": 1549, + "description": "Harry will be at the O'Reilly Authors booth #719, signing copies of his book, Test-Driven Development with Python.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37664", + "speakers": [], + "categories": [ + + "Author Signings" + + ] + }, + + { + "serial": 37665, + "name": "Author Book Signing with Mike Amundsen", + "event_type": "Author Signing", + + "time_start": "2014-07-22 15:40:00", + "time_stop": "2014-07-22 16:10:00", + "venue_serial": 1598, + "description": "Mike will be at the O'Reilly Authors booth #719, signing copies of his book, RESTful Web APIs.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37665", + "speakers": [], + "categories": [ + + "Author Signings" + + ] + }, + + { + "serial": 37666, + "name": "Author Book Signing with Arun Gupta", + "event_type": "Author Signing", + + "time_start": "2014-07-22 15:10:00", + "time_stop": "2014-07-22 15:40:00", + "venue_serial": 1549, + "description": "Arun will be at the O'Reilly Authors booth #719, signing copies of his book, Java EE 7 Essentials.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37666", + "speakers": [], + "categories": [ + + "Author Signings" + + ] + }, + + { + "serial": 37667, + "name": "Author Book Signing with Anil Madhavapeddy", + "event_type": "Author Signing", + + "time_start": "2014-07-22 10:10:00", + "time_stop": "2014-07-22 10:40:00", + "venue_serial": 1550, + "description": "Anil will be at the O'Reilly Authors booth #719, signing copies of his book, Real World OCaml.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37667", + "speakers": [], + "categories": [ + + "Author Signings" + + ] + }, + + { + "serial": 37668, + "name": "Author Book Signing with Lorna Jane Mitchell", + "event_type": "Author Signing", + + "time_start": "2014-07-23 10:10:00", + "time_stop": "2014-07-23 10:40:00", + "venue_serial": 1549, + "description": "Lorna will be at the O'Reilly Authors booth #719, signing copies of her book, PHP Web Services.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37668", + "speakers": [], + "categories": [ + + "Author Signings" + + ] + }, + + { + "serial": 37669, + "name": "Author Book Signing with Kyle Simpson", + "event_type": "Author Signing", + + "time_start": "2014-07-23 15:10:00", + "time_stop": "2014-07-23 15:40:00", + "venue_serial": 1549, + "description": "Kyle will be at the O'Reilly Authors booth #719, signing copies of his book, You Don't Know JS: Scope & Closures.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37669", + "speakers": [], + "categories": [ + + "Author Signings" + + ] + }, + + { + "serial": 37670, + "name": "Author Book Signing with Ruth Suehle", + "event_type": "Author Signing", + + "time_start": "2014-07-22 15:40:00", + "time_stop": "2014-07-22 16:10:00", + "venue_serial": 1549, + "description": "Ruth will be at the O'Reilly Authors booth #719, signing copies of her book, Raspberry Pi Hacks.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37670", + "speakers": [], + "categories": [ + + "Author Signings" + + ] + }, + + { + "serial": 37671, + "name": "Author Book Signing with Mike Wolfson", + "event_type": "Author Signing", + + "time_start": "2014-07-22 17:40:00", + "time_stop": "2014-07-22 18:00:00", + "venue_serial": 1549, + "description": "Mike will be at the O'Reilly Authors booth #719, signing copies of his book, Programming the Android Developer Tools Essentials.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37671", + "speakers": [], + "categories": [ + + "Author Signings" + + ] + }, + + { + "serial": 37672, + "name": "Author Book Signing with Neal Ford", + "event_type": "Author Signing", + + "time_start": "2014-07-22 15:10:00", + "time_stop": "2014-07-22 15:40:00", + "venue_serial": 1550, + "description": "Neal will be at the O'Reilly Authors booth #719, signing copies of his book, Functional Thinking.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37672", + "speakers": [], + "categories": [ + + "Author Signings" + + ] + }, + + { + "serial": 37673, + "name": "Author Book Signing with Francesco Cesarini", + "event_type": "Author Signing", + + "time_start": "2014-07-22 17:40:00", + "time_stop": "2014-07-22 18:00:00", + "venue_serial": 1550, + "description": "Francesco will be at the O'Reilly Authors booth #719, signing copies of his book, Designing for Scalability with Erlang/OTP.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37673", + "speakers": [], + "categories": [ + + "Author Signings" + + ] + }, + + { + "serial": 37674, + "name": "Scalable, Fault Tolerant, Never-stop Systems: The World of Erlang", + "event_type": "BoF", + + "time_start": "2014-07-23 19:00:00", + "time_stop": "2014-07-23 20:00:00", + "venue_serial": 1458, + "description": "Join us for lighting talks and discussion of how to approach the hard problems of building scalable, fault tolerant systems in the real world using Erlang.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37674", + "speakers": [], + "categories": [ + + ] + }, + + { + "serial": 37675, + "name": "Author Book Signing with Sebastien Goasguen", + "event_type": "Author Signing", + + "time_start": "2014-07-22 18:00:00", + "time_stop": "2014-07-22 18:30:00", + "venue_serial": 1549, + "description": "Sebastien will be at the O'Reilly Authors booth #719, signing copies of his book, 60 Recipes for Apache CloudStack.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37675", + "speakers": [], + "categories": [ + + "Author Signings" + + ] + }, + + { + "serial": 37676, + "name": "Author Book Signing with Dan Sanderson", + "event_type": "Author Signing", + + "time_start": "2014-07-23 15:10:00", + "time_stop": "2014-07-23 15:40:00", + "venue_serial": 1550, + "description": "Dan will be at the O'Reilly Authors booth #719, signing copies of his books, Programming Google App Engine with Java and Programming Google App Engine with Python.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37676", + "speakers": [], + "categories": [ + + "Author Signings" + + ] + }, + + { + "serial": 37677, + "name": "Author Book Signing with Alasdair Allan", + "event_type": "Author Signing", + + "time_start": "2014-07-22 15:40:00", + "time_stop": "2014-07-22 16:10:00", + "venue_serial": 1550, + "description": "Alasdair will be at the O'Reilly Authors booth #719, signing copies of his book, Distributed Network Data.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37677", + "speakers": [], + "categories": [ + + "Author Signings" + + ] + }, + + { + "serial": 37678, + "name": "What's a 'DPDK', and Where Can I Get One?", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 10:40:00", + "time_stop": "2014-07-23 11:20:00", + "venue_serial": 1463, + "description": "Legend tells of a legendary piece of software whose packet processing speed is the stuff of legend. Find out all about this software, how it's made, where you can get it, and how it can help getting network packets into your native or virtualized application.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37678", + "speakers": [182043], + "categories": [ + + "Sponsored Sessions" + + ] + }, + + { + "serial": 37679, + "name": "Author Book Signing with Steven Pousty and Katie Miller", + "event_type": "Author Signing", + + "time_start": "2014-07-22 18:00:00", + "time_stop": "2014-07-22 18:30:00", + "venue_serial": 1550, + "description": "Steven and Katie will be at the O'Reilly Authors booth #719, signing copies of their book, Getting Started with OpenShift.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37679", + "speakers": [], + "categories": [ + + "Author Signings" + + ] + }, + + { + "serial": 37680, + "name": "Author Book Signing with Scott Murray", + "event_type": "Author Signing", + + "time_start": "2014-07-23 15:10:00", + "time_stop": "2014-07-23 15:40:00", + "venue_serial": 1598, + "description": "Scott will be at the O'Reilly Authors booth #719, signing copies of his book, Interactive Data Visualization for the Web.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37680", + "speakers": [], + "categories": [ + + "Author Signings" + + ] + }, + + { + "serial": 37681, + "name": "Author Book Signing with Jason Strimpel", + "event_type": "Author Signing", + + "time_start": "2014-07-23 15:40:00", + "time_stop": "2014-07-23 16:10:00", + "venue_serial": 1549, + "description": "Jason will be at the O'Reilly Authors booth #719, signing copies of his book, Web Components.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37681", + "speakers": [], + "categories": [ + + "Author Signings" + + ] + }, + + { + "serial": 37682, + "name": "Author Book Signing with Jamie Allen", + "event_type": "Author Signing", + + "time_start": "2014-07-23 13:30:00", + "time_stop": "2014-07-23 14:00:00", + "venue_serial": 1549, + "description": "Jamie will be at the O'Reilly Authors booth #719, signing copies of his book, Effective Akka.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37682", + "speakers": [], + "categories": [ + + "Author Signings" + + ] + }, + + { + "serial": 37683, + "name": "Author Book Signing with Ethan Brown", + "event_type": "Author Signing", + + "time_start": "2014-07-23 10:10:00", + "time_stop": "2014-07-23 10:40:00", + "venue_serial": 1550, + "description": "Ethan will be at the O'Reilly Authors booth #719, signing copies of his book, Web Development with Node and Express.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37683", + "speakers": [], + "categories": [ + + "Author Signings" + + ] + }, + + { + "serial": 37684, + "name": "Author Book Signing with Tom Fifield and Everett Toews", + "event_type": "Author Signing", + + "time_start": "2014-07-23 15:40:00", + "time_stop": "2014-07-23 16:10:00", + "venue_serial": 1598, + "description": "Tom and Everett will be at the O'Reilly Authors booth #719, signing copies of their book, OpenStack Operations Guide.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37684", + "speakers": [], + "categories": [ + + "Author Signings" + + ] + }, + + { + "serial": 37685, + "name": "Author Book Signing with Jeff Sheltren and Narayan Newton", + "event_type": "Author Signing", + + "time_start": "2014-07-23 10:10:00", + "time_stop": "2014-07-23 10:40:00", + "venue_serial": 1598, + "description": "Jeff and Narayan will be at the O'Reilly Authors booth #719, signing copies of their book, High Performance Drupal.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37685", + "speakers": [], + "categories": [ + + "Author Signings" + + ] + }, + + { + "serial": 37688, + "name": "Containermania: The Hype, The Reality, and The Future", + "event_type": "Open Cloud Day", + + "time_start": "2014-07-21 16:45:00", + "time_stop": "2014-07-21 17:00:00", + "venue_serial": 1584, + "description": "For this presentation, we'll take a look at the current state of Docker\r\ncontainers, what they're useful for, and where it's going.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37688", + "speakers": [6845], + "categories": [ + + "Cloud" + + ] + }, + + { + "serial": 37689, + "name": "The Human Element of APIs", + "event_type": "Open Cloud Day", + + "time_start": "2014-07-21 12:00:00", + "time_stop": "2014-07-21 12:30:00", + "venue_serial": 1584, + "description": "This talk will cover the interactions and influences that people and\r\ncommunities have over open source software and with one another. Special\r\nattention will be given to many of the hot technologies being used in\r\nand for Open Cloud technologies.\r\n", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37689", + "speakers": [182080], + "categories": [ + + "Cloud" + + ] + }, + + { + "serial": 37690, + "name": "How Disney Built a Modern Cloud with Open Source", + "event_type": "Open Cloud Day", + + "time_start": "2014-07-21 10:15:00", + "time_stop": "2014-07-21 10:45:00", + "venue_serial": 1584, + "description": "Working to help reboot the delivery and consumption of\r\ntechnology services within the Walt Disney Company, we've built a cloud\r\nservice offering and integration platform on open source technologies like\r\nOpenStack with the goal of providing ways to host any application -\r\nwhether modern and cloud ready or on software development hospice care.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37690", + "speakers": [182081], + "categories": [ + + "Cloud" + + ] + }, + + { + "serial": 37698, + "name": "Designers, See Your Designs Differently: Color Contrast Tips and Techniques", + "event_type": "BoF", + + "time_start": "2014-07-23 19:00:00", + "time_stop": "2014-07-23 20:00:00", + "venue_serial": 1461, + "description": "This session will cover the strategies, tips and techniques we have used at PayPal with our design teams to move towards a goal of delivering products with sufficient color contrast for all user experiences to assure that as many people as possible can see and use them. Designers and developers are welcome to attend. \r\n", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37698", + "speakers": [], + "categories": [ + + ] + }, + + { + "serial": 37699, + "name": "Glimpse of Git's Future", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 16:10:00", + "time_stop": "2014-07-22 16:50:00", + "venue_serial": 1454, + "description": "Peek into the future of Git with Android, Google and GitHub. Learn\r\nabout the 450x server performance improvement developed by Google and\r\nGitHub, and get a glimpse of the scaling roadmap.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37699", + "speakers": [64512], + "categories": [ + + "Tools & Techniques" + + ] + }, + + { + "serial": 37703, + "name": "Alchemy.js", + "event_type": "BoF", + + "time_start": "2014-07-20 19:00:00", + "time_stop": "2014-07-20 20:00:00", + "venue_serial": 1470, + "description": "Alchemy.js is a new open source visualization library from GraphAlchemist. Come join the team behind the new tool and learn about the benefits of using Alchemy.js.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37703", + "speakers": [], + "categories": [ + + ] + }, + + { + "serial": 37704, + "name": "Identity: Authentication and User Management", + "event_type": "BoF", + + "time_start": "2014-07-23 19:00:00", + "time_stop": "2014-07-23 20:00:00", + "venue_serial": 1463, + "description": "Acquiring users, getting them signed up, and protecting both your and their interests are all hard things. What are the hard parts, what are the available tools, and what are the OSS angles?", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37704", + "speakers": [], + "categories": [ + + ] + }, + + { + "serial": 37708, + "name": "Taming Hybrid Clouds with ManageIQ ", + "event_type": "BoF", + + "time_start": "2014-07-22 19:00:00", + "time_stop": "2014-07-22 20:00:00", + "venue_serial": 1460, + "description": "ManageIQ is a newly open sourced project that lets operations and developers manage the lifecycle of their virtualization and cloud infrastructures. Place virtual workloads according to your policies and automate them, prioritizing for cost, performance, security, and/or reliability. In this BoF, we will demonstrate how to use ManageIQ as a single API and gateway for cloud workloads.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37708", + "speakers": [], + "categories": [ + + ] + }, + + { + "serial": 37709, + "name": "Leveraging a Cloud Architecture for Fun, Ease, and Profit", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 11:30:00", + "time_stop": "2014-07-22 12:10:00", + "venue_serial": 1463, + "description": "The world of cloud and application development is not just for the hardened developer these days. In their session, Phil Jackson, Development Community Advocate for SoftLayer, and Harold Hannon, Sr. Software Architect at SoftLayer, will pull back the curtain of the architecture of a fun demo application purpose-built for the cloud.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37709", + "speakers": [140339,122564], + "categories": [ + + "Sponsored Sessions" + + ] + }, + + { + "serial": 37712, + "name": "Gluster Community BoF", + "event_type": "BoF", + + "time_start": "2014-07-22 19:00:00", + "time_stop": "2014-07-22 20:00:00", + "venue_serial": 1458, + "description": "Get up to speed with the Gluster Community - we released 3.5, planned 3.6, created lots of language bindings for GFAPI, and are currently planning the Gluster Software Distribution. Come hear about the latest developments in this BoF and how you can participate and benefit.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37712", + "speakers": [], + "categories": [ + + ] + }, + + { + "serial": 37713, + "name": "A Hands-on Intro to Data Science and R", + "event_type": "Event", + + "time_start": "2014-07-21 09:00:00", + "time_stop": "2014-07-21 12:30:00", + "venue_serial": 1607, + "description": "Data scientists need to have a grab bag of tools available to accomplish the task of value-driven data analytics. Many of those tools are open source. Come see how Pivotal is leveraging and contributing to open source with data science. Special focus on: R, Python, MADlib, Open Chorus, Apache Tomcat, Apache Hadoop, Redis, Rabbit MQ, Cloud Foundry and other open source toolkits. ", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37713", + "speakers": [182463], + "categories": [ + + "Sponsored Tutorials" + + ] + }, + + { + "serial": 37714, + "name": "Author Signing with Miguel Grinberg", + "event_type": "Author Signing", + + "time_start": "2014-07-22 10:10:00", + "time_stop": "2014-07-22 10:40:00", + "venue_serial": 1598, + "description": "Miguel will be at the O'Reilly Authors booth #719, signing copies of his book, Flask Web Development.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37714", + "speakers": [], + "categories": [ + + "Author Signings" + + ] + }, + + { + "serial": 37715, + "name": "TA3M", + "event_type": "BoF", + + "time_start": "2014-07-22 19:00:00", + "time_stop": "2014-07-22 20:00:00", + "venue_serial": 1466, + "description": "Leaders in technology convene to discuss the 'state of the movement' for issues around privacy, censorship, and surveillance. What are our successes and failures? What are the current threats and opportunities to affect meaningful change? ", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37715", + "speakers": [], + "categories": [ + + ] + }, + + { + "serial": 37717, + "name": "CLA\u2019s: Best Thing Since Sliced Bread or Tool of the Devil\u2026 A Panel Discussion", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 16:10:00", + "time_stop": "2014-07-23 16:50:00", + "venue_serial": 1462, + "description": "Open Source licenses are mostly grounded in US Copyright Law, which requires 51% representation to claim standing in any copyright-related action (including defense against infringement claims as well as re-licensing). Yet, they are also a barrier to participation, since you often must have one in place before you make a substantial (or in some cases any) contribution.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37717", + "speakers": [179595,7969,46421,6380], + "categories": [ + + "Business" + + ] + }, + + { + "serial": 37719, + "name": "Making a Difference through Open Source", + "event_type": "Keynote", + + "time_start": "2014-07-22 09:20:00", + "time_stop": "2014-07-22 09:30:00", + "venue_serial": 1525, + "description": "Non-profit entities help impact the lives for millions of people, and make the world a better place. Bluehost is working with Grassroots.org to help make it simpler for non-profits to get online and share their messages with the world. Join us and learn how you can help make a difference.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37719", + "speakers": [3476,181009,182521], + "categories": [ + + "Keynotes" + + ] + }, + + { + "serial": 37721, + "name": "10 Years of Google Summer of Code", + "event_type": "Keynote", + + "time_start": "2014-07-22 09:50:00", + "time_stop": "2014-07-22 09:55:00", + "venue_serial": 1525, + "description": "A (very) fast overview of the results of the program: lines of code, #'s of participants, and so on. ", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37721", + "speakers": [81759], + "categories": [ + + "Keynotes" + + ] + }, + + { + "serial": 37723, + "name": "Bringing OpenStack based Cloud to the Enterprise", + "event_type": "Keynote", + + "time_start": "2014-07-22 09:45:00", + "time_stop": "2014-07-22 09:50:00", + "venue_serial": 1525, + "description": "More and more Enterprises are evaluating and adopting OpenStack as an option for deployments in the cloud. HP Helion OpenStack is\r\nthe latest addition to the OpenStack distribution, find out how your Enterprise can leverage it to deliver a scalable, secure and stable cloud environment for complex workloads.\r\n", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37723", + "speakers": [177325], + "categories": [ + + "Keynotes" + + ] + }, + + { + "serial": 37724, + "name": "High Performance Visualizations with Canvas", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 13:40:00", + "time_stop": "2014-07-22 14:20:00", + "venue_serial": 1460, + "description": "This session will dig into the nuts and bolts of Canvas and explore techniques for implementing beautiful, smooth, and efficient visualizations.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37724", + "speakers": [160033], + "categories": [ + + "Sponsored Sessions" + + ] + }, + + { + "serial": 37733, + "name": "Comics for Communication and Fun!", + "event_type": "BoF", + + "time_start": "2014-07-23 19:00:00", + "time_stop": "2014-07-23 20:00:00", + "venue_serial": 1465, + "description": "Learn how to draw comics or practice drawing with fellow cartoonists at this fun session. We're all friends, regardless of skill level!", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37733", + "speakers": [], + "categories": [ + + ] + }, + + { + "serial": 37736, + "name": "Apache Spark: A Killer or Savior of Apache Hadoop?", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 13:40:00", + "time_stop": "2014-07-23 14:20:00", + "venue_serial": 1463, + "description": "The Big Boss(tm) has just OKed the first Hadoop cluster in the company. You are the guy in charge of analyzing petabytes of your company's valuable data using a combination of custom MapReduce jobs and SQL-on-Hadoop solutions. All of a sudden the web is full of articles telling you that Hadoop is dead, Spark has won and you should quit while you're still ahead. But should you? ", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37736", + "speakers": [151691], + "categories": [ + + "Sponsored Sessions" + + ] + }, + + { + "serial": 37738, + "name": "Open Manufacturing: Bringing Open Hardware Beyond 3D Printing", + "event_type": "Keynote", + + "time_start": "2014-07-24 09:30:00", + "time_stop": "2014-07-24 09:40:00", + "venue_serial": 1525, + "description": "Open source design has been a recent trend in hardware, but it tends to be limited to open libraries of 3D-printable parts. These are geared at makers, artists, hobbyists, and whoever else really wants to print their own figurines, not necessarily the engineering community.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37738", + "speakers": [182587], + "categories": [ + + "Keynotes" + + ] + }, + + { + "serial": 37742, + "name": "OpenBTS: Develop Your Own Cell Network & Apps!", + "event_type": "BoF", + + "time_start": "2014-07-23 19:00:00", + "time_stop": "2014-07-23 20:00:00", + "venue_serial": 1466, + "description": "Learn how to build your own cell network and invent cell network applications and mobile services using OpenBTS APIs in a session led by Harvind Samra, co-founder of the OpenBTS project, and Michael Iedema, Senior Engineer at Range Networks.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37742", + "speakers": [], + "categories": [ + + ] + }, + + { + "serial": 37744, + "name": "Office Hour with Renee DiResta (OATV), Bryce Roberts (OATV), and Roger Chen (OATV)", + "event_type": "Office Hours", + + "time_start": "2014-07-22 13:00:00", + "time_stop": "2014-07-22 13:30:00", + "venue_serial": 1546, + "description": "Got an Open Source project you\u2019re thinking of turning into a startup? Got a startup you\u2019re planning to raise funding for? Want help or feedback on your business plan? O\u2019Reilly AlphaTech Ventures is at OSCON and here to help. Come by and say hi!", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37744", + "speakers": [122516,1402,171704], + "categories": [ + + "Office Hours" + + ] + }, + + { + "serial": 37755, + "name": "Cassandra 2.X: Transactions, NoSQL, and Performance", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 14:30:00", + "time_stop": "2014-07-22 15:10:00", + "venue_serial": 1463, + "description": "Cassandra 2.0 introduced lightweight transactions (LWT), making it the first database to allow mixing linearizable transactions into a fully distributed, highly availability system ('AP' in CAP terminology).\r\n", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37755", + "speakers": [140062], + "categories": [ + + "Sponsored Sessions" + + ] + }, + + { + "serial": 37759, + "name": "Geek Choir", + "event_type": "BoF", + + "time_start": "2014-07-23 20:00:00", + "time_stop": "2014-07-23 21:00:00", + "venue_serial": 1464, + "description": "Much has been written about the connections between mathematics and music. Come meet and collaborate with your fellow attendees as we engage in another OSCON tradition: Geek Choir! (Yes, there will be singing.)", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37759", + "speakers": [], + "categories": [ + + ] + }, + + { + "serial": 37767, + "name": "PostgreSQL BoF", + "event_type": "BoF", + + "time_start": "2014-07-22 19:00:00", + "time_stop": "2014-07-22 20:00:00", + "venue_serial": 1451, + "description": "Get together with the local users group + Pg folks in town for OSCON.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37767", + "speakers": [], + "categories": [ + + ] + }, + + { + "serial": 37768, + "name": "Office Hour with Tim O'Reilly (O'Reilly Media, Inc.)", + "event_type": "Office Hours", + + "time_start": "2014-07-22 12:45:00", + "time_stop": "2014-07-22 13:15:00", + "venue_serial": 1547, + "description": "Have you wondered about \u201cbig idea marketing\u201d? That is, how to align what you do with big movements many people care about? Tim has advice on this and other topics, such as company culture, startup and project pitches, how programming is changing in the era of cloud computing and DevOps, and why it\u2019s important for coders to help improve government services (like the healthcare.gov rescue effort).", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37768", + "speakers": [251], + "categories": [ + + "Office Hours" + + ] + }, + + { + "serial": 37769, + "name": "Office Hour with Tim Bray (Independent)", + "event_type": "Office Hours", + + "time_start": "2014-07-22 17:45:00", + "time_stop": "2014-07-22 18:15:00", + "venue_serial": 1546, + "description": "Are you worried about software, the Net, and life online? According to Tim, now is the time for sensible, reasonable, extreme paranoia. Come chat with him about privacy policy and technology, public-key encryption, identity federation, and related topics. ", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37769", + "speakers": [24978], + "categories": [ + + "Office Hours" + + ] + }, + + { + "serial": 37771, + "name": "Tim O'Reilly", + "event_type": "Keynote", + + "time_start": "2014-07-23 10:00:00", + "time_stop": "2014-07-23 10:10:00", + "venue_serial": 1525, + "description": "Keynote by Tim O'Reilly, founder and CEO of O'Reilly Media.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37771", + "speakers": [251], + "categories": [ + + "Keynotes" + + ] + }, + + { + "serial": 37773, + "name": "Hello User Group", + "event_type": "BoF", + + "time_start": "2014-07-23 19:00:00", + "time_stop": "2014-07-23 20:00:00", + "venue_serial": 1451, + "description": "An open discussion on how to start a local user group and grow it into a successful community.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37773", + "speakers": [], + "categories": [ + + ] + }, + + { + "serial": 37775, + "name": "Gophers of a Feather", + "event_type": "BoF", + + "time_start": "2014-07-23 20:00:00", + "time_stop": "2014-07-23 21:00:00", + "venue_serial": 1454, + "description": "Come meet other Go developers and hear why Go is becoming the new language of the cloud.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37775", + "speakers": [], + "categories": [ + + ] + }, + + { + "serial": 37776, + "name": "Application Blueprints with Apache Brooklyn", + "event_type": "BoF", + + "time_start": "2014-07-22 20:00:00", + "time_stop": "2014-07-22 21:00:00", + "venue_serial": 1456, + "description": "brooklyn.io lets you create blueprints to deploy and manage distributed applications, building on best-practice blueprints and policies maintained by the community. Recently accepted into the Apache Incubator, Brooklyn is a multi-cloud autonomic control plane based on the OASIS CAMP YAML standard for composing deployment plans.\r\n\r\nThis BoF is for anyone wanting to share experiences or learn more.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37776", + "speakers": [], + "categories": [ + + ] + }, + + { + "serial": 37777, + "name": "Managing Containerized Applications", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 14:30:00", + "time_stop": "2014-07-22 15:10:00", + "venue_serial": 1460, + "description": "Containers provide an opportunity for more direct management of\r\napplications. However, loosely coupled, distributed, elastic\r\nmicro-services require more than individual containers and hosts.\r\nKubernetes is a new open source project inspired by Google\u2019s internal\r\nworkload management systems that establishes robust primitives for\r\nmanaging applications comprised of multiple containers.\r\n", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37777", + "speakers": [183169], + "categories": [ + + "Sponsored Sessions" + + ] + }, + + { + "serial": 37778, + "name": "Bringing More Women to Free and Open Source Software", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 17:00:00", + "time_stop": "2014-07-22 17:40:00", + "venue_serial": 1463, + "description": "This talk will introduce GNOME's Outreach Program for Women, outline\r\nhow the program works and update you on what's happened recently.\r\nTired of people talking about how there isn't diversity in free\r\nsoftware? Come to this talk and find out how you can help actually\r\nchange things.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37778", + "speakers": [173364], + "categories": [ + + "Sponsored Sessions" + + ] + }, + + { + "serial": 37779, + "name": "The Epic Battle: Scala at PayPal", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 13:40:00", + "time_stop": "2014-07-22 14:20:00", + "venue_serial": 1463, + "description": "There\u2019s only one way to do it here at PayPal \u2013 with a framework. Everything you'll ever need is done if you stay inside the lines. Imagine my reaction when I joined PayPal with Scala and high hopes. We have many reasons to avoid the monolithic framework, so we were going against the grain from day 1.\r\n", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37779", + "speakers": [128980], + "categories": [ + + "Sponsored Sessions" + + ] + }, + + { + "serial": 37780, + "name": "DreamFactory REST API Platform for Mobile and IoT", + "event_type": "BoF", + + "time_start": "2014-07-23 20:00:00", + "time_stop": "2014-07-23 21:00:00", + "venue_serial": 1458, + "description": "DreamFactory is an open source REST API platform that makes it easy to develop mobile and IoT applications without rolling your own user management, security, and REST APIs on the server. Come learn what DreamFactory is all about!", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37780", + "speakers": [], + "categories": [ + + ] + }, + + { + "serial": 37794, + "name": "Containers - What We Have and What's Missing?", + "event_type": "BoF", + + "time_start": "2014-07-22 20:00:00", + "time_stop": "2014-07-22 21:00:00", + "venue_serial": 1463, + "description": "We will discuss the current state of containers and what can/should be improved.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37794", + "speakers": [], + "categories": [ + + ] + }, + + { + "serial": 37795, + "name": "Gophers with Hammers: Fun with Parsing and Generating Go", + "event_type": "40-minute conference session", + + "time_start": "2014-07-22 14:30:00", + "time_stop": "2014-07-22 15:10:00", + "venue_serial": 1461, + "description": "Go is an open source language first released in 2009. Go is popular\r\nbecause it makes coding super-fun again. Josh will illustrate the deep role tools\r\nlike gofmt, godoc, 'go vet', 'go test' and others play in the Go\r\nexperience, show you how to roll your own, and talk about some unexplored\r\npossibilities.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37795", + "speakers": [179435], + "categories": [ + + "Sponsored Sessions" + + ] + }, + + { + "serial": 37801, + "name": "Racing Change: Accelerating Innovation Through Radical Transparency", + "event_type": "Keynote", + + "time_start": "2014-07-23 09:20:00", + "time_stop": "2014-07-23 09:30:00", + "venue_serial": 1525, + "description": "In February of this year, PayPal announced it had hired Danese Cooper as their first Head of Open Source. PayPal? And Open Source? In fact, Open Source is playing a key role in reinventing PayPal engineering as a place where innovation at scale is easy and fun - especially if you like to work in Open Source.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37801", + "speakers": [76338,179599,179595,179435], + "categories": [ + + "Keynotes" + + ] + }, + + { + "serial": 37808, + "name": "Build Responsive Web Apps with OpenUI5", + "event_type": "40-minute conference session", + + "time_start": "2014-07-23 13:40:00", + "time_stop": "2014-07-23 14:20:00", + "venue_serial": 1461, + "description": "Meet OpenUI5--a powerful web UI library for developing responsive web apps that run on and adapt to any current browser and device. \r\n\r\n", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37808", + "speakers": [173233,104828,170822], + "categories": [ + + "Sponsored Sessions" + + ] + }, + + { + "serial": 37810, + "name": "Gigawatts", + "event_type": "Event", + + "time_start": "2014-07-23 19:00:00", + "time_stop": "2014-07-23 19:30:00", + "venue_serial": 1450, + "description": "Some talks carefully guide the listeners through the entirety of a topic,\r\n starting with the basics and ending with the fine details.\r\n\r\nThat's\u2026 not the plan for this talk.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37810", + "speakers": [3189], + "categories": [ + + "Events", + + "Perl" + + ] + }, + + + + { + "serial": "slot_15176", + "name": "Lunch", + "event_type": "break", + + "time_start": "2014-07-20 12:30:00", + "time_stop": "2014-07-20 13:30:00", + "venue_serial": 1468, + "categories": [ + "Break" + ] + }, + + { + "serial": "slot_15177", + "name": "Lunch", + "event_type": "break", + + "time_start": "2014-07-21 12:30:00", + "time_stop": "2014-07-21 13:30:00", + "venue_serial": 1468, + "categories": [ + "Break" + ] + }, + + { + "serial": "slot_15276", + "name": "Morning Break", + "event_type": "break", + + "time_start": "2014-07-22 10:10:00", + "time_stop": "2014-07-22 10:40:00", + "venue_serial": 1467, + "categories": [ + "Break" + ] + }, + + { + "serial": "slot_15277", + "name": "Lunch", + "event_type": "break", + + "time_start": "2014-07-22 12:10:00", + "time_stop": "2014-07-22 13:40:00", + "venue_serial": 1469, + "categories": [ + "Break" + ] + }, + + { + "serial": "slot_15279", + "name": "Afternoon break", + "event_type": "break", + + "time_start": "2014-07-22 15:10:00", + "time_stop": "2014-07-22 16:10:00", + "venue_serial": 1467, + "categories": [ + "Break" + ] + }, + + { + "serial": "slot_15294", + "name": "Morning Break", + "event_type": "break", + + "time_start": "2014-07-23 10:10:00", + "time_stop": "2014-07-23 10:40:00", + "venue_serial": 1467, + "categories": [ + "Break" + ] + }, + + { + "serial": "slot_15295", + "name": "Lunch", + "event_type": "break", + + "time_start": "2014-07-23 12:10:00", + "time_stop": "2014-07-23 13:40:00", + "venue_serial": 1469, + "categories": [ + "Break" + ] + }, + + { + "serial": "slot_15311", + "name": "Afternoon Break", + "event_type": "break", + + "time_start": "2014-07-23 15:10:00", + "time_stop": "2014-07-23 16:10:00", + "venue_serial": 1467, + "categories": [ + "Break" + ] + }, + + { + "serial": "slot_15409", + "name": "Morning Break", + "event_type": "break", + + "time_start": "2014-07-24 10:40:00", + "time_stop": "2014-07-24 11:00:00", + "venue_serial": 1473, + "categories": [ + "Break" + ] + }, + + { + "serial": "slot_15895", + "name": "Dinner", + "event_type": "break", + + "time_start": "2014-07-23 17:40:00", + "time_stop": "2014-07-23 19:00:00", + "venue_serial": 1523, + "categories": [ + "Break" + ] + } + + ], + + "speakers": [ + + { + + "serial": 149868, + "name": "Faisal Abid", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_149868.jpg", + "url": "http://www.faisalabid.com/", + "position": "Engineer & Entrepreneur ", + "affiliation": "Dynamatik, Inc.", + "twitter": "FaisalAbid", + "bio": "

I am a software engineer, author, teacher and entrepreneur.

\n

From the hardcore server-side and database challenges to the the front end issues, I love solving problems and strive to create software that can make a difference.

\n

I’m an author published by Manning and O’Reilly and have also appeared in leading publications with my articles on ColdFusion and Flex.

\n

In my free time I teach Android or Node.js at workshops around the word, speak at conferences such as OSCON, CodeMotion, FITC and AndroidTO.

\n

Currently, I’m the founder of Dynamatik, a design and development agency in Toronto.

\n

During the days I am a Software Engineer at Kobo working on OS and app level features for Android tablets.

" + }, + + { + + "serial": 172532, + "name": "Josh Adams", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_172532.jpg", + "url": "http://isotope11.com/", + "position": "CTO", + "affiliation": "Isotope11", + "twitter": "knewter", + "bio": "

I’m the CTO of Isotope11, a mildly successful software development company that focuses on Ruby, JavaScript, and Erlang/Elixir. I’m also responsible for http://www.elixirsips.com, a screencast series wherein I walk through elixir as I learn it, and record 2 shortish (5-12 minutes, occasionally longer) videos per week. I’ve also been a co-author and a technical reviewer for multiple books on the Arduino microprocessor and Ruby, and have had a lot of fun doing robotics with Ruby as well. I’m currently aiming to bring that robotics fun to Erlang (or Elixir)

" + }, + + { + + "serial": 104828, + "name": "DJ Adams", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_104828.jpg", + "url": "http://www.pipetree.com/qmacro", + "position": "Architect, Integrator, Author, Bread-Maker", + "affiliation": "Bluefin Solutions Ltd", + "twitter": "qmacro", + "bio": "

DJ Adams is an enterprise architect and open source programmer, author, and bread-maker living in Manchester, working as a Principal Consultant for Bluefin Solutions. He has a degree in Latin & Greek (Classics) from the University of London, and despite having been referred to as an alpha geek, can nevertheless tie his own shoelaces and drink beer without spilling it.

\n

He has written two books for O’Reilly, on Jabber and on Google.

\n

He is married to his theoretical childhood sweetheart Michelle, and has a son, Joseph, of whom he is very proud.

" + }, + + { + + "serial": 29558, + "name": "Lance Albertson", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_29558.jpg", + "url": "http://www.lancealbertson.com/", + "position": "Director", + "affiliation": "Oregon State University Open Source Lab", + "twitter": "ramereth", + "bio": "

Lance Albertson is the Director for the Oregon State University Open Source Lab (OSL) and has been involved with the Gentoo Linux project as a developer and package maintainer since 2003. Since joining the OSL in 2007, Lance has managed all of the hosting activities that the OSL provides for nearly 160 high-profile open source projects. He was recently promoted to Director in early 2013 after being the Lead Systems Administration and Architect since 2007.

\n

Prior to joining the OSUOSL, Lance was a UNIX Administrator for the Enterprise Server Technologies group at Kansas State University. Lance prepared for life as a career systems administrator by grappling with natural systems first, joining his father near Hiawatha, Kansas on the family farm growing corn and soybeans.

\n

In his free time he helps organize the Corvallis Beer and Blog and plays trumpet in a local jazz group The Infallible Collective. He holds a B.A. in Agriculture Technology Management from Kansas State University, where he minored in Agronomy and Computer Science.

" + }, + + { + + "serial": 109270, + "name": "Andrei Alexandrescu", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_109270.jpg", + "url": "http://erdani.com/", + "position": "Research Scientist", + "affiliation": "Facebook", + "twitter": "incomputable", + "bio": "

Andrei Alexandrescu is a Research Scientist at Facebook and coined the colloquial term ‘modern C++’, used today to describe a collection of important C++ styles and idioms. His eponymous book on the topic, Modern C++ Design (Addison-Wesley, 2001), revolutionized C++ programming and
\nproduced a lasting influence not only on subsequent work on C++, but also on other languages and systems. With Herb Sutter, Andrei is also the
\ncoauthor of C++ Coding Standards (Addison-Wesley, 2004).

\n

Through Andrei’s varied work on libraries and applications, as well as his research in
\nmachine learning and natural language processing, he has garnered a solid reputation in both industrial and academic circles. Andrei has also been
\nthe key designer of many important features of the D programming language and has authored a large part of D’s standard library, positioning him to
\nwrite an authoritative book on the new language, appropriately entitled The D Programming Language (Addison-Wesley, 2010).

\n

Andrei holds a Ph.D. in Computer Science from the University of Washington and a B.Sc. in Electrical Engineering from University ‘Politehnica’ Bucharest.

" + }, + + { + + "serial": 2216, + "name": "Alasdair Allan", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_2216.jpg", + "url": "http://thethingsystem.com/", + "position": "Director", + "affiliation": "Babilim Light Industries", + "twitter": "aallan", + "bio": "

Alasdair Allan is a Scientist, Author, Hacker, Tinkerer and Journalist who has been thinking about the Internet of Things, which he thinks is broken.

\n

He is the author of a number of books, and from time to time he also stands in front of cameras. You can often find him at conferences talking about interesting things, or deploying sensors to measure them. He recently rolled out a mesh network of five hundred sensors motes covering the entire of Moscone West during Google I/O. He’s still recovering.

\n

A few years before that he caused a privacy scandal by uncovering that your iPhone was recording your location all the time. This caused several class action lawsuits and a U.S. Senate hearing. Several years on, he still isn’t sure what to think about that.

\n

He sporadically writes blog posts about things that interest him, or more frequently provides commentary in 140 characters or less. He is a contributing editor for MAKE magazine, and a contributor to the O’Reilly Radar.

\n

Alasdair is a former academic. As part of his work he built a distributed peer-to-peer network of telescopes which, acting autonomously, reactively scheduled observations of time-critical events. Notable successes included contributing to the detection of what was\u2014at the time\u2014the most distant object yet discovered, a gamma-ray burster at a redshift of 8.2.

" + }, + + { + + "serial": 170293, + "name": "Jamie Allen", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_170293.jpg", + "url": "http://typesafe.com/", + "position": "Director of Consulting", + "affiliation": "Typesafe", + "twitter": "jamie_allen", + "bio": "

Jamie Allen has worked in consulting since 1994, with top firms including Price Waterhouse and Chariot Solutions. He has a long track record of collaborating closely with clients to build high-quality, mission-critical systems that scale to meet the needs of their businesses, and has worked in myriad industries including automotive, retail, pharmaceuticals, telecommunications and more. Jamie has been coding in Scala and actor-based systems since 2009, and is the author of “Effective Akka” book from O’Reilly.

" + }, + + { + + "serial": 46440, + "name": "Rob Allen", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_46440.jpg", + "url": "http://akrabat.com/", + "position": "Owner", + "affiliation": "Nineteen Feet Limited", + "twitter": "", + "bio": "

Rob Allen is a software engineer, project manager and trainer. Rob Allen has been programming in a with PHP for a very long time now and contributes to Zend Framework and other open source projects. He is a ZF contributor, member of the ZF Community team and also wrote Zend Framework in Action. Rob holds a Masters degree in Electronic Engineering from the University of Birmingham in the UK and started out writing C++ applications; he now concentrates solely on web-based applications in PHP. Rob is UK-based and runs Nineteen Feet Limited, focussing on web development, training and consultancy.

" + }, + + { + + "serial": 117513, + "name": "Dan Allen", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_117513.jpg", + "url": "http://google.com/profiles/dan.j.allen", + "position": "Open Source Community Catalyst", + "affiliation": "OpenDevise", + "twitter": "mojavelinux", + "bio": "

Dan is an open source advocate, community catalyst, software generalist, author and speaker. Most of the time, he’s hacking with some JVM language. He leads the Asciidoctor project and serves as the community liaison for Arquillian. He builds on these experiences to help make a variety of open source projects wildly successful, including Asciidoctor, Arquillian, Opal and JBoss Forge.

\n

Dan is the author of Seam in Action (Manning, 2008) and has written articles for NFJS, the Magazine, IBM developerWorks, Java Tech Journal and JAXenter. He’s also an internationally recognized speaker, having presented at major software conferences including JavaOne, Devoxx, OSCON, NFJS / UberConf / RWX, JAX and jFokus. He’s recognized as a JavaOne Rock Star and Java (JVM) Champion.

\n

After a long conference day, you’ll likely find Dan geeking out about technology, documentation and testing with fellow community members over a Trappist beer or Kentucky Bourbon.

" + }, + + { + + "serial": 173281, + "name": "Mohammad Almalkawi", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_173281.jpg", + "url": null, + "position": "Senior Software Engineer", + "affiliation": "Quip", + "twitter": "moh", + "bio": "

Mohammad is a software engineer at Quip. Before Quip, Mohammad was a technical lead on Twitter’s Mobile team where he was focused on Android platform and mobile tools. Prior to Twitter, Mohammad worked on WinRT Core in the operating systems group at Microsoft. Mohammad studied computer engineering at the University of Illinois at Urbana-Champaign where he specialized in real-time embedded systems.

" + }, + + { + + "serial": 108272, + "name": "Mike Amundsen", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_108272.jpg", + "url": "http://amundsen.com/blog/", + "position": "Director of API Architecture", + "affiliation": "API Academy, CA Technologies", + "twitter": "mamund", + "bio": "

An internationally known author and lecturer, Mike Amundsen travels throughout the world consulting and speaking on a wide range of topics including distributed network architecture, Web application development, and other subjects.

\n

In his role of API architect at Layer 7, Amundsen heads up the API Architecture and Design Practice in North America. He is responsible for working with companies to provide insight on how best to capitalize on the myriad opportunities APIs present to both consumers and the enterprise.

\n

Amundsen has authored numerous books and papers on programming over the last 15 years. His most recent book is a collaboration with Leonard Richardson titled “RESTful Web APIs”. His 2011 book, \u201cBuilding Hypermedia APIs with HTML5 and Node\u201d, is an oft-cited reference on building adaptable distributed systems.

" + }, + + { + + "serial": 122599, + "name": "Ishan Anand", + "photo": null, + "url": "http://moovweb.com/", + "position": "Director of New Products", + "affiliation": "Moovweb", + "twitter": null, + "bio": "

Ishan Anand is Director of New Products at Moovweb. He has been building and launching mobile products for the iPhone since the day it was released, and his work has been featured on TechCrunch, ReadWriteWeb and LifeHacker. Ishan has a solid background in software engineering with a focus on web and native application development for iOS devices and WebKit browsers. Prior to Moovweb, Ishan worked at Digidesign and his expertise was in multi-threaded real-time systems programming for the computer music industry. He holds dual-degrees in electrical engineering and mathematics from MIT.

" + }, + + { + + "serial": 173201, + "name": "John Anderson", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_173201.jpg", + "url": "http://genehack.org/", + "position": "Director of Technology", + "affiliation": "Infinity Interactive", + "twitter": "genehack", + "bio": "

Lapsed biologist turned sysadmin turned programmer turned tech lead. Film at 11.

" + }, + + { + + "serial": 179599, + "name": "Edwin Aoki", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_179599.jpg", + "url": null, + "position": "Technical Fellow", + "affiliation": "PayPal", + "twitter": null, + "bio": "" + }, + + { + + "serial": 143135, + "name": "Yazz Atlas", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_143135.jpg", + "url": null, + "position": "Principle Engineer", + "affiliation": "Hewlett-Packard", + "twitter": "EntropyWorks", + "bio": "

Yazz Atlas, who has been involved with open source projects for the past eighteen years, is currently working for the Advanced Technology Group within HP. His primary focus is replacing him self with a script and deploying OpenStack for the HP Cloud PaaS Core.

" + }, + + { + + "serial": 4429, + "name": "R Geoffrey Avery", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_4429.jpg", + "url": null, + "position": "Computer Scientist", + "affiliation": "Platypi Ventures", + "twitter": "rGeoffrey", + "bio": "

For many years a Perl Bioinformatics programmer in Philadelphia for a major pharma company helping scientists load, analyze and view their data.

" + }, + + { + + "serial": 173455, + "name": "Vishwas Babu", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_173455.jpg", + "url": null, + "position": "Architect", + "affiliation": "Conflux Technologies", + "twitter": "", + "bio": "

Vishwas Babu is the lead engineer at the Mifos Initiative where he works on building an open technology platform for financial inclusion to the poor. He is also a co-founder of Conflux Technologies Private Limited, an organization based out of Bangalore, India which provides a multitude of financial solutions targeted toward microfinance institutions.

" + }, + + { + + "serial": 108813, + "name": "Jono Bacon", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_108813.jpg", + "url": "http://www.jonobacon.org/", + "position": "Senior Director of Community", + "affiliation": "XPRIZE Foundation", + "twitter": "jonobacon", + "bio": "

Jono Bacon is a leading community manager, speaker, and author. Currently he works as Senior Director of Community at the XPRIZE Foundation and was formally the Ubuntu Community Manager at Canonical, optimizing and growing the global Ubuntu community.

\n

Bacon is a prominent author and speaker on community management and best practice, and wrote the best-selling The Art of Community (O\u2019Reilly), is the founder of the primary annual conference for community managers and leaders, the Community Leadership Summit, founder of the Community Leadership Forum, and is a regular keynote speaker at events about community management, leadership, and best practice.

\n

Bacon has provided community management consultancy for both internal and external communities for a range of organizations. This includes Deutsche Bank, Intel, SAP, Sony Mobile, Samsung, Open Compute Project, IBM, Dyson, Mozilla, National Finishing Contractors Association, AlienVault, and others.

\n

In addition to The Art of Community, Bacon co-authored Linux Desktop Hacks (O\u2019Reilly), Official Ubuntu Book (Prentice Hall), and Practical PHP and MySQL (Prentice Hall), and has written over 500 articles across 12 different publications. He writes regularly for a range of magazines.

\n

Bacon was the co-founder of the popular LugRadio podcast, which ran for four years with 2million+ downloads and 15,000 listeners, as well as spawning five live events in both the UK and the USA, and co-founded the Shot Of Jaq podcast. He co-founded the Bad Voltage podcast, a popular show about technology, Open Source, politics, and more. Bacon is also the founder of the Ubuntu Accomplishments, Jokosher, Acire, Python Snippets, and Lernid software projects.

\n

He lives in the San Francisco Bay Area in California with his wife, Erica, and their son, Jack.

" + }, + + { + + "serial": 59574, + "name": "Josh Barratt", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_59574.jpg", + "url": "http://serialized.net/", + "position": "Chief Architect", + "affiliation": "Media Temple", + "twitter": "jbarratt", + "bio": "

Josh joined leading (mt) Media Temple, a leading LA-based web hosting and cloud services company, as a system engineer in 2003 before making his way up to CTO and then Chief Architect. Josh’s world is a blend of computer science, software development, systems administration and the people and processes that tie it all together. Prior to Media Temple, he worked six years as a software engineer and has built everything from large clustered systems to embedded real-time motion control for special effects, and almost everything in between. Though experienced in a number of programming languages and environments, his current happy place is with Python.

" + }, + + { + + "serial": 23017, + "name": "Rick Barraza", + "photo": null, + "url": "http://www.cynergysystems.com/", + "position": "Senior Experience Architect", + "affiliation": "Cynergy Systems", + "twitter": "rickbarraza", + "bio": "

Rick Barraza has been working at the forefront of integrating design and technology for over a decade. A strong voice in the interactive design community, Rick is often engaged in promoting the balance of great experiences, creative technology and business motivators. Rick’s wide experience across many aspects of both design and development have made him a key asset at Cynergy where he works with a diverse clientele across a broad spectrum of needs- from branding and design to prototype development and experience architecture.

" + }, + + { + + "serial": 180437, + "name": "Doran Barton", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_180437.jpg", + "url": "http://fozzolog.fozzilinymoo.org/", + "position": "Senior Developer, Billing", + "affiliation": "Bluehost", + "twitter": "fozzmoo", + "bio": "

Doran is a long-time open source advocate. A Unix user and instructor beginning in the early 1990s, he began using Linux in late 1994. In 1998, he started a consulting company in northern Utah promoting the use of Linux and open source solutions for businesses. His career has been split between systems administration/architecture and development.

\n

Doran now works as a senior developer at Bluehost, an Endurance International web hosting company. Leveraging his mixed background, he has helped move the company forward in using Software Collections to use more modern versions of Perl and other software on Enterprise Linux distributions.

" + }, + + { + + "serial": 179963, + "name": "David Baumgold", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_179963.jpg", + "url": "http://www.davidbaumgold.com/", + "position": "Developer Advocate", + "affiliation": "edX", + "twitter": null, + "bio": "

David Baumgold is a web developer and open source advocate based in the Boston area. He enjoys teaching, learning, and connecting interesting people with each other. He genuinely believes that everything will all work out in the end, somehow.

" + }, + + { + + "serial": 152215, + "name": "Brent Beer", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_152215.jpg", + "url": "http://github.com/", + "position": "Trainer", + "affiliation": "GitHub", + "twitter": "brntbeer", + "bio": "

Brent Beer has used Git and GitHub for over 5 years through university classes, contributions to open source projects, and professionally as a web developer. He now enjoys his role teaching the world to use Git and GitHub to their full potential as a member of the GitHub Training team.

" + }, + + { + + "serial": 7969, + "name": "Brian Behlendorf", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_7969.jpg", + "url": "http://brian.behlendorf.com/", + "position": "Managing Director", + "affiliation": "Mithril Capital Management LLC", + "twitter": "brianbehlendorf", + "bio": "

Brian Behlendorf is Managing Director at Mithril Capital Management in San Francisco. His career has been a mix of technology start-up, public policy, and non-profit tech leadership. Brian serves on the Boards of the Mozilla Foundation, the Electronic Frontier Foundation, and Benetech, three organizations using technology to fight for civil liberties, open technologies, and social impact in the digital domain. Prior to Mithril, Brian was Chief Technology Officer at the World Economic Forum. He also served for two years at the White House as advisor to the Open Government project within the Office of Science and Technology Policy, and then later as advisor to Health and Human Services on open software approaches to health information sharing. Before that he has founded two tech companies (CollabNet and Organic) and several Open Source software projects (Apache, Subversion, and more).

" + }, + + { + + "serial": 170052, + "name": "Tim Bell", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_170052.jpg", + "url": null, + "position": "Infrastructure Manager", + "affiliation": "CERN", + "twitter": "noggin143", + "bio": "Tim Bell is responsible for the CERN IT Infrastructure Group which supports Windows, Mac and Linux across the site along with virtualisation, E-mail and web services. These systems are used by over 11,000 scientists researching fundamental physics, finding out what the Universe is made of and how it works. Prior to working at CERN, Tim worked for Deutsche Bank managing private banking infrastructure in Europe and for IBM as a Unix kernel developer and deploying large scale technical computing solutions." + }, + + { + + "serial": 173340, + "name": "Adam Benayoun", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_173340.jpg", + "url": "http://binpress.com/", + "position": "CEO", + "affiliation": "Binpress", + "twitter": "adambn", + "bio": "

Adam is the co-founder and CEO of Binpress, a marketplace for commercial open source – providing a platform for building a profitable business from creating and working on open source projects.

\n

Adam has launched over 15 web ventures for the past 10 years and utilized numerous open source projects in the process of building his products.

\n

He studied Animation at the Minshar School of Arts and is an alumni of the 500startups accelerator in Mountain View.

" + }, + + { + + "serial": 173233, + "name": "Frederic Berg", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_173233.jpg", + "url": "http://www.sap.com/", + "position": "Product Owner", + "affiliation": "SAP AG", + "twitter": "frdrcbrg", + "bio": "

Frederic is a product owner with 15 years of experience in the software industry living in Heidelberg, Germany. He is passionate about software development and a major driver behind the open sourcing of OpenUI5.

" + }, + + { + + "serial": 137697, + "name": "Tim Berglund", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_137697.jpg", + "url": "http://www.datastax.com/", + "position": "Global Director of Technical Enablement", + "affiliation": "DataStax", + "twitter": "tlberglund", + "bio": "

Tim is a teacher, author, and technology leader with DataStax. He is a conference speaker internationally and in the United States, and contributes to the Denver, Colorado tech community as president of the Denver Open Source User Group. He is the co-presenter of various O\u2019Reilly training videos on topics ranging from Git to Mac OS X Productivity Tips to Apache Cassandra, and is the author of Gradle Beyond the Basics. He blogs very occasionally at timberglund.com, and lives in Littleton, CO, USA with the wife of his youth and their three children.

" + }, + + { + + "serial": 106355, + "name": "Andrew Berkowitz", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_106355.jpg", + "url": "http://www.teamsnap.com/", + "position": "Chief Product Officer", + "affiliation": "TeamSnap", + "twitter": "andrewberkowitz", + "bio": "

Andrew Berkowitz is a founder and Chief Product Officer at TeamSnap.com. He is also Head Coach of ComedySportz Portland, a branch of the international Comedy Improv Theater.

" + }, + + { + + "serial": 3397, + "name": "Josh Berkus", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_3397.jpg", + "url": "http://www.pgexperts.com/", + "position": "CEO", + "affiliation": "PostgreSQL Experts, Inc.", + "twitter": "FuzzyChef", + "bio": "

Josh Berkus is primarily known as one of the Core Team of the world-spanning open source database project PostgreSQL. As CEO of PostgreSQL Experts, Inc., he speaks on database and open source topics all over the world, and consults on database design, performance, and open source community building. He also makes pottery and is a darned good cook.

" + }, + + { + + "serial": 10, + "name": "Gina Blaber", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_10.jpg", + "url": null, + "position": "VP, Conferences", + "affiliation": "O'Reilly Media, Inc.", + "twitter": "ginablaber", + "bio": "

VP of Conferences, O’Reilly. Interested in big data, web performance and operations, open source, publishing, location-based technologies, JavaScript, social media, and related topics. Love to hear about cutting edge content and speakers in these areas, and practical ideas for reaching a more diverse audience and speaker base.

" + }, + + { + + "serial": 179435, + "name": "Josh Bleecher Snyder", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_179435.jpg", + "url": "http://www.paypal.com/", + "position": "Director of Software Engineering", + "affiliation": "PayPal", + "twitter": "josharian", + "bio": "

Josh Bleecher Snyder is the Director Of Software Engineering at PayPal. He was a co-founder / CTO of Card.io, which was acquired by PayPal in 2012. Josh was leading card.io\u2019s technology development. Before card.io, Josh founded Treeline Labs, an iOS software development company, and was a Senior Software Engineer at AdMob where he was the first iOS engineer. Josh dropped out of the Philosophy Ph.D. program at Stanford University (ABD).

" + }, + + { + + "serial": 2593, + "name": "Michael Bleigh", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_2593.jpg", + "url": "http://www.divshot.com/", + "position": "CEO/Cofounder", + "affiliation": "Divshot", + "twitter": "mbleigh", + "bio": "

Michael is a JavaScript and Ruby developer and the CEO/Cofounder of Divshot, a static web hosting platform for developers. He has been working to make web development better through open source contributions (such as OmniAuth, Grape, and Themestrap) as well as by speaking at events including as RailsConf, Confoo, Open Source Bridge, and OSCON.

" + }, + + { + + "serial": 143232, + "name": "Olivier Bloch", + "photo": null, + "url": null, + "position": "Senior Technical Evangelist", + "affiliation": "Microsoft", + "twitter": "obloch", + "bio": "

I am Senior Technical Evangelist at Microsoft Open Technologies, Inc., looking into interoperability, cross platform and open source development for Microsoft client platforms: Windows Phone, Windows and IE.
\nPrior to this role I have been in the embedded space for almost 10 years prior to this, developing, consulting, training and talking about the Internet Of Things, connected devices and other cool and interesting topics in France, in the US and in other places around the world.
\nI am not just a geek who loves to play around with the latest gadgets\u2026 well, mostly a geek\u2026 but I love the idea that geeking around with these devices is all about what the consumer and industrial ecosystems are becoming these days.

" + }, + + { + + "serial": 172898, + "name": "Adam Bordelon", + "photo": null, + "url": "http://mesosphere.io/", + "position": "Distributed Systems Engineer", + "affiliation": "Mesosphere, Inc.", + "twitter": "", + "bio": "

Adam is a distributed systems engineer at Mesosphere and works on Apache Mesos.
\nBefore joining Mesosphere, Adam was lead developer on the Hadoop core team at MapR Technologies, he developed distributed systems for personalized recommendations at Amazon, and he rearchitected the LabVIEW compiler at National Instruments. He completed his Master\u2019s degree at Rice University, building a tool to analyze supercomputer performance data for bottlenecks and anomalies.

" + }, + + { + + "serial": 180268, + "name": "Jay Borenstein", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_180268.jpg", + "url": null, + "position": "CS Lecturer", + "affiliation": "Stanford", + "twitter": "", + "bio": "

I was born in Southern California. I did my undergraduate and graduate studies at Stanford, specializing in quantitative economics and operations research. I discovered both academic disciplines benefited greatly from computer simulation and I found my passions increasingly directed toward realizing products and experiences through software.

\n

Another thread in my personal and professional life has been a love for teams of people. I find few things to be as stimulating as a smart and motivated group of people coming together in a common pursuit.

\n

Combining the above interests, the culture of Silicon Valley (and Stanford itself, for that matter) has been a perfect fit for
\n me. After initially working as software engineer for a number of years and then plying the waters in technology management as a CTO, I founded Integration Appliance in 2000. I stepped down as CEO and away from day-to-day operations at IntApp in May, 2007 and served on the board through 2012. Today, IntApp is a successful and growing company.

\n

Part of the reason for stepping away from a mature business is the quest for new challenges. Another part is the desire to get
\n back to the root of where many great technologies and ideas emerge; academia. I currently teach computer science at Stanford University and also run Facebook Open Academy as part of a larger Facebook effort to modernize education. I find it very fulfilling to help motivated, bright minds grow and succeed.

" + }, + + { + + "serial": 96208, + "name": "Joe Bowser", + "photo": null, + "url": "http://phonegap.com/", + "position": "Computer Scientist", + "affiliation": "Adobe Systems", + "twitter": "infil00p", + "bio": "

Joe is the creator of PhoneGap for Android and is the longest contributing committer to the PhoneGap and Apache Cordova projects respectively. When he is not contributing to Open Source at Adobe, he spends his spare time working on various hardware projects at home, as well as at the Vancouver Hack Space, which he co-founded.

" + }, + + { + + "serial": 173248, + "name": "Garth Braithwaite", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_173248.jpg", + "url": "http://www.garthdb.com/", + "position": "Open Source Designer and Developer", + "affiliation": "Adobe", + "twitter": "garthdb", + "bio": "

Previous lead designer on Brackets. Current developer on Topcoat. Core contributor for Open Source Design.

\n

Four daughters under the age of 9 (ladies man). Fanboy.

" + }, + + { + + "serial": 159586, + "name": "Alex Brandt ", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_159586.jpg", + "url": null, + "position": "", + "affiliation": "Rackspace Hosting", + "twitter": "", + "bio": "

Alex Brandt is a cloud developer at Rackspace, helping people build distributed scalable systems with a variety of technologies. An emerging evangelist for all things cloud, he has worked in IT support and solutioning as well as research in the realm of physics. Alex holds a B.S. in computer science and physics from Minnesota State University Moorhead.

" + }, + + { + + "serial": 131404, + "name": "VM Brasseur", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_131404.jpg", + "url": "http://shoeless-consulting.com/", + "position": "Founder", + "affiliation": "shoeless consulting", + "twitter": "vmbrasseur", + "bio": "

VM is a manager of technical people, projects, processes, products and p^Hbusinesses. In her over 15 years in the tech industry she has been an analyst, programmer, product manager, software engineering manager and director of software engineering. Currently she is splitting her time between shoeless consulting, a tech recruiting and management consulting firm, and writing a book translating business concepts into geek speak.

\n

VM blogs at “{a=>h}”:http://anonymoushash.vmbrasseur.com and tweets at @vmbrasseur.

" + }, + + { + + "serial": 24978, + "name": "Tim Bray", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_24978.jpg", + "url": "http://www.tbray.org/ongoing/", + "position": "Web Guy", + "affiliation": "Independent", + "twitter": "timbray", + "bio": "

Tim has co-founded two companies, helped raise five rounds of venture capital, written over a million words on his blog, edited the official specifications of both XML and JSON, worked for Sun Microsystems and Google, and is worried about passwords, Web culture, and tractable concurrency.

" + }, + + { + + "serial": 25862, + "name": "Michael Brewer", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_25862.jpg", + "url": "http://www.franklin.uga.edu/people/mbrewer.php", + "position": "Application Programmer Specialist", + "affiliation": "UGA: Franklin College OIT", + "twitter": "operatic", + "bio": "

Michael Brewer is an Application Programmer Specialist for the Franklin College Office of Information Technology at The University of Georgia. He designs database-backed web applications used by thousands of students and faculty and serves on several college and University-wide committees on Web development, best practices, and application security. In 2005, he won an Advising Technology Innovation Award from the National Academic Advising Association for an academic advising application he created and maintains. A speaker at OSCON in 2011 and 2012, he is also on the board of the United States PostgreSQL Association. He holds dual degrees in Mathematics and Music from The University of Georgia. A member of ASCAP, he also conducts the oldest continually operating community band in the state of Georgia; he has arranged music for orchestra, band, chorus, and has even composed incidental music for plays and musicals.

" + }, + + { + + "serial": 6845, + "name": "Joe Brockmeier", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_6845.jpg", + "url": "http://dissociatedpress.net/", + "position": "Principal Cloud & Storage Analyst", + "affiliation": "Red Hat", + "twitter": "jzb", + "bio": "

Joe Brockmeier is a member of Red Hat’s Open Source and Standards
\n(OSAS) team, and is involved with Project Atomic, the Fedora Project’s
\nCloud Working Group, and is a member of the Apache Software Foundation.
\nBrockmeier has a long history of involvement with Linux and open source,
\nand has also spent many years working as a technology journalist.
\nBrockmeier has written for ReadWriteWeb, LWN, Linux.com, Linux Magazine,
\nLinux Pro Magazine, ZDNet, and many others.

" + }, + + { + + "serial": 161486, + "name": "Ethan Brown", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_161486.jpg", + "url": "http://blogs.popart.com/author/ethanbrown", + "position": "Software Engineer", + "affiliation": "Pop Art", + "twitter": "EthanRBrown", + "bio": "

I work for Pop Art, an interactive marketing agency, doing mostly back-end website work. A lot of my current work is in C#/.NET, but I am shifting a lot of my attention to the Node.js stack (check out my projects on NPM!). My undergraduate work was in mathematics and computer science, and I have a broad and diverse background in software technologies.

" + }, + + { + + "serial": 90628, + "name": "Avi Bryant", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_90628.jpg", + "url": null, + "position": "Developer", + "affiliation": "Stripe", + "twitter": "avibryant", + "bio": "

Avi has led product, engineering, and data science teams at Etsy, Twitter and Dabble DB (which he co-founded and Twitter acquired). He\u2019s known for his open source work on projects such as Seaside, Scalding, and Algebird. Avi currently works at Stripe.

" + }, + + { + + "serial": 148534, + "name": "Brian Bulkowski", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_148534.jpg", + "url": "http://aerospike.com/", + "position": "co-founder, CTO & Product", + "affiliation": "Aerospike", + "twitter": "aerospikedb", + "bio": "

Brian Bulkowski, founder and CTO of Aerospike Inc. (formerly Citrusleaf), has 20-plus years experience designing, developing and tuning networking systems and high-performance Webscale infrastructures. He founded Aerospike after learning first hand, the scaling limitations of sharded MySQL systems at Aggregate Knowledge. As director of performance at this media intelligence SaaS company, Brian led the team in building and operating a clustered recommendation engine. Prior to Aggregate Knowledge, Brian was a founding member of the digital TV team at Navio Communications and chief architect of Cable Solutions at Liberate Technologies where he built the high-performance embedded networking stack and the Internetscale broadcast server infrastructure. Before Liberate, Brian was a lead engineer at Novell, where he was responsible for the AppleTalk stack for Netware 3 and 4.

" + }, + + { + + "serial": 171495, + "name": "Greg Bulmash", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_171495.jpg", + "url": "http://www.seattlecoderdojo.com/", + "position": "Content Developer", + "affiliation": "Microsoft.com", + "twitter": "GregBulmash", + "bio": "

Greg Bulmash is an urban legend, former senior editor at IMDb, and he makes really awesome chocolate cookies.

\n

During the days, he writes developer documentation for Microsoft, but evenings and weekends belong to Seattle CoderDojo where he somehow brings together dozens and dozens of kids, parents, and volunteers to run a free Saturday morning coding club.

" + }, + + { + + "serial": 169673, + "name": "Cody Bunch", + "photo": null, + "url": "http://professionalvmware.com/", + "position": "Principal Architect", + "affiliation": "Rackspace", + "twitter": "cody_bunch", + "bio": "

Cody Bunch is a Private Cloud / Virtualization Architect, VMware vExpert, and VMware VCP from San Antonio, TX. Cody has authored or co-authored several OpenStack and VMware books. Additionally he has been a tech editor on a number of projects. Cody also regularly speaks at industry events and local user groups.

" + }, + + { + + "serial": 173431, + "name": "Eric Butler", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_173431.jpg", + "url": "http://cedexis.com/", + "position": "Sr. Director, Products", + "affiliation": "Cedexis", + "twitter": "", + "bio": "

Eric leads the Technology team at Webtrends, chartered with creating creative disruptive solutions in the analytics and optimization space. His team is responsible for the newly introduced Webtrends Streams product, among other patent-pending technologies recently released by Webtrends.

\n

Eric has held technology leadership positions at Webtrends, Jive Software and Intel in his career. He is a runner, follows his kids around playing soccer, and lives in Portland with his family.

" + }, + + { + + "serial": 108884, + "name": "Paris Buttfield-Addison", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_108884.jpg", + "url": "http://www.secretlab.com.au/", + "position": "Serial Entrepreneur and Product Guy", + "affiliation": "Secret Lab Pty. Ltd.", + "twitter": "parisba", + "bio": "

Paris is co-founder of Secret Lab Pty. Ltd., leading production and design efforts in the mobile game and app development space. A frequent speaker at conferences, workshops and training sessions, Paris enjoys discussing engineering, product development, design and other facets of the mobile and game development worlds. Recent conferences include Apple Australia\u2019s /dev/world/2013 in Melbourne (and 2008, 2009, 2010, 2011, 2012), a keynote at CreateWorld Brisbane 2010 (and a speaker in 2009, 2011, 2012, and 2014), IxDA\u2019s Interaction 11 in Boulder (March 2011), XMediaLab Location-Based Services in Malmo, Sweden (January 2011), a tutorial and a session at OSCON 2011, OSCON 2012, OSCON 2013, linux.conf.au 2011, and many others.

\n

Paris is currently writing “Mobile Game Development With Unity” and “iOS Game Development Cookbook”, both for O’Reilly, and is the co-author of the books “Learning Cocoa with Objective-C Third Edition” (O’Reilly, 2012 and 2014), “iPhone and iPad Game Development For Dummies” (Wiley, 2010) and “Unity Mobile Game Development For Dummies” (Wiley, 2011).

\n

Paris is a highly experienced software developer, product and project manager. Key technologies include Objective-C/Cocoa on the Macintosh and iPhone/iPod Touch and iPad platforms, Java on Blackberry and Google Android and C# on Windows Mobile. Open GL ES and Unity are also favourites.

\n

Paris spent several years leading Meebo Inc.\u2019s mobile strategy in Mountain View, CA; Meebo was one of the world\u2019s fastest growing consumer internet companies and was acquired by Google in 2012. Paris is currently working on his next book, also with O’Reilly, and has recently submitted a PhD in Human-Computer Interaction, focusing on the use of tablets for information management. He is currently co-founder, producer, and occasional programmer at Secret Lab, in Tasmania, Australia.

" + }, + + { + + "serial": 155881, + "name": "Alejandro Cabrera", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_155881.jpg", + "url": "https://blog.cppcabrera.com/", + "position": "Software Developer II", + "affiliation": "Rackspace Hosting Inc.", + "twitter": "cppcabrera", + "bio": "

Hey! Nice to meet you. I’m a developer at Rackspace, and I work on the Openstack Marconi project during the day. I enjoy learning from others and sharing knowledge.

\n

Great friends, Linux, open source, Python, and Haskell have helped me get to where I am today.

" + }, + + { + + "serial": 63576, + "name": "Bryan Call", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_63576.jpg", + "url": "http://incubator.apache.org/projects/trafficserver.html", + "position": "Technical Yahoo!", + "affiliation": "Yahoo!", + "twitter": "", + "bio": "

Bryan Call has been writing code and working with on large scale solutions for 14 years. He has experience optimizing and profiling projects, including Apache Traffic Server and many internal projects at Yahoo!.

\n

He came to Yahoo! through an acquisition of a startup and has been working there for the last 12 years. He has worked on various products and teams, such as WebRing, GeoCities, People Search, Yahoo! Personal, Tiger Team (internal consulting team), Architect in the Platform Group, Architect in the Edge Platform, and now is working in the R&D Systems Group.

\n

Bryan is also a commiter on the Apache Traffic Server project and instrumental in bring Traffic Server to the Apache Foundation.

" + }, + + { + + "serial": 182198, + "name": "Darryn Campbell", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_182198.jpg", + "url": null, + "position": "Developer lead, RhoMobile suite", + "affiliation": "Motorola Solutions, Inc.", + "twitter": "", + "bio": "

Darryn Campbell is the development lead on RhoMobile suite, the cross platform development framework provided by Motorola Solutions. He has been developing application development frameworks targeted at enterprise since 2008 seeing the product space increase from a small number of niche suppliers to the crowded marketplace we see today.

" + }, + + { + + "serial": 155088, + "name": "Francesc Campoy Flores", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_155088.jpg", + "url": "http://golang.org/", + "position": "Go Developer Programs Engineer", + "affiliation": "Google Inc.", + "twitter": "francesc", + "bio": "

Francesc Campoy Flores joined the Go team in 2012 as Developer Programs Engineer. Since then, he has written some considerable didactic resources and traveled the world attending conferences and organizing live courses.

\n

He joined Google in 2011 as a backend software engineer working mostly in C++ and Python, but it was with Go that he rediscovered how fun programming can be. He loves languages; fluent in four of them, he’s now tackling a fifth one.

" + }, + + { + + "serial": 6921, + "name": "Brian Capouch", + "photo": null, + "url": "http://www.saintjoe.edu/", + "position": "Assistant Professor", + "affiliation": "Saint Josephs College", + "twitter": "", + "bio": "

Brian Capouch is a longtime open source user, programmer, and hacker.

\n

He teaches CS using 100% Open Source tools at small Indiana college, run a small wireless ISP; Asterisk and openWRT are his specialities.

" + }, + + { + + "serial": 109289, + "name": "Thierry Carrez", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_109289.jpg", + "url": "http://wiki.openstack.org/", + "position": "Release Manager", + "affiliation": "OpenStack", + "twitter": "tcarrez", + "bio": "

Thierry Carrez has been the Release Manager for the OpenStack project since its inception, coordinating the effort and facilitating collaboration between contributors. He is the elected chair of the OpenStack Technical Committee, which is in charge of the technical direction of the project. He spoke about OpenStack, open innovation and open source project management at various conferences around the world, including OSCON, LinuxCon and FOSDEM. A Python Software Foundation fellow, he was previously the Technical lead for Ubuntu Server at Canonical, and an operational manager for the Gentoo Linux Security Team.

" + }, + + { + + "serial": 75349, + "name": "Piers Cawley", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_75349.jpg", + "url": "http://www.bofh.org.uk/", + "position": "Senior Programmer", + "affiliation": "Thermeon", + "twitter": "pdcawley", + "bio": "

Piers Cawley started programming Perl in the mid nineties, but recently spent a few years working as a Ruby programmer.

\n

He’s currently writing Perl for Thermeon Europe

\n

He’s a singer and balloon modeller, and has created custom balloon millinery for Sarah Novotny.

" + }, + + { + + "serial": 123894, + "name": "Bill Cernansky", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_123894.jpg", + "url": "http://portlandcomedy.com/", + "position": "Duke of Tech", + "affiliation": "ComedySportz", + "twitter": "", + "bio": "

Bill Cernansky is a veteran Software Configuration Management Engineer at Jeppesen. Bill also performs and teaches improvisation and oversees tech at ComedySportz Portland. In addition, he is arguably the best at jumping over this one thing in his backyard.

" + }, + + { + + "serial": 10595, + "name": "Francesco Cesarini", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_10595.jpg", + "url": "http://www.erlang-solutions.com/", + "position": "Technical Director, Founder", + "affiliation": "Erlang Solutions Ltd", + "twitter": "FrancescoC", + "bio": "

Francesco Cesarini is the founder and technical director of Erlang Solutions Ltd. He has used Erlang on a daily basis since 1995, starting as an intern at Ericsson\u2019s computer science laboratory, the birthplace of Erlang. He moved on to Ericsson\u2019s Erlang training and consulting arm working on the first release of the OTP middleware, applying it to turnkey solutions and flagship telecom applications. In 1999, soon after Erlang was released as open source, he founded Erlang Solutions. With offices in five countries, they have become the world leaders in Erlang based support, consulting, training, certification, systems development and conferences. Francesco has worked in major Erlang based projects both within and outside Ericsson, and as Technical Director, has led the development and consulting teams at Erlang Solutions. He is also the co-author of Erlang Programming, a book published by O\u2019Reilly and is currently co-authoring Designing For Scalability With Erlang/OTP. He lectures the graduate students at Oxford University. You can follow his ramblings on twitter.

" + }, + + { + + "serial": 837, + "name": "Scott Chacon", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_837.jpg", + "url": "http://jointheconversation.org/", + "position": "Cofounder, CIO", + "affiliation": "GitHub", + "twitter": "", + "bio": "

Scott Chacon is a cofounder and the CIO of GitHub. He is also the author of the Pro Git book by Apress and the maintainer of the Git homepage (git-scm.com). Scott has presented at dozens of conferences around the world on Git, GitHub and the future of work.

" + }, + + { + + "serial": 147, + "name": "Colin Charles", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_147.jpg", + "url": "http://bytebot.net/blog/", + "position": "Open source software hacker and entrepreneur", + "affiliation": "SkySQL Ab/MariaDB Foundation", + "twitter": "bytebot", + "bio": "

Colin Charles works on MariaDB at SkySQL. He has been the Chief Evangelist for MariaDB since 2009, with work ranging from speaking engagements to consultancy and engineering works around MariaDB. He lives in Kuala Lumpur, Malaysia and had worked at MySQL since 2005, and been a MySQL user since 2000. Before joining MySQL, he worked actively on the Fedora and OpenOffice.org projects. He’s well known within open source communities in Asia and Australia, and has spoken at many conferences to boot.

" + }, + + { + + "serial": 133198, + "name": "Christopher Chedeau", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_133198.jpg", + "url": "http://blog.vjeux.com/", + "position": "Front-End Engineer", + "affiliation": "Facebook", + "twitter": "vjeux", + "bio": "

Christopher “vjeux” Chedeau is a Front-End Engineer at Facebook. He is passionate about the web and its ability to very easily write great user interfaces.

" + }, + + { + + "serial": 133360, + "name": "Doris Chen", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_133360.jpg", + "url": "http://blogs.msdn.com/b/dorischen/", + "position": "Developer Evangelist", + "affiliation": "Microsoft", + "twitter": "doristchen", + "bio": "

Dr. Doris Chen
\nhttp://blogs.msdn.com/b/dorischen/
\nTwitter @doristchen

\n

Doris is a Developer Evangelist at Microsoft for the Western region of the United States, specialized in web technologies (HTML5, jQuery, JavaScript, Ajax, and Java).
\nDoris has over 15 years of experience in the software industry working in several open source web tier technologies, Java platform, .NET and distributed computing technologies. She has developed and delivered over 400 keynotes, technical sessions, code camps worldwide, published widely at numerous international conferences and user groups including JavaOne, O\u2019Reilly, WebVisions, SD Forum, HTML5 and JavaScript meetups, and worldwide User Groups. Doris works very closely to create and foster the community around NetBeans, Glassfish, and related technologies. Before joining Microsoft, Doris Chen was a Technology Evangelist at Sun Microsystems.
\nDoris received her Ph.D. from the University of California at Los Angeles (UCLA) in computer engineering, specializing in medical informatics.

" + }, + + { + + "serial": 171704, + "name": "Roger Chen", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_171704.jpg", + "url": null, + "position": "Associate", + "affiliation": "OATV", + "twitter": "rgrchen", + "bio": "

Roger is an investor at O\u2019Reilly AlphaTech Ventures (OATV), where he looks for collisions between unmet needs and enabling technologies. He spent his past life as a scientist and engineer, alternating between academia and industry while dabbling in the startup and venture capital world. When he is not tinkering with technology, Roger plays sports and wonders what lies beyond bell curves. Roger has a BS from Boston University and a PhD from UC Berkeley, both in Electrical Engineering.

" + }, + + { + + "serial": 133377, + "name": "Sara Chipps", + "photo": null, + "url": "http://sarajchipps.com/", + "position": "Developer", + "affiliation": "Girl Develop It", + "twitter": "sarajchipps", + "bio": "

Sara Chipps is a JavaScript developer and she blogs here. She is CTO of Flatiron School. In 2010 she started an organization called Girl Develop It which offers low cost software development classes geared towards women. Girl Develop It has had over 1000 unique students in New York City and now has 25 chapters around the world.

\n

She enjoys speaking to and meeting with diverse groups from the Girl Scouts to straight up code junkies. Her goal is to inspire more females to see that working with software is fun and glamorous.

" + }, + + { + + "serial": 17417, + "name": "Wendy Chisholm", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_17417.jpg", + "url": "http://friendfeed.com/wendyc", + "position": "Senior Accessibility Strategist and Universal Design Evangelist", + "affiliation": "Microsoft", + "twitter": "wendyabc", + "bio": "

I am an author, activist and project manager. I co-wrote “Universal Design for Web Applications” with Matt May (O’Reilly, 2008) and edited Web Content Accessibility Guidelines 1.0 and 2.0–the basis of most web accessibility policies. I have appeared as Wonder Woman in a web comic with the other HTML5 Super Friends and as myself in interviews on Minnesota Public Radio, Puget Sound Public Radio, and at Ignite Seattle. In November 2009, I was the Seattle PI’s Geek of the Week.

\n

I\u2019ve focused on universal design since 1995. Being both a developer (B.S. in Computer Science) and a Human Factors Engineer (M.S. in Industrial Engineering/Human Factors), I bridge communication between developers and designers. As a Senior Strategist at Microsoft, I help make Bing services and apps accessible.

\n

Photo taken by Andy Farnum

" + }, + + { + + "serial": 170054, + "name": "Andrew Cholakian", + "photo": null, + "url": "http://found.no/", + "position": "Developer/Evangelist", + "affiliation": "Found", + "twitter": "andrewvc", + "bio": "

Author of Exploring Elasticsearch, and developer/evangelist at Found.no, Andrew Cholakian is an active member of the Elasticsearch community. Additionally, he is behind several elasticsearch OSS projects as well, including the popular \u201cStretcher\u201d Elasticsearch library for the Ruby language. He\u2019s also a veteran of multiple startups in the LA area, and is an active member of the local startup community.

" + }, + + { + + "serial": 172534, + "name": "Robby Clements", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_172534.jpg", + "url": "http://isotope11.com/", + "position": "Software Developer", + "affiliation": "Isotope11", + "twitter": "robby_clements", + "bio": "

Robby Clements is a software developer for Isotope11. His primary language has been Ruby for the past five years, but he’s been branching out into Erlang recently and finding it fascinating.

" + }, + + { + + "serial": 6894, + "name": "John Coggeshall", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_6894.jpg", + "url": "http://www.coggeshall.org/", + "position": "Owner", + "affiliation": "Internet Technology Solutions, LLC", + "twitter": null, + "bio": "

John Coggeshall is owner of Internet Technology Solutions, LLC. a high-end web consulting firm based in Michigan. He got started with PHP in 1997 and is the author of three published books and over 100 articles on PHP technologies with some of the biggest names in the industry such as Sams Publishing, Apress and O’Reilly. John also is a active contributor to the PHP core as the author of the tidy extension, a member of the Zend Education Advisory Board, and frequent speaker at PHP-related conferences worldwide.

" + }, + + { + + "serial": 141235, + "name": "C. Aaron Cois", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_141235.jpg", + "url": "http://www.codehenge.net/", + "position": "Software Development Team Lead", + "affiliation": "Carnegie Mellon University, Software Engineering Institute", + "twitter": "aaroncois", + "bio": "

Aaron is a software engineer currently located in Pittsburgh, PA. He received his Ph.D. in 2007, developing algorithms and software for 3D medical image analysis. He currently leads a software development team at Carnegie Mellon University, focusing on web application development and cloud systems.

\n

Aaron is a polyglot programmer, with a keen interest in open source technologies. Some favorite technologies at the moment include Node.js, Python/Django, MongoDB, and Redis.

" + }, + + { + + "serial": 99280, + "name": "Tina Coleman", + "photo": null, + "url": null, + "position": "Principal Software Architect", + "affiliation": "Next Century Corporation", + "twitter": "", + "bio": "

Girl geek, web application development and architecture, agile advocate, community management

" + }, + + { + + "serial": 4710, + "name": "Damian Conway", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_4710.jpg", + "url": "http://damian.conway.org/", + "position": "CEO and Chief Trainer", + "affiliation": "Thoughtstream", + "twitter": "", + "bio": "

Damian Conway is an internationally renowned speaker, author, and trainer, and a prominent contributor to the Perl community. Currently he runs Thoughtstream, an international IT training company that provides programmer training from beginner to masterclass level throughout Europe, North America, and Australasia. Most of his spare time over the past decade has been spent working with Larry Wall on the design and explication of the Perl 6 programming language. He has a PhD in Computer Science and was until recently an Adjunct Associate Professor in the Faculty of Information Technology at Monash University, Australia.

" + }, + + { + + "serial": 179595, + "name": "Danese Cooper", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_179595.jpg", + "url": "http://www.paypal.com/", + "position": "Head of Open Source", + "affiliation": "PayPal", + "twitter": "DivaDanese", + "bio": "

Danese Cooper has an 22-year history in the software industry and has long been an advocate for transparent development methodologies. Ms. Cooper joined PayPal in February 2014, and has held many leadership roles within the computer science sector. She has managed teams at Symantec and Apple Inc. and for six years served as Chief Open Source Evangelist for Sun Microsystems before leaving to serve as Senior Director for Open Source Strategies at Intel. She advised on open source policy to the R community while at REvolution Computing (now Revolution Analytics), and she served from February 2010 to July 2011 as Chief Technical Officer for the Wikimedia Foundation. She currently runs a successful consultancy to companies wishing to pursue open source strategies, which has served the SETI Foundation, Harris Corporation and the Bill & Melinda Gates Foundation among other clients. She is a director on the boards of the Drupal Association, and the Open Source Hardware Association, a board advisor for Mozilla and Ushahidi, and has served since 2005 as a Member of the Apache Software Foundation. She was a board member for 10 years at Open Source Initiative.

" + }, + + { + + "serial": 172201, + "name": "William Cox", + "photo": null, + "url": "http://www.gallamine.com/", + "position": "Data Scientist", + "affiliation": "Distil Networks", + "twitter": "gallamine", + "bio": "

When William isn’t busy being a husband and father, he is an electrical engineer specializing in signal processing and machine learning. He’s worked on underwater robots, radar detection, algorithmic forex, and torpedo tracking. He tweets @gallamine and blogs at http://gallamine.com

" + }, + + { + + "serial": 182463, + "name": "Kevin Crocker", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_182463.jpg", + "url": "http://www.pivotal.io/training#datascience", + "position": "Consulting Instructor", + "affiliation": "Pivotal", + "twitter": "", + "bio": "

Kevin Crocker is a Consulting Instructor at Pivotal. He combines 40 years of technology awareness with 30 years of instructional expertise spanning multiple technologies and disciplines to bring a rich educational experience to his classes.

\n

His teaching background includes: Statistics; operations research; optimization theory; distance education; adult education; instructional design; UNIX and Linux operating systems, servers, and scripting; Java; business, banking, and finance; virtualization; and Data Analytics.

\n

He is a published author and award winning course designer. His guiding principle is: Driven by light bulbs!

\n

When he’s not helping people learn leading edge technology he’s busy with his family, golf, and home brewing special beers.

" + }, + + { + + "serial": 169862, + "name": "Adam Culp", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_169862.jpg", + "url": "http://geekyboy.com/", + "position": "Senior Professional Services Consultant", + "affiliation": "Zend Technologies", + "twitter": "adamculp", + "bio": "

Adam Culp, organizer of the SunshinePHP Developer Conference and South Florida PHP Users Group (SoFloPHP) where he speaks regularly, is a Zend Certified PHP 5.3 Engineer consulting for Zend Technologies. Adam is passionate about developing with PHP and enjoys helping others write good code, implement standards, refactor efficiently, and incorporate unit and functional tests into their projects. When he is not coding or contributing to various developer communities, he can be found hiking around the United States National Parks, teaching judo, or long distance running.

" + }, + + { + + "serial": 183161, + "name": "Jim Cupples", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_183161.jpg", + "url": null, + "position": "Civic Hacker", + "affiliation": "Ballot Path", + "twitter": null, + "bio": "

Jim is a political science nerd who has minimal tech chops, but a deep love of Open Source. Jim is currently working with college students from around Oregon (especially PSU) on a project called Ballot Path that will allow users to see all of their elected reps, and how they can replace them. A side project of Ballot Path is creating Open Source GIS political boundary maps (we have to create many of them from scratch for the smaller elected offices, especially in rural areas), which will hopefully be used by people looking to help shape a world that they want their kids to live in.

\n

“Fortune may have yet a better success in reserve for you, and they who lose today may win tomorrow.” Cervantes

" + }, + + { + + "serial": 116276, + "name": "Patrick Curran", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_116276.jpg", + "url": "http://jcp.org/", + "position": "Chair, Java Community Process", + "affiliation": "Java Community Process", + "twitter": "", + "bio": "

Patrick Curran is Chair of the JCP. In this role he oversees the activities of the organization’s Program Management Office including driving the process, managing its membership, guiding specification leads and experts through the process, leading Executive Committee meetings, and managing the JCP.org web site.

\n

Patrick has worked in the software industry for more than 25 years, and at Sun (and now Oracle) for almost 20 years. He has a long-standing record in conformance testing, and most recently led the Java Conformance Engineering team in Sun’s Client Software Group. He was also chair of Sun’s Conformance Council, which was responsible for defining Sun’s policies and strategies around Java conformance and compatibility.

\n

Patrick has participated actively in several consortia and communities including the W3C (as a member of the Quality Assurance Working Group and co-chair of the Quality Assurance Interest Group), and OASIS (as co-chair of the Test Assertions Guidelines Technical Committee). Patrick’s blog is here.

" + }, + + { + + "serial": 173396, + "name": "Benjamin Curtis", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_173396.jpg", + "url": "http://www.honeybadger.io/", + "position": "Co-founder", + "affiliation": "Honeybadger Industries", + "twitter": "stympy", + "bio": "

Ben has been developing web apps and building startups since ’99, and fell in love with Ruby and Rails in 2005. Before co-founding Honeybadger, he launched a couple of his own startups: Catch the Best, to help companies manage the hiring process, and RailsKits, to help Rails developers get a jump start on their projects.

\n

Ben’s role at Honeybadger ranges from bare-metal to front-end… he keeps the server lights blinking happily, builds a lot of the back-end Rails code, and dips his toes into the front-end code from time to time.

\n

When he’s not working, Ben likes to hang out with his wife and kids, ride his road bike, and of course hack on open source projects.

" + }, + + { + + "serial": 108736, + "name": "Michael Dale", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_108736.jpg", + "url": null, + "position": "Developer", + "affiliation": "Kaltura, Wikimedia", + "twitter": "michael_dale", + "bio": "

Michael is the lead front end architect for Kaltura Open Source Video Platform. In 2006 Michael Dale co-founded metavid.org an open video community archive of US house and senate floor proceedings. Later he worked on video for Wikipedia. With Kaltura he has worked on HTML5 video for Wikipedia the Internet Archive and dozens of major web properties that make use of Kaltura’s platform. His work includes work on scalable video delivery across numerous browsers and devices, subtitling, advertising, analytics and video editing in HTML5.

" + }, + + { + + "serial": 172988, + "name": "Avik Das", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_172988.jpg", + "url": null, + "position": "Software Engineer", + "affiliation": "LinkedIn", + "twitter": "", + "bio": "

I graduated from UC Berkeley with Bachelor’s degrees in EECS and Math, and have since worked at LinkedIn as a software developer. I currently am the lead for the server that powers the LinkedIn iPad application.

\n

In my free time, I love working out, and when I find the time, I like to dabble in cooking.

" + }, + + { + + "serial": 131890, + "name": "Jennifer Davidson", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_131890.jpg", + "url": null, + "position": "Program Manager", + "affiliation": "ChickTech", + "twitter": "jewifer", + "bio": "

Jennifer Davidson is Program Manager for ChickTech, a nonprofit geared toward getting more girls and women interested and retained in technology careers. She is a PhD candidate in Computer Science with a minor in Aging Sciences at Oregon State University. She is currently working on research related to involving older adults in the design and development of open source software. Her research bridges the fields of human-computer interaction, open source software communities, and gerontechnology. She is also the Community Manager for Privly, an open source project related to internet privacy.

" + }, + + { + + "serial": 172536, + "name": "A. Jesse Jiryu Davis", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_172536.jpg", + "url": "http://emptysqua.re/", + "position": "Senior Python Engineer", + "affiliation": "MongoDB", + "twitter": "jessejiryudavis", + "bio": "

Senior Python Engineer at MongoDB in New York City. Author of Motor, an async MongoDB driver for Tornado, and of Toro, a library of locks and queues for Tornado coroutines. Contributor to Python, PyMongo, MongoDB, Tornado, and Tulip.

" + }, + + { + + "serial": 173393, + "name": "Kristen Dedeaux", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_173393.jpg", + "url": null, + "position": "Technical Writer, Editor ", + "affiliation": "Kristen Dedeaux Consulting ", + "twitter": "", + "bio": "

Kristen Dedeaux is a technical writer and an open source enthusiast. She has a passion for the written word and enjoys sharing her editorial expertise with others. Throughout her seven-year career as a content manager and editor in the fast-paced financial industry, she has championed several successful training programs and designed numerous writing workshops. Within the past year, she created an internal corporate blog, “The Writer\u2019s Block: Tips for Better Business Writing”, which received positive feedback from users across the globe, particularly from software engineers. The fastest red pen in the West, she can wrangle any stubborn sentence into shape.

" + }, + + { + + "serial": 173414, + "name": "Ethan Dereszynski", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_173414.jpg", + "url": null, + "position": "Research Scientist", + "affiliation": "Webtrends", + "twitter": "", + "bio": "

Ethan Dereszynski is a research scientist in machine learning and data mining at Webtrends. He earned his PhD in computer science at Oregon State University in 2012. Ethan\u2019s research focuses on the application of machine learning, in particular Bayesian statistics and probabilistic models, to challenging problems across multiple disciplines. Combining work and play, Ethan is also interested in unsupervised approaches for learning models of player behavior in real-time strategy games (read: accepting all challengers at StarCraft). Prior to studying at Oregon State, he received a B.S. in computer science at Alma College, where he minored in Mathematics and English.

" + }, + + { + + "serial": 174072, + "name": "Henning Diedrich", + "photo": null, + "url": "http://www.eonblast.com/", + "position": "Founder / CEO", + "affiliation": "Eonblast Corporation", + "twitter": "hdiedrich", + "bio": "

Henning is an entrepreneur, programmer and game designer. He is the Lead Software Engineer / Berlin at SumUp, a European mobile POS company. Henning is the creator of the Erlang VoltDB driver Erlvolt and a maintainer of the Erlang MySQL driver Emysql. His Open Source contributions for Erlang, Lua, MySQL and VoltDB are direct results of what pieces he found missing for a better game server stack.

\n

Henning wrote his first games on the C64, develops for the web since Netscape 1.0 and produced his first game server with Java 1.0. He created a language to describe tariffs for AXA and programmed and produced browser games. He founded Eonblast to create a more immersive online game experience and as official cover for the Time Tuner mission.

\n

Starting out on a 1MHz CPU, Henning’s special interest tends to be speed as an enabler. He has talked about evil performance hacks at the annual Lua Workshop, about his record setting Node.js VoltDB benchmark, and was elected to explain ‘Why Erlang?’ to the game developer community at the GDC Online 2012 (15.000 hits on slideshare.) He also contributed the underrated sed script markedoc to the OTP stack, which converts markdown to edoc.

" + }, + + { + + "serial": 122516, + "name": "Renee DiResta", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_122516.jpg", + "url": "http://reneediresta.com/", + "position": "Principal", + "affiliation": "OATV", + "twitter": "noupside", + "bio": "

Renee DiResta is a Principal at O’Reilly AlphaTech Ventures (OATV), where she evaluates seed-stage investments. Prior to joining OATV in June of 2011, Renee spent seven as a trader at Jane Street Capital, a quantitative proprietary trading firm in New York City. She is interested in meeting interesting startups, data science, and improving liquidity and transparency in private markets.

" + }, + + { + + "serial": 172973, + "name": "Connor Doyle", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_172973.jpg", + "url": "http://mesosphere.io/", + "position": "Software Engineer", + "affiliation": "Mesosphere, Inc.", + "twitter": "nor0101", + "bio": "

Connor Doyle is a software engineer at Mesosphere, focused on developing tools around Apache Mesos. Before joining Mesosphere, Connor worked at the design firm Gensler where he worked on process improvement, internal libraries and distributed architecture in Scala. He completed his Master’s degree at the University of Wisconsin – La Crosse, where his capstone project was a distributed simulator for doing multi-agent learning research.

" + }, + + { + + "serial": 173105, + "name": "Drasko Draskovic", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_173105.jpg", + "url": "http://we-io.net/", + "position": "Embedded Linux Software Engineer", + "affiliation": "nodesign.net", + "twitter": "draskodraskovic", + "bio": "

Engineer – artist, Dra\u0161ko usualy builds computers with operating systems in less than 5 square millimeters. He holds MSc degree in Electrical Engineering and has over 10 years of expertise in embedded systems, semicoductor and telecommunications industry, where he hacked the things beyond the limits.

\n

Dra\u0161ko earned his reputation in Open Source community by being constantly involved and contributing to several projects dealing with low-level kernel programming and Linux device drivers like WeIO platform, OpenOCD JTAG debugger or CodeZero L4 hypervisor. He constantly walks on the thin line where hardware meets the software.

" + }, + + { + + "serial": 137149, + "name": "Clinton Dreisbach", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_137149.jpg", + "url": "http://github.com/cndreisbach", + "position": "Software developer", + "affiliation": "The Iron Yard", + "twitter": "cndreisbach", + "bio": "

Clojure and Python hacker for the Consumer Financial Protection Bureau. Lead developer on Qu, the CFPB’s public data platform, and contributor to Clojure, Hy, and other open source projects.

" + }, + + { + + "serial": 123516, + "name": "Emily Dunham", + "photo": null, + "url": "http://github.com/edunham", + "position": "Student Software Developer", + "affiliation": "Oregon State University Open Source Lab", + "twitter": "", + "bio": "

Emily is a senior in computer science at Oregon State University. Since joining the OSU Open Source Lab in April 2011 a software developer on the Ganeti Web Manager project, she has worked as an intern at Intel, a teaching assistant in the computer science department, and a systems engineer at the OSL. She founded the OSL’s DevOps Bootcamp outreach program in August 2013, and is involved with the OSU Linux Users Group and local FIRST Robotics competitions.

" + }, + + { + + "serial": 94695, + "name": "David Elfi", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_94695.jpg", + "url": null, + "position": "Sr. Software Engineer", + "affiliation": "Intel Corporation", + "twitter": "", + "bio": "

Certainly, David has gained experience in all the different roles he played at Intel since 2008. Mainly based on Ecommerce products guided by AppUp product, he worked for different product flavors in the areas of web services, consumer experience and mobile application development.
\nRecently, he entered in the world of managing data for business analysis in the research of improvements based on data collected from the field.

" + }, + + { + + "serial": 140062, + "name": "Jonathan Ellis", + "photo": null, + "url": "http://www.datastax.com/", + "position": "CTO & Co-founder", + "affiliation": "DataStax, Inc", + "twitter": "spyced", + "bio": "

Jonathan is CTO and co-founder at DataStax. Prior to DataStax, Jonathan worked extensively with Apache Cassandra while employed at Rackspace. Prior to Rackspace, Jonathan built a multi-petabyte, scalable storage system based on Reed-Solomon encoding for backup provider Mozy. In addition to his work with DataStax, Jonathan is project chair of Apache Cassandra.

" + }, + + { + + "serial": 172370, + "name": "Michael Enescu", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_172370.jpg", + "url": "http://blogs.cisco.com/openatcisco/", + "position": "CTO Open Source Initiatives", + "affiliation": "Cisco", + "twitter": "michaelenescu", + "bio": "

Michael Enescu is CTO of Open Source Initiatives at Cisco, leading open source programs across multiple technologies and product lines. Previously he served as the first Vice President of Product at XenSource (acquired by Citrix) and one of the first employees. He has a broad range of experience having developed and delivered over two dozen enterprise and consumer software products. Previously he was a founding member of the Mobile Web Services group at Palm and a founding member of the Java Content and J2ME teams at Sun. He led the development of first streaming video servers and digital libraries at SGI. He started his career in storage virtualization at IBM where he managed the development of the earlier versions of IBM\u2019s core middleware platform product in the WebSphere suite, along with leading development in expert systems, operating systems and virtualization in IBM’s Storage Products group. Michael has a BS degree from Caltech and MS in Computer Science from Stanford University.

" + }, + + { + + "serial": 169992, + "name": "Catherine Farman", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_169992.jpg", + "url": "http://cfarman.com/", + "position": "Developer", + "affiliation": "Happy Cog", + "twitter": "cfarm", + "bio": "

Catherine Farman is a Developer at Happy Cog, where she builds standards-based websites using HTML, CSS and Javascript. Catherine has taught responsive web design, Javascript, and Sass courses for Girl Develop It. She\u2019s also helped develop new courses and has written open source curricula to share with other teachers. When she’s not at a computer she likes to sew and watch soccer.

" + }, + + { + + "serial": 6631, + "name": "Paul Fenwick", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_6631.jpg", + "url": "http://perltraining.com.au/", + "position": "Managing Director", + "affiliation": "Perl Training Australia", + "twitter": "pjf", + "bio": "

Paul Fenwick is the managing director of Perl Training Australia, and has been teaching computer science for over a decade. He is an internationally acclaimed presenter at conferences and user-groups worldwide, where he is well-known for his humour and off-beat topics.

\n

In his spare time, Paul’s interests include security, mycology, cycling, coffee, scuba diving, and lexically scoped user pragmata.

\n

*Photograph by Joshua Button.

" + }, + + { + + "serial": 173433, + "name": "Mark Ferree", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_173433.jpg", + "url": "http://chapterthree.com/", + "position": "Director of Engineering", + "affiliation": "Chapter Three", + "twitter": "mrf", + "bio": "

I am a developer who quickly grew to rely on open source software and tools for all of my projects.

\n

An active Drupal developer for the last seven years I am currently working as the Director of Engineering at Chapter Three where we use Drupal to power large content-managed sites.

" + }, + + { + + "serial": 1639, + "name": "Edward Finkler", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_1639.jpg", + "url": "http://funkatron.com/", + "position": "Dead Agent", + "affiliation": "Funkatron Productions", + "twitter": "", + "bio": "

Ed Finkler, also known as Funkatron, started making web sites before browsers had frames. He does front-end and server-side work in Python, PHP, and JavaScript.

\n

He served as web lead and security researcher at The Center for Education and Research in Information Assurance and Security (CERIAS) at Purdue University for 9 years. Now he’s a proud member of the Fictive Kin team. Along with Chris Hartjes, Ed is co-host of the Development Hell podcast.

\n

Ed’s current passion is raising mental health awareness in the tech community with his Open Sourcing Mental Illness speaking campaign. He is part of Engine Yard’s Prompt campaign.

\n

Ed writes at funkatron.com.

" + }, + + { + + "serial": 152242, + "name": "Keith Fiske", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_152242.jpg", + "url": "http://www.keithf4.com/", + "position": "Database Administrator", + "affiliation": "OmniTI, Inc", + "twitter": "keithf4", + "bio": "

Database Administrator with OmniTI, Inc

" + }, + + { + + "serial": 181484, + "name": "Tyler Fitch", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_181484.jpg", + "url": "http://www.getchef.com/", + "position": "Customer Success Engineer", + "affiliation": "CHEF", + "twitter": "", + "bio": "

Tyler recently joined CHEF as a Customer Success Engineer – championing
\nbest practices and delightful experiences in automation. Prior to
\nworking at Chef he spent a decade as an engineer for Adobe developing
\nand automating commerce services for adobe.com using
\na variety of technologies. He lives in Vancouver, WA and when he’s not
\nprogramming enjoys lacrosse and using his passport.

" + }, + + { + + "serial": 178139, + "name": "Beth Flanagan", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_178139.jpg", + "url": "http://www.intel.com/", + "position": "Senior Software Engineer, Opensource Technology Center", + "affiliation": "Intel Corp.", + "twitter": "dirtycitybird", + "bio": "

Beth ‘pidge’ Flanagan is a Senior Software Engineer with Intel’s Opensource Technology Center. She spends most of her work life hacking on OpenEmbedded and the Yocto Project. She is the release engineer for the Yocto Project, maintainer of the yocto-autobuilder and pilot of the Yocto Blimp.

" + }, + + { + + "serial": 46421, + "name": "Richard Fontana", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_46421.jpg", + "url": "http://www.redhat.com/", + "position": "Counsel", + "affiliation": "Red Hat, Inc.", + "twitter": "", + "bio": "

Richard Fontana is a lawyer at Red Hat, with particular responsibility
\nfor legal issues arising out of the software development process.
\nRichard specializes in copyright, trademark and patent issues,
\ntechnology transactions, free software/open source issues, computing
\ntechnology standards, data privacy and protection, information
\nsecurity, and legal matters relating to cloud computing. Richard is the
\nsole open source legal specialist at Red Hat, which is the world’s
\nlargest provider of open source-based enterprise software and cloud
\nsolutions, and he has been an active and influential public speaker on
\nmatters at the intersection of open source, law and policy. In
\naddition, Richard is an Individual Member-elected Board Director
\nof the Open Source Initiative.

" + }, + + { + + "serial": 2650, + "name": "Neal Ford", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_2650.jpg", + "url": "http://www.nealford.com/", + "position": "meme wrangler", + "affiliation": "ThoughtWorks", + "twitter": "neal4d", + "bio": "

Neal Ford is Software Architect and Meme Wrangler at *Thought*Works, a global IT consultancy with an exclusive focus on end-to-end software development and delivery. He is also the designer and developer of applications, instructional materials, magazine articles, courseware, video/DVD presentations, and author and/or editor of 6 books spanning a variety of technologies, including the most recent The Productive Programmer. He focuses on designing and building of large-scale enterprise applications. He is also an internationally acclaimed speaker, speaking at over 100 developer conferences worldwide, delivering more than 600 talks. Check out his web site at www.nealford.com. He welcomes feedback and can be reached at nford@thoughtworks.com.

" + }, + + { + + "serial": 142995, + "name": "Steve Francia", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_142995.jpg", + "url": "http://spf13.com/", + "position": "Chief Developer Advocate", + "affiliation": "MongoDB", + "twitter": "spf13", + "bio": "

Steve Francia is the creator of hugo, cobra, nitro & spf13-vim. An author of multiple O’Reilly books, Steve blogs at spf13.com and gives many talks and workshops around the world. He is the Chief Developer Advocate at MongoDB responsible for the developer experience of MongoDB and leads the software engineering team responsible for drivers and integrations with all languages, libraries and frameworks. He loves open source and is thrilled to be able to work on it full time. When not coding he enjoys skateboarding and having fun outdoors with his wife and four children.

" + }, + + { + + "serial": 28902, + "name": "Roberto Galoppini", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_28902.jpg", + "url": "http://robertogaloppini.net/", + "position": "Senior Director of Biz Dev", + "affiliation": "Slashdotmedia", + "twitter": null, + "bio": "

Roberto is a computer industry insider of 15+ years standing. Up until 1994 Roberto had never heard of Linux, until he chanced to lead a group of geeks in starting up a mobile ISP with just a bunch of old PCs. Since then Roberto has worked in such hands-on roles as programmer and systems analyst, eventually founding an open source firm open source firm in 2001, and an open source consortium in 2004.

\n

Roberto has taken an active interest in several free/open source software organizations. He currently serves on the Advisory Board of the SourceForge Marketplace and acts as the Institutional Relationship Manager for the OpenOffice.org Italian Association. Since 2003 Roberto has researched the economics of OSS, collaborating with universities and EC funded research projects. Roberto is a technical writer for IT and computer-related magazines, he regularly keeps a blog on Commercial Open Source at http://robertogaloppini.net.

" + }, + + { + + "serial": 6852, + "name": "Matthew Garrett", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_6852.jpg", + "url": null, + "position": "Senior Security Developer", + "affiliation": "Nebula", + "twitter": null, + "bio": "

Matthew Garrett is a Linux kernel developer, firmware wrangler and meddler in cloud security. By day he works at Nebula, dealing with parts of the cloud that can be used to scare small children. By night, those parts of the cloud scare him.

" + }, + + { + + "serial": 177325, + "name": "Omri Gazitt", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_177325.jpg", + "url": "http://www.hpcloud.com/", + "position": "VP of Engineering, HP Helion Platform", + "affiliation": "Hewlett Packard", + "twitter": "", + "bio": "

Omri Gazitt serves as VP of Engineering of HP\u2019s Helion Platform team, which delivers core parts of the Helion OpenStack and Helion Development Platform products, targeted at Enterprises that want to stand up their own Cloud on-premises, or consume it as a hosted platform in HP\u2019s Managed and Public Clouds. Omri\u2019s team is responsible for Identity & Access, Metering, Monitoring, Management Console, Load Balancing, DNS, Database, Messaging, Application Lifecycle, Developer Experience, and User Experience, and contributes heavily into the OpenStack and Cloud Foundry open source projects.

\n

In prior lives, Omri helped create .NET, Web Services, and Azure, as well as the Xbox and Kinect developer platforms. He lives with his wife and three daughters in Redmond, WA, and loves rainy winter days in the northwest because they mean fresh powder on the weekends.

" + }, + + { + + "serial": 132564, + "name": "Trisha Gee", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_132564.jpg", + "url": "http://mechanitis.blogspot.com/", + "position": "Developer", + "affiliation": "MongoDB", + "twitter": "trisha_gee", + "bio": "

Trisha is a developer at MongoDB. She has expertise in Java high performance systems, is passionate about enabling developer productivity, and has a wide breadth of industry experience from the thirteen years she’s been a professional developer. Trisha is a leader in the London Java Community and the Sevilla Java & MongoDB Communities, she believes we shouldn’t all have to make the same mistakes again and again.

" + }, + + { + + "serial": 172751, + "name": "Vidhya Gholkar", + "photo": null, + "url": "http://vgholkar/", + "position": "Developer", + "affiliation": "Freelance", + "twitter": "vgholkar", + "bio": "

With a PhD in Signal Processing has developed new Machine Learning technologies for oil and gas exploration (Schlumberger). Managed Open Communication technology Standards at Vodafone Group. CTO at mobile startup (Argogroup) which has now been acquired.

" + }, + + { + + "serial": 171201, + "name": "Adam Gibson", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_171201.jpg", + "url": "http://skymind.io/", + "position": "Owner", + "affiliation": "Skymind.io", + "twitter": "agibsonccc", + "bio": "

Adam Gibson is a deep\u00ad learning specialist based in San Francisco assisting Fortune 500 companies, hedge funds, PR firms and startup accelerators with their machine learning projects. Adam has a strong track record helping companies handle and interpret big real\u00ad-time data. Adam has been a computer nerd since he was 13 and actively contributes to the open\u00ad source community.

" + }, + + { + + "serial": 150440, + "name": "Kelsey Gilmore-Innis", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_150440.jpg", + "url": "http://nerd.kelseyinnis.com/", + "position": "Software engineer", + "affiliation": "Reverb", + "twitter": "kelseyinnis", + "bio": "

Kelsey Gilmore-Innis is a back-end engineer at Reverb, where she uses Scala & functional programming to build powerful, scalable software systems. She strives to write code with charisma, uniqueness, nerve and talent and hopes to one day really, truly, deeply understand what a monad is.

" + }, + + { + + "serial": 152123, + "name": "Sebastien Goasguen", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_152123.jpg", + "url": null, + "position": "Open Source Cloud Evangelist", + "affiliation": "Citrix", + "twitter": "sebgoa", + "bio": "

Sebastien has over 15 years experience in the computing area, after a career in academia that led him to be an associate professor at Clemson University he joined Citrix to participate in the Apache CloudStack community. He is now an Apache committer and PMC member in CloudStack and Libcloud.

" + }, + + { + + "serial": 173336, + "name": "Sara Golemon", + "photo": null, + "url": "http://twitter.com/saramg", + "position": "Software Engineer", + "affiliation": "Facebook", + "twitter": "saramg", + "bio": "

I work on PHP, HHVM, and wrote libssh2. I wrote a sizable chunk of Yahoo! Search’s front end, and now I make Facebook fast.

" + }, + + { + + "serial": 173116, + "name": "Jesus M. Gonzalez-Barahona", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_173116.jpg", + "url": "http://bitergia.com/", + "position": "Co-founder", + "affiliation": "Bitergia", + "twitter": "jgbarah", + "bio": "

Jesus M. Gonzalez-Barahona is co-founder of Bitergia, the software development analytics company specialized in the analysis of free / open source software projects. He also teaches and researches in Universidad Rey Juan Carlos (Spain), in the context of the GSyC/LibreSoft research group. His interests include the study of communities of software development, with a focus on quantitative and empirical studies. He enjoys taking photos of the coffee he drinks around the world .

" + }, + + { + + "serial": 156989, + "name": "Patricia Gorla", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_156989.jpg", + "url": "http://thelastpickle.com/", + "position": "Apache Cassandra Architect", + "affiliation": "The Last Pickle", + "twitter": "patriciagorla", + "bio": "

Patricia Gorla is an Apache Cassandra Architect at The Last Pickle, a Cassandra consultancy.

\n

She has been involved in all aspects of software development, from server administration to application development, and from data analysis to data storage.

\n

She has worked with companies and governmental entities on all aspects of data migration to non-relational data stores, and training the technical teams on the new architecture.

\n

She helped the US Patent and Trademark Office ingest more than 6 million patent documents and images; architect secure search systems for a large mortgage insurer; and introduce Cassandra to a digital marketing firm’s data pipeline.

\n

Prior to architecting databases Patricia focused on the analysis and visualization of data.

\n

Patricia speaks often at conferences and meetups such as O’Reilly’s StrataConf + Hadoop World, the Datastax Cassandra Summits, and local user groups. She was also voted a DataStax MVP for Apache Cassandra by the community.

" + }, + + { + + "serial": 180122, + "name": "Jessica Goulding", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_180122.jpg", + "url": null, + "position": "Developer and Marketer", + "affiliation": "Self-Employed", + "twitter": null, + "bio": "

Jessica spent 8 years in marketing, becoming an expert in SEO and Social Media, when a passion sparked for web development. Her interest began while doing marketing and customizing Wordpress themes to help small business clientele build websites. After two years, she decided to dive right into being a full-time developer and in the summer of 2012 I taught myself Ruby on Rails and advanced HTML and CSS. She has since worked with everything from the command line, git, bootstrap integration, heroku, amazon web services, pivotal tracker, postgres and mysql database setup.

\n

A forever student, after entering the world of development she gained a passion for attending developer conferences and leaving more inspired and knowledgable. Currently she is attending Turing.IO, a software development school based in Denver, Colorado.

" + }, + + { + + "serial": 173325, + "name": "Neel Goyal", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_173325.jpg", + "url": null, + "position": "Engineer", + "affiliation": "Verisign, Inc.", + "twitter": "", + "bio": "

Neel is a R&D developer at Verisign with over ten years of experience. He
\nhas worked on a variety of initiatives for Verisign ranging from domain related tools, API specifications, and routing protocol implementations. He also serves as a member on Verisign\u00b9s Open Source Committee. Prior to Verisign, Neel worked at a startup developing consumer software for embedded devices and PCs.

" + }, + + { + + "serial": 132323, + "name": "Adam Granicz", + "photo": null, + "url": "http://intellifactory.com/", + "position": "CEO", + "affiliation": "IntelliFactory", + "twitter": "granicz", + "bio": "

Adam Granicz is a long-time F# insider and key community member, and the co-author of four F# books, including Expert F# 3.0 with Don Syme, the designer of the language. His company IntelliFactory specializes in consulting on the most demanding F# projects; shaping the future of the development of F# web, mobile and cloud applications; and developing WebSharper, the main web development framework for F#.

" + }, + + { + + "serial": 183169, + "name": "Brian Grant", + "photo": null, + "url": null, + "position": "Engineer", + "affiliation": "Google", + "twitter": "", + "bio": "

Brian Grant is an engineer at Google. He was formerly a lead of
\nGoogle\u2019s internal cluster management system, codenamed Borg, and was a founder of the Omega project. He now contributes to Google\u2019s cloud and open-source container efforts.

" + }, + + { + + "serial": 179224, + "name": "Chad Granum", + "photo": null, + "url": "https://github.com/exodist", + "position": "Software Developer", + "affiliation": "Dreamhost", + "twitter": "", + "bio": "

Current maintainer of Fennec (perl test tool, not the browser), Child.pm, and Test::Builder/Simple/More

" + }, + + { + + "serial": 181009, + "name": "James Grierson", + "photo": null, + "url": "http://www.bluehost.com/", + "position": "Chief Operating Officer", + "affiliation": "Bluehost.com", + "twitter": "jamesgrierson", + "bio": "

James Grierson is the current Chief Operating Officer at Bluehost.com which powers over 2 million websites worldwide. During his time at Bluehost, James co-founded SimpleScripts automated installer for Open Source applications (merged with the MOJO Marketplace in 2013) and created the Bluehost Open Source Solutions program to support Open Source Communities. These programs provide Open Source projects with a distribution model, user feedback loop, discounted hosting services, financial support and leadership consulting.

" + }, + + { + + "serial": 171381, + "name": "John Griffith", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_171381.jpg", + "url": "http://www.solidfire.com/", + "position": "Project Technical Lead, OpenStack Block Storage, Lead OpenStack Developer", + "affiliation": "SolidFire", + "twitter": "solidfire", + "bio": "

John Griffith is a Senior Software Engineer at SolidFire, where his primary responsibility is driving the OpenStack Cinder Block Storage Project. John is an open source expert serving as the project team lead for Cinder. He leads the team to provide first-class SolidFire product functionality and Quality of Service integration within OpenStack. John is also responsible for the development of a true Quality of Service storage appliance designed and built for the cloud.
\nHe has significant software engineering experience, most recently serving as lead software engineer at HP. During his tenure, John focused on building large scale fibre channel SANS to continuously test and develop storage and lead the development on a Unified Storage API.

\n

John holds a bachelor\u2019s degree from Regis University in Denver, Colorado.

" + }, + + { + + "serial": 170237, + "name": "Sarah Guido", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_170237.jpg", + "url": "https://github.com/sarguido", + "position": "Data scientist", + "affiliation": "Reonomy", + "twitter": "sarah_guido", + "bio": "

Sarah is a data scientist at Reonomy, where she’s helping to build disruptive tech in the commercial real estate industry in New York City. Three of her favorite things are Python, data, and machine learning.

" + }, + + { + + "serial": 2397, + "name": "Andy Gup", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_2397.jpg", + "url": "http://blog.andygup.net/", + "position": "Tech Lead", + "affiliation": "Esri", + "twitter": "agup", + "bio": "

Andy Gup is a Tech Lead at Esri where he focuses on web and mobile geo-spatial APIs. He is an active contributor to a number of open source projects in the geo community.

\n

His background includes working with a wide variety of cutting edge technologies from small websites and mobile apps to high-performance Fortune 500 enterprise systems.

" + }, + + { + + "serial": 143377, + "name": "Arun Gupta", + "photo": null, + "url": "http://blog.arungupta.me/", + "position": "Director, Developer Advocacy", + "affiliation": "Red Hat", + "twitter": "arungupta", + "bio": "

Arun Gupta is Director of Developer Advocacy at Red Hat and focuses on JBoss Middleware. As a founding member of the Java EE team at Sun Microsystems, he spread the love for technology all around the world. At Oracle, he led a cross-functional team to drive the global launch of the Java EE 7 platform through strategy, planning, and execution of content, marketing campaigns, and program. After authoring ~1400 blogs at blogs.oracle.com/arungupta on different Java technologies, he continues to promote Red Hat technologies and products at blog.arungupta.me. Arun has extensive speaking experience in 37 countries on myriad topics and is a JavaOne Rockstar. An author of a best-selling book, an avid runner, a globe trotter, a Java Champion, he is easily accessible at @arungupta.

" + }, + + { + + "serial": 131884, + "name": "Florian Haas", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_131884.jpg", + "url": "http://www.hastexo.com/who/florian", + "position": "CEO & Principal Consultant", + "affiliation": "hastexo", + "twitter": "", + "bio": "

Florian is an open source software specialist, experienced technical consultant, seasoned training instructor, and frequent public speaker. He has spoken at conferences like LinuxCon, OSCON, linux.conf.au, the OpenStack Summit, the MySQL Conference and Expo, and countless user and meetup groups across the globe.

" + }, + + { + + "serial": 171418, + "name": "Steve Hannah", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_171418.jpg", + "url": "http://sjhannah.com/", + "position": "Web Services Developer", + "affiliation": "Simon Fraser University", + "twitter": "shannah78", + "bio": "

I am a software developer because I love to create things and software imposes the fewest limitations on my creativity of any medium. Computer + Idea + Time + Perspiration = Any outcome you want.

\n

I started out with a G3 Bondi-Blue iMac and a free copy of Adobe Page Mill in 1998, but quickly expanded my horizon to include HTML, Javascript, PERL, Flash 3, and finally PHP 3 – in that order. Finding myself spending hours reading and writing web apps before and my day job, I decided to take my aspirations to the next level and attend University. I never looked back.

\n

I am a recovering O’Reilly book junkie who still has serious relapses from time-to-time. I try to stay open to any new software techniques and languages that come along, but currently I use PHP, Javascript, CSS, MySQL for most of my web application development, and Java for most of my desktop and mobile application development.

\n

I founded a few open source projects, including Xataface (a framework for building data-driven web applications with PHP and MySQL) and SWeTE (Simple Website Translation Engine), a proxy for internationalizing web applications. I also enjoy blogging about software-related issues.

" + }, + + { + + "serial": 140339, + "name": "Harold Hannon", + "photo": null, + "url": "http://www.softlayer.com/", + "position": "Senior Software Architect ", + "affiliation": "SoftLayer", + "twitter": "", + "bio": "

Harold Hannon has been working in the field of software development as both an Architect and Developer for over 15 years, with a focus on workflow, integration and distributed systems. He is currently a Sr. Software Architect at SoftLayer Technologies working within their Product Innovation team. Harold has a passion for leveraging open source solutions to bring real value to the Enterprise space, and has implemented open source solutions with many companies across the globe. Harold is also active in mobile application development, with multiple published applications.

" + }, + + { + + "serial": 132865, + "name": "Scott Hanselman", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_132865.jpg", + "url": "http://www.hanselman.com/", + "position": "Principal Architect", + "affiliation": "Microsoft", + "twitter": "shanselman", + "bio": "

Scott is a web developer who has been blogging at http://hanselman.com for over a decade. He works on Azure and ASP.NET for Microsoft out of his home office in Portland. Scott has three podcasts, http://hanselminutes.com for tech talk, http://thisdeveloperslife.com on developers’ lives and loves, and http://ratchetandthegeek.com for pop culture and tech media. He’s written a number of books and spoken in person to almost a half million developers worldwide.

" + }, + + { + + "serial": 86090, + "name": "Jonah Harris", + "photo": null, + "url": "http://www.meetme.com/", + "position": "Vice President of Architecture", + "affiliation": "MeetMe, Inc.", + "twitter": "oracleinternals", + "bio": "

Jonah has administered, developed against, and consulted on every major commercial and open source database system to date; his range of knowledge includes everything from query language specifics to the details of Oracle, EnterpriseDB, PostgreSQL, MySQL, SAP DB, Firebird, Ingres, and InnoDB internals, file formats, and network protocols.

" + }, + + { + + "serial": 152118, + "name": "Adam Harvey", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_152118.jpg", + "url": "http://about.me/LawnGnome", + "position": "Web developer", + "affiliation": "New Relic", + "twitter": "LGnome", + "bio": "

Adam is a PHP Agenteer (it’s totally a word) at New Relic who is celebrating his 20th year of swearing at browsers that refuse to do his bidding. In between said bouts of invective, Adam works on various open source projects, including PHP, and attempts to figure out the great mysteries of life (well, the cricket related ones, at least).

" + }, + + { + + "serial": 8837, + "name": "Leslie Hawthorn", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_8837.jpg", + "url": "http://hawthornlandings.org/", + "position": "Community Manager", + "affiliation": "Elasticsearch", + "twitter": "lhawthorn", + "bio": "

An internationally known community manager, speaker and author, Leslie Hawthorn has spent the past decade creating, cultivating and enabling open source communities. She created the world\u2019s first initiative to involve pre-university students in open source software development, launched Google\u2019s #2 Developer Blog, received an O\u2019Reilly Open Source Award in 2010 and gave a few great talks on many things open source.

\n

In August 2013, she joined Elasticsearch as Community Manager, where she leads Developer Relations. She works from Elasticsearch’s EU HQ in Amsterdam, The Netherlands – when not out and about gathering user praise and pain points. You can follow her adventures on Twitter

" + }, + + { + + "serial": 104522, + "name": "Steve Heffernan", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_104522.jpg", + "url": "http://videojs.com/", + "position": "Director of Open Source Player Technology", + "affiliation": "Brightcove, Video.js", + "twitter": "heff", + "bio": "

Steve Heffernan is the creator of Video.js, an open source web video player in use on over 100,000 websites and with over 1 billion views per month. Steve was previously co-founder of Zencoder, a cloud video encoding service that was acquired by Brightcove. As part of Brightcove, Steve now works full-time managing the Video.js project and community.

" + }, + + { + + "serial": 108520, + "name": "Christopher Helm", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_108520.jpg", + "url": "http://developer.geoiq.com/", + "position": "Geo Analytics Wizard", + "affiliation": "GeoIQ", + "twitter": "", + "bio": "

Javascript Engineer / solver of geospatial problems

" + }, + + { + + "serial": 171372, + "name": "Sam Helman", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_171372.jpg", + "url": null, + "position": "Software Engineer", + "affiliation": "MongoDB, Inc", + "twitter": "", + "bio": "

Sam has been working as a software engineer at MongoDB since August 2012. Before that, he got his degree in Computer Science at Brown University, where in addition to spending huge amounts of time programming he made films, did comedy, and played music and soccer.

" + }, + + { + + "serial": 182521, + "name": "Raymond Henderson", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_182521.jpg", + "url": "http://www.grassroots.org/", + "position": "Executive Director", + "affiliation": "Grassroots.org", + "twitter": "", + "bio": "

Ray has over 25 years of experience in the business and non-profit sector. He is the Executive Director of Grassroots.org, a nonprofit organization that helps over 8500 nonprofits accelerate their efforts through the use of technology and serves on the Board of Directors for Fight the New Drug.org. Previously, he served as the Executive Director of Ohana Makamae Inc, an award winning substance abuse clinic and as Board President of Ma Ka Hana Ka `Ike, a nationally recognized, building and trades program for \u201cat risk\u201d youth in Hawaii. Prior to his work with non-profits, he worked marketing management for US West Direct, MCI WorldCom and MCA Records. His awards include: 2009 State of Hawaii \u2013 Governor\u2019s Innovation Award for Nonprofits, 2008 Maui- Executive Director of the Year, 2005 Outstanding Community Service Award and 2004 State of Hawaii – Collaborative Nonprofit Leader of the Year.

" + }, + + { + + "serial": 173093, + "name": "Ben Henick", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_173093.jpg", + "url": "http://henick.net/", + "position": "Sitebuilder At-Large", + "affiliation": "[sole proprietor]", + "twitter": "bhenick", + "bio": "

Ben Henick has worked as a freelance web developer for more than fifteen years, catering especially to SMBs with unique project requirements. He has also fulfilled a number of production contracts relating to sites well-known to technology workers; odds are short that at one time or another, your web browser has rendered a stylesheet that he wrote.

\n

Ben has fulfilled one contract for O’Reilly Media, for HTML & CSS: The Good Parts (2010). He is currently working on three others:

\n
    \n\t
  • The New Beginner’s Guide to HTML (\u2026And CSS) (2014, Atlas-only, free-to-read)
  • \n\t
  • No-Nonsense HTML & CSS (2014)
  • \n\t
  • Mastering HTML & CSS (2014)
  • \n
\n

Prior to joining O’Reilly’s author roster, Ben served a stint as the Managing Editor of the erstwhile Digital Web Magazine and was active in several volunteer professional education settings, including the Web Standards Project, Evolt, and webdesign-l. He still lurks webdesign-l and the css-d mailing lists, along with other O’Reilly authors.

\n

Ben lives in his hometown of Portland, Oregon, and is currently seeking to switch from freelance to fulltime exempt work.

" + }, + + { + + "serial": 161475, + "name": "Cody Herriges", + "photo": null, + "url": "http://puppetlabs.com/", + "position": "Operations Engineer", + "affiliation": "Puppet Labs", + "twitter": "cody_herriges", + "bio": "

Cody Herriges joined Puppet Labs in 2010 and now serves as the current Tech Lead of Systems maintaining Puppet Labs’ public and private cloud platforms, storage, and hardware infrastructure. He started his career in higher education infrastructure management where he deployed his school’s first x86 virtualization cluster on top of KVM. It was a world of complicated legacy software needing to fit into a transformed open source world. Needing to maintain all this software eventually taught him the importance of automation.

" + }, + + { + + "serial": 156534, + "name": "Jason Hibbets", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_156534.jpg", + "url": "http://opensource.com/", + "position": "Project manager - opensource.com", + "affiliation": "Red Hat", + "twitter": "jhibbets", + "bio": "

Jason Hibbets is a project manager in Corporate Marketing at Red Hat. He is the lead administrator, content curator, and community manager for Opensource.com and has been with Red Hat since 2003. He is the author of “The foundation for an open source city,” a book that outlines how to implement the open source city brand through open government.

\n

He graduated from NC State University and lives in Raleigh, NC. where he’s been applying open source principles in neighborhood organizations in Raleigh for several years, highlighting the importance of transparency, collaboration, and community building. In his spare time, he enjoys surfing, gardening, watching football, participating in his local government, blogging for South West Raleigh, and training his Border Collies to be frisbee and agility dogs. He heads to the beaches of North Carolina during hurricane season to ride the waves.

" + }, + + { + + "serial": 171822, + "name": "Bridget Hillyer", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_171822.jpg", + "url": "http://bridgetconsulting.com/", + "position": "Software Consultant", + "affiliation": "Self", + "twitter": "BridgetHillyer", + "bio": "

Bridget is an independent software consultant of many years. She is on the journey to become a Clojure programmer.

" + }, + + { + + "serial": 6653, + "name": "Mark Hinkle", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_6653.jpg", + "url": "http://www.socializedsoftware.com/", + "position": "Senior Director, Open Source Solutions", + "affiliation": "Citrix", + "twitter": "mrhinkle", + "bio": "

Once upon a time, a lot longer ago then he wished Mark Hinkle was born in a small Pennsylvania town and through the most unlikely sequence of events ended up as a technologist involved in the development and evangelism of free and open source software. Mark has written extensively on open source as the former editor-in-chief of LinuxWorld Magazine and for numerous other publications (Network World, Forbes, Linux.com). He currently is the Senior Director, Open Source Solutions at Citrix Systems where he helps support the Apache CloudStack and Xen.org projects. You can find his blog at www.socializedsoftware.com and you can follow him on Twitter @mrhinkle.

" + }, + + { + + "serial": 122917, + "name": "Rob Hirschfeld", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_122917.jpg", + "url": "http://www.dell.com/cloud", + "position": "Principal Architect, Cloud Solutions", + "affiliation": "Dell, Inc.", + "twitter": "zehicle", + "bio": "

With a background in Mechanical and Systems Engineering, Rob Hirschfeld, Sr. Distinguished Cloud Architect at Dell, specializes in operations for large scale, integrated, and innovative cloud systems. He’s a community elected OpenStack board member, a leader of Dell’s OpenStack efforts, and founder of the Crowbar project. Rob helps Dell to set strategy for cloud computing, drives innovative cloud solutions to market, and works with customers on their cloud implementations. Rob is a graduate of Duke and Louisiana State University. In addition to cloud technologies, Rob is also known for his passion and expertise on the Agile/Lean process. You can find Rob\u2019s thoughts on cloud innovation at his blog RobHirschfeld.com or as @Zehicle. on Twitter.

" + }, + + { + + "serial": 62631, + "name": "Jeanne Holm", + "photo": null, + "url": "http://km.nasa.gov/home/index.html", + "position": "Chief Knowledge Architect", + "affiliation": "Jet Propulsion Laboratory, NASA", + "twitter": "jeanne_jpl", + "bio": "

Jeanne Holm is the Chief Knowledge Architect at the Jet Propulsion Laboratory (JPL), California Institute of Technology. Ms. Holm leads NASA\u2019s Knowledge Management Team, looking at how to access and use the knowledge gathered over the many missions of the US space agency to support missions and to drive innovation. As a lead for the award-winning NASA public and internal portals, she was at the helm of NASA\u2019s web during the largest Internet event in Government history\u2014the landing of the Mars Exploration Rovers on the surface of Mars. As the lead implementer for technologies supporting project managers at NASA, her team\u2019s solutions are helping to drive how people will manage space missions in the future, learn virtually, and share lessons learned. Her latest activities involve the transformation of NASA into a learning organization through innovative techniques in developing communities of practice and ensuring lessons are shared and embedded across the organization. Ms. Holm chairs The Federal Knowledge Management Group and a United Nations group looking at KM for Space.

\n

Her degrees are from UCLA and Claremont Graduate University. She is an instructor at UCLA where she teaches internationally and her online and ground-based courses focuses on KM strategies and social networking. She has been awarded numerous honors, including the NASA Exceptional Service Medal for leadership (twice), the NASA Achievement Award for her work on the Galileo and Voyager spacecraft, three Webby\u2019s from The International Academy of Digital Arts and Sciences, Competia\u2019s 2003 Champion of the Year, and a best practice from the APQC for \u201cUsing Knowledge Management to Drive Innovation\u201d.

\n

Specialties
\nDesigning and implementing knowledge architectures, system design and integration, knowledge management practices and systems, knowledge-based engineering, e-learning, instructional design and delivery, social networking analysis, inter-agency and inter-organizational knowledge sharing

" + }, + + { + + "serial": 173380, + "name": "Johnny Hughes", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_173380.jpg", + "url": "http://www.centos.org/", + "position": "Software Engineer", + "affiliation": "CentOS Project", + "twitter": "", + "bio": "

CentOS-4 Lead Developer, CentOS-5 updates, CentOS-6 updates. If you are looking for me in the CentOS IRC channels or Forums I am hughesjr there.

\n

I first started working with UNIX in the early 1980’s with an AT&T 3b2 server.

\n

I first started working with Linux in 1995 with a Red Hat Linux webserver.

\n

I retired from the US Navy in 1997 (I spent ~20 years as a Engineering Laboratory Technician on Nuclear Submarines while in the US Navy). Lots of that time I also spent as a UNIX systems administrator working with AT&T and HP-UX servers.

\n

Since 1997 I have been a UNIX (HP-UX, AIX, Solaris), Windows (NT, 2000, 2003), Linux (Red Hat, CentOS) systems administrator, an Oracle DBA, a Senior Systems Analyst and a Network Engineer … and finally now a Software Engineer for the CentOS Project

" + }, + + { + + "serial": 77939, + "name": "Nathan Humbert", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_77939.jpg", + "url": "http://nathanhumbert.com/", + "position": "Senior Software Engineer", + "affiliation": "New Relic, Inc.", + "twitter": "", + "bio": "

Nathan Humbert is a Senior Software Engineer at New Relic where he works with an amazing group of people to build software that helps people understand how their software is functioning.

" + }, + + { + + "serial": 159719, + "name": "Michael Hunger", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_159719.jpg", + "url": "http://www.neo4j.org/", + "position": "Developer Evangelist", + "affiliation": "Neo Technology", + "twitter": "mesirii", + "bio": "

Michael Hunger has been passionate about software development for a long time. He is particularly interested in the people who develop software, software craftsmanship, programming languages, and improving code.

\n

For the last two years he has been working with Neo Technology on the Neo4j graph database. As the project lead of Spring Data Neo4j he helped developing the idea to become a convenient and complete solution for object graph mapping. He is also taking care of Neo4j cloud hosting efforts.

\n

As a developer he loves to work with many aspects of programming languages, learning new things every day, participating in exciting and ambitious open source projects and contributing to different programming related books. Michael is a frequent speaker at industry events and is an active editor and interviewer at InfoQ.

" + }, + + { + + "serial": 164756, + "name": "Pete Hunt", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_164756.jpg", + "url": "http://www.petehunt.net/", + "position": "Software Engineer", + "affiliation": "Facebook / Instagram", + "twitter": "floydophone", + "bio": "

Hacker at Facebook, currently leading Instagram.com web engineering. Formerly Facebook Photos and Videos lead.

\n

Core team on http://facebook.github.io/react/ and http://github.com/facebook/huxley

" + }, + + { + + "serial": 4265, + "name": "Kirsten Hunter", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_4265.jpg", + "url": "http://www.princesspolymath.com/", + "position": "API Evangelist", + "affiliation": "Akamai", + "twitter": "synedra", + "bio": "

Kirsten Hunter is an unapologetic hacker and passionate advocate for the development community. Her technical interests range from graph databases to cloud services, and her experience supporting and evangelizing REST APIs has given her a unique perspective on developer success. In her copious free time she’s a gamer, fantasy reader, and all around rabble-rouser. Code samples, recipes, and philosophical musings can be found on her blog, Princess Polymath.

" + }, + + { + + "serial": 152299, + "name": "Vanessa Hurst", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_152299.jpg", + "url": "http://vanessahurst.com/", + "position": "CEO", + "affiliation": "CodeMontage", + "twitter": "DBNess", + "bio": "

Vanessa is a data-focused developer and the CEO of CodeMontage, which empowers coders to improve their impact on the world. She believes computing is one of the most efficient and effective ways to improve the human experience. She founded Developers for Good and co-founded WriteSpeakCode and Girl Develop It. Previously, she wrangled data at Paperless Post, Capital IQ, and WealthEngine. Vanessa holds a B.S. in Computer Science with a minor in Systems and Information Engineering from the University of Virginia.

\n

Vanessa’s work in technology education and social change has appeared on the TODAY show, NPR, Al Jazeera America, Entrepreneur, The New York Times, Fast Company, and other media. She serves as a cast member for Code.org, a nonprofit dedicated to ensuring every student has the opportunity to learn to code, which reached over 20 million students in 2013 alone.

" + }, + + { + + "serial": 173235, + "name": "Mihail Irintchev", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_173235.jpg", + "url": "http://www.siteground.com/", + "position": "Head of Software Development", + "affiliation": "SiteGround", + "twitter": "irintchev", + "bio": "

Born and living in Sofia, Bulgaria

\n

Lived in Ohio for a while

\n

Bachelor’s degree in CS from the American University in Bulgaria, class of 2003

\n

MSc from Sofia University – Electronic Business, class of 2006

\n

Web developer since 2003

\n

Co-founder of the BulgariaPHP user group

\n

Working at SiteGround since 2003

\n

Interested in history, military vehicles, motorcycles, science fiction, rock’n’roll

" + }, + + { + + "serial": 122564, + "name": "Phil Jackson", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_122564.jpg", + "url": "http://sldn.softlayer.com/", + "position": "Development Community Advocate", + "affiliation": "SoftLayer", + "twitter": "", + "bio": "

Phil Jackson is Development Community Advocate for SoftLayer. He helps customers and partners integrate with the SoftLayer’s API. He also architects the company’s Drupal websites, writes API documentation, and maintains the developer blog. Formerly, he was a Sales Engineer building internal tools and providing technical consultation for potential and existing customers. Jackson started his career in webhosting at Ev1Servers where he led the training department. With a passion for technology that started at a young age, he has developed skills in a variety of scripting and programming languages and enjoys sharing his knowledge with the tech community.

" + }, + + { + + "serial": 173189, + "name": "Daniel Jacobson", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_173189.jpg", + "url": "http://www.netflix.com/", + "position": "Vice President of Engineering, Netflix API and Playback", + "affiliation": "Netflix", + "twitter": "daniel_jacobson", + "bio": "

Daniel Jacobson is the Director of Engineering for the API and Playback Services at Netflix, responsible for delivering more than one billion streaming hours a month to Netflix users on more than 1,000 device types. Prior to Netflix, he was Director of Application Development at NPR, leading the development of NPR\u2019s custom content management system. He also created NPR\u2019s API which became the distribution channel for getting NPR content to mobile platforms, member station sites throughout the country as well as to the public developer community. Daniel also co-authored the O\u2019Reilly book, \u201cAPIs: A Strategy Guide.

" + }, + + { + + "serial": 173435, + "name": "Paul Kehrer", + "photo": null, + "url": "http://rackspace.com/", + "position": "Developer IV", + "affiliation": "Rackspace", + "twitter": "reaperhulk", + "bio": "

Paul Kehrer is the crypto expert on the Barbican project, an open source key management platform for OpenStack. He has experience running a public certificate authority as well as doing significant open source work in cryptography by writing and maintaining r509, a ruby library for managing a certificate infrastructure and cryptography, a modern python crypto library.

" + }, + + { + + "serial": 120025, + "name": "Benjamin Kerensa", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_120025.jpg", + "url": "http://benjaminkerensa.com/", + "position": "Community Release Manager", + "affiliation": "Mozilla", + "twitter": "bkerensa", + "bio": "

Benjamin Kerensa is an internationally recognized open source evangelist, community manager, writer and speaker who currently is a Firefox Release Manager and Community Manager for Trovebox.

" + }, + + { + + "serial": 182019, + "name": "Saud Khan", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_182019.jpg", + "url": "http://twitter.com/bidyut", + "position": "Software Engineer", + "affiliation": "Twitter", + "twitter": "", + "bio": "

Over the past years Saud has worked on upgrading the user experience of Twitter for Android including search, trends and collections. From bringing search suggestions to the amazing photo grid of search results, Saud has worked with the designers to bring advanced search features in a mix of Twitter and Android design paradigm.

\n

Before that Saud developed a generic call management library used to bring VoIP capabilities in various mobile devices including Android, iOS and Windows CE. Additionally he worked on porting the solution over to myriad of embedded platforms like radio gateways and call boxes.

" + }, + + { + + "serial": 157931, + "name": "Shashank Khandelwal", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_157931.jpg", + "url": "http://cfpb.gov/", + "position": "Software Developer", + "affiliation": "Consumer Financial Protection Bureau", + "twitter": "gazeti", + "bio": "

As a Design and Technology Fellow at the Consumer Financial Protection Bureau, Shashank Khandelwal develops software to make consumer financial products work for the American people. He have over eight years of software development experience and his career spans multiple domains, languages and technologies. More recently, Shashank used a data-driven approach to study human health behavior. He wrote software to collect large-scale social media data, filter, and analyze it using machine learning techniques, and released it as a disease surveillance platform for the academic, private, and government health community. One of his earlier Twitter-based studies has been featured on NPR (“What Twitter Knows about Flu” by Jordan Calmes on October 14, 2011). Previously, he wrote document management software and worked on the vacation packages team at Orbitz (in Chicago). Over the years, he has written software in Python, Java, PHP and other languages using a variety of tools, applications and libraries.

" + }, + + { + + "serial": 172929, + "name": "Kevin Kluge", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_172929.jpg", + "url": "http://www.elasticsearch.org/", + "position": "VP Engineering", + "affiliation": "Elasticsearch", + "twitter": "kevinkluge", + "bio": "

Kevin is Vice President of Engineering at Elasticsearch, which produces open source search and analytics software. Previously Kevin was at Citrix Systems where he was responsible for engineering of Citrix CloudPlatform, powered by Apache CloudStack. Kevin has also held senior engineering positions at several other start-ups, including Cloud.com and Zimbra, and Sun Microsystems. Kevin has a B.S. and M.S. in Computer Science from Stanford University.

" + }, + + { + + "serial": 144736, + "name": "Francesca Krihely", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_144736.jpg", + "url": "http://10gen.com/", + "position": "Community Lead", + "affiliation": "MongoDB", + "twitter": "francium", + "bio": "

Francesca is the Community Lead for MongoDB, managing the fastest growing community in Big Data. In this role she is responsible for managing acquisition and development of new community members and build out open source contributions to MongoDB’s server and project ecosystem. She holds a BA from Oberlin College in History and Sociology and is a recovering Philosophy nerd.

" + }, + + { + + "serial": 125113, + "name": "Spencer Krum", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_125113.jpg", + "url": null, + "position": "Cloud Engineer", + "affiliation": "HP", + "twitter": "", + "bio": "

Spencer is a DevOps Engineer at UTi Worldwide Inc, a logistics firm. He has been using Puppet for 4 years. He is one of the co-authors of Pro Puppet 2nd edition from Apress. He lives and works in Portland and enjoys cycling and Starcraft.

" + }, + + { + + "serial": 6380, + "name": "Bradley Kuhn", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_6380.jpg", + "url": "http://ebb.org/bkuhn/", + "position": "President", + "affiliation": "Software Freedom Conservancy", + "twitter": "bkuhn_ebb_org", + "bio": "

Bradley M. Kuhn is a Director of FSF, President of the Software Freedom Conservancy, and won the O’Reilly Open Source Award at OSCON 2012. Kuhn began his work in the Free, Libre and Open Source Software (FLOSS) Movement as a volunteer in 1992, when he became an early adopter of the GNU /Linux operating system, and began contributing to various FLOSS projects. He worked during the 1990s as a system administrator and software developer for various companies, and taught AP Computer Science at Walnut Hills High School in Cincinnati. Kuhn’s non-profit career began in 2000, when he was hired by the Free Software Foundation. As FSF’s Executive Director from 2001-2005, Kuhn led FSF’s GPL enforcement, launched its Associate Member program, and invented the Affero GPL. From 2005-2010, Kuhn worked as the Policy Analyst and Technology Director of the Software Freedom Law Center. Kuhn holds a summa cum laude B.S. in Computer Science from Loyola University in Maryland, and an M.S. in Computer Science from the University of Cincinnati. His Master’s thesis (an excerpt from which won the Damien Conway Award for Best Technical Paper at this conference in 2000) discussed methods for dynamic interoperability of FLOSS languages. Kuhn has a regular blog and a microblog (@bkuhn on identi.ca).

" + }, + + { + + "serial": 170822, + "name": "Andreas Kunz", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_170822.jpg", + "url": "http://openui5.org/", + "position": "Development Architect", + "affiliation": "SAP SE", + "twitter": "aku_dev", + "bio": "

Working as Software Developer and Architect at SAP since 2005, focusing on user interfaces.
\nMember of the UI5 (HTML/JS UI library created by SAP) development team since its inception and spending considerable time to work around browser bugs…

" + }, + + { + + "serial": 62981, + "name": "Lars Kurth", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_62981.jpg", + "url": "http://www.xen.org/", + "position": "Director Open Source Programs Office, Community Manager Xen Project", + "affiliation": "Citrix Systems Ltd", + "twitter": "lars_kurth", + "bio": "

Lars Kurth had his first contact with the open source community in 1997 when he worked on various parts of the ARM toolchain. This experience led Lars to become a passionate open source enthusiast who worked with and for many open source communities over the past 15 years. Lars contributed to projects such as GCC, Eclipse, Symbian and Xen and became the open source community manager for Xen.org in 2011. Lars is an IT generalist with a wide range of skills in software development and methodology. He is experienced in leading and building engineering teams and communities, as well as constructing marketing, product management, and change programs impacting 1000s of users. He has 17 years of industry experience in the infrastructure, tools and mobile sectors working at ARM, Citrix, Nokia and the Symbian Foundation.

" + }, + + { + + "serial": 177245, + "name": "Shadaj Laddad", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_177245.jpg", + "url": "http://shadaj.me/", + "position": "Student", + "affiliation": "School", + "twitter": "", + "bio": "

Shadaj is a 14 year old who loves to program. He has programmed in Logo, NXT Mindstorms, Ruby, Python, C, Java, and Scala\u2014his favorite. Shadaj hosts his projects on GitHub, and has an educational channel on Youtube. He has presented at Scala Days 2013, Scala Days 2012, and the Bay Area Scala Enthusiast group showing his Scala projects. Besides programming, he likes Math and Science. Shadaj is also an active member of his school community as Student Council President. He loves spreading a love of technology, and started TechTalks\u2014a program that brings guest speakers to share their knowledge and enthusiasm with students at his school. When not doing his school work or programming, he plays guitar, sitar, and games, some of which he created.

" + }, + + { + + "serial": 172658, + "name": "Michael Laing", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_172658.jpg", + "url": "http://nytimes.com/", + "position": "Systems Architect", + "affiliation": "New York Times", + "twitter": "", + "bio": "

NYTimes; Systems Architect; 2011-present \u2014 Architect of nyt\u2a0da\u0431rik

\n

United Nations; IT Evangelist; 2002-2011 \u2014 Principal advisor to CIO

\n

Blue Note Technology; CEO etc; 1998-2002

\n
    \n\t
  • 2001 New England Web Design award
  • \n\t
  • 2000 Software and Information Industry Association Codie award
  • \n
\n

Harvard University Tech Eval Group; consultant; 1985-1998

\n

Education:

\n
    \n\t
  • Harvard Business School
  • \n\t
  • United States Military Academy
  • \n
\n

Personal:

\n
    \n\t
  • Sings
  • \n\t
  • Dances
  • \n
" + }, + + { + + "serial": 173308, + "name": "Alex Lakatos", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_173308.jpg", + "url": "https://github.com/AlexLakatos", + "position": "Mozilla Representative", + "affiliation": "Mozilla", + "twitter": "lakatos88", + "bio": "

Alex Lakatos is a Mozilla Representative and contributor to the Mozilla project for the past three years, based in Cluj-Napoca, the heart of Transylvania. He’s a JavaScript developer building on the open web, trying to push it’s boundaries every day. You can check out his github profile or get in touch on twitter. When he\u2019s not programming, he likes to travel the world, so it\u2019s likely you\u2019ll bump into him in an airport lounge.

" + }, + + { + + "serial": 137347, + "name": "James Lance", + "photo": null, + "url": "http://www.bluehost.com/", + "position": "Developer - Team Architect", + "affiliation": "bluehost.com", + "twitter": "", + "bio": "

James Lance is a developer at Bluehost.com. He has been developing in Perl for over 10 years. He is also an active member in the Utah Open Source community and Core Team member of the OpenWest Conference

\n

When he\u2019s not behind a computer you can usually find a yo-yo in his hand. He also enjoys Ham radio, camping, and various forms of BBQing (smoking in particular).

" + }, + + { + + "serial": 116050, + "name": "Yoav Landman", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_116050.jpg", + "url": "http://www.jfrog.com/", + "position": "CTO", + "affiliation": "JFrog", + "twitter": "yoavlandman", + "bio": "

Yoav is the CTO of JFrog, the Artifactory Binary Repository creators, JavaOne 2011 and 2013 Duke Choice Awards winner; Yoav laid the foundation to JFrog’s flagship product in 2006 when he founded Artifactory as an open source project.
\nIn 2008 he co-founded JFrog where he leads the future of products like Artifactory and, more recently, Bintray.
\nPrior to starting up JFrog, Yoav was a software architect and consultant in the field of configuration management and server-side JVM applications.
\nYoav blogs at jfrog.org and java.net.

" + }, + + { + + "serial": 172864, + "name": "Markus Lanthaler", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_172864.jpg", + "url": "http://www.markus-lanthaler.com/", + "position": "Developer, Consultant, W3C Invited Expert", + "affiliation": "Freelancer", + "twitter": "markuslanthaler", + "bio": "

Markus has been programming since the age of 9. After a short journey with Perl, he quickly moved to PHP for most of his Web-related programming. He implemented his first PHP-based CMS more than 10 years ago (about the time of Drupal\u2019s first release), worked with Symfony when it was still Mojavi, and launched his first online shop in 2003. Since then he has been programming almost everything from microcontrollers on Smart Cards (in assembler) up to large-scale distributed systems. More recently Markus\u2019 focus shifted to large, distributed Web applications. He is one of the core designers of JSON-LD, the inventor of Hydra, and a W3C Invited Expert.

" + }, + + { + + "serial": 183609, + "name": "Walter `wxl` Lapchynski", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_183609.jpg", + "url": "http://wxl.freeshell.org/", + "position": "Sales Consultant & IS Assistant", + "affiliation": "Bike Friday", + "twitter": null, + "bio": "

Walter \u2665 everything about bikes & computers. He’s a Release Manager for Lubuntu & the Team Leader for Ubuntu Oregon. Bioinformatics looms in his future, as much as his obsession with Unicode allows\u2026

" + }, + + { + + "serial": 182081, + "name": "Chris Launey", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_182081.jpg", + "url": null, + "position": "Director of Cloud Services & Architecture", + "affiliation": "The Walt Disney Company", + "twitter": "clauney", + "bio": "

Chris Launey is the Director of Cloud Services & Architecture in the core
\narchitecture and engineering group at the Walt Disney Company. His
\norganization lives at the intersection of the quickly shifting demands of
\na media and entertainment company and the mission of a managed service
\nprovider to maintain a stable, scalable, and efficient technology
\nplatform. Chris team launched Disney\u00b9s first true on-demand IaaS offering
\nand built a service integration platform to cloudify more internal
\ntechnology services. His team is now building on that to make pubic
\nservices easier to broker and consume for the Walt Disney Company.

" + }, + + { + + "serial": 151665, + "name": "Mark Lavin", + "photo": null, + "url": null, + "position": "Development Director", + "affiliation": "Caktus Consulting Group", + "twitter": "", + "bio": "

Mark is a lead Python/Django developer and technical manager at Caktus Consulting Group in Carrboro, NC. He also runs a small homebrewing website written in Django called brewedbyus.com. He came to Python web development after a few years pricing derivatives on Wall Street. Mark maintains a number of open source projects primarily related to Django development and frequently contributes back to projects used by Caktus. When he isn’t programming, Mark enjoys spending time with his wife and daughter, brewing beer, and running.

" + }, + + { + + "serial": 152106, + "name": "Ed Leafe", + "photo": null, + "url": "http://rackspace.com/", + "position": "Developer", + "affiliation": "Rackspace", + "twitter": "EdLeafe", + "bio": "

Ed has been a developer for over 20 years, and is one of the original developers involved with the creation of OpenStack. After leaving the Microsoft world over a decade ago he has been primarily a Python developer, and has spoken as several US PyCons, as well as PyCon Australia and PyCon Canada. He has been working with Rackspace for the past 6 years as a senior Python developer. He also throws a mean Frisbee.

" + }, + + { + + "serial": 74565, + "name": "Jonathan LeBlanc", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_74565.jpg", + "url": "http://www.jcleblanc.com/", + "position": "Head of Global Developer Evangelism", + "affiliation": "PayPal + Braintree", + "twitter": "jcleblanc", + "bio": "

Jonathan LeBlanc is an Emmy award winning software engineer, author of the O\u2019Reilly book “Programming Social Applications”, and the head of North American Developer Evangelism at PayPal. Specializing in user identity concepts, hardware to web interconnectivity, data mining techniques, as well as open source initiatives around social engagement, Jonathan works on the development of emerging initiatives towards building a more user-centric web.

" + }, + + { + + "serial": 157509, + "name": "Robert Lefkowitz", + "photo": null, + "url": "http://sharewave.com/", + "position": "CTO", + "affiliation": "Sharewave", + "twitter": "sharewaveteam", + "bio": "

Robert “r0ml” Lefkowitz is the CTO at Sharewave, a startup building an investor management portal. This year, he was a resident at the winter session of Hacker School. He is a Distinguished Engineer of the ACM.

" + }, + + { + + "serial": 124700, + "name": "Janice Levenhagen-Seeley", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_124700.jpg", + "url": "http://www.chicktech.org/", + "position": "Executive Director", + "affiliation": "ChickTech", + "twitter": "", + "bio": "

Janice is the Executive Director of ChickTech. She has a BS in Computer Engineering from Oregon State University and an MBA from Willamette University. She believes strongly that the diversity and strengths that women can bring will push high tech to even more impressive heights. Her inspiration for creating ChickTech came from her own experiences in computer engineering and the realization that the percentage of women in engineering isn\u2019t going to get higher by itself.

" + }, + + { + + "serial": 161517, + "name": "Pernilla Lind", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_161517.jpg", + "url": "http://www.neo4j.org/", + "position": "Community Manager", + "affiliation": "Neo4j", + "twitter": "p3rnilla", + "bio": "

Pernilla is the Community Manager at Neo Technology, the company behind the open source project Neo4j. Pernilla loves to connect and spread the word of graphs and bring new ideas into life. She is an engaging speaker and teacher and hasthe mission in life to spread knowledge about technology.Pernilla spent some times in Kenya with the massai people for a year to build up their infrastructure and engage their community to do income generating activities. Before that she lived in Gambia to help up a womens organization.

\n

Pernilla is also an engaging member and organizer of Geek Girl Meetup, a network for women in tech in Sweden, where she plans and organizes conferences and meetups. She is always involved in different technoogy ecosystems and loves to meet new people and learn about new cool tech stuff. To be a role model and stand up for peoples right it\u2019s something Pernilla always do.

\n

Cats, people, philantrophy, graphs, open data, women in tech, innovation, crazy ideas and Doctor Who are subjects Pernilla can talk about forever.

" + }, + + { + + "serial": 77469, + "name": "Philip Lindsay", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_77469.jpg", + "url": "http://rancidbacon.com/", + "position": "Troublemaker/Artisan", + "affiliation": "rancidbacon.com", + "twitter": "RancidBacon", + "bio": "

Philip Lindsay (also known as “follower” from rancidbacon.com ) writes documentation, creates code libraries, develops example projects and provides developer support for companies including Pebble Technology, SparkFun Electronics, Arduino and other clients.

\n

Tim O’Reilly once called Philip a “troublemaker” for his early Google Maps reverse engineering efforts.

\n

He has a particular interest in the areas where design, art, craft and technology intersect.

\n

Follow his project logs at Labradoc.

" + }, + + { + + "serial": 173403, + "name": "Steve Lipner", + "photo": null, + "url": "http://www.microsoft.com/security/sdl/", + "position": "Partner Director of Software Security, Trustworthy Computing Security", + "affiliation": "Microsoft Corp.", + "twitter": "msftsecurity", + "bio": "

Steve Lipner, Senior Director of Security Engineering Strategy, Microsoft\u2019s Trustworthy Computing Group

\n

As the senior director of security engineering strategy in Microsoft Corp.\u2019s Trustworthy Computing Group, Steve Lipner is responsible for Microsoft\u2019s Security Development Lifecycle team, including the development of programs that provide improved product security and privacy to Microsoft\u00ae customers. Additionally, Lipner is responsible for Microsoft\u2019s engineering strategies related to the company\u2019s End to End Trust initiative, aimed at extending Trustworthy Computing to the Internet.

\n

Lipner has more than 35 years experience as a researcher, development manager and general manager in information technology security, and is named as inventor on thirteen U.S. patents in the field of computer and network security. He holds both an S.B. and S.M. degree from the Massachusetts Institute of Technology, and attended the Harvard Business School\u2019s Program for Management Development.

" + }, + + { + + "serial": 171564, + "name": "Joshua Long", + "photo": null, + "url": "http://spring.io/", + "position": "Spring Developer Advocate", + "affiliation": "Pivotal", + "twitter": "starbuxman", + "bio": "

Josh Long is the Spring developer advocate at [Pivotal](http://gopivotal.com). Josh is the lead author on 4 books and instructor in one of Safari’s best-selling video series, all on Spring. When he’s not hacking on code, he can be found at the local Java User Group or at the local coffee shop. Josh likes solutions that push the boundaries of the technologies that enable them. His interests include cloud-computing, business-process management, big-data and high availability, mobile computing and smart systems. He blogs on [spring.io](https://spring.io/team/jlong) or on [his personal blog](http://joshlong.com) and [on Twitter (@starbuxman)](http://twitter.com/starbuxman).

" + }, + + { + + "serial": 141574, + "name": "Tim Mackey", + "photo": null, + "url": "http://www.citrix.com/", + "position": "Cloud Evangelist", + "affiliation": "Citrix Systems", + "twitter": "XenServerArmy", + "bio": "

Tim Mackey is a technology evangelist for XenServer and CloudStack within the Citrix Open Source Business Office and focused on server virtualization and cloud orchestration technical competencies. When he joined Citrix in 2003, he was an engineer and architect focused on low impact application performance monitoring. During his tenure at Citrix he has held roles in the XenServer team as an architect, consultant, product manager and product marketing.

" + }, + + { + + "serial": 109140, + "name": "Anil Madhavapeddy", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_109140.jpg", + "url": "http://anil.recoil.org/", + "position": "Research Fellow", + "affiliation": "University of Cambridge", + "twitter": "avsm", + "bio": "

Dr. Anil Madhavapeddy is a Senior Research Fellow at Wolfson College and is based in the Cambridge University Computer Laboratory, investigating programming models for cloud computing. Anil was on the original team at Cambridge that developed the Xen hypervisor, and subsequently served as the senior architect and product director for XenSource/Citrix before returning to academia.

\n

Anil has a diverse background in industry at Network Appliance, Citrix, NASA, and Internet Vision. He is an active member of the open source development community with the OpenBSD operating system and more, and the co-chair of this year’s Commercial Uses of Functional Programming conference.

" + }, + + { + + "serial": 173303, + "name": "Rachel Magario", + "photo": null, + "url": "http://www.rachelmagario.com/contact/", + "position": "Adversity Expert", + "affiliation": "The Blind Visionary", + "twitter": "rachelmagario", + "bio": "

Rachel Magario is an Assistive Technology Specialist at Pacer Simon Technology Center. As the first totally blind interaction designer, Rachel is known as a leader and visionary who has triumphed over adversity consistently throughout her life.

\n

Rachel has presented at SXSW Interactive and consulted for various companies and universities throughout the United States on accessibility. Rachel has an MBA with a concentration in Marketing and a Masters in Interaction design from the University of Kansas.

\n

Magario\u2019s dream is that usability and accessibility can be considered from the start of a project and not as an after thought. She believes this would open the door for access of information and for accessible tools. This shift would allow her and others to pursue their careers of choice and live with the dignity that should be the right of every human.

\n

Rachel has been involved with accessibility consulting and advocacy since the early 2000s. Throughout the years, Rachel served as an accessibility consultant to several university related projects and non-profit sites that were required to comply with section 508.

\n

Rachel soon realized through her experience that accessibility issues often involve problems of usability that affect anyone who accesses information. When Rachel started her Masters in Interaction Design, she experienced the lack of accessibility in the design tools that she was using as well as in end products coming out of these tools.
\nSince then, Rachel has made her mission to research and develop models and prototypes of accessible user experience. She enjoys working closely with designers and developers to ensure standards are met and to create awareness of the importance of accessible user experience.

" + }, + + { + + "serial": 173324, + "name": "Allison Mankin", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_173324.jpg", + "url": null, + "position": "Director of Verisign Labs", + "affiliation": "Verisign, Inc.", + "twitter": "", + "bio": "

Allison is the Director of Verisign Labs, a research organization focusing on long-term evolution of the
\nInternet infrastructure and on open standards-based prototyping. She has been active in Internet engineering
\nand research for over 25 years, including having served at the Internet Engineering Task Force as an area
\ndirector for 10 of those years. She is best known having co-led the IPng Selection Process at IETF (long ago).
\nPast open source projects include open internet conferencing, multicast and IPv6. Her research and RFCs have been primarily in the areas of TCP and DNS and their security.

" + }, + + { + + "serial": 118998, + "name": "Jonathon Manning", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_118998.jpg", + "url": "http://tothesecretlab.com/", + "position": "Co-founder", + "affiliation": "Secret Lab Pty. Ltd.", + "twitter": "desplesda", + "bio": "

Jon Manning is the co-founder of Secret Lab, an independent game development studio based in Hobart, Tasmania, Australia. He\u2019s worked on apps of all sorts, ranging from iPad games for children to instant messaging clients. He\u2019s a Core Animation demigod, and frequently finds himself gesticulating wildly in front of classes full of eager-to-learn iOS developers.

\n

Jon is currently writing “Mobile Game Development With Unity” and “iOS Game Development Cookbook”, both for O’Reilly, and is the co-author of the books “Learning Cocoa with Objective-C Third Edition” (O’Reilly, 2012 and 2014), “iPhone and iPad Game Development For Dummies” (Wiley, 2010) and “Unity Mobile Game Development For Dummies” (Wiley, 2011).

\n

Jon is the world\u2019s biggest Horse_ebooks fan.

" + }, + + { + + "serial": 6931, + "name": "Joshua Marinacci", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_6931.jpg", + "url": "http://www.joshondesign.com/", + "position": "Researcher", + "affiliation": "Nokia", + "twitter": "joshmarinacci", + "bio": "

Ask me about HTML Canvas, mobile apps, and visual design. Or 3D printing and wearable computing. Or just ask me to rant about Java.

\n

Josh Marinacci is a blogger and co-author of “Swing Hacks” and “Building Mobile Apps with Java” for O’Reilly. He is currently a researcher for Nokia.

\n

He previously worked on webOS at Palm and JavaFX, Swing, NetBeans, and the Java Store at Sun Microsystems.

\n

Josh lives in Eugene, Oregon and is passionate about open source technology & great user experiences.

" + }, + + { + + "serial": 173388, + "name": "Elaine Marino", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_173388.jpg", + "url": "http://ladycodersboulder.com/homepage/about", + "position": "Owner", + "affiliation": "LadyCoder Productions", + "twitter": "elaine_marino", + "bio": "

Elaine Marino is a Ruby on Rails coder, marketer and entrepreneur with a 13-year background in strategic marketing and brand-building for Fortune 500 companies. Her ability to communicate effectively, manage large-scale projects, build brands and communities as well as web applications, puts her in a unique position within the software sector. There is a sweet-spot where technology enhances the lives of people and communities — that is where she thrives.

\n

She founded LadyCoders Boulder, a 2-day career career conference for 60 women software engineers, hosted at Google Boulder. LadyCoders Boulder provides tangible, real-world advice to women engineers to advance their careers.

" + }, + + { + + "serial": 164144, + "name": "Will Marshall", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_164144.jpg", + "url": "http://www.planet.com/", + "position": "CEO", + "affiliation": "Planet Labs", + "twitter": "wsm1", + "bio": "

Will is responsible for setting the company\u2019s vision and and for architecting the company strategy. Previously, Will was a Scientist at NASA/USRA where he served as Co-Investigator for PhoneSat, Science Team member on the LCROSS and LADEE lunar missions. He led research projects in orbital space debris remediation. Will has published over 30 articles in scientific publications. Will received his Ph.D. in Physics from the University of Oxford and was a Postdoctoral Fellow at Harvard University.

" + }, + + { + + "serial": 5199, + "name": "Alex Martelli", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_5199.jpg", + "url": "http://www.aleax.it/", + "position": "Senior Staff Engineer", + "affiliation": "Google", + "twitter": "", + "bio": "

Alex Martelli wrote “Python in a Nutshell” and co-edited
\n“Python Cookbook”. He’s a PSF member, and won the 2002 Activators’ Choice Award and the 2006 Frank Willison Award for contributions to the Python community. He works as Senior Staff Engineer for Google. You can read some PDFs and
\nwatch some videos of his past presentations.

" + }, + + { + + "serial": 173465, + "name": "Ritchie Martori", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_173465.jpg", + "url": null, + "position": "", + "affiliation": "StrongLoop", + "twitter": "", + "bio": "

Ritchie Martori is Node.js and Mobile developer at StrongLoop where he focuses on LoopBack, an open source mobile backend-as-a-service. He previously authored deployd, another mBaaS in use by over 20,000 developers to create their backend APIs, and is a contributor to Angular.js.

" + }, + + { + + "serial": 17378, + "name": "Matt May", + "photo": null, + "url": "http://adobe.com/accessibility/", + "position": "Accessibility Engineer", + "affiliation": "Adobe Systems", + "twitter": null, + "bio": "

Matt May
\nAccessibility Evangelist, Open Source and Accessibility
\nAdobe

\n

Matt May is a developer, technologist, and accessibility advocate who is responsible for working internally and externally with Adobe product teams and customers to address accessibility in Adobe products, ensure interoperability with assistive technologies, and make customers aware of the many accessibility features that already exist in Adobe products.

\n

Prior to joining Adobe, May worked for W3C/WAI on many of the core standards in web accessibility, led the Web Standards Project\u2019s Accessibility Task Force, helped to architect one of the first online grocery sites, HomeGrocer.com, and co-founded Blue Flavor, a respected web and mobile design consultancy. He is a member of the W3C/WAI Web Content Accessibility Guidelines Working Group and co-edited the first JavaScript techniques document for WCAG 2.0 in 2001.

\n

May is an accomplished speaker, having presented at dozens of conferences including Web 2.0 Expo, WebVisions, SXSW Interactive, CSUN Conference on Technology and Persons with Disabilities, Podcast and Portable Media Expo, Web Design World Seattle, Gilbane CMS Conference, and the International World Wide Web Conference, to name just a few.

" + }, + + { + + "serial": 142336, + "name": "Steve Mayzak", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_142336.jpg", + "url": "http://elasticsearch.com/", + "position": "Director of Systems Engineering", + "affiliation": "Elasticsearch", + "twitter": "", + "bio": "

Steve Mayzak heads up the Systems engineering team at Elasticsearch. Having been in the software industry for over 15 years he has worked as a Developer, Architect and most recently as a Systems engineering leader. Steve loves bringing cutting edge Open Source technology to customers large and small and helping them solve some of their biggest and most interesting challenges.

" + }, + + { + + "serial": 182080, + "name": "Caroline McCrory", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_182080.jpg", + "url": "http://research.gigaom.com/", + "position": "Senior Director", + "affiliation": "Gigaom Research", + "twitter": "CloudOfCaroline", + "bio": "

Caroline is Senior Director at Gigaom Research. Caroline has a background in network and security engineering, enterprise operations and has served at companies such as ServiceMesh, VMware, Cisco, EMC, Siemens, CSC, the BBC and Motorola. Most recently, Caroline was Head of Product at Piston Cloud Computing, an enterprise OpenStack software company.

" + }, + + { + + "serial": 74852, + "name": "Matthew McCullough", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_74852.jpg", + "url": "http://www.github.com/training", + "position": "Training Pioneer", + "affiliation": "GitHub, Inc.", + "twitter": "matthewmccull", + "bio": "

Matthew McCullough, Training Pioneer for GitHub, is an energetic 15 year veteran of enterprise software development, world-traveling open source educator, and co-founder of a US consultancy. All of these activities provide him avenues of sharing success stories of leveraging Git and GitHub. Matthew is a contributing author to the Gradle and Jenkins O’Reilly books and creator of the Git Master Class series for O’Reilly. Matthew regularly speaks on the No Fluff Just Stuff conference tour, is the author of the DZone Git RefCard, and is President of the Denver Open Source Users Group.

" + }, + + { + + "serial": 172994, + "name": "Chris McEniry", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_172994.jpg", + "url": "http://corgalabs.com/", + "position": "Systems Architect", + "affiliation": "Sony Network Entertainment", + "twitter": "macmceniry", + "bio": "

Chris “Mac” McEniry is a practicing sysadmin responsible for running a large ecommerce and gaming service. He’s been working and developing in an operational capacity for 15 years. In his free time, he builds tools and thinks about efficiency.

" + }, + + { + + "serial": 152376, + "name": "Patrick McFadin", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_152376.jpg", + "url": null, + "position": "Chief Evangilist", + "affiliation": "Datastax", + "twitter": null, + "bio": "

Patrick McFadin is regarded as a foremost expert for Apache Cassandra and data modeling. As Chief Evangelist for Apache Cassandra and consultant working for DataStax, he has been involved in some of the biggest deployments in the world.

" + }, + + { + + "serial": 183246, + "name": "Patrick McGarry", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_183246.jpg", + "url": "http://www.redhat.com/", + "position": "Director Ceph Community", + "affiliation": "Red Hat", + "twitter": "", + "bio": "

Patrick is currently incarnated as a Director of Community at Red Hat working to spread the Ceph gospel. An experienced community manager, gamer, mischief maker, and all around geek, Patrick spent five years writing and managing Slashdot under the nomme du keyboard
\n‘scuttlemonkey.’ Patrick enthusiastically helps companies to understand and adopt Open Source ideals and continues to be a strong advocate of FOSS on the desktop and in the enterprise. He has strong feelings about tomatoes, longs for his deep, dark cave, and still
\nhates writing these bios.

" + }, + + { + + "serial": 172824, + "name": "Mark McLoughlin", + "photo": null, + "url": "http://blogs.gnome.org/markmc", + "position": "Consulting Engineer", + "affiliation": "Red Hat", + "twitter": "markmc_", + "bio": "

Mark McLoughlin is a consulting engineer at Red Hat and has spent over a decade contributing to and leading open source projects like GNOME, Fedora, KVM, qemu, libvirt, oVirt and, of course, OpenStack.

\n

Mark is a member of OpenStack’s technical committee and the OpenStack Foundation board of directors. He contributes mostly to Oslo, Nova and TripleO but will happily dive in to any project.

\n

Mark is responsible for Red Hat’s OpenStack technical direction from the CTO office.

" + }, + + { + + "serial": 171621, + "name": "Harrison Mebane", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_171621.jpg", + "url": "http://www.svds.com/", + "position": "Data Analyst", + "affiliation": "Silicon Valley Data Science", + "twitter": "harrisonmebane", + "bio": "

After several years spent on a PhD in theoretical physics, I recently entered the “big data” community to apply what I’ve learned. I work primarily in Python but am always looking for new tools. I work closely with data scientists and engineers skilled in machine learning and data pipelining.

" + }, + + { + + "serial": 153566, + "name": "Gian Merlino", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_153566.jpg", + "url": null, + "position": "Engineer", + "affiliation": "Metamarkets", + "twitter": null, + "bio": "

Gian is a contributor to the Kafka, Storm, and Druid open source projects and a developer at Metamarkets. He previously worked at Yahoo!, where he was responsible for its worldwide server deployment and configuration management platform. He holds a BS in Computer Science from California Institute of Technology

" + }, + + { + + "serial": 104652, + "name": "Justin Miller", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_104652.jpg", + "url": "http://mapbox.com/", + "position": "Mobile lead", + "affiliation": "Mapbox", + "twitter": "incanus77", + "bio": "

Justin Miller is mobile lead at Washington, DC-based Mapbox working on the future of open mapping. He created the SQLite-based MBTiles open map tile format, contributed the leading offline map implementation for iOS, wrote the Simple KML parser, contributes to the Mac version of the TileMill map-making studio, and does it all in the open here. He lives and works here in Portland.

" + }, + + { + + "serial": 175488, + "name": "Katie Miller", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_175488.jpg", + "url": "http://www.codemiller.com/", + "position": "OpenShift Developer Advocate", + "affiliation": "Red Hat", + "twitter": "codemiller", + "bio": "

Katie Miller is an OpenShift Developer Advocate at Red Hat, where she tries to squeeze as many different programming languages into her workdays as possible. The former newspaper journalist loves language, whether code or copy, and will pedantically edit any text you let her touch. Katie is a Co-Counder of the Lambda Ladies group for women in functional programming and one of the organisers of the Brisbane Functional Programming Group.

" + }, + + { + + "serial": 171147, + "name": "Michael Minella", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_171147.jpg", + "url": "http://spring.io/team/mminella", + "position": "Project Lead - Spring Batch", + "affiliation": "Pivotal", + "twitter": "michaelminella", + "bio": "

Michael Minella is a software engineer, teacher and author with over a decade of enterprise development experience. Michael was a member of the expert group for JSR-352 (java batch processing). He currently works for Pivotal as the project lead for the Spring Batch project as well as an instructor at DePaul University. Michael is the author of Pro Spring Batch from Apress and the popular Refcard JUnit and EasyMock.

\n

Outside of the daily grind, Michael enjoys spending time with his family and enjoys woodworking, photography, and InfoSec as hobbies.

" + }, + + { + + "serial": 4378, + "name": "Wade Minter", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_4378.jpg", + "url": "http://teamsnap.com/", + "position": "Chief Technology Officer", + "affiliation": "TeamSnap", + "twitter": "minter", + "bio": "

H. Wade Minter is the CTO and founding team member at TeamSnap, an application that makes managing sports teams and groups easy. He is also the ring announcer for a professional wrestling federation. The two may be related.

" + }, + + { + + "serial": 181598, + "name": "Manav Mishra", + "photo": null, + "url": "http://www.hp.com/", + "position": "Director of Product, HP Helion", + "affiliation": "HP Helion", + "twitter": "", + "bio": "

Manav is currently the Director of Product for HP Helion where he leads the product team responsible for the developer platform and experience.

\n

Manav has been in several product and engineering leadership roles across the tech industry. Most recently, at Google, Manav led the product management team responsible for the core and enterprise products for Google Analytics. Prior to joining Google, Manav spent 10-years at Microsoft leading product teams responsible for Windows (shell user experience, search and file management, cloud integration and the developer platform), Live Mesh (SkyDrive), OneCare and machine-learning based safety and security protection in Outlook, Exchange, Hotmail and Internet Explorer. Manav started his career at Intel as a software engineer working on x86 instruction set optimizations and networking protocols for Linux. He also has several international publications and 50+ patents to his credit.

\n

Manav graduated with a M.S. in Electrical Engineering from Washington State University and is based in San Francisco.

" + }, + + { + + "serial": 45968, + "name": "Lorna Jane Mitchell", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_45968.jpg", + "url": "http://www.lornajane.net/", + "position": "Consultant", + "affiliation": "LornaJane", + "twitter": null, + "bio": "

Lorna is an independent PHP consultant based in Leeds, UK. She leads the joind.in open source project, which provides a platform for real-time, public feedback at community events. She is an experienced event organiser and speaker herself, having hosted the Dutch PHP Conference and co-founded the PHP North West conference and user group. She has spoken at technical events across Europe and beyond, predominantly on technical topics around PHP and APIs, but also on topics around business, projects and open source. She regularly delivers technical training sessions and is also active as a mentor with PHPWomen.org. Author of the book PHP Master from Sitepoint, Lorna loves to write and is regularly published at a number of outlets including netmagazine and of course her own blog lornajane.net.

" + }, + + { + + "serial": 173399, + "name": "Eric Mittelette", + "photo": null, + "url": "http://msopentech.com/", + "position": "Technical evangelist", + "affiliation": "Microsoft Open Tech", + "twitter": "ericmitt", + "bio": "

Developer Evangelist since the year 2000 in Microsoft France, Eric animate sessions and keynotes about Microsoft development platform since the beginning of .NET. At this time Eric integrate open source software and framework in his work, and join recently Microsoft Open Technologies, Inc as a Senior Technical Evangelist. Passionate about code algorithm since the 90\u2019s, eric work with C, C++, C# and try to illustrate technologies breakout with simplicity and fun. His background as Agronomic researcher provide him, an different point of view and approach and a real vulgarization expertise.

" + }, + + { + + "serial": 159772, + "name": "Richard Mortier", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_159772.jpg", + "url": null, + "position": "Horizon Transitional Fellow in Computer Science", + "affiliation": "University of Nottingham", + "twitter": "mort___", + "bio": "

Richard Mortier is Horizon Transitional Fellow in Computer Science at the University of Nottingham. His research focuses on user-centred systems, investigating the challenges that arise when we design and deploy infrastructure technology with which real people must interact. Specific current projects include exokernels for secure high-performance multiscale computing (Mirage); infrastructure for building a market around privacy-preserving third-party access to personal data (Dataware); and novel approaches to deploying and managing personal network services. Prior to joining Nottingham he spent two years as a founder of Vipadia Limited designing and building the Clackpoint and Karaka real-time communications services (acquired by Voxeo Corp.), six years as a researcher with Microsoft Research Cambridge, and seven months as a visitor at Sprint ATL, CA. He received a Ph.D. from the Systems Research Group at the University of Cambridge Computer Laboratory, and a B.A. in Mathematics, also from the University of Cambridge.

" + }, + + { + + "serial": 33987, + "name": "Diane Mueller", + "photo": null, + "url": "http://www.openshift.com/", + "position": "VP, Development & Member, XBRL International Steering Committee", + "affiliation": "Red Hat", + "twitter": null, + "bio": "

Diane Mueller is RedHat’s OpenShift Origin Open Source Community Manager. Diane has been designing and implementing products and applications embedded into mission critical systems at F500 corporations for over 20 years She is a thought leader in cloud computing, open source community building, and cat herding.

\n

.Follow her on twitter @pythondj
\n

" + }, + + { + + "serial": 156427, + "name": "Dan Muey", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_156427.jpg", + "url": "http://cpanel.net/", + "position": "Senior Software Developer", + "affiliation": "cPanel Inc", + "twitter": null, + "bio": "

I have dedicated over ten years to cPanel and have loved every minute of it: I \u2665 cPanel! Before cPanel I spent ten years at various technology companies working on everything from server and database administration to front/back\u2013end web development and other programming. In my free time I enjoy playing music, time with my wife, and being an uncle.

" + }, + + { + + "serial": 147840, + "name": "Scott Murray", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_147840.jpg", + "url": "http://alignedleft.com/", + "position": "Assistant Professor of Design", + "affiliation": "University of San Francisco", + "twitter": "alignedleft", + "bio": "

Scott Murray is a code artist who writes software to create data visualizations and other interactive phenomena. His work incorporates elements of interaction design, systems design, and generative art. Scott is an Assistant Professor of Design at the University of San Francisco, where he teaches data visualization and interaction design. He is a contributor to Processing, and is author of the O’Reilly title “Interactive Data Visualization for the Web”.

" + }, + + { + + "serial": 120866, + "name": "Henri Muurimaa", + "photo": null, + "url": "http://www.vaadin.com/henri", + "position": "SVP, Services", + "affiliation": "Vaadin Ltd", + "twitter": "henrimuurimaa", + "bio": "

Henri Muurimaa, MSc. has been a professional developer and team leader since 1997. Over the years he has used many Java technologies and tools in a plethora of projects ranging from days to dozens of man-years. Lately he has been exploring building desktop-like web applications with Scala and Vaadin. He is a contributor to the Scaladin project and has been a member of the Vaadin team since 2002.

" + }, + + { + + "serial": 173146, + "name": "Chad Naber", + "photo": null, + "url": null, + "position": "Data Architect", + "affiliation": "Intel", + "twitter": "", + "bio": "

Chad is a data engineer with more than 12 years of progressive experience in leading, industry-recognized multi-national service organizations such as Intel, Nike and Edmunds.com. He has experience with leading edge technologies such as Redshift, SQL Server , Hadoop (both utilizing the streaming API and direct Java), and Hive with a baseline of experience in Agile data warehousing. Chad currently is working as a big data architect at Intel.

" + }, + + { + + "serial": 143674, + "name": "Rachel Nabors", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_143674.jpg", + "url": "http://rachelnabors.com/", + "position": "Interaction Developer and Cartoonist", + "affiliation": "rachelnabors.com", + "twitter": "RachelNabors", + "bio": "

Rachel Nabors is an interaction developer, award-winning cartoonist, and host of the Infinite Canvas Screencast. She deftly blends the art of traditional storytelling with digital media to \u201ctell better stories through code\u201d at her company Tin Magpie. You can catch her as @RachelNabors on Twitter and at rachelnabors.com.

" + }, + + { + + "serial": 141881, + "name": "David Nalley", + "photo": null, + "url": "http://cloudstack.org/", + "position": "Committer", + "affiliation": "Apache CloudStack (incubating)", + "twitter": "ke4qqq", + "bio": "

David is a recovering sysadmin with a decade of experience. He’s a committer on the Apache CloudStack (incubating) project, and a contributor to the Fedora Project.

" + }, + + { + + "serial": 146540, + "name": "Paco Nathan", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_146540.jpg", + "url": "http://liber118.com/pxn/", + "position": "consultant, author, instructor, OSS evangelist", + "affiliation": "Databricks", + "twitter": "pacoid", + "bio": "

O’Reilly author (Enterprise Data Workflows with Cascading) and a \u201cplayer/coach\u201d who’s led innovative Data teams building large-scale apps for 10+ yrs. Expert in machine learning, cluster computing, and Enterprise use cases for Big Data. Interests: Mesos, PMML, Open Data, Cascalog, Scalding, Python for analytics, NLP.

" + }, + + { + + "serial": 109297, + "name": "Wynn Netherland", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_109297.jpg", + "url": "http://wynnnetherland.com/", + "position": "Developer", + "affiliation": "GitHub", + "twitter": "pengwynn", + "bio": "

Wynn Netherland has been building the web for nearly twenty years. With a passion for API user experience, he’s a prolific creator and maintainer of Ruby API wrappers. He now spends his days hacking on the GitHub API and is author of several books, most recently Sass and Compass in Action (Manning 2013).

" + }, + + { + + "serial": 109468, + "name": "Christopher Neugebauer", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_109468.jpg", + "url": "http://chris.neugebauer.id.au/", + "position": "Semi-professional nerd", + "affiliation": "chris.neugebauer.id.au", + "twitter": "chrisjrn", + "bio": "

Christopher is a Python programmer from Hobart, Tasmania. He\u2019s a Computer Science Honours graduate of the University of Tasmania, and he now works primarily as an Android developer. Working with Android means that his day job involves more Java than he\u2019d like. He has a strong interest in the development of the Australian Python Community \u2014 he is an immediate past convenor of PyCon Australia 2012 and 2013 in Hobart, and is a member of the Python Software Foundation.

\n

In his spare time, Christopher enjoys presenting on Mobile development at Open Source conferences, and presenting on Open Source development at Mobile conferences.

" + }, + + { + + "serial": 130731, + "name": "Deb Nicholson", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_130731.jpg", + "url": "http://eximiousproductions.com/", + "position": "Community Outreach Director ", + "affiliation": "Open Invention Network", + "twitter": "", + "bio": "

Deb Nicholson works at the intersection of technology and social justice. She has over fifteen years of non-profit management experience and got involved in the free software movement about five years ago when she started working for the Free Software Foundation. She is currently the Community Outreach Director for the Open Invention Network – the defensive patent pool built to protect Linux projects. She is also the Community Manager for GNU MediaGoblin, a brand new federated media hosting program. In her spare time, she serves on the board of OpenHatch, a small non-profit dedicated to identifying and mentoring new free software contributors with a particular interest in building a more diverse free software movement.

" + }, + + { + + "serial": 171598, + "name": "Niklas Nielsen", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_171598.jpg", + "url": "http://mesosphere.io/", + "position": "Software Engineer", + "affiliation": "Mesosphere, Inc.", + "twitter": "quarfot", + "bio": "

Niklas is also a distributed systems engineer at Mesosphere and a committer in the Apache Mesos Open Source project. Before joining Mesosphere, Niklas worked at Adobe as a virtual machine and compiler engineer and completed his Master\u2019s degree doing supercomputer debugger design at Lawrence Livermore National Laboratory.

" + }, + + { + + "serial": 140811, + "name": "Kelley Nielsen", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_140811.jpg", + "url": "http://salticidoftheearth.com/", + "position": "Intern", + "affiliation": "Linux Foundation, Gnome Foundation", + "twitter": null, + "bio": "

Kelley is a relatively new kernel developer, who is learning under the excellent guidance of mentors through the Gnome Outreach Program for Women. She is also a co-coordinator for Codechix, which organizes in-person events of all kinds for women and everybody. Before settling on the kernel, she wrote a collection of Linux screen saver demos, aptly titled Xtra Screen Hacks, and a few Android apps. She blogs here.

" + }, + + { + + "serial": 173503, + "name": "Jesse Noller", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_173503.jpg", + "url": "http://developer.rackspace.com/", + "position": "Principal Engineer", + "affiliation": "Rackspace", + "twitter": "jessenoller", + "bio": "

Jesse Noller is a long time Python community member, developer and has contributed to everything from distributed systems to front-end interfaces. He\u2019s passionate about community, developer experience and empowering developers everywhere, in any language to build amazing applications. He currently works for Rackspace as a Developer Advocate and open source contributor.

" + }, + + { + + "serial": 76338, + "name": "Sarah Novotny", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_76338.jpg", + "url": "http://nginx.com/", + "position": "Evangelist and Community Leader", + "affiliation": "NGINX", + "twitter": "sarahnovotny", + "bio": "

Sarah Novotny is a technical evangelist and community manager for NGINX. Novotny has run large scale technology infrastructures as a Systems Engineer and a Database administrator for Amazon.com and the ill fated Ads.com. In 2001, she founded Blue Gecko, a remote database administration company with two peers from Amazon. Blue Gecko, was sold to DatAvail in 2012. She\u2019s also curated teams and been a leader in customer communities focused on high availability web application and platform delivery for Meteor Entertainment and Chef.

\n

Novotny regularly talks about technology infrastructure and geek lifestyle. She is additionally a program chair for O’Reilly Media’s OSCON. Her technology writing and adventures as well as her more esoteric musings are found at sarahnovotny.com.

" + }, + + { + + "serial": 173452, + "name": "Tim Nugent", + "photo": null, + "url": null, + "position": "Freelance", + "affiliation": "Freelance", + "twitter": "The_McJones", + "bio": "

Tim Nugent pretends to be a mobile app developer, game designer, PhD student and now he even pretends to be an author (he co-wrote the latest update to \u201cLearning Cocoa with Objective-C\u201d for O\u2019Reilly). When he isn\u2019t busy avoiding being found out as a fraud, he spends most of his time designing and creating little apps and games he won\u2019t let anyone see. Tim spent a disproportionately long time writing this tiny little bio, most of which was trying to stick a witty sci-fi reference in, before he simply gave up. He is, obviously, an avid board game player.

" + }, + + { + + "serial": 251, + "name": "Tim O'Reilly", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_251.jpg", + "url": "http://www.oreilly.com/", + "position": "Founder and CEO", + "affiliation": "O'Reilly Media, Inc.", + "twitter": "timoreilly", + "bio": "

Tim O’Reilly is the founder and CEO of O’Reilly Media. His original business plan was “interesting work for interesting people,” and that’s worked out pretty well. He publishes books, runs conferences, invests in early-stage startups, urges companies to create more value than they capture, and tries to change the world by spreading and amplifying the knowledge of innovators.

\n

Tim is also a partner at O’Reilly AlphaTech Ventures, a founder and board member of Safari Books Online and Maker Media, and on the boards of Code for America and PeerJ.

" + }, + + { + + "serial": 133624, + "name": "Stephen O'Sullivan", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_133624.jpg", + "url": "http://svds.com/", + "position": "VP Engineering", + "affiliation": "Silicon Valley Data Science", + "twitter": "steveos", + "bio": "

A leading expert on big data architecture and Hadoop, Stephen brings over 20 years of experience creating scalable, high-availability, data and applications solutions. A veteran of WalmartLabs, Sun and Yahoo!, Stephen leads data architecture and infrastructure.

" + }, + + { + + "serial": 172986, + "name": "Anne Ogborn", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_172986.jpg", + "url": "http://www.robokindrobots.com/", + "position": "Artgineer", + "affiliation": "Robokind", + "twitter": "", + "bio": "

Anne Ogborn is an avid SWI-Prolog fan, professional SWI-Prolog programmer, and sometimes contributor to the SWI-Prolog project.
\nShe uses SWI-Prolog to create games that create change for the OSU Wavicles, and to program advanced social robotics for Robokind.

" + }, + + { + + "serial": 147649, + "name": "Tim Palko", + "photo": null, + "url": null, + "position": "Senior Software Engineer", + "affiliation": "Carnegie Mellon University, Software Engineering Institute", + "twitter": "", + "bio": "

Tim celebrates software development using many languages and frameworks, heeding less to past experience in choosing technologies. Spring MVC, Hibernate, Rails, .NET MVC, Django and the variety of languages that come with are in his L1 cache. Among other endeavors to keep him sharp, he currently provides coded solutions for the Software Engineering Institute at CMU.

\n

Tim received a B.S. in Computer Engineering in 2003 and resides in Pittsburgh, PA.

" + }, + + { + + "serial": 26426, + "name": "Rajeev Pandey", + "photo": null, + "url": "http://www.hp.com/", + "position": "Software Architect", + "affiliation": "Hewlett-Packard Company", + "twitter": null, + "bio": "

Rajeev Pandey joined Hewlett-Packard in 1995. He has worked in a wide variety of R&D and IT positions within HP Labs, HP-IT and HP R&D prior to joining HP Cloud. He has spent the last few years using Agile methods to architect, develop, and deploy digital imaging and printing-related web services. He holds BS degrees in Computer Science and Mathematics from Montana Tech, MS and PhD degrees in Computer Science from Oregon State University, and is a senior member of the IEEE and ACM.

" + }, + + { + + "serial": 113667, + "name": "Manish Pandit", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_113667.jpg", + "url": "http://mpandit.me/", + "position": "Engineering Manager", + "affiliation": "Netflix", + "twitter": "lobster1234", + "bio": "

A programmer at heart, I work with great teams to build great products. Over the last decade, I’ve worked with companies ranging from software, financials, to media and entertainment. Solving scale problems with leading innovations in the tech space has been an area of interest to me. From building APIs at E*Trade, IGN, and Netflix I’ve evolved both as a leader as well as an engineer focused on scalable yet flexible, and highly performant architectures.

\n

I am also active with the developer community via github, stackoverflow, meetups, and conferences.

\n

My slideshare has decks from my talks at various events. Currently I am working at Netflix as an Engineering Manager in the Streaming Platforms group, where my team builds APIs and tools around device metadata and partner products. Prior to Netflix I was Director of Engineering at IGN.com, where I helped build the next-gen API for the social and content platforms.

\n

Follow me on LinkedIn.

" + }, + + { + + "serial": 173223, + "name": "James Pannacciulli", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_173223.jpg", + "url": "http://jpnc.info/", + "position": "Systems Engineer", + "affiliation": "Media Temple", + "twitter": "_jpnc", + "bio": "

James Pannacciulli has been an enthusiast and user of GNU/Linux and related software since 1997 and is a supporter of the free (libre) software movement. He holds an MA and BA in theoretical linguistics from UCLA and Rutgers universities, respectively, and is currently a Systems Engineer at Media Temple.

\n

James has presented on Bash at SCALE and UUASC in Los Angeles.

" + }, + + { + + "serial": 172630, + "name": "Soohong Park", + "photo": null, + "url": "http://www.samsung.com/", + "position": "Senior Researcher", + "affiliation": "Samsung", + "twitter": "", + "bio": "

Dr. Soohong Daniel Park is a senior member of research staff in open source office, Samsung and currently leading Internet of Things open source project collaborated with various partners. He has been working on IPv6 over Low Power Sensor Networks in IETF and advanced research topics around IoT. Also, he is currently working in W3C as Advisor Board.

" + }, + + { + + "serial": 180019, + "name": "Sanjay Patil", + "photo": null, + "url": null, + "position": "Sr Director", + "affiliation": "SAP", + "twitter": "", + "bio": "

Sanjay Patil is a member of the Industry Standards & Open Source team at SAP Labs \u2013 Palo Alto, and is responsible for driving strategic Open Source programs as well as governance of SAP\u2019s outbound Open Source projects. He has over 14 years of experience with Industry Standards and Open Source projects. He has led critical industry-wide standardization initiatives such as OASIS Web Services Reliable Messaging. He currently serves as a Director on the Board of OASIS, a standards development organization, and represents SAP on Open Source foundations such as CloudFoundry. His areas of interest include application platform technologies, Big Data and Cloud.

" + }, + + { + + "serial": 171197, + "name": "Josh Patterson", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_171197.jpg", + "url": null, + "position": "Principal", + "affiliation": "Patterson Consulting", + "twitter": "jpatanooga", + "bio": "

Josh Patterson currently runs a consultancy in the Big Data Machine Learning space. Previously Josh worked as a Principal Solutions Architect at Cloudera and an engineer at the Tennessee Valley Authority where he was responsible for bringing Hadoop into the smartgrid during his involvement in the openPDC project. Josh is a graduate of the University of Tennessee at Chattanooga with a Masters of Computer Science where he did research in mesh networks and social insect swarm algorithms. Josh has over 15 years in software development and continues to contribute to projects such as Apache Mahout, Metronome, IterativeReduce, openPDC, and JMotif.

" + }, + + { + + "serial": 118233, + "name": "James Pearce", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_118233.jpg", + "url": "http://facebook.com/", + "position": "Head of Open Source", + "affiliation": "Facebook", + "twitter": "jamespearce", + "bio": "

James manages the open source program at Facebook. He’s a developer and writer with a special passion for the web, mobile platforms of all sorts, and helping developers explore their potential.

\n

James’ mobile projects include confess.js, WhitherApps, tinySrc, ready.mobi, Device Atlas, and mobiForge. Previously at Sencha, dotMobi, Argogroup and Ernst & Young, he has also written books on the mobile web for Wrox & Wiley. He’s easy to find at /jamesgpearce or http://tripleodeon.com

" + }, + + { + + "serial": 64512, + "name": "Shawn Pearce", + "photo": null, + "url": "http://www.google.com/", + "position": "Software Engineer", + "affiliation": "Google", + "twitter": "", + "bio": "

Shawn Pearce has been actively involved in Git since early 2006. Shawn is the original author of git-gui, a Tk based graphical interface shipped with git, and git fast-import, a stream based import system often used for converting projects to git. Besides being the primary author of both git-gui and git fast-import, Shawn’s opinion, backed by his code, has influenced many decisions that form the modern git implementation.

\n

In early 2006 Shawn also founded the JGit project, creating a 100% pure Java reimplementation of the Git version control system. The JGit library can often be found in Java based products that interact with Git, including plugins for Eclipse and NetBeans IDEs, the Hudson CI server, Apache Maven, and Gerrit Code Review, a peer code review system specially designed for Git. Today he continues to develop and maintain JGit, EGit, and Gerrit Code Review.

" + }, + + { + + "serial": 180056, + "name": "Harry Percival", + "photo": null, + "url": "http://www.obeythetestinggoat.com/", + "position": "Developer", + "affiliation": "PythonAnywhere", + "twitter": "hjwp", + "bio": "

During his childhood Harry seemed to be doing everything right — learning to program BASIC on Thomson TO-7s (whose rubber keys went “boop” when you pressed them) and Logo on a Green-screen Amstrad PCW. Something went wrong as he grew up, and Harry wasted several years studying Economics, becoming a management consultant (shudder), and programming little aside from overcomplicated Excel spreadsheets.

\n

But in 2009 Harry saw the light, let his true geek shine once again, did a new degree in Computer Science, and was lucky enough to secure an internship with Resolver Systems, the London-based company that has since morphed into PythonAnywhere. Here he was inculcated into the cult of Extreme Programming (XP), and rigorous TDD. After much moaning and dragging of feet, he finally came to see the wisdom of the approach, and now spreads the gospel of TDD through beginner’s workshops, tutorials and talks, with all the passion of a recent convert.

\n

Harry is currently writing a book for O’Reilly, provisionally titled “Test-Driven Development of Web Applications with Python”. He is trying to persuade his editor to have the title changed to “Obey the Testing Goat!”.

" + }, + + { + + "serial": 173381, + "name": "Jim Perrin", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_173381.jpg", + "url": null, + "position": "", + "affiliation": "CentOS Project", + "twitter": "BitIntegrity", + "bio": "

I began my linux journey in 1999 with Mandrake 6.1 and migrated to RHL around the 7.2 release I found out about CentOS in 2004, and joined the project formally later that year. Over the years I’ve been a consultant for defense contractors as well as for the oil and gas industry, handling large scale deployment, automation, and systems integration.

\n

My current responsibilities with CentOS:

\n

Governing Board member
\nInfrastructure
\nSIG/Variant sponsor/coordinator

" + }, + + { + + "serial": 151611, + "name": "Jerome Petazzoni", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_151611.jpg", + "url": "http://www.docker.com/", + "position": "Senior Software Engineer", + "affiliation": "Docker Inc.", + "twitter": "jpetazzo", + "bio": "

Jerome is a senior engineer at Docker, where he rotates between Ops, Support and Evangelist duties. In another life he built and operated Xen clouds when EC2 was just the name of a plane, developed a GIS to deploy fiber interconnects through the French subway, managed commando deployments of large-scale video streaming systems in bandwidth-constrained environments such as conference centers, and various other feats of technical wizardry. When annoyed, he threatens to replace things with a very small shell script. His left hand cares for the dotCloud PAAS servers, while his right hand builds cool hacks around Docker.

" + }, + + { + + "serial": 30412, + "name": "Brandon Philips", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_30412.jpg", + "url": "http://ifup.org/", + "position": "CTO", + "affiliation": "CoreOS", + "twitter": "philips", + "bio": "

Brandon Philips is helping to build modern Linux server infrastructure at CoreOS. Prior to CoreOS, he worked at Rackspace hacking on cloud monitoring and was a Linux kernel developer at SUSE. In addition to his work at CoreOS, Brandon sits on Docker’s governance board and is one of the top contributors to Docker. As a graduate of Oregon State’s Open Source Lab he is passionate about open source technologies.

" + }, + + { + + "serial": 29591, + "name": "Simon Phipps", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_29591.jpg", + "url": "http://webmink.com/", + "position": "President", + "affiliation": "Open Source Initiative", + "twitter": "webmink", + "bio": "

Simon Phipps has engaged at a strategic level in the world\u2019s leading technology companies, starting in roles such as field engineer, programmer, systems analyst and more recently taking executive leadership roles around open source. He worked with OSI standards in the 80s, on collaborative conferencing software in the 90s, helped introduce both Java and XML at IBM and was instrumental in open sourcing the whole software portfolio at Sun Microsystems.

\n

As President of the Open Source Initiative and a director of the UK’s Open Rights Group, he takes an active interest in digital rights issues and is a widely read commentator at InfoWorld, Computerworld and his own Webmink blog.

\n

He holds a BSc in electronic engineering and is a Fellow of the British Computer Society and of the Open Forum Academy.

" + }, + + { + + "serial": 141661, + "name": "Andy Piper", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_141661.jpg", + "url": "http://andypiper.co.uk/", + "position": "Developer Advocate", + "affiliation": "Twitter", + "twitter": "andypiper", + "bio": "

Andy Piper is a Developer Advocate for Cloud Foundry, the Open Source Platform-as-a-Service. He is an Eclipse M2M IWG Community Member and has been involved with the Eclipse Paho project since the start, particularly through his former role advocating the use of MQTT at IBM, and helping to run the mqtt.org community website. Andy has a passionate interest in Open Source, small and mobile devices, cloud, the Internet of Things, and Arduino and related technologies. He is probably best known online as a \u201csocial bridgebuilder\u201d. He was previously with IBM Software Group for more than 10 years, as a consultant, strategist, and WebSphere Messaging Community Lead.

" + }, + + { + + "serial": 170158, + "name": "Curtis Poe", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_170158.jpg", + "url": "http://www.allaroundtheworld.fr/", + "position": "Freelance Perl expert and Agile consultant", + "affiliation": "All Around The World", + "twitter": "OvidPerl", + "bio": "

I’m a well-known Perl expert, better known online as Ovid. I specialize in large-scale, database driven code bases and wrote the test harness that currently ships with the Perl programming language. I’m constantly trying to create better testing tools for the Perl community.

\n

I sit on the Board of Directors of the Perl Foundation and run a consulting company with my lovely wife, Le\u00efla, from our offices in La Rochelle, a medieval port town on the west coast of France.

\n

I speak at conferences all over the world and also do private speaking engagements and training for companies. Currently I’m a specialist for hire, often focusing on complex ETL problems or making developers more productive by fixing test suites and making them run much faster.

" + }, + + { + + "serial": 171078, + "name": "Jess Portnoy", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_171078.jpg", + "url": "http://www.kaltura.com/", + "position": "DevOps and software packager", + "affiliation": "Kaltura Inc", + "twitter": "jess_port01", + "bio": "

Jess Portnoy has been an open source developer and believer for the last 15 years. Prior to Kaltura, Jess worked, among other places, at Zend where she was responsible for porting and packaging PHP and Zend projects on all supported .*nix platforms. Jess also grows 3 pet projects hosted in sourceforge.

\n

You can view my LinkedIn profile here.

" + }, + + { + + "serial": 142320, + "name": "Steven Pousty", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_142320.jpg", + "url": null, + "position": "PaaS Dust Spreader", + "affiliation": "Red Hat", + "twitter": "TheSteve0", + "bio": "

Steve is a PaaS Dust Spreader (aka developer evangelist) with OpenShift. He goes around and shows off all the great work the OpenShift engineers do. He can teach you about PaaS with Java, Python, PostgreSQL MongoDB, and some JavaScript. He has deep subject area expertise in GIS/Spatial, Statistics, and Ecology. He has spoken at over 50 conferences and done over 30 workshops including Monktoberfest, MongoNY, JavaOne, FOSS4G, CTIA, AjaxWorld, GeoWeb, Where2.0, and OSCON. Before OpenShift, Steve was a developer evangelist for LinkedIn, deCarta, and ESRI. Steve has a Ph.D. in Ecology from University of Connecticut. He likes building interesting applications and helping developers create great solutions.

" + }, + + { + + "serial": 173111, + "name": "Mark Powell", + "photo": null, + "url": null, + "position": "Senior Computer Scientist", + "affiliation": "Jet Propulsion Laboratory/NASA", + "twitter": "drmarkpowell", + "bio": "

Mark Powell is a Senior Computer Scientist at the Jet Propulsion Laboratory, Pasadena, CA since 2001. Mark is the product lead for the Mars Science Laboratory mission science planning interface (MSLICE). At JPL his areas of focus are science data visualization and science planning for telerobotics. He received the 2004 NASA Software of the Year Award for his work on the Science Activity Planner science visualization and activity planning software used for MER operations. Mark is currently supporting a variety of projects at JPL including Opportunity and Curiosity rover operations and radar data processing for volcanology and seismology research.

" + }, + + { + + "serial": 155107, + "name": "Austin Putman", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_155107.jpg", + "url": "http://omadahealth.com/", + "position": "VP of Engineering", + "affiliation": "Omada Health", + "twitter": "austinfrmboston", + "bio": "

I empower people to make a difference with appropriate technology. Currently I’m working to turn around the diabetes epidemic.

\n

I was a founding partner and lead dev at Radical Designs, a tech cooperative for nonprofits, and a team anchor for Pivotal Labs, training and collaborating with earth’s best agile engineers.

" + }, + + { + + "serial": 173467, + "name": "Steven Quella", + "photo": null, + "url": null, + "position": "Student", + "affiliation": "Saint Joseph's College (Indiana)", + "twitter": "", + "bio": "

Steven is a senior at Saint Joseph’s College, majoring in Computer Science. He is seeking a position after he graduates as a Java developer.

" + }, + + { + + "serial": 151833, + "name": "Dave Quigley", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_151833.jpg", + "url": null, + "position": "Computer Science Professional", + "affiliation": "KEYW Corporation", + "twitter": "", + "bio": "

David Quigley making a return appearance to OSCON after his \u201cDemystifying SELinux: WTF is it saying?\u201d talk started his career as a Computer Systems Researcher for the National Information Assurance Research Lab at the NSA where he worked as a member of the SELinux team. David leads the design and implementation efforts to provide Labeled-NFS support for SELinux. David has previously contributed to the open source community through maintaining the Unionfs 1.0 code base and through code contributions to various other projects. David has presented at conferences such as the Ottawa Linux Symposium, the StorageSS workshop, LinuxCon and several local Linux User Group meetings where presentation topics have included storage, file systems, and security. David currently works as a Computer Science Professional for the Operations, Analytics and Software Development (OASD) Division at Keyw Corporation.

" + }, + + { + + "serial": 161577, + "name": "Carl Quinn", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_161577.jpg", + "url": "http://www.riotgames.com/", + "position": "Software Architect", + "affiliation": "Riot Games", + "twitter": null, + "bio": "

Carl Quinn has been developing software professionally for 34 years, starting with BASIC on an Apple II, slogging through C/C++ on DOS, Windows and embedded, and finally landing in the Java-on-Linux world. The one thread through his career has been an inexplicable attraction to developer tools, spending time building them at Borland (C++ & Java IDEs), Sun (Java RAD), Google (Java & C++ build system), Netflix (Java build and cloud deployment automation) and most recently at Riot Games (Cloud Architect). Carl also co-hosts the Java Posse podcast, the #1 ranked Java technology podcast.

" + }, + + { + + "serial": 182741, + "name": "Kate Rafter", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_182741.jpg", + "url": "https://www.facebook.com/Automal", + "position": "Choreographer", + "affiliation": "AUTOMAL", + "twitter": "", + "bio": "

As a choreographer and interdisciplinary performance artist, Kate Rafter has presented work at the Edinburgh Festival Fringe, Scotland\u2019s Dance Base, Portland’s Fertile Ground Festival, Conduit, American College Dance Festival NW, the Dance Coalition of Oregon, (a)merging 2014, and the Someday:Incubator. She heads Automal, a project-based indie dance company.

\n

Kate’s performing arts ambitions go beyond dance and theatre conventions to include audience interactivity, immersive environments, and an intent to break every wall, starting with the fourth.

" + }, + + { + + "serial": 173432, + "name": "Jarret Raim", + "photo": null, + "url": "http://www.rackspace.com/", + "position": "Cloud Security Product Manager", + "affiliation": "Rackspace", + "twitter": "jarretraim", + "bio": "

Jarret Raim is the Security Product Manager at Rackspace Hosting. Since joining Rackspace, he has built a software assurance program for Rackspace\u2019s internal software teams as well as defined strategy for building secure systems on Rackspace\u2019s OpenStack Cloud implementation. Through his experience at Rackspace, and as a consultant for Denim Group, Jarret has assessed and remediated applications in all industries and has experience width a wide variety of both development environments and the tools used to audit them. Jarret has recently taken charge of Rackspace’s efforts to secure the Cloud through new product development, training and research. Jarret holds a Masters in Computer Science from Lehigh University and Bachelors in Computer Science from Trinity University.

" + }, + + { + + "serial": 150170, + "name": "Luciano Ramalho", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_150170.jpg", + "url": "http://python.pro.br/", + "position": "Principal", + "affiliation": "Python.pro.br", + "twitter": "ramalhoorg", + "bio": "

Luciano Ramalho was a Web developer before the Netscape IPO in 1995, and switched from Perl to Java to Python in 1998. Since then he worked on some of the largest news portals in Brazil using Python, and taught Python web development in the Brazilian media, banking and government sectors. His speaking credentials include OSCON 2013 (slides) and 2002, two talks at PyCon USA 2013 and 17 talks over the years at PythonBrasil (the Brazilian PyCon), FISL (the largest FLOSS conference in the Southern Hemisphere) and a keynote at the RuPy Strongly Dynamic Conference in Brazil. Ramalho is a member of the Python Software Foundation and co-founder of Garoa Hacker Clube, the first hackerspace in Brazil. He is a managing partner at Python.pro.br, a training company.

" + }, + + { + + "serial": 3471, + "name": "Anna Martelli Ravenscroft", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_3471.jpg", + "url": "http://www.google.com/profiles/annaraven", + "position": "Urban Homesteader", + "affiliation": "Self", + "twitter": "annaraven", + "bio": "

Anna Martelli Ravenscroft has a background in training and mentoring. Her focus is on practical, real-world problem solving and the benefits of diversity and accessibility. Anna graduated in 2010 from Stanford University with a degree in Cognitive Science. She is a member of the Python Software Foundation, a program committee member for several open source conferences, winner of the 2013 Frank Willison Award, and co-edited the Python Cookbook 2nd edition. She has spoken at PyCon, EuroPython, OSCON, and several regional Python conferences.

" + }, + + { + + "serial": 141169, + "name": "Matt Ray", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_141169.jpg", + "url": "http://leastresistance.net/", + "position": "Director of Partner Integration", + "affiliation": "Chef Software, Inc.", + "twitter": "mattray", + "bio": "

Matt Ray is an open source hacker working as the Director of Cloud Integrations for the company and open source systems integration platform Chef. He is active in the Chef, Ruby and OpenStack communities and was the Community Manager for Zenoss Core. He has been a contributor in the open source community for well over a decade and was one of the founders of the Texas LinuxFest. He resides in Austin, blogs at LeastResistance.net and is @mattray on Twitter, IRC and GitHub.

" + }, + + { + + "serial": 77350, + "name": "Rob Reilly", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_77350.jpg", + "url": "http://home.earthlink.net/~robreilly", + "position": "Independent consultant, writer, and speaker", + "affiliation": "Rob Reilly Consulting", + "twitter": "", + "bio": "

Rob Reilly is an independent consultant, writer, and speaker specializing in Linux, Open Hardware, technology media, and the mobile professional. He\u2019s been hired for a variety of engineering, business analysis, and special projects with AT&T, Intermedia Communications, Lockheed-Martin, Pachube, and Dice. As a 10-year veteran of the tech media, Rob has posted hundreds of feature-length technology articles for LinuxPlanet.com, Linux.com, Linux Journal magazine, PC Update magazine, and Nuts & Volts. He is a co-author of \u201cPoint & Click OpenOffice.org\u201d and worked as a contributing editor for LinuxToday.com. He\u2019s also chaired speaking committees for the old LinuxWorld shows. Rob has a BS in Mechanical Technology from Purdue University and first used the Unix command line in 1981.

" + }, + + { + + "serial": 160033, + "name": "Ryan Richards", + "photo": null, + "url": null, + "position": "Engineer", + "affiliation": "Fastly", + "twitter": null, + "bio": "

I am a full stack engineer at Fastly with an interest in the front-end, game programming, and theory. In my spare time I read books, write music, play games, and drink tea.

" + }, + + { + + "serial": 11886, + "name": "Chris Richardson", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_11886.jpg", + "url": "http://plainoldobjects.com/", + "position": "Software architect", + "affiliation": "Chris Richardson Consulting, Inc", + "twitter": "", + "bio": "

Chris Richardson is a developer and architect with over 20 years of experience. He is a Java Champion and the author of POJOs in Action, which describes how to build enterprise Java applications with POJOs and frameworks such as Spring and Hibernate. Chris is the founder of the original CloudFoundry.com, an early Java PaaS (Platform-as-a-Service) for Amazon EC2. He spends his time investigating better ways of developing and deploying software. Chris has a computer science degree from the University of Cambridge in England and lives in Oakland, CA.

" + }, + + { + + "serial": 182043, + "name": "Bruce Richardson", + "photo": null, + "url": null, + "position": "Software Engineer", + "affiliation": "Intel Corporation", + "twitter": "", + "bio": "

Bruce Richardson is the lead software engineer working on the DPDK at Intel Corporation, based out of Shannon in the West of Ireland. He has almost 10 years experience in software for telecoms, and has been working with Intel on the DPDK for over 4 of those. He\u2019s helped design many of the features now present in the DPDK, and when he\u2019s not too busy with other – less interesting \u2013 things at work, occasionally gets to have fun implementing some of them.

" + }, + + { + + "serial": 1402, + "name": "Bryce Roberts", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_1402.jpg", + "url": "http://www.oatv.com/", + "position": "Managing Director", + "affiliation": "O'Reilly AlphaTech Ventures", + "twitter": "bryce", + "bio": "

Bryce co-founded O’Reilly AlphaTech Ventures (OATV) in 2005. At OATV he focuses on consumer and enterprise software and services investments.

\n

Prior to OATV, Bryce sourced and lead a number of successful early stage investments at Wasatch Venture Fund, a Draper Fisher Jurvetson affiliate. In 2004, Bryce co-founded the Open Source Business Conference (sold to IDG) in order to spark a conversation around commercializing the highly disruptive technologies and services emerging from the open source community. Prior to Wasatch, Bryce was a member of a small team at vertical search pioneer Whizbang! Labs. While at WhizBang!, he defined and launched the FlipDog.com division (sold to Monster Worldwide). He began his career in technology doing large enterprise software deployments, saving his employer from the dreaded Y2K Bug.

\n

His investments at OATV include GameLayers, Get Satisfaction, OpenCandy, OpenX, Parakey (acquired by Facebook), Path Intelligence and Wesabe. He holds a B.A. in Philosophy from Brigham Young University.

" + }, + + { + + "serial": 173173, + "name": "Gail Roper", + "photo": null, + "url": "http://raleighnc.gov/", + "position": "Chief Information and Community Relations Officer at City of Raleigh", + "affiliation": "City of Raleigh", + "twitter": "gailmroper", + "bio": "

28 year IT executive, CIO, and C-level administrator. Vast experience in the area of strategic planning, IT consolidation, and large scale system implementation including ERP, GIS, VoIP, web efforts and fiber/conduit implementation projects. Understands the value of partnerships, well connected with national strategist and innovative thinkers. Fortunate to have well-rounded experience in progressive environments that influence national strategies for innovation. Highly effective in political arenas, governance strategy, and working with auditing process. Has historically brought resources to the organization in the form of grants, funding, and partnerships with public and private entities.

\n

Specialties: Strategic thinker with the idea that technology should promote efficiency in any organization. Focused on innovation and promoting the use of technology to solve problems. Significant success in the area of public/private partnerships, multi-jurisictional efforts, complex contract negotiations, RFP negotiations and evaluations. Prior experience in the development of cost models, Return on Investment strategies, and business case models including financial self funding models. Accountable leader maintaining a focus on productivity and effective practices.

" + }, + + { + + "serial": 173247, + "name": "Erik Rose", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_173247.jpg", + "url": "http://www.grinchcentral.com/", + "position": "Lexeme Juggler", + "affiliation": "Mozilla", + "twitter": "ErikRose", + "bio": "

Erik Rose leads Mozilla\u2019s DXR project, which does regex searches and static analysis on large codebases like Firefox. He is an Elasticsearch veteran, maintaining the pyelasticsearch library, transitioning Mozilla\u2019s support knowledgebase to ES, and building a burly cluster to do realtime fuzzy matching against the entire corpus of U.S. voters. Erik is a frequent speaker at conferences around the world, the author of \u201cPlone 3 for Education\u201d, and the nexus of the kind of animal magnetism that comes only from writing your own bio.

" + }, + + { + + "serial": 124630, + "name": "William A Rowe Jr", + "photo": null, + "url": null, + "position": "Staff Engineer", + "affiliation": "Pivotal", + "twitter": "wrowe", + "bio": "

William is a member of the Application Products engineering team at Pivotal, where he has developed and maintained Apache Web Server based products since the turn of the century. He is an active committer to several Apache Software Foundation projects and serves on the ASF security response team. Over the past dozen years, William has contributed to the Apache Software Foundation, initially as a contributor to the Apache HTTP Server and APR projects, served as a Project Chair to both, mentored a number of projects new to the Foundation, participated on the convention committee for the foundation, and served as a Director of the Foundation. He is sometimes teased as the Unix developer who happens to work on Windows, and was largely responsible for stabilizing httpd running on Windows and ensuring Windows was a first class supported platform of the APR library.

" + }, + + { + + "serial": 152026, + "name": "Byron Ruth", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_152026.jpg", + "url": "http://devel.io/", + "position": "Lead Analyst/Programmer", + "affiliation": "The Children's Hospital of Philadelphia", + "twitter": "thedevel", + "bio": "

Byron Ruth is a Lead Analyst/Programmer in the Center for Biomedical Informatics at The Children’s Hospital of Philadelphia. Byron’s skills in advanced web programming environments, API, and architectural software design have enabled him to lead a variety of projects at CHOP, including the development of a highly integrated audiology research database, an electronic health record-mediated clinical decision support engine for the care of premature infants, and a data management system that helps to discover relationships between genetic markers of congenital heart defects and clinical outcomes.

" + }, + + { + + "serial": 172610, + "name": "Justin Ryan", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_172610.jpg", + "url": "http://netflix.com/", + "position": "Senior Software Engineer", + "affiliation": "Netflix", + "twitter": "quidryan", + "bio": "

Justin Ryan is a Senior Software Engineer in the Engineering Tools team at Netflix, where he applies his years of experience as a developer to the problems of build automation and dependency management. He’s consistently raising the bar for the quality of build tools and build analysis. He is tasked with finding patterns and best practices between builds and applying them back to the hundreds of projects at Netflix. Justin has worked on Web UIs to Server development to Embedded programming.

" + }, + + { + + "serial": 114822, + "name": "Baruch Sadogursky", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_114822.jpg", + "url": "http://jfrog.com/", + "position": "Developer Advocate", + "affiliation": "JFrog", + "twitter": "jbaruch", + "bio": "

Baruch Sadogursky (a.k.a JBaruch) is the Developer Advocate of JFrog, the creators of Artifactory Binary Repository, the home of Bintray, JavaOne 2011 and 2013 Duke Choice Awards winner.

\n

For a living he hangs out with the JFrog tech leaders, writes some code around Artifactory and Bintray, and then speaks and blogs about all that. He does it repeatedly for the last 10 years and enjoys every moment of it.

\n

Baruch mostly blogs on http://blog.bintray.com and http://blogs.jfrog.org. His speaker history is on Lanyrd

" + }, + + { + + "serial": 173464, + "name": "Nathan Samano", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_173464.jpg", + "url": null, + "position": "Student", + "affiliation": "Saint Joseph's College (Indiana)", + "twitter": "", + "bio": "

Nathan is a junior Computer Science major at Saint Joseph’s College. He is minoring in biology. His computing interests include game development and cloud programming.

" + }, + + { + + "serial": 169557, + "name": "Peter Sand", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_169557.jpg", + "url": "https://www.modularscience.com/", + "position": "Founder", + "affiliation": "Modular Science", + "twitter": "", + "bio": "

Peter has a Ph.D. in Computer Science from MIT. He is the founder of ManyLabs, a nonprofit focused on teaching math and science using sensors and simulations. Peter also founded Modular Science, a company working on hardware and software tools for science labs. He has given talks at Science Hack Day, Launch Edu, and multiple academic conferences, including SIGGRAPH.

" + }, + + { + + "serial": 173364, + "name": "Karen Sandler", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_173364.jpg", + "url": "http://sfconservancy.org/", + "position": "Executive Director", + "affiliation": "Software Freedom Conservancy", + "twitter": "o0karen0o", + "bio": "

Karen M. Sandler is the Executive Director of the GNOME Foundation. She is known for her advocacy for free software, particularly in relation to the software on medical devices. Prior to joining GNOME, she was General Counsel of the Software Freedom Law Center. Karen continues to do pro bono legal work with SFLC and serves as an officer of the Software Freedom Conservancy and an advisor to the Ada Initiative. She is also pro bono General Counsel to QuestionCopyright.Org. Karen is a recipient of the O’Reilly Open Source Award.

" + }, + + { + + "serial": 2699, + "name": "Ed Schipul", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_2699.jpg", + "url": "http://eschipul.com/", + "position": "CEO", + "affiliation": "Tendenci - The Open Source Platform for NPOs", + "twitter": "eschipul", + "bio": "

Ed Schipul is CEO of Tendenci, formerly Schipul, a 16-year-old bootstrapped company started in Houston Texas. Ed\u2019s team created the Open Source software platform Tendenci, an all in one CMS built specifically for nonprofits, membership associations and arts organizations.

\n

Under Ed’s leadership, the company has been listed among Houston’s fastest growing companies by the Houston Business Journal, won the Fastech 50, the Aggie 100, and numerous other awards. Ed has personally been nominated for the Ernst and Young Entrepreneur of the year award twice and roasted by the AIGA.

\n

Ed has presented at OSCON, SXSW Interactive, Public Relations Society of America\u2019s International Conference, the Bulldog Reporter National Conference, the Sarasota International Design Summit, Mom 2.0 and dozens of other organizations.

\n

Ed is frequently asked to share his insights regarding online marketing and has been published in Nonprofit World, Association News, The Public Relations Strategist and PR Tactics, among others. He blogs for Hearst Newspaper\u2019s Houston City Brights and has been interviewed on NBC and ABC news as an expert in successfully using digital marketing tools to accelerate organizational growth.

\n

As a past participant and sponsor of the AIR Accessibility Internet Rally community hackathon, Ed has worked to make technology globally accessible, especially for those with disabilities.

\n

Ed is a graduate of Texas A&M University, builds aerial drones as a hobby, is an amateur photographer and a very amateur tennis player.

" + }, + + { + + "serial": 128980, + "name": "Aaron Schlesinger", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_128980.jpg", + "url": "http://www.paypal.com/", + "position": "Sr. Member of the Technical Staff", + "affiliation": "PayPal", + "twitter": "arschles", + "bio": "

I joined PayPal as part of the StackMob acquisition in late 2013, and I’m currently a Sr. Member of the Technical Staff here. My areas of interest include large scale distributed systems, high throughput server architectures, concurrency in large scale data systems, functional programming patterns, and emerging devops patterns.

\n

In my personal life, I live to play and watch soccer. My goal is to be playing well into my 50s.

" + }, + + { + + "serial": 108125, + "name": "Nathaniel Schutta", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_108125.jpg", + "url": "http://ntschutta.com/", + "position": "Solution Architect", + "affiliation": "ntschutta.com", + "twitter": "ntschutta", + "bio": "

Nathaniel T. Schutta is a solution architect focussed on making usable applications. A proponent of polyglot programming, Nate has written two books on Ajax and speaks regularly at various worldwide conferences, No Fluff Just Stuff symposia, universities, and Java user groups. In addition to his day job, Nate is an adjunct professor at the University of Minnesota where he teaches students to embrace dynamic languages. Most recently, Nate coauthored the book Presentation Patterns with Neal Ford and Matthew McCullough.

" + }, + + { + + "serial": 181502, + "name": "Daniel Juyung Seo", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_181502.jpg", + "url": "http://seoz.com/", + "position": "Senior Engineer", + "affiliation": "Samsung Electronics", + "twitter": "seojuyung", + "bio": "

Daniel Juyung Seo is a software engineer at Samsung Electronics focusing on the development and improvements of EFL. He joined the EFL/Enlightenment community in 2010 and is actively involved the project. He is mostly working on the Ecore core library and Elementary widget set among many libraries in EFL and helps people develop EFL applications on Tizen. As an active technical writer, he contributed EFL and Tizen articles to magazines and manages his own blogs. He participated in Tizen Camera (NX300) and Tizen Wearable Device (Gear 2) product projects in the past as well.

" + }, + + { + + "serial": 24052, + "name": "Andrew Clay Shafer", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_24052.jpg", + "url": "http://andrewclayshafer.com/", + "position": "Senior Director of Technology", + "affiliation": "Pivotal", + "twitter": "littleidea", + "bio": "

Andrew has been a consumer and contributor to open source for many years. He is always up for an adventure with a broad background contributing to technology and business efforts. As a co-founder at Puppet Labs, followed by working in and around the CloudStack and OpenStack ecosystems, he gained a lot of perspective on building open source businesses and communities. Sometimes he has been known to talk about devops and organizational learning. Andrew recently joined Pivotal, starting another open source business adventure focused on Cloud Foundry.

" + }, + + { + + "serial": 173314, + "name": "Brent Shaffer", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_173314.jpg", + "url": "http://brentertainment.com/", + "position": "Software Engineer", + "affiliation": "Adobe Systems Inc", + "twitter": "bshaffer", + "bio": "

The tale of a young musician with his head filled with hopes and dreams who finds himself burdened with a mathematical mind and with little hopes of making it in the music world, decides to jump ship and finds he loves programming as much and maybe even more than his original passion.

" + }, + + { + + "serial": 126882, + "name": "Gwen Shapira", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_126882.jpg", + "url": "http://www.pythian.com/news/author/shapira/", + "position": "Solutions Architect", + "affiliation": "Cloudera", + "twitter": "gwenshap", + "bio": "

Gwen Shapira is a Solutions Architect at Cloudera and leader of IOUG Big Data SIG. Gwen Shapira studied computer science, statistics and operations research at the University of Tel Aviv, and then went on to spend the next 15 years in different technical positions in the IT industry. She specializes in scalable and resilient solutions and helps her customers build high-performance large-scale data architectures using Hadoop. Gwen Shapira is a frequent presenter at conferences and regularly publishes articles in technical magazines and her blog.

" + }, + + { + + "serial": 151691, + "name": "Roman Shaposhnik", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_151691.jpg", + "url": "http://bigtop.apache.org/", + "position": "Sr. Manager, Open Source Hadoop Platform", + "affiliation": "Pivotal Inc.", + "twitter": "", + "bio": "

Roman Shaposhnik is a committer on Apache Hadoop, and holds a Chair position for the Apache Bigtop and Apache Incubator projects. Roman has been involved in Open Source software for more than a decade and has hacked projects ranging from the Linux kernel to the flagship multimedia library known as FFmpeg. He grew up in Sun Microsystems where he had an opportunity to learn from the best software engineers in the industry. Roman’s alma mater is St. Petersburg State University, Russia where he studied to be a mathematician.

" + }, + + { + + "serial": 141561, + "name": "Michael Shiloh", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_141561.jpg", + "url": "http://michaelshiloh.wordpress.com/", + "position": "Educational Materials Coordinator", + "affiliation": "Arduino", + "twitter": "michaelshiloh", + "bio": "

Artist, designer, tinkerer, teacher, geek; practitioner and supporter of Open Source Hardware, Open Source Software, Open Education, Linux, and Arduino, and most things in between

" + }, + + { + + "serial": 169647, + "name": "Egle Sigler", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_169647.jpg", + "url": "http://anystacker.com/", + "position": "Principal Architect", + "affiliation": "Rackspace", + "twitter": "eglute", + "bio": "Egle Sigler is a Private Cloud Architect, who started working at Rackspace in 2008. She was one of the first to receive Rackspace OpenStack certification. Before working with OpenStack, Egle worked on MyRackspace customer control panel, software architecture and enterprise tools. In her spare time, Egle enjoys traveling, hiking, snorkeling and nature photography." + }, + + { + + "serial": 3189, + "name": "Ricardo Signes", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_3189.jpg", + "url": "http://rjbs.manxome.org/", + "position": "Research & Development", + "affiliation": "Pobox.com", + "twitter": "rjbs", + "bio": "

Ricardo Signes was thrust into the job market with only a rudimentary humanities education, and was forced to learn to fend for himself. He is now a full-time Perl programmer, the project manager for Perl 5, and frequent contributor to the CPAN.

" + }, + + { + + "serial": 180050, + "name": "Alan Sill", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_180050.jpg", + "url": "http://www.depts.ttu.edu/cac/", + "position": "Director, Cloud and Autonomic Computing Center", + "affiliation": "Texas Tech University", + "twitter": "ogfstandards", + "bio": "

Alan Sill directs the National Science Foundation Center for Cloud and Autonomic Computing at Texas Tech University, where he is also a senior scientist at the High Performance Computing Center. A particle physicist by training, he serves as VP of Standards for Open Grid Forum, co-chairs the US National Institute of Standards and Technology “Standards Acceleration to Jumpstart Adoption of Cloud Computing” working group, and co-edits the IEEE Cloud Computing magazine. He is active in many cloud standards working groups and on national and international standards roadmap committees and is committed to development of advanced distributed computing methods for real-world science and business applications.

" + }, + + { + + "serial": 74368, + "name": "Kyle Simpson", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_74368.jpg", + "url": "http://getify.me/", + "position": "Open Web Evangelist", + "affiliation": "Getify Solutions", + "twitter": "getify", + "bio": "

Kyle Simpson is an Open Web Evangelist from Austin, TX. He’s passionate about JavaScript, HTML5, real-time/peer-to-peer communications, and web performance. Otherwise, he’s probably bored by it. Kyle is an author, workshop trainer, tech speaker, and avid OSS community member.

" + }, + + { + + "serial": 156591, + "name": "Samantha Simpson", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_156591.jpg", + "url": null, + "position": "Product Director", + "affiliation": "Consumer Financial Protection Bureau", + "twitter": "SamHSimpson", + "bio": "

Samantha Simpson moved to Washington, D.C. to work at the Consumer Financial Protection Bureau in the Office of Technology and Innovation. She work as both a Technology Portfolio Manager and Product Director. Samantha is passionate about helping consumer understand their finances. Samantha was born and raised on the South Side of Chicago and is a graduate of The Johns Hopkins University.

" + }, + + { + + "serial": 165643, + "name": "Thomas Smith", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_165643.jpg", + "url": "http://www.projectgado.org/", + "position": "Founder and Project Manager", + "affiliation": "Project Gado", + "twitter": "projectgado", + "bio": "

Thomas Smith is an inventor and entrepreneur, and the creator of the open source Gado 2 archival scanning robot. Hailed by the Wall Street Journal as “a robot which rescues black history”, the Gado 2 has been used to digitize 120,000+ images in the archives of the Afro American Newspapers, and is now in use at archives from California to Finland. Mr. Smith currently leads Project Gado, as well as several other ventures.

" + }, + + { + + "serial": 131729, + "name": "Garrett Smith", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_131729.jpg", + "url": null, + "position": "Architect", + "affiliation": "CloudBees", + "twitter": "gar1t", + "bio": "

Garrett Smith is senior architect at CloudBees, the company behind Jenkins CI and leading Java platform-as-a-service vendor. Garrett specializes in distributed systems and reliable software. His language of choice for systems programming is Erlang, a high productivity functional language specializing in concurrency and reliability. Garrett is an Erlang instructor and the author of the e2 library, which was built from his experience teaching Erlang. Garrett is also known for the videos “MongoDB is Web Scale” and “Node.js Is Bad Ass Rock Star Tech”.

" + }, + + { + + "serial": 109116, + "name": "Chris Smith", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_109116.jpg", + "url": "http://portlandtransport.com/", + "position": "President", + "affiliation": "Portland Transport", + "twitter": "chrissmithus", + "bio": "

Chris Smith is a web site architect for Xerox.com by day and transportation geek and City of Portland Planning and Sustainability Commissioner by night

" + }, + + { + + "serial": 77757, + "name": "Bryan Smith", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_77757.jpg", + "url": "http://fossetcon.org/", + "position": "Program Director", + "affiliation": "Fossetcon", + "twitter": "fossetcon", + "bio": "

Bryan A Smith is a Debian Gnu/Linux and BSD enthusiast, hardware hacker and Systems Engineer. Bryan has used Open Source Operating Systems since the days of Red Hat 5 Hurricane.

\n

He contributes to several Open Source projects and has helped launch several startup ISP’s based in his area using Free and Open Source software as a framework.

\n

Bryan is currently the Program Director for Fossetcon Fossetcon – Free and Open Source Software Expo and Technology Conference

\n

Bryan spends his free time organizing Fossetcon, administering his free shell server, advocating Free and Open Source, proctoring BSD Associate Certification, writing poetry and playing Bossa Nova and Flamenco guitar.

" + }, + + { + + "serial": 81759, + "name": "Carol Smith", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_81759.jpg", + "url": "http://www.fossygirl.com/", + "position": "Program Manager", + "affiliation": "Google, Inc.", + "twitter": "fossygrl", + "bio": "

Carol Smith is the Open Source Programs Manager running Google
\nSummer of Code. She’s been at Google as a program manager for 9 years.
\nShe has a degree in photojournalism from California State University,
\nNorthridge, and is an avid cyclist and bibliophile.

" + }, + + { + + "serial": 2732, + "name": "Nathan Sobo", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_2732.jpg", + "url": null, + "position": "Programmer", + "affiliation": "GitHub", + "twitter": "nathansobo", + "bio": "

Nathan Sobo is the co-founder of the Atom open-source text editor at GitHub. He is also the author of Treetop, a Ruby-based parser generator based on parsing expression grammars.

" + }, + + { + + "serial": 44753, + "name": "Juhan Sonin", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_44753.jpg", + "url": "http://www.mit.edu/~juhan", + "position": "Designer", + "affiliation": "Involution Studios", + "twitter": "", + "bio": "

Juhan Sonin is the Creative Director of Involution Studios and has produced work recognized by the New York Times, Newsweek, BBC International, Billboard Magazine, and National Public Radio (NPR). He has spent time at Apple, the National Center for Supercomputing Applications (NCSA), and MITRE. Juhan lectures on design and engineering at the Massachusetts Institute of Technology (MIT).

" + }, + + { + + "serial": 178738, + "name": "Andrew Sorensen", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_178738.jpg", + "url": "http://extempore.moso.com.au/", + "position": "Artist-programmer", + "affiliation": "QUT", + "twitter": "digego", + "bio": "

Andrew Sorensen is an artist-programmer whose interests lie at the
\nintersection of computer science and creative practice. Andrew is well
\nknown for creating the programming languages that he uses in live
\nperformance to generate improvised audiovisual theatre. He has been
\ninvited to perform these contemporary audiovisual improvisations
\naround the world. Andrew is a Senior Research Fellow at the Queensland
\nUniversity of Technology and is the author of the Impromptu and
\nExtempore programming language environments.

" + }, + + { + + "serial": 175353, + "name": "Derek Sorkin", + "photo": null, + "url": null, + "position": "Sales Director", + "affiliation": "GitHub", + "twitter": "", + "bio": "

GitHub

" + }, + + { + + "serial": 151991, + "name": "Francisco Souza", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_151991.jpg", + "url": "http://f.souza.cc/", + "position": "Software engineer", + "affiliation": "Globo.com", + "twitter": "franciscosouza", + "bio": "

Francisco Souza is a software engineer at Globo.com, the largest media company in Latin America. He works in the cloud platform team, building an open source solution to be adopted by all Globo.com portals.

" + }, + + { + + "serial": 142767, + "name": "Kara Sowles", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_142767.jpg", + "url": null, + "position": "Community Initiatives Manager", + "affiliation": "Puppet Labs", + "twitter": "FeyNudibranch", + "bio": "

Kara Sowles is Community Initiatives Manager at Puppet Labs in Portland, OR. She’s excited to swap user group tips with you for far too long.

\n

When she’s done planning events and running community programs at Puppet Labs, she enjoys going home and making stop motion animation with actual Puppets.

" + }, + + { + + "serial": 172240, + "name": "Kaushik Srenevasan", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_172240.jpg", + "url": null, + "position": "Staff Software Engineer", + "affiliation": "Twitter", + "twitter": "", + "bio": "

Kaushik Srenevasan is the Lead on the VM Diagnostics team at Twitter,
\nwhere he hacks on the Hotspot JVM, on, among other things, improving
\nits observability. Before joining Twitter he authored the Chakra
\nJavaScript runtime’s AMD64 backend compiler and worked on the .NET CLR at
\nMicrosoft.

" + }, + + { + + "serial": 131499, + "name": "Raghavan Srinivas", + "photo": null, + "url": "http://www.couchbase.org/", + "position": "Solutions Architect", + "affiliation": "Independent", + "twitter": "ragss", + "bio": "

Raghavan “Rags” Srinivas works as a Cloud Solutions Architect. His general focus area is in distributed systems, with a specialization in Cloud Computing and Big Data. He worked on Hadoop, HBase and NoSQL during its early stages. He has spoken on a variety of technical topics at conferences around the world, conducted and organized Hands-on Labs and taught graduate classes in the evening.

\n

Rags brings with him over 25 years of hands-on software development and over 15 years of architecture and technology evangelism experience.

\n

Rags holds a Masters degree in Computer Science from the Center of Advanced Computer Studies at the University of Louisiana at Lafayette. He likes to hike, run and generally be outdoors but most of all loves to eat.

" + }, + + { + + "serial": 3476, + "name": "Simon St. Laurent", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_3476.jpg", + "url": "http://simonstl.com/", + "position": "Senior Editor", + "affiliation": "O'Reilly Media, Inc.", + "twitter": "simonstl", + "bio": "

Simon St.Laurent has spent most of his career explaining technology. He is co-chair of the Fluent and OSCON conferences, a Senior Editor at O’Reilly, and a web developer. He has written over a dozen books, including Introducing Elixir, Introducing Erlang, Learning Rails, XML Elements of Style, and XML: A Primer. He spends his spare time making objects out of wood and presenting on local history.

" + }, + + { + + "serial": 170134, + "name": "Mark Stanislav", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_170134.jpg", + "url": "https://www.duosecurity.com/", + "position": "Security Evangelist", + "affiliation": "Duo Security", + "twitter": "markstanislav", + "bio": "

Mark Stanislav is the Security Evangelist for Duo Security, an Ann Arbor, Michigan-based startup focused on two-factor authentication and mobile security. With a career spanning over a decade, Mark has worked within small business, academia, startup, and corporate environments, primarily focused on Linux architecture, information security, and web application development.

\n

Mark has spoken nationally at over 70 events including RSA, ISSA, B-Sides, GrrCon, Infragard, and the Rochester Security Summit. Mark\u2019s security research has been featured on web sites including CSO Online, Security Ledger, and Slashdot. Additionally, Mark is an active participant of local and nationals security organizations including ISSA, Infragard, HTCIA, ArbSec, and MiSec.

\n

Mark earned his Bachelor of Science Degree in Networking and IT Administration and his Master of Science Degree in Technology Studies, focused on Information Assurance, both from Eastern Michigan University. During his time at EMU, Mark built the curriculum for two courses focused on Linux administration and taught as an Adjunct Lecturer for two years. Mark holds CISSP, Security+, Linux+, and CCSK certifications.

" + }, + + { + + "serial": 172899, + "name": "Nicolas Steenhout", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_172899.jpg", + "url": "http://accessibility.net.nz/", + "position": "Principal", + "affiliation": "Accessibility NZ", + "twitter": "vavroom", + "bio": "

Nicolas Steenhout is a veteran of, and passionate advocate for, web accessibility. Nic had taken the lead in building several websites prior to taking up a federally-funded position in the disability sector in the US in 1996. An international speaker, trainer and consultant, Nic works with government, corporations, and small teams, in the areas of both web and physical accessibility. Working with and for thousands of people with disabilities in North America and Australasia, while working with web technologies and their impact, has given Nic a unique insight into the challenges, solutions, nuts and bolts of web accessibility.

" + }, + + { + + "serial": 138530, + "name": "Boyd Stephens", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_138530.jpg", + "url": "http://www.netelysis.com/", + "position": "Founder", + "affiliation": "Netelysis", + "twitter": "", + "bio": "

Boyd Stephens – the founder of Netelysis, LLC and is currently employed within the firm’s network engineering division. Having entered his 17th year as an entrepreneur, Boyd is now a member of Netelysis’ networking services development team where his primary focus is upon the research and design of internetworking systems and network management services.

\n

For the last twenty-six years he has worked within some facet of the information technology industry having designed and managed an array of networking systems and services for educational, corporate and governmental entities. His professional efforts have been the source of immeasurable fulfillment both personally and professionally.

" + }, + + { + + "serial": 172656, + "name": "Simon Stewart", + "photo": null, + "url": "http://www.facebook.com/", + "position": "Software Engineer", + "affiliation": "Facebook", + "twitter": "shs96c", + "bio": "

Simon works at Facebook as a Software Engineer, where he works as part of the internal tools team. He’s also the inventor of WebDriver and the current lead of the Selenium project, and co-edits the W3C WebDriver specification.

" + }, + + { + + "serial": 173088, + "name": "Matt Stine", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_173088.jpg", + "url": "http://www.mattstine.com/", + "position": "Senior Product Manager, Spring Cloud Services for Pivotal Cloud Foundry", + "affiliation": "Pivotal", + "twitter": "mstine", + "bio": "

Matt Stine is a Cloud Foundry Platform Engineer at Pivotal. He is a 13-year veteran of the enterprise IT industry, with experience spanning numerous business domains.

\n

Matt is obsessed with the idea that enterprise IT \u201cdoesn\u2019t have to suck,\u201d and spends much of his time thinking about lean/agile software development methodologies, DevOps, architectural principles/patterns/practices, and programming paradigms, in an attempt to find the perfect storm of techniques that will allow corporate IT departments to not only function like startup companies, but also create software that delights users while maintaining a high degree of conceptual integrity. He currently specializes in helping customers achieve success with Platform as a Service (PaaS) using Cloud Foundry and BOSH.

\n

Matt has spoken at conferences ranging from JavaOne to CodeMash, is a regular speaker on the No Fluff Just Stuff tour, and serves as Technical Editor of NFJS the Magazine. Matt is also the founder and past president of the Memphis/Mid-South Java User Group.

" + }, + + { + + "serial": 122293, + "name": "Deirdr\u00e9 Straughan", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_122293.jpg", + "url": "http://www.beginningwithi.com/", + "position": "Business Unit Cloud & IP", + "affiliation": "Ericsson", + "twitter": "deirdres", + "bio": "

In a career spanning tech companies large and small, worldwide, Deirdr\u00e9 Straughan has constantly developed new ways to foster communication about technology between customers and companies, online and offline. She has particularly focused on innovative uses of media, in recent years producing hundreds of technical videos and live video streams for Sun Microsystems, Oracle, and Joyent, and for open source communities including OpenSolaris, illumos, SmartOS, and OpenZFS. You can learn more on beginningwithi.com.

" + }, + + { + + "serial": 135908, + "name": "Jason Strimpel", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_135908.jpg", + "url": null, + "position": "Staff Software Engineer", + "affiliation": "WalmartLabs", + "twitter": "", + "bio": "

I engineer software.

" + }, + + { + + "serial": 108840, + "name": "Ruth Suehle", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_108840.jpg", + "url": "http://www.opensource.com/", + "position": "Marketing Manager", + "affiliation": "Red Hat", + "twitter": "suehle", + "bio": "

Ruth Suehle is a community marketing manager in Red Hat\u2019s Open Source and Standards group, which supports upstream open source software communities. She also leads the Fedora Project\u2019s marketing team and is co-author of Raspberry Pi Hacks (O\u2019Reilly, December 2013). Previously an editor for Red Hat Magazine, she now leads discussions about open source principles as an editor at opensource.com. Ruth is also a senior editor at GeekMom.com, where she covers the adventures of motherhood alongside technology and sci-fi.

" + }, + + { + + "serial": 173421, + "name": "Marc Sugiyama", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_173421.jpg", + "url": "https://www.erlang-solutions.com/", + "position": "Senior Architect", + "affiliation": "Erlang Solutions, Inc", + "twitter": "", + "bio": "

Marc Sugiyama is a Senior Architect at Erlang Solutions, Inc. A Bay Area native, he has 30 years of software development experience and has worked on everything from testing frameworks in Tcl at Sybase and Cisco, to SMP relational database engines in C at Sybase, to a MMO engine in Twisted Python for Pixverse (a company he co-founded), to a large scale real time chat system in Erlang for hi5 Networks. Prior to joining Erlang Solutions, he built a call handling service in Erlang for Ribbit/British Telecom leading a team of developers in Brazil, Sweden, the US, and the UK. A published author, he wrote his first magazine articles and books while still in high school. He has presented at Sybase User Group Meetings and the Colorado Software Summit. He holds a Bachelors of Science in Engineering and Masters of Engineering from Harvey Mudd College (Claremont, CA) and serves on the Board of Trustees of The College Preparatory School in Oakland, CA.

" + }, + + { + + "serial": 164229, + "name": "Nick Sullivan", + "photo": null, + "url": "http://blog.cloudflare.com/author/nick-sullivan", + "position": "Systems Engineer", + "affiliation": "CloudFlare", + "twitter": "grittygrease", + "bio": "

Nick is a software engineering leader innovating in the world of Internet scale data at CloudFlare. He is also a respected digital rights management innovator with a thorough understanding of the digital media distribution process through over half a decade working on the iTunes store. He previously worked as a security analyst worked at Symantec analyzing large scale threat data. He holds an MSc in Cryptography and a BMath in Pure Mathematics.

" + }, + + { + + "serial": 175040, + "name": "Zach Supalla", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_175040.jpg", + "url": "http://www.spark.io/", + "position": "Founder and CEO", + "affiliation": "Spark Labs", + "twitter": "zsupalla", + "bio": "

Zach Supalla is an entrepreneur, a Maker, and a designer. He is the founder and CEO of Spark, a start-up that’s making it easier to build internet-connected hardware. Zach juggles hardware design, front-end software development, and leading his team through the trials and tribulations of a hardware start-up.

\n

The Spark team led a successful Kickstarter campaign for their product, the Spark Core, in May 2013, raising nearly $600K in 30 days off of a goal of $10K. They\u2019re now shipping to 61 countries, with thousands of engineers and developers building new connected devices with their technology. Their products have been featured in WIRED, Engadget, Fast Company, TechCrunch, the Discovery Channel, and many other publications.

\n

Zach is a graduate of HAXLR8R, the only incubator for hardware start-ups that will teach you to order bubble tea in perfect Mandarin. He also has an MBA from Kellogg School of Management and an MEM (Masters in Engineering Management) from McCormick School of Engineering at Northwestern. Before Spark, Zach worked as a management consultant with McKinsey & Company, advising Fortune 500 companies on strategy, operations, and product development.

" + }, + + { + + "serial": 171226, + "name": "Jason Swartz", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_171226.jpg", + "url": "http://www.bks2.com/", + "position": "Software Engineer", + "affiliation": "Netflix, Inc", + "twitter": "swartzrock", + "bio": "

Jason is a Software Engineer in the San Francisco Bay Area, developing applications and services in Scala at Netflix. Before making the switch to functional programming he managed the developer docs and support team at eBay, wrote advertising and merchandising platforms in Java and built tools and UI prototypes at Apple. His book, “Learning Scala”, will be published by O’Reilly Media in Spring 2014.

" + }, + + { + + "serial": 153857, + "name": "Kiyoto Tamura", + "photo": null, + "url": "http://github.com/kiyoto", + "position": "Developer Marketing Manager", + "affiliation": "Treasure Data", + "twitter": null, + "bio": "

Kiyoto is one of the maintainers of Fluentd, the open source log collector with users ranging from Nintendo to Slideshare. Raised in Japan, New York and California, he brings a unique bilingual, bicultural perspective to the open source world.

\n

He spends much of his day at Treasure Data as a developer marketer/community manager for Fluentd. He also is a math nerd turned quantitative trader turned software engineer turned open source community advocate and cherishes American brunch and Japanese game shows.

" + }, + + { + + "serial": 173262, + "name": "Paul Tarjan", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_173262.jpg", + "url": "http://paultarjan.com/", + "position": "Software Engineer", + "affiliation": "Facebook", + "twitter": "ptarjan", + "bio": "

I’m a juggling unicylcing web hacker @ Facebook.

\n

I’m on the HipHop for PHP team making HHVM able to run all existing PHP code. Before that, I build many of the Open Graph tools and features. Before that I was the Tech Lead for Yahoo! SearchMonkey.

" + }, + + { + + "serial": 41059, + "name": "James Tauber", + "photo": null, + "url": "http://jtauber.com/", + "position": "Open Source Community Manager", + "affiliation": "edX", + "twitter": "jtauber", + "bio": "

James Tauber is an Australian web developer and entrepreneur now based in Boston. He has been involved in open source, Web standards and Python for over 15 years.

" + }, + + { + + "serial": 173285, + "name": "Christian Ternus", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_173285.jpg", + "url": "http://cternus.net/", + "position": "Security Researcher", + "affiliation": "Akamai", + "twitter": "ternus", + "bio": "

Christian Ternus is a security researcher on Akamai Technologies’ Adversarial Resilience team, where he works on attacks, architecture, design, analysis, and the human factors in security. He graduated from MIT and has previously worked in kernel security and mobile health-tech. He has previously spoken at industry conferences including Boston Security, SOURCE Boston, and BrainTank, as well as organizing Akamai’s Humanity in Security miniconference. In his spare time, he is an avid photographer and adventure motorcyclist.

" + }, + + { + + "serial": 173472, + "name": "Jeffrey Thalhammer", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_173472.jpg", + "url": "https://stratopan.com/", + "position": "Co-Founder", + "affiliation": "Stratopan", + "twitter": "thaljef", + "bio": "

Jeffrey Thalhammer has been developing software for more than 15 years. He is the creator of Perl::Critic, the widely used static analyzer for Perl. Jeffrey is also a co-organizer of the San Francisco Perl Mongers.

" + }, + + { + + "serial": 150, + "name": "Laura Thomson", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_150.jpg", + "url": null, + "position": "Senior Software Engineer", + "affiliation": "Mozilla Corporation", + "twitter": null, + "bio": "

Laura Thomson is a Senior Engineering Manager at Mozilla Corporation. She works with the Web Engineering team, which is responsible for the Firefox crash reporting system and other developer tools, and the Release Engineering team, which is responsible for shipping Firefox.

\n

Laura is the co-author of \u201cPHP and MySQL Web Development\u201d and \u201cMySQL Tutorial\u201d. She is a veteran speaker at Open Source conferences worldwide.

" + }, + + { + + "serial": 173378, + "name": "Sebastian Tiedtke", + "photo": null, + "url": "https://saucelabs.com/", + "position": "Director of Web Development", + "affiliation": "Sauce Labs Inc", + "twitter": "sourishkrout", + "bio": "

Born and raised at the foot of the Alps just outside of Munich in Germany, Sebastian spent his youth listening to David Hasselhoff songs. He wore Lederhosen and eagerly anticipated the day he could legally drink his first stein of beer.

\n

When daddy put the first computer on his desk in the mid 90s, Sebastian quickly went on to figure out BASIC, Pascal and C/C++. Rainy afternoons were filled with building websites, countless iterations of Linux kernel makefile tweaks and compiler runs.

\n

Before joining Sauce, Sebastian finished a degree in Software Development (in Munich), he spent almost a decade building customer information systems making transit information more accessible for people living in large metropolitan areas worldwide. His job commitment and passions for technology eventually made him move to San Francisco in 2009.

\n

When Sebastian is not busy improving the Sauce experience he likes to take joyrides on his 1961 vespa. He’s a natural optimist in life and always sees the stein half full.

" + }, + + { + + "serial": 53442, + "name": "Jim Tommaney", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_53442.jpg", + "url": "http://infinidb.co/", + "position": "Chief Technology Officer", + "affiliation": "InfiniDB", + "twitter": "InfiniDB", + "bio": "

Jim has extensive experience in leading the development, management, and performance for enterprise data architectures, including clustered, large SMP, and distributed systems for the retail, web, and telecom industries. He is responsible for the architecture, vision, direction, and technical evangelization of InfiniDB. Jim holds a BBA from Texas A&M and a Masters in Management Information Systems from the University of Texas at Dallas.

" + }, + + { + + "serial": 172661, + "name": "Sudhir Tonse", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_172661.jpg", + "url": "http://sudhirtonse.com/", + "position": "Manager, Cloud Platform Infrastructure", + "affiliation": "Netflix", + "twitter": "stonse", + "bio": "

Sudhir Tonse manages the Cloud Platform Infrastructure team at Netflix and is responsible for many of the services and components that form the Netflix Cloud Platform as a Service.

\n

Many of these components have been open sourced under the NetflixOSS umbrella. Open source contribution includes Archaius: a dynamic configuration/properties management library, Ribbon: a Inter Process Communications framework that includes Cloud friendly Software load balancers, Karyon: the nucleus of a PaaS service etc.
\nPrior to Netflix, Sudhir was an Architect at Netscape/AOL delivering large-scale consumer and enterprise applications in the area of Personalization, Infrastructure and Advertising Solutions.

\n

Sudhir is a weekend golfer and tries to make the most of the wonderful California weather and public courses.

" + }, + + { + + "serial": 173326, + "name": "Willem Toorop", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_173326.jpg", + "url": null, + "position": "Software Engineer", + "affiliation": "NLNet Labs", + "twitter": "", + "bio": "

Willem is a developer at NLnet Labs, a not-for-profit foundation dedicated to the development of open-source implementations of open standards. At NLnet Labs Willem is the lead developer of the C DNS utility library: ldns. Willem has implemented leading edge DNS functionality for ldns based on new open standards such as DNSSEC and DANE. Our getdns-api implementation utilizes ldns for processing DNS data. Another of NLnet Labs C-libraries, libunbound, is used for DNS resolving. Besides working on ldns Willem also maintains and develops the perl Net::DNS and Net::DNS::SEC modules and actively researches Path MTU black holes that hamper DNSSEC deployment.

" + }, + + { + + "serial": 181972, + "name": "Joan Touzet", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_181972.jpg", + "url": "https://cloudant.com/", + "position": "Senior Software Development Manager", + "affiliation": "Cloudant, an IBM Company", + "twitter": "wohali", + "bio": "

Joan Touzet has been using Apache CouchDB since 2008. She is now a committer and PMC member for Apache CouchDB, and acts as Senior Software Development Manager at Cloudant, an IBM Company.

\n

Her major effort for 2014 is coordinating the merge of Cloudant’s BigCouch branch back into CouchDB towards a CouchDB 2.0 release. Recently, Joan has given talks at CloudantCON 2014, CouchDB Conf 2013, ChefConf 2013 and PyCon Canada 2012, all of which are viewable on YouTube.

\n

In her spare time, Joan builds and repairs motorcycles, pilots small aircraft, and composes music for video game soundtracks.

" + }, + + { + + "serial": 172990, + "name": "Brian Troutwine", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_172990.jpg", + "url": "http://adroll.com/", + "position": "Software Engineer", + "affiliation": "AdRoll", + "twitter": "bltroutwine", + "bio": "

I’m a software engineer at AdRoll where I work on soft real-time system in Erlang. I deal with mission-critical systems that operate at large scale. Functional programming and Erlang in particular are my main areas of interest and I’m becoming increasingly interested in “large” embedded systems.

\n

I graduated in 2010 with a degree in Computer Science from Portland State University. I spoke at Erlang Factory 2014, Bay Area.

" + }, + + { + + "serial": 172607, + "name": "Eric Tschetter", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_172607.jpg", + "url": "http://www.druid.io/", + "position": "Druid Collaborator", + "affiliation": "Tidepool.org", + "twitter": "zedruid", + "bio": "

Eric Tschetter is the creator and one of the main Contributors to Druid, an open source, real-time analytical data store. He is currently an individual contributor to Tidepool.org, a non-profit diabetes research organization. Eric was previously the VP of Engineering and lead architect at Metamarkets, and has held senior engineering positions at Ning and LinkedIn.He holds bachelors degrees in Computer Science and Japanese from the University of Texas at Austin, and a M.S. from the University of Tokyo in Computer Science.

" + }, + + { + + "serial": 5060, + "name": "James Turnbull", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_5060.jpg", + "url": "http://www.kartar.net/", + "position": "VP Services", + "affiliation": "Docker", + "twitter": "kartar", + "bio": "

James Turnbull is the author of seven technical books about open source software and a long-time member of the open source community. James authored the The Logstash Book and The Docker Book. He also wrote two books about Puppet (Pro Puppet\"\" and the earlier book about Puppet as well as Pro Linux System Administration, Pro Nagios 2.0, and Hardening Linux.

\n

For a real job, James is VP of Services for Docker. He likes food, wine, books, photography and cats. He is not overly keen on long walks on the beach and holding hands.

" + }, + + { + + "serial": 169870, + "name": "James Turner", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_169870.jpg", + "url": "http://www.oreilly.com/", + "position": "Contributing Editor", + "affiliation": "O'Reilly Media", + "twitter": "blackbearnh", + "bio": "

James Turner, contributing editor for oreilly.com, is a freelance journalist who has written for publications as diverse as the Christian Science Monitor, Processor, Linuxworld Magazine, Developer.com and WIRED Magazine. In addition to his shorter writing, he has also written two books on Java Web Development (MySQL & JSP Web Applications" and “Struts: Kick Start”) as well as the O’Reilly title “Developing Enterprise iOS Applications”. He is the former Senior Editor of LinuxWorld Magazine and Senior Contributing Editor for Linux Today. He has also spent more than 30 years as a software engineer and system administrator, and currently works as a Senior Software Engineer for a company in the Boston area. His past employers have included the MIT Artificial Intelligence Laboratory, Xerox AI Systems, Solbourne Computer, Interleaf, the Christian Science Monitor and contracting positions at BBN and Fidelity Investments. He is a committer on the Apache Jakarta Struts project and served as the Struts 1.1B3 release manager. He lives in a 200 year old Colonial farmhouse in Derry, NH along with his wife and son. He is an open water diver and instrument-rated private pilot, as well as an avid science fiction fan.

" + }, + + { + + "serial": 173406, + "name": "Andrew Turner", + "photo": null, + "url": null, + "position": "", + "affiliation": "Esri", + "twitter": "ajturner", + "bio": "

CTO of the Esri DC Dev Center

" + }, + + { + + "serial": 175354, + "name": "Tim Tyler", + "photo": null, + "url": null, + "position": "", + "affiliation": "Qualcomm", + "twitter": "", + "bio": "

Sr. Staff Engineer
\nQualcomm Open Source Portal Team

" + }, + + { + + "serial": 86111, + "name": "David Uhlman", + "photo": null, + "url": "http://www.clear-health.com/", + "position": "chairman", + "affiliation": "clearhealth inc.", + "twitter": "", + "bio": "

David Uhlman is CEO of ClearHealth Inc. and a has been longstanding contributor and entrepreneur in open source technology for 15 years including contributions to Linux, Java, CentOS, and Joomla. He is a frequent speaker and contributor including previous talks at OSCON, SCALE, and others.

\n

ClearHealth Inc is the company associated with the ClearHealth open source project, a practice management and electronic medical record system derived from VA VistA started in 2003 which now has over 18 million patients in more than 1,000 installations including notable large scale systems like Tarrant County TX, The Primary Care Coalition, The University of Texas Medical Branch and numerous Federally Qualified Health Centers (FQHC) nationwide.

" + }, + + { + + "serial": 173056, + "name": "Manish Vachharajani", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_173056.jpg", + "url": "http://linerate.f5.com/", + "position": "Senior Architect", + "affiliation": "F5 Networks", + "twitter": "mvachhar", + "bio": "

Manish was the Founder and Chief Software Architect of LineRate Systems, a high-performance software networking startup that was acquired by F5 Networks in February 2013.

\n

LineRate Systems’ core technology is based on Node.js and Manish’s research group’s work on high-performance networking at the University of Colorado at Boulder.

\n

Prior to LineRate, Manish dedicated 13 years studying
\nsoftware performance on general purpose processors. He co-authored nearly 50 publications on processor performance in a range of fields including optimizing compiler design, on-chip optics, performance modeling, parallel programming, and high performance networking. His work has been recognized by best paper and presentation awards at
\ntop-tier conferences, support from the National Science Foundation, and support from industry leaders such as Intel and NVIDIA. Manish is currently a Senior Architect at F5 Networks leading the design of the
\nLineRate product.

" + }, + + { + + "serial": 76735, + "name": "Jos\u00e9 Valim", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_76735.jpg", + "url": null, + "position": "Engineer", + "affiliation": "Plataforma Tec", + "twitter": "josevalim", + "bio": "

Jos\u00e9 Valim is the creator of the Elixir programming language and member of the Rails Core Team. He graduated in Engineering in S\u00e3o Paulo University, Brazil and has a Master of Science by Politecnico di Torino, Italy. He is also the lead-developer of Plataformatec, a consultancy firm based in Brazil, and an active member of the Open Source community.

" + }, + + { + + "serial": 99817, + "name": "William Van Hevelingen", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_99817.jpg", + "url": null, + "position": "Unix Lead, College of Engineering", + "affiliation": "Portland State University", + "twitter": "pdx_blkperl", + "bio": "

William Van Hevelingen started with Linux and configuration management as part of the Computer Action Team\u2019s Braindump program at Portland State University. He worked on the Wintel, Nix, and Networking teams as a volunteer and later as a student worker helping to manage hundreds of workstations, servers, and networking infrastructure. William now works full time for the Computer Action Team (TheCAT), which provides IT for the Maseeh College of Engineering and Computer Science at Portland State University, as the Unix Team lead. He helps teach the Unix portion of the CAT\u2019s Braindump program, covering topics like web servers, databases, storage, virtualization, and Puppet. William is a co-author of Pro Puppet 2nd Edition and Beginning Puppet book which is to be released in late 2014.

" + }, + + { + + "serial": 65329, + "name": "Heather VanCura", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_65329.jpg", + "url": "http://jcp.org/", + "position": "Group Manager, JCP program office", + "affiliation": "Java Community Process JCP", + "twitter": "", + "bio": "

Heather VanCura manages the JCP Program Office and is responsible for the day-to-day nurturing, support, and leadership of the community. She oversees the JCP.org web site, JSR management and posting, community building, events, marketing, communications, and growth of the membership through new members and renewals. Heather has a front row seat for studying trends within the community and recommending changes. Several changes to the program in recent years have included enabling broader participation, increased transparency and agility in JSR development.

" + }, + + { + + "serial": 183835, + "name": "Poornima Venkatakrishnan", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_183835.jpg", + "url": null, + "position": "Software Engineer", + "affiliation": "PayPal", + "twitter": null, + "bio": "

I am a web developer who is passionate about all things node.js and open source. I am an active contributor/ maintainer of Krakenjs – Paypal’s open sourced app framework for express. I like playing with new technologies, solving difficult problems and working on game changing projects. To sum up, I love a good challenge that keeps me on my toes.

" + }, + + { + + "serial": 112672, + "name": "Alvaro Videla", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_112672.jpg", + "url": "http://rabbitmq.com/", + "position": "Software Developer", + "affiliation": "RabbitMQ", + "twitter": "old_sound", + "bio": "

Alvaro Videla works as Developer Advocate for RabbitMQ/Pivotal. Before moving to Europe he used to work in Shanghai where he helped building one of Germany biggest dating websites. He co-authored the book “RabbitMQ in Action” for Manning Publishing. Some of his open source projects can be found here. Apart from code related stuff he likes traveling with his wife, listening/playing music and reading books.

" + }, + + { + + "serial": 182587, + "name": "Ryan Vinyard", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_182587.jpg", + "url": "http://highway1.io/", + "position": "Engineering Lead", + "affiliation": "Highway1", + "twitter": "", + "bio": "

Ryan is the Engineering Lead at Highway1, a hardware-focused startup accelerator located in San Francisco under parent company PCH International. He is a Mechanical Engineer who came to PCH through its consulting arm Lime Lab, where he developed consumer products for Fortune 500 brands. Previous to PCH, Ryan worked at startups in the cleantech and electric vehicle space where he developed novel powertrain, motor control, and thermal systems. Ryan holds a B.S. in Product Design from Stanford University.

" + }, + + { + + "serial": 174073, + "name": "Robert Virding", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_174073.jpg", + "url": "https://www.erlang-solutions.com/", + "position": "Principal Language Expert", + "affiliation": "Erlang Solutions Ltd.", + "twitter": "rvirding", + "bio": "

Robert is Principal Language Expert at Erlang Solutions Ltd. He was one of the early members of the Ericsson Computer Science Lab, and co-inventor of the Erlang language. He took part in the original system design and contributed much of the original libraries, as well as to the current compiler. While at the lab he also did a lot of work on the implementation of logic and functional languages, as well as garbage collection.

\n

Robert’s passion is language implementation and he created a Lisp Flavored Erlang (LFE) and Luerl, leveraging his intimate knowledge of the Erlang environment, compiler and VM. He did reactive programming long before it became a buzz word. He is also rumored to be the best Erlang teacher on this planet.

\n

Robert has worked as an entrepreneur and was one of the co-founders of one of the first Erlang startups (Bluetail). He worked a number of years at the Swedish Defence Materiel Administration (FMV) Modelling and Simulations Group. And he co-authored the first book (Prentice-Hall) on Erlang, and is regularly invited to teach and present throughout the world.

" + }, + + { + + "serial": 46737, + "name": "Karsten Wade", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_46737.jpg", + "url": "http://quaid.fedorapeople.org/", + "position": "Sr. Community Gardener", + "affiliation": "Red Hat/Fedora Project", + "twitter": null, + "bio": "

Since 2000 Karsten has been teaching and living the open source way. As a member of Red Hat’s Open Source and Standards team, he helps with community activities in projects Red Hat is involved in. As a 19 year IT industry veteran, Karsten has worked most sides of common business equations as an IS manager, professional services consultant, technical writer, and developer advocate. As of 2013, Karsten has been working on the CentOS Project as a new Board member, Red Hat liaison on the Board, and engineering team manager. You’ll see him getting involved in infrastructure, documentation, and distro building. He blogs here, microblogs here , and is found on IRC as ‘quaid’.

\n

Karsten lives in his hometown of Santa Cruz, CA with his wife and two daughters on their small urban farm, Fairy-Tale Farm, where they focus on growing their own food and nurturing sustainable community living. Most recently, Karsten has been a partner in a collectively-run business of people-powered transportation, Santa Cruz Pedicab, and some weekends you’ll find him taking tourists and late-nighters around downtown.

" + }, + + { + + "serial": 39928, + "name": "Ken Walker", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_39928.jpg", + "url": "https://kenwalker.github.io/", + "position": "Shopify Storefront Editor", + "affiliation": "Shopify Inc.", + "twitter": null, + "bio": "

Ken is the lead for the Open Source Orion project. He aims to make web based development tools match and exceed the capabilities of a desktop IDE and not just for Web applications. His work in developer tools includes a long history from ENVY/Smalltalk, VisualAge for Java, VisualAge Micro Edition, Eclipse and now the JavaScript based client side Orion platform. Previously he was responsible for IBM’s J9 Mobile JVM platform.

" + }, + + { + + "serial": 1158, + "name": "James Ward", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_1158.jpg", + "url": "http://www.jamesward.com/", + "position": "Developer Advocate", + "affiliation": "Typesafe", + "twitter": "_JamesWard", + "bio": "

James Ward works for Typesafe where he teaches developers the Typesafe Platform (Play Framework, Scala, and Akka).

" + }, + + { + + "serial": 6219, + "name": "Simon Wardley", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_6219.jpg", + "url": "http://www.lef.csc.com/", + "position": "Researcher", + "affiliation": "Leading Edge Forum (CSC)", + "twitter": "swardley", + "bio": "

based in the UK, works for CSC\u2019s Leading Edge Forum, a global research and advisory programme. Simon\u2019s focus is on the intersection of strategy, economics and new technologies.

\n

As a geneticist with a love of mathematics and a fascination in economics, Simon has always found himself dealing with complex systems, whether it\u2019s in behavioural patterns, environmental risks of chemical pollution, developing novel computer systems or managing companies. He is a passionate advocate and researcher in the fields of open source, commoditization, innovation, organizational structure and cybernetics.

\n

Simon is a regular presenter at conferences worldwide, and was voted as one of the UK’s top 50 most influential people in IT in ComputerWeekly’s poll in 2011 and 2012.

" + }, + + { + + "serial": 173025, + "name": "Corinne Warnshuis", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_173025.jpg", + "url": "http://girldevelopit.com/", + "position": "Executive Director", + "affiliation": "Girl Develop It", + "twitter": "corinnepw", + "bio": "

Corinne is the Executive Director of Girl Develop It, a nonprofit with chapters in 36 cities that exists to offer affordable classes for women to learn web and software development. She moved to Philadelphia in 2011 after graduating from University of California, Santa Cruz.

" + }, + + { + + "serial": 171565, + "name": "Phil Webb", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_171565.jpg", + "url": "http://spring.io/", + "position": "Spring Framework Commiter", + "affiliation": "Pivotal", + "twitter": "phillip_webb", + "bio": "

Phil Webb is a Spring Framework developer and co-creator of the Spring Boot project. Prior to joining Pivotal and relocating to California, Phil worked for a number of UK technology companies.

" + }, + + { + + "serial": 4146, + "name": "Emma Jane Westby", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_4146.jpg", + "url": null, + "position": "Author / Educator", + "affiliation": "Freelance", + "twitter": "emmajanehw", + "bio": "

\ufeffEmma Jane Westby (n\u00e9e Hogbin) has been working as a web developer since 1996, and has been participating in FOSS communities for over a decade. She’s authored two books on Drupal (including the ever-popular Front End Drupal), and contributed technical reviews, and articles to many more publications. Passionate about information, and knowledge acquisition, Emma Jane teaches Web-based technologies online, at her local community college, and at conferences around the world. She is well-known in the Drupal community for her Drupal socks and their GPLed pattern. In her spare time, Emma Jane crafts, keeps bees, and likes to drink Scotch. You can find her at emmajane.net.

" + }, + + { + + "serial": 141524, + "name": "Langdon White", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_141524.jpg", + "url": "http://red.ht/rheldevelop", + "position": "RHEL Developer Evangelist", + "affiliation": "Red Hat", + "twitter": "langdonwhite", + "bio": "

Evangelist for the Red Hat Enterprise Linux platform and it\u2019s associated Developer Program. Has spent 15 years architecting and implementing high-impact software systems for companies ranging from startups to large companies.

" + }, + + { + + "serial": 142111, + "name": "Sarah White", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_142111.jpg", + "url": "http://asciidoctor.org/", + "position": "Founder, Content Strategist", + "affiliation": "Asciidoctor, OpenDevise", + "twitter": "carbonfray", + "bio": "

Sarah is the co-founder of OpenDevise. She’s passionate about helping open source projects find practical yet fun ways to communicate with their users and contributors.

\n

Long ago, in a not-too-distant galaxy, she assessed hazardous waste sites and tracked pesticide routes through watersheds. So she knows a thing or two about identifying and eradicating stuff that kills projects, including poor documentation.

" + }, + + { + + "serial": 54107, + "name": "Dustin Whittle", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_54107.jpg", + "url": "http://dustinwhittle.com/", + "position": "Developer Evangelist", + "affiliation": "AppDynamics", + "twitter": "dustinwhittle", + "bio": "

Dustin Whittle is a Developer Evangelist at AppDynamics focused on helping organizations manage their application performance. Before joining AppDynamics, Dustin was CTO at Kwarter, a consultant at SensioLabs, and developer evangelist at Yahoo!. He has experience building and leading engineering teams and working with developers and partners to scale up. When Dustin isn’t working he enjoys flying, sailing, diving, golfing, and travelling around the world. Find out more at dustinwhittle.com.

" + }, + + { + + "serial": 172895, + "name": "Glen Wiley", + "photo": null, + "url": "http://verisigninc.com/", + "position": "Sr. Engineer", + "affiliation": "Verisign, Inc.", + "twitter": "glenswiley", + "bio": "

Glen spent seven years serving as the systems architect for the DNS resolution platforms for the largest domain in the world (.COM and two of the Internet root servers). He currently works in an R&D group at Verisign where he contributes to Internet standards and builds proof of concepts exploring new products and technologies. After more than 25 years in the industry Glen brings a rich blend of history and hands on experience to the talk.

" + }, + + { + + "serial": 6574, + "name": "Eric Wilhelm", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_6574.jpg", + "url": "http://scratchcomputing.com/", + "position": "programmer", + "affiliation": "Cisco, Inc.", + "twitter": "", + "bio": "

Eric Wilhelm is a programmer at Cisco, Inc. He is a father of two, former leader of the Portland Perl Mongers, author of many CPAN modules, and a contributor to several open source projects. He has spoken numerous times at OSCON and local user groups.

" + }, + + { + + "serial": 182808, + "name": "Kjerstin Williams", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_182808.jpg", + "url": "http://appliedminds.com/", + "position": "Project Manager, Robotics and Intelligent Systems", + "affiliation": "Applied Minds", + "twitter": "kjkjerstin", + "bio": "

Dr. Kjerstin ‘KJ’ Williams directs the robotics and intelligent systems efforts at Applied Minds in Glendale, California. Kjerstin has delivered a wide variety of invited lectures and demonstrations on topics ranging from biologically-inspired design to computer vision in venues ranging from academic conferences to science museums
\nto the main stage at O’Reilly Media’s Maker Faire. Her current research interests include multi-modal perception strategies and the design and control of truly field-deployable, intelligent systems.

\n

Also, she\u2019s a fantastic jazz singer with KJ and the Conspirators in Los Angeles.

" + }, + + { + + "serial": 183908, + "name": "Cedric Williams", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_183908.jpg", + "url": "http://www.paypal.com/", + "position": "Leadership Cohort", + "affiliation": "PayPal", + "twitter": "AskCedricW", + "bio": "

I am a technologist, advocate, and coach. I aspire to nurture products, businesses, and societies that make a difference in people’s lives.

" + }, + + { + + "serial": 122424, + "name": "John Willis", + "photo": null, + "url": "http://www.statelessnetworks.com/", + "position": "VP of Customer Enablement", + "affiliation": "Stateless Networks", + "twitter": null, + "bio": "

John Willis is the VP of Customer Enablement for Stateless Networks. Willis, a 30-year systems management veteran, joined Stateless Networks from Dell where he was Chief DevOps evangelist. Willis, a noted expert on agile philosophies in systems management, came to Dell as part of their Enstratius acquisition. At Enstratius, Willis was the VP of Customer Enablement responsible for product support and services for the multi-cloud management platform. During his career, he has held positions at Opscode and also founded Gulf Breeze Software, an award-winning IBM business partner specializing in deploying Tivoli technology for the enterprise. Willis has authored six IBM Redbooks for IBM on enterprise systems management and was the founder and chief architect at Chain Bridge Systems. He is also co author of the \u201cDevops Cookbook\u201d and the upcoming \u201cNetwork Operations\u201d published by O\u201dReilly.

" + }, + + { + + "serial": 150073, + "name": "Mike Wolfson", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_150073.jpg", + "url": "http://www.mikewolfson.com/", + "position": "Android Architect", + "affiliation": "Epocrates", + "twitter": "mikewolfson", + "bio": "

Mike is a passionate mobile designer/developer working out of Phoenix. He has been working in the software field for more than 15 years, and with Android since its introduction. Currently, he develops Android applications for the health care field, and is a Lead Mobile Developer at Athenahealth\\Epocrates. He has a few successful apps in the Market and is an active contributor to the tech community, including organizing the local GDG.

\n

Mike has spoken about Android and mobile development at a variety of conferences and user groups (including Oscon). When he is not geeking out about phones, he enjoys the outdoors (snowboarding, hiking, scuba diving), collecting PEZ dispensers, and chasing his young (but quick) daughter.

" + }, + + { + + "serial": 175481, + "name": "Jeff Wolski", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_175481.jpg", + "url": "http://www.uber.com/", + "position": "Staff Engineer", + "affiliation": "Uber", + "twitter": "", + "bio": "

Jeff Wolski has over 10 years of experience in tech and has worked in a variety of environments: investment banking, hospital operating rooms, broadcast television graphics, flash sale retailers and now Uber. At Uber, he spends his time hacking on Node.js, learning how to Scala, building Graphite dashboards and drinking jasmine green tea. He originally hails from New York and has recently made the trek over to San Francisco, a city which he finds amazingly beautiful.

" + }, + + { + + "serial": 153565, + "name": "Fangjin Yang", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_153565.jpg", + "url": "http://www.metamarkets.com/", + "position": "Engineer", + "affiliation": "Metamarkets", + "twitter": null, + "bio": "

Fangjin is one of the main Druid contributors and one of the first developers to Metamarkets. He mainly works on core infrastructure and platform development. Fangjin comes to Metamarkets from Cisco where he developed diagnostic algorithms for various routers and switches. He holds a BASc in Electrical Engineering and a MASc in Computer Engineering from the University of Waterloo, Canada.

" + }, + + { + + "serial": 173466, + "name": "Alex Yong", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_173466.jpg", + "url": null, + "position": "Student", + "affiliation": "Saint Joseph's College (Indiana)", + "twitter": "", + "bio": "

Alex is a graduating senior at Saint Joseph’s College, where he is majoring in Computer Science. After graduation, he has accepted a position working in network operations at Indiana University.

" + }, + + { + + "serial": 171450, + "name": "Danny Yuan", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_171450.jpg", + "url": null, + "position": "", + "affiliation": "Netflix Inc", + "twitter": "g9yuayon", + "bio": "

Danny is an architect and software developer in Netflix’s Platform Engineering team. He works on Netflix’s distributed crypto service, data pipeline, and real-time analytics. He is the owner of Netflix’s open sourced data pipeline, Suro, and also the owner of Netflix’s predictive autoscaling engine.

" + }, + + { + + "serial": 169932, + "name": "Tobias Zander", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_169932.jpg", + "url": "http://www.tobiaszander.de/", + "position": "CTO", + "affiliation": "Sitewards GmbH", + "twitter": "airbone42", + "bio": "

Tobias is the CTO and a partner at Sitewards in Frankfurt, who specialize in e-commerce solutions.
\nPreviously he was well regarded as a freelance consultant and software architect. Over the past years he has built up a development team at Sitewards that thrives to be at the cutting edge of web development.
\nWith passion of inspiring developers he has taken part in and spoken at conferences such as Meet Magento, Developers Paradise, IPC, User groups and Unconferences. He has also had articles published in t3n and PHPMagazin.

" + }, + + { + + "serial": 173468, + "name": "Danilo Zekovic", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_173468.jpg", + "url": null, + "position": "Student", + "affiliation": "Saint Joseph's College (Indiana)", + "twitter": "", + "bio": "

Danilo is a sophomore at Saint Joseph’s College, from Novi Sad, Serbia. His interests are web programming, teaching programming, and anything in general that involves programming.

" + }, + + { + + "serial": 141590, + "name": "Carina C. Zona", + "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_141590.jpg", + "url": "http://zerovm.com/", + "position": "Community Manager", + "affiliation": "ZeroVM", + "twitter": "cczona", + "bio": "

Carina C. Zona is a developer and advocate. Her day job is as the community manager for open source software ZeroVM. She has also been a teacher & organizer for many tech women\u2019s organizations. Carina is the founder of @CallbackWomen, an initiative to connect first-time speakers with conferences. She is also a certified sex educator. In her spare time, she engineers baked goods. Using git. Because she loves versioning that much.

" + } + + ], + + "venues": [ + + { + "serial": 1448, + "name": "Portland Ballroom", + "category": "Conference Venues" + }, + + { + "serial": 1449, + "name": "Portland 251", + "category": "Conference Venues" + }, + + { + "serial": 1450, + "name": "Portland 252", + "category": "Conference Venues" + }, + + { + "serial": 1452, + "name": "Portland 255", + "category": "Conference Venues" + }, + + { + "serial": 1475, + "name": "Portland 256", + "category": "Conference Venues" + }, + + { + "serial": 1454, + "name": "D135", + "category": "Conference Venues" + }, + + { + "serial": 1456, + "name": "D136", + "category": "Conference Venues" + }, + + { + "serial": 1457, + "name": "D137/138", + "category": "Conference Venues" + }, + + { + "serial": 1458, + "name": "D139/140", + "category": "Conference Venues" + }, + + { + "serial": 1470, + "name": "E143/144", + "category": "Conference Venues" + }, + + { + "serial": 1464, + "name": "E144", + "category": "Conference Venues" + }, + + { + "serial": 1465, + "name": "E145", + "category": "Conference Venues" + }, + + { + "serial": 1471, + "name": "E145/146", + "category": "Conference Venues" + }, + + { + "serial": 1466, + "name": "E146", + "category": "Conference Venues" + }, + + { + "serial": 1451, + "name": "E147/148", + "category": "Conference Venues" + }, + + { + "serial": 1587, + "name": "D133/135", + "category": "Conference Venues" + }, + + { + "serial": 1607, + "name": "E 141/142", + "category": "Conference Venues" + }, + + { + "serial": 1459, + "name": "F150", + "category": "Conference Venues" + }, + + { + "serial": 1462, + "name": "F151", + "category": "Conference Venues" + }, + + { + "serial": 1460, + "name": "E141", + "category": "Conference Venues" + }, + + { + "serial": 1461, + "name": "E142", + "category": "Conference Venues" + }, + + { + "serial": 1463, + "name": "E143", + "category": "Conference Venues" + }, + + { + "serial": 1507, + "name": "D130", + "category": "Conference Venues" + }, + + { + "serial": 1520, + "name": " D135", + "category": "Conference Venues" + }, + + { + "serial": 1525, + "name": "Portland Ballroom", + "category": "Conference Venues" + }, + + { + "serial": 1467, + "name": "Exhibit Hall D", + "category": "Conference Venues" + }, + + { + "serial": 1469, + "name": "Exhibit Hall C", + "category": "Conference Venues" + }, + + { + "serial": 1468, + "name": "Exhibit Hall E", + "category": "Conference Venues" + }, + + { + "serial": 1453, + "name": "E147 / E148", + "category": "Conference Venues" + }, + + { + "serial": 1473, + "name": "Portland Ballroom Foyer", + "category": "Conference Venues" + }, + + { + "serial": 1474, + "name": "Expo Hall", + "category": "Conference Venues" + }, + + { + "serial": 1522, + "name": "See BoF Schedule Onsite for Locations", + "category": "Conference Venues" + }, + + { + "serial": 1521, + "name": "Offsite", + "category": "Conference Venues" + }, + + { + "serial": 1523, + "name": "On Your Own", + "category": "Conference Venues" + }, + + { + "serial": 1524, + "name": "Expo Hall", + "category": "Conference Venues" + }, + + { + "serial": 1626, + "name": "Puppet Labs Headquarters, 926 Northwest 13th Avenue, #210", + "category": "Conference Venues" + }, + + { + "serial": 1526, + "name": "Exhibit Hall D", + "category": "Conference Venues" + }, + + { + "serial": 1546, + "name": "Expo Hall (Table A)", + "category": "Conference Venues" + }, + + { + "serial": 1547, + "name": "Expo Hall (Table B)", + "category": "Conference Venues" + }, + + { + "serial": 1548, + "name": "Expo Hall (Table C)", + "category": "Conference Venues" + }, + + { + "serial": 1579, + "name": "Expo Hall (Table D)", + "category": "Conference Venues" + }, + + { + "serial": 1580, + "name": "Expo Hall (Table E)", + "category": "Conference Venues" + }, + + { + "serial": 1596, + "name": "Office Hours Expo Hall", + "category": "Conference Venues" + }, + + { + "serial": 1597, + "name": "Author Signings (O'Reilly Booth) ", + "category": "Conference Venues" + }, + + { + "serial": 1549, + "name": "Author Signing A", + "category": "Conference Venues" + }, + + { + "serial": 1606, + "name": "123 NE Third Ave.", + "category": "Conference Venues" + }, + + { + "serial": 1550, + "name": "Author Signing B", + "category": "Conference Venues" + }, + + { + "serial": 1598, + "name": "Author Signing C", + "category": "Conference Venues" + }, + + { + "serial": 1583, + "name": " Union Pine, 525 SE Pine St.", + "category": "Conference Venues" + }, + + { + "serial": 1584, + "name": "F 150", + "category": "Conference Venues" + }, + + { + "serial": 1585, + "name": "Exhibit Hall B", + "category": "Conference Venues" + }, + + { + "serial": 1574, + "name": "Bottom of the stairs by the E Rooms", + "category": "Conference Venues" + }, + + { + "serial": 1578, + "name": "Jupiter Hotel", + "category": "Conference Venues" + } + + ] + }} + + + + diff --git a/23-dyn-attr-prop/oscon/demo_schedule2.py b/23-dyn-attr-prop/oscon/demo_schedule2.py new file mode 100755 index 0000000..12fd440 --- /dev/null +++ b/23-dyn-attr-prop/oscon/demo_schedule2.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python3 + +import shelve + +from schedule_v2 import DB_NAME, CONFERENCE, load_db +from schedule_v2 import DbRecord, Event + +with shelve.open(DB_NAME) as db: + if CONFERENCE not in db: + load_db(db) + + DbRecord.set_db(db) + event = DbRecord.fetch('event.33950') + print(event) + print(event.venue) + print(event.venue.name) + for spkr in event.speakers: + print(f'{spkr.serial}:', spkr.name) + + print(repr(Event.venue)) + + event2 = DbRecord.fetch('event.33451') + print(event2) + print(event2.fetch) + print(event2.venue) \ No newline at end of file diff --git a/23-dyn-attr-prop/oscon/explore0.py b/23-dyn-attr-prop/oscon/explore0.py new file mode 100644 index 0000000..1ea7843 --- /dev/null +++ b/23-dyn-attr-prop/oscon/explore0.py @@ -0,0 +1,65 @@ +""" +explore0.py: Script to explore the OSCON schedule feed + +# tag::EXPLORE0_DEMO[] + >>> import json + >>> raw_feed = json.load(open('data/osconfeed.json')) + >>> feed = FrozenJSON(raw_feed) # <1> + >>> len(feed.Schedule.speakers) # <2> + 357 + >>> feed.keys() + dict_keys(['Schedule']) + >>> sorted(feed.Schedule.keys()) # <3> + ['conferences', 'events', 'speakers', 'venues'] + >>> for key, value in sorted(feed.Schedule.items()): # <4> + ... print(f'{len(value):3} {key}') + ... + 1 conferences + 484 events + 357 speakers + 53 venues + >>> feed.Schedule.speakers[-1].name # <5> + 'Carina C. Zona' + >>> talk = feed.Schedule.events[40] + >>> type(talk) # <6> + + >>> talk.name + 'There *Will* Be Bugs' + >>> talk.speakers # <7> + [3471, 5199] + >>> talk.flavor # <8> + Traceback (most recent call last): + ... + KeyError: 'flavor' + +# end::EXPLORE0_DEMO[] + +""" + +# tag::EXPLORE0[] +from collections import abc + + +class FrozenJSON: + """A read-only façade for navigating a JSON-like object + using attribute notation + """ + + def __init__(self, mapping): + self.__data = dict(mapping) # <1> + + def __getattr__(self, name): # <2> + try: + return getattr(self.__data, name) # <3> + except AttributeError: + return FrozenJSON.build(self.__data[name]) # <4> + + @classmethod + def build(cls, obj): # <5> + if isinstance(obj, abc.Mapping): # <6> + return cls(obj) + elif isinstance(obj, abc.MutableSequence): # <7> + return [cls.build(item) for item in obj] + else: # <8> + return obj +# end::EXPLORE0[] diff --git a/23-dyn-attr-prop/oscon/explore1.py b/23-dyn-attr-prop/oscon/explore1.py new file mode 100644 index 0000000..ecb53b0 --- /dev/null +++ b/23-dyn-attr-prop/oscon/explore1.py @@ -0,0 +1,78 @@ +""" +explore1.py: Script to explore the OSCON schedule feed + + >>> import json + >>> raw_feed = json.load(open('data/osconfeed.json')) + >>> feed = FrozenJSON(raw_feed) + >>> len(feed.Schedule.speakers) + 357 + >>> sorted(feed.Schedule.keys()) + ['conferences', 'events', 'speakers', 'venues'] + >>> for key, value in sorted(feed.Schedule.items()): + ... print(f'{len(value):3} {key}') + ... + 1 conferences + 484 events + 357 speakers + 53 venues + >>> feed.Schedule.speakers[-1].name + 'Carina C. Zona' + >>> talk = feed.Schedule.events[40] + >>> type(talk) + + >>> talk.name + 'There *Will* Be Bugs' + >>> talk.speakers + [3471, 5199] + >>> talk.flavor + Traceback (most recent call last): + ... + KeyError: 'flavor' + +Handle keywords by appending a `_`. + +# tag::EXPLORE1_DEMO[] + + >>> grad = FrozenJSON({'name': 'Jim Bo', 'class': 1982}) + >>> grad.name + 'Jim Bo' + >>> grad.class_ + 1982 + +# end::EXPLORE1_DEMO[] + +""" + +from collections import abc +import keyword + + +class FrozenJSON: + """A read-only façade for navigating a JSON-like object + using attribute notation + """ + +# tag::EXPLORE1[] + def __init__(self, mapping): + self.__data = {} + for key, value in mapping.items(): + if keyword.iskeyword(key): # <1> + key += '_' + self.__data[key] = value +# end::EXPLORE1[] + + def __getattr__(self, name): + if hasattr(self.__data, name): + return getattr(self.__data, name) + else: + return FrozenJSON.build(self.__data[name]) + + @classmethod + def build(cls, obj): + if isinstance(obj, abc.Mapping): + return cls(obj) + elif isinstance(obj, abc.MutableSequence): + return [cls.build(item) for item in obj] + else: # <8> + return obj + diff --git a/23-dyn-attr-prop/oscon/explore2.py b/23-dyn-attr-prop/oscon/explore2.py new file mode 100644 index 0000000..8cb9348 --- /dev/null +++ b/23-dyn-attr-prop/oscon/explore2.py @@ -0,0 +1,54 @@ +""" +explore2.py: Script to explore the OSCON schedule feed + + >>> import json + >>> raw_feed = json.load(open('data/osconfeed.json')) + >>> feed = FrozenJSON(raw_feed) + >>> len(feed.Schedule.speakers) + 357 + >>> sorted(feed.Schedule.keys()) + ['conferences', 'events', 'speakers', 'venues'] + >>> feed.Schedule.speakers[-1].name + 'Carina C. Zona' + >>> talk = feed.Schedule.events[40] + >>> talk.name + 'There *Will* Be Bugs' + >>> talk.speakers + [3471, 5199] + >>> talk.flavor + Traceback (most recent call last): + ... + KeyError: 'flavor' + +""" + +# tag::EXPLORE2[] +from collections import abc +import keyword + +class FrozenJSON: + """A read-only façade for navigating a JSON-like object + using attribute notation + """ + + def __new__(cls, arg): # <1> + if isinstance(arg, abc.Mapping): + return super().__new__(cls) # <2> + elif isinstance(arg, abc.MutableSequence): # <3> + return [cls(item) for item in arg] + else: + return arg + + def __init__(self, mapping): + self.__data = {} + for key, value in mapping.items(): + if keyword.iskeyword(key): + key += '_' + self.__data[key] = value + + def __getattr__(self, name): + if hasattr(self.__data, name): + return getattr(self.__data, name) + else: + return FrozenJSON(self.__data[name]) # <4> +# end::EXPLORE2[] diff --git a/23-dyn-attr-prop/oscon/osconfeed-sample.json b/23-dyn-attr-prop/oscon/osconfeed-sample.json new file mode 100644 index 0000000..dbbd7c9 --- /dev/null +++ b/23-dyn-attr-prop/oscon/osconfeed-sample.json @@ -0,0 +1,32 @@ +{ "Schedule": + { "conferences": [{"serial": 115 }], + "events": [ + { "serial": 34505, + "name": "Why Schools Don´t Use Open Source to Teach Programming", + "event_type": "40-minute conference session", + "time_start": "2014-07-23 11:30:00", + "time_stop": "2014-07-23 12:10:00", + "venue_serial": 1462, + "description": "Aside from the fact that high school programming...", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34505", + "speakers": [157509], + "categories": ["Education"] } + ], + "speakers": [ + { "serial": 157509, + "name": "Robert Lefkowitz", + "photo": null, + "url": "http://sharewave.com/", + "position": "CTO", + "affiliation": "Sharewave", + "twitter": "sharewaveteam", + "bio": "Robert ´r0ml´ Lefkowitz is the CTO at Sharewave, a startup..." } + ], + "venues": [ + { "serial": 1462, + "name": "F151", + "category": "Conference Venues" } + ] + } +} + \ No newline at end of file diff --git a/23-dyn-attr-prop/oscon/osconfeed_explore.rst b/23-dyn-attr-prop/oscon/osconfeed_explore.rst new file mode 100644 index 0000000..3507d08 --- /dev/null +++ b/23-dyn-attr-prop/oscon/osconfeed_explore.rst @@ -0,0 +1,20 @@ +>>> import json +>>> with open('data/osconfeed.json') as fp: +... feed = json.load(fp) # <1> +>>> sorted(feed['Schedule'].keys()) # <2> +['conferences', 'events', 'speakers', 'venues'] +>>> for key, value in sorted(feed['Schedule'].items()): +... print(f'{len(value):3} {key}') # <3> +... + 1 conferences +484 events +357 speakers + 53 venues +>>> feed['Schedule']['speakers'][-1]['name'] # <4> +'Carina C. Zona' +>>> feed['Schedule']['speakers'][-1]['serial'] # <5> +141590 +>>> feed['Schedule']['events'][40]['name'] +'There *Will* Be Bugs' +>>> feed['Schedule']['events'][40]['speakers'] # <6> +[3471, 5199] diff --git a/23-dyn-attr-prop/oscon/runtests.sh b/23-dyn-attr-prop/oscon/runtests.sh new file mode 100755 index 0000000..1f778c7 --- /dev/null +++ b/23-dyn-attr-prop/oscon/runtests.sh @@ -0,0 +1,2 @@ +#!/bin/bash +pytest --doctest-modules $2 $1 test_$1 \ No newline at end of file diff --git a/23-dyn-attr-prop/oscon/schedule_v1.py b/23-dyn-attr-prop/oscon/schedule_v1.py new file mode 100644 index 0000000..1be6c41 --- /dev/null +++ b/23-dyn-attr-prop/oscon/schedule_v1.py @@ -0,0 +1,39 @@ +""" +schedule_v1.py: traversing OSCON schedule data + +# tag::SCHEDULE1_DEMO[] + >>> records = load(JSON_PATH) # <1> + >>> speaker = records['speaker.3471'] # <2> + >>> speaker # <3> + + >>> speaker.name, speaker.twitter # <4> + ('Anna Martelli Ravenscroft', 'annaraven') + +# end::SCHEDULE1_DEMO[] + +""" + +# tag::SCHEDULE1[] +import json + +JSON_PATH = 'data/osconfeed.json' + +class Record: + def __init__(self, **kwargs): + self.__dict__.update(kwargs) # <1> + + def __repr__(self): + cls_name = self.__class__.__name__ + return f'<{cls_name} serial={self.serial!r}>' # <2> + +def load(path=JSON_PATH): + records = {} # <3> + with open(path) as fp: + raw_data = json.load(fp) # <4> + for collection, raw_records in raw_data['Schedule'].items(): # <5> + record_type = collection[:-1] # <6> + for raw_record in raw_records: + key = f'{record_type}.{raw_record["serial"]}' # <7> + records[key] = Record(**raw_record) # <8> + return records +# end::SCHEDULE1[] diff --git a/23-dyn-attr-prop/oscon/schedule_v2.py b/23-dyn-attr-prop/oscon/schedule_v2.py new file mode 100644 index 0000000..14f1ed4 --- /dev/null +++ b/23-dyn-attr-prop/oscon/schedule_v2.py @@ -0,0 +1,76 @@ +""" +schedule_v2.py: property to get venue linked to an event + +# tag::SCHEDULE2_DEMO[] + >>> event = Record.fetch('event.33950') # <1> + >>> event # <2> + + >>> event.venue # <3> + + >>> event.venue.name # <4> + 'Portland 251' + >>> event.venue_serial # <5> + 1449 + +# end::SCHEDULE2_DEMO[] +""" + +# tag::SCHEDULE2_RECORD[] +import json +import inspect # <1> + +JSON_PATH = 'data/osconfeed.json' + +class Record: + + __index = None # <2> + + def __init__(self, **kwargs): + self.__dict__.update(kwargs) + + def __repr__(self): + cls_name = self.__class__.__name__ + return f'<{cls_name} serial={self.serial!r}>' + + @staticmethod # <3> + def fetch(key): + if Record.__index is None: # <4> + Record.__index = load() + return Record.__index[key] # <5> +# end::SCHEDULE2_RECORD[] + + +# tag::SCHEDULE2_EVENT[] +class Event(Record): # <1> + + def __repr__(self): + if hasattr(self, 'name'): # <2> + cls_name = self.__class__.__name__ + return f'<{cls_name} {self.name!r}>' + else: + return super().__repr__() + + @property + def venue(self): + key = f'venue.{self.venue_serial}' + return self.__class__.fetch(key) # <3> +# end::SCHEDULE2_EVENT[] + +# tag::SCHEDULE2_LOAD[] +def load(path=JSON_PATH): + records = {} + with open(path) as fp: + raw_data = json.load(fp) + for collection, raw_records in raw_data['Schedule'].items(): + record_type = collection[:-1] # <1> + cls_name = record_type.capitalize() # <2> + cls = globals().get(cls_name, Record) # <3> + if inspect.isclass(cls) and issubclass(cls, Record): # <4> + factory = cls # <5> + else: + factory = Record # <6> + for raw_record in raw_records: # <7> + key = f'{record_type}.{raw_record["serial"]}' + records[key] = factory(**raw_record) # <8> + return records +# end::SCHEDULE2_LOAD[] diff --git a/23-dyn-attr-prop/oscon/schedule_v3.py b/23-dyn-attr-prop/oscon/schedule_v3.py new file mode 100644 index 0000000..c443a39 --- /dev/null +++ b/23-dyn-attr-prop/oscon/schedule_v3.py @@ -0,0 +1,86 @@ +""" +schedule_v3.py: property to get list of event speakers + + >>> event = Record.fetch('event.33950') + >>> event + + >>> event.venue + + >>> event.venue_serial + 1449 + >>> event.venue.name + 'Portland 251' + +# tag::SCHEDULE3_DEMO[] + >>> for spkr in event.speakers: + ... print(f'{spkr.serial}: {spkr.name}') + 3471: Anna Martelli Ravenscroft + 5199: Alex Martelli + +# end::SCHEDULE3_DEMO[] +""" + +import json +import inspect + +JSON_PATH = 'data/osconfeed.json' + +class Record: + + __index = None + + def __init__(self, **kwargs): + self.__dict__.update(kwargs) + + def __repr__(self): + cls_name = self.__class__.__name__ + return f'<{cls_name} serial={self.serial!r}>' + + @staticmethod + def fetch(key): + if Record.__index is None: + Record.__index = load() + return Record.__index[key] + + +class Event(Record): + + def __repr__(self): + if hasattr(self, 'name'): # <3> + cls_name = self.__class__.__name__ + return f'<{cls_name} {self.name!r}>' + else: + return super().__repr__() # <4> + + @property + def venue(self): + key = f'venue.{self.venue_serial}' + return self.__class__.fetch(key) + +# tag::SCHEDULE3_SPEAKERS[] + @property + def speakers(self): + spkr_serials = self.__dict__['speakers'] # <1> + fetch = self.__class__.fetch + return [fetch(f'speaker.{key}') + for key in spkr_serials] # <2> + +# end::SCHEDULE3_SPEAKERS[] + + +def load(path=JSON_PATH): + records = {} + with open(path) as fp: + raw_data = json.load(fp) + for collection, raw_records in raw_data['Schedule'].items(): + record_type = collection[:-1] + cls_name = record_type.capitalize() + cls = globals().get(cls_name, Record) + if inspect.isclass(cls) and issubclass(cls, Record): + factory = cls + else: + factory = Record + for raw_record in raw_records: + key = f'{record_type}.{raw_record["serial"]}' + records[key] = factory(**raw_record) + return records diff --git a/23-dyn-attr-prop/oscon/schedule_v4.py b/23-dyn-attr-prop/oscon/schedule_v4.py new file mode 100644 index 0000000..a609917 --- /dev/null +++ b/23-dyn-attr-prop/oscon/schedule_v4.py @@ -0,0 +1,94 @@ +""" +schedule_v4.py: homegrown cached property for speakers + + >>> event = Record.fetch('event.33950') + +# tag::SCHEDULE4_DEMO[] + >>> event # <1> + + >>> event.venue # <2> + + >>> event.venue.name # <3> + 'Portland 251' + >>> for spkr in event.speakers: # <4> + ... print(f'{spkr.serial}: {spkr.name}') + ... + 3471: Anna Martelli Ravenscroft + 5199: Alex Martelli + +# end::SCHEDULE4_DEMO[] +""" + +import json +import inspect + +JSON_PATH = 'data/osconfeed.json' + +class Record: + + __index = None + + def __init__(self, **kwargs): + self.__dict__.update(kwargs) + + def __repr__(self): + cls_name = self.__class__.__name__ + return f'<{cls_name} serial={self.serial!r}>' + + @staticmethod + def fetch(key): + if Record.__index is None: + Record.__index = load() + return Record.__index[key] + + +# tag::SCHEDULE4_INIT[] +class Event(Record): + + def __init__(self, **kwargs): + self.__speaker_objs = None + super().__init__(**kwargs) + +# end::SCHEDULE4_INIT[] + + def __repr__(self): + if hasattr(self, 'name'): + cls_name = self.__class__.__name__ + return f'<{cls_name} {self.name!r}>' + else: + return super().__repr__() # <4> + + @property + def venue(self): + key = f'venue.{self.venue_serial}' + return self.__class__.fetch(key) + +# tag::SCHEDULE4_CACHE[] + @property + def speakers(self): + if self.__speaker_objs is None: + spkr_serials = self.__dict__['speakers'] + fetch = self.__class__.fetch + self.__speaker_objs = [fetch(f'speaker.{key}') + for key in spkr_serials] + return self.__speaker_objs + +# end::SCHEDULE4_CACHE[] + + +def load(path=JSON_PATH): + records = {} + with open(path) as fp: + raw_data = json.load(fp) + for collection, raw_records in raw_data['Schedule'].items(): + record_type = collection[:-1] + cls_name = record_type.capitalize() + cls = globals().get(cls_name, Record) + if inspect.isclass(cls) and issubclass(cls, Record): + factory = cls + else: + factory = Record + for raw_record in raw_records: + key = f'{record_type}.{raw_record["serial"]}' + records[key] = factory(**raw_record) + return records diff --git a/23-dyn-attr-prop/oscon/schedule_v4_hasattr.py b/23-dyn-attr-prop/oscon/schedule_v4_hasattr.py new file mode 100644 index 0000000..93a12d1 --- /dev/null +++ b/23-dyn-attr-prop/oscon/schedule_v4_hasattr.py @@ -0,0 +1,86 @@ +""" +schedule_v4.py: homegrown cached property for speakers + + >>> event = Record.fetch('event.33950') + +# tag::SCHEDULE4_DEMO[] + >>> event # <1> + + >>> event.venue # <2> + + >>> event.venue.name # <3> + 'Portland 251' + >>> for spkr in event.speakers: # <4> + ... print(f'{spkr.serial}: {spkr.name}') + 3471: Anna Martelli Ravenscroft + 5199: Alex Martelli + +# end::SCHEDULE4_DEMO[] +""" + +import json +import inspect + +JSON_PATH = 'data/osconfeed.json' + +class Record: + + __index = None + + def __init__(self, **kwargs): + self.__dict__.update(kwargs) + + def __repr__(self): + cls_name = self.__class__.__name__ + return f'<{cls_name} serial={self.serial!r}>' + + @staticmethod + def fetch(key): + if Record.__index is None: + Record.__index = load() + return Record.__index[key] + + +class Event(Record): + + def __repr__(self): + if hasattr(self, 'name'): + cls_name = self.__class__.__name__ + return f'<{cls_name} {self.name!r}>' + else: + return super().__repr__() # <4> + + @property + def venue(self): + key = f'venue.{self.venue_serial}' + return self.__class__.fetch(key) + +# tag::SCHEDULE4_HASATTR_CACHE[] + @property + def speakers(self): + if not hasattr(self, '__speaker_objs'): # <1> + spkr_serials = self.__dict__['speakers'] + fetch = self.__class__.fetch + self.__speaker_objs = [fetch(f'speaker.{key}') + for key in spkr_serials] + return self.__speaker_objs # <2> + +# end::SCHEDULE4_HASATTR_CACHE[] + + +def load(path=JSON_PATH): + records = {} + with open(path) as fp: + raw_data = json.load(fp) + for collection, raw_records in raw_data['Schedule'].items(): + record_type = collection[:-1] + cls_name = record_type.capitalize() + cls = globals().get(cls_name, Record) + if inspect.isclass(cls) and issubclass(cls, Record): + factory = cls + else: + factory = Record + for raw_record in raw_records: + key = f'{record_type}.{raw_record["serial"]}' + records[key] = factory(**raw_record) + return records diff --git a/23-dyn-attr-prop/oscon/schedule_v5.py b/23-dyn-attr-prop/oscon/schedule_v5.py new file mode 100644 index 0000000..2517e57 --- /dev/null +++ b/23-dyn-attr-prop/oscon/schedule_v5.py @@ -0,0 +1,89 @@ +""" +schedule_v5.py: cached properties using functools + + >>> event = Record.fetch('event.33950') + >>> event + + >>> event.venue + + >>> event.venue_serial + 1449 + >>> event.venue.name + 'Portland 251' + +# tag::SCHEDULE3_DEMO[] + >>> for spkr in event.speakers: # <3> + ... print(f'{spkr.serial}: {spkr.name}') + ... + 3471: Anna Martelli Ravenscroft + 5199: Alex Martelli + +# end::SCHEDULE3_DEMO[] +""" + +import json +import inspect + +from functools import cached_property, cache + +JSON_PATH = 'data/osconfeed.json' + +class Record: + + __index = None + + def __init__(self, **kwargs): + self.__dict__.update(kwargs) + + def __repr__(self): + cls_name = self.__class__.__name__ + return f'<{cls_name} serial={self.serial!r}>' + + @staticmethod + def fetch(key): + if Record.__index is None: + Record.__index = load() + return Record.__index[key] + + +class Event(Record): + + def __repr__(self): + if hasattr(self, 'name'): + cls_name = self.__class__.__name__ + return f'<{cls_name} {self.name!r}>' + else: + return super().__repr__() + +# tag::SCHEDULE5_CACHED_PROPERTY[] + @cached_property + def venue(self): + key = f'venue.{self.venue_serial}' + return self.__class__.fetch(key) +# end::SCHEDULE5_CACHED_PROPERTY[] +# tag::SCHEDULE5_PROPERTY_OVER_CACHE[] + @property # <1> + @cache # <2> + def speakers(self): + spkr_serials = self.__dict__['speakers'] + fetch = self.__class__.fetch + return [fetch(f'speaker.{key}') + for key in spkr_serials] +# end::SCHEDULE5_PROPERTY_OVER_CACHE[] + +def load(path=JSON_PATH): + records = {} + with open(path) as fp: + raw_data = json.load(fp) + for collection, raw_records in raw_data['Schedule'].items(): + record_type = collection[:-1] + cls_name = record_type.capitalize() + cls = globals().get(cls_name, Record) + if inspect.isclass(cls) and issubclass(cls, Record): + factory = cls + else: + factory = Record + for raw_record in raw_records: + key = f'{record_type}.{raw_record["serial"]}' + records[key] = factory(**raw_record) + return records diff --git a/23-dyn-attr-prop/oscon/test_schedule_v1.py b/23-dyn-attr-prop/oscon/test_schedule_v1.py new file mode 100644 index 0000000..643f04f --- /dev/null +++ b/23-dyn-attr-prop/oscon/test_schedule_v1.py @@ -0,0 +1,24 @@ +import pytest + +import schedule_v1 as schedule + + +@pytest.yield_fixture +def records(): + yield schedule.load(schedule.JSON_PATH) + + +def test_load(records): + assert len(records) == 895 + + +def test_record_attr_access(): + rec = schedule.Record(spam=99, eggs=12) + assert rec.spam == 99 + assert rec.eggs == 12 + + +def test_venue_record(records): + venue = records['venue.1469'] + assert venue.serial == 1469 + assert venue.name == 'Exhibit Hall C' diff --git a/23-dyn-attr-prop/oscon/test_schedule_v2.py b/23-dyn-attr-prop/oscon/test_schedule_v2.py new file mode 100644 index 0000000..a11b24d --- /dev/null +++ b/23-dyn-attr-prop/oscon/test_schedule_v2.py @@ -0,0 +1,48 @@ +import pytest + +import schedule_v2 as schedule + +@pytest.yield_fixture +def records(): + yield schedule.load(schedule.JSON_PATH) + + +def test_load(records): + assert len(records) == 895 + + +def test_record_attr_access(): + rec = schedule.Record(spam=99, eggs=12) + assert rec.spam == 99 + assert rec.eggs == 12 + + +def test_venue_record(records): + venue = records['venue.1469'] + assert venue.serial == 1469 + assert venue.name == 'Exhibit Hall C' + + +def test_fetch_speaker_record(): + speaker = schedule.Record.fetch('speaker.3471') + assert speaker.name == 'Anna Martelli Ravenscroft' + + +def test_event_type(): + event = schedule.Record.fetch('event.33950') + assert type(event) is schedule.Event + assert repr(event) == "" + + +def test_event_repr(): + event = schedule.Record.fetch('event.33950') + assert repr(event) == "" + event2 = schedule.Event(serial=77, kind='show') + assert repr(event2) == '' + + +def test_event_venue(): + event = schedule.Record.fetch('event.33950') + assert event.venue_serial == 1449 + assert event.venue == schedule.Record.fetch('venue.1449') + assert event.venue.name == 'Portland 251' diff --git a/23-dyn-attr-prop/oscon/test_schedule_v3.py b/23-dyn-attr-prop/oscon/test_schedule_v3.py new file mode 100644 index 0000000..1b7ea75 --- /dev/null +++ b/23-dyn-attr-prop/oscon/test_schedule_v3.py @@ -0,0 +1,59 @@ +import pytest + +import schedule_v3 as schedule + +@pytest.yield_fixture +def records(): + yield schedule.load(schedule.JSON_PATH) + + +def test_load(records): + assert len(records) == 895 + + +def test_record_attr_access(): + rec = schedule.Record(spam=99, eggs=12) + assert rec.spam == 99 + assert rec.eggs == 12 + + +def test_venue_record(records): + venue = records['venue.1469'] + assert venue.serial == 1469 + assert venue.name == 'Exhibit Hall C' + + +def test_fetch_speaker_record(): + speaker = schedule.Record.fetch('speaker.3471') + assert speaker.name == 'Anna Martelli Ravenscroft' + + +def test_event_type(): + event = schedule.Record.fetch('event.33950') + assert type(event) is schedule.Event + assert repr(event) == "" + + +def test_event_repr(): + event = schedule.Record.fetch('event.33950') + assert repr(event) == "" + event2 = schedule.Event(serial=77, kind='show') + assert repr(event2) == '' + + +def test_event_venue(): + event = schedule.Record.fetch('event.33950') + assert event.venue_serial == 1449 + assert event.venue == schedule.Record.fetch('venue.1449') + assert event.venue.name == 'Portland 251' + + +def test_event_speakers(): + event = schedule.Record.fetch('event.33950') + assert len(event.speakers) == 2 + anna, alex = [schedule.Record.fetch(f'speaker.{s}') for s in (3471, 5199)] + assert event.speakers == [anna, alex] + +def test_event_no_speakers(): + event = schedule.Record.fetch('event.36848') + assert event.speakers == [] \ No newline at end of file diff --git a/23-dyn-attr-prop/oscon/test_schedule_v4.py b/23-dyn-attr-prop/oscon/test_schedule_v4.py new file mode 100644 index 0000000..1e5560a --- /dev/null +++ b/23-dyn-attr-prop/oscon/test_schedule_v4.py @@ -0,0 +1,59 @@ +import pytest + +import schedule_v4 as schedule + +@pytest.yield_fixture +def records(): + yield schedule.load(schedule.JSON_PATH) + + +def test_load(records): + assert len(records) == 895 + + +def test_record_attr_access(): + rec = schedule.Record(spam=99, eggs=12) + assert rec.spam == 99 + assert rec.eggs == 12 + + +def test_venue_record(records): + venue = records['venue.1469'] + assert venue.serial == 1469 + assert venue.name == 'Exhibit Hall C' + + +def test_fetch_speaker_record(): + speaker = schedule.Record.fetch('speaker.3471') + assert speaker.name == 'Anna Martelli Ravenscroft' + + +def test_event_type(): + event = schedule.Record.fetch('event.33950') + assert type(event) is schedule.Event + assert repr(event) == "" + + +def test_event_repr(): + event = schedule.Record.fetch('event.33950') + assert repr(event) == "" + event2 = schedule.Event(serial=77, kind='show') + assert repr(event2) == '' + + +def test_event_venue(): + event = schedule.Record.fetch('event.33950') + assert event.venue_serial == 1449 + assert event.venue == schedule.Record.fetch('venue.1449') + assert event.venue.name == 'Portland 251' + + +def test_event_speakers(): + event = schedule.Record.fetch('event.33950') + assert len(event.speakers) == 2 + anna, alex = [schedule.Record.fetch(f'speaker.{s}') for s in (3471, 5199)] + assert event.speakers == [anna, alex] + +def test_event_no_speakers(): + event = schedule.Record.fetch('event.36848') + assert event.speakers == [] \ No newline at end of file diff --git a/23-dyn-attr-prop/oscon/test_schedule_v5.py b/23-dyn-attr-prop/oscon/test_schedule_v5.py new file mode 100644 index 0000000..2151844 --- /dev/null +++ b/23-dyn-attr-prop/oscon/test_schedule_v5.py @@ -0,0 +1,59 @@ +import pytest + +import schedule_v5 as schedule + +@pytest.yield_fixture +def records(): + yield schedule.load(schedule.JSON_PATH) + + +def test_load(records): + assert len(records) == 895 + + +def test_record_attr_access(): + rec = schedule.Record(spam=99, eggs=12) + assert rec.spam == 99 + assert rec.eggs == 12 + + +def test_venue_record(records): + venue = records['venue.1469'] + assert venue.serial == 1469 + assert venue.name == 'Exhibit Hall C' + + +def test_fetch_speaker_record(): + speaker = schedule.Record.fetch('speaker.3471') + assert speaker.name == 'Anna Martelli Ravenscroft' + + +def test_event_type(): + event = schedule.Record.fetch('event.33950') + assert type(event) is schedule.Event + assert repr(event) == "" + + +def test_event_repr(): + event = schedule.Record.fetch('event.33950') + assert repr(event) == "" + event2 = schedule.Event(serial=77, kind='show') + assert repr(event2) == '' + + +def test_event_venue(): + event = schedule.Record.fetch('event.33950') + assert event.venue_serial == 1449 + assert event.venue == schedule.Record.fetch('venue.1449') + assert event.venue.name == 'Portland 251' + + +def test_event_speakers(): + event = schedule.Record.fetch('event.33950') + assert len(event.speakers) == 2 + anna, alex = [schedule.Record.fetch(f'speaker.{s}') for s in (3471, 5199)] + assert event.speakers == [anna, alex] + +def test_event_no_speakers(): + event = schedule.Record.fetch('event.36848') + assert event.speakers == [] \ No newline at end of file diff --git a/23-dyn-attr-prop/pseudo_construction.py b/23-dyn-attr-prop/pseudo_construction.py new file mode 100644 index 0000000..5656192 --- /dev/null +++ b/23-dyn-attr-prop/pseudo_construction.py @@ -0,0 +1,10 @@ +# pseudo-code for object construction +def make(the_class, some_arg): + new_object = the_class.__new__(some_arg) + if isinstance(new_object, the_class): + the_class.__init__(new_object, some_arg) + return new_object + +# the following statements are roughly equivalent +x = Foo('bar') +x = make(Foo, 'bar') diff --git a/24-descriptor/README.rst b/24-descriptor/README.rst new file mode 100644 index 0000000..d0cad28 --- /dev/null +++ b/24-descriptor/README.rst @@ -0,0 +1,4 @@ +Sample code for Chapter 20 - "Attribute descriptors" + +From the book "Fluent Python" by Luciano Ramalho (O'Reilly, 2015) +http://shop.oreilly.com/product/0636920032519.do diff --git a/24-descriptor/bulkfood/bulkfood_v3.py b/24-descriptor/bulkfood/bulkfood_v3.py new file mode 100644 index 0000000..d68887c --- /dev/null +++ b/24-descriptor/bulkfood/bulkfood_v3.py @@ -0,0 +1,67 @@ +""" + +A line item for a bulk food order has description, weight and price fields:: + + >>> raisins = LineItem('Golden raisins', 10, 6.95) + >>> raisins.weight, raisins.description, raisins.price + (10, 'Golden raisins', 6.95) + +A ``subtotal`` method gives the total price for that line item:: + + >>> raisins.subtotal() + 69.5 + +The weight of a ``LineItem`` must be greater than 0:: + + >>> raisins.weight = -20 + Traceback (most recent call last): + ... + ValueError: weight must be > 0 + +Negative or 0 price is not acceptable either:: + + >>> truffle = LineItem('White truffle', 100, 0) + Traceback (most recent call last): + ... + ValueError: price must be > 0 + +No change was made:: + + >>> raisins.weight + 10 + +""" + + +# tag::LINEITEM_QUANTITY_V3[] +class Quantity: # <1> + + def __init__(self, storage_name): + self.storage_name = storage_name # <2> + + def __set__(self, instance, value): # <3> + if value > 0: + instance.__dict__[self.storage_name] = value # <4> + else: + msg = f'{self.storage_name} must be > 0' + raise ValueError(msg) + + def __get__(self, instance, owner): # <5> + return instance.__dict__[self.storage_name] + + +# end::LINEITEM_QUANTITY_V3[] + +# tag::LINEITEM_V3[] +class LineItem: + weight = Quantity('weight') # <1> + price = Quantity('price') # <2> + + def __init__(self, description, weight, price): # <3> + self.description = description + self.weight = weight + self.price = price + + def subtotal(self): + return self.weight * self.price +# end::LINEITEM_V3[] diff --git a/24-descriptor/bulkfood/bulkfood_v4.py b/24-descriptor/bulkfood/bulkfood_v4.py new file mode 100644 index 0000000..b672e89 --- /dev/null +++ b/24-descriptor/bulkfood/bulkfood_v4.py @@ -0,0 +1,70 @@ +""" + +A line item for a bulk food order has description, weight and price fields:: + + >>> raisins = LineItem('Golden raisins', 10, 6.95) + >>> raisins.weight, raisins.description, raisins.price + (10, 'Golden raisins', 6.95) + +A ``subtotal`` method gives the total price for that line item:: + + >>> raisins.subtotal() + 69.5 + +The weight of a ``LineItem`` must be greater than 0:: + + >>> raisins.weight = -20 + Traceback (most recent call last): + ... + ValueError: weight must be > 0 + +No change was made:: + + >>> raisins.weight + 10 + +Negative or 0 price is not acceptable either:: + + >>> truffle = LineItem('White truffle', 100, 0) + Traceback (most recent call last): + ... + ValueError: price must be > 0 + +If the descriptor is accessed in the class, the descriptor object is +returned: + + >>> LineItem.weight # doctest: +ELLIPSIS + + >>> LineItem.weight.storage_name + 'weight' + +""" + + +# tag::LINEITEM_V4[] +class Quantity: + + def __set_name__(self, owner, name): # <1> + self.storage_name = name # <2> + + def __set__(self, instance, value): # <3> + if value > 0: + instance.__dict__[self.storage_name] = value + else: + msg = f'{self.storage_name} must be > 0' + raise ValueError(msg) + + # no __get__ needed + +class LineItem: + weight = Quantity() # <5> + price = Quantity() + + def __init__(self, description, weight, price): + self.description = description + self.weight = weight + self.price = price + + def subtotal(self): + return self.weight * self.price +# end::LINEITEM_V4[] diff --git a/24-descriptor/bulkfood/bulkfood_v4c.py b/24-descriptor/bulkfood/bulkfood_v4c.py new file mode 100644 index 0000000..0ca0a52 --- /dev/null +++ b/24-descriptor/bulkfood/bulkfood_v4c.py @@ -0,0 +1,58 @@ +""" + +A line item for a bulk food order has description, weight and price fields:: + + >>> raisins = LineItem('Golden raisins', 10, 6.95) + >>> raisins.weight, raisins.description, raisins.price + (10, 'Golden raisins', 6.95) + +A ``subtotal`` method gives the total price for that line item:: + + >>> raisins.subtotal() + 69.5 + +The weight of a ``LineItem`` must be greater than 0:: + + >>> raisins.weight = -20 + Traceback (most recent call last): + ... + ValueError: weight must be > 0 + +No change was made:: + + >>> raisins.weight + 10 + +Negative or 0 price is not acceptable either:: + + >>> truffle = LineItem('White truffle', 100, 0) + Traceback (most recent call last): + ... + ValueError: price must be > 0 + +If the descriptor is accessed in the class, the descriptor object is +returned: + + >>> LineItem.weight # doctest: +ELLIPSIS + + >>> LineItem.weight.storage_name + 'weight' + +""" + +# tag::LINEITEM_V4C[] +import model_v4c as model # <1> + + +class LineItem: + weight = model.Quantity() # <2> + price = model.Quantity() + + def __init__(self, description, weight, price): + self.description = description + self.weight = weight + self.price = price + + def subtotal(self): + return self.weight * self.price +# end::LINEITEM_V4C[] diff --git a/24-descriptor/bulkfood/bulkfood_v5.py b/24-descriptor/bulkfood/bulkfood_v5.py new file mode 100644 index 0000000..602dadd --- /dev/null +++ b/24-descriptor/bulkfood/bulkfood_v5.py @@ -0,0 +1,72 @@ +""" + +A line item for a bulk food order has description, weight and price fields:: + + >>> raisins = LineItem('Golden raisins', 10, 6.95) + >>> raisins.weight, raisins.description, raisins.price + (10, 'Golden raisins', 6.95) + +A ``subtotal`` method gives the total price for that line item:: + + >>> raisins.subtotal() + 69.5 + +The weight of a ``LineItem`` must be greater than 0:: + + >>> raisins.weight = -20 + Traceback (most recent call last): + ... + ValueError: weight must be > 0 + +No change was made:: + + >>> raisins.weight + 10 + +Negative or 0 price is not acceptable either:: + + >>> truffle = LineItem('White truffle', 100, 0) + Traceback (most recent call last): + ... + ValueError: price must be > 0 + +If the descriptor is accessed in the class, the descriptor object is +returned: + + >>> LineItem.weight # doctest: +ELLIPSIS + + >>> LineItem.weight.storage_name + 'weight' + +The `NonBlank` descriptor prevents empty or blank strings to be used +for the description: + + >>> br_nuts = LineItem('Brazil Nuts', 10, 34.95) + >>> br_nuts.description = ' ' + Traceback (most recent call last): + ... + ValueError: description cannot be blank + >>> void = LineItem('', 1, 1) + Traceback (most recent call last): + ... + ValueError: description cannot be blank + + +""" + +# tag::LINEITEM_V5[] +import model_v5 as model # <1> + +class LineItem: + description = model.NonBlank() # <2> + weight = model.Quantity() + price = model.Quantity() + + def __init__(self, description, weight, price): + self.description = description + self.weight = weight + self.price = price + + def subtotal(self): + return self.weight * self.price +# end::LINEITEM_V5[] diff --git a/24-descriptor/bulkfood/model_v4c.py b/24-descriptor/bulkfood/model_v4c.py new file mode 100644 index 0000000..b827ee0 --- /dev/null +++ b/24-descriptor/bulkfood/model_v4c.py @@ -0,0 +1,13 @@ +# BEGIN MODEL_V4 +class Quantity: + + def __set_name__(self, owner, name): # <1> + self.storage_name = name # <2> + + def __set__(self, instance, value): # <3> + if value > 0: + instance.__dict__[self.storage_name] = value + else: + msg = f'{self.storage_name} must be > 0' + raise ValueError(msg) +# END MODEL_V4 diff --git a/24-descriptor/bulkfood/model_v5.py b/24-descriptor/bulkfood/model_v5.py new file mode 100644 index 0000000..647de18 --- /dev/null +++ b/24-descriptor/bulkfood/model_v5.py @@ -0,0 +1,36 @@ +# tag::MODEL_V5_VALIDATED_ABC[] +import abc + +class Validated(abc.ABC): + + def __set_name__(self, owner, name): + self.storage_name = name + + def __set__(self, instance, value): + value = self.validate(self.storage_name, value) # <1> + instance.__dict__[self.storage_name] = value # <2> + + @abc.abstractmethod + def validate(self, name, value): # <3> + """return validated value or raise ValueError""" +# end::MODEL_V5_VALIDATED_ABC[] + +# tag::MODEL_V5_VALIDATED_SUB[] +class Quantity(Validated): + """a number greater than zero""" + + def validate(self, name, value): # <1> + if value <= 0: + raise ValueError(f'{name} must be > 0') + return value + + +class NonBlank(Validated): + """a string with at least one non-space character""" + + def validate(self, name, value): + value = value.strip() + if len(value) == 0: + raise ValueError(f'{name} cannot be blank') + return value # <8> +# end::MODEL_V5_VALIDATED_SUB[] diff --git a/24-descriptor/descriptorkinds.py b/24-descriptor/descriptorkinds.py new file mode 100644 index 0000000..0e2710c --- /dev/null +++ b/24-descriptor/descriptorkinds.py @@ -0,0 +1,204 @@ +""" +Overriding descriptor (a.k.a. data descriptor or enforced descriptor): + +# tag::DESCR_KINDS_DEMO1[] + + >>> obj = Managed() # <1> + >>> obj.over # <2> + -> Overriding.__get__(, , + ) + >>> Managed.over # <3> + -> Overriding.__get__(, None, ) + >>> obj.over = 7 # <4> + -> Overriding.__set__(, , 7) + >>> obj.over # <5> + -> Overriding.__get__(, , + ) + >>> obj.__dict__['over'] = 8 # <6> + >>> vars(obj) # <7> + {'over': 8} + >>> obj.over # <8> + -> Overriding.__get__(, , + ) + +# end::DESCR_KINDS_DEMO1[] + +Overriding descriptor without ``__get__``: + +(these tests are reproduced below without +ELLIPSIS directives for inclusion in the book; +look for DESCR_KINDS_DEMO2) + + >>> obj.over_no_get # doctest: +ELLIPSIS + + >>> Managed.over_no_get # doctest: +ELLIPSIS + + >>> obj.over_no_get = 7 + -> OverridingNoGet.__set__(, , 7) + >>> obj.over_no_get # doctest: +ELLIPSIS + + >>> obj.__dict__['over_no_get'] = 9 + >>> obj.over_no_get + 9 + >>> obj.over_no_get = 7 + -> OverridingNoGet.__set__(, , 7) + >>> obj.over_no_get + 9 + +Non-overriding descriptor (a.k.a. non-data descriptor or shadowable descriptor): + +# tag::DESCR_KINDS_DEMO3[] + + >>> obj = Managed() + >>> obj.non_over # <1> + -> NonOverriding.__get__(, , + ) + >>> obj.non_over = 7 # <2> + >>> obj.non_over # <3> + 7 + >>> Managed.non_over # <4> + -> NonOverriding.__get__(, None, ) + >>> del obj.non_over # <5> + >>> obj.non_over # <6> + -> NonOverriding.__get__(, , + ) + +# end::DESCR_KINDS_DEMO3[] + +No descriptor type survives being overwritten on the class itself: + +# tag::DESCR_KINDS_DEMO4[] + + >>> obj = Managed() # <1> + >>> Managed.over = 1 # <2> + >>> Managed.over_no_get = 2 + >>> Managed.non_over = 3 + >>> obj.over, obj.over_no_get, obj.non_over # <3> + (1, 2, 3) + +# end::DESCR_KINDS_DEMO4[] + +Methods are non-overriding descriptors: + + >>> obj.spam # doctest: +ELLIPSIS + > + >>> Managed.spam # doctest: +ELLIPSIS + + >>> obj.spam() + -> Managed.spam() + >>> Managed.spam() + Traceback (most recent call last): + ... + TypeError: spam() missing 1 required positional argument: 'self' + >>> Managed.spam(obj) + -> Managed.spam() + >>> Managed.spam.__get__(obj) # doctest: +ELLIPSIS + > + >>> obj.spam.__func__ is Managed.spam + True + >>> obj.spam = 7 + >>> obj.spam + 7 + + +""" + +""" +NOTE: These tests are here because I can't add callouts after +ELLIPSIS +directives and if doctest runs them without +ELLIPSIS I get test failures. + +# tag::DESCR_KINDS_DEMO2[] + + >>> obj.over_no_get # <1> + <__main__.OverridingNoGet object at 0x665bcc> + >>> Managed.over_no_get # <2> + <__main__.OverridingNoGet object at 0x665bcc> + >>> obj.over_no_get = 7 # <3> + -> OverridingNoGet.__set__(, , 7) + >>> obj.over_no_get # <4> + <__main__.OverridingNoGet object at 0x665bcc> + >>> obj.__dict__['over_no_get'] = 9 # <5> + >>> obj.over_no_get # <6> + 9 + >>> obj.over_no_get = 7 # <7> + -> OverridingNoGet.__set__(, , 7) + >>> obj.over_no_get # <8> + 9 + +# end::DESCR_KINDS_DEMO2[] + +Methods are non-overriding descriptors: + +# tag::DESCR_KINDS_DEMO5[] + + >>> obj = Managed() + >>> obj.spam # <1> + > + >>> Managed.spam # <2> + + >>> obj.spam = 7 # <3> + >>> obj.spam + 7 + +# end::DESCR_KINDS_DEMO5[] + +""" + +# tag::DESCR_KINDS[] + +### auxiliary functions for display only ### + +def cls_name(obj_or_cls): + cls = type(obj_or_cls) + if cls is type: + cls = obj_or_cls + return cls.__name__.split('.')[-1] + +def display(obj): + cls = type(obj) + if cls is type: + return ''.format(obj.__name__) + elif cls in [type(None), int]: + return repr(obj) + else: + return '<{} object>'.format(cls_name(obj)) + +def print_args(name, *args): + pseudo_args = ', '.join(display(x) for x in args) + print('-> {}.__{}__({})'.format(cls_name(args[0]), name, pseudo_args)) + + +### essential classes for this example ### + +class Overriding: # <1> + """a.k.a. data descriptor or enforced descriptor""" + + def __get__(self, instance, owner): + print_args('get', self, instance, owner) # <2> + + def __set__(self, instance, value): + print_args('set', self, instance, value) + + +class OverridingNoGet: # <3> + """an overriding descriptor without ``__get__``""" + + def __set__(self, instance, value): + print_args('set', self, instance, value) + + +class NonOverriding: # <4> + """a.k.a. non-data or shadowable descriptor""" + + def __get__(self, instance, owner): + print_args('get', self, instance, owner) + + +class Managed: # <5> + over = Overriding() + over_no_get = OverridingNoGet() + non_over = NonOverriding() + + def spam(self): # <6> + print('-> Managed.spam({})'.format(display(self))) + +# end::DESCR_KINDS[] diff --git a/24-descriptor/descriptorkinds_dump.py b/24-descriptor/descriptorkinds_dump.py new file mode 100644 index 0000000..d03a61f --- /dev/null +++ b/24-descriptor/descriptorkinds_dump.py @@ -0,0 +1,169 @@ + + +""" +Overriding descriptor (a.k.a. data descriptor or enforced descriptor): + + >>> obj = Model() + >>> obj.over # doctest: +ELLIPSIS + Overriding.__get__() invoked with args: + self = + instance = + owner = + >>> Model.over # doctest: +ELLIPSIS + Overriding.__get__() invoked with args: + self = + instance = None + owner = + + +An overriding descriptor cannot be shadowed by assigning to an instance: + + >>> obj = Model() + >>> obj.over = 7 # doctest: +ELLIPSIS + Overriding.__set__() invoked with args: + self = + instance = + value = 7 + >>> obj.over # doctest: +ELLIPSIS + Overriding.__get__() invoked with args: + self = + instance = + owner = + + +Not even by poking the attribute into the instance ``__dict__``: + + >>> obj.__dict__['over'] = 8 + >>> obj.over # doctest: +ELLIPSIS + Overriding.__get__() invoked with args: + self = + instance = + owner = + >>> vars(obj) + {'over': 8} + +Overriding descriptor without ``__get__``: + + >>> obj.over_no_get # doctest: +ELLIPSIS + + >>> Model.over_no_get # doctest: +ELLIPSIS + + >>> obj.over_no_get = 7 # doctest: +ELLIPSIS + OverridingNoGet.__set__() invoked with args: + self = + instance = + value = 7 + >>> obj.over_no_get # doctest: +ELLIPSIS + + + +Poking the attribute into the instance ``__dict__`` means you can read the new +value for the attribute, but setting it still triggers ``__set__``: + + >>> obj.__dict__['over_no_get'] = 9 + >>> obj.over_no_get + 9 + >>> obj.over_no_get = 7 # doctest: +ELLIPSIS + OverridingNoGet.__set__() invoked with args: + self = + instance = + value = 7 + >>> obj.over_no_get + 9 + + +Non-overriding descriptor (a.k.a. non-data descriptor or shadowable descriptor): + + >>> obj = Model() + >>> obj.non_over # doctest: +ELLIPSIS + NonOverriding.__get__() invoked with args: + self = + instance = + owner = + >>> Model.non_over # doctest: +ELLIPSIS + NonOverriding.__get__() invoked with args: + self = + instance = None + owner = + + +A non-overriding descriptor can be shadowed by assigning to an instance: + + >>> obj.non_over = 7 + >>> obj.non_over + 7 + + +Methods are non-over descriptors: + + >>> obj.spam # doctest: +ELLIPSIS + > + >>> Model.spam # doctest: +ELLIPSIS + + >>> obj.spam() # doctest: +ELLIPSIS + Model.spam() invoked with arg: + self = + >>> obj.spam = 7 + >>> obj.spam + 7 + + +No descriptor type survives being overwritten on the class itself: + + >>> Model.over = 1 + >>> obj.over + 1 + >>> Model.over_no_get = 2 + >>> obj.over_no_get + 2 + >>> Model.non_over = 3 + >>> obj.non_over + 7 + +""" + +# BEGIN DESCRIPTORKINDS +def print_args(name, *args): # <1> + cls_name = args[0].__class__.__name__ + arg_names = ['self', 'instance', 'owner'] + if name == 'set': + arg_names[-1] = 'value' + print('{}.__{}__() invoked with args:'.format(cls_name, name)) + for arg_name, value in zip(arg_names, args): + print(' {:8} = {}'.format(arg_name, value)) + + +class Overriding: # <2> + """a.k.a. data descriptor or enforced descriptor""" + + def __get__(self, instance, owner): + print_args('get', self, instance, owner) # <3> + + def __set__(self, instance, value): + print_args('set', self, instance, value) + + +class OverridingNoGet: # <4> + """an overriding descriptor without ``__get__``""" + + def __set__(self, instance, value): + print_args('set', self, instance, value) + + +class NonOverriding: # <5> + """a.k.a. non-data or shadowable descriptor""" + + def __get__(self, instance, owner): + print_args('get', self, instance, owner) + + +class Model: # <6> + over = Overriding() + over_no_get = OverridingNoGet() + non_over = NonOverriding() + + def spam(self): # <7> + print('Model.spam() invoked with arg:') + print(' self =', self) + +#END DESCRIPTORKINDS diff --git a/24-descriptor/method_is_descriptor.py b/24-descriptor/method_is_descriptor.py new file mode 100644 index 0000000..de5b8a2 --- /dev/null +++ b/24-descriptor/method_is_descriptor.py @@ -0,0 +1,41 @@ +""" +# tag::FUNC_DESCRIPTOR_DEMO[] + + >>> word = Text('forward') + >>> word # <1> + Text('forward') + >>> word.reverse() # <2> + Text('drawrof') + >>> Text.reverse(Text('backward')) # <3> + Text('drawkcab') + >>> type(Text.reverse), type(word.reverse) # <4> + (, ) + >>> list(map(Text.reverse, ['repaid', (10, 20, 30), Text('stressed')])) # <5> + ['diaper', (30, 20, 10), Text('desserts')] + >>> Text.reverse.__get__(word) # <6> + + >>> Text.reverse.__get__(None, Text) # <7> + + >>> word.reverse # <8> + + >>> word.reverse.__self__ # <9> + Text('forward') + >>> word.reverse.__func__ is Text.reverse # <10> + True + +# end::FUNC_DESCRIPTOR_DEMO[] +""" + +# tag::FUNC_DESCRIPTOR_EX[] +import collections + + +class Text(collections.UserString): + + def __repr__(self): + return 'Text({!r})'.format(self.data) + + def reverse(self): + return self[::-1] + +# end::FUNC_DESCRIPTOR_EX[] diff --git a/LICENSE b/LICENSE index 75bedcf..a8e5e88 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2014 Luciano Ramalho +Copyright (c) 2021 Luciano Ramalho Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From 1702717182cff9a48beb55b2a9f5618e9bd1da18 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Wed, 24 Feb 2021 18:55:55 -0300 Subject: [PATCH 019/166] ch22 example files --- 22-asyncio/mojifinder/bottle.py | 3771 +++++++++++++++++ 22-asyncio/mojifinder/charindex.py | 89 + 22-asyncio/mojifinder/requirements.txt | 6 + 22-asyncio/mojifinder/static/form.html | 83 + 22-asyncio/mojifinder/tcp_mojifinder.py | 72 + 22-asyncio/mojifinder/web_mojifinder.py | 38 + .../mojifinder/web_mojifinder_bottle.py | 37 + 7 files changed, 4096 insertions(+) create mode 100644 22-asyncio/mojifinder/bottle.py create mode 100755 22-asyncio/mojifinder/charindex.py create mode 100644 22-asyncio/mojifinder/requirements.txt create mode 100644 22-asyncio/mojifinder/static/form.html create mode 100755 22-asyncio/mojifinder/tcp_mojifinder.py create mode 100644 22-asyncio/mojifinder/web_mojifinder.py create mode 100755 22-asyncio/mojifinder/web_mojifinder_bottle.py diff --git a/22-asyncio/mojifinder/bottle.py b/22-asyncio/mojifinder/bottle.py new file mode 100644 index 0000000..96ca3e4 --- /dev/null +++ b/22-asyncio/mojifinder/bottle.py @@ -0,0 +1,3771 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +Bottle is a fast and simple micro-framework for small web applications. It +offers request dispatching (Routes) with url parameter support, templates, +a built-in HTTP Server and adapters for many third party WSGI/HTTP-server and +template engines - all in a single file and with no dependencies other than the +Python Standard Library. + +Homepage and documentation: http://bottlepy.org/ + +Copyright (c) 2016, Marcel Hellkamp. +License: MIT (see LICENSE for details) +""" + +from __future__ import with_statement + +__author__ = 'Marcel Hellkamp' +__version__ = '0.12.18' +__license__ = 'MIT' + +# The gevent server adapter needs to patch some modules before they are imported +# This is why we parse the commandline parameters here but handle them later +if __name__ == '__main__': + from optparse import OptionParser + _cmd_parser = OptionParser(usage="usage: %prog [options] package.module:app") + _opt = _cmd_parser.add_option + _opt("--version", action="store_true", help="show version number.") + _opt("-b", "--bind", metavar="ADDRESS", help="bind socket to ADDRESS.") + _opt("-s", "--server", default='wsgiref', help="use SERVER as backend.") + _opt("-p", "--plugin", action="append", help="install additional plugin/s.") + _opt("--debug", action="store_true", help="start server in debug mode.") + _opt("--reload", action="store_true", help="auto-reload on file changes.") + _cmd_options, _cmd_args = _cmd_parser.parse_args() + if _cmd_options.server and _cmd_options.server.startswith('gevent'): + import gevent.monkey; gevent.monkey.patch_all() + +import base64, cgi, email.utils, functools, hmac, itertools, mimetypes,\ + os, re, subprocess, sys, tempfile, threading, time, warnings, hashlib + +from datetime import date as datedate, datetime, timedelta +from tempfile import TemporaryFile +from traceback import format_exc, print_exc +from inspect import getargspec +from unicodedata import normalize + + +try: from simplejson import dumps as json_dumps, loads as json_lds +except ImportError: # pragma: no cover + try: from json import dumps as json_dumps, loads as json_lds + except ImportError: + try: from django.utils.simplejson import dumps as json_dumps, loads as json_lds + except ImportError: + def json_dumps(data): + raise ImportError("JSON support requires Python 2.6 or simplejson.") + json_lds = json_dumps + + + +# We now try to fix 2.5/2.6/3.1/3.2 incompatibilities. +# It ain't pretty but it works... Sorry for the mess. + +py = sys.version_info +py3k = py >= (3, 0, 0) +py25 = py < (2, 6, 0) +py31 = (3, 1, 0) <= py < (3, 2, 0) + +# Workaround for the missing "as" keyword in py3k. +def _e(): return sys.exc_info()[1] + +# Workaround for the "print is a keyword/function" Python 2/3 dilemma +# and a fallback for mod_wsgi (resticts stdout/err attribute access) +try: + _stdout, _stderr = sys.stdout.write, sys.stderr.write +except IOError: + _stdout = lambda x: sys.stdout.write(x) + _stderr = lambda x: sys.stderr.write(x) + +# Lots of stdlib and builtin differences. +if py3k: + import http.client as httplib + import _thread as thread + from urllib.parse import urljoin, SplitResult as UrlSplitResult + from urllib.parse import urlencode, quote as urlquote, unquote as urlunquote + urlunquote = functools.partial(urlunquote, encoding='latin1') + from http.cookies import SimpleCookie + if py >= (3, 3, 0): + from collections.abc import MutableMapping as DictMixin + from types import ModuleType as new_module + else: + from collections import MutableMapping as DictMixin + from imp import new_module + import pickle + from io import BytesIO + from configparser import ConfigParser + basestring = str + unicode = str + json_loads = lambda s: json_lds(touni(s)) + callable = lambda x: hasattr(x, '__call__') + imap = map + def _raise(*a): raise a[0](a[1]).with_traceback(a[2]) +else: # 2.x + import httplib + import thread + from urlparse import urljoin, SplitResult as UrlSplitResult + from urllib import urlencode, quote as urlquote, unquote as urlunquote + from Cookie import SimpleCookie + from itertools import imap + import cPickle as pickle + from imp import new_module + from StringIO import StringIO as BytesIO + from ConfigParser import SafeConfigParser as ConfigParser + if py25: + msg = "Python 2.5 support may be dropped in future versions of Bottle." + warnings.warn(msg, DeprecationWarning) + from UserDict import DictMixin + def next(it): return it.next() + bytes = str + else: # 2.6, 2.7 + from collections import MutableMapping as DictMixin + unicode = unicode + json_loads = json_lds + eval(compile('def _raise(*a): raise a[0], a[1], a[2]', '', 'exec')) + +# Some helpers for string/byte handling +def tob(s, enc='utf8'): + return s.encode(enc) if isinstance(s, unicode) else bytes(s) +def touni(s, enc='utf8', err='strict'): + return s.decode(enc, err) if isinstance(s, bytes) else unicode(s) +tonat = touni if py3k else tob + +# 3.2 fixes cgi.FieldStorage to accept bytes (which makes a lot of sense). +# 3.1 needs a workaround. +if py31: + from io import TextIOWrapper + class NCTextIOWrapper(TextIOWrapper): + def close(self): pass # Keep wrapped buffer open. + + +# A bug in functools causes it to break if the wrapper is an instance method +def update_wrapper(wrapper, wrapped, *a, **ka): + try: functools.update_wrapper(wrapper, wrapped, *a, **ka) + except AttributeError: pass + + + +# These helpers are used at module level and need to be defined first. +# And yes, I know PEP-8, but sometimes a lower-case classname makes more sense. + +def depr(message, hard=False): + warnings.warn(message, DeprecationWarning, stacklevel=3) + +def makelist(data): # This is just to handy + if isinstance(data, (tuple, list, set, dict)): return list(data) + elif data: return [data] + else: return [] + + +class DictProperty(object): + ''' Property that maps to a key in a local dict-like attribute. ''' + def __init__(self, attr, key=None, read_only=False): + self.attr, self.key, self.read_only = attr, key, read_only + + def __call__(self, func): + functools.update_wrapper(self, func, updated=[]) + self.getter, self.key = func, self.key or func.__name__ + return self + + def __get__(self, obj, cls): + if obj is None: return self + key, storage = self.key, getattr(obj, self.attr) + if key not in storage: storage[key] = self.getter(obj) + return storage[key] + + def __set__(self, obj, value): + if self.read_only: raise AttributeError("Read-Only property.") + getattr(obj, self.attr)[self.key] = value + + def __delete__(self, obj): + if self.read_only: raise AttributeError("Read-Only property.") + del getattr(obj, self.attr)[self.key] + + +class cached_property(object): + ''' A property that is only computed once per instance and then replaces + itself with an ordinary attribute. Deleting the attribute resets the + property. ''' + + def __init__(self, func): + self.__doc__ = getattr(func, '__doc__') + self.func = func + + def __get__(self, obj, cls): + if obj is None: return self + value = obj.__dict__[self.func.__name__] = self.func(obj) + return value + + +class lazy_attribute(object): + ''' A property that caches itself to the class object. ''' + def __init__(self, func): + functools.update_wrapper(self, func, updated=[]) + self.getter = func + + def __get__(self, obj, cls): + value = self.getter(cls) + setattr(cls, self.__name__, value) + return value + + + + + + +############################################################################### +# Exceptions and Events ######################################################## +############################################################################### + + +class BottleException(Exception): + """ A base class for exceptions used by bottle. """ + pass + + + + + + +############################################################################### +# Routing ###################################################################### +############################################################################### + + +class RouteError(BottleException): + """ This is a base class for all routing related exceptions """ + + +class RouteReset(BottleException): + """ If raised by a plugin or request handler, the route is reset and all + plugins are re-applied. """ + +class RouterUnknownModeError(RouteError): pass + + +class RouteSyntaxError(RouteError): + """ The route parser found something not supported by this router. """ + + +class RouteBuildError(RouteError): + """ The route could not be built. """ + + +def _re_flatten(p): + ''' Turn all capturing groups in a regular expression pattern into + non-capturing groups. ''' + if '(' not in p: return p + return re.sub(r'(\\*)(\(\?P<[^>]+>|\((?!\?))', + lambda m: m.group(0) if len(m.group(1)) % 2 else m.group(1) + '(?:', p) + + +class Router(object): + ''' A Router is an ordered collection of route->target pairs. It is used to + efficiently match WSGI requests against a number of routes and return + the first target that satisfies the request. The target may be anything, + usually a string, ID or callable object. A route consists of a path-rule + and a HTTP method. + + The path-rule is either a static path (e.g. `/contact`) or a dynamic + path that contains wildcards (e.g. `/wiki/`). The wildcard syntax + and details on the matching order are described in docs:`routing`. + ''' + + default_pattern = '[^/]+' + default_filter = 're' + + #: The current CPython regexp implementation does not allow more + #: than 99 matching groups per regular expression. + _MAX_GROUPS_PER_PATTERN = 99 + + def __init__(self, strict=False): + self.rules = [] # All rules in order + self._groups = {} # index of regexes to find them in dyna_routes + self.builder = {} # Data structure for the url builder + self.static = {} # Search structure for static routes + self.dyna_routes = {} + self.dyna_regexes = {} # Search structure for dynamic routes + #: If true, static routes are no longer checked first. + self.strict_order = strict + self.filters = { + 're': lambda conf: + (_re_flatten(conf or self.default_pattern), None, None), + 'int': lambda conf: (r'-?\d+', int, lambda x: str(int(x))), + 'float': lambda conf: (r'-?[\d.]+', float, lambda x: str(float(x))), + 'path': lambda conf: (r'.+?', None, None)} + + def add_filter(self, name, func): + ''' Add a filter. The provided function is called with the configuration + string as parameter and must return a (regexp, to_python, to_url) tuple. + The first element is a string, the last two are callables or None. ''' + self.filters[name] = func + + rule_syntax = re.compile('(\\\\*)'\ + '(?:(?::([a-zA-Z_][a-zA-Z_0-9]*)?()(?:#(.*?)#)?)'\ + '|(?:<([a-zA-Z_][a-zA-Z_0-9]*)?(?::([a-zA-Z_]*)'\ + '(?::((?:\\\\.|[^\\\\>]+)+)?)?)?>))') + + def _itertokens(self, rule): + offset, prefix = 0, '' + for match in self.rule_syntax.finditer(rule): + prefix += rule[offset:match.start()] + g = match.groups() + if len(g[0])%2: # Escaped wildcard + prefix += match.group(0)[len(g[0]):] + offset = match.end() + continue + if prefix: + yield prefix, None, None + name, filtr, conf = g[4:7] if g[2] is None else g[1:4] + yield name, filtr or 'default', conf or None + offset, prefix = match.end(), '' + if offset <= len(rule) or prefix: + yield prefix+rule[offset:], None, None + + def add(self, rule, method, target, name=None): + ''' Add a new rule or replace the target for an existing rule. ''' + anons = 0 # Number of anonymous wildcards found + keys = [] # Names of keys + pattern = '' # Regular expression pattern with named groups + filters = [] # Lists of wildcard input filters + builder = [] # Data structure for the URL builder + is_static = True + + for key, mode, conf in self._itertokens(rule): + if mode: + is_static = False + if mode == 'default': mode = self.default_filter + mask, in_filter, out_filter = self.filters[mode](conf) + if not key: + pattern += '(?:%s)' % mask + key = 'anon%d' % anons + anons += 1 + else: + pattern += '(?P<%s>%s)' % (key, mask) + keys.append(key) + if in_filter: filters.append((key, in_filter)) + builder.append((key, out_filter or str)) + elif key: + pattern += re.escape(key) + builder.append((None, key)) + + self.builder[rule] = builder + if name: self.builder[name] = builder + + if is_static and not self.strict_order: + self.static.setdefault(method, {}) + self.static[method][self.build(rule)] = (target, None) + return + + try: + re_pattern = re.compile('^(%s)$' % pattern) + re_match = re_pattern.match + except re.error: + raise RouteSyntaxError("Could not add Route: %s (%s)" % (rule, _e())) + + if filters: + def getargs(path): + url_args = re_match(path).groupdict() + for name, wildcard_filter in filters: + try: + url_args[name] = wildcard_filter(url_args[name]) + except ValueError: + raise HTTPError(400, 'Path has wrong format.') + return url_args + elif re_pattern.groupindex: + def getargs(path): + return re_match(path).groupdict() + else: + getargs = None + + flatpat = _re_flatten(pattern) + whole_rule = (rule, flatpat, target, getargs) + + if (flatpat, method) in self._groups: + if DEBUG: + msg = 'Route <%s %s> overwrites a previously defined route' + warnings.warn(msg % (method, rule), RuntimeWarning) + self.dyna_routes[method][self._groups[flatpat, method]] = whole_rule + else: + self.dyna_routes.setdefault(method, []).append(whole_rule) + self._groups[flatpat, method] = len(self.dyna_routes[method]) - 1 + + self._compile(method) + + def _compile(self, method): + all_rules = self.dyna_routes[method] + comborules = self.dyna_regexes[method] = [] + maxgroups = self._MAX_GROUPS_PER_PATTERN + for x in range(0, len(all_rules), maxgroups): + some = all_rules[x:x+maxgroups] + combined = (flatpat for (_, flatpat, _, _) in some) + combined = '|'.join('(^%s$)' % flatpat for flatpat in combined) + combined = re.compile(combined).match + rules = [(target, getargs) for (_, _, target, getargs) in some] + comborules.append((combined, rules)) + + def build(self, _name, *anons, **query): + ''' Build an URL by filling the wildcards in a rule. ''' + builder = self.builder.get(_name) + if not builder: raise RouteBuildError("No route with that name.", _name) + try: + for i, value in enumerate(anons): query['anon%d'%i] = value + url = ''.join([f(query.pop(n)) if n else f for (n,f) in builder]) + return url if not query else url+'?'+urlencode(query) + except KeyError: + raise RouteBuildError('Missing URL argument: %r' % _e().args[0]) + + def match(self, environ): + ''' Return a (target, url_agrs) tuple or raise HTTPError(400/404/405). ''' + verb = environ['REQUEST_METHOD'].upper() + path = environ['PATH_INFO'] or '/' + target = None + if verb == 'HEAD': + methods = ['PROXY', verb, 'GET', 'ANY'] + else: + methods = ['PROXY', verb, 'ANY'] + + for method in methods: + if method in self.static and path in self.static[method]: + target, getargs = self.static[method][path] + return target, getargs(path) if getargs else {} + elif method in self.dyna_regexes: + for combined, rules in self.dyna_regexes[method]: + match = combined(path) + if match: + target, getargs = rules[match.lastindex - 1] + return target, getargs(path) if getargs else {} + + # No matching route found. Collect alternative methods for 405 response + allowed = set([]) + nocheck = set(methods) + for method in set(self.static) - nocheck: + if path in self.static[method]: + allowed.add(verb) + for method in set(self.dyna_regexes) - allowed - nocheck: + for combined, rules in self.dyna_regexes[method]: + match = combined(path) + if match: + allowed.add(method) + if allowed: + allow_header = ",".join(sorted(allowed)) + raise HTTPError(405, "Method not allowed.", Allow=allow_header) + + # No matching route and no alternative method found. We give up + raise HTTPError(404, "Not found: " + repr(path)) + + + + + + +class Route(object): + ''' This class wraps a route callback along with route specific metadata and + configuration and applies Plugins on demand. It is also responsible for + turing an URL path rule into a regular expression usable by the Router. + ''' + + def __init__(self, app, rule, method, callback, name=None, + plugins=None, skiplist=None, **config): + #: The application this route is installed to. + self.app = app + #: The path-rule string (e.g. ``/wiki/:page``). + self.rule = rule + #: The HTTP method as a string (e.g. ``GET``). + self.method = method + #: The original callback with no plugins applied. Useful for introspection. + self.callback = callback + #: The name of the route (if specified) or ``None``. + self.name = name or None + #: A list of route-specific plugins (see :meth:`Bottle.route`). + self.plugins = plugins or [] + #: A list of plugins to not apply to this route (see :meth:`Bottle.route`). + self.skiplist = skiplist or [] + #: Additional keyword arguments passed to the :meth:`Bottle.route` + #: decorator are stored in this dictionary. Used for route-specific + #: plugin configuration and meta-data. + self.config = ConfigDict().load_dict(config, make_namespaces=True) + + def __call__(self, *a, **ka): + depr("Some APIs changed to return Route() instances instead of"\ + " callables. Make sure to use the Route.call method and not to"\ + " call Route instances directly.") #0.12 + return self.call(*a, **ka) + + @cached_property + def call(self): + ''' The route callback with all plugins applied. This property is + created on demand and then cached to speed up subsequent requests.''' + return self._make_callback() + + def reset(self): + ''' Forget any cached values. The next time :attr:`call` is accessed, + all plugins are re-applied. ''' + self.__dict__.pop('call', None) + + def prepare(self): + ''' Do all on-demand work immediately (useful for debugging).''' + self.call + + @property + def _context(self): + depr('Switch to Plugin API v2 and access the Route object directly.') #0.12 + return dict(rule=self.rule, method=self.method, callback=self.callback, + name=self.name, app=self.app, config=self.config, + apply=self.plugins, skip=self.skiplist) + + def all_plugins(self): + ''' Yield all Plugins affecting this route. ''' + unique = set() + for p in reversed(self.app.plugins + self.plugins): + if True in self.skiplist: break + name = getattr(p, 'name', False) + if name and (name in self.skiplist or name in unique): continue + if p in self.skiplist or type(p) in self.skiplist: continue + if name: unique.add(name) + yield p + + def _make_callback(self): + callback = self.callback + for plugin in self.all_plugins(): + try: + if hasattr(plugin, 'apply'): + api = getattr(plugin, 'api', 1) + context = self if api > 1 else self._context + callback = plugin.apply(callback, context) + else: + callback = plugin(callback) + except RouteReset: # Try again with changed configuration. + return self._make_callback() + if not callback is self.callback: + update_wrapper(callback, self.callback) + return callback + + def get_undecorated_callback(self): + ''' Return the callback. If the callback is a decorated function, try to + recover the original function. ''' + func = self.callback + func = getattr(func, '__func__' if py3k else 'im_func', func) + closure_attr = '__closure__' if py3k else 'func_closure' + while hasattr(func, closure_attr) and getattr(func, closure_attr): + func = getattr(func, closure_attr)[0].cell_contents + return func + + def get_callback_args(self): + ''' Return a list of argument names the callback (most likely) accepts + as keyword arguments. If the callback is a decorated function, try + to recover the original function before inspection. ''' + return getargspec(self.get_undecorated_callback())[0] + + def get_config(self, key, default=None): + ''' Lookup a config field and return its value, first checking the + route.config, then route.app.config.''' + for conf in (self.config, self.app.conifg): + if key in conf: return conf[key] + return default + + def __repr__(self): + cb = self.get_undecorated_callback() + return '<%s %r %r>' % (self.method, self.rule, cb) + + + + + + +############################################################################### +# Application Object ########################################################### +############################################################################### + + +class Bottle(object): + """ Each Bottle object represents a single, distinct web application and + consists of routes, callbacks, plugins, resources and configuration. + Instances are callable WSGI applications. + + :param catchall: If true (default), handle all exceptions. Turn off to + let debugging middleware handle exceptions. + """ + + def __init__(self, catchall=True, autojson=True): + + #: A :class:`ConfigDict` for app specific configuration. + self.config = ConfigDict() + self.config._on_change = functools.partial(self.trigger_hook, 'config') + self.config.meta_set('autojson', 'validate', bool) + self.config.meta_set('catchall', 'validate', bool) + self.config['catchall'] = catchall + self.config['autojson'] = autojson + + #: A :class:`ResourceManager` for application files + self.resources = ResourceManager() + + self.routes = [] # List of installed :class:`Route` instances. + self.router = Router() # Maps requests to :class:`Route` instances. + self.error_handler = {} + + # Core plugins + self.plugins = [] # List of installed plugins. + if self.config['autojson']: + self.install(JSONPlugin()) + self.install(TemplatePlugin()) + + #: If true, most exceptions are caught and returned as :exc:`HTTPError` + catchall = DictProperty('config', 'catchall') + + __hook_names = 'before_request', 'after_request', 'app_reset', 'config' + __hook_reversed = 'after_request' + + @cached_property + def _hooks(self): + return dict((name, []) for name in self.__hook_names) + + def add_hook(self, name, func): + ''' Attach a callback to a hook. Three hooks are currently implemented: + + before_request + Executed once before each request. The request context is + available, but no routing has happened yet. + after_request + Executed once after each request regardless of its outcome. + app_reset + Called whenever :meth:`Bottle.reset` is called. + ''' + if name in self.__hook_reversed: + self._hooks[name].insert(0, func) + else: + self._hooks[name].append(func) + + def remove_hook(self, name, func): + ''' Remove a callback from a hook. ''' + if name in self._hooks and func in self._hooks[name]: + self._hooks[name].remove(func) + return True + + def trigger_hook(self, __name, *args, **kwargs): + ''' Trigger a hook and return a list of results. ''' + return [hook(*args, **kwargs) for hook in self._hooks[__name][:]] + + def hook(self, name): + """ Return a decorator that attaches a callback to a hook. See + :meth:`add_hook` for details.""" + def decorator(func): + self.add_hook(name, func) + return func + return decorator + + def mount(self, prefix, app, **options): + ''' Mount an application (:class:`Bottle` or plain WSGI) to a specific + URL prefix. Example:: + + root_app.mount('/admin/', admin_app) + + :param prefix: path prefix or `mount-point`. If it ends in a slash, + that slash is mandatory. + :param app: an instance of :class:`Bottle` or a WSGI application. + + All other parameters are passed to the underlying :meth:`route` call. + ''' + if isinstance(app, basestring): + depr('Parameter order of Bottle.mount() changed.', True) # 0.10 + + segments = [p for p in prefix.split('/') if p] + if not segments: raise ValueError('Empty path prefix.') + path_depth = len(segments) + + def mountpoint_wrapper(): + try: + request.path_shift(path_depth) + rs = HTTPResponse([]) + def start_response(status, headerlist, exc_info=None): + if exc_info: + try: + _raise(*exc_info) + finally: + exc_info = None + rs.status = status + for name, value in headerlist: rs.add_header(name, value) + return rs.body.append + body = app(request.environ, start_response) + if body and rs.body: body = itertools.chain(rs.body, body) + rs.body = body or rs.body + return rs + finally: + request.path_shift(-path_depth) + + options.setdefault('skip', True) + options.setdefault('method', 'PROXY') + options.setdefault('mountpoint', {'prefix': prefix, 'target': app}) + options['callback'] = mountpoint_wrapper + + self.route('/%s/<:re:.*>' % '/'.join(segments), **options) + if not prefix.endswith('/'): + self.route('/' + '/'.join(segments), **options) + + def merge(self, routes): + ''' Merge the routes of another :class:`Bottle` application or a list of + :class:`Route` objects into this application. The routes keep their + 'owner', meaning that the :data:`Route.app` attribute is not + changed. ''' + if isinstance(routes, Bottle): + routes = routes.routes + for route in routes: + self.add_route(route) + + def install(self, plugin): + ''' Add a plugin to the list of plugins and prepare it for being + applied to all routes of this application. A plugin may be a simple + decorator or an object that implements the :class:`Plugin` API. + ''' + if hasattr(plugin, 'setup'): plugin.setup(self) + if not callable(plugin) and not hasattr(plugin, 'apply'): + raise TypeError("Plugins must be callable or implement .apply()") + self.plugins.append(plugin) + self.reset() + return plugin + + def uninstall(self, plugin): + ''' Uninstall plugins. Pass an instance to remove a specific plugin, a type + object to remove all plugins that match that type, a string to remove + all plugins with a matching ``name`` attribute or ``True`` to remove all + plugins. Return the list of removed plugins. ''' + removed, remove = [], plugin + for i, plugin in list(enumerate(self.plugins))[::-1]: + if remove is True or remove is plugin or remove is type(plugin) \ + or getattr(plugin, 'name', True) == remove: + removed.append(plugin) + del self.plugins[i] + if hasattr(plugin, 'close'): plugin.close() + if removed: self.reset() + return removed + + def reset(self, route=None): + ''' Reset all routes (force plugins to be re-applied) and clear all + caches. If an ID or route object is given, only that specific route + is affected. ''' + if route is None: routes = self.routes + elif isinstance(route, Route): routes = [route] + else: routes = [self.routes[route]] + for route in routes: route.reset() + if DEBUG: + for route in routes: route.prepare() + self.trigger_hook('app_reset') + + def close(self): + ''' Close the application and all installed plugins. ''' + for plugin in self.plugins: + if hasattr(plugin, 'close'): plugin.close() + self.stopped = True + + def run(self, **kwargs): + ''' Calls :func:`run` with the same parameters. ''' + run(self, **kwargs) + + def match(self, environ): + """ Search for a matching route and return a (:class:`Route` , urlargs) + tuple. The second value is a dictionary with parameters extracted + from the URL. Raise :exc:`HTTPError` (404/405) on a non-match.""" + return self.router.match(environ) + + def get_url(self, routename, **kargs): + """ Return a string that matches a named route """ + scriptname = request.environ.get('SCRIPT_NAME', '').strip('/') + '/' + location = self.router.build(routename, **kargs).lstrip('/') + return urljoin(urljoin('/', scriptname), location) + + def add_route(self, route): + ''' Add a route object, but do not change the :data:`Route.app` + attribute.''' + self.routes.append(route) + self.router.add(route.rule, route.method, route, name=route.name) + if DEBUG: route.prepare() + + def route(self, path=None, method='GET', callback=None, name=None, + apply=None, skip=None, **config): + """ A decorator to bind a function to a request URL. Example:: + + @app.route('/hello/:name') + def hello(name): + return 'Hello %s' % name + + The ``:name`` part is a wildcard. See :class:`Router` for syntax + details. + + :param path: Request path or a list of paths to listen to. If no + path is specified, it is automatically generated from the + signature of the function. + :param method: HTTP method (`GET`, `POST`, `PUT`, ...) or a list of + methods to listen to. (default: `GET`) + :param callback: An optional shortcut to avoid the decorator + syntax. ``route(..., callback=func)`` equals ``route(...)(func)`` + :param name: The name for this route. (default: None) + :param apply: A decorator or plugin or a list of plugins. These are + applied to the route callback in addition to installed plugins. + :param skip: A list of plugins, plugin classes or names. Matching + plugins are not installed to this route. ``True`` skips all. + + Any additional keyword arguments are stored as route-specific + configuration and passed to plugins (see :meth:`Plugin.apply`). + """ + if callable(path): path, callback = None, path + plugins = makelist(apply) + skiplist = makelist(skip) + def decorator(callback): + # TODO: Documentation and tests + if isinstance(callback, basestring): callback = load(callback) + for rule in makelist(path) or yieldroutes(callback): + for verb in makelist(method): + verb = verb.upper() + route = Route(self, rule, verb, callback, name=name, + plugins=plugins, skiplist=skiplist, **config) + self.add_route(route) + return callback + return decorator(callback) if callback else decorator + + def get(self, path=None, method='GET', **options): + """ Equals :meth:`route`. """ + return self.route(path, method, **options) + + def post(self, path=None, method='POST', **options): + """ Equals :meth:`route` with a ``POST`` method parameter. """ + return self.route(path, method, **options) + + def put(self, path=None, method='PUT', **options): + """ Equals :meth:`route` with a ``PUT`` method parameter. """ + return self.route(path, method, **options) + + def delete(self, path=None, method='DELETE', **options): + """ Equals :meth:`route` with a ``DELETE`` method parameter. """ + return self.route(path, method, **options) + + def error(self, code=500): + """ Decorator: Register an output handler for a HTTP error code""" + def wrapper(handler): + self.error_handler[int(code)] = handler + return handler + return wrapper + + def default_error_handler(self, res): + return tob(template(ERROR_PAGE_TEMPLATE, e=res)) + + def _handle(self, environ): + path = environ['bottle.raw_path'] = environ['PATH_INFO'] + if py3k: + try: + environ['PATH_INFO'] = path.encode('latin1').decode('utf8') + except UnicodeError: + return HTTPError(400, 'Invalid path string. Expected UTF-8') + + try: + environ['bottle.app'] = self + request.bind(environ) + response.bind() + try: + self.trigger_hook('before_request') + route, args = self.router.match(environ) + environ['route.handle'] = route + environ['bottle.route'] = route + environ['route.url_args'] = args + return route.call(**args) + finally: + self.trigger_hook('after_request') + + except HTTPResponse: + return _e() + except RouteReset: + route.reset() + return self._handle(environ) + except (KeyboardInterrupt, SystemExit, MemoryError): + raise + except Exception: + if not self.catchall: raise + stacktrace = format_exc() + environ['wsgi.errors'].write(stacktrace) + return HTTPError(500, "Internal Server Error", _e(), stacktrace) + + def _cast(self, out, peek=None): + """ Try to convert the parameter into something WSGI compatible and set + correct HTTP headers when possible. + Support: False, str, unicode, dict, HTTPResponse, HTTPError, file-like, + iterable of strings and iterable of unicodes + """ + + # Empty output is done here + if not out: + if 'Content-Length' not in response: + response['Content-Length'] = 0 + return [] + # Join lists of byte or unicode strings. Mixed lists are NOT supported + if isinstance(out, (tuple, list))\ + and isinstance(out[0], (bytes, unicode)): + out = out[0][0:0].join(out) # b'abc'[0:0] -> b'' + # Encode unicode strings + if isinstance(out, unicode): + out = out.encode(response.charset) + # Byte Strings are just returned + if isinstance(out, bytes): + if 'Content-Length' not in response: + response['Content-Length'] = len(out) + return [out] + # HTTPError or HTTPException (recursive, because they may wrap anything) + # TODO: Handle these explicitly in handle() or make them iterable. + if isinstance(out, HTTPError): + out.apply(response) + out = self.error_handler.get(out.status_code, self.default_error_handler)(out) + return self._cast(out) + if isinstance(out, HTTPResponse): + out.apply(response) + return self._cast(out.body) + + # File-like objects. + if hasattr(out, 'read'): + if 'wsgi.file_wrapper' in request.environ: + return request.environ['wsgi.file_wrapper'](out) + elif hasattr(out, 'close') or not hasattr(out, '__iter__'): + return WSGIFileWrapper(out) + + # Handle Iterables. We peek into them to detect their inner type. + try: + iout = iter(out) + first = next(iout) + while not first: + first = next(iout) + except StopIteration: + return self._cast('') + except HTTPResponse: + first = _e() + except (KeyboardInterrupt, SystemExit, MemoryError): + raise + except Exception: + if not self.catchall: raise + first = HTTPError(500, 'Unhandled exception', _e(), format_exc()) + + # These are the inner types allowed in iterator or generator objects. + if isinstance(first, HTTPResponse): + return self._cast(first) + elif isinstance(first, bytes): + new_iter = itertools.chain([first], iout) + elif isinstance(first, unicode): + encoder = lambda x: x.encode(response.charset) + new_iter = imap(encoder, itertools.chain([first], iout)) + else: + msg = 'Unsupported response type: %s' % type(first) + return self._cast(HTTPError(500, msg)) + if hasattr(out, 'close'): + new_iter = _closeiter(new_iter, out.close) + return new_iter + + def wsgi(self, environ, start_response): + """ The bottle WSGI-interface. """ + try: + out = self._cast(self._handle(environ)) + # rfc2616 section 4.3 + if response._status_code in (100, 101, 204, 304)\ + or environ['REQUEST_METHOD'] == 'HEAD': + if hasattr(out, 'close'): out.close() + out = [] + start_response(response._status_line, response.headerlist) + return out + except (KeyboardInterrupt, SystemExit, MemoryError): + raise + except Exception: + if not self.catchall: raise + err = '

Critical error while processing request: %s

' \ + % html_escape(environ.get('PATH_INFO', '/')) + if DEBUG: + err += '

Error:

\n
\n%s\n
\n' \ + '

Traceback:

\n
\n%s\n
\n' \ + % (html_escape(repr(_e())), html_escape(format_exc())) + environ['wsgi.errors'].write(err) + headers = [('Content-Type', 'text/html; charset=UTF-8')] + start_response('500 INTERNAL SERVER ERROR', headers, sys.exc_info()) + return [tob(err)] + + def __call__(self, environ, start_response): + ''' Each instance of :class:'Bottle' is a WSGI application. ''' + return self.wsgi(environ, start_response) + + + + + + +############################################################################### +# HTTP and WSGI Tools ########################################################## +############################################################################### + +class BaseRequest(object): + """ A wrapper for WSGI environment dictionaries that adds a lot of + convenient access methods and properties. Most of them are read-only. + + Adding new attributes to a request actually adds them to the environ + dictionary (as 'bottle.request.ext.'). This is the recommended + way to store and access request-specific data. + """ + + __slots__ = ('environ') + + #: Maximum size of memory buffer for :attr:`body` in bytes. + MEMFILE_MAX = 102400 + + def __init__(self, environ=None): + """ Wrap a WSGI environ dictionary. """ + #: The wrapped WSGI environ dictionary. This is the only real attribute. + #: All other attributes actually are read-only properties. + self.environ = {} if environ is None else environ + self.environ['bottle.request'] = self + + @DictProperty('environ', 'bottle.app', read_only=True) + def app(self): + ''' Bottle application handling this request. ''' + raise RuntimeError('This request is not connected to an application.') + + @DictProperty('environ', 'bottle.route', read_only=True) + def route(self): + """ The bottle :class:`Route` object that matches this request. """ + raise RuntimeError('This request is not connected to a route.') + + @DictProperty('environ', 'route.url_args', read_only=True) + def url_args(self): + """ The arguments extracted from the URL. """ + raise RuntimeError('This request is not connected to a route.') + + @property + def path(self): + ''' The value of ``PATH_INFO`` with exactly one prefixed slash (to fix + broken clients and avoid the "empty path" edge case). ''' + return '/' + self.environ.get('PATH_INFO','').lstrip('/') + + @property + def method(self): + ''' The ``REQUEST_METHOD`` value as an uppercase string. ''' + return self.environ.get('REQUEST_METHOD', 'GET').upper() + + @DictProperty('environ', 'bottle.request.headers', read_only=True) + def headers(self): + ''' A :class:`WSGIHeaderDict` that provides case-insensitive access to + HTTP request headers. ''' + return WSGIHeaderDict(self.environ) + + def get_header(self, name, default=None): + ''' Return the value of a request header, or a given default value. ''' + return self.headers.get(name, default) + + @DictProperty('environ', 'bottle.request.cookies', read_only=True) + def cookies(self): + """ Cookies parsed into a :class:`FormsDict`. Signed cookies are NOT + decoded. Use :meth:`get_cookie` if you expect signed cookies. """ + cookies = SimpleCookie(self.environ.get('HTTP_COOKIE','')).values() + return FormsDict((c.key, c.value) for c in cookies) + + def get_cookie(self, key, default=None, secret=None): + """ Return the content of a cookie. To read a `Signed Cookie`, the + `secret` must match the one used to create the cookie (see + :meth:`BaseResponse.set_cookie`). If anything goes wrong (missing + cookie or wrong signature), return a default value. """ + value = self.cookies.get(key) + if secret and value: + dec = cookie_decode(value, secret) # (key, value) tuple or None + return dec[1] if dec and dec[0] == key else default + return value or default + + @DictProperty('environ', 'bottle.request.query', read_only=True) + def query(self): + ''' The :attr:`query_string` parsed into a :class:`FormsDict`. These + values are sometimes called "URL arguments" or "GET parameters", but + not to be confused with "URL wildcards" as they are provided by the + :class:`Router`. ''' + get = self.environ['bottle.get'] = FormsDict() + pairs = _parse_qsl(self.environ.get('QUERY_STRING', '')) + for key, value in pairs: + get[key] = value + return get + + @DictProperty('environ', 'bottle.request.forms', read_only=True) + def forms(self): + """ Form values parsed from an `url-encoded` or `multipart/form-data` + encoded POST or PUT request body. The result is returned as a + :class:`FormsDict`. All keys and values are strings. File uploads + are stored separately in :attr:`files`. """ + forms = FormsDict() + for name, item in self.POST.allitems(): + if not isinstance(item, FileUpload): + forms[name] = item + return forms + + @DictProperty('environ', 'bottle.request.params', read_only=True) + def params(self): + """ A :class:`FormsDict` with the combined values of :attr:`query` and + :attr:`forms`. File uploads are stored in :attr:`files`. """ + params = FormsDict() + for key, value in self.query.allitems(): + params[key] = value + for key, value in self.forms.allitems(): + params[key] = value + return params + + @DictProperty('environ', 'bottle.request.files', read_only=True) + def files(self): + """ File uploads parsed from `multipart/form-data` encoded POST or PUT + request body. The values are instances of :class:`FileUpload`. + + """ + files = FormsDict() + for name, item in self.POST.allitems(): + if isinstance(item, FileUpload): + files[name] = item + return files + + @DictProperty('environ', 'bottle.request.json', read_only=True) + def json(self): + ''' If the ``Content-Type`` header is ``application/json``, this + property holds the parsed content of the request body. Only requests + smaller than :attr:`MEMFILE_MAX` are processed to avoid memory + exhaustion. ''' + ctype = self.environ.get('CONTENT_TYPE', '').lower().split(';')[0] + if ctype == 'application/json': + b = self._get_body_string() + if not b: + return None + return json_loads(b) + return None + + def _iter_body(self, read, bufsize): + maxread = max(0, self.content_length) + while maxread: + part = read(min(maxread, bufsize)) + if not part: break + yield part + maxread -= len(part) + + def _iter_chunked(self, read, bufsize): + err = HTTPError(400, 'Error while parsing chunked transfer body.') + rn, sem, bs = tob('\r\n'), tob(';'), tob('') + while True: + header = read(1) + while header[-2:] != rn: + c = read(1) + header += c + if not c: raise err + if len(header) > bufsize: raise err + size, _, _ = header.partition(sem) + try: + maxread = int(tonat(size.strip()), 16) + except ValueError: + raise err + if maxread == 0: break + buff = bs + while maxread > 0: + if not buff: + buff = read(min(maxread, bufsize)) + part, buff = buff[:maxread], buff[maxread:] + if not part: raise err + yield part + maxread -= len(part) + if read(2) != rn: + raise err + + @DictProperty('environ', 'bottle.request.body', read_only=True) + def _body(self): + body_iter = self._iter_chunked if self.chunked else self._iter_body + read_func = self.environ['wsgi.input'].read + body, body_size, is_temp_file = BytesIO(), 0, False + for part in body_iter(read_func, self.MEMFILE_MAX): + body.write(part) + body_size += len(part) + if not is_temp_file and body_size > self.MEMFILE_MAX: + body, tmp = TemporaryFile(mode='w+b'), body + body.write(tmp.getvalue()) + del tmp + is_temp_file = True + self.environ['wsgi.input'] = body + body.seek(0) + return body + + def _get_body_string(self): + ''' read body until content-length or MEMFILE_MAX into a string. Raise + HTTPError(413) on requests that are to large. ''' + clen = self.content_length + if clen > self.MEMFILE_MAX: + raise HTTPError(413, 'Request to large') + if clen < 0: clen = self.MEMFILE_MAX + 1 + data = self.body.read(clen) + if len(data) > self.MEMFILE_MAX: # Fail fast + raise HTTPError(413, 'Request to large') + return data + + @property + def body(self): + """ The HTTP request body as a seek-able file-like object. Depending on + :attr:`MEMFILE_MAX`, this is either a temporary file or a + :class:`io.BytesIO` instance. Accessing this property for the first + time reads and replaces the ``wsgi.input`` environ variable. + Subsequent accesses just do a `seek(0)` on the file object. """ + self._body.seek(0) + return self._body + + @property + def chunked(self): + ''' True if Chunked transfer encoding was. ''' + return 'chunked' in self.environ.get('HTTP_TRANSFER_ENCODING', '').lower() + + #: An alias for :attr:`query`. + GET = query + + @DictProperty('environ', 'bottle.request.post', read_only=True) + def POST(self): + """ The values of :attr:`forms` and :attr:`files` combined into a single + :class:`FormsDict`. Values are either strings (form values) or + instances of :class:`cgi.FieldStorage` (file uploads). + """ + post = FormsDict() + # We default to application/x-www-form-urlencoded for everything that + # is not multipart and take the fast path (also: 3.1 workaround) + if not self.content_type.startswith('multipart/'): + pairs = _parse_qsl(tonat(self._get_body_string(), 'latin1')) + for key, value in pairs: + post[key] = value + return post + + safe_env = {'QUERY_STRING':''} # Build a safe environment for cgi + for key in ('REQUEST_METHOD', 'CONTENT_TYPE', 'CONTENT_LENGTH'): + if key in self.environ: safe_env[key] = self.environ[key] + args = dict(fp=self.body, environ=safe_env, keep_blank_values=True) + if py31: + args['fp'] = NCTextIOWrapper(args['fp'], encoding='utf8', + newline='\n') + elif py3k: + args['encoding'] = 'utf8' + data = cgi.FieldStorage(**args) + self['_cgi.FieldStorage'] = data #http://bugs.python.org/issue18394#msg207958 + data = data.list or [] + for item in data: + if item.filename: + post[item.name] = FileUpload(item.file, item.name, + item.filename, item.headers) + else: + post[item.name] = item.value + return post + + @property + def url(self): + """ The full request URI including hostname and scheme. If your app + lives behind a reverse proxy or load balancer and you get confusing + results, make sure that the ``X-Forwarded-Host`` header is set + correctly. """ + return self.urlparts.geturl() + + @DictProperty('environ', 'bottle.request.urlparts', read_only=True) + def urlparts(self): + ''' The :attr:`url` string as an :class:`urlparse.SplitResult` tuple. + The tuple contains (scheme, host, path, query_string and fragment), + but the fragment is always empty because it is not visible to the + server. ''' + env = self.environ + http = env.get('HTTP_X_FORWARDED_PROTO') or env.get('wsgi.url_scheme', 'http') + host = env.get('HTTP_X_FORWARDED_HOST') or env.get('HTTP_HOST') + if not host: + # HTTP 1.1 requires a Host-header. This is for HTTP/1.0 clients. + host = env.get('SERVER_NAME', '127.0.0.1') + port = env.get('SERVER_PORT') + if port and port != ('80' if http == 'http' else '443'): + host += ':' + port + path = urlquote(self.fullpath) + return UrlSplitResult(http, host, path, env.get('QUERY_STRING'), '') + + @property + def fullpath(self): + """ Request path including :attr:`script_name` (if present). """ + return urljoin(self.script_name, self.path.lstrip('/')) + + @property + def query_string(self): + """ The raw :attr:`query` part of the URL (everything in between ``?`` + and ``#``) as a string. """ + return self.environ.get('QUERY_STRING', '') + + @property + def script_name(self): + ''' The initial portion of the URL's `path` that was removed by a higher + level (server or routing middleware) before the application was + called. This script path is returned with leading and tailing + slashes. ''' + script_name = self.environ.get('SCRIPT_NAME', '').strip('/') + return '/' + script_name + '/' if script_name else '/' + + def path_shift(self, shift=1): + ''' Shift path segments from :attr:`path` to :attr:`script_name` and + vice versa. + + :param shift: The number of path segments to shift. May be negative + to change the shift direction. (default: 1) + ''' + script = self.environ.get('SCRIPT_NAME','/') + self['SCRIPT_NAME'], self['PATH_INFO'] = path_shift(script, self.path, shift) + + @property + def content_length(self): + ''' The request body length as an integer. The client is responsible to + set this header. Otherwise, the real length of the body is unknown + and -1 is returned. In this case, :attr:`body` will be empty. ''' + return int(self.environ.get('CONTENT_LENGTH') or -1) + + @property + def content_type(self): + ''' The Content-Type header as a lowercase-string (default: empty). ''' + return self.environ.get('CONTENT_TYPE', '').lower() + + @property + def is_xhr(self): + ''' True if the request was triggered by a XMLHttpRequest. This only + works with JavaScript libraries that support the `X-Requested-With` + header (most of the popular libraries do). ''' + requested_with = self.environ.get('HTTP_X_REQUESTED_WITH','') + return requested_with.lower() == 'xmlhttprequest' + + @property + def is_ajax(self): + ''' Alias for :attr:`is_xhr`. "Ajax" is not the right term. ''' + return self.is_xhr + + @property + def auth(self): + """ HTTP authentication data as a (user, password) tuple. This + implementation currently supports basic (not digest) authentication + only. If the authentication happened at a higher level (e.g. in the + front web-server or a middleware), the password field is None, but + the user field is looked up from the ``REMOTE_USER`` environ + variable. On any errors, None is returned. """ + basic = parse_auth(self.environ.get('HTTP_AUTHORIZATION','')) + if basic: return basic + ruser = self.environ.get('REMOTE_USER') + if ruser: return (ruser, None) + return None + + @property + def remote_route(self): + """ A list of all IPs that were involved in this request, starting with + the client IP and followed by zero or more proxies. This does only + work if all proxies support the ```X-Forwarded-For`` header. Note + that this information can be forged by malicious clients. """ + proxy = self.environ.get('HTTP_X_FORWARDED_FOR') + if proxy: return [ip.strip() for ip in proxy.split(',')] + remote = self.environ.get('REMOTE_ADDR') + return [remote] if remote else [] + + @property + def remote_addr(self): + """ The client IP as a string. Note that this information can be forged + by malicious clients. """ + route = self.remote_route + return route[0] if route else None + + def copy(self): + """ Return a new :class:`Request` with a shallow :attr:`environ` copy. """ + return Request(self.environ.copy()) + + def get(self, value, default=None): return self.environ.get(value, default) + def __getitem__(self, key): return self.environ[key] + def __delitem__(self, key): self[key] = ""; del(self.environ[key]) + def __iter__(self): return iter(self.environ) + def __len__(self): return len(self.environ) + def keys(self): return self.environ.keys() + def __setitem__(self, key, value): + """ Change an environ value and clear all caches that depend on it. """ + + if self.environ.get('bottle.request.readonly'): + raise KeyError('The environ dictionary is read-only.') + + self.environ[key] = value + todelete = () + + if key == 'wsgi.input': + todelete = ('body', 'forms', 'files', 'params', 'post', 'json') + elif key == 'QUERY_STRING': + todelete = ('query', 'params') + elif key.startswith('HTTP_'): + todelete = ('headers', 'cookies') + + for key in todelete: + self.environ.pop('bottle.request.'+key, None) + + def __repr__(self): + return '<%s: %s %s>' % (self.__class__.__name__, self.method, self.url) + + def __getattr__(self, name): + ''' Search in self.environ for additional user defined attributes. ''' + try: + var = self.environ['bottle.request.ext.%s'%name] + return var.__get__(self) if hasattr(var, '__get__') else var + except KeyError: + raise AttributeError('Attribute %r not defined.' % name) + + def __setattr__(self, name, value): + if name == 'environ': return object.__setattr__(self, name, value) + self.environ['bottle.request.ext.%s'%name] = value + + +def _hkey(key): + if '\n' in key or '\r' in key or '\0' in key: + raise ValueError("Header names must not contain control characters: %r" % key) + return key.title().replace('_', '-') + + +def _hval(value): + value = tonat(value) + if '\n' in value or '\r' in value or '\0' in value: + raise ValueError("Header value must not contain control characters: %r" % value) + return value + + + +class HeaderProperty(object): + def __init__(self, name, reader=None, writer=None, default=''): + self.name, self.default = name, default + self.reader, self.writer = reader, writer + self.__doc__ = 'Current value of the %r header.' % name.title() + + def __get__(self, obj, cls): + if obj is None: return self + value = obj.get_header(self.name, self.default) + return self.reader(value) if self.reader else value + + def __set__(self, obj, value): + obj[self.name] = self.writer(value) if self.writer else value + + def __delete__(self, obj): + del obj[self.name] + + +class BaseResponse(object): + """ Storage class for a response body as well as headers and cookies. + + This class does support dict-like case-insensitive item-access to + headers, but is NOT a dict. Most notably, iterating over a response + yields parts of the body and not the headers. + + :param body: The response body as one of the supported types. + :param status: Either an HTTP status code (e.g. 200) or a status line + including the reason phrase (e.g. '200 OK'). + :param headers: A dictionary or a list of name-value pairs. + + Additional keyword arguments are added to the list of headers. + Underscores in the header name are replaced with dashes. + """ + + default_status = 200 + default_content_type = 'text/html; charset=UTF-8' + + # Header blacklist for specific response codes + # (rfc2616 section 10.2.3 and 10.3.5) + bad_headers = { + 204: set(('Content-Type',)), + 304: set(('Allow', 'Content-Encoding', 'Content-Language', + 'Content-Length', 'Content-Range', 'Content-Type', + 'Content-Md5', 'Last-Modified'))} + + def __init__(self, body='', status=None, headers=None, **more_headers): + self._cookies = None + self._headers = {} + self.body = body + self.status = status or self.default_status + if headers: + if isinstance(headers, dict): + headers = headers.items() + for name, value in headers: + self.add_header(name, value) + if more_headers: + for name, value in more_headers.items(): + self.add_header(name, value) + + def copy(self, cls=None): + ''' Returns a copy of self. ''' + cls = cls or BaseResponse + assert issubclass(cls, BaseResponse) + copy = cls() + copy.status = self.status + copy._headers = dict((k, v[:]) for (k, v) in self._headers.items()) + if self._cookies: + copy._cookies = SimpleCookie() + copy._cookies.load(self._cookies.output(header='')) + return copy + + def __iter__(self): + return iter(self.body) + + def close(self): + if hasattr(self.body, 'close'): + self.body.close() + + @property + def status_line(self): + ''' The HTTP status line as a string (e.g. ``404 Not Found``).''' + return self._status_line + + @property + def status_code(self): + ''' The HTTP status code as an integer (e.g. 404).''' + return self._status_code + + def _set_status(self, status): + if isinstance(status, int): + code, status = status, _HTTP_STATUS_LINES.get(status) + elif ' ' in status: + status = status.strip() + code = int(status.split()[0]) + else: + raise ValueError('String status line without a reason phrase.') + if not 100 <= code <= 999: raise ValueError('Status code out of range.') + self._status_code = code + self._status_line = str(status or ('%d Unknown' % code)) + + def _get_status(self): + return self._status_line + + status = property(_get_status, _set_status, None, + ''' A writeable property to change the HTTP response status. It accepts + either a numeric code (100-999) or a string with a custom reason + phrase (e.g. "404 Brain not found"). Both :data:`status_line` and + :data:`status_code` are updated accordingly. The return value is + always a status string. ''') + del _get_status, _set_status + + @property + def headers(self): + ''' An instance of :class:`HeaderDict`, a case-insensitive dict-like + view on the response headers. ''' + hdict = HeaderDict() + hdict.dict = self._headers + return hdict + + def __contains__(self, name): return _hkey(name) in self._headers + def __delitem__(self, name): del self._headers[_hkey(name)] + def __getitem__(self, name): return self._headers[_hkey(name)][-1] + def __setitem__(self, name, value): self._headers[_hkey(name)] = [_hval(value)] + + def get_header(self, name, default=None): + ''' Return the value of a previously defined header. If there is no + header with that name, return a default value. ''' + return self._headers.get(_hkey(name), [default])[-1] + + def set_header(self, name, value): + ''' Create a new response header, replacing any previously defined + headers with the same name. ''' + self._headers[_hkey(name)] = [_hval(value)] + + def add_header(self, name, value): + ''' Add an additional response header, not removing duplicates. ''' + self._headers.setdefault(_hkey(name), []).append(_hval(value)) + + def iter_headers(self): + ''' Yield (header, value) tuples, skipping headers that are not + allowed with the current response status code. ''' + return self.headerlist + + @property + def headerlist(self): + """ WSGI conform list of (header, value) tuples. """ + out = [] + headers = list(self._headers.items()) + if 'Content-Type' not in self._headers: + headers.append(('Content-Type', [self.default_content_type])) + if self._status_code in self.bad_headers: + bad_headers = self.bad_headers[self._status_code] + headers = [h for h in headers if h[0] not in bad_headers] + out += [(name, val) for (name, vals) in headers for val in vals] + if self._cookies: + for c in self._cookies.values(): + out.append(('Set-Cookie', _hval(c.OutputString()))) + if py3k: + out = [(k, v.encode('utf8').decode('latin1')) for (k, v) in out] + return out + + content_type = HeaderProperty('Content-Type') + content_length = HeaderProperty('Content-Length', reader=int) + expires = HeaderProperty('Expires', + reader=lambda x: datetime.utcfromtimestamp(parse_date(x)), + writer=lambda x: http_date(x)) + + @property + def charset(self, default='UTF-8'): + """ Return the charset specified in the content-type header (default: utf8). """ + if 'charset=' in self.content_type: + return self.content_type.split('charset=')[-1].split(';')[0].strip() + return default + + def set_cookie(self, name, value, secret=None, **options): + ''' Create a new cookie or replace an old one. If the `secret` parameter is + set, create a `Signed Cookie` (described below). + + :param name: the name of the cookie. + :param value: the value of the cookie. + :param secret: a signature key required for signed cookies. + + Additionally, this method accepts all RFC 2109 attributes that are + supported by :class:`cookie.Morsel`, including: + + :param max_age: maximum age in seconds. (default: None) + :param expires: a datetime object or UNIX timestamp. (default: None) + :param domain: the domain that is allowed to read the cookie. + (default: current domain) + :param path: limits the cookie to a given path (default: current path) + :param secure: limit the cookie to HTTPS connections (default: off). + :param httponly: prevents client-side javascript to read this cookie + (default: off, requires Python 2.6 or newer). + + If neither `expires` nor `max_age` is set (default), the cookie will + expire at the end of the browser session (as soon as the browser + window is closed). + + Signed cookies may store any pickle-able object and are + cryptographically signed to prevent manipulation. Keep in mind that + cookies are limited to 4kb in most browsers. + + Warning: Signed cookies are not encrypted (the client can still see + the content) and not copy-protected (the client can restore an old + cookie). The main intention is to make pickling and unpickling + save, not to store secret information at client side. + ''' + if not self._cookies: + self._cookies = SimpleCookie() + + if secret: + value = touni(cookie_encode((name, value), secret)) + elif not isinstance(value, basestring): + raise TypeError('Secret key missing for non-string Cookie.') + + if len(value) > 4096: raise ValueError('Cookie value to long.') + self._cookies[name] = value + + for key, value in options.items(): + if key == 'max_age': + if isinstance(value, timedelta): + value = value.seconds + value.days * 24 * 3600 + if key == 'expires': + if isinstance(value, (datedate, datetime)): + value = value.timetuple() + elif isinstance(value, (int, float)): + value = time.gmtime(value) + value = time.strftime("%a, %d %b %Y %H:%M:%S GMT", value) + self._cookies[name][key.replace('_', '-')] = value + + def delete_cookie(self, key, **kwargs): + ''' Delete a cookie. Be sure to use the same `domain` and `path` + settings as used to create the cookie. ''' + kwargs['max_age'] = -1 + kwargs['expires'] = 0 + self.set_cookie(key, '', **kwargs) + + def __repr__(self): + out = '' + for name, value in self.headerlist: + out += '%s: %s\n' % (name.title(), value.strip()) + return out + + +def local_property(name=None): + if name: depr('local_property() is deprecated and will be removed.') #0.12 + ls = threading.local() + def fget(self): + try: return ls.var + except AttributeError: + raise RuntimeError("Request context not initialized.") + def fset(self, value): ls.var = value + def fdel(self): del ls.var + return property(fget, fset, fdel, 'Thread-local property') + + +class LocalRequest(BaseRequest): + ''' A thread-local subclass of :class:`BaseRequest` with a different + set of attributes for each thread. There is usually only one global + instance of this class (:data:`request`). If accessed during a + request/response cycle, this instance always refers to the *current* + request (even on a multithreaded server). ''' + bind = BaseRequest.__init__ + environ = local_property() + + +class LocalResponse(BaseResponse): + ''' A thread-local subclass of :class:`BaseResponse` with a different + set of attributes for each thread. There is usually only one global + instance of this class (:data:`response`). Its attributes are used + to build the HTTP response at the end of the request/response cycle. + ''' + bind = BaseResponse.__init__ + _status_line = local_property() + _status_code = local_property() + _cookies = local_property() + _headers = local_property() + body = local_property() + + +Request = BaseRequest +Response = BaseResponse + + +class HTTPResponse(Response, BottleException): + def __init__(self, body='', status=None, headers=None, **more_headers): + super(HTTPResponse, self).__init__(body, status, headers, **more_headers) + + def apply(self, response): + response._status_code = self._status_code + response._status_line = self._status_line + response._headers = self._headers + response._cookies = self._cookies + response.body = self.body + + +class HTTPError(HTTPResponse): + default_status = 500 + def __init__(self, status=None, body=None, exception=None, traceback=None, + **options): + self.exception = exception + self.traceback = traceback + super(HTTPError, self).__init__(body, status, **options) + + + + + +############################################################################### +# Plugins ###################################################################### +############################################################################### + +class PluginError(BottleException): pass + + +class JSONPlugin(object): + name = 'json' + api = 2 + + def __init__(self, json_dumps=json_dumps): + self.json_dumps = json_dumps + + def apply(self, callback, route): + dumps = self.json_dumps + if not dumps: return callback + def wrapper(*a, **ka): + try: + rv = callback(*a, **ka) + except HTTPError: + rv = _e() + + if isinstance(rv, dict): + #Attempt to serialize, raises exception on failure + json_response = dumps(rv) + #Set content type only if serialization succesful + response.content_type = 'application/json' + return json_response + elif isinstance(rv, HTTPResponse) and isinstance(rv.body, dict): + rv.body = dumps(rv.body) + rv.content_type = 'application/json' + return rv + + return wrapper + + +class TemplatePlugin(object): + ''' This plugin applies the :func:`view` decorator to all routes with a + `template` config parameter. If the parameter is a tuple, the second + element must be a dict with additional options (e.g. `template_engine`) + or default variables for the template. ''' + name = 'template' + api = 2 + + def apply(self, callback, route): + conf = route.config.get('template') + if isinstance(conf, (tuple, list)) and len(conf) == 2: + return view(conf[0], **conf[1])(callback) + elif isinstance(conf, str): + return view(conf)(callback) + else: + return callback + + +#: Not a plugin, but part of the plugin API. TODO: Find a better place. +class _ImportRedirect(object): + def __init__(self, name, impmask): + ''' Create a virtual package that redirects imports (see PEP 302). ''' + self.name = name + self.impmask = impmask + self.module = sys.modules.setdefault(name, new_module(name)) + self.module.__dict__.update({'__file__': __file__, '__path__': [], + '__all__': [], '__loader__': self}) + sys.meta_path.append(self) + + def find_module(self, fullname, path=None): + if '.' not in fullname: return + packname = fullname.rsplit('.', 1)[0] + if packname != self.name: return + return self + + def load_module(self, fullname): + if fullname in sys.modules: return sys.modules[fullname] + modname = fullname.rsplit('.', 1)[1] + realname = self.impmask % modname + __import__(realname) + module = sys.modules[fullname] = sys.modules[realname] + setattr(self.module, modname, module) + module.__loader__ = self + return module + + + + + + +############################################################################### +# Common Utilities ############################################################# +############################################################################### + + +class MultiDict(DictMixin): + """ This dict stores multiple values per key, but behaves exactly like a + normal dict in that it returns only the newest value for any given key. + There are special methods available to access the full list of values. + """ + + def __init__(self, *a, **k): + self.dict = dict((k, [v]) for (k, v) in dict(*a, **k).items()) + + def __len__(self): return len(self.dict) + def __iter__(self): return iter(self.dict) + def __contains__(self, key): return key in self.dict + def __delitem__(self, key): del self.dict[key] + def __getitem__(self, key): return self.dict[key][-1] + def __setitem__(self, key, value): self.append(key, value) + def keys(self): return self.dict.keys() + + if py3k: + def values(self): return (v[-1] for v in self.dict.values()) + def items(self): return ((k, v[-1]) for k, v in self.dict.items()) + def allitems(self): + return ((k, v) for k, vl in self.dict.items() for v in vl) + iterkeys = keys + itervalues = values + iteritems = items + iterallitems = allitems + + else: + def values(self): return [v[-1] for v in self.dict.values()] + def items(self): return [(k, v[-1]) for k, v in self.dict.items()] + def iterkeys(self): return self.dict.iterkeys() + def itervalues(self): return (v[-1] for v in self.dict.itervalues()) + def iteritems(self): + return ((k, v[-1]) for k, v in self.dict.iteritems()) + def iterallitems(self): + return ((k, v) for k, vl in self.dict.iteritems() for v in vl) + def allitems(self): + return [(k, v) for k, vl in self.dict.iteritems() for v in vl] + + def get(self, key, default=None, index=-1, type=None): + ''' Return the most recent value for a key. + + :param default: The default value to be returned if the key is not + present or the type conversion fails. + :param index: An index for the list of available values. + :param type: If defined, this callable is used to cast the value + into a specific type. Exception are suppressed and result in + the default value to be returned. + ''' + try: + val = self.dict[key][index] + return type(val) if type else val + except Exception: + pass + return default + + def append(self, key, value): + ''' Add a new value to the list of values for this key. ''' + self.dict.setdefault(key, []).append(value) + + def replace(self, key, value): + ''' Replace the list of values with a single value. ''' + self.dict[key] = [value] + + def getall(self, key): + ''' Return a (possibly empty) list of values for a key. ''' + return self.dict.get(key) or [] + + #: Aliases for WTForms to mimic other multi-dict APIs (Django) + getone = get + getlist = getall + + +class FormsDict(MultiDict): + ''' This :class:`MultiDict` subclass is used to store request form data. + Additionally to the normal dict-like item access methods (which return + unmodified data as native strings), this container also supports + attribute-like access to its values. Attributes are automatically de- + or recoded to match :attr:`input_encoding` (default: 'utf8'). Missing + attributes default to an empty string. ''' + + #: Encoding used for attribute values. + input_encoding = 'utf8' + #: If true (default), unicode strings are first encoded with `latin1` + #: and then decoded to match :attr:`input_encoding`. + recode_unicode = True + + def _fix(self, s, encoding=None): + if isinstance(s, unicode) and self.recode_unicode: # Python 3 WSGI + return s.encode('latin1').decode(encoding or self.input_encoding) + elif isinstance(s, bytes): # Python 2 WSGI + return s.decode(encoding or self.input_encoding) + else: + return s + + def decode(self, encoding=None): + ''' Returns a copy with all keys and values de- or recoded to match + :attr:`input_encoding`. Some libraries (e.g. WTForms) want a + unicode dictionary. ''' + copy = FormsDict() + enc = copy.input_encoding = encoding or self.input_encoding + copy.recode_unicode = False + for key, value in self.allitems(): + copy.append(self._fix(key, enc), self._fix(value, enc)) + return copy + + def getunicode(self, name, default=None, encoding=None): + ''' Return the value as a unicode string, or the default. ''' + try: + return self._fix(self[name], encoding) + except (UnicodeError, KeyError): + return default + + def __getattr__(self, name, default=unicode()): + # Without this guard, pickle generates a cryptic TypeError: + if name.startswith('__') and name.endswith('__'): + return super(FormsDict, self).__getattr__(name) + return self.getunicode(name, default=default) + +class HeaderDict(MultiDict): + """ A case-insensitive version of :class:`MultiDict` that defaults to + replace the old value instead of appending it. """ + + def __init__(self, *a, **ka): + self.dict = {} + if a or ka: self.update(*a, **ka) + + def __contains__(self, key): return _hkey(key) in self.dict + def __delitem__(self, key): del self.dict[_hkey(key)] + def __getitem__(self, key): return self.dict[_hkey(key)][-1] + def __setitem__(self, key, value): self.dict[_hkey(key)] = [_hval(value)] + def append(self, key, value): self.dict.setdefault(_hkey(key), []).append(_hval(value)) + def replace(self, key, value): self.dict[_hkey(key)] = [_hval(value)] + def getall(self, key): return self.dict.get(_hkey(key)) or [] + def get(self, key, default=None, index=-1): + return MultiDict.get(self, _hkey(key), default, index) + def filter(self, names): + for name in (_hkey(n) for n in names): + if name in self.dict: + del self.dict[name] + + +class WSGIHeaderDict(DictMixin): + ''' This dict-like class wraps a WSGI environ dict and provides convenient + access to HTTP_* fields. Keys and values are native strings + (2.x bytes or 3.x unicode) and keys are case-insensitive. If the WSGI + environment contains non-native string values, these are de- or encoded + using a lossless 'latin1' character set. + + The API will remain stable even on changes to the relevant PEPs. + Currently PEP 333, 444 and 3333 are supported. (PEP 444 is the only one + that uses non-native strings.) + ''' + #: List of keys that do not have a ``HTTP_`` prefix. + cgikeys = ('CONTENT_TYPE', 'CONTENT_LENGTH') + + def __init__(self, environ): + self.environ = environ + + def _ekey(self, key): + ''' Translate header field name to CGI/WSGI environ key. ''' + key = key.replace('-','_').upper() + if key in self.cgikeys: + return key + return 'HTTP_' + key + + def raw(self, key, default=None): + ''' Return the header value as is (may be bytes or unicode). ''' + return self.environ.get(self._ekey(key), default) + + def __getitem__(self, key): + return tonat(self.environ[self._ekey(key)], 'latin1') + + def __setitem__(self, key, value): + raise TypeError("%s is read-only." % self.__class__) + + def __delitem__(self, key): + raise TypeError("%s is read-only." % self.__class__) + + def __iter__(self): + for key in self.environ: + if key[:5] == 'HTTP_': + yield key[5:].replace('_', '-').title() + elif key in self.cgikeys: + yield key.replace('_', '-').title() + + def keys(self): return [x for x in self] + def __len__(self): return len(self.keys()) + def __contains__(self, key): return self._ekey(key) in self.environ + + + +class ConfigDict(dict): + ''' A dict-like configuration storage with additional support for + namespaces, validators, meta-data, on_change listeners and more. + + This storage is optimized for fast read access. Retrieving a key + or using non-altering dict methods (e.g. `dict.get()`) has no overhead + compared to a native dict. + ''' + __slots__ = ('_meta', '_on_change') + + class Namespace(DictMixin): + + def __init__(self, config, namespace): + self._config = config + self._prefix = namespace + + def __getitem__(self, key): + depr('Accessing namespaces as dicts is discouraged. ' + 'Only use flat item access: ' + 'cfg["names"]["pace"]["key"] -> cfg["name.space.key"]') #0.12 + return self._config[self._prefix + '.' + key] + + def __setitem__(self, key, value): + self._config[self._prefix + '.' + key] = value + + def __delitem__(self, key): + del self._config[self._prefix + '.' + key] + + def __iter__(self): + ns_prefix = self._prefix + '.' + for key in self._config: + ns, dot, name = key.rpartition('.') + if ns == self._prefix and name: + yield name + + def keys(self): return [x for x in self] + def __len__(self): return len(self.keys()) + def __contains__(self, key): return self._prefix + '.' + key in self._config + def __repr__(self): return '' % self._prefix + def __str__(self): return '' % self._prefix + + # Deprecated ConfigDict features + def __getattr__(self, key): + depr('Attribute access is deprecated.') #0.12 + if key not in self and key[0].isupper(): + self[key] = ConfigDict.Namespace(self._config, self._prefix + '.' + key) + if key not in self and key.startswith('__'): + raise AttributeError(key) + return self.get(key) + + def __setattr__(self, key, value): + if key in ('_config', '_prefix'): + self.__dict__[key] = value + return + depr('Attribute assignment is deprecated.') #0.12 + if hasattr(DictMixin, key): + raise AttributeError('Read-only attribute.') + if key in self and self[key] and isinstance(self[key], self.__class__): + raise AttributeError('Non-empty namespace attribute.') + self[key] = value + + def __delattr__(self, key): + if key in self: + val = self.pop(key) + if isinstance(val, self.__class__): + prefix = key + '.' + for key in self: + if key.startswith(prefix): + del self[prefix+key] + + def __call__(self, *a, **ka): + depr('Calling ConfDict is deprecated. Use the update() method.') #0.12 + self.update(*a, **ka) + return self + + def __init__(self, *a, **ka): + self._meta = {} + self._on_change = lambda name, value: None + if a or ka: + depr('Constructor does no longer accept parameters.') #0.12 + self.update(*a, **ka) + + def load_config(self, filename): + ''' Load values from an *.ini style config file. + + If the config file contains sections, their names are used as + namespaces for the values within. The two special sections + ``DEFAULT`` and ``bottle`` refer to the root namespace (no prefix). + ''' + conf = ConfigParser() + conf.read(filename) + for section in conf.sections(): + for key, value in conf.items(section): + if section not in ('DEFAULT', 'bottle'): + key = section + '.' + key + self[key] = value + return self + + def load_dict(self, source, namespace='', make_namespaces=False): + ''' Import values from a dictionary structure. Nesting can be used to + represent namespaces. + + >>> ConfigDict().load_dict({'name': {'space': {'key': 'value'}}}) + {'name.space.key': 'value'} + ''' + stack = [(namespace, source)] + while stack: + prefix, source = stack.pop() + if not isinstance(source, dict): + raise TypeError('Source is not a dict (r)' % type(key)) + for key, value in source.items(): + if not isinstance(key, basestring): + raise TypeError('Key is not a string (%r)' % type(key)) + full_key = prefix + '.' + key if prefix else key + if isinstance(value, dict): + stack.append((full_key, value)) + if make_namespaces: + self[full_key] = self.Namespace(self, full_key) + else: + self[full_key] = value + return self + + def update(self, *a, **ka): + ''' If the first parameter is a string, all keys are prefixed with this + namespace. Apart from that it works just as the usual dict.update(). + Example: ``update('some.namespace', key='value')`` ''' + prefix = '' + if a and isinstance(a[0], basestring): + prefix = a[0].strip('.') + '.' + a = a[1:] + for key, value in dict(*a, **ka).items(): + self[prefix+key] = value + + def setdefault(self, key, value): + if key not in self: + self[key] = value + return self[key] + + def __setitem__(self, key, value): + if not isinstance(key, basestring): + raise TypeError('Key has type %r (not a string)' % type(key)) + + value = self.meta_get(key, 'filter', lambda x: x)(value) + if key in self and self[key] is value: + return + self._on_change(key, value) + dict.__setitem__(self, key, value) + + def __delitem__(self, key): + dict.__delitem__(self, key) + + def clear(self): + for key in self: + del self[key] + + def meta_get(self, key, metafield, default=None): + ''' Return the value of a meta field for a key. ''' + return self._meta.get(key, {}).get(metafield, default) + + def meta_set(self, key, metafield, value): + ''' Set the meta field for a key to a new value. This triggers the + on-change handler for existing keys. ''' + self._meta.setdefault(key, {})[metafield] = value + if key in self: + self[key] = self[key] + + def meta_list(self, key): + ''' Return an iterable of meta field names defined for a key. ''' + return self._meta.get(key, {}).keys() + + # Deprecated ConfigDict features + def __getattr__(self, key): + depr('Attribute access is deprecated.') #0.12 + if key not in self and key[0].isupper(): + self[key] = self.Namespace(self, key) + if key not in self and key.startswith('__'): + raise AttributeError(key) + return self.get(key) + + def __setattr__(self, key, value): + if key in self.__slots__: + return dict.__setattr__(self, key, value) + depr('Attribute assignment is deprecated.') #0.12 + if hasattr(dict, key): + raise AttributeError('Read-only attribute.') + if key in self and self[key] and isinstance(self[key], self.Namespace): + raise AttributeError('Non-empty namespace attribute.') + self[key] = value + + def __delattr__(self, key): + if key in self: + val = self.pop(key) + if isinstance(val, self.Namespace): + prefix = key + '.' + for key in self: + if key.startswith(prefix): + del self[prefix+key] + + def __call__(self, *a, **ka): + depr('Calling ConfDict is deprecated. Use the update() method.') #0.12 + self.update(*a, **ka) + return self + + + +class AppStack(list): + """ A stack-like list. Calling it returns the head of the stack. """ + + def __call__(self): + """ Return the current default application. """ + return self[-1] + + def push(self, value=None): + """ Add a new :class:`Bottle` instance to the stack """ + if not isinstance(value, Bottle): + value = Bottle() + self.append(value) + return value + + +class WSGIFileWrapper(object): + + def __init__(self, fp, buffer_size=1024*64): + self.fp, self.buffer_size = fp, buffer_size + for attr in ('fileno', 'close', 'read', 'readlines', 'tell', 'seek'): + if hasattr(fp, attr): setattr(self, attr, getattr(fp, attr)) + + def __iter__(self): + buff, read = self.buffer_size, self.read + while True: + part = read(buff) + if not part: return + yield part + + +class _closeiter(object): + ''' This only exists to be able to attach a .close method to iterators that + do not support attribute assignment (most of itertools). ''' + + def __init__(self, iterator, close=None): + self.iterator = iterator + self.close_callbacks = makelist(close) + + def __iter__(self): + return iter(self.iterator) + + def close(self): + for func in self.close_callbacks: + func() + + +class ResourceManager(object): + ''' This class manages a list of search paths and helps to find and open + application-bound resources (files). + + :param base: default value for :meth:`add_path` calls. + :param opener: callable used to open resources. + :param cachemode: controls which lookups are cached. One of 'all', + 'found' or 'none'. + ''' + + def __init__(self, base='./', opener=open, cachemode='all'): + self.opener = open + self.base = base + self.cachemode = cachemode + + #: A list of search paths. See :meth:`add_path` for details. + self.path = [] + #: A cache for resolved paths. ``res.cache.clear()`` clears the cache. + self.cache = {} + + def add_path(self, path, base=None, index=None, create=False): + ''' Add a new path to the list of search paths. Return False if the + path does not exist. + + :param path: The new search path. Relative paths are turned into + an absolute and normalized form. If the path looks like a file + (not ending in `/`), the filename is stripped off. + :param base: Path used to absolutize relative search paths. + Defaults to :attr:`base` which defaults to ``os.getcwd()``. + :param index: Position within the list of search paths. Defaults + to last index (appends to the list). + + The `base` parameter makes it easy to reference files installed + along with a python module or package:: + + res.add_path('./resources/', __file__) + ''' + base = os.path.abspath(os.path.dirname(base or self.base)) + path = os.path.abspath(os.path.join(base, os.path.dirname(path))) + path += os.sep + if path in self.path: + self.path.remove(path) + if create and not os.path.isdir(path): + os.makedirs(path) + if index is None: + self.path.append(path) + else: + self.path.insert(index, path) + self.cache.clear() + return os.path.exists(path) + + def __iter__(self): + ''' Iterate over all existing files in all registered paths. ''' + search = self.path[:] + while search: + path = search.pop() + if not os.path.isdir(path): continue + for name in os.listdir(path): + full = os.path.join(path, name) + if os.path.isdir(full): search.append(full) + else: yield full + + def lookup(self, name): + ''' Search for a resource and return an absolute file path, or `None`. + + The :attr:`path` list is searched in order. The first match is + returend. Symlinks are followed. The result is cached to speed up + future lookups. ''' + if name not in self.cache or DEBUG: + for path in self.path: + fpath = os.path.join(path, name) + if os.path.isfile(fpath): + if self.cachemode in ('all', 'found'): + self.cache[name] = fpath + return fpath + if self.cachemode == 'all': + self.cache[name] = None + return self.cache[name] + + def open(self, name, mode='r', *args, **kwargs): + ''' Find a resource and return a file object, or raise IOError. ''' + fname = self.lookup(name) + if not fname: raise IOError("Resource %r not found." % name) + return self.opener(fname, mode=mode, *args, **kwargs) + + +class FileUpload(object): + + def __init__(self, fileobj, name, filename, headers=None): + ''' Wrapper for file uploads. ''' + #: Open file(-like) object (BytesIO buffer or temporary file) + self.file = fileobj + #: Name of the upload form field + self.name = name + #: Raw filename as sent by the client (may contain unsafe characters) + self.raw_filename = filename + #: A :class:`HeaderDict` with additional headers (e.g. content-type) + self.headers = HeaderDict(headers) if headers else HeaderDict() + + content_type = HeaderProperty('Content-Type') + content_length = HeaderProperty('Content-Length', reader=int, default=-1) + + def get_header(self, name, default=None): + """ Return the value of a header within the mulripart part. """ + return self.headers.get(name, default) + + @cached_property + def filename(self): + ''' Name of the file on the client file system, but normalized to ensure + file system compatibility. An empty filename is returned as 'empty'. + + Only ASCII letters, digits, dashes, underscores and dots are + allowed in the final filename. Accents are removed, if possible. + Whitespace is replaced by a single dash. Leading or tailing dots + or dashes are removed. The filename is limited to 255 characters. + ''' + fname = self.raw_filename + if not isinstance(fname, unicode): + fname = fname.decode('utf8', 'ignore') + fname = normalize('NFKD', fname).encode('ASCII', 'ignore').decode('ASCII') + fname = os.path.basename(fname.replace('\\', os.path.sep)) + fname = re.sub(r'[^a-zA-Z0-9-_.\s]', '', fname).strip() + fname = re.sub(r'[-\s]+', '-', fname).strip('.-') + return fname[:255] or 'empty' + + def _copy_file(self, fp, chunk_size=2**16): + read, write, offset = self.file.read, fp.write, self.file.tell() + while 1: + buf = read(chunk_size) + if not buf: break + write(buf) + self.file.seek(offset) + + def save(self, destination, overwrite=False, chunk_size=2**16): + ''' Save file to disk or copy its content to an open file(-like) object. + If *destination* is a directory, :attr:`filename` is added to the + path. Existing files are not overwritten by default (IOError). + + :param destination: File path, directory or file(-like) object. + :param overwrite: If True, replace existing files. (default: False) + :param chunk_size: Bytes to read at a time. (default: 64kb) + ''' + if isinstance(destination, basestring): # Except file-likes here + if os.path.isdir(destination): + destination = os.path.join(destination, self.filename) + if not overwrite and os.path.exists(destination): + raise IOError('File exists.') + with open(destination, 'wb') as fp: + self._copy_file(fp, chunk_size) + else: + self._copy_file(destination, chunk_size) + + + + + + +############################################################################### +# Application Helper ########################################################### +############################################################################### + + +def abort(code=500, text='Unknown Error.'): + """ Aborts execution and causes a HTTP error. """ + raise HTTPError(code, text) + + +def redirect(url, code=None): + """ Aborts execution and causes a 303 or 302 redirect, depending on + the HTTP protocol version. """ + if not code: + code = 303 if request.get('SERVER_PROTOCOL') == "HTTP/1.1" else 302 + res = response.copy(cls=HTTPResponse) + res.status = code + res.body = "" + res.set_header('Location', urljoin(request.url, url)) + raise res + + +def _file_iter_range(fp, offset, bytes, maxread=1024*1024): + ''' Yield chunks from a range in a file. No chunk is bigger than maxread.''' + fp.seek(offset) + while bytes > 0: + part = fp.read(min(bytes, maxread)) + if not part: break + bytes -= len(part) + yield part + + +def static_file(filename, root, mimetype='auto', download=False, charset='UTF-8'): + """ Open a file in a safe way and return :exc:`HTTPResponse` with status + code 200, 305, 403 or 404. The ``Content-Type``, ``Content-Encoding``, + ``Content-Length`` and ``Last-Modified`` headers are set if possible. + Special support for ``If-Modified-Since``, ``Range`` and ``HEAD`` + requests. + + :param filename: Name or path of the file to send. + :param root: Root path for file lookups. Should be an absolute directory + path. + :param mimetype: Defines the content-type header (default: guess from + file extension) + :param download: If True, ask the browser to open a `Save as...` dialog + instead of opening the file with the associated program. You can + specify a custom filename as a string. If not specified, the + original filename is used (default: False). + :param charset: The charset to use for files with a ``text/*`` + mime-type. (default: UTF-8) + """ + + root = os.path.abspath(root) + os.sep + filename = os.path.abspath(os.path.join(root, filename.strip('/\\'))) + headers = dict() + + if not filename.startswith(root): + return HTTPError(403, "Access denied.") + if not os.path.exists(filename) or not os.path.isfile(filename): + return HTTPError(404, "File does not exist.") + if not os.access(filename, os.R_OK): + return HTTPError(403, "You do not have permission to access this file.") + + if mimetype == 'auto': + mimetype, encoding = mimetypes.guess_type(filename) + if encoding: headers['Content-Encoding'] = encoding + + if mimetype: + if mimetype[:5] == 'text/' and charset and 'charset' not in mimetype: + mimetype += '; charset=%s' % charset + headers['Content-Type'] = mimetype + + if download: + download = os.path.basename(filename if download == True else download) + headers['Content-Disposition'] = 'attachment; filename="%s"' % download + + stats = os.stat(filename) + headers['Content-Length'] = clen = stats.st_size + lm = time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(stats.st_mtime)) + headers['Last-Modified'] = lm + + ims = request.environ.get('HTTP_IF_MODIFIED_SINCE') + if ims: + ims = parse_date(ims.split(";")[0].strip()) + if ims is not None and ims >= int(stats.st_mtime): + headers['Date'] = time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime()) + return HTTPResponse(status=304, **headers) + + body = '' if request.method == 'HEAD' else open(filename, 'rb') + + headers["Accept-Ranges"] = "bytes" + ranges = request.environ.get('HTTP_RANGE') + if 'HTTP_RANGE' in request.environ: + ranges = list(parse_range_header(request.environ['HTTP_RANGE'], clen)) + if not ranges: + return HTTPError(416, "Requested Range Not Satisfiable") + offset, end = ranges[0] + headers["Content-Range"] = "bytes %d-%d/%d" % (offset, end-1, clen) + headers["Content-Length"] = str(end-offset) + if body: body = _file_iter_range(body, offset, end-offset) + return HTTPResponse(body, status=206, **headers) + return HTTPResponse(body, **headers) + + + + + + +############################################################################### +# HTTP Utilities and MISC (TODO) ############################################### +############################################################################### + + +def debug(mode=True): + """ Change the debug level. + There is only one debug level supported at the moment.""" + global DEBUG + if mode: warnings.simplefilter('default') + DEBUG = bool(mode) + +def http_date(value): + if isinstance(value, (datedate, datetime)): + value = value.utctimetuple() + elif isinstance(value, (int, float)): + value = time.gmtime(value) + if not isinstance(value, basestring): + value = time.strftime("%a, %d %b %Y %H:%M:%S GMT", value) + return value + +def parse_date(ims): + """ Parse rfc1123, rfc850 and asctime timestamps and return UTC epoch. """ + try: + ts = email.utils.parsedate_tz(ims) + return time.mktime(ts[:8] + (0,)) - (ts[9] or 0) - time.timezone + except (TypeError, ValueError, IndexError, OverflowError): + return None + +def parse_auth(header): + """ Parse rfc2617 HTTP authentication header string (basic) and return (user,pass) tuple or None""" + try: + method, data = header.split(None, 1) + if method.lower() == 'basic': + user, pwd = touni(base64.b64decode(tob(data))).split(':',1) + return user, pwd + except (KeyError, ValueError): + return None + +def parse_range_header(header, maxlen=0): + ''' Yield (start, end) ranges parsed from a HTTP Range header. Skip + unsatisfiable ranges. The end index is non-inclusive.''' + if not header or header[:6] != 'bytes=': return + ranges = [r.split('-', 1) for r in header[6:].split(',') if '-' in r] + for start, end in ranges: + try: + if not start: # bytes=-100 -> last 100 bytes + start, end = max(0, maxlen-int(end)), maxlen + elif not end: # bytes=100- -> all but the first 99 bytes + start, end = int(start), maxlen + else: # bytes=100-200 -> bytes 100-200 (inclusive) + start, end = int(start), min(int(end)+1, maxlen) + if 0 <= start < end <= maxlen: + yield start, end + except ValueError: + pass + +def _parse_qsl(qs): + r = [] + for pair in qs.replace(';','&').split('&'): + if not pair: continue + nv = pair.split('=', 1) + if len(nv) != 2: nv.append('') + key = urlunquote(nv[0].replace('+', ' ')) + value = urlunquote(nv[1].replace('+', ' ')) + r.append((key, value)) + return r + +def _lscmp(a, b): + ''' Compares two strings in a cryptographically safe way: + Runtime is not affected by length of common prefix. ''' + return not sum(0 if x==y else 1 for x, y in zip(a, b)) and len(a) == len(b) + + +def cookie_encode(data, key): + ''' Encode and sign a pickle-able object. Return a (byte) string ''' + msg = base64.b64encode(pickle.dumps(data, -1)) + sig = base64.b64encode(hmac.new(tob(key), msg, digestmod=hashlib.md5).digest()) + return tob('!') + sig + tob('?') + msg + + +def cookie_decode(data, key): + ''' Verify and decode an encoded string. Return an object or None.''' + data = tob(data) + if cookie_is_encoded(data): + sig, msg = data.split(tob('?'), 1) + if _lscmp(sig[1:], base64.b64encode(hmac.new(tob(key), msg, digestmod=hashlib.md5).digest())): + return pickle.loads(base64.b64decode(msg)) + return None + + +def cookie_is_encoded(data): + ''' Return True if the argument looks like a encoded cookie.''' + return bool(data.startswith(tob('!')) and tob('?') in data) + + +def html_escape(string): + ''' Escape HTML special characters ``&<>`` and quotes ``'"``. ''' + return string.replace('&','&').replace('<','<').replace('>','>')\ + .replace('"','"').replace("'",''') + + +def html_quote(string): + ''' Escape and quote a string to be used as an HTTP attribute.''' + return '"%s"' % html_escape(string).replace('\n',' ')\ + .replace('\r',' ').replace('\t',' ') + + +def yieldroutes(func): + """ Return a generator for routes that match the signature (name, args) + of the func parameter. This may yield more than one route if the function + takes optional keyword arguments. The output is best described by example:: + + a() -> '/a' + b(x, y) -> '/b//' + c(x, y=5) -> '/c/' and '/c//' + d(x=5, y=6) -> '/d' and '/d/' and '/d//' + """ + path = '/' + func.__name__.replace('__','/').lstrip('/') + spec = getargspec(func) + argc = len(spec[0]) - len(spec[3] or []) + path += ('/<%s>' * argc) % tuple(spec[0][:argc]) + yield path + for arg in spec[0][argc:]: + path += '/<%s>' % arg + yield path + + +def path_shift(script_name, path_info, shift=1): + ''' Shift path fragments from PATH_INFO to SCRIPT_NAME and vice versa. + + :return: The modified paths. + :param script_name: The SCRIPT_NAME path. + :param script_name: The PATH_INFO path. + :param shift: The number of path fragments to shift. May be negative to + change the shift direction. (default: 1) + ''' + if shift == 0: return script_name, path_info + pathlist = path_info.strip('/').split('/') + scriptlist = script_name.strip('/').split('/') + if pathlist and pathlist[0] == '': pathlist = [] + if scriptlist and scriptlist[0] == '': scriptlist = [] + if shift > 0 and shift <= len(pathlist): + moved = pathlist[:shift] + scriptlist = scriptlist + moved + pathlist = pathlist[shift:] + elif shift < 0 and shift >= -len(scriptlist): + moved = scriptlist[shift:] + pathlist = moved + pathlist + scriptlist = scriptlist[:shift] + else: + empty = 'SCRIPT_NAME' if shift < 0 else 'PATH_INFO' + raise AssertionError("Cannot shift. Nothing left from %s" % empty) + new_script_name = '/' + '/'.join(scriptlist) + new_path_info = '/' + '/'.join(pathlist) + if path_info.endswith('/') and pathlist: new_path_info += '/' + return new_script_name, new_path_info + + +def auth_basic(check, realm="private", text="Access denied"): + ''' Callback decorator to require HTTP auth (basic). + TODO: Add route(check_auth=...) parameter. ''' + def decorator(func): + def wrapper(*a, **ka): + user, password = request.auth or (None, None) + if user is None or not check(user, password): + err = HTTPError(401, text) + err.add_header('WWW-Authenticate', 'Basic realm="%s"' % realm) + return err + return func(*a, **ka) + return wrapper + return decorator + + +# Shortcuts for common Bottle methods. +# They all refer to the current default application. + +def make_default_app_wrapper(name): + ''' Return a callable that relays calls to the current default app. ''' + @functools.wraps(getattr(Bottle, name)) + def wrapper(*a, **ka): + return getattr(app(), name)(*a, **ka) + return wrapper + +route = make_default_app_wrapper('route') +get = make_default_app_wrapper('get') +post = make_default_app_wrapper('post') +put = make_default_app_wrapper('put') +delete = make_default_app_wrapper('delete') +error = make_default_app_wrapper('error') +mount = make_default_app_wrapper('mount') +hook = make_default_app_wrapper('hook') +install = make_default_app_wrapper('install') +uninstall = make_default_app_wrapper('uninstall') +url = make_default_app_wrapper('get_url') + + + + + + + +############################################################################### +# Server Adapter ############################################################### +############################################################################### + + +class ServerAdapter(object): + quiet = False + def __init__(self, host='127.0.0.1', port=8080, **options): + self.options = options + self.host = host + self.port = int(port) + + def run(self, handler): # pragma: no cover + pass + + def __repr__(self): + args = ', '.join(['%s=%s'%(k,repr(v)) for k, v in self.options.items()]) + return "%s(%s)" % (self.__class__.__name__, args) + + +class CGIServer(ServerAdapter): + quiet = True + def run(self, handler): # pragma: no cover + from wsgiref.handlers import CGIHandler + def fixed_environ(environ, start_response): + environ.setdefault('PATH_INFO', '') + return handler(environ, start_response) + CGIHandler().run(fixed_environ) + + +class FlupFCGIServer(ServerAdapter): + def run(self, handler): # pragma: no cover + import flup.server.fcgi + self.options.setdefault('bindAddress', (self.host, self.port)) + flup.server.fcgi.WSGIServer(handler, **self.options).run() + + +class WSGIRefServer(ServerAdapter): + def run(self, app): # pragma: no cover + from wsgiref.simple_server import WSGIRequestHandler, WSGIServer + from wsgiref.simple_server import make_server + import socket + + class FixedHandler(WSGIRequestHandler): + def address_string(self): # Prevent reverse DNS lookups please. + return self.client_address[0] + def log_request(*args, **kw): + if not self.quiet: + return WSGIRequestHandler.log_request(*args, **kw) + + handler_cls = self.options.get('handler_class', FixedHandler) + server_cls = self.options.get('server_class', WSGIServer) + + if ':' in self.host: # Fix wsgiref for IPv6 addresses. + if getattr(server_cls, 'address_family') == socket.AF_INET: + class server_cls(server_cls): + address_family = socket.AF_INET6 + + srv = make_server(self.host, self.port, app, server_cls, handler_cls) + srv.serve_forever() + + +class CherryPyServer(ServerAdapter): + def run(self, handler): # pragma: no cover + from cherrypy import wsgiserver + self.options['bind_addr'] = (self.host, self.port) + self.options['wsgi_app'] = handler + + certfile = self.options.get('certfile') + if certfile: + del self.options['certfile'] + keyfile = self.options.get('keyfile') + if keyfile: + del self.options['keyfile'] + + server = wsgiserver.CherryPyWSGIServer(**self.options) + if certfile: + server.ssl_certificate = certfile + if keyfile: + server.ssl_private_key = keyfile + + try: + server.start() + finally: + server.stop() + + +class WaitressServer(ServerAdapter): + def run(self, handler): + from waitress import serve + serve(handler, host=self.host, port=self.port) + + +class PasteServer(ServerAdapter): + def run(self, handler): # pragma: no cover + from paste import httpserver + from paste.translogger import TransLogger + handler = TransLogger(handler, setup_console_handler=(not self.quiet)) + httpserver.serve(handler, host=self.host, port=str(self.port), + **self.options) + + +class MeinheldServer(ServerAdapter): + def run(self, handler): + from meinheld import server + server.listen((self.host, self.port)) + server.run(handler) + + +class FapwsServer(ServerAdapter): + """ Extremely fast webserver using libev. See http://www.fapws.org/ """ + def run(self, handler): # pragma: no cover + import fapws._evwsgi as evwsgi + from fapws import base, config + port = self.port + if float(config.SERVER_IDENT[-2:]) > 0.4: + # fapws3 silently changed its API in 0.5 + port = str(port) + evwsgi.start(self.host, port) + # fapws3 never releases the GIL. Complain upstream. I tried. No luck. + if 'BOTTLE_CHILD' in os.environ and not self.quiet: + _stderr("WARNING: Auto-reloading does not work with Fapws3.\n") + _stderr(" (Fapws3 breaks python thread support)\n") + evwsgi.set_base_module(base) + def app(environ, start_response): + environ['wsgi.multiprocess'] = False + return handler(environ, start_response) + evwsgi.wsgi_cb(('', app)) + evwsgi.run() + + +class TornadoServer(ServerAdapter): + """ The super hyped asynchronous server by facebook. Untested. """ + def run(self, handler): # pragma: no cover + import tornado.wsgi, tornado.httpserver, tornado.ioloop + container = tornado.wsgi.WSGIContainer(handler) + server = tornado.httpserver.HTTPServer(container) + server.listen(port=self.port,address=self.host) + tornado.ioloop.IOLoop.instance().start() + + +class AppEngineServer(ServerAdapter): + """ Adapter for Google App Engine. """ + quiet = True + def run(self, handler): + from google.appengine.ext.webapp import util + # A main() function in the handler script enables 'App Caching'. + # Lets makes sure it is there. This _really_ improves performance. + module = sys.modules.get('__main__') + if module and not hasattr(module, 'main'): + module.main = lambda: util.run_wsgi_app(handler) + util.run_wsgi_app(handler) + + +class TwistedServer(ServerAdapter): + """ Untested. """ + def run(self, handler): + from twisted.web import server, wsgi + from twisted.python.threadpool import ThreadPool + from twisted.internet import reactor + thread_pool = ThreadPool() + thread_pool.start() + reactor.addSystemEventTrigger('after', 'shutdown', thread_pool.stop) + factory = server.Site(wsgi.WSGIResource(reactor, thread_pool, handler)) + reactor.listenTCP(self.port, factory, interface=self.host) + reactor.run() + + +class DieselServer(ServerAdapter): + """ Untested. """ + def run(self, handler): + from diesel.protocols.wsgi import WSGIApplication + app = WSGIApplication(handler, port=self.port) + app.run() + + +class GeventServer(ServerAdapter): + """ Untested. Options: + + * `fast` (default: False) uses libevent's http server, but has some + issues: No streaming, no pipelining, no SSL. + * See gevent.wsgi.WSGIServer() documentation for more options. + """ + def run(self, handler): + from gevent import pywsgi, local + if not isinstance(threading.local(), local.local): + msg = "Bottle requires gevent.monkey.patch_all() (before import)" + raise RuntimeError(msg) + if self.options.pop('fast', None): + depr('The "fast" option has been deprecated and removed by Gevent.') + if self.quiet: + self.options['log'] = None + address = (self.host, self.port) + server = pywsgi.WSGIServer(address, handler, **self.options) + if 'BOTTLE_CHILD' in os.environ: + import signal + signal.signal(signal.SIGINT, lambda s, f: server.stop()) + server.serve_forever() + + +class GeventSocketIOServer(ServerAdapter): + def run(self,handler): + from socketio import server + address = (self.host, self.port) + server.SocketIOServer(address, handler, **self.options).serve_forever() + + +class GunicornServer(ServerAdapter): + """ Untested. See http://gunicorn.org/configure.html for options. """ + def run(self, handler): + from gunicorn.app.base import Application + + config = {'bind': "%s:%d" % (self.host, int(self.port))} + config.update(self.options) + + class GunicornApplication(Application): + def init(self, parser, opts, args): + return config + + def load(self): + return handler + + GunicornApplication().run() + + +class EventletServer(ServerAdapter): + """ Untested """ + def run(self, handler): + from eventlet import wsgi, listen + try: + wsgi.server(listen((self.host, self.port)), handler, + log_output=(not self.quiet)) + except TypeError: + # Fallback, if we have old version of eventlet + wsgi.server(listen((self.host, self.port)), handler) + + +class RocketServer(ServerAdapter): + """ Untested. """ + def run(self, handler): + from rocket import Rocket + server = Rocket((self.host, self.port), 'wsgi', { 'wsgi_app' : handler }) + server.start() + + +class BjoernServer(ServerAdapter): + """ Fast server written in C: https://github.com/jonashaag/bjoern """ + def run(self, handler): + from bjoern import run + run(handler, self.host, self.port) + + +class AutoServer(ServerAdapter): + """ Untested. """ + adapters = [WaitressServer, PasteServer, TwistedServer, CherryPyServer, WSGIRefServer] + def run(self, handler): + for sa in self.adapters: + try: + return sa(self.host, self.port, **self.options).run(handler) + except ImportError: + pass + +server_names = { + 'cgi': CGIServer, + 'flup': FlupFCGIServer, + 'wsgiref': WSGIRefServer, + 'waitress': WaitressServer, + 'cherrypy': CherryPyServer, + 'paste': PasteServer, + 'fapws3': FapwsServer, + 'tornado': TornadoServer, + 'gae': AppEngineServer, + 'twisted': TwistedServer, + 'diesel': DieselServer, + 'meinheld': MeinheldServer, + 'gunicorn': GunicornServer, + 'eventlet': EventletServer, + 'gevent': GeventServer, + 'geventSocketIO':GeventSocketIOServer, + 'rocket': RocketServer, + 'bjoern' : BjoernServer, + 'auto': AutoServer, +} + + + + + + +############################################################################### +# Application Control ########################################################## +############################################################################### + + +def load(target, **namespace): + """ Import a module or fetch an object from a module. + + * ``package.module`` returns `module` as a module object. + * ``pack.mod:name`` returns the module variable `name` from `pack.mod`. + * ``pack.mod:func()`` calls `pack.mod.func()` and returns the result. + + The last form accepts not only function calls, but any type of + expression. Keyword arguments passed to this function are available as + local variables. Example: ``import_string('re:compile(x)', x='[a-z]')`` + """ + module, target = target.split(":", 1) if ':' in target else (target, None) + if module not in sys.modules: __import__(module) + if not target: return sys.modules[module] + if target.isalnum(): return getattr(sys.modules[module], target) + package_name = module.split('.')[0] + namespace[package_name] = sys.modules[package_name] + return eval('%s.%s' % (module, target), namespace) + + +def load_app(target): + """ Load a bottle application from a module and make sure that the import + does not affect the current default application, but returns a separate + application object. See :func:`load` for the target parameter. """ + global NORUN; NORUN, nr_old = True, NORUN + try: + tmp = default_app.push() # Create a new "default application" + rv = load(target) # Import the target module + return rv if callable(rv) else tmp + finally: + default_app.remove(tmp) # Remove the temporary added default application + NORUN = nr_old + +_debug = debug +def run(app=None, server='wsgiref', host='127.0.0.1', port=8080, + interval=1, reloader=False, quiet=False, plugins=None, + debug=None, **kargs): + """ Start a server instance. This method blocks until the server terminates. + + :param app: WSGI application or target string supported by + :func:`load_app`. (default: :func:`default_app`) + :param server: Server adapter to use. See :data:`server_names` keys + for valid names or pass a :class:`ServerAdapter` subclass. + (default: `wsgiref`) + :param host: Server address to bind to. Pass ``0.0.0.0`` to listens on + all interfaces including the external one. (default: 127.0.0.1) + :param port: Server port to bind to. Values below 1024 require root + privileges. (default: 8080) + :param reloader: Start auto-reloading server? (default: False) + :param interval: Auto-reloader interval in seconds (default: 1) + :param quiet: Suppress output to stdout and stderr? (default: False) + :param options: Options passed to the server adapter. + """ + if NORUN: return + if reloader and not os.environ.get('BOTTLE_CHILD'): + try: + lockfile = None + fd, lockfile = tempfile.mkstemp(prefix='bottle.', suffix='.lock') + os.close(fd) # We only need this file to exist. We never write to it + while os.path.exists(lockfile): + args = [sys.executable] + sys.argv + environ = os.environ.copy() + environ['BOTTLE_CHILD'] = 'true' + environ['BOTTLE_LOCKFILE'] = lockfile + p = subprocess.Popen(args, env=environ) + while p.poll() is None: # Busy wait... + os.utime(lockfile, None) # I am alive! + time.sleep(interval) + if p.poll() != 3: + if os.path.exists(lockfile): os.unlink(lockfile) + sys.exit(p.poll()) + except KeyboardInterrupt: + pass + finally: + if os.path.exists(lockfile): + os.unlink(lockfile) + return + + try: + if debug is not None: _debug(debug) + app = app or default_app() + if isinstance(app, basestring): + app = load_app(app) + if not callable(app): + raise ValueError("Application is not callable: %r" % app) + + for plugin in plugins or []: + app.install(plugin) + + if server in server_names: + server = server_names.get(server) + if isinstance(server, basestring): + server = load(server) + if isinstance(server, type): + server = server(host=host, port=port, **kargs) + if not isinstance(server, ServerAdapter): + raise ValueError("Unknown or unsupported server: %r" % server) + + server.quiet = server.quiet or quiet + if not server.quiet: + _stderr("Bottle v%s server starting up (using %s)...\n" % (__version__, repr(server))) + _stderr("Listening on http://%s:%d/\n" % (server.host, server.port)) + _stderr("Hit Ctrl-C to quit.\n\n") + + if reloader: + lockfile = os.environ.get('BOTTLE_LOCKFILE') + bgcheck = FileCheckerThread(lockfile, interval) + with bgcheck: + server.run(app) + if bgcheck.status == 'reload': + sys.exit(3) + else: + server.run(app) + except KeyboardInterrupt: + pass + except (SystemExit, MemoryError): + raise + except: + if not reloader: raise + if not getattr(server, 'quiet', quiet): + print_exc() + time.sleep(interval) + sys.exit(3) + + + +class FileCheckerThread(threading.Thread): + ''' Interrupt main-thread as soon as a changed module file is detected, + the lockfile gets deleted or gets to old. ''' + + def __init__(self, lockfile, interval): + threading.Thread.__init__(self) + self.lockfile, self.interval = lockfile, interval + #: Is one of 'reload', 'error' or 'exit' + self.status = None + + def run(self): + exists = os.path.exists + mtime = lambda path: os.stat(path).st_mtime + files = dict() + + for module in list(sys.modules.values()): + path = getattr(module, '__file__', '') or '' + if path[-4:] in ('.pyo', '.pyc'): path = path[:-1] + if path and exists(path): files[path] = mtime(path) + + while not self.status: + if not exists(self.lockfile)\ + or mtime(self.lockfile) < time.time() - self.interval - 5: + self.status = 'error' + thread.interrupt_main() + for path, lmtime in list(files.items()): + if not exists(path) or mtime(path) > lmtime: + self.status = 'reload' + thread.interrupt_main() + break + time.sleep(self.interval) + + def __enter__(self): + self.start() + + def __exit__(self, exc_type, exc_val, exc_tb): + if not self.status: self.status = 'exit' # silent exit + self.join() + return exc_type is not None and issubclass(exc_type, KeyboardInterrupt) + + + + + +############################################################################### +# Template Adapters ############################################################ +############################################################################### + + +class TemplateError(HTTPError): + def __init__(self, message): + HTTPError.__init__(self, 500, message) + + +class BaseTemplate(object): + """ Base class and minimal API for template adapters """ + extensions = ['tpl','html','thtml','stpl'] + settings = {} #used in prepare() + defaults = {} #used in render() + + def __init__(self, source=None, name=None, lookup=[], encoding='utf8', **settings): + """ Create a new template. + If the source parameter (str or buffer) is missing, the name argument + is used to guess a template filename. Subclasses can assume that + self.source and/or self.filename are set. Both are strings. + The lookup, encoding and settings parameters are stored as instance + variables. + The lookup parameter stores a list containing directory paths. + The encoding parameter should be used to decode byte strings or files. + The settings parameter contains a dict for engine-specific settings. + """ + self.name = name + self.source = source.read() if hasattr(source, 'read') else source + self.filename = source.filename if hasattr(source, 'filename') else None + self.lookup = [os.path.abspath(x) for x in lookup] + self.encoding = encoding + self.settings = self.settings.copy() # Copy from class variable + self.settings.update(settings) # Apply + if not self.source and self.name: + self.filename = self.search(self.name, self.lookup) + if not self.filename: + raise TemplateError('Template %s not found.' % repr(name)) + if not self.source and not self.filename: + raise TemplateError('No template specified.') + self.prepare(**self.settings) + + @classmethod + def search(cls, name, lookup=[]): + """ Search name in all directories specified in lookup. + First without, then with common extensions. Return first hit. """ + if not lookup: + depr('The template lookup path list should not be empty.') #0.12 + lookup = ['.'] + + if os.path.isabs(name) and os.path.isfile(name): + depr('Absolute template path names are deprecated.') #0.12 + return os.path.abspath(name) + + for spath in lookup: + spath = os.path.abspath(spath) + os.sep + fname = os.path.abspath(os.path.join(spath, name)) + if not fname.startswith(spath): continue + if os.path.isfile(fname): return fname + for ext in cls.extensions: + if os.path.isfile('%s.%s' % (fname, ext)): + return '%s.%s' % (fname, ext) + + @classmethod + def global_config(cls, key, *args): + ''' This reads or sets the global settings stored in class.settings. ''' + if args: + cls.settings = cls.settings.copy() # Make settings local to class + cls.settings[key] = args[0] + else: + return cls.settings[key] + + def prepare(self, **options): + """ Run preparations (parsing, caching, ...). + It should be possible to call this again to refresh a template or to + update settings. + """ + raise NotImplementedError + + def render(self, *args, **kwargs): + """ Render the template with the specified local variables and return + a single byte or unicode string. If it is a byte string, the encoding + must match self.encoding. This method must be thread-safe! + Local variables may be provided in dictionaries (args) + or directly, as keywords (kwargs). + """ + raise NotImplementedError + + +class MakoTemplate(BaseTemplate): + def prepare(self, **options): + from mako.template import Template + from mako.lookup import TemplateLookup + options.update({'input_encoding':self.encoding}) + options.setdefault('format_exceptions', bool(DEBUG)) + lookup = TemplateLookup(directories=self.lookup, **options) + if self.source: + self.tpl = Template(self.source, lookup=lookup, **options) + else: + self.tpl = Template(uri=self.name, filename=self.filename, lookup=lookup, **options) + + def render(self, *args, **kwargs): + for dictarg in args: kwargs.update(dictarg) + _defaults = self.defaults.copy() + _defaults.update(kwargs) + return self.tpl.render(**_defaults) + + +class CheetahTemplate(BaseTemplate): + def prepare(self, **options): + from Cheetah.Template import Template + self.context = threading.local() + self.context.vars = {} + options['searchList'] = [self.context.vars] + if self.source: + self.tpl = Template(source=self.source, **options) + else: + self.tpl = Template(file=self.filename, **options) + + def render(self, *args, **kwargs): + for dictarg in args: kwargs.update(dictarg) + self.context.vars.update(self.defaults) + self.context.vars.update(kwargs) + out = str(self.tpl) + self.context.vars.clear() + return out + + +class Jinja2Template(BaseTemplate): + def prepare(self, filters=None, tests=None, globals={}, **kwargs): + from jinja2 import Environment, FunctionLoader + if 'prefix' in kwargs: # TODO: to be removed after a while + raise RuntimeError('The keyword argument `prefix` has been removed. ' + 'Use the full jinja2 environment name line_statement_prefix instead.') + self.env = Environment(loader=FunctionLoader(self.loader), **kwargs) + if filters: self.env.filters.update(filters) + if tests: self.env.tests.update(tests) + if globals: self.env.globals.update(globals) + if self.source: + self.tpl = self.env.from_string(self.source) + else: + self.tpl = self.env.get_template(self.filename) + + def render(self, *args, **kwargs): + for dictarg in args: kwargs.update(dictarg) + _defaults = self.defaults.copy() + _defaults.update(kwargs) + return self.tpl.render(**_defaults) + + def loader(self, name): + fname = self.search(name, self.lookup) + if not fname: return + with open(fname, "rb") as f: + return f.read().decode(self.encoding) + + +class SimpleTemplate(BaseTemplate): + + def prepare(self, escape_func=html_escape, noescape=False, syntax=None, **ka): + self.cache = {} + enc = self.encoding + self._str = lambda x: touni(x, enc) + self._escape = lambda x: escape_func(touni(x, enc)) + self.syntax = syntax + if noescape: + self._str, self._escape = self._escape, self._str + + @cached_property + def co(self): + return compile(self.code, self.filename or '', 'exec') + + @cached_property + def code(self): + source = self.source + if not source: + with open(self.filename, 'rb') as f: + source = f.read() + try: + source, encoding = touni(source), 'utf8' + except UnicodeError: + depr('Template encodings other than utf8 are no longer supported.') #0.11 + source, encoding = touni(source, 'latin1'), 'latin1' + parser = StplParser(source, encoding=encoding, syntax=self.syntax) + code = parser.translate() + self.encoding = parser.encoding + return code + + def _rebase(self, _env, _name=None, **kwargs): + if _name is None: + depr('Rebase function called without arguments.' + ' You were probably looking for {{base}}?', True) #0.12 + _env['_rebase'] = (_name, kwargs) + + def _include(self, _env, _name=None, **kwargs): + if _name is None: + depr('Rebase function called without arguments.' + ' You were probably looking for {{base}}?', True) #0.12 + env = _env.copy() + env.update(kwargs) + if _name not in self.cache: + self.cache[_name] = self.__class__(name=_name, lookup=self.lookup) + return self.cache[_name].execute(env['_stdout'], env) + + def execute(self, _stdout, kwargs): + env = self.defaults.copy() + env.update(kwargs) + env.update({'_stdout': _stdout, '_printlist': _stdout.extend, + 'include': functools.partial(self._include, env), + 'rebase': functools.partial(self._rebase, env), '_rebase': None, + '_str': self._str, '_escape': self._escape, 'get': env.get, + 'setdefault': env.setdefault, 'defined': env.__contains__ }) + eval(self.co, env) + if env.get('_rebase'): + subtpl, rargs = env.pop('_rebase') + rargs['base'] = ''.join(_stdout) #copy stdout + del _stdout[:] # clear stdout + return self._include(env, subtpl, **rargs) + return env + + def render(self, *args, **kwargs): + """ Render the template using keyword arguments as local variables. """ + env = {}; stdout = [] + for dictarg in args: env.update(dictarg) + env.update(kwargs) + self.execute(stdout, env) + return ''.join(stdout) + + +class StplSyntaxError(TemplateError): pass + + +class StplParser(object): + ''' Parser for stpl templates. ''' + _re_cache = {} #: Cache for compiled re patterns + # This huge pile of voodoo magic splits python code into 8 different tokens. + # 1: All kinds of python strings (trust me, it works) + _re_tok = '([urbURB]?(?:\'\'(?!\')|""(?!")|\'{6}|"{6}' \ + '|\'(?:[^\\\\\']|\\\\.)+?\'|"(?:[^\\\\"]|\\\\.)+?"' \ + '|\'{3}(?:[^\\\\]|\\\\.|\\n)+?\'{3}' \ + '|"{3}(?:[^\\\\]|\\\\.|\\n)+?"{3}))' + _re_inl = _re_tok.replace('|\\n','') # We re-use this string pattern later + # 2: Comments (until end of line, but not the newline itself) + _re_tok += '|(#.*)' + # 3,4: Open and close grouping tokens + _re_tok += '|([\\[\\{\\(])' + _re_tok += '|([\\]\\}\\)])' + # 5,6: Keywords that start or continue a python block (only start of line) + _re_tok += '|^([ \\t]*(?:if|for|while|with|try|def|class)\\b)' \ + '|^([ \\t]*(?:elif|else|except|finally)\\b)' + # 7: Our special 'end' keyword (but only if it stands alone) + _re_tok += '|((?:^|;)[ \\t]*end[ \\t]*(?=(?:%(block_close)s[ \\t]*)?\\r?$|;|#))' + # 8: A customizable end-of-code-block template token (only end of line) + _re_tok += '|(%(block_close)s[ \\t]*(?=\\r?$))' + # 9: And finally, a single newline. The 10th token is 'everything else' + _re_tok += '|(\\r?\\n)' + + # Match the start tokens of code areas in a template + _re_split = '(?m)^[ \t]*(\\\\?)((%(line_start)s)|(%(block_start)s))(%%?)' + # Match inline statements (may contain python strings) + _re_inl = '(?m)%%(inline_start)s((?:%s|[^\'"\n]*?)+)%%(inline_end)s' % _re_inl + _re_tok = '(?m)' + _re_tok + + default_syntax = '<% %> % {{ }}' + + def __init__(self, source, syntax=None, encoding='utf8'): + self.source, self.encoding = touni(source, encoding), encoding + self.set_syntax(syntax or self.default_syntax) + self.code_buffer, self.text_buffer = [], [] + self.lineno, self.offset = 1, 0 + self.indent, self.indent_mod = 0, 0 + self.paren_depth = 0 + + def get_syntax(self): + ''' Tokens as a space separated string (default: <% %> % {{ }}) ''' + return self._syntax + + def set_syntax(self, syntax): + self._syntax = syntax + self._tokens = syntax.split() + if not syntax in self._re_cache: + names = 'block_start block_close line_start inline_start inline_end' + etokens = map(re.escape, self._tokens) + pattern_vars = dict(zip(names.split(), etokens)) + patterns = (self._re_split, self._re_tok, self._re_inl) + patterns = [re.compile(p%pattern_vars) for p in patterns] + self._re_cache[syntax] = patterns + self.re_split, self.re_tok, self.re_inl = self._re_cache[syntax] + + syntax = property(get_syntax, set_syntax) + + def translate(self): + if self.offset: raise RuntimeError('Parser is a one time instance.') + while True: + m = self.re_split.search(self.source[self.offset:]) + if m: + text = self.source[self.offset:self.offset+m.start()] + self.text_buffer.append(text) + self.offset += m.end() + if m.group(1): # New escape syntax + line, sep, _ = self.source[self.offset:].partition('\n') + self.text_buffer.append(m.group(2)+m.group(5)+line+sep) + self.offset += len(line+sep)+1 + continue + elif m.group(5): # Old escape syntax + depr('Escape code lines with a backslash.') #0.12 + line, sep, _ = self.source[self.offset:].partition('\n') + self.text_buffer.append(m.group(2)+line+sep) + self.offset += len(line+sep)+1 + continue + self.flush_text() + self.read_code(multiline=bool(m.group(4))) + else: break + self.text_buffer.append(self.source[self.offset:]) + self.flush_text() + return ''.join(self.code_buffer) + + def read_code(self, multiline): + code_line, comment = '', '' + while True: + m = self.re_tok.search(self.source[self.offset:]) + if not m: + code_line += self.source[self.offset:] + self.offset = len(self.source) + self.write_code(code_line.strip(), comment) + return + code_line += self.source[self.offset:self.offset+m.start()] + self.offset += m.end() + _str, _com, _po, _pc, _blk1, _blk2, _end, _cend, _nl = m.groups() + if (code_line or self.paren_depth > 0) and (_blk1 or _blk2): # a if b else c + code_line += _blk1 or _blk2 + continue + if _str: # Python string + code_line += _str + elif _com: # Python comment (up to EOL) + comment = _com + if multiline and _com.strip().endswith(self._tokens[1]): + multiline = False # Allow end-of-block in comments + elif _po: # open parenthesis + self.paren_depth += 1 + code_line += _po + elif _pc: # close parenthesis + if self.paren_depth > 0: + # we could check for matching parentheses here, but it's + # easier to leave that to python - just check counts + self.paren_depth -= 1 + code_line += _pc + elif _blk1: # Start-block keyword (if/for/while/def/try/...) + code_line, self.indent_mod = _blk1, -1 + self.indent += 1 + elif _blk2: # Continue-block keyword (else/elif/except/...) + code_line, self.indent_mod = _blk2, -1 + elif _end: # The non-standard 'end'-keyword (ends a block) + self.indent -= 1 + elif _cend: # The end-code-block template token (usually '%>') + if multiline: multiline = False + else: code_line += _cend + else: # \n + self.write_code(code_line.strip(), comment) + self.lineno += 1 + code_line, comment, self.indent_mod = '', '', 0 + if not multiline: + break + + def flush_text(self): + text = ''.join(self.text_buffer) + del self.text_buffer[:] + if not text: return + parts, pos, nl = [], 0, '\\\n'+' '*self.indent + for m in self.re_inl.finditer(text): + prefix, pos = text[pos:m.start()], m.end() + if prefix: + parts.append(nl.join(map(repr, prefix.splitlines(True)))) + if prefix.endswith('\n'): parts[-1] += nl + parts.append(self.process_inline(m.group(1).strip())) + if pos < len(text): + prefix = text[pos:] + lines = prefix.splitlines(True) + if lines[-1].endswith('\\\\\n'): lines[-1] = lines[-1][:-3] + elif lines[-1].endswith('\\\\\r\n'): lines[-1] = lines[-1][:-4] + parts.append(nl.join(map(repr, lines))) + code = '_printlist((%s,))' % ', '.join(parts) + self.lineno += code.count('\n')+1 + self.write_code(code) + + def process_inline(self, chunk): + if chunk[0] == '!': return '_str(%s)' % chunk[1:] + return '_escape(%s)' % chunk + + def write_code(self, line, comment=''): + line, comment = self.fix_backward_compatibility(line, comment) + code = ' ' * (self.indent+self.indent_mod) + code += line.lstrip() + comment + '\n' + self.code_buffer.append(code) + + def fix_backward_compatibility(self, line, comment): + parts = line.strip().split(None, 2) + if parts and parts[0] in ('include', 'rebase'): + depr('The include and rebase keywords are functions now.') #0.12 + if len(parts) == 1: return "_printlist([base])", comment + elif len(parts) == 2: return "_=%s(%r)" % tuple(parts), comment + else: return "_=%s(%r, %s)" % tuple(parts), comment + if self.lineno <= 2 and not line.strip() and 'coding' in comment: + m = re.match(r"#.*coding[:=]\s*([-\w.]+)", comment) + if m: + depr('PEP263 encoding strings in templates are deprecated.') #0.12 + enc = m.group(1) + self.source = self.source.encode(self.encoding).decode(enc) + self.encoding = enc + return line, comment.replace('coding','coding*') + return line, comment + + +def template(*args, **kwargs): + ''' + Get a rendered template as a string iterator. + You can use a name, a filename or a template string as first parameter. + Template rendering arguments can be passed as dictionaries + or directly (as keyword arguments). + ''' + tpl = args[0] if args else None + adapter = kwargs.pop('template_adapter', SimpleTemplate) + lookup = kwargs.pop('template_lookup', TEMPLATE_PATH) + tplid = (id(lookup), tpl) + if tplid not in TEMPLATES or DEBUG: + settings = kwargs.pop('template_settings', {}) + if isinstance(tpl, adapter): + TEMPLATES[tplid] = tpl + if settings: TEMPLATES[tplid].prepare(**settings) + elif "\n" in tpl or "{" in tpl or "%" in tpl or '$' in tpl: + TEMPLATES[tplid] = adapter(source=tpl, lookup=lookup, **settings) + else: + TEMPLATES[tplid] = adapter(name=tpl, lookup=lookup, **settings) + if not TEMPLATES[tplid]: + abort(500, 'Template (%s) not found' % tpl) + for dictarg in args[1:]: kwargs.update(dictarg) + return TEMPLATES[tplid].render(kwargs) + +mako_template = functools.partial(template, template_adapter=MakoTemplate) +cheetah_template = functools.partial(template, template_adapter=CheetahTemplate) +jinja2_template = functools.partial(template, template_adapter=Jinja2Template) + + +def view(tpl_name, **defaults): + ''' Decorator: renders a template for a handler. + The handler can control its behavior like that: + + - return a dict of template vars to fill out the template + - return something other than a dict and the view decorator will not + process the template, but return the handler result as is. + This includes returning a HTTPResponse(dict) to get, + for instance, JSON with autojson or other castfilters. + ''' + def decorator(func): + @functools.wraps(func) + def wrapper(*args, **kwargs): + result = func(*args, **kwargs) + if isinstance(result, (dict, DictMixin)): + tplvars = defaults.copy() + tplvars.update(result) + return template(tpl_name, **tplvars) + elif result is None: + return template(tpl_name, defaults) + return result + return wrapper + return decorator + +mako_view = functools.partial(view, template_adapter=MakoTemplate) +cheetah_view = functools.partial(view, template_adapter=CheetahTemplate) +jinja2_view = functools.partial(view, template_adapter=Jinja2Template) + + + + + + +############################################################################### +# Constants and Globals ######################################################## +############################################################################### + + +TEMPLATE_PATH = ['./', './views/'] +TEMPLATES = {} +DEBUG = False +NORUN = False # If set, run() does nothing. Used by load_app() + +#: A dict to map HTTP status codes (e.g. 404) to phrases (e.g. 'Not Found') +HTTP_CODES = httplib.responses +HTTP_CODES[418] = "I'm a teapot" # RFC 2324 +HTTP_CODES[422] = "Unprocessable Entity" # RFC 4918 +HTTP_CODES[428] = "Precondition Required" +HTTP_CODES[429] = "Too Many Requests" +HTTP_CODES[431] = "Request Header Fields Too Large" +HTTP_CODES[511] = "Network Authentication Required" +_HTTP_STATUS_LINES = dict((k, '%d %s'%(k,v)) for (k,v) in HTTP_CODES.items()) + +#: The default template used for error pages. Override with @error() +ERROR_PAGE_TEMPLATE = """ +%%try: + %%from %s import DEBUG, HTTP_CODES, request, touni + + + + Error: {{e.status}} + + + +

Error: {{e.status}}

+

Sorry, the requested URL {{repr(request.url)}} + caused an error:

+
{{e.body}}
+ %%if DEBUG and e.exception: +

Exception:

+
{{repr(e.exception)}}
+ %%end + %%if DEBUG and e.traceback: +

Traceback:

+
{{e.traceback}}
+ %%end + + +%%except ImportError: + ImportError: Could not generate the error page. Please add bottle to + the import path. +%%end +""" % __name__ + +#: A thread-safe instance of :class:`LocalRequest`. If accessed from within a +#: request callback, this instance always refers to the *current* request +#: (even on a multithreaded server). +request = LocalRequest() + +#: A thread-safe instance of :class:`LocalResponse`. It is used to change the +#: HTTP response for the *current* request. +response = LocalResponse() + +#: A thread-safe namespace. Not used by Bottle. +local = threading.local() + +# Initialize app stack (create first empty Bottle app) +# BC: 0.6.4 and needed for run() +app = default_app = AppStack() +app.push() + +#: A virtual package that redirects import statements. +#: Example: ``import bottle.ext.sqlite`` actually imports `bottle_sqlite`. +ext = _ImportRedirect('bottle.ext' if __name__ == '__main__' else __name__+".ext", 'bottle_%s').module + +if __name__ == '__main__': + opt, args, parser = _cmd_options, _cmd_args, _cmd_parser + if opt.version: + _stdout('Bottle %s\n'%__version__) + sys.exit(0) + if not args: + parser.print_help() + _stderr('\nError: No application specified.\n') + sys.exit(1) + + sys.path.insert(0, '.') + sys.modules.setdefault('bottle', sys.modules['__main__']) + + host, port = (opt.bind or 'localhost'), 8080 + if ':' in host and host.rfind(']') < host.rfind(':'): + host, port = host.rsplit(':', 1) + host = host.strip('[]') + + run(args[0], host=host, port=int(port), server=opt.server, + reloader=opt.reload, plugins=opt.plugin, debug=opt.debug) + + + + +# THE END diff --git a/22-asyncio/mojifinder/charindex.py b/22-asyncio/mojifinder/charindex.py new file mode 100755 index 0000000..5312af1 --- /dev/null +++ b/22-asyncio/mojifinder/charindex.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python + +""" +Class ``InvertedIndex`` builds an inverted index mapping each word to +the set of Unicode characters which contain that word in their names. + +Optional arguments to the constructor are ``first`` and ``last+1`` character +codes to index, to make testing easier. + +In the example below, only the ASCII range was indexed:: + + >>> idx = InvertedIndex(32, 128) + >>> sorted(idx.entries['SIGN']) + ['#', '$', '%', '+', '<', '=', '>'] + >>> sorted(idx.entries['DIGIT']) + ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'] + >>> idx.entries['DIGIT'] & idx.entries['EIGHT'] + {'8'} + >>> idx.search('digit') + ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'] + >>> idx.search('eight digit') + ['8'] + >>> idx.search('a letter') + ['A', 'a'] + >>> idx.search('a letter capital') + ['A'] + >>> idx.search('borogove') + [] + +""" + +import sys +import unicodedata +from collections import defaultdict +from collections.abc import Iterator + +STOP_CODE: int = sys.maxunicode + 1 + +Char = str +Index = defaultdict[str, set[Char]] + + +def tokenize(text: str) -> Iterator[str]: + """return iterator of uppercased words""" + for word in text.upper().replace('-', ' ').split(): + yield word + + +class InvertedIndex: + entries: Index + + def __init__(self, start: int = 32, stop: int = STOP_CODE): + entries: Index = defaultdict(set) + for char in (chr(i) for i in range(start, stop)): + name = unicodedata.name(char, '') + if name: + for word in tokenize(name): + entries[word].add(char) + self.entries = entries + + def search(self, query: str) -> list[Char]: + if words := list(tokenize(query)): + first = self.entries[words[0]] + result = first.intersection(*(self.entries[w] for w in words[1:])) + return sorted(result) + else: + return [] + + +def format_results(chars: list[Char]) -> Iterator[str]: + for char in chars: + name = unicodedata.name(char) + code = ord(char) + yield f'U+{code:04X}\t{char}\t{name}' + + +def main(words: list[str]) -> None: + if not words: + print('Please give one or more words to search.') + sys.exit() + index = InvertedIndex() + chars = index.search(' '.join(words)) + for line in format_results(chars): + print(line) + print('─' * 66, f'{len(chars)} found') + + +if __name__ == '__main__': + main(sys.argv[1:]) diff --git a/22-asyncio/mojifinder/requirements.txt b/22-asyncio/mojifinder/requirements.txt new file mode 100644 index 0000000..4c17510 --- /dev/null +++ b/22-asyncio/mojifinder/requirements.txt @@ -0,0 +1,6 @@ +click==7.1.2 +fastapi==0.63.0 +h11==0.12.0 +pydantic==1.7.3 +starlette==0.13.6 +uvicorn==0.13.4 diff --git a/22-asyncio/mojifinder/static/form.html b/22-asyncio/mojifinder/static/form.html new file mode 100644 index 0000000..8ccc91a --- /dev/null +++ b/22-asyncio/mojifinder/static/form.html @@ -0,0 +1,83 @@ + + + + + Mojifinder + + + + + +
+ + +
+ + +
+ + diff --git a/22-asyncio/mojifinder/tcp_mojifinder.py b/22-asyncio/mojifinder/tcp_mojifinder.py new file mode 100755 index 0000000..01e18e8 --- /dev/null +++ b/22-asyncio/mojifinder/tcp_mojifinder.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python3 + +# tag::TCP_MOJIFINDER_TOP[] +import sys +import asyncio +import functools + +from charindex import InvertedIndex, format_results # <1> + +CRLF = b'\r\n' +PROMPT = b'?> ' + +async def finder(index: InvertedIndex, # <2> + reader: asyncio.StreamReader, + writer: asyncio.StreamWriter): + client = writer.get_extra_info('peername') # <3> + while True: # <4> + writer.write(PROMPT) # can't await! # <5> + await writer.drain() # must await! # <6> + data = await reader.readline() # <7> + try: + query = data.decode().strip() # <8> + except UnicodeDecodeError: # <9> + query = '\x00' + print(f' From {client}: {query!r}') # <10> + if query: + if ord(query[:1]) < 32: # <11> + break + results = await search(query, index, writer) # <12> + print(f' To {client}: {results} results.') # <13> + + writer.close() # <14> + await writer.wait_closed() # <15> + print(f'Close {client}.') # <16> +# end::TCP_MOJIFINDER_TOP[] + +# tag::TCP_MOJIFINDER_SEARCH[] +async def search(query: str, # <1> + index: InvertedIndex, + writer: asyncio.StreamWriter) -> int: + chars = index.search(query) # <2> + lines = (line.encode() + CRLF for line # <3> + in format_results(chars)) + writer.writelines(lines) # <4> + await writer.drain() # <5> + status_line = f'{"─" * 66} {len(chars)} found' # <6> + writer.write(status_line.encode() + CRLF) + await writer.drain() + return len(chars) +# end::TCP_MOJIFINDER_SEARCH[] + +# tag::TCP_MOJIFINDER_MAIN[] +async def supervisor(index: InvertedIndex, host: str, port: int): + server = await asyncio.start_server( # <1> + functools.partial(finder, index), # <2> + host, port) # <3> + addr = server.sockets[0].getsockname() # type: ignore # <4> + print(f'Serving on {addr}. Hit CTRL-C to stop.') + await server.serve_forever() # <5> + +def main(host: str = '127.0.0.1', port_arg: str = '2323'): + port = int(port_arg) + print('Building index.') + index = InvertedIndex() # <6> + try: + asyncio.run(supervisor(index, host, port)) # <7> + except KeyboardInterrupt: # <8> + print('\nServer shut down.') + +if __name__ == '__main__': + main(*sys.argv[1:]) +# end::TCP_MOJIFINDER_MAIN[] diff --git a/22-asyncio/mojifinder/web_mojifinder.py b/22-asyncio/mojifinder/web_mojifinder.py new file mode 100644 index 0000000..44ef24e --- /dev/null +++ b/22-asyncio/mojifinder/web_mojifinder.py @@ -0,0 +1,38 @@ +""" +uvicorn main:app --reload +""" + +import pathlib +from unicodedata import name + +from fastapi import FastAPI +from fastapi.responses import HTMLResponse +from pydantic import BaseModel + +from charindex import InvertedIndex + +app = FastAPI( + title='Mojifinder Web', + description='Search for Unicode characters by name.', +) + +class CharName(BaseModel): + char: str + name: str + +def init(app): + app.state.index = InvertedIndex() + static = pathlib.Path(__file__).parent.absolute() / 'static' + with open(static / 'form.html') as fp: + app.state.form = fp.read() + +init(app) + +@app.get('/', response_class=HTMLResponse, include_in_schema=False) +def form(): + return app.state.form + +@app.get('/search', response_model=list[CharName]) +async def search(q: str): + chars = app.state.index.search(q) + return [{'char': c, 'name': name(c)} for c in chars] diff --git a/22-asyncio/mojifinder/web_mojifinder_bottle.py b/22-asyncio/mojifinder/web_mojifinder_bottle.py new file mode 100755 index 0000000..1aa0c2b --- /dev/null +++ b/22-asyncio/mojifinder/web_mojifinder_bottle.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python3 + +import json +import unicodedata + +from bottle import route, request, run, static_file + +from charindex import InvertedIndex + +index = {} + +@route('/') +def form(): + return static_file('form.html', root = 'static/') + + +@route('/search') +def search(): + query = request.query['q'] + chars = index.search(query) + results = [] + for char in chars: + name = unicodedata.name(char) + results.append({'char': char, 'name': name}) + return json.dumps(results).encode('UTF-8') + + +def main(port): + global index + index = InvertedIndex() + host = 'localhost' + run(host='localhost', port=port, debug=True) + + +if __name__ == '__main__': + main(8000) + From 66e46a6db65deb2fafd77b2fc0955b03d068d312 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Sun, 28 Feb 2021 17:16:44 -0300 Subject: [PATCH 020/166] ch 21 and 22: updated examples --- 22-asyncio/mojifinder/web_mojifinder.py | 26 ++++++++++++------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/22-asyncio/mojifinder/web_mojifinder.py b/22-asyncio/mojifinder/web_mojifinder.py index 44ef24e..7e4ef2e 100644 --- a/22-asyncio/mojifinder/web_mojifinder.py +++ b/22-asyncio/mojifinder/web_mojifinder.py @@ -1,7 +1,3 @@ -""" -uvicorn main:app --reload -""" - import pathlib from unicodedata import name @@ -11,28 +7,30 @@ from charindex import InvertedIndex -app = FastAPI( +app = FastAPI( # <1> title='Mojifinder Web', description='Search for Unicode characters by name.', ) -class CharName(BaseModel): +class CharName(BaseModel): # <2> char: str name: str -def init(app): +def init(app): # <3> app.state.index = InvertedIndex() static = pathlib.Path(__file__).parent.absolute() / 'static' with open(static / 'form.html') as fp: app.state.form = fp.read() -init(app) +init(app) # <4> + +@app.get('/search', response_model=list[CharName]) # <5> +async def search(q: str): # <6> + chars = app.state.index.search(q) + return ({'char': c, 'name': name(c)} for c in chars) # <7> -@app.get('/', response_class=HTMLResponse, include_in_schema=False) +@app.get('/', # <8> + response_class=HTMLResponse, + include_in_schema=False) def form(): return app.state.form - -@app.get('/search', response_model=list[CharName]) -async def search(q: str): - chars = app.state.index.search(q) - return [{'char': c, 'name': name(c)} for c in chars] From d2bcf655d7df385e6dd4164e414852754ec870a7 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Sun, 28 Feb 2021 17:19:53 -0300 Subject: [PATCH 021/166] updated aiohttp due to https://github.com/advisories/GHSA-v6wp-4m6f-gcjg --- 21-futures/getflags/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/21-futures/getflags/requirements.txt b/21-futures/getflags/requirements.txt index a09b073..0971a38 100644 --- a/21-futures/getflags/requirements.txt +++ b/21-futures/getflags/requirements.txt @@ -1,4 +1,4 @@ -aiohttp==3.7.3 +aiohttp==3.7.4 async-timeout==3.0.1 attrs==20.3.0 certifi==2020.12.5 From a751c86836c80f340fc1114ad1749ab3f87d5bda Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Sun, 28 Feb 2021 21:19:40 -0300 Subject: [PATCH 022/166] ch22: examples --- 21-futures/getflags/flags.py | 5 +- 21-futures/getflags/flags2_asyncio.py | 84 +++++++++++------------- 21-futures/getflags/flags2_common.py | 8 ++- 21-futures/getflags/flags2_sequential.py | 5 +- 21-futures/getflags/flags2_threadpool.py | 4 +- 21-futures/getflags/flags_asyncio.py | 26 ++++---- 21-futures/getflags/flags_threadpool.py | 2 +- 21-futures/getflags/slow_server.py | 3 +- 22-asyncio/domains/netaddr.py | 25 +++++++ 22-asyncio/domains/probe.py | 21 ++++++ 10 files changed, 108 insertions(+), 75 deletions(-) create mode 100644 22-asyncio/domains/netaddr.py create mode 100755 22-asyncio/domains/probe.py diff --git a/21-futures/getflags/flags.py b/21-futures/getflags/flags.py index 1411041..3d6d36f 100755 --- a/21-futures/getflags/flags.py +++ b/21-futures/getflags/flags.py @@ -33,16 +33,15 @@ def save_flag(img: bytes, filename: str) -> None: # <5> (DEST_DIR / filename).write_bytes(img) def get_flag(cc: str) -> bytes: # <6> - cc = cc.lower() - url = f'{BASE_URL}/{cc}/{cc}.gif' + url = f'{BASE_URL}/{cc}/{cc}.gif'.lower() resp = requests.get(url) return resp.content def download_many(cc_list: list[str]) -> int: # <7> for cc in sorted(cc_list): # <8> image = get_flag(cc) + save_flag(image, f'{cc}.gif') print(cc, end=' ', flush=True) # <9> - save_flag(image, cc.lower() + '.gif') return len(cc_list) def main(downloader: Callable[[list[str]], int]) -> None: # <10> diff --git a/21-futures/getflags/flags2_asyncio.py b/21-futures/getflags/flags2_asyncio.py index 739c531..e7a73ac 100755 --- a/21-futures/getflags/flags2_asyncio.py +++ b/21-futures/getflags/flags2_asyncio.py @@ -5,13 +5,11 @@ asyncio async/await version """ -# BEGIN FLAGS2_ASYNCIO_TOP +# tag::FLAGS2_ASYNCIO_TOP[] import asyncio from collections import Counter import aiohttp -from aiohttp import web -from aiohttp.http_exceptions import HttpProcessingError import tqdm # type: ignore from flags2_common import main, HTTPStatus, Result, save_flag @@ -23,91 +21,83 @@ class FetchError(Exception): # <1> - def __init__(self, country_code): + def __init__(self, country_code: str): self.country_code = country_code -async def get_flag(session, base_url, cc): # <2> - cc = cc.lower() - url = f'{base_url}/{cc}/{cc}.gif' +async def get_flag(session: aiohttp.ClientSession, # <2> + base_url: str, + cc: str) -> bytes: + url = f'{base_url}/{cc}/{cc}.gif'.lower() async with session.get(url) as resp: if resp.status == 200: return await resp.read() - elif resp.status == 404: - raise web.HTTPNotFound() else: - raise HttpProcessingError( - code=resp.status, message=resp.reason, - headers=resp.headers) - - -async def download_one(session, cc, base_url, semaphore, verbose): # <3> + resp.raise_for_status() # <3> + return bytes() + +async def download_one(session: aiohttp.ClientSession, # <4> + cc: str, + base_url: str, + semaphore: asyncio.Semaphore, + verbose: bool) -> Result: try: - async with semaphore: # <4> - image = await get_flag(session, base_url, cc) # <5> - except web.HTTPNotFound: # <6> - status = HTTPStatus.not_found - msg = 'not found' - except Exception as exc: - raise FetchError(cc) from exc # <7> + async with semaphore: # <5> + image = await get_flag(session, base_url, cc) + except aiohttp.ClientResponseError as exc: + if exc.status == 404: # <6> + status = HTTPStatus.not_found + msg = 'not found' + else: + raise FetchError(cc) from exc # <7> else: - save_flag(image, cc.lower() + '.gif') # <8> + save_flag(image, f'{cc}.gif') status = HTTPStatus.ok msg = 'OK' - if verbose and msg: print(cc, msg) - return Result(status, cc) -# END FLAGS2_ASYNCIO_TOP +# end::FLAGS2_ASYNCIO_TOP[] -# BEGIN FLAGS2_ASYNCIO_DOWNLOAD_MANY -async def downloader_coro(cc_list: list[str], - base_url: str, - verbose: bool, - concur_req: int) -> Counter[HTTPStatus]: # <1> +# tag::FLAGS2_ASYNCIO_START[] +async def supervisor(cc_list: list[str], + base_url: str, + verbose: bool, + concur_req: int) -> Counter[HTTPStatus]: # <1> counter: Counter[HTTPStatus] = Counter() semaphore = asyncio.Semaphore(concur_req) # <2> - async with aiohttp.ClientSession() as session: # <8> + async with aiohttp.ClientSession() as session: to_do = [download_one(session, cc, base_url, semaphore, verbose) for cc in sorted(cc_list)] # <3> - to_do_iter = asyncio.as_completed(to_do) # <4> if not verbose: to_do_iter = tqdm.tqdm(to_do_iter, total=len(cc_list)) # <5> - for future in to_do_iter: # <6> + for coro in to_do_iter: # <6> try: - res = await future # <7> + res = await coro # <7> except FetchError as exc: # <8> country_code = exc.country_code # <9> try: - if exc.__cause__ is None: - error_msg = 'Unknown cause' - else: - error_msg = exc.__cause__.args[0] # <10> - except IndexError: - error_msg = exc.__cause__.__class__.__name__ # <11> + error_msg = exc.__cause__.message # type: ignore # <10> + except AttributeError: + error_msg = 'Unknown cause' # <11> if verbose and error_msg: print(f'*** Error for {country_code}: {error_msg}') status = HTTPStatus.error else: status = res.status - counter[status] += 1 # <12> - return counter # <13> - def download_many(cc_list: list[str], base_url: str, verbose: bool, concur_req: int) -> Counter[HTTPStatus]: - coro = downloader_coro(cc_list, base_url, verbose, concur_req) + coro = supervisor(cc_list, base_url, verbose, concur_req) counts = asyncio.run(coro) # <14> return counts - if __name__ == '__main__': main(download_many, DEFAULT_CONCUR_REQ, MAX_CONCUR_REQ) -# END FLAGS2_ASYNCIO_DOWNLOAD_MANY +# end::FLAGS2_ASYNCIO_START[] diff --git a/21-futures/getflags/flags2_common.py b/21-futures/getflags/flags2_common.py index 7c76498..c772820 100644 --- a/21-futures/getflags/flags2_common.py +++ b/21-futures/getflags/flags2_common.py @@ -45,8 +45,10 @@ def initial_report(cc_list: list[str], print(f'{server_label} site: {SERVERS[server_label]}') plural = 's' if len(cc_list) != 1 else '' print(f'Searching for {len(cc_list)} flag{plural}: {cc_msg}') - plural = 's' if actual_req != 1 else '' - print(f'{actual_req} concurrent connection{plural} will be used.') + if actual_req == 1: + print('1 connection will be used.') + else: + print(f'{actual_req} concurrent connections will be used.') def final_report(cc_list: list[str], @@ -60,7 +62,7 @@ def final_report(cc_list: list[str], print(f'{counter[HTTPStatus.not_found]} not found.') if counter[HTTPStatus.error]: plural = 's' if counter[HTTPStatus.error] != 1 else '' - print(f'{counter[HTTPStatus.error]} error{plural}') + print(f'{counter[HTTPStatus.error]} error{plural}.') print(f'Elapsed time: {elapsed:.2f}s') diff --git a/21-futures/getflags/flags2_sequential.py b/21-futures/getflags/flags2_sequential.py index d08bd50..2e30856 100755 --- a/21-futures/getflags/flags2_sequential.py +++ b/21-futures/getflags/flags2_sequential.py @@ -29,8 +29,7 @@ # tag::FLAGS2_BASIC_HTTP_FUNCTIONS[] def get_flag(base_url: str, cc: str) -> bytes: - cc = cc.lower() - url = f'{base_url}/{cc}/{cc}.gif' + url = f'{base_url}/{cc}/{cc}.gif'.lower() resp = requests.get(url) if resp.status_code != 200: # <1> resp.raise_for_status() @@ -47,7 +46,7 @@ def download_one(cc: str, base_url: str, verbose: bool = False): else: # <4> raise else: - save_flag(image, cc.lower() + '.gif') + save_flag(image, f'{cc}.gif') status = HTTPStatus.ok msg = 'OK' diff --git a/21-futures/getflags/flags2_threadpool.py b/21-futures/getflags/flags2_threadpool.py index 8454a80..bbe71cb 100755 --- a/21-futures/getflags/flags2_threadpool.py +++ b/21-futures/getflags/flags2_threadpool.py @@ -40,8 +40,8 @@ def download_many(cc_list: list[str], with futures.ThreadPoolExecutor(max_workers=concur_req) as executor: # <6> to_do_map = {} # <7> for cc in sorted(cc_list): # <8> - future = executor.submit(download_one, - cc, base_url, verbose) # <9> + future = executor.submit(download_one, cc, + base_url, verbose) # <9> to_do_map[future] = cc # <10> done_iter = futures.as_completed(to_do_map) # <11> if not verbose: diff --git a/21-futures/getflags/flags_asyncio.py b/21-futures/getflags/flags_asyncio.py index b5dad24..ee431d0 100755 --- a/21-futures/getflags/flags_asyncio.py +++ b/21-futures/getflags/flags_asyncio.py @@ -20,31 +20,27 @@ async def download_one(session: ClientSession, cc: str): # <3> image = await get_flag(session, cc) + save_flag(image, f'{cc}.gif') print(cc, end=' ', flush=True) - save_flag(image, cc.lower() + '.gif') return cc async def get_flag(session: ClientSession, cc: str) -> bytes: # <4> - cc = cc.lower() - url = f'{BASE_URL}/{cc}/{cc}.gif' + url = f'{BASE_URL}/{cc}/{cc}.gif'.lower() async with session.get(url) as resp: # <5> - print(resp) return await resp.read() # <6> - - # end::FLAGS_ASYNCIO_TOP[] -# end::FLAGS_ASYNCIO_START[] -def download_many(cc_list): # <1> - return asyncio.run(supervisor(cc_list)) # <2> +# tag::FLAGS_ASYNCIO_START[] +def download_many(cc_list: list[str]) -> int: # <1> + return asyncio.run(supervisor(cc_list)) # <2> -async def supervisor(cc_list): - async with ClientSession() as session: # <3> - to_do = [download_one(session, cc) - for cc in sorted(cc_list)] # <4> - res = await asyncio.gather(*to_do) # <5> +async def supervisor(cc_list: list[str]) -> int: + async with ClientSession() as session: # <3> + to_do = [download_one(session, cc) # <4> + for cc in sorted(cc_list)] + res = await asyncio.gather(*to_do) # <5> - return len(res) # <6> + return len(res) # <6> if __name__ == '__main__': main(download_many) diff --git a/21-futures/getflags/flags_threadpool.py b/21-futures/getflags/flags_threadpool.py index 4fdef0e..fef56f6 100755 --- a/21-futures/getflags/flags_threadpool.py +++ b/21-futures/getflags/flags_threadpool.py @@ -19,8 +19,8 @@ def download_one(cc: str): # <2> image = get_flag(cc) + save_flag(image, f'{cc}.gif') print(cc, end=' ', flush=True) - save_flag(image, cc.lower() + '.gif') return cc def download_many(cc_list: list[str]) -> int: diff --git a/21-futures/getflags/slow_server.py b/21-futures/getflags/slow_server.py index 25587a0..9d8142b 100755 --- a/21-futures/getflags/slow_server.py +++ b/21-futures/getflags/slow_server.py @@ -83,7 +83,8 @@ def server_bind(self): socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0) return super().server_bind() - server.test( + # test is a top-level function in http.server omitted from __all__ + server.test( # type: ignore HandlerClass=handler_class, ServerClass=DualStackServer, port=args.port, diff --git a/22-asyncio/domains/netaddr.py b/22-asyncio/domains/netaddr.py new file mode 100644 index 0000000..4df3b78 --- /dev/null +++ b/22-asyncio/domains/netaddr.py @@ -0,0 +1,25 @@ +import asyncio +import socket +from collections.abc import Iterable, AsyncIterator +from typing import NamedTuple + + +class Result(NamedTuple): + name: str + found: bool + + +async def probe(loop: asyncio.AbstractEventLoop, name: str) -> Result: + try: + await loop.getaddrinfo(name, None) + except socket.gaierror: + return Result(name, False) + return Result(name, True) + + +async def multi_probe(names: Iterable[str]) -> AsyncIterator[Result]: + loop = asyncio.get_running_loop() + coros = [probe(loop, name) for name in names] + for coro in asyncio.as_completed(coros): + result = await coro + yield result diff --git a/22-asyncio/domains/probe.py b/22-asyncio/domains/probe.py new file mode 100755 index 0000000..0ab235c --- /dev/null +++ b/22-asyncio/domains/probe.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 + +import asyncio +import sys +from keyword import kwlist + +from netaddr import multi_probe + + +async def main(tld: str) -> None: + names = (f'{w}.{tld}'.lower() for w in kwlist if len(w) <= 4) + async for name, found in multi_probe(sorted(names)): + mark = '.' if found else '?\t\t' + print(f'{mark} {name}') + + +if __name__ == '__main__': + if len(sys.argv) == 2: + asyncio.run(main(sys.argv[1])) + else: + print('Please provide a TLD.', f'Example: {sys.argv[0]} COM.BR') From 5aaa4e49296a9e61b16074dcf57a290314eee224 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Sun, 28 Feb 2021 21:45:34 -0300 Subject: [PATCH 023/166] ch22: examples --- 22-asyncio/domains/probe.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/22-asyncio/domains/probe.py b/22-asyncio/domains/probe.py index 0ab235c..1f20e76 100755 --- a/22-asyncio/domains/probe.py +++ b/22-asyncio/domains/probe.py @@ -8,8 +8,10 @@ async def main(tld: str) -> None: - names = (f'{w}.{tld}'.lower() for w in kwlist if len(w) <= 4) - async for name, found in multi_probe(sorted(names)): + tld = tld.strip('.') + names = (w.lower() for w in kwlist if len(w) <= 4) + domains = (f'{name}.{tld}' for name in names) + async for name, found in multi_probe(domains): mark = '.' if found else '?\t\t' print(f'{mark} {name}') From 98b8cfd068ae157ba7e2ea6a832b5c548a02f513 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Sun, 28 Feb 2021 21:58:35 -0300 Subject: [PATCH 024/166] ch22: examples --- 22-asyncio/domains/probe.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/22-asyncio/domains/probe.py b/22-asyncio/domains/probe.py index 1f20e76..2df75b3 100755 --- a/22-asyncio/domains/probe.py +++ b/22-asyncio/domains/probe.py @@ -9,8 +9,8 @@ async def main(tld: str) -> None: tld = tld.strip('.') - names = (w.lower() for w in kwlist if len(w) <= 4) - domains = (f'{name}.{tld}' for name in names) + names = (kw for kw in kwlist if len(kw) <= 4) + domains = (f'{name}.{tld}'.lower() for name in names) async for name, found in multi_probe(domains): mark = '.' if found else '?\t\t' print(f'{mark} {name}') From 4dcb6aadf4829e5c52a03c092a99fa1d17d41b5b Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Sun, 28 Feb 2021 22:09:40 -0300 Subject: [PATCH 025/166] ch22: examples --- 22-asyncio/domains/netaddr.py | 14 +++++++------- 22-asyncio/domains/probe.py | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/22-asyncio/domains/netaddr.py b/22-asyncio/domains/netaddr.py index 4df3b78..e01624d 100644 --- a/22-asyncio/domains/netaddr.py +++ b/22-asyncio/domains/netaddr.py @@ -5,21 +5,21 @@ class Result(NamedTuple): - name: str + domain: str found: bool -async def probe(loop: asyncio.AbstractEventLoop, name: str) -> Result: +async def probe(loop: asyncio.AbstractEventLoop, domain: str) -> Result: try: - await loop.getaddrinfo(name, None) + await loop.getaddrinfo(domain, None) except socket.gaierror: - return Result(name, False) - return Result(name, True) + return Result(domain, False) + return Result(domain, True) -async def multi_probe(names: Iterable[str]) -> AsyncIterator[Result]: +async def multi_probe(domains: Iterable[str]) -> AsyncIterator[Result]: loop = asyncio.get_running_loop() - coros = [probe(loop, name) for name in names] + coros = [probe(loop, domain) for domain in domains] for coro in asyncio.as_completed(coros): result = await coro yield result diff --git a/22-asyncio/domains/probe.py b/22-asyncio/domains/probe.py index 2df75b3..b3c0844 100755 --- a/22-asyncio/domains/probe.py +++ b/22-asyncio/domains/probe.py @@ -11,9 +11,9 @@ async def main(tld: str) -> None: tld = tld.strip('.') names = (kw for kw in kwlist if len(kw) <= 4) domains = (f'{name}.{tld}'.lower() for name in names) - async for name, found in multi_probe(domains): + async for domain, found in multi_probe(domains): mark = '.' if found else '?\t\t' - print(f'{mark} {name}') + print(f'{mark} {domain}') if __name__ == '__main__': From cdbca8030e9ff33abfd39a738fe2d65394ff31c4 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Tue, 2 Mar 2021 17:22:02 -0300 Subject: [PATCH 026/166] ch22: examples --- 21-futures/getflags/flags3_asyncio.py | 127 ++++++++++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100755 21-futures/getflags/flags3_asyncio.py diff --git a/21-futures/getflags/flags3_asyncio.py b/21-futures/getflags/flags3_asyncio.py new file mode 100755 index 0000000..37b5957 --- /dev/null +++ b/21-futures/getflags/flags3_asyncio.py @@ -0,0 +1,127 @@ +#!/usr/bin/env python3 + +"""Download flags of countries (with error handling). + +asyncio async/await version using run_in_executor for save_flag. + +""" + +import asyncio +from collections import Counter + +import aiohttp +import tqdm # type: ignore + +from flags2_common import main, HTTPStatus, Result, save_flag + +# default set low to avoid errors from remote site, such as +# 503 - Service Temporarily Unavailable +DEFAULT_CONCUR_REQ = 5 +MAX_CONCUR_REQ = 1000 + + +class FetchError(Exception): + def __init__(self, country_code: str): + self.country_code = country_code + +async def get_flag(session: aiohttp.ClientSession, + base_url: str, + cc: str) -> bytes: + url = f'{base_url}/{cc}/{cc}.gif' + async with session.get(url) as resp: + if resp.status == 200: + return await resp.read() + else: + resp.raise_for_status() + return bytes() + +# tag::FLAGS3_ASYNCIO_GET_COUNTRY[] +async def get_country(session: aiohttp.ClientSession, # <1> + base_url: str, + cc: str) -> str: + url = f'{base_url}/{cc}/metadata.json' + async with session.get(url) as resp: + if resp.status == 200: + metadata = await resp.json() # <2> + return metadata.get('country', 'no name') # <3> + else: + resp.raise_for_status() + return '' +# end::FLAGS3_ASYNCIO_GET_COUNTRY[] + +# tag::FLAGS3_ASYNCIO_DOWNLOAD_ONE[] +async def download_one(session: aiohttp.ClientSession, + cc: str, + base_url: str, + semaphore: asyncio.Semaphore, + verbose: bool) -> Result: + try: + async with semaphore: + image = await get_flag(session, base_url, cc) # <1> + async with semaphore: + country = await get_country(session, base_url, cc) # <2> + except aiohttp.ClientResponseError as exc: + if exc.status == 404: + status = HTTPStatus.not_found + msg = 'not found' + else: + raise FetchError(cc) from exc + else: + filename = country.replace(' ', '_') # <3> + filename = f'{filename}.gif' + loop = asyncio.get_running_loop() + loop.run_in_executor(None, + save_flag, image, filename) + status = HTTPStatus.ok + msg = 'OK' + if verbose and msg: + print(cc, msg) + return Result(status, cc) +# end::FLAGS3_ASYNCIO_DOWNLOAD_ONE[] + +async def supervisor(cc_list: list[str], + base_url: str, + verbose: bool, + concur_req: int) -> Counter[HTTPStatus]: + counter: Counter[HTTPStatus] = Counter() + semaphore = asyncio.Semaphore(concur_req) + async with aiohttp.ClientSession() as session: + to_do = [download_one(session, cc, base_url, + semaphore, verbose) + for cc in sorted(cc_list)] + + to_do_iter = asyncio.as_completed(to_do) + if not verbose: + to_do_iter = tqdm.tqdm(to_do_iter, total=len(cc_list)) + for coro in to_do_iter: + try: + res = await coro + except FetchError as exc: + country_code = exc.country_code + try: + error_msg = exc.__cause__.message # type: ignore + except AttributeError: + error_msg = 'Unknown cause' + if verbose and error_msg: + print(f'*** Error for {country_code}: {error_msg}') + status = HTTPStatus.error + else: + status = res.status + + counter[status] += 1 + + return counter + + +def download_many(cc_list: list[str], + base_url: str, + verbose: bool, + concur_req: int) -> Counter[HTTPStatus]: + coro = supervisor(cc_list, base_url, verbose, concur_req) + counts = asyncio.run(coro) # <14> + + return counts + + +if __name__ == '__main__': + main(download_many, DEFAULT_CONCUR_REQ, MAX_CONCUR_REQ) From 14e6d536fa4fb313a5ba947d2f8273e301441c9d Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Wed, 10 Mar 2021 20:48:17 -0300 Subject: [PATCH 027/166] ch22: sync --- 22-asyncio/domains/blogdom.py | 29 +++++++++++++++++++++++++++++ 22-asyncio/domains/domaincheck.py | 24 ++++++++++++++++++++++++ 22-asyncio/domains/domainlib.py | 30 ++++++++++++++++++++++++++++++ 22-asyncio/domains/netaddr.py | 25 ------------------------- 22-asyncio/domains/probe.py | 23 ----------------------- 5 files changed, 83 insertions(+), 48 deletions(-) create mode 100755 22-asyncio/domains/blogdom.py create mode 100755 22-asyncio/domains/domaincheck.py create mode 100644 22-asyncio/domains/domainlib.py delete mode 100644 22-asyncio/domains/netaddr.py delete mode 100755 22-asyncio/domains/probe.py diff --git a/22-asyncio/domains/blogdom.py b/22-asyncio/domains/blogdom.py new file mode 100755 index 0000000..0121733 --- /dev/null +++ b/22-asyncio/domains/blogdom.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 +import asyncio +import socket +from keyword import kwlist + +MAX_KEYWORD_LEN = 4 # <1> + + +async def probe(domain: str) -> tuple[str, bool]: # <2> + loop = asyncio.get_running_loop() # <3> + try: + await loop.getaddrinfo(domain, None) # <4> + except socket.gaierror: + return (domain, False) + return (domain, True) + + +async def main() -> None: # <5> + names = (kw for kw in kwlist if len(kw) <= MAX_KEYWORD_LEN) # <6> + domains = (f'{name}.dev'.lower() for name in names) # <7> + coros = [probe(domain) for domain in domains] # <8> + for coro in asyncio.as_completed(coros): # <9> + domain, found = await coro # <10> + mark = '+' if found else ' ' + print(f'{mark} {domain}') + + +if __name__ == '__main__': + asyncio.run(main()) # <11> diff --git a/22-asyncio/domains/domaincheck.py b/22-asyncio/domains/domaincheck.py new file mode 100755 index 0000000..cf18995 --- /dev/null +++ b/22-asyncio/domains/domaincheck.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python3 +import asyncio +import sys +from keyword import kwlist + +from domainlib import multi_probe + + +async def main(tld: str) -> None: + tld = tld.strip('.') + names = (kw for kw in kwlist if len(kw) <= 4) # <1> + domains = (f'{name}.{tld}'.lower() for name in names) # <2> + print('FOUND\t\tNOT FOUND') # <3> + print('=====\t\t=========') + async for domain, found in multi_probe(domains): # <4> + indent = '' if found else '\t\t' # <5> + print(f'{indent}{domain}') + + +if __name__ == '__main__': + if len(sys.argv) == 2: + asyncio.run(main(sys.argv[1])) # <6> + else: + print('Please provide a TLD.', f'Example: {sys.argv[0]} COM.BR') diff --git a/22-asyncio/domains/domainlib.py b/22-asyncio/domains/domainlib.py new file mode 100644 index 0000000..29c231a --- /dev/null +++ b/22-asyncio/domains/domainlib.py @@ -0,0 +1,30 @@ +import asyncio +import socket +from collections.abc import Iterable, AsyncIterator +from typing import NamedTuple, Optional + + +class Result(NamedTuple): # <1> + domain: str + found: bool + + +OptionalLoop = Optional[asyncio.AbstractEventLoop] # <2> + + +async def probe(domain: str, loop: OptionalLoop = None) -> Result: # <3> + if loop is None: + loop = asyncio.get_running_loop() + try: + await loop.getaddrinfo(domain, None) + except socket.gaierror: + return Result(domain, False) + return Result(domain, True) + + +async def multi_probe(domains: Iterable[str]) -> AsyncIterator[Result]: # <4> + loop = asyncio.get_running_loop() + coros = [probe(domain, loop) for domain in domains] # <5> + for coro in asyncio.as_completed(coros): # <6> + result = await coro # <7> + yield result # <8> diff --git a/22-asyncio/domains/netaddr.py b/22-asyncio/domains/netaddr.py deleted file mode 100644 index e01624d..0000000 --- a/22-asyncio/domains/netaddr.py +++ /dev/null @@ -1,25 +0,0 @@ -import asyncio -import socket -from collections.abc import Iterable, AsyncIterator -from typing import NamedTuple - - -class Result(NamedTuple): - domain: str - found: bool - - -async def probe(loop: asyncio.AbstractEventLoop, domain: str) -> Result: - try: - await loop.getaddrinfo(domain, None) - except socket.gaierror: - return Result(domain, False) - return Result(domain, True) - - -async def multi_probe(domains: Iterable[str]) -> AsyncIterator[Result]: - loop = asyncio.get_running_loop() - coros = [probe(loop, domain) for domain in domains] - for coro in asyncio.as_completed(coros): - result = await coro - yield result diff --git a/22-asyncio/domains/probe.py b/22-asyncio/domains/probe.py deleted file mode 100755 index b3c0844..0000000 --- a/22-asyncio/domains/probe.py +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env python3 - -import asyncio -import sys -from keyword import kwlist - -from netaddr import multi_probe - - -async def main(tld: str) -> None: - tld = tld.strip('.') - names = (kw for kw in kwlist if len(kw) <= 4) - domains = (f'{name}.{tld}'.lower() for name in names) - async for domain, found in multi_probe(domains): - mark = '.' if found else '?\t\t' - print(f'{mark} {domain}') - - -if __name__ == '__main__': - if len(sys.argv) == 2: - asyncio.run(main(sys.argv[1])) - else: - print('Please provide a TLD.', f'Example: {sys.argv[0]} COM.BR') From 7c88948b2236b1676ab2b14557dce5fd665d96d7 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Thu, 11 Mar 2021 14:35:00 -0300 Subject: [PATCH 028/166] simple Curio examples --- 22-asyncio/domains/{ => asyncio}/blogdom.py | 0 .../domains/{ => asyncio}/domaincheck.py | 0 22-asyncio/domains/{ => asyncio}/domainlib.py | 0 22-asyncio/domains/curio/blogdom.py | 30 +++++++++++++++++++ 22-asyncio/domains/curio/domaincheck.py | 24 +++++++++++++++ 22-asyncio/domains/curio/domainlib.py | 25 ++++++++++++++++ 22-asyncio/domains/curio/requirements.txt | 1 + 7 files changed, 80 insertions(+) rename 22-asyncio/domains/{ => asyncio}/blogdom.py (100%) rename 22-asyncio/domains/{ => asyncio}/domaincheck.py (100%) rename 22-asyncio/domains/{ => asyncio}/domainlib.py (100%) create mode 100755 22-asyncio/domains/curio/blogdom.py create mode 100755 22-asyncio/domains/curio/domaincheck.py create mode 100644 22-asyncio/domains/curio/domainlib.py create mode 100644 22-asyncio/domains/curio/requirements.txt diff --git a/22-asyncio/domains/blogdom.py b/22-asyncio/domains/asyncio/blogdom.py similarity index 100% rename from 22-asyncio/domains/blogdom.py rename to 22-asyncio/domains/asyncio/blogdom.py diff --git a/22-asyncio/domains/domaincheck.py b/22-asyncio/domains/asyncio/domaincheck.py similarity index 100% rename from 22-asyncio/domains/domaincheck.py rename to 22-asyncio/domains/asyncio/domaincheck.py diff --git a/22-asyncio/domains/domainlib.py b/22-asyncio/domains/asyncio/domainlib.py similarity index 100% rename from 22-asyncio/domains/domainlib.py rename to 22-asyncio/domains/asyncio/domainlib.py diff --git a/22-asyncio/domains/curio/blogdom.py b/22-asyncio/domains/curio/blogdom.py new file mode 100755 index 0000000..b36a77d --- /dev/null +++ b/22-asyncio/domains/curio/blogdom.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 +from curio import run, TaskGroup +from curio.socket import getaddrinfo, gaierror +from keyword import kwlist + +MAX_KEYWORD_LEN = 4 # <1> + + +async def probe(domain: str) -> tuple[str, bool]: # <2> + try: + await getaddrinfo(domain, None) # <4> + except gaierror: + return (domain, False) + return (domain, True) + + +async def main() -> None: # <5> + names = (kw for kw in kwlist if len(kw) <= MAX_KEYWORD_LEN) # <6> + domains = (f'{name}.dev'.lower() for name in names) # <7> + async with TaskGroup() as group: + for domain in domains: + await group.spawn(probe, domain) + async for task in group: # <9> + domain, found = task.result # <10> + mark = '+' if found else ' ' + print(f'{mark} {domain}') + + +if __name__ == '__main__': + run(main()) # <11> diff --git a/22-asyncio/domains/curio/domaincheck.py b/22-asyncio/domains/curio/domaincheck.py new file mode 100755 index 0000000..54c17b3 --- /dev/null +++ b/22-asyncio/domains/curio/domaincheck.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python3 +import curio +import sys +from keyword import kwlist + +from domainlib import multi_probe + + +async def main(tld: str) -> None: + tld = tld.strip('.') + names = (kw for kw in kwlist if len(kw) <= 4) + domains = (f'{name}.{tld}'.lower() for name in names) + print('FOUND\t\tNOT FOUND') + print('=====\t\t=========') + async for domain, found in multi_probe(domains): + indent = '' if found else '\t\t' + print(f'{indent}{domain}') + + +if __name__ == '__main__': + if len(sys.argv) == 2: + curio.run(main(sys.argv[1])) + else: + print('Please provide a TLD.', f'Example: {sys.argv[0]} COM.BR') diff --git a/22-asyncio/domains/curio/domainlib.py b/22-asyncio/domains/curio/domainlib.py new file mode 100644 index 0000000..4f6bb90 --- /dev/null +++ b/22-asyncio/domains/curio/domainlib.py @@ -0,0 +1,25 @@ +from curio import TaskGroup +from curio.socket import getaddrinfo, gaierror +from collections.abc import Iterable, AsyncIterator +from typing import NamedTuple + + +class Result(NamedTuple): + domain: str + found: bool + + +async def probe(domain: str) -> Result: + try: + await getaddrinfo(domain, None) + except gaierror: + return Result(domain, False) + return Result(domain, True) + + +async def multi_probe(domains: Iterable[str]) -> AsyncIterator[Result]: + async with TaskGroup() as group: + for domain in domains: + await group.spawn(probe, domain) + async for task in group: + yield task.result diff --git a/22-asyncio/domains/curio/requirements.txt b/22-asyncio/domains/curio/requirements.txt new file mode 100644 index 0000000..e38c404 --- /dev/null +++ b/22-asyncio/domains/curio/requirements.txt @@ -0,0 +1 @@ +curio==1.5 From e1cd63aa040afc8d984706531e203304d0540cbe Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Thu, 11 Mar 2021 16:11:39 -0300 Subject: [PATCH 029/166] renamed 22-async/ --- .../domains/asyncio/blogdom.py | 0 .../domains/asyncio/domaincheck.py | 0 .../domains/asyncio/domainlib.py | 0 .../domains/curio/blogdom.py | 0 .../domains/curio/domaincheck.py | 0 .../domains/curio/domainlib.py | 0 .../domains/curio/requirements.txt | 0 {22-asyncio => 22-async}/mojifinder/bottle.py | 0 {22-asyncio => 22-async}/mojifinder/charindex.py | 0 .../mojifinder/requirements.txt | 0 .../mojifinder/static/form.html | 0 .../mojifinder/tcp_mojifinder.py | 0 .../mojifinder/web_mojifinder.py | 0 .../mojifinder/web_mojifinder_bottle.py | 0 README.md | 16 ++++++++-------- 15 files changed, 8 insertions(+), 8 deletions(-) rename {22-asyncio => 22-async}/domains/asyncio/blogdom.py (100%) rename {22-asyncio => 22-async}/domains/asyncio/domaincheck.py (100%) rename {22-asyncio => 22-async}/domains/asyncio/domainlib.py (100%) rename {22-asyncio => 22-async}/domains/curio/blogdom.py (100%) rename {22-asyncio => 22-async}/domains/curio/domaincheck.py (100%) rename {22-asyncio => 22-async}/domains/curio/domainlib.py (100%) rename {22-asyncio => 22-async}/domains/curio/requirements.txt (100%) rename {22-asyncio => 22-async}/mojifinder/bottle.py (100%) rename {22-asyncio => 22-async}/mojifinder/charindex.py (100%) rename {22-asyncio => 22-async}/mojifinder/requirements.txt (100%) rename {22-asyncio => 22-async}/mojifinder/static/form.html (100%) rename {22-asyncio => 22-async}/mojifinder/tcp_mojifinder.py (100%) rename {22-asyncio => 22-async}/mojifinder/web_mojifinder.py (100%) rename {22-asyncio => 22-async}/mojifinder/web_mojifinder_bottle.py (100%) diff --git a/22-asyncio/domains/asyncio/blogdom.py b/22-async/domains/asyncio/blogdom.py similarity index 100% rename from 22-asyncio/domains/asyncio/blogdom.py rename to 22-async/domains/asyncio/blogdom.py diff --git a/22-asyncio/domains/asyncio/domaincheck.py b/22-async/domains/asyncio/domaincheck.py similarity index 100% rename from 22-asyncio/domains/asyncio/domaincheck.py rename to 22-async/domains/asyncio/domaincheck.py diff --git a/22-asyncio/domains/asyncio/domainlib.py b/22-async/domains/asyncio/domainlib.py similarity index 100% rename from 22-asyncio/domains/asyncio/domainlib.py rename to 22-async/domains/asyncio/domainlib.py diff --git a/22-asyncio/domains/curio/blogdom.py b/22-async/domains/curio/blogdom.py similarity index 100% rename from 22-asyncio/domains/curio/blogdom.py rename to 22-async/domains/curio/blogdom.py diff --git a/22-asyncio/domains/curio/domaincheck.py b/22-async/domains/curio/domaincheck.py similarity index 100% rename from 22-asyncio/domains/curio/domaincheck.py rename to 22-async/domains/curio/domaincheck.py diff --git a/22-asyncio/domains/curio/domainlib.py b/22-async/domains/curio/domainlib.py similarity index 100% rename from 22-asyncio/domains/curio/domainlib.py rename to 22-async/domains/curio/domainlib.py diff --git a/22-asyncio/domains/curio/requirements.txt b/22-async/domains/curio/requirements.txt similarity index 100% rename from 22-asyncio/domains/curio/requirements.txt rename to 22-async/domains/curio/requirements.txt diff --git a/22-asyncio/mojifinder/bottle.py b/22-async/mojifinder/bottle.py similarity index 100% rename from 22-asyncio/mojifinder/bottle.py rename to 22-async/mojifinder/bottle.py diff --git a/22-asyncio/mojifinder/charindex.py b/22-async/mojifinder/charindex.py similarity index 100% rename from 22-asyncio/mojifinder/charindex.py rename to 22-async/mojifinder/charindex.py diff --git a/22-asyncio/mojifinder/requirements.txt b/22-async/mojifinder/requirements.txt similarity index 100% rename from 22-asyncio/mojifinder/requirements.txt rename to 22-async/mojifinder/requirements.txt diff --git a/22-asyncio/mojifinder/static/form.html b/22-async/mojifinder/static/form.html similarity index 100% rename from 22-asyncio/mojifinder/static/form.html rename to 22-async/mojifinder/static/form.html diff --git a/22-asyncio/mojifinder/tcp_mojifinder.py b/22-async/mojifinder/tcp_mojifinder.py similarity index 100% rename from 22-asyncio/mojifinder/tcp_mojifinder.py rename to 22-async/mojifinder/tcp_mojifinder.py diff --git a/22-asyncio/mojifinder/web_mojifinder.py b/22-async/mojifinder/web_mojifinder.py similarity index 100% rename from 22-asyncio/mojifinder/web_mojifinder.py rename to 22-async/mojifinder/web_mojifinder.py diff --git a/22-asyncio/mojifinder/web_mojifinder_bottle.py b/22-async/mojifinder/web_mojifinder_bottle.py similarity index 100% rename from 22-asyncio/mojifinder/web_mojifinder_bottle.py rename to 22-async/mojifinder/web_mojifinder_bottle.py diff --git a/README.md b/README.md index 3835e54..3e6eecf 100644 --- a/README.md +++ b/README.md @@ -27,26 +27,26 @@ Part / Chapter #|Title|Directory|Notebook|1st ed. Chapter # 3|Dictionaries and Sets|[03-dict-set](03-dict-set)||3 4|Text versus Bytes|[04-text-byte](04-text-byte)||4 🆕 5|Record-like Data Structures|[05-record-like](05-record-like)||– +6|Object References, Mutability, and Recycling|[06-obj-ref](06-obj-ref)||8 **III – Functions as Objects**| -6|First-Class Funcions|[06-1class-func](06-1class-func)||5 -7|Design Patterns with First-Class Functions|[07-dp-1class-func](07-dp-1class-func)||6 -8|Function Decorators and Closures|[08-closure-deco](08-closure-deco)||7 -🆕 9|Type Hints in Function Definitions|[09-def-type-hints](09-def-type-hints)||– +7|First-Class Funcions|[07-1class-func](07-1class-func)||5 +🆕 8|Type Hints in Function Definitions|[08-def-type-hints](08-def-type-hints)||– +9|Function Decorators and Closures|[09-closure-deco](09-closure-deco)||7 +10|Design Patterns with First-Class Functions|[10-dp-1class-func](10-dp-1class-func)||6 **IV – Object-Oriented Idioms**| -10|Object References, Mutability, and Recycling|[10-obj-ref](10-obj-ref)||8 11|A Pythonic Object|[11-pythonic-obj](11-pythonic-obj)||9 12|Sequence Hacking, Hashing, and Slicing|[12-seq-hacking](12-seq-hacking)||10 -13|Interfaces: From Protocols to ABCs|[13-iface-abc](13-iface-abc)||11 +13|Interfaces: Interfaces, Protocols, and ABCs|[13-protocl-abc](13-protocol-abc)||11 14|Inheritance: For Good or For Worse|[14-inheritance](14-inheritance)||12 🆕 15|More About Type Hints|[15-type-hints](15-type-hints)||– 16|Operator Overloading: Doing It Right|[16-op-overloading](16-op-overloading)||13 **V – Control Flow**| 17|Iterables, Iterators, and Generators|[17-it-generator](17-it-generator)||14 18|Context Managers and else Blocks|[18-context-mngr](18-context-mngr)||15 -19|Coroutines|[19-coroutine](19-coroutine)||16 +19|Classic Coroutines|[19-coroutine](19-coroutine)||16 🆕 20|Concurrency Models in Python|[20-concurrency](20-concurrency)||- 21|Concurrency with Futures|[21-futures](21-futures)||17 -22|Concurrency with asyncio|[22-asyncio](22-asyncio)||18 +22|Asynchronous Programming|[22-async](22-async)||18 **VI – Metaprogramming**| 23|Dynamic Attributes and Properties|[22-dyn-attr-prop](22-dyn-attr-prop)||19 24|Attribute Descriptors|[23-descriptor](23-descriptor)||20 From 2f8bf06270f58670cdddf07715d1d1526cc87df2 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Mon, 22 Mar 2021 12:24:21 -0300 Subject: [PATCH 030/166] update from O'Reilly repo --- 08-def-type-hints/comparable/comparable.py | 2 +- 08-def-type-hints/comparable/mymax.py | 26 ++--- 08-def-type-hints/comparable/mymax_demo.py | 1 + 08-def-type-hints/comparable/top.py | 6 +- 08-def-type-hints/comparable/top_test.py | 1 + 08-def-type-hints/passdrill.py | 6 +- 11-pythonic-obj/mem_test.py | 2 +- 15-type-hints/erp.py | 14 +++ 15-type-hints/erp_test.py | 38 ++++++ 15-type-hints/randompick_generic.py | 7 ++ 15-type-hints/randompick_generic_test.py | 35 ++++++ 15-type-hints/randompop.py | 6 + 15-type-hints/randompop_test.py | 24 ++++ 17-it-generator/sentence_genexp.py | 2 +- 17-it-generator/sentence_iter.py | 2 +- 20-concurrency/primes/procs_py37.py | 43 ------- 20-concurrency/primes/py36/primes.py | 51 ++++++++ 20-concurrency/primes/py36/procs.py | 71 ++++++++++++ .../getflags/flags2_asyncio_executor.py | 109 ++++++++++++++++++ 21-futures/getflags/flags2_common.py | 10 +- 22-async/README.rst | 4 + 22-async/domains/README.rst | 28 +++++ 22-async/domains/curio/blogdom.py | 28 +++-- 22-async/domains/curio/domainlib.py | 8 +- 22-async/mojifinder/charindex.py | 49 ++++---- 22-async/mojifinder/static/form.html | 2 - 22-async/mojifinder/web_mojifinder.py | 18 +-- README.md | 2 +- 28 files changed, 470 insertions(+), 125 deletions(-) create mode 100644 15-type-hints/erp.py create mode 100644 15-type-hints/erp_test.py create mode 100644 15-type-hints/randompick_generic.py create mode 100644 15-type-hints/randompick_generic_test.py create mode 100644 15-type-hints/randompop.py create mode 100644 15-type-hints/randompop_test.py delete mode 100644 20-concurrency/primes/procs_py37.py create mode 100755 20-concurrency/primes/py36/primes.py create mode 100755 20-concurrency/primes/py36/procs.py create mode 100755 21-futures/getflags/flags2_asyncio_executor.py create mode 100644 22-async/README.rst create mode 100644 22-async/domains/README.rst diff --git a/08-def-type-hints/comparable/comparable.py b/08-def-type-hints/comparable/comparable.py index 2c8aa16..179e050 100644 --- a/08-def-type-hints/comparable/comparable.py +++ b/08-def-type-hints/comparable/comparable.py @@ -1,4 +1,4 @@ from typing import Protocol, Any -class Comparable(Protocol): # <1> +class SupportsLessThan(Protocol): # <1> def __lt__(self, other: Any) -> bool: ... # <2> diff --git a/08-def-type-hints/comparable/mymax.py b/08-def-type-hints/comparable/mymax.py index 26dfec2..868ecd4 100644 --- a/08-def-type-hints/comparable/mymax.py +++ b/08-def-type-hints/comparable/mymax.py @@ -1,35 +1,35 @@ # tag::MYMAX_TYPES[] from typing import Protocol, Any, TypeVar, overload, Callable, Iterable, Union -class _Comparable(Protocol): +class SupportsLessThan(Protocol): def __lt__(self, other: Any) -> bool: ... -_T = TypeVar('_T') -_CT = TypeVar('_CT', bound=_Comparable) -_DT = TypeVar('_DT') +T = TypeVar('T') +LT = TypeVar('LT', bound=SupportsLessThan) +DT = TypeVar('DT') MISSING = object() EMPTY_MSG = 'max() arg is an empty sequence' @overload -def max(__arg1: _CT, __arg2: _CT, *_args: _CT, key: None = ...) -> _CT: +def max(__arg1: LT, __arg2: LT, *_args: LT, key: None = ...) -> LT: ... @overload -def max(__arg1: _T, __arg2: _T, *_args: _T, key: Callable[[_T], _CT]) -> _T: +def max(__arg1: T, __arg2: T, *_args: T, key: Callable[[T], LT]) -> T: ... @overload -def max(__iterable: Iterable[_CT], *, key: None = ...) -> _CT: +def max(__iterable: Iterable[LT], *, key: None = ...) -> LT: ... @overload -def max(__iterable: Iterable[_T], *, key: Callable[[_T], _CT]) -> _T: +def max(__iterable: Iterable[T], *, key: Callable[[T], LT]) -> T: ... @overload -def max(__iterable: Iterable[_CT], *, key: None = ..., - default: _DT) -> Union[_CT, _DT]: +def max(__iterable: Iterable[LT], *, key: None = ..., + default: DT) -> Union[LT, DT]: ... @overload -def max(__iterable: Iterable[_T], *, key: Callable[[_T], _CT], - default: _DT) -> Union[_T, _DT]: +def max(__iterable: Iterable[T], *, key: Callable[[T], LT], + default: DT) -> Union[T, DT]: ... # end::MYMAX_TYPES[] # tag::MYMAX[] @@ -57,4 +57,4 @@ def max(first, *args, key=None, default=MISSING): candidate = current candidate_key = current_key return candidate -# end::MYMAX[] \ No newline at end of file +# end::MYMAX[] diff --git a/08-def-type-hints/comparable/mymax_demo.py b/08-def-type-hints/comparable/mymax_demo.py index 7ee6123..f2fe3d9 100644 --- a/08-def-type-hints/comparable/mymax_demo.py +++ b/08-def-type-hints/comparable/mymax_demo.py @@ -116,6 +116,7 @@ def error_single_arg_not_iterable() -> None: except TypeError as exc: print(exc) +###################################### run demo and error functions def main(): for name, val in globals().items(): diff --git a/08-def-type-hints/comparable/top.py b/08-def-type-hints/comparable/top.py index 320b696..c552890 100644 --- a/08-def-type-hints/comparable/top.py +++ b/08-def-type-hints/comparable/top.py @@ -21,10 +21,10 @@ # tag::TOP[] from typing import TypeVar, Iterable, List -from comparable import Comparable +from comparable import SupportsLessThan -CT = TypeVar('CT', bound=Comparable) +LT = TypeVar('LT', bound=SupportsLessThan) -def top(series: Iterable[CT], length: int) -> List[CT]: +def top(series: Iterable[LT], length: int) -> List[LT]: return sorted(series, reverse=True)[:length] # end::TOP[] diff --git a/08-def-type-hints/comparable/top_test.py b/08-def-type-hints/comparable/top_test.py index 2e69e6c..baff36b 100644 --- a/08-def-type-hints/comparable/top_test.py +++ b/08-def-type-hints/comparable/top_test.py @@ -29,6 +29,7 @@ def test_top_tuples() -> None: reveal_type(result) assert result == expected +# intentional type error def test_top_objects_error() -> None: series = [object() for _ in range(4)] if TYPE_CHECKING: diff --git a/08-def-type-hints/passdrill.py b/08-def-type-hints/passdrill.py index 612e504..08d1ff0 100755 --- a/08-def-type-hints/passdrill.py +++ b/08-def-type-hints/passdrill.py @@ -54,7 +54,9 @@ def load_hash() -> Tuple[bytes, bytes]: salted_hash = fp.read() except FileNotFoundError: print('ERROR: passphrase hash file not found.', HELP) - sys.exit(2) + # "standard" exit status codes: + # https://stackoverflow.com/questions/1101957/are-there-any-standard-exit-status-codes-in-linux/40484670#40484670 + sys.exit(74) # input/output error salt, stored_hash = salted_hash.split(b':') return b64decode(salt), b64decode(stored_hash) @@ -93,7 +95,7 @@ def main(argv: Sequence[str]) -> None: save_hash() else: print('ERROR: invalid argument.', HELP) - sys.exit(1) + sys.exit(2) # command line usage error if __name__ == '__main__': diff --git a/11-pythonic-obj/mem_test.py b/11-pythonic-obj/mem_test.py index c18527a..0d745f2 100644 --- a/11-pythonic-obj/mem_test.py +++ b/11-pythonic-obj/mem_test.py @@ -9,7 +9,7 @@ module = importlib.import_module(module_name) else: print(f'Usage: {sys.argv[0]} ') - sys.exit(1) + sys.exit(2) # command line usage error fmt = 'Selected Vector2d type: {.__name__}.{.__name__}' print(fmt.format(module, module.Vector2d)) diff --git a/15-type-hints/erp.py b/15-type-hints/erp.py new file mode 100644 index 0000000..a91622f --- /dev/null +++ b/15-type-hints/erp.py @@ -0,0 +1,14 @@ +import random +from typing import TypeVar, Generic, List, Iterable + + +T = TypeVar('T') + + +class EnterpriserRandomPopper(Generic[T]): + def __init__(self, items: Iterable[T]) -> None: + self._items: List[T] = list(items) + random.shuffle(self._items) + + def pop_random(self) -> T: + return self._items.pop() diff --git a/15-type-hints/erp_test.py b/15-type-hints/erp_test.py new file mode 100644 index 0000000..5e8c067 --- /dev/null +++ b/15-type-hints/erp_test.py @@ -0,0 +1,38 @@ +import random +from typing import Iterable, TYPE_CHECKING, List + +from erp import EnterpriserRandomPopper +import randompop + + +def test_issubclass() -> None: + assert issubclass(EnterpriserRandomPopper, randompop.RandomPopper) + + + +def test_isinstance_untyped_items_argument() -> None: + items = [1, 2, 3] + popper = EnterpriserRandomPopper(items) # [int] is not required + if TYPE_CHECKING: + reveal_type(popper) + # Revealed type is 'erp.EnterpriserRandomPopper[builtins.int*]' + assert isinstance(popper, randompop.RandomPopper) + + +def test_isinstance_untyped_items_in_var_type() -> None: + items = [1, 2, 3] + popper: EnterpriserRandomPopper = EnterpriserRandomPopper[int](items) + if TYPE_CHECKING: + reveal_type(popper) + # Revealed type is 'erp.EnterpriserRandomPopper[Any]' + assert isinstance(popper, randompop.RandomPopper) + + +def test_isinstance_item() -> None: + items = [1, 2, 3] + popper = EnterpriserRandomPopper[int](items) # [int] is not required + popped = popper.pop_random() + if TYPE_CHECKING: + reveal_type(popped) + # Revealed type is 'builtins.int*' + assert isinstance(popped, int) diff --git a/15-type-hints/randompick_generic.py b/15-type-hints/randompick_generic.py new file mode 100644 index 0000000..6e4aa10 --- /dev/null +++ b/15-type-hints/randompick_generic.py @@ -0,0 +1,7 @@ +from typing import Protocol, runtime_checkable, TypeVar + +T_co = TypeVar('T_co', covariant=True) + +@runtime_checkable +class GenericRandomPicker(Protocol[T_co]): + def pick(self) -> T_co: ... diff --git a/15-type-hints/randompick_generic_test.py b/15-type-hints/randompick_generic_test.py new file mode 100644 index 0000000..07ebdc4 --- /dev/null +++ b/15-type-hints/randompick_generic_test.py @@ -0,0 +1,35 @@ +import random +from typing import Iterable, TYPE_CHECKING + +from randompick_generic import GenericRandomPicker + + +class LottoPicker(): + def __init__(self, items: Iterable[int]) -> None: + self._items = list(items) + random.shuffle(self._items) + + def pick(self) -> int: + return self._items.pop() + + +def test_issubclass() -> None: + assert issubclass(LottoPicker, GenericRandomPicker) + + +def test_isinstance() -> None: + popper: GenericRandomPicker = LottoPicker([1]) + if TYPE_CHECKING: + reveal_type(popper) + # Revealed type is '???' + assert isinstance(popper, LottoPicker) + + +def test_pick_type() -> None: + balls = [1, 2, 3] + popper = LottoPicker(balls) + pick = popper.pick() + assert pick in balls + if TYPE_CHECKING: + reveal_type(pick) + # Revealed type is '???' \ No newline at end of file diff --git a/15-type-hints/randompop.py b/15-type-hints/randompop.py new file mode 100644 index 0000000..35cad38 --- /dev/null +++ b/15-type-hints/randompop.py @@ -0,0 +1,6 @@ +from typing import Protocol, TypeVar, runtime_checkable, Any + + +@runtime_checkable +class RandomPopper(Protocol): + def pop_random(self) -> Any: ... diff --git a/15-type-hints/randompop_test.py b/15-type-hints/randompop_test.py new file mode 100644 index 0000000..0b0f317 --- /dev/null +++ b/15-type-hints/randompop_test.py @@ -0,0 +1,24 @@ +from randompop import RandomPopper +import random +from typing import Any, Iterable, TYPE_CHECKING + + +class SimplePopper(): + def __init__(self, items: Iterable) -> None: + self._items = list(items) + random.shuffle(self._items) + + def pop_random(self) -> Any: + return self._items.pop() + + +def test_issubclass() -> None: + assert issubclass(SimplePopper, RandomPopper) + + +def test_isinstance() -> None: + popper: RandomPopper = SimplePopper([1]) + if TYPE_CHECKING: + reveal_type(popper) + # Revealed type is 'randompop.RandomPopper' + assert isinstance(popper, RandomPopper) diff --git a/17-it-generator/sentence_genexp.py b/17-it-generator/sentence_genexp.py index a5ff584..b75e859 100644 --- a/17-it-generator/sentence_genexp.py +++ b/17-it-generator/sentence_genexp.py @@ -30,7 +30,7 @@ def main(): word_number = int(sys.argv[2]) except (IndexError, ValueError): print('Usage: %s ' % sys.argv[0]) - sys.exit(1) + sys.exit(2) # command line usage error with open(filename, 'rt', encoding='utf-8') as text_file: s = Sentence(text_file.read()) for n, word in enumerate(s, 1): diff --git a/17-it-generator/sentence_iter.py b/17-it-generator/sentence_iter.py index 71ee3f3..5472179 100644 --- a/17-it-generator/sentence_iter.py +++ b/17-it-generator/sentence_iter.py @@ -51,7 +51,7 @@ def main(): word_number = int(sys.argv[2]) except (IndexError, ValueError): print('Usage: %s ' % sys.argv[0]) - sys.exit(1) + sys.exit(2) # command line usage error with open(filename, 'rt', encoding='utf-8') as text_file: s = Sentence(text_file.read()) for n, word in enumerate(s, 1): diff --git a/20-concurrency/primes/procs_py37.py b/20-concurrency/primes/procs_py37.py deleted file mode 100644 index 1810aec..0000000 --- a/20-concurrency/primes/procs_py37.py +++ /dev/null @@ -1,43 +0,0 @@ -# tag::PRIMES_PROC_TOP[] -from time import perf_counter -from typing import List, NamedTuple -from multiprocessing import Process, SimpleQueue # <1> - -from primes import is_prime, NUMBERS - -class Result(NamedTuple): # <3> - flag: bool - elapsed: float - -def check(n: int) -> Result: # <5> - t0 = perf_counter() - res = is_prime(n) - return Result(res, perf_counter() - t0) - -def job(n: int, results: SimpleQueue) -> None: # <6> - results.put((n, check(n))) # <7> -# end::PRIMES_PROC_TOP[] - -# tag::PRIMES_PROC_MAIN[] -def main() -> None: - t0 = perf_counter() - results = SimpleQueue() # type: ignore - workers: List[Process] = [] # <2> - - for n in NUMBERS: - worker = Process(target=job, args=(n, results)) # <3> - worker.start() # <4> - workers.append(worker) # <5> - - for _ in workers: # <6> - n, (prime, elapsed) = results.get() # <7> - label = 'P' if prime else ' ' - print(f'{n:16} {label} {elapsed:9.6f}s') - - elapsed = perf_counter() - t0 - print(f'Total time: {elapsed:.2f}s') - - -if __name__ == '__main__': - main() -# end::PRIMES_PROC_MAIN[] diff --git a/20-concurrency/primes/py36/primes.py b/20-concurrency/primes/py36/primes.py new file mode 100755 index 0000000..4c5559f --- /dev/null +++ b/20-concurrency/primes/py36/primes.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python3 + +import math + +PRIME_FIXTURE = [ + (2, True), + (142702110479723, True), + (299593572317531, True), + (3333333333333301, True), + (3333333333333333, False), + (3333335652092209, False), + (4444444444444423, True), + (4444444444444444, False), + (4444444488888889, False), + (5555553133149889, False), + (5555555555555503, True), + (5555555555555555, False), + (6666666666666666, False), + (6666666666666719, True), + (6666667141414921, False), + (7777777536340681, False), + (7777777777777753, True), + (7777777777777777, False), + (9999999999999917, True), + (9999999999999999, False), +] + +NUMBERS = [n for n, _ in PRIME_FIXTURE] + +# tag::IS_PRIME[] +def is_prime(n: int) -> bool: + if n < 2: + return False + if n == 2: + return True + if n % 2 == 0: + return False + + root = math.floor(math.sqrt(n)) + for i in range(3, root + 1, 2): + if n % i == 0: + return False + return True +# end::IS_PRIME[] + +if __name__ == '__main__': + + for n, prime in PRIME_FIXTURE: + prime_res = is_prime(n) + assert prime_res == prime + print(n, prime) diff --git a/20-concurrency/primes/py36/procs.py b/20-concurrency/primes/py36/procs.py new file mode 100755 index 0000000..12ed36f --- /dev/null +++ b/20-concurrency/primes/py36/procs.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python3 + +""" +procs.py: shows that multiprocessing on a multicore machine +can be faster than sequential code for CPU-intensive work. +""" + +# tag::PRIMES_PROC_TOP[] +import sys +from time import perf_counter +from typing import NamedTuple +from multiprocessing import Process, SimpleQueue, cpu_count # <1> +from multiprocessing import queues # <2> + +from primes import is_prime, NUMBERS + +class PrimeResult(NamedTuple): # <3> + n: int + prime: bool + elapsed: float + +JobQueue = queues.SimpleQueue # <4> +ResultQueue = queues.SimpleQueue # <5> + +def check(n: int) -> PrimeResult: # <6> + t0 = perf_counter() + res = is_prime(n) + return PrimeResult(n, res, perf_counter() - t0) + +def worker(jobs: JobQueue, results: ResultQueue) -> None: # <7> + while True: + n = jobs.get() # <8> + if n == 0: + break + results.put(check(n)) # <9> +# end::PRIMES_PROC_TOP[] + +# tag::PRIMES_PROC_MAIN[] +def main() -> None: + if len(sys.argv) < 2: # <1> + workers = cpu_count() + else: + workers = int(sys.argv[1]) + + print(f'Checking {len(NUMBERS)} numbers with {workers} processes:') + + jobs: JobQueue = SimpleQueue() # <2> + results: ResultQueue = SimpleQueue() + t0 = perf_counter() + + for n in NUMBERS: # <3> + jobs.put(n) + + for _ in range(workers): + proc = Process(target=worker, args=(jobs, results)) # <4> + proc.start() # <5> + jobs.put(0) # <6> + + while True: + n, prime, elapsed = results.get() # <7> + label = 'P' if prime else ' ' + print(f'{n:16} {label} {elapsed:9.6f}s') # <8> + if jobs.empty(): # <9> + break + + elapsed = perf_counter() - t0 + print(f'Total time: {elapsed:.2f}s') + +if __name__ == '__main__': + main() +# end::PRIMES_PROC_MAIN[] diff --git a/21-futures/getflags/flags2_asyncio_executor.py b/21-futures/getflags/flags2_asyncio_executor.py new file mode 100755 index 0000000..5b75d59 --- /dev/null +++ b/21-futures/getflags/flags2_asyncio_executor.py @@ -0,0 +1,109 @@ +#!/usr/bin/env python3 + +"""Download flags of countries (with error handling). + +asyncio async/await version using run_in_executor for save_flag. + +""" + +import asyncio +from collections import Counter + +import aiohttp +import tqdm # type: ignore + +from flags2_common import main, HTTPStatus, Result, save_flag + +# default set low to avoid errors from remote site, such as +# 503 - Service Temporarily Unavailable +DEFAULT_CONCUR_REQ = 5 +MAX_CONCUR_REQ = 1000 + + +class FetchError(Exception): + def __init__(self, country_code: str): + self.country_code = country_code + + +async def get_flag(session: aiohttp.ClientSession, + base_url: str, + cc: str) -> bytes: + url = f'{base_url}/{cc}/{cc}.gif'.lower() + async with session.get(url) as resp: + if resp.status == 200: + return await resp.read() + else: + resp.raise_for_status() + return bytes() + +# tag::FLAGS2_ASYNCIO_EXECUTOR[] +async def download_one(session: aiohttp.ClientSession, + cc: str, + base_url: str, + semaphore: asyncio.Semaphore, + verbose: bool) -> Result: + try: + async with semaphore: + image = await get_flag(session, base_url, cc) + except aiohttp.ClientResponseError as exc: + if exc.status == 404: + status = HTTPStatus.not_found + msg = 'not found' + else: + raise FetchError(cc) from exc + else: + loop = asyncio.get_running_loop() # <1> + loop.run_in_executor(None, # <2> + save_flag, image, f'{cc}.gif') # <3> + status = HTTPStatus.ok + msg = 'OK' + if verbose and msg: + print(cc, msg) + return Result(status, cc) +# end::FLAGS2_ASYNCIO_EXECUTOR[] + +async def supervisor(cc_list: list[str], + base_url: str, + verbose: bool, + concur_req: int) -> Counter[HTTPStatus]: + counter: Counter[HTTPStatus] = Counter() + semaphore = asyncio.Semaphore(concur_req) + async with aiohttp.ClientSession() as session: + to_do = [download_one(session, cc, base_url, semaphore, verbose) + for cc in sorted(cc_list)] + + to_do_iter = asyncio.as_completed(to_do) + if not verbose: + to_do_iter = tqdm.tqdm(to_do_iter, total=len(cc_list)) + for coro in to_do_iter: + try: + res = await coro + except FetchError as exc: + country_code = exc.country_code + try: + error_msg = exc.__cause__.message # type: ignore + except AttributeError: + error_msg = 'Unknown cause' + if verbose and error_msg: + print(f'*** Error for {country_code}: {error_msg}') + status = HTTPStatus.error + else: + status = res.status + + counter[status] += 1 + + return counter + + +def download_many(cc_list: list[str], + base_url: str, + verbose: bool, + concur_req: int) -> Counter[HTTPStatus]: + coro = supervisor(cc_list, base_url, verbose, concur_req) + counts = asyncio.run(coro) # <14> + + return counts + + +if __name__ == '__main__': + main(download_many, DEFAULT_CONCUR_REQ, MAX_CONCUR_REQ) diff --git a/21-futures/getflags/flags2_common.py b/21-futures/getflags/flags2_common.py index c772820..74cd888 100644 --- a/21-futures/getflags/flags2_common.py +++ b/21-futures/getflags/flags2_common.py @@ -121,23 +121,25 @@ def process_args(default_concur_req): if args.max_req < 1: print('*** Usage error: --max_req CONCURRENT must be >= 1') parser.print_usage() - sys.exit(1) + # "standard" exit status codes: + # https://stackoverflow.com/questions/1101957/are-there-any-standard-exit-status-codes-in-linux/40484670#40484670 + sys.exit(2) # command line usage error if args.limit < 1: print('*** Usage error: --limit N must be >= 1') parser.print_usage() - sys.exit(1) + sys.exit(2) # command line usage error args.server = args.server.upper() if args.server not in SERVERS: print(f'*** Usage error: --server LABEL ' f'must be one of {server_options}') parser.print_usage() - sys.exit(1) + sys.exit(2) # command line usage error try: cc_list = expand_cc_args(args.every, args.all, args.cc, args.limit) except ValueError as exc: print(exc.args[0]) parser.print_usage() - sys.exit(1) + sys.exit(2) # command line usage error if not cc_list: cc_list = sorted(POP20_CC) diff --git a/22-async/README.rst b/22-async/README.rst new file mode 100644 index 0000000..865534e --- /dev/null +++ b/22-async/README.rst @@ -0,0 +1,4 @@ +Sample code for Chapter 22 - "Asynchronous programming" + +From the book "Fluent Python, Second Edition" by Luciano Ramalho (O'Reilly, 2021) +https://learning.oreilly.com/library/view/fluent-python-2nd/9781492056348/ diff --git a/22-async/domains/README.rst b/22-async/domains/README.rst new file mode 100644 index 0000000..d1ed993 --- /dev/null +++ b/22-async/domains/README.rst @@ -0,0 +1,28 @@ +domainlib demonstration +======================= + +Run Python's async console (requires Python ≥ 3.8):: + + $ python3 -m asyncio + +I'll see ``asyncio`` imported automatically:: + + >>> import asyncio + +Now you can experiment with ``domainlib``. + +At the `>>>` prompt, type these commands:: + + >>> from domainlib import * + >>> await probe('python.org') + +Note the result. + +Next:: + + >>> names = 'python.org rust-lang.org golang.org n05uch1an9.org'.split() + >>> async for result in multi_probe(names): + ... print(*result, sep='\t') + +Note that if you run the last two lines again, +the results are likely to appear in a different order. diff --git a/22-async/domains/curio/blogdom.py b/22-async/domains/curio/blogdom.py index b36a77d..f3dd598 100755 --- a/22-async/domains/curio/blogdom.py +++ b/22-async/domains/curio/blogdom.py @@ -1,30 +1,28 @@ #!/usr/bin/env python3 from curio import run, TaskGroup -from curio.socket import getaddrinfo, gaierror +import curio.socket as socket from keyword import kwlist -MAX_KEYWORD_LEN = 4 # <1> +MAX_KEYWORD_LEN = 4 -async def probe(domain: str) -> tuple[str, bool]: # <2> +async def probe(domain: str) -> tuple[str, bool]: # <1> try: - await getaddrinfo(domain, None) # <4> - except gaierror: + await socket.getaddrinfo(domain, None) # <2> + except socket.gaierror: return (domain, False) return (domain, True) - -async def main() -> None: # <5> - names = (kw for kw in kwlist if len(kw) <= MAX_KEYWORD_LEN) # <6> - domains = (f'{name}.dev'.lower() for name in names) # <7> - async with TaskGroup() as group: +async def main() -> None: + names = (kw for kw in kwlist if len(kw) <= MAX_KEYWORD_LEN) + domains = (f'{name}.dev'.lower() for name in names) + async with TaskGroup() as group: # <3> for domain in domains: - await group.spawn(probe, domain) - async for task in group: # <9> - domain, found = task.result # <10> + await group.spawn(probe, domain) # <4> + async for task in group: # <5> + domain, found = task.result mark = '+' if found else ' ' print(f'{mark} {domain}') - if __name__ == '__main__': - run(main()) # <11> + run(main()) # <6> diff --git a/22-async/domains/curio/domainlib.py b/22-async/domains/curio/domainlib.py index 4f6bb90..d359a21 100644 --- a/22-async/domains/curio/domainlib.py +++ b/22-async/domains/curio/domainlib.py @@ -1,5 +1,5 @@ from curio import TaskGroup -from curio.socket import getaddrinfo, gaierror +import curio.socket as socket from collections.abc import Iterable, AsyncIterator from typing import NamedTuple @@ -9,10 +9,10 @@ class Result(NamedTuple): found: bool -async def probe(domain: str) -> Result: + async def probe(domain: str) -> Result: try: - await getaddrinfo(domain, None) - except gaierror: + await socket.getaddrinfo(domain, None) + except socket.gaierror: return Result(domain, False) return Result(domain, True) diff --git a/22-async/mojifinder/charindex.py b/22-async/mojifinder/charindex.py index 5312af1..945c1f6 100755 --- a/22-async/mojifinder/charindex.py +++ b/22-async/mojifinder/charindex.py @@ -4,28 +4,28 @@ Class ``InvertedIndex`` builds an inverted index mapping each word to the set of Unicode characters which contain that word in their names. -Optional arguments to the constructor are ``first`` and ``last+1`` character -codes to index, to make testing easier. +Optional arguments to the constructor are ``first`` and ``last+1`` +character codes to index, to make testing easier. In the examples +below, only the ASCII range was indexed. -In the example below, only the ASCII range was indexed:: +The `entries` attribute is a `defaultdict` with uppercased single +words as keys:: >>> idx = InvertedIndex(32, 128) + >>> idx.entries['DOLLAR'] + {'$'} >>> sorted(idx.entries['SIGN']) ['#', '$', '%', '+', '<', '=', '>'] - >>> sorted(idx.entries['DIGIT']) - ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'] - >>> idx.entries['DIGIT'] & idx.entries['EIGHT'] - {'8'} - >>> idx.search('digit') - ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'] - >>> idx.search('eight digit') - ['8'] - >>> idx.search('a letter') - ['A', 'a'] - >>> idx.search('a letter capital') - ['A'] - >>> idx.search('borogove') - [] + >>> idx.entries['A'] & idx.entries['SMALL'] + {'a'} + >>> idx.entries['BRILLIG'] + set() + +The `.search()` method takes a string, uppercases it, splits it into +words, and returns the intersection of the entries for each word:: + + >>> idx.search('capital a') + {'A'} """ @@ -58,17 +58,16 @@ def __init__(self, start: int = 32, stop: int = STOP_CODE): entries[word].add(char) self.entries = entries - def search(self, query: str) -> list[Char]: + def search(self, query: str) -> set[Char]: if words := list(tokenize(query)): - first = self.entries[words[0]] - result = first.intersection(*(self.entries[w] for w in words[1:])) - return sorted(result) + found = self.entries[words[0]] + return found.intersection(*(self.entries[w] for w in words[1:])) else: - return [] + return set() -def format_results(chars: list[Char]) -> Iterator[str]: - for char in chars: +def format_results(chars: set[Char]) -> Iterator[str]: + for char in sorted(chars): name = unicodedata.name(char) code = ord(char) yield f'U+{code:04X}\t{char}\t{name}' @@ -77,7 +76,7 @@ def format_results(chars: list[Char]) -> Iterator[str]: def main(words: list[str]) -> None: if not words: print('Please give one or more words to search.') - sys.exit() + sys.exit(2) # command line usage error index = InvertedIndex() chars = index.search(' '.join(words)) for line in format_results(chars): diff --git a/22-async/mojifinder/static/form.html b/22-async/mojifinder/static/form.html index 8ccc91a..91e6911 100644 --- a/22-async/mojifinder/static/form.html +++ b/22-async/mojifinder/static/form.html @@ -66,8 +66,6 @@ const input = document.getElementById('query'); input.addEventListener('change', updateTable); }); - - diff --git a/22-async/mojifinder/web_mojifinder.py b/22-async/mojifinder/web_mojifinder.py index 7e4ef2e..96004d8 100644 --- a/22-async/mojifinder/web_mojifinder.py +++ b/22-async/mojifinder/web_mojifinder.py @@ -18,19 +18,19 @@ class CharName(BaseModel): # <2> def init(app): # <3> app.state.index = InvertedIndex() - static = pathlib.Path(__file__).parent.absolute() / 'static' + static = pathlib.Path(__file__).parent.absolute() / 'static' # <4> with open(static / 'form.html') as fp: app.state.form = fp.read() -init(app) # <4> +init(app) # <5> -@app.get('/search', response_model=list[CharName]) # <5> -async def search(q: str): # <6> +@app.get('/search', response_model=list[CharName]) # <6> +async def search(q: str): # <7> chars = app.state.index.search(q) - return ({'char': c, 'name': name(c)} for c in chars) # <7> + return ({'char': c, 'name': name(c)} for c in chars) # <8> -@app.get('/', # <8> - response_class=HTMLResponse, - include_in_schema=False) -def form(): +@app.get('/', response_class=HTMLResponse, include_in_schema=False) +def form(): # <9> return app.state.form + +# no main funcion # <10> diff --git a/README.md b/README.md index 3e6eecf..35d8e48 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ Part / Chapter #|Title|Directory|Notebook|1st ed. Chapter # **IV – Object-Oriented Idioms**| 11|A Pythonic Object|[11-pythonic-obj](11-pythonic-obj)||9 12|Sequence Hacking, Hashing, and Slicing|[12-seq-hacking](12-seq-hacking)||10 -13|Interfaces: Interfaces, Protocols, and ABCs|[13-protocl-abc](13-protocol-abc)||11 +13|Interfaces, Protocols, and ABCs|[13-protocl-abc](13-protocol-abc)||11 14|Inheritance: For Good or For Worse|[14-inheritance](14-inheritance)||12 🆕 15|More About Type Hints|[15-type-hints](15-type-hints)||– 16|Operator Overloading: Doing It Right|[16-op-overloading](16-op-overloading)||13 From 2e064d68a139c3504ee31acdff95a5b951a49aee Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Mon, 5 Apr 2021 18:08:55 -0300 Subject: [PATCH 031/166] ch25: simple enum metaclasses --- 25-class-metaprog/evalsupport.py | 29 +++++++++ 25-class-metaprog/evaltime.py | 49 +++++++++++++++ 25-class-metaprog/evaltime_meta.py | 53 ++++++++++++++++ 25-class-metaprog/factories.py | 61 ++++++++++++++++++ 25-class-metaprog/tinyenums/microenum.py | 62 +++++++++++++++++++ 25-class-metaprog/tinyenums/microenum_demo.py | 17 +++++ 25-class-metaprog/tinyenums/nanoenum.py | 39 ++++++++++++ 25-class-metaprog/tinyenums/nanoenum_demo.py | 17 +++++ 8 files changed, 327 insertions(+) create mode 100644 25-class-metaprog/evalsupport.py create mode 100644 25-class-metaprog/evaltime.py create mode 100644 25-class-metaprog/evaltime_meta.py create mode 100644 25-class-metaprog/factories.py create mode 100644 25-class-metaprog/tinyenums/microenum.py create mode 100644 25-class-metaprog/tinyenums/microenum_demo.py create mode 100644 25-class-metaprog/tinyenums/nanoenum.py create mode 100644 25-class-metaprog/tinyenums/nanoenum_demo.py diff --git a/25-class-metaprog/evalsupport.py b/25-class-metaprog/evalsupport.py new file mode 100644 index 0000000..a7f3b5b --- /dev/null +++ b/25-class-metaprog/evalsupport.py @@ -0,0 +1,29 @@ +# tag::BEGINNING[] +print('<[100]> evalsupport module start') + +def deco_alpha(cls): + print('<[200]> deco_alpha') + + def inner_1(self): + print('<[300]> deco_alpha:inner_1') + + cls.method_y = inner_1 + return cls + +# end::BEGINNING[] +# tag::META_ALEPH[] +class MetaAleph(type): + print('<[400]> MetaAleph body') + + def __init__(cls, name, bases, dic): + print('<[500]> MetaAleph.__init__') + + def inner_2(self): + print('<[600]> MetaAleph.__init__:inner_2') + + cls.method_z = inner_2 + +# end::META_ALEPH[] +# tag::END[] +print('<[700]> evalsupport module end') +# end::END[] diff --git a/25-class-metaprog/evaltime.py b/25-class-metaprog/evaltime.py new file mode 100644 index 0000000..299098a --- /dev/null +++ b/25-class-metaprog/evaltime.py @@ -0,0 +1,49 @@ +from evalsupport import deco_alpha + +print('<[1]> evaltime module start') + + +class ClassOne(): + print('<[2]> ClassOne body') + + def __init__(self): + print('<[3]> ClassOne.__init__') + + def __del__(self): + print('<[4]> ClassOne.__del__') + + def method_x(self): + print('<[5]> ClassOne.method_x') + + class ClassTwo(object): + print('<[6]> ClassTwo body') + + +@deco_alpha +class ClassThree(): + print('<[7]> ClassThree body') + + def method_y(self): + print('<[8]> ClassThree.method_y') + + +class ClassFour(ClassThree): + print('<[9]> ClassFour body') + + def method_y(self): + print('<[10]> ClassFour.method_y') + + +if __name__ == '__main__': + print('<[11]> ClassOne tests', 30 * '.') + one = ClassOne() + one.method_x() + print('<[12]> ClassThree tests', 30 * '.') + three = ClassThree() + three.method_y() + print('<[13]> ClassFour tests', 30 * '.') + four = ClassFour() + four.method_y() + + +print('<[14]> evaltime module end') diff --git a/25-class-metaprog/evaltime_meta.py b/25-class-metaprog/evaltime_meta.py new file mode 100644 index 0000000..41ed098 --- /dev/null +++ b/25-class-metaprog/evaltime_meta.py @@ -0,0 +1,53 @@ +from evalsupport import deco_alpha +from evalsupport import MetaAleph + +print('<[1]> evaltime_meta module start') + + +@deco_alpha +class ClassThree(): + print('<[2]> ClassThree body') + + def method_y(self): + print('<[3]> ClassThree.method_y') + + +class ClassFour(ClassThree): + print('<[4]> ClassFour body') + + def method_y(self): + print('<[5]> ClassFour.method_y') + + +class ClassFive(metaclass=MetaAleph): + print('<[6]> ClassFive body') + + def __init__(self): + print('<[7]> ClassFive.__init__') + + def method_z(self): + print('<[8]> ClassFive.method_z') + + +class ClassSix(ClassFive): + print('<[9]> ClassSix body') + + def method_z(self): + print('<[10]> ClassSix.method_z') + + +if __name__ == '__main__': + print('<[11]> ClassThree tests', 30 * '.') + three = ClassThree() + three.method_y() + print('<[12]> ClassFour tests', 30 * '.') + four = ClassFour() + four.method_y() + print('<[13]> ClassFive tests', 30 * '.') + five = ClassFive() + five.method_z() + print('<[14]> ClassSix tests', 30 * '.') + six = ClassSix() + six.method_z() + +print('<[15]> evaltime_meta module end') diff --git a/25-class-metaprog/factories.py b/25-class-metaprog/factories.py new file mode 100644 index 0000000..fb2a9c5 --- /dev/null +++ b/25-class-metaprog/factories.py @@ -0,0 +1,61 @@ +""" +record_factory: create simple classes just for holding data fields + +# tag::RECORD_FACTORY_DEMO[] + >>> Dog = record_factory('Dog', 'name weight owner') # <1> + >>> rex = Dog('Rex', 30, 'Bob') + >>> rex # <2> + Dog(name='Rex', weight=30, owner='Bob') + >>> name, weight, _ = rex # <3> + >>> name, weight + ('Rex', 30) + >>> "{2}'s dog weighs {1}kg".format(*rex) # <4> + "Bob's dog weighs 30kg" + >>> rex.weight = 32 # <5> + >>> rex + Dog(name='Rex', weight=32, owner='Bob') + >>> Dog.__mro__ # <6> + (, ) + +# end::RECORD_FACTORY_DEMO[] + +The factory also accepts a list or tuple of identifiers: + + >>> Dog = record_factory('Dog', ['name', 'weight', 'owner']) + >>> Dog.__slots__ + ('name', 'weight', 'owner') + +""" + +# tag::RECORD_FACTORY[] +def record_factory(cls_name, field_names): + try: + field_names = field_names.replace(',', ' ').split() # <1> + except AttributeError: # no .replace or .split + pass # assume it's already a sequence of strings + field_names = tuple(field_names) # <2> + if not all(s.isidentifier() for s in field_names): + raise ValueError('field_names must all be valid identifiers') + + def __init__(self, *args, **kwargs): # <3> + attrs = dict(zip(self.__slots__, args)) + attrs.update(kwargs) + for name, value in attrs.items(): + setattr(self, name, value) + + def __iter__(self): # <4> + for name in self.__slots__: + yield getattr(self, name) + + def __repr__(self): # <5> + values = ', '.join('{}={!r}'.format(*i) for i + in zip(self.__slots__, self)) + return '{}({})'.format(self.__class__.__name__, values) + + cls_attrs = dict(__slots__ = field_names, # <6> + __init__ = __init__, + __iter__ = __iter__, + __repr__ = __repr__) + + return type(cls_name, (object,), cls_attrs) # <7> +# end::RECORD_FACTORY[] diff --git a/25-class-metaprog/tinyenums/microenum.py b/25-class-metaprog/tinyenums/microenum.py new file mode 100644 index 0000000..7010a9e --- /dev/null +++ b/25-class-metaprog/tinyenums/microenum.py @@ -0,0 +1,62 @@ +""" +Testing ``AutoFillDict``:: + + >>> adict = AutoFillDict() + >>> len(adict) + 0 + >>> adict['first'] + 0 + >>> adict + {'first': 0} + >>> adict['second'] + 1 + >>> adict['third'] + 2 + >>> len(adict) + 3 + >>> adict + {'first': 0, 'second': 1, 'third': 2} + >>> adict['__magic__'] + Traceback (most recent call last): + ... + KeyError: '__magic__' + +Testing ``MicroEnum``:: + + >>> class Flavor(MicroEnum): + ... cocoa + ... coconut + ... vanilla + >>> Flavor.cocoa, Flavor.vanilla + (0, 2) + >>> Flavor[1] + 'coconut' +""" + + +class AutoFillDict(dict): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.__next_value = 0 + + def __missing__(self, key): + if key.startswith('__') and key.endswith('__'): + raise KeyError(key) + self[key] = value = self.__next_value + self.__next_value += 1 + return value + + +class MicroEnumMeta(type): + def __prepare__(name, bases, **kwargs): + return AutoFillDict() + + def __getitem__(cls, key): + for k, v in cls.__dict__.items(): + if v == key: + return k + raise KeyError(key) + + +class MicroEnum(metaclass=MicroEnumMeta): + pass diff --git a/25-class-metaprog/tinyenums/microenum_demo.py b/25-class-metaprog/tinyenums/microenum_demo.py new file mode 100644 index 0000000..f3adc1d --- /dev/null +++ b/25-class-metaprog/tinyenums/microenum_demo.py @@ -0,0 +1,17 @@ +""" +Testing ``Flavor``:: + + >>> Flavor.cocoa, Flavor.coconut, Flavor.vanilla + (0, 1, 2) + >>> Flavor[1] + 'coconut' + +""" + +from microenum import MicroEnum + + +class Flavor(MicroEnum): + cocoa + coconut + vanilla diff --git a/25-class-metaprog/tinyenums/nanoenum.py b/25-class-metaprog/tinyenums/nanoenum.py new file mode 100644 index 0000000..36cee6b --- /dev/null +++ b/25-class-metaprog/tinyenums/nanoenum.py @@ -0,0 +1,39 @@ +""" +Testing ``KeyIsValueDict``:: + + >>> adict = KeyIsValueDict() + >>> len(adict) + 0 + >>> adict['first'] + 'first' + >>> adict + {'first': 'first'} + >>> adict['second'] + 'second' + >>> len(adict) + 2 + >>> adict + {'first': 'first', 'second': 'second'} + >>> adict['__magic__'] + Traceback (most recent call last): + ... + KeyError: '__magic__' +""" + + +class KeyIsValueDict(dict): + + def __missing__(self, key): + if key.startswith('__') and key.endswith('__'): + raise KeyError(key) + self[key] = key + return key + + +class NanoEnumMeta(type): + def __prepare__(name, bases, **kwargs): + return KeyIsValueDict() + + +class NanoEnum(metaclass=NanoEnumMeta): + pass diff --git a/25-class-metaprog/tinyenums/nanoenum_demo.py b/25-class-metaprog/tinyenums/nanoenum_demo.py new file mode 100644 index 0000000..80234c0 --- /dev/null +++ b/25-class-metaprog/tinyenums/nanoenum_demo.py @@ -0,0 +1,17 @@ +""" +Testing ``Flavor``:: + + >>> Flavor.coconut + 'coconut' + >>> Flavor.cocoa, Flavor.vanilla + ('cocoa', 'vanilla') + +""" + +from nanoenum import NanoEnum + + +class Flavor(NanoEnum): + cocoa + coconut + vanilla From fd768348764b3d865e0043744109b436ad00ede8 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Mon, 5 Apr 2021 18:30:53 -0300 Subject: [PATCH 032/166] Update README.md --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 35d8e48..6ee9e4a 100644 --- a/README.md +++ b/README.md @@ -26,11 +26,11 @@ Part / Chapter #|Title|Directory|Notebook|1st ed. Chapter # 2|An Array of Sequences|[02-array-seq](02-array-seq)|[array-seq.ipynb](02-array-seq/array-seq.ipynb)|2 3|Dictionaries and Sets|[03-dict-set](03-dict-set)||3 4|Text versus Bytes|[04-text-byte](04-text-byte)||4 -🆕 5|Record-like Data Structures|[05-record-like](05-record-like)||– +5|Record-like Data Structures|[05-record-like](05-record-like)||🆕 6|Object References, Mutability, and Recycling|[06-obj-ref](06-obj-ref)||8 **III – Functions as Objects**| 7|First-Class Funcions|[07-1class-func](07-1class-func)||5 -🆕 8|Type Hints in Function Definitions|[08-def-type-hints](08-def-type-hints)||– +8|Type Hints in Function Definitions|[08-def-type-hints](08-def-type-hints)||🆕 9|Function Decorators and Closures|[09-closure-deco](09-closure-deco)||7 10|Design Patterns with First-Class Functions|[10-dp-1class-func](10-dp-1class-func)||6 **IV – Object-Oriented Idioms**| @@ -38,16 +38,16 @@ Part / Chapter #|Title|Directory|Notebook|1st ed. Chapter # 12|Sequence Hacking, Hashing, and Slicing|[12-seq-hacking](12-seq-hacking)||10 13|Interfaces, Protocols, and ABCs|[13-protocl-abc](13-protocol-abc)||11 14|Inheritance: For Good or For Worse|[14-inheritance](14-inheritance)||12 -🆕 15|More About Type Hints|[15-type-hints](15-type-hints)||– +15|More About Type Hints|15-type-hints||🆕 16|Operator Overloading: Doing It Right|[16-op-overloading](16-op-overloading)||13 **V – Control Flow**| 17|Iterables, Iterators, and Generators|[17-it-generator](17-it-generator)||14 18|Context Managers and else Blocks|[18-context-mngr](18-context-mngr)||15 19|Classic Coroutines|[19-coroutine](19-coroutine)||16 -🆕 20|Concurrency Models in Python|[20-concurrency](20-concurrency)||- +20|Concurrency Models in Python|[20-concurrency](20-concurrency)||🆕 21|Concurrency with Futures|[21-futures](21-futures)||17 22|Asynchronous Programming|[22-async](22-async)||18 **VI – Metaprogramming**| -23|Dynamic Attributes and Properties|[22-dyn-attr-prop](22-dyn-attr-prop)||19 -24|Attribute Descriptors|[23-descriptor](23-descriptor)||20 -25|Class Metaprogramming|[24-class-metaprog](24-class-metaprog)||21 +23|Dynamic Attributes and Properties|[23-dyn-attr-prop](23-dyn-attr-prop)||19 +24|Attribute Descriptors|[24-descriptor](24-descriptor)||20 +25|Class Metaprogramming|[25-class-metaprog](25-class-metaprog)||21 From 9ebe6bc50de4557fcd4e09da8ea7f5332bf33223 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Mon, 5 Apr 2021 18:32:30 -0300 Subject: [PATCH 033/166] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6ee9e4a..6fac09d 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ Part / Chapter #|Title|Directory|Notebook|1st ed. Chapter # 12|Sequence Hacking, Hashing, and Slicing|[12-seq-hacking](12-seq-hacking)||10 13|Interfaces, Protocols, and ABCs|[13-protocl-abc](13-protocol-abc)||11 14|Inheritance: For Good or For Worse|[14-inheritance](14-inheritance)||12 -15|More About Type Hints|15-type-hints||🆕 +15|More About Type Hints|15-more-typing||🆕 16|Operator Overloading: Doing It Right|[16-op-overloading](16-op-overloading)||13 **V – Control Flow**| 17|Iterables, Iterators, and Generators|[17-it-generator](17-it-generator)||14 From da82a068a318f97cb0439a8aecf12324785fe46c Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Mon, 5 Apr 2021 18:35:43 -0300 Subject: [PATCH 034/166] Update README.md --- README.md | 54 +++++++++++++++++++++++++++--------------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 6fac09d..f916160 100644 --- a/README.md +++ b/README.md @@ -14,40 +14,40 @@ Example code for the book **Fluent Python, 2nd edition** by Luciano R All chapters are undergoing review and updates, including significant rewrites in the chapters about concurrency in **Part V**. -New chapters in **Fluent Python 2e** are marked with 🆕. +New chapters in **Fluent Python 2nd edition** are marked with 🆕. 🚨 This table of contents is subject to change at any time until the book goes to the printer. -Part / Chapter #|Title|Directory|Notebook|1st ed. Chapter # +Part / Chapter #|Title|Directory|1st edition Chapter # ---:|---|---|---|:---: **I – Prologue**| -1|The Python Data Model|[01-data-model](01-data-model)|[data-model.ipynb](01-data-model/data-model.ipynb)|1 +1|The Python Data Model|[01-data-model](01-data-model)|1 **II – Data Structures**| -2|An Array of Sequences|[02-array-seq](02-array-seq)|[array-seq.ipynb](02-array-seq/array-seq.ipynb)|2 -3|Dictionaries and Sets|[03-dict-set](03-dict-set)||3 -4|Text versus Bytes|[04-text-byte](04-text-byte)||4 -5|Record-like Data Structures|[05-record-like](05-record-like)||🆕 -6|Object References, Mutability, and Recycling|[06-obj-ref](06-obj-ref)||8 +2|An Array of Sequences|[02-array-seq](02-array-seq)|2 +3|Dictionaries and Sets|[03-dict-set](03-dict-set)|3 +4|Text versus Bytes|[04-text-byte](04-text-byte)|4 +5|Record-like Data Structures|[05-record-like](05-record-like)|🆕 +6|Object References, Mutability, and Recycling|[06-obj-ref](06-obj-ref)|8 **III – Functions as Objects**| -7|First-Class Funcions|[07-1class-func](07-1class-func)||5 -8|Type Hints in Function Definitions|[08-def-type-hints](08-def-type-hints)||🆕 -9|Function Decorators and Closures|[09-closure-deco](09-closure-deco)||7 -10|Design Patterns with First-Class Functions|[10-dp-1class-func](10-dp-1class-func)||6 +7|First-Class Funcions|[07-1class-func](07-1class-func)|5 +8|Type Hints in Function Definitions|[08-def-type-hints](08-def-type-hints)|🆕 +9|Function Decorators and Closures|[09-closure-deco](09-closure-deco)|7 +10|Design Patterns with First-Class Functions|[10-dp-1class-func](10-dp-1class-func)|6 **IV – Object-Oriented Idioms**| -11|A Pythonic Object|[11-pythonic-obj](11-pythonic-obj)||9 -12|Sequence Hacking, Hashing, and Slicing|[12-seq-hacking](12-seq-hacking)||10 -13|Interfaces, Protocols, and ABCs|[13-protocl-abc](13-protocol-abc)||11 -14|Inheritance: For Good or For Worse|[14-inheritance](14-inheritance)||12 -15|More About Type Hints|15-more-typing||🆕 -16|Operator Overloading: Doing It Right|[16-op-overloading](16-op-overloading)||13 +11|A Pythonic Object|[11-pythonic-obj](11-pythonic-obj)|9 +12|Sequence Hacking, Hashing, and Slicing|[12-seq-hacking](12-seq-hacking)|10 +13|Interfaces, Protocols, and ABCs|[13-protocl-abc](13-protocol-abc)|11 +14|Inheritance: For Good or For Worse|[14-inheritance](14-inheritance)|12 +15|More About Type Hints|15-more-typing|🆕 +16|Operator Overloading: Doing It Right|[16-op-overloading](16-op-overloading)|13 **V – Control Flow**| -17|Iterables, Iterators, and Generators|[17-it-generator](17-it-generator)||14 -18|Context Managers and else Blocks|[18-context-mngr](18-context-mngr)||15 -19|Classic Coroutines|[19-coroutine](19-coroutine)||16 -20|Concurrency Models in Python|[20-concurrency](20-concurrency)||🆕 -21|Concurrency with Futures|[21-futures](21-futures)||17 -22|Asynchronous Programming|[22-async](22-async)||18 +17|Iterables, Iterators, and Generators|[17-it-generator](17-it-generator)|14 +18|Context Managers and else Blocks|[18-context-mngr](18-context-mngr)|15 +19|Classic Coroutines|[19-coroutine](19-coroutine)|16 +20|Concurrency Models in Python|[20-concurrency](20-concurrency)|🆕 +21|Concurrency with Futures|[21-futures](21-futures)|17 +22|Asynchronous Programming|[22-async](22-async)|18 **VI – Metaprogramming**| -23|Dynamic Attributes and Properties|[23-dyn-attr-prop](23-dyn-attr-prop)||19 -24|Attribute Descriptors|[24-descriptor](24-descriptor)||20 -25|Class Metaprogramming|[25-class-metaprog](25-class-metaprog)||21 +23|Dynamic Attributes and Properties|[23-dyn-attr-prop](23-dyn-attr-prop)|19 +24|Attribute Descriptors|[24-descriptor](24-descriptor)|20 +25|Class Metaprogramming|[25-class-metaprog](25-class-metaprog)|21 From 4f974e9be2219e35cbbb628524f6c4611a1422b6 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Mon, 5 Apr 2021 18:39:20 -0300 Subject: [PATCH 035/166] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f916160..4b00890 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ New chapters in **Fluent Python 2nd edition** are marked with 🆕. 🚨 This table of contents is subject to change at any time until the book goes to the printer. Part / Chapter #|Title|Directory|1st edition Chapter # ----:|---|---|---|:---: +---:|---|---|:---: **I – Prologue**| 1|The Python Data Model|[01-data-model](01-data-model)|1 **II – Data Structures**| From 426c903d0966317be515a1505b7132fbebe88cca Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Mon, 5 Apr 2021 18:40:05 -0300 Subject: [PATCH 036/166] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4b00890..56e7420 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Fluent Python 2e example code -Example code for the book **Fluent Python, 2nd edition** by Luciano Ramalho (O'Reilly, 2020). +Example code for the book **Fluent Python, 2nd edition** by Luciano Ramalho (O'Reilly, 2021). > **BEWARE**: This is a work in progress! > From 206ce4d6f3740ad3ff3f592c8b007eae7ccff24a Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Wed, 7 Apr 2021 19:02:35 -0300 Subject: [PATCH 037/166] updated urllib3 dependency due to GHSA-5phf-pp7p-vc2r --- 21-futures/getflags/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/21-futures/getflags/requirements.txt b/21-futures/getflags/requirements.txt index 0971a38..447f2e9 100644 --- a/21-futures/getflags/requirements.txt +++ b/21-futures/getflags/requirements.txt @@ -5,7 +5,7 @@ certifi==2020.12.5 chardet==4.0.0 idna==2.10 requests==2.25.1 -urllib3==1.26.3 +urllib3==1.26.4 tqdm==4.56.2 multidict==5.1.0 yarl==1.6.3 From c6a1da4882728b0174a334141a55777e5b2406b3 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Thu, 8 Apr 2021 14:34:06 -0300 Subject: [PATCH 038/166] added credits to @gwidion --- 25-class-metaprog/tinyenums/microenum.py | 3 +++ 25-class-metaprog/tinyenums/nanoenum.py | 3 +++ 2 files changed, 6 insertions(+) diff --git a/25-class-metaprog/tinyenums/microenum.py b/25-class-metaprog/tinyenums/microenum.py index 7010a9e..9e516f6 100644 --- a/25-class-metaprog/tinyenums/microenum.py +++ b/25-class-metaprog/tinyenums/microenum.py @@ -1,3 +1,6 @@ +# This is an implementation of an idea by João S. O. Bueno (@gwidion) +# shared privately with me, with permission to use in Fluent Python 2e. + """ Testing ``AutoFillDict``:: diff --git a/25-class-metaprog/tinyenums/nanoenum.py b/25-class-metaprog/tinyenums/nanoenum.py index 36cee6b..f47ccfa 100644 --- a/25-class-metaprog/tinyenums/nanoenum.py +++ b/25-class-metaprog/tinyenums/nanoenum.py @@ -1,3 +1,6 @@ +# This is a simplification of an idea by João S. O. Bueno (@gwidion) +# shared privately with me, with permission to use in Fluent Python 2e. + """ Testing ``KeyIsValueDict``:: From bbc664308a50e0c62d7e7b2f1715eb49d7061456 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Thu, 8 Apr 2021 14:43:15 -0300 Subject: [PATCH 039/166] ch25: changed MicroEnum to use __class_getitem__ --- 25-class-metaprog/tinyenums/microenum.py | 8 +++----- 25-class-metaprog/tinyenums/nanoenum.py | 1 - 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/25-class-metaprog/tinyenums/microenum.py b/25-class-metaprog/tinyenums/microenum.py index 9e516f6..873d469 100644 --- a/25-class-metaprog/tinyenums/microenum.py +++ b/25-class-metaprog/tinyenums/microenum.py @@ -54,12 +54,10 @@ class MicroEnumMeta(type): def __prepare__(name, bases, **kwargs): return AutoFillDict() - def __getitem__(cls, key): + +class MicroEnum(metaclass=MicroEnumMeta): + def __class_getitem__(cls, key): for k, v in cls.__dict__.items(): if v == key: return k raise KeyError(key) - - -class MicroEnum(metaclass=MicroEnumMeta): - pass diff --git a/25-class-metaprog/tinyenums/nanoenum.py b/25-class-metaprog/tinyenums/nanoenum.py index f47ccfa..5be2ccc 100644 --- a/25-class-metaprog/tinyenums/nanoenum.py +++ b/25-class-metaprog/tinyenums/nanoenum.py @@ -25,7 +25,6 @@ class KeyIsValueDict(dict): - def __missing__(self, key): if key.startswith('__') and key.endswith('__'): raise KeyError(key) From 37d3e2b73ca2877c7e2e3efd83ae34483e5ef094 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Wed, 14 Apr 2021 23:20:15 -0300 Subject: [PATCH 040/166] Add persistent lib to Chapter 25 --- 25-class-metaprog/persistent/.gitignore | 1 + 25-class-metaprog/persistent/dblib.py | 151 +++++++++++++++++++++ 25-class-metaprog/persistent/dblib_test.py | 131 ++++++++++++++++++ 25-class-metaprog/persistent/persistlib.py | 119 ++++++++++++++++ 4 files changed, 402 insertions(+) create mode 100644 25-class-metaprog/persistent/.gitignore create mode 100644 25-class-metaprog/persistent/dblib.py create mode 100644 25-class-metaprog/persistent/dblib_test.py create mode 100644 25-class-metaprog/persistent/persistlib.py diff --git a/25-class-metaprog/persistent/.gitignore b/25-class-metaprog/persistent/.gitignore new file mode 100644 index 0000000..98e6ef6 --- /dev/null +++ b/25-class-metaprog/persistent/.gitignore @@ -0,0 +1 @@ +*.db diff --git a/25-class-metaprog/persistent/dblib.py b/25-class-metaprog/persistent/dblib.py new file mode 100644 index 0000000..68dfeab --- /dev/null +++ b/25-class-metaprog/persistent/dblib.py @@ -0,0 +1,151 @@ +# SQLite3 does not support parameterized table and field names, +# for CREATE TABLE and PRAGMA so we must use Python string formatting. +# Applying `check_identifier` to parameters prevents SQL injection. + +import sqlite3 +from typing import NamedTuple + +DEFAULT_DB_PATH = ':memory:' +CONNECTION = None + +SQL_TYPES = { + int: 'INTEGER', + str: 'TEXT', + float: 'REAL', + bytes: 'BLOB', +} + + +class NoConnection(Exception): + """Call connect() to open connection.""" + + +class SchemaMismatch(ValueError): + """The table's schema doesn't match the class.""" + + def __init__(self, table_name): + self.table_name = table_name + + +class NoSuchRecord(LookupError): + """The given primary key does not exist.""" + + def __init__(self, pk): + self.pk = pk + + +class UnexpectedMultipleResults(Exception): + """Query returned more than 1 row.""" + + +class ColumnSchema(NamedTuple): + name: str + sql_type: str + + +def check_identifier(name): + if not name.isidentifier(): + raise ValueError(f'{name!r} is not an identifier') + + +def connect(db_path=DEFAULT_DB_PATH): + global CONNECTION + CONNECTION = sqlite3.connect(db_path) + return CONNECTION + + +def get_connection(): + if CONNECTION is None: + raise NoConnection() + return CONNECTION + + +def gen_columns_sql(fields): + for name, py_type in fields.items(): + check_identifier(name) + try: + sql_type = SQL_TYPES[py_type] + except KeyError as e: + raise ValueError(f'type {py_type!r} is not supported') from e + yield ColumnSchema(name, sql_type) + + +def make_schema_sql(table_name, fields): + check_identifier(table_name) + pk = 'pk INTEGER PRIMARY KEY,' + spcs = ' ' * 4 + columns = ',\n '.join( + f'{field_name} {sql_type}' + for field_name, sql_type in gen_columns_sql(fields) + ) + return f'CREATE TABLE {table_name} (\n{spcs}{pk}\n{spcs}{columns}\n)' + + +def create_table(table_name, fields): + con = get_connection() + con.execute(make_schema_sql(table_name, fields)) + + +def read_columns_sql(table_name): + con = get_connection() + check_identifier(table_name) + rows = con.execute(f'PRAGMA table_info({table_name!r})') + # row fields: cid name type notnull dflt_value pk + return [ColumnSchema(r[1], r[2]) for r in rows] + + +def valid_table(table_name, fields): + table_columns = read_columns_sql(table_name) + return set(gen_columns_sql(fields)) <= set(table_columns) + + +def ensure_table(table_name, fields): + table_columns = read_columns_sql(table_name) + if len(table_columns) == 0: + create_table(table_name, fields) + if not valid_table(table_name, fields): + raise SchemaMismatch(table_name) + + +def insert_record(table_name, fields): + con = get_connection() + check_identifier(table_name) + placeholders = ', '.join(['?'] * len(fields)) + sql = f'INSERT INTO {table_name} VALUES (NULL, {placeholders})' + cursor = con.execute(sql, tuple(fields.values())) + pk = cursor.lastrowid + con.commit() + cursor.close() + return pk + + +def fetch_record(table_name, pk): + con = get_connection() + check_identifier(table_name) + sql = f'SELECT * FROM {table_name} WHERE pk = ? LIMIT 2' + result = list(con.execute(sql, (pk,))) + if len(result) == 0: + raise NoSuchRecord(pk) + elif len(result) == 1: + return result[0] + else: + raise UnexpectedMultipleResults() + + +def update_record(table_name, pk, fields): + con = get_connection() + check_identifier(table_name) + names = ', '.join(fields.keys()) + placeholders = ', '.join(['?'] * len(fields)) + values = tuple(fields.values()) + (pk,) + sql = f'UPDATE {table_name} SET ({names}) = ({placeholders}) WHERE pk = ?' + con.execute(sql, values) + con.commit() + return sql, values + + +def delete_record(table_name, pk): + con = get_connection() + check_identifier(table_name) + sql = f'DELETE FROM {table_name} WHERE pk = ?' + return con.execute(sql, (pk,)) diff --git a/25-class-metaprog/persistent/dblib_test.py b/25-class-metaprog/persistent/dblib_test.py new file mode 100644 index 0000000..9a0a93c --- /dev/null +++ b/25-class-metaprog/persistent/dblib_test.py @@ -0,0 +1,131 @@ +from textwrap import dedent + +import pytest + +from dblib import gen_columns_sql, make_schema_sql, connect, read_columns_sql +from dblib import ColumnSchema, insert_record, fetch_record, update_record +from dblib import NoSuchRecord, delete_record, valid_table + + +@pytest.fixture +def create_movies_sql(): + sql = ''' + CREATE TABLE movies ( + pk INTEGER PRIMARY KEY, + title TEXT, + revenue REAL + ) + ''' + return dedent(sql).strip() + + +@pytest.mark.parametrize( + 'fields, expected', + [ + ( + dict(title=str, awards=int), + [('title', 'TEXT'), ('awards', 'INTEGER')], + ), + ( + dict(picture=bytes, score=float), + [('picture', 'BLOB'), ('score', 'REAL')], + ), + ], +) +def test_gen_columns_sql(fields, expected): + result = list(gen_columns_sql(fields)) + assert result == expected + + +def test_make_schema_sql(create_movies_sql): + fields = dict(title=str, revenue=float) + result = make_schema_sql('movies', fields) + assert result == create_movies_sql + + +def test_read_columns_sql(create_movies_sql): + expected = [ + ColumnSchema(name='pk', sql_type='INTEGER'), + ColumnSchema(name='title', sql_type='TEXT'), + ColumnSchema(name='revenue', sql_type='REAL'), + ] + with connect() as con: + con.execute(create_movies_sql) + result = read_columns_sql('movies') + assert result == expected + + +def test_read_columns_sql_no_such_table(create_movies_sql): + with connect() as con: + con.execute(create_movies_sql) + result = read_columns_sql('no_such_table') + assert result == [] + + +def test_insert_record(create_movies_sql): + fields = dict(title='Frozen', revenue=1_290_000_000) + with connect() as con: + con.execute(create_movies_sql) + for _ in range(3): + result = insert_record('movies', fields) + assert result == 3 + + +def test_fetch_record(create_movies_sql): + fields = dict(title='Frozen', revenue=1_290_000_000) + with connect() as con: + con.execute(create_movies_sql) + pk = insert_record('movies', fields) + row = fetch_record('movies', pk) + assert row == (1, 'Frozen', 1_290_000_000.0) + + +def test_fetch_record_no_such_pk(create_movies_sql): + with connect() as con: + con.execute(create_movies_sql) + with pytest.raises(NoSuchRecord) as e: + fetch_record('movies', 42) + assert e.value.pk == 42 + + +def test_update_record(create_movies_sql): + fields = dict(title='Frozen', revenue=1_290_000_000) + with connect() as con: + con.execute(create_movies_sql) + pk = insert_record('movies', fields) + fields['revenue'] = 1_299_999_999 + sql, values = update_record('movies', pk, fields) + row = fetch_record('movies', pk) + assert sql == 'UPDATE movies SET (title, revenue) = (?, ?) WHERE pk = ?' + assert values == ('Frozen', 1_299_999_999, 1) + assert row == (1, 'Frozen', 1_299_999_999.0) + + +def test_delete_record(create_movies_sql): + fields = dict(title='Frozen', revenue=1_290_000_000) + with connect() as con: + con.execute(create_movies_sql) + pk = insert_record('movies', fields) + delete_record('movies', pk) + with pytest.raises(NoSuchRecord) as e: + fetch_record('movies', pk) + assert e.value.pk == pk + + +def test_persistent_valid_table(create_movies_sql): + fields = dict(title=str, revenue=float) + + with connect() as con: + con.execute(create_movies_sql) + con.commit() + assert valid_table('movies', fields) + + +def test_persistent_valid_table_false(create_movies_sql): + # year field not in movies_sql + fields = dict(title=str, revenue=float, year=int) + + with connect() as con: + con.execute(create_movies_sql) + con.commit() + assert not valid_table('movies', fields) diff --git a/25-class-metaprog/persistent/persistlib.py b/25-class-metaprog/persistent/persistlib.py new file mode 100644 index 0000000..633f48c --- /dev/null +++ b/25-class-metaprog/persistent/persistlib.py @@ -0,0 +1,119 @@ +""" +A ``Persistent`` class definition:: + + >>> class Movie(Persistent): + ... title: str + ... year: int + ... boxmega: float + +Implemented behavior:: + + >>> Movie._connect() # doctest: +ELLIPSIS + + >>> movie = Movie('The Godfather', 1972, 137) + >>> movie.title + 'The Godfather' + >>> movie.boxmega + 137.0 + +Instances always have a ``.pk`` attribute, but it is ``None`` until the +object is saved:: + + >>> movie.pk is None + True + >>> movie._persist() + >>> movie.pk + 1 + +Delete the in-memory ``movie``, and fetch the record from the database, +using ``Movie[pk]``—item access on the class itself:: + + >>> del movie + >>> film = Movie[1] + >>> film + Movie('The Godfather', 1972, 137.0, pk=1) + +By default, the table name is the class name lowercased, with an appended +"s" for plural:: + + >>> Movie._TABLE_NAME + 'movies' + +If needed, a custom table name can be given as a keyword argument in the +class declaration:: + + >>> class Aircraft(Persistent, table='aircraft'): + ... registration: str + ... model: str + ... + >>> Aircraft._TABLE_NAME + 'aircraft' + +""" + +from typing import get_type_hints + +import dblib as db + + +class Field: + def __init__(self, name, py_type): + self.name = name + self.type = py_type + + def __set__(self, instance, value): + try: + value = self.type(value) + except TypeError as e: + msg = f'{value!r} is not compatible with {self.name}:{self.type}.' + raise TypeError(msg) from e + instance.__dict__[self.name] = value + + +class Persistent: + def __init_subclass__( + cls, *, db_path=db.DEFAULT_DB_PATH, table='', **kwargs + ): + super().__init_subclass__(**kwargs) + cls._TABLE_NAME = table if table else cls.__name__.lower() + 's' + cls._TABLE_READY = False + for name, py_type in get_type_hints(cls).items(): + setattr(cls, name, Field(name, py_type)) + + @staticmethod + def _connect(db_path=db.DEFAULT_DB_PATH): + return db.connect(db_path) + + @classmethod + def _ensure_table(cls): + if not cls._TABLE_READY: + db.ensure_table(cls._TABLE_NAME, get_type_hints(cls)) + cls._TABLE_READY = True + return cls._TABLE_NAME + + def _fields(self): + return { + name: getattr(self, name) + for name, attr in self.__class__.__dict__.items() + if isinstance(attr, Field) + } + + def __init__(self, *args, pk=None): + for name, arg in zip(self._fields(), args): + setattr(self, name, arg) + self.pk = pk + + def __class_getitem__(cls, pk): + return cls(*db.fetch_record(cls._TABLE_NAME, pk)[1:], pk=pk) + + def __repr__(self): + args = ', '.join(repr(value) for value in self._fields().values()) + pk = '' if self.pk is None else f', pk={self.pk}' + return f'{self.__class__.__name__}({args}{pk})' + + def _persist(self): + table = self.__class__._ensure_table() + if self.pk is None: + self.pk = db.insert_record(table, self._fields()) + else: + db.update_record(table, self.pk, self._fields()) From f8a1268fb10c8d1f4fdc24caacce572035904561 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Thu, 15 Apr 2021 12:50:50 -0300 Subject: [PATCH 041/166] dblib.update_record: Get connection only if identifiers are ok Co-authored-by: Leonardo Rochael Almeida --- 25-class-metaprog/persistent/dblib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/25-class-metaprog/persistent/dblib.py b/25-class-metaprog/persistent/dblib.py index 68dfeab..c858e3c 100644 --- a/25-class-metaprog/persistent/dblib.py +++ b/25-class-metaprog/persistent/dblib.py @@ -133,8 +133,8 @@ def fetch_record(table_name, pk): def update_record(table_name, pk, fields): - con = get_connection() check_identifier(table_name) + con = get_connection() names = ', '.join(fields.keys()) placeholders = ', '.join(['?'] * len(fields)) values = tuple(fields.values()) + (pk,) From ee418d7d972baf418c1f01f17736e80b214d3338 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Thu, 15 Apr 2021 13:34:17 -0300 Subject: [PATCH 042/166] Fix unneeded posessive 's in docstring Co-authored-by: Leonardo Rochael Almeida --- 25-class-metaprog/persistent/dblib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/25-class-metaprog/persistent/dblib.py b/25-class-metaprog/persistent/dblib.py index c858e3c..e81edc4 100644 --- a/25-class-metaprog/persistent/dblib.py +++ b/25-class-metaprog/persistent/dblib.py @@ -21,7 +21,7 @@ class NoConnection(Exception): class SchemaMismatch(ValueError): - """The table's schema doesn't match the class.""" + """The table schema doesn't match the class.""" def __init__(self, table_name): self.table_name = table_name From 4ff0a59608a55af62a7539fb8a886c7eb766a4d9 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Thu, 15 Apr 2021 17:15:53 -0300 Subject: [PATCH 043/166] refactoring after reviewers feedback + type hints --- 25-class-metaprog/persistent/dblib.py | 76 +++++++------- 25-class-metaprog/persistent/dblib_test.py | 4 +- 25-class-metaprog/persistent/persistlib.py | 99 +++++++++++-------- .../persistent/persistlib_test.py | 37 +++++++ 4 files changed, 142 insertions(+), 74 deletions(-) create mode 100644 25-class-metaprog/persistent/persistlib_test.py diff --git a/25-class-metaprog/persistent/dblib.py b/25-class-metaprog/persistent/dblib.py index e81edc4..dd1fae4 100644 --- a/25-class-metaprog/persistent/dblib.py +++ b/25-class-metaprog/persistent/dblib.py @@ -3,17 +3,10 @@ # Applying `check_identifier` to parameters prevents SQL injection. import sqlite3 -from typing import NamedTuple +from typing import NamedTuple, Optional, Iterator, Any DEFAULT_DB_PATH = ':memory:' -CONNECTION = None - -SQL_TYPES = { - int: 'INTEGER', - str: 'TEXT', - float: 'REAL', - bytes: 'BLOB', -} +CONNECTION: Optional[sqlite3.Connection] = None class NoConnection(Exception): @@ -38,29 +31,45 @@ class UnexpectedMultipleResults(Exception): """Query returned more than 1 row.""" +SQLType = str + +TypeMap = dict[type, SQLType] + +SQL_TYPES: TypeMap = { + int: 'INTEGER', + str: 'TEXT', + float: 'REAL', + bytes: 'BLOB', +} + + class ColumnSchema(NamedTuple): name: str - sql_type: str + sql_type: SQLType -def check_identifier(name): +FieldMap = dict[str, type] + + +def check_identifier(name: str) -> None: if not name.isidentifier(): raise ValueError(f'{name!r} is not an identifier') -def connect(db_path=DEFAULT_DB_PATH): +def connect(db_path: str = DEFAULT_DB_PATH) -> sqlite3.Connection: global CONNECTION CONNECTION = sqlite3.connect(db_path) + CONNECTION.row_factory = sqlite3.Row return CONNECTION -def get_connection(): +def get_connection() -> sqlite3.Connection: if CONNECTION is None: raise NoConnection() return CONNECTION -def gen_columns_sql(fields): +def gen_columns_sql(fields: FieldMap) -> Iterator[ColumnSchema]: for name, py_type in fields.items(): check_identifier(name) try: @@ -70,7 +79,7 @@ def gen_columns_sql(fields): yield ColumnSchema(name, sql_type) -def make_schema_sql(table_name, fields): +def make_schema_sql(table_name: str, fields: FieldMap) -> str: check_identifier(table_name) pk = 'pk INTEGER PRIMARY KEY,' spcs = ' ' * 4 @@ -81,25 +90,24 @@ def make_schema_sql(table_name, fields): return f'CREATE TABLE {table_name} (\n{spcs}{pk}\n{spcs}{columns}\n)' -def create_table(table_name, fields): +def create_table(table_name: str, fields: FieldMap) -> None: con = get_connection() con.execute(make_schema_sql(table_name, fields)) -def read_columns_sql(table_name): - con = get_connection() +def read_columns_sql(table_name: str) -> list[ColumnSchema]: check_identifier(table_name) + con = get_connection() rows = con.execute(f'PRAGMA table_info({table_name!r})') - # row fields: cid name type notnull dflt_value pk - return [ColumnSchema(r[1], r[2]) for r in rows] + return [ColumnSchema(r['name'], r['type']) for r in rows] -def valid_table(table_name, fields): +def valid_table(table_name: str, fields: FieldMap) -> bool: table_columns = read_columns_sql(table_name) return set(gen_columns_sql(fields)) <= set(table_columns) -def ensure_table(table_name, fields): +def ensure_table(table_name: str, fields: FieldMap) -> None: table_columns = read_columns_sql(table_name) if len(table_columns) == 0: create_table(table_name, fields) @@ -107,21 +115,21 @@ def ensure_table(table_name, fields): raise SchemaMismatch(table_name) -def insert_record(table_name, fields): - con = get_connection() +def insert_record(table_name: str, data: dict[str, Any]) -> int: check_identifier(table_name) - placeholders = ', '.join(['?'] * len(fields)) + con = get_connection() + placeholders = ', '.join(['?'] * len(data)) sql = f'INSERT INTO {table_name} VALUES (NULL, {placeholders})' - cursor = con.execute(sql, tuple(fields.values())) + cursor = con.execute(sql, tuple(data.values())) pk = cursor.lastrowid con.commit() cursor.close() return pk -def fetch_record(table_name, pk): - con = get_connection() +def fetch_record(table_name: str, pk: int) -> sqlite3.Row: check_identifier(table_name) + con = get_connection() sql = f'SELECT * FROM {table_name} WHERE pk = ? LIMIT 2' result = list(con.execute(sql, (pk,))) if len(result) == 0: @@ -132,19 +140,21 @@ def fetch_record(table_name, pk): raise UnexpectedMultipleResults() -def update_record(table_name, pk, fields): +def update_record( + table_name: str, pk: int, data: dict[str, Any] +) -> tuple[str, tuple[Any, ...]]: check_identifier(table_name) con = get_connection() - names = ', '.join(fields.keys()) - placeholders = ', '.join(['?'] * len(fields)) - values = tuple(fields.values()) + (pk,) + names = ', '.join(data.keys()) + placeholders = ', '.join(['?'] * len(data)) + values = tuple(data.values()) + (pk,) sql = f'UPDATE {table_name} SET ({names}) = ({placeholders}) WHERE pk = ?' con.execute(sql, values) con.commit() return sql, values -def delete_record(table_name, pk): +def delete_record(table_name: str, pk: int) -> sqlite3.Cursor: con = get_connection() check_identifier(table_name) sql = f'DELETE FROM {table_name} WHERE pk = ?' diff --git a/25-class-metaprog/persistent/dblib_test.py b/25-class-metaprog/persistent/dblib_test.py index 9a0a93c..dcaf0bb 100644 --- a/25-class-metaprog/persistent/dblib_test.py +++ b/25-class-metaprog/persistent/dblib_test.py @@ -77,7 +77,7 @@ def test_fetch_record(create_movies_sql): con.execute(create_movies_sql) pk = insert_record('movies', fields) row = fetch_record('movies', pk) - assert row == (1, 'Frozen', 1_290_000_000.0) + assert tuple(row) == (1, 'Frozen', 1_290_000_000.0) def test_fetch_record_no_such_pk(create_movies_sql): @@ -98,7 +98,7 @@ def test_update_record(create_movies_sql): row = fetch_record('movies', pk) assert sql == 'UPDATE movies SET (title, revenue) = (?, ?) WHERE pk = ?' assert values == ('Frozen', 1_299_999_999, 1) - assert row == (1, 'Frozen', 1_299_999_999.0) + assert tuple(row) == (1, 'Frozen', 1_299_999_999.0) def test_delete_record(create_movies_sql): diff --git a/25-class-metaprog/persistent/persistlib.py b/25-class-metaprog/persistent/persistlib.py index 633f48c..15c9d38 100644 --- a/25-class-metaprog/persistent/persistlib.py +++ b/25-class-metaprog/persistent/persistlib.py @@ -4,25 +4,26 @@ >>> class Movie(Persistent): ... title: str ... year: int - ... boxmega: float + ... megabucks: float Implemented behavior:: >>> Movie._connect() # doctest: +ELLIPSIS - >>> movie = Movie('The Godfather', 1972, 137) + >>> movie = Movie(title='The Godfather', year=1972, megabucks=137) >>> movie.title 'The Godfather' - >>> movie.boxmega + >>> movie.megabucks 137.0 -Instances always have a ``.pk`` attribute, but it is ``None`` until the +Instances always have a ``._pk`` attribute, but it is ``None`` until the object is saved:: - >>> movie.pk is None + >>> movie._pk is None True - >>> movie._persist() - >>> movie.pk + >>> movie._save() + 1 + >>> movie._pk 1 Delete the in-memory ``movie``, and fetch the record from the database, @@ -31,7 +32,7 @@ >>> del movie >>> film = Movie[1] >>> film - Movie('The Godfather', 1972, 137.0, pk=1) + Movie(title='The Godfather', year=1972, megabucks=137.0, _pk=1) By default, the table name is the class name lowercased, with an appended "s" for plural:: @@ -51,69 +52,89 @@ class declaration:: """ -from typing import get_type_hints +from typing import Any, ClassVar, get_type_hints import dblib as db class Field: - def __init__(self, name, py_type): + def __init__(self, name: str, py_type: type) -> None: self.name = name self.type = py_type - def __set__(self, instance, value): + def __set__(self, instance: 'Persistent', value: Any) -> None: try: value = self.type(value) - except TypeError as e: - msg = f'{value!r} is not compatible with {self.name}:{self.type}.' + except (TypeError, ValueError) as e: + type_name = self.type.__name__ + msg = f'{value!r} is not compatible with {self.name}:{type_name}.' raise TypeError(msg) from e instance.__dict__[self.name] = value class Persistent: - def __init_subclass__( - cls, *, db_path=db.DEFAULT_DB_PATH, table='', **kwargs - ): - super().__init_subclass__(**kwargs) + _TABLE_NAME: ClassVar[str] + _TABLE_READY: ClassVar[bool] = False + + @classmethod + def _fields(cls) -> dict[str, type]: + return { + name: py_type + for name, py_type in get_type_hints(cls).items() + if not name.startswith('_') + } + + def __init_subclass__(cls, *, table: str = '', **kwargs: dict): + super().__init_subclass__(**kwargs) # type:ignore cls._TABLE_NAME = table if table else cls.__name__.lower() + 's' - cls._TABLE_READY = False - for name, py_type in get_type_hints(cls).items(): + for name, py_type in cls._fields().items(): setattr(cls, name, Field(name, py_type)) @staticmethod - def _connect(db_path=db.DEFAULT_DB_PATH): + def _connect(db_path: str = db.DEFAULT_DB_PATH): return db.connect(db_path) @classmethod - def _ensure_table(cls): + def _ensure_table(cls) -> str: if not cls._TABLE_READY: - db.ensure_table(cls._TABLE_NAME, get_type_hints(cls)) + db.ensure_table(cls._TABLE_NAME, cls._fields()) cls._TABLE_READY = True return cls._TABLE_NAME - def _fields(self): + def __class_getitem__(cls, pk: int) -> 'Persistent': + field_names = ['_pk'] + list(cls._fields()) + values = db.fetch_record(cls._TABLE_NAME, pk) + return cls(**dict(zip(field_names, values))) + + def _asdict(self) -> dict[str, Any]: return { name: getattr(self, name) for name, attr in self.__class__.__dict__.items() if isinstance(attr, Field) } - def __init__(self, *args, pk=None): - for name, arg in zip(self._fields(), args): + def __init__(self, *, _pk=None, **kwargs): + field_names = self._asdict().keys() + for name, arg in kwargs.items(): + if name not in field_names: + msg = f'{self.__class__.__name__!r} has no attribute {name!r}' + raise AttributeError(msg) setattr(self, name, arg) - self.pk = pk - - def __class_getitem__(cls, pk): - return cls(*db.fetch_record(cls._TABLE_NAME, pk)[1:], pk=pk) - - def __repr__(self): - args = ', '.join(repr(value) for value in self._fields().values()) - pk = '' if self.pk is None else f', pk={self.pk}' - return f'{self.__class__.__name__}({args}{pk})' - - def _persist(self): + self._pk = _pk + + def __repr__(self) -> str: + kwargs = ', '.join( + f'{key}={value!r}' for key, value in self._asdict().items() + ) + cls_name = self.__class__.__name__ + if self._pk is None: + return f'{cls_name}({kwargs})' + return f'{cls_name}({kwargs}, _pk={self._pk})' + + def _save(self) -> int: table = self.__class__._ensure_table() - if self.pk is None: - self.pk = db.insert_record(table, self._fields()) + if self._pk is None: + self._pk = db.insert_record(table, self._asdict()) else: - db.update_record(table, self.pk, self._fields()) + db.update_record(table, self._pk, self._asdict()) + return self._pk diff --git a/25-class-metaprog/persistent/persistlib_test.py b/25-class-metaprog/persistent/persistlib_test.py new file mode 100644 index 0000000..1604ccb --- /dev/null +++ b/25-class-metaprog/persistent/persistlib_test.py @@ -0,0 +1,37 @@ +import pytest + + +from persistlib import Persistent + + +def test_field_descriptor_validation_type_error(): + class Cat(Persistent): + name: str + weight: float + + with pytest.raises(TypeError) as e: + felix = Cat(name='Felix', weight=None) + + assert str(e.value) == 'None is not compatible with weight:float.' + + +def test_field_descriptor_validation_value_error(): + class Cat(Persistent): + name: str + weight: float + + with pytest.raises(TypeError) as e: + felix = Cat(name='Felix', weight='half stone') + + assert str(e.value) == "'half stone' is not compatible with weight:float." + + +def test_constructor_attribute_error(): + class Cat(Persistent): + name: str + weight: float + + with pytest.raises(AttributeError) as e: + felix = Cat(name='Felix', weight=3.2, age=7) + + assert str(e.value) == "'Cat' has no attribute 'age'" From 177d914c9f99e8a4fc6b56a08cf900ec26d27c00 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Sat, 17 Apr 2021 21:02:51 -0300 Subject: [PATCH 044/166] ch25: new example with __init_subclas__ --- 25-class-metaprog/checked/checkedlib.py | 143 +++++++++++++++++++ 25-class-metaprog/checked/checkedlib_demo.py | 21 +++ 25-class-metaprog/checked/checkedlib_test.py | 37 +++++ 3 files changed, 201 insertions(+) create mode 100644 25-class-metaprog/checked/checkedlib.py create mode 100644 25-class-metaprog/checked/checkedlib_demo.py create mode 100644 25-class-metaprog/checked/checkedlib_test.py diff --git a/25-class-metaprog/checked/checkedlib.py b/25-class-metaprog/checked/checkedlib.py new file mode 100644 index 0000000..505c915 --- /dev/null +++ b/25-class-metaprog/checked/checkedlib.py @@ -0,0 +1,143 @@ +""" +A ``Checked`` subclass definition requires that keyword arguments are +used to create an instance, and provides a nice ``__repr__``:: + +# tag::MOVIE_DEFINITION[] + + >>> class Movie(Checked): # <1> + ... title: str # <2> + ... year: int + ... megabucks: float + ... + >>> movie = Movie(title='The Godfather', year=1972, megabucks=137) # <3> + >>> movie.title + 'The Godfather' + >>> movie # <4> + Movie(title='The Godfather', year=1972, megabucks=137.0) + +# end::MOVIE_DEFINITION[] + +The type of arguments is runtime checked when an attribute is set, +including during instantiation:: + +# tag::MOVIE_TYPE_VALIDATION[] + + >>> movie.year = 'MCMLXXII' # <1> + Traceback (most recent call last): + ... + TypeError: 'MCMLXXII' is not compatible with year:int + >>> blockbuster = Movie(title='Avatar', year=2009, megabucks='billions') # <2> + Traceback (most recent call last): + ... + TypeError: 'billions' is not compatible with megabucks:float + +# end::MOVIE_TYPE_VALIDATION[] + +Attributes not passed as arguments to the constructor are initialized with +default values:: + +# tag::MOVIE_DEFAULTS[] + + >>> Movie(title='Life of Brian') + Movie(title='Life of Brian', year=0, megabucks=0.0) + +# end::MOVIE_DEFAULTS[] + +Providing extra arguments to the constructor is not allowed:: + + >>> blockbuster = Movie(title='Avatar', year=2009, megabucks=2000, + ... director='James Cameron') + Traceback (most recent call last): + ... + AttributeError: 'Movie' has no attribute 'director' + +Creating new attributes at runtime is restricted as well:: + + >>> movie.director = 'Francis Ford Coppola' + Traceback (most recent call last): + ... + AttributeError: 'Movie' has no attribute 'director' + +The `_as_dict` instance creates a `dict` from the attributes of a `Movie` object:: + + >>> movie._asdict() + {'title': 'The Godfather', 'year': 1972, 'megabucks': 137.0} + +""" + +# tag::CHECKED_FIELD[] +from collections.abc import Callable # <1> +from typing import Any, NoReturn, get_type_hints + +MISSING = object() # <2> + + +class Field: + def __init__(self, name: str, constructor: Callable) -> None: # <3> + self.name = name + self.constructor = constructor + + def __set__(self, instance: 'Checked', value: Any) -> None: # <4> + if value is MISSING: # <5> + value = self.constructor() + else: + try: + value = self.constructor(value) # <6> + except (TypeError, ValueError) as e: + type_name = self.constructor.__name__ + msg = f'{value!r} is not compatible with {self.name}:{type_name}' + raise TypeError(msg) from e + instance.__dict__[self.name] = value # <7> + + +# end::CHECKED_FIELD[] + +# tag::CHECKED_TOP[] +class Checked: + @classmethod + def _fields(cls) -> dict[str, type]: # <1> + return get_type_hints(cls) + + def __init_subclass__(subclass) -> None: # <2> + super().__init_subclass__() # <3> + for name, constructor in subclass._fields().items(): # <4> + setattr(subclass, name, Field(name, constructor)) # <5> + + def __init__(self, **kwargs: Any) -> None: + for name in self._fields(): # <6> + value = kwargs.pop(name, MISSING) # <7> + setattr(self, name, value) # <8> + if kwargs: # <9> + self.__flag_unknown_attrs(*kwargs) + + def __setattr__(self, name: str, value: Any) -> None: # <10> + if name in self._fields(): # <11> + cls = self.__class__ + descriptor = getattr(cls, name) + descriptor.__set__(self, value) # <12> + else: # <13> + self.__flag_unknown_attrs(name) + + # end::CHECKED_TOP[] + + # tag::CHECKED_BOTTOM[] + def __flag_unknown_attrs(self, *names: str) -> NoReturn: # <1> + plural = 's' if len(names) > 1 else '' + extra = ', '.join(f'{name!r}' for name in names) + cls_name = repr(self.__class__.__name__) + raise AttributeError(f'{cls_name} has no attribute{plural} {extra}') + + def _asdict(self) -> dict[str, Any]: # <2> + return { + name: getattr(self, name) + for name, attr in self.__class__.__dict__.items() + if isinstance(attr, Field) + } + + def __repr__(self) -> str: # <3> + kwargs = ', '.join( + f'{key}={value!r}' for key, value in self._asdict().items() + ) + return f'{self.__class__.__name__}({kwargs})' + +# end::CHECKED_BOTTOM[] diff --git a/25-class-metaprog/checked/checkedlib_demo.py b/25-class-metaprog/checked/checkedlib_demo.py new file mode 100644 index 0000000..203f90b --- /dev/null +++ b/25-class-metaprog/checked/checkedlib_demo.py @@ -0,0 +1,21 @@ +from checkedlib import Checked + +class Movie(Checked): + title: str + year: int + megabucks: float + + +if __name__ == '__main__': + movie = Movie(title='The Godfather', year=1972, megabucks=137) + print(movie.title) + print(movie) + try: + # remove the "type: ignore" comment to see Mypy error + movie.year = 'MCMLXXII' # type: ignore + except TypeError as e: + print(e) + try: + blockbuster = Movie(title='Avatar', year=2009, megabucks='billions') + except TypeError as e: + print(e) diff --git a/25-class-metaprog/checked/checkedlib_test.py b/25-class-metaprog/checked/checkedlib_test.py new file mode 100644 index 0000000..6635a66 --- /dev/null +++ b/25-class-metaprog/checked/checkedlib_test.py @@ -0,0 +1,37 @@ +import pytest + + +from checkedlib import Checked + + +def test_field_descriptor_validation_type_error(): + class Cat(Checked): + name: str + weight: float + + with pytest.raises(TypeError) as e: + felix = Cat(name='Felix', weight=None) + + assert str(e.value) == 'None is not compatible with weight:float' + + +def test_field_descriptor_validation_value_error(): + class Cat(Checked): + name: str + weight: float + + with pytest.raises(TypeError) as e: + felix = Cat(name='Felix', weight='half stone') + + assert str(e.value) == "'half stone' is not compatible with weight:float" + + +def test_constructor_attribute_error(): + class Cat(Checked): + name: str + weight: float + + with pytest.raises(AttributeError) as e: + felix = Cat(name='Felix', weight=3.2, age=7) + + assert str(e.value) == "'Cat' has no attribute 'age'" From 674fad84c5942a45b95fa31d6c8a72203de0062d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 20 Apr 2021 20:06:32 +0000 Subject: [PATCH 045/166] build(deps): bump py from 1.8.1 to 1.10.0 in /10-dp-1class-func Bumps [py](https://github.com/pytest-dev/py) from 1.8.1 to 1.10.0. - [Release notes](https://github.com/pytest-dev/py/releases) - [Changelog](https://github.com/pytest-dev/py/blob/master/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/py/compare/1.8.1...1.10.0) Signed-off-by: dependabot[bot] --- 10-dp-1class-func/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/10-dp-1class-func/requirements.txt b/10-dp-1class-func/requirements.txt index 2b20c05..1826922 100644 --- a/10-dp-1class-func/requirements.txt +++ b/10-dp-1class-func/requirements.txt @@ -6,7 +6,7 @@ attrs==19.3.0 more-itertools==8.2.0 packaging==20.3 pluggy==0.13.1 -py==1.8.1 +py==1.10.0 pyparsing==2.4.6 pytest==5.4.1 six==1.14.0 From 8ec5fd08612c99c9c130e0f20346bb24b6874924 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Thu, 22 Apr 2021 19:39:49 -0300 Subject: [PATCH 046/166] ch25: added checkeddeco example --- 25-class-metaprog/checked/checkedlib.py | 20 +-- 25-class-metaprog/checkeddeco/checkeddeco.py | 170 ++++++++++++++++++ .../checkeddeco/checkeddeco_demo.py | 22 +++ .../checkeddeco/checkeddeco_test.py | 40 +++++ 4 files changed, 242 insertions(+), 10 deletions(-) create mode 100644 25-class-metaprog/checkeddeco/checkeddeco.py create mode 100644 25-class-metaprog/checkeddeco/checkeddeco_demo.py create mode 100644 25-class-metaprog/checkeddeco/checkeddeco_test.py diff --git a/25-class-metaprog/checked/checkedlib.py b/25-class-metaprog/checked/checkedlib.py index 505c915..63442e3 100644 --- a/25-class-metaprog/checked/checkedlib.py +++ b/25-class-metaprog/checked/checkedlib.py @@ -104,18 +104,18 @@ def __init_subclass__(subclass) -> None: # <2> setattr(subclass, name, Field(name, constructor)) # <5> def __init__(self, **kwargs: Any) -> None: - for name in self._fields(): # <6> - value = kwargs.pop(name, MISSING) # <7> - setattr(self, name, value) # <8> - if kwargs: # <9> - self.__flag_unknown_attrs(*kwargs) - - def __setattr__(self, name: str, value: Any) -> None: # <10> - if name in self._fields(): # <11> + for name in self._fields(): # <6> + value = kwargs.pop(name, MISSING) # <7> + setattr(self, name, value) # <8> + if kwargs: # <9> + self.__flag_unknown_attrs(*kwargs) # <10> + + def __setattr__(self, name: str, value: Any) -> None: # <11> + if name in self._fields(): # <12> cls = self.__class__ descriptor = getattr(cls, name) - descriptor.__set__(self, value) # <12> - else: # <13> + descriptor.__set__(self, value) # <13> + else: # <14> self.__flag_unknown_attrs(name) # end::CHECKED_TOP[] diff --git a/25-class-metaprog/checkeddeco/checkeddeco.py b/25-class-metaprog/checkeddeco/checkeddeco.py new file mode 100644 index 0000000..3eeba15 --- /dev/null +++ b/25-class-metaprog/checkeddeco/checkeddeco.py @@ -0,0 +1,170 @@ +""" +A ``Checked`` subclass definition requires that keyword arguments are +used to create an instance, and provides a nice ``__repr__``:: + +# tag::MOVIE_DEFINITION[] + + >>> @checked + ... class Movie: + ... title: str + ... year: int + ... megabucks: float + ... + >>> movie = Movie(title='The Godfather', year=1972, megabucks=137) # <3> + >>> movie.title + 'The Godfather' + >>> movie # <4> + Movie(title='The Godfather', year=1972, megabucks=137.0) + +# end::MOVIE_DEFINITION[] + +The type of arguments is runtime checked when an attribute is set, +including during instantiation:: + +# tag::MOVIE_TYPE_VALIDATION[] + + >>> movie.year = 'MCMLXXII' # <1> + Traceback (most recent call last): + ... + TypeError: 'MCMLXXII' is not compatible with year:int + >>> blockbuster = Movie(title='Avatar', year=2009, megabucks='billions') # <2> + Traceback (most recent call last): + ... + TypeError: 'billions' is not compatible with megabucks:float + +# end::MOVIE_TYPE_VALIDATION[] + +Attributes not passed as arguments to the constructor are initialized with +default values:: + +# tag::MOVIE_DEFAULTS[] + + >>> Movie(title='Life of Brian') + Movie(title='Life of Brian', year=0, megabucks=0.0) + +# end::MOVIE_DEFAULTS[] + +Providing extra arguments to the constructor is not allowed:: + + >>> blockbuster = Movie(title='Avatar', year=2009, megabucks=2000, + ... director='James Cameron') + Traceback (most recent call last): + ... + AttributeError: 'Movie' has no attribute 'director' + +Creating new attributes at runtime is restricted as well:: + + >>> movie.director = 'Francis Ford Coppola' + Traceback (most recent call last): + ... + AttributeError: 'Movie' has no attribute 'director' + +The `_as_dict` instance creates a `dict` from the attributes of a `Movie` object:: + + >>> movie._asdict() + {'title': 'The Godfather', 'year': 1972, 'megabucks': 137.0} + +""" + +from collections.abc import Callable # <1> +from typing import Any, NoReturn, get_type_hints + +MISSING = object() # <2> + + +class Field: + def __init__(self, name: str, constructor: Callable) -> None: # <3> + self.name = name + self.constructor = constructor + + def __set__(self, instance: Any, value: Any) -> None: # <4> + if value is MISSING: # <5> + value = self.constructor() + else: + try: + value = self.constructor(value) # <6> + except (TypeError, ValueError) as e: + type_name = self.constructor.__name__ + msg = ( + f'{value!r} is not compatible with {self.name}:{type_name}' + ) + raise TypeError(msg) from e + instance.__dict__[self.name] = value # <7> + + +# tag::CHECKED_DECORATOR_TOP[] +_methods_to_inject: list[Callable] = [] +_classmethods_to_inject: list[Callable] = [] + +def checked(cls: type) -> type: # <2> + for func in _methods_to_inject: + name = func.__name__ + setattr(cls, name, func) # <5> + + for func in _classmethods_to_inject: + name = func.__name__ + setattr(cls, name, classmethod(func)) # <5> + + for name, constructor in _fields(cls).items(): # <4> + setattr(cls, name, Field(name, constructor)) # <5> + + return cls + + +def _method(func: Callable) -> Callable: + _methods_to_inject.append(func) + return func + + +def _classmethod(func: Callable) -> Callable: + _classmethods_to_inject.append(func) + return func + +# tag::CHECKED_METHODS_TOP[] +@_classmethod +def _fields(cls: type) -> dict[str, type]: # <1> + return get_type_hints(cls) + +@_method +def __init__(self: Any, **kwargs: Any) -> None: + for name in self._fields(): # <6> + value = kwargs.pop(name, MISSING) # <7> + setattr(self, name, value) # <8> + if kwargs: # <9> + self.__flag_unknown_attrs(*kwargs) # <10> + +@_method +def __setattr__(self: Any, name: str, value: Any) -> None: # <11> + if name in self._fields(): # <12> + cls = self.__class__ + descriptor = getattr(cls, name) + descriptor.__set__(self, value) # <13> + else: # <14> + self.__flag_unknown_attrs(name) +# end::CHECKED_METHODS_TOP[] + +# tag::CHECKED_METHODS_BOTTOM[] +@_method +def __flag_unknown_attrs(self: Any, *names: str) -> NoReturn: # <1> + plural = 's' if len(names) > 1 else '' + extra = ', '.join(f'{name!r}' for name in names) + cls_name = repr(self.__class__.__name__) + raise AttributeError(f'{cls_name} has no attribute{plural} {extra}') + + +@_method +def _asdict(self: Any) -> dict[str, Any]: # <2> + return { + name: getattr(self, name) + for name, attr in self.__class__.__dict__.items() + if isinstance(attr, Field) + } + + +@_method +def __repr__(self: Any) -> str: # <3> + kwargs = ', '.join( + f'{key}={value!r}' for key, value in self._asdict().items() + ) + return f'{self.__class__.__name__}({kwargs})' +# end::CHECKED_METHODS_BOTTOM[] diff --git a/25-class-metaprog/checkeddeco/checkeddeco_demo.py b/25-class-metaprog/checkeddeco/checkeddeco_demo.py new file mode 100644 index 0000000..f4c45e7 --- /dev/null +++ b/25-class-metaprog/checkeddeco/checkeddeco_demo.py @@ -0,0 +1,22 @@ +from checkeddeco import checked + +@checked +class Movie: + title: str + year: int + megabucks: float + + +if __name__ == '__main__': + movie = Movie(title='The Godfather', year=1972, megabucks=137) + print(movie.title) + print(movie) + try: + # remove the "type: ignore" comment to see Mypy error + movie.year = 'MCMLXXII' # type: ignore + except TypeError as e: + print(e) + try: + blockbuster = Movie(title='Avatar', year=2009, megabucks='billions') + except TypeError as e: + print(e) diff --git a/25-class-metaprog/checkeddeco/checkeddeco_test.py b/25-class-metaprog/checkeddeco/checkeddeco_test.py new file mode 100644 index 0000000..7f4d8dc --- /dev/null +++ b/25-class-metaprog/checkeddeco/checkeddeco_test.py @@ -0,0 +1,40 @@ +import pytest + + +from checkeddeco import checked + + +def test_field_descriptor_validation_type_error(): + @checked + class Cat: + name: str + weight: float + + with pytest.raises(TypeError) as e: + felix = Cat(name='Felix', weight=None) + + assert str(e.value) == 'None is not compatible with weight:float' + + +def test_field_descriptor_validation_value_error(): + @checked + class Cat: + name: str + weight: float + + with pytest.raises(TypeError) as e: + felix = Cat(name='Felix', weight='half stone') + + assert str(e.value) == "'half stone' is not compatible with weight:float" + + +def test_constructor_attribute_error(): + @checked + class Cat: + name: str + weight: float + + with pytest.raises(AttributeError) as e: + felix = Cat(name='Felix', weight=3.2, age=7) + + assert str(e.value) == "'Cat' has no attribute 'age'" From eee989cbd68af7f5fefaf74f441aeb66c8ac5718 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Thu, 22 Apr 2021 19:43:07 -0300 Subject: [PATCH 047/166] ch10: removed development requirements --- 10-dp-1class-func/requirements.txt | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 10-dp-1class-func/requirements.txt diff --git a/10-dp-1class-func/requirements.txt b/10-dp-1class-func/requirements.txt deleted file mode 100644 index 2b20c05..0000000 --- a/10-dp-1class-func/requirements.txt +++ /dev/null @@ -1,13 +0,0 @@ -mypy==0.770 -mypy-extensions==0.4.3 -typed-ast==1.4.1 -typing-extensions==3.7.4.1 -attrs==19.3.0 -more-itertools==8.2.0 -packaging==20.3 -pluggy==0.13.1 -py==1.8.1 -pyparsing==2.4.6 -pytest==5.4.1 -six==1.14.0 -wcwidth==0.1.9 From 063a540868c46d4af375bd9983138ea2badcfe30 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Sat, 24 Apr 2021 20:49:53 -0300 Subject: [PATCH 048/166] ch22: new README.md and minor edits --- 22-async/mojifinder/README.md | 41 ++++++++++++++++++++ 22-async/mojifinder/bottle.py | 6 +-- 22-async/mojifinder/requirements.txt | 3 +- 22-async/mojifinder/web_mojifinder.py | 9 ++--- 22-async/mojifinder/web_mojifinder_bottle.py | 4 +- 5 files changed, 51 insertions(+), 12 deletions(-) create mode 100644 22-async/mojifinder/README.md diff --git a/22-async/mojifinder/README.md b/22-async/mojifinder/README.md new file mode 100644 index 0000000..16b8c2e --- /dev/null +++ b/22-async/mojifinder/README.md @@ -0,0 +1,41 @@ +# Mojifinder: Unicode character search examples + +Examples from _Fluent Python, Second Edition_—Chapter 22, _Asynchronous Programming_. + +## How to run `web_mojifinder.py` + +`web_mojifinder.py` is a Web application built with _[FastAPI](https://fastapi.tiangolo.com/)_. +To run it, first install _FastAPI_ and an ASGI server. +The application was tested with _[Uvicorn](https://www.uvicorn.org/)_. + +``` +$ pip install fastapi uvicorn +``` + +Now you can use `uvicorn` to run the app. + +``` +$ uvicorn web_mojifinder:app +``` + +Finally, visit http://127.0.0.1:8000/ with your browser to see the search form. + + +## Directory contents + +These files can be run as scripts directly from the command line: + +- `charindex.py`: libray used by the Mojifinder examples. Also works as CLI search script. +- `tcp_mojifinder.py`: TCP/IP Unicode search server. Depends only on the Python 3.9 standard library. Use a telnet application as client. +- `web_mojifinder_bottle.py`: Unicode Web service. Depends on `bottle.py` and `static/form.html`. Use an HTTP browser as client. + +This program requires an ASGI server to run it: + +- `web_mojifinder.py`: Unicode Web service. Depends on _[FastAPI](https://fastapi.tiangolo.com/)_ and `static/form.html`. + +Support files: + +- `bottle.py`: local copy of the single-file _[Bottle](https://bottlepy.org/)_ Web framework. +- `requirements.txt`: list of dependencies for `web_mojifinder.py`. +- `static/form.html`: HTML form used by the `web_*` examples. +- `README.md`: this file! diff --git a/22-async/mojifinder/bottle.py b/22-async/mojifinder/bottle.py index 96ca3e4..9806efd 100644 --- a/22-async/mojifinder/bottle.py +++ b/22-async/mojifinder/bottle.py @@ -16,7 +16,7 @@ from __future__ import with_statement __author__ = 'Marcel Hellkamp' -__version__ = '0.12.18' +__version__ = '0.12.19' __license__ = 'MIT' # The gevent server adapter needs to patch some modules before they are imported @@ -440,7 +440,7 @@ def match(self, environ): nocheck = set(methods) for method in set(self.static) - nocheck: if path in self.static[method]: - allowed.add(verb) + allowed.add(method) for method in set(self.dyna_regexes) - allowed - nocheck: for combined, rules in self.dyna_regexes[method]: match = combined(path) @@ -2585,7 +2585,7 @@ def parse_range_header(header, maxlen=0): def _parse_qsl(qs): r = [] - for pair in qs.replace(';','&').split('&'): + for pair in qs.split('&'): if not pair: continue nv = pair.split('=', 1) if len(nv) != 2: nv.append('') diff --git a/22-async/mojifinder/requirements.txt b/22-async/mojifinder/requirements.txt index 4c17510..4d97ea9 100644 --- a/22-async/mojifinder/requirements.txt +++ b/22-async/mojifinder/requirements.txt @@ -1,6 +1,7 @@ click==7.1.2 fastapi==0.63.0 h11==0.12.0 -pydantic==1.7.3 +pydantic==1.8.1 starlette==0.13.6 +typing-extensions==3.7.4.3 uvicorn==0.13.4 diff --git a/22-async/mojifinder/web_mojifinder.py b/22-async/mojifinder/web_mojifinder.py index 96004d8..7e0ff96 100644 --- a/22-async/mojifinder/web_mojifinder.py +++ b/22-async/mojifinder/web_mojifinder.py @@ -1,4 +1,4 @@ -import pathlib +from pathlib import Path from unicodedata import name from fastapi import FastAPI @@ -18,9 +18,8 @@ class CharName(BaseModel): # <2> def init(app): # <3> app.state.index = InvertedIndex() - static = pathlib.Path(__file__).parent.absolute() / 'static' # <4> - with open(static / 'form.html') as fp: - app.state.form = fp.read() + static = Path(__file__).parent.absolute() / 'static' # <4> + app.state.form = (static / 'form.html').read_text() init(app) # <5> @@ -33,4 +32,4 @@ async def search(q: str): # <7> def form(): # <9> return app.state.form -# no main funcion # <10> +# no main funcion # <10> \ No newline at end of file diff --git a/22-async/mojifinder/web_mojifinder_bottle.py b/22-async/mojifinder/web_mojifinder_bottle.py index 1aa0c2b..cc158a6 100755 --- a/22-async/mojifinder/web_mojifinder_bottle.py +++ b/22-async/mojifinder/web_mojifinder_bottle.py @@ -11,7 +11,7 @@ @route('/') def form(): - return static_file('form.html', root = 'static/') + return static_file('form.html', root='static/') @route('/search') @@ -28,10 +28,8 @@ def search(): def main(port): global index index = InvertedIndex() - host = 'localhost' run(host='localhost', port=port, debug=True) if __name__ == '__main__': main(8000) - From 5312d4f8246b85d9d82cc993b7fa2b962838acbf Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Sat, 24 Apr 2021 20:51:33 -0300 Subject: [PATCH 049/166] Update README.md --- 22-async/mojifinder/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/22-async/mojifinder/README.md b/22-async/mojifinder/README.md index 16b8c2e..b3df996 100644 --- a/22-async/mojifinder/README.md +++ b/22-async/mojifinder/README.md @@ -38,4 +38,4 @@ Support files: - `bottle.py`: local copy of the single-file _[Bottle](https://bottlepy.org/)_ Web framework. - `requirements.txt`: list of dependencies for `web_mojifinder.py`. - `static/form.html`: HTML form used by the `web_*` examples. -- `README.md`: this file! +- `README.md`: this file 🤓 From 1689eec623a07f4b2824d5195d9b2a34d39b36e2 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Thu, 20 May 2021 22:58:05 -0300 Subject: [PATCH 050/166] ch15: draft examples --- 15-more-types/cafeteria/cafeteria.py | 41 +++++++ 15-more-types/cafeteria/cafeteria_demo.py | 23 ++++ 15-more-types/collections_variance.py | 16 +++ {15-type-hints => 15-more-types}/erp.py | 0 {15-type-hints => 15-more-types}/erp_test.py | 4 +- 15-more-types/gen_contra.py | 59 +++++++++ 15-more-types/petbox/petbox.py | 47 ++++++++ 15-more-types/petbox/petbox_demo.py | 42 +++++++ .../randompick_generic.py | 0 .../randompick_generic_test.py | 2 +- {15-type-hints => 15-more-types}/randompop.py | 2 +- .../randompop_test.py | 2 +- 15-more-types/typeddict/books.py | 32 +++++ 15-more-types/typeddict/books_any.py | 32 +++++ 15-more-types/typeddict/demo_books.py | 20 ++++ 15-more-types/typeddict/demo_not_book.py | 23 ++++ 15-more-types/typeddict/test_books.py | 112 ++++++++++++++++++ .../typeddict/test_books_check_fails.py | 20 ++++ README.md | 60 +++++----- 19 files changed, 501 insertions(+), 36 deletions(-) create mode 100644 15-more-types/cafeteria/cafeteria.py create mode 100644 15-more-types/cafeteria/cafeteria_demo.py create mode 100644 15-more-types/collections_variance.py rename {15-type-hints => 15-more-types}/erp.py (100%) rename {15-type-hints => 15-more-types}/erp_test.py (94%) create mode 100644 15-more-types/gen_contra.py create mode 100644 15-more-types/petbox/petbox.py create mode 100644 15-more-types/petbox/petbox_demo.py rename {15-type-hints => 15-more-types}/randompick_generic.py (100%) rename {15-type-hints => 15-more-types}/randompick_generic_test.py (97%) rename {15-type-hints => 15-more-types}/randompop.py (59%) rename {15-type-hints => 15-more-types}/randompop_test.py (96%) create mode 100644 15-more-types/typeddict/books.py create mode 100644 15-more-types/typeddict/books_any.py create mode 100644 15-more-types/typeddict/demo_books.py create mode 100644 15-more-types/typeddict/demo_not_book.py create mode 100644 15-more-types/typeddict/test_books.py create mode 100644 15-more-types/typeddict/test_books_check_fails.py diff --git a/15-more-types/cafeteria/cafeteria.py b/15-more-types/cafeteria/cafeteria.py new file mode 100644 index 0000000..8c88a92 --- /dev/null +++ b/15-more-types/cafeteria/cafeteria.py @@ -0,0 +1,41 @@ +from typing import TypeVar, Generic + + +class Beverage: + """Any beverage""" + + +class Juice(Beverage): + """Any fruit juice""" + + +class OrangeJuice(Juice): + """Delicious juice Brazilian oranges""" + + +class Coak(Beverage): + """Secret formula with lots of sugar""" + + +BeverageT = TypeVar('BeverageT', bound=Beverage) +JuiceT = TypeVar('JuiceT', bound=Juice) + + +class BeverageDispenser(Generic[BeverageT]): + + beverage: BeverageT + + def __init__(self, beverage: BeverageT) -> None: + self.beverage = beverage + + def dispense(self) -> BeverageT: + return self.beverage + + +class JuiceDispenser(BeverageDispenser[JuiceT]): + pass + + +class Cafeteria: + def __init__(self, dispenser: BeverageDispenser[JuiceT]): + self.dispenser = dispenser diff --git a/15-more-types/cafeteria/cafeteria_demo.py b/15-more-types/cafeteria/cafeteria_demo.py new file mode 100644 index 0000000..d93636b --- /dev/null +++ b/15-more-types/cafeteria/cafeteria_demo.py @@ -0,0 +1,23 @@ +from cafeteria import ( + Cafeteria, + BeverageDispenser, + JuiceDispenser, + Juice, + OrangeJuice, + Coak, +) + +orange = OrangeJuice() + +orange_dispenser: JuiceDispenser[OrangeJuice] = JuiceDispenser(orange) + +juice: Juice = orange_dispenser.dispense() + +soda = Coak() + +## Value of type variable "JuiceT" of "JuiceDispenser" cannot be "Coak" +# soda_dispenser = JuiceDispenser(soda) + +soda_dispenser = BeverageDispenser(soda) + +arnold_hall = Cafeteria(soda_dispenser) diff --git a/15-more-types/collections_variance.py b/15-more-types/collections_variance.py new file mode 100644 index 0000000..12fb9dc --- /dev/null +++ b/15-more-types/collections_variance.py @@ -0,0 +1,16 @@ +from collections.abc import Collection, Sequence + +col_int: Collection[int] + +seq_int: Sequence[int] = (1, 2, 3) + +## Incompatible types in assignment +## expression has type "Collection[int]" +## variable has type "Sequence[int]" +# seq_int = col_int + +col_int = seq_int + +## List item 0 has incompatible type "float" +## expected "int" +# col_int = [1.1] diff --git a/15-type-hints/erp.py b/15-more-types/erp.py similarity index 100% rename from 15-type-hints/erp.py rename to 15-more-types/erp.py diff --git a/15-type-hints/erp_test.py b/15-more-types/erp_test.py similarity index 94% rename from 15-type-hints/erp_test.py rename to 15-more-types/erp_test.py index 5e8c067..9abdc86 100644 --- a/15-type-hints/erp_test.py +++ b/15-more-types/erp_test.py @@ -1,5 +1,4 @@ -import random -from typing import Iterable, TYPE_CHECKING, List +from typing import TYPE_CHECKING from erp import EnterpriserRandomPopper import randompop @@ -9,7 +8,6 @@ def test_issubclass() -> None: assert issubclass(EnterpriserRandomPopper, randompop.RandomPopper) - def test_isinstance_untyped_items_argument() -> None: items = [1, 2, 3] popper = EnterpriserRandomPopper(items) # [int] is not required diff --git a/15-more-types/gen_contra.py b/15-more-types/gen_contra.py new file mode 100644 index 0000000..a8ed9a9 --- /dev/null +++ b/15-more-types/gen_contra.py @@ -0,0 +1,59 @@ +""" +In ``Generator[YieldType, SendType, ReturnType]``, +``SendType`` is contravariant. +The other type variables are covariant. + +This is how ``typing.Generator`` is declared:: + + class Generator(Iterator[T_co], Generic[T_co, T_contra, V_co]): + +(from https://docs.python.org/3/library/typing.html#typing.Generator) + +""" + +from typing import Generator + + +# Generator[YieldType, SendType, ReturnType] + +def gen_float_take_int() -> Generator[float, int, str]: + received = yield -1.0 + while received: + received = yield float(received) + return 'Done' + + +def gen_float_take_float() -> Generator[float, float, str]: + received = yield -1.0 + while received: + received = yield float(received) + return 'Done' + + +def gen_float_take_complex() -> Generator[float, complex, str]: + received = yield -1.0 + while received: + received = yield abs(received) + return 'Done' + +# Generator[YieldType, SendType, ReturnType] + +g0: Generator[float, float, str] = gen_float_take_float() + +g1: Generator[complex, float, str] = gen_float_take_float() + +## Incompatible types in assignment +## expression has type "Generator[float, float, str]" +## variable has type "Generator[int, float, str]") +# g2: Generator[int, float, str] = gen_float_take_float() + + +# Generator[YieldType, SendType, ReturnType] + +g3: Generator[float, int, str] = gen_float_take_float() + +## Incompatible types in assignment +## expression has type "Generator[float, float, str]" +## variable has type "Generator[float, complex, str]") +## g4: Generator[float, complex, str] = gen_float_take_float() + diff --git a/15-more-types/petbox/petbox.py b/15-more-types/petbox/petbox.py new file mode 100644 index 0000000..04b72ea --- /dev/null +++ b/15-more-types/petbox/petbox.py @@ -0,0 +1,47 @@ +from typing import TypeVar, Generic, Any + + +class Pet: + """Domestic animal kept for companionship.""" + + +class Dog(Pet): + """Canis familiaris""" + + +class Cat(Pet): + """Felis catus""" + + +class Siamese(Cat): + """Cat breed from Thailand""" + + +T = TypeVar('T') + + +class Box(Generic[T]): + def put(self, item: T) -> None: + self.contents = item + + def get(self) -> T: + return self.contents + + +T_contra = TypeVar('T_contra', contravariant=True) + + +class InBox(Generic[T_contra]): + def put(self, item: T) -> None: + self.contents = item + + +T_co = TypeVar('T_co', covariant=True) + + +class OutBox(Generic[T_co]): + def __init__(self, contents: Any): + self.contents = contents + + def get(self) -> Any: + return self.contents diff --git a/15-more-types/petbox/petbox_demo.py b/15-more-types/petbox/petbox_demo.py new file mode 100644 index 0000000..d09cab4 --- /dev/null +++ b/15-more-types/petbox/petbox_demo.py @@ -0,0 +1,42 @@ +from typing import TYPE_CHECKING + +from petbox import * + + +cat_box: Box[Cat] = Box() + +si = Siamese() + +cat_box.put(si) + +animal = cat_box.get() + +#if TYPE_CHECKING: +# reveal_type(animal) # Revealed: petbox.Cat* + + +################### Covariance + +out_box: OutBox[Cat] = OutBox(Cat()) + +out_box_si: OutBox[Siamese] = OutBox(Siamese()) + +## Incompatible types in assignment +## expression has type "OutBox[Cat]" +# variable has type "OutBox[Siamese]" +# out_box_si = out_box + +out_box = out_box_si + +################### Contravariance + +in_box: InBox[Cat] = InBox() + +in_box_si: InBox[Siamese] = InBox() + +in_box_si = in_box + +## Incompatible types in assignment +## expression has type "InBox[Siamese]" +## variable has type "InBox[Cat]" +# in_box = in_box_si diff --git a/15-type-hints/randompick_generic.py b/15-more-types/randompick_generic.py similarity index 100% rename from 15-type-hints/randompick_generic.py rename to 15-more-types/randompick_generic.py diff --git a/15-type-hints/randompick_generic_test.py b/15-more-types/randompick_generic_test.py similarity index 97% rename from 15-type-hints/randompick_generic_test.py rename to 15-more-types/randompick_generic_test.py index 07ebdc4..d642b26 100644 --- a/15-type-hints/randompick_generic_test.py +++ b/15-more-types/randompick_generic_test.py @@ -4,7 +4,7 @@ from randompick_generic import GenericRandomPicker -class LottoPicker(): +class LottoPicker: def __init__(self, items: Iterable[int]) -> None: self._items = list(items) random.shuffle(self._items) diff --git a/15-type-hints/randompop.py b/15-more-types/randompop.py similarity index 59% rename from 15-type-hints/randompop.py rename to 15-more-types/randompop.py index 35cad38..cf9c811 100644 --- a/15-type-hints/randompop.py +++ b/15-more-types/randompop.py @@ -1,4 +1,4 @@ -from typing import Protocol, TypeVar, runtime_checkable, Any +from typing import Protocol, runtime_checkable, Any @runtime_checkable diff --git a/15-type-hints/randompop_test.py b/15-more-types/randompop_test.py similarity index 96% rename from 15-type-hints/randompop_test.py rename to 15-more-types/randompop_test.py index 0b0f317..7cce7f6 100644 --- a/15-type-hints/randompop_test.py +++ b/15-more-types/randompop_test.py @@ -3,7 +3,7 @@ from typing import Any, Iterable, TYPE_CHECKING -class SimplePopper(): +class SimplePopper: def __init__(self, items: Iterable) -> None: self._items = list(items) random.shuffle(self._items) diff --git a/15-more-types/typeddict/books.py b/15-more-types/typeddict/books.py new file mode 100644 index 0000000..5a0bc82 --- /dev/null +++ b/15-more-types/typeddict/books.py @@ -0,0 +1,32 @@ +# tag::BOOKDICT[] +from typing import TypedDict, List +import json + +class BookDict(TypedDict): + isbn: str + title: str + authors: List[str] + pagecount: int +# end::BOOKDICT[] + +# tag::TOXML[] +AUTHOR_EL = '{}' + +def to_xml(book: BookDict) -> str: # <1> + elements: List[str] = [] # <2> + for key, value in book.items(): + if isinstance(value, list): # <3> + elements.extend( + AUTHOR_EL.format(n) for n in value) # <4> + else: + tag = key.upper() + elements.append(f'<{tag}>{value}') + xml = '\n\t'.join(elements) + return f'\n\t{xml}\n' +# end::TOXML[] + +# tag::FROMJSON[] +def from_json(data: str) -> BookDict: + whatever: BookDict = json.loads(data) # <1> + return whatever # <2> +# end::FROMJSON[] \ No newline at end of file diff --git a/15-more-types/typeddict/books_any.py b/15-more-types/typeddict/books_any.py new file mode 100644 index 0000000..49a544e --- /dev/null +++ b/15-more-types/typeddict/books_any.py @@ -0,0 +1,32 @@ +# tag::BOOKDICT[] +from typing import TypedDict, List +import json + +class BookDict(TypedDict): + isbn: str + title: str + authors: List[str] + pagecount: int +# end::BOOKDICT[] + +# tag::TOXML[] +AUTHOR_EL = '{}' + +def to_xml(book: BookDict) -> str: # <1> + elements: List[str] = [] # <2> + for key, value in book.items(): + if isinstance(value, list): # <3> + elements.extend(AUTHOR_EL.format(n) + for n in value) + else: + tag = key.upper() + elements.append(f'<{tag}>{value}') + xml = '\n\t'.join(elements) + return f'\n\t{xml}\n' +# end::TOXML[] + +# tag::FROMJSON[] +def from_json(data: str) -> BookDict: + whatever = json.loads(data) # <1> + return whatever # <2> +# end::FROMJSON[] diff --git a/15-more-types/typeddict/demo_books.py b/15-more-types/typeddict/demo_books.py new file mode 100644 index 0000000..5203acb --- /dev/null +++ b/15-more-types/typeddict/demo_books.py @@ -0,0 +1,20 @@ +from books import BookDict +from typing import TYPE_CHECKING + +def demo() -> None: # <1> + book = BookDict( # <2> + isbn='0134757599', + title='Refactoring, 2e', + authors=['Martin Fowler', 'Kent Beck'], + pagecount=478 + ) + authors = book['authors'] # <3> + if TYPE_CHECKING: # <4> + reveal_type(authors) # <5> + authors = 'Bob' # <6> + book['weight'] = 4.2 + del book['title'] + + +if __name__ == '__main__': + demo() diff --git a/15-more-types/typeddict/demo_not_book.py b/15-more-types/typeddict/demo_not_book.py new file mode 100644 index 0000000..7bf0711 --- /dev/null +++ b/15-more-types/typeddict/demo_not_book.py @@ -0,0 +1,23 @@ +from books import to_xml, from_json +from typing import TYPE_CHECKING + +def demo() -> None: + NOT_BOOK_JSON = """ + {"title": "Andromeda Strain", + "flavor": "pistachio", + "authors": true} + """ + not_book = from_json(NOT_BOOK_JSON) # <1> + if TYPE_CHECKING: # <2> + reveal_type(not_book) + reveal_type(not_book['authors']) + + print(not_book) # <3> + print(not_book['flavor']) # <4> + + xml = to_xml(not_book) # <5> + print(xml) # <6> + + +if __name__ == '__main__': + demo() diff --git a/15-more-types/typeddict/test_books.py b/15-more-types/typeddict/test_books.py new file mode 100644 index 0000000..fc9d245 --- /dev/null +++ b/15-more-types/typeddict/test_books.py @@ -0,0 +1,112 @@ +import json +from typing import cast + +from books import BookDict, to_xml, from_json + +XML_SAMPLE = """ + +\t0134757599 +\tRefactoring, 2e +\tMartin Fowler +\tKent Beck +\t478 + +""".strip() + + +# using plain dicts + +def test_1() -> None: + xml = to_xml({ + 'isbn': '0134757599', + 'title': 'Refactoring, 2e', + 'authors': ['Martin Fowler', 'Kent Beck'], + 'pagecount': 478, + }) + assert xml == XML_SAMPLE + +def test_2() -> None: + xml = to_xml(dict( + isbn='0134757599', + title='Refactoring, 2e', + authors=['Martin Fowler', 'Kent Beck'], + pagecount=478)) + assert xml == XML_SAMPLE + +def test_5() -> None: + book_data: BookDict = dict( + isbn='0134757599', + title='Refactoring, 2e', + authors=['Martin Fowler', 'Kent Beck'], + pagecount=478 + ) + xml = to_xml(book_data) + assert xml == XML_SAMPLE + +def test_6() -> None: + book_data = dict( + isbn='0134757599', + title='Refactoring, 2e', + authors=['Martin Fowler', 'Kent Beck'], + pagecount=478 + ) + xml = to_xml(cast(BookDict, book_data)) # cast needed + assert xml == XML_SAMPLE + +def test_4() -> None: + xml = to_xml(BookDict( + isbn='0134757599', + title='Refactoring, 2e', + authors=['Martin Fowler', 'Kent Beck'], + pagecount=478)) + assert xml == XML_SAMPLE + +def test_7() -> None: + book_data = BookDict( + isbn='0134757599', + title='Refactoring, 2e', + authors=['Martin Fowler', 'Kent Beck'], + pagecount=478 + ) + xml = to_xml(book_data) + assert xml == XML_SAMPLE + +def test_8() -> None: + book_data: BookDict = { + 'isbn': '0134757599', + 'title': 'Refactoring, 2e', + 'authors': ['Martin Fowler', 'Kent Beck'], + 'pagecount': 478, + } + xml = to_xml(book_data) + assert xml == XML_SAMPLE + +BOOK_JSON = """ + {"isbn": "0134757599", + "title": "Refactoring, 2e", + "authors": ["Martin Fowler", "Kent Beck"], + "pagecount": 478} +""" + +def test_load_book_0() -> None: + book_data: BookDict = json.loads(BOOK_JSON) # typed var + xml = to_xml(book_data) + assert xml == XML_SAMPLE + +def test_load_book() -> None: + book_data = from_json(BOOK_JSON) + xml = to_xml(book_data) + assert xml == XML_SAMPLE + + +NOT_BOOK_JSON = """ + {"isbn": 3.141592653589793 + "title": [1, 2, 3], + "authors": ["Martin Fowler", "Kent Beck"], + "flavor": "strawberry"} +""" + +def test_load_not_book() -> None: + book_data: BookDict = json.loads(BOOK_JSON) # typed var + xml = to_xml(book_data) + assert xml == XML_SAMPLE diff --git a/15-more-types/typeddict/test_books_check_fails.py b/15-more-types/typeddict/test_books_check_fails.py new file mode 100644 index 0000000..6166f97 --- /dev/null +++ b/15-more-types/typeddict/test_books_check_fails.py @@ -0,0 +1,20 @@ +from books import BookDict, to_xml + +XML_SAMPLE = """ + +\t0134757599 +\tRefactoring, 2e +\tMartin Fowler +\tKent Beck +\t478 + +""".strip() + +def test_3() -> None: + xml = to_xml(BookDict(dict([ # Expected keyword arguments, {...}, or dict(...) in TypedDict constructor + ('isbn', '0134757599'), + ('title', 'Refactoring, 2e'), + ('authors', ['Martin Fowler', 'Kent Beck']), + ('pagecount', 478), + ]))) + assert xml == XML_SAMPLE diff --git a/README.md b/README.md index 56e7420..d0b57b3 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Fluent Python 2e example code -Example code for the book **Fluent Python, 2nd edition** by Luciano Ramalho (O'Reilly, 2021). +Example code for the book **Fluent Python, 2nd edition** by Luciano Ramalho (O'Reilly, 2020). > **BEWARE**: This is a work in progress! > @@ -14,40 +14,40 @@ Example code for the book **Fluent Python, 2nd edition** by Luciano R All chapters are undergoing review and updates, including significant rewrites in the chapters about concurrency in **Part V**. -New chapters in **Fluent Python 2nd edition** are marked with 🆕. +New chapters in **Fluent Python 2e** are marked with 🆕. -🚨 This table of contents is subject to change at any time until the book goes to the printer. +🚨 This table of contents is subject to change at any time until the book goes to the printer. -Part / Chapter #|Title|Directory|1st edition Chapter # ----:|---|---|:---: +Part / Chapter #|Title|Directory|Notebook|1st ed. Chapter # +---:|---|---|---|:---: **I – Prologue**| -1|The Python Data Model|[01-data-model](01-data-model)|1 +1|The Python Data Model|[01-data-model](01-data-model)|[data-model.ipynb](01-data-model/data-model.ipynb)|1 **II – Data Structures**| -2|An Array of Sequences|[02-array-seq](02-array-seq)|2 -3|Dictionaries and Sets|[03-dict-set](03-dict-set)|3 -4|Text versus Bytes|[04-text-byte](04-text-byte)|4 -5|Record-like Data Structures|[05-record-like](05-record-like)|🆕 -6|Object References, Mutability, and Recycling|[06-obj-ref](06-obj-ref)|8 +2|An Array of Sequences|[02-array-seq](02-array-seq)|[array-seq.ipynb](02-array-seq/array-seq.ipynb)|2 +3|Dictionaries and Sets|[03-dict-set](03-dict-set)||3 +4|Text versus Bytes|[04-text-byte](04-text-byte)||4 +🆕 5|Record-like Data Structures|[05-record-like](05-record-like)||– +6|Object References, Mutability, and Recycling|[06-obj-ref](06-obj-ref)||8 **III – Functions as Objects**| -7|First-Class Funcions|[07-1class-func](07-1class-func)|5 -8|Type Hints in Function Definitions|[08-def-type-hints](08-def-type-hints)|🆕 -9|Function Decorators and Closures|[09-closure-deco](09-closure-deco)|7 -10|Design Patterns with First-Class Functions|[10-dp-1class-func](10-dp-1class-func)|6 +7|First-Class Funcions|[07-1class-func](07-1class-func)||5 +🆕 8|Type Hints in Function Definitions|[08-def-type-hints](08-def-type-hints)||– +9|Function Decorators and Closures|[09-closure-deco](09-closure-deco)||7 +10|Design Patterns with First-Class Functions|[10-dp-1class-func](10-dp-1class-func)||6 **IV – Object-Oriented Idioms**| -11|A Pythonic Object|[11-pythonic-obj](11-pythonic-obj)|9 -12|Sequence Hacking, Hashing, and Slicing|[12-seq-hacking](12-seq-hacking)|10 -13|Interfaces, Protocols, and ABCs|[13-protocl-abc](13-protocol-abc)|11 -14|Inheritance: For Good or For Worse|[14-inheritance](14-inheritance)|12 -15|More About Type Hints|15-more-typing|🆕 -16|Operator Overloading: Doing It Right|[16-op-overloading](16-op-overloading)|13 +11|A Pythonic Object|[11-pythonic-obj](11-pythonic-obj)||9 +12|Sequence Hacking, Hashing, and Slicing|[12-seq-hacking](12-seq-hacking)||10 +13|Interfaces, Protocols, and ABCs|[13-protocl-abc](13-protocol-abc)||11 +14|Inheritance: For Good or For Worse|[14-inheritance](14-inheritance)||12 +🆕 15|More About Type Hints|[15-more-types](15-more-types)||– +16|Operator Overloading: Doing It Right|[16-op-overloading](16-op-overloading)||13 **V – Control Flow**| -17|Iterables, Iterators, and Generators|[17-it-generator](17-it-generator)|14 -18|Context Managers and else Blocks|[18-context-mngr](18-context-mngr)|15 -19|Classic Coroutines|[19-coroutine](19-coroutine)|16 -20|Concurrency Models in Python|[20-concurrency](20-concurrency)|🆕 -21|Concurrency with Futures|[21-futures](21-futures)|17 -22|Asynchronous Programming|[22-async](22-async)|18 +17|Iterables, Iterators, and Generators|[17-it-generator](17-it-generator)||14 +18|Context Managers and else Blocks|[18-context-mngr](18-context-mngr)||15 +19|Classic Coroutines|[19-coroutine](19-coroutine)||16 +🆕 20|Concurrency Models in Python|[20-concurrency](20-concurrency)||- +21|Concurrency with Futures|[21-futures](21-futures)||17 +22|Asynchronous Programming|[22-async](22-async)||18 **VI – Metaprogramming**| -23|Dynamic Attributes and Properties|[23-dyn-attr-prop](23-dyn-attr-prop)|19 -24|Attribute Descriptors|[24-descriptor](24-descriptor)|20 -25|Class Metaprogramming|[25-class-metaprog](25-class-metaprog)|21 +23|Dynamic Attributes and Properties|[22-dyn-attr-prop](22-dyn-attr-prop)||19 +24|Attribute Descriptors|[23-descriptor](23-descriptor)||20 +25|Class Metaprogramming|[24-class-metaprog](24-class-metaprog)||21 From 14e1758bf7d2da85061fe71c8175f9ba5b287a1e Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Thu, 20 May 2021 23:01:56 -0300 Subject: [PATCH 051/166] Update pydantic version (low risc vulnerability) --- 22-async/mojifinder/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/22-async/mojifinder/requirements.txt b/22-async/mojifinder/requirements.txt index 4d97ea9..04ef58d 100644 --- a/22-async/mojifinder/requirements.txt +++ b/22-async/mojifinder/requirements.txt @@ -1,7 +1,7 @@ click==7.1.2 fastapi==0.63.0 h11==0.12.0 -pydantic==1.8.1 +pydantic==1.8.2 starlette==0.13.6 typing-extensions==3.7.4.3 uvicorn==0.13.4 From eb8982f9249843d8d1c942b9fb5bfb10743697da Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Thu, 20 May 2021 23:13:16 -0300 Subject: [PATCH 052/166] added credits to petbox examples --- 15-more-types/petbox/petbox.py | 5 +++++ 15-more-types/petbox/petbox_demo.py | 7 ++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/15-more-types/petbox/petbox.py b/15-more-types/petbox/petbox.py index 04b72ea..5b963f3 100644 --- a/15-more-types/petbox/petbox.py +++ b/15-more-types/petbox/petbox.py @@ -1,3 +1,8 @@ +""" +Example adapted from `Atomic Kotlin` by Bruce Eckel & Svetlana Isakova, +chapter `Creating Generics`, section `Variance`. +""" + from typing import TypeVar, Generic, Any diff --git a/15-more-types/petbox/petbox_demo.py b/15-more-types/petbox/petbox_demo.py index d09cab4..a7e8bab 100644 --- a/15-more-types/petbox/petbox_demo.py +++ b/15-more-types/petbox/petbox_demo.py @@ -1,3 +1,8 @@ +""" +Example adapted from `Atomic Kotlin` by Bruce Eckel & Svetlana Isakova, +chapter `Creating Generics`, section `Variance`. +""" + from typing import TYPE_CHECKING from petbox import * @@ -11,7 +16,7 @@ animal = cat_box.get() -#if TYPE_CHECKING: +# if TYPE_CHECKING: # reveal_type(animal) # Revealed: petbox.Cat* From fc81928f36416a21a544a793189e2879288fa4c5 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Thu, 20 May 2021 23:21:26 -0300 Subject: [PATCH 053/166] change order of experiments --- 15-more-types/petbox/petbox_demo.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/15-more-types/petbox/petbox_demo.py b/15-more-types/petbox/petbox_demo.py index a7e8bab..e73f2b7 100644 --- a/15-more-types/petbox/petbox_demo.py +++ b/15-more-types/petbox/petbox_demo.py @@ -26,22 +26,22 @@ out_box_si: OutBox[Siamese] = OutBox(Siamese()) +out_box = out_box_si + ## Incompatible types in assignment ## expression has type "OutBox[Cat]" -# variable has type "OutBox[Siamese]" +## variable has type "OutBox[Siamese]" # out_box_si = out_box -out_box = out_box_si - ################### Contravariance in_box: InBox[Cat] = InBox() in_box_si: InBox[Siamese] = InBox() -in_box_si = in_box - ## Incompatible types in assignment ## expression has type "InBox[Siamese]" ## variable has type "InBox[Cat]" # in_box = in_box_si + +in_box_si = in_box From ff5fdd8f7c22c95a4fe79731ce5146c7e8af98ca Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Fri, 21 May 2021 00:26:43 -0300 Subject: [PATCH 054/166] added cafeteria example --- 15-more-types/cafeteria.py | 82 +++++++++++++++++++++++ 15-more-types/cafeteria/cafeteria.py | 41 ------------ 15-more-types/cafeteria/cafeteria_demo.py | 23 ------- 15-more-types/petbox/petbox.py | 4 -- 4 files changed, 82 insertions(+), 68 deletions(-) create mode 100644 15-more-types/cafeteria.py delete mode 100644 15-more-types/cafeteria/cafeteria.py delete mode 100644 15-more-types/cafeteria/cafeteria_demo.py diff --git a/15-more-types/cafeteria.py b/15-more-types/cafeteria.py new file mode 100644 index 0000000..4a9fad9 --- /dev/null +++ b/15-more-types/cafeteria.py @@ -0,0 +1,82 @@ +from typing import TypeVar, Generic + + +class Beverage: + """Any beverage""" + + +class Juice(Beverage): + """Any fruit juice""" + + +class OrangeJuice(Juice): + """Delicious juice from Brazilian oranges""" + + +BeverageT = TypeVar('BeverageT', covariant=True) + + +class BeverageDispenser(Generic[BeverageT]): + def __init__(self, beverage: BeverageT) -> None: + self.beverage = beverage + + def dispense(self) -> BeverageT: + return self.beverage + + +class Garbage: + """Any garbage.""" + + +class Biodegradable(Garbage): + """Biodegradable garbage.""" + + +class Compostable(Biodegradable): + """Compostable garbage.""" + + +GarbageT = TypeVar('GarbageT', contravariant=True) + + +class TrashCan(Generic[GarbageT]): + def put(self, trash) -> None: + """Store trash until dumped...""" + + +class Cafeteria: + def __init__( + self, + dispenser: BeverageDispenser[Juice], + trash_can: TrashCan[Biodegradable] + ): + """Initialize...""" + + +beverage_dispenser = BeverageDispenser(Beverage()) +juice_dispenser = BeverageDispenser(Juice()) +orange_juice_dispenser = BeverageDispenser(OrangeJuice()) + +trash_can: TrashCan[Garbage] = TrashCan() +bio_can: TrashCan[Biodegradable] = TrashCan() +compost_can: TrashCan[Compostable] = TrashCan() + +arnold_hall = Cafeteria(juice_dispenser, bio_can) + +######################## covariance on 1st argument +arnold_hall = Cafeteria(orange_juice_dispenser, trash_can) + +## Argument 1 to "Cafeteria" has +## incompatible type "BeverageDispenser[Beverage]" +## expected "BeverageDispenser[Juice]" +# arnold_hall = Cafeteria(beverage_dispenser, trash_can) + + +######################## contravariance on 2nd argument + +## Argument 2 to "Cafeteria" has +## incompatible type "TrashCan[Compostable]" +## expected "TrashCan[Biodegradable]" +# arnold_hall = Cafeteria(juice_dispenser, compost_can) + +arnold_hall = Cafeteria(juice_dispenser, trash_can) diff --git a/15-more-types/cafeteria/cafeteria.py b/15-more-types/cafeteria/cafeteria.py deleted file mode 100644 index 8c88a92..0000000 --- a/15-more-types/cafeteria/cafeteria.py +++ /dev/null @@ -1,41 +0,0 @@ -from typing import TypeVar, Generic - - -class Beverage: - """Any beverage""" - - -class Juice(Beverage): - """Any fruit juice""" - - -class OrangeJuice(Juice): - """Delicious juice Brazilian oranges""" - - -class Coak(Beverage): - """Secret formula with lots of sugar""" - - -BeverageT = TypeVar('BeverageT', bound=Beverage) -JuiceT = TypeVar('JuiceT', bound=Juice) - - -class BeverageDispenser(Generic[BeverageT]): - - beverage: BeverageT - - def __init__(self, beverage: BeverageT) -> None: - self.beverage = beverage - - def dispense(self) -> BeverageT: - return self.beverage - - -class JuiceDispenser(BeverageDispenser[JuiceT]): - pass - - -class Cafeteria: - def __init__(self, dispenser: BeverageDispenser[JuiceT]): - self.dispenser = dispenser diff --git a/15-more-types/cafeteria/cafeteria_demo.py b/15-more-types/cafeteria/cafeteria_demo.py deleted file mode 100644 index d93636b..0000000 --- a/15-more-types/cafeteria/cafeteria_demo.py +++ /dev/null @@ -1,23 +0,0 @@ -from cafeteria import ( - Cafeteria, - BeverageDispenser, - JuiceDispenser, - Juice, - OrangeJuice, - Coak, -) - -orange = OrangeJuice() - -orange_dispenser: JuiceDispenser[OrangeJuice] = JuiceDispenser(orange) - -juice: Juice = orange_dispenser.dispense() - -soda = Coak() - -## Value of type variable "JuiceT" of "JuiceDispenser" cannot be "Coak" -# soda_dispenser = JuiceDispenser(soda) - -soda_dispenser = BeverageDispenser(soda) - -arnold_hall = Cafeteria(soda_dispenser) diff --git a/15-more-types/petbox/petbox.py b/15-more-types/petbox/petbox.py index 5b963f3..a344a96 100644 --- a/15-more-types/petbox/petbox.py +++ b/15-more-types/petbox/petbox.py @@ -10,10 +10,6 @@ class Pet: """Domestic animal kept for companionship.""" -class Dog(Pet): - """Canis familiaris""" - - class Cat(Pet): """Felis catus""" From 8a330d822b997c7992d0b7675c82ad75832300c6 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Fri, 21 May 2021 18:56:12 -0300 Subject: [PATCH 055/166] updade from Atlas repo --- 03-dict-set/dialcodes.py | 2 +- 03-dict-set/index.py | 6 +- 03-dict-set/index0.py | 4 +- 03-dict-set/index_default.py | 8 +- 04-text-byte/categories.py | 6 +- 04-text-byte/default_encodings.py | 3 +- 04-text-byte/zwj_sample.py | 6 +- 05-record-like/dataclass/coordinates.py | 3 +- 08-def-type-hints/colors.py | 1 - 08-def-type-hints/columnize2.py | 2 - 08-def-type-hints/columnize_alias.py | 1 - 08-def-type-hints/comparable/mymax_test.py | 2 +- 08-def-type-hints/double/double_test.py | 2 +- 08-def-type-hints/mode/mode_T.py | 3 +- 08-def-type-hints/passdrill.py | 9 +- 08-def-type-hints/replacer2.py | 2 +- 08-def-type-hints/romans.py | 4 +- 08-def-type-hints/typeddict/books.py | 32 -- 08-def-type-hints/typeddict/books_any.py | 32 -- 08-def-type-hints/typeddict/demo_books.py | 20 - 08-def-type-hints/typeddict/demo_not_book.py | 23 - 08-def-type-hints/typeddict/test_books.py | 112 ----- .../typeddict/test_books_check_fails.py | 20 - 09-closure-deco/clockdeco_cls.py | 1 - 09-closure-deco/registration.py | 4 +- .../monkeytype/classic_strategy_test.py | 3 +- 10-dp-1class-func/untyped/strategy_best3.py | 1 - 12-seq-hacking/vector_v1.py | 4 +- 12-seq-hacking/vector_v2.py | 4 +- 12-seq-hacking/vector_v3.py | 10 +- 12-seq-hacking/vector_v4.py | 8 +- 12-seq-hacking/vector_v5.py | 10 +- 13-protocol-abc/double/double_test.py | 2 +- 13-protocol-abc/tombola.py | 1 - 13-protocol-abc/typing/randompick_test.py | 2 +- 13-protocol-abc/typing/randompickload.py | 2 +- 13-protocol-abc/typing/randompickload_test.py | 6 +- 15-more-types/cafeteria.py | 57 ++- 15-more-types/petbox/petbox.py | 16 +- 16-op-overloading/bingoaddable.py | 21 +- 16-op-overloading/tombola.py | 3 +- 16-op-overloading/vector_py3_5.py | 431 ------------------ 16-op-overloading/vector_v6.py | 10 +- 16-op-overloading/vector_v7.py | 10 +- 16-op-overloading/vector_v8.py | 10 +- 17-it-generator/aritprog_float_error.py | 2 +- 17-it-generator/columnize_iter.py | 2 +- 17-it-generator/fibo_by_hand.py | 3 +- 17-it-generator/sentence_gen2.py | 4 +- 17-it-generator/sentence_genexp.py | 6 +- 17-it-generator/sentence_iter.py | 6 +- 17-it-generator/sentence_iter2.py | 4 +- 17-it-generator/tree/extra/pretty_tree.py | 8 +- .../tree/extra/test_pretty_tree.py | 11 +- 17-it-generator/tree/extra/test_tree.py | 12 +- 17-it-generator/tree/extra/tree.py | 2 +- 17-it-generator/tree/step0/test_tree.py | 2 +- 17-it-generator/tree/step0/tree.py | 2 +- 17-it-generator/tree/step1/test_tree.py | 4 +- 17-it-generator/tree/step2/test_tree.py | 4 +- 17-it-generator/tree/step3/test_tree.py | 6 +- 17-it-generator/tree/step4/test_tree.py | 11 +- 17-it-generator/tree/step5/test_tree.py | 13 +- 17-it-generator/tree/step6/test_tree.py | 13 +- 19-coroutine/coroutil.py | 4 +- 19-coroutine/taxi_sim.py | 12 +- 19-coroutine/taxi_sim0.py | 10 +- 21-futures/getflags/requirements.txt | 2 +- 22-async/domains/curio/domainlib.py | 7 +- 23-dyn-attr-prop/oscon/schedule_v2.py | 2 +- 23-dyn-attr-prop/oscon/schedule_v3.py | 2 +- 23-dyn-attr-prop/oscon/schedule_v4_hasattr.py | 2 +- 24-descriptor/bulkfood/bulkfood_v4.py | 2 +- 24-descriptor/bulkfood/model_v5.py | 2 +- 24-descriptor/descriptorkinds.py | 10 +- 25-class-metaprog/autoconst/autoconst.py | 22 + 25-class-metaprog/autoconst/autoconst_demo.py | 55 +++ 25-class-metaprog/bulkfood/README.md | 34 ++ 25-class-metaprog/bulkfood/bulkfood_v6.py | 84 ++++ 25-class-metaprog/bulkfood/bulkfood_v7.py | 79 ++++ 25-class-metaprog/bulkfood/bulkfood_v8.py | 86 ++++ 25-class-metaprog/bulkfood/model_v6.py | 60 +++ 25-class-metaprog/bulkfood/model_v7.py | 66 +++ 25-class-metaprog/bulkfood/model_v8.py | 80 ++++ .../decorator}/checkeddeco.py | 119 ++--- .../checked/decorator/checkeddeco_demo.py | 26 ++ .../decorator}/checkeddeco_test.py | 10 + .../checked_demo.py} | 8 +- .../checked/{ => initsub}/checkedlib.py | 68 ++- .../checked/initsub/checkedlib_test.py | 59 +++ .../metaclass/checked_demo.py} | 19 +- .../checked/metaclass/checkedlib.py | 148 ++++++ .../{ => metaclass}/checkedlib_test.py | 23 +- 25-class-metaprog/evalsupport.py | 29 -- 25-class-metaprog/evaltime.py | 49 -- 25-class-metaprog/evaltime/builderlib.py | 50 ++ 25-class-metaprog/evaltime/evaldemo.py | 30 ++ 25-class-metaprog/evaltime/evaldemo_meta.py | 33 ++ 25-class-metaprog/evaltime/metalib.py | 43 ++ 25-class-metaprog/evaltime_meta.py | 53 --- 25-class-metaprog/factories.py | 53 ++- 25-class-metaprog/factories_ducktyped.py | 79 ++++ 25-class-metaprog/hours/hours.py | 115 +++++ 25-class-metaprog/hours/hours_test.py | 71 +++ 25-class-metaprog/metabunch/README.md | 20 + 25-class-metaprog/metabunch/from3.6/bunch.py | 77 ++++ .../metabunch/from3.6/bunch_test.py | 59 +++ .../metabunch/nutshell3e/bunch.py | 85 ++++ .../metabunch/nutshell3e/bunch_test.py | 38 ++ 25-class-metaprog/metabunch/original/bunch.py | 70 +++ .../metabunch/original/bunch_test.py | 38 ++ 25-class-metaprog/metabunch/pre3.6/bunch.py | 41 ++ .../metabunch/pre3.6/bunch_test.py | 38 ++ 25-class-metaprog/persistent/persistlib.py | 59 +-- 25-class-metaprog/qualname/fakedjango.py | 5 + 25-class-metaprog/qualname/models.py | 13 + 25-class-metaprog/setattr/example_from_leo.py | 19 + 25-class-metaprog/slots/slots_timing.py | 48 ++ 25-class-metaprog/tinyenums/microenum.py | 16 +- pytest.ini | 2 + 120 files changed, 2187 insertions(+), 1181 deletions(-) delete mode 100644 08-def-type-hints/typeddict/books.py delete mode 100644 08-def-type-hints/typeddict/books_any.py delete mode 100644 08-def-type-hints/typeddict/demo_books.py delete mode 100644 08-def-type-hints/typeddict/demo_not_book.py delete mode 100644 08-def-type-hints/typeddict/test_books.py delete mode 100644 08-def-type-hints/typeddict/test_books_check_fails.py delete mode 100644 16-op-overloading/vector_py3_5.py create mode 100644 25-class-metaprog/autoconst/autoconst.py create mode 100755 25-class-metaprog/autoconst/autoconst_demo.py create mode 100644 25-class-metaprog/bulkfood/README.md create mode 100644 25-class-metaprog/bulkfood/bulkfood_v6.py create mode 100644 25-class-metaprog/bulkfood/bulkfood_v7.py create mode 100644 25-class-metaprog/bulkfood/bulkfood_v8.py create mode 100644 25-class-metaprog/bulkfood/model_v6.py create mode 100644 25-class-metaprog/bulkfood/model_v7.py create mode 100644 25-class-metaprog/bulkfood/model_v8.py rename 25-class-metaprog/{checkeddeco => checked/decorator}/checkeddeco.py (52%) create mode 100755 25-class-metaprog/checked/decorator/checkeddeco_demo.py rename 25-class-metaprog/{checkeddeco => checked/decorator}/checkeddeco_test.py (79%) rename 25-class-metaprog/checked/{checkedlib_demo.py => initsub/checked_demo.py} (65%) mode change 100644 => 100755 rename 25-class-metaprog/checked/{ => initsub}/checkedlib.py (66%) create mode 100644 25-class-metaprog/checked/initsub/checkedlib_test.py rename 25-class-metaprog/{checkeddeco/checkeddeco_demo.py => checked/metaclass/checked_demo.py} (52%) mode change 100644 => 100755 create mode 100644 25-class-metaprog/checked/metaclass/checkedlib.py rename 25-class-metaprog/checked/{ => metaclass}/checkedlib_test.py (58%) delete mode 100644 25-class-metaprog/evalsupport.py delete mode 100644 25-class-metaprog/evaltime.py create mode 100644 25-class-metaprog/evaltime/builderlib.py create mode 100755 25-class-metaprog/evaltime/evaldemo.py create mode 100755 25-class-metaprog/evaltime/evaldemo_meta.py create mode 100644 25-class-metaprog/evaltime/metalib.py delete mode 100644 25-class-metaprog/evaltime_meta.py create mode 100644 25-class-metaprog/factories_ducktyped.py create mode 100644 25-class-metaprog/hours/hours.py create mode 100644 25-class-metaprog/hours/hours_test.py create mode 100644 25-class-metaprog/metabunch/README.md create mode 100644 25-class-metaprog/metabunch/from3.6/bunch.py create mode 100644 25-class-metaprog/metabunch/from3.6/bunch_test.py create mode 100644 25-class-metaprog/metabunch/nutshell3e/bunch.py create mode 100644 25-class-metaprog/metabunch/nutshell3e/bunch_test.py create mode 100644 25-class-metaprog/metabunch/original/bunch.py create mode 100644 25-class-metaprog/metabunch/original/bunch_test.py create mode 100644 25-class-metaprog/metabunch/pre3.6/bunch.py create mode 100644 25-class-metaprog/metabunch/pre3.6/bunch_test.py create mode 100644 25-class-metaprog/qualname/fakedjango.py create mode 100755 25-class-metaprog/qualname/models.py create mode 100755 25-class-metaprog/setattr/example_from_leo.py create mode 100755 25-class-metaprog/slots/slots_timing.py create mode 100644 pytest.ini diff --git a/03-dict-set/dialcodes.py b/03-dict-set/dialcodes.py index 07b06a7..7130e08 100644 --- a/03-dict-set/dialcodes.py +++ b/03-dict-set/dialcodes.py @@ -17,7 +17,7 @@ print('d1:', d1.keys()) d2 = dict(sorted(DIAL_CODES)) # <2> print('d2:', d2.keys()) -d3 = dict(sorted(DIAL_CODES, key=lambda x:x[1])) # <3> +d3 = dict(sorted(DIAL_CODES, key=lambda x: x[1])) # <3> print('d3:', d3.keys()) assert d1 == d2 and d2 == d3 # <4> # end::DIALCODES[] diff --git a/03-dict-set/index.py b/03-dict-set/index.py index 5da48b1..116a0f8 100644 --- a/03-dict-set/index.py +++ b/03-dict-set/index.py @@ -5,8 +5,8 @@ # tag::INDEX[] """Build an index mapping word -> list of occurrences""" -import sys import re +import sys WORD_RE = re.compile(r'\w+') @@ -15,11 +15,11 @@ for line_no, line in enumerate(fp, 1): for match in WORD_RE.finditer(line): word = match.group() - column_no = match.start()+1 + column_no = match.start() + 1 location = (line_no, column_no) index.setdefault(word, []).append(location) # <1> -# print in alphabetical order +# display in alphabetical order for word in sorted(index, key=str.upper): print(word, index[word]) # end::INDEX[] diff --git a/03-dict-set/index0.py b/03-dict-set/index0.py index 0bc9bf4..bd69ab2 100644 --- a/03-dict-set/index0.py +++ b/03-dict-set/index0.py @@ -5,8 +5,8 @@ # tag::INDEX0[] """Build an index mapping word -> list of occurrences""" -import sys import re +import sys WORD_RE = re.compile(r'\w+') @@ -22,7 +22,7 @@ occurrences.append(location) # <2> index[word] = occurrences # <3> -# print in alphabetical order +# display in alphabetical order for word in sorted(index, key=str.upper): # <4> print(word, index[word]) # end::INDEX0[] diff --git a/03-dict-set/index_default.py b/03-dict-set/index_default.py index 60279a9..d4203d2 100644 --- a/03-dict-set/index_default.py +++ b/03-dict-set/index_default.py @@ -5,9 +5,9 @@ # tag::INDEX_DEFAULT[] """Build an index mapping word -> list of occurrences""" -import sys -import re import collections +import re +import sys WORD_RE = re.compile(r'\w+') @@ -16,11 +16,11 @@ for line_no, line in enumerate(fp, 1): for match in WORD_RE.finditer(line): word = match.group() - column_no = match.start()+1 + column_no = match.start() + 1 location = (line_no, column_no) index[word].append(location) # <2> -# print in alphabetical order +# display in alphabetical order for word in sorted(index, key=str.upper): print(word, index[word]) # end::INDEX_DEFAULT[] diff --git a/04-text-byte/categories.py b/04-text-byte/categories.py index a4cecbe..11cc28e 100644 --- a/04-text-byte/categories.py +++ b/04-text-byte/categories.py @@ -1,6 +1,6 @@ import sys import collections -from unicodedata import name, category +from unicodedata import category def category_stats(): @@ -19,7 +19,7 @@ def category_scan(desired): for code in range(sys.maxunicode + 1): char = chr(code) if category(char) == desired: - yield char + yield char def main(args): @@ -30,7 +30,7 @@ def main(args): count += 1 if count > 200: break - print() + print() print(count, 'characters shown') else: counts, firsts = category_stats() diff --git a/04-text-byte/default_encodings.py b/04-text-byte/default_encodings.py index c230dea..4ff70a6 100644 --- a/04-text-byte/default_encodings.py +++ b/04-text-byte/default_encodings.py @@ -1,4 +1,5 @@ -import sys, locale +import locale +import sys expressions = """ locale.getpreferredencoding() diff --git a/04-text-byte/zwj_sample.py b/04-text-byte/zwj_sample.py index cdd7a30..28893cc 100644 --- a/04-text-byte/zwj_sample.py +++ b/04-text-byte/zwj_sample.py @@ -10,9 +10,9 @@ 1F469 200D 2764 FE0F 200D 1F48B 200D 1F469 |kiss: woman, woman |E2.0 """ -markers = {'\u200D': 'ZWG', # ZERO WIDTH JOINER - '\uFE0F': 'V16', # VARIATION SELECTOR-16 - } +markers = {'\u200D': 'ZWG', # ZERO WIDTH JOINER + '\uFE0F': 'V16', # VARIATION SELECTOR-16 + } for line in zwg_sample.strip().split('\n'): code, descr, version = (s.strip() for s in line.split('|')) diff --git a/05-record-like/dataclass/coordinates.py b/05-record-like/dataclass/coordinates.py index 7bcd7cd..baacb6e 100644 --- a/05-record-like/dataclass/coordinates.py +++ b/05-record-like/dataclass/coordinates.py @@ -13,7 +13,6 @@ @dataclass(frozen=True) class Coordinate: - lat: float long: float @@ -21,4 +20,4 @@ def __str__(self): ns = 'N' if self.lat >= 0 else 'S' we = 'E' if self.long >= 0 else 'W' return f'{abs(self.lat):.1f}°{ns}, {abs(self.long):.1f}°{we}' -# end::COORDINATE[] \ No newline at end of file +# end::COORDINATE[] diff --git a/08-def-type-hints/colors.py b/08-def-type-hints/colors.py index 36518d6..b57413c 100644 --- a/08-def-type-hints/colors.py +++ b/08-def-type-hints/colors.py @@ -76,6 +76,5 @@ def demo(): print(n, name2hex(n, o)) - if __name__ == '__main__': demo() diff --git a/08-def-type-hints/columnize2.py b/08-def-type-hints/columnize2.py index 67b5fdd..2524fd3 100644 --- a/08-def-type-hints/columnize2.py +++ b/08-def-type-hints/columnize2.py @@ -10,7 +10,6 @@ def columnize(sequence: Sequence[T], num_columns: int = 0) -> List[Tuple[T, ...] return [tuple(sequence[i::num_rows]) for i in range(num_rows)] - def demo() -> None: nato = ('Alfa Bravo Charlie Delta Echo Foxtrot Golf Hotel India' ' Juliett Kilo Lima Mike November Oscar Papa Quebec Romeo' @@ -22,7 +21,6 @@ def demo() -> None: print(f'{word:15}', end='') print() - print() for length in range(2, 21, 6): values = list(range(1, length + 1)) diff --git a/08-def-type-hints/columnize_alias.py b/08-def-type-hints/columnize_alias.py index 95b85b3..b783469 100644 --- a/08-def-type-hints/columnize_alias.py +++ b/08-def-type-hints/columnize_alias.py @@ -10,7 +10,6 @@ def columnize(sequence: Sequence[str], num_columns: int) -> List[Row]: return [tuple(sequence[i::num_rows]) for i in range(num_rows)] - def demo() -> None: nato = ('Alfa Bravo Charlie Delta Echo Foxtrot Golf Hotel India' ' Juliett Kilo Lima Mike November Oscar Papa Quebec Romeo' diff --git a/08-def-type-hints/comparable/mymax_test.py b/08-def-type-hints/comparable/mymax_test.py index 3434364..a169232 100644 --- a/08-def-type-hints/comparable/mymax_test.py +++ b/08-def-type-hints/comparable/mymax_test.py @@ -1,4 +1,4 @@ -from typing import List, Callable, TypeVar +from typing import List, Callable import pytest # type: ignore diff --git a/08-def-type-hints/double/double_test.py b/08-def-type-hints/double/double_test.py index d37aba5..1871a1a 100644 --- a/08-def-type-hints/double/double_test.py +++ b/08-def-type-hints/double/double_test.py @@ -53,4 +53,4 @@ def test_double_nparray() -> None: def test_double_none() -> None: given = None with pytest.raises(TypeError): - result = double(given) + double(given) diff --git a/08-def-type-hints/mode/mode_T.py b/08-def-type-hints/mode/mode_T.py index ca26d7b..cf88bbb 100644 --- a/08-def-type-hints/mode/mode_T.py +++ b/08-def-type-hints/mode/mode_T.py @@ -12,7 +12,7 @@ def mode(data: Iterable[T]) -> T: def demo() -> None: - from typing import List, Set, TYPE_CHECKING + from typing import TYPE_CHECKING pop: list[set] = [set(), set()] m = mode(pop) if TYPE_CHECKING: @@ -21,5 +21,6 @@ def demo() -> None: print(pop) print(repr(m), type(m)) + if __name__ == '__main__': demo() diff --git a/08-def-type-hints/passdrill.py b/08-def-type-hints/passdrill.py index 08d1ff0..83b1be1 100755 --- a/08-def-type-hints/passdrill.py +++ b/08-def-type-hints/passdrill.py @@ -3,12 +3,11 @@ """passdrill: typing drills for practicing passphrases """ -import sys import os +import sys +from base64 import b64encode, b64decode from getpass import getpass from hashlib import scrypt -from base64 import b64encode, b64decode - from typing import Sequence, Tuple HASH_FILENAME = 'passdrill.hash' @@ -20,7 +19,7 @@ def prompt() -> str: confirmed = '' while confirmed != 'y': passphrase = input('Type passphrase to hash (it will be echoed): ') - if passphrase == '' or passphrase == 'q': + if passphrase in ('', 'q'): print('ERROR: the passphrase cannot be empty or "q".') continue print(f'Passphrase to be hashed -> {passphrase}') @@ -45,7 +44,7 @@ def save_hash() -> None: salted_hash = build_hash(prompt()) with open(HASH_FILENAME, 'wb') as fp: fp.write(salted_hash) - print(f'Passphrase hash saved to', HASH_FILENAME) + print(f'Passphrase hash saved to {HASH_FILENAME}') def load_hash() -> Tuple[bytes, bytes]: diff --git a/08-def-type-hints/replacer2.py b/08-def-type-hints/replacer2.py index 786c8d7..33cdaeb 100644 --- a/08-def-type-hints/replacer2.py +++ b/08-def-type-hints/replacer2.py @@ -20,7 +20,7 @@ class FromTo(NamedTuple): to: str -def zip_replace(text: str, changes: Iterable[FromTo], count:int = -1) -> str: +def zip_replace(text: str, changes: Iterable[FromTo], count: int = -1) -> str: for from_, to in changes: text = text.replace(from_, to, count) return text diff --git a/08-def-type-hints/romans.py b/08-def-type-hints/romans.py index a8bd6b6..ab23957 100644 --- a/08-def-type-hints/romans.py +++ b/08-def-type-hints/romans.py @@ -1,6 +1,6 @@ values_map = [ - (1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1), - ( 'M', 'CM', 'D', 'CD', 'C', 'XC', 'L', 'XL', 'X', 'IX', 'V', 'IV','I') + (1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1), + ( 'M', 'CM', 'D', 'CD', 'C', 'XC', 'L', 'XL', 'X', 'IX', 'V', 'IV', 'I') ] def to_roman(arabic: int) -> str: diff --git a/08-def-type-hints/typeddict/books.py b/08-def-type-hints/typeddict/books.py deleted file mode 100644 index 5a0bc82..0000000 --- a/08-def-type-hints/typeddict/books.py +++ /dev/null @@ -1,32 +0,0 @@ -# tag::BOOKDICT[] -from typing import TypedDict, List -import json - -class BookDict(TypedDict): - isbn: str - title: str - authors: List[str] - pagecount: int -# end::BOOKDICT[] - -# tag::TOXML[] -AUTHOR_EL = '{}' - -def to_xml(book: BookDict) -> str: # <1> - elements: List[str] = [] # <2> - for key, value in book.items(): - if isinstance(value, list): # <3> - elements.extend( - AUTHOR_EL.format(n) for n in value) # <4> - else: - tag = key.upper() - elements.append(f'<{tag}>{value}') - xml = '\n\t'.join(elements) - return f'\n\t{xml}\n' -# end::TOXML[] - -# tag::FROMJSON[] -def from_json(data: str) -> BookDict: - whatever: BookDict = json.loads(data) # <1> - return whatever # <2> -# end::FROMJSON[] \ No newline at end of file diff --git a/08-def-type-hints/typeddict/books_any.py b/08-def-type-hints/typeddict/books_any.py deleted file mode 100644 index e696ff3..0000000 --- a/08-def-type-hints/typeddict/books_any.py +++ /dev/null @@ -1,32 +0,0 @@ -# tag::BOOKDICT[] -from typing import TypedDict, List -import json - -class BookDict(TypedDict): - isbn: str - title: str - authors: List[str] - pagecount: int -# end::BOOKDICT[] - -# tag::TOXML[] -AUTHOR_EL = '{}' - -def to_xml(book: BookDict) -> str: # <1> - elements: List[str] = [] # <2> - for key, value in book.items(): - if isinstance(value, list): # <3> - elements.extend(AUTHOR_EL.format(n) - for n in value) - else: - tag = key.upper() - elements.append(f'<{tag}>{value}') - xml = '\n\t'.join(elements) - return f'\n\t{xml}\n' -# end::TOXML[] - -# tag::FROMJSON[] -def from_json(data: str) -> BookDict: - whatever = json.loads(data) # <1> - return whatever # <2> -# end::FROMJSON[] \ No newline at end of file diff --git a/08-def-type-hints/typeddict/demo_books.py b/08-def-type-hints/typeddict/demo_books.py deleted file mode 100644 index 5203acb..0000000 --- a/08-def-type-hints/typeddict/demo_books.py +++ /dev/null @@ -1,20 +0,0 @@ -from books import BookDict -from typing import TYPE_CHECKING - -def demo() -> None: # <1> - book = BookDict( # <2> - isbn='0134757599', - title='Refactoring, 2e', - authors=['Martin Fowler', 'Kent Beck'], - pagecount=478 - ) - authors = book['authors'] # <3> - if TYPE_CHECKING: # <4> - reveal_type(authors) # <5> - authors = 'Bob' # <6> - book['weight'] = 4.2 - del book['title'] - - -if __name__ == '__main__': - demo() diff --git a/08-def-type-hints/typeddict/demo_not_book.py b/08-def-type-hints/typeddict/demo_not_book.py deleted file mode 100644 index 7bf0711..0000000 --- a/08-def-type-hints/typeddict/demo_not_book.py +++ /dev/null @@ -1,23 +0,0 @@ -from books import to_xml, from_json -from typing import TYPE_CHECKING - -def demo() -> None: - NOT_BOOK_JSON = """ - {"title": "Andromeda Strain", - "flavor": "pistachio", - "authors": true} - """ - not_book = from_json(NOT_BOOK_JSON) # <1> - if TYPE_CHECKING: # <2> - reveal_type(not_book) - reveal_type(not_book['authors']) - - print(not_book) # <3> - print(not_book['flavor']) # <4> - - xml = to_xml(not_book) # <5> - print(xml) # <6> - - -if __name__ == '__main__': - demo() diff --git a/08-def-type-hints/typeddict/test_books.py b/08-def-type-hints/typeddict/test_books.py deleted file mode 100644 index fc9d245..0000000 --- a/08-def-type-hints/typeddict/test_books.py +++ /dev/null @@ -1,112 +0,0 @@ -import json -from typing import cast - -from books import BookDict, to_xml, from_json - -XML_SAMPLE = """ - -\t0134757599 -\tRefactoring, 2e -\tMartin Fowler -\tKent Beck -\t478 - -""".strip() - - -# using plain dicts - -def test_1() -> None: - xml = to_xml({ - 'isbn': '0134757599', - 'title': 'Refactoring, 2e', - 'authors': ['Martin Fowler', 'Kent Beck'], - 'pagecount': 478, - }) - assert xml == XML_SAMPLE - -def test_2() -> None: - xml = to_xml(dict( - isbn='0134757599', - title='Refactoring, 2e', - authors=['Martin Fowler', 'Kent Beck'], - pagecount=478)) - assert xml == XML_SAMPLE - -def test_5() -> None: - book_data: BookDict = dict( - isbn='0134757599', - title='Refactoring, 2e', - authors=['Martin Fowler', 'Kent Beck'], - pagecount=478 - ) - xml = to_xml(book_data) - assert xml == XML_SAMPLE - -def test_6() -> None: - book_data = dict( - isbn='0134757599', - title='Refactoring, 2e', - authors=['Martin Fowler', 'Kent Beck'], - pagecount=478 - ) - xml = to_xml(cast(BookDict, book_data)) # cast needed - assert xml == XML_SAMPLE - -def test_4() -> None: - xml = to_xml(BookDict( - isbn='0134757599', - title='Refactoring, 2e', - authors=['Martin Fowler', 'Kent Beck'], - pagecount=478)) - assert xml == XML_SAMPLE - -def test_7() -> None: - book_data = BookDict( - isbn='0134757599', - title='Refactoring, 2e', - authors=['Martin Fowler', 'Kent Beck'], - pagecount=478 - ) - xml = to_xml(book_data) - assert xml == XML_SAMPLE - -def test_8() -> None: - book_data: BookDict = { - 'isbn': '0134757599', - 'title': 'Refactoring, 2e', - 'authors': ['Martin Fowler', 'Kent Beck'], - 'pagecount': 478, - } - xml = to_xml(book_data) - assert xml == XML_SAMPLE - -BOOK_JSON = """ - {"isbn": "0134757599", - "title": "Refactoring, 2e", - "authors": ["Martin Fowler", "Kent Beck"], - "pagecount": 478} -""" - -def test_load_book_0() -> None: - book_data: BookDict = json.loads(BOOK_JSON) # typed var - xml = to_xml(book_data) - assert xml == XML_SAMPLE - -def test_load_book() -> None: - book_data = from_json(BOOK_JSON) - xml = to_xml(book_data) - assert xml == XML_SAMPLE - - -NOT_BOOK_JSON = """ - {"isbn": 3.141592653589793 - "title": [1, 2, 3], - "authors": ["Martin Fowler", "Kent Beck"], - "flavor": "strawberry"} -""" - -def test_load_not_book() -> None: - book_data: BookDict = json.loads(BOOK_JSON) # typed var - xml = to_xml(book_data) - assert xml == XML_SAMPLE diff --git a/08-def-type-hints/typeddict/test_books_check_fails.py b/08-def-type-hints/typeddict/test_books_check_fails.py deleted file mode 100644 index 6166f97..0000000 --- a/08-def-type-hints/typeddict/test_books_check_fails.py +++ /dev/null @@ -1,20 +0,0 @@ -from books import BookDict, to_xml - -XML_SAMPLE = """ - -\t0134757599 -\tRefactoring, 2e -\tMartin Fowler -\tKent Beck -\t478 - -""".strip() - -def test_3() -> None: - xml = to_xml(BookDict(dict([ # Expected keyword arguments, {...}, or dict(...) in TypedDict constructor - ('isbn', '0134757599'), - ('title', 'Refactoring, 2e'), - ('authors', ['Martin Fowler', 'Kent Beck']), - ('pagecount', 478), - ]))) - assert xml == XML_SAMPLE diff --git a/09-closure-deco/clockdeco_cls.py b/09-closure-deco/clockdeco_cls.py index edb1ac4..4400006 100644 --- a/09-closure-deco/clockdeco_cls.py +++ b/09-closure-deco/clockdeco_cls.py @@ -41,4 +41,3 @@ def snooze(seconds): for i in range(3): snooze(.123) - diff --git a/09-closure-deco/registration.py b/09-closure-deco/registration.py index 855b6da..f1a8a0b 100644 --- a/09-closure-deco/registration.py +++ b/09-closure-deco/registration.py @@ -25,7 +25,7 @@ def main(): # <8> f2() f3() -if __name__=='__main__': +if __name__ == '__main__': main() # <9> -# end::REGISTRATION[] \ No newline at end of file +# end::REGISTRATION[] diff --git a/10-dp-1class-func/monkeytype/classic_strategy_test.py b/10-dp-1class-func/monkeytype/classic_strategy_test.py index b730962..6f62091 100644 --- a/10-dp-1class-func/monkeytype/classic_strategy_test.py +++ b/10-dp-1class-func/monkeytype/classic_strategy_test.py @@ -56,8 +56,7 @@ def test_large_order_promo_no_discount(customer_fidelity_0, cart_plain) -> None: def test_large_order_promo_with_discount(customer_fidelity_0) -> None: - cart = [LineItem(str(item_code), 1, 1.0) - for item_code in range(10)] + cart = [LineItem(str(item_code), 1, 1.0) for item_code in range(10)] order = Order(customer_fidelity_0, cart, LargeOrderPromo()) assert order.total() == 10.0 assert order.due() == 9.3 diff --git a/10-dp-1class-func/untyped/strategy_best3.py b/10-dp-1class-func/untyped/strategy_best3.py index de6ce4d..8d21ffc 100644 --- a/10-dp-1class-func/untyped/strategy_best3.py +++ b/10-dp-1class-func/untyped/strategy_best3.py @@ -89,4 +89,3 @@ def best_promo(order): # end::STRATEGY_BEST3[] - diff --git a/12-seq-hacking/vector_v1.py b/12-seq-hacking/vector_v1.py index 5f75c04..9d734b6 100644 --- a/12-seq-hacking/vector_v1.py +++ b/12-seq-hacking/vector_v1.py @@ -100,7 +100,7 @@ def __iter__(self): def __repr__(self): components = reprlib.repr(self._components) # <3> components = components[components.find('['):-1] # <4> - return 'Vector({})'.format(components) + return f'Vector({components})' def __str__(self): return str(tuple(self)) @@ -113,7 +113,7 @@ def __eq__(self, other): return tuple(self) == tuple(other) def __abs__(self): - return math.sqrt(sum(x * x for x in self)) # <6> + return math.hypot(*self) # <6> def __bool__(self): return bool(abs(self)) diff --git a/12-seq-hacking/vector_v2.py b/12-seq-hacking/vector_v2.py index 23d98ee..f25c243 100644 --- a/12-seq-hacking/vector_v2.py +++ b/12-seq-hacking/vector_v2.py @@ -127,7 +127,7 @@ def __iter__(self): def __repr__(self): components = reprlib.repr(self._components) components = components[components.find('['):-1] - return 'Vector({})'.format(components) + return f'Vector({components})' def __str__(self): return str(tuple(self)) @@ -140,7 +140,7 @@ def __eq__(self, other): return tuple(self) == tuple(other) def __abs__(self): - return math.sqrt(sum(x * x for x in self)) + return math.hypot(*self) def __bool__(self): return bool(abs(self)) diff --git a/12-seq-hacking/vector_v3.py b/12-seq-hacking/vector_v3.py index ea4ec52..6ee18b5 100644 --- a/12-seq-hacking/vector_v3.py +++ b/12-seq-hacking/vector_v3.py @@ -170,7 +170,7 @@ def __iter__(self): def __repr__(self): components = reprlib.repr(self._components) components = components[components.find('['):-1] - return 'Vector({})'.format(components) + return f'Vector({components})' def __str__(self): return str(tuple(self)) @@ -183,7 +183,7 @@ def __eq__(self, other): return tuple(self) == tuple(other) def __abs__(self): - return math.sqrt(sum(x * x for x in self)) + return math.hypot(*self) def __bool__(self): return bool(abs(self)) @@ -207,8 +207,8 @@ def __getattr__(self, name): pos = cls.shortcut_names.find(name) # <3> if 0 <= pos < len(self._components): # <4> return self._components[pos] - msg = '{.__name__!r} object has no attribute {!r}' # <5> - raise AttributeError(msg.format(cls, name)) + msg = f'{cls.__name__!r} object has no attribute {name!r}' # <5> + raise AttributeError(msg) # end::VECTOR_V3_GETATTR[] # tag::VECTOR_V3_SETATTR[] @@ -216,7 +216,7 @@ def __setattr__(self, name, value): cls = type(self) if len(name) == 1: # <1> if name in cls.shortcut_names: # <2> - error = 'readonly attribute {attr_name!r}' + error = 'read-only attribute {attr_name!r}' elif name.islower(): # <3> error = "can't set attributes 'a' to 'z' in {cls_name!r}" else: diff --git a/12-seq-hacking/vector_v4.py b/12-seq-hacking/vector_v4.py index 211a076..95530eb 100644 --- a/12-seq-hacking/vector_v4.py +++ b/12-seq-hacking/vector_v4.py @@ -166,7 +166,7 @@ def __iter__(self): def __repr__(self): components = reprlib.repr(self._components) components = components[components.find('['):-1] - return 'Vector({})'.format(components) + return f'Vector({components})' def __str__(self): return str(tuple(self)) @@ -184,7 +184,7 @@ def __hash__(self): return functools.reduce(operator.xor, hashes, 0) def __abs__(self): - return math.sqrt(sum(x * x for x in self)) + return math.hypot(*self) def __bool__(self): return bool(abs(self)) @@ -207,8 +207,8 @@ def __getattr__(self, name): pos = cls.shortcut_names.find(name) if 0 <= pos < len(self._components): return self._components[pos] - msg = '{.__name__!r} object has no attribute {!r}' - raise AttributeError(msg.format(cls, name)) + msg = f'{cls.__name__!r} object has no attribute {name!r}' + raise AttributeError(msg) @classmethod def frombytes(cls, octets): diff --git a/12-seq-hacking/vector_v5.py b/12-seq-hacking/vector_v5.py index c5c7344..ebbd523 100644 --- a/12-seq-hacking/vector_v5.py +++ b/12-seq-hacking/vector_v5.py @@ -209,7 +209,7 @@ def __iter__(self): def __repr__(self): components = reprlib.repr(self._components) components = components[components.find('['):-1] - return 'Vector({})'.format(components) + return f'Vector({components})' def __str__(self): return str(tuple(self)) @@ -227,7 +227,7 @@ def __hash__(self): return functools.reduce(operator.xor, hashes, 0) def __abs__(self): - return math.sqrt(sum(x * x for x in self)) + return math.hypot(*self) def __bool__(self): return bool(abs(self)) @@ -250,11 +250,11 @@ def __getattr__(self, name): pos = cls.shortcut_names.find(name) if 0 <= pos < len(self._components): return self._components[pos] - msg = '{.__name__!r} object has no attribute {!r}' - raise AttributeError(msg.format(cls, name)) + msg = f'{cls.__name__!r} object has no attribute {name!r}' + raise AttributeError(msg) def angle(self, n): # <2> - r = math.sqrt(sum(x * x for x in self[n:])) + r = math.hypot(*self[n:]) a = math.atan2(r, self[n-1]) if (n == len(self) - 1) and (self[-1] < 0): return math.pi * 2 - a diff --git a/13-protocol-abc/double/double_test.py b/13-protocol-abc/double/double_test.py index d37aba5..1871a1a 100644 --- a/13-protocol-abc/double/double_test.py +++ b/13-protocol-abc/double/double_test.py @@ -53,4 +53,4 @@ def test_double_nparray() -> None: def test_double_none() -> None: given = None with pytest.raises(TypeError): - result = double(given) + double(given) diff --git a/13-protocol-abc/tombola.py b/13-protocol-abc/tombola.py index ea6cb78..6f4c6cd 100644 --- a/13-protocol-abc/tombola.py +++ b/13-protocol-abc/tombola.py @@ -19,7 +19,6 @@ def loaded(self): # <4> """Return `True` if there's at least 1 item, `False` otherwise.""" return bool(self.inspect()) # <5> - def inspect(self): """Return a sorted tuple with the items currently inside.""" items = [] diff --git a/13-protocol-abc/typing/randompick_test.py b/13-protocol-abc/typing/randompick_test.py index 957441c..f090022 100644 --- a/13-protocol-abc/typing/randompick_test.py +++ b/13-protocol-abc/typing/randompick_test.py @@ -3,7 +3,7 @@ from randompick import RandomPicker # <1> -class SimplePicker(): # <2> +class SimplePicker: # <2> def __init__(self, items: Iterable) -> None: self._items = list(items) random.shuffle(self._items) diff --git a/13-protocol-abc/typing/randompickload.py b/13-protocol-abc/typing/randompickload.py index 7357915..88d93a8 100644 --- a/13-protocol-abc/typing/randompickload.py +++ b/13-protocol-abc/typing/randompickload.py @@ -1,4 +1,4 @@ -from typing import Protocol, runtime_checkable, Any, Iterable +from typing import Protocol, runtime_checkable from randompick import RandomPicker @runtime_checkable # <1> diff --git a/13-protocol-abc/typing/randompickload_test.py b/13-protocol-abc/typing/randompickload_test.py index 0bef0db..ef4d049 100644 --- a/13-protocol-abc/typing/randompickload_test.py +++ b/13-protocol-abc/typing/randompickload_test.py @@ -1,9 +1,9 @@ import random -from typing import Any, Iterable, TYPE_CHECKING +from typing import Any, Iterable from randompickload import LoadableRandomPicker -class SimplePicker(): +class SimplePicker: def __init__(self, items: Iterable) -> None: self._items = list(items) random.shuffle(self._items) @@ -11,7 +11,7 @@ def __init__(self, items: Iterable) -> None: def pick(self) -> Any: return self._items.pop() -class LoadablePicker(): # <1> +class LoadablePicker: # <1> def __init__(self, items: Iterable) -> None: self.load(items) diff --git a/15-more-types/cafeteria.py b/15-more-types/cafeteria.py index 4a9fad9..622f8e4 100644 --- a/15-more-types/cafeteria.py +++ b/15-more-types/cafeteria.py @@ -2,25 +2,25 @@ class Beverage: - """Any beverage""" + """Any beverage.""" class Juice(Beverage): - """Any fruit juice""" + """Any fruit juice.""" class OrangeJuice(Juice): - """Delicious juice from Brazilian oranges""" + """Delicious juice from Brazilian oranges.""" -BeverageT = TypeVar('BeverageT', covariant=True) +T_co = TypeVar('T_co', covariant=True) -class BeverageDispenser(Generic[BeverageT]): - def __init__(self, beverage: BeverageT) -> None: +class BeverageDispenser(Generic[T_co]): + def __init__(self, beverage: T_co) -> None: self.beverage = beverage - def dispense(self) -> BeverageT: + def dispense(self) -> T_co: return self.beverage @@ -36,11 +36,11 @@ class Compostable(Biodegradable): """Compostable garbage.""" -GarbageT = TypeVar('GarbageT', contravariant=True) +T_contra = TypeVar('T_contra', contravariant=True) -class TrashCan(Generic[GarbageT]): - def put(self, trash) -> None: +class TrashCan(Generic[T_contra]): + def put(self, trash: T_contra) -> None: """Store trash until dumped...""" @@ -48,35 +48,48 @@ class Cafeteria: def __init__( self, dispenser: BeverageDispenser[Juice], - trash_can: TrashCan[Biodegradable] + trash_can: TrashCan[Biodegradable], ): """Initialize...""" -beverage_dispenser = BeverageDispenser(Beverage()) -juice_dispenser = BeverageDispenser(Juice()) -orange_juice_dispenser = BeverageDispenser(OrangeJuice()) +################################################ exact types -trash_can: TrashCan[Garbage] = TrashCan() +juice_dispenser = BeverageDispenser(Juice()) bio_can: TrashCan[Biodegradable] = TrashCan() -compost_can: TrashCan[Compostable] = TrashCan() arnold_hall = Cafeteria(juice_dispenser, bio_can) -######################## covariance on 1st argument -arnold_hall = Cafeteria(orange_juice_dispenser, trash_can) + +################################################ covariant dispenser + +orange_juice_dispenser = BeverageDispenser(OrangeJuice()) + +arnold_hall = Cafeteria(orange_juice_dispenser, bio_can) + + +################################################ non-covariant dispenser + +beverage_dispenser = BeverageDispenser(Beverage()) ## Argument 1 to "Cafeteria" has ## incompatible type "BeverageDispenser[Beverage]" ## expected "BeverageDispenser[Juice]" -# arnold_hall = Cafeteria(beverage_dispenser, trash_can) +# arnold_hall = Cafeteria(beverage_dispenser, bio_can) + + +################################################ contravariant trash + +trash_can: TrashCan[Garbage] = TrashCan() +arnold_hall = Cafeteria(juice_dispenser, trash_can) + + +################################################ non-contravariant trash -######################## contravariance on 2nd argument +compost_can: TrashCan[Compostable] = TrashCan() ## Argument 2 to "Cafeteria" has ## incompatible type "TrashCan[Compostable]" ## expected "TrashCan[Biodegradable]" # arnold_hall = Cafeteria(juice_dispenser, compost_can) - -arnold_hall = Cafeteria(juice_dispenser, trash_can) diff --git a/15-more-types/petbox/petbox.py b/15-more-types/petbox/petbox.py index a344a96..6ecd86d 100644 --- a/15-more-types/petbox/petbox.py +++ b/15-more-types/petbox/petbox.py @@ -29,14 +29,6 @@ def get(self) -> T: return self.contents -T_contra = TypeVar('T_contra', contravariant=True) - - -class InBox(Generic[T_contra]): - def put(self, item: T) -> None: - self.contents = item - - T_co = TypeVar('T_co', covariant=True) @@ -46,3 +38,11 @@ def __init__(self, contents: Any): def get(self) -> Any: return self.contents + + +T_contra = TypeVar('T_contra', contravariant=True) + + +class InBox(Generic[T_contra]): + def put(self, item: T) -> None: + self.contents = item diff --git a/16-op-overloading/bingoaddable.py b/16-op-overloading/bingoaddable.py index 20294a4..38a70c7 100644 --- a/16-op-overloading/bingoaddable.py +++ b/16-op-overloading/bingoaddable.py @@ -53,34 +53,29 @@ """ # tag::ADDABLE_BINGO[] -import itertools # <1> - from tombola import Tombola from bingo import BingoCage -class AddableBingoCage(BingoCage): # <2> +class AddableBingoCage(BingoCage): # <1> def __add__(self, other): - if isinstance(other, Tombola): # <3> - return AddableBingoCage(self.inspect() + other.inspect()) + if isinstance(other, Tombola): # <2> + return AddableBingoCage(self.inspect() + other.inspect()) else: return NotImplemented def __iadd__(self, other): if isinstance(other, Tombola): - other_iterable = other.inspect() # <4> + other_iterable = other.inspect() # <3> else: try: - other_iterable = iter(other) # <5> - except TypeError: # <6> + other_iterable = iter(other) # <4> + except TypeError: # <5> self_cls = type(self).__name__ msg = "right operand in += must be {!r} or an iterable" raise TypeError(msg.format(self_cls)) - self.load(other_iterable) # <7> - return self # <8> - - - + self.load(other_iterable) # <6> + return self # <7> # end::ADDABLE_BINGO[] diff --git a/16-op-overloading/tombola.py b/16-op-overloading/tombola.py index 5ed0f85..b38d075 100644 --- a/16-op-overloading/tombola.py +++ b/16-op-overloading/tombola.py @@ -19,7 +19,6 @@ def loaded(self): # <4> """Return `True` if there's at least 1 item, `False` otherwise.""" return bool(self.inspect()) # <5> - def inspect(self): """Return a sorted tuple with the items currently inside.""" items = [] @@ -31,5 +30,5 @@ def inspect(self): self.load(items) # <7> return tuple(sorted(items)) - # END TOMBOLA_ABC + diff --git a/16-op-overloading/vector_py3_5.py b/16-op-overloading/vector_py3_5.py deleted file mode 100644 index ad04864..0000000 --- a/16-op-overloading/vector_py3_5.py +++ /dev/null @@ -1,431 +0,0 @@ -""" -A multi-dimensional ``Vector`` class, take 9: operator ``@`` - -WARNING: This example requires Python 3.5 or later. - -A ``Vector`` is built from an iterable of numbers:: - - >>> Vector([3.1, 4.2]) - Vector([3.1, 4.2]) - >>> Vector((3, 4, 5)) - Vector([3.0, 4.0, 5.0]) - >>> Vector(range(10)) - Vector([0.0, 1.0, 2.0, 3.0, 4.0, ...]) - - -Tests with 2-dimensions (same results as ``vector2d_v1.py``):: - - >>> v1 = Vector([3, 4]) - >>> x, y = v1 - >>> x, y - (3.0, 4.0) - >>> v1 - Vector([3.0, 4.0]) - >>> v1_clone = eval(repr(v1)) - >>> v1 == v1_clone - True - >>> print(v1) - (3.0, 4.0) - >>> octets = bytes(v1) - >>> octets - b'd\\x00\\x00\\x00\\x00\\x00\\x00\\x08@\\x00\\x00\\x00\\x00\\x00\\x00\\x10@' - >>> abs(v1) - 5.0 - >>> bool(v1), bool(Vector([0, 0])) - (True, False) - - -Test of ``.frombytes()`` class method: - - >>> v1_clone = Vector.frombytes(bytes(v1)) - >>> v1_clone - Vector([3.0, 4.0]) - >>> v1 == v1_clone - True - - -Tests with 3-dimensions:: - - >>> v1 = Vector([3, 4, 5]) - >>> x, y, z = v1 - >>> x, y, z - (3.0, 4.0, 5.0) - >>> v1 - Vector([3.0, 4.0, 5.0]) - >>> v1_clone = eval(repr(v1)) - >>> v1 == v1_clone - True - >>> print(v1) - (3.0, 4.0, 5.0) - >>> abs(v1) # doctest:+ELLIPSIS - 7.071067811... - >>> bool(v1), bool(Vector([0, 0, 0])) - (True, False) - - -Tests with many dimensions:: - - >>> v7 = Vector(range(7)) - >>> v7 - Vector([0.0, 1.0, 2.0, 3.0, 4.0, ...]) - >>> abs(v7) # doctest:+ELLIPSIS - 9.53939201... - - -Test of ``.__bytes__`` and ``.frombytes()`` methods:: - - >>> v1 = Vector([3, 4, 5]) - >>> v1_clone = Vector.frombytes(bytes(v1)) - >>> v1_clone - Vector([3.0, 4.0, 5.0]) - >>> v1 == v1_clone - True - - -Tests of sequence behavior:: - - >>> v1 = Vector([3, 4, 5]) - >>> len(v1) - 3 - >>> v1[0], v1[len(v1)-1], v1[-1] - (3.0, 5.0, 5.0) - - -Test of slicing:: - - >>> v7 = Vector(range(7)) - >>> v7[-1] - 6.0 - >>> v7[1:4] - Vector([1.0, 2.0, 3.0]) - >>> v7[-1:] - Vector([6.0]) - >>> v7[1,2] - Traceback (most recent call last): - ... - TypeError: Vector indices must be integers - - -Tests of dynamic attribute access:: - - >>> v7 = Vector(range(10)) - >>> v7.x - 0.0 - >>> v7.y, v7.z, v7.t - (1.0, 2.0, 3.0) - -Dynamic attribute lookup failures:: - - >>> v7.k - Traceback (most recent call last): - ... - AttributeError: 'Vector' object has no attribute 'k' - >>> v3 = Vector(range(3)) - >>> v3.t - Traceback (most recent call last): - ... - AttributeError: 'Vector' object has no attribute 't' - >>> v3.spam - Traceback (most recent call last): - ... - AttributeError: 'Vector' object has no attribute 'spam' - - -Tests of hashing:: - - >>> v1 = Vector([3, 4]) - >>> v2 = Vector([3.1, 4.2]) - >>> v3 = Vector([3, 4, 5]) - >>> v6 = Vector(range(6)) - >>> hash(v1), hash(v3), hash(v6) - (7, 2, 1) - - -Most hash codes of non-integers vary from a 32-bit to 64-bit Python build:: - - >>> import sys - >>> hash(v2) == (384307168202284039 if sys.maxsize > 2**32 else 357915986) - True - - -Tests of ``format()`` with Cartesian coordinates in 2D:: - - >>> v1 = Vector([3, 4]) - >>> format(v1) - '(3.0, 4.0)' - >>> format(v1, '.2f') - '(3.00, 4.00)' - >>> format(v1, '.3e') - '(3.000e+00, 4.000e+00)' - - -Tests of ``format()`` with Cartesian coordinates in 3D and 7D:: - - >>> v3 = Vector([3, 4, 5]) - >>> format(v3) - '(3.0, 4.0, 5.0)' - >>> format(Vector(range(7))) - '(0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0)' - - -Tests of ``format()`` with spherical coordinates in 2D, 3D and 4D:: - - >>> format(Vector([1, 1]), 'h') # doctest:+ELLIPSIS - '<1.414213..., 0.785398...>' - >>> format(Vector([1, 1]), '.3eh') - '<1.414e+00, 7.854e-01>' - >>> format(Vector([1, 1]), '0.5fh') - '<1.41421, 0.78540>' - >>> format(Vector([1, 1, 1]), 'h') # doctest:+ELLIPSIS - '<1.73205..., 0.95531..., 0.78539...>' - >>> format(Vector([2, 2, 2]), '.3eh') - '<3.464e+00, 9.553e-01, 7.854e-01>' - >>> format(Vector([0, 0, 0]), '0.5fh') - '<0.00000, 0.00000, 0.00000>' - >>> format(Vector([-1, -1, -1, -1]), 'h') # doctest:+ELLIPSIS - '<2.0, 2.09439..., 2.18627..., 3.92699...>' - >>> format(Vector([2, 2, 2, 2]), '.3eh') - '<4.000e+00, 1.047e+00, 9.553e-01, 7.854e-01>' - >>> format(Vector([0, 1, 0, 0]), '0.5fh') - '<1.00000, 1.57080, 0.00000, 0.00000>' - - -Basic tests of operator ``+``:: - - >>> v1 = Vector([3, 4, 5]) - >>> v2 = Vector([6, 7, 8]) - >>> v1 + v2 - Vector([9.0, 11.0, 13.0]) - >>> v1 + v2 == Vector([3+6, 4+7, 5+8]) - True - >>> v3 = Vector([1, 2]) - >>> v1 + v3 # short vectors are filled with 0.0 on addition - Vector([4.0, 6.0, 5.0]) - - -Tests of ``+`` with mixed types:: - - >>> v1 + (10, 20, 30) - Vector([13.0, 24.0, 35.0]) - >>> from vector2d_v3 import Vector2d - >>> v2d = Vector2d(1, 2) - >>> v1 + v2d - Vector([4.0, 6.0, 5.0]) - - -Tests of ``+`` with mixed types, swapped operands:: - - >>> (10, 20, 30) + v1 - Vector([13.0, 24.0, 35.0]) - >>> from vector2d_v3 import Vector2d - >>> v2d = Vector2d(1, 2) - >>> v2d + v1 - Vector([4.0, 6.0, 5.0]) - - -Tests of ``+`` with an unsuitable operand: - - >>> v1 + 1 - Traceback (most recent call last): - ... - TypeError: unsupported operand type(s) for +: 'Vector' and 'int' - >>> v1 + 'ABC' - Traceback (most recent call last): - ... - TypeError: unsupported operand type(s) for +: 'Vector' and 'str' - - -Basic tests of operator ``*``:: - - >>> v1 = Vector([1, 2, 3]) - >>> v1 * 10 - Vector([10.0, 20.0, 30.0]) - >>> 10 * v1 - Vector([10.0, 20.0, 30.0]) - - -Tests of ``*`` with unusual but valid operands:: - - >>> v1 * True - Vector([1.0, 2.0, 3.0]) - >>> from fractions import Fraction - >>> v1 * Fraction(1, 3) # doctest:+ELLIPSIS - Vector([0.3333..., 0.6666..., 1.0]) - - -Tests of ``*`` with unsuitable operands:: - - >>> v1 * (1, 2) - Traceback (most recent call last): - ... - TypeError: can't multiply sequence by non-int of type 'Vector' - - -Tests of operator `==`:: - - >>> va = Vector(range(1, 4)) - >>> vb = Vector([1.0, 2.0, 3.0]) - >>> va == vb - True - >>> vc = Vector([1, 2]) - >>> from vector2d_v3 import Vector2d - >>> v2d = Vector2d(1, 2) - >>> vc == v2d - True - >>> va == (1, 2, 3) - False - - -Tests of operator `!=`:: - - >>> va != vb - False - >>> vc != v2d - False - >>> va != (1, 2, 3) - True - - -Tests for operator `@` (Python >= 3.5), computing the dot product:: - - >>> va = Vector([1, 2, 3]) - >>> vz = Vector([5, 6, 7]) - >>> va @ vz == 38.0 # 1*5 + 2*6 + 3*7 - True - >>> [10, 20, 30] @ vz - 380.0 - >>> va @ 3 - Traceback (most recent call last): - ... - TypeError: unsupported operand type(s) for @: 'Vector' and 'int' - - -""" - -from array import array -import reprlib -import math -import functools -import operator -import itertools -import numbers - - -class Vector: - typecode = 'd' - - def __init__(self, components): - self._components = array(self.typecode, components) - - def __iter__(self): - return iter(self._components) - - def __repr__(self): - components = reprlib.repr(self._components) - components = components[components.find('['):-1] - return 'Vector({})'.format(components) - - def __str__(self): - return str(tuple(self)) - - def __bytes__(self): - return (bytes([ord(self.typecode)]) + - bytes(self._components)) - - def __eq__(self, other): - if isinstance(other, Vector): - return (len(self) == len(other) and - all(a == b for a, b in zip(self, other))) - else: - return NotImplemented - - def __hash__(self): - hashes = (hash(x) for x in self) - return functools.reduce(operator.xor, hashes, 0) - - def __abs__(self): - return math.sqrt(sum(x * x for x in self)) - - def __bool__(self): - return bool(abs(self)) - - def __len__(self): - return len(self._components) - - def __getitem__(self, index): - cls = type(self) - if isinstance(index, slice): - return cls(self._components[index]) - elif isinstance(index, int): - return self._components[index] - else: - msg = '{.__name__} indices must be integers' - raise TypeError(msg.format(cls)) - - shortcut_names = 'xyzt' - - def __getattr__(self, name): - cls = type(self) - if len(name) == 1: - pos = cls.shortcut_names.find(name) - if 0 <= pos < len(self._components): - return self._components[pos] - msg = '{.__name__!r} object has no attribute {!r}' - raise AttributeError(msg.format(cls, name)) - - def angle(self, n): - r = math.sqrt(sum(x * x for x in self[n:])) - a = math.atan2(r, self[n-1]) - if (n == len(self) - 1) and (self[-1] < 0): - return math.pi * 2 - a - else: - return a - - def angles(self): - return (self.angle(n) for n in range(1, len(self))) - - def __format__(self, fmt_spec=''): - if fmt_spec.endswith('h'): # hyperspherical coordinates - fmt_spec = fmt_spec[:-1] - coords = itertools.chain([abs(self)], - self.angles()) - outer_fmt = '<{}>' - else: - coords = self - outer_fmt = '({})' - components = (format(c, fmt_spec) for c in coords) - return outer_fmt.format(', '.join(components)) - - @classmethod - def frombytes(cls, octets): - typecode = chr(octets[0]) - memv = memoryview(octets[1:]).cast(typecode) - return cls(memv) - - def __add__(self, other): - try: - pairs = itertools.zip_longest(self, other, fillvalue=0.0) - return Vector(a + b for a, b in pairs) - except TypeError: - return NotImplemented - - def __radd__(self, other): - return self + other - - def __mul__(self, scalar): - if isinstance(scalar, numbers.Real): - return Vector(n * scalar for n in self) - else: - return NotImplemented - - def __rmul__(self, scalar): - return self * scalar - - def __matmul__(self, other): - try: - return sum(a * b for a, b in zip(self, other)) - except TypeError: - return NotImplemented - - def __rmatmul__(self, other): - return self @ other # this only works in Python 3.5 diff --git a/16-op-overloading/vector_v6.py b/16-op-overloading/vector_v6.py index a272a9b..7154851 100644 --- a/16-op-overloading/vector_v6.py +++ b/16-op-overloading/vector_v6.py @@ -264,7 +264,7 @@ def __iter__(self): def __repr__(self): components = reprlib.repr(self._components) components = components[components.find('['):-1] - return 'Vector({})'.format(components) + return f'Vector({components})' def __str__(self): return str(tuple(self)) @@ -283,7 +283,7 @@ def __hash__(self): # tag::VECTOR_V6_UNARY[] def __abs__(self): - return math.sqrt(sum(x * x for x in self)) + return math.hypot(*self) def __neg__(self): return Vector(-x for x in self) # <1> @@ -313,11 +313,11 @@ def __getattr__(self, name): pos = cls.shortcut_names.find(name) if 0 <= pos < len(self._components): return self._components[pos] - msg = '{.__name__!r} object has no attribute {!r}' - raise AttributeError(msg.format(cls, name)) + msg = f'{cls.__name__!r} object has no attribute {name!r}' + raise AttributeError(msg) def angle(self, n): - r = math.sqrt(sum(x * x for x in self[n:])) + r = math.hypot(*self[n:]) a = math.atan2(r, self[n-1]) if (n == len(self) - 1) and (self[-1] < 0): return math.pi * 2 - a diff --git a/16-op-overloading/vector_v7.py b/16-op-overloading/vector_v7.py index 241956e..e59c896 100644 --- a/16-op-overloading/vector_v7.py +++ b/16-op-overloading/vector_v7.py @@ -316,7 +316,7 @@ def __iter__(self): def __repr__(self): components = reprlib.repr(self._components) components = components[components.find('['):-1] - return 'Vector({})'.format(components) + return f'Vector({components})' def __str__(self): return str(tuple(self)) @@ -334,7 +334,7 @@ def __hash__(self): return functools.reduce(operator.xor, hashes, 0) def __abs__(self): - return math.sqrt(sum(x * x for x in self)) + return math.hypot(*self) def __neg__(self): return Vector(-x for x in self) @@ -363,11 +363,11 @@ def __getattr__(self, name): pos = cls.shortcut_names.find(name) if 0 <= pos < len(self._components): return self._components[pos] - msg = '{.__name__!r} object has no attribute {!r}' - raise AttributeError(msg.format(cls, name)) + msg = f'{cls.__name__!r} object has no attribute {name!r}' + raise AttributeError(msg) def angle(self, n): - r = math.sqrt(sum(x * x for x in self[n:])) + r = math.hypot(*self[n:]) a = math.atan2(r, self[n-1]) if (n == len(self) - 1) and (self[-1] < 0): return math.pi * 2 - a diff --git a/16-op-overloading/vector_v8.py b/16-op-overloading/vector_v8.py index 48b4cc5..ee2cb48 100644 --- a/16-op-overloading/vector_v8.py +++ b/16-op-overloading/vector_v8.py @@ -317,7 +317,7 @@ def __iter__(self): def __repr__(self): components = reprlib.repr(self._components) components = components[components.find('['):-1] - return 'Vector({})'.format(components) + return f'Vector({components})' def __str__(self): return str(tuple(self)) @@ -340,7 +340,7 @@ def __hash__(self): return functools.reduce(operator.xor, hashes, 0) def __abs__(self): - return math.sqrt(sum(x * x for x in self)) + return math.hypot(*self) def __neg__(self): return Vector(-x for x in self) @@ -369,11 +369,11 @@ def __getattr__(self, name): pos = cls.shortcut_names.find(name) if 0 <= pos < len(self._components): return self._components[pos] - msg = '{.__name__!r} object has no attribute {!r}' - raise AttributeError(msg.format(cls, name)) + msg = f'{cls.__name__!r} object has no attribute {name!r}' + raise AttributeError(msg) def angle(self, n): - r = math.sqrt(sum(x * x for x in self[n:])) + r = math.hypot(*self[n:]) a = math.atan2(r, self[n-1]) if (n == len(self) - 1) and (self[-1] < 0): return math.pi * 2 - a diff --git a/17-it-generator/aritprog_float_error.py b/17-it-generator/aritprog_float_error.py index b871ad8..9a0e7cc 100644 --- a/17-it-generator/aritprog_float_error.py +++ b/17-it-generator/aritprog_float_error.py @@ -20,7 +20,7 @@ while abs(delta) <= epsilon: delta = next(ap0) - next(ap1) frac = next(ap_frac) - iteration +=1 + iteration += 1 print('iteration: {}\tfraction: {}\tepsilon: {}\tdelta: {}'. format(iteration, frac, epsilon, delta)) diff --git a/17-it-generator/columnize_iter.py b/17-it-generator/columnize_iter.py index 867b677..c9a6f7d 100644 --- a/17-it-generator/columnize_iter.py +++ b/17-it-generator/columnize_iter.py @@ -1,5 +1,5 @@ # tag::COLUMNIZE[] -from typing import Sequence, Tuple, Iterator +from typing import Sequence, Tuple, Iterator def columnize(sequence: Sequence[str], num_columns: int = 0) -> Iterator[Tuple[str, ...]]: if num_columns == 0: diff --git a/17-it-generator/fibo_by_hand.py b/17-it-generator/fibo_by_hand.py index 9bf8ab4..4fa41c5 100644 --- a/17-it-generator/fibo_by_hand.py +++ b/17-it-generator/fibo_by_hand.py @@ -44,8 +44,9 @@ def fibonacci(): if __name__ == '__main__': for x, y in zip(Fibonacci(), fibonacci()): - assert x == y, '%s != %s' % (x, y) + assert x == y, f'{x} != {y}' print(x) if x > 10**10: break print('etc...') + diff --git a/17-it-generator/sentence_gen2.py b/17-it-generator/sentence_gen2.py index 503fecd..f362ffc 100644 --- a/17-it-generator/sentence_gen2.py +++ b/17-it-generator/sentence_gen2.py @@ -6,7 +6,7 @@ import re import reprlib -RE_WORD = re.compile('r\w+') +RE_WORD = re.compile(r'\w+') class Sentence: @@ -15,7 +15,7 @@ def __init__(self, text): self.text = text # <1> def __repr__(self): - return 'Sentence(%s)' % reprlib.repr(self.text) + return f'Sentence({reprlib.repr(self.text)})' def __iter__(self): for match in RE_WORD.finditer(self.text): # <2> diff --git a/17-it-generator/sentence_genexp.py b/17-it-generator/sentence_genexp.py index b75e859..cb9366a 100644 --- a/17-it-generator/sentence_genexp.py +++ b/17-it-generator/sentence_genexp.py @@ -15,7 +15,7 @@ def __init__(self, text): self.text = text def __repr__(self): - return 'Sentence(%s)' % reprlib.repr(self.text) + return f'Sentence({reprlib.repr(self.text)})' def __iter__(self): return (match.group() for match in RE_WORD.finditer(self.text)) @@ -29,7 +29,7 @@ def main(): filename = sys.argv[1] word_number = int(sys.argv[2]) except (IndexError, ValueError): - print('Usage: %s ' % sys.argv[0]) + print(f'Usage: {sys.argv[0]} ') sys.exit(2) # command line usage error with open(filename, 'rt', encoding='utf-8') as text_file: s = Sentence(text_file.read()) @@ -38,7 +38,7 @@ def main(): print(word) break else: - warnings.warn('last word is #%d, "%s"' % (n, word)) + warnings.warn(f'last word is #{n}, {word!r}') if __name__ == '__main__': main() diff --git a/17-it-generator/sentence_iter.py b/17-it-generator/sentence_iter.py index 5472179..0c5f225 100644 --- a/17-it-generator/sentence_iter.py +++ b/17-it-generator/sentence_iter.py @@ -19,7 +19,7 @@ def __init__(self, text): self.words = RE_WORD.findall(text) def __repr__(self): - return 'Sentence(%s)' % reprlib.repr(self.text) + return f'Sentence({reprlib.repr(self.text)})' def __iter__(self): # <1> return SentenceIterator(self.words) # <2> @@ -50,7 +50,7 @@ def main(): filename = sys.argv[1] word_number = int(sys.argv[2]) except (IndexError, ValueError): - print('Usage: %s ' % sys.argv[0]) + print(f'Usage: {sys.argv[0]} ') sys.exit(2) # command line usage error with open(filename, 'rt', encoding='utf-8') as text_file: s = Sentence(text_file.read()) @@ -59,7 +59,7 @@ def main(): print(word) break else: - warnings.warn('last word is #%d, "%s"' % (n, word)) + warnings.warn(f'last word is #{n}, {word!r}') if __name__ == '__main__': main() diff --git a/17-it-generator/sentence_iter2.py b/17-it-generator/sentence_iter2.py index 2663f3f..14ad374 100644 --- a/17-it-generator/sentence_iter2.py +++ b/17-it-generator/sentence_iter2.py @@ -17,14 +17,14 @@ def __init__(self, text): self.text = text def __repr__(self): - return 'Sentence(%s)' % reprlib.repr(self.text) + return f'Sentence({reprlib.repr(self.text)})' def __iter__(self): word_iter = RE_WORD.finditer(self.text) # <1> return SentenceIter(word_iter) # <2> -class SentenceIter(): +class SentenceIter: def __init__(self, word_iter): self.word_iter = word_iter # <3> diff --git a/17-it-generator/tree/extra/pretty_tree.py b/17-it-generator/tree/extra/pretty_tree.py index 668df54..6d6d91a 100644 --- a/17-it-generator/tree/extra/pretty_tree.py +++ b/17-it-generator/tree/extra/pretty_tree.py @@ -1,11 +1,11 @@ from tree import tree SPACES = ' ' * 4 -HLINE = '\u2500' # ─ BOX DRAWINGS LIGHT HORIZONTAL +HLINE = '\u2500' # ─ BOX DRAWINGS LIGHT HORIZONTAL HLINE2 = HLINE * 2 -ELBOW = f'\u2514{HLINE2} ' # └ BOX DRAWINGS LIGHT UP AND RIGHT -TEE = f'\u251C{HLINE2} ' # ├ BOX DRAWINGS LIGHT VERTICAL AND RIGHT -PIPE = f'\u2502 ' # │ BOX DRAWINGS LIGHT VERTICAL +ELBOW = f'\u2514{HLINE2} ' # └ BOX DRAWINGS LIGHT UP AND RIGHT +TEE = f'\u251C{HLINE2} ' # ├ BOX DRAWINGS LIGHT VERTICAL AND RIGHT +PIPE = '\u2502 ' # │ BOX DRAWINGS LIGHT VERTICAL def render_lines(tree_iter): diff --git a/17-it-generator/tree/extra/test_pretty_tree.py b/17-it-generator/tree/extra/test_pretty_tree.py index 943f0f1..70019a3 100644 --- a/17-it-generator/tree/extra/test_pretty_tree.py +++ b/17-it-generator/tree/extra/test_pretty_tree.py @@ -1,5 +1,3 @@ -import pytest - from pretty_tree import tree, render_lines def test_1_level(): @@ -7,7 +5,7 @@ def test_1_level(): expected = [ 'BrokenPipeError', ] - assert expected == result + assert expected == result def test_2_levels_1_leaf(): @@ -16,7 +14,7 @@ def test_2_levels_1_leaf(): 'IndentationError', '└── TabError', ] - assert expected == result + assert expected == result def test_3_levels_1_leaf(): @@ -29,7 +27,7 @@ class Z(Y): pass '└── Y', ' └── Z', ] - assert expected == result + assert expected == result def test_4_levels_1_leaf(): @@ -98,4 +96,5 @@ class B2(A): pass ] result = list(render_lines(tree(A))) - assert expected == result + assert expected == result + diff --git a/17-it-generator/tree/extra/test_tree.py b/17-it-generator/tree/extra/test_tree.py index da878bc..4c6404c 100644 --- a/17-it-generator/tree/extra/test_tree.py +++ b/17-it-generator/tree/extra/test_tree.py @@ -5,7 +5,7 @@ def test_1_level(): class One: pass expected = [('One', 0, True)] result = list(tree(One)) - assert expected == result + assert expected == result def test_2_levels_2_leaves(): @@ -18,7 +18,7 @@ class Leaf2(Branch): pass ('Leaf2', 1, True), ] result = list(tree(Branch)) - assert expected == result + assert expected == result def test_3_levels_1_leaf(): @@ -31,7 +31,7 @@ class Z(Y): pass ('Z', 2, True), ] result = list(tree(X)) - assert expected == result + assert expected == result def test_4_levels_1_leaf(): @@ -47,7 +47,7 @@ class Level3(Level2): pass ] result = list(tree(Level0)) - assert expected == result + assert expected == result def test_4_levels_3_leaves(): @@ -69,7 +69,7 @@ class D2(C1): pass ] result = list(tree(A)) - assert expected == result + assert expected == result def test_many_levels_1_leaf(): @@ -87,4 +87,4 @@ class Root: pass assert len(result) == level_count assert result[0] == ('Root', 0, True) assert result[-1] == ('Sub99', 99, True) - assert expected == result + assert expected == result diff --git a/17-it-generator/tree/extra/tree.py b/17-it-generator/tree/extra/tree.py index 1a6ec6d..7169a28 100644 --- a/17-it-generator/tree/extra/tree.py +++ b/17-it-generator/tree/extra/tree.py @@ -2,7 +2,7 @@ def tree(cls, level=0, last_in_level=True): yield cls.__name__, level, last_in_level subclasses = cls.__subclasses__() if subclasses: - last = subclasses[-1] + last = subclasses[-1] for sub_cls in subclasses: yield from tree(sub_cls, level+1, sub_cls is last) diff --git a/17-it-generator/tree/step0/test_tree.py b/17-it-generator/tree/step0/test_tree.py index 1ab22ae..8684dbb 100644 --- a/17-it-generator/tree/step0/test_tree.py +++ b/17-it-generator/tree/step0/test_tree.py @@ -5,4 +5,4 @@ def test_1_level(): class One: pass expected = ['One'] result = list(tree(One)) - assert expected == result + assert expected == result diff --git a/17-it-generator/tree/step0/tree.py b/17-it-generator/tree/step0/tree.py index 5466949..07e34af 100644 --- a/17-it-generator/tree/step0/tree.py +++ b/17-it-generator/tree/step0/tree.py @@ -8,4 +8,4 @@ def display(cls): if __name__ == '__main__': - display(BaseException) \ No newline at end of file + display(BaseException) diff --git a/17-it-generator/tree/step1/test_tree.py b/17-it-generator/tree/step1/test_tree.py index 9896bb3..c89a363 100644 --- a/17-it-generator/tree/step1/test_tree.py +++ b/17-it-generator/tree/step1/test_tree.py @@ -5,7 +5,7 @@ def test_1_level(): class One: pass expected = [('One', 0)] result = list(tree(One)) - assert expected == result + assert expected == result def test_2_levels_2_leaves(): @@ -18,4 +18,4 @@ class Leaf2(Branch): pass ('Leaf2', 1), ] result = list(tree(Branch)) - assert expected == result + assert expected == result diff --git a/17-it-generator/tree/step2/test_tree.py b/17-it-generator/tree/step2/test_tree.py index 9896bb3..c89a363 100644 --- a/17-it-generator/tree/step2/test_tree.py +++ b/17-it-generator/tree/step2/test_tree.py @@ -5,7 +5,7 @@ def test_1_level(): class One: pass expected = [('One', 0)] result = list(tree(One)) - assert expected == result + assert expected == result def test_2_levels_2_leaves(): @@ -18,4 +18,4 @@ class Leaf2(Branch): pass ('Leaf2', 1), ] result = list(tree(Branch)) - assert expected == result + assert expected == result diff --git a/17-it-generator/tree/step3/test_tree.py b/17-it-generator/tree/step3/test_tree.py index 37a685c..673ed14 100644 --- a/17-it-generator/tree/step3/test_tree.py +++ b/17-it-generator/tree/step3/test_tree.py @@ -5,7 +5,7 @@ def test_1_level(): class One: pass expected = [('One', 0)] result = list(tree(One)) - assert expected == result + assert expected == result def test_2_levels_2_leaves(): @@ -18,7 +18,7 @@ class Leaf2(Branch): pass ('Leaf2', 1), ] result = list(tree(Branch)) - assert expected == result + assert expected == result def test_3_levels_1_leaf(): @@ -31,4 +31,4 @@ class Z(Y): pass ('Z', 2), ] result = list(tree(X)) - assert expected == result + assert expected == result diff --git a/17-it-generator/tree/step4/test_tree.py b/17-it-generator/tree/step4/test_tree.py index 602941f..44c79ce 100644 --- a/17-it-generator/tree/step4/test_tree.py +++ b/17-it-generator/tree/step4/test_tree.py @@ -5,7 +5,7 @@ def test_1_level(): class One: pass expected = [('One', 0)] result = list(tree(One)) - assert expected == result + assert expected == result def test_2_levels_2_leaves(): @@ -18,7 +18,7 @@ class Leaf2(Branch): pass ('Leaf2', 1), ] result = list(tree(Branch)) - assert expected == result + assert expected == result def test_3_levels_1_leaf(): @@ -31,7 +31,7 @@ class Z(Y): pass ('Z', 2), ] result = list(tree(X)) - assert expected == result + assert expected == result def test_4_levels_1_leaf(): @@ -47,7 +47,7 @@ class Level3(Level2): pass ] result = list(tree(Level0)) - assert expected == result + assert expected == result def test_4_levels_3_leaves(): @@ -69,4 +69,5 @@ class C2(B2): pass ] result = list(tree(A)) - assert expected == result + assert expected == result + diff --git a/17-it-generator/tree/step5/test_tree.py b/17-it-generator/tree/step5/test_tree.py index d1621b5..fed7a16 100644 --- a/17-it-generator/tree/step5/test_tree.py +++ b/17-it-generator/tree/step5/test_tree.py @@ -5,7 +5,7 @@ def test_1_level(): class One: pass expected = [('One', 0)] result = list(tree(One)) - assert expected == result + assert expected == result def test_2_levels_2_leaves(): @@ -18,7 +18,7 @@ class Leaf2(Branch): pass ('Leaf2', 1), ] result = list(tree(Branch)) - assert expected == result + assert expected == result def test_3_levels_1_leaf(): @@ -31,7 +31,7 @@ class Z(Y): pass ('Z', 2), ] result = list(tree(X)) - assert expected == result + assert expected == result def test_4_levels_1_leaf(): @@ -47,7 +47,7 @@ class Level3(Level2): pass ] result = list(tree(Level0)) - assert expected == result + assert expected == result def test_4_levels_3_leaves(): @@ -69,7 +69,7 @@ class D2(C1): pass ] result = list(tree(A)) - assert expected == result + assert expected == result def test_many_levels_1_leaf(): @@ -87,4 +87,5 @@ class Root: pass assert len(result) == level_count assert result[0] == ('Root', 0) assert result[-1] == ('Sub99', 99) - assert expected == result + assert expected == result + diff --git a/17-it-generator/tree/step6/test_tree.py b/17-it-generator/tree/step6/test_tree.py index d1621b5..fed7a16 100644 --- a/17-it-generator/tree/step6/test_tree.py +++ b/17-it-generator/tree/step6/test_tree.py @@ -5,7 +5,7 @@ def test_1_level(): class One: pass expected = [('One', 0)] result = list(tree(One)) - assert expected == result + assert expected == result def test_2_levels_2_leaves(): @@ -18,7 +18,7 @@ class Leaf2(Branch): pass ('Leaf2', 1), ] result = list(tree(Branch)) - assert expected == result + assert expected == result def test_3_levels_1_leaf(): @@ -31,7 +31,7 @@ class Z(Y): pass ('Z', 2), ] result = list(tree(X)) - assert expected == result + assert expected == result def test_4_levels_1_leaf(): @@ -47,7 +47,7 @@ class Level3(Level2): pass ] result = list(tree(Level0)) - assert expected == result + assert expected == result def test_4_levels_3_leaves(): @@ -69,7 +69,7 @@ class D2(C1): pass ] result = list(tree(A)) - assert expected == result + assert expected == result def test_many_levels_1_leaf(): @@ -87,4 +87,5 @@ class Root: pass assert len(result) == level_count assert result[0] == ('Root', 0) assert result[-1] == ('Sub99', 99) - assert expected == result + assert expected == result + diff --git a/19-coroutine/coroutil.py b/19-coroutine/coroutil.py index ecd5de7..4420db8 100644 --- a/19-coroutine/coroutil.py +++ b/19-coroutine/coroutil.py @@ -4,8 +4,8 @@ def coroutine(func): """Decorator: primes `func` by advancing to first `yield`""" @wraps(func) - def primer(*args,**kwargs): # <1> - gen = func(*args,**kwargs) # <2> + def primer(*args, **kwargs): # <1> + gen = func(*args, **kwargs) # <2> next(gen) # <3> return gen # <4> return primer diff --git a/19-coroutine/taxi_sim.py b/19-coroutine/taxi_sim.py index 97933bb..6bf0ce9 100644 --- a/19-coroutine/taxi_sim.py +++ b/19-coroutine/taxi_sim.py @@ -49,11 +49,11 @@ """ -import random +import argparse import collections +import random import queue -import argparse -import time + DEFAULT_NUMBER_OF_TAXIS = 3 DEFAULT_END_TIME = 180 @@ -126,8 +126,8 @@ def compute_duration(previous_action): elif previous_action == 'going home': interval = 1 else: - raise ValueError('Unknown previous_action: %s' % previous_action) - return int(random.expovariate(1/interval)) + 1 + raise ValueError(f'Unknown previous_action: {previous_action}') + return int(random.expovariate(1 / interval)) + 1 def main(end_time=DEFAULT_END_TIME, num_taxis=DEFAULT_NUMBER_OF_TAXIS, @@ -136,7 +136,7 @@ def main(end_time=DEFAULT_END_TIME, num_taxis=DEFAULT_NUMBER_OF_TAXIS, if seed is not None: random.seed(seed) # get reproducible results - taxis = {i: taxi_process(i, (i+1)*2, i*DEPARTURE_INTERVAL) + taxis = {i: taxi_process(i, (i + 1) * 2, i * DEPARTURE_INTERVAL) for i in range(num_taxis)} sim = Simulator(taxis) sim.run(end_time) diff --git a/19-coroutine/taxi_sim0.py b/19-coroutine/taxi_sim0.py index 4078fdd..ed988f5 100644 --- a/19-coroutine/taxi_sim0.py +++ b/19-coroutine/taxi_sim0.py @@ -27,11 +27,10 @@ """ -import sys -import random +import argparse import collections import queue -import argparse +import random DEFAULT_NUMBER_OF_TAXIS = 3 DEFAULT_END_TIME = 80 @@ -44,7 +43,7 @@ def compute_delay(interval): """Compute action delay using exponential distribution""" - return int(random.expovariate(1/interval)) + 1 + return int(random.expovariate(1 / interval)) + 1 # BEGIN TAXI_PROCESS def taxi_process(ident, trips, start_time=0): # <1> @@ -68,7 +67,6 @@ def __init__(self, procs_map): self.events = queue.PriorityQueue() self.procs = dict(procs_map) - def run(self, end_time): # <1> """Schedule and display events until time is up""" # schedule the first event for each cab @@ -108,7 +106,7 @@ def main(end_time=DEFAULT_END_TIME, num_taxis=DEFAULT_NUMBER_OF_TAXIS, if seed is not None: random.seed(seed) # get reproducible results - taxis = {i: taxi_process(i, (i+1)*2, i*DEPARTURE_INTERVAL) + taxis = {i: taxi_process(i, (i + 1) * 2, i * DEPARTURE_INTERVAL) for i in range(num_taxis)} sim = Simulator(taxis) sim.run(end_time) diff --git a/21-futures/getflags/requirements.txt b/21-futures/getflags/requirements.txt index 447f2e9..0971a38 100644 --- a/21-futures/getflags/requirements.txt +++ b/21-futures/getflags/requirements.txt @@ -5,7 +5,7 @@ certifi==2020.12.5 chardet==4.0.0 idna==2.10 requests==2.25.1 -urllib3==1.26.4 +urllib3==1.26.3 tqdm==4.56.2 multidict==5.1.0 yarl==1.6.3 diff --git a/22-async/domains/curio/domainlib.py b/22-async/domains/curio/domainlib.py index d359a21..5469fbf 100644 --- a/22-async/domains/curio/domainlib.py +++ b/22-async/domains/curio/domainlib.py @@ -1,15 +1,16 @@ -from curio import TaskGroup -import curio.socket as socket from collections.abc import Iterable, AsyncIterator from typing import NamedTuple +from curio import TaskGroup +import curio.socket as socket + class Result(NamedTuple): domain: str found: bool - async def probe(domain: str) -> Result: +async def probe(domain: str) -> Result: try: await socket.getaddrinfo(domain, None) except socket.gaierror: diff --git a/23-dyn-attr-prop/oscon/schedule_v2.py b/23-dyn-attr-prop/oscon/schedule_v2.py index 14f1ed4..35827eb 100644 --- a/23-dyn-attr-prop/oscon/schedule_v2.py +++ b/23-dyn-attr-prop/oscon/schedule_v2.py @@ -16,8 +16,8 @@ """ # tag::SCHEDULE2_RECORD[] -import json import inspect # <1> +import json JSON_PATH = 'data/osconfeed.json' diff --git a/23-dyn-attr-prop/oscon/schedule_v3.py b/23-dyn-attr-prop/oscon/schedule_v3.py index c443a39..786a412 100644 --- a/23-dyn-attr-prop/oscon/schedule_v3.py +++ b/23-dyn-attr-prop/oscon/schedule_v3.py @@ -20,8 +20,8 @@ # end::SCHEDULE3_DEMO[] """ -import json import inspect +import json JSON_PATH = 'data/osconfeed.json' diff --git a/23-dyn-attr-prop/oscon/schedule_v4_hasattr.py b/23-dyn-attr-prop/oscon/schedule_v4_hasattr.py index 93a12d1..ac5d5de 100644 --- a/23-dyn-attr-prop/oscon/schedule_v4_hasattr.py +++ b/23-dyn-attr-prop/oscon/schedule_v4_hasattr.py @@ -18,8 +18,8 @@ # end::SCHEDULE4_DEMO[] """ -import json import inspect +import json JSON_PATH = 'data/osconfeed.json' diff --git a/24-descriptor/bulkfood/bulkfood_v4.py b/24-descriptor/bulkfood/bulkfood_v4.py index b672e89..bb05da9 100644 --- a/24-descriptor/bulkfood/bulkfood_v4.py +++ b/24-descriptor/bulkfood/bulkfood_v4.py @@ -54,7 +54,7 @@ def __set__(self, instance, value): # <3> msg = f'{self.storage_name} must be > 0' raise ValueError(msg) - # no __get__ needed + # no __get__ needed # <4> class LineItem: weight = Quantity() # <5> diff --git a/24-descriptor/bulkfood/model_v5.py b/24-descriptor/bulkfood/model_v5.py index 647de18..018afcd 100644 --- a/24-descriptor/bulkfood/model_v5.py +++ b/24-descriptor/bulkfood/model_v5.py @@ -32,5 +32,5 @@ def validate(self, name, value): value = value.strip() if len(value) == 0: raise ValueError(f'{name} cannot be blank') - return value # <8> + return value # <2> # end::MODEL_V5_VALIDATED_SUB[] diff --git a/24-descriptor/descriptorkinds.py b/24-descriptor/descriptorkinds.py index 0e2710c..44f4018 100644 --- a/24-descriptor/descriptorkinds.py +++ b/24-descriptor/descriptorkinds.py @@ -5,20 +5,20 @@ >>> obj = Managed() # <1> >>> obj.over # <2> - -> Overriding.__get__(, , + -> Overriding.__get__(, , ) >>> Managed.over # <3> -> Overriding.__get__(, None, ) >>> obj.over = 7 # <4> -> Overriding.__set__(, , 7) >>> obj.over # <5> - -> Overriding.__get__(, , + -> Overriding.__get__(, , ) >>> obj.__dict__['over'] = 8 # <6> >>> vars(obj) # <7> {'over': 8} >>> obj.over # <8> - -> Overriding.__get__(, , + -> Overriding.__get__(, , ) # end::DESCR_KINDS_DEMO1[] @@ -50,7 +50,7 @@ >>> obj = Managed() >>> obj.non_over # <1> - -> NonOverriding.__get__(, , + -> NonOverriding.__get__(, , ) >>> obj.non_over = 7 # <2> >>> obj.non_over # <3> @@ -59,7 +59,7 @@ -> NonOverriding.__get__(, None, ) >>> del obj.non_over # <5> >>> obj.non_over # <6> - -> NonOverriding.__get__(, , + -> NonOverriding.__get__(, , ) # end::DESCR_KINDS_DEMO3[] diff --git a/25-class-metaprog/autoconst/autoconst.py b/25-class-metaprog/autoconst/autoconst.py new file mode 100644 index 0000000..ac34342 --- /dev/null +++ b/25-class-metaprog/autoconst/autoconst.py @@ -0,0 +1,22 @@ +# tag::WilyDict[] +class WilyDict(dict): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.__next_value = 0 + + def __missing__(self, key): + if key.startswith('__') and key.endswith('__'): + raise KeyError(key) + self[key] = value = self.__next_value + self.__next_value += 1 + return value +# end::WilyDict[] + +# tag::AUTOCONST[] +class AutoConstMeta(type): + def __prepare__(name, bases, **kwargs): + return WilyDict() + +class AutoConst(metaclass=AutoConstMeta): + pass +# end::AUTOCONST[] diff --git a/25-class-metaprog/autoconst/autoconst_demo.py b/25-class-metaprog/autoconst/autoconst_demo.py new file mode 100755 index 0000000..008eae8 --- /dev/null +++ b/25-class-metaprog/autoconst/autoconst_demo.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 + +""" +Testing ``WilyDict``:: + + >>> from autoconst import WilyDict + >>> wd = WilyDict() + >>> len(wd) + 0 + >>> wd['first'] + 0 + >>> wd + {'first': 0} + >>> wd['second'] + 1 + >>> wd['third'] + 2 + >>> len(wd) + 3 + >>> wd + {'first': 0, 'second': 1, 'third': 2} + >>> wd['__magic__'] + Traceback (most recent call last): + ... + KeyError: '__magic__' + +Testing ``AutoConst``:: + + >>> from autoconst import AutoConst + +# tag::AUTOCONST[] + >>> class Flavor(AutoConst): + ... banana + ... coconut + ... vanilla + ... + >>> Flavor.vanilla + 2 + >>> Flavor.banana, Flavor.coconut + (0, 1) + +# end::AUTOCONST[] + +""" + +from autoconst import AutoConst + + +class Flavor(AutoConst): + banana + coconut + vanilla + + +print('Flavor.vanilla ==', Flavor.vanilla) \ No newline at end of file diff --git a/25-class-metaprog/bulkfood/README.md b/25-class-metaprog/bulkfood/README.md new file mode 100644 index 0000000..61104c8 --- /dev/null +++ b/25-class-metaprog/bulkfood/README.md @@ -0,0 +1,34 @@ +# Legacy Class Descriptor and Metaclass Examples + +Examples from _Fluent Python, First Edition_—Chapter 21, _Class Metaprogramming_, +that are mentioned in _Fluent Python, Second Edition_—Chapter 25, _Class Metaprogramming_. + +These examples were developed with Python 3.4. +They run correctly in Python 3.9, but now it is easier to fullfill the same requirements +without resorting to class decorators or metaclasses. + +I have preserved them here as examples of class metaprogramming techniques +that you may find in legacy code, and that can be refactored to simpler code +using a base class with `__init_subclass__` and decorators implementing `__set_name__`. + +## Suggested Exercise + +If you'd like to practice the concepts presented in chapters 24 and 25 of +_Fluent Python, Second Edition_, +you may to refactor the most advanced example, `model_v8.py` with these changes: + +1. Simplify the `AutoStorage` descriptor by implementing `__set_name__`. +This will allow you to simplify the `EntityMeta` metaclass as well. + +2. Rewrite the `Entity` class to use `__init_subclass__` instead of the `EntityMeta` metaclass—which you can then delete. + +Nothing should change in the `bulkfood_v8.py` code, and its doctests should still pass. + +To run the doctests while refactoring, it's often convenient to pass the `-f` option, +to exit the test runner on the first failing test. + +``` +$ python3 -m doctest -f bulkfood_v8.py +``` + +Enjoy! diff --git a/25-class-metaprog/bulkfood/bulkfood_v6.py b/25-class-metaprog/bulkfood/bulkfood_v6.py new file mode 100644 index 0000000..18b2f40 --- /dev/null +++ b/25-class-metaprog/bulkfood/bulkfood_v6.py @@ -0,0 +1,84 @@ +""" + +A line item for a bulk food order has description, weight and price fields:: + + >>> raisins = LineItem('Golden raisins', 10, 6.95) + >>> raisins.weight, raisins.description, raisins.price + (10, 'Golden raisins', 6.95) + +A ``subtotal`` method gives the total price for that line item:: + + >>> raisins.subtotal() + 69.5 + +The weight of a ``LineItem`` must be greater than 0:: + + >>> raisins.weight = -20 + Traceback (most recent call last): + ... + ValueError: value must be > 0 + +No change was made:: + + >>> raisins.weight + 10 + +The value of the attributes managed by the descriptors are stored in +alternate attributes, created by the descriptors in each ``LineItem`` +instance:: + +# tag::LINEITEM_V6_DEMO[] + >>> raisins = LineItem('Golden raisins', 10, 6.95) + >>> dir(raisins)[:3] + ['_NonBlank#description', '_Quantity#price', '_Quantity#weight'] + >>> LineItem.description.storage_name + '_NonBlank#description' + >>> raisins.description + 'Golden raisins' + >>> getattr(raisins, '_NonBlank#description') + 'Golden raisins' + +# end::LINEITEM_V6_DEMO[] + +If the descriptor is accessed in the class, the descriptor object is +returned: + + >>> LineItem.weight # doctest: +ELLIPSIS + + >>> LineItem.weight.storage_name + '_Quantity#weight' + + +The `NonBlank` descriptor prevents empty or blank strings to be used +for the description: + + >>> br_nuts = LineItem('Brazil Nuts', 10, 34.95) + >>> br_nuts.description = ' ' + Traceback (most recent call last): + ... + ValueError: value cannot be empty or blank + >>> void = LineItem('', 1, 1) + Traceback (most recent call last): + ... + ValueError: value cannot be empty or blank + + +""" + +# tag::LINEITEM_V6[] +import model_v6 as model + +@model.entity # <1> +class LineItem: + description = model.NonBlank() + weight = model.Quantity() + price = model.Quantity() + + def __init__(self, description, weight, price): + self.description = description + self.weight = weight + self.price = price + + def subtotal(self): + return self.weight * self.price +# end::LINEITEM_V6[] diff --git a/25-class-metaprog/bulkfood/bulkfood_v7.py b/25-class-metaprog/bulkfood/bulkfood_v7.py new file mode 100644 index 0000000..cbb5d82 --- /dev/null +++ b/25-class-metaprog/bulkfood/bulkfood_v7.py @@ -0,0 +1,79 @@ +""" + +A line item for a bulk food order has description, weight and price fields:: + + >>> raisins = LineItem('Golden raisins', 10, 6.95) + >>> raisins.weight, raisins.description, raisins.price + (10, 'Golden raisins', 6.95) + +A ``subtotal`` method gives the total price for that line item:: + + >>> raisins.subtotal() + 69.5 + +The weight of a ``LineItem`` must be greater than 0:: + + >>> raisins.weight = -20 + Traceback (most recent call last): + ... + ValueError: value must be > 0 + +No change was made:: + + >>> raisins.weight + 10 + +The value of the attributes managed by the descriptors are stored in +alternate attributes, created by the descriptors in each ``LineItem`` +instance:: + + >>> raisins = LineItem('Golden raisins', 10, 6.95) + >>> dir(raisins)[:3] + ['_NonBlank#description', '_Quantity#price', '_Quantity#weight'] + >>> LineItem.description.storage_name + '_NonBlank#description' + >>> raisins.description + 'Golden raisins' + >>> getattr(raisins, '_NonBlank#description') + 'Golden raisins' + +If the descriptor is accessed in the class, the descriptor object is +returned: + + >>> LineItem.weight # doctest: +ELLIPSIS + + >>> LineItem.weight.storage_name + '_Quantity#weight' + + +The `NonBlank` descriptor prevents empty or blank strings to be used +for the description: + + >>> br_nuts = LineItem('Brazil Nuts', 10, 34.95) + >>> br_nuts.description = ' ' + Traceback (most recent call last): + ... + ValueError: value cannot be empty or blank + >>> void = LineItem('', 1, 1) + Traceback (most recent call last): + ... + ValueError: value cannot be empty or blank + +""" + +# tag::LINEITEM_V7[] +import model_v7 as model + +class LineItem(model.Entity): # <1> + description = model.NonBlank() + weight = model.Quantity() + price = model.Quantity() + + def __init__(self, description, weight, price): + self.description = description + self.weight = weight + self.price = price + + def subtotal(self): + return self.weight * self.price +# end::LINEITEM_V7[] diff --git a/25-class-metaprog/bulkfood/bulkfood_v8.py b/25-class-metaprog/bulkfood/bulkfood_v8.py new file mode 100644 index 0000000..f6ed4d6 --- /dev/null +++ b/25-class-metaprog/bulkfood/bulkfood_v8.py @@ -0,0 +1,86 @@ +""" + +A line item for a bulk food order has description, weight and price fields:: + + >>> raisins = LineItem('Golden raisins', 10, 6.95) + >>> raisins.weight, raisins.description, raisins.price + (10, 'Golden raisins', 6.95) + +A ``subtotal`` method gives the total price for that line item:: + + >>> raisins.subtotal() + 69.5 + +The weight of a ``LineItem`` must be greater than 0:: + + >>> raisins.weight = -20 + Traceback (most recent call last): + ... + ValueError: value must be > 0 + +No change was made:: + + >>> raisins.weight + 10 + + >>> raisins = LineItem('Golden raisins', 10, 6.95) + >>> dir(raisins)[:3] + ['_NonBlank#description', '_Quantity#price', '_Quantity#weight'] + >>> LineItem.description.storage_name + '_NonBlank#description' + >>> raisins.description + 'Golden raisins' + >>> getattr(raisins, '_NonBlank#description') + 'Golden raisins' + +If the descriptor is accessed in the class, the descriptor object is +returned: + + >>> LineItem.weight # doctest: +ELLIPSIS + + >>> LineItem.weight.storage_name + '_Quantity#weight' + + +The `NonBlank` descriptor prevents empty or blank strings to be used +for the description: + + >>> br_nuts = LineItem('Brazil Nuts', 10, 34.95) + >>> br_nuts.description = ' ' + Traceback (most recent call last): + ... + ValueError: value cannot be empty or blank + >>> void = LineItem('', 1, 1) + Traceback (most recent call last): + ... + ValueError: value cannot be empty or blank + + +Fields can be retrieved in the order they were declared: + +# tag::LINEITEM_V8_DEMO[] + >>> for name in LineItem.field_names(): + ... print(name) + ... + description + weight + price + +# end::LINEITEM_V8_DEMO[] + +""" + +import model_v8 as model + +class LineItem(model.Entity): + description = model.NonBlank() + weight = model.Quantity() + price = model.Quantity() + + def __init__(self, description, weight, price): + self.description = description + self.weight = weight + self.price = price + + def subtotal(self): + return self.weight * self.price diff --git a/25-class-metaprog/bulkfood/model_v6.py b/25-class-metaprog/bulkfood/model_v6.py new file mode 100644 index 0000000..716cf89 --- /dev/null +++ b/25-class-metaprog/bulkfood/model_v6.py @@ -0,0 +1,60 @@ +import abc + + +class AutoStorage: + __counter = 0 + + def __init__(self): + cls = self.__class__ + prefix = cls.__name__ + index = cls.__counter + self.storage_name = '_{}#{}'.format(prefix, index) + cls.__counter += 1 + + def __get__(self, instance, owner): + if instance is None: + return self + else: + return getattr(instance, self.storage_name) + + def __set__(self, instance, value): + setattr(instance, self.storage_name, value) + + +class Validated(abc.ABC, AutoStorage): + + def __set__(self, instance, value): + value = self.validate(instance, value) + super().__set__(instance, value) + + @abc.abstractmethod + def validate(self, instance, value): + """return validated value or raise ValueError""" + + +class Quantity(Validated): + """a number greater than zero""" + + def validate(self, instance, value): + if value <= 0: + raise ValueError('value must be > 0') + return value + + +class NonBlank(Validated): + """a string with at least one non-space character""" + + def validate(self, instance, value): + value = value.strip() + if len(value) == 0: + raise ValueError('value cannot be empty or blank') + return value + +# tag::MODEL_V6[] +def entity(cls): # <1> + for key, attr in cls.__dict__.items(): # <2> + if isinstance(attr, Validated): # <3> + type_name = type(attr).__name__ + attr.storage_name = '_{}#{}'.format(type_name, key) # <4> + return cls # <5> +# end::MODEL_V6[] diff --git a/25-class-metaprog/bulkfood/model_v7.py b/25-class-metaprog/bulkfood/model_v7.py new file mode 100644 index 0000000..9e8e34d --- /dev/null +++ b/25-class-metaprog/bulkfood/model_v7.py @@ -0,0 +1,66 @@ +import abc + + +class AutoStorage: + __counter = 0 + + def __init__(self): + cls = self.__class__ + prefix = cls.__name__ + index = cls.__counter + self.storage_name = '_{}#{}'.format(prefix, index) + cls.__counter += 1 + + def __get__(self, instance, owner): + if instance is None: + return self + else: + return getattr(instance, self.storage_name) + + def __set__(self, instance, value): + setattr(instance, self.storage_name, value) + + +class Validated(abc.ABC, AutoStorage): + + def __set__(self, instance, value): + value = self.validate(instance, value) + super().__set__(instance, value) + + @abc.abstractmethod + def validate(self, instance, value): + """return validated value or raise ValueError""" + + +class Quantity(Validated): + """a number greater than zero""" + + def validate(self, instance, value): + if value <= 0: + raise ValueError('value must be > 0') + return value + + +class NonBlank(Validated): + """a string with at least one non-space character""" + + def validate(self, instance, value): + value = value.strip() + if len(value) == 0: + raise ValueError('value cannot be empty or blank') + return value + +# tag::MODEL_V7[] +class EntityMeta(type): + """Metaclass for business entities with validated fields""" + + def __init__(cls, name, bases, attr_dict): + super().__init__(name, bases, attr_dict) # <1> + for key, attr in attr_dict.items(): # <2> + if isinstance(attr, Validated): + type_name = type(attr).__name__ + attr.storage_name = '_{}#{}'.format(type_name, key) + +class Entity(metaclass=EntityMeta): # <3> + """Business entity with validated fields""" +# end::MODEL_V7[] diff --git a/25-class-metaprog/bulkfood/model_v8.py b/25-class-metaprog/bulkfood/model_v8.py new file mode 100644 index 0000000..cc7c332 --- /dev/null +++ b/25-class-metaprog/bulkfood/model_v8.py @@ -0,0 +1,80 @@ +import abc +import collections + + +class AutoStorage: + __counter = 0 + + def __init__(self): + cls = self.__class__ + prefix = cls.__name__ + index = cls.__counter + self.storage_name = '_{}#{}'.format(prefix, index) + cls.__counter += 1 + + def __get__(self, instance, owner): + if instance is None: + return self + else: + return getattr(instance, self.storage_name) + + def __set__(self, instance, value): + setattr(instance, self.storage_name, value) + + +class Validated(abc.ABC, AutoStorage): + + def __set__(self, instance, value): + value = self.validate(instance, value) + super().__set__(instance, value) + + @abc.abstractmethod + def validate(self, instance, value): + """return validated value or raise ValueError""" + + +class Quantity(Validated): + """a number greater than zero""" + + def validate(self, instance, value): + if value <= 0: + raise ValueError('value must be > 0') + return value + + +class NonBlank(Validated): + """a string with at least one non-space character""" + + def validate(self, instance, value): + value = value.strip() + if len(value) == 0: + raise ValueError('value cannot be empty or blank') + return value + +# tag::MODEL_V8[] +class EntityMeta(type): + """Metaclass for business entities with validated fields""" + + @classmethod + def __prepare__(cls, name, bases): + return collections.OrderedDict() # <1> + + def __init__(cls, name, bases, attr_dict): + super().__init__(name, bases, attr_dict) + cls._field_names = [] # <2> + for key, attr in attr_dict.items(): # <3> + if isinstance(attr, Validated): + type_name = type(attr).__name__ + attr.storage_name = '_{}#{}'.format(type_name, key) + cls._field_names.append(key) # <4> + + +class Entity(metaclass=EntityMeta): + """Business entity with validated fields""" + + @classmethod + def field_names(cls): # <5> + for name in cls._field_names: + yield name + +# end::MODEL_V8[] diff --git a/25-class-metaprog/checkeddeco/checkeddeco.py b/25-class-metaprog/checked/decorator/checkeddeco.py similarity index 52% rename from 25-class-metaprog/checkeddeco/checkeddeco.py rename to 25-class-metaprog/checked/decorator/checkeddeco.py index 3eeba15..401835d 100644 --- a/25-class-metaprog/checkeddeco/checkeddeco.py +++ b/25-class-metaprog/checked/decorator/checkeddeco.py @@ -8,13 +8,13 @@ ... class Movie: ... title: str ... year: int - ... megabucks: float + ... box_office: float ... - >>> movie = Movie(title='The Godfather', year=1972, megabucks=137) # <3> + >>> movie = Movie(title='The Godfather', year=1972, box_office=137) >>> movie.title 'The Godfather' - >>> movie # <4> - Movie(title='The Godfather', year=1972, megabucks=137.0) + >>> movie + Movie(title='The Godfather', year=1972, box_office=137.0) # end::MOVIE_DEFINITION[] @@ -23,14 +23,14 @@ # tag::MOVIE_TYPE_VALIDATION[] - >>> movie.year = 'MCMLXXII' # <1> + >>> blockbuster = Movie(title='Avatar', year=2009, box_office='billions') Traceback (most recent call last): ... - TypeError: 'MCMLXXII' is not compatible with year:int - >>> blockbuster = Movie(title='Avatar', year=2009, megabucks='billions') # <2> + TypeError: 'billions' is not compatible with box_office:float + >>> movie.year = 'MCMLXXII' Traceback (most recent call last): ... - TypeError: 'billions' is not compatible with megabucks:float + TypeError: 'MCMLXXII' is not compatible with year:int # end::MOVIE_TYPE_VALIDATION[] @@ -40,13 +40,13 @@ # tag::MOVIE_DEFAULTS[] >>> Movie(title='Life of Brian') - Movie(title='Life of Brian', year=0, megabucks=0.0) + Movie(title='Life of Brian', year=0, box_office=0.0) # end::MOVIE_DEFAULTS[] Providing extra arguments to the constructor is not allowed:: - >>> blockbuster = Movie(title='Avatar', year=2009, megabucks=2000, + >>> blockbuster = Movie(title='Avatar', year=2009, box_office=2000, ... director='James Cameron') Traceback (most recent call last): ... @@ -62,109 +62,90 @@ The `_as_dict` instance creates a `dict` from the attributes of a `Movie` object:: >>> movie._asdict() - {'title': 'The Godfather', 'year': 1972, 'megabucks': 137.0} + {'title': 'The Godfather', 'year': 1972, 'box_office': 137.0} """ from collections.abc import Callable # <1> from typing import Any, NoReturn, get_type_hints -MISSING = object() # <2> - - class Field: - def __init__(self, name: str, constructor: Callable) -> None: # <3> + def __init__(self, name: str, constructor: Callable) -> None: # <2> + if not callable(constructor) or constructor is type(None): + raise TypeError(f'{name!r} type hint must be callable') self.name = name self.constructor = constructor - def __set__(self, instance: Any, value: Any) -> None: # <4> - if value is MISSING: # <5> + def __set__(self, instance: Any, value: Any) -> None: # <3> + if value is ...: # <4> value = self.constructor() else: try: - value = self.constructor(value) # <6> + value = self.constructor(value) # <5> except (TypeError, ValueError) as e: type_name = self.constructor.__name__ msg = ( f'{value!r} is not compatible with {self.name}:{type_name}' ) raise TypeError(msg) from e - instance.__dict__[self.name] = value # <7> - - -# tag::CHECKED_DECORATOR_TOP[] -_methods_to_inject: list[Callable] = [] -_classmethods_to_inject: list[Callable] = [] + instance.__dict__[self.name] = value # <6> -def checked(cls: type) -> type: # <2> - for func in _methods_to_inject: - name = func.__name__ - setattr(cls, name, func) # <5> - for func in _classmethods_to_inject: - name = func.__name__ - setattr(cls, name, classmethod(func)) # <5> +# tag::CHECKED_DECORATOR[] +def checked(cls: type) -> type: # <1> + for name, constructor in _fields(cls).items(): # <2> + setattr(cls, name, Field(name, constructor)) # <3> - for name, constructor in _fields(cls).items(): # <4> - setattr(cls, name, Field(name, constructor)) # <5> - - return cls - - -def _method(func: Callable) -> Callable: - _methods_to_inject.append(func) - return func + cls._fields = classmethod(_fields) #type: ignore # <4> + instance_methods = ( # <5> + __init__, + __repr__, + __setattr__, + _asdict, + __flag_unknown_attrs, + ) + for method in instance_methods: # <6> + setattr(cls, method.__name__, method) -def _classmethod(func: Callable) -> Callable: - _classmethods_to_inject.append(func) - return func + return cls # <7> +# end::CHECKED_DECORATOR[] -# tag::CHECKED_METHODS_TOP[] -@_classmethod -def _fields(cls: type) -> dict[str, type]: # <1> +# tag::CHECKED_METHODS[] +def _fields(cls: type) -> dict[str, type]: return get_type_hints(cls) -@_method def __init__(self: Any, **kwargs: Any) -> None: - for name in self._fields(): # <6> - value = kwargs.pop(name, MISSING) # <7> - setattr(self, name, value) # <8> - if kwargs: # <9> - self.__flag_unknown_attrs(*kwargs) # <10> - -@_method -def __setattr__(self: Any, name: str, value: Any) -> None: # <11> - if name in self._fields(): # <12> + for name in self._fields(): + value = kwargs.pop(name, ...) + setattr(self, name, value) + if kwargs: + self.__flag_unknown_attrs(*kwargs) + +def __setattr__(self: Any, name: str, value: Any) -> None: + if name in self._fields(): cls = self.__class__ descriptor = getattr(cls, name) - descriptor.__set__(self, value) # <13> - else: # <14> + descriptor.__set__(self, value) + else: self.__flag_unknown_attrs(name) -# end::CHECKED_METHODS_TOP[] -# tag::CHECKED_METHODS_BOTTOM[] -@_method -def __flag_unknown_attrs(self: Any, *names: str) -> NoReturn: # <1> +def __flag_unknown_attrs(self: Any, *names: str) -> NoReturn: plural = 's' if len(names) > 1 else '' extra = ', '.join(f'{name!r}' for name in names) cls_name = repr(self.__class__.__name__) raise AttributeError(f'{cls_name} has no attribute{plural} {extra}') - -@_method -def _asdict(self: Any) -> dict[str, Any]: # <2> +def _asdict(self: Any) -> dict[str, Any]: return { name: getattr(self, name) for name, attr in self.__class__.__dict__.items() if isinstance(attr, Field) } - -@_method -def __repr__(self: Any) -> str: # <3> +def __repr__(self: Any) -> str: kwargs = ', '.join( f'{key}={value!r}' for key, value in self._asdict().items() ) return f'{self.__class__.__name__}({kwargs})' -# end::CHECKED_METHODS_BOTTOM[] +# end::CHECKED_METHODS[] diff --git a/25-class-metaprog/checked/decorator/checkeddeco_demo.py b/25-class-metaprog/checked/decorator/checkeddeco_demo.py new file mode 100755 index 0000000..b72acbc --- /dev/null +++ b/25-class-metaprog/checked/decorator/checkeddeco_demo.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 + +from checkeddeco import checked + +@checked +class Movie: + title: str + year: int + box_office: float + + +if __name__ == '__main__': + # No static type checker can understand this... + movie = Movie(title='The Godfather', year=1972, box_office=137) # type: ignore + print(movie.title) + print(movie) + try: + # remove the "type: ignore" comment to see Mypy correctly spot the error + movie.year = 'MCMLXXII' # type: ignore + except TypeError as e: + print(e) + try: + # Again, no static type checker can understand this... + blockbuster = Movie(title='Avatar', year=2009, box_office='billions') # type: ignore + except TypeError as e: + print(e) diff --git a/25-class-metaprog/checkeddeco/checkeddeco_test.py b/25-class-metaprog/checked/decorator/checkeddeco_test.py similarity index 79% rename from 25-class-metaprog/checkeddeco/checkeddeco_test.py rename to 25-class-metaprog/checked/decorator/checkeddeco_test.py index 7f4d8dc..b79aae3 100644 --- a/25-class-metaprog/checkeddeco/checkeddeco_test.py +++ b/25-class-metaprog/checked/decorator/checkeddeco_test.py @@ -38,3 +38,13 @@ class Cat: felix = Cat(name='Felix', weight=3.2, age=7) assert str(e.value) == "'Cat' has no attribute 'age'" + + +def test_field_invalid_constructor(): + with pytest.raises(TypeError) as e: + @checked + class Cat: + name: str + weight: None + + assert str(e.value) == "'weight' type hint must be callable" \ No newline at end of file diff --git a/25-class-metaprog/checked/checkedlib_demo.py b/25-class-metaprog/checked/initsub/checked_demo.py old mode 100644 new mode 100755 similarity index 65% rename from 25-class-metaprog/checked/checkedlib_demo.py rename to 25-class-metaprog/checked/initsub/checked_demo.py index 203f90b..d21279f --- a/25-class-metaprog/checked/checkedlib_demo.py +++ b/25-class-metaprog/checked/initsub/checked_demo.py @@ -1,13 +1,15 @@ +#!/usr/bin/env python3 + from checkedlib import Checked class Movie(Checked): title: str year: int - megabucks: float + box_office: float if __name__ == '__main__': - movie = Movie(title='The Godfather', year=1972, megabucks=137) + movie = Movie(title='The Godfather', year=1972, box_office=137) print(movie.title) print(movie) try: @@ -16,6 +18,6 @@ class Movie(Checked): except TypeError as e: print(e) try: - blockbuster = Movie(title='Avatar', year=2009, megabucks='billions') + blockbuster = Movie(title='Avatar', year=2009, box_office='billions') except TypeError as e: print(e) diff --git a/25-class-metaprog/checked/checkedlib.py b/25-class-metaprog/checked/initsub/checkedlib.py similarity index 66% rename from 25-class-metaprog/checked/checkedlib.py rename to 25-class-metaprog/checked/initsub/checkedlib.py index 63442e3..01bc0a8 100644 --- a/25-class-metaprog/checked/checkedlib.py +++ b/25-class-metaprog/checked/initsub/checkedlib.py @@ -7,29 +7,29 @@ >>> class Movie(Checked): # <1> ... title: str # <2> ... year: int - ... megabucks: float + ... box_office: float ... - >>> movie = Movie(title='The Godfather', year=1972, megabucks=137) # <3> + >>> movie = Movie(title='The Godfather', year=1972, box_office=137) # <3> >>> movie.title 'The Godfather' >>> movie # <4> - Movie(title='The Godfather', year=1972, megabucks=137.0) + Movie(title='The Godfather', year=1972, box_office=137.0) # end::MOVIE_DEFINITION[] -The type of arguments is runtime checked when an attribute is set, -including during instantiation:: +The type of arguments is runtime checked during instantiation +and when an attribute is set:: # tag::MOVIE_TYPE_VALIDATION[] - >>> movie.year = 'MCMLXXII' # <1> + >>> blockbuster = Movie(title='Avatar', year=2009, box_office='billions') Traceback (most recent call last): ... - TypeError: 'MCMLXXII' is not compatible with year:int - >>> blockbuster = Movie(title='Avatar', year=2009, megabucks='billions') # <2> + TypeError: 'billions' is not compatible with box_office:float + >>> movie.year = 'MCMLXXII' Traceback (most recent call last): ... - TypeError: 'billions' is not compatible with megabucks:float + TypeError: 'MCMLXXII' is not compatible with year:int # end::MOVIE_TYPE_VALIDATION[] @@ -39,29 +39,29 @@ # tag::MOVIE_DEFAULTS[] >>> Movie(title='Life of Brian') - Movie(title='Life of Brian', year=0, megabucks=0.0) + Movie(title='Life of Brian', year=0, box_office=0.0) # end::MOVIE_DEFAULTS[] Providing extra arguments to the constructor is not allowed:: - >>> blockbuster = Movie(title='Avatar', year=2009, megabucks=2000, + >>> blockbuster = Movie(title='Avatar', year=2009, box_office=2000, ... director='James Cameron') Traceback (most recent call last): ... - AttributeError: 'Movie' has no attribute 'director' + AttributeError: 'Movie' object has no attribute 'director' Creating new attributes at runtime is restricted as well:: >>> movie.director = 'Francis Ford Coppola' Traceback (most recent call last): ... - AttributeError: 'Movie' has no attribute 'director' + AttributeError: 'Movie' object has no attribute 'director' The `_as_dict` instance creates a `dict` from the attributes of a `Movie` object:: >>> movie._asdict() - {'title': 'The Godfather', 'year': 1972, 'megabucks': 137.0} + {'title': 'The Godfather', 'year': 1972, 'box_office': 137.0} """ @@ -69,27 +69,25 @@ from collections.abc import Callable # <1> from typing import Any, NoReturn, get_type_hints -MISSING = object() # <2> - class Field: - def __init__(self, name: str, constructor: Callable) -> None: # <3> + def __init__(self, name: str, constructor: Callable) -> None: # <2> + if not callable(constructor) or constructor is type(None): # <3> + raise TypeError(f'{name!r} type hint must be callable') self.name = name self.constructor = constructor - def __set__(self, instance: 'Checked', value: Any) -> None: # <4> - if value is MISSING: # <5> + def __set__(self, instance: Any, value: Any) -> None: + if value is ...: # <4> value = self.constructor() else: try: - value = self.constructor(value) # <6> - except (TypeError, ValueError) as e: + value = self.constructor(value) # <5> + except (TypeError, ValueError) as e: # <6> type_name = self.constructor.__name__ msg = f'{value!r} is not compatible with {self.name}:{type_name}' raise TypeError(msg) from e instance.__dict__[self.name] = value # <7> - - # end::CHECKED_FIELD[] # tag::CHECKED_TOP[] @@ -105,36 +103,36 @@ def __init_subclass__(subclass) -> None: # <2> def __init__(self, **kwargs: Any) -> None: for name in self._fields(): # <6> - value = kwargs.pop(name, MISSING) # <7> + value = kwargs.pop(name, ...) # <7> setattr(self, name, value) # <8> if kwargs: # <9> self.__flag_unknown_attrs(*kwargs) # <10> - def __setattr__(self, name: str, value: Any) -> None: # <11> - if name in self._fields(): # <12> + # end::CHECKED_TOP[] + + # tag::CHECKED_BOTTOM[] + def __setattr__(self, name: str, value: Any) -> None: # <1> + if name in self._fields(): # <2> cls = self.__class__ descriptor = getattr(cls, name) - descriptor.__set__(self, value) # <13> - else: # <14> + descriptor.__set__(self, value) # <3> + else: # <4> self.__flag_unknown_attrs(name) - # end::CHECKED_TOP[] - - # tag::CHECKED_BOTTOM[] - def __flag_unknown_attrs(self, *names: str) -> NoReturn: # <1> + def __flag_unknown_attrs(self, *names: str) -> NoReturn: # <5> plural = 's' if len(names) > 1 else '' extra = ', '.join(f'{name!r}' for name in names) cls_name = repr(self.__class__.__name__) - raise AttributeError(f'{cls_name} has no attribute{plural} {extra}') + raise AttributeError(f'{cls_name} object has no attribute{plural} {extra}') - def _asdict(self) -> dict[str, Any]: # <2> + def _asdict(self) -> dict[str, Any]: # <6> return { name: getattr(self, name) for name, attr in self.__class__.__dict__.items() if isinstance(attr, Field) } - def __repr__(self) -> str: # <3> + def __repr__(self) -> str: # <7> kwargs = ', '.join( f'{key}={value!r}' for key, value in self._asdict().items() ) diff --git a/25-class-metaprog/checked/initsub/checkedlib_test.py b/25-class-metaprog/checked/initsub/checkedlib_test.py new file mode 100644 index 0000000..803b1c9 --- /dev/null +++ b/25-class-metaprog/checked/initsub/checkedlib_test.py @@ -0,0 +1,59 @@ +import pytest + + +from checkedlib import Checked + + +def test_field_validation_type_error(): + class Cat(Checked): + name: str + weight: float + + with pytest.raises(TypeError) as e: + felix = Cat(name='Felix', weight=None) + + assert str(e.value) == 'None is not compatible with weight:float' + + +def test_field_validation_value_error(): + class Cat(Checked): + name: str + weight: float + + with pytest.raises(TypeError) as e: + felix = Cat(name='Felix', weight='half stone') + + assert str(e.value) == "'half stone' is not compatible with weight:float" + + +def test_constructor_attribute_error(): + class Cat(Checked): + name: str + weight: float + + with pytest.raises(AttributeError) as e: + felix = Cat(name='Felix', weight=3.2, age=7) + + assert str(e.value) == "'Cat' object has no attribute 'age'" + + +def test_assignment_attribute_error(): + class Cat(Checked): + name: str + weight: float + + felix = Cat(name='Felix', weight=3.2) + with pytest.raises(AttributeError) as e: + felix.color = 'tan' + + assert str(e.value) == "'Cat' object has no attribute 'color'" + + +def test_field_invalid_constructor(): + with pytest.raises(TypeError) as e: + class Cat(Checked): + name: str + weight: None + + assert str(e.value) == "'weight' type hint must be callable" + diff --git a/25-class-metaprog/checkeddeco/checkeddeco_demo.py b/25-class-metaprog/checked/metaclass/checked_demo.py old mode 100644 new mode 100755 similarity index 52% rename from 25-class-metaprog/checkeddeco/checkeddeco_demo.py rename to 25-class-metaprog/checked/metaclass/checked_demo.py index f4c45e7..27f39b3 --- a/25-class-metaprog/checkeddeco/checkeddeco_demo.py +++ b/25-class-metaprog/checked/metaclass/checked_demo.py @@ -1,22 +1,25 @@ -from checkeddeco import checked +#!/usr/bin/env python3 -@checked -class Movie: +# tag::MOVIE_DEMO[] +from checkedlib import Checked + +class Movie(Checked): title: str year: int - megabucks: float - + box_office: float if __name__ == '__main__': - movie = Movie(title='The Godfather', year=1972, megabucks=137) - print(movie.title) + movie = Movie(title='The Godfather', year=1972, box_office=137) print(movie) + print(movie.title) + # end::MOVIE_DEMO[] + try: # remove the "type: ignore" comment to see Mypy error movie.year = 'MCMLXXII' # type: ignore except TypeError as e: print(e) try: - blockbuster = Movie(title='Avatar', year=2009, megabucks='billions') + blockbuster = Movie(title='Avatar', year=2009, box_office='billions') except TypeError as e: print(e) diff --git a/25-class-metaprog/checked/metaclass/checkedlib.py b/25-class-metaprog/checked/metaclass/checkedlib.py new file mode 100644 index 0000000..34484ac --- /dev/null +++ b/25-class-metaprog/checked/metaclass/checkedlib.py @@ -0,0 +1,148 @@ +""" +A ``Checked`` subclass definition requires that keyword arguments are +used to create an instance, and provides a nice ``__repr__``:: + +# tag::MOVIE_DEFINITION[] + + >>> class Movie(Checked): # <1> + ... title: str # <2> + ... year: int + ... box_office: float + ... + >>> movie = Movie(title='The Godfather', year=1972, box_office=137) # <3> + >>> movie.title + 'The Godfather' + >>> movie # <4> + Movie(title='The Godfather', year=1972, box_office=137.0) + +# end::MOVIE_DEFINITION[] + +The type of arguments is runtime checked during instantiation +and when an attribute is set:: + +# tag::MOVIE_TYPE_VALIDATION[] + + >>> blockbuster = Movie(title='Avatar', year=2009, box_office='billions') + Traceback (most recent call last): + ... + TypeError: 'billions' is not compatible with box_office:float + >>> movie.year = 'MCMLXXII' + Traceback (most recent call last): + ... + TypeError: 'MCMLXXII' is not compatible with year:int + +# end::MOVIE_TYPE_VALIDATION[] + +Attributes not passed as arguments to the constructor are initialized with +default values:: + +# tag::MOVIE_DEFAULTS[] + + >>> Movie(title='Life of Brian') + Movie(title='Life of Brian', year=0, box_office=0.0) + +# end::MOVIE_DEFAULTS[] + +Providing extra arguments to the constructor is not allowed:: + + >>> blockbuster = Movie(title='Avatar', year=2009, box_office=2000, + ... director='James Cameron') + Traceback (most recent call last): + ... + AttributeError: 'Movie' object has no attribute 'director' + +Creating new attributes at runtime is restricted as well:: + + >>> movie.director = 'Francis Ford Coppola' + Traceback (most recent call last): + ... + AttributeError: 'Movie' object has no attribute 'director' + +The `_as_dict` instance creates a `dict` from the attributes of a `Movie` object:: + + >>> movie._asdict() + {'title': 'The Godfather', 'year': 1972, 'box_office': 137.0} + +""" + +from collections.abc import Callable +from typing import Any, NoReturn, get_type_hints + +# tag::CHECKED_FIELD[] +class Field: + def __init__(self, name: str, constructor: Callable) -> None: + if not callable(constructor) or constructor is type(None): + raise TypeError(f'{name!r} type hint must be callable') + self.name = name + self.storage_name = '_' + name # <1> + self.constructor = constructor + + def __get__(self, instance, owner=None): # <2> + return getattr(instance, self.storage_name) # <3> + + def __set__(self, instance: Any, value: Any) -> None: + if value is ...: + value = self.constructor() + else: + try: + value = self.constructor(value) + except (TypeError, ValueError) as e: + type_name = self.constructor.__name__ + msg = f'{value!r} is not compatible with {self.name}:{type_name}' + raise TypeError(msg) from e + setattr(instance, self.storage_name, value) # <4> +# end::CHECKED_FIELD[] + +# tag::CHECKED_META[] +class CheckedMeta(type): + + def __new__(meta_cls, cls_name, bases, cls_dict): # <1> + if '__slots__' not in cls_dict: # <2> + slots = [] + type_hints = cls_dict.get('__annotations__', {}) # <3> + for name, constructor in type_hints.items(): # <4> + field = Field(name, constructor) # <5> + cls_dict[name] = field # <6> + slots.append(field.storage_name) # <7> + + cls_dict['__slots__'] = slots # <8> + + return super().__new__( + meta_cls, cls_name, bases, cls_dict) # <9> +# end::CHECKED_META[] + +# tag::CHECKED_CLASS[] +class Checked(metaclass=CheckedMeta): + __slots__ = () # skip CheckedMeta.__new__ processing + + @classmethod + def _fields(cls) -> dict[str, type]: + return get_type_hints(cls) + + def __init__(self, **kwargs: Any) -> None: + for name in self._fields(): + value = kwargs.pop(name, ...) + setattr(self, name, value) + if kwargs: + self.__flag_unknown_attrs(*kwargs) + + def __flag_unknown_attrs(self, *names: str) -> NoReturn: + plural = 's' if len(names) > 1 else '' + extra = ', '.join(f'{name!r}' for name in names) + cls_name = repr(self.__class__.__name__) + raise AttributeError(f'{cls_name} object has no attribute{plural} {extra}') + + def _asdict(self) -> dict[str, Any]: + return { + name: getattr(self, name) + for name, attr in self.__class__.__dict__.items() + if isinstance(attr, Field) + } + + def __repr__(self) -> str: + kwargs = ', '.join( + f'{key}={value!r}' for key, value in self._asdict().items() + ) + return f'{self.__class__.__name__}({kwargs})' + +# end::CHECKED_CLASS[] diff --git a/25-class-metaprog/checked/checkedlib_test.py b/25-class-metaprog/checked/metaclass/checkedlib_test.py similarity index 58% rename from 25-class-metaprog/checked/checkedlib_test.py rename to 25-class-metaprog/checked/metaclass/checkedlib_test.py index 6635a66..0e3552f 100644 --- a/25-class-metaprog/checked/checkedlib_test.py +++ b/25-class-metaprog/checked/metaclass/checkedlib_test.py @@ -34,4 +34,25 @@ class Cat(Checked): with pytest.raises(AttributeError) as e: felix = Cat(name='Felix', weight=3.2, age=7) - assert str(e.value) == "'Cat' has no attribute 'age'" + assert str(e.value) == "'Cat' object has no attribute 'age'" + + +def test_assignment_attribute_error(): + class Cat(Checked): + name: str + weight: float + + felix = Cat(name='Felix', weight=3.2) + with pytest.raises(AttributeError) as e: + felix.color = 'tan' + + assert str(e.value) == "'Cat' object has no attribute 'color'" + + +def test_field_invalid_constructor(): + with pytest.raises(TypeError) as e: + class Cat(Checked): + name: str + weight: None + + assert str(e.value) == "'weight' type hint must be callable" \ No newline at end of file diff --git a/25-class-metaprog/evalsupport.py b/25-class-metaprog/evalsupport.py deleted file mode 100644 index a7f3b5b..0000000 --- a/25-class-metaprog/evalsupport.py +++ /dev/null @@ -1,29 +0,0 @@ -# tag::BEGINNING[] -print('<[100]> evalsupport module start') - -def deco_alpha(cls): - print('<[200]> deco_alpha') - - def inner_1(self): - print('<[300]> deco_alpha:inner_1') - - cls.method_y = inner_1 - return cls - -# end::BEGINNING[] -# tag::META_ALEPH[] -class MetaAleph(type): - print('<[400]> MetaAleph body') - - def __init__(cls, name, bases, dic): - print('<[500]> MetaAleph.__init__') - - def inner_2(self): - print('<[600]> MetaAleph.__init__:inner_2') - - cls.method_z = inner_2 - -# end::META_ALEPH[] -# tag::END[] -print('<[700]> evalsupport module end') -# end::END[] diff --git a/25-class-metaprog/evaltime.py b/25-class-metaprog/evaltime.py deleted file mode 100644 index 299098a..0000000 --- a/25-class-metaprog/evaltime.py +++ /dev/null @@ -1,49 +0,0 @@ -from evalsupport import deco_alpha - -print('<[1]> evaltime module start') - - -class ClassOne(): - print('<[2]> ClassOne body') - - def __init__(self): - print('<[3]> ClassOne.__init__') - - def __del__(self): - print('<[4]> ClassOne.__del__') - - def method_x(self): - print('<[5]> ClassOne.method_x') - - class ClassTwo(object): - print('<[6]> ClassTwo body') - - -@deco_alpha -class ClassThree(): - print('<[7]> ClassThree body') - - def method_y(self): - print('<[8]> ClassThree.method_y') - - -class ClassFour(ClassThree): - print('<[9]> ClassFour body') - - def method_y(self): - print('<[10]> ClassFour.method_y') - - -if __name__ == '__main__': - print('<[11]> ClassOne tests', 30 * '.') - one = ClassOne() - one.method_x() - print('<[12]> ClassThree tests', 30 * '.') - three = ClassThree() - three.method_y() - print('<[13]> ClassFour tests', 30 * '.') - four = ClassFour() - four.method_y() - - -print('<[14]> evaltime module end') diff --git a/25-class-metaprog/evaltime/builderlib.py b/25-class-metaprog/evaltime/builderlib.py new file mode 100644 index 0000000..5f19ba2 --- /dev/null +++ b/25-class-metaprog/evaltime/builderlib.py @@ -0,0 +1,50 @@ +# tag::BUILDERLIB_TOP[] +print('@ builderlib module start') + +class Builder: # <1> + print('@ Builder body') + + def __init_subclass__(cls): # <2> + print(f'@ Builder.__init_subclass__({cls!r})') + + def inner_0(self): # <3> + print(f'@ SuperA.__init_subclass__:inner_0({self!r})') + + cls.method_a = inner_0 + + def __init__(self): + super().__init__() + print(f'@ Builder.__init__({self!r})') + + +def deco(cls): # <4> + print(f'@ deco({cls!r})') + + def inner_1(self): # <5> + print(f'@ deco:inner_1({self!r})') + + cls.method_b = inner_1 + return cls # <6> +# end::BUILDERLIB_TOP[] + +# tag::BUILDERLIB_BOTTOM[] +class Descriptor: # <1> + print('@ Descriptor body') + + def __init__(self): # <2> + print(f'@ Descriptor.__init__({self!r})') + + def __set_name__(self, owner, name): # <3> + args = (self, owner, name) + print(f'@ Descriptor.__set_name__{args!r}') + + def __set__(self, instance, value): # <4> + args = (self, instance, value) + print(f'@ Descriptor.__set__{args!r}') + + def __repr__(self): + return '' + + +print('@ builderlib module end') +# end::BUILDERLIB_BOTTOM[] diff --git a/25-class-metaprog/evaltime/evaldemo.py b/25-class-metaprog/evaltime/evaldemo.py new file mode 100755 index 0000000..7be9ba1 --- /dev/null +++ b/25-class-metaprog/evaltime/evaldemo.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 + +from builderlib import Builder, deco, Descriptor + +print('# evaldemo module start') + +@deco # <1> +class Klass(Builder): # <2> + print('# Klass body') + + attr = Descriptor() # <3> + + def __init__(self): + super().__init__() + print(f'# Klass.__init__({self!r})') + + def __repr__(self): + return '' + + +def main(): # <4> + obj = Klass() + obj.method_a() + obj.method_b() + obj.attr = 999 + +if __name__ == '__main__': + main() + +print('# evaldemo module end') diff --git a/25-class-metaprog/evaltime/evaldemo_meta.py b/25-class-metaprog/evaltime/evaldemo_meta.py new file mode 100755 index 0000000..dade50f --- /dev/null +++ b/25-class-metaprog/evaltime/evaldemo_meta.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python3 + +from builderlib import Builder, deco, Descriptor +from metalib import MetaKlass # <1> + +print('# evaldemo_meta module start') + +@deco +class Klass(Builder, metaclass=MetaKlass): # <2> + print('# Klass body') + + attr = Descriptor() + + def __init__(self): + super().__init__() + print(f'# Klass.__init__({self!r})') + + def __repr__(self): + return '' + + +def main(): + obj = Klass() + obj.method_a() + obj.method_b() + obj.method_c() # <3> + obj.attr = 999 + + +if __name__ == '__main__': + main() + +print('# evaldemo_meta module end') diff --git a/25-class-metaprog/evaltime/metalib.py b/25-class-metaprog/evaltime/metalib.py new file mode 100644 index 0000000..82f4ebf --- /dev/null +++ b/25-class-metaprog/evaltime/metalib.py @@ -0,0 +1,43 @@ +# tag::METALIB_TOP[] +print('% metalib module start') + +import collections + +class NosyDict(collections.UserDict): + def __setitem__(self, key, value): + args = (self, key, value) + print(f'% NosyDict.__setitem__{args!r}') + super().__setitem__(key, value) + + def __repr__(self): + return '' +# end::METALIB_TOP[] + +# tag::METALIB_BOTTOM[] +class MetaKlass(type): + print('% MetaKlass body') + + @classmethod # <1> + def __prepare__(meta_cls, cls_name, bases): # <2> + args = (meta_cls, cls_name, bases) + print(f'% MetaKlass.__prepare__{args!r}') + return NosyDict() # <3> + + def __new__(meta_cls, cls_name, bases, cls_dict): # <4> + args = (meta_cls, cls_name, bases, cls_dict) + print(f'% MetaKlass.__new__{args!r}') + def inner_2(self): + print(f'% MetaKlass.__new__:inner_2({self!r})') + + cls = super().__new__(meta_cls, cls_name, bases, cls_dict.data) # <5> + + cls.method_c = inner_2 # <6> + + return cls # <7> + + def __repr__(cls): # <8> + cls_name = cls.__name__ + return f"" + +print('% metalib module end') +# end::METALIB_BOTTOM[] diff --git a/25-class-metaprog/evaltime_meta.py b/25-class-metaprog/evaltime_meta.py deleted file mode 100644 index 41ed098..0000000 --- a/25-class-metaprog/evaltime_meta.py +++ /dev/null @@ -1,53 +0,0 @@ -from evalsupport import deco_alpha -from evalsupport import MetaAleph - -print('<[1]> evaltime_meta module start') - - -@deco_alpha -class ClassThree(): - print('<[2]> ClassThree body') - - def method_y(self): - print('<[3]> ClassThree.method_y') - - -class ClassFour(ClassThree): - print('<[4]> ClassFour body') - - def method_y(self): - print('<[5]> ClassFour.method_y') - - -class ClassFive(metaclass=MetaAleph): - print('<[6]> ClassFive body') - - def __init__(self): - print('<[7]> ClassFive.__init__') - - def method_z(self): - print('<[8]> ClassFive.method_z') - - -class ClassSix(ClassFive): - print('<[9]> ClassSix body') - - def method_z(self): - print('<[10]> ClassSix.method_z') - - -if __name__ == '__main__': - print('<[11]> ClassThree tests', 30 * '.') - three = ClassThree() - three.method_y() - print('<[12]> ClassFour tests', 30 * '.') - four = ClassFour() - four.method_y() - print('<[13]> ClassFive tests', 30 * '.') - five = ClassFive() - five.method_z() - print('<[14]> ClassSix tests', 30 * '.') - six = ClassSix() - six.method_z() - -print('<[15]> evaltime_meta module end') diff --git a/25-class-metaprog/factories.py b/25-class-metaprog/factories.py index fb2a9c5..38ec763 100644 --- a/25-class-metaprog/factories.py +++ b/25-class-metaprog/factories.py @@ -27,35 +27,48 @@ """ + # tag::RECORD_FACTORY[] -def record_factory(cls_name, field_names): - try: - field_names = field_names.replace(',', ' ').split() # <1> - except AttributeError: # no .replace or .split - pass # assume it's already a sequence of strings - field_names = tuple(field_names) # <2> - if not all(s.isidentifier() for s in field_names): - raise ValueError('field_names must all be valid identifiers') - - def __init__(self, *args, **kwargs): # <3> +from typing import Union, Any +from collections.abc import Iterable, Iterator + +FieldNames = Union[str, Iterable[str]] # <1> + +def record_factory(cls_name: str, field_names: FieldNames) -> type[tuple]: # <2> + + slots = parse_identifiers(field_names) # <3> + + def __init__(self, *args, **kwargs) -> None: # <4> attrs = dict(zip(self.__slots__, args)) attrs.update(kwargs) for name, value in attrs.items(): setattr(self, name, value) - def __iter__(self): # <4> + def __iter__(self) -> Iterator[Any]: # <5> for name in self.__slots__: yield getattr(self, name) - def __repr__(self): # <5> - values = ', '.join('{}={!r}'.format(*i) for i - in zip(self.__slots__, self)) - return '{}({})'.format(self.__class__.__name__, values) + def __repr__(self): # <6> + values = ', '.join( + '{}={!r}'.format(*i) for i in zip(self.__slots__, self) + ) + cls_name = self.__class__.__name__ + return f'{cls_name}({values})' + + cls_attrs = dict( # <7> + __slots__=slots, + __init__=__init__, + __iter__=__iter__, + __repr__=__repr__, + ) + + return type(cls_name, (object,), cls_attrs) # <8> - cls_attrs = dict(__slots__ = field_names, # <6> - __init__ = __init__, - __iter__ = __iter__, - __repr__ = __repr__) - return type(cls_name, (object,), cls_attrs) # <7> +def parse_identifiers(names: FieldNames) -> tuple[str, ...]: + if isinstance(names, str): + names = names.replace(',', ' ').split() # <9> + if not all(s.isidentifier() for s in names): + raise ValueError('names must all be valid identifiers') + return tuple(names) # end::RECORD_FACTORY[] diff --git a/25-class-metaprog/factories_ducktyped.py b/25-class-metaprog/factories_ducktyped.py new file mode 100644 index 0000000..a7a509a --- /dev/null +++ b/25-class-metaprog/factories_ducktyped.py @@ -0,0 +1,79 @@ +""" +record_factory: create simple classes just for holding data fields + +# tag::RECORD_FACTORY_DEMO[] + >>> Dog = record_factory('Dog', 'name weight owner') # <1> + >>> rex = Dog('Rex', 30, 'Bob') + >>> rex # <2> + Dog(name='Rex', weight=30, owner='Bob') + >>> name, weight, _ = rex # <3> + >>> name, weight + ('Rex', 30) + >>> "{2}'s dog weighs {1}kg".format(*rex) # <4> + "Bob's dog weighs 30kg" + >>> rex.weight = 32 # <5> + >>> rex + Dog(name='Rex', weight=32, owner='Bob') + >>> Dog.__mro__ # <6> + (, ) + +# end::RECORD_FACTORY_DEMO[] + +The factory also accepts a list or tuple of identifiers: + + >>> Dog = record_factory('Dog', ['name', 'weight', 'owner']) + >>> Dog.__slots__ + ('name', 'weight', 'owner') + +""" + + +# tag::RECORD_FACTORY[] +from typing import Union, Any +from collections.abc import Sequence, Iterator + +FieldNames = Union[str, Sequence[str]] + + +def parse_identifiers(names): + try: + names = names.replace(',', ' ').split() # <1> + except AttributeError: # no .replace or .split + pass # assume it's already a sequence of strings + if not all(s.isidentifier() for s in names): + raise ValueError('names must all be valid identifiers') + return tuple(names) + + +def record_factory(cls_name, field_names): + + field_identifiers = parse_identifiers(field_names) + + def __init__(self, *args, **kwargs) -> None: # <4> + attrs = dict(zip(self.__slots__, args)) + attrs.update(kwargs) + for name, value in attrs.items(): + setattr(self, name, value) + + def __iter__(self) -> Iterator[Any]: # <5> + for name in self.__slots__: + yield getattr(self, name) + + def __repr__(self): # <6> + values = ', '.join( + '{}={!r}'.format(*i) for i in zip(self.__slots__, self) + ) + cls_name = self.__class__.__name__ + return f'{cls_name}({values})' + + cls_attrs = dict( + __slots__=field_identifiers, # <7> + __init__=__init__, + __iter__=__iter__, + __repr__=__repr__, + ) + + return type(cls_name, (object,), cls_attrs) # <8> + + +# end::RECORD_FACTORY[] diff --git a/25-class-metaprog/hours/hours.py b/25-class-metaprog/hours/hours.py new file mode 100644 index 0000000..c673144 --- /dev/null +++ b/25-class-metaprog/hours/hours.py @@ -0,0 +1,115 @@ +""" +Abusing ``__class_getitem__`` to make a nano-DSL for working +with hours, minutes, and seconds--these last two in base 60. + +``H`` is an alias for the ``Hours`` class:: + + >>> H[1] + 1:00 + >>> H[1:30] + 1:30 + >>> H[1::5] + 1:00:05 + >>> H[::5] + 0:00:05 + +An ``H`` instance can be converted to a float number of hours:: + + >>> float(H[1:15]) + 1.25 + >>> float(H[1:30:30]) # doctest: +ELLIPSIS + 1.5083333... + >>> float(H[1::5]) # doctest: +ELLIPSIS + 1.0013888... + +The ``H`` constructor accepts hours, minutes, and/or seconds:: + + >>> H(1.5) + 1:30 + >>> H(1.9) + 1:54 + >>> H(1, 30, 30) + 1:30:30 + >>> H(s = 7205) + 2:00:05 + >>> H(1/3) + 0:20 + >>> H(1/1000) + 0:00:03.6 + +An ``H`` instance is iterable, for convenient unpacking:: + + >>> hms = H[1:22:33] + >>> h, m, s = hms + >>> h, m, s + (1, 22, 33) + >>> tuple(hms) + (1, 22, 33) + + +``H`` instances can be added:: + + >>> H[1:45:12] + H[2:15:50] + 4:01:02 +""" + +from typing import Tuple, Union + + +def normalize(s: float) -> Tuple[int, int, float]: + h, r = divmod(s, 3600) + m, s = divmod(r, 60) + return int(h), int(m), s + + +def valid_base_60(n, unit): + if not (0 <= n < 60): + raise ValueError(f'invalid {unit} {n}') + return n + + +class Hours: + h: int + _m: int + _s: float + + def __class_getitem__(cls, parts: Union[slice, float]) -> 'Hours': + if isinstance(parts, slice): + h = parts.start or 0 + m = valid_base_60(parts.stop or 0, 'minutes') + s = valid_base_60(parts.step or 0, 'seconds') + else: + h, m, s = normalize(parts * 3600) + return Hours(h, m, s) + + def __init__(self, h: float = 0, m: float = 0, s: float = 0): + if h < 0 or m < 0 or s < 0: + raise ValueError('invalid negative argument') + self.h, self.m, self.s = normalize(h * 3600 + m * 60 + s) + + def __repr__(self): + h, m, s = self + display_s = f'{s:06.3f}' + display_s = display_s.rstrip('0').rstrip('.') + if display_s == '00': + return f'{h}:{m:02d}' + return f'{h}:{m:02d}:{display_s}' + + def __float__(self): + return self.h + self.m / 60 + self.s / 3600 + + def __eq__(self, other): + return repr(self) == repr(other) + + def __iter__(self): + yield self.h + yield self.m + yield self.s + + def __add__(self, other): + if not isinstance(other, Hours): + return NotImplemented + return Hours(*(a + b for a, b in zip(self, other))) + + +H = Hours diff --git a/25-class-metaprog/hours/hours_test.py b/25-class-metaprog/hours/hours_test.py new file mode 100644 index 0000000..dc57e3f --- /dev/null +++ b/25-class-metaprog/hours/hours_test.py @@ -0,0 +1,71 @@ +# content of test_expectation.py +from math import isclose + +import pytest + +from hours import normalize, H + +HOURS_TO_HMS = [ + [1, (1, 0, 0.0)], + [1.5, (1, 30, 0.0)], + [1.1, (1, 6, 0.0)], + [1.9, (1, 54, 0.0)], + [1.01, (1, 0, 36.0)], + [1.09, (1, 5, 24.0)], + [2 + 1/60, (2, 1, 0.0)], + [3 + 1/3600, (3, 0, 1.0)], + [1.251, (1, 15, 3.6)], +] + + +@pytest.mark.parametrize('hours, expected', HOURS_TO_HMS) +def test_normalize(hours, expected): + h, m, s = expected + got_h, got_m, got_s = normalize(hours * 3600) + assert (h, m) == (got_h, got_m) + assert isclose(s, got_s, abs_tol=1e-12) + got_hours = got_h + got_m / 60 + got_s / 3600 + assert isclose(hours, got_hours) + + +@pytest.mark.parametrize('h, expected', [ + (H[1], '1:00'), + (H[1:0], '1:00'), + (H[1:3], '1:03'), + (H[1:59], '1:59'), + (H[1:0:0], '1:00'), + (H[1:2:3], '1:02:03'), + (H[1:2:3.4], '1:02:03.4'), + (H[1:2:0.1], '1:02:00.1'), + (H[1:2:0.01], '1:02:00.01'), + (H[1:2:0.001], '1:02:00.001'), + (H[1:2:0.0001], '1:02'), +]) +def test_repr(h, expected): + assert expected == repr(h), f'seconds: {h.s}' + + +@pytest.mark.parametrize('expected, hms', HOURS_TO_HMS) +def test_float(expected, hms): + got = float(H[slice(*hms)]) + assert isclose(expected, got) + + +@pytest.mark.parametrize('hms, units', [ + ((0, 60, 0), 'minutes'), + ((0, 0, 60), 'seconds'), + ((0, 60, 60), 'minutes'), +]) +def test_class_getitem_errors(hms, units): + with pytest.raises(ValueError) as excinfo: + H[slice(*hms)] + assert units in str(excinfo.value) + + +@pytest.mark.parametrize('hms1, hms2, expected', [ + (H[0:30], H[0:15], H[0:45]), + (H[0:30], H[0:30], H[1:00]), + (H[0:59:59], H[0:00:1], H[1:00]), +]) +def test_add(hms1, hms2, expected): + assert expected == hms1 + hms2 diff --git a/25-class-metaprog/metabunch/README.md b/25-class-metaprog/metabunch/README.md new file mode 100644 index 0000000..e6cf753 --- /dev/null +++ b/25-class-metaprog/metabunch/README.md @@ -0,0 +1,20 @@ +# Examples from Python in a Nutshell, 3rd edition + +The metaclass `MetaBunch` example in `original/bunch.py` is an exact copy of the +last example in the _How a Metaclass Creates a Class_ section of +_Chapter 4: Object Oriented Python_ from +[_Python in a Nutshell, 3rd edition_](https://learning.oreilly.com/library/view/python-in-a/9781491913833) +by Alex Martelli, Anna Ravenscroft, and Steve Holden. + +The version in `pre3.6/bunch.py` is slightly simplified by taking advantage +of Python 3 `super()` and removing comments and docstrings, +to make it easier to compare to the `from3.6` version. + +The version in `from3.6/bunch.py` is further simplified by taking advantage +of the order-preserving `dict` that appeared in Python 3.6, +as well as other simplifications, +such as leveraging closures in `__init__` and `__repr__` +to avoid adding a `__defaults__` mapping to the class. + +The external behavior of all three versions is the same, and +the test files `bunch_test.py` are identical in the three directories. diff --git a/25-class-metaprog/metabunch/from3.6/bunch.py b/25-class-metaprog/metabunch/from3.6/bunch.py new file mode 100644 index 0000000..a8b3d5a --- /dev/null +++ b/25-class-metaprog/metabunch/from3.6/bunch.py @@ -0,0 +1,77 @@ +""" +The `MetaBunch` metaclass is a simplified version of the +last example in the _How a Metaclass Creates a Class_ section +of _Chapter 4: Object Oriented Python_ from +[_Python in a Nutshell, 3rd edition_](https://learning.oreilly.com/library/view/python-in-a/9781491913833) +by Alex Martelli, Anna Ravenscroft, and Steve Holden. + +Here are a few tests. ``bunch_test.py`` has a few more. + +# tag::BUNCH_POINT_DEMO_1[] + >>> class Point(Bunch): + ... x = 0.0 + ... y = 0.0 + ... color = 'gray' + ... + >>> Point(x=1.2, y=3, color='green') + Point(x=1.2, y=3, color='green') + >>> p = Point() + >>> p.x, p.y, p.color + (0.0, 0.0, 'gray') + >>> p + Point() + +# end::BUNCH_POINT_DEMO_1[] + +# tag::BUNCH_POINT_DEMO_2[] + + >>> Point(x=1, y=2, z=3) + Traceback (most recent call last): + ... + AttributeError: 'Point' object has no attribute 'z' + >>> p = Point(x=21) + >>> p.y = 42 + >>> p + Point(x=21, y=42) + >>> p.flavor = 'banana' + Traceback (most recent call last): + ... + AttributeError: 'Point' object has no attribute 'flavor' + +# end::BUNCH_POINT_DEMO_2[] +""" + +# tag::METABUNCH[] +class MetaBunch(type): # <1> + def __new__(meta_cls, cls_name, bases, cls_dict): # <2> + + defaults = {} # <3> + + def __init__(self, **kwargs): # <4> + for name, default in defaults.items(): # <5> + setattr(self, name, kwargs.pop(name, default)) + if kwargs: # <6> + setattr(self, *kwargs.popitem()) + + def __repr__(self): # <7> + rep = ', '.join(f'{name}={value!r}' + for name, default in defaults.items() + if (value := getattr(self, name)) != default) + return f'{cls_name}({rep})' + + new_dict = dict(__slots__=[], __init__=__init__, __repr__=__repr__) # <8> + + for name, value in cls_dict.items(): # <9> + if name.startswith('__') and name.endswith('__'): # <10> + if name in new_dict: + raise AttributeError(f"Can't set {name!r} in {cls_name!r}") + new_dict[name] = value + else: # <11> + new_dict['__slots__'].append(name) + defaults[name] = value + return super().__new__(meta_cls, cls_name, bases, new_dict) # <12> + + +class Bunch(metaclass=MetaBunch): # <13> + pass +# end::METABUNCH[] diff --git a/25-class-metaprog/metabunch/from3.6/bunch_test.py b/25-class-metaprog/metabunch/from3.6/bunch_test.py new file mode 100644 index 0000000..322487a --- /dev/null +++ b/25-class-metaprog/metabunch/from3.6/bunch_test.py @@ -0,0 +1,59 @@ +import pytest + +from bunch import Bunch + +class Point(Bunch): + """ A point has x and y coordinates, defaulting to 0.0, + and a color, defaulting to 'gray'—and nothing more, + except what Python and the metaclass conspire to add, + such as __init__ and __repr__ + """ + x = 0.0 + y = 0.0 + color = 'gray' + + +def test_init_defaults(): + p = Point() + assert repr(p) == 'Point()' + + +def test_init(): + p = Point(x=1.2, y=3.4, color='red') + assert repr(p) == "Point(x=1.2, y=3.4, color='red')" + + +def test_init_wrong_argument(): + with pytest.raises(AttributeError) as exc: + p = Point(x=1.2, y=3.4, flavor='coffee') + assert "no attribute 'flavor'" in str(exc.value) + + +def test_slots(): + p = Point() + with pytest.raises(AttributeError) as exc: + p.z = 5.6 + assert "no attribute 'z'" in str(exc.value) + + +def test_dunder_permitted(): + class Cat(Bunch): + name = '' + weight = 0 + + def __str__(self): + return f'{self.name} ({self.weight} kg)' + + cheshire = Cat(name='Cheshire') + assert str(cheshire) == 'Cheshire (0 kg)' + + +def test_dunder_forbidden(): + with pytest.raises(AttributeError) as exc: + class Cat(Bunch): + name = '' + weight = 0 + + def __init__(self): + pass + assert "Can't set '__init__' in 'Cat'" in str(exc.value) diff --git a/25-class-metaprog/metabunch/nutshell3e/bunch.py b/25-class-metaprog/metabunch/nutshell3e/bunch.py new file mode 100644 index 0000000..b6f09cb --- /dev/null +++ b/25-class-metaprog/metabunch/nutshell3e/bunch.py @@ -0,0 +1,85 @@ +import collections +import warnings + +class MetaBunch(type): + """ + Metaclass for new and improved "Bunch": implicitly defines + __slots__, __init__ and __repr__ from variables bound in + class scope. + A class statement for an instance of MetaBunch (i.e., for a + class whose metaclass is MetaBunch) must define only + class-scope data attributes (and possibly special methods, but + NOT __init__ and __repr__). MetaBunch removes the data + attributes from class scope, snuggles them instead as items in + a class-scope dict named __dflts__, and puts in the class a + __slots__ with those attributes' names, an __init__ that takes + as optional named arguments each of them (using the values in + __dflts__ as defaults for missing ones), and a __repr__ that + shows the repr of each attribute that differs from its default + value (the output of __repr__ can be passed to __eval__ to make + an equal instance, as per usual convention in the matter, if + each non-default-valued attribute respects the convention too). + + In v3, the order of data attributes remains the same as in the + class body; in v2, there is no such guarantee. + """ + def __prepare__(name, *bases, **kwargs): + # precious in v3—harmless although useless in v2 + return collections.OrderedDict() + + def __new__(mcl, classname, bases, classdict): + """ Everything needs to be done in __new__, since + type.__new__ is where __slots__ are taken into account. + """ + # define as local functions the __init__ and __repr__ that + # we'll use in the new class + def __init__(self, **kw): + """ Simplistic __init__: first set all attributes to + default values, then override those explicitly + passed in kw. + """ + for k in self.__dflts__: + setattr(self, k, self.__dflts__[k]) + for k in kw: + setattr(self, k, kw[k]) + def __repr__(self): + """ Clever __repr__: show only attributes that differ + from default values, for compactness. + """ + rep = ['{}={!r}'.format(k, getattr(self, k)) + for k in self.__dflts__ + if getattr(self, k) != self.__dflts__[k] + ] + return '{}({})'.format(classname, ', '.join(rep)) + # build the newdict that we'll use as class-dict for the + # new class + newdict = { '__slots__':[], + '__dflts__':collections.OrderedDict(), + '__init__':__init__, '__repr__':__repr__, } + for k in classdict: + if k.startswith('__') and k.endswith('__'): + # dunder methods: copy to newdict, or warn + # about conflicts + if k in newdict: + warnings.warn( + "Can't set attr {!r} in bunch-class {!r}". + format(k, classname)) + else: + newdict[k] = classdict[k] + else: + # class variables, store name in __slots__, and + # name and value as an item in __dflts__ + newdict['__slots__'].append(k) + newdict['__dflts__'][k] = classdict[k] + # finally delegate the rest of the work to type.__new__ + return super(MetaBunch, mcl).__new__( + mcl, classname, bases, newdict) + +class Bunch(metaclass=MetaBunch): + """ For convenience: inheriting from Bunch can be used to get + the new metaclass (same as defining metaclass= yourself). + + In v2, remove the (metaclass=MetaBunch) above and add + instead __metaclass__=MetaBunch as the class body. + """ + pass diff --git a/25-class-metaprog/metabunch/nutshell3e/bunch_test.py b/25-class-metaprog/metabunch/nutshell3e/bunch_test.py new file mode 100644 index 0000000..00bbcd2 --- /dev/null +++ b/25-class-metaprog/metabunch/nutshell3e/bunch_test.py @@ -0,0 +1,38 @@ +import pytest + +from bunch import Bunch + +class Point(Bunch): + """ A point has x and y coordinates, defaulting to 0.0, + and a color, defaulting to 'gray'—and nothing more, + except what Python and the metaclass conspire to add, + such as __init__ and __repr__ + """ + x = 0.0 + y = 0.0 + color = 'gray' + + +def test_init_defaults(): + p = Point() + assert repr(p) == 'Point()' + + +def test_init(): + p = Point(x=1.2, y=3.4, color='red') + assert repr(p) == "Point(x=1.2, y=3.4, color='red')" + + +def test_init_wrong_argument(): + with pytest.raises(AttributeError) as exc: + p = Point(x=1.2, y=3.4, flavor='coffee') + assert "no attribute 'flavor'" in str(exc.value) + + +def test_slots(): + p = Point() + with pytest.raises(AttributeError) as exc: + p.z = 5.6 + assert "no attribute 'z'" in str(exc.value) + + diff --git a/25-class-metaprog/metabunch/original/bunch.py b/25-class-metaprog/metabunch/original/bunch.py new file mode 100644 index 0000000..f58a5ec --- /dev/null +++ b/25-class-metaprog/metabunch/original/bunch.py @@ -0,0 +1,70 @@ +import warnings + +class metaMetaBunch(type): + """ + metaclass for new and improved "Bunch": implicitly defines + __slots__, __init__ and __repr__ from variables bound in class scope. + + An instance of metaMetaBunch (a class whose metaclass is metaMetaBunch) + defines only class-scope variables (and possibly special methods, but + NOT __init__ and __repr__!). metaMetaBunch removes those variables from + class scope, snuggles them instead as items in a class-scope dict named + __dflts__, and puts in the class a __slots__ listing those variables' + names, an __init__ that takes as optional keyword arguments each of + them (using the values in __dflts__ as defaults for missing ones), and + a __repr__ that shows the repr of each attribute that differs from its + default value (the output of __repr__ can be passed to __eval__ to make + an equal instance, as per the usual convention in the matter). + """ + + def __new__(cls, classname, bases, classdict): + """ Everything needs to be done in __new__, since type.__new__ is + where __slots__ are taken into account. + """ + + # define as local functions the __init__ and __repr__ that we'll + # use in the new class + + def __init__(self, **kw): + """ Simplistic __init__: first set all attributes to default + values, then override those explicitly passed in kw. + """ + for k in self.__dflts__: setattr(self, k, self.__dflts__[k]) + for k in kw: setattr(self, k, kw[k]) + + def __repr__(self): + """ Clever __repr__: show only attributes that differ from the + respective default values, for compactness. + """ + rep = [ '%s=%r' % (k, getattr(self, k)) for k in self.__dflts__ + if getattr(self, k) != self.__dflts__[k] + ] + return '%s(%s)' % (classname, ', '.join(rep)) + + # build the newdict that we'll use as class-dict for the new class + newdict = { '__slots__':[], '__dflts__':{}, + '__init__':__init__, '__repr__':__repr__, } + + for k in classdict: + if k.startswith('__'): + # special methods &c: copy to newdict, warn about conflicts + if k in newdict: + warnings.warn("Can't set attr %r in bunch-class %r" % ( + k, classname)) + else: + newdict[k] = classdict[k] + else: + # class variables, store name in __slots__ and name and + # value as an item in __dflts__ + newdict['__slots__'].append(k) + newdict['__dflts__'][k] = classdict[k] + + # finally delegate the rest of the work to type.__new__ + return type.__new__(cls, classname, bases, newdict) + + +class MetaBunch(metaclass=metaMetaBunch): + """ For convenience: inheriting from MetaBunch can be used to get + the new metaclass (same as defining __metaclass__ yourself). + """ + __metaclass__ = metaMetaBunch diff --git a/25-class-metaprog/metabunch/original/bunch_test.py b/25-class-metaprog/metabunch/original/bunch_test.py new file mode 100644 index 0000000..174f58d --- /dev/null +++ b/25-class-metaprog/metabunch/original/bunch_test.py @@ -0,0 +1,38 @@ +import pytest + +from bunch import MetaBunch + +class Point(MetaBunch): + """ A point has x and y coordinates, defaulting to 0.0, + and a color, defaulting to 'gray'—and nothing more, + except what Python and the metaclass conspire to add, + such as __init__ and __repr__ + """ + x = 0.0 + y = 0.0 + color = 'gray' + + +def test_init_defaults(): + p = Point() + assert repr(p) == 'Point()' + + +def test_init(): + p = Point(x=1.2, y=3.4, color='red') + assert repr(p) == "Point(x=1.2, y=3.4, color='red')" + + +def test_init_wrong_argument(): + with pytest.raises(AttributeError) as exc: + p = Point(x=1.2, y=3.4, flavor='coffee') + assert "no attribute 'flavor'" in str(exc.value) + + +def test_slots(): + p = Point() + with pytest.raises(AttributeError) as exc: + p.z = 5.6 + assert "no attribute 'z'" in str(exc.value) + + diff --git a/25-class-metaprog/metabunch/pre3.6/bunch.py b/25-class-metaprog/metabunch/pre3.6/bunch.py new file mode 100644 index 0000000..186d645 --- /dev/null +++ b/25-class-metaprog/metabunch/pre3.6/bunch.py @@ -0,0 +1,41 @@ +import collections +import warnings + +class MetaBunch(type): + def __prepare__(name, *bases, **kwargs): + return collections.OrderedDict() + + def __new__(meta_cls, cls_name, bases, cls_dict): + def __init__(self, **kw): + for k in self.__defaults__: + setattr(self, k, self.__defaults__[k]) + for k in kw: + setattr(self, k, kw[k]) + + def __repr__(self): + rep = ['{}={!r}'.format(k, getattr(self, k)) + for k in self.__defaults__ + if getattr(self, k) != self.__defaults__[k] + ] + return '{}({})'.format(cls_name, ', '.join(rep)) + + new_dict = { '__slots__':[], + '__defaults__':collections.OrderedDict(), + '__init__':__init__, '__repr__':__repr__, } + + for k in cls_dict: + if k.startswith('__') and k.endswith('__'): + if k in new_dict: + warnings.warn( + "Can't set attr {!r} in bunch-class {!r}". + format(k, cls_name)) + else: + new_dict[k] = cls_dict[k] + else: + new_dict['__slots__'].append(k) + new_dict['__defaults__'][k] = cls_dict[k] + + return super().__new__(meta_cls, cls_name, bases, new_dict) + +class Bunch(metaclass=MetaBunch): + pass diff --git a/25-class-metaprog/metabunch/pre3.6/bunch_test.py b/25-class-metaprog/metabunch/pre3.6/bunch_test.py new file mode 100644 index 0000000..00bbcd2 --- /dev/null +++ b/25-class-metaprog/metabunch/pre3.6/bunch_test.py @@ -0,0 +1,38 @@ +import pytest + +from bunch import Bunch + +class Point(Bunch): + """ A point has x and y coordinates, defaulting to 0.0, + and a color, defaulting to 'gray'—and nothing more, + except what Python and the metaclass conspire to add, + such as __init__ and __repr__ + """ + x = 0.0 + y = 0.0 + color = 'gray' + + +def test_init_defaults(): + p = Point() + assert repr(p) == 'Point()' + + +def test_init(): + p = Point(x=1.2, y=3.4, color='red') + assert repr(p) == "Point(x=1.2, y=3.4, color='red')" + + +def test_init_wrong_argument(): + with pytest.raises(AttributeError) as exc: + p = Point(x=1.2, y=3.4, flavor='coffee') + assert "no attribute 'flavor'" in str(exc.value) + + +def test_slots(): + p = Point() + with pytest.raises(AttributeError) as exc: + p.z = 5.6 + assert "no attribute 'z'" in str(exc.value) + + diff --git a/25-class-metaprog/persistent/persistlib.py b/25-class-metaprog/persistent/persistlib.py index 15c9d38..0179ca3 100644 --- a/25-class-metaprog/persistent/persistlib.py +++ b/25-class-metaprog/persistent/persistlib.py @@ -4,16 +4,16 @@ >>> class Movie(Persistent): ... title: str ... year: int - ... megabucks: float + ... box_office: float Implemented behavior:: >>> Movie._connect() # doctest: +ELLIPSIS - >>> movie = Movie(title='The Godfather', year=1972, megabucks=137) + >>> movie = Movie(title='The Godfather', year=1972, box_office=137) >>> movie.title 'The Godfather' - >>> movie.megabucks + >>> movie.box_office 137.0 Instances always have a ``._pk`` attribute, but it is ``None`` until the @@ -32,7 +32,7 @@ >>> del movie >>> film = Movie[1] >>> film - Movie(title='The Godfather', year=1972, megabucks=137.0, _pk=1) + Movie(title='The Godfather', year=1972, box_office=137.0, _pk=1) By default, the table name is the class name lowercased, with an appended "s" for plural:: @@ -84,35 +84,12 @@ def _fields(cls) -> dict[str, type]: if not name.startswith('_') } - def __init_subclass__(cls, *, table: str = '', **kwargs: dict): + def __init_subclass__(cls, *, table: str = '', **kwargs: Any): super().__init_subclass__(**kwargs) # type:ignore cls._TABLE_NAME = table if table else cls.__name__.lower() + 's' for name, py_type in cls._fields().items(): setattr(cls, name, Field(name, py_type)) - @staticmethod - def _connect(db_path: str = db.DEFAULT_DB_PATH): - return db.connect(db_path) - - @classmethod - def _ensure_table(cls) -> str: - if not cls._TABLE_READY: - db.ensure_table(cls._TABLE_NAME, cls._fields()) - cls._TABLE_READY = True - return cls._TABLE_NAME - - def __class_getitem__(cls, pk: int) -> 'Persistent': - field_names = ['_pk'] + list(cls._fields()) - values = db.fetch_record(cls._TABLE_NAME, pk) - return cls(**dict(zip(field_names, values))) - - def _asdict(self) -> dict[str, Any]: - return { - name: getattr(self, name) - for name, attr in self.__class__.__dict__.items() - if isinstance(attr, Field) - } - def __init__(self, *, _pk=None, **kwargs): field_names = self._asdict().keys() for name, arg in kwargs.items(): @@ -131,6 +108,32 @@ def __repr__(self) -> str: return f'{cls_name}({kwargs})' return f'{cls_name}({kwargs}, _pk={self._pk})' + def _asdict(self) -> dict[str, Any]: + return { + name: getattr(self, name) + for name, attr in self.__class__.__dict__.items() + if isinstance(attr, Field) + } + + + # database methods + + @staticmethod + def _connect(db_path: str = db.DEFAULT_DB_PATH): + return db.connect(db_path) + + @classmethod + def _ensure_table(cls) -> str: + if not cls._TABLE_READY: + db.ensure_table(cls._TABLE_NAME, cls._fields()) + cls._TABLE_READY = True + return cls._TABLE_NAME + + def __class_getitem__(cls, pk: int) -> 'Persistent': + field_names = ['_pk'] + list(cls._fields()) + values = db.fetch_record(cls._TABLE_NAME, pk) + return cls(**dict(zip(field_names, values))) + def _save(self) -> int: table = self.__class__._ensure_table() if self._pk is None: diff --git a/25-class-metaprog/qualname/fakedjango.py b/25-class-metaprog/qualname/fakedjango.py new file mode 100644 index 0000000..76a68eb --- /dev/null +++ b/25-class-metaprog/qualname/fakedjango.py @@ -0,0 +1,5 @@ +class models: + class Model: + "nothing to see here" + class IntegerField: + "nothing to see here" diff --git a/25-class-metaprog/qualname/models.py b/25-class-metaprog/qualname/models.py new file mode 100755 index 0000000..6045703 --- /dev/null +++ b/25-class-metaprog/qualname/models.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python3 + +from fakedjango import models + +class Ox(models.Model): + horn_length = models.IntegerField() + + class Meta: + ordering = ['horn_length'] + verbose_name_plural = 'oxen' + +print(Ox.Meta.__name__) +print(Ox.Meta.__qualname__) diff --git a/25-class-metaprog/setattr/example_from_leo.py b/25-class-metaprog/setattr/example_from_leo.py new file mode 100755 index 0000000..980fb55 --- /dev/null +++ b/25-class-metaprog/setattr/example_from_leo.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 + +class Foo: + @property + def bar(self): + return self._bar + + @bar.setter + def bar(self, value): + self._bar = value + + def __setattr__(self, name, value): + print(f'setting {name!r} to {value!r}') + super().__setattr__(name, value) + +o = Foo() +o.bar = 8 +print(o.bar) +print(o._bar) diff --git a/25-class-metaprog/slots/slots_timing.py b/25-class-metaprog/slots/slots_timing.py new file mode 100755 index 0000000..ef7d941 --- /dev/null +++ b/25-class-metaprog/slots/slots_timing.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 + +class Wrong: + + def __init_subclass__(subclass): + subclass.__slots__ = ('x', 'y') + + +class Klass0(Wrong): + pass + + +o = Klass0() +o.z = 3 +print('o.z = 3 # did not raise Attribute error because __slots__ was created too late') + + +class Correct1(type): + + def __new__(meta_cls, cls_name, bases, cls_dict): + cls_dict['__slots__'] = ('x', 'y') + return super().__new__( + meta_cls, cls_name, bases, cls_dict) + + +class Klass1(metaclass=Correct1): + pass + +o = Klass1() +try: + o.z = 3 +except AttributeError as e: + print('Raised as expected:', e) + + +class Correct2(type): + def __prepare__(name, bases): + return dict(__slots__=('x', 'y')) + +class Klass2(metaclass=Correct2): + pass + +o = Klass2() +try: + o.z = 3 +except AttributeError as e: + print('Raised as expected:', e) + diff --git a/25-class-metaprog/tinyenums/microenum.py b/25-class-metaprog/tinyenums/microenum.py index 873d469..26aea64 100644 --- a/25-class-metaprog/tinyenums/microenum.py +++ b/25-class-metaprog/tinyenums/microenum.py @@ -2,9 +2,9 @@ # shared privately with me, with permission to use in Fluent Python 2e. """ -Testing ``AutoFillDict``:: +Testing ``WilyDict``:: - >>> adict = AutoFillDict() + >>> adict = WilyDict() >>> len(adict) 0 >>> adict['first'] @@ -37,7 +37,7 @@ """ -class AutoFillDict(dict): +class WilyDict(dict): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.__next_value = 0 @@ -52,12 +52,14 @@ def __missing__(self, key): class MicroEnumMeta(type): def __prepare__(name, bases, **kwargs): - return AutoFillDict() + return WilyDict() - -class MicroEnum(metaclass=MicroEnumMeta): - def __class_getitem__(cls, key): + def __getitem__(cls, key): for k, v in cls.__dict__.items(): if v == key: return k raise KeyError(key) + + +class MicroEnum(metaclass=MicroEnumMeta): + pass diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..df3eb51 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,2 @@ +[pytest] +addopts = --doctest-modules From f248baf418c49e5748bd503dd992b52440ed236b Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Sun, 23 May 2021 22:12:13 -0300 Subject: [PATCH 056/166] adding sentinel metaclass example --- 08-def-type-hints/typevars_constrained.py | 26 +++++++++ 15-more-types/{ => cafeteria}/cafeteria.py | 2 +- 15-more-types/cafeteria/contravariant.py | 45 ++++++++++++++++ 15-more-types/cafeteria/covariant.py | 48 +++++++++++++++++ 15-more-types/cafeteria/invariant.py | 54 +++++++++++++++++++ .../clip_annot.py | 0 .../clip_annot_1ed.py | 0 .../clip_annot_signature.rst | 0 .../mymax}/mymax.py | 0 .../mymax}/mymax_demo.py | 0 .../mymax}/mymax_test.py | 0 {08-def-type-hints => 15-more-types}/mysum.py | 0 25-class-metaprog/sentinel/sentinel.py | 24 +++++++++ 25-class-metaprog/sentinel/sentinel_test.py | 22 ++++++++ 14 files changed, 220 insertions(+), 1 deletion(-) create mode 100644 08-def-type-hints/typevars_constrained.py rename 15-more-types/{ => cafeteria}/cafeteria.py (98%) create mode 100644 15-more-types/cafeteria/contravariant.py create mode 100644 15-more-types/cafeteria/covariant.py create mode 100644 15-more-types/cafeteria/invariant.py rename {08-def-type-hints => 15-more-types}/clip_annot.py (100%) rename {08-def-type-hints => 15-more-types}/clip_annot_1ed.py (100%) rename {08-def-type-hints => 15-more-types}/clip_annot_signature.rst (100%) rename {08-def-type-hints/comparable => 15-more-types/mymax}/mymax.py (100%) rename {08-def-type-hints/comparable => 15-more-types/mymax}/mymax_demo.py (100%) rename {08-def-type-hints/comparable => 15-more-types/mymax}/mymax_test.py (100%) rename {08-def-type-hints => 15-more-types}/mysum.py (100%) create mode 100644 25-class-metaprog/sentinel/sentinel.py create mode 100644 25-class-metaprog/sentinel/sentinel_test.py diff --git a/08-def-type-hints/typevars_constrained.py b/08-def-type-hints/typevars_constrained.py new file mode 100644 index 0000000..2bfea13 --- /dev/null +++ b/08-def-type-hints/typevars_constrained.py @@ -0,0 +1,26 @@ +from typing import TypeVar, TYPE_CHECKING +from decimal import Decimal + +# tag::TYPEVAR_RESTRICTED[] +RT = TypeVar('RT', float, Decimal) + +def triple1(a: RT) -> RT: + return a * 3 + +res1 = triple1(1, 2) + +if TYPE_CHECKING: + reveal_type(res1) +# end::TYPEVAR_RESTRICTED[] + +# tag::TYPEVAR_BOUNDED[] +BT = TypeVar('BT', bound=float) + +def triple2(a: BT) -> BT: + return a * 3 + +res2 = triple2(1, 2) + +if TYPE_CHECKING: + reveal_type(res2) +# tag::TYPEVAR_BOUNDED[] diff --git a/15-more-types/cafeteria.py b/15-more-types/cafeteria/cafeteria.py similarity index 98% rename from 15-more-types/cafeteria.py rename to 15-more-types/cafeteria/cafeteria.py index 622f8e4..36769df 100644 --- a/15-more-types/cafeteria.py +++ b/15-more-types/cafeteria/cafeteria.py @@ -41,7 +41,7 @@ class Compostable(Biodegradable): class TrashCan(Generic[T_contra]): def put(self, trash: T_contra) -> None: - """Store trash until dumped...""" + """Store trash until dumped.""" class Cafeteria: diff --git a/15-more-types/cafeteria/contravariant.py b/15-more-types/cafeteria/contravariant.py new file mode 100644 index 0000000..399a748 --- /dev/null +++ b/15-more-types/cafeteria/contravariant.py @@ -0,0 +1,45 @@ +# tag::TRASH_TYPES[] +from typing import TypeVar, Generic + +class Refuse: # <1> + """Any refuse.""" + +class Biodegradable(Refuse): + """Biodegradable refuse.""" + +class Compostable(Biodegradable): + """Compostable refuse.""" + +T_contra = TypeVar('T_contra', contravariant=True) # <2> + +class TrashCan(Generic[T_contra]): # <3> + def put(self, refuse: T_contra) -> None: + """Store trash until dumped.""" + +def deploy(trash_can: TrashCan[Biodegradable]): + """Deploy a trash can for biodegradable refuse.""" +# end::TRASH_TYPES[] + + +################################################ contravariant trash can + + +# tag::DEPLOY_TRASH_CANS[] +bio_can: TrashCan[Biodegradable] = TrashCan() +deploy(bio_can) + +trash_can: TrashCan[Refuse] = TrashCan() +deploy(trash_can) +# end::DEPLOY_TRASH_CANS[] + + +################################################ more specific trash can + +# tag::DEPLOY_NOT_VALID[] +compost_can: TrashCan[Compostable] = TrashCan() +deploy(compost_can) +# end::DEPLOY_NOT_VALID[] + +## Argument 1 to "deploy" has +## incompatible type "TrashCan[Compostable]" +## expected "TrashCan[Biodegradable]" diff --git a/15-more-types/cafeteria/covariant.py b/15-more-types/cafeteria/covariant.py new file mode 100644 index 0000000..87879dd --- /dev/null +++ b/15-more-types/cafeteria/covariant.py @@ -0,0 +1,48 @@ +from typing import TypeVar, Generic + + +class Beverage: + """Any beverage.""" + + +class Juice(Beverage): + """Any fruit juice.""" + + +class OrangeJuice(Juice): + """Delicious juice from Brazilian oranges.""" + + +# tag::BEVERAGE_TYPES[] +T_co = TypeVar('T_co', covariant=True) # <1> + + +class BeverageDispenser(Generic[T_co]): # <2> + def __init__(self, beverage: T_co) -> None: + self.beverage = beverage + + def dispense(self) -> T_co: + return self.beverage + +def install(dispenser: BeverageDispenser[Juice]) -> None: # <3> + """Install a fruit juice dispenser.""" +# end::BEVERAGE_TYPES[] + +################################################ covariant dispenser + +# tag::INSTALL_JUICE_DISPENSERS[] +juice_dispenser = BeverageDispenser(Juice()) +install(juice_dispenser) + +orange_juice_dispenser = BeverageDispenser(OrangeJuice()) +install(orange_juice_dispenser) +# end::INSTALL_JUICE_DISPENSERS[] + +################################################ not a juice dispenser + +beverage_dispenser = BeverageDispenser(Beverage()) + +## Argument 1 to "install" has +## incompatible type "BeverageDispenser[Beverage]" +## expected "BeverageDispenser[Juice]" +install(beverage_dispenser) diff --git a/15-more-types/cafeteria/invariant.py b/15-more-types/cafeteria/invariant.py new file mode 100644 index 0000000..1f9d6f6 --- /dev/null +++ b/15-more-types/cafeteria/invariant.py @@ -0,0 +1,54 @@ +# tag::BEVERAGE_TYPES[] +from typing import TypeVar, Generic + +class Beverage: # <1> + """Any beverage.""" + +class Juice(Beverage): + """Any fruit juice.""" + +class OrangeJuice(Juice): + """Delicious juice from Brazilian oranges.""" + +T = TypeVar('T') # <2> + +class BeverageDispenser(Generic[T]): # <3> + """A dispenser parameterized on the beverage type.""" + def __init__(self, beverage: T) -> None: + self.beverage = beverage + + def dispense(self) -> T: + return self.beverage + +def install(dispenser: BeverageDispenser[Juice]) -> None: # <4> + """Install a fruit juice dispenser.""" +# end::BEVERAGE_TYPES[] + +################################################ exact type + +# tag::INSTALL_JUICE_DISPENSER[] +juice_dispenser = BeverageDispenser(Juice()) +install(juice_dispenser) +# end::INSTALL_JUICE_DISPENSER[] + + +################################################ variant dispenser + +# tag::INSTALL_BEVERAGE_DISPENSER[] +beverage_dispenser = BeverageDispenser(Beverage()) +install(beverage_dispenser) +## Argument 1 to "install" has +## incompatible type "BeverageDispenser[Beverage]" +## expected "BeverageDispenser[Juice]" +# end::INSTALL_BEVERAGE_DISPENSER[] + + +################################################ variant dispenser + +# tag::INSTALL_ORANGE_JUICE_DISPENSER[] +orange_juice_dispenser = BeverageDispenser(OrangeJuice()) +install(orange_juice_dispenser) +# end::INSTALL_ORANGE_JUICE_DISPENSER[] +## Argument 1 to "install" has +## incompatible type "BeverageDispenser[OrangeJuice]" +## expected "BeverageDispenser[Juice]" diff --git a/08-def-type-hints/clip_annot.py b/15-more-types/clip_annot.py similarity index 100% rename from 08-def-type-hints/clip_annot.py rename to 15-more-types/clip_annot.py diff --git a/08-def-type-hints/clip_annot_1ed.py b/15-more-types/clip_annot_1ed.py similarity index 100% rename from 08-def-type-hints/clip_annot_1ed.py rename to 15-more-types/clip_annot_1ed.py diff --git a/08-def-type-hints/clip_annot_signature.rst b/15-more-types/clip_annot_signature.rst similarity index 100% rename from 08-def-type-hints/clip_annot_signature.rst rename to 15-more-types/clip_annot_signature.rst diff --git a/08-def-type-hints/comparable/mymax.py b/15-more-types/mymax/mymax.py similarity index 100% rename from 08-def-type-hints/comparable/mymax.py rename to 15-more-types/mymax/mymax.py diff --git a/08-def-type-hints/comparable/mymax_demo.py b/15-more-types/mymax/mymax_demo.py similarity index 100% rename from 08-def-type-hints/comparable/mymax_demo.py rename to 15-more-types/mymax/mymax_demo.py diff --git a/08-def-type-hints/comparable/mymax_test.py b/15-more-types/mymax/mymax_test.py similarity index 100% rename from 08-def-type-hints/comparable/mymax_test.py rename to 15-more-types/mymax/mymax_test.py diff --git a/08-def-type-hints/mysum.py b/15-more-types/mysum.py similarity index 100% rename from 08-def-type-hints/mysum.py rename to 15-more-types/mysum.py diff --git a/25-class-metaprog/sentinel/sentinel.py b/25-class-metaprog/sentinel/sentinel.py new file mode 100644 index 0000000..bc94e5d --- /dev/null +++ b/25-class-metaprog/sentinel/sentinel.py @@ -0,0 +1,24 @@ +""" + + >>> class Missing(Sentinel): pass + >>> Missing + + >>> class CustomRepr(Sentinel): + ... repr = '*** sentinel ***' + ... + >>> CustomRepr + *** sentinel *** + +""" + +class SentinelMeta(type): + def __repr__(cls): + try: + return cls.repr + except AttributeError: + return f'<{cls.__name__}>' + +class Sentinel(metaclass=SentinelMeta): + def __new__(cls): + return cls + diff --git a/25-class-metaprog/sentinel/sentinel_test.py b/25-class-metaprog/sentinel/sentinel_test.py new file mode 100644 index 0000000..60a2919 --- /dev/null +++ b/25-class-metaprog/sentinel/sentinel_test.py @@ -0,0 +1,22 @@ +from sentinel import Sentinel + +class PlainSentinel(Sentinel): pass + + +class SentinelCustomRepr(Sentinel): + repr = '***SentinelRepr***' + + +def test_repr(): + assert repr(PlainSentinel) == '' + + +def test_pickle(): + from pickle import dumps, loads + s = dumps(PlainSentinel) + ps = loads(s) + assert ps is PlainSentinel + +def test_custom_repr(): + assert repr(SentinelCustomRepr) == '***SentinelRepr***' + \ No newline at end of file From be7a67c93e948a63e6b942a3347f7e1fbd33126a Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Sun, 23 May 2021 22:19:15 -0300 Subject: [PATCH 057/166] change standard repr to ClassName --- 25-class-metaprog/sentinel/sentinel.py | 9 ++++----- 25-class-metaprog/sentinel/sentinel_test.py | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/25-class-metaprog/sentinel/sentinel.py b/25-class-metaprog/sentinel/sentinel.py index bc94e5d..20fcc7c 100644 --- a/25-class-metaprog/sentinel/sentinel.py +++ b/25-class-metaprog/sentinel/sentinel.py @@ -2,12 +2,12 @@ >>> class Missing(Sentinel): pass >>> Missing - + Missing >>> class CustomRepr(Sentinel): - ... repr = '*** sentinel ***' + ... repr = '' ... >>> CustomRepr - *** sentinel *** + """ @@ -16,9 +16,8 @@ def __repr__(cls): try: return cls.repr except AttributeError: - return f'<{cls.__name__}>' + return cls.__name__ class Sentinel(metaclass=SentinelMeta): def __new__(cls): return cls - diff --git a/25-class-metaprog/sentinel/sentinel_test.py b/25-class-metaprog/sentinel/sentinel_test.py index 60a2919..1a645fa 100644 --- a/25-class-metaprog/sentinel/sentinel_test.py +++ b/25-class-metaprog/sentinel/sentinel_test.py @@ -8,7 +8,7 @@ class SentinelCustomRepr(Sentinel): def test_repr(): - assert repr(PlainSentinel) == '' + assert repr(PlainSentinel) == 'PlainSentinel' def test_pickle(): From 8d882d9560e778bf1e797d0da37e547baf9bc3df Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Sun, 23 May 2021 22:24:14 -0300 Subject: [PATCH 058/166] added blank line --- 25-class-metaprog/sentinel/sentinel_test.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/25-class-metaprog/sentinel/sentinel_test.py b/25-class-metaprog/sentinel/sentinel_test.py index 1a645fa..b328dae 100644 --- a/25-class-metaprog/sentinel/sentinel_test.py +++ b/25-class-metaprog/sentinel/sentinel_test.py @@ -17,6 +17,7 @@ def test_pickle(): ps = loads(s) assert ps is PlainSentinel + def test_custom_repr(): assert repr(SentinelCustomRepr) == '***SentinelRepr***' - \ No newline at end of file + From 493b0d2a565b42b53f51fd053e239220c54ea951 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Sun, 23 May 2021 22:30:40 -0300 Subject: [PATCH 059/166] ch25: sentinel metaclass example --- 25-class-metaprog/sentinel/sentinel_test.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/25-class-metaprog/sentinel/sentinel_test.py b/25-class-metaprog/sentinel/sentinel_test.py index b328dae..4142289 100644 --- a/25-class-metaprog/sentinel/sentinel_test.py +++ b/25-class-metaprog/sentinel/sentinel_test.py @@ -1,3 +1,5 @@ +import pickle + from sentinel import Sentinel class PlainSentinel(Sentinel): pass @@ -12,12 +14,17 @@ def test_repr(): def test_pickle(): - from pickle import dumps, loads - s = dumps(PlainSentinel) - ps = loads(s) + s = pickle.dumps(PlainSentinel) + ps = pickle.loads(s) assert ps is PlainSentinel def test_custom_repr(): assert repr(SentinelCustomRepr) == '***SentinelRepr***' - + + +def test_sentinel_comes_ready_to_use(): + assert repr(Sentinel) == 'Sentinel' + s = pickle.dumps(Sentinel) + ps = pickle.loads(s) + assert ps is Sentinel From 08a4001b430f882191ac2c121082190bbb2f85c8 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Sun, 23 May 2021 22:33:33 -0300 Subject: [PATCH 060/166] upgrade urllib3 due to vulnerability --- 21-futures/getflags/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/21-futures/getflags/requirements.txt b/21-futures/getflags/requirements.txt index 0971a38..447f2e9 100644 --- a/21-futures/getflags/requirements.txt +++ b/21-futures/getflags/requirements.txt @@ -5,7 +5,7 @@ certifi==2020.12.5 chardet==4.0.0 idna==2.10 requests==2.25.1 -urllib3==1.26.3 +urllib3==1.26.4 tqdm==4.56.2 multidict==5.1.0 yarl==1.6.3 From 0ce109a9fef0e1a71576268b6469aa2b02fe3a9c Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Mon, 24 May 2021 13:27:07 -0300 Subject: [PATCH 061/166] improved sentinel after learning from @taleinat on python-dev --- 25-class-metaprog/sentinel/sentinel.py | 26 +++++++++++++++++---- 25-class-metaprog/sentinel/sentinel_test.py | 21 +++++++++++++---- 2 files changed, 38 insertions(+), 9 deletions(-) diff --git a/25-class-metaprog/sentinel/sentinel.py b/25-class-metaprog/sentinel/sentinel.py index 20fcc7c..1cce65e 100644 --- a/25-class-metaprog/sentinel/sentinel.py +++ b/25-class-metaprog/sentinel/sentinel.py @@ -1,23 +1,41 @@ """ +This module provides a ``Sentinel`` class that can be used directly as a +sentinel singleton, or subclassed if a distinct sentinel singleton is needed. + +The ``repr`` of a ``Sentinel`` class is its name:: >>> class Missing(Sentinel): pass >>> Missing Missing + +If a different ``repr`` is required, +you can define it as a class attribute:: + >>> class CustomRepr(Sentinel): ... repr = '' ... >>> CustomRepr +``Sentinel`` classes cannot be instantiated:: + + >>> Missing() + Traceback (most recent call last): + ... + TypeError: 'Missing' is a sentinel and cannot be instantiated + """ -class SentinelMeta(type): + +class _SentinelMeta(type): def __repr__(cls): try: return cls.repr except AttributeError: - return cls.__name__ + return f'{cls.__name__}' + -class Sentinel(metaclass=SentinelMeta): +class Sentinel(metaclass=_SentinelMeta): def __new__(cls): - return cls + msg = 'is a sentinel and cannot be instantiated' + raise TypeError(f"'{cls!r}' {msg}") diff --git a/25-class-metaprog/sentinel/sentinel_test.py b/25-class-metaprog/sentinel/sentinel_test.py index 4142289..5e304b7 100644 --- a/25-class-metaprog/sentinel/sentinel_test.py +++ b/25-class-metaprog/sentinel/sentinel_test.py @@ -1,8 +1,12 @@ import pickle +import pytest + from sentinel import Sentinel -class PlainSentinel(Sentinel): pass + +class PlainSentinel(Sentinel): + pass class SentinelCustomRepr(Sentinel): @@ -13,16 +17,23 @@ def test_repr(): assert repr(PlainSentinel) == 'PlainSentinel' -def test_pickle(): - s = pickle.dumps(PlainSentinel) - ps = pickle.loads(s) - assert ps is PlainSentinel +def test_cannot_instantiate(): + with pytest.raises(TypeError) as e: + PlainSentinel() + msg = "'PlainSentinel' is a sentinel and cannot be instantiated" + assert msg in str(e.value) def test_custom_repr(): assert repr(SentinelCustomRepr) == '***SentinelRepr***' +def test_pickle(): + s = pickle.dumps(SentinelCustomRepr) + ps = pickle.loads(s) + assert ps is SentinelCustomRepr + + def test_sentinel_comes_ready_to_use(): assert repr(Sentinel) == 'Sentinel' s = pickle.dumps(Sentinel) From e6e79b75d717038249e0988281b4a929608408e1 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Mon, 24 May 2021 13:35:53 -0300 Subject: [PATCH 062/166] removed unnecessary f-string --- 25-class-metaprog/sentinel/sentinel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/25-class-metaprog/sentinel/sentinel.py b/25-class-metaprog/sentinel/sentinel.py index 1cce65e..5cae279 100644 --- a/25-class-metaprog/sentinel/sentinel.py +++ b/25-class-metaprog/sentinel/sentinel.py @@ -32,7 +32,7 @@ def __repr__(cls): try: return cls.repr except AttributeError: - return f'{cls.__name__}' + return cls.__name__ class Sentinel(metaclass=_SentinelMeta): From 1fffea6244fb07cce222de598959a41debe49530 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Mon, 24 May 2021 13:48:55 -0300 Subject: [PATCH 063/166] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d0b57b3..7d786e1 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Fluent Python 2e example code -Example code for the book **Fluent Python, 2nd edition** by Luciano Ramalho (O'Reilly, 2020). +Example code for the book **Fluent Python, 2nd edition** by Luciano Ramalho (O'Reilly, 2021). > **BEWARE**: This is a work in progress! > From abf7ac2977777764759e199fc1a1b415966299da Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Wed, 26 May 2021 19:19:24 -0300 Subject: [PATCH 064/166] ch15: typing.cast example --- 15-more-types/cast/tcp_echo.py | 34 ++++++++++++++++++++++++++ 15-more-types/cast/tcp_echo_no_cast.py | 29 ++++++++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 15-more-types/cast/tcp_echo.py create mode 100644 15-more-types/cast/tcp_echo_no_cast.py diff --git a/15-more-types/cast/tcp_echo.py b/15-more-types/cast/tcp_echo.py new file mode 100644 index 0000000..d0f078a --- /dev/null +++ b/15-more-types/cast/tcp_echo.py @@ -0,0 +1,34 @@ +import asyncio + +from asyncio import StreamReader, StreamWriter +from asyncio.trsock import TransportSocket +from typing import cast + +async def handle_echo(reader: StreamReader, writer: StreamWriter) -> None: + data = await reader.read(100) + message = data.decode() + addr = writer.get_extra_info('peername') + + print(f"Received {message!r} from {addr!r}") + + print(f"Send: {message!r}") + writer.write(data) + await writer.drain() + + print("Close the connection") + writer.close() + +async def main() -> None: + server = await asyncio.start_server( + handle_echo, '127.0.0.1', 8888) + + # tag::CAST[] + socket_list = cast(tuple[TransportSocket, ...], server.sockets) + addr = socket_list[0].getsockname() + # end::CAST[] + print(f'Serving on {addr}') + + async with server: + await server.serve_forever() + +asyncio.run(main()) diff --git a/15-more-types/cast/tcp_echo_no_cast.py b/15-more-types/cast/tcp_echo_no_cast.py new file mode 100644 index 0000000..168db55 --- /dev/null +++ b/15-more-types/cast/tcp_echo_no_cast.py @@ -0,0 +1,29 @@ +import asyncio + +from asyncio import StreamReader, StreamWriter + +async def handle_echo(reader: StreamReader, writer: StreamWriter) -> None: + data = await reader.read(100) + message = data.decode() + addr = writer.get_extra_info('peername') + + print(f"Received {message!r} from {addr!r}") + + print(f"Send: {message!r}") + writer.write(data) + await writer.drain() + + print("Close the connection") + writer.close() + +async def main() -> None: + server = await asyncio.start_server( + handle_echo, '127.0.0.1', 8888) + + addr = server.sockets[0].getsockname() + print(f'Serving on {addr}') + + async with server: + await server.serve_forever() + +asyncio.run(main()) From 135eca25d91595e30eb8538a4b9b7f70393ad5a6 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Wed, 9 Jun 2021 00:13:02 -0300 Subject: [PATCH 065/166] complete draft: update from Atlas --- 13-protocol-abc/lotto.py | 6 +- 13-protocol-abc/tombola.py | 2 +- 13-protocol-abc/tombola_runner.py | 7 +- 13-protocol-abc/tombola_tests.rst | 4 +- 13-protocol-abc/tombolist.py | 2 +- 15-more-types/cafeteria/contravariant.py | 5 +- 15-more-types/cafeteria/covariant.py | 9 +- 15-more-types/cafeteria/invariant.py | 6 +- 15-more-types/cast/empty.py | 2 + 15-more-types/cast/find.py | 23 + 15-more-types/cast/tcp_echo.py | 37 + 15-more-types/cast/tcp_echo_no_cast.py | 29 + 15-more-types/clip_annot_demo.py | 3 + .../{clip_annot_1ed.py => clip_annot_post.py} | 10 +- 15-more-types/clip_annot_signature.rst | 10 - 15-more-types/lotto/generic_lotto.py | 29 + 15-more-types/lotto/generic_lotto_demo.py | 28 + 15-more-types/lotto/generic_lotto_errors.py | 18 + 15-more-types/lotto/tombola.py | 34 + 15-more-types/mysum.py | 17 +- 15-more-types/protocol/abs_demo.py | 32 + 15-more-types/{ => protocol}/mymax/mymax.py | 4 +- .../{ => protocol}/mymax/mymax_demo.py | 0 .../{ => protocol}/mymax/mymax_test.py | 0 15-more-types/{ => protocol/random}/erp.py | 0 .../{ => protocol/random}/erp_test.py | 0 .../protocol/random/generic_randompick.py | 7 + .../random/generic_randompick_test.py} | 16 +- .../{ => protocol/random}/randompop.py | 0 .../{ => protocol/random}/randompop_test.py | 0 15-more-types/randompick_generic.py | 7 - 18-context-mngr/lispy/LICENSE | 21 + 18-context-mngr/lispy/README.md | 24 + 18-context-mngr/lispy/original/lis.py | 132 + 18-context-mngr/lispy/original/lispy.py | 316 + 18-context-mngr/lispy/original/lispytest.py | 122 + 18-context-mngr/lispy/py3.10/lis.py | 160 + 18-context-mngr/lispy/py3.10/lis_test.py | 147 + 18-context-mngr/lispy/py3.9/lis.py | 163 + 18-context-mngr/lispy/py3.9/lis_test.py | 147 + 22-async/mojifinder/tcp_mojifinder.py | 18 +- 23-dyn-attr-prop/oscon/data/osconfeed.json | 6346 ++++++++--------- .../checked/decorator/checkeddeco.py | 2 +- 43 files changed, 4705 insertions(+), 3240 deletions(-) create mode 100644 15-more-types/cast/empty.py create mode 100644 15-more-types/cast/find.py create mode 100644 15-more-types/cast/tcp_echo.py create mode 100644 15-more-types/cast/tcp_echo_no_cast.py create mode 100644 15-more-types/clip_annot_demo.py rename 15-more-types/{clip_annot_1ed.py => clip_annot_post.py} (76%) delete mode 100644 15-more-types/clip_annot_signature.rst create mode 100644 15-more-types/lotto/generic_lotto.py create mode 100755 15-more-types/lotto/generic_lotto_demo.py create mode 100755 15-more-types/lotto/generic_lotto_errors.py create mode 100644 15-more-types/lotto/tombola.py create mode 100755 15-more-types/protocol/abs_demo.py rename 15-more-types/{ => protocol}/mymax/mymax.py (91%) rename 15-more-types/{ => protocol}/mymax/mymax_demo.py (100%) rename 15-more-types/{ => protocol}/mymax/mymax_test.py (100%) rename 15-more-types/{ => protocol/random}/erp.py (100%) rename 15-more-types/{ => protocol/random}/erp_test.py (100%) create mode 100644 15-more-types/protocol/random/generic_randompick.py rename 15-more-types/{randompick_generic_test.py => protocol/random/generic_randompick_test.py} (59%) rename 15-more-types/{ => protocol/random}/randompop.py (100%) rename 15-more-types/{ => protocol/random}/randompop_test.py (100%) delete mode 100644 15-more-types/randompick_generic.py create mode 100644 18-context-mngr/lispy/LICENSE create mode 100644 18-context-mngr/lispy/README.md create mode 100644 18-context-mngr/lispy/original/lis.py create mode 100644 18-context-mngr/lispy/original/lispy.py create mode 100644 18-context-mngr/lispy/original/lispytest.py create mode 100644 18-context-mngr/lispy/py3.10/lis.py create mode 100755 18-context-mngr/lispy/py3.10/lis_test.py create mode 100644 18-context-mngr/lispy/py3.9/lis.py create mode 100755 18-context-mngr/lispy/py3.9/lis_test.py diff --git a/13-protocol-abc/lotto.py b/13-protocol-abc/lotto.py index 5026041..5fc849d 100644 --- a/13-protocol-abc/lotto.py +++ b/13-protocol-abc/lotto.py @@ -5,7 +5,7 @@ from tombola import Tombola -class LotteryBlower(Tombola): +class LottoBlower(Tombola): def __init__(self, iterable): self._balls = list(iterable) # <1> @@ -17,14 +17,14 @@ def pick(self): try: position = random.randrange(len(self._balls)) # <2> except ValueError: - raise LookupError('pick from empty BingoCage') + raise LookupError('pick from empty LottoBlower') return self._balls.pop(position) # <3> def loaded(self): # <4> return bool(self._balls) def inspect(self): # <5> - return tuple(sorted(self._balls)) + return tuple(self._balls) # end::LOTTERY_BLOWER[] diff --git a/13-protocol-abc/tombola.py b/13-protocol-abc/tombola.py index 6f4c6cd..50f19fc 100644 --- a/13-protocol-abc/tombola.py +++ b/13-protocol-abc/tombola.py @@ -28,7 +28,7 @@ def inspect(self): except LookupError: break self.load(items) # <7> - return tuple(sorted(items)) + return tuple(items) # end::TOMBOLA_ABC[] diff --git a/13-protocol-abc/tombola_runner.py b/13-protocol-abc/tombola_runner.py index bf41046..a537734 100644 --- a/13-protocol-abc/tombola_runner.py +++ b/13-protocol-abc/tombola_runner.py @@ -1,4 +1,5 @@ -# tag::TOMBOLA_RUNNER[] +#!/usr/bin/env python3 + import doctest from tombola import Tombola @@ -13,8 +14,7 @@ def main(argv): verbose = '-v' in argv real_subclasses = Tombola.__subclasses__() # <2> - virtual_subclasses = list(Tombola._abc_registry) # <3> - + virtual_subclasses = [tombolist.TomboList] # <3> for cls in real_subclasses + virtual_subclasses: # <4> test(cls, verbose) @@ -33,4 +33,3 @@ def test(cls, verbose=False): if __name__ == '__main__': import sys main(sys.argv) -# end::TOMBOLA_RUNNER[] diff --git a/13-protocol-abc/tombola_tests.rst b/13-protocol-abc/tombola_tests.rst index 1489903..6562db8 100644 --- a/13-protocol-abc/tombola_tests.rst +++ b/13-protocol-abc/tombola_tests.rst @@ -11,8 +11,8 @@ Create and load instance from iterable:: >>> globe = ConcreteTombola(balls) >>> globe.loaded() True - >>> globe.inspect() - (0, 1, 2) + >>> sorted(globe.inspect()) + [0, 1, 2] Pick and collect balls:: diff --git a/13-protocol-abc/tombolist.py b/13-protocol-abc/tombolist.py index b3ca2a6..e034e9e 100644 --- a/13-protocol-abc/tombolist.py +++ b/13-protocol-abc/tombolist.py @@ -18,6 +18,6 @@ def loaded(self): return bool(self) # <6> def inspect(self): - return tuple(sorted(self)) + return tuple(self) # Tombola.register(TomboList) # <7> diff --git a/15-more-types/cafeteria/contravariant.py b/15-more-types/cafeteria/contravariant.py index 399a748..30ef046 100644 --- a/15-more-types/cafeteria/contravariant.py +++ b/15-more-types/cafeteria/contravariant.py @@ -38,8 +38,7 @@ def deploy(trash_can: TrashCan[Biodegradable]): # tag::DEPLOY_NOT_VALID[] compost_can: TrashCan[Compostable] = TrashCan() deploy(compost_can) -# end::DEPLOY_NOT_VALID[] - -## Argument 1 to "deploy" has +## mypy: Argument 1 to "deploy" has ## incompatible type "TrashCan[Compostable]" ## expected "TrashCan[Biodegradable]" +# end::DEPLOY_NOT_VALID[] diff --git a/15-more-types/cafeteria/covariant.py b/15-more-types/cafeteria/covariant.py index 87879dd..768fe2d 100644 --- a/15-more-types/cafeteria/covariant.py +++ b/15-more-types/cafeteria/covariant.py @@ -38,11 +38,12 @@ def install(dispenser: BeverageDispenser[Juice]) -> None: # <3> install(orange_juice_dispenser) # end::INSTALL_JUICE_DISPENSERS[] -################################################ not a juice dispenser +################################################ more general dispenser +# tag::INSTALL_BEVERAGE_DISPENSER[] beverage_dispenser = BeverageDispenser(Beverage()) - -## Argument 1 to "install" has +install(beverage_dispenser) +## mypy: Argument 1 to "install" has ## incompatible type "BeverageDispenser[Beverage]" ## expected "BeverageDispenser[Juice]" -install(beverage_dispenser) +# end::INSTALL_BEVERAGE_DISPENSER[] diff --git a/15-more-types/cafeteria/invariant.py b/15-more-types/cafeteria/invariant.py index 1f9d6f6..0c44643 100644 --- a/15-more-types/cafeteria/invariant.py +++ b/15-more-types/cafeteria/invariant.py @@ -37,7 +37,7 @@ def install(dispenser: BeverageDispenser[Juice]) -> None: # <4> # tag::INSTALL_BEVERAGE_DISPENSER[] beverage_dispenser = BeverageDispenser(Beverage()) install(beverage_dispenser) -## Argument 1 to "install" has +## mypy: Argument 1 to "install" has ## incompatible type "BeverageDispenser[Beverage]" ## expected "BeverageDispenser[Juice]" # end::INSTALL_BEVERAGE_DISPENSER[] @@ -48,7 +48,7 @@ def install(dispenser: BeverageDispenser[Juice]) -> None: # <4> # tag::INSTALL_ORANGE_JUICE_DISPENSER[] orange_juice_dispenser = BeverageDispenser(OrangeJuice()) install(orange_juice_dispenser) -# end::INSTALL_ORANGE_JUICE_DISPENSER[] -## Argument 1 to "install" has +## mypy: Argument 1 to "install" has ## incompatible type "BeverageDispenser[OrangeJuice]" ## expected "BeverageDispenser[Juice]" +# end::INSTALL_ORANGE_JUICE_DISPENSER[] diff --git a/15-more-types/cast/empty.py b/15-more-types/cast/empty.py new file mode 100644 index 0000000..9e31173 --- /dev/null +++ b/15-more-types/cast/empty.py @@ -0,0 +1,2 @@ +# Mypy 0.812 can't spot this inevitable runtime IndexError +print([][0]) \ No newline at end of file diff --git a/15-more-types/cast/find.py b/15-more-types/cast/find.py new file mode 100644 index 0000000..c853ea8 --- /dev/null +++ b/15-more-types/cast/find.py @@ -0,0 +1,23 @@ +# tag::CAST[] +from typing import cast + +def find_first_str(a: list[object]) -> str: + index = next(i for i, x in enumerate(a) if isinstance(x, str)) + # We only get here if there's at least one string in a + return cast(str, a[index]) +# end::CAST[] + + +from typing import TYPE_CHECKING + +l1 = [10, 20, 'thirty', 40] +if TYPE_CHECKING: + reveal_type(l1) + +print(find_first_str(l1)) + +l2 = [0, ()] +try: + find_first_str(l2) +except StopIteration as e: + print(repr(e)) diff --git a/15-more-types/cast/tcp_echo.py b/15-more-types/cast/tcp_echo.py new file mode 100644 index 0000000..be8aaac --- /dev/null +++ b/15-more-types/cast/tcp_echo.py @@ -0,0 +1,37 @@ +import asyncio + +from asyncio import StreamReader, StreamWriter + +# tag::CAST_IMPORTS[] +from asyncio.trsock import TransportSocket +from typing import cast +# end::CAST_IMPORTS[] + +async def handle_echo(reader: StreamReader, writer: StreamWriter) -> None: + data = await reader.read(100) + message = data.decode() + addr = writer.get_extra_info('peername') + + print(f"Received {message!r} from {addr!r}") + + print(f"Send: {message!r}") + writer.write(data) + await writer.drain() + + print("Close the connection") + writer.close() + +async def main() -> None: + server = await asyncio.start_server( + handle_echo, '127.0.0.1', 8888) + + # tag::CAST_USE[] + socket_list = cast(tuple[TransportSocket, ...], server.sockets) + addr = socket_list[0].getsockname() + # end::CAST_USE[] + print(f'Serving on {addr}') + + async with server: + await server.serve_forever() + +asyncio.run(main()) diff --git a/15-more-types/cast/tcp_echo_no_cast.py b/15-more-types/cast/tcp_echo_no_cast.py new file mode 100644 index 0000000..168db55 --- /dev/null +++ b/15-more-types/cast/tcp_echo_no_cast.py @@ -0,0 +1,29 @@ +import asyncio + +from asyncio import StreamReader, StreamWriter + +async def handle_echo(reader: StreamReader, writer: StreamWriter) -> None: + data = await reader.read(100) + message = data.decode() + addr = writer.get_extra_info('peername') + + print(f"Received {message!r} from {addr!r}") + + print(f"Send: {message!r}") + writer.write(data) + await writer.drain() + + print("Close the connection") + writer.close() + +async def main() -> None: + server = await asyncio.start_server( + handle_echo, '127.0.0.1', 8888) + + addr = server.sockets[0].getsockname() + print(f'Serving on {addr}') + + async with server: + await server.serve_forever() + +asyncio.run(main()) diff --git a/15-more-types/clip_annot_demo.py b/15-more-types/clip_annot_demo.py new file mode 100644 index 0000000..dab27ae --- /dev/null +++ b/15-more-types/clip_annot_demo.py @@ -0,0 +1,3 @@ +from clip_annot_post import clip + +print(clip.__annotations__) diff --git a/15-more-types/clip_annot_1ed.py b/15-more-types/clip_annot_post.py similarity index 76% rename from 15-more-types/clip_annot_1ed.py rename to 15-more-types/clip_annot_post.py index eedd954..3760daa 100644 --- a/15-more-types/clip_annot_1ed.py +++ b/15-more-types/clip_annot_post.py @@ -1,3 +1,5 @@ +from __future__ import annotations + """ >>> clip('banana ', 6) 'banana' @@ -18,9 +20,9 @@ """ # tag::CLIP_ANNOT[] - -def clip(text: str, max_len: 'int > 0' = 80) -> str: # <1> - """Return text clipped at the last space before or after max_len +def clip(text: str, max_len: int = 80) -> str: + """Return new ``str`` clipped at last space before or after ``max_len``. + Return full ``text`` if no space found. """ end = None if len(text) > max_len: @@ -31,7 +33,7 @@ def clip(text: str, max_len: 'int > 0' = 80) -> str: # <1> space_after = text.rfind(' ', max_len) if space_after >= 0: end = space_after - if end is None: # no spaces were found + if end is None: end = len(text) return text[:end].rstrip() diff --git a/15-more-types/clip_annot_signature.rst b/15-more-types/clip_annot_signature.rst deleted file mode 100644 index 6f41aac..0000000 --- a/15-more-types/clip_annot_signature.rst +++ /dev/null @@ -1,10 +0,0 @@ ->>> from clip_annot import clip ->>> from inspect import signature ->>> sig = signature(clip) ->>> sig.return_annotation - ->>> for param in sig.parameters.values(): -... note = repr(param.annotation).ljust(13) -... print(note, ':', param.name, '=', param.default) - : text = -'int > 0' : max_len = 80 diff --git a/15-more-types/lotto/generic_lotto.py b/15-more-types/lotto/generic_lotto.py new file mode 100644 index 0000000..018d794 --- /dev/null +++ b/15-more-types/lotto/generic_lotto.py @@ -0,0 +1,29 @@ +import random + +from collections.abc import Iterable +from typing import TypeVar, Generic + +from tombola import Tombola + +T = TypeVar('T') + +class LottoBlower(Tombola, Generic[T]): # <1> + + def __init__(self, items: Iterable[T]) -> None: # <2> + self._balls = list[T](items) + + def load(self, items: Iterable[T]) -> None: # <3> + self._balls.extend(items) + + def pick(self) -> T: # <4> + try: + position = random.randrange(len(self._balls)) + except ValueError: + raise LookupError('pick from empty LottoBlower') + return self._balls.pop(position) + + def loaded(self) -> bool: # <5> + return bool(self._balls) + + def inspect(self) -> tuple[T, ...]: # <6> + return tuple(self._balls) diff --git a/15-more-types/lotto/generic_lotto_demo.py b/15-more-types/lotto/generic_lotto_demo.py new file mode 100755 index 0000000..3b63d0f --- /dev/null +++ b/15-more-types/lotto/generic_lotto_demo.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 + +from typing import TYPE_CHECKING + +# tag::LOTTO_USE[] +from generic_lotto import LottoBlower + +machine = LottoBlower[int](range(1, 11)) # <1> + +first = machine.pick() # <2> +remain = machine.inspect() # <3> +# end::LOTTO_USE[] + +expected = set(i for i in range(1, 11) if i != first) + +assert set(remain) == expected + +print('picked:', first) +print('remain:', remain) + +if TYPE_CHECKING: + reveal_type(first) + # Revealed type is 'builtins.int*' +if TYPE_CHECKING: + reveal_type(remain) + # Revealed type is 'builtins.tuple[builtins.int*]' + + diff --git a/15-more-types/lotto/generic_lotto_errors.py b/15-more-types/lotto/generic_lotto_errors.py new file mode 100755 index 0000000..17a34d5 --- /dev/null +++ b/15-more-types/lotto/generic_lotto_errors.py @@ -0,0 +1,18 @@ +from generic_lotto import LottoBlower + +machine = LottoBlower[int]([1, .2]) +## error: List item 1 has incompatible type "float"; # <1> +## expected "int" + +machine = LottoBlower[int](range(1, 11)) + +machine.load('ABC') +## error: Argument 1 to "load" of "LottoBlower" # <2> +## has incompatible type "str"; +## expected "Iterable[int]" +## note: Following member(s) of "str" have conflicts: +## note: Expected: +## note: def __iter__(self) -> Iterator[int] +## note: Got: +## note: def __iter__(self) -> Iterator[str] + diff --git a/15-more-types/lotto/tombola.py b/15-more-types/lotto/tombola.py new file mode 100644 index 0000000..50f19fc --- /dev/null +++ b/15-more-types/lotto/tombola.py @@ -0,0 +1,34 @@ +# tag::TOMBOLA_ABC[] + +import abc + +class Tombola(abc.ABC): # <1> + + @abc.abstractmethod + def load(self, iterable): # <2> + """Add items from an iterable.""" + + @abc.abstractmethod + def pick(self): # <3> + """Remove item at random, returning it. + + This method should raise `LookupError` when the instance is empty. + """ + + def loaded(self): # <4> + """Return `True` if there's at least 1 item, `False` otherwise.""" + return bool(self.inspect()) # <5> + + def inspect(self): + """Return a sorted tuple with the items currently inside.""" + items = [] + while True: # <6> + try: + items.append(self.pick()) + except LookupError: + break + self.load(items) # <7> + return tuple(items) + + +# end::TOMBOLA_ABC[] diff --git a/15-more-types/mysum.py b/15-more-types/mysum.py index 3fb041a..2c341aa 100644 --- a/15-more-types/mysum.py +++ b/15-more-types/mysum.py @@ -1,13 +1,14 @@ -from functools import reduce # <1> -from operator import add -from typing import overload, Iterable, Union, TypeVar +import functools +import operator +from collections.abc import Iterable +from typing import overload, Union, TypeVar T = TypeVar('T') -S = TypeVar('S') # <2> +S = TypeVar('S') # <1> @overload -def sum(it: Iterable[T]) -> Union[T, int]: ... # <3> +def sum(it: Iterable[T]) -> Union[T, int]: ... # <2> @overload -def sum(it: Iterable[T], /, start: S) -> Union[T, S]: ... # <4> -def sum(it, /, start=0): # <5> - return reduce(add, it, start) +def sum(it: Iterable[T], /, start: S) -> Union[T, S]: ... # <3> +def sum(it, /, start=0): # <4> + return functools.reduce(operator.add, it, start) diff --git a/15-more-types/protocol/abs_demo.py b/15-more-types/protocol/abs_demo.py new file mode 100755 index 0000000..9cb7f80 --- /dev/null +++ b/15-more-types/protocol/abs_demo.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 + +import math +from typing import NamedTuple, SupportsAbs + +class Vector2d(NamedTuple): + x: float + y: float + + def __abs__(self) -> float: # <1> + return math.hypot(self.x, self.y) + +def is_unit(v: SupportsAbs[float]) -> bool: # <2> + """'True' if the magnitude of 'v' is close to 1.""" + return math.isclose(abs(v), 1.0) # <3> + +assert issubclass(Vector2d, SupportsAbs) # <4> + +v0 = Vector2d(0, 1) # <5> +sqrt2 = math.sqrt(2) +v1 = Vector2d(sqrt2 / 2, sqrt2 / 2) +v2 = Vector2d(1, 1) +v3 = complex(.5, math.sqrt(3) / 2) +v4 = 1 # <6> + +assert is_unit(v0) +assert is_unit(v1) +assert not is_unit(v2) +assert is_unit(v3) +assert is_unit(v4) + +print('OK') diff --git a/15-more-types/mymax/mymax.py b/15-more-types/protocol/mymax/mymax.py similarity index 91% rename from 15-more-types/mymax/mymax.py rename to 15-more-types/protocol/mymax/mymax.py index 868ecd4..5b9f4b2 100644 --- a/15-more-types/mymax/mymax.py +++ b/15-more-types/protocol/mymax/mymax.py @@ -12,10 +12,10 @@ def __lt__(self, other: Any) -> bool: ... EMPTY_MSG = 'max() arg is an empty sequence' @overload -def max(__arg1: LT, __arg2: LT, *_args: LT, key: None = ...) -> LT: +def max(__arg1: LT, __arg2: LT, *args: LT, key: None = ...) -> LT: ... @overload -def max(__arg1: T, __arg2: T, *_args: T, key: Callable[[T], LT]) -> T: +def max(__arg1: T, __arg2: T, *args: T, key: Callable[[T], LT]) -> T: ... @overload def max(__iterable: Iterable[LT], *, key: None = ...) -> LT: diff --git a/15-more-types/mymax/mymax_demo.py b/15-more-types/protocol/mymax/mymax_demo.py similarity index 100% rename from 15-more-types/mymax/mymax_demo.py rename to 15-more-types/protocol/mymax/mymax_demo.py diff --git a/15-more-types/mymax/mymax_test.py b/15-more-types/protocol/mymax/mymax_test.py similarity index 100% rename from 15-more-types/mymax/mymax_test.py rename to 15-more-types/protocol/mymax/mymax_test.py diff --git a/15-more-types/erp.py b/15-more-types/protocol/random/erp.py similarity index 100% rename from 15-more-types/erp.py rename to 15-more-types/protocol/random/erp.py diff --git a/15-more-types/erp_test.py b/15-more-types/protocol/random/erp_test.py similarity index 100% rename from 15-more-types/erp_test.py rename to 15-more-types/protocol/random/erp_test.py diff --git a/15-more-types/protocol/random/generic_randompick.py b/15-more-types/protocol/random/generic_randompick.py new file mode 100644 index 0000000..e3ecb58 --- /dev/null +++ b/15-more-types/protocol/random/generic_randompick.py @@ -0,0 +1,7 @@ +from typing import Protocol, runtime_checkable, TypeVar + +T_co = TypeVar('T_co', covariant=True) # <1> + +@runtime_checkable +class RandomPicker(Protocol[T_co]): # <2> + def pick(self) -> T_co: ... # <3> diff --git a/15-more-types/randompick_generic_test.py b/15-more-types/protocol/random/generic_randompick_test.py similarity index 59% rename from 15-more-types/randompick_generic_test.py rename to 15-more-types/protocol/random/generic_randompick_test.py index d642b26..4e43f20 100644 --- a/15-more-types/randompick_generic_test.py +++ b/15-more-types/protocol/random/generic_randompick_test.py @@ -1,24 +1,26 @@ import random -from typing import Iterable, TYPE_CHECKING +from typing import Iterable, Generic, TypeVar, TYPE_CHECKING -from randompick_generic import GenericRandomPicker +T_co = TypeVar('T_co', covariant=True) +from generic_randompick import RandomPicker -class LottoPicker: - def __init__(self, items: Iterable[int]) -> None: + +class LottoPicker(Generic[T_co]): + def __init__(self, items: Iterable[T_co]) -> None: self._items = list(items) random.shuffle(self._items) - def pick(self) -> int: + def pick(self) -> T_co: return self._items.pop() def test_issubclass() -> None: - assert issubclass(LottoPicker, GenericRandomPicker) + assert issubclass(LottoPicker, RandomPicker) def test_isinstance() -> None: - popper: GenericRandomPicker = LottoPicker([1]) + popper: RandomPicker = LottoPicker[int]([1]) if TYPE_CHECKING: reveal_type(popper) # Revealed type is '???' diff --git a/15-more-types/randompop.py b/15-more-types/protocol/random/randompop.py similarity index 100% rename from 15-more-types/randompop.py rename to 15-more-types/protocol/random/randompop.py diff --git a/15-more-types/randompop_test.py b/15-more-types/protocol/random/randompop_test.py similarity index 100% rename from 15-more-types/randompop_test.py rename to 15-more-types/protocol/random/randompop_test.py diff --git a/15-more-types/randompick_generic.py b/15-more-types/randompick_generic.py deleted file mode 100644 index 6e4aa10..0000000 --- a/15-more-types/randompick_generic.py +++ /dev/null @@ -1,7 +0,0 @@ -from typing import Protocol, runtime_checkable, TypeVar - -T_co = TypeVar('T_co', covariant=True) - -@runtime_checkable -class GenericRandomPicker(Protocol[T_co]): - def pick(self) -> T_co: ... diff --git a/18-context-mngr/lispy/LICENSE b/18-context-mngr/lispy/LICENSE new file mode 100644 index 0000000..ca550a2 --- /dev/null +++ b/18-context-mngr/lispy/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2010-2017 Peter Norvig + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/18-context-mngr/lispy/README.md b/18-context-mngr/lispy/README.md new file mode 100644 index 0000000..1a2acfb --- /dev/null +++ b/18-context-mngr/lispy/README.md @@ -0,0 +1,24 @@ +# lis.py + +This directory contains 3 versions of +[Peter Norvig's `lis.py` interpreter](https://norvig.com/lispy.html) +for Scheme. + +* `original/`: Norvig's `lis.py` unchanged, `lispy.py` with +[minor changes](https://github.com/norvig/pytudes/pull/106) to run on Python 3, +and the `lispytest.py` custom test suite; +* `py3.9/`: `lis.py` with type hints and a few minor edits—requires Python 3.9; +* `py3.10/`: `lis.py` with type hints, pattern matching, and minor edits—requires Python 3.10. + +The `py3.9/` and `py3.10/` directories also have identical `lis_test.py` to run with +[pytest](https://docs.pytest.org). +These files include all the +[`lis_tests` suite](https://github.com/norvig/pytudes/blob/60168bce8cdfacf57c92a5b2979f0b2e95367753/py/lispytest.py#L5) +from `original/lispytest.py`, +and individual tests for each expression and special form handled by `evaluate`. + +## Provenance, Copyright and License + +`lis.py` is published in the [norvig/pytudes](https://github.com/norvig/pytudes) repository on Github. +The copyright holder is Peter Norvig and the code is licensed under the +[MIT license](https://github.com/norvig/pytudes/blob/60168bce8cdfacf57c92a5b2979f0b2e95367753/LICENSE). diff --git a/18-context-mngr/lispy/original/lis.py b/18-context-mngr/lispy/original/lis.py new file mode 100644 index 0000000..f81376a --- /dev/null +++ b/18-context-mngr/lispy/original/lis.py @@ -0,0 +1,132 @@ +################ Lispy: Scheme Interpreter in Python 3.3+ + +## (c) Peter Norvig, 2010-18; See http://norvig.com/lispy.html + +################ Imports and Types + +import math +import operator as op +from collections import ChainMap as Environment + +Symbol = str # A Lisp Symbol is implemented as a Python str +List = list # A Lisp List is implemented as a Python list +Number = (int, float) # A Lisp Number is implemented as a Python int or float + +class Procedure(object): + "A user-defined Scheme procedure." + def __init__(self, parms, body, env): + self.parms, self.body, self.env = parms, body, env + def __call__(self, *args): + env = Environment(dict(zip(self.parms, args)), self.env) + return eval(self.body, env) + +################ Global Environment + +def standard_env(): + "An environment with some Scheme standard procedures." + env = {} + env.update(vars(math)) # sin, cos, sqrt, pi, ... + env.update({ + '+':op.add, '-':op.sub, '*':op.mul, '/':op.truediv, + '>':op.gt, '<':op.lt, '>=':op.ge, '<=':op.le, '=':op.eq, + 'abs': abs, + 'append': op.add, + 'apply': lambda proc, args: proc(*args), + 'begin': lambda *x: x[-1], + 'car': lambda x: x[0], + 'cdr': lambda x: x[1:], + 'cons': lambda x,y: [x] + y, + 'eq?': op.is_, + 'equal?': op.eq, + 'length': len, + 'list': lambda *x: list(x), + 'list?': lambda x: isinstance(x,list), + 'map': lambda *args: list(map(*args)), + 'max': max, + 'min': min, + 'not': op.not_, + 'null?': lambda x: x == [], + 'number?': lambda x: isinstance(x, Number), + 'procedure?': callable, + 'round': round, + 'symbol?': lambda x: isinstance(x, Symbol), + }) + return env + +global_env = standard_env() + +################ Parsing: parse, tokenize, and read_from_tokens + +def parse(program): + "Read a Scheme expression from a string." + return read_from_tokens(tokenize(program)) + +def tokenize(s): + "Convert a string into a list of tokens." + return s.replace('(',' ( ').replace(')',' ) ').split() + +def read_from_tokens(tokens): + "Read an expression from a sequence of tokens." + if len(tokens) == 0: + raise SyntaxError('unexpected EOF while reading') + token = tokens.pop(0) + if '(' == token: + L = [] + while tokens[0] != ')': + L.append(read_from_tokens(tokens)) + tokens.pop(0) # pop off ')' + return L + elif ')' == token: + raise SyntaxError('unexpected )') + else: + return atom(token) + +def atom(token): + "Numbers become numbers; every other token is a symbol." + try: return int(token) + except ValueError: + try: return float(token) + except ValueError: + return Symbol(token) + +################ Interaction: A REPL + +def repl(prompt='lis.py> '): + "A prompt-read-eval-print loop." + while True: + val = eval(parse(input(prompt))) + if val is not None: + print(lispstr(val)) + +def lispstr(exp): + "Convert a Python object back into a Lisp-readable string." + if isinstance(exp, List): + return '(' + ' '.join(map(lispstr, exp)) + ')' + else: + return str(exp) + +################ eval + +def eval(x, env=global_env): + "Evaluate an expression in an environment." + if isinstance(x, Symbol): # variable reference + return env[x] + elif not isinstance(x, List): # constant literal + return x + elif x[0] == 'quote': # (quote exp) + (_, exp) = x + return exp + elif x[0] == 'if': # (if test conseq alt) + (_, test, conseq, alt) = x + exp = (conseq if eval(test, env) else alt) + return eval(exp, env) + elif x[0] == 'define': # (define var exp) + (_, var, exp) = x + env[var] = eval(exp, env) + elif x[0] == 'lambda': # (lambda (var...) body) + (_, parms, body) = x + return Procedure(parms, body, env) + else: # (proc arg...) + proc = eval(x[0], env) + args = [eval(exp, env) for exp in x[1:]] + return proc(*args) diff --git a/18-context-mngr/lispy/original/lispy.py b/18-context-mngr/lispy/original/lispy.py new file mode 100644 index 0000000..b17341c --- /dev/null +++ b/18-context-mngr/lispy/original/lispy.py @@ -0,0 +1,316 @@ +################ Scheme Interpreter in Python + +## (c) Peter Norvig, 2010; See http://norvig.com/lispy2.html + +################ Symbol, Procedure, classes + +import re, sys, io + +class Symbol(str): pass + +def Sym(s, symbol_table={}): + "Find or create unique Symbol entry for str s in symbol table." + if s not in symbol_table: symbol_table[s] = Symbol(s) + return symbol_table[s] + +_quote, _if, _set, _define, _lambda, _begin, _definemacro, = map(Sym, +"quote if set! define lambda begin define-macro".split()) + +_quasiquote, _unquote, _unquotesplicing = map(Sym, +"quasiquote unquote unquote-splicing".split()) + +class Procedure: + "A user-defined Scheme procedure." + def __init__(self, parms, exp, env): + self.parms, self.exp, self.env = parms, exp, env + def __call__(self, *args): + return eval(self.exp, Env(self.parms, args, self.env)) + +################ parse, read, and user interaction + +def parse(inport): + "Parse a program: read and expand/error-check it." + # Backwards compatibility: given a str, convert it to an InPort + if isinstance(inport, str): inport = InPort(io.StringIO(inport)) + return expand(read(inport), toplevel=True) + +eof_object = Symbol('#') # Note: uninterned; can't be read + +class InPort: + "An input port. Retains a line of chars." + tokenizer = r"""\s*(,@|[('`,)]|"(?:[\\].|[^\\"])*"|;.*|[^\s('"`,;)]*)(.*)""" + def __init__(self, file): + self.file = file; self.line = '' + def next_token(self): + "Return the next token, reading new text into line buffer if needed." + while True: + if self.line == '': self.line = self.file.readline() + if self.line == '': return eof_object + token, self.line = re.match(InPort.tokenizer, self.line).groups() + if token != '' and not token.startswith(';'): + return token + +def readchar(inport): + "Read the next character from an input port." + if inport.line != '': + ch, inport.line = inport.line[0], inport.line[1:] + return ch + else: + return inport.file.read(1) or eof_object + +def read(inport): + "Read a Scheme expression from an input port." + def read_ahead(token): + if '(' == token: + L = [] + while True: + token = inport.next_token() + if token == ')': return L + else: L.append(read_ahead(token)) + elif ')' == token: raise SyntaxError('unexpected )') + elif token in quotes: return [quotes[token], read(inport)] + elif token is eof_object: raise SyntaxError('unexpected EOF in list') + else: return atom(token) + # body of read: + token1 = inport.next_token() + return eof_object if token1 is eof_object else read_ahead(token1) + +quotes = {"'":_quote, "`":_quasiquote, ",":_unquote, ",@":_unquotesplicing} + +def atom(token): + 'Numbers become numbers; #t and #f are booleans; "..." string; otherwise Symbol.' + if token == '#t': return True + elif token == '#f': return False + elif token[0] == '"': return token[1:-1] + try: return int(token) + except ValueError: + try: return float(token) + except ValueError: + try: return complex(token.replace('i', 'j', 1)) + except ValueError: + return Sym(token) + +def to_string(x): + "Convert a Python object back into a Lisp-readable string." + if x is True: return "#t" + elif x is False: return "#f" + elif isa(x, Symbol): return x + elif isa(x, str): return repr(x) + elif isa(x, list): return '('+' '.join(map(to_string, x))+')' + elif isa(x, complex): return str(x).replace('j', 'i') + else: return str(x) + +def load(filename): + "Eval every expression from a file." + repl(None, InPort(open(filename)), None) + +def repl(prompt='lispy> ', inport=InPort(sys.stdin), out=sys.stdout): + "A prompt-read-eval-print loop." + sys.stderr.write("Lispy version 2.0\n") + while True: + try: + if prompt: sys.stderr.write(prompt) + x = parse(inport) + if x is eof_object: return + val = eval(x) + if val is not None and out: print(to_string(val), file=out) + except Exception as e: + print('%s: %s' % (type(e).__name__, e)) + +################ Environment class + +class Env(dict): + "An environment: a dict of {'var':val} pairs, with an outer Env." + def __init__(self, parms=(), args=(), outer=None): + # Bind parm list to corresponding args, or single parm to list of args + self.outer = outer + if isa(parms, Symbol): + self.update({parms:list(args)}) + else: + if len(args) != len(parms): + raise TypeError('expected %s, given %s, ' + % (to_string(parms), to_string(args))) + self.update(zip(parms,args)) + def find(self, var): + "Find the innermost Env where var appears." + if var in self: return self + elif self.outer is None: raise LookupError(var) + else: return self.outer.find(var) + +def is_pair(x): return x != [] and isa(x, list) +def cons(x, y): return [x]+y + +def callcc(proc): + "Call proc with current continuation; escape only" + ball = RuntimeWarning("Sorry, can't continue this continuation any longer.") + def throw(retval): ball.retval = retval; raise ball + try: + return proc(throw) + except RuntimeWarning as w: + if w is ball: return ball.retval + else: raise w + +def add_globals(self): + "Add some Scheme standard procedures." + import math, cmath, operator as op + self.update(vars(math)) + self.update(vars(cmath)) + self.update({ + '+':op.add, '-':op.sub, '*':op.mul, '/':op.truediv, 'not':op.not_, + '>':op.gt, '<':op.lt, '>=':op.ge, '<=':op.le, '=':op.eq, + 'equal?':op.eq, 'eq?':op.is_, 'length':len, 'cons':cons, + 'car':lambda x:x[0], 'cdr':lambda x:x[1:], 'append':op.add, + 'list':lambda *x:list(x), 'list?': lambda x:isa(x,list), + 'null?':lambda x:x==[], 'symbol?':lambda x: isa(x, Symbol), + 'boolean?':lambda x: isa(x, bool), 'pair?':is_pair, + 'port?': lambda x:isa(x,file), 'apply':lambda proc,l: proc(*l), + 'eval':lambda x: eval(expand(x)), 'load':lambda fn: load(fn), 'call/cc':callcc, + 'open-input-file':open,'close-input-port':lambda p: p.file.close(), + 'open-output-file':lambda f:open(f,'w'), 'close-output-port':lambda p: p.close(), + 'eof-object?':lambda x:x is eof_object, 'read-char':readchar, + 'read':read, 'write':lambda x,port=sys.stdout:port.write(to_string(x)), + 'display':lambda x,port=sys.stdout:port.write(x if isa(x,str) else to_string(x))}) + return self + +isa = isinstance + +global_env = add_globals(Env()) + +################ eval (tail recursive) + +def eval(x, env=global_env): + "Evaluate an expression in an environment." + while True: + if isa(x, Symbol): # variable reference + return env.find(x)[x] + elif not isa(x, list): # constant literal + return x + elif x[0] is _quote: # (quote exp) + (_, exp) = x + return exp + elif x[0] is _if: # (if test conseq alt) + (_, test, conseq, alt) = x + x = (conseq if eval(test, env) else alt) + elif x[0] is _set: # (set! var exp) + (_, var, exp) = x + env.find(var)[var] = eval(exp, env) + return None + elif x[0] is _define: # (define var exp) + (_, var, exp) = x + env[var] = eval(exp, env) + return None + elif x[0] is _lambda: # (lambda (var*) exp) + (_, vars, exp) = x + return Procedure(vars, exp, env) + elif x[0] is _begin: # (begin exp+) + for exp in x[1:-1]: + eval(exp, env) + x = x[-1] + else: # (proc exp*) + exps = [eval(exp, env) for exp in x] + proc = exps.pop(0) + if isa(proc, Procedure): + x = proc.exp + env = Env(proc.parms, exps, proc.env) + else: + return proc(*exps) + +################ expand + +def expand(x, toplevel=False): + "Walk tree of x, making optimizations/fixes, and signaling SyntaxError." + require(x, x!=[]) # () => Error + if not isa(x, list): # constant => unchanged + return x + elif x[0] is _quote: # (quote exp) + require(x, len(x)==2) + return x + elif x[0] is _if: + if len(x)==3: x = x + [None] # (if t c) => (if t c None) + require(x, len(x)==4) + return list(map(expand, x)) + elif x[0] is _set: + require(x, len(x)==3); + var = x[1] # (set! non-var exp) => Error + require(x, isa(var, Symbol), "can set! only a symbol") + return [_set, var, expand(x[2])] + elif x[0] is _define or x[0] is _definemacro: + require(x, len(x)>=3) + _def, v, body = x[0], x[1], x[2:] + if isa(v, list) and v: # (define (f args) body) + f, args = v[0], v[1:] # => (define f (lambda (args) body)) + return expand([_def, f, [_lambda, args]+body]) + else: + require(x, len(x)==3) # (define non-var/list exp) => Error + require(x, isa(v, Symbol), "can define only a symbol") + exp = expand(x[2]) + if _def is _definemacro: + require(x, toplevel, "define-macro only allowed at top level") + proc = eval(exp) + require(x, callable(proc), "macro must be a procedure") + macro_table[v] = proc # (define-macro v proc) + return None # => None; add v:proc to macro_table + return [_define, v, exp] + elif x[0] is _begin: + if len(x)==1: return None # (begin) => None + else: return [expand(xi, toplevel) for xi in x] + elif x[0] is _lambda: # (lambda (x) e1 e2) + require(x, len(x)>=3) # => (lambda (x) (begin e1 e2)) + vars, body = x[1], x[2:] + require(x, (isa(vars, list) and all(isa(v, Symbol) for v in vars)) + or isa(vars, Symbol), "illegal lambda argument list") + exp = body[0] if len(body) == 1 else [_begin] + body + return [_lambda, vars, expand(exp)] + elif x[0] is _quasiquote: # `x => expand_quasiquote(x) + require(x, len(x)==2) + return expand_quasiquote(x[1]) + elif isa(x[0], Symbol) and x[0] in macro_table: + return expand(macro_table[x[0]](*x[1:]), toplevel) # (m arg...) + else: # => macroexpand if m isa macro + return list(map(expand, x)) # (f arg...) => expand each + +def require(x, predicate, msg="wrong length"): + "Signal a syntax error if predicate is false." + if not predicate: raise SyntaxError(to_string(x)+': '+msg) + +_append, _cons, _let = map(Sym, "append cons let".split()) + +def expand_quasiquote(x): + """Expand `x => 'x; `,x => x; `(,@x y) => (append x y) """ + if not is_pair(x): + return [_quote, x] + require(x, x[0] is not _unquotesplicing, "can't splice here") + if x[0] is _unquote: + require(x, len(x)==2) + return x[1] + elif is_pair(x[0]) and x[0][0] is _unquotesplicing: + require(x[0], len(x[0])==2) + return [_append, x[0][1], expand_quasiquote(x[1:])] + else: + return [_cons, expand_quasiquote(x[0]), expand_quasiquote(x[1:])] + +def let(*args): + args = list(args) + x = cons(_let, args) + require(x, len(args)>1) + bindings, body = args[0], args[1:] + require(x, all(isa(b, list) and len(b)==2 and isa(b[0], Symbol) + for b in bindings), "illegal binding list") + vars, vals = zip(*bindings) + return [[_lambda, list(vars)]+list(map(expand, body))] + list(map(expand, vals)) + +macro_table = {_let:let} ## More macros can go here + +eval(parse("""(begin + +(define-macro and (lambda args + (if (null? args) #t + (if (= (length args) 1) (car args) + `(if ,(car args) (and ,@(cdr args)) #f))))) + +;; More macros can also go here + +)""")) + +if __name__ == '__main__': + repl() diff --git a/18-context-mngr/lispy/original/lispytest.py b/18-context-mngr/lispy/original/lispytest.py new file mode 100644 index 0000000..b324ff3 --- /dev/null +++ b/18-context-mngr/lispy/original/lispytest.py @@ -0,0 +1,122 @@ +from __future__ import print_function + +################ Tests for lis.py and lispy.py + +lis_tests = [ + ("(quote (testing 1 (2.0) -3.14e159))", ['testing', 1, [2.0], -3.14e159]), + ("(+ 2 2)", 4), + ("(+ (* 2 100) (* 1 10))", 210), + ("(if (> 6 5) (+ 1 1) (+ 2 2))", 2), + ("(if (< 6 5) (+ 1 1) (+ 2 2))", 4), + ("(define x 3)", None), ("x", 3), ("(+ x x)", 6), + ("((lambda (x) (+ x x)) 5)", 10), + ("(define twice (lambda (x) (* 2 x)))", None), ("(twice 5)", 10), + ("(define compose (lambda (f g) (lambda (x) (f (g x)))))", None), + ("((compose list twice) 5)", [10]), + ("(define repeat (lambda (f) (compose f f)))", None), + ("((repeat twice) 5)", 20), ("((repeat (repeat twice)) 5)", 80), + ("(define fact (lambda (n) (if (<= n 1) 1 (* n (fact (- n 1))))))", None), + ("(fact 3)", 6), + ("(fact 50)", 30414093201713378043612608166064768844377641568960512000000000000), + ("(define abs (lambda (n) ((if (> n 0) + -) 0 n)))", None), + ("(list (abs -3) (abs 0) (abs 3))", [3, 0, 3]), + ("""(define combine (lambda (f) + (lambda (x y) + (if (null? x) (quote ()) + (f (list (car x) (car y)) + ((combine f) (cdr x) (cdr y)))))))""", None), + ("(define zip (combine cons))", None), + ("(zip (list 1 2 3 4) (list 5 6 7 8))", [[1, 5], [2, 6], [3, 7], [4, 8]]), + ("""(define riff-shuffle (lambda (deck) (begin + (define take (lambda (n seq) (if (<= n 0) (quote ()) (cons (car seq) (take (- n 1) (cdr seq)))))) + (define drop (lambda (n seq) (if (<= n 0) seq (drop (- n 1) (cdr seq))))) + (define mid (lambda (seq) (/ (length seq) 2))) + ((combine append) (take (mid deck) deck) (drop (mid deck) deck)))))""", None), + ("(riff-shuffle (list 1 2 3 4 5 6 7 8))", [1, 5, 2, 6, 3, 7, 4, 8]), + ("((repeat riff-shuffle) (list 1 2 3 4 5 6 7 8))", [1, 3, 5, 7, 2, 4, 6, 8]), + ("(riff-shuffle (riff-shuffle (riff-shuffle (list 1 2 3 4 5 6 7 8))))", [1,2,3,4,5,6,7,8]), + ] + +lispy_tests = [ + ("()", SyntaxError), ("(set! x)", SyntaxError), + ("(define 3 4)", SyntaxError), + ("(quote 1 2)", SyntaxError), ("(if 1 2 3 4)", SyntaxError), + ("(lambda 3 3)", SyntaxError), ("(lambda (x))", SyntaxError), + ("""(if (= 1 2) (define-macro a 'a) + (define-macro a 'b))""", SyntaxError), + ("(define (twice x) (* 2 x))", None), ("(twice 2)", 4), + ("(twice 2 2)", TypeError), + ("(define lyst (lambda items items))", None), + ("(lyst 1 2 3 (+ 2 2))", [1,2,3,4]), + ("(if 1 2)", 2), + ("(if (= 3 4) 2)", None), + ("(begin (define x 1) (set! x (+ x 1)) (+ x 1))", 3), + ("(define ((account bal) amt) (set! bal (+ bal amt)) bal)", None), + ("(define a1 (account 100))", None), + ("(a1 0)", 100), ("(a1 10)", 110), ("(a1 10)", 120), + ("""(define (newton guess function derivative epsilon) + (define guess2 (- guess (/ (function guess) (derivative guess)))) + (if (< (abs (- guess guess2)) epsilon) guess2 + (newton guess2 function derivative epsilon)))""", None), + ("""(define (square-root a) + (newton 1 (lambda (x) (- (* x x) a)) (lambda (x) (* 2 x)) 1e-8))""", None), + ("(> (square-root 200.) 14.14213)", True), + ("(< (square-root 200.) 14.14215)", True), + ("(= (square-root 200.) (sqrt 200.))", True), + ("""(define (sum-squares-range start end) + (define (sumsq-acc start end acc) + (if (> start end) acc (sumsq-acc (+ start 1) end (+ (* start start) acc)))) + (sumsq-acc start end 0))""", None), + ("(sum-squares-range 1 3000)", 9004500500), ## Tests tail recursion + ("(call/cc (lambda (throw) (+ 5 (* 10 (throw 1))))) ;; throw", 1), + ("(call/cc (lambda (throw) (+ 5 (* 10 1)))) ;; do not throw", 15), + ("""(call/cc (lambda (throw) + (+ 5 (* 10 (call/cc (lambda (escape) (* 100 (escape 3)))))))) ; 1 level""", 35), + ("""(call/cc (lambda (throw) + (+ 5 (* 10 (call/cc (lambda (escape) (* 100 (throw 3)))))))) ; 2 levels""", 3), + ("""(call/cc (lambda (throw) + (+ 5 (* 10 (call/cc (lambda (escape) (* 100 1))))))) ; 0 levels""", 1005), + ("(* 1i 1i)", -1), ("(sqrt -1)", 1j), + ("(let ((a 1) (b 2)) (+ a b))", 3), + ("(let ((a 1) (b 2 3)) (+ a b))", SyntaxError), + ("(and 1 2 3)", 3), ("(and (> 2 1) 2 3)", 3), ("(and)", True), + ("(and (> 2 1) (> 2 3))", False), + ("(define-macro unless (lambda args `(if (not ,(car args)) (begin ,@(cdr args))))) ; test `", None), + ("(unless (= 2 (+ 1 1)) (display 2) 3 4)", None), + (r'(unless (= 4 (+ 1 1)) (display 2) (display "\n") 3 4)', 4), + ("(quote x)", 'x'), + ("(quote (1 2 three))", [1, 2, 'three']), + ("'x", 'x'), + ("'(one 2 3)", ['one', 2, 3]), + ("(define L (list 1 2 3))", None), + ("`(testing ,@L testing)", ['testing',1,2,3,'testing']), + ("`(testing ,L testing)", ['testing',[1,2,3],'testing']), + ("`,@L", SyntaxError), + ("""'(1 ;test comments ' + ;skip this line + 2 ; more ; comments ; ) ) + 3) ; final comment""", [1,2,3]), + ] + +def test(tests, name=''): + "For each (exp, expected) test case, see if eval(parse(exp)) == expected." + fails = 0 + for (x, expected) in tests: + try: + result = eval(parse(x)) + print(x, '=>', lispstr(result)) + ok = (result == expected) + except Exception as e: + print(x, '=raises=>', type(e).__name__, e) + ok = isinstance(expected, type) and issubclass(expected, Exception) and isinstance(e, expected) + if not ok: + fails += 1 + print('FAIL!!! Expected', expected) + print('%s %s: %d out of %d tests fail.' % ('*'*45, name, fails, len(tests))) + +if __name__ == '__main__': + from lis import * + test(lis_tests, 'lis.py') + from lispy import * + test(lis_tests+lispy_tests, 'lispy.py') + diff --git a/18-context-mngr/lispy/py3.10/lis.py b/18-context-mngr/lispy/py3.10/lis.py new file mode 100644 index 0000000..ca0f8e8 --- /dev/null +++ b/18-context-mngr/lispy/py3.10/lis.py @@ -0,0 +1,160 @@ +################ Lispy: Scheme Interpreter in Python 3.9 + +## (c) Peter Norvig, 2010-18; See http://norvig.com/lispy.html +## Minor edits for Fluent Python, Second Edition (O'Reilly, 2021) +## by Luciano Ramalho, adding type hints and pattern matching. + +################ Imports and Types + +import math +import operator as op +from collections import ChainMap +from collections.abc import MutableMapping +from typing import Any, TypeAlias + +Atom: TypeAlias = float | int | str +Expression: TypeAlias = Atom | list + +Environment: TypeAlias = MutableMapping[str, object] + + +class Procedure: + "A user-defined Scheme procedure." + + def __init__(self, parms: list[str], body: Expression, env: Environment): + self.parms, self.body, self.env = parms, body, env + + def __call__(self, *args: Expression) -> Any: + env: Environment = ChainMap(dict(zip(self.parms, args)), self.env) + return evaluate(self.body, env) + + +################ Global Environment + + +def standard_env() -> Environment: + "An environment with some Scheme standard procedures." + env: Environment = {} + env.update(vars(math)) # sin, cos, sqrt, pi, ... + env.update( + { + '+': op.add, + '-': op.sub, + '*': op.mul, + '/': op.truediv, + '>': op.gt, + '<': op.lt, + '>=': op.ge, + '<=': op.le, + '=': op.eq, + 'abs': abs, + 'append': op.add, + 'apply': lambda proc, args: proc(*args), + 'begin': lambda *x: x[-1], + 'car': lambda x: x[0], + 'cdr': lambda x: x[1:], + 'cons': lambda x, y: [x] + y, + 'eq?': op.is_, + 'equal?': op.eq, + 'length': len, + 'list': lambda *x: list(x), + 'list?': lambda x: isinstance(x, list), + 'map': lambda *args: list(map(*args)), + 'max': max, + 'min': min, + 'not': op.not_, + 'null?': lambda x: x == [], + 'number?': lambda x: isinstance(x, (int, float)), + 'procedure?': callable, + 'round': round, + 'symbol?': lambda x: isinstance(x, str), + } + ) + return env + + +global_env: Environment = standard_env() + +################ Parsing: parse, tokenize, and read_from_tokens + + +def parse(program: str) -> Expression: + "Read a Scheme expression from a string." + return read_from_tokens(tokenize(program)) + + +def tokenize(s: str) -> list[str]: + "Convert a string into a list of tokens." + return s.replace('(', ' ( ').replace(')', ' ) ').split() + + +def read_from_tokens(tokens: list[str]) -> Expression: + "Read an expression from a sequence of tokens." + if len(tokens) == 0: + raise SyntaxError('unexpected EOF while reading') + token = tokens.pop(0) + if '(' == token: + L = [] + while tokens[0] != ')': + L.append(read_from_tokens(tokens)) + tokens.pop(0) # pop off ')' + return L + elif ')' == token: + raise SyntaxError('unexpected )') + else: + return parse_atom(token) + + +def parse_atom(token: str) -> Atom: + "Numbers become numbers; every other token is a symbol." + try: + return int(token) + except ValueError: + try: + return float(token) + except ValueError: + return str(token) + + +################ Interaction: A REPL + + +def repl(prompt: str = 'lis.py> ') -> None: + "A prompt-read-evaluate-print loop." + while True: + val = evaluate(parse(input(prompt))) + if val is not None: + print(lispstr(val)) + + +def lispstr(exp: object) -> str: + "Convert a Python object back into a Lisp-readable string." + if isinstance(exp, list): + return '(' + ' '.join(map(lispstr, exp)) + ')' + else: + return str(exp) + + +################ eval + + +def evaluate(x: Expression, env: Environment = global_env) -> Any: + "Evaluate an expression in an environment." + match x: + case str(): # variable reference + return env[x] + case literal if not isinstance(x, list): # constant literal + return literal + case ['quote', exp]: # (quote exp) + return exp + case ['if', test, conseq, alt]: # (if test conseq alt) + exp = conseq if evaluate(test, env) else alt + return evaluate(exp, env) + case ['define', var, exp]: # (define var exp) + env[var] = evaluate(exp, env) + case ['lambda', parms, body]: # (lambda (var...) body) + return Procedure(parms, body, env) + case [op, *args]: # (proc arg...) + proc = evaluate(op, env) + values = (evaluate(arg, env) for arg in args) + return proc(*values) diff --git a/18-context-mngr/lispy/py3.10/lis_test.py b/18-context-mngr/lispy/py3.10/lis_test.py new file mode 100755 index 0000000..591046d --- /dev/null +++ b/18-context-mngr/lispy/py3.10/lis_test.py @@ -0,0 +1,147 @@ +from typing import Optional + +from pytest import mark + +from lis import parse, evaluate, Expression, Environment, standard_env + + +@mark.parametrize( 'source, expected', [ + ("(quote (testing 1 (2.0) -3.14e159))", ['testing', 1, [2.0], -3.14e159]), + ("(+ 2 2)", 4), + ("(+ (* 2 100) (* 1 10))", 210), + ("(if (> 6 5) (+ 1 1) (+ 2 2))", 2), + ("(if (< 6 5) (+ 1 1) (+ 2 2))", 4), + ("(define x 3)", None), ("x", 3), ("(+ x x)", 6), + ("((lambda (x) (+ x x)) 5)", 10), + ("(define twice (lambda (x) (* 2 x)))", None), ("(twice 5)", 10), + ("(define compose (lambda (f g) (lambda (x) (f (g x)))))", None), + ("((compose list twice) 5)", [10]), + ("(define repeat (lambda (f) (compose f f)))", None), + ("((repeat twice) 5)", 20), ("((repeat (repeat twice)) 5)", 80), + ("(define fact (lambda (n) (if (<= n 1) 1 (* n (fact (- n 1))))))", None), + ("(fact 3)", 6), + ("(fact 50)", 30414093201713378043612608166064768844377641568960512000000000000), + ("(define abs (lambda (n) ((if (> n 0) + -) 0 n)))", None), + ("(list (abs -3) (abs 0) (abs 3))", [3, 0, 3]), + ("""(define combine (lambda (f) + (lambda (x y) + (if (null? x) (quote ()) + (f (list (car x) (car y)) + ((combine f) (cdr x) (cdr y)))))))""", None), + ("(define zip (combine cons))", None), + ("(zip (list 1 2 3 4) (list 5 6 7 8))", [[1, 5], [2, 6], [3, 7], [4, 8]]), + ("""(define riff-shuffle (lambda (deck) (begin + (define take (lambda (n seq) (if (<= n 0) (quote ()) (cons (car seq) (take (- n 1) (cdr seq)))))) + (define drop (lambda (n seq) (if (<= n 0) seq (drop (- n 1) (cdr seq))))) + (define mid (lambda (seq) (/ (length seq) 2))) + ((combine append) (take (mid deck) deck) (drop (mid deck) deck)))))""", None), + ("(riff-shuffle (list 1 2 3 4 5 6 7 8))", [1, 5, 2, 6, 3, 7, 4, 8]), + ("((repeat riff-shuffle) (list 1 2 3 4 5 6 7 8))", [1, 3, 5, 7, 2, 4, 6, 8]), + ("(riff-shuffle (riff-shuffle (riff-shuffle (list 1 2 3 4 5 6 7 8))))", [1,2,3,4,5,6,7,8]), +]) +@mark.skip +def test_evaluate(source: str, expected: Optional[Expression]) -> None: + got = evaluate(parse(source)) + assert got == expected + + +# tests for each of the cases in evaluate + +def test_evaluate_variable() -> None: + env: Environment = dict(x=10) + source = 'x' + expected = 10 + got = evaluate(parse(source), env) + assert got == expected + + +def test_evaluate_literal() -> None: + source = '3.3' + expected = 3.3 + got = evaluate(parse(source)) + assert got == expected + + +def test_evaluate_quote() -> None: + source = '(quote (1.1 is not 1))' + expected = [1.1, 'is', 'not', 1] + got = evaluate(parse(source)) + assert got == expected + + +def test_evaluate_if_true() -> None: + source = '(if 1 10 no-such-thing)' + expected = 10 + got = evaluate(parse(source)) + assert got == expected + + +def test_evaluate_if_false() -> None: + source = '(if 0 no-such-thing 20)' + expected = 20 + got = evaluate(parse(source)) + assert got == expected + + +def test_define() -> None: + env: Environment = standard_env() + source = '(define answer (* 6 7))' + got = evaluate(parse(source), env) + assert got is None + assert env['answer'] == 42 + + +def test_lambda() -> None: + env: Environment = standard_env() + source = '(lambda (a b) (if (>= a b) a b))' + func = evaluate(parse(source), env) + assert func.parms == ['a', 'b'] + assert func.body == ['if', ['>=', 'a', 'b'], 'a', 'b'] + assert func.env is env + assert func(1, 2) == 2 + assert func(3, 2) == 3 + + +def test_begin() -> None: + env: Environment = standard_env() + source = """ + (begin + (define x (* 2 3)) + (* x 7) + ) + """ + got = evaluate(parse(source), env) + assert got == 42 + + +def test_invocation_builtin_car() -> None: + env: Environment = standard_env() + source = '(car (quote (11 22 33)))' + got = evaluate(parse(source), env) + assert got == 11 + + +def test_invocation_builtin_append() -> None: + env: Environment = standard_env() + source = '(append (quote (a b)) (quote (c d)))' + got = evaluate(parse(source), env) + assert got == ['a', 'b', 'c', 'd'] + + +def test_invocation_builtin_map() -> None: + env: Environment = standard_env() + source = '(map (lambda (x) (* x 2)) (quote (1 2 3))))' + got = evaluate(parse(source), env) + assert got == [2, 4, 6] + + +def test_invocation_user_procedure() -> None: + env: Environment = standard_env() + source = """ + (begin + (define max (lambda (a b) (if (>= a b) a b))) + (max 22 11) + ) + """ + got = evaluate(parse(source), env) + assert got == 22 diff --git a/18-context-mngr/lispy/py3.9/lis.py b/18-context-mngr/lispy/py3.9/lis.py new file mode 100644 index 0000000..fcedd1e --- /dev/null +++ b/18-context-mngr/lispy/py3.9/lis.py @@ -0,0 +1,163 @@ +################ Lispy: Scheme Interpreter in Python 3.9 + +## (c) Peter Norvig, 2010-18; See http://norvig.com/lispy.html +## Minor edits for Fluent Python, Second Edition (O'Reilly, 2021) +## by Luciano Ramalho, mostly adding type hints. + +################ Imports and Types + +import math +import operator as op +from collections import ChainMap +from collections.abc import MutableMapping +from typing import Union, Any + +Atom = Union[float, int, str] +Expression = Union[Atom, list] + +Environment = MutableMapping[str, object] + + +class Procedure: + "A user-defined Scheme procedure." + + def __init__(self, parms: list[str], body: Expression, env: Environment): + self.parms, self.body, self.env = parms, body, env + + def __call__(self, *args: Expression) -> Any: + env: Environment = ChainMap(dict(zip(self.parms, args)), self.env) + return evaluate(self.body, env) + + +################ Global Environment + + +def standard_env() -> Environment: + "An environment with some Scheme standard procedures." + env: Environment = {} + env.update(vars(math)) # sin, cos, sqrt, pi, ... + env.update( + { + '+': op.add, + '-': op.sub, + '*': op.mul, + '/': op.truediv, + '>': op.gt, + '<': op.lt, + '>=': op.ge, + '<=': op.le, + '=': op.eq, + 'abs': abs, + 'append': op.add, + 'apply': lambda proc, args: proc(*args), + 'begin': lambda *x: x[-1], + 'car': lambda x: x[0], + 'cdr': lambda x: x[1:], + 'cons': lambda x, y: [x] + y, + 'eq?': op.is_, + 'equal?': op.eq, + 'length': len, + 'list': lambda *x: list(x), + 'list?': lambda x: isinstance(x, list), + 'map': lambda *args: list(map(*args)), + 'max': max, + 'min': min, + 'not': op.not_, + 'null?': lambda x: x == [], + 'number?': lambda x: isinstance(x, (int, float)), + 'procedure?': callable, + 'round': round, + 'symbol?': lambda x: isinstance(x, str), + } + ) + return env + + +global_env: Environment = standard_env() + +################ Parsing: parse, tokenize, and read_from_tokens + + +def parse(program: str) -> Expression: + "Read a Scheme expression from a string." + return read_from_tokens(tokenize(program)) + + +def tokenize(s: str) -> list[str]: + "Convert a string into a list of tokens." + return s.replace('(', ' ( ').replace(')', ' ) ').split() + + +def read_from_tokens(tokens: list[str]) -> Expression: + "Read an expression from a sequence of tokens." + if len(tokens) == 0: + raise SyntaxError('unexpected EOF while reading') + token = tokens.pop(0) + if '(' == token: + L = [] + while tokens[0] != ')': + L.append(read_from_tokens(tokens)) + tokens.pop(0) # pop off ')' + return L + elif ')' == token: + raise SyntaxError('unexpected )') + else: + return parse_atom(token) + + +def parse_atom(token: str) -> Atom: + "Numbers become numbers; every other token is a symbol." + try: + return int(token) + except ValueError: + try: + return float(token) + except ValueError: + return str(token) + + +################ Interaction: A REPL + + +def repl(prompt: str = 'lis.py> ') -> None: + "A prompt-read-evaluate-print loop." + while True: + val = evaluate(parse(input(prompt))) + if val is not None: + print(lispstr(val)) + + +def lispstr(exp: object) -> str: + "Convert a Python object back into a Lisp-readable string." + if isinstance(exp, list): + return '(' + ' '.join(map(lispstr, exp)) + ')' + else: + return str(exp) + + +################ eval + + +def evaluate(x: Expression, env: Environment = global_env) -> Any: + "Evaluate an expression in an environment." + if isinstance(x, str): # variable reference + return env[x] + elif not isinstance(x, list): # constant literal + return x + elif x[0] == 'quote': # (quote exp) + (_, exp) = x + return exp + elif x[0] == 'if': # (if test conseq alt) + (_, test, conseq, alt) = x + exp = conseq if evaluate(test, env) else alt + return evaluate(exp, env) + elif x[0] == 'define': # (define var exp) + (_, var, exp) = x + env[var] = evaluate(exp, env) + elif x[0] == 'lambda': # (lambda (var...) body) + (_, parms, body) = x + return Procedure(parms, body, env) + else: # (proc arg...) + proc = evaluate(x[0], env) + args = [evaluate(exp, env) for exp in x[1:]] + return proc(*args) diff --git a/18-context-mngr/lispy/py3.9/lis_test.py b/18-context-mngr/lispy/py3.9/lis_test.py new file mode 100755 index 0000000..591046d --- /dev/null +++ b/18-context-mngr/lispy/py3.9/lis_test.py @@ -0,0 +1,147 @@ +from typing import Optional + +from pytest import mark + +from lis import parse, evaluate, Expression, Environment, standard_env + + +@mark.parametrize( 'source, expected', [ + ("(quote (testing 1 (2.0) -3.14e159))", ['testing', 1, [2.0], -3.14e159]), + ("(+ 2 2)", 4), + ("(+ (* 2 100) (* 1 10))", 210), + ("(if (> 6 5) (+ 1 1) (+ 2 2))", 2), + ("(if (< 6 5) (+ 1 1) (+ 2 2))", 4), + ("(define x 3)", None), ("x", 3), ("(+ x x)", 6), + ("((lambda (x) (+ x x)) 5)", 10), + ("(define twice (lambda (x) (* 2 x)))", None), ("(twice 5)", 10), + ("(define compose (lambda (f g) (lambda (x) (f (g x)))))", None), + ("((compose list twice) 5)", [10]), + ("(define repeat (lambda (f) (compose f f)))", None), + ("((repeat twice) 5)", 20), ("((repeat (repeat twice)) 5)", 80), + ("(define fact (lambda (n) (if (<= n 1) 1 (* n (fact (- n 1))))))", None), + ("(fact 3)", 6), + ("(fact 50)", 30414093201713378043612608166064768844377641568960512000000000000), + ("(define abs (lambda (n) ((if (> n 0) + -) 0 n)))", None), + ("(list (abs -3) (abs 0) (abs 3))", [3, 0, 3]), + ("""(define combine (lambda (f) + (lambda (x y) + (if (null? x) (quote ()) + (f (list (car x) (car y)) + ((combine f) (cdr x) (cdr y)))))))""", None), + ("(define zip (combine cons))", None), + ("(zip (list 1 2 3 4) (list 5 6 7 8))", [[1, 5], [2, 6], [3, 7], [4, 8]]), + ("""(define riff-shuffle (lambda (deck) (begin + (define take (lambda (n seq) (if (<= n 0) (quote ()) (cons (car seq) (take (- n 1) (cdr seq)))))) + (define drop (lambda (n seq) (if (<= n 0) seq (drop (- n 1) (cdr seq))))) + (define mid (lambda (seq) (/ (length seq) 2))) + ((combine append) (take (mid deck) deck) (drop (mid deck) deck)))))""", None), + ("(riff-shuffle (list 1 2 3 4 5 6 7 8))", [1, 5, 2, 6, 3, 7, 4, 8]), + ("((repeat riff-shuffle) (list 1 2 3 4 5 6 7 8))", [1, 3, 5, 7, 2, 4, 6, 8]), + ("(riff-shuffle (riff-shuffle (riff-shuffle (list 1 2 3 4 5 6 7 8))))", [1,2,3,4,5,6,7,8]), +]) +@mark.skip +def test_evaluate(source: str, expected: Optional[Expression]) -> None: + got = evaluate(parse(source)) + assert got == expected + + +# tests for each of the cases in evaluate + +def test_evaluate_variable() -> None: + env: Environment = dict(x=10) + source = 'x' + expected = 10 + got = evaluate(parse(source), env) + assert got == expected + + +def test_evaluate_literal() -> None: + source = '3.3' + expected = 3.3 + got = evaluate(parse(source)) + assert got == expected + + +def test_evaluate_quote() -> None: + source = '(quote (1.1 is not 1))' + expected = [1.1, 'is', 'not', 1] + got = evaluate(parse(source)) + assert got == expected + + +def test_evaluate_if_true() -> None: + source = '(if 1 10 no-such-thing)' + expected = 10 + got = evaluate(parse(source)) + assert got == expected + + +def test_evaluate_if_false() -> None: + source = '(if 0 no-such-thing 20)' + expected = 20 + got = evaluate(parse(source)) + assert got == expected + + +def test_define() -> None: + env: Environment = standard_env() + source = '(define answer (* 6 7))' + got = evaluate(parse(source), env) + assert got is None + assert env['answer'] == 42 + + +def test_lambda() -> None: + env: Environment = standard_env() + source = '(lambda (a b) (if (>= a b) a b))' + func = evaluate(parse(source), env) + assert func.parms == ['a', 'b'] + assert func.body == ['if', ['>=', 'a', 'b'], 'a', 'b'] + assert func.env is env + assert func(1, 2) == 2 + assert func(3, 2) == 3 + + +def test_begin() -> None: + env: Environment = standard_env() + source = """ + (begin + (define x (* 2 3)) + (* x 7) + ) + """ + got = evaluate(parse(source), env) + assert got == 42 + + +def test_invocation_builtin_car() -> None: + env: Environment = standard_env() + source = '(car (quote (11 22 33)))' + got = evaluate(parse(source), env) + assert got == 11 + + +def test_invocation_builtin_append() -> None: + env: Environment = standard_env() + source = '(append (quote (a b)) (quote (c d)))' + got = evaluate(parse(source), env) + assert got == ['a', 'b', 'c', 'd'] + + +def test_invocation_builtin_map() -> None: + env: Environment = standard_env() + source = '(map (lambda (x) (* x 2)) (quote (1 2 3))))' + got = evaluate(parse(source), env) + assert got == [2, 4, 6] + + +def test_invocation_user_procedure() -> None: + env: Environment = standard_env() + source = """ + (begin + (define max (lambda (a b) (if (>= a b) a b))) + (max 22 11) + ) + """ + got = evaluate(parse(source), env) + assert got == 22 diff --git a/22-async/mojifinder/tcp_mojifinder.py b/22-async/mojifinder/tcp_mojifinder.py index 01e18e8..8ff4e50 100755 --- a/22-async/mojifinder/tcp_mojifinder.py +++ b/22-async/mojifinder/tcp_mojifinder.py @@ -1,9 +1,11 @@ #!/usr/bin/env python3 # tag::TCP_MOJIFINDER_TOP[] -import sys import asyncio import functools +import sys +from asyncio.trsock import TransportSocket +from typing import cast from charindex import InvertedIndex, format_results # <1> @@ -54,17 +56,19 @@ async def supervisor(index: InvertedIndex, host: str, port: int): server = await asyncio.start_server( # <1> functools.partial(finder, index), # <2> host, port) # <3> - addr = server.sockets[0].getsockname() # type: ignore # <4> - print(f'Serving on {addr}. Hit CTRL-C to stop.') - await server.serve_forever() # <5> + + socket_list = cast(tuple[TransportSocket, ...], server.sockets) # <4> + addr = socket_list[0].getsockname() + print(f'Serving on {addr}. Hit CTRL-C to stop.') # <5> + await server.serve_forever() # <6> def main(host: str = '127.0.0.1', port_arg: str = '2323'): port = int(port_arg) print('Building index.') - index = InvertedIndex() # <6> + index = InvertedIndex() # <7> try: - asyncio.run(supervisor(index, host, port)) # <7> - except KeyboardInterrupt: # <8> + asyncio.run(supervisor(index, host, port)) # <8> + except KeyboardInterrupt: # <9> print('\nServer shut down.') if __name__ == '__main__': diff --git a/23-dyn-attr-prop/oscon/data/osconfeed.json b/23-dyn-attr-prop/oscon/data/osconfeed.json index 9961768..50ed313 100644 --- a/23-dyn-attr-prop/oscon/data/osconfeed.json +++ b/23-dyn-attr-prop/oscon/data/osconfeed.json @@ -6,7 +6,7 @@ { "Schedule": { "conferences": [{"serial": 115 }], "events": [ - + { "serial": 33451, "name": "Migrating to the Web Using Dart and Polymer - A Guide for Legacy OOP Developers", @@ -16,8554 +16,8554 @@ "time_stop": "2014-07-23 17:40:00", "venue_serial": 1458, "description": "The web development platform is massive. With tons of libraries, frameworks and concepts out there, it might be daunting for the 'legacy' developer to jump into it.\r\n\r\nIn this presentation we will introduce Google Dart & Polymer. Two hot technologies that work in harmony to create powerful web applications using concepts familiar to OOP developers.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33451", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33451", "speakers": [149868], "categories": [ - + "Emerging Languages" - + ] }, - + { "serial": 33457, "name": "Refactoring 101", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 11:30:00", "time_stop": "2014-07-23 12:10:00", "venue_serial": 1449, "description": "Refactoring code (altering code to make it cleaner, simpler, and often faster, while not sacrificing functionality) We hate to do it, so learn how to do it better. Covers: When to refactor. How to refactor. Why refactor. How refactor can help us write better code. Common methodology for refactoring.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33457", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33457", "speakers": [169862], "categories": [ - + "PHP" - + ] }, - + { "serial": 33463, "name": "Open Source and Mobile Development: Where Does it go From Here?", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 10:40:00", "time_stop": "2014-07-23 11:20:00", "venue_serial": 1459, "description": "Until iOS and Android came along, the opportunities for open source to flourish in the mobile space were limited, because platforms were totally proprietary. Now you can find countless FL/OSS projects that help mobile developers get their job done. So what's on the horizon, and what are the best open source tools today to deliver the next great app?", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33463", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33463", "speakers": [169870,2216,96208,150073], "categories": [ - + "Mobile Platforms" - + ] }, - + { "serial": 33464, "name": "Open Source Protocols and Architectures to Fix the Internet of Things", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 16:10:00", "time_stop": "2014-07-23 16:50:00", "venue_serial": 1451, "description": "Everyday things are becoming smarter. The problem? The things are becoming smarter, but they\u2019re also becoming selfish and you\u2019ve ended up as a mechanical turk inside your own software. How can we fix the Internet of Things? The things have to become not just smarter, but more co-operative, and the Internet of things needs to become anticipatory rather than reactive.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33464", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33464", "speakers": [2216], "categories": [ - + "Open Hardware" - + ] }, - + { "serial": 33476, "name": "Scaling PHP in the Real World!", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 14:30:00", "time_stop": "2014-07-23 15:10:00", "venue_serial": 1458, "description": "PHP is used by the likes of Facebook, Yahoo, Zynga, Tumblr, Etsy, and Wikipedia. How do the largest internet companies scale PHP to meet their demand? Join this session and find out how to use the latest tools in PHP for developing high performance applications. We\u2019ll take a look at common techniques for scaling PHP applications and best practices for profiling and optimizing performance.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33476", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33476", "speakers": [54107], "categories": [ - + "PHP" - + ] }, - + { "serial": 33481, "name": "API Ecosystem with Scala, Scalatra, and Swagger at Netflix", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 17:00:00", "time_stop": "2014-07-23 17:40:00", "venue_serial": 1456, "description": "At Netflix Engineering's Partner Product Innovation group, we underwent a revamp of the tech stack to make it API-driven. This was to not only help with the expanding list of API consumers, but also to address the evolving streaming business. With Scala, Scalatra, and Swagger, we achieved one of the best architecture for the scale, agility and robustness needed.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33481", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33481", "speakers": [113667], "categories": [ - + "Emerging Languages" - + ] }, - + { "serial": 33485, "name": "XSS and SQL Injections: The Tip of the Web Security Iceberg ", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 16:10:00", "time_stop": "2014-07-23 16:50:00", "venue_serial": 1458, "description": "You might know about XSS and usual SQL injection, but time has changed and we have to keep up-to-date with the latest attack scenarios.\r\nDo you also know what a clickjacking is? If not I'll show you how to protect against it.\r\nI'll also present techniques like Perfect Pixel Timing and a combination of xss/time-based-sql-injection to access intranet sites, which are not even compromised.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33485", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33485", "speakers": [169932], "categories": [ - + "PHP" - + ] }, - + { "serial": 33503, "name": "Scalable Analytics with R, Hadoop and RHadoop", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 14:30:00", "time_stop": "2014-07-23 15:10:00", "venue_serial": 1475, "description": "Do you use Hadoop for large scale data analysis? Do your data scientists love R? This presentation will discuss the challenges of scaling R to multi-terabyte data sets and how RHadoop can be used to solve them.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33503", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33503", "speakers": [126882], "categories": [ - + "Databases & Datastores" - + ] }, - + { "serial": 33520, "name": "HA 101 with OpenStack", "event_type": "40-minute conference session", - + "time_start": "2014-07-24 10:00:00", "time_stop": "2014-07-24 10:40:00", "venue_serial": 1466, "description": "There are a number of interrelated concepts which make the understanding and implementation of HA complex. The potential for not implementing HA correctly would be disastrous. This session will use demos to reinforce the concepts and how to connect the dots using OpenStack infrastructure as an example although the lessons learned can be used for implementing HA in general.\r\n\r\n", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33520", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33520", "speakers": [131499], "categories": [ - + "Cloud" - + ] }, - + { "serial": 33549, "name": "Eyes on IZON: Surveilling IP Camera Security", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 14:30:00", "time_stop": "2014-07-23 15:10:00", "venue_serial": 1454, "description": "This presentation will provide insight into the security mechanisms being used by the IZON IP camera, some of the weaknesses found during research, and a few recommendations for them (or anyone else developing these sorts of cameras) to benefit from. Attention will be paid to topics such as network protocols, iOS app security, APIs, and other aspects of the camera's attack surface.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33549", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33549", "speakers": [170134], "categories": [ - + "Security" - + ] }, - + { "serial": 33557, "name": "How to Fake a Database Design", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 17:00:00", "time_stop": "2014-07-22 17:40:00", "venue_serial": 1475, "description": "Many expert programmers who write complex SQL without a second thought still struggle with database design. Unfortunately, many introductory topics cause eyes to glaze over when we read 'transitive dependencies' and 'Boyce-Codd normal form'. When you're done with this talk, you'll understand the basics of creating a database that won't make a DBA yell at you. We won't even use (many) big words.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33557", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33557", "speakers": [170158], "categories": [ - + "Databases & Datastores" - + ] }, - + { "serial": 33564, "name": "Testing with Test::Class::Moose", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 14:30:00", "time_stop": "2014-07-22 15:10:00", "venue_serial": 1465, "description": "Perl is known for its testing culture. Unfortunately it's often focused on quantity over quality. Perl's Test::Class::Moose project started out as an experiment but morphed into a way of having higher quality testing. With this module, you can get fine-grained control over your test suite, better understand your *real* code coverage and get an quick boost to test suite performance.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33564", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33564", "speakers": [170158], "categories": [ - + "Perl" - + ] }, - + { "serial": 33571, "name": "An Elasticsearch Crash Course", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 10:40:00", "time_stop": "2014-07-22 11:20:00", "venue_serial": 1475, "description": "Elasticsearch is about more than just search. It\u2019s currently being used in production for everything from traditional text search, to big data analytics, to distributed document storage. This talk will introduce you to Elasticsearch\u2019s REST API, and discuss the basics of full text search and analytics with Elasticsearch.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33571", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33571", "speakers": [170054], "categories": [ - + "Databases & Datastores" - + ] }, - + { "serial": 33581, "name": "Building a Massively Scalable Cloud Service from the Grounds Up", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 16:10:00", "time_stop": "2014-07-22 16:50:00", "venue_serial": 1459, "description": "In this talk, we'll go into excruciating technical detail about building a greenfield, massively scalable cloud service. Along the path to constructing a scalable cloud service, there are many options and critical decisions to take, and we'll share our choices that brought both success and frustrations.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33581", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33581", "speakers": [116050], "categories": [ - + "Cloud" - + ] }, - + { "serial": 33585, "name": "Open/Closed Software - Approaches to Developing Freemium Applications", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 10:40:00", "time_stop": "2014-07-22 11:20:00", "venue_serial": 1457, "description": "Developing freemium which involves OSS is not a trivial task. In this talk we\u2019ll showcase Artifactory, which successfully combines open-source and Pro versions. We will talk about developing, building, testing, and releasing hybrid freemium application and will review the existing approaches, discussing pros and cons of each of them.\r\n", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33585", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33585", "speakers": [114822], "categories": [ - + "Business" - + ] }, - + { "serial": 33590, "name": "How to Build Reactive Applications", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 10:40:00", "time_stop": "2014-07-23 11:20:00", "venue_serial": 1456, "description": "An introduction to building Reactive Applications and what tools you can use to do so.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33590", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33590", "speakers": [170293], "categories": [ - + "Java & JVM" - + ] }, - + { "serial": 33596, "name": "Designing Irresistible APIs", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 16:10:00", "time_stop": "2014-07-23 16:50:00", "venue_serial": 1452, "description": "So you want to create a platform for your product? Creating a fantastic open API (or even a closed one) is not the same as creating other products. I'll talk about how what you need to know to design, plan and execute a successful, engaging API and how to avoid common pitfalls.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33596", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33596", "speakers": [4265], "categories": [ - + "Computational Thinking" - + ] }, - + { "serial": 33597, "name": "Quantifying your Fitness", "event_type": "40-minute conference session", - + "time_start": "2014-07-24 11:50:00", "time_stop": "2014-07-24 12:30:00", "venue_serial": 1475, "description": "The Quantified Self movement is all about keeping measurements about your life in order to track progress in various ways. As geeks we all enjoy playing with new toys, and there are a variety of devices and applications out there to help measure steps, activity and fitness. Combining the data from these devices can help you build tools to track your fitness in a way that makes sense for you.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33597", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33597", "speakers": [4265,182808], "categories": [ - + "Geek Lifestyle" - + ] }, - + { "serial": 33627, "name": "Introduction to Docker: Containerization is the New Virtualization", "event_type": "tutorial", - + "time_start": "2014-07-20 13:30:00", "time_stop": "2014-07-20 17:00:00", "venue_serial": 1450, "description": "You've heard the hype about Docker and container virtualization now see it in action. This tutorial will introduce you to Docker and take you through installing it, running it and integrating it into your development and operational workflow.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33627", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33627", "speakers": [5060], "categories": [ - + "Operations & System Administration", - + "Tools & Techniques" - + ] }, - + { "serial": 33631, "name": "Mind the Gap: Architecting UIs in the Era of Diverse Devices", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 17:00:00", "time_stop": "2014-07-23 17:40:00", "venue_serial": 1449, "description": "Architecting and developing user interfaces used to be relatively easy, pick a server side framework, define a standard monitor resolution and spend your days dealing with browser quirks. But today, the landscape presents us with a plethora of screen sizes and resolutions. How does a team embrace this brave new world knowing that the future will introduce even more volatility to the client space?", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33631", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33631", "speakers": [108125], "categories": [ - + "User Experience" - + ] }, - + { "serial": 33648, "name": "Devoxx4Kids: So Your Kid Interested in Programming, Robotics, Engineering?", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 11:30:00", "time_stop": "2014-07-22 12:10:00", "venue_serial": 1462, "description": "Devoxx4Kids is a worldwide initiative that introduces programming, robotics, and engineering to kids by organizing events and workshops. This session will share how Devoxx4Kids is giving Scratch, Greenfoot, Minecraft, Raspberry Pi, Arduino, NAO, Tynker workshops. The session will show a path that can be followed by parents to keep their kids engaged and build, instead of just play games. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33648", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33648", "speakers": [143377], "categories": [ - + "Education" - + ] }, - + { "serial": 33687, "name": "Debugging LAMP Apps on Linux/UNIX Using Open Source Tools", "event_type": "tutorial", - + "time_start": "2014-07-20 09:00:00", "time_stop": "2014-07-20 12:30:00", "venue_serial": 1451, "description": "The purpose of this tutorial is to train web developers working on a Linux/UNIX ENV on production, development ENVs, or both.\r\nOften, these developers, while proficient in say, PHP, lack UNIX system knowledge and therefore come across a brick wall when debugging production issues.\r\nOften times, because the development ENV is different than production.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33687", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33687", "speakers": [171078], "categories": [ - + "PHP", - + "Tools & Techniques" - + ] }, - + { "serial": 33689, "name": "Forty New Features of Java EE 7 in 40 Minutes", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 10:40:00", "time_stop": "2014-07-22 11:20:00", "venue_serial": 1456, "description": "The Java EE 7 platform has 4 new components (WebSocket, JSON-P, batch, and concurrency), 3 that are significantly updated (JAX-RS, JMS, and EL), and several others that bring significant changes to the platform. This session explains each feature with a code snippet and provides details on where and how you can use it in your applications.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33689", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33689", "speakers": [143377], "categories": [ - + "Java & JVM" - + ] }, - + { "serial": 33704, "name": "Building a Recommendation Engine with Spring and Hadoop", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 11:30:00", "time_stop": "2014-07-23 12:10:00", "venue_serial": 1456, "description": "Recommendation engines are the mainstay of e-commerce sites. What if you could build one with only a few lines of code using open source tools. Come to this talk to find out how as we build one using the data from StackOverflow!", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33704", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33704", "speakers": [171147], "categories": [ - + "Java & JVM" - + ] }, - + { "serial": 33709, "name": "Introduction to Parallel Iterative Deep Learning on Hadoop\u2019s Next\u200b-Generation YARN Framework", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 13:40:00", "time_stop": "2014-07-23 14:20:00", "venue_serial": 1452, "description": "In this session, we will take a look at how we parallelize Deep Belief Networks in Deep Learning on the next\u200b-generation YARN framework Iterative Reduce and the parallel machine learning library Metronome. We\u2019ll also take a look at some real world applications of Deep Learning on Hadoop such as image classification and NLP.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33709", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33709", "speakers": [171197,171201], "categories": [ - + "Computational Thinking" - + ] }, - + { "serial": 33727, "name": "Open Sourcing Mental Illness", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 13:40:00", "time_stop": "2014-07-22 14:20:00", "venue_serial": 1451, "description": "I was diagnosed with depression and anxiety when I was thirteen, and I've been struggling with it my whole life. In this talk, I'll discuss how it has impacted my work as a developer, husband, and father. By speaking openly about my experiences, I hope those struggling with mental illness will know the are not alone, and others can better understand how to be helpful and supportive.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33727", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33727", "speakers": [1639], "categories": [ - + "Geek Lifestyle" - + ] }, - + { "serial": 33733, "name": "When PostgreSQL Can't, You Can", "event_type": "40-minute conference session", - + "time_start": "2014-07-24 10:00:00", "time_stop": "2014-07-24 10:40:00", "venue_serial": 1475, "description": "After using PostgreSQL for a while, you realize that there are missing features that would make it significantly easier to use in large production environments. Thankfully, it's extremely easy to make add-ons to enable some of those features right now, even without knowing C! This talk will discuss projects I've worked on and show how easy it is to make an impact in the PostgreSQL community.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33733", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33733", "speakers": [152242], "categories": [ - + "Databases & Datastores" - + ] }, - + { "serial": 33741, "name": "How to Achieve Enterprise Storage Functionality with OpenStack Block Storage", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 16:10:00", "time_stop": "2014-07-22 16:50:00", "venue_serial": 1475, "description": "In this session, SolidFire's John Griffith will review some of the key features included within OpenStack Block Storage to help achieve the enterprise storage functionality they require to host production applications in their cloud infrastructure. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33741", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33741", "speakers": [171381], "categories": [ - + "Databases & Datastores" - + ] }, - + { "serial": 33800, "name": "Building Native iOS and Android Apps in Java", "event_type": "tutorial", - + "time_start": "2014-07-21 09:00:00", "time_stop": "2014-07-21 12:30:00", "venue_serial": 1456, "description": "This tutorial will demonstrate the use of Codename One to develop a cross-platform mobile application in Java. In it you will build a non-trivial application and deploy it to your mobile device.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33800", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33800", "speakers": [171418], "categories": [ - + "Java & JVM", - + "Mobile Platforms" - + ] }, - + { "serial": 33834, "name": "Presentation Aikido", "event_type": "tutorial", - + "time_start": "2014-07-20 13:30:00", "time_stop": "2014-07-20 17:00:00", "venue_serial": 1475, "description": "This tutorial explores a set of simple and practical techniques for giving better, more effective, more entertaining technical presentations. Discover how to capture an audience, hold their interest, convey your message to them clearly\u2026and maybe even inspire them.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33834", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33834", "speakers": [4710], "categories": [ - + "Education", - + "Geek Lifestyle" - + ] }, - + { "serial": 33839, "name": "Everyday Perl 6", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 10:40:00", "time_stop": "2014-07-23 11:20:00", "venue_serial": 1465, "description": "Perl 6's many advanced features (junctions, multiple dispatch, generics, grammars, lazy evaluation, coroutines, etc.) may well offer awesome cosmic power, but for most of us the real and immediate benefits of switching to Perl 6 are the numerous minor Perl annoyances it fixes. This talk offers a dozen practical reasons why Perl 6 might now be a better choice as your everyday go-to problem-solver.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33839", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33839", "speakers": [4710], "categories": [ - + "Perl" - + ] }, - + { "serial": 33841, "name": "The Conway Channel", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 17:00:00", "time_stop": "2014-07-22 17:40:00", "venue_serial": 1465, "description": "Join Damian for his annual kaleidoscopic tour of the strange and wonderful new Perl modules he's been developing over the past twelve months.\r\n ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33841", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33841", "speakers": [4710], "categories": [ - + "Perl" - + ] }, - + { "serial": 33863, "name": "Building a Resilient API with Open Source", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 10:40:00", "time_stop": "2014-07-23 11:20:00", "venue_serial": 1452, "description": "How do you build and maintain a stable API while rapidly iterating and innovating in your business? Change can never be eliminated, but its impact can be minimized. GitHub takes a pragmatic approach to Hypermedia that emphasizes workflows over data retrieval and employs open source to ensure a consistent experience for API consumers.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33863", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33863", "speakers": [109297], "categories": [ - + "Computational Thinking" - + ] }, - + { "serial": 33875, "name": "The Full Stack Java Developer", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 11:30:00", "time_stop": "2014-07-22 12:10:00", "venue_serial": 1456, "description": "Today's Java developer is a rare bird: SQL and JPA on the backend, or MongoDB or Hadoop? HTTP, REST and websockets on the web? What about security? JavaScript, HTML, CSS, (not to mention LESS, SASS, and CoffeeScript!) on the client? Today's Java developer is a _full stack_ developer. Join Josh Long and Phillip Webb for a look at how Spring Boot simplifies full-stack development for everyone.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33875", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33875", "speakers": [171564,171565], "categories": [ - + "Java & JVM", - + "JavaScript - HTML5 - Web" - + ] }, - + { "serial": 33880, "name": "How Instagram.com Works", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 16:10:00", "time_stop": "2014-07-22 16:50:00", "venue_serial": 1448, "description": "Instagram.com renders almost all of its UI in JavaScript. I'll talk about how our packaging and push systems work in great detail, which are clever combinations of existing open-source tools. Anyone building a large site using lots of JavaScript would find what we've learned interesting!", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33880", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33880", "speakers": [164756], "categories": [ - + "Main Stage" - + ] }, - + { "serial": 33913, "name": "Trolls Aren't the Only Threat Under the Bridge", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 13:40:00", "time_stop": "2014-07-23 14:20:00", "venue_serial": 1457, "description": "There's been a lot of talk about patent trolls, but how can the free and open source software community address the more complicated (and potentially more damaging) problem of anti-competitive litigation? ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33913", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33913", "speakers": [130731], "categories": [ - + "Business", - + "Community" - + ] }, - + { "serial": 33937, "name": "Kraken.js - Bringing Open Source to the Enterprise", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 10:40:00", "time_stop": "2014-07-23 11:20:00", "venue_serial": 1457, "description": "PayPal has recently moved their web application stack from a proprietary framework, resulting in weeks of training per developer and large maintenance costs, to an open source-based stack that allows our engineers to come in the door coding. This is the story of how we changed our enterprise culture and started giving back to the open source community.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33937", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33937", "speakers": [183835,183908], "categories": [ - + "Business" - + ] }, - + { "serial": 33943, "name": "You Don't Know How Your Computer Works", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 16:10:00", "time_stop": "2014-07-22 16:50:00", "venue_serial": 1456, "description": "Software development is easy. You tell a computer to do something. It does it. Someone sends you a packet. The OS receives it. Things don't happen unless you ask them to. Simple.\r\n\r\nBut what if that wasn't true? What if your computer is full of hidden magic? What if your hardware makes assumptions about your software? Vendors wouldn't do that, would they?\r\n\r\n(Spoiler: Yes, they would)", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33943", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33943", "speakers": [6852], "categories": [ - + "Operations & System Administration" - + ] }, - + { "serial": 33945, "name": "Leaflet, Node.JS, and MongoDB for an Easy and Fun Web Mapping Experience", "event_type": "40-minute conference session", - + "time_start": "2014-07-24 10:00:00", "time_stop": "2014-07-24 10:40:00", "venue_serial": 1450, "description": "You have seen the stuff that FourSquare has done with spatial and you want some of that hotness for your app. We will load some data into MongoDB, show you how to handle spatial and finally plug in in some Node.JS JavaScript code to build simple REST services to query your data. Finally we will show how to use the REST service with OpenStreetMap and Leaflet for a fully interactive map.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33945", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33945", "speakers": [142320], "categories": [ - + "JavaScript - HTML5 - Web" - + ] }, - + { "serial": 33947, "name": "The Simplicity of Clojure", "event_type": "tutorial", - + "time_start": "2014-07-20 09:00:00", "time_stop": "2014-07-20 12:30:00", "venue_serial": 1456, "description": "Clojure: it's a Lisp that runs on the JVM and it's gotten a lot of buzz in the last few years. What is it actually good for? In this tutorial, you'll learn about Clojure's radically simple approach to data and state and how it can help you build real-world projects from web applications to servers to mobile apps.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33947", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33947", "speakers": [137149,171822], "categories": [ - + "Emerging Languages", - + "Java & JVM" - + ] }, - + { "serial": 33950, "name": "There *Will* Be Bugs", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 14:30:00", "time_stop": "2014-07-23 15:10:00", "venue_serial": 1449, "description": "If you're pushing the envelope of programming (or of your own skills)... and even when you\u2019re not... there *will* be bugs in your code. Don't panic! We cover the attitudes and skills (not taught in most schools) to minimize your bugs, track them, find them, fix them, ensure they never recur, and deploy fixes to your users.\r\n", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33950", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33950", "speakers": [3471,5199], "categories": [ - + "Python" - + ] }, - + { "serial": 33973, "name": "Apache Cordova: Past, Present and Future", "event_type": "40-minute conference session", - + "time_start": "2014-07-24 10:00:00", "time_stop": "2014-07-24 10:40:00", "venue_serial": 1449, "description": "A review of the past six years of Apache Cordova development, starting from its origins as PhoneGap, to its donation to the Apache Software Foundation, told from the point of view of its longest running contributor. This will include a simple introduction to cross-platform hybrid applications on iOS and Android, and their evolution.\r\n", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33973", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33973", "speakers": [96208], "categories": [ - + "Mobile Platforms" - + ] }, - + { "serial": 33978, "name": "WITH What? CTEs For Fun And Profit", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 13:40:00", "time_stop": "2014-07-23 14:20:00", "venue_serial": 1466, "description": "Have you tried some recursion in your SQL? In this session, we will go over the concept of Common Table Expressions (CTE), also known as WITH queries. We will explore syntax, features, and use cases for this powerful SQL construct.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33978", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33978", "speakers": [25862], "categories": [ - + "Databases & Datastores" - + ] }, - + { "serial": 33994, "name": "Rebooting Open Source at Facebook", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 14:30:00", "time_stop": "2014-07-23 15:10:00", "venue_serial": 1448, "description": "Open source has always been a huge part of Facebook's culture. But in 2013, we rebooted our portfolio and launched a unique suite of internal tools & instrumentation to support hundreds of repos, thousands of engineers, and tens of thousands of contributors. The result? Better-than-ever community adoption - and an open & responsible stewardship, attuned to our ethos of hacking & moving fast.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33994", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33994", "speakers": [118233], "categories": [ - + "Main Stage" - + ] }, - + { "serial": 33997, "name": "CERN's Approach to Mass and Agility", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 17:00:00", "time_stop": "2014-07-23 17:40:00", "venue_serial": 1459, "description": "As part of a large-scale adoption of cloud computing to support the increasing computing needs of the Large Hadron Collider processing over 35 PB/year, the infrastructure of CERN IT is undergoing major changes in both technology and culture. This session will describe the steps taken, the challenges encountered and our outlook for the future.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33997", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/33997", "speakers": [170052], "categories": [ - + "Cloud" - + ] }, - + { "serial": 34005, "name": "Let Them Be Your Heroes", "event_type": "40-minute conference session", - + "time_start": "2014-07-24 10:00:00", "time_stop": "2014-07-24 10:40:00", "venue_serial": 1464, "description": "How can you encourage involvement and participation in Open Source? They key is through empowerment. We'll discuss how to empower and encourage more people to participate in your open source project by enabling Heroism. This talk will also discuss issues of diversity and inclusion. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34005", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34005", "speakers": [144736], "categories": [ - + "Community" - + ] }, - + { "serial": 34012, "name": "A Presentation Toolbox that Just Might Blow Your Audience Away", "event_type": "40-minute conference session", - + "time_start": "2014-07-24 11:50:00", "time_stop": "2014-07-24 12:30:00", "venue_serial": 1450, "description": "Still building presentations in an office suite? That's so 2013! Today, you can build awesome, engaging presentations that run in your browser or on your phone, using nothing but HTML5 and a few clever JavaScript libraries. And it's super simple! This talk shows you how.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34012", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34012", "speakers": [131884], "categories": [ - + "Geek Lifestyle" - + ] }, - + { "serial": 34018, "name": "Raspberry Pi Hacks", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 13:40:00", "time_stop": "2014-07-22 14:20:00", "venue_serial": 1465, "description": "Ruth Suehle, one of the authors of Raspberry Pi Hacks (O\u2019Reilly, December 2013) will offer technical tips for hardware and software hackers who want to build around the Raspberry Pi computer. She\u2019ll start with some tips like how to add a power switch and go on to share fun projects, from costume builds to radios and light displays.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34018", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34018", "speakers": [108840], "categories": [ - + "Open Hardware" - + ] }, - + { "serial": 34019, "name": "From Cloud to Fog Computing and the Internet of Things", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 13:40:00", "time_stop": "2014-07-23 14:20:00", "venue_serial": 1449, "description": "Open Source is ubiquitous in Cloud compute. Just as we became familiar with Cloud computing, a new model has emerged, an extension of the cloud to the edge of the network, some call it Fog computing, some call it the Internet of Things. This talk describes how the compute model is changing as the new generation of devices stretched what we previously knew as Cloud compute.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34019", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34019", "speakers": [172370], "categories": [ - + "Mobile Platforms" - + ] }, - + { "serial": 34026, "name": "The Accidental DBA", "event_type": "tutorial", - + "time_start": "2014-07-21 13:30:00", "time_stop": "2014-07-21 17:00:00", "venue_serial": 1475, "description": "So, you\u2019ve inherited a PostgreSQL server. Congratulations? This tutorial will cover the essential care and feeding of a Postgres server so that you can get back to your real job.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34026", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34026", "speakers": [3397], "categories": [ - + "Databases & Datastores", - + "Operations & System Administration" - + ] }, - + { "serial": 34037, "name": "Distributed Robots with Elixir", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 10:40:00", "time_stop": "2014-07-23 11:20:00", "venue_serial": 1451, "description": "This talk will take you on a journey from making an LED blink through to building your own Elixir-powered robot using a RaspberryPi and Android.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34037", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34037", "speakers": [172532,172534], "categories": [ - + "Open Hardware" - + ] }, - + { "serial": 34040, "name": "What is Async, How Does it Work, and When Should I Use it?", "event_type": "40-minute conference session", - + "time_start": "2014-07-24 11:00:00", "time_stop": "2014-07-24 11:40:00", "venue_serial": 1458, "description": "Asynchronous frameworks like Tornado, Twisted, and Node are increasingly important for writing high-performance web applications. Even if you\u2019re an experienced web programmer, you may lack a rigorous understanding of how these frameworks work and when to use them. See how Tornado's event loop works, and learn how to efficiently handle very large numbers of concurrent connections.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34040", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34040", "speakers": [172536], "categories": [ - + "Python" - + ] }, - + { "serial": 34047, "name": "Go for Object Oriented Programmers (or OO Programming without Objects) ", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 16:10:00", "time_stop": "2014-07-23 16:50:00", "venue_serial": 1465, "description": "Object Oriented programming has dominated software engineering for the last two decades. Although Go is not OO in the strict sense, we can continue to leverage the skills we've honed as OO engineers. This talk will cover how to use our OO programming fundamentals in go, common mistakes made by those coming to go from other OO languages (Ruby, Python, JS, etc.), and principles of good design in go.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34047", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34047", "speakers": [142995], "categories": [ - + "Emerging Languages" - + ] }, - + { "serial": 34056, "name": "Unicorns, Dragons, Open Source Business Models And Other Mythical Creatures", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 14:30:00", "time_stop": "2014-07-22 15:10:00", "venue_serial": 1448, "description": "No one size fits all formula can be applied to build a business around open source, and attempting to do so may end in humiliation and disaster.\r\n\r\nThere is no doubt that 'Open Source' has impacted the dynamics of all manner of business, but building a business on 'Open Source' is not a solved problem.\r\n\r\nA guided tour of open source business models, real and imaginary.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34056", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34056", "speakers": [24052], "categories": [ - + "Main Stage" - + ] }, - + { "serial": 34063, "name": "Tutorial: Node.js Three Ways", "event_type": "tutorial", - + "time_start": "2014-07-21 13:30:00", "time_stop": "2014-07-21 17:00:00", "venue_serial": 1452, "description": "In this tutorial, we\u2019ll explore three unique technologies, and accompanying use cases, for Node.js development. We\u2019ll divide the tutorial into three one-hour segments, in which you will develop three different Node.js-powered applications.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34063", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34063", "speakers": [141235,147649], "categories": [ - + "Emerging Languages", - + "JavaScript - HTML5 - Web" - + ] }, - + { "serial": 34064, "name": "Node.js Patterns for the Discerning Developer", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 10:40:00", "time_stop": "2014-07-22 11:20:00", "venue_serial": 1450, "description": "In this session, I\u2019ll presenting high-quality Node.js design patterns. I\u2019ll bring to the table design patterns I\u2019ve stumbled across in my own Node projects, as well as patterns observed from experts in the Node.js community.\r\n\r\nTopics include: Mastering Modules, Object Inheritance in Node.js, Patterns to avoid callback hell, Batch and Queuing patterns for massively concurrent asynchronous I/O", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34064", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34064", "speakers": [141235], "categories": [ - + "JavaScript - HTML5 - Web" - + ] }, - + { "serial": 34075, "name": "Druid: Interactive Queries Meet Real-time Data", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 11:30:00", "time_stop": "2014-07-23 12:10:00", "venue_serial": 1475, "description": "This talk will focus on the motivation, design, and architecture of Druid (druid.io), an open-source, real-time analytical data store. Druid is used in production at several organizations to facilitate rapid exploration of high dimensional spaces. Druid can maintain a 95% query latency under 1 second on data sets with >50 billion rows and 2 trillion impressions in tables with 30+ dimensions.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34075", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34075", "speakers": [172607], "categories": [ - + "Databases & Datastores" - + ] }, - + { "serial": 34076, "name": "Real-time Analytics with Open Source Technologies", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 10:40:00", "time_stop": "2014-07-23 11:20:00", "venue_serial": 1475, "description": "The maturation and development of open source technologies has made it easier than ever for companies to derive insights from vast quantities of data. In this session, we will cover how to build a real-time analytics stack using Kafka, Storm, and Druid. This combination of technologies can power a robust data pipeline that supports real-time ingestion and flexible, low-latency queries.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34076", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34076", "speakers": [153565,153566], "categories": [ - + "Databases & Datastores" - + ] }, - + { "serial": 34078, "name": "Moose is Perl: A Guide to the New Revolution", "event_type": "tutorial", - + "time_start": "2014-07-21 09:00:00", "time_stop": "2014-07-21 12:30:00", "venue_serial": 1457, "description": "Moose continues to emerge as the new standard for writing OO libraries in Perl. It provides a powerful, consistent API for building classes with a minimum of code. It can be customized with reusable components, making it easier to refactor your code as you go. This tutorial will explain what Moose is, how its parts work together, and how to start using Moose today to get more done with less.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34078", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34078", "speakers": [3189], "categories": [ - + "Perl" - + ] }, - + { "serial": 34081, "name": "Debugging HTTP", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 11:30:00", "time_stop": "2014-07-22 12:10:00", "venue_serial": 1449, "description": "Show how tools including cURL, Wireshark and Charles can be used to inspect and change HTTP traffic when debugging applications which consume APIs and other remote sources.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34081", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34081", "speakers": [45968], "categories": [ - + "Tools & Techniques" - + ] }, - + { "serial": 34083, "name": "Creating Models", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 11:30:00", "time_stop": "2014-07-22 12:10:00", "venue_serial": 1458, "description": "How should you organise your models in a PHP MVC application? What is a service class, a mapper or an entity? This talk will look at the components of the model layer and the options you have when creating your models. We\u2019ll look at the different schools of thought in this area and compare and contrast their strengths and weaknesses with an eye to flexibility and testability.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34083", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34083", "speakers": [46440], "categories": [ - + "PHP" - + ] }, - + { "serial": 34087, "name": "Incorporating Your Passions into Open Source Hardware ", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 13:40:00", "time_stop": "2014-07-23 14:20:00", "venue_serial": 1451, "description": "It doesn't matter if your passion boating, fashion, kids, carpentry, architecture, race cars or rockets. Building OS hardware can be incorporated into all of these things. We will learn how to bring what you learn in your software day job into your weekend fun time. The result will be better at what you love and making work more fun. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34087", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34087", "speakers": [133377], "categories": [ - + "Open Hardware" - + ] }, - + { "serial": 34093, "name": "Tsuru: Open Source Cloud Application Platform", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 17:00:00", "time_stop": "2014-07-22 17:40:00", "venue_serial": 1459, "description": "Tsuru is an open source, component oriented PaaS. It allows developers to focus on writing, testing and deploying applications in an easier way, without worrying how they get deployed in a server. Its key features include easy extensibility, a fully open source stack and graceful handling of failures. This talk aims to introduce Tsuru and its components, showing how they work together.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34093", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34093", "speakers": [151991], "categories": [ - + "Cloud" - + ] }, - + { "serial": 34094, "name": "Tracing and Profiling Java (and Native) Applications in Production", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 14:30:00", "time_stop": "2014-07-22 15:10:00", "venue_serial": 1456, "description": "This talk describes the implementation and use of a full stack, low overhead tracing and profiling tool based on the Linux kernel profiler (perf) and extensions to the OpenJDK Hotspot JVM, that we've built at Twitter to help understand the behavior of the kernel, native and managed applications in production.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34094", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34094", "speakers": [172240], "categories": [ - + "Java & JVM" - + ] }, - + { "serial": 34095, "name": "Get Started Developing with Scala", "event_type": "tutorial", - + "time_start": "2014-07-20 13:30:00", "time_stop": "2014-07-20 17:00:00", "venue_serial": 1449, "description": "Scala powers some of the biggest companies in the world, including Twitter, Intel, and LinkedIn. Come learn what led them to choose this powerful JVM language and try it out yourself. You\u2019ll get a hands-on intro to Scala and functional programming concepts by building your own performant REST API. No FP experience needed--if you can build apps in Java, Python or Ruby you\u2019ll do great in this class.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34095", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34095", "speakers": [171226,150440], "categories": [ - + "Computational Thinking", - + "Java & JVM" - + ] }, - + { "serial": 34103, "name": "Reactive All The Way Down", "event_type": "tutorial", - + "time_start": "2014-07-21 09:00:00", "time_stop": "2014-07-21 12:30:00", "venue_serial": 1471, "description": "In this tutorial you will build a Reactive application with Play Framework, Scala, WebSockets, and AngularJS. We will get started with a template app in Typesafe Activator. Then we will add a Reactive RESTful JSON service and a WebSocket in Scala. We will then build the UI with AngularJS.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34103", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34103", "speakers": [1158], "categories": [ - + "Java & JVM", - + "JavaScript - HTML5 - Web" - + ] }, - + { "serial": 34104, "name": "Building Modern Web Apps with Play Framework and Scala", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 14:30:00", "time_stop": "2014-07-23 15:10:00", "venue_serial": 1456, "description": "Play Framework is the High Velocity Web Framework For Java and Scala. It is lightweight, stateless, RESTful, and developer friendly. This is an introduction to building web applications with Play. You will learn about: routing, Scala controllers & templates, database access, asset compilation for LESS & CoffeeScript, and JSON services.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34104", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34104", "speakers": [1158], "categories": [ - + "Java & JVM" - + ] }, - + { "serial": 34107, "name": "Full Monty: Intro to Python Metaprogramming", "event_type": "tutorial", - + "time_start": "2014-07-21 13:30:00", "time_stop": "2014-07-21 17:00:00", "venue_serial": 1451, "description": "Metaprograming in Python is fun and profitable thanks to its rich Data Model \u2013 APIs that let you handle functions, modules and even classes as objects that you can create, inspect and modify at runtime. The Data Model also enables your own objects to support infix operators, become iterable and emulate collections. This workshop shows how, through a diverse selection of examples and exercises.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34107", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34107", "speakers": [150170], "categories": [ - + "Python" - + ] }, - + { "serial": 34108, "name": "Idiomatic APIs with the Python Data Model", "event_type": "40-minute conference session", - + "time_start": "2014-07-24 10:00:00", "time_stop": "2014-07-24 10:40:00", "venue_serial": 1465, "description": "The key to writing Pythonic classes, APIs and frameworks is leveraging the Python Data Model: a set of interfaces which, when implemented in your classes, enables them to leverage fundamental language features such as iteration, context managers, infix operators, attribute access control etc. This talk shows how, through a diverse selection of examples.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34108", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34108", "speakers": [150170], "categories": [ - + "Python" - + ] }, - + { "serial": 34109, "name": "Data.gov: Open Government as Open Source", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 13:40:00", "time_stop": "2014-07-23 14:20:00", "venue_serial": 1454, "description": "The underpinnings of open government are transparency and citizen participation. In re-imagining a new Data.gov (the open data, open government initiative for the White House), this was taken to heart. This system was created using open source and with comments, issues, and commits worked with the public all along the way.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34109", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34109", "speakers": [62631], "categories": [ - + "Databases & Datastores" - + ] }, - + { "serial": 34112, "name": "Python: Encapsulation with Descriptors", "event_type": "40-minute conference session", - + "time_start": "2014-07-24 11:00:00", "time_stop": "2014-07-24 11:40:00", "venue_serial": 1465, "description": "Python has no private fields, but the property decorator lets you replace public attributes with getters and setters without breaking client code. And the descriptor mechanism, used in Django for model field declarations, enables wide reuse of getter/setter logic via composition instead of inheritance. This talk explains how properties and descriptors work by refactoring a practical example.\r\n", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34112", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34112", "speakers": [150170], "categories": [ - + "Python" - + ] }, - + { "serial": 34116, "name": "Choosing a caching HTTP Proxy", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 16:10:00", "time_stop": "2014-07-23 16:50:00", "venue_serial": 1450, "description": "With Web performance and scalability becoming more and more important,\r\nchoosing advanced HTTP intermediaries is a vital skill. This presentation\r\nwill give the audience a thorough walkthrough of the most popular and\r\nadvanced solutions available today. The audience will gain a solid\r\nbackground to be able to make the right choices when it comes to HTTP\r\nintermediaries and proxy caches.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34116", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34116", "speakers": [63576], "categories": [ - + "Operations & System Administration" - + ] }, - + { "serial": 34117, "name": "Git for Grown-ups", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 10:40:00", "time_stop": "2014-07-22 11:20:00", "venue_serial": 1452, "description": "You are a clever and talented person. You have architected a system that even my cat could use; your spreadsheet-fu is legendary. Your peers adore you. Your clients love you. But, until now, you haven\u2019t *&^#^! been able to make Git work. It makes you angry inside that you have to ask for help, again, to figure out that *&^#^! command to upload your work. It's not you. It's Git. Promise.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34117", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34117", "speakers": [4146], "categories": [ - + "Tools & Techniques" - + ] }, - + { "serial": 34125, "name": "How Open Source Powers Facebook on Android", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 11:30:00", "time_stop": "2014-07-23 12:10:00", "venue_serial": 1454, "description": "The Facebook Android app is large and developed by hundreds of software engineers. This talk will cover how OSS helps us build Facebook for Android - and how we are good OSS citizens - by looking at the full life cycle of a release, from how we organize our git repo, do code reviews in Phabricator, through building using Buck, to how we've improved the quality of our releases using Selendroid.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34125", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34125", "speakers": [172656], "categories": [ - + "Tools & Techniques" - + ] }, - + { "serial": 34135, "name": "Design for Life", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 13:40:00", "time_stop": "2014-07-22 14:20:00", "venue_serial": 1462, "description": "It's time to design products to capture life and physiologic signs invisibly\u2026 usually through non-invasive sensors that don't require a single drop of blood, but just whiffs and sniffs. And when it is visible, it must be designed to feel wonderful.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34135", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34135", "speakers": [44753], "categories": [ - + "User Experience" - + ] }, - + { "serial": 34136, "name": "Shipping Applications to Production in Containers with Docker", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 13:40:00", "time_stop": "2014-07-22 14:20:00", "venue_serial": 1449, "description": "Since its first release in early 2013, Docker has been deployed successfully to implement continuous integration and testing environments, where the very fast lifecycle of containers gives them an edge over virtual machines. We will see how to extend the workflow from development to testing and all the way to production. We'll also address challenges like reliability and scaling with Docker.\r\n", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34136", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34136", "speakers": [151611], "categories": [ - + "Operations & System Administration" - + ] }, - + { "serial": 34137, "name": "Is it Safe to Run Applications in Linux Containers?", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 11:30:00", "time_stop": "2014-07-22 12:10:00", "venue_serial": 1475, "description": "Linux Containers (or LXC) is now a popular choice for development and testing environments. As more and more people use them in production deployments, they face a common question: are Linux Containers secure enough? It is often claimed that containers have weaker isolation than virtual machines. We will explore whether this is true, if it matters, and what can be done about it.\r\n", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34137", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34137", "speakers": [151611], "categories": [ - + "Operations & System Administration" - + ] }, - + { "serial": 34145, "name": "DevOps for University Students", "event_type": "40-minute conference session", - + "time_start": "2014-07-24 10:00:00", "time_stop": "2014-07-24 10:40:00", "venue_serial": 1462, "description": "University students rarely get a chance to fully embrace the Devops or FOSS development culture while in school. This year, we\u2019ve started a program called Devops Bootcamp, which is a hands-on, informal workshop open to any student at OSU. Devops Bootcamp immerses college students in the basics of Linux, Linux system administration and FOSS development practices. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34145", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34145", "speakers": [29558,123516], "categories": [ - + "Operations & System Administration" - + ] }, - + { "serial": 34148, "name": "Protocols for the Internet of Things", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 11:30:00", "time_stop": "2014-07-22 12:10:00", "venue_serial": 1465, "description": "A large part of the internet of things will be made up of small constrained devices. The IETF is standardising protocols which are memory, energy and network efficient. Come and get an overview of these and of some Open Source implementations. See devices including Arduinos and Raspberry Pis with several sensors talking with one another and be inspired to build/connect your own devices.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34148", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34148", "speakers": [172751], "categories": [ - + "Open Hardware" - + ] }, - + { "serial": 34156, "name": "REST: It's not just for servers", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 10:40:00", "time_stop": "2014-07-22 11:20:00", "venue_serial": 1462, "description": "Have you ever written or used an API wrapper for a webservice? REST is a client-server architecture model and building the server is only half of the challenge. This talk will walk through some of the challenges of building a REST client, describe some best practices and some patterns to avoid, and discuss how we can all work to build better APIs for an open web.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34156", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34156", "speakers": [151665], "categories": [ - + "Tools & Techniques" - + ] }, - + { "serial": 34159, "name": "Big Data Pipeline and Analytics Platform Using NetflixOSS and Other Open Source Libraries", "event_type": "40-minute conference session", - + "time_start": "2014-07-24 11:00:00", "time_stop": "2014-07-24 11:40:00", "venue_serial": 1452, "description": "This session presents the data platform used at Netflix for event collection, aggregation, and analysis. The platform helps Netflix process and analyze billions of events every day. Attendees will learn how to assemble their own large-scale data pipeline/analytics platform using open source software from NetflixOSS and others, such as Kafka, ElasticSearch, Druid from Metamarkets, and Hive. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34159", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34159", "speakers": [172661,171450], "categories": [ - + "Computational Thinking" - + ] }, - + { "serial": 34162, "name": "Application Deployment and Auto-scaling On OpenStack using Heat ", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 11:30:00", "time_stop": "2014-07-23 12:10:00", "venue_serial": 1466, "description": "This session will show how devops can use Heat to orchestrate the deployment &scaling of complex applications on top of OpenStack. Starting with a walk-thru of OpenStack example deployment Heat Templates for OpenShift Origin (available in openstack github repository) and enhance them to provide additional functions such as positioning alarms, responding to alarms, adding instances, &autoscaling. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34162", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34162", "speakers": [33987], "categories": [ - + "Operations & System Administration" - + ] }, - + { "serial": 34164, "name": "Timeseries Data Superpowers: Intuitive Understanding of FIR Filtering and Fourier Transforms", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 17:00:00", "time_stop": "2014-07-22 17:40:00", "venue_serial": 1452, "description": "In this talk we'll explore the Fourier transform and FIR filters in an intuitive way to make it accessible. You'll come out with the ability to look at your time-series data in a new way and explore new uses for otherwise useless data.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34164", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34164", "speakers": [172201], "categories": [ - + "Computational Thinking" - + ] }, - + { "serial": 34176, "name": "Functional Vaadin", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 16:10:00", "time_stop": "2014-07-22 16:50:00", "venue_serial": 1466, "description": "Exploring how the functional language features of Java 8 and Scala combine with Vaadin to allow you to write clearer UI code.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34176", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34176", "speakers": [120866], "categories": [ - + "Java & JVM" - + ] }, - + { "serial": 34187, "name": "Continuous Delivery", "event_type": "tutorial", - + "time_start": "2014-07-21 13:30:00", "time_stop": "2014-07-21 17:00:00", "venue_serial": 1450, "description": "Getting software released to users is often a painful, risky, and time-consuming process. This tutorial sets out the principles and technical practices that enable rapid, incremental delivery of high quality and valuable new functionality to users.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34187", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34187", "speakers": [2650], "categories": [ - + "Business", - + "Tools & Techniques" - + ] }, - + { "serial": 34188, "name": "Community War Stories : Squaring the Circle between Business and Community", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 17:00:00", "time_stop": "2014-07-22 17:40:00", "venue_serial": 1464, "description": "In this talk we will look at some of the basic dynamics playing out in open source communities and introduce some mental models explaining them. We will look at the Open Source Flywheel (inspired by Walton's Productivity Loop and the Bezos Flywheel) and the Open Source Community Funnel (inspired by Sales Funnels) to explain them.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34188", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34188", "speakers": [62981], "categories": [ - + "Business", - + "Community" - + ] }, - + { "serial": 34192, "name": "Functional Thinking", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 11:30:00", "time_stop": "2014-07-22 12:10:00", "venue_serial": 1452, "description": "Learning the syntax of a new language is easy, but learning to think under a different paradigm is hard. This session helps you transition from a Java writing imperative programmer to a functional programmer, using Java, Clojure and Scala for examples.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34192", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34192", "speakers": [2650], "categories": [ - + "Computational Thinking" - + ] }, - + { "serial": 34194, "name": "The Curious Clojureist", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 13:40:00", "time_stop": "2014-07-23 14:20:00", "venue_serial": 1456, "description": "Clojure is the most interesting new language on the horizon, but many developers suffer from the Blub Paradox when they see the Lisp syntax. This talk introduces Clojure to developers who haven\u2019t been exposed to it yet, focusing on the things that truly set it apart from other languages.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34194", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34194", "speakers": [2650], "categories": [ - + "Emerging Languages" - + ] }, - + { "serial": 34198, "name": "Syncing Async", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 16:10:00", "time_stop": "2014-07-22 16:50:00", "venue_serial": 1450, "description": "'Callback hell' has very little to do with callbacks. Are promises delivering on the promise of better async flow control, or muddying the waters? Generating general generators, WAT? Let's wade through the world of async in JS to find order in the chaos.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34198", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34198", "speakers": [74368], "categories": [ - + "JavaScript - HTML5 - Web" - + ] }, - + { "serial": 34201, "name": "Map, Flatmap and Reduce are Your New Best Friends: Simpler Collections, Concurrency, and Big Data", "event_type": "40-minute conference session", - + "time_start": "2014-07-24 11:50:00", "time_stop": "2014-07-24 12:30:00", "venue_serial": 1452, "description": "Higher-order functions such as map(), flatmap(), filter() and reduce() have their origins in mathematics and ancient functional programming languages such as Lisp. But today they have become mainstream and are available in languages such as JavaScript, Scala and Java 8. Learn how to they can be used to simplify code in a variety of domains including collection processing, concurrency and big data.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34201", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34201", "speakers": [11886], "categories": [ - + "Computational Thinking" - + ] }, - + { "serial": 34203, "name": "Creating Awesome Web APIs is a Breeze", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 13:40:00", "time_stop": "2014-07-23 14:20:00", "venue_serial": 1458, "description": "Web APIs are increasingly important but their creation is still more an art than a science. This talk will demonstrate how Web APIs consumable by generic clients can be implemented in considerably less time. It will also give a brief introduction to JSON-LD and Hydra.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34203", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34203", "speakers": [172864], "categories": [ - + "PHP" - + ] }, - + { "serial": 34208, "name": "Git for Teams of One or More", "event_type": "tutorial", - + "time_start": "2014-07-21 09:00:00", "time_stop": "2014-07-21 12:30:00", "venue_serial": 1452, "description": "You've dabbled a little in version control using Git. You can follow along with the various tutorials you've found online. But now you've been asked to implement a work flow strategy and you're not really sure how (or where) to start. You have a lot of choices, we'll help you pick the right one for your project.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34208", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34208", "speakers": [4146], "categories": [ - + "Community", - + "Tools & Techniques" - + ] }, - + { "serial": 34212, "name": "Telling Technology Stories with IPython Notebook", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 13:40:00", "time_stop": "2014-07-23 14:20:00", "venue_serial": 1465, "description": "As technologists, sometimes it\u2019s as important to be able to share information with others as to be able to actually build something. IPython notebook is a powerful tool to both experiment with code (and data) and share the results with others, technical and non-technical alike. This session introduces the notebook and gives examples and techniques for using it effectively.\r\n", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34212", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34212", "speakers": [59574], "categories": [ - + "Python" - + ] }, - + { "serial": 34224, "name": "Accessibility and Security - For Everyone. Gotchas to Avoid.", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 13:40:00", "time_stop": "2014-07-23 14:20:00", "venue_serial": 1462, "description": "Did you hear about the double arm amputee who was refused service at a bank because he could not provide a thumbprint? Or the online petition to increase services for blind folks, that they couldn\u2019t sign because of CAPTCHA? These are examples of security practices that cause barriers to people with disabilities. Security can create barriers, but it doesn\u2019t have to reduce accessibility!", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34224", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34224", "speakers": [172899], "categories": [ - + "Security", - + "User Experience" - + ] }, - + { "serial": 34232, "name": "AngularJS Tutorial", "event_type": "tutorial", - + "time_start": "2014-07-20 13:30:00", "time_stop": "2014-07-20 17:00:00", "venue_serial": 1452, "description": "AngularJS is relatively new, meteorically popular, and functionally powerful. However, a lot of AngularJS\u2019s workings are very opaque and confusing. In this tutorial, my goal is to walk you through building a basic app, and introduce you to concepts, patterns, and ways of thinking that will allow you to comfortably dive further into using AngularJS for future projects.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34232", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34232", "speakers": [171372], "categories": [ - + "JavaScript - HTML5 - Web", - + "Tools & Techniques" - + ] }, - + { "serial": 34236, "name": "JavaScript and Internet Controlled Hardware Prototyping", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 17:00:00", "time_stop": "2014-07-22 17:40:00", "venue_serial": 1451, "description": "In this session we'll be exploring how to build rapid hardware prototypes using wifi and bluetooth low energy enabled Arduino boards, all controlled through JavaScript and API data, to allow for innovative, web enabled, software to hardware development techniques.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34236", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34236", "speakers": [74565], "categories": [ - + "Open Hardware" - + ] }, - + { "serial": 34238, "name": "No More Whiteboard: Hiring Processes that Don't Waste Time", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 16:10:00", "time_stop": "2014-07-22 16:50:00", "venue_serial": 1457, "description": "Learn how someone writes code by writing code with them. Using katas and pair programming on actual code allows you to get an honest look at the candidate's thought process and capabilities, while exposing them to your team's culture and key players. Help your evaluators be focused on what kind of people you actually want on your project by creating key prompts for them to check.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34238", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34238", "speakers": [155107], "categories": [ - + "Business" - + ] }, - + { "serial": 34243, "name": "Monitoring Your Drone Project with Elasticsearch", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 11:30:00", "time_stop": "2014-07-22 12:10:00", "venue_serial": 1466, "description": "Elasticsearch is an open-source document store known for enabling search and real-time analytics on large data sets. In this presentation we will walk through the development of an application that monitors the Parrot AR.Drone. This application will collect metrics from the drone and then transform them to JSON for storage and real-time analysis in Elasticsearch.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34243", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34243", "speakers": [172929,142336], "categories": [ - + "Databases & Datastores" - + ] }, - + { "serial": 34244, "name": "Grow Your Community with Events", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 14:30:00", "time_stop": "2014-07-22 15:10:00", "venue_serial": 1464, "description": "Events are an attractive way to grow a community, but how do you choose which types of events work best for your users? We'll cover a variety of community event types, as well as building and scaling user groups remotely, simultaneous in-person and online participation, and getting other departments at your company invested in what you're planning.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34244", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34244", "speakers": [142767], "categories": [ - + "Community" - + ] }, - + { "serial": 34247, "name": "Community Management Training", "event_type": "tutorial", - + "time_start": "2014-07-21 09:00:00", "time_stop": "2014-07-21 17:00:00", "venue_serial": 1454, "description": "This full day of community management training is delivered by Jono Bacon, author of The Art of Community, and covers a wide range of topics for community managers and leaders to build fun, productive, and rewarding communities.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34247", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34247", "speakers": [108813], "categories": [ - + "Community" - + ] }, - + { "serial": 34248, "name": "Dealing With Disrespect", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 14:30:00", "time_stop": "2014-07-23 15:10:00", "venue_serial": 1464, "description": "In this new presentation from Jono Bacon, author of The Art of Community, founder of the Community Leadership Summit, and Ubuntu Community Manager, he discusses how to process, interpret, and manage rude, disrespectful, and non-constructive feedback in communities so the constructive criticism gets through but the hate doesn't.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34248", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34248", "speakers": [108813], "categories": [ - + "Community" - + ] }, - + { "serial": 34252, "name": "Zero to Cloud with @NetflixOSS", "event_type": "tutorial", - + "time_start": "2014-07-21 09:00:00", "time_stop": "2014-07-21 12:30:00", "venue_serial": 1449, "description": "We want you to leave OSCON with a working cloud account, including supporting infrastructure that Amazon DOESN\u2019T provide but that will make your cloud life way more manageable! Once your account is bootstrapped with Asgard and Aminator, we\u2019ll be baking some of the myriad of @NetflixOSS apps. This tutorial will be meaningful for anyone getting started with or currently using AWS. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34252", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34252", "speakers": [172610], "categories": [ - + "Cloud", - + "Operations & System Administration" - + ] }, - + { "serial": 34254, "name": "Hands-On Data Analysis with Python", "event_type": "tutorial", - + "time_start": "2014-07-21 13:30:00", "time_stop": "2014-07-21 17:00:00", "venue_serial": 1449, "description": "Python is quickly becoming the go-to language for data analysis. However, it can be difficult to figure out which tools are good to use. In this workshop, we\u2019ll work through in-depth examples of tools for data wrangling, machine learning, and data visualization. I\u2019ll show you how to work through a data analysis workflow, and how to deal with different kinds of data. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34254", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34254", "speakers": [170237], "categories": [ - + "Python", - + "Tools & Techniques" - + ] }, - + { "serial": 34255, "name": "Analyzing Data with Python", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 14:30:00", "time_stop": "2014-07-23 15:10:00", "venue_serial": 1459, "description": "Python is quickly becoming the go-to language for data analysis, but it can be difficult to figure out which tools to use. In this presentation, I\u2019ll give a bird\u2019s eye overview of some of the best tools for data analysis and how you can apply them to your own workflow. I\u2019ll introduce you to how you can use Pandas, Scikit-Learn, NLTK, MRJob, and matplotlib for data analysis.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34255", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34255", "speakers": [170237], "categories": [ - + "Python" - + ] }, - + { "serial": 34258, "name": "Crash Course in Tech Management", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 14:30:00", "time_stop": "2014-07-23 15:10:00", "venue_serial": 1452, "description": "'Programmer' and 'Manager' are two different titles for a reason: they're two different jobs and skill sets. If you have managerial aspirations (or have had them foisted upon you), come to this session to learn some of the tricks of the managerial trade.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34258", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34258", "speakers": [131404], "categories": [ - + "Business", - + "Community" - + ] }, - + { "serial": 34260, "name": "How We Went Remote", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 17:00:00", "time_stop": "2014-07-22 17:40:00", "venue_serial": 1448, "description": "Hiring remote workers is great for filling those holes on the team...but if you don't have the correct infrastructure in place you're just setting yourself--and your team members--up for a world of hurt. This session will detail how our engineering department went remote and thrived because of it.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34260", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34260", "speakers": [131404], "categories": [ - + "Main Stage" - + ] }, - + { "serial": 34267, "name": "A Quick Introduction to System Tools Programming with Go", "event_type": "tutorial", - + "time_start": "2014-07-21 13:30:00", "time_stop": "2014-07-21 17:00:00", "venue_serial": 1457, "description": "This tutorial provides an introduction to Go with a focus on using it for everyday sysadmins tooling. A example of working from iostat is used to show a practical approach to learning the language.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34267", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34267", "speakers": [172994], "categories": [ - + "Operations & System Administration", - + "Tools & Techniques" - + ] }, - + { "serial": 34273, "name": "SWI-Prolog for the Real World", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 17:00:00", "time_stop": "2014-07-22 17:40:00", "venue_serial": 1454, "description": "Savvy functional programmers are discovering logic programming, and SWI-Prolog's vast niftiness. Come watch Annie run her debugger in reverse, directly execute syntax specifications, and lets the computer figure out it's own darn execution strategy. Be amazed as Annie constrains variables and shrinks her APIs. Ooh and Aah at the many libraries, nifty web framework and clean environment.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34273", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34273", "speakers": [172986], "categories": [ - + "Emerging Languages" - + ] }, - + { "serial": 34274, "name": "Arduino + Furby Broken Build Notification - Oh, You'll Want to Fix it Quick!", "event_type": "40-minute conference session", - + "time_start": "2014-07-24 11:50:00", "time_stop": "2014-07-24 12:30:00", "venue_serial": 1448, "description": "Furby's are back and more annoying than ever. Forget about a traffic light flashing or an email. When that Furby starts jabbering, you'll do ANYTHING to fix that build quickly. This talk will connect an Arduino board with Jenkins continuous integration framework and out to the Furby to let it annoy your development team, rather than you!", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34274", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34274", "speakers": [99280], "categories": [ - + "Main Stage", - + "Open Hardware" - + ] }, - + { "serial": 34275, "name": "PHP Development for Google Glass using Phass", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 16:10:00", "time_stop": "2014-07-22 16:50:00", "venue_serial": 1458, "description": "Phass is a ZF2-based framework (implemented as a Module) designed to make building Google GlassWare applications in PHP as easy as possible. In this talk we\u2019ll show you how Phass works, complete with a live Google Glass demo of an application in action! \r\n", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34275", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34275", "speakers": [6894], "categories": [ - + "Mobile Platforms", - + "PHP" - + ] }, - + { "serial": 34280, "name": "Portable Logic/Native UI", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 16:10:00", "time_stop": "2014-07-23 16:50:00", "venue_serial": 1449, "description": "This talk shows how to design mobile apps whose complex internal logic runs on many mobile operating systems, but with native UI on those platforms. This ensures that the best possible user experience on each platform.\r\n\r\nThis talk focuses on design patterns for structuring your app for dealing with a mix of cross\u2013platform code and platform-specific UI code.\r\n", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34280", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34280", "speakers": [109468], "categories": [ - + "Mobile Platforms" - + ] }, - + { "serial": 34281, "name": "Erlang, LFE, Joxa and Elixir: Established and Emerging Languages in the Erlang Ecosystem", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 16:10:00", "time_stop": "2014-07-23 16:50:00", "venue_serial": 1456, "description": "Erlang is a concurrent programming language with a small, active community and many high-uptime, critical deployments. It's syntax is a bit odd, being inspired by Prolog. Other languages--Elixir, notably--have begun to reap the benefits of Erlang's VM, BEAM, modifying syntax and semantics. This talk will provide a view of the BEAM languages, their history, motivations and benefits. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34281", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34281", "speakers": [172990], "categories": [ - + "Emerging Languages" - + ] }, - + { "serial": 34283, "name": "Obey the Testing Goat! TDD for Web Development with Python", "event_type": "tutorial", - + "time_start": "2014-07-21 09:00:00", "time_stop": "2014-07-21 12:30:00", "venue_serial": 1450, "description": "Learn Test-Driven-Development and how it applies to web applications by building a simple web app from scratch using Python and Django. We'll cover unit testing, Django models, views and templates, as well as using Selenium to open up a real web browser for functional tests.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34283", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34283", "speakers": [180056], "categories": [ - + "JavaScript - HTML5 - Web", - + "Python", - + "Tools & Techniques" - + ] }, - + { "serial": 34285, "name": "Idioms for Building Distributed Fault-tolerant Applications with Elixir", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 17:00:00", "time_stop": "2014-07-23 17:40:00", "venue_serial": 1454, "description": "This talk will introduce developers to the Elixir programming language and the Erlang VM and show how they introduce a completely new vocabulary which shapes how developers design and build distributed, fault-tolerant applications. This talk also discusses Elixir goals and what it brings to the Erlang VM.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34285", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34285", "speakers": [76735], "categories": [ - + "Emerging Languages" - + ] }, - + { "serial": 34289, "name": "Lessons from Girl Develop It: Getting More Women Involved in Open Source", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 10:40:00", "time_stop": "2014-07-23 11:20:00", "venue_serial": 1462, "description": "Women make up only 11% of open source developers. As Girl Develop It leaders in Philadelphia, we\u2019ve learned about what works to get women involved in open source projects at the grassroots level. We\u2019ll share our experience encouraging women to make open source contributions, using concrete methods that can be replicated in your own communities.\r\n", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34289", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34289", "speakers": [169992,173025], "categories": [ - + "Community", - + "Education" - + ] }, - + { "serial": 34293, "name": "Performing High-Performance Parallel Data Fetching in PHP", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 13:40:00", "time_stop": "2014-07-22 14:20:00", "venue_serial": 1458, "description": "While Node.js and other asynchronous technologies have been receiving quite a bit of attention, in this session, we'll discuss a technology stack we've written which permits PHP developers to perform complex database, cache, and API requests asynchronously, in parallel, resulting in excellent response times. This can be done natively in PHP with NO gearman and NO custom PECL extensions.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34293", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34293", "speakers": [86090], "categories": [ - + "PHP" - + ] }, - + { "serial": 34299, "name": "Painless Data Storage with MongoDB and Go", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 13:40:00", "time_stop": "2014-07-22 14:20:00", "venue_serial": 1475, "description": "Find out why some people claim Go and MongoDB are a 'pair made in heaven' and 'the best database driver they've ever used' in this talk by Gustavo Niemeyer, the author of the mgo driver, and Steve Francia, the drivers team lead at MongoDB Inc.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34299", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34299", "speakers": [142995], "categories": [ - + "Databases & Datastores" - + ] }, - + { "serial": 34314, "name": "Developing Micro-services with Java and Spring", "event_type": "40-minute conference session", - + "time_start": "2014-07-24 10:00:00", "time_stop": "2014-07-24 10:40:00", "venue_serial": 1458, "description": "With plenty of live code and demos, this talk will show you how incredibly easy it is to write Java micro-services with modern Spring. We will walk though the process of creating a simple REST service, discuss deployment options and talk about how self-contained, stand-alone applications work in production.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34314", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34314", "speakers": [171565], "categories": [ - + "Java & JVM", - + "Tools & Techniques" - + ] }, - + { "serial": 34327, "name": "Embedding Node.js into a High-performance Network Datapath", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 11:30:00", "time_stop": "2014-07-23 12:10:00", "venue_serial": 1450, - "description": "This talk will discuss why LineRate, a high-performance Layer 7 app proxy for developers, chose to embed Node.js as the programming language for the data path. The talk will focus on the challenges of building an embeddable\r\nNode.js and conclude with how the open source Node.js code base could evolve to better support embeddable use cases.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34327", + "description": "This talk will discuss why LineRate, a high-performance Layer 7 app proxy for developers, chose to embed Node.js as the programming language for the data path. The talk will focus on the challenges of building an embeddable\r\nNode.js and conclude with how the open source Node.js codebase could evolve to better support embeddable use cases.", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34327", "speakers": [173056], "categories": [ - + "JavaScript - HTML5 - Web" - + ] }, - + { "serial": 34329, "name": "Static Web Rising", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 11:30:00", "time_stop": "2014-07-22 12:10:00", "venue_serial": 1450, "description": "A combination of open standards, open source projects, and evolving browser technologies have made static web apps an increasingly appealing target even for complex applications. Learn how you can \u201cgo static\u201d and why you might want to do so.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34329", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34329", "speakers": [2593], "categories": [ - + "JavaScript - HTML5 - Web" - + ] }, - + { "serial": 34332, "name": "Global Scaling at the New York Times using RabbitMQ ", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 10:40:00", "time_stop": "2014-07-22 11:20:00", "venue_serial": 1459, "description": "Learn about the 'nyt\u2a0da\u0431rik' platform which sits behind The New York Times website. Learn how it scales across many continents and AWS availability zones using RabbitMQ as the backbone of communication for exchanging messages in near real-time. nyt\u2a0da\u0431rik is built on open-source and most of it will be open sourced.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34332", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34332", "speakers": [112672,172658], "categories": [ - + "Cloud" - + ] }, - + { "serial": 34336, "name": "One Hat, Two Hats - How to Handle Open Source and Work", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 11:30:00", "time_stop": "2014-07-22 12:10:00", "venue_serial": 1464, "description": "Working in open source is living the dream, right? What happens when that dream clashes with the real world deliveries associated with those paying you to work in the open. Speaking from 3 years of experience working on a new tools project and through interviews with others in the industry, this talk should help show you through the ups and downs.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34336", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34336", "speakers": [39928], "categories": [ - + "Community" - + ] }, - + { "serial": 34356, "name": "Making Money at Open Source without Losing Your Soul - A Practical Guide", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 13:40:00", "time_stop": "2014-07-22 14:20:00", "venue_serial": 1457, "description": "One of the tenets of Open Source is \u201cFree as in beer\u201d but there is still a viable commercial model. There are many successful open source companies that, despite the fact they give away their product, still manage to make money. A lot of money. How can this be possible? Let\u2019s explore some of the time tested strategies without compromising your core values.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34356", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34356", "speakers": [152376], "categories": [ - + "Business" - + ] }, - + { "serial": 34362, "name": "Money for Nothing and Your Downloads for Free", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 13:40:00", "time_stop": "2014-07-23 14:20:00", "venue_serial": 1464, "description": "Not all projects benefit from a deep-pocketed corporate sponsor to fund their community activities. There are bills that need paying for server hosting, download bandwidth and the like, and maybe for trademark registration and other legal costs for larger projects. What's the best way to fund your project? These speakers know!", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34362", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34362", "speakers": [29591,28902,173340], "categories": [ - + "Community" - + ] }, - + { "serial": 34371, "name": "A Recovering Java Developer Learns to Go", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 13:40:00", "time_stop": "2014-07-22 14:20:00", "venue_serial": 1461, "description": "The Go programming language has emerged as a favorite tool of DevOps and cloud practitioners alike. In many ways, Go is more famous for what it doesn't include than what it does, and co-author Rob Pike has said that Go represents a 'less is more' approach to language design. This talk will introduce Go and its distinctives to Java developers looking to add Go to their toolkits.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34371", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34371", "speakers": [173088], "categories": [ - + "Emerging Languages", - + "Java & JVM" - + ] }, - + { "serial": 34374, "name": "CSS: Declarative Nonsense Made Sensible", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 13:40:00", "time_stop": "2014-07-22 14:20:00", "venue_serial": 1450, "description": "Unless you're working full time as a front-end engineer, odds are that CSS frustrates you from time to time. This session offers advice on how to see past the obtuse corners of CSS, backed by fifteen years' hands-on experience.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34374", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34374", "speakers": [173093], "categories": [ - + "JavaScript - HTML5 - Web" - + ] }, - + { "serial": 34377, "name": "NASA Open Source Projects for Science and Exploration", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 11:30:00", "time_stop": "2014-07-23 12:10:00", "venue_serial": 1459, "description": "The Jet Propulsion Laboratory has been busy lately open sourcing its software, such as mobile apps for viewing the latest Mars images, communicating between robots, and sharing scientific analysis software in using app containers and cloud computing. Come and listen to stories and anecdotes about working on NASA projects and our journey into open source.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34377", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34377", "speakers": [173111], "categories": [ - + "Cloud" - + ] }, - + { "serial": 34378, "name": "WeIO Platform for Internet of Things", "event_type": "40-minute conference session", - + "time_start": "2014-07-24 11:00:00", "time_stop": "2014-07-24 11:40:00", "venue_serial": 1451, "description": "WeIO is an innovative Open Source hardware and software platform for Internet of Things that allows the creation of wirelessly connected objects using popular web languages such as HTML5 or Python.\r\nAll further details can be found on project's web-site: http://we-io.net.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34378", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34378", "speakers": [173105], "categories": [ - + "Open Hardware" - + ] }, - + { "serial": 34383, "name": "Getting Started with Scalding, Twitter's High-level Scala API for Hadoop MapReduce", "event_type": "tutorial", - + "time_start": "2014-07-21 13:30:00", "time_stop": "2014-07-21 17:00:00", "venue_serial": 1471, "description": "Scalding is an open source framework developed at Twitter that provides a high level abstraction over Hadoop MapReduce, letting you concisely specify complex data analysis pipelines using simple Scala operations like map, filter, join, group, and sum. This introductory tutorial does not require experience with either Hadoop or Scala.\r\n", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34383", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34383", "speakers": [90628], "categories": [ - + "Databases & Datastores", - + "Java & JVM" - + ] }, - + { "serial": 34394, "name": "Just My Type", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 10:40:00", "time_stop": "2014-07-22 11:20:00", "venue_serial": 1465, "description": "We perl programmers aren't known as fans of formal types. Types are for straitjacketed languages like Java.\r\n\r\nBut... the Moose revolution's changing all that. Types are a great way of encapsulating the messy business of data conversion and parameter validation, and can help you think more clearly about what's going on in complex code.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34394", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34394", "speakers": [75349], "categories": [ - + "Perl" - + ] }, - + { "serial": 34395, "name": "Getting Started with Go", "event_type": "tutorial", - + "time_start": "2014-07-21 09:00:00", "time_stop": "2014-07-21 12:30:00", "venue_serial": 1475, "description": "This tutorial will give developers an introduction and practical experience in building applications with the go language. Go expert Steve Francia will lead the class to build a working go web and cli application together teaching fundamentals, key features and best practices along the way. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34395", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34395", "speakers": [142995], "categories": [ - + "Emerging Languages" - + ] }, - + { "serial": 34407, "name": "Big Data Analysis 0-60 in 90 days", "event_type": "40-minute conference session", - + "time_start": "2014-07-24 11:00:00", "time_stop": "2014-07-24 11:40:00", "venue_serial": 1457, "description": "Do you know how long could it take to your team start producing value in the Big Data and Machine Learning area? This talk shows a real team experience starting from scratch to a functional Big Data and Machine Learning platform using several open source tools such as Apache Hadoop, Apache Hive and Python frameworks SciPy/Numpy/scikit-learn", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34407", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34407", "speakers": [173146,94695], "categories": [ - + "Databases & Datastores" - + ] }, - + { "serial": 34414, "name": "Train Spotting with Raspberry Pi and Data Science", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 14:30:00", "time_stop": "2014-07-23 15:10:00", "venue_serial": 1451, "description": "Can computers tell if trains run on time? Using microphones, IP cameras, Arduino and Raspberry Pi we set up sensors to detect commuter trains as they passed by. Together with signal processing in Python, streaming data aggregation with Flume and storing in Hadoop, we\u2019ll show you how you can do this too.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34414", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34414", "speakers": [171621,133624], "categories": [ - + "Open Hardware" - + ] }, - + { "serial": 34422, "name": "Mesos: Elastically Scalable Operations, Simplified", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 14:30:00", "time_stop": "2014-07-23 15:10:00", "venue_serial": 1466, "description": "Apache Mesos is a cluster manager that provides efficient resource isolation and sharing across distributed applications. Mesos is not only a resource scheduler but also a library for rapidly developing scalable and fault-tolerant distributed systems. This talk will take the audience through the key aspects contributing to the growing adoption of Mesos in companies with large-scale data centers.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34422", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34422", "speakers": [172898,171598], "categories": [ - + "Cloud", - + "Operations & System Administration" - + ] }, - + { "serial": 34430, "name": "How Does Raleigh Use Open Source?", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 14:30:00", "time_stop": "2014-07-22 15:10:00", "venue_serial": 1457, "description": "Open source, open data, and open access, that's what the City of Raleigh is all about. But how did Raleigh go from open government resolution to an open data portal and a preference for open source software for IT procurement? Come to this session to learn how city government and citizens are working together to create an open source city. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34430", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34430", "speakers": [156534,173173], "categories": [ - + "Business", - + "Community" - + ] }, - + { "serial": 34431, "name": "Neo4j 2.0 Intro Training", "event_type": "tutorial", - + "time_start": "2014-07-21 13:30:00", "time_stop": "2014-07-21 17:00:00", "venue_serial": 1470, "description": "This training offers the first step in building a good knowledge of graph databases, and covers the core functionality of the open source Neo4j graph database. With a mixture of theory and hands-on practice sessions, you will quickly learn how easy it is to work with a powerful graph database using Cypher as the query language. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34431", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34431", "speakers": [159719], "categories": [ - + "Databases & Datastores" - + ] }, - + { "serial": 34432, "name": "Mesos: An SDK for Distributed Systems Developers", "event_type": "tutorial", - + "time_start": "2014-07-20 13:30:00", "time_stop": "2014-07-20 17:00:00", "venue_serial": 1456, "description": "The shift to the cloud is old news. Unfortunately, the pain of developing distributed architectures is not. Apache Mesos handles the hard parts of building distributed systems and lets developers focus on what makes their application special. In this workshop, we will illustrate how to write applications on Mesos by walking through the implementation of an example framework.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34432", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34432", "speakers": [172973,171598,172898], "categories": [ - + "Cloud", - + "Tools & Techniques" - + ] }, - + { "serial": 34434, "name": "Hacking Lessons: Which Micro-Controller Board Do I Use?", "event_type": "40-minute conference session", - + "time_start": "2014-07-24 10:00:00", "time_stop": "2014-07-24 10:40:00", "venue_serial": 1451, "description": "It's a great time to be a hardware hacker. What started with the Arduino has now evolved to the Raspberry Pi, the BeagleBone Black, the Spark Core, the new Arduino Yun, and a host of other boards. How do you know which one is right for your project? This talk will compare the mainstream boards, how they are applied and help you decide which one best fits your needs. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34434", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34434", "speakers": [77350], "categories": [ - + "Open Hardware" - + ] }, - + { "serial": 34442, "name": "Perl 5.20: Perl 5 at 20", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 11:30:00", "time_stop": "2014-07-22 12:10:00", "venue_serial": 1451, "description": "This year brings the release of Perl 5.20.0, and the 20th anniversary of the Perl 5 programming language. In this session, Ricardo Signes, the Perl 5 project manager, covers the latest developments in the language, the development process, and changes we're hoping for in the near future.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34442", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34442", "speakers": [3189], "categories": [ - + "Perl" - + ] }, - + { "serial": 34447, "name": "HTML Canvas Deep Dive", "event_type": "tutorial", - + "time_start": "2014-07-20 09:00:00", "time_stop": "2014-07-20 12:30:00", "venue_serial": 1449, "description": "In the fourth edition of this popular tutorial, we will focus on data visualization. Finding, parsing, drawing, and animating interesting data sets to promote understanding.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34447", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34447", "speakers": [6931,183609], "categories": [ - + "JavaScript - HTML5 - Web" - + ] }, - + { "serial": 34450, "name": "Bluetooth Low Energy: Big Progress for Small Consumption!", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 14:30:00", "time_stop": "2014-07-22 15:10:00", "venue_serial": 1451, "description": "The last year has been great for Bluetooth LE. Supported on all smartphone OSes, hackable with Arduino and Raspberry PI, and ready for wearable computing. Review the year of BLE, and build your own smart watch.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34450", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34450", "speakers": [6931], "categories": [ - + "Open Hardware" - + ] }, - + { "serial": 34451, "name": "Netflix API : Top 10 Lessons Learned", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 10:40:00", "time_stop": "2014-07-23 11:20:00", "venue_serial": 1449, "description": "Operating a massive-scale system, such as the Netflix API, is no trivial task. It supports over 44M members in 40+ countries and sees billions of requests a day. Along the way, there have been many mistakes, yet it is still at the center of the Netflix streaming ecosystem. In this session, I will go into detail on the top ten lessons learned in operating this complex and critical system.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34451", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34451", "speakers": [173189], "categories": [ - + "Cloud" - + ] }, - + { "serial": 34458, "name": "Start a Free Coding Club for Kids", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 14:30:00", "time_stop": "2014-07-22 15:10:00", "venue_serial": 1462, "description": "Most Saturday mornings, Greg Bulmash brings together 70-80 boys and girls, dozens of parents and volunteers, and they teach the kids to code at a free club called CoderDojo. Come learn how to start a CoderDojo in your city and join the hundreds of cities around the world where kids are learning everything from 'hello world' to NodeCopters to building apps.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34458", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34458", "speakers": [171495], "categories": [ - + "Education" - + ] }, - + { "serial": 34459, "name": "\u201cUnfortunately, Design Tutorial Has Stopped\u201d, and Other Ways to Infuriate People With Mobile Apps", "event_type": "tutorial", - + "time_start": "2014-07-21 09:00:00", "time_stop": "2014-07-21 12:30:00", "venue_serial": 1451, "description": "In this tutorial you'll learn why you can't consider UX + design an optional extra when creating mobile apps, and how to tell an awesome app from a bad app. This highly interactive platform-agnostic design-heavy workshop is for programmers of any background. Learn how mobile apps work from a UI perspective, and how + why to build wireframes, and how to evaluate your designs for future improvement.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34459", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34459", "speakers": [108884,118998,109468], "categories": [ - + "Mobile Platforms", - + "User Experience" - + ] }, - + { "serial": 34461, "name": "How Do I Game Design?", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 14:30:00", "time_stop": "2014-07-22 15:10:00", "venue_serial": 1450, "description": "Understanding games means understanding user engagement and interaction. In this session, you'll learn a fresh perspective on user experience design by understanding how users engage with the fastest-growing form of entertainment in the world.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34461", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34461", "speakers": [108884,118998], "categories": [ - + "User Experience" - + ] }, - + { "serial": 34462, "name": "The Case for Haskell", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 17:00:00", "time_stop": "2014-07-22 17:40:00", "venue_serial": 1462, "description": "We're building ever larger and more complex systems. Coupled with changing requirements and demands for scaling concurrency and parallelism, taming this complexity is no small order.\r\n\r\nAllow me to share my excitement with you! I'll show you how Haskell helps tame this complexity, allows you to overcome the challenges of modern software, and make predictions about what the near future holds.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34462", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34462", "speakers": [155881], "categories": [ - + "Emerging Languages" - + ] }, - + { "serial": 34463, "name": "GitGot: The Swiss Army Chainsaw of Git Repo Management", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 13:40:00", "time_stop": "2014-07-22 14:20:00", "venue_serial": 1454, "description": "GitGot is a Perl-based tool for batch management of collections of git repos. It has a number of interesting features and acts as a force multiplier when dealing with a large varied collection of repositories. My talk will cover why you would want to use GitGot as well as how to use it effectively. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34463", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34463", "speakers": [173201], "categories": [ - + "Tools & Techniques" - + ] }, - + { "serial": 34471, "name": "My Journey as a Community Manager (Literally)", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 17:00:00", "time_stop": "2014-07-23 17:40:00", "venue_serial": 1464, "description": "I am going to expand on the experiences of setting up a worldwide community around our product, the Neo4j graph database. I will be presenting through the prism of being a woman in the technology world and how that has affected the way i had to work.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34471", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34471", "speakers": [161517], "categories": [ - + "Community" - + ] }, - + { "serial": 34473, "name": "Get Started With the Arduino - A Hands-On Introductory Workshop", "event_type": "tutorial", - + "time_start": "2014-07-20 09:00:00", "time_stop": "2014-07-20 12:30:00", "venue_serial": 1470, "description": "Have you always wanted to create hardware devices to interact with the real world? Heard about the Arduino electronics prototyping platform but not sure how to get started? When you attend this workshop you will: set up an Arduino board & software; learn how the Arduino fits into the field of physical computing; and make your Arduino respond to button presses and blink lights. Hardware is fun!\r\n", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34473", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34473", "speakers": [77469], "categories": [ - + "Geek Lifestyle", - + "Open Hardware" - + ] }, - + { "serial": 34498, "name": "Best Practices for MySQL High Availability", "event_type": "tutorial", - + "time_start": "2014-07-21 09:00:00", "time_stop": "2014-07-21 12:30:00", "venue_serial": 1470, "description": "The MySQL world is full of tradeoffs and choosing a High Availability (HA) solution is no exception. We demystify all the alternatives in an unbiased nature. Preference is of course only given to opensource solutions. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34498", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34498", "speakers": [147], "categories": [ - + "Databases & Datastores", - + "Operations & System Administration" - + ] }, - + { "serial": 34505, "name": "Why Schools Don't Use Open Source to Teach Programming", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 11:30:00", "time_stop": "2014-07-23 12:10:00", "venue_serial": 1462, "description": "Aside from the fact that high school programming curricula often require proprietary IDEs, they also don't involve examining any source code from Open Source software projects. What changes would be required in programming curricula to incorporate Open Source? And is that a desirable objective?", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34505", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34505", "speakers": [157509], "categories": [ - + "Education" - + ] }, - + { "serial": 34506, "name": "How to Deploy PHP Apps Safely, Efficiently, and Frequently without Losing Your Sanity (Completely)", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 10:40:00", "time_stop": "2014-07-23 11:20:00", "venue_serial": 1458, "description": "Sane and safe continuous deployment (and testing) can be achieved without much effort using a set of freely-available open-source tools, such as a good source control system, Phing, PHPUnit, some security tools, phpDocumentor and others.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34506", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34506", "speakers": [173235], "categories": [ - + "PHP" - + ] }, - + { "serial": 34509, "name": "Inside the Go Tour", "event_type": "40-minute conference session", - + "time_start": "2014-07-24 11:50:00", "time_stop": "2014-07-24 12:30:00", "venue_serial": 1458, "description": "One of the most important tools created to help people learn Go is the Go tour (http://tour.golang.org)\r\n\r\nIt allows the user to learn the basics of Go and put them in practice directly on their browsers, running code without installing any compiler.\r\n\r\nImplementing this in a safe way is not an easy task! In this talk I present some techniques used to make sure everything goes as expected.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34509", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34509", "speakers": [155088], "categories": [ - + "Security" - + ] }, - + { "serial": 34522, "name": "Internet ALL the Things - a walking tour of MQTT", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 14:30:00", "time_stop": "2014-07-23 15:10:00", "venue_serial": 1465, "description": "As the internet grows, there are more and more interesting devices to connect to it - some of which are mobile, sensor platforms, or healthcare devices. This is all part of the 'Internet of Things' that has been an emerging area of excitement for the last few years. MQTT is a lightweight, messaging system for connected devices, the Industrial Internet, mobile, and the IoT.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34522", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34522", "speakers": [141661], "categories": [ - + "Mobile Platforms" - + ] }, - + { "serial": 34530, "name": "Thinking in a Highly Concurrent, Mostly-functional Language", "event_type": "40-minute conference session", - + "time_start": "2014-07-24 10:00:00", "time_stop": "2014-07-24 10:40:00", "venue_serial": 1452, "description": "The actor model has received much attention because of its scalable and intuitive approach to concurrency. But the notion of concurrency is as fundamental to certain languages as object-orientation is to Java. In this talk, we will describe the evolution of concurrent thinking in Erlang, providing valuable lessons for Go, Rust, Elixir and AKKA developers who will undertake a similar journey.. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34530", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34530", "speakers": [10595], "categories": [ - + "Computational Thinking" - + ] }, - + { "serial": 34535, "name": "Cheap Data Dashboards with Node, Amino and the Raspberry PI", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 11:30:00", "time_stop": "2014-07-23 12:10:00", "venue_serial": 1458, "description": "Cheap LCD TV + Raspberry Pi = instant data dashboard. Learn how to use NodeJS and Amino for full screen GPU accelerated graphics (no X) to quickly build data dashboards. Show feeds, chart tweets, or visualize your build server with a particle fountain. Unleash gratuitous graphics for all to see.\r\n", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34535", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34535", "speakers": [6931], "categories": [ - + "Open Hardware" - + ] }, - + { "serial": 34536, "name": "HTML5 JavaScript Storage for Structured Data", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 17:00:00", "time_stop": "2014-07-23 17:40:00", "venue_serial": 1450, "description": "Learn about going beyond simple cookies and busting the 5MB limit imposed by Web Storage. We'll dive into the IndexedDB API and open your world to reading and writing not just strings from within browser storage, but also blobs, Arrays and Objects too.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34536", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34536", "speakers": [2397], "categories": [ - + "JavaScript - HTML5 - Web" - + ] }, - + { "serial": 34542, "name": "Multiple Datastores Working Together: Will It Blend?", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 16:10:00", "time_stop": "2014-07-23 16:50:00", "venue_serial": 1475, "description": "There has been an explosion in datastore technologies. There are five main types of datastores: Relational, Column Family, Graph, Key-Value and Document. Polyglot Persistence, or the ability to have many different types of datastores interacting with one application, is becoming more prominent and beginning to take center stage.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34542", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34542", "speakers": [159586], "categories": [ - + "Databases & Datastores" - + ] }, - + { "serial": 34551, "name": "Marketing Your Tech Talent ", "event_type": "40-minute conference session", - + "time_start": "2014-07-24 11:50:00", "time_stop": "2014-07-24 12:30:00", "venue_serial": 1454, "description": "Today's tech job descriptions want 'superstars', but most companies \u2013 and employees! \u2013 still treat employee talent as a replaceable commodity. How can you market yourself and your talents, to benefit your own career as well as the company or project you work for? This talk will provide practical ideas and real-life case studies, based on years of experience helping geeks communicate what they do.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34551", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34551", "speakers": [122293], "categories": [ - + "Geek Lifestyle" - + ] }, - + { "serial": 34552, "name": "Improv: Think, React, Go!", "event_type": "tutorial", - + "time_start": "2014-07-21 13:30:00", "time_stop": "2014-07-21 17:00:00", "venue_serial": 1456, "description": "Getting everyone in your company or development team on the same page can be a challenge. This on-your-feet workshop will teach fast, fun improv techniques for helping your group to bond, generate quality ideas and make quick decisions. Learn the secrets of applied improv from two professionals who have decades of experience working in open source, Internet startups and corporate training.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34552", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34552", "speakers": [4378,106355,123894], "categories": [ - + "Business", - + "Community" - + ] }, - + { "serial": 34555, "name": "Build your Own Android App using Open Source Libraries - A Hands On Tutorial", "event_type": "tutorial", - + "time_start": "2014-07-20 13:30:00", "time_stop": "2014-07-20 17:00:00", "venue_serial": 1471, "description": "In this tutorial, we will develop a working Android application using open source libraries for key platform components: HTTP client, JSON parsing, Async image download and caching.\r\n\r\nYou will learn how to manage dependencies using Gradle and best practices for building Android apps using open source libraries.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34555", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34555", "speakers": [173281,182019], "categories": [ - + "Mobile Platforms" - + ] }, - + { "serial": 34568, "name": "React's Architecture", "event_type": "40-minute conference session", - + "time_start": "2014-07-24 11:50:00", "time_stop": "2014-07-24 12:30:00", "venue_serial": 1449, "description": "React is a JavaScript library for building user interfaces developed by Facebook and Instagram. It has a novel rendering architecture that we're going to explore in this talk.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34568", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34568", "speakers": [133198], "categories": [ - + "JavaScript - HTML5 - Web" - + ] }, - + { "serial": 34570, "name": "Satisfying Business and Engineering Requirements: Client-server JavaScript, SEO, and Optimized Page Load", "event_type": "40-minute conference session", - + "time_start": "2014-07-24 10:00:00", "time_stop": "2014-07-24 10:40:00", "venue_serial": 1457, "description": "Often business and developer needs are at odds when developing public facing websites that need to be indexed. Business is concerned with factors such as SEO, visitor retention and bounce rates, while engineering is concerned with developer ergonomics, re-usage, separation of concerns, and maintenance. This talk will describe a solution that satisfies both business and engineering requirements.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34570", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34570", "speakers": [135908], "categories": [ - + "Business", - + "JavaScript - HTML5 - Web" - + ] }, - + { "serial": 34575, "name": "Open-Source DoS Testing and Defense", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 16:10:00", "time_stop": "2014-07-23 16:50:00", "venue_serial": 1466, "description": "Denial of Service (DoS) attacks have been making the news lately -- can your site hold up? In this talk, we'll look at a number of open-source tools for testing your site and walk through ways to guard yourself against web attackers.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34575", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34575", "speakers": [173285], "categories": [ - + "Security" - + ] }, - + { "serial": 34578, "name": "ETL: The Dirty Little Secret of Data Science", "event_type": "40-minute conference session", - + "time_start": "2014-07-24 11:00:00", "time_stop": "2014-07-24 11:40:00", "venue_serial": 1475, "description": "There is an adage that given enough data, a data scientist can answer the world's questions. The untold truth is that the majority of work happens during the ETL and data preprocessing phase. In this talk I discuss Origins, an open source Python library for extracting and mapping structural metadata across heterogenous data stores.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34578", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34578", "speakers": [152026], "categories": [ - + "Databases & Datastores" - + ] }, - + { "serial": 34587, "name": "Monitoring Distributed Systems in Real-time with Riemann and Cassandra", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 17:00:00", "time_stop": "2014-07-22 17:40:00", "venue_serial": 1449, "description": "Computing is spreading outwards: clusters of 1000s of nodes serve a single database, and hundreds of machines analyze the same KPIs.\r\n\r\nHow do we monitor a cluster with many nodes?\r\n\r\nThis talk presents how to effectively monitor a multi-node Cassandra cluster using Riemann and other graphing solutions.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34587", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34587", "speakers": [156989], "categories": [ - + "Operations & System Administration" - + ] }, - + { "serial": 34588, "name": "Getting Started Contributing to Firefox OS", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 17:00:00", "time_stop": "2014-07-22 17:40:00", "venue_serial": 1466, "description": "Firefox OS is a new mobile operating system, developed by Mozilla, which lets users install and run open web applications created using HTML, CSS, and JavaScript.\r\nThe session will introduce people to Firefox OS, the overview, branding and distribution and will explain the governance behind it. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34588", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34588", "speakers": [120025,173308], "categories": [ - + "Mobile Platforms" - + ] }, - + { "serial": 34589, "name": "Make your Open Source More Open \u2013 Conquering the Accessibility Challenge", "event_type": "tutorial", - + "time_start": "2014-07-20 09:00:00", "time_stop": "2014-07-20 12:30:00", "venue_serial": 1457, "description": "How accessible are your development projects? This session puts development to the ultimate accessibility test. The presenters will guide you through an experience of accessibility for people who are blind and then go on to cover best practices, testing, and pitfalls in implementing accessible web and program design. You will walk away with actionable tips to use in your development projects.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34589", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34589", "speakers": [2699,173303], "categories": [ - + "Tools & Techniques", - + "User Experience" - + ] }, - + { "serial": 34595, "name": "Robots in Finland: How a Small Open Hardware Project Sparked International Collaboration Across 10 Timezones and 5,000 Miles ", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 13:40:00", "time_stop": "2014-07-22 14:20:00", "venue_serial": 1464, "description": "This talk shares the success story of how a small open hardware project used an Arduino/Python archival digitization robot to spark an international collaboration spanning cultures and continents. The talk focuses on how the collaboration came to be, how the teams used tools like 3D printing and video to work together across 5000+ miles, and how other OS projects can create similar partnerships.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34595", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34595", "speakers": [165643], "categories": [ - + "Community" - + ] }, - + { "serial": 34610, "name": "DNSSEC Via a New Stub Resolver", "event_type": "40-minute conference session", - + "time_start": "2014-07-24 11:00:00", "time_stop": "2014-07-24 11:40:00", "venue_serial": 1466, "description": "The need for secure DNS is more pressing than ever but the current standard API for using the DNS can't take advantage of modern DNS features. We will give an application developers view of DNSSEC and describe the independently written getDNS API specification. We will showcase the open source implementation of the specification built by our team of developers from NLNet Labs and Verisign.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34610", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34610", "speakers": [173324,173326,173325,172895], "categories": [ - + "Security" - + ] }, - + { "serial": 34612, "name": "Sepia: How LinkedIn Mobile Made Integration Testing Fast and Reliable in Node.js", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 16:10:00", "time_stop": "2014-07-22 16:50:00", "venue_serial": 1449, "description": "LinkedIn runs a node.js server to power its phone clients. Because the server makes HTTP requests to other services, network latencies make for slow, and potentially unreliable end-to-end tests. This presentation walks through how LinkedIn built an open-source tool, sepia, to address the challenge of scaling a complex test infrastructure in order to release high quality code with high confidence.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34612", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34612", "speakers": [172988], "categories": [ - + "Mobile Platforms" - + ] }, - + { "serial": 34627, "name": "Creating an SDK - Lessons Learned", "event_type": "40-minute conference session", - + "time_start": "2014-07-24 11:50:00", "time_stop": "2014-07-24 12:30:00", "venue_serial": 1459, "description": "Taking a complex API and wrapping it to create a coherent SDK for a programming language is a huge undertaking, and even harder when it has to be done by a single developer. I created pyrax, the Python SDK for OpenStack, at the request of my company. It has been a success, but it didn't come easy. In this talk I'll share some of the many lessons learned, both technical and political.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34627", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34627", "speakers": [152106], "categories": [ - + "Cloud" - + ] }, - + { "serial": 34630, "name": "Working with Design in Open Source", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 11:30:00", "time_stop": "2014-07-23 12:10:00", "venue_serial": 1464, "description": "In this session we'll look at how design effects an open source project and how to encourage designers to contribute. We'll also cover the fundamentals of design, in case a developer finds themselves in the role of designer.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34630", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34630", "speakers": [173248], "categories": [ - + "Community", - + "User Experience" - + ] }, - + { "serial": 34632, "name": "OpenStack :: Where Continuous Delivery and Distros Collide", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 13:40:00", "time_stop": "2014-07-22 14:20:00", "venue_serial": 1459, "description": "Mark McLoughlin and Monty Taylor - both members of the OpenStack Technical Committee and Foundation Board - gives their perspectives on how OpenStack caters to two distinct audiences with its time-based release process and its support for continuous deployment. They will also talk to this a case study for how DevOps is influencing the way open-source projects are managed and consumed.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34632", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34632", "speakers": [172824,109289], "categories": [ - + "Cloud" - + ] }, - + { "serial": 34635, "name": "Move Fast and Ship Things ", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 10:40:00", "time_stop": "2014-07-22 11:20:00", "venue_serial": 1449, "description": "This talk will explore the 'move fast' side of Facebook\u2019s software engineering culture: development process, organizational structure, and the vast amounts of tooling we create and use to make sure we don\u2019t screw up. We\u2019ll also dig into how we 'ship things': release process, A/B testing, gate keepers, test infrastructure, and more.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34635", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34635", "speakers": [109270], "categories": [ - + "Cloud", - + "Operations & System Administration" - + ] }, - + { "serial": 34637, "name": "Chef and OpenStack", "event_type": "40-minute conference session", - + "time_start": "2014-07-24 10:00:00", "time_stop": "2014-07-24 10:40:00", "venue_serial": 1459, "description": "The open source configuration management and automation framework Chef is used to configure, deploy and manage many large public and private installations of OpenStack and supports a wide variety of integration opportunities. OpenStack is a large and complex ecosystem, this session will highlight the Chef resources available for developers and operators.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34637", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34637", "speakers": [141169], "categories": [ - + "Operations & System Administration" - + ] }, - + { "serial": 34640, "name": "Include Hack - HHVM - PHP++", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 10:40:00", "time_stop": "2014-07-22 11:20:00", "venue_serial": 1458, "description": "Did you know that one of the biggest PHP sites on the internet isn't running PHP? Did you know that HHVM clocks in at anywhere between 2x and 10x faster than standard PHP with an Opcode Cache? Come take a look at \u201cThe other PHP engine\u201d, how to get a server up and running, what pitfalls to watch out for in migrating over, and what exciting extras are waiting. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34640", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34640", "speakers": [173262,173336], "categories": [ - + "PHP" - + ] }, - + { "serial": 34642, "name": "Playing Chess with Companies", "event_type": "tutorial", - + "time_start": "2014-07-21 09:00:00", "time_stop": "2014-07-21 12:30:00", "venue_serial": 1458, "description": "Most organisations have strategy documents full of implementation, purchasing, tactical and operational choices. Remove this and you're often left with a vague 'why' which normally boils down to copying everyone else. In this tutorial I'll demonstrate how a large number of companies are playing a game of chess in which they can't see the board and how you can exploit this.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34642", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34642", "speakers": [6219], "categories": [ - + "Business" - + ] }, - + { "serial": 34646, "name": "Predicting Global Unrest with GDELT and SQL on Hadoop ", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 10:40:00", "time_stop": "2014-07-23 11:20:00", "venue_serial": 1454, "description": "The Global Database of Events, Language, and Tone (GDELT) is an initiative to construct a catalog of human societal-scale behavior and beliefs across all countries of the world. Analysis of this data set requires addressing typical data quality and data skew issues. \r\n\r\nUse a combined Hadoop + SQL on Hadoop stack to cleanse the data and deliver insights into the state of the world. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34646", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34646", "speakers": [53442], "categories": [ - + "Databases & Datastores" - + ] }, - + { "serial": 34650, "name": "Arduino Yun for Intermediate Arduino Users: Using the Onboard Linux Computer to Communicate with Other Computers and the Internet", "event_type": "tutorial", - + "time_start": "2014-07-20 13:30:00", "time_stop": "2014-07-20 17:00:00", "venue_serial": 1470, "description": "The new Arduino Yun contains both an Arduino Leonardo and a full Linux system on a chip with built-in Ethernet and Wifi. This intermediate level hands-on tutorial will teach you how to use the Yun to communicate between Yun and Yun, Yun and laptop, and Yun and internet services, such Gmail, Twitter, and other services with APIs", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34650", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34650", "speakers": [141561], "categories": [ - + "Education", - + "Open Hardware" - + ] }, - + { "serial": 34654, "name": "Red October: Implementing the Two-man Rule for Keeping Secrets", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 14:30:00", "time_stop": "2014-07-23 15:10:00", "venue_serial": 1462, "description": "Red October is an open source encryption server with a twist -- it can encrypt secrets, requiring more than one person to decrypt them. This talk will describe what goes into building an open source security product and using it in the real world. From motivation, design decisions, pitfalls of using a young programming language like Go, through deployment and opening the work up to the community.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34654", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34654", "speakers": [164229], "categories": [ - + "Security" - + ] }, - + { "serial": 34668, "name": "OpenUI5 - The New Responsive Web UI Library", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 17:00:00", "time_stop": "2014-07-22 17:40:00", "venue_serial": 1450, "description": "OpenUI5 is a comprehensive enterprise-grade HTML5 UI library (developed by SAP) which has been open-sourced recently. Explore its power through concrete code examples and demos for key features like declarative UIs, data binding, and responsiveness: write ONE app and it will adapt to any device, from desktop screen to smartphones.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34668", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34668", "speakers": [170822,173233], "categories": [ - + "User Experience" - + ] }, - + { "serial": 34677, "name": "Elasticsearch: The Missing Tutorial", "event_type": "tutorial", - + "time_start": "2014-07-20 09:00:00", "time_stop": "2014-07-20 12:30:00", "venue_serial": 1452, "description": "Elasticsearch provides a powerful combination of clustered full-text search, synonyms, faceting, and geographic math, but there's a big gap between its documentation and real life. We'll tell hard-won war stories, work through hands-on examples, and show what happens behind the scenes, leaving you equipped to get the best use out of Elasticseach in your projects.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34677", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34677", "speakers": [173247,150], "categories": [ - + "Cloud", - + "Databases & Datastores" - + ] }, - + { "serial": 34678, "name": "Open HeARTware with ChickTech", "event_type": "tutorial", - + "time_start": "2014-07-20 13:30:00", "time_stop": "2014-07-20 17:00:00", "venue_serial": 1451, "description": "Are you a software person? An artsy type? Never thought you would like hardware? Or perhaps you love hardware? No matter what your skill level, this workshop is for you. Get in on the open hardware movement and join ChickTech to create your own \u201csoft circuit\u201d using conductive thread, fabric, inputs/outputs, and a microcontroller! ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34678", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34678", "speakers": [131890,124700], "categories": [ - + "Education" - + ] }, - + { "serial": 34687, "name": "Identity Crisis: Are We Really Who We Say We Are?", "event_type": "40-minute conference session", - + "time_start": "2014-07-24 10:00:00", "time_stop": "2014-07-24 10:40:00", "venue_serial": 1456, "description": "Karen Sandler, Executive Director of the GNOME Foundation, will discuss the peculiar tension in the intersection of free and open source software and corporate interest. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34687", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34687", "speakers": [173364], "categories": [ - + "Business", - + "Community" - + ] }, - + { "serial": 34688, "name": "Healthcare for Geeks", "event_type": "40-minute conference session", - + "time_start": "2014-07-24 11:00:00", "time_stop": "2014-07-24 11:40:00", "venue_serial": 1448, "description": "Hacking Healthcare author David Uhlman will show you how to 3D print your body parts, order your own lab work, build a DNA analyzer, tour an array of personal monitoring devices for fitness, health and open biology projects, stop eating altogether by switching to soylent. Also tips on what insurance to get, navigating hospitals and finding the right doctor.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34688", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34688", "speakers": [86111], "categories": [ - + "Main Stage" - + ] }, - + { "serial": 34690, "name": "Contributing to Contributors: Breaking Down the Barriers to First-commit", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 16:10:00", "time_stop": "2014-07-23 16:50:00", "venue_serial": 1448, "description": "Every open source project is a unique snowflake of technology choices, coding style, and communication channels. Learning not only how, but the 'correct' way to contribute to each new project can be a blocker for would-be contributors. This talk will give practical examples of how you can reduce the learning curve for new contributors and improve the quality of first-commits.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34690", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34690", "speakers": [104522], "categories": [ - + "Main Stage" - + ] }, - + { "serial": 34695, "name": "Open edX: an Open-source MOOC Platform", "event_type": "40-minute conference session", - + "time_start": "2014-07-24 11:00:00", "time_stop": "2014-07-24 11:40:00", "venue_serial": 1462, "description": "Open edX is an open-source platform for delivering online courses. It's in use by the 31 member universities of edx.org (Harvard, MIT, Berkeley, etc), as well as Stanford, Google, and many other colleges and universities. This talk will describe the platform and show you ways to participate, as a course author, tool developer, or offering institution.\r\n ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34695", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34695", "speakers": [41059,179963], "categories": [ - + "Education" - + ] }, - + { "serial": 34700, "name": "PHP 5.6 and Beyond: Because Incrementing Major Versions is for Suckers", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 14:30:00", "time_stop": "2014-07-22 15:10:00", "venue_serial": 1458, "description": "PHP 5.6 is out, and comes with useful new features and internal cleanups, as the last few 5.x releases have. In this talk, I'll discuss those features, but also where PHP is going: will there be a PHP 6 or 7 in the near future? What might it contain? How can we learn from Python 3 and Perl 6?", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34700", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34700", "speakers": [152118], "categories": [ - + "PHP" - + ] }, - + { "serial": 34702, "name": "HTML5 Video Part Deux; New Opportunities and New Challenges", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 14:30:00", "time_stop": "2014-07-23 15:10:00", "venue_serial": 1450, "description": "This talk gives a close look at second wave HTML5 features around video delivery \u2014 specifically, mediaSource API / adaptive streaming, encrypted media extension and WebRTC. We look at open tools and techniques for transcending platform limitations and delivery these experiences across increasingly diverse set of devices with real world examples from Kaltura, Wikimedia and others.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34702", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34702", "speakers": [108736], "categories": [ - + "JavaScript - HTML5 - Web" - + ] }, - + { "serial": 34705, "name": "What is Happening at the CentOS Project?", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 17:00:00", "time_stop": "2014-07-23 17:40:00", "venue_serial": 1457, "description": "What is the future of CentOS Linux? Hear the true story from the project leaders behind the surprise announcement that the CentOS Project and Red Hat are joining forces.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34705", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34705", "speakers": [46737,173380,173381], "categories": [ - + "Community" - + ] }, - + { "serial": 34711, "name": "Schemas for the Real World", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 14:30:00", "time_stop": "2014-07-22 15:10:00", "venue_serial": 1475, "description": "Development challenges us to code for users\u2019 personal world. Users give push-back to ill-fitted assumptions about their own name, gender, sexual orientation, important relationships, & other attributes that are individually meaningful. We'll explore how to develop software that brings real world into focus & that allows individuals to authentically reflect their personhood & physical world.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34711", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34711", "speakers": [141590], "categories": [ - + "Databases & Datastores" - + ] }, - + { "serial": 34713, "name": "Functionally Mobile (Automation)", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 11:30:00", "time_stop": "2014-07-23 12:10:00", "venue_serial": 1451, "description": "Mobile's here to stay! This talk will showcase how Open Source tools can power your test automation for mobile apps. It entirely relies on Open Source components such as Appium, Cordova/PhoneGap an Topcoat.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34713", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34713", "speakers": [173378], "categories": [ - + "Mobile Platforms" - + ] }, - + { "serial": 34717, "name": "Developing High Performance Websites and Modern Apps with JavaScript and HTML5", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 10:40:00", "time_stop": "2014-07-23 11:20:00", "venue_serial": 1450, "description": "Creating high performance sites and apps is crucial for every developer. In this session, we will explore the best practices and performance tricks, to make your apps running faster and fluid. Come learn the tips, tricks, and tools for maximizing the performance of your sites and apps with JavaScript and HTML5.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34717", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34717", "speakers": [133360], "categories": [ - + "JavaScript - HTML5 - Web" - + ] }, - + { "serial": 34718, "name": "Android Developer Tools Essentials", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 13:40:00", "time_stop": "2014-07-22 14:20:00", "venue_serial": 1466, "description": "This session is an overview of the Android Developer Tools (ADT and Android Studio), including many useful techniques, tips and tricks for getting the most out of them.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34718", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34718", "speakers": [150073], "categories": [ - + "Mobile Platforms" - + ] }, - + { "serial": 34731, "name": "How We Built a Cloud Platform Using Netflix OSS", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 13:40:00", "time_stop": "2014-07-23 14:20:00", "venue_serial": 1459, "description": "\r\nThe Netflix OSS Cloud stack is clearly a great set of components for building a cloud infrastructure and platform\u2014if you are Netflix. But how does that architecture work for other businesses? Learn how at Riot we leveraged Netflix OSS cloud tools and platform components to create an infrastructure for our global game platform\u2014maybe it can work for you too.\r\n", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34731", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34731", "speakers": [161577], "categories": [ - + "Cloud" - + ] }, - + { "serial": 34737, "name": "Making Federal Regulations Readable with Python", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 17:00:00", "time_stop": "2014-07-23 17:40:00", "venue_serial": 1465, "description": "The Consumer Financial Protection Bureau (http://cfpb.gov) has\r\ndeveloped an open source web-based tool to make regulations easy to\r\nread, access and understand. We talk about the unique parsing and\r\nother challenges we encountered working with these legal documents,\r\nand how we used Python, pyParsing, Django and other open source tools\r\nto solve them.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34737", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34737", "speakers": [157931], "categories": [ - + "Python" - + ] }, - + { "serial": 34744, "name": "Machine Learning for Rubyists", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 17:00:00", "time_stop": "2014-07-23 17:40:00", "venue_serial": 1466, "description": "In this presentation we'll cover five important machine learning techniques that can be used in a wide range of applications. It will be a wide and shallow introduction, for Rubyists, not mathematicians - we'll have plenty of simple code examples. \r\n", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34744", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34744", "speakers": [173396], "categories": [ - + "Computational Thinking" - + ] }, - + { "serial": 34745, "name": "Secure Development is Much Easier Than You Think", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 17:00:00", "time_stop": "2014-07-23 17:40:00", "venue_serial": 1462, "description": "Secure software development is something absolutely critical to helping create safer more trusted computing experiences for everyone.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34745", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34745", "speakers": [173399,173403], "categories": [ - + "Security" - + ] }, - + { "serial": 34746, "name": "Making maps with OpenStreetMap and Koop", "event_type": "40-minute conference session", - + "time_start": "2014-07-24 11:00:00", "time_stop": "2014-07-24 11:40:00", "venue_serial": 1450, "description": "Like maps and open data? Koop has created a new way of accessing open data and making cool maps with wide variety of data \r\n", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34746", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34746", "speakers": [108520,173406], "categories": [ - + "JavaScript - HTML5 - Web" - + ] }, - + { "serial": 34753, "name": "International Community Building: Bridging Japan and the Rest of the World", "event_type": "40-minute conference session", - + "time_start": "2014-07-24 11:00:00", "time_stop": "2014-07-24 11:40:00", "venue_serial": 1464, "description": "Japan has a thriving open source technology community. It\u2019s also the third largest IT market in the world, with more engineers per capita than the US, and a history of game-changing open source projects like Ruby and Jenkins. Hear a first hand account of managing and cultivating open source communities in Japan, the US and other countries and discuss international community building.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34753", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34753", "speakers": [153857], "categories": [ - + "Community" - + ] }, - + { "serial": 34756, "name": "Graph Theory You Need to Know", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 14:30:00", "time_stop": "2014-07-22 15:10:00", "venue_serial": 1452, "description": "A brief and friendly tour of the basics of graph theory, including a description and classification of the kinds of graphs and some interesting problems they can be employed to solve.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34756", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34756", "speakers": [137697], "categories": [ - + "Computational Thinking" - + ] }, - + { "serial": 34757, "name": "The Data Structures (You Think) You Need to Know", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 13:40:00", "time_stop": "2014-07-23 14:20:00", "venue_serial": 1448, "description": "A fun and approachable tour of some otherwise intimidating data structures. Learn how to solve difficult problems efficiently through the clever organization and linking of data.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34757", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34757", "speakers": [137697], "categories": [ - + "Computational Thinking" - + ] }, - + { "serial": 34769, "name": "Tapping into Ruby from the JVM", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 13:40:00", "time_stop": "2014-07-22 14:20:00", "venue_serial": 1456, "description": "With the diversity and innovation in the Ruby ecosystem and the popularity of polyglot programming on the robust and efficient JVM, Ruby and the JVM make a great fit. We\u2019ll cover numerous ways to invoke Ruby from Java and other JVM languages and how to package and deploy this style of application. We\u2019ll study examples from AsciidoctorJ, a Java API to the Ruby-based text processor, Asciidoctor.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34769", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34769", "speakers": [117513], "categories": [ - + "Ruby" - + ] }, - + { "serial": 34772, "name": "Introduction to Ceph", "event_type": "tutorial", - + "time_start": "2014-07-20 13:30:00", "time_stop": "2014-07-20 17:00:00", "venue_serial": 1458, "description": "This Introduction to Ceph tutorial will include a mix of lecture and instructor-led demonstrations that will introduce students to the Ceph distributed storage system, the challenges it addresses, its architecture, and solutions it offers.\r\n\r\nStudents will leave understanding how Ceph works, how it can be integrated with your services and applications, and how it works alongside OpenStack.\r\n", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34772", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34772", "speakers": [183246], "categories": [ - + "Databases & Datastores", - + "Operations & System Administration" - + ] }, - + { "serial": 34773, "name": "Demystifying SELinux Part II: Who\u2019s Policy Is It Anyway?", "event_type": "tutorial", - + "time_start": "2014-07-20 09:00:00", "time_stop": "2014-07-20 12:30:00", "venue_serial": 1458, "description": "Building on last year\u2019s critically acclaimed \u2018Demystifying SELinux: WTF is it saying?\u2019 talk Demystifying \u2018SELinux Part II: Who\u2019s policy is it anyway?\u2019 is an extended tutorial which has attendees work through real life examples of SELinux configuration and policy construction.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34773", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34773", "speakers": [151833], "categories": [ - + "Operations & System Administration", - + "Security" - + ] }, - + { "serial": 34788, "name": "Painlessly Functional and Concurrent: An Introduction to Elixir", "event_type": "tutorial", - + "time_start": "2014-07-20 13:30:00", "time_stop": "2014-07-20 17:00:00", "venue_serial": 1457, "description": "This tutorial is a quick introduction to the Elixir programming language. We\u2019ll explore the basics of the language, meta programming, and explore why you want to use Elixir to write concurrent, scalable, and robust programs.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34788", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34788", "speakers": [173421], "categories": [ - + "Emerging Languages", - + "Tools & Techniques" - + ] }, - + { "serial": 34797, "name": "Open Mobile Accessibility with GitHub and Cordova", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 14:30:00", "time_stop": "2014-07-22 15:10:00", "venue_serial": 1454, "description": "The story of an open-source project that brings mobile accessibility APIs together with native webviews to make mobile app development more responsive to users with disabilities.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34797", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34797", "speakers": [17378], "categories": [ - + "Mobile Platforms" - + ] }, - + { "serial": 34808, "name": "Writing English ", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 13:40:00", "time_stop": "2014-07-22 14:20:00", "venue_serial": 1448, "description": "So you know Java, Scala, Python, and Perl, but do you know the correct usage of a semicolon when it comes to the English language? Writers and engineers alike often fall victim to grammatical blunders that can obscure their intended message. Fortunately, there are some simple ways of spotting and correcting these errors. Once learned, your writing will improve and your readers will thank you. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34808", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34808", "speakers": [173393], "categories": [ - + "Main Stage" - + ] }, - + { "serial": 34809, "name": "Streaming Predictions of User Behavior in Real-Time", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 16:10:00", "time_stop": "2014-07-22 16:50:00", "venue_serial": 1452, "description": "Visitors to an online store rarely make their intention explicit. A valuable goal in digital marketing is to infer this intention so to influence the visitor's behavior in-situ. We describe a data-driven approach to identifying and predicting online user behavior. The talk focuses on the construction of real-time machine learning tools for inference to sites with thousands of concurrent visitors.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34809", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34809", "speakers": [173414,173431], "categories": [ - + "Computational Thinking" - + ] }, - + { "serial": 34811, "name": "Digital Dancing", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 10:40:00", "time_stop": "2014-07-22 11:20:00", "venue_serial": 1448, "description": "You check out the schedule, and you note with excitement that there's a presentation called DIGITAL DANCING. You grab your stuff and head for that conference room.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34811", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34811", "speakers": [161486,182741], "categories": [ - + "Main Stage" - + ] }, - + { "serial": 34814, "name": "Why You Should Be Looking at Functional Web Development", "event_type": "40-minute conference session", - + "time_start": "2014-07-24 11:50:00", "time_stop": "2014-07-24 12:30:00", "venue_serial": 1466, "description": "Web combined with functional programming gives pure awesomeness. Come and learn about WebSharper, an open source web development framework for F#, and how it makes programmers happier and more productive.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34814", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34814", "speakers": [132323], "categories": [ - + "Emerging Languages" - + ] }, - + { "serial": 34820, "name": "The State of Crypto in Python", "event_type": "40-minute conference session", - + "time_start": "2014-07-24 11:50:00", "time_stop": "2014-07-24 12:30:00", "venue_serial": 1465, "description": "Python has a complex past with crypto. There are half a dozen frameworks built on at least three separate C implementations, each with their own strengths and weaknesses and in various states of maintenance. This presentation will review the current state of the art and discuss the future of crypto in Python including a new library aimed at fixing modern crypto support in Python.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34820", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34820", "speakers": [173432,173435], "categories": [ - + "Python" - + ] }, - + { "serial": 34823, "name": "Building a Culture of Continuous Learning", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 16:10:00", "time_stop": "2014-07-22 16:50:00", "venue_serial": 1462, "description": "Technology moves too quickly for us to ever really stop learning - but how can we establish and maintain a culture of continuous learning in our business teams? And how can we ensure that continuous learning is effective?", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34823", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34823", "speakers": [152299], "categories": [ - + "Business", - + "Education" - + ] }, - + { "serial": 34824, "name": "Getting Started with OpenStack: Hands on Tutorial", "event_type": "tutorial", - + "time_start": "2014-07-20 09:00:00", "time_stop": "2014-07-20 12:30:00", "venue_serial": 1475, "description": "Curious about OpenStack, but don't know where to start? In this hands on tutorial we will walk you through the basics of OpenStack, the OpenSource cloud computing platform that is used to build private and public clouds. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34824", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34824", "speakers": [169647,169673], "categories": [ - + "Cloud" - + ] }, - + { "serial": 34829, "name": "Building Reliable Systems: Lessons from Erlang", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 13:40:00", "time_stop": "2014-07-22 14:20:00", "venue_serial": 1452, "description": "Erlang is famous for building systems that are incredibly reliable, having virtually no down time! What are the principles that Erlang uses? Can we apply them in other languages? In this presentation, you'll learn how Erlang's design enables reliability and how you can use similar patterns to improve your own software and software systems.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34829", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34829", "speakers": [131729], "categories": [ - + "Computational Thinking" - + ] }, - + { "serial": 34831, "name": "Kinect for Creative Development with Cinder, openFrameworks", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 17:00:00", "time_stop": "2014-07-22 17:40:00", "venue_serial": 1458, "description": "Want to integrate some body, face, voice recognition into your 3D application? You can do this fairly easily using the Kinect for Windows sensor along with the Kinect Common Bridge, an open source library that makes it simple to integrate Kinect experiences into your C++ code/library. OpenFrameworks, Cinder and other creative development communities have adopted it already! Cool creative demos!", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34831", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34831", "speakers": [143232,23017], "categories": [ - + "User Experience" - + ] }, - + { "serial": 34836, "name": "Perl Lightning Talks", "event_type": "Event", - + "time_start": "2014-07-23 19:30:00", "time_stop": "2014-07-23 20:30:00", "venue_serial": 1450, "description": "Join us for the ever popular Perl Lightning Talks.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34836", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34836", "speakers": [4429], "categories": [ - + "Events", - + "Perl" - + ] }, - + { "serial": 34839, "name": "Moving Java into the Open", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 17:00:00", "time_stop": "2014-07-22 17:40:00", "venue_serial": 1456, "description": "This session will explore how Java development has been brought into the open over the past several years. Several Java developer community efforts have brought open source development processes and new levels of transparency and participation into their communities. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34839", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34839", "speakers": [65329,116276], "categories": [ - + "Java & JVM" - + ] }, - + { "serial": 34840, "name": "Open Community Infrastructure How-to", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 10:40:00", "time_stop": "2014-07-23 11:20:00", "venue_serial": 1466, "description": "Does every open source project need an open infrastructure? Should root be potentially available to any community member? If you think, 'Maybe, yes,' come learn how-to and why-to with lessons-learned from Fedora, oVirt, CentOS Project, and other projects.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34840", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34840", "speakers": [46737], "categories": [ - + "Operations & System Administration" - + ] }, - + { "serial": 34844, "name": "Pro Puppet", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 16:10:00", "time_stop": "2014-07-23 16:50:00", "venue_serial": 1454, "description": "Learn to use Puppet like a Pro! We will take you through several examples of how to bring your Puppet deployment to the next level. We will cover Hiera, deploying puppet code, code architecture best practices, and integrating external tools.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34844", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34844", "speakers": [125113,99817], "categories": [ - + "Operations & System Administration", - + "Tools & Techniques" - + ] }, - + { "serial": 34845, "name": "Transit Appliance at Three", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 17:00:00", "time_stop": "2014-07-23 17:40:00", "venue_serial": 1451, "description": "At OSCON 2011 we introduced the Transit Appliance, a project to use open hardware, open source software and open APIs to create a low-cost display for transit arrivals. Three years later we have two dozen displays deployed in the community, have seen the retirement of the Chumby, the rise of the Raspberry Pi and many new web services enriching the display. Progress and lessons learned.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34845", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34845", "speakers": [109116], "categories": [ - + "Open Hardware" - + ] }, - + { "serial": 34849, "name": "tmux - A Multiplexer's Multiplexer", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 14:30:00", "time_stop": "2014-07-22 15:10:00", "venue_serial": 1466, "description": "Many developers, system/network admins, and designers spend good portions of their careers avoiding any interaction with their systems' command line interface(s) (CLI's). Unfortunately, the CLI is viewed as an archaic and inefficient means of being productive. In tmux, a powerful terminal multiplexer, developers and admins have a tool for more fully exploiting the power of the CLI.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34849", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34849", "speakers": [138530], "categories": [ - + "Operations & System Administration" - + ] }, - + { "serial": 34853, "name": "Build Your Own Exobrain", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 10:40:00", "time_stop": "2014-07-23 11:20:00", "venue_serial": 1448, "description": "Online services like 'If This Then That' (IFTTT) are great for automating your life. However they provide limited ways for the end-user to add their own services, and often require credentials that one may normally wish to keep secret.\r\n\r\nThe 'exobrain' project allows for service integration and extension on a machine *you* control.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34853", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34853", "speakers": [6631], "categories": [ - + "Main Stage" - + ] }, - + { "serial": 34855, "name": "A Platform for Open Science", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 10:40:00", "time_stop": "2014-07-22 11:20:00", "venue_serial": 1466, "description": "This talk will introduce a new open source platform for citizen science data. It allows anyone anywhere to create online data sets by uploading data from their own environmental sensors, mobile devices, do-it-yourself science equipment, and other measurement tools. The talk will describe the design and use of the platform, covering multiple applications and alternatives.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34855", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34855", "speakers": [169557], "categories": [ - + "Education" - + ] }, - + { "serial": 34856, "name": "Grow Developers. Grow Diversity. ", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 10:40:00", "time_stop": "2014-07-22 11:20:00", "venue_serial": 1464, "description": "The United States needs more tech talent. Period.\r\n\r\nAnd yet there is a solution \u2014 Grow Developers. My talk will cover all the many ways this community can actively solve the lack of talent problem, and at the same time give solutions for also growing the female and minority tech populations.\r\n\r\nPeople will walk away with a 3-tiered approach for growing developers and growing diversity. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34856", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34856", "speakers": [173388], "categories": [ - + "Community" - + ] }, - + { "serial": 34860, "name": "From Madison Avenue to git Checkout -- A Journey", "event_type": "40-minute conference session", - + "time_start": "2014-07-24 11:50:00", "time_stop": "2014-07-24 12:30:00", "venue_serial": 1457, "description": "This talk is going to provide insight into what\u2019s it\u2019s like to view Software Development as an outsider, who happens to be an experienced successful professional. I will also tackle the issues that are implicit with imposter syndrome, such as how to grow developers, how to grow diversity, fixing broken hiring practices, and what it\u2019s like to be afraid of open source. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34860", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34860", "speakers": [173388,180122], "categories": [ - + "Community", - + "Education" - + ] }, - + { "serial": 34863, "name": "Keeping Open Source Open", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 10:40:00", "time_stop": "2014-07-23 11:20:00", "venue_serial": 1464, "description": "What happens to an open source community full of hobbyists when the project cleans up its pile of spaghetti and chooses to adopt widely held programming paradigms and systems?", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34863", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34863", "speakers": [173433], "categories": [ - + "Community" - + ] }, - + { "serial": 34865, "name": "Installing OpenStack using SaltStack", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 11:30:00", "time_stop": "2014-07-22 12:10:00", "venue_serial": 1459, "description": "OpenStack is an open source implementation of cloud computing, potentially at very large scale. However, it has many moving parts and is complex to operate. \r\nSaltStack appears to provide scalable and secure orchestration for OpenStack.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34865", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34865", "speakers": [143135], "categories": [ - + "Cloud" - + ] }, - + { "serial": 34870, "name": "Open Source: Emerging in Asia / The Asian Open Source Report Card ", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 16:10:00", "time_stop": "2014-07-22 16:50:00", "venue_serial": 1464, "description": "Asia is a huge untapped market for open source expansion, but it is very unlike North America or Europe. Learn differences within Asia, see open source proliferation in all markets, and most importantly get into thinking Asia expansion is prime.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34870", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34870", "speakers": [147], "categories": [ - + "Business", - + "Community" - + ] }, - + { "serial": 34873, "name": "Just Enough Math", "event_type": "tutorial", - + "time_start": "2014-07-20 09:00:00", "time_stop": "2014-07-20 12:30:00", "venue_serial": 1471, "description": "Advanced math for business people: \u201cjust enough math\u201d to take advantage of new classes of open source frameworks. Many take college math up to calculus, but never learn how to approach sparse matrices, complex graphs, or supply chain optimizations. This tutorial ties these pieces together into a conceptual whole, with use cases and simple Python code, as a new approach to computational thinking.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34873", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34873", "speakers": [146540], "categories": [ - + "Business", - + "Computational Thinking" - + ] }, - + { "serial": 34875, "name": "Money Machines", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 17:00:00", "time_stop": "2014-07-22 17:40:00", "venue_serial": 1457, "description": "Money Machines are small scale highly technical or craft based entrepreneurial excursions. The purpose of the Money Machine is to empower individuals with tools which will allow her/him/them to follow their geeky and nerdy passion of passions while enabling them to address the various financial necessities of life. Money Machines allow people to work in the manner of their choosing.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34875", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34875", "speakers": [138530], "categories": [ - + "Business" - + ] }, - + { "serial": 34881, "name": "Building a Massively Scalable Web Server In Erlang", "event_type": "tutorial", - + "time_start": "2014-07-21 13:30:00", "time_stop": "2014-07-21 17:00:00", "venue_serial": 1458, "description": "Learn the fundamentals of Erlang - a high productivity, functional programming language used to build scalable, highly concurrent systems. In this tutorial, we'll introduce Erlang by way of a fun problem: building an HTTP server! You'll learn the basic of networking programming in Erlang along with key techniques for performance and scalability.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34881", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34881", "speakers": [131729], "categories": [ - + "JavaScript - HTML5 - Web", - + "Tools & Techniques" - + ] }, - + { "serial": 34882, "name": "Highly Functional Programming in Perl", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 11:30:00", "time_stop": "2014-07-23 12:10:00", "venue_serial": 1465, "description": "Functional programming is everywhere, hiding between imperative procedures. Stateless code with no side-effects may seem academic, but practical application of functional techniques leads to fewer bugs and cleaner code. Functional thinking is useful whether you're wrestling with a mess of copy-pasta or doing test-first development on some new object library.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34882", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34882", "speakers": [6574], "categories": [ - + "Perl" - + ] }, - + { "serial": 34888, "name": "Cathedrals in the Cloud: Musings on APIs for the Web", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 14:30:00", "time_stop": "2014-07-22 15:10:00", "venue_serial": 1459, "description": "With the rise of cloud-based services and Web APIs, it may be time to re-visit Raymond's 19 'lessons' from his book 'The Cathedral and the Bazaar' to see how they can be applied (and/or modified) to fit a world where much of the software we use is no longer installed locally and is often kept out of reach from most developers and users. \r\n", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34888", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34888", "speakers": [108272], "categories": [ - + "Cloud" - + ] }, - + { "serial": 34894, "name": "Mobile and Multi-Device Web Development with Tritium", "event_type": "40-minute conference session", - + "time_start": "2014-07-24 11:00:00", "time_stop": "2014-07-24 11:40:00", "venue_serial": 1449, "description": "Tritium is a new open source language from the creator of the popular Sass and HAML languages that brings a modern approach to web development with transforms. In this talk, we'll introduce the Tritium language and the power of transform based approaches for separating content from presentation in the building of multi-device websites for desktops, smartphones, tablets, TVs, wearables and beyond.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34894", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34894", "speakers": [122599], "categories": [ - + "Emerging Languages" - + ] }, - + { "serial": 34897, "name": "My Friends Keep Leaving and it is Ruining Board Games Day", "event_type": "40-minute conference session", - + "time_start": "2014-07-24 11:00:00", "time_stop": "2014-07-24 11:40:00", "venue_serial": 1454, "description": "These days, moving away doesn\u2019t put too much of a dampener on staying in touch with your friends. Unfortunately, it has had a severe effect on my regular board games day.\r\nSo, I thought, why not solve this problem with telepresence board gaming? Can\u2019t be too hard! This session will cover what's been done, and what problems are still out there I have no idea about how to solve?", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34897", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34897", "speakers": [173452], "categories": [ - + "Geek Lifestyle" - + ] }, - + { "serial": 34905, "name": "OAuth2: The Swiss-Army Framework", "event_type": "40-minute conference session", - + "time_start": "2014-07-24 11:50:00", "time_stop": "2014-07-24 12:30:00", "venue_serial": 1456, "description": "If your application doesn't have APIs, it's probably written in Cold Fusion. Every application has APIs, and APIs need authentication. See how OAuth2 is robust enough to satisfy the demands of the enterprise, while still serving the smallest of side projects. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34905", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34905", "speakers": [173314], "categories": [ - + "Security" - + ] }, - + { "serial": 34906, "name": "Mapbox: Building the Future of Open Mapping", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 11:30:00", "time_stop": "2014-07-22 12:10:00", "venue_serial": 1457, "description": "Mapbox is leading the way in open mapping. Large companies are switching to OpenStreetMap and open source software for mapping. Learn how Mapbox is running a business like you would run an open source project and how it is succeeding in a field dominated by large, well-funded players by being open. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34906", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34906", "speakers": [104652], "categories": [ - + "Business" - + ] }, - + { "serial": 34913, "name": "Data Workflows for Machine Learning", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 11:30:00", "time_stop": "2014-07-23 12:10:00", "venue_serial": 1452, "description": "Several frameworks have emerged for handling data workflows. Meanwhile, business use of Machine Learning is less about algorithms and more about leveraging workflows. This talk compares/contrasts different workflow approaches with focus on use cases, plus how some leverage the PMML open standard. Summary points build a scorecard for evaluating frameworks based on your use case needs.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34913", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34913", "speakers": [146540], "categories": [ - + "Computational Thinking" - + ] }, - + { "serial": 34920, "name": "Bringing Banking to the Poor with the Help of AngularJS", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 11:30:00", "time_stop": "2014-07-23 12:10:00", "venue_serial": 1457, "description": "AngularJS is one of the most widely adopted open source Javascript frameworks in recent times. We use it for a not-so-typical use case: web apps to deliver financial services to the poor. In this case-study session, we analyze the pros/cons of AngularJS, establish why it was right for us, and go over our experiences using this powerful lightweight framework which adds value to our community daily.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34920", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34920", "speakers": [173455], "categories": [ - + "Business", - + "JavaScript - HTML5 - Web" - + ] }, - + { "serial": 34921, "name": "Hacking Radio for Under $10", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 16:10:00", "time_stop": "2014-07-22 16:50:00", "venue_serial": 1465, "description": "We'll explore how to use an $8 dollar DVB-T TV dongle to monitor and capture various radio frequencies. The RTL-SDR library turns a cheap Realtek DVB-T into a very powerful Software Defined Radio receiver which can be used to inspect and hack various wireless protocols. All of the code is Free and Open Source so it runs on all platforms.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34921", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34921", "speakers": [77757], "categories": [ - + "Open Hardware" - + ] }, - + { "serial": 34922, "name": "Designing for Reuse: Creating APIs for the Future", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 16:10:00", "time_stop": "2014-07-23 16:50:00", "venue_serial": 1459, "description": "Sometimes your API is meant for a small group and will live for only a short time. Other times, your aim is to create an interface that will have wide appeal and should last years into the future. \r\n\r\nThis talk shows you how to create and maintain an API that it can be both stable and vital well into the future.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34922", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34922", "speakers": [108272], "categories": [ - + "Cloud" - + ] }, - + { "serial": 34941, "name": "LoopBack: Open Source mBaaS", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 11:30:00", "time_stop": "2014-07-22 12:10:00", "venue_serial": 1454, "description": "Parse is a popular mobile Backend-as-a-Service allowing mobile developers to use backend APIs in conjunction with mobile apps. LoopBack is an open source mBaaS implementation that offers all the same functionality, is written in Node.js, and can be extended with Node.js' community of over 50,000 modules.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34941", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34941", "speakers": [173465], "categories": [ - + "Mobile Platforms" - + ] }, - + { "serial": 34942, "name": "Forking Culture and Committing Ops in Government", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 16:10:00", "time_stop": "2014-07-23 16:50:00", "venue_serial": 1464, "description": "Culture shift is a huge challenge in the public sector. I will walk through how the Consumer Financial Protection Bureau is able to successfully open source data, platforms, and standards.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34942", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34942", "speakers": [156591], "categories": [ - + "Community" - + ] }, - + { "serial": 34944, "name": "Open Source Tools for the Polyglot Developer", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 14:30:00", "time_stop": "2014-07-22 15:10:00", "venue_serial": 1449, "description": "Developers, increasingly, need to work in several different development languages. It is hard enough to remember all the bits and pieces of the languages themselves, do you really need to know all the unique toolchains to make them work?", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34944", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34944", "speakers": [141524], "categories": [ - + "Emerging Languages" - + ] }, - + { "serial": 34952, "name": "Writing Documentation that Satisfies Your Users", "event_type": "40-minute conference session", - + "time_start": "2014-07-24 11:50:00", "time_stop": "2014-07-24 12:30:00", "venue_serial": 1462, "description": "Documentation is paramount to increasing an open source project's adoption and growth. But writing good documentation is hard. Using examples from new and mature projects, we'll explore detailed tactics for selecting, prioritizing, outlining, and writing documentation targeted at multiple audiences.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34952", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34952", "speakers": [142111], "categories": [ - + "User Experience" - + ] }, - + { "serial": 34953, "name": "Adventures in the WebRTC Garden--or is it Wilderness?", "event_type": "40-minute conference session", - + "time_start": "2014-07-24 10:00:00", "time_stop": "2014-07-24 10:40:00", "venue_serial": 1454, "description": "The prospects and promise of webRTC--direct browser-to-browser multimedia communications--have led to an explosion of tools, both proprietary and Open Source. In this session we present an overview of a variety of tools vying for attention, along with a demonstration of the sipML Javascript toolkit, using webRTC-enabled browsers and the latest version of Asterisk.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34953", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34953", "speakers": [6921,173464,173466,173467,173468], "categories": [ - + "Emerging Languages" - + ] }, - + { "serial": 34954, "name": "Introduction to Advanced Bash Usage", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 13:40:00", "time_stop": "2014-07-23 14:20:00", "venue_serial": 1475, "description": "Broad introduction to Bash features for users who want to go beyond simple command execution. Covered topics include builtins, keywords, functions, parameters (arguments, variables, arrays, special parameters), parameter expansion and manipulation, compound commands (loops, groups, conditionals), and brace expansion.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34954", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34954", "speakers": [173223], "categories": [ - + "Tools & Techniques" - + ] }, - + { "serial": 34955, "name": "Hacking the Kernel, Hacking Myself", "event_type": "40-minute conference session", - + "time_start": "2014-07-24 11:50:00", "time_stop": "2014-07-24 12:30:00", "venue_serial": 1451, "description": "I was chosen, out of eighteen successful applicants, to be one of four Linux kernel interns through the Gnome Outreach Program for Women. This is the story of my journey from a frustrated retail worker, dreaming of writing code for a living, to a full fledged kernel developer. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34955", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34955", "speakers": [140811], "categories": [ - + "Geek Lifestyle" - + ] }, - + { "serial": 35024, "name": "Nymote: Git Your Own Cloud Here", "event_type": "40-minute conference session", - + "time_start": "2014-07-24 11:00:00", "time_stop": "2014-07-24 11:40:00", "venue_serial": 1459, "description": "If you want to run your own Internet node, it requires gluing together an awful lot of software, and maintaining it. We'll show you a fresh approach: use the Mirage operating system to easily compile the protocols you need (DNS, HTTP, XMPP and IMAP) for your Internet presence into a type-safe unikernel, and deploy the whole thing using just Travis CI and Git directly on the cloud or on ARM.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35024", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35024", "speakers": [109140,159772], "categories": [ - + "Cloud" - + ] }, - + { "serial": 35027, "name": "Git and GitHub Essentials", "event_type": "tutorial", - + "time_start": "2014-07-20 09:00:00", "time_stop": "2014-07-20 12:30:00", "venue_serial": 1450, "description": "Learn everything you need to know from Git and GitHub to be the most effective member of your team, save yourself from any jam, and work with the rest of your team flawlessly.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35027", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35027", "speakers": [152215], "categories": [ - + "Tools & Techniques" - + ] }, - + { "serial": 35038, "name": "A Reactive Game Stack: Using Erlang, Lua and VoltDB to Enable a Non-Sharded Game World ", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 17:00:00", "time_stop": "2014-07-23 17:40:00", "venue_serial": 1475, "description": "Discover why Electronic Arts goes Erlang and hear about a powerful, reactive server architecture that supports a highly concurrent, analyzable and secure simulation stack for gaming. Learn how to easily script composable entities using a server environment purpose-built for event-driven programming, which is scalable under load, resilient and enables evaluation of huge data sets in real-time.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35038", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35038", "speakers": [174072,174073], "categories": [ - + "Databases & Datastores" - + ] }, - + { "serial": 35367, "name": "Apache HTTP Server; SSL from End-to-End", "event_type": "40-minute conference session", - + "time_start": "2014-07-24 11:00:00", "time_stop": "2014-07-24 11:40:00", "venue_serial": 1456, "description": "This presentation covers all aspects of configuring Apache HTTP Server for https/TLS, including ECC, RSA and DH keys and key strength, cipher suites, SSL session caches vs. session tickets, OCSP stapling and TLS virtual hostnames. These elements are integrated to provide perfect forward secrecy and meet modern best practices for both client and proxied connections.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35367", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35367", "speakers": [124630], "categories": [ - + "Security" - + ] }, - + { "serial": 35390, "name": "Building an Open Source Learning Thermostat", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 10:40:00", "time_stop": "2014-07-22 11:20:00", "venue_serial": 1451, "description": "The Nest learning thermostat has won the hearts and minds of consumers everywhere by completely re-thinking how a thermostat works. In this session, we'll explore the Internet of Things by discussing how to build an amazing connected device using open source technology, with Spark's open source thermostat project acting as a case study.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35390", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35390", "speakers": [175040], "categories": [ - + "Open Hardware" - + ] }, - + { "serial": 35406, "name": "Pinto: Hassle-Free Dependency Management for Perl", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 16:10:00", "time_stop": "2014-07-22 16:50:00", "venue_serial": 1451, "description": "Managing CPAN dependencies can be a major frustration for Perl developers. In this session, you'll discover how to easily manage those dependencies by creating a private CPAN repository with Pinto.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35406", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35406", "speakers": [173472], "categories": [ - + "Perl" - + ] }, - + { "serial": 35437, "name": "Open Source and the Enterprise", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 16:10:00", "time_stop": "2014-07-23 16:50:00", "venue_serial": 1457, "description": "How can businesses take the best ideas from the open source community to improve their end product and the happiness of their developers? In this fireside-chat-styled session, Derek Sorkin from GitHub will talk with Tim Tyler about his experiences setting up a community inside Qualcomm that mimics an open source project. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35437", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35437", "speakers": [175353,175354], "categories": [ - + "Business" - + ] }, - + { "serial": 35444, "name": "HTML5/Angular.js/Groovy/Java/MongoDB all together - what could possibly go wrong?", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 17:00:00", "time_stop": "2014-07-23 17:40:00", "venue_serial": 1448, "description": "It seems to have been a common theme amongst startups to create the MVP\r\n(Minimum Viable Product) in a language that facilitates rapid\r\nprototyping (for example Ruby), and then migrate to the JVM when the\r\napplication has proved itself and requires more in terms of stability\r\nand performance.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35444", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35444", "speakers": [132564], "categories": [ - + "JavaScript - HTML5 - Web" - + ] }, - + { "serial": 35455, "name": "CANCELLED: Using Multi-key AVL Trees to Simplify and Speed Up Complex Searches", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 14:30:00", "time_stop": "2014-07-23 15:10:00", "venue_serial": 1457, "description": "Multiple indexes into data structures add complexity and slow down processing. A single multi-keyed AVL tree can allow complex searches to be constructed more easily and performed quickly, with a single O(lg N) lookup.\r\n\r\nIn this talk we will discuss how these trees work and how to implement them. Examples will be shown using python version 3, with C++ libraries for optimization of key routines.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35455", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35455", "speakers": [31044], "categories": [ - + "Computational Thinking" - + ] }, - + { "serial": 35464, "name": "Real-time Engineering at Uber and the Evolution of an Event-Driven Architecture", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 17:00:00", "time_stop": "2014-07-23 17:40:00", "venue_serial": 1452, "description": "Uber is one of the fastest growing companies in the world and the real-time engineering team are responsible for their mission critical Node.js-powered systems. Learn how they are adapting their services to be autonomous, loosely-coupled and highly-available by applying the principles of event-driven architecture.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35464", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35464", "speakers": [175481], "categories": [ - + "Computational Thinking" - + ] }, - + { "serial": 35493, "name": "Coder Decoder: Functional Programmer Lingo Explained, with Pictures", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 10:40:00", "time_stop": "2014-07-22 11:20:00", "venue_serial": 1454, "description": "For the uninitiated, a conversation with functional programmers can feel like ground zero of a jargon explosion. In this talk Lambda Ladies Co-Founder Katie Miller will help you to defend against the blah-blah blast by demystifying several terms commonly used by FP fans with bite-sized Haskell examples and friendly pictures. Expect appearances by Curry, Lens, and the infamous M-word, among others.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35493", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35493", "speakers": [175488], "categories": [ - + "Computational Thinking" - + ] }, - + { "serial": 35511, "name": "Checking Your Privilege: A How-To for Hard Things", "event_type": "Keynote", - + "time_start": "2014-07-23 09:45:00", "time_stop": "2014-07-23 10:00:00", "venue_serial": 1525, "description": "The purpose of this talk is to reexamine the topic through the lens of concrete things individuals can do to check their privilege \u2013 and to put it to work serving themselves and others.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35511", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35511", "speakers": [8837], "categories": [ - + "Keynotes" - + ] }, - + { "serial": 35544, "name": "A Multi-Platform Microsoft: Azure, ASP.NET, Open Source, Git and How We Build Things Now", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 11:30:00", "time_stop": "2014-07-22 12:10:00", "venue_serial": 1448, "description": "A lot has changed at Microsoft. Azure has 1000 Linux VMs to choose from, there's RESTful APIs abound, and more OSS than ever before. What are Microsoft's web folks thinking and how are they developing software today? Is is a good thing?", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35544", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35544", "speakers": [132865], "categories": [ - + "Main Stage" - + ] }, - + { "serial": 35684, "name": "Open Source Your Data Design Process", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 11:30:00", "time_stop": "2014-07-23 12:10:00", "venue_serial": 1448, "description": "Design is a process, not a product. What processes do successful data designers follow, and how can we all benefit by open-sourcing our processes (to make better products)?", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35684", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35684", "speakers": [147840], "categories": [ - + "Main Stage" - + ] }, - + { "serial": 35743, "name": "Anticipating the Future - An Introduction to Value Chain Mapping", "event_type": "Keynote", - + "time_start": "2014-07-23 09:30:00", "time_stop": "2014-07-23 09:45:00", "venue_serial": 1525, "description": "In this keynote, Simon will present the general principles of industry change and describe what can and cannot be predicted. He will then examine how companies can better understand the environment around them and by anticipating the nature of change then manipulate the market in their favor through open techniques.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35743", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35743", "speakers": [6219], "categories": [ - + "Keynotes" - + ] }, - + { "serial": 35744, "name": "Threats", "event_type": "Keynote", - + "time_start": "2014-07-23 09:10:00", "time_stop": "2014-07-23 09:20:00", "venue_serial": 1525, "description": "What do you care about most in the worlds of software, the Net, and Life Online? Are you worried about it? Now is the time for sensible, reasonable, extreme paranoia.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35744", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35744", "speakers": [24978], "categories": [ - + "Keynotes" - + ] }, - + { "serial": 35847, "name": "OSCON Kids Day (Sold Out)", "event_type": "Event", - + "time_start": "2014-07-20 09:00:00", "time_stop": "2014-07-20 17:00:00", "venue_serial": 1587, "description": "If you have a school aged children interested in learning more about computer programming, bring them to OSCON. We'll be hosting an entire day of workshops for kids about Java, Python, Scratch, Minecraft Modding, Arduino and more. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35847", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35847", "speakers": [], "categories": [ - + "Events" - + ] }, - + { "serial": 35848, "name": "Ignite OSCON (sponsored by Orly Atomics)", "event_type": "Event", - + "time_start": "2014-07-20 17:30:00", "time_stop": "2014-07-20 19:00:00", "venue_serial": 1525, "description": "If you had five minutes on stage what would you say? What if you only got 20 slides and they rotated automatically after 15 seconds? Would you pitch a project? Launch a web site? Teach a hack? We\u2019ll find out at our annual Ignite event at OSCON.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35848", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35848", "speakers": [], "categories": [ - + "Events" - + ] }, - + { "serial": 35853, "name": "OSCON 5K Glow Run and After Party", "event_type": "Event", - + "time_start": "2014-07-20 20:30:00", "time_stop": "2014-07-20 22:00:00", "venue_serial": 1606, "description": "Don't forget to pack your running shoes and your glow-in-the-dark gear, because the OSCON 5K fun run is back. Whether you are an avid runner or just starting out, you are invited to join other OSCON attendees Sunday evening for a run/jog/walk through some of the most scenic and emblematic sites of Portland. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35853", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35853", "speakers": [], "categories": [ - + "Events" - + ] }, - + { "serial": 35854, "name": "Expo Hall Opening Reception (sponsored by Bluehost)", "event_type": "Event", - + "time_start": "2014-07-21 17:00:00", "time_stop": "2014-07-21 18:00:00", "venue_serial": 1474, "description": "Grab a drink and kick off OSCON by meeting and mingling with exhibitors and fellow attendees.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35854", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35854", "speakers": [], "categories": [ - + "Events" - + ] }, - + { "serial": 35855, "name": "OSCON Elements Attendee Party", "event_type": "Event", - + "time_start": "2014-07-21 18:00:00", "time_stop": "2014-07-21 20:00:00", "venue_serial": 1585, "description": "This year's attendee party focuses on the four classical elements--fire, earth, air, and water. Wait till you see how each of these essential ideas transforms Hall B into new areas to explore and savor. Trust us, this is one party you don't want to miss! ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35855", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35855", "speakers": [], "categories": [ - + "Events" - + ] }, - + { "serial": 35856, "name": "Puppet Labs Party", "event_type": "Event", - + "time_start": "2014-07-21 20:00:00", "time_stop": "2014-07-21 22:00:00", "venue_serial": 1626, "description": "Join Puppet Labs for our OSCON \u201cOpen\u201d House Party! We are excited to open our doors to all our OSCON and Puppet Labs Friends.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35856", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35856", "speakers": [], "categories": [ - + "Events" - + ] }, - + { "serial": 35857, "name": "Booth Crawl", "event_type": "Event", - + "time_start": "2014-07-22 17:40:00", "time_stop": "2014-07-22 19:00:00", "venue_serial": 1474, "description": "Quench your thirst with vendor-hosted libations and snacks while you check out all the cool stuff in the expo hall. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35857", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35857", "speakers": [], "categories": [ - + "Events" - + ] }, - + { "serial": 35858, "name": "Citrix Open Cloud Poker Party", "event_type": "Event", - + "time_start": "2014-07-22 20:30:00", "time_stop": "2014-07-22 23:30:00", "venue_serial": 1473, "description": "*8:30pm - 12:00am*\r\n\r\nCitrix is sponsoring a night of poker, cocktails and hors d'oeuvres. For one night only, OSCON\u2019s Foyer will be transformed into Portland\u2019s only poker room complete with professional dealers. You'll be playing poker above the city lights with a perfect view of the city.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35858", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35858", "speakers": [], "categories": [ - + "Events" - + ] }, - + { "serial": 35859, "name": "Closing Get Together", "event_type": "Event", - + "time_start": "2014-07-24 13:15:00", "time_stop": "2014-07-24 14:00:00", "venue_serial": 1473, "description": "Take the opportunity to network one last time and exchange contact information with one another. Drinks and snacks provided. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35859", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35859", "speakers": [], "categories": [ - + "Events" - + ] }, - + { "serial": 35864, "name": "Something To Remember", "event_type": "Keynote", - + "time_start": "2014-07-23 09:05:00", "time_stop": "2014-07-23 09:10:00", "venue_serial": 1525, "description": "Keynote by Piers Cawley, Perl programmer, singer and balloon modeller.\r\n", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35864", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35864", "speakers": [75349], "categories": [ - + "Keynotes" - + ] }, - + { "serial": 35883, "name": "Open Source and Social Change \u2014 Making the World a Better Place", "event_type": "Keynote", - + "time_start": "2014-07-24 12:45:00", "time_stop": "2014-07-24 13:10:00", "venue_serial": 1525, "description": "Keynote by Paul Fenwick, managing director of Perl Training Australia.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35883", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35883", "speakers": [6631], "categories": [ - + "Keynotes" - + ] }, - + { "serial": 35913, "name": "Introvert? Extrovert? Klingon? We've Got You Covered.", "event_type": "Keynote", - + "time_start": "2014-07-22 09:30:00", "time_stop": "2014-07-22 09:45:00", "venue_serial": 1525, "description": "Keynote by Wendy Chisholm, Senior Accessibility Strategist and Universal Design Evangelist, Microsoft.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35913", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35913", "speakers": [17417], "categories": [ - + "Keynotes" - + ] }, - + { "serial": 35915, "name": "Opening Welcome and Keynotes", "event_type": "Keynote", - + "time_start": "2014-07-22 09:00:00", "time_stop": "2014-07-22 09:05:00", "venue_serial": 1525, "description": "Opening remarks by OSCON program chairs, Matthew McCullough, Sarah Novotny and Simon St. Laurent. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35915", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35915", "speakers": [74852,76338,3476], "categories": [ - + ] }, - + { "serial": 35950, "name": "Announcements & Keynotes", "event_type": "Keynote", - + "time_start": "2014-07-23 09:00:00", "time_stop": "2014-07-23 09:05:00", "venue_serial": 1525, "description": "Wednesday announcements and remarks by OSCON program chairs, Matthew McCullough, Sarah Novotny and Simon St. Laurent. We'll be announcing more keynote speakers here soon.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35950", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35950", "speakers": [76338,74852,3476], "categories": [ - + ] }, - + { "serial": 35951, "name": "Announcements & Keynotes", "event_type": "Keynote", - + "time_start": "2014-07-24 09:00:00", "time_stop": "2014-07-24 09:05:00", "venue_serial": 1525, "description": "Thursday announcements and remarks by OSCON program chairs, Matthew McCullough, Sarah Novotny and Simon St. Laurent. We'll be announcing more keynote speakers here soon.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35951", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35951", "speakers": [74852,76338,3476], "categories": [ - + ] }, - + { "serial": 35956, "name": "The Wonders of Programming", "event_type": "Keynote", - + "time_start": "2014-07-22 09:05:00", "time_stop": "2014-07-22 09:20:00", "venue_serial": 1525, "description": "Kids can start to learn to program at any age; I started at six. All I needed was tools, guidance, and encouragement. Once I got hooked, a whole new world of possibilities opened up for me. I could create my own video games instead of being restricted by the rules of games made by others. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35956", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35956", "speakers": [177245], "categories": [ - + "Keynotes" - + ] }, - + { "serial": 35988, "name": "Discover OpenUI5 \u2013 The New Web UI library from SAP", "event_type": "Event", - + "time_start": "2014-07-21 09:00:00", "time_stop": "2014-07-21 12:30:00", "venue_serial": 1462, "description": "OpenUI5 is a powerful web UI library from SAP that has recently entered the open source world. With OpenUI5 you can easily develop enterprise-grade responsive web applications that run on multiple platforms. It is based on many open source libraries. Start from scratch and learn how to build OpenUI5 applications in this tutorial.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35988", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/35988", "speakers": [173233,104828,170822], "categories": [ - + "Sponsored Tutorials" - + ] }, - + { "serial": 36005, "name": "Migrating Data from MySQL and Oracle into Hadoop", "event_type": "BoF", - + "time_start": "2014-07-22 20:00:00", "time_stop": "2014-07-22 21:00:00", "venue_serial": 1464, "description": "Open discussion to talk about the methods, tricks and different tools available for copying and replicating data from traditional RDBMS into Hadoop, covering best practices and customer stories.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/36005", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/36005", "speakers": [], "categories": [ - + ] }, - + { "serial": 36014, "name": "Hadoop Get-together", "event_type": "BoF", - + "time_start": "2014-07-22 19:00:00", "time_stop": "2014-07-22 20:00:00", "venue_serial": 1464, "description": "Do you Hadoop? Or at least interested in stories from people who do?\r\nWe will meet to share our experience and trade tips about working with\r\nthe open source data storage and processing framework - Hadoop.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/36014", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/36014", "speakers": [], "categories": [ - + ] }, - + { "serial": 36065, "name": "Docker BoF", "event_type": "BoF", - + "time_start": "2014-07-20 19:00:00", "time_stop": "2014-07-20 20:00:00", "venue_serial": 1607, "description": "Come join some of the team from Docker and get your questions answered and your problems resolved!", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/36065", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/36065", "speakers": [], "categories": [ - + ] }, - + { "serial": 36174, "name": "Distributed Caching", "event_type": "BoF", - + "time_start": "2014-07-22 20:00:00", "time_stop": "2014-07-22 21:00:00", "venue_serial": 1454, "description": "Caching at scale might be troublesome and sometimes require adapting your usage patterns to really take advantage of the employed solutions.\r\nMoving the business logic to the caching subsystem and allowing horizontal scaling might be a key factor to minimise the impact of introducing a caching layer in your infrastructure.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/36174", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/36174", "speakers": [], "categories": [ - + ] }, - + { "serial": 36180, "name": "Hands-on CloudStack Ecosystem Tutorial", "event_type": "Event", - + "time_start": "2014-07-21 13:30:00", "time_stop": "2014-07-21 17:00:00", "venue_serial": 1462, "description": "Building a cloud is one part of the equation. To get work done with a cloud you need a solid ecosystem that goes with it. In this hands-on tutorial we go through some of the tools in the CloudStack ecosystem: Cloudmonkey, Libcloud, Vagrant, Ansible, and Ec2stack. Come ready to learn and use a cloud.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/36180", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/36180", "speakers": [152123], "categories": [ - + "Sponsored Tutorials" - + ] }, - + { "serial": 36202, "name": "Building an API for the Planet with a New Approach to Satellites", "event_type": "Keynote", - + "time_start": "2014-07-22 09:55:00", "time_stop": "2014-07-22 10:10:00", "venue_serial": 1525, "description": "Keynote by Will Marshall, CEO of Planet Labs. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/36202", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/36202", "speakers": [164144], "categories": [ - + "Keynotes" - + ] }, - + { "serial": 36203, "name": "Google Summer of Code BOF", "event_type": "BoF", - + "time_start": "2014-07-20 19:00:00", "time_stop": "2014-07-20 20:00:00", "venue_serial": 1456, "description": "Meetup for students, mentors, and org admins who have participated or are participating in Google Summer of Code. Also come to learn more about Google Summer of Code if you think you might be interested in participating in a future year!", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/36203", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/36203", "speakers": [], "categories": [ - + ] }, - + { "serial": 36257, "name": "Yes, Your Refrigerator Is Trying To Kill You: Bad Actors and the Internet of Things", "event_type": "Keynote", - + "time_start": "2014-07-24 09:20:00", "time_stop": "2014-07-24 09:30:00", "venue_serial": 1525, "description": "As more and more atypical devices are internet enabled, operating system providers need to look at the longer term impacts and plan accordingly. How can CE manufactures keep devices up to date and secure over the lifetime of the device. What does it look like when we fail to plan to do so? How can the open source way solve some of these problems.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/36257", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/36257", "speakers": [178139], "categories": [ - + "Keynotes" - + ] }, - + { "serial": 36404, "name": "ELK Stack BoF", "event_type": "BoF", - + "time_start": "2014-07-23 19:00:00", "time_stop": "2014-07-23 20:00:00", "venue_serial": 1460, "description": "If you use and love the ELK stack (that's Elasticsearch for search & analytics, Logstash for centralized logging & Kibana for beautiful visualizations), join us for an evening of discussion about these three open source tools and how they make developers and sysadmins lives way better. And your business humans, too. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/36404", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/36404", "speakers": [], "categories": [ - + ] }, - + { "serial": 36494, "name": "O'Reilly Open Source Awards", "event_type": "Keynote", - + "time_start": "2014-07-24 12:40:00", "time_stop": "2014-07-24 12:45:00", "venue_serial": 1525, "description": "The 10th Annual O\u2019Reilly Open Source Award winners will be announced. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/36494", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/36494", "speakers": [], "categories": [ - + "Keynotes" - + ] }, - + { "serial": 36818, "name": "The Concert Programmer", "event_type": "Keynote", - + "time_start": "2014-07-24 09:05:00", "time_stop": "2014-07-24 09:20:00", "venue_serial": 1525, "description": "In this presentation Andrew will be live-coding the generative algorithms that will be producing the music that the audience will be listening too. As Andrew is typing he will also attempt to narrate the journey, discussing the various computational and musical choices made along the way. A must see for anyone interested in creative computing.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/36818", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/36818", "speakers": [178738], "categories": [ - + "Keynotes" - + ] }, - + { "serial": 36819, "name": "Programmer, Program, Machine and Environment", "event_type": "40-minute conference session", - + "time_start": "2014-07-24 10:00:00", "time_stop": "2014-07-24 10:40:00", "venue_serial": 1448, "description": "In this talk, Andrew will delve deeper into the technical and philosophical underpinnings of live-coding as an artistic performance practice. Using his own Extempore language as a reference, Andrew will demonstrate, in a very hands on way, how live-coding works in practice, from both an end-user perspective, as well as a systems design perspective. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/36819", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/36819", "speakers": [178738], "categories": [ - + "Main Stage" - + ] }, - + { "serial": 36848, "name": "Mozilla Webmaker ", "event_type": "BoF", - + "time_start": "2014-07-20 19:00:00", "time_stop": "2014-07-20 20:00:00", "venue_serial": 1457, "description": "Webmaker is a collection of innovative tools and curricula for a global community that is teaching the web. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/36848", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/36848", "speakers": [], "categories": [ - + ] }, - + { "serial": 36849, "name": "Designing Projects for Particpation", "event_type": "BoF", - + "time_start": "2014-07-22 19:00:00", "time_stop": "2014-07-22 20:00:00", "venue_serial": 1461, "description": "Designing for Participation was created in order to get Mozilla community members to think about how they can structure the work Mozilla does to better enable contributions from anywhere. This BOF will lead discussion through a workshop designed by Mozilla to help project leaders Design projects for participation", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/36849", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/36849", "speakers": [], "categories": [ - + ] }, - + { "serial": 36890, "name": "Understanding Hypervisor Selection in Apache CloudStack", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 14:30:00", "time_stop": "2014-07-23 15:10:00", "venue_serial": 1460, "description": "Apache CloudStack enables cloud operators to quickly create scalable clouds with support for multiple hypervisors. Choice is wonderful, but also requires an understanding of how hypervisor features integrate with CloudStack. In this session we'll look at the options and provide a template for deployment success.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/36890", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/36890", "speakers": [141574], "categories": [ - + "Sponsored Sessions" - + ] }, - + { "serial": 36923, "name": "OSCON Town Hall", "event_type": "Keynote", - + "time_start": "2014-07-24 11:50:00", "time_stop": "2014-07-24 12:30:00", "venue_serial": 1464, "description": "OSCON belongs to its attendees, and we want to hear what you think of this year\u2019s show. Join the organizers to talk about what you loved and hated about OSCON, and what you\u2019d like to see next year.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/36923", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/36923", "speakers": [10,74852,76338,3476], "categories": [ - + "Keynotes" - + ] }, - + { "serial": 36934, "name": "Open Cloud Day", "event_type": "Event", - + "time_start": "2014-07-21 09:00:00", "time_stop": "2014-07-21 17:00:00", "venue_serial": 1459, "description": "Open Cloud Day at OSCON will cover the latest innovations in public and private clouds, IaaS, and PaaS platforms. You'll learn from industry practitioners from a variety of platforms, who will share their expertise, and provide you with a vision of where open source in the cloud is heading. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/36934", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/36934", "speakers": [], "categories": [ - + "Events", - + "Sponsored Tutorials" - + ] }, - + { "serial": 37002, "name": "Crash Course in Open Source Cloud Computing", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 11:30:00", "time_stop": "2014-07-22 12:10:00", "venue_serial": 1461, "description": "The open source mantra is to release early and release often. That means software velocity can be difficult to keep up with. This discussion will expand on the latest open source software used to deliver and manage cloud computing infrastructure. Topics covered include virtualization (KVM, Xen Project, LXC), orchestration (OpenStack, CloudStack, Eucalyptus), and other complimentary technology.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37002", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37002", "speakers": [6653], "categories": [ - + "Sponsored Sessions" - + ] }, - + { "serial": 37014, "name": "OpenStack Redefining 'Core' Using Community, Tests and Selected Upstream Code", "event_type": "Open Cloud Day", - + "time_start": "2014-07-21 11:30:00", "time_stop": "2014-07-21 12:00:00", "venue_serial": 1584, "description": "Discuss how OpenStack works towards an interoperable open IaaS using a process we call DefCore. We'll review how we're creating a process that is open, collaborative, technically relevant and principles driven.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37014", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37014", "speakers": [122917], "categories": [ - + "Cloud" - + ] }, - + { "serial": 37016, "name": "The quantitative state of the Open Cloud", "event_type": "Open Cloud Day", - + "time_start": "2014-07-21 15:45:00", "time_stop": "2014-07-21 16:15:00", "venue_serial": 1584, "description": "The talk will present a quantitative analysis of the projects producing the main free, open source software cloud platforms: OpenStack, Apache CloudStack, OpenNebula and Eucalyptus. The analysis will focus on the communities behind those projects, their main development parameters, and the trends that can be observed.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37016", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37016", "speakers": [173116], "categories": [ - + "Cloud" - + ] }, - + { "serial": 37020, "name": "Building an Open Cloud From the Network Perspective ", "event_type": "Open Cloud Day", - + "time_start": "2014-07-21 16:15:00", "time_stop": "2014-07-21 16:45:00", "venue_serial": 1584, "description": "When designing and building a private cloud often times the network is not treated as a first class citizen and even sometimes dealt with as an after thought. This presentation will look at the process of building a cloud from the network view. We will look at some best practices for configuring servers and switches in a rack as the basis for the cloud deployment and on going management. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37020", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37020", "speakers": [122424], "categories": [ - + "Cloud" - + ] }, - + { "serial": 37021, "name": "Behind the Scenes: How We Produce OpenStack", "event_type": "Open Cloud Day", - + "time_start": "2014-07-21 14:00:00", "time_stop": "2014-07-21 14:30:00", "venue_serial": 1584, "description": "The last OpenStack release was the result of the combined work of more than 1200 contributors. How can all those people be coordinated and deliver results on schedule, with no classic management structure ?", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37021", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37021", "speakers": [109289], "categories": [ - + "Cloud" - + ] }, - + { "serial": 37030, "name": "Open Source in Enterprise Software", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 10:40:00", "time_stop": "2014-07-22 11:20:00", "venue_serial": 1461, "description": "This session is aimed at providing an understanding of why, where, and how SAP is engaged in adopting and leading Open Source projects in the enterprise software space. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37030", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37030", "speakers": [180019], "categories": [ - + "Sponsored Sessions" - + ] }, - + { "serial": 37039, "name": "Office Hour with Gwen Shapira (Cloudera) ", "event_type": "Office Hours", - + "time_start": "2014-07-23 15:20:00", "time_stop": "2014-07-23 16:00:00", "venue_serial": 1546, "description": "Gwen\u2019s got answers when it comes to Hadoop, R, analytics and more. She\u2019s happy to talk to you about getting started with Hadoop; R, Python and data analysis with Hadoop; architecture, design, and implementation of Hadoop applications; and anything else you\u2019d like to bring up.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37039", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37039", "speakers": [126882], "categories": [ - + "Office Hours" - + ] }, - + { "serial": 37042, "name": "Office Hour with Jono Bacon (XPRIZE Foundation)", "event_type": "Office Hours", - + "time_start": "2014-07-22 10:40:00", "time_stop": "2014-07-22 11:20:00", "venue_serial": 1546, "description": "If you have questions about community management and leadership, hiring community managers, or managing community relationships, come by and talk to Jono. He\u2019ll discuss building collaborative workflow and tooling, conflict resolution and managing complex personalities, and building buzz and excitement around your community.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37042", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37042", "speakers": [108813], "categories": [ - + "Office Hours" - + ] }, - + { "serial": 37043, "name": "Office Hour with Stephen OSullivan (Silicon Valley Data Science) ", "event_type": "Office Hours", - + "time_start": "2014-07-23 15:20:00", "time_stop": "2014-07-23 16:00:00", "venue_serial": 1547, "description": "Come by and talk to Stephen about anything related to big data, Hadoop, and creating scalable, high-availability, data and applications solutions\u2014including data pipelines, data platforms, and Fourier analysis. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37043", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37043", "speakers": [133624], "categories": [ - + "Office Hours" - + ] }, - + { "serial": 37044, "name": "Office Hour with Garth Braithwaite (Adobe)", "event_type": "Office Hours", - + "time_start": "2014-07-23 13:40:00", "time_stop": "2014-07-23 14:20:00", "venue_serial": 1546, "description": "Garth has found that the topic of open source is a bit controversial when people start talk about applying it to the visual and experience design process. He\u2019d like to talk with open source contributors about how the design process could be worked into specific projects, and the proposal (as well its rebuttals) that design should be done in the open.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37044", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37044", "speakers": [173248], "categories": [ - + "Office Hours" - + ] }, - + { "serial": 37045, "name": "Office Hour with Constantine Aaron Cois (Carnegie Mellon University, Software Engineering Institute) ", "event_type": "Office Hours", - + "time_start": "2014-07-22 11:30:00", "time_stop": "2014-07-22 12:10:00", "venue_serial": 1546, "description": "Constantine will be more than happy to discuss Node.js or anything else related to programming, software architecture, web apps, and other topics. Let\u2019s have some fun! Dive into Node.js programming and design patterns, event loops and high concurrency apps, reactive apps, and the Meteor framework.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37045", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37045", "speakers": [141235], "categories": [ - + "Office Hours" - + ] }, - + { "serial": 37046, "name": "Office Hour with Harrison Mebane (Silicon Valley Data Science) and Stephen OSullivan (Silicon Valley Data Science)", "event_type": "Office Hours", - + "time_start": "2014-07-23 16:10:00", "time_stop": "2014-07-23 16:50:00", "venue_serial": 1546, "description": "Can computers tell if trains run on time? Harrison and Stephen are ready to discuss this topic and other matters related to the Internet of Things and data\u2014including data pipelines, data platforms, and Fourier analysis.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37046", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37046", "speakers": [171621,133624], "categories": [ - + "Office Hours" - + ] }, - + { "serial": 37047, "name": "Office Hour with J\u00e9r\u00f4me Petazzoni (Docker Inc.) ", "event_type": "Office Hours", - + "time_start": "2014-07-23 10:40:00", "time_stop": "2014-07-23 11:20:00", "venue_serial": 1546, "description": "Jerome has worked with Docker since its inception, and before that, built the dotCloud PAAS. He\u2019ll be glad to share his experiences in those domains, and discuss everything about Docker and containers, including how to make containers secure; service discovery, network integration, scaling, and failover; and containers for desktop applications.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37047", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37047", "speakers": [151611], "categories": [ - + "Office Hours" - + ] }, - + { "serial": 37048, "name": "Office Hour with Heather VanCura (Java Community Process JCP) ", "event_type": "Office Hours", - + "time_start": "2014-07-22 14:30:00", "time_stop": "2014-07-22 15:10:00", "venue_serial": 1546, "description": "The Adopt-a-JSR and Adopt OpenJDK programs have gained worldwide community participation in the past two years. Come discuss the details with Heather and find out how you can contribute to better, more practical standards. She\u2019ll talk about upcoming changes aimed at broadening participation in the standards process. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37048", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37048", "speakers": [65329], "categories": [ - + "Office Hours" - + ] }, - + { "serial": 37049, "name": "Office Hour with Kara Sowles (Puppet Labs) ", "event_type": "Office Hours", - + "time_start": "2014-07-22 15:20:00", "time_stop": "2014-07-22 16:00:00", "venue_serial": 1546, "description": "Kara is always eager to swap user group tips with you. Come by and pick her brain about ways to remove common obstacles that user groups encounter, and how to craft welcoming, friendly meetings and events. Find out how to plan the right events for your community.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37049", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37049", "speakers": [142767], "categories": [ - + "Office Hours" - + ] }, - + { "serial": 37051, "name": "Office Hour with Michael Bleigh (Divshot) ", "event_type": "Office Hours", - + "time_start": "2014-07-22 13:40:00", "time_stop": "2014-07-22 14:20:00", "venue_serial": 1546, "description": "Come by and talk to Michael about single-page web applications, static site generators, and static content hosting. He\u2019s also willing to tackle other issues such as CORS web services, browser technology, and web components.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37051", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37051", "speakers": [2593], "categories": [ - + "Office Hours" - + ] }, - + { "serial": 37052, "name": "Office Hour with Michael Hunger (Neo Technology) ", "event_type": "Office Hours", - + "time_start": "2014-07-22 10:40:00", "time_stop": "2014-07-22 11:20:00", "venue_serial": 1547, "description": "Graph Databases take a different approach than relational and aggregate-oriented NoSQL databases. They make connections cheap and fast by making them an explicit part and first-level citizen of your database. Michael will discuss graph-enabled applications, answer questions about their pros and cons, and help you model your domain and use-cases in an interactive way. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37052", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37052", "speakers": [159719], "categories": [ - + "Office Hours" - + ] }, - + { "serial": 37053, "name": "Office Hour with James Turnbull (Docker) ", "event_type": "Office Hours", - + "time_start": "2014-07-22 10:40:00", "time_stop": "2014-07-22 11:20:00", "venue_serial": 1548, "description": "If you\u2019re ready to learn about Docker for building, shipping, and running distributed applications, James is ready to talk Docker with you. You\u2019ll not only find out how to get started, but also learn about Docker use cases, the Docker roadmap, and how to integrate Docker with Puppet, Chef, SaltStack, or Ansible.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37053", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37053", "speakers": [5060], "categories": [ - + "Office Hours" - + ] }, - + { "serial": 37054, "name": "Office Hour with Catherine Farman (Happy Cog) and Corinne Warnshuis (Girl Develop It Philadelphia) ", "event_type": "Office Hours", - + "time_start": "2014-07-23 11:30:00", "time_stop": "2014-07-23 12:10:00", "venue_serial": 1546, "description": "You want to get more women involved in open source projects and communities. And Catherine and Corinne want to help. Come by find out how you can organize communities that are welcoming to women, and how you can be an ally to underrepresented minorities in tech. Start or help a Girl Develop It chapter, and learn about other nonprofits you can and should partner with both nationally and locally.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37054", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37054", "speakers": [169992,173025], "categories": [ - + "Office Hours" - + ] }, - + { "serial": 37055, "name": "Open Cloud Standards In The Real World", "event_type": "Open Cloud Day", - + "time_start": "2014-07-21 15:15:00", "time_stop": "2014-07-21 15:45:00", "venue_serial": 1584, "description": "In the range of API and protocol development from free-form to totally static, there is room in open clouds for patterns that allow both for dynamic discovery and predictable reuse. This talk highlights open cloud standards-based methods that have stood up to testing in the National Science Foundation's Cloud and Autonomic Computing Center Standards Testing Lab under real-world conditions.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37055", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37055", "speakers": [180050], "categories": [ - + "Cloud" - + ] }, - + { "serial": 37056, "name": "Office Hour with Shadaj Laddad (School) ", "event_type": "Office Hours", - + "time_start": "2014-07-22 10:40:00", "time_stop": "2014-07-22 11:20:00", "venue_serial": 1579, "description": "Shadaj is a 14 year old student who loves to program. He\u2019s happy to chat about anything from Scala programming to technology in schools today. He\u2019ll answer your questions about kids programming, including how to get started and what tools are available. And he\u2019ll engage you on topics such bioinformatics algorithms, Android, web development, and game programming.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37056", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37056", "speakers": [177245], "categories": [ - + "Office Hours" - + ] }, - + { "serial": 37058, "name": "Open Is As Open Does", "event_type": "Open Cloud Day", - + "time_start": "2014-07-21 13:30:00", "time_stop": "2014-07-21 14:00:00", "venue_serial": 1584, "description": "'The Cloud' was built on Open Source. In no way does anything resembling IaaS 'cloud' exist without the foundation of freely available open source operating systems and virtualization, but what does 'Open' mean to the user of a service? Does it matter if the code behind a service is open? How would one even know? Do other aspects and definitions of openness become more important than source code?", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37058", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37058", "speakers": [24052], "categories": [ - + "Cloud" - + ] }, - + { "serial": 37059, "name": "Office Hour with Harry Percival (Harry Percival) ", "event_type": "Office Hours", - + "time_start": "2014-07-22 11:30:00", "time_stop": "2014-07-22 12:10:00", "venue_serial": 1547, "description": "Whether the testing goat is steering you right or wrong, you\u2019ll want to join Harry for an informal discussion of testing and TDD. Find out how to get started with testing and how to get the most value from it. Learn the relative merits of pure isolated unit tests, integrated tests, and functional tests. And explore how to adapt your approach for different project types.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37059", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37059", "speakers": [180056], "categories": [ - + "Office Hours" - + ] }, - + { "serial": 37061, "name": "Office Hour with Florian Haas (hastexo) ", "event_type": "Office Hours", - + "time_start": "2014-07-22 16:10:00", "time_stop": "2014-07-22 16:50:00", "venue_serial": 1546, "description": "Tap Florian\u2019s brain about all things OpenStack, and take advantage of his broad experience in real-life production OpenStack deployments. Whether it\u2019s OpenStack strategy, organizational concerns, or getting down into the nitty-gritty aspects of OpenStack technology, Florian is happy to talk to you about it. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37061", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37061", "speakers": [131884], "categories": [ - + "Office Hours" - + ] }, - + { "serial": 37063, "name": "Office Hour with A. Jesse Jiryu Davis (MongoDB) ", "event_type": "Office Hours", - + "time_start": "2014-07-23 16:10:00", "time_stop": "2014-07-23 16:50:00", "venue_serial": 1547, "description": "Come by and have an informal chat with Jesse about MongoDB and Python-related topics, such as Python\u2019s async frameworks, using MongoDB with Python, and MongoDB in general.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37063", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37063", "speakers": [172536], "categories": [ - + "Office Hours" - + ] }, - + { "serial": 37066, "name": "Office Hour with Matt Stine (Pivotal) ", "event_type": "Office Hours", - + "time_start": "2014-07-23 14:30:00", "time_stop": "2014-07-23 15:10:00", "venue_serial": 1546, "description": "Why would a Java developer want to learn the Go programming language? Matt is ready to have an informal chat with you about it. Find out how the idiomatic use of Go interfaces encourage you to structure your programs differently (as opposed to Java\u2019s object-oriented constructs), and how Go\u2019s built-in concurrency features make writing concurrent software accessible to mere mortals. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37066", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37066", "speakers": [173088], "categories": [ - + "Office Hours" - + ] }, - + { "serial": 37067, "name": "Office Hour with Anne Ogborn (Robokind) ", "event_type": "Office Hours", - + "time_start": "2014-07-23 10:40:00", "time_stop": "2014-07-23 11:20:00", "venue_serial": 1547, "description": "Ann is ready to talk SWI-Prolog with you\u2014and help you get it installed. She\u2019ll chat with you about your planned uses for Prolog, talk you out of the \u201cwrap my rules engine in a real language\u201d mentality, and help you get through the \u201chey, 2+2 = 4 fails, wtf?\u201d stage. And she\u2019ll laugh evilly when you mention Tomcat.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37067", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37067", "speakers": [172986], "categories": [ - + "Office Hours" - + ] }, - + { "serial": 37068, "name": "Office Hour with Matt Ray (Chef Software, Inc.) ", "event_type": "Office Hours", - + "time_start": "2014-07-23 16:10:00", "time_stop": "2014-07-23 16:50:00", "venue_serial": 1548, "description": "Chef questions? Bring any and all directly to Matt. He\u2019s ready to discuss how Chef is used to manage the deployment of OpenStack as well as the infrastructure on top of OpenStack, and how to get involved with the Chef and OpenStack community. Beyond that, the sky\u2019s the limit.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37068", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37068", "speakers": [141169], "categories": [ - + "Office Hours" - + ] }, - + { "serial": 37069, "name": "Office Hour with Phil Webb (Pivotal) ", "event_type": "Office Hours", - + "time_start": "2014-07-22 13:40:00", "time_stop": "2014-07-22 14:20:00", "venue_serial": 1547, "description": "If there\u2019s anything you want to know about Spring Boot or the Spring Framework in general, Phil is ready to help. He\u2019ll talk to you about an array of issues, such as developing micro-services with Spring, how Pivotal developed the new \u201cspring.io\u201d website, and how to become a full-stack Java developer.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37069", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37069", "speakers": [171565], "categories": [ - + "Office Hours" - + ] }, - + { "serial": 37071, "name": "Office Hour with Jamie Allen (Typesafe) ", "event_type": "Office Hours", - + "time_start": "2014-07-23 11:30:00", "time_stop": "2014-07-23 12:10:00", "venue_serial": 1547, "description": "Reactive applications are taking over the enterprise world. Join Jamie to chat about various Reactive topics. He\u2019ll answer questions including: How well do various technologies support the core Reactive tenets of event-driven, scalable, fault tolerant and responsive? How do you use open source technologies to deploy an elastically Reactive application? What is the role of big data in Reactive?", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37071", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37071", "speakers": [170293], "categories": [ - + "Office Hours" - + ] }, - + { "serial": 37073, "name": "Office Hour with Scott Murray (University of San Francisco)", "event_type": "Office Hours", - + "time_start": "2014-07-23 14:30:00", "time_stop": "2014-07-23 15:10:00", "venue_serial": 1580, "description": "Scott\u2019s here to answer your questions about data and how to successfully conveying meaning through well-designed graphics. He\u2019ll chat with you about the data visualization design process; related technologies, including d3.js and Processing; and how we can all benefit by open-sourcing our processes. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37073", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37073", "speakers": [147840], "categories": [ - + "Office Hours" - + ] }, - + { "serial": 37075, "name": "Office Hour with Francesc Campoy (Google Inc.) ", "event_type": "Office Hours", - + "time_start": "2014-07-23 15:20:00", "time_stop": "2014-07-23 16:00:00", "venue_serial": 1548, "description": "If you\u2019ve got questions about Go, you\u2019re in luck. Francesc will answer questions about Go basics, programming, organizing your code, and more. Find out about Go concurrency and concurrency primitives, and how Go interfaces simplify your code and break fictitious dependencies.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37075", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37075", "speakers": [155088], "categories": [ - + "Office Hours" - + ] }, - + { "serial": 37077, "name": "Office Hour with Kirsten Hunter (Akamai)", "event_type": "Office Hours", - + "time_start": "2014-07-23 14:30:00", "time_stop": "2014-07-23 15:10:00", "venue_serial": 1547, "description": "If you want your APIs to amaze and delight your developer partners, bring Kristen all of your API design questions. She\u2019ll chat with you about the API design process (how to plan and create an API that will be successful), developer engagement and support (aka the care and feeding of your developer partners), and how you can help your customers learn and troubleshoot your API.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37077", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37077", "speakers": [4265], "categories": [ - + "Office Hours" - + ] }, - + { "serial": 37078, "name": "Office Hour with Benjamin Kerensa (Mozilla) ", "event_type": "Office Hours", - + "time_start": "2014-07-23 10:40:00", "time_stop": "2014-07-23 11:20:00", "venue_serial": 1548, "description": "If you\u2019ve ever toyed with the idea of contributing to Firefox, Benjamin would love to talk to you about ways to make it happen. Find out why Firefox OS matters, how Mozilla builds community, and how you can contribute to Firefox.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37078", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37078", "speakers": [120025], "categories": [ - + "Office Hours" - + ] }, - + { "serial": 37085, "name": "Morning Yoga", "event_type": "Event", - + "time_start": "2014-07-22 07:30:00", "time_stop": "2014-07-22 08:15:00", "venue_serial": 1574, "description": "Programmers do a lot of sitting, so come refresh your body, mind, and spirit before you head into the day\u2019s sessions. This will be an easy beginner\u2019s yoga session \u2013 so don\u2019t be shy about coming out even if this will be your first yoga experience. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37085", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37085", "speakers": [], "categories": [ - + "Events" - + ] }, - + { "serial": 37086, "name": "Morning Yoga", "event_type": "Event", - + "time_start": "2014-07-23 07:30:00", "time_stop": "2014-07-23 08:15:00", "venue_serial": 1574, "description": "Programmers do a lot of sitting, so come refresh your body, mind, and spirit before you head into the day\u2019s sessions. This will be an easy beginner\u2019s yoga session \u2013 so don\u2019t be shy about coming out even if this will be your first yoga experience. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37086", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37086", "speakers": [], "categories": [ - + "Events" - + ] }, - + { "serial": 37087, "name": "Office Hour with Emma Jane Westby (Freelance)", "event_type": "Office Hours", - + "time_start": "2014-07-22 11:30:00", "time_stop": "2014-07-22 12:10:00", "venue_serial": 1548, "description": "Git can be frustrating and opaque. Don\u2019t worry, it\u2019s not you. It\u2019s Git. Come ask questions about making Git work for you instead of feeling stuck in a detached HEAD state. Emma Jane will talk about upgrading from a centralized system to a distributed workflow and improving team efficiencies with the right branching strategy. She\u2019ll also provide tips for teaching anyone how to use Git.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37087", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37087", "speakers": [4146], "categories": [ - + "Office Hours" - + ] }, - + { "serial": 37090, "name": "Office Hour with Ken Walker (IBM Canada) ", "event_type": "Office Hours", - + "time_start": "2014-07-22 13:40:00", "time_stop": "2014-07-22 14:20:00", "venue_serial": 1548, "description": "Need some advice on your next great open source project? Ken can steer you in the right direction. Talk to him about the benefits of the Eclipse Foundation as a home for your project, and discuss what prevents you from trying out cloud IDEs. Ken will also point out which open source components from Orion\u2019s JavaScript libraries you can leverage in your own project. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37090", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37090", "speakers": [39928], "categories": [ - + "Office Hours" - + ] }, - + { "serial": 37091, "name": "Office Hour with John Griffith (SolidFire) ", "event_type": "Office Hours", - + "time_start": "2014-07-23 10:40:00", "time_stop": "2014-07-23 11:20:00", "venue_serial": 1579, "description": "If you\u2019re looking for answers about OpenStack, John is ready to field your questions. He\u2019ll chat with you about block storage in OpenStack with Cinder, core OpenStack features and functionality, and general queries about OpenStack in general.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37091", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37091", "speakers": [171381], "categories": [ - + "Office Hours" - + ] }, - + { "serial": 37095, "name": "Office Hour with Damian Conway (Thoughtstream) ", "event_type": "Office Hours", - + "time_start": "2014-07-23 11:30:00", "time_stop": "2014-07-23 12:10:00", "venue_serial": 1548, "description": "The professor is in. Perl\u2019s own Dr. Evil emerges from his secret lair to talk about Perl 6, Perl 5, Vim, and presentation skills. Damian knows a lot about all of the above as the widely sought-after speaker who runs Thoughtstream, an international provider of programmer training. He devotes much of his spare time to Larry Wall and the design of Perl 6.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37095", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37095", "speakers": [4710], "categories": [ - + "Office Hours" - + ] }, - + { "serial": 37097, "name": "Office Hour with Adam Culp (Zend Technologies)", "event_type": "Office Hours", - + "time_start": "2014-07-23 13:40:00", "time_stop": "2014-07-23 14:20:00", "venue_serial": 1579, "description": "Organizer of the Sunshine PHP Developer Conference, Adam is ready to hold an open discussion on efficient code refactoring. Find out when to refactor, the key indicators for triggering a refactor, how to get started with code refactoring, and anything else you think is related.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37097", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37097", "speakers": [169862], "categories": [ - + "Office Hours" - + ] }, - + { "serial": 37098, "name": "Office Hour with Paco Nathan (Liber 118)", "event_type": "Office Hours", - + "time_start": "2014-07-22 14:30:00", "time_stop": "2014-07-22 15:10:00", "venue_serial": 1547, "description": ".", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37098", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37098", "speakers": [146540], "categories": [ - + "Office Hours" - + ] }, - + { "serial": 37099, "name": "Office Hour with Luciano Ramalho (Python.pro.br) ", "event_type": "Office Hours", - + "time_start": "2014-07-22 14:30:00", "time_stop": "2014-07-22 15:10:00", "venue_serial": 1548, "description": "Luciano is busy at OSCON with two sessions and a tutorial, but he\u2019s happy to answer any questions you may have about those talks: \u201cIntroduction to Python Metaprogramming\u201d, \u201cIdiomatic APIs with the Python Data Model\u201d, and \u201cPython: Encapsulation with Descriptors\u201d. The common theme for all three is how to use the special methods defined in the Python Data Model. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37099", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37099", "speakers": [150170], "categories": [ - + "Office Hours" - + ] }, - + { "serial": 37101, "name": "Office Hour with Jess Portnoy (Kaltura Inc) ", "event_type": "Office Hours", - + "time_start": "2014-07-22 11:30:00", "time_stop": "2014-07-22 12:10:00", "venue_serial": 1579, "description": "Join Jess to talk about useful debugging tools, PHP-based apps, and some of her other favorite open source topics. She\u2019ll hold forth on managing open source projects as a commercial company, deploying and maintaining large-scale video applications in the cloud, and open source development in general.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37101", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37101", "speakers": [171078], "categories": [ - + "Office Hours" - + ] }, - + { "serial": 37105, "name": "Office Hour with Michael Minella (Pivotal) ", "event_type": "Office Hours", - + "time_start": "2014-07-23 13:40:00", "time_stop": "2014-07-23 14:20:00", "venue_serial": 1547, "description": ".", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37105", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37105", "speakers": [171147], "categories": [ - + "Office Hours" - + ] }, - + { "serial": 37108, "name": "Office Hour with Wade Minter (TeamSnap) and Andrew Berkowitz (TeamSnap)", "event_type": "Office Hours", - + "time_start": "2014-07-22 14:30:00", "time_stop": "2014-07-22 15:10:00", "venue_serial": 1579, "description": "If you have questions about using applied improvisation for better communication, idea generation, and decision making in your company or team, come to Wade and Andrew. They\u2019ll talk to you about growing a distributed engineering team, and building and maintaining culture in a distributed organization. They\u2019ll also share tools and tips for improving communication.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37108", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37108", "speakers": [4378,106355], "categories": [ - + "Office Hours" - + ] }, - + { "serial": 37114, "name": "Office Hour with Tim Berglund (DataStax) ", "event_type": "Office Hours", - + "time_start": "2014-07-22 13:40:00", "time_stop": "2014-07-22 14:20:00", "venue_serial": 1579, "description": "Do you have questions on Apache Cassandra? Bring any and all of them to Tim during Office Hours. He\u2019s also ready to chat about technical training, the technologies formerly known as Big Data, and any other topics of mutual interest.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37114", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37114", "speakers": [137697], "categories": [ - + "Office Hours" - + ] }, - + { "serial": 37122, "name": "Office Hour with Joshua Marinacci (Nokia) ", "event_type": "Office Hours", - + "time_start": "2014-07-23 13:40:00", "time_stop": "2014-07-23 14:20:00", "venue_serial": 1548, "description": "Joshua is available to talk to you about any of his favorite topics, such as HTML Canvas, Bluetooth Low Energy, wearable computing, and the Internet of Things. And he\u2019s happy just to rant about Java.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37122", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37122", "speakers": [6931], "categories": [ - + "Office Hours" - + ] }, - + { "serial": 37129, "name": "Office Hour with Zach Supalla (Spark Labs) ", "event_type": "Office Hours", - + "time_start": "2014-07-22 15:20:00", "time_stop": "2014-07-22 16:00:00", "venue_serial": 1547, "description": "Now that it is possible for startups to build products as powerful and game changing as Nest\u2014without millions of dollars\u2014how can you get in on the action? Zach Supalla from the Spark Labs startup accelerator will share details and answer questions about developing an IoT product, building a business on open source hardware, and growing from a Kickstarter campaign to a business.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37129", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37129", "speakers": [175040], "categories": [ - + "Office Hours" - + ] }, - + { "serial": 37134, "name": "Open Health Data/Content", "event_type": "BoF", - + "time_start": "2014-07-20 19:00:00", "time_stop": "2014-07-20 20:00:00", "venue_serial": 1458, "description": "Explore thoughts and ideas around public health and open data.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37134", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37134", "speakers": [], "categories": [ - + ] }, - + { "serial": 37144, "name": "Office Hour with Josh Berkus (PostgreSQL Experts, Inc.) ", "event_type": "Office Hours", - + "time_start": "2014-07-22 15:20:00", "time_stop": "2014-07-22 16:00:00", "venue_serial": 1548, "description": "Josh Berkus is happy to chat with you about PostgreSQL high-availability and scale-out techniques, especially on cloud hosting. He\u2019ll discuss things like connection pooling and load-balancing tools, automated failover, RDS vs. roll-your-own on AWS, and scaling out multi-tenant apps.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37144", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37144", "speakers": [3397], "categories": [ - + "Office Hours" - + ] }, - + { "serial": 37148, "name": "Modernizing CS Education with Open Source", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 13:40:00", "time_stop": "2014-07-23 14:20:00", "venue_serial": 1450, "description": "Scott Chacon, co-founder of GitHub, and Jay Borenstein, CS professor at Stanford and founder of Facebook's Open Academy, a program designed to match university students with open source projects for academic credit, will discuss how to bring the best of the open source community's learning frameworks into formal computer science education. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37148", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37148", "speakers": [837,180268], "categories": [ - + "Education" - + ] }, - + { "serial": 37153, "name": "Mandrill Party", "event_type": "Event", - + "time_start": "2014-07-23 20:30:00", "time_stop": "2014-07-23 22:00:00", "venue_serial": 1578, "description": "Join Mandrill for an OSCON Party at the Jupiter Hotel!", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37153", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37153", "speakers": [], "categories": [ - + "Events" - + ] }, - + { "serial": 37173, "name": "Office Hour with Andrei Alexandrescu (Facebook) ", "event_type": "Office Hours", - + "time_start": "2014-07-22 16:10:00", "time_stop": "2014-07-22 16:50:00", "venue_serial": 1547, "description": "Andrei will be happy to answer questions about the \u201cmove fast\u201d side of Facebook, including large-scale design, the D programming language, and Facebook\u2019s software engineering culture.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37173", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37173", "speakers": [109270], "categories": [ - + "Office Hours" - + ] }, - + { "serial": 37201, "name": "Office Hour with Brian Troutwine (AdRoll) ", "event_type": "Office Hours", - + "time_start": "2014-07-23 14:30:00", "time_stop": "2014-07-23 15:10:00", "venue_serial": 1579, "description": "In his talk (\u201cErlang, LFE, Joxa and Elixir: Established and Emerging Languages in the Erlang Ecosystem\u201d), Brian talks about the differences between these BEAM languages, and how each one is applicable to a distinct engineering task. During Office Hours, he\u2019ll tell you how to get started with each of these languages, and talk about the domains in which they succeed and fail.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37201", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37201", "speakers": [172990], "categories": [ - + "Office Hours" - + ] }, - + { "serial": 37253, "name": "Juju BoF", "event_type": "BoF", - + "time_start": "2014-07-22 19:00:00", "time_stop": "2014-07-22 20:00:00", "venue_serial": 1456, "description": "Deploying workloads in the cloud can be challenging, in this BoF we'll discuss how to use Ubuntu and Juju to get up and running in the cloud quickly ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37253", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37253", "speakers": [], "categories": [ - + ] }, - + { "serial": 37256, "name": "Office Hour with Michael Enescu (Cisco) ", "event_type": "Office Hours", - + "time_start": "2014-07-23 15:20:00", "time_stop": "2014-07-23 16:00:00", "venue_serial": 1579, "description": "Now that you\u2019re familiar with cloud computing, along comes the next new thing: Fog computing. Michael will tell you what exactly Fog computing is, how it evolved from cloud compute, and where it\u2019s going. He\u2019ll also talk about network challenges in cloud computing with the Internet of Things, as well as IoT open source projects.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37256", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37256", "speakers": [172370], "categories": [ - + "Office Hours" - + ] }, - + { "serial": 37259, "name": "Modernizing your Cloud with Software Collections", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 14:30:00", "time_stop": "2014-07-23 15:10:00", "venue_serial": 1461, "description": "Software Collections is a new way to run newer packages on Enterprise Linux (RHEL/CentOS) such as Python, Ruby, PHP, MySQL/MariaDB, and others. Learn how this enables us to use Perl 5.16 and PostgreSQL 9.2 alongside distribution-provided versions (on EL6, Perl 5.10, and PostgreSQL 8.4.). Also learn how we've extended the Perl 5.16 collection with additional packages.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37259", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37259", "speakers": [180437], "categories": [ - + "Sponsored Sessions" - + ] }, - + { "serial": 37292, "name": "Office Hour with Deb Nicholson (Open Invention Network)", "event_type": "Office Hours", - + "time_start": "2014-07-22 16:10:00", "time_stop": "2014-07-22 16:50:00", "venue_serial": 1580, "description": "Wondering how patents and copyright and trademark laws work with free and open source software projects? Deb is ready to share her knowledge. Find out if legislation and recent court decisions about patent trolls will keep you from being sued, and how the landscape likely to change. None of this is legal advice, just good solid background! ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37292", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37292", "speakers": [130731], "categories": [ - + "Office Hours" - + ] }, - + { "serial": 37306, "name": "Office Hour with Gian Merlino (Metamarkets) and Fangjin Yang (Metamarkets)", "event_type": "Office Hours", - + "time_start": "2014-07-22 16:10:00", "time_stop": "2014-07-22 16:50:00", "venue_serial": 1548, "description": "Gian and Fangjin will talk to you about real-time analytics with open source technologies, including the motivation for the Kafka, Storm, Hadoop, and Druid real-time analytics stack. They\u2019ll discuss implementation details (if you\u2019re interested in trying the stack out at home) and show you how to scale the stack and make it highly available. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37306", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37306", "speakers": [153566,153565], "categories": [ - + "Office Hours" - + ] }, - + { "serial": 37310, "name": "acilos, your Personal Social Valet", "event_type": "BoF", - + "time_start": "2014-07-22 19:00:00", "time_stop": "2014-07-22 20:00:00", "venue_serial": 1465, "description": "In this session, you will discover a new Open Source Personal Social Media Aggregation system called acilos. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37310", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37310", "speakers": [], "categories": [ - + ] }, - + { "serial": 37311, "name": "Open Standards vs. Open Source", "event_type": "BoF", - + "time_start": "2014-07-22 19:00:00", "time_stop": "2014-07-22 20:00:00", "venue_serial": 1463, "description": "There is a debate on the relevance of Open Standards when faced with Open Source efforts. Open Source is an industry force, yet government bodies and others still rely on and give preference to ANSI and ISO standards. Standards representatives from across the industry will be on hand to discuss possible paths forward that are complementary rather than competitive.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37311", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37311", "speakers": [], "categories": [ - + ] }, - + { "serial": 37316, "name": "Office Hour with Spencer Krum (HP) and William Van Hevelingen (Portland State University)", "event_type": "Office Hours", - + "time_start": "2014-07-23 11:30:00", "time_stop": "2014-07-23 12:10:00", "venue_serial": 1579, "description": "Spencer and William are ready to talk about all things Puppet related. They\u2019ll answer questions about module best practices and testing, using Hiera to manage data, and interfacing with the community. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37316", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37316", "speakers": [125113,99817], "categories": [ - + "Office Hours" - + ] }, - + { "serial": 37338, "name": "Office Hour with Drasko DRASKOVIC (DEVIALET) ", "event_type": "Office Hours", - + "time_start": "2014-07-23 16:10:00", "time_stop": "2014-07-23 16:50:00", "venue_serial": 1579, "description": "If you want to explore that thin line where hardware meets software, stop by and chat with Drasko. He\u2019ll talk to you about electronic prototyping of connected objects for the Internet of Things from a designer\u2019s point of view; discuss open source HW schematic modifications, component sourcing, and production; and weigh in on monitoring, data harvesting, remote control, sensors, and actuators.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37338", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37338", "speakers": [173105], "categories": [ - + "Office Hours" - + ] }, - + { "serial": 37345, "name": "Office Hour with Rob Reilly (Rob Reilly Consulting) ", "event_type": "Office Hours", - + "time_start": "2014-07-23 16:10:00", "time_stop": "2014-07-23 16:50:00", "venue_serial": 1580, "description": "When it comes to hacking micro-controller and nano-computing projects, Rob is your man. He\u2019s ready to discuss all your hacker questions, such as the latest micro-controller trends, the hacker cross-pollination with artists, scientists, engineers, students, and suits, and\u2014of course\u2014how to get started hacking and making.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37345", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37345", "speakers": [77350], "categories": [ - + "Office Hours" - + ] }, - + { "serial": 37363, "name": "Office Hour with Yoav Landman (JFrog) and Baruch Sadogursky (JFrog)", "event_type": "Office Hours", - + "time_start": "2014-07-23 10:40:00", "time_stop": "2014-07-23 11:20:00", "venue_serial": 1580, "description": "Talk to Yoav and Baruch about all things CI/CD, such as choosing the right build tools, and automating your build, deployment, and distribution process. They\u2019ll discuss the issues of moving to a cloud-based development stack, how to speed up development by dealing with bottlenecks in your CI/CD flow, and distributing software releases all the way to end users.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37363", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37363", "speakers": [116050,114822], "categories": [ - + "Office Hours" - + ] }, - + { "serial": 37401, "name": "Office Hour with Glen Wiley (Verisign, Inc.), Neel Goyal (Verisign, Inc.), Willem Toorop (NLNet Labs), and Allison Mankin (Verisign, Inc.) ", "event_type": "Office Hours", - + "time_start": "2014-07-23 15:20:00", "time_stop": "2014-07-23 16:00:00", "venue_serial": 1580, "description": "Learn more about getdns for accessing DNS security and other powerful features. Targeted to application developers, getdns provides both basic and advanced DNS capabilities, while requiring little DNS expertise. Glen, Neel, Willem, and Allison will explain how to leverage DNS to create authenticated services with getdns modes. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37401", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37401", "speakers": [173324,172895,173325,173326], "categories": [ - + "Office Hours" - + ] }, - + { "serial": 37405, "name": "Office Hour with Dr. Doris Chen (Microsoft) ", "event_type": "Office Hours", - + "time_start": "2014-07-23 11:30:00", "time_stop": "2014-07-23 12:10:00", "venue_serial": 1580, "description": "Doris is ready to answer any questions you have about developing high performance websites and apps with JavaScript and HTML5. She\u2019ll share tips and tricks for tackling real-world web platform performance problems, including network requests, speed and responsiveness, and optimizing media usage.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37405", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37405", "speakers": [133360], "categories": [ - + "Office Hours" - + ] }, - + { "serial": 37408, "name": "Office Hour with Boyd Stephens (Netelysis, LLC)", "event_type": "Office Hours", - + "time_start": "2014-07-22 16:10:00", "time_stop": "2014-07-22 16:50:00", "venue_serial": 1579, "description": "Want to learn the intricacies of the money machine and where and when you can hack it? Boyd can help. Find out why staying in total control of your money machine/cash contraption can rival accepting venture capital and angel funding when creating a business. And learn how the Lean Business Model can open up unique opportunities for money machines. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37408", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37408", "speakers": [138530], "categories": [ - + "Office Hours" - + ] }, - + { "serial": 37409, "name": "Office Hour with Benjamin Curtis (Honeybadger Industries) ", "event_type": "Office Hours", - + "time_start": "2014-07-23 13:40:00", "time_stop": "2014-07-23 14:20:00", "venue_serial": 1580, "description": "If you want to find out more about machine learning, Benjamin is available to discuss several techniques, especially those available as Ruby gems. He\u2019ll talk to you about machine learning with Ruby, scaling Rails with PostgreSQL, and other questions you have on your mind. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37409", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37409", "speakers": [173396], "categories": [ - + "Office Hours" - + ] }, - + { "serial": 37410, "name": "Office Hour with David Quigley (KEYW Corporation) ", "event_type": "Office Hours", - + "time_start": "2014-07-22 10:40:00", "time_stop": "2014-07-22 11:20:00", "venue_serial": 1580, "description": "In his ongoing campaign to demystify SELinux, David is ready to tackle any questions you have\u2014including what it was like to work on the SELinux team at the NSA. He\u2019ll gladly talk to you about SELinux Internals and anything he covered (or didn\u2019t cover) in his tutorial, \u201cDemystifying SELinux Part II: Who\u2019s Policy Is It Anyway?\u201d", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37410", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37410", "speakers": [151833], "categories": [ - + "Office Hours" - + ] }, - + { "serial": 37411, "name": "Office Hour with Niklas Nielsen (Mesosphere, Inc.), Connor Doyle (Mesosphere, Inc.), and Adam Bordelon (Mesosphere, Inc.)", "event_type": "Office Hours", - + "time_start": "2014-07-22 15:20:00", "time_stop": "2014-07-22 16:00:00", "venue_serial": 1579, "description": "Niklas, Connor, and Adam are ready to talk to you about the Apache Mesos ecosystem for combining your datacenter servers and cloud instances into one shared pool. Find out how to take control of the datacenter with multi-tenancy and fault-tolerance, how to deploy and manage distributed applications in many languages, and how to migrate existing applications to Mesos using Marathon and Chronos.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37411", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37411", "speakers": [171598,172973,172898], "categories": [ - + "Office Hours" - + ] }, - + { "serial": 37412, "name": "Office Hour with Kelley Nielsen (Linux Foundation, Gnome Foundation) ", "event_type": "Office Hours", - + "time_start": "2014-07-23 14:30:00", "time_stop": "2014-07-23 15:10:00", "venue_serial": 1548, "description": "Learn how Kelley evolved from frustrated retail worker to Linux kernel developer through the Gnome Outreach Program for Women. She\u2019ll talk about the personal ways that outreach and mentoring affect new contributors\u2014and how important introspection and self-knowledge are in the process. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37412", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37412", "speakers": [140811], "categories": [ - + "Office Hours" - + ] }, - + { "serial": 37476, "name": "OpenStack Community Celebrates Four Years", "event_type": "Event", - + "time_start": "2014-07-22 19:00:00", "time_stop": "2014-07-22 20:30:00", "venue_serial": 1583, "description": "Celebrate OpenStack's Birthday\r\n\r\n ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37476", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37476", "speakers": [], "categories": [ - + "Events" - + ] }, - + { "serial": 37488, "name": "Current Best Practices for Building Enterprise Mobile Apps", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 10:40:00", "time_stop": "2014-07-23 11:20:00", "venue_serial": 1460, "description": "With current best practices for mobile development, you can create great enterprise applications faster, iterate more often, and future-proof against a fast-changing mobile OS and hardware landscape. This session will look at key considerations for developers building enterprise apps for any device and OS.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37488", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37488", "speakers": [182198], "categories": [ - + "Sponsored Sessions" - + ] }, - + { "serial": 37566, "name": "Office Hour with Jason Swartz (Netflix, Inc.) ", "event_type": "Office Hours", - + "time_start": "2014-07-22 13:40:00", "time_stop": "2014-07-22 14:20:00", "venue_serial": 1580, "description": "Looking to get started with Scala? Jason is on hand to answer questions about this object-functional programming language. Find out how Netflix is using Scala for rapid API development, and why Scala may be useful to you. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37566", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37566", "speakers": [171226], "categories": [ - + "Office Hours" - + ] }, - + { "serial": 37567, "name": "Office Hour with Brian Bulkowski (Aerospike)", "event_type": "Office Hours", - + "time_start": "2014-07-22 15:20:00", "time_stop": "2014-07-22 16:00:00", "venue_serial": 1580, "description": "Stop by and chat with Brian about using the Aerospike database in real-time big data-driven applications. He\u2019ll talk to you about subjects including low latency, high throughput applications and cacheless architectures, what does and what doesn\u2019t work when using flash as a storage option, and big data management with Hadoop, Storm, Spark, and NoSQL.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37567", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37567", "speakers": [148534], "categories": [ - + "Office Hours" - + ] }, - + { "serial": 37578, "name": "Office Hour with Francesco Cesarini (Erlang Solutions Ltd), Robert Virding (Erlang Solutions Ltd.), and Marc Sugiyama (Erlang Solutions, Inc) ", "event_type": "Office Hours", - + "time_start": "2014-07-22 11:30:00", "time_stop": "2014-07-22 12:10:00", "venue_serial": 1580, "description": "Think you have the right use case to adopt Erlang or Elixir? Concerned about selling this new technology to management and operations? The Erlang Solutions crew will explain the advantages of the Erlang stack\u2014including when and when not to use it. Find out how to run an Erlang project, support and maintain Erlang clusters, and learn how to introduce Erlang to your organization.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37578", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37578", "speakers": [10595,174073,173421], "categories": [ - + "Office Hours" - + ] }, - + { "serial": 37581, "name": "Open Source Multiplies New Relic Awesomeness", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 10:40:00", "time_stop": "2014-07-22 11:20:00", "venue_serial": 1463, "description": "New Relic believes in the principals of open source. So much so, that we built the New Relic Platform according those principals: by making the SDKs and our plugins open source, we ignited a rapidly growing and rich community of plugin authors. Come hear about our open source strategy, efforts in building community, and see how easy it is to participate.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37581", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37581", "speakers": [77939], "categories": [ - + "Sponsored Sessions" - + ] }, - + { "serial": 37583, "name": "Perl Web Development with CGI::Ex::App", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 13:40:00", "time_stop": "2014-07-23 14:20:00", "venue_serial": 1460, "description": "CGI::Ex::App is a lightweight, high performance framework that has been quietly driving million-dollar websites since 2004. Come see why this application framework might be the perfect fit for you.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37583", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37583", "speakers": [137347], "categories": [ - + "Sponsored Sessions" - + ] }, - + { "serial": 37584, "name": "Welcome and Introduction", "event_type": "Open Cloud Day", - + "time_start": "2014-07-21 09:00:00", "time_stop": "2014-07-21 09:15:00", "venue_serial": 1584, "description": "Sarah Novotny, OSCON Program Chair, technical evangelist and community manager for NGINX will kick off Open Cloud Day. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37584", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37584", "speakers": [76338], "categories": [ - + "Cloud" - + ] }, - + { "serial": 37585, "name": "How to Build Your Applications to Scale in the Cloud", "event_type": "Open Cloud Day", - + "time_start": "2014-07-21 09:15:00", "time_stop": "2014-07-21 09:45:00", "venue_serial": 1584, "description": "Whether you have one or a million visitors accessing your web app, they are all going to demand a great user experience regardless of what it takes for you to deliver it. This invariably means quick page loads and fast response times every single time.I am about different ways to start scaling your application with the new 'Cloud' technology.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37585", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37585", "speakers": [142320], "categories": [ - + "Cloud" - + ] }, - + { "serial": 37586, "name": "Open Cloud APIs", "event_type": "Open Cloud Day", - + "time_start": "2014-07-21 11:00:00", "time_stop": "2014-07-21 11:30:00", "venue_serial": 1584, "description": "The success of development and deployment of anything is often unrelated to the technical superiority of a cloud platform but a product of what the platform enables you to do. We need robust but more importantly enjoyable to use APIs at every stage in an application's lifecycle. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37586", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37586", "speakers": [161475], "categories": [ - + "Cloud" - + ] }, - + { "serial": 37588, "name": "Full Stack Development with Node, Angular, and Neo4j", "event_type": "BoF", - + "time_start": "2014-07-23 19:00:00", "time_stop": "2014-07-23 20:00:00", "venue_serial": 1454, "description": "Full stack is becoming a popular developer specialization for web and mobile application development. We'll be talking about the role of the full stack developer using open source platforms for building recommendation-based apps. We'll walk through full stack development on node.js, AngularJS, and Neo4j graph database.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37588", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37588", "speakers": [], "categories": [ - + ] }, - + { "serial": 37589, "name": "Office Hour with Steven Pousty (Red Hat) and Katie Miller (Red Hat)", "event_type": "Office Hours", - + "time_start": "2014-07-22 14:30:00", "time_stop": "2014-07-22 15:10:00", "venue_serial": 1580, "description": "Ready to write your components in the language of your choice, and put those components wherever you want on your network? Then come talk to Steve and Katie about the event-driven application framework Vert.X. They\u2019ll show you what the framework looks like and how you can get started with it.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37589", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37589", "speakers": [142320,175488], "categories": [ - + "Office Hours" - + ] }, - + { "serial": 37593, "name": "A Technical Exploration of Atom\u2019s Text Editor Component", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 11:30:00", "time_stop": "2014-07-23 12:10:00", "venue_serial": 1461, "description": "Atom is an open-source desktop text editor built with web technology. This talk will be a deep, technical exploration of how Atom manipulates and renders text. Nathan will share lessons we\u2019ve learned about efficiently rendering text via the DOM, and then explore the key components involved in Atom\u2019s text editing system and how the concepts they model are surfaced in the API.\r\n", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37593", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37593", "speakers": [2732], "categories": [ - + "Sponsored Sessions" - + ] }, - + { "serial": 37603, "name": "Dive into Cloud Foundry PaaS", "event_type": "BoF", - + "time_start": "2014-07-20 19:00:00", "time_stop": "2014-07-20 20:00:00", "venue_serial": 1471, "description": "Cloud Foundry is an open source Platform as a Service (PaaS) that features a range of components which provide a faster and easier way to build, test, deploy and scale applications. We will go through the most important elements and capabilities that make Cloud Foundry a first class citizen PaaS.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37603", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37603", "speakers": [], "categories": [ - + ] }, - + { "serial": 37604, "name": "Morning Break", "event_type": "Open Cloud Day", - + "time_start": "2014-07-21 10:45:00", "time_stop": "2014-07-21 11:00:00", "venue_serial": 1584, "description": "Located outside of F150 in the Lobby Foyer.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37604", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37604", "speakers": [], "categories": [ - + ] }, - + { "serial": 37605, "name": "Afternoon Break", "event_type": "Open Cloud Day", - + "time_start": "2014-07-21 15:00:00", "time_stop": "2014-07-21 15:15:00", "venue_serial": 1584, "description": "Located outside of F150 in the Lobby Foyer.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37605", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37605", "speakers": [], "categories": [ - + ] }, - + { "serial": 37606, "name": "Lunch", "event_type": "Open Cloud Day", - + "time_start": "2014-07-21 12:30:00", "time_stop": "2014-07-21 13:30:00", "venue_serial": 1584, "description": "Located in Exhibit Hall E. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37606", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37606", "speakers": [], "categories": [ - + ] }, - + { "serial": 37622, "name": "The Enterprise Challenge for Cloud Computing", "event_type": "Open Cloud Day", - + "time_start": "2014-07-21 09:45:00", "time_stop": "2014-07-21 10:15:00", "venue_serial": 1584, "description": "Clouds - they started as nice tools, became a favorite of\r\nstartups and Web 2.0 companies. But the enterprise, why hasn't cloud\r\ncomputing dominated in the enterprise already? Well, it's complicated...", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37622", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37622", "speakers": [141881], "categories": [ - + "Cloud" - + ] }, - + { "serial": 37623, "name": "Real Time Backend for the Internet of Things", "event_type": "BoF", - + "time_start": "2014-07-22 20:00:00", "time_stop": "2014-07-22 21:00:00", "venue_serial": 1457, "description": "We will discuss the advantages and disadvantages of using SQL over NoSQL datastores, optimal solutions to the Blob problems, what \u201creal-time\u201d really means, dealing with sharding, spatial data types and the expected throughputs of such systems and more. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37623", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37623", "speakers": [], "categories": [ - + ] }, - + { "serial": 37624, "name": "Modern API Design via RAML, Swagger & Blueprints", "event_type": "BoF", - + "time_start": "2014-07-20 19:00:00", "time_stop": "2014-07-20 20:00:00", "venue_serial": 1453, "description": "Lets discuss how to iterate on APIs with our users, using their feedback to help us get the API right from the start. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37624", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37624", "speakers": [], "categories": [ - + ] }, - + { "serial": 37625, "name": "A Path to Achieving True Cloud Portability", "event_type": "Open Cloud Day", - + "time_start": "2014-07-21 14:30:00", "time_stop": "2014-07-21 15:00:00", "venue_serial": 1584, "description": "Moving applications from a traditional data center to a cloud\r\nprovides immediate and real advantages in the way services are delivered\r\nto customers. What if you could also easily move from one cloud to\r\nanother? ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37625", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37625", "speakers": [181484], "categories": [ - + "Cloud" - + ] }, - + { "serial": 37626, "name": "The Art of Tizen UI Theme Technology in Various Profiles", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 11:30:00", "time_stop": "2014-07-23 12:10:00", "venue_serial": 1460, "description": "Tizen is aimed at various profiles, not only mobile. The UI must be scalable and themeable to support these diverse profiles. This presentation will share the technology behind the scalable and themeable Tizen UI which is called EFL (Enlightenment Foundation Libraries). This will reduce development time tremendously to support multiple products and applications.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37626", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37626", "speakers": [181502], "categories": [ - + "Sponsored Sessions" - + ] }, - + { "serial": 37627, "name": "A New Community is Born in Internet of Things: Standard Specification to Open Source", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 10:40:00", "time_stop": "2014-07-22 11:20:00", "venue_serial": 1460, "description": "This talk includes an introduction to IoT and background, lessons learned from standard specification and its limitation, reason for open source in IoT, Samsung's efforts on IoT open source, and the future of IoT.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37627", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37627", "speakers": [172630], "categories": [ - + "Sponsored Sessions" - + ] }, - + { "serial": 37629, "name": "Why Open Platforms Matter to Enterprises and Developers", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 11:30:00", "time_stop": "2014-07-22 12:10:00", "venue_serial": 1460, "description": "The industry needs cloud solutions built on an open, extensible architecture that delivers consistent access to infrastructure, runtimes, and application resources. As customers continue to adopt cloud service-based solutions, they need to avoid vendor lock-in, simplify building of complex cloud environments, and quickly develop cloud-ready applications that drive massively scalable cloud models.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37629", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37629", "speakers": [181598], "categories": [ - + "Sponsored Sessions" - + ] }, - + { "serial": 37631, "name": "A Deployment Architecture for OpenStack in the Enterprise", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 11:30:00", "time_stop": "2014-07-23 12:10:00", "venue_serial": 1463, "description": "Enterprise developers want flexible, open architectures to develop cloud-native applications and bring new ideas to market faster. Yet enterprise IT needs to quickly deliver services, applications, and infrastructures in a consistent, secure, repeatable manner. How do you get the agility and flexibility while maintaining control?", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37631", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37631", "speakers": [26426], "categories": [ - + "Sponsored Sessions" - + ] }, - + { "serial": 37646, "name": "Storytelling on the Shoulders of Giants", "event_type": "Keynote", - + "time_start": "2014-07-24 09:40:00", "time_stop": "2014-07-24 09:50:00", "venue_serial": 1525, "description": "You may not feel like you\u2019re a \u201ccreative person,\u201d but never underestimate where your code could turn up or what stories it might tell. The most unassuming repo can be remixed into something magnificent. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37646", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37646", "speakers": [143674], "categories": [ - + "Keynotes" - + ] }, - + { "serial": 37647, "name": "Open Platform for Family History", "event_type": "BoF", - + "time_start": "2014-07-22 20:00:00", "time_stop": "2014-07-22 21:00:00", "venue_serial": 1460, "description": "Meet to Learn, Define and Deliver Winning Products that Shape Tomorrow\r\n\r\nBillion of genealogical records available to read and write through open restful APIs. Learn what's available, how to use them, and get profiles of many application opportunities.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37647", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37647", "speakers": [], "categories": [ - + ] }, - + { "serial": 37648, "name": "Github RootsDev Javascript Open Source API Project", "event_type": "BoF", - + "time_start": "2014-07-23 19:00:00", "time_stop": "2014-07-23 20:00:00", "venue_serial": 1456, "description": "RootsDev has created a full function Javascript API that can be used to authenticate and read historical person data with in 30 minutes. Learn how and why this SDK was built and what open source apps are already talking to it.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37648", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37648", "speakers": [], "categories": [ - + ] }, - + { "serial": 37649, "name": "O'Reilly Author Book Signings - Tuesday", "event_type": "Event", - + "time_start": "2014-07-22 10:10:00", "time_stop": "2014-07-22 16:30:00", "venue_serial": 1597, "description": "Author book signings will be held in the O'Reilly Authors' booth on Tuesday and Wednesday. This is a great opportunity for you to meet O'Reilly authors and to get a free copy of their book. Complimentary copies will be provided for the first 25 attendees. Limit one free book per attendee.\r\n", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37649", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37649", "speakers": [], "categories": [ - + ] }, - + { "serial": 37650, "name": "O'Reilly Author Book Signings - Wednesday", "event_type": "Event", - + "time_start": "2014-07-23 10:10:00", "time_stop": "2014-07-23 17:00:00", "venue_serial": 1597, "description": "Author book signings will be held in the O'Reilly Authors' booth on Tuesday and Wednesday. This is a great opportunity for you to meet O'Reilly authors and to get a free copy of their book. Complimentary copies will be provided for the first 25 attendees. Limit one free book per attendee.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37650", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37650", "speakers": [], "categories": [ - + ] }, - + { "serial": 37651, "name": "Office Hours - Tuesday", "event_type": "Event", - + "time_start": "2014-07-22 10:40:00", "time_stop": "2014-07-22 16:30:00", "venue_serial": 1596, "description": "Office Hours are your chance to meet face-to-face with OSCON presenters in a small-group setting. Drop in to discuss their sessions, ask questions, or make suggestions.\r\n", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37651", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37651", "speakers": [], "categories": [ - + ] }, - + { "serial": 37652, "name": "Office Hours - Wednesday", "event_type": "Event", - + "time_start": "2014-07-23 10:40:00", "time_stop": "2014-07-23 17:00:00", "venue_serial": 1596, "description": "Office Hours are your chance to meet face-to-face with OSCON presenters in a small-group setting. Drop in to discuss their sessions, ask questions, or make suggestions.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37652", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37652", "speakers": [], "categories": [ - + ] }, - + { "serial": 37657, "name": "Ubuntu Server Deep Dive", "event_type": "BoF", - + "time_start": "2014-07-22 20:00:00", "time_stop": "2014-07-22 21:00:00", "venue_serial": 1461, "description": "The Ubuntu Server BoF - come to tell the Ubuntu Server Product and Project Managers what would you like to see next - and hear from us what's new for Cloud users and a comprehensive tour of our security features. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37657", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37657", "speakers": [], "categories": [ - + ] }, - + { "serial": 37658, "name": "Adventures in Hackademia: Bridging Open Source and Higher Education", "event_type": "BoF", - + "time_start": "2014-07-23 19:00:00", "time_stop": "2014-07-23 20:00:00", "venue_serial": 1457, "description": "RIT recently announced the first Academic Minor in Free/Open Source Software and Free Culture in the United States (http://boingboing.net/2014/03/06/get-a-wee-degree-in-free-from.html) This session will detail the engagement strategies, metrics of success and failure, and educational resources that made it possible. Patches welcome. Forks encouraged. Teacher, Learner, and Hacker friendly session.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37658", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37658", "speakers": [], "categories": [ - + ] }, - + { "serial": 37659, "name": "Evolution of the Apache CouchDB Development Community", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 16:10:00", "time_stop": "2014-07-23 16:50:00", "venue_serial": 1460, "description": "The Apache CouchDB project and world of open source development has seen a lot of change since 2005. In this talk, Joan Touzet will discuss the change journey the CouchDB community has taken as it has matured, covering advocacy efforts, Bylaws and Code of Conduct, GitHub, marketing efforts, mailing lists, growth of the committer base, and operating within the larger ASF community.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37659", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37659", "speakers": [181972], "categories": [ - + "Sponsored Sessions" - + ] }, - + { "serial": 37661, "name": "Driving Innovation and Next Generation Application Architectures with Open Source", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 10:40:00", "time_stop": "2014-07-23 11:20:00", "venue_serial": 1461, "description": "Join Rackspace and CoreOS as they discuss/examine that developers are beginning to see a new set of disruptive technologies come into play - beyond virtual machine, beyond just configuration management.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37661", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37661", "speakers": [173503,30412], "categories": [ - + "Sponsored Sessions" - + ] }, - + { "serial": 37662, "name": "Author Book Signing with Paris Buttfield-Addison, Jonathon Manning, and Tim Nugent", "event_type": "Author Signing", - + "time_start": "2014-07-22 15:10:00", "time_stop": "2014-07-22 15:40:00", "venue_serial": 1598, "description": "Paris, Jon, and Tim will be at the O'Reilly Authors booth #719, signing copies of their book, Learning Cocoa with Objective-C, 4th Edition.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37662", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37662", "speakers": [], "categories": [ - + "Author Signings" - + ] }, - + { "serial": 37663, "name": "Author Book Signing with Paco Nathan", "event_type": "Author Signing", - + "time_start": "2014-07-22 18:30:00", "time_stop": "2014-07-22 19:00:00", "venue_serial": 1549, "description": "Paco will be at the O'Reilly Authors booth #719, signing copies of his book, Enterprise Data Workflows with Cascading.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37663", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37663", "speakers": [], "categories": [ - + "Author Signings" - + ] }, - + { "serial": 37664, "name": "Author Book Signing with Harry Percival", "event_type": "Author Signing", - + "time_start": "2014-07-22 10:10:00", "time_stop": "2014-07-22 10:40:00", "venue_serial": 1549, "description": "Harry will be at the O'Reilly Authors booth #719, signing copies of his book, Test-Driven Development with Python.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37664", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37664", "speakers": [], "categories": [ - + "Author Signings" - + ] }, - + { "serial": 37665, "name": "Author Book Signing with Mike Amundsen", "event_type": "Author Signing", - + "time_start": "2014-07-22 15:40:00", "time_stop": "2014-07-22 16:10:00", "venue_serial": 1598, "description": "Mike will be at the O'Reilly Authors booth #719, signing copies of his book, RESTful Web APIs.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37665", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37665", "speakers": [], "categories": [ - + "Author Signings" - + ] }, - + { "serial": 37666, "name": "Author Book Signing with Arun Gupta", "event_type": "Author Signing", - + "time_start": "2014-07-22 15:10:00", "time_stop": "2014-07-22 15:40:00", "venue_serial": 1549, "description": "Arun will be at the O'Reilly Authors booth #719, signing copies of his book, Java EE 7 Essentials.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37666", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37666", "speakers": [], "categories": [ - + "Author Signings" - + ] }, - + { "serial": 37667, "name": "Author Book Signing with Anil Madhavapeddy", "event_type": "Author Signing", - + "time_start": "2014-07-22 10:10:00", "time_stop": "2014-07-22 10:40:00", "venue_serial": 1550, "description": "Anil will be at the O'Reilly Authors booth #719, signing copies of his book, Real World OCaml.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37667", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37667", "speakers": [], "categories": [ - + "Author Signings" - + ] }, - + { "serial": 37668, "name": "Author Book Signing with Lorna Jane Mitchell", "event_type": "Author Signing", - + "time_start": "2014-07-23 10:10:00", "time_stop": "2014-07-23 10:40:00", "venue_serial": 1549, "description": "Lorna will be at the O'Reilly Authors booth #719, signing copies of her book, PHP Web Services.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37668", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37668", "speakers": [], "categories": [ - + "Author Signings" - + ] }, - + { "serial": 37669, "name": "Author Book Signing with Kyle Simpson", "event_type": "Author Signing", - + "time_start": "2014-07-23 15:10:00", "time_stop": "2014-07-23 15:40:00", "venue_serial": 1549, "description": "Kyle will be at the O'Reilly Authors booth #719, signing copies of his book, You Don't Know JS: Scope & Closures.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37669", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37669", "speakers": [], "categories": [ - + "Author Signings" - + ] }, - + { "serial": 37670, "name": "Author Book Signing with Ruth Suehle", "event_type": "Author Signing", - + "time_start": "2014-07-22 15:40:00", "time_stop": "2014-07-22 16:10:00", "venue_serial": 1549, "description": "Ruth will be at the O'Reilly Authors booth #719, signing copies of her book, Raspberry Pi Hacks.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37670", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37670", "speakers": [], "categories": [ - + "Author Signings" - + ] }, - + { "serial": 37671, "name": "Author Book Signing with Mike Wolfson", "event_type": "Author Signing", - + "time_start": "2014-07-22 17:40:00", "time_stop": "2014-07-22 18:00:00", "venue_serial": 1549, "description": "Mike will be at the O'Reilly Authors booth #719, signing copies of his book, Programming the Android Developer Tools Essentials.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37671", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37671", "speakers": [], "categories": [ - + "Author Signings" - + ] }, - + { "serial": 37672, "name": "Author Book Signing with Neal Ford", "event_type": "Author Signing", - + "time_start": "2014-07-22 15:10:00", "time_stop": "2014-07-22 15:40:00", "venue_serial": 1550, "description": "Neal will be at the O'Reilly Authors booth #719, signing copies of his book, Functional Thinking.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37672", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37672", "speakers": [], "categories": [ - + "Author Signings" - + ] }, - + { "serial": 37673, "name": "Author Book Signing with Francesco Cesarini", "event_type": "Author Signing", - + "time_start": "2014-07-22 17:40:00", "time_stop": "2014-07-22 18:00:00", "venue_serial": 1550, "description": "Francesco will be at the O'Reilly Authors booth #719, signing copies of his book, Designing for Scalability with Erlang/OTP.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37673", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37673", "speakers": [], "categories": [ - + "Author Signings" - + ] }, - + { "serial": 37674, "name": "Scalable, Fault Tolerant, Never-stop Systems: The World of Erlang", "event_type": "BoF", - + "time_start": "2014-07-23 19:00:00", "time_stop": "2014-07-23 20:00:00", "venue_serial": 1458, "description": "Join us for lighting talks and discussion of how to approach the hard problems of building scalable, fault tolerant systems in the real world using Erlang.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37674", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37674", "speakers": [], "categories": [ - + ] }, - + { "serial": 37675, "name": "Author Book Signing with Sebastien Goasguen", "event_type": "Author Signing", - + "time_start": "2014-07-22 18:00:00", "time_stop": "2014-07-22 18:30:00", "venue_serial": 1549, "description": "Sebastien will be at the O'Reilly Authors booth #719, signing copies of his book, 60 Recipes for Apache CloudStack.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37675", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37675", "speakers": [], "categories": [ - + "Author Signings" - + ] }, - + { "serial": 37676, "name": "Author Book Signing with Dan Sanderson", "event_type": "Author Signing", - + "time_start": "2014-07-23 15:10:00", "time_stop": "2014-07-23 15:40:00", "venue_serial": 1550, "description": "Dan will be at the O'Reilly Authors booth #719, signing copies of his books, Programming Google App Engine with Java and Programming Google App Engine with Python.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37676", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37676", "speakers": [], "categories": [ - + "Author Signings" - + ] }, - + { "serial": 37677, "name": "Author Book Signing with Alasdair Allan", "event_type": "Author Signing", - + "time_start": "2014-07-22 15:40:00", "time_stop": "2014-07-22 16:10:00", "venue_serial": 1550, "description": "Alasdair will be at the O'Reilly Authors booth #719, signing copies of his book, Distributed Network Data.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37677", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37677", "speakers": [], "categories": [ - + "Author Signings" - + ] }, - + { "serial": 37678, "name": "What's a 'DPDK', and Where Can I Get One?", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 10:40:00", "time_stop": "2014-07-23 11:20:00", "venue_serial": 1463, "description": "Legend tells of a legendary piece of software whose packet processing speed is the stuff of legend. Find out all about this software, how it's made, where you can get it, and how it can help getting network packets into your native or virtualized application.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37678", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37678", "speakers": [182043], "categories": [ - + "Sponsored Sessions" - + ] }, - + { "serial": 37679, "name": "Author Book Signing with Steven Pousty and Katie Miller", "event_type": "Author Signing", - + "time_start": "2014-07-22 18:00:00", "time_stop": "2014-07-22 18:30:00", "venue_serial": 1550, "description": "Steven and Katie will be at the O'Reilly Authors booth #719, signing copies of their book, Getting Started with OpenShift.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37679", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37679", "speakers": [], "categories": [ - + "Author Signings" - + ] }, - + { "serial": 37680, "name": "Author Book Signing with Scott Murray", "event_type": "Author Signing", - + "time_start": "2014-07-23 15:10:00", "time_stop": "2014-07-23 15:40:00", "venue_serial": 1598, "description": "Scott will be at the O'Reilly Authors booth #719, signing copies of his book, Interactive Data Visualization for the Web.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37680", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37680", "speakers": [], "categories": [ - + "Author Signings" - + ] }, - + { "serial": 37681, "name": "Author Book Signing with Jason Strimpel", "event_type": "Author Signing", - + "time_start": "2014-07-23 15:40:00", "time_stop": "2014-07-23 16:10:00", "venue_serial": 1549, "description": "Jason will be at the O'Reilly Authors booth #719, signing copies of his book, Web Components.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37681", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37681", "speakers": [], "categories": [ - + "Author Signings" - + ] }, - + { "serial": 37682, "name": "Author Book Signing with Jamie Allen", "event_type": "Author Signing", - + "time_start": "2014-07-23 13:30:00", "time_stop": "2014-07-23 14:00:00", "venue_serial": 1549, "description": "Jamie will be at the O'Reilly Authors booth #719, signing copies of his book, Effective Akka.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37682", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37682", "speakers": [], "categories": [ - + "Author Signings" - + ] }, - + { "serial": 37683, "name": "Author Book Signing with Ethan Brown", "event_type": "Author Signing", - + "time_start": "2014-07-23 10:10:00", "time_stop": "2014-07-23 10:40:00", "venue_serial": 1550, "description": "Ethan will be at the O'Reilly Authors booth #719, signing copies of his book, Web Development with Node and Express.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37683", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37683", "speakers": [], "categories": [ - + "Author Signings" - + ] }, - + { "serial": 37684, "name": "Author Book Signing with Tom Fifield and Everett Toews", "event_type": "Author Signing", - + "time_start": "2014-07-23 15:40:00", "time_stop": "2014-07-23 16:10:00", "venue_serial": 1598, "description": "Tom and Everett will be at the O'Reilly Authors booth #719, signing copies of their book, OpenStack Operations Guide.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37684", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37684", "speakers": [], "categories": [ - + "Author Signings" - + ] }, - + { "serial": 37685, "name": "Author Book Signing with Jeff Sheltren and Narayan Newton", "event_type": "Author Signing", - + "time_start": "2014-07-23 10:10:00", "time_stop": "2014-07-23 10:40:00", "venue_serial": 1598, "description": "Jeff and Narayan will be at the O'Reilly Authors booth #719, signing copies of their book, High Performance Drupal.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37685", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37685", "speakers": [], "categories": [ - + "Author Signings" - + ] }, - + { "serial": 37688, "name": "Containermania: The Hype, The Reality, and The Future", "event_type": "Open Cloud Day", - + "time_start": "2014-07-21 16:45:00", "time_stop": "2014-07-21 17:00:00", "venue_serial": 1584, "description": "For this presentation, we'll take a look at the current state of Docker\r\ncontainers, what they're useful for, and where it's going.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37688", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37688", "speakers": [6845], "categories": [ - + "Cloud" - + ] }, - + { "serial": 37689, "name": "The Human Element of APIs", "event_type": "Open Cloud Day", - + "time_start": "2014-07-21 12:00:00", "time_stop": "2014-07-21 12:30:00", "venue_serial": 1584, "description": "This talk will cover the interactions and influences that people and\r\ncommunities have over open source software and with one another. Special\r\nattention will be given to many of the hot technologies being used in\r\nand for Open Cloud technologies.\r\n", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37689", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37689", "speakers": [182080], "categories": [ - + "Cloud" - + ] }, - + { "serial": 37690, "name": "How Disney Built a Modern Cloud with Open Source", "event_type": "Open Cloud Day", - + "time_start": "2014-07-21 10:15:00", "time_stop": "2014-07-21 10:45:00", "venue_serial": 1584, "description": "Working to help reboot the delivery and consumption of\r\ntechnology services within the Walt Disney Company, we've built a cloud\r\nservice offering and integration platform on open source technologies like\r\nOpenStack with the goal of providing ways to host any application -\r\nwhether modern and cloud ready or on software development hospice care.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37690", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37690", "speakers": [182081], "categories": [ - + "Cloud" - + ] }, - + { "serial": 37698, "name": "Designers, See Your Designs Differently: Color Contrast Tips and Techniques", "event_type": "BoF", - + "time_start": "2014-07-23 19:00:00", "time_stop": "2014-07-23 20:00:00", "venue_serial": 1461, "description": "This session will cover the strategies, tips and techniques we have used at PayPal with our design teams to move towards a goal of delivering products with sufficient color contrast for all user experiences to assure that as many people as possible can see and use them. Designers and developers are welcome to attend. \r\n", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37698", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37698", "speakers": [], "categories": [ - + ] }, - + { "serial": 37699, "name": "Glimpse of Git's Future", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 16:10:00", "time_stop": "2014-07-22 16:50:00", "venue_serial": 1454, "description": "Peek into the future of Git with Android, Google and GitHub. Learn\r\nabout the 450x server performance improvement developed by Google and\r\nGitHub, and get a glimpse of the scaling roadmap.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37699", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37699", "speakers": [64512], "categories": [ - + "Tools & Techniques" - + ] }, - + { "serial": 37703, "name": "Alchemy.js", "event_type": "BoF", - + "time_start": "2014-07-20 19:00:00", "time_stop": "2014-07-20 20:00:00", "venue_serial": 1470, "description": "Alchemy.js is a new open source visualization library from GraphAlchemist. Come join the team behind the new tool and learn about the benefits of using Alchemy.js.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37703", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37703", "speakers": [], "categories": [ - + ] }, - + { "serial": 37704, "name": "Identity: Authentication and User Management", "event_type": "BoF", - + "time_start": "2014-07-23 19:00:00", "time_stop": "2014-07-23 20:00:00", "venue_serial": 1463, "description": "Acquiring users, getting them signed up, and protecting both your and their interests are all hard things. What are the hard parts, what are the available tools, and what are the OSS angles?", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37704", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37704", "speakers": [], "categories": [ - + ] }, - + { "serial": 37708, "name": "Taming Hybrid Clouds with ManageIQ ", "event_type": "BoF", - + "time_start": "2014-07-22 19:00:00", "time_stop": "2014-07-22 20:00:00", "venue_serial": 1460, "description": "ManageIQ is a newly open sourced project that lets operations and developers manage the lifecycle of their virtualization and cloud infrastructures. Place virtual workloads according to your policies and automate them, prioritizing for cost, performance, security, and/or reliability. In this BoF, we will demonstrate how to use ManageIQ as a single API and gateway for cloud workloads.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37708", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37708", "speakers": [], "categories": [ - + ] }, - + { "serial": 37709, "name": "Leveraging a Cloud Architecture for Fun, Ease, and Profit", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 11:30:00", "time_stop": "2014-07-22 12:10:00", "venue_serial": 1463, "description": "The world of cloud and application development is not just for the hardened developer these days. In their session, Phil Jackson, Development Community Advocate for SoftLayer, and Harold Hannon, Sr. Software Architect at SoftLayer, will pull back the curtain of the architecture of a fun demo application purpose-built for the cloud.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37709", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37709", "speakers": [140339,122564], "categories": [ - + "Sponsored Sessions" - + ] }, - + { "serial": 37712, "name": "Gluster Community BoF", "event_type": "BoF", - + "time_start": "2014-07-22 19:00:00", "time_stop": "2014-07-22 20:00:00", "venue_serial": 1458, "description": "Get up to speed with the Gluster Community - we released 3.5, planned 3.6, created lots of language bindings for GFAPI, and are currently planning the Gluster Software Distribution. Come hear about the latest developments in this BoF and how you can participate and benefit.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37712", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37712", "speakers": [], "categories": [ - + ] }, - + { "serial": 37713, "name": "A Hands-on Intro to Data Science and R", "event_type": "Event", - + "time_start": "2014-07-21 09:00:00", "time_stop": "2014-07-21 12:30:00", "venue_serial": 1607, "description": "Data scientists need to have a grab bag of tools available to accomplish the task of value-driven data analytics. Many of those tools are open source. Come see how Pivotal is leveraging and contributing to open source with data science. Special focus on: R, Python, MADlib, Open Chorus, Apache Tomcat, Apache Hadoop, Redis, Rabbit MQ, Cloud Foundry and other open source toolkits. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37713", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37713", "speakers": [182463], "categories": [ - + "Sponsored Tutorials" - + ] }, - + { "serial": 37714, "name": "Author Signing with Miguel Grinberg", "event_type": "Author Signing", - + "time_start": "2014-07-22 10:10:00", "time_stop": "2014-07-22 10:40:00", "venue_serial": 1598, "description": "Miguel will be at the O'Reilly Authors booth #719, signing copies of his book, Flask Web Development.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37714", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37714", "speakers": [], "categories": [ - + "Author Signings" - + ] }, - + { "serial": 37715, "name": "TA3M", "event_type": "BoF", - + "time_start": "2014-07-22 19:00:00", "time_stop": "2014-07-22 20:00:00", "venue_serial": 1466, "description": "Leaders in technology convene to discuss the 'state of the movement' for issues around privacy, censorship, and surveillance. What are our successes and failures? What are the current threats and opportunities to affect meaningful change? ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37715", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37715", "speakers": [], "categories": [ - + ] }, - + { "serial": 37717, "name": "CLA\u2019s: Best Thing Since Sliced Bread or Tool of the Devil\u2026 A Panel Discussion", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 16:10:00", "time_stop": "2014-07-23 16:50:00", "venue_serial": 1462, "description": "Open Source licenses are mostly grounded in US Copyright Law, which requires 51% representation to claim standing in any copyright-related action (including defense against infringement claims as well as re-licensing). Yet, they are also a barrier to participation, since you often must have one in place before you make a substantial (or in some cases any) contribution.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37717", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37717", "speakers": [179595,7969,46421,6380], "categories": [ - + "Business" - + ] }, - + { "serial": 37719, "name": "Making a Difference through Open Source", "event_type": "Keynote", - + "time_start": "2014-07-22 09:20:00", "time_stop": "2014-07-22 09:30:00", "venue_serial": 1525, "description": "Non-profit entities help impact the lives for millions of people, and make the world a better place. Bluehost is working with Grassroots.org to help make it simpler for non-profits to get online and share their messages with the world. Join us and learn how you can help make a difference.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37719", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37719", "speakers": [3476,181009,182521], "categories": [ - + "Keynotes" - + ] }, - + { "serial": 37721, "name": "10 Years of Google Summer of Code", "event_type": "Keynote", - + "time_start": "2014-07-22 09:50:00", "time_stop": "2014-07-22 09:55:00", "venue_serial": 1525, "description": "A (very) fast overview of the results of the program: lines of code, #'s of participants, and so on. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37721", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37721", "speakers": [81759], "categories": [ - + "Keynotes" - + ] }, - + { "serial": 37723, "name": "Bringing OpenStack based Cloud to the Enterprise", "event_type": "Keynote", - + "time_start": "2014-07-22 09:45:00", "time_stop": "2014-07-22 09:50:00", "venue_serial": 1525, "description": "More and more Enterprises are evaluating and adopting OpenStack as an option for deployments in the cloud. HP Helion OpenStack is\r\nthe latest addition to the OpenStack distribution, find out how your Enterprise can leverage it to deliver a scalable, secure and stable cloud environment for complex workloads.\r\n", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37723", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37723", "speakers": [177325], "categories": [ - + "Keynotes" - + ] }, - + { "serial": 37724, "name": "High Performance Visualizations with Canvas", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 13:40:00", "time_stop": "2014-07-22 14:20:00", "venue_serial": 1460, "description": "This session will dig into the nuts and bolts of Canvas and explore techniques for implementing beautiful, smooth, and efficient visualizations.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37724", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37724", "speakers": [160033], "categories": [ - + "Sponsored Sessions" - + ] }, - + { "serial": 37733, "name": "Comics for Communication and Fun!", "event_type": "BoF", - + "time_start": "2014-07-23 19:00:00", "time_stop": "2014-07-23 20:00:00", "venue_serial": 1465, "description": "Learn how to draw comics or practice drawing with fellow cartoonists at this fun session. We're all friends, regardless of skill level!", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37733", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37733", "speakers": [], "categories": [ - + ] }, - + { "serial": 37736, "name": "Apache Spark: A Killer or Savior of Apache Hadoop?", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 13:40:00", "time_stop": "2014-07-23 14:20:00", "venue_serial": 1463, "description": "The Big Boss(tm) has just OKed the first Hadoop cluster in the company. You are the guy in charge of analyzing petabytes of your company's valuable data using a combination of custom MapReduce jobs and SQL-on-Hadoop solutions. All of a sudden the web is full of articles telling you that Hadoop is dead, Spark has won and you should quit while you're still ahead. But should you? ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37736", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37736", "speakers": [151691], "categories": [ - + "Sponsored Sessions" - + ] }, - + { "serial": 37738, "name": "Open Manufacturing: Bringing Open Hardware Beyond 3D Printing", "event_type": "Keynote", - + "time_start": "2014-07-24 09:30:00", "time_stop": "2014-07-24 09:40:00", "venue_serial": 1525, "description": "Open source design has been a recent trend in hardware, but it tends to be limited to open libraries of 3D-printable parts. These are geared at makers, artists, hobbyists, and whoever else really wants to print their own figurines, not necessarily the engineering community.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37738", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37738", "speakers": [182587], "categories": [ - + "Keynotes" - + ] }, - + { "serial": 37742, "name": "OpenBTS: Develop Your Own Cell Network & Apps!", "event_type": "BoF", - + "time_start": "2014-07-23 19:00:00", "time_stop": "2014-07-23 20:00:00", "venue_serial": 1466, "description": "Learn how to build your own cell network and invent cell network applications and mobile services using OpenBTS APIs in a session led by Harvind Samra, co-founder of the OpenBTS project, and Michael Iedema, Senior Engineer at Range Networks.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37742", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37742", "speakers": [], "categories": [ - + ] }, - + { "serial": 37744, "name": "Office Hour with Renee DiResta (OATV), Bryce Roberts (OATV), and Roger Chen (OATV)", "event_type": "Office Hours", - + "time_start": "2014-07-22 13:00:00", "time_stop": "2014-07-22 13:30:00", "venue_serial": 1546, "description": "Got an Open Source project you\u2019re thinking of turning into a startup? Got a startup you\u2019re planning to raise funding for? Want help or feedback on your business plan? O\u2019Reilly AlphaTech Ventures is at OSCON and here to help. Come by and say hi!", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37744", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37744", "speakers": [122516,1402,171704], "categories": [ - + "Office Hours" - + ] }, - + { "serial": 37755, "name": "Cassandra 2.X: Transactions, NoSQL, and Performance", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 14:30:00", "time_stop": "2014-07-22 15:10:00", "venue_serial": 1463, "description": "Cassandra 2.0 introduced lightweight transactions (LWT), making it the first database to allow mixing linearizable transactions into a fully distributed, highly availability system ('AP' in CAP terminology).\r\n", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37755", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37755", "speakers": [140062], "categories": [ - + "Sponsored Sessions" - + ] }, - + { "serial": 37759, "name": "Geek Choir", "event_type": "BoF", - + "time_start": "2014-07-23 20:00:00", "time_stop": "2014-07-23 21:00:00", "venue_serial": 1464, "description": "Much has been written about the connections between mathematics and music. Come meet and collaborate with your fellow attendees as we engage in another OSCON tradition: Geek Choir! (Yes, there will be singing.)", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37759", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37759", "speakers": [], "categories": [ - + ] }, - + { "serial": 37767, "name": "PostgreSQL BoF", "event_type": "BoF", - + "time_start": "2014-07-22 19:00:00", "time_stop": "2014-07-22 20:00:00", "venue_serial": 1451, "description": "Get together with the local users group + Pg folks in town for OSCON.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37767", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37767", "speakers": [], "categories": [ - + ] }, - + { "serial": 37768, "name": "Office Hour with Tim O'Reilly (O'Reilly Media, Inc.)", "event_type": "Office Hours", - + "time_start": "2014-07-22 12:45:00", "time_stop": "2014-07-22 13:15:00", "venue_serial": 1547, "description": "Have you wondered about \u201cbig idea marketing\u201d? That is, how to align what you do with big movements many people care about? Tim has advice on this and other topics, such as company culture, startup and project pitches, how programming is changing in the era of cloud computing and DevOps, and why it\u2019s important for coders to help improve government services (like the healthcare.gov rescue effort).", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37768", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37768", "speakers": [251], "categories": [ - + "Office Hours" - + ] }, - + { "serial": 37769, "name": "Office Hour with Tim Bray (Independent)", "event_type": "Office Hours", - + "time_start": "2014-07-22 17:45:00", "time_stop": "2014-07-22 18:15:00", "venue_serial": 1546, "description": "Are you worried about software, the Net, and life online? According to Tim, now is the time for sensible, reasonable, extreme paranoia. Come chat with him about privacy policy and technology, public-key encryption, identity federation, and related topics. ", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37769", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37769", "speakers": [24978], "categories": [ - + "Office Hours" - + ] }, - + { "serial": 37771, "name": "Tim O'Reilly", "event_type": "Keynote", - + "time_start": "2014-07-23 10:00:00", "time_stop": "2014-07-23 10:10:00", "venue_serial": 1525, "description": "Keynote by Tim O'Reilly, founder and CEO of O'Reilly Media.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37771", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37771", "speakers": [251], "categories": [ - + "Keynotes" - + ] }, - + { "serial": 37773, "name": "Hello User Group", "event_type": "BoF", - + "time_start": "2014-07-23 19:00:00", "time_stop": "2014-07-23 20:00:00", "venue_serial": 1451, "description": "An open discussion on how to start a local user group and grow it into a successful community.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37773", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37773", "speakers": [], "categories": [ - + ] }, - + { "serial": 37775, "name": "Gophers of a Feather", "event_type": "BoF", - + "time_start": "2014-07-23 20:00:00", "time_stop": "2014-07-23 21:00:00", "venue_serial": 1454, "description": "Come meet other Go developers and hear why Go is becoming the new language of the cloud.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37775", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37775", "speakers": [], "categories": [ - + ] }, - + { "serial": 37776, "name": "Application Blueprints with Apache Brooklyn", "event_type": "BoF", - + "time_start": "2014-07-22 20:00:00", "time_stop": "2014-07-22 21:00:00", "venue_serial": 1456, "description": "brooklyn.io lets you create blueprints to deploy and manage distributed applications, building on best-practice blueprints and policies maintained by the community. Recently accepted into the Apache Incubator, Brooklyn is a multi-cloud autonomic control plane based on the OASIS CAMP YAML standard for composing deployment plans.\r\n\r\nThis BoF is for anyone wanting to share experiences or learn more.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37776", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37776", "speakers": [], "categories": [ - + ] }, - + { "serial": 37777, "name": "Managing Containerized Applications", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 14:30:00", "time_stop": "2014-07-22 15:10:00", "venue_serial": 1460, "description": "Containers provide an opportunity for more direct management of\r\napplications. However, loosely coupled, distributed, elastic\r\nmicro-services require more than individual containers and hosts.\r\nKubernetes is a new open source project inspired by Google\u2019s internal\r\nworkload management systems that establishes robust primitives for\r\nmanaging applications comprised of multiple containers.\r\n", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37777", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37777", "speakers": [183169], "categories": [ - + "Sponsored Sessions" - + ] }, - + { "serial": 37778, "name": "Bringing More Women to Free and Open Source Software", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 17:00:00", "time_stop": "2014-07-22 17:40:00", "venue_serial": 1463, "description": "This talk will introduce GNOME's Outreach Program for Women, outline\r\nhow the program works and update you on what's happened recently.\r\nTired of people talking about how there isn't diversity in free\r\nsoftware? Come to this talk and find out how you can help actually\r\nchange things.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37778", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37778", "speakers": [173364], "categories": [ - + "Sponsored Sessions" - + ] }, - + { "serial": 37779, "name": "The Epic Battle: Scala at PayPal", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 13:40:00", "time_stop": "2014-07-22 14:20:00", "venue_serial": 1463, "description": "There\u2019s only one way to do it here at PayPal \u2013 with a framework. Everything you'll ever need is done if you stay inside the lines. Imagine my reaction when I joined PayPal with Scala and high hopes. We have many reasons to avoid the monolithic framework, so we were going against the grain from day 1.\r\n", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37779", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37779", "speakers": [128980], "categories": [ - + "Sponsored Sessions" - + ] }, - + { "serial": 37780, "name": "DreamFactory REST API Platform for Mobile and IoT", "event_type": "BoF", - + "time_start": "2014-07-23 20:00:00", "time_stop": "2014-07-23 21:00:00", "venue_serial": 1458, "description": "DreamFactory is an open source REST API platform that makes it easy to develop mobile and IoT applications without rolling your own user management, security, and REST APIs on the server. Come learn what DreamFactory is all about!", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37780", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37780", "speakers": [], "categories": [ - + ] }, - + { "serial": 37794, "name": "Containers - What We Have and What's Missing?", "event_type": "BoF", - + "time_start": "2014-07-22 20:00:00", "time_stop": "2014-07-22 21:00:00", "venue_serial": 1463, "description": "We will discuss the current state of containers and what can/should be improved.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37794", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37794", "speakers": [], "categories": [ - + ] }, - + { "serial": 37795, "name": "Gophers with Hammers: Fun with Parsing and Generating Go", "event_type": "40-minute conference session", - + "time_start": "2014-07-22 14:30:00", "time_stop": "2014-07-22 15:10:00", "venue_serial": 1461, "description": "Go is an open source language first released in 2009. Go is popular\r\nbecause it makes coding super-fun again. Josh will illustrate the deep role tools\r\nlike gofmt, godoc, 'go vet', 'go test' and others play in the Go\r\nexperience, show you how to roll your own, and talk about some unexplored\r\npossibilities.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37795", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37795", "speakers": [179435], "categories": [ - + "Sponsored Sessions" - + ] }, - + { "serial": 37801, "name": "Racing Change: Accelerating Innovation Through Radical Transparency", "event_type": "Keynote", - + "time_start": "2014-07-23 09:20:00", "time_stop": "2014-07-23 09:30:00", "venue_serial": 1525, "description": "In February of this year, PayPal announced it had hired Danese Cooper as their first Head of Open Source. PayPal? And Open Source? In fact, Open Source is playing a key role in reinventing PayPal engineering as a place where innovation at scale is easy and fun - especially if you like to work in Open Source.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37801", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37801", "speakers": [76338,179599,179595,179435], "categories": [ - + "Keynotes" - + ] }, - + { "serial": 37808, "name": "Build Responsive Web Apps with OpenUI5", "event_type": "40-minute conference session", - + "time_start": "2014-07-23 13:40:00", "time_stop": "2014-07-23 14:20:00", "venue_serial": 1461, "description": "Meet OpenUI5--a powerful web UI library for developing responsive web apps that run on and adapt to any current browser and device. \r\n\r\n", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37808", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37808", "speakers": [173233,104828,170822], "categories": [ - + "Sponsored Sessions" - + ] }, - + { "serial": 37810, "name": "Gigawatts", "event_type": "Event", - + "time_start": "2014-07-23 19:00:00", "time_stop": "2014-07-23 19:30:00", "venue_serial": 1450, "description": "Some talks carefully guide the listeners through the entirety of a topic,\r\n starting with the basics and ending with the fine details.\r\n\r\nThat's\u2026 not the plan for this talk.", - "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37810", + "website_url": "http://oscon.com/oscon2014/public/schedule/detail/37810", "speakers": [3189], "categories": [ - + "Events", - + "Perl" - + ] }, - - + + { "serial": "slot_15176", "name": "Lunch", "event_type": "break", - + "time_start": "2014-07-20 12:30:00", "time_stop": "2014-07-20 13:30:00", "venue_serial": 1468, @@ -8571,12 +8571,12 @@ "Break" ] }, - + { "serial": "slot_15177", "name": "Lunch", "event_type": "break", - + "time_start": "2014-07-21 12:30:00", "time_stop": "2014-07-21 13:30:00", "venue_serial": 1468, @@ -8584,12 +8584,12 @@ "Break" ] }, - + { "serial": "slot_15276", "name": "Morning Break", "event_type": "break", - + "time_start": "2014-07-22 10:10:00", "time_stop": "2014-07-22 10:40:00", "venue_serial": 1467, @@ -8597,12 +8597,12 @@ "Break" ] }, - + { "serial": "slot_15277", "name": "Lunch", "event_type": "break", - + "time_start": "2014-07-22 12:10:00", "time_stop": "2014-07-22 13:40:00", "venue_serial": 1469, @@ -8610,12 +8610,12 @@ "Break" ] }, - + { "serial": "slot_15279", "name": "Afternoon break", "event_type": "break", - + "time_start": "2014-07-22 15:10:00", "time_stop": "2014-07-22 16:10:00", "venue_serial": 1467, @@ -8623,12 +8623,12 @@ "Break" ] }, - + { "serial": "slot_15294", "name": "Morning Break", "event_type": "break", - + "time_start": "2014-07-23 10:10:00", "time_stop": "2014-07-23 10:40:00", "venue_serial": 1467, @@ -8636,12 +8636,12 @@ "Break" ] }, - + { "serial": "slot_15295", "name": "Lunch", "event_type": "break", - + "time_start": "2014-07-23 12:10:00", "time_stop": "2014-07-23 13:40:00", "venue_serial": 1469, @@ -8649,12 +8649,12 @@ "Break" ] }, - + { "serial": "slot_15311", "name": "Afternoon Break", "event_type": "break", - + "time_start": "2014-07-23 15:10:00", "time_stop": "2014-07-23 16:10:00", "venue_serial": 1467, @@ -8662,12 +8662,12 @@ "Break" ] }, - + { "serial": "slot_15409", "name": "Morning Break", "event_type": "break", - + "time_start": "2014-07-24 10:40:00", "time_stop": "2014-07-24 11:00:00", "venue_serial": 1473, @@ -8675,12 +8675,12 @@ "Break" ] }, - + { "serial": "slot_15895", "name": "Dinner", "event_type": "break", - + "time_start": "2014-07-23 17:40:00", "time_stop": "2014-07-23 19:00:00", "venue_serial": 1523, @@ -8688,13 +8688,13 @@ "Break" ] } - + ], "speakers": [ - + { - + "serial": 149868, "name": "Faisal Abid", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_149868.jpg", @@ -8704,9 +8704,9 @@ "twitter": "FaisalAbid", "bio": "

I am a software engineer, author, teacher and entrepreneur.

\n

From the hardcore server-side and database challenges to the the front end issues, I love solving problems and strive to create software that can make a difference.

\n

I’m an author published by Manning and O’Reilly and have also appeared in leading publications with my articles on ColdFusion and Flex.

\n

In my free time I teach Android or Node.js at workshops around the word, speak at conferences such as OSCON, CodeMotion, FITC and AndroidTO.

\n

Currently, I’m the founder of Dynamatik, a design and development agency in Toronto.

\n

During the days I am a Software Engineer at Kobo working on OS and app level features for Android tablets.

" }, - + { - + "serial": 172532, "name": "Josh Adams", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_172532.jpg", @@ -8716,9 +8716,9 @@ "twitter": "knewter", "bio": "

I’m the CTO of Isotope11, a mildly successful software development company that focuses on Ruby, JavaScript, and Erlang/Elixir. I’m also responsible for http://www.elixirsips.com, a screencast series wherein I walk through elixir as I learn it, and record 2 shortish (5-12 minutes, occasionally longer) videos per week. I’ve also been a co-author and a technical reviewer for multiple books on the Arduino microprocessor and Ruby, and have had a lot of fun doing robotics with Ruby as well. I’m currently aiming to bring that robotics fun to Erlang (or Elixir)

" }, - + { - + "serial": 104828, "name": "DJ Adams", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_104828.jpg", @@ -8728,9 +8728,9 @@ "twitter": "qmacro", "bio": "

DJ Adams is an enterprise architect and open source programmer, author, and bread-maker living in Manchester, working as a Principal Consultant for Bluefin Solutions. He has a degree in Latin & Greek (Classics) from the University of London, and despite having been referred to as an alpha geek, can nevertheless tie his own shoelaces and drink beer without spilling it.

\n

He has written two books for O’Reilly, on Jabber and on Google.

\n

He is married to his theoretical childhood sweetheart Michelle, and has a son, Joseph, of whom he is very proud.

" }, - + { - + "serial": 29558, "name": "Lance Albertson", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_29558.jpg", @@ -8740,9 +8740,9 @@ "twitter": "ramereth", "bio": "

Lance Albertson is the Director for the Oregon State University Open Source Lab (OSL) and has been involved with the Gentoo Linux project as a developer and package maintainer since 2003. Since joining the OSL in 2007, Lance has managed all of the hosting activities that the OSL provides for nearly 160 high-profile open source projects. He was recently promoted to Director in early 2013 after being the Lead Systems Administration and Architect since 2007.

\n

Prior to joining the OSUOSL, Lance was a UNIX Administrator for the Enterprise Server Technologies group at Kansas State University. Lance prepared for life as a career systems administrator by grappling with natural systems first, joining his father near Hiawatha, Kansas on the family farm growing corn and soybeans.

\n

In his free time he helps organize the Corvallis Beer and Blog and plays trumpet in a local jazz group The Infallible Collective. He holds a B.A. in Agriculture Technology Management from Kansas State University, where he minored in Agronomy and Computer Science.

" }, - + { - + "serial": 109270, "name": "Andrei Alexandrescu", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_109270.jpg", @@ -8752,9 +8752,9 @@ "twitter": "incomputable", "bio": "

Andrei Alexandrescu is a Research Scientist at Facebook and coined the colloquial term ‘modern C++’, used today to describe a collection of important C++ styles and idioms. His eponymous book on the topic, Modern C++ Design (Addison-Wesley, 2001), revolutionized C++ programming and
\nproduced a lasting influence not only on subsequent work on C++, but also on other languages and systems. With Herb Sutter, Andrei is also the
\ncoauthor of C++ Coding Standards (Addison-Wesley, 2004).

\n

Through Andrei’s varied work on libraries and applications, as well as his research in
\nmachine learning and natural language processing, he has garnered a solid reputation in both industrial and academic circles. Andrei has also been
\nthe key designer of many important features of the D programming language and has authored a large part of D’s standard library, positioning him to
\nwrite an authoritative book on the new language, appropriately entitled The D Programming Language (Addison-Wesley, 2010).

\n

Andrei holds a Ph.D. in Computer Science from the University of Washington and a B.Sc. in Electrical Engineering from University ‘Politehnica’ Bucharest.

" }, - + { - + "serial": 2216, "name": "Alasdair Allan", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_2216.jpg", @@ -8764,9 +8764,9 @@ "twitter": "aallan", "bio": "

Alasdair Allan is a Scientist, Author, Hacker, Tinkerer and Journalist who has been thinking about the Internet of Things, which he thinks is broken.

\n

He is the author of a number of books, and from time to time he also stands in front of cameras. You can often find him at conferences talking about interesting things, or deploying sensors to measure them. He recently rolled out a mesh network of five hundred sensors motes covering the entire of Moscone West during Google I/O. He’s still recovering.

\n

A few years before that he caused a privacy scandal by uncovering that your iPhone was recording your location all the time. This caused several class action lawsuits and a U.S. Senate hearing. Several years on, he still isn’t sure what to think about that.

\n

He sporadically writes blog posts about things that interest him, or more frequently provides commentary in 140 characters or less. He is a contributing editor for MAKE magazine, and a contributor to the O’Reilly Radar.

\n

Alasdair is a former academic. As part of his work he built a distributed peer-to-peer network of telescopes which, acting autonomously, reactively scheduled observations of time-critical events. Notable successes included contributing to the detection of what was\u2014at the time\u2014the most distant object yet discovered, a gamma-ray burster at a redshift of 8.2.

" }, - + { - + "serial": 170293, "name": "Jamie Allen", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_170293.jpg", @@ -8776,9 +8776,9 @@ "twitter": "jamie_allen", "bio": "

Jamie Allen has worked in consulting since 1994, with top firms including Price Waterhouse and Chariot Solutions. He has a long track record of collaborating closely with clients to build high-quality, mission-critical systems that scale to meet the needs of their businesses, and has worked in myriad industries including automotive, retail, pharmaceuticals, telecommunications and more. Jamie has been coding in Scala and actor-based systems since 2009, and is the author of “Effective Akka” book from O’Reilly.

" }, - + { - + "serial": 46440, "name": "Rob Allen", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_46440.jpg", @@ -8788,9 +8788,9 @@ "twitter": "", "bio": "

Rob Allen is a software engineer, project manager and trainer. Rob Allen has been programming in a with PHP for a very long time now and contributes to Zend Framework and other open source projects. He is a ZF contributor, member of the ZF Community team and also wrote Zend Framework in Action. Rob holds a Masters degree in Electronic Engineering from the University of Birmingham in the UK and started out writing C++ applications; he now concentrates solely on web-based applications in PHP. Rob is UK-based and runs Nineteen Feet Limited, focussing on web development, training and consultancy.

" }, - + { - + "serial": 117513, "name": "Dan Allen", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_117513.jpg", @@ -8800,9 +8800,9 @@ "twitter": "mojavelinux", "bio": "

Dan is an open source advocate, community catalyst, software generalist, author and speaker. Most of the time, he’s hacking with some JVM language. He leads the Asciidoctor project and serves as the community liaison for Arquillian. He builds on these experiences to help make a variety of open source projects wildly successful, including Asciidoctor, Arquillian, Opal and JBoss Forge.

\n

Dan is the author of Seam in Action (Manning, 2008) and has written articles for NFJS, the Magazine, IBM developerWorks, Java Tech Journal and JAXenter. He’s also an internationally recognized speaker, having presented at major software conferences including JavaOne, Devoxx, OSCON, NFJS / UberConf / RWX, JAX and jFokus. He’s recognized as a JavaOne Rock Star and Java (JVM) Champion.

\n

After a long conference day, you’ll likely find Dan geeking out about technology, documentation and testing with fellow community members over a Trappist beer or Kentucky Bourbon.

" }, - + { - + "serial": 173281, "name": "Mohammad Almalkawi", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_173281.jpg", @@ -8812,9 +8812,9 @@ "twitter": "moh", "bio": "

Mohammad is a software engineer at Quip. Before Quip, Mohammad was a technical lead on Twitter’s Mobile team where he was focused on Android platform and mobile tools. Prior to Twitter, Mohammad worked on WinRT Core in the operating systems group at Microsoft. Mohammad studied computer engineering at the University of Illinois at Urbana-Champaign where he specialized in real-time embedded systems.

" }, - + { - + "serial": 108272, "name": "Mike Amundsen", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_108272.jpg", @@ -8824,9 +8824,9 @@ "twitter": "mamund", "bio": "

An internationally known author and lecturer, Mike Amundsen travels throughout the world consulting and speaking on a wide range of topics including distributed network architecture, Web application development, and other subjects.

\n

In his role of API architect at Layer 7, Amundsen heads up the API Architecture and Design Practice in North America. He is responsible for working with companies to provide insight on how best to capitalize on the myriad opportunities APIs present to both consumers and the enterprise.

\n

Amundsen has authored numerous books and papers on programming over the last 15 years. His most recent book is a collaboration with Leonard Richardson titled “RESTful Web APIs”. His 2011 book, \u201cBuilding Hypermedia APIs with HTML5 and Node\u201d, is an oft-cited reference on building adaptable distributed systems.

" }, - + { - + "serial": 122599, "name": "Ishan Anand", "photo": null, @@ -8836,9 +8836,9 @@ "twitter": null, "bio": "

Ishan Anand is Director of New Products at Moovweb. He has been building and launching mobile products for the iPhone since the day it was released, and his work has been featured on TechCrunch, ReadWriteWeb and LifeHacker. Ishan has a solid background in software engineering with a focus on web and native application development for iOS devices and WebKit browsers. Prior to Moovweb, Ishan worked at Digidesign and his expertise was in multi-threaded real-time systems programming for the computer music industry. He holds dual-degrees in electrical engineering and mathematics from MIT.

" }, - + { - + "serial": 173201, "name": "John Anderson", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_173201.jpg", @@ -8848,9 +8848,9 @@ "twitter": "genehack", "bio": "

Lapsed biologist turned sysadmin turned programmer turned tech lead. Film at 11.

" }, - + { - + "serial": 179599, "name": "Edwin Aoki", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_179599.jpg", @@ -8860,9 +8860,9 @@ "twitter": null, "bio": "" }, - + { - + "serial": 143135, "name": "Yazz Atlas", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_143135.jpg", @@ -8872,9 +8872,9 @@ "twitter": "EntropyWorks", "bio": "

Yazz Atlas, who has been involved with open source projects for the past eighteen years, is currently working for the Advanced Technology Group within HP. His primary focus is replacing him self with a script and deploying OpenStack for the HP Cloud PaaS Core.

" }, - + { - + "serial": 4429, "name": "R Geoffrey Avery", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_4429.jpg", @@ -8884,9 +8884,9 @@ "twitter": "rGeoffrey", "bio": "

For many years a Perl Bioinformatics programmer in Philadelphia for a major pharma company helping scientists load, analyze and view their data.

" }, - + { - + "serial": 173455, "name": "Vishwas Babu", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_173455.jpg", @@ -8896,9 +8896,9 @@ "twitter": "", "bio": "

Vishwas Babu is the lead engineer at the Mifos Initiative where he works on building an open technology platform for financial inclusion to the poor. He is also a co-founder of Conflux Technologies Private Limited, an organization based out of Bangalore, India which provides a multitude of financial solutions targeted toward microfinance institutions.

" }, - + { - + "serial": 108813, "name": "Jono Bacon", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_108813.jpg", @@ -8908,9 +8908,9 @@ "twitter": "jonobacon", "bio": "

Jono Bacon is a leading community manager, speaker, and author. Currently he works as Senior Director of Community at the XPRIZE Foundation and was formally the Ubuntu Community Manager at Canonical, optimizing and growing the global Ubuntu community.

\n

Bacon is a prominent author and speaker on community management and best practice, and wrote the best-selling The Art of Community (O\u2019Reilly), is the founder of the primary annual conference for community managers and leaders, the Community Leadership Summit, founder of the Community Leadership Forum, and is a regular keynote speaker at events about community management, leadership, and best practice.

\n

Bacon has provided community management consultancy for both internal and external communities for a range of organizations. This includes Deutsche Bank, Intel, SAP, Sony Mobile, Samsung, Open Compute Project, IBM, Dyson, Mozilla, National Finishing Contractors Association, AlienVault, and others.

\n

In addition to The Art of Community, Bacon co-authored Linux Desktop Hacks (O\u2019Reilly), Official Ubuntu Book (Prentice Hall), and Practical PHP and MySQL (Prentice Hall), and has written over 500 articles across 12 different publications. He writes regularly for a range of magazines.

\n

Bacon was the co-founder of the popular LugRadio podcast, which ran for four years with 2million+ downloads and 15,000 listeners, as well as spawning five live events in both the UK and the USA, and co-founded the Shot Of Jaq podcast. He co-founded the Bad Voltage podcast, a popular show about technology, Open Source, politics, and more. Bacon is also the founder of the Ubuntu Accomplishments, Jokosher, Acire, Python Snippets, and Lernid software projects.

\n

He lives in the San Francisco Bay Area in California with his wife, Erica, and their son, Jack.

" }, - + { - + "serial": 59574, "name": "Josh Barratt", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_59574.jpg", @@ -8920,9 +8920,9 @@ "twitter": "jbarratt", "bio": "

Josh joined leading (mt) Media Temple, a leading LA-based web hosting and cloud services company, as a system engineer in 2003 before making his way up to CTO and then Chief Architect. Josh’s world is a blend of computer science, software development, systems administration and the people and processes that tie it all together. Prior to Media Temple, he worked six years as a software engineer and has built everything from large clustered systems to embedded real-time motion control for special effects, and almost everything in between. Though experienced in a number of programming languages and environments, his current happy place is with Python.

" }, - + { - + "serial": 23017, "name": "Rick Barraza", "photo": null, @@ -8932,9 +8932,9 @@ "twitter": "rickbarraza", "bio": "

Rick Barraza has been working at the forefront of integrating design and technology for over a decade. A strong voice in the interactive design community, Rick is often engaged in promoting the balance of great experiences, creative technology and business motivators. Rick’s wide experience across many aspects of both design and development have made him a key asset at Cynergy where he works with a diverse clientele across a broad spectrum of needs- from branding and design to prototype development and experience architecture.

" }, - + { - + "serial": 180437, "name": "Doran Barton", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_180437.jpg", @@ -8944,9 +8944,9 @@ "twitter": "fozzmoo", "bio": "

Doran is a long-time open source advocate. A Unix user and instructor beginning in the early 1990s, he began using Linux in late 1994. In 1998, he started a consulting company in northern Utah promoting the use of Linux and open source solutions for businesses. His career has been split between systems administration/architecture and development.

\n

Doran now works as a senior developer at Bluehost, an Endurance International web hosting company. Leveraging his mixed background, he has helped move the company forward in using Software Collections to use more modern versions of Perl and other software on Enterprise Linux distributions.

" }, - + { - + "serial": 179963, "name": "David Baumgold", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_179963.jpg", @@ -8956,9 +8956,9 @@ "twitter": null, "bio": "

David Baumgold is a web developer and open source advocate based in the Boston area. He enjoys teaching, learning, and connecting interesting people with each other. He genuinely believes that everything will all work out in the end, somehow.

" }, - + { - + "serial": 152215, "name": "Brent Beer", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_152215.jpg", @@ -8968,9 +8968,9 @@ "twitter": "brntbeer", "bio": "

Brent Beer has used Git and GitHub for over 5 years through university classes, contributions to open source projects, and professionally as a web developer. He now enjoys his role teaching the world to use Git and GitHub to their full potential as a member of the GitHub Training team.

" }, - + { - + "serial": 7969, "name": "Brian Behlendorf", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_7969.jpg", @@ -8980,9 +8980,9 @@ "twitter": "brianbehlendorf", "bio": "

Brian Behlendorf is Managing Director at Mithril Capital Management in San Francisco. His career has been a mix of technology start-up, public policy, and non-profit tech leadership. Brian serves on the Boards of the Mozilla Foundation, the Electronic Frontier Foundation, and Benetech, three organizations using technology to fight for civil liberties, open technologies, and social impact in the digital domain. Prior to Mithril, Brian was Chief Technology Officer at the World Economic Forum. He also served for two years at the White House as advisor to the Open Government project within the Office of Science and Technology Policy, and then later as advisor to Health and Human Services on open software approaches to health information sharing. Before that he has founded two tech companies (CollabNet and Organic) and several Open Source software projects (Apache, Subversion, and more).

" }, - + { - + "serial": 170052, "name": "Tim Bell", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_170052.jpg", @@ -8992,9 +8992,9 @@ "twitter": "noggin143", "bio": "Tim Bell is responsible for the CERN IT Infrastructure Group which supports Windows, Mac and Linux across the site along with virtualisation, E-mail and web services. These systems are used by over 11,000 scientists researching fundamental physics, finding out what the Universe is made of and how it works. Prior to working at CERN, Tim worked for Deutsche Bank managing private banking infrastructure in Europe and for IBM as a Unix kernel developer and deploying large scale technical computing solutions." }, - + { - + "serial": 173340, "name": "Adam Benayoun", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_173340.jpg", @@ -9004,9 +9004,9 @@ "twitter": "adambn", "bio": "

Adam is the co-founder and CEO of Binpress, a marketplace for commercial open source – providing a platform for building a profitable business from creating and working on open source projects.

\n

Adam has launched over 15 web ventures for the past 10 years and utilized numerous open source projects in the process of building his products.

\n

He studied Animation at the Minshar School of Arts and is an alumni of the 500startups accelerator in Mountain View.

" }, - + { - + "serial": 173233, "name": "Frederic Berg", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_173233.jpg", @@ -9016,9 +9016,9 @@ "twitter": "frdrcbrg", "bio": "

Frederic is a product owner with 15 years of experience in the software industry living in Heidelberg, Germany. He is passionate about software development and a major driver behind the open sourcing of OpenUI5.

" }, - + { - + "serial": 137697, "name": "Tim Berglund", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_137697.jpg", @@ -9028,9 +9028,9 @@ "twitter": "tlberglund", "bio": "

Tim is a teacher, author, and technology leader with DataStax. He is a conference speaker internationally and in the United States, and contributes to the Denver, Colorado tech community as president of the Denver Open Source User Group. He is the co-presenter of various O\u2019Reilly training videos on topics ranging from Git to Mac OS X Productivity Tips to Apache Cassandra, and is the author of Gradle Beyond the Basics. He blogs very occasionally at timberglund.com, and lives in Littleton, CO, USA with the wife of his youth and their three children.

" }, - + { - + "serial": 106355, "name": "Andrew Berkowitz", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_106355.jpg", @@ -9040,9 +9040,9 @@ "twitter": "andrewberkowitz", "bio": "

Andrew Berkowitz is a founder and Chief Product Officer at TeamSnap.com. He is also Head Coach of ComedySportz Portland, a branch of the international Comedy Improv Theater.

" }, - + { - + "serial": 3397, "name": "Josh Berkus", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_3397.jpg", @@ -9052,9 +9052,9 @@ "twitter": "FuzzyChef", "bio": "

Josh Berkus is primarily known as one of the Core Team of the world-spanning open source database project PostgreSQL. As CEO of PostgreSQL Experts, Inc., he speaks on database and open source topics all over the world, and consults on database design, performance, and open source community building. He also makes pottery and is a darned good cook.

" }, - + { - + "serial": 10, "name": "Gina Blaber", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_10.jpg", @@ -9064,9 +9064,9 @@ "twitter": "ginablaber", "bio": "

VP of Conferences, O’Reilly. Interested in big data, web performance and operations, open source, publishing, location-based technologies, JavaScript, social media, and related topics. Love to hear about cutting edge content and speakers in these areas, and practical ideas for reaching a more diverse audience and speaker base.

" }, - + { - + "serial": 179435, "name": "Josh Bleecher Snyder", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_179435.jpg", @@ -9076,9 +9076,9 @@ "twitter": "josharian", "bio": "

Josh Bleecher Snyder is the Director Of Software Engineering at PayPal. He was a co-founder / CTO of Card.io, which was acquired by PayPal in 2012. Josh was leading card.io\u2019s technology development. Before card.io, Josh founded Treeline Labs, an iOS software development company, and was a Senior Software Engineer at AdMob where he was the first iOS engineer. Josh dropped out of the Philosophy Ph.D. program at Stanford University (ABD).

" }, - + { - + "serial": 2593, "name": "Michael Bleigh", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_2593.jpg", @@ -9088,9 +9088,9 @@ "twitter": "mbleigh", "bio": "

Michael is a JavaScript and Ruby developer and the CEO/Cofounder of Divshot, a static web hosting platform for developers. He has been working to make web development better through open source contributions (such as OmniAuth, Grape, and Themestrap) as well as by speaking at events including as RailsConf, Confoo, Open Source Bridge, and OSCON.

" }, - + { - + "serial": 143232, "name": "Olivier Bloch", "photo": null, @@ -9100,9 +9100,9 @@ "twitter": "obloch", "bio": "

I am Senior Technical Evangelist at Microsoft Open Technologies, Inc., looking into interoperability, cross platform and open source development for Microsoft client platforms: Windows Phone, Windows and IE.
\nPrior to this role I have been in the embedded space for almost 10 years prior to this, developing, consulting, training and talking about the Internet Of Things, connected devices and other cool and interesting topics in France, in the US and in other places around the world.
\nI am not just a geek who loves to play around with the latest gadgets\u2026 well, mostly a geek\u2026 but I love the idea that geeking around with these devices is all about what the consumer and industrial ecosystems are becoming these days.

" }, - + { - + "serial": 172898, "name": "Adam Bordelon", "photo": null, @@ -9112,9 +9112,9 @@ "twitter": "", "bio": "

Adam is a distributed systems engineer at Mesosphere and works on Apache Mesos.
\nBefore joining Mesosphere, Adam was lead developer on the Hadoop core team at MapR Technologies, he developed distributed systems for personalized recommendations at Amazon, and he rearchitected the LabVIEW compiler at National Instruments. He completed his Master\u2019s degree at Rice University, building a tool to analyze supercomputer performance data for bottlenecks and anomalies.

" }, - + { - + "serial": 180268, "name": "Jay Borenstein", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_180268.jpg", @@ -9124,9 +9124,9 @@ "twitter": "", "bio": "

I was born in Southern California. I did my undergraduate and graduate studies at Stanford, specializing in quantitative economics and operations research. I discovered both academic disciplines benefited greatly from computer simulation and I found my passions increasingly directed toward realizing products and experiences through software.

\n

Another thread in my personal and professional life has been a love for teams of people. I find few things to be as stimulating as a smart and motivated group of people coming together in a common pursuit.

\n

Combining the above interests, the culture of Silicon Valley (and Stanford itself, for that matter) has been a perfect fit for
\n me. After initially working as software engineer for a number of years and then plying the waters in technology management as a CTO, I founded Integration Appliance in 2000. I stepped down as CEO and away from day-to-day operations at IntApp in May, 2007 and served on the board through 2012. Today, IntApp is a successful and growing company.

\n

Part of the reason for stepping away from a mature business is the quest for new challenges. Another part is the desire to get
\n back to the root of where many great technologies and ideas emerge; academia. I currently teach computer science at Stanford University and also run Facebook Open Academy as part of a larger Facebook effort to modernize education. I find it very fulfilling to help motivated, bright minds grow and succeed.

" }, - + { - + "serial": 96208, "name": "Joe Bowser", "photo": null, @@ -9136,9 +9136,9 @@ "twitter": "infil00p", "bio": "

Joe is the creator of PhoneGap for Android and is the longest contributing committer to the PhoneGap and Apache Cordova projects respectively. When he is not contributing to Open Source at Adobe, he spends his spare time working on various hardware projects at home, as well as at the Vancouver Hack Space, which he co-founded.

" }, - + { - + "serial": 173248, "name": "Garth Braithwaite", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_173248.jpg", @@ -9148,9 +9148,9 @@ "twitter": "garthdb", "bio": "

Previous lead designer on Brackets. Current developer on Topcoat. Core contributor for Open Source Design.

\n

Four daughters under the age of 9 (ladies man). Fanboy.

" }, - + { - + "serial": 159586, "name": "Alex Brandt ", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_159586.jpg", @@ -9160,9 +9160,9 @@ "twitter": "", "bio": "

Alex Brandt is a cloud developer at Rackspace, helping people build distributed scalable systems with a variety of technologies. An emerging evangelist for all things cloud, he has worked in IT support and solutioning as well as research in the realm of physics. Alex holds a B.S. in computer science and physics from Minnesota State University Moorhead.

" }, - + { - + "serial": 131404, "name": "VM Brasseur", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_131404.jpg", @@ -9172,9 +9172,9 @@ "twitter": "vmbrasseur", "bio": "

VM is a manager of technical people, projects, processes, products and p^Hbusinesses. In her over 15 years in the tech industry she has been an analyst, programmer, product manager, software engineering manager and director of software engineering. Currently she is splitting her time between shoeless consulting, a tech recruiting and management consulting firm, and writing a book translating business concepts into geek speak.

\n

VM blogs at “{a=>h}”:http://anonymoushash.vmbrasseur.com and tweets at @vmbrasseur.

" }, - + { - + "serial": 24978, "name": "Tim Bray", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_24978.jpg", @@ -9184,9 +9184,9 @@ "twitter": "timbray", "bio": "

Tim has co-founded two companies, helped raise five rounds of venture capital, written over a million words on his blog, edited the official specifications of both XML and JSON, worked for Sun Microsystems and Google, and is worried about passwords, Web culture, and tractable concurrency.

" }, - + { - + "serial": 25862, "name": "Michael Brewer", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_25862.jpg", @@ -9196,9 +9196,9 @@ "twitter": "operatic", "bio": "

Michael Brewer is an Application Programmer Specialist for the Franklin College Office of Information Technology at The University of Georgia. He designs database-backed web applications used by thousands of students and faculty and serves on several college and University-wide committees on Web development, best practices, and application security. In 2005, he won an Advising Technology Innovation Award from the National Academic Advising Association for an academic advising application he created and maintains. A speaker at OSCON in 2011 and 2012, he is also on the board of the United States PostgreSQL Association. He holds dual degrees in Mathematics and Music from The University of Georgia. A member of ASCAP, he also conducts the oldest continually operating community band in the state of Georgia; he has arranged music for orchestra, band, chorus, and has even composed incidental music for plays and musicals.

" }, - + { - + "serial": 6845, "name": "Joe Brockmeier", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_6845.jpg", @@ -9208,9 +9208,9 @@ "twitter": "jzb", "bio": "

Joe Brockmeier is a member of Red Hat’s Open Source and Standards
\n(OSAS) team, and is involved with Project Atomic, the Fedora Project’s
\nCloud Working Group, and is a member of the Apache Software Foundation.
\nBrockmeier has a long history of involvement with Linux and open source,
\nand has also spent many years working as a technology journalist.
\nBrockmeier has written for ReadWriteWeb, LWN, Linux.com, Linux Magazine,
\nLinux Pro Magazine, ZDNet, and many others.

" }, - + { - + "serial": 161486, "name": "Ethan Brown", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_161486.jpg", @@ -9220,9 +9220,9 @@ "twitter": "EthanRBrown", "bio": "

I work for Pop Art, an interactive marketing agency, doing mostly back-end website work. A lot of my current work is in C#/.NET, but I am shifting a lot of my attention to the Node.js stack (check out my projects on NPM!). My undergraduate work was in mathematics and computer science, and I have a broad and diverse background in software technologies.

" }, - + { - + "serial": 90628, "name": "Avi Bryant", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_90628.jpg", @@ -9232,9 +9232,9 @@ "twitter": "avibryant", "bio": "

Avi has led product, engineering, and data science teams at Etsy, Twitter and Dabble DB (which he co-founded and Twitter acquired). He\u2019s known for his open source work on projects such as Seaside, Scalding, and Algebird. Avi currently works at Stripe.

" }, - + { - + "serial": 148534, "name": "Brian Bulkowski", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_148534.jpg", @@ -9244,9 +9244,9 @@ "twitter": "aerospikedb", "bio": "

Brian Bulkowski, founder and CTO of Aerospike Inc. (formerly Citrusleaf), has 20-plus years experience designing, developing and tuning networking systems and high-performance Webscale infrastructures. He founded Aerospike after learning first hand, the scaling limitations of sharded MySQL systems at Aggregate Knowledge. As director of performance at this media intelligence SaaS company, Brian led the team in building and operating a clustered recommendation engine. Prior to Aggregate Knowledge, Brian was a founding member of the digital TV team at Navio Communications and chief architect of Cable Solutions at Liberate Technologies where he built the high-performance embedded networking stack and the Internetscale broadcast server infrastructure. Before Liberate, Brian was a lead engineer at Novell, where he was responsible for the AppleTalk stack for Netware 3 and 4.

" }, - + { - + "serial": 171495, "name": "Greg Bulmash", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_171495.jpg", @@ -9256,9 +9256,9 @@ "twitter": "GregBulmash", "bio": "

Greg Bulmash is an urban legend, former senior editor at IMDb, and he makes really awesome chocolate cookies.

\n

During the days, he writes developer documentation for Microsoft, but evenings and weekends belong to Seattle CoderDojo where he somehow brings together dozens and dozens of kids, parents, and volunteers to run a free Saturday morning coding club.

" }, - + { - + "serial": 169673, "name": "Cody Bunch", "photo": null, @@ -9268,9 +9268,9 @@ "twitter": "cody_bunch", "bio": "

Cody Bunch is a Private Cloud / Virtualization Architect, VMware vExpert, and VMware VCP from San Antonio, TX. Cody has authored or co-authored several OpenStack and VMware books. Additionally he has been a tech editor on a number of projects. Cody also regularly speaks at industry events and local user groups.

" }, - + { - + "serial": 173431, "name": "Eric Butler", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_173431.jpg", @@ -9280,9 +9280,9 @@ "twitter": "", "bio": "

Eric leads the Technology team at Webtrends, chartered with creating creative disruptive solutions in the analytics and optimization space. His team is responsible for the newly introduced Webtrends Streams product, among other patent-pending technologies recently released by Webtrends.

\n

Eric has held technology leadership positions at Webtrends, Jive Software and Intel in his career. He is a runner, follows his kids around playing soccer, and lives in Portland with his family.

" }, - + { - + "serial": 108884, "name": "Paris Buttfield-Addison", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_108884.jpg", @@ -9292,9 +9292,9 @@ "twitter": "parisba", "bio": "

Paris is co-founder of Secret Lab Pty. Ltd., leading production and design efforts in the mobile game and app development space. A frequent speaker at conferences, workshops and training sessions, Paris enjoys discussing engineering, product development, design and other facets of the mobile and game development worlds. Recent conferences include Apple Australia\u2019s /dev/world/2013 in Melbourne (and 2008, 2009, 2010, 2011, 2012), a keynote at CreateWorld Brisbane 2010 (and a speaker in 2009, 2011, 2012, and 2014), IxDA\u2019s Interaction 11 in Boulder (March 2011), XMediaLab Location-Based Services in Malmo, Sweden (January 2011), a tutorial and a session at OSCON 2011, OSCON 2012, OSCON 2013, linux.conf.au 2011, and many others.

\n

Paris is currently writing “Mobile Game Development With Unity” and “iOS Game Development Cookbook”, both for O’Reilly, and is the co-author of the books “Learning Cocoa with Objective-C Third Edition” (O’Reilly, 2012 and 2014), “iPhone and iPad Game Development For Dummies” (Wiley, 2010) and “Unity Mobile Game Development For Dummies” (Wiley, 2011).

\n

Paris is a highly experienced software developer, product and project manager. Key technologies include Objective-C/Cocoa on the Macintosh and iPhone/iPod Touch and iPad platforms, Java on Blackberry and Google Android and C# on Windows Mobile. Open GL ES and Unity are also favourites.

\n

Paris spent several years leading Meebo Inc.\u2019s mobile strategy in Mountain View, CA; Meebo was one of the world\u2019s fastest growing consumer internet companies and was acquired by Google in 2012. Paris is currently working on his next book, also with O’Reilly, and has recently submitted a PhD in Human-Computer Interaction, focusing on the use of tablets for information management. He is currently co-founder, producer, and occasional programmer at Secret Lab, in Tasmania, Australia.

" }, - + { - + "serial": 155881, "name": "Alejandro Cabrera", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_155881.jpg", @@ -9304,9 +9304,9 @@ "twitter": "cppcabrera", "bio": "

Hey! Nice to meet you. I’m a developer at Rackspace, and I work on the Openstack Marconi project during the day. I enjoy learning from others and sharing knowledge.

\n

Great friends, Linux, open source, Python, and Haskell have helped me get to where I am today.

" }, - + { - + "serial": 63576, "name": "Bryan Call", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_63576.jpg", @@ -9316,9 +9316,9 @@ "twitter": "", "bio": "

Bryan Call has been writing code and working with on large scale solutions for 14 years. He has experience optimizing and profiling projects, including Apache Traffic Server and many internal projects at Yahoo!.

\n

He came to Yahoo! through an acquisition of a startup and has been working there for the last 12 years. He has worked on various products and teams, such as WebRing, GeoCities, People Search, Yahoo! Personal, Tiger Team (internal consulting team), Architect in the Platform Group, Architect in the Edge Platform, and now is working in the R&D Systems Group.

\n

Bryan is also a commiter on the Apache Traffic Server project and instrumental in bring Traffic Server to the Apache Foundation.

" }, - + { - + "serial": 182198, "name": "Darryn Campbell", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_182198.jpg", @@ -9328,9 +9328,9 @@ "twitter": "", "bio": "

Darryn Campbell is the development lead on RhoMobile suite, the cross platform development framework provided by Motorola Solutions. He has been developing application development frameworks targeted at enterprise since 2008 seeing the product space increase from a small number of niche suppliers to the crowded marketplace we see today.

" }, - + { - + "serial": 155088, "name": "Francesc Campoy Flores", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_155088.jpg", @@ -9340,9 +9340,9 @@ "twitter": "francesc", "bio": "

Francesc Campoy Flores joined the Go team in 2012 as Developer Programs Engineer. Since then, he has written some considerable didactic resources and traveled the world attending conferences and organizing live courses.

\n

He joined Google in 2011 as a backend software engineer working mostly in C++ and Python, but it was with Go that he rediscovered how fun programming can be. He loves languages; fluent in four of them, he’s now tackling a fifth one.

" }, - + { - + "serial": 6921, "name": "Brian Capouch", "photo": null, @@ -9352,9 +9352,9 @@ "twitter": "", "bio": "

Brian Capouch is a longtime open source user, programmer, and hacker.

\n

He teaches CS using 100% Open Source tools at small Indiana college, run a small wireless ISP; Asterisk and openWRT are his specialities.

" }, - + { - + "serial": 109289, "name": "Thierry Carrez", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_109289.jpg", @@ -9364,9 +9364,9 @@ "twitter": "tcarrez", "bio": "

Thierry Carrez has been the Release Manager for the OpenStack project since its inception, coordinating the effort and facilitating collaboration between contributors. He is the elected chair of the OpenStack Technical Committee, which is in charge of the technical direction of the project. He spoke about OpenStack, open innovation and open source project management at various conferences around the world, including OSCON, LinuxCon and FOSDEM. A Python Software Foundation fellow, he was previously the Technical lead for Ubuntu Server at Canonical, and an operational manager for the Gentoo Linux Security Team.

" }, - + { - + "serial": 75349, "name": "Piers Cawley", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_75349.jpg", @@ -9376,9 +9376,9 @@ "twitter": "pdcawley", "bio": "

Piers Cawley started programming Perl in the mid nineties, but recently spent a few years working as a Ruby programmer.

\n

He’s currently writing Perl for Thermeon Europe

\n

He’s a singer and balloon modeller, and has created custom balloon millinery for Sarah Novotny.

" }, - + { - + "serial": 123894, "name": "Bill Cernansky", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_123894.jpg", @@ -9388,9 +9388,9 @@ "twitter": "", "bio": "

Bill Cernansky is a veteran Software Configuration Management Engineer at Jeppesen. Bill also performs and teaches improvisation and oversees tech at ComedySportz Portland. In addition, he is arguably the best at jumping over this one thing in his backyard.

" }, - + { - + "serial": 10595, "name": "Francesco Cesarini", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_10595.jpg", @@ -9400,9 +9400,9 @@ "twitter": "FrancescoC", "bio": "

Francesco Cesarini is the founder and technical director of Erlang Solutions Ltd. He has used Erlang on a daily basis since 1995, starting as an intern at Ericsson\u2019s computer science laboratory, the birthplace of Erlang. He moved on to Ericsson\u2019s Erlang training and consulting arm working on the first release of the OTP middleware, applying it to turnkey solutions and flagship telecom applications. In 1999, soon after Erlang was released as open source, he founded Erlang Solutions. With offices in five countries, they have become the world leaders in Erlang based support, consulting, training, certification, systems development and conferences. Francesco has worked in major Erlang based projects both within and outside Ericsson, and as Technical Director, has led the development and consulting teams at Erlang Solutions. He is also the co-author of Erlang Programming, a book published by O\u2019Reilly and is currently co-authoring Designing For Scalability With Erlang/OTP. He lectures the graduate students at Oxford University. You can follow his ramblings on twitter.

" }, - + { - + "serial": 837, "name": "Scott Chacon", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_837.jpg", @@ -9412,9 +9412,9 @@ "twitter": "", "bio": "

Scott Chacon is a cofounder and the CIO of GitHub. He is also the author of the Pro Git book by Apress and the maintainer of the Git homepage (git-scm.com). Scott has presented at dozens of conferences around the world on Git, GitHub and the future of work.

" }, - + { - + "serial": 147, "name": "Colin Charles", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_147.jpg", @@ -9424,9 +9424,9 @@ "twitter": "bytebot", "bio": "

Colin Charles works on MariaDB at SkySQL. He has been the Chief Evangelist for MariaDB since 2009, with work ranging from speaking engagements to consultancy and engineering works around MariaDB. He lives in Kuala Lumpur, Malaysia and had worked at MySQL since 2005, and been a MySQL user since 2000. Before joining MySQL, he worked actively on the Fedora and OpenOffice.org projects. He’s well known within open source communities in Asia and Australia, and has spoken at many conferences to boot.

" }, - + { - + "serial": 133198, "name": "Christopher Chedeau", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_133198.jpg", @@ -9436,9 +9436,9 @@ "twitter": "vjeux", "bio": "

Christopher “vjeux” Chedeau is a Front-End Engineer at Facebook. He is passionate about the web and its ability to very easily write great user interfaces.

" }, - + { - + "serial": 133360, "name": "Doris Chen", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_133360.jpg", @@ -9448,9 +9448,9 @@ "twitter": "doristchen", "bio": "

Dr. Doris Chen
\nhttp://blogs.msdn.com/b/dorischen/
\nTwitter @doristchen

\n

Doris is a Developer Evangelist at Microsoft for the Western region of the United States, specialized in web technologies (HTML5, jQuery, JavaScript, Ajax, and Java).
\nDoris has over 15 years of experience in the software industry working in several open source web tier technologies, Java platform, .NET and distributed computing technologies. She has developed and delivered over 400 keynotes, technical sessions, code camps worldwide, published widely at numerous international conferences and user groups including JavaOne, O\u2019Reilly, WebVisions, SD Forum, HTML5 and JavaScript meetups, and worldwide User Groups. Doris works very closely to create and foster the community around NetBeans, Glassfish, and related technologies. Before joining Microsoft, Doris Chen was a Technology Evangelist at Sun Microsystems.
\nDoris received her Ph.D. from the University of California at Los Angeles (UCLA) in computer engineering, specializing in medical informatics.

" }, - + { - + "serial": 171704, "name": "Roger Chen", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_171704.jpg", @@ -9460,9 +9460,9 @@ "twitter": "rgrchen", "bio": "

Roger is an investor at O\u2019Reilly AlphaTech Ventures (OATV), where he looks for collisions between unmet needs and enabling technologies. He spent his past life as a scientist and engineer, alternating between academia and industry while dabbling in the startup and venture capital world. When he is not tinkering with technology, Roger plays sports and wonders what lies beyond bell curves. Roger has a BS from Boston University and a PhD from UC Berkeley, both in Electrical Engineering.

" }, - + { - + "serial": 133377, "name": "Sara Chipps", "photo": null, @@ -9472,9 +9472,9 @@ "twitter": "sarajchipps", "bio": "

Sara Chipps is a JavaScript developer and she blogs here. She is CTO of Flatiron School. In 2010 she started an organization called Girl Develop It which offers low cost software development classes geared towards women. Girl Develop It has had over 1000 unique students in New York City and now has 25 chapters around the world.

\n

She enjoys speaking to and meeting with diverse groups from the Girl Scouts to straight up code junkies. Her goal is to inspire more females to see that working with software is fun and glamorous.

" }, - + { - + "serial": 17417, "name": "Wendy Chisholm", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_17417.jpg", @@ -9484,9 +9484,9 @@ "twitter": "wendyabc", "bio": "

I am an author, activist and project manager. I co-wrote “Universal Design for Web Applications” with Matt May (O’Reilly, 2008) and edited Web Content Accessibility Guidelines 1.0 and 2.0–the basis of most web accessibility policies. I have appeared as Wonder Woman in a web comic with the other HTML5 Super Friends and as myself in interviews on Minnesota Public Radio, Puget Sound Public Radio, and at Ignite Seattle. In November 2009, I was the Seattle PI’s Geek of the Week.

\n

I\u2019ve focused on universal design since 1995. Being both a developer (B.S. in Computer Science) and a Human Factors Engineer (M.S. in Industrial Engineering/Human Factors), I bridge communication between developers and designers. As a Senior Strategist at Microsoft, I help make Bing services and apps accessible.

\n

Photo taken by Andy Farnum

" }, - + { - + "serial": 170054, "name": "Andrew Cholakian", "photo": null, @@ -9496,9 +9496,9 @@ "twitter": "andrewvc", "bio": "

Author of Exploring Elasticsearch, and developer/evangelist at Found.no, Andrew Cholakian is an active member of the Elasticsearch community. Additionally, he is behind several elasticsearch OSS projects as well, including the popular \u201cStretcher\u201d Elasticsearch library for the Ruby language. He\u2019s also a veteran of multiple startups in the LA area, and is an active member of the local startup community.

" }, - + { - + "serial": 172534, "name": "Robby Clements", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_172534.jpg", @@ -9508,9 +9508,9 @@ "twitter": "robby_clements", "bio": "

Robby Clements is a software developer for Isotope11. His primary language has been Ruby for the past five years, but he’s been branching out into Erlang recently and finding it fascinating.

" }, - + { - + "serial": 6894, "name": "John Coggeshall", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_6894.jpg", @@ -9520,9 +9520,9 @@ "twitter": null, "bio": "

John Coggeshall is owner of Internet Technology Solutions, LLC. a high-end web consulting firm based in Michigan. He got started with PHP in 1997 and is the author of three published books and over 100 articles on PHP technologies with some of the biggest names in the industry such as Sams Publishing, Apress and O’Reilly. John also is a active contributor to the PHP core as the author of the tidy extension, a member of the Zend Education Advisory Board, and frequent speaker at PHP-related conferences worldwide.

" }, - + { - + "serial": 141235, "name": "C. Aaron Cois", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_141235.jpg", @@ -9532,9 +9532,9 @@ "twitter": "aaroncois", "bio": "

Aaron is a software engineer currently located in Pittsburgh, PA. He received his Ph.D. in 2007, developing algorithms and software for 3D medical image analysis. He currently leads a software development team at Carnegie Mellon University, focusing on web application development and cloud systems.

\n

Aaron is a polyglot programmer, with a keen interest in open source technologies. Some favorite technologies at the moment include Node.js, Python/Django, MongoDB, and Redis.

" }, - + { - + "serial": 99280, "name": "Tina Coleman", "photo": null, @@ -9544,9 +9544,9 @@ "twitter": "", "bio": "

Girl geek, web application development and architecture, agile advocate, community management

" }, - + { - + "serial": 4710, "name": "Damian Conway", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_4710.jpg", @@ -9556,9 +9556,9 @@ "twitter": "", "bio": "

Damian Conway is an internationally renowned speaker, author, and trainer, and a prominent contributor to the Perl community. Currently he runs Thoughtstream, an international IT training company that provides programmer training from beginner to masterclass level throughout Europe, North America, and Australasia. Most of his spare time over the past decade has been spent working with Larry Wall on the design and explication of the Perl 6 programming language. He has a PhD in Computer Science and was until recently an Adjunct Associate Professor in the Faculty of Information Technology at Monash University, Australia.

" }, - + { - + "serial": 179595, "name": "Danese Cooper", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_179595.jpg", @@ -9568,9 +9568,9 @@ "twitter": "DivaDanese", "bio": "

Danese Cooper has an 22-year history in the software industry and has long been an advocate for transparent development methodologies. Ms. Cooper joined PayPal in February 2014, and has held many leadership roles within the computer science sector. She has managed teams at Symantec and Apple Inc. and for six years served as Chief Open Source Evangelist for Sun Microsystems before leaving to serve as Senior Director for Open Source Strategies at Intel. She advised on open source policy to the R community while at REvolution Computing (now Revolution Analytics), and she served from February 2010 to July 2011 as Chief Technical Officer for the Wikimedia Foundation. She currently runs a successful consultancy to companies wishing to pursue open source strategies, which has served the SETI Foundation, Harris Corporation and the Bill & Melinda Gates Foundation among other clients. She is a director on the boards of the Drupal Association, and the Open Source Hardware Association, a board advisor for Mozilla and Ushahidi, and has served since 2005 as a Member of the Apache Software Foundation. She was a board member for 10 years at Open Source Initiative.

" }, - + { - + "serial": 172201, "name": "William Cox", "photo": null, @@ -9580,9 +9580,9 @@ "twitter": "gallamine", "bio": "

When William isn’t busy being a husband and father, he is an electrical engineer specializing in signal processing and machine learning. He’s worked on underwater robots, radar detection, algorithmic forex, and torpedo tracking. He tweets @gallamine and blogs at http://gallamine.com

" }, - + { - + "serial": 182463, "name": "Kevin Crocker", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_182463.jpg", @@ -9592,9 +9592,9 @@ "twitter": "", "bio": "

Kevin Crocker is a Consulting Instructor at Pivotal. He combines 40 years of technology awareness with 30 years of instructional expertise spanning multiple technologies and disciplines to bring a rich educational experience to his classes.

\n

His teaching background includes: Statistics; operations research; optimization theory; distance education; adult education; instructional design; UNIX and Linux operating systems, servers, and scripting; Java; business, banking, and finance; virtualization; and Data Analytics.

\n

He is a published author and award winning course designer. His guiding principle is: Driven by light bulbs!

\n

When he’s not helping people learn leading edge technology he’s busy with his family, golf, and home brewing special beers.

" }, - + { - + "serial": 169862, "name": "Adam Culp", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_169862.jpg", @@ -9604,9 +9604,9 @@ "twitter": "adamculp", "bio": "

Adam Culp, organizer of the SunshinePHP Developer Conference and South Florida PHP Users Group (SoFloPHP) where he speaks regularly, is a Zend Certified PHP 5.3 Engineer consulting for Zend Technologies. Adam is passionate about developing with PHP and enjoys helping others write good code, implement standards, refactor efficiently, and incorporate unit and functional tests into their projects. When he is not coding or contributing to various developer communities, he can be found hiking around the United States National Parks, teaching judo, or long distance running.

" }, - + { - + "serial": 183161, "name": "Jim Cupples", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_183161.jpg", @@ -9616,9 +9616,9 @@ "twitter": null, "bio": "

Jim is a political science nerd who has minimal tech chops, but a deep love of Open Source. Jim is currently working with college students from around Oregon (especially PSU) on a project called Ballot Path that will allow users to see all of their elected reps, and how they can replace them. A side project of Ballot Path is creating Open Source GIS political boundary maps (we have to create many of them from scratch for the smaller elected offices, especially in rural areas), which will hopefully be used by people looking to help shape a world that they want their kids to live in.

\n

“Fortune may have yet a better success in reserve for you, and they who lose today may win tomorrow.” Cervantes

" }, - + { - + "serial": 116276, "name": "Patrick Curran", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_116276.jpg", @@ -9628,9 +9628,9 @@ "twitter": "", "bio": "

Patrick Curran is Chair of the JCP. In this role he oversees the activities of the organization’s Program Management Office including driving the process, managing its membership, guiding specification leads and experts through the process, leading Executive Committee meetings, and managing the JCP.org web site.

\n

Patrick has worked in the software industry for more than 25 years, and at Sun (and now Oracle) for almost 20 years. He has a long-standing record in conformance testing, and most recently led the Java Conformance Engineering team in Sun’s Client Software Group. He was also chair of Sun’s Conformance Council, which was responsible for defining Sun’s policies and strategies around Java conformance and compatibility.

\n

Patrick has participated actively in several consortia and communities including the W3C (as a member of the Quality Assurance Working Group and co-chair of the Quality Assurance Interest Group), and OASIS (as co-chair of the Test Assertions Guidelines Technical Committee). Patrick’s blog is here.

" }, - + { - + "serial": 173396, "name": "Benjamin Curtis", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_173396.jpg", @@ -9640,9 +9640,9 @@ "twitter": "stympy", "bio": "

Ben has been developing web apps and building startups since ’99, and fell in love with Ruby and Rails in 2005. Before co-founding Honeybadger, he launched a couple of his own startups: Catch the Best, to help companies manage the hiring process, and RailsKits, to help Rails developers get a jump start on their projects.

\n

Ben’s role at Honeybadger ranges from bare-metal to front-end… he keeps the server lights blinking happily, builds a lot of the back-end Rails code, and dips his toes into the front-end code from time to time.

\n

When he’s not working, Ben likes to hang out with his wife and kids, ride his road bike, and of course hack on open source projects.

" }, - + { - + "serial": 108736, "name": "Michael Dale", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_108736.jpg", @@ -9652,9 +9652,9 @@ "twitter": "michael_dale", "bio": "

Michael is the lead front end architect for Kaltura Open Source Video Platform. In 2006 Michael Dale co-founded metavid.org an open video community archive of US house and senate floor proceedings. Later he worked on video for Wikipedia. With Kaltura he has worked on HTML5 video for Wikipedia the Internet Archive and dozens of major web properties that make use of Kaltura’s platform. His work includes work on scalable video delivery across numerous browsers and devices, subtitling, advertising, analytics and video editing in HTML5.

" }, - + { - + "serial": 172988, "name": "Avik Das", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_172988.jpg", @@ -9664,9 +9664,9 @@ "twitter": "", "bio": "

I graduated from UC Berkeley with Bachelor’s degrees in EECS and Math, and have since worked at LinkedIn as a software developer. I currently am the lead for the server that powers the LinkedIn iPad application.

\n

In my free time, I love working out, and when I find the time, I like to dabble in cooking.

" }, - + { - + "serial": 131890, "name": "Jennifer Davidson", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_131890.jpg", @@ -9676,9 +9676,9 @@ "twitter": "jewifer", "bio": "

Jennifer Davidson is Program Manager for ChickTech, a nonprofit geared toward getting more girls and women interested and retained in technology careers. She is a PhD candidate in Computer Science with a minor in Aging Sciences at Oregon State University. She is currently working on research related to involving older adults in the design and development of open source software. Her research bridges the fields of human-computer interaction, open source software communities, and gerontechnology. She is also the Community Manager for Privly, an open source project related to internet privacy.

" }, - + { - + "serial": 172536, "name": "A. Jesse Jiryu Davis", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_172536.jpg", @@ -9688,9 +9688,9 @@ "twitter": "jessejiryudavis", "bio": "

Senior Python Engineer at MongoDB in New York City. Author of Motor, an async MongoDB driver for Tornado, and of Toro, a library of locks and queues for Tornado coroutines. Contributor to Python, PyMongo, MongoDB, Tornado, and Tulip.

" }, - + { - + "serial": 173393, "name": "Kristen Dedeaux", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_173393.jpg", @@ -9700,9 +9700,9 @@ "twitter": "", "bio": "

Kristen Dedeaux is a technical writer and an open source enthusiast. She has a passion for the written word and enjoys sharing her editorial expertise with others. Throughout her seven-year career as a content manager and editor in the fast-paced financial industry, she has championed several successful training programs and designed numerous writing workshops. Within the past year, she created an internal corporate blog, “The Writer\u2019s Block: Tips for Better Business Writing”, which received positive feedback from users across the globe, particularly from software engineers. The fastest red pen in the West, she can wrangle any stubborn sentence into shape.

" }, - + { - + "serial": 173414, "name": "Ethan Dereszynski", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_173414.jpg", @@ -9712,9 +9712,9 @@ "twitter": "", "bio": "

Ethan Dereszynski is a research scientist in machine learning and data mining at Webtrends. He earned his PhD in computer science at Oregon State University in 2012. Ethan\u2019s research focuses on the application of machine learning, in particular Bayesian statistics and probabilistic models, to challenging problems across multiple disciplines. Combining work and play, Ethan is also interested in unsupervised approaches for learning models of player behavior in real-time strategy games (read: accepting all challengers at StarCraft). Prior to studying at Oregon State, he received a B.S. in computer science at Alma College, where he minored in Mathematics and English.

" }, - + { - + "serial": 174072, "name": "Henning Diedrich", "photo": null, @@ -9724,9 +9724,9 @@ "twitter": "hdiedrich", "bio": "

Henning is an entrepreneur, programmer and game designer. He is the Lead Software Engineer / Berlin at SumUp, a European mobile POS company. Henning is the creator of the Erlang VoltDB driver Erlvolt and a maintainer of the Erlang MySQL driver Emysql. His Open Source contributions for Erlang, Lua, MySQL and VoltDB are direct results of what pieces he found missing for a better game server stack.

\n

Henning wrote his first games on the C64, develops for the web since Netscape 1.0 and produced his first game server with Java 1.0. He created a language to describe tariffs for AXA and programmed and produced browser games. He founded Eonblast to create a more immersive online game experience and as official cover for the Time Tuner mission.

\n

Starting out on a 1MHz CPU, Henning’s special interest tends to be speed as an enabler. He has talked about evil performance hacks at the annual Lua Workshop, about his record setting Node.js VoltDB benchmark, and was elected to explain ‘Why Erlang?’ to the game developer community at the GDC Online 2012 (15.000 hits on slideshare.) He also contributed the underrated sed script markedoc to the OTP stack, which converts markdown to edoc.

" }, - + { - + "serial": 122516, "name": "Renee DiResta", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_122516.jpg", @@ -9736,9 +9736,9 @@ "twitter": "noupside", "bio": "

Renee DiResta is a Principal at O’Reilly AlphaTech Ventures (OATV), where she evaluates seed-stage investments. Prior to joining OATV in June of 2011, Renee spent seven as a trader at Jane Street Capital, a quantitative proprietary trading firm in New York City. She is interested in meeting interesting startups, data science, and improving liquidity and transparency in private markets.

" }, - + { - + "serial": 172973, "name": "Connor Doyle", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_172973.jpg", @@ -9748,9 +9748,9 @@ "twitter": "nor0101", "bio": "

Connor Doyle is a software engineer at Mesosphere, focused on developing tools around Apache Mesos. Before joining Mesosphere, Connor worked at the design firm Gensler where he worked on process improvement, internal libraries and distributed architecture in Scala. He completed his Master’s degree at the University of Wisconsin – La Crosse, where his capstone project was a distributed simulator for doing multi-agent learning research.

" }, - + { - + "serial": 173105, "name": "Drasko Draskovic", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_173105.jpg", @@ -9760,9 +9760,9 @@ "twitter": "draskodraskovic", "bio": "

Engineer – artist, Dra\u0161ko usualy builds computers with operating systems in less than 5 square millimeters. He holds MSc degree in Electrical Engineering and has over 10 years of expertise in embedded systems, semicoductor and telecommunications industry, where he hacked the things beyond the limits.

\n

Dra\u0161ko earned his reputation in Open Source community by being constantly involved and contributing to several projects dealing with low-level kernel programming and Linux device drivers like WeIO platform, OpenOCD JTAG debugger or CodeZero L4 hypervisor. He constantly walks on the thin line where hardware meets the software.

" }, - + { - + "serial": 137149, "name": "Clinton Dreisbach", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_137149.jpg", @@ -9772,9 +9772,9 @@ "twitter": "cndreisbach", "bio": "

Clojure and Python hacker for the Consumer Financial Protection Bureau. Lead developer on Qu, the CFPB’s public data platform, and contributor to Clojure, Hy, and other open source projects.

" }, - + { - + "serial": 123516, "name": "Emily Dunham", "photo": null, @@ -9784,9 +9784,9 @@ "twitter": "", "bio": "

Emily is a senior in computer science at Oregon State University. Since joining the OSU Open Source Lab in April 2011 a software developer on the Ganeti Web Manager project, she has worked as an intern at Intel, a teaching assistant in the computer science department, and a systems engineer at the OSL. She founded the OSL’s DevOps Bootcamp outreach program in August 2013, and is involved with the OSU Linux Users Group and local FIRST Robotics competitions.

" }, - + { - + "serial": 94695, "name": "David Elfi", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_94695.jpg", @@ -9796,9 +9796,9 @@ "twitter": "", "bio": "

Certainly, David has gained experience in all the different roles he played at Intel since 2008. Mainly based on Ecommerce products guided by AppUp product, he worked for different product flavors in the areas of web services, consumer experience and mobile application development.
\nRecently, he entered in the world of managing data for business analysis in the research of improvements based on data collected from the field.

" }, - + { - + "serial": 140062, "name": "Jonathan Ellis", "photo": null, @@ -9808,9 +9808,9 @@ "twitter": "spyced", "bio": "

Jonathan is CTO and co-founder at DataStax. Prior to DataStax, Jonathan worked extensively with Apache Cassandra while employed at Rackspace. Prior to Rackspace, Jonathan built a multi-petabyte, scalable storage system based on Reed-Solomon encoding for backup provider Mozy. In addition to his work with DataStax, Jonathan is project chair of Apache Cassandra.

" }, - + { - + "serial": 172370, "name": "Michael Enescu", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_172370.jpg", @@ -9820,9 +9820,9 @@ "twitter": "michaelenescu", "bio": "

Michael Enescu is CTO of Open Source Initiatives at Cisco, leading open source programs across multiple technologies and product lines. Previously he served as the first Vice President of Product at XenSource (acquired by Citrix) and one of the first employees. He has a broad range of experience having developed and delivered over two dozen enterprise and consumer software products. Previously he was a founding member of the Mobile Web Services group at Palm and a founding member of the Java Content and J2ME teams at Sun. He led the development of first streaming video servers and digital libraries at SGI. He started his career in storage virtualization at IBM where he managed the development of the earlier versions of IBM\u2019s core middleware platform product in the WebSphere suite, along with leading development in expert systems, operating systems and virtualization in IBM’s Storage Products group. Michael has a BS degree from Caltech and MS in Computer Science from Stanford University.

" }, - + { - + "serial": 169992, "name": "Catherine Farman", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_169992.jpg", @@ -9832,9 +9832,9 @@ "twitter": "cfarm", "bio": "

Catherine Farman is a Developer at Happy Cog, where she builds standards-based websites using HTML, CSS and Javascript. Catherine has taught responsive web design, Javascript, and Sass courses for Girl Develop It. She\u2019s also helped develop new courses and has written open source curricula to share with other teachers. When she’s not at a computer she likes to sew and watch soccer.

" }, - + { - + "serial": 6631, "name": "Paul Fenwick", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_6631.jpg", @@ -9844,9 +9844,9 @@ "twitter": "pjf", "bio": "

Paul Fenwick is the managing director of Perl Training Australia, and has been teaching computer science for over a decade. He is an internationally acclaimed presenter at conferences and user-groups worldwide, where he is well-known for his humour and off-beat topics.

\n

In his spare time, Paul’s interests include security, mycology, cycling, coffee, scuba diving, and lexically scoped user pragmata.

\n

*Photograph by Joshua Button.

" }, - + { - + "serial": 173433, "name": "Mark Ferree", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_173433.jpg", @@ -9856,9 +9856,9 @@ "twitter": "mrf", "bio": "

I am a developer who quickly grew to rely on open source software and tools for all of my projects.

\n

An active Drupal developer for the last seven years I am currently working as the Director of Engineering at Chapter Three where we use Drupal to power large content-managed sites.

" }, - + { - + "serial": 1639, "name": "Edward Finkler", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_1639.jpg", @@ -9868,9 +9868,9 @@ "twitter": "", "bio": "

Ed Finkler, also known as Funkatron, started making web sites before browsers had frames. He does front-end and server-side work in Python, PHP, and JavaScript.

\n

He served as web lead and security researcher at The Center for Education and Research in Information Assurance and Security (CERIAS) at Purdue University for 9 years. Now he’s a proud member of the Fictive Kin team. Along with Chris Hartjes, Ed is co-host of the Development Hell podcast.

\n

Ed’s current passion is raising mental health awareness in the tech community with his Open Sourcing Mental Illness speaking campaign. He is part of Engine Yard’s Prompt campaign.

\n

Ed writes at funkatron.com.

" }, - + { - + "serial": 152242, "name": "Keith Fiske", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_152242.jpg", @@ -9880,9 +9880,9 @@ "twitter": "keithf4", "bio": "

Database Administrator with OmniTI, Inc

" }, - + { - + "serial": 181484, "name": "Tyler Fitch", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_181484.jpg", @@ -9892,9 +9892,9 @@ "twitter": "", "bio": "

Tyler recently joined CHEF as a Customer Success Engineer – championing
\nbest practices and delightful experiences in automation. Prior to
\nworking at Chef he spent a decade as an engineer for Adobe developing
\nand automating commerce services for adobe.com using
\na variety of technologies. He lives in Vancouver, WA and when he’s not
\nprogramming enjoys lacrosse and using his passport.

" }, - + { - + "serial": 178139, "name": "Beth Flanagan", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_178139.jpg", @@ -9904,9 +9904,9 @@ "twitter": "dirtycitybird", "bio": "

Beth ‘pidge’ Flanagan is a Senior Software Engineer with Intel’s Opensource Technology Center. She spends most of her work life hacking on OpenEmbedded and the Yocto Project. She is the release engineer for the Yocto Project, maintainer of the yocto-autobuilder and pilot of the Yocto Blimp.

" }, - + { - + "serial": 46421, "name": "Richard Fontana", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_46421.jpg", @@ -9916,9 +9916,9 @@ "twitter": "", "bio": "

Richard Fontana is a lawyer at Red Hat, with particular responsibility
\nfor legal issues arising out of the software development process.
\nRichard specializes in copyright, trademark and patent issues,
\ntechnology transactions, free software/open source issues, computing
\ntechnology standards, data privacy and protection, information
\nsecurity, and legal matters relating to cloud computing. Richard is the
\nsole open source legal specialist at Red Hat, which is the world’s
\nlargest provider of open source-based enterprise software and cloud
\nsolutions, and he has been an active and influential public speaker on
\nmatters at the intersection of open source, law and policy. In
\naddition, Richard is an Individual Member-elected Board Director
\nof the Open Source Initiative.

" }, - + { - + "serial": 2650, "name": "Neal Ford", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_2650.jpg", @@ -9928,9 +9928,9 @@ "twitter": "neal4d", "bio": "

Neal Ford is Software Architect and Meme Wrangler at *Thought*Works, a global IT consultancy with an exclusive focus on end-to-end software development and delivery. He is also the designer and developer of applications, instructional materials, magazine articles, courseware, video/DVD presentations, and author and/or editor of 6 books spanning a variety of technologies, including the most recent The Productive Programmer. He focuses on designing and building of large-scale enterprise applications. He is also an internationally acclaimed speaker, speaking at over 100 developer conferences worldwide, delivering more than 600 talks. Check out his web site at www.nealford.com. He welcomes feedback and can be reached at nford@thoughtworks.com.

" }, - + { - + "serial": 142995, "name": "Steve Francia", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_142995.jpg", @@ -9940,9 +9940,9 @@ "twitter": "spf13", "bio": "

Steve Francia is the creator of hugo, cobra, nitro & spf13-vim. An author of multiple O’Reilly books, Steve blogs at spf13.com and gives many talks and workshops around the world. He is the Chief Developer Advocate at MongoDB responsible for the developer experience of MongoDB and leads the software engineering team responsible for drivers and integrations with all languages, libraries and frameworks. He loves open source and is thrilled to be able to work on it full time. When not coding he enjoys skateboarding and having fun outdoors with his wife and four children.

" }, - + { - + "serial": 28902, "name": "Roberto Galoppini", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_28902.jpg", @@ -9952,9 +9952,9 @@ "twitter": null, "bio": "

Roberto is a computer industry insider of 15+ years standing. Up until 1994 Roberto had never heard of Linux, until he chanced to lead a group of geeks in starting up a mobile ISP with just a bunch of old PCs. Since then Roberto has worked in such hands-on roles as programmer and systems analyst, eventually founding an open source firm open source firm in 2001, and an open source consortium in 2004.

\n

Roberto has taken an active interest in several free/open source software organizations. He currently serves on the Advisory Board of the SourceForge Marketplace and acts as the Institutional Relationship Manager for the OpenOffice.org Italian Association. Since 2003 Roberto has researched the economics of OSS, collaborating with universities and EC funded research projects. Roberto is a technical writer for IT and computer-related magazines, he regularly keeps a blog on Commercial Open Source at http://robertogaloppini.net.

" }, - + { - + "serial": 6852, "name": "Matthew Garrett", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_6852.jpg", @@ -9964,9 +9964,9 @@ "twitter": null, "bio": "

Matthew Garrett is a Linux kernel developer, firmware wrangler and meddler in cloud security. By day he works at Nebula, dealing with parts of the cloud that can be used to scare small children. By night, those parts of the cloud scare him.

" }, - + { - + "serial": 177325, "name": "Omri Gazitt", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_177325.jpg", @@ -9976,9 +9976,9 @@ "twitter": "", "bio": "

Omri Gazitt serves as VP of Engineering of HP\u2019s Helion Platform team, which delivers core parts of the Helion OpenStack and Helion Development Platform products, targeted at Enterprises that want to stand up their own Cloud on-premises, or consume it as a hosted platform in HP\u2019s Managed and Public Clouds. Omri\u2019s team is responsible for Identity & Access, Metering, Monitoring, Management Console, Load Balancing, DNS, Database, Messaging, Application Lifecycle, Developer Experience, and User Experience, and contributes heavily into the OpenStack and Cloud Foundry open source projects.

\n

In prior lives, Omri helped create .NET, Web Services, and Azure, as well as the Xbox and Kinect developer platforms. He lives with his wife and three daughters in Redmond, WA, and loves rainy winter days in the northwest because they mean fresh powder on the weekends.

" }, - + { - + "serial": 132564, "name": "Trisha Gee", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_132564.jpg", @@ -9988,9 +9988,9 @@ "twitter": "trisha_gee", "bio": "

Trisha is a developer at MongoDB. She has expertise in Java high performance systems, is passionate about enabling developer productivity, and has a wide breadth of industry experience from the thirteen years she’s been a professional developer. Trisha is a leader in the London Java Community and the Sevilla Java & MongoDB Communities, she believes we shouldn’t all have to make the same mistakes again and again.

" }, - + { - + "serial": 172751, "name": "Vidhya Gholkar", "photo": null, @@ -10000,9 +10000,9 @@ "twitter": "vgholkar", "bio": "

With a PhD in Signal Processing has developed new Machine Learning technologies for oil and gas exploration (Schlumberger). Managed Open Communication technology Standards at Vodafone Group. CTO at mobile startup (Argogroup) which has now been acquired.

" }, - + { - + "serial": 171201, "name": "Adam Gibson", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_171201.jpg", @@ -10012,9 +10012,9 @@ "twitter": "agibsonccc", "bio": "

Adam Gibson is a deep\u00ad learning specialist based in San Francisco assisting Fortune 500 companies, hedge funds, PR firms and startup accelerators with their machine learning projects. Adam has a strong track record helping companies handle and interpret big real\u00ad-time data. Adam has been a computer nerd since he was 13 and actively contributes to the open\u00ad source community.

" }, - + { - + "serial": 150440, "name": "Kelsey Gilmore-Innis", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_150440.jpg", @@ -10024,9 +10024,9 @@ "twitter": "kelseyinnis", "bio": "

Kelsey Gilmore-Innis is a back-end engineer at Reverb, where she uses Scala & functional programming to build powerful, scalable software systems. She strives to write code with charisma, uniqueness, nerve and talent and hopes to one day really, truly, deeply understand what a monad is.

" }, - + { - + "serial": 152123, "name": "Sebastien Goasguen", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_152123.jpg", @@ -10036,9 +10036,9 @@ "twitter": "sebgoa", "bio": "

Sebastien has over 15 years experience in the computing area, after a career in academia that led him to be an associate professor at Clemson University he joined Citrix to participate in the Apache CloudStack community. He is now an Apache committer and PMC member in CloudStack and Libcloud.

" }, - + { - + "serial": 173336, "name": "Sara Golemon", "photo": null, @@ -10048,9 +10048,9 @@ "twitter": "saramg", "bio": "

I work on PHP, HHVM, and wrote libssh2. I wrote a sizable chunk of Yahoo! Search’s front end, and now I make Facebook fast.

" }, - + { - + "serial": 173116, "name": "Jesus M. Gonzalez-Barahona", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_173116.jpg", @@ -10060,9 +10060,9 @@ "twitter": "jgbarah", "bio": "

Jesus M. Gonzalez-Barahona is co-founder of Bitergia, the software development analytics company specialized in the analysis of free / open source software projects. He also teaches and researches in Universidad Rey Juan Carlos (Spain), in the context of the GSyC/LibreSoft research group. His interests include the study of communities of software development, with a focus on quantitative and empirical studies. He enjoys taking photos of the coffee he drinks around the world .

" }, - + { - + "serial": 156989, "name": "Patricia Gorla", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_156989.jpg", @@ -10072,9 +10072,9 @@ "twitter": "patriciagorla", "bio": "

Patricia Gorla is an Apache Cassandra Architect at The Last Pickle, a Cassandra consultancy.

\n

She has been involved in all aspects of software development, from server administration to application development, and from data analysis to data storage.

\n

She has worked with companies and governmental entities on all aspects of data migration to non-relational data stores, and training the technical teams on the new architecture.

\n

She helped the US Patent and Trademark Office ingest more than 6 million patent documents and images; architect secure search systems for a large mortgage insurer; and introduce Cassandra to a digital marketing firm’s data pipeline.

\n

Prior to architecting databases Patricia focused on the analysis and visualization of data.

\n

Patricia speaks often at conferences and meetups such as O’Reilly’s StrataConf + Hadoop World, the Datastax Cassandra Summits, and local user groups. She was also voted a DataStax MVP for Apache Cassandra by the community.

" }, - + { - + "serial": 180122, "name": "Jessica Goulding", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_180122.jpg", @@ -10084,9 +10084,9 @@ "twitter": null, "bio": "

Jessica spent 8 years in marketing, becoming an expert in SEO and Social Media, when a passion sparked for web development. Her interest began while doing marketing and customizing Wordpress themes to help small business clientele build websites. After two years, she decided to dive right into being a full-time developer and in the summer of 2012 I taught myself Ruby on Rails and advanced HTML and CSS. She has since worked with everything from the command line, git, bootstrap integration, heroku, amazon web services, pivotal tracker, postgres and mysql database setup.

\n

A forever student, after entering the world of development she gained a passion for attending developer conferences and leaving more inspired and knowledgable. Currently she is attending Turing.IO, a software development school based in Denver, Colorado.

" }, - + { - + "serial": 173325, "name": "Neel Goyal", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_173325.jpg", @@ -10096,9 +10096,9 @@ "twitter": "", "bio": "

Neel is a R&D developer at Verisign with over ten years of experience. He
\nhas worked on a variety of initiatives for Verisign ranging from domain related tools, API specifications, and routing protocol implementations. He also serves as a member on Verisign\u00b9s Open Source Committee. Prior to Verisign, Neel worked at a startup developing consumer software for embedded devices and PCs.

" }, - + { - + "serial": 132323, "name": "Adam Granicz", "photo": null, @@ -10108,9 +10108,9 @@ "twitter": "granicz", "bio": "

Adam Granicz is a long-time F# insider and key community member, and the co-author of four F# books, including Expert F# 3.0 with Don Syme, the designer of the language. His company IntelliFactory specializes in consulting on the most demanding F# projects; shaping the future of the development of F# web, mobile and cloud applications; and developing WebSharper, the main web development framework for F#.

" }, - + { - + "serial": 183169, "name": "Brian Grant", "photo": null, @@ -10120,9 +10120,9 @@ "twitter": "", "bio": "

Brian Grant is an engineer at Google. He was formerly a lead of
\nGoogle\u2019s internal cluster management system, codenamed Borg, and was a founder of the Omega project. He now contributes to Google\u2019s cloud and open-source container efforts.

" }, - + { - + "serial": 179224, "name": "Chad Granum", "photo": null, @@ -10132,9 +10132,9 @@ "twitter": "", "bio": "

Current maintainer of Fennec (perl test tool, not the browser), Child.pm, and Test::Builder/Simple/More

" }, - + { - + "serial": 181009, "name": "James Grierson", "photo": null, @@ -10144,9 +10144,9 @@ "twitter": "jamesgrierson", "bio": "

James Grierson is the current Chief Operating Officer at Bluehost.com which powers over 2 million websites worldwide. During his time at Bluehost, James co-founded SimpleScripts automated installer for Open Source applications (merged with the MOJO Marketplace in 2013) and created the Bluehost Open Source Solutions program to support Open Source Communities. These programs provide Open Source projects with a distribution model, user feedback loop, discounted hosting services, financial support and leadership consulting.

" }, - + { - + "serial": 171381, "name": "John Griffith", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_171381.jpg", @@ -10156,9 +10156,9 @@ "twitter": "solidfire", "bio": "

John Griffith is a Senior Software Engineer at SolidFire, where his primary responsibility is driving the OpenStack Cinder Block Storage Project. John is an open source expert serving as the project team lead for Cinder. He leads the team to provide first-class SolidFire product functionality and Quality of Service integration within OpenStack. John is also responsible for the development of a true Quality of Service storage appliance designed and built for the cloud.
\nHe has significant software engineering experience, most recently serving as lead software engineer at HP. During his tenure, John focused on building large scale fibre channel SANS to continuously test and develop storage and lead the development on a Unified Storage API.

\n

John holds a bachelor\u2019s degree from Regis University in Denver, Colorado.

" }, - + { - + "serial": 170237, "name": "Sarah Guido", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_170237.jpg", @@ -10168,9 +10168,9 @@ "twitter": "sarah_guido", "bio": "

Sarah is a data scientist at Reonomy, where she’s helping to build disruptive tech in the commercial real estate industry in New York City. Three of her favorite things are Python, data, and machine learning.

" }, - + { - + "serial": 2397, "name": "Andy Gup", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_2397.jpg", @@ -10180,9 +10180,9 @@ "twitter": "agup", "bio": "

Andy Gup is a Tech Lead at Esri where he focuses on web and mobile geo-spatial APIs. He is an active contributor to a number of open source projects in the geo community.

\n

His background includes working with a wide variety of cutting edge technologies from small websites and mobile apps to high-performance Fortune 500 enterprise systems.

" }, - + { - + "serial": 143377, "name": "Arun Gupta", "photo": null, @@ -10192,9 +10192,9 @@ "twitter": "arungupta", "bio": "

Arun Gupta is Director of Developer Advocacy at Red Hat and focuses on JBoss Middleware. As a founding member of the Java EE team at Sun Microsystems, he spread the love for technology all around the world. At Oracle, he led a cross-functional team to drive the global launch of the Java EE 7 platform through strategy, planning, and execution of content, marketing campaigns, and program. After authoring ~1400 blogs at blogs.oracle.com/arungupta on different Java technologies, he continues to promote Red Hat technologies and products at blog.arungupta.me. Arun has extensive speaking experience in 37 countries on myriad topics and is a JavaOne Rockstar. An author of a best-selling book, an avid runner, a globe trotter, a Java Champion, he is easily accessible at @arungupta.

" }, - + { - + "serial": 131884, "name": "Florian Haas", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_131884.jpg", @@ -10204,9 +10204,9 @@ "twitter": "", "bio": "

Florian is an open source software specialist, experienced technical consultant, seasoned training instructor, and frequent public speaker. He has spoken at conferences like LinuxCon, OSCON, linux.conf.au, the OpenStack Summit, the MySQL Conference and Expo, and countless user and meetup groups across the globe.

" }, - + { - + "serial": 171418, "name": "Steve Hannah", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_171418.jpg", @@ -10216,9 +10216,9 @@ "twitter": "shannah78", "bio": "

I am a software developer because I love to create things and software imposes the fewest limitations on my creativity of any medium. Computer + Idea + Time + Perspiration = Any outcome you want.

\n

I started out with a G3 Bondi-Blue iMac and a free copy of Adobe Page Mill in 1998, but quickly expanded my horizon to include HTML, Javascript, PERL, Flash 3, and finally PHP 3 – in that order. Finding myself spending hours reading and writing web apps before and my day job, I decided to take my aspirations to the next level and attend University. I never looked back.

\n

I am a recovering O’Reilly book junkie who still has serious relapses from time-to-time. I try to stay open to any new software techniques and languages that come along, but currently I use PHP, Javascript, CSS, MySQL for most of my web application development, and Java for most of my desktop and mobile application development.

\n

I founded a few open source projects, including Xataface (a framework for building data-driven web applications with PHP and MySQL) and SWeTE (Simple Website Translation Engine), a proxy for internationalizing web applications. I also enjoy blogging about software-related issues.

" }, - + { - + "serial": 140339, "name": "Harold Hannon", "photo": null, @@ -10228,9 +10228,9 @@ "twitter": "", "bio": "

Harold Hannon has been working in the field of software development as both an Architect and Developer for over 15 years, with a focus on workflow, integration and distributed systems. He is currently a Sr. Software Architect at SoftLayer Technologies working within their Product Innovation team. Harold has a passion for leveraging open source solutions to bring real value to the Enterprise space, and has implemented open source solutions with many companies across the globe. Harold is also active in mobile application development, with multiple published applications.

" }, - + { - + "serial": 132865, "name": "Scott Hanselman", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_132865.jpg", @@ -10240,9 +10240,9 @@ "twitter": "shanselman", "bio": "

Scott is a web developer who has been blogging at http://hanselman.com for over a decade. He works on Azure and ASP.NET for Microsoft out of his home office in Portland. Scott has three podcasts, http://hanselminutes.com for tech talk, http://thisdeveloperslife.com on developers’ lives and loves, and http://ratchetandthegeek.com for pop culture and tech media. He’s written a number of books and spoken in person to almost a half million developers worldwide.

" }, - + { - + "serial": 86090, "name": "Jonah Harris", "photo": null, @@ -10252,9 +10252,9 @@ "twitter": "oracleinternals", "bio": "

Jonah has administered, developed against, and consulted on every major commercial and open source database system to date; his range of knowledge includes everything from query language specifics to the details of Oracle, EnterpriseDB, PostgreSQL, MySQL, SAP DB, Firebird, Ingres, and InnoDB internals, file formats, and network protocols.

" }, - + { - + "serial": 152118, "name": "Adam Harvey", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_152118.jpg", @@ -10264,9 +10264,9 @@ "twitter": "LGnome", "bio": "

Adam is a PHP Agenteer (it’s totally a word) at New Relic who is celebrating his 20th year of swearing at browsers that refuse to do his bidding. In between said bouts of invective, Adam works on various open source projects, including PHP, and attempts to figure out the great mysteries of life (well, the cricket related ones, at least).

" }, - + { - + "serial": 8837, "name": "Leslie Hawthorn", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_8837.jpg", @@ -10276,9 +10276,9 @@ "twitter": "lhawthorn", "bio": "

An internationally known community manager, speaker and author, Leslie Hawthorn has spent the past decade creating, cultivating and enabling open source communities. She created the world\u2019s first initiative to involve pre-university students in open source software development, launched Google\u2019s #2 Developer Blog, received an O\u2019Reilly Open Source Award in 2010 and gave a few great talks on many things open source.

\n

In August 2013, she joined Elasticsearch as Community Manager, where she leads Developer Relations. She works from Elasticsearch’s EU HQ in Amsterdam, The Netherlands – when not out and about gathering user praise and pain points. You can follow her adventures on Twitter

" }, - + { - + "serial": 104522, "name": "Steve Heffernan", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_104522.jpg", @@ -10288,9 +10288,9 @@ "twitter": "heff", "bio": "

Steve Heffernan is the creator of Video.js, an open source web video player in use on over 100,000 websites and with over 1 billion views per month. Steve was previously co-founder of Zencoder, a cloud video encoding service that was acquired by Brightcove. As part of Brightcove, Steve now works full-time managing the Video.js project and community.

" }, - + { - + "serial": 108520, "name": "Christopher Helm", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_108520.jpg", @@ -10300,9 +10300,9 @@ "twitter": "", "bio": "

Javascript Engineer / solver of geospatial problems

" }, - + { - + "serial": 171372, "name": "Sam Helman", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_171372.jpg", @@ -10312,9 +10312,9 @@ "twitter": "", "bio": "

Sam has been working as a software engineer at MongoDB since August 2012. Before that, he got his degree in Computer Science at Brown University, where in addition to spending huge amounts of time programming he made films, did comedy, and played music and soccer.

" }, - + { - + "serial": 182521, "name": "Raymond Henderson", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_182521.jpg", @@ -10324,9 +10324,9 @@ "twitter": "", "bio": "

Ray has over 25 years of experience in the business and non-profit sector. He is the Executive Director of Grassroots.org, a nonprofit organization that helps over 8500 nonprofits accelerate their efforts through the use of technology and serves on the Board of Directors for Fight the New Drug.org. Previously, he served as the Executive Director of Ohana Makamae Inc, an award winning substance abuse clinic and as Board President of Ma Ka Hana Ka `Ike, a nationally recognized, building and trades program for \u201cat risk\u201d youth in Hawaii. Prior to his work with non-profits, he worked marketing management for US West Direct, MCI WorldCom and MCA Records. His awards include: 2009 State of Hawaii \u2013 Governor\u2019s Innovation Award for Nonprofits, 2008 Maui- Executive Director of the Year, 2005 Outstanding Community Service Award and 2004 State of Hawaii – Collaborative Nonprofit Leader of the Year.

" }, - + { - + "serial": 173093, "name": "Ben Henick", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_173093.jpg", @@ -10336,9 +10336,9 @@ "twitter": "bhenick", "bio": "

Ben Henick has worked as a freelance web developer for more than fifteen years, catering especially to SMBs with unique project requirements. He has also fulfilled a number of production contracts relating to sites well-known to technology workers; odds are short that at one time or another, your web browser has rendered a stylesheet that he wrote.

\n

Ben has fulfilled one contract for O’Reilly Media, for HTML & CSS: The Good Parts (2010). He is currently working on three others:

\n
    \n\t
  • The New Beginner’s Guide to HTML (\u2026And CSS) (2014, Atlas-only, free-to-read)
  • \n\t
  • No-Nonsense HTML & CSS (2014)
  • \n\t
  • Mastering HTML & CSS (2014)
  • \n
\n

Prior to joining O’Reilly’s author roster, Ben served a stint as the Managing Editor of the erstwhile Digital Web Magazine and was active in several volunteer professional education settings, including the Web Standards Project, Evolt, and webdesign-l. He still lurks webdesign-l and the css-d mailing lists, along with other O’Reilly authors.

\n

Ben lives in his hometown of Portland, Oregon, and is currently seeking to switch from freelance to fulltime exempt work.

" }, - + { - + "serial": 161475, "name": "Cody Herriges", "photo": null, @@ -10348,9 +10348,9 @@ "twitter": "cody_herriges", "bio": "

Cody Herriges joined Puppet Labs in 2010 and now serves as the current Tech Lead of Systems maintaining Puppet Labs’ public and private cloud platforms, storage, and hardware infrastructure. He started his career in higher education infrastructure management where he deployed his school’s first x86 virtualization cluster on top of KVM. It was a world of complicated legacy software needing to fit into a transformed open source world. Needing to maintain all this software eventually taught him the importance of automation.

" }, - + { - + "serial": 156534, "name": "Jason Hibbets", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_156534.jpg", @@ -10360,9 +10360,9 @@ "twitter": "jhibbets", "bio": "

Jason Hibbets is a project manager in Corporate Marketing at Red Hat. He is the lead administrator, content curator, and community manager for Opensource.com and has been with Red Hat since 2003. He is the author of “The foundation for an open source city,” a book that outlines how to implement the open source city brand through open government.

\n

He graduated from NC State University and lives in Raleigh, NC. where he’s been applying open source principles in neighborhood organizations in Raleigh for several years, highlighting the importance of transparency, collaboration, and community building. In his spare time, he enjoys surfing, gardening, watching football, participating in his local government, blogging for South West Raleigh, and training his Border Collies to be frisbee and agility dogs. He heads to the beaches of North Carolina during hurricane season to ride the waves.

" }, - + { - + "serial": 171822, "name": "Bridget Hillyer", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_171822.jpg", @@ -10372,9 +10372,9 @@ "twitter": "BridgetHillyer", "bio": "

Bridget is an independent software consultant of many years. She is on the journey to become a Clojure programmer.

" }, - + { - + "serial": 6653, "name": "Mark Hinkle", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_6653.jpg", @@ -10384,9 +10384,9 @@ "twitter": "mrhinkle", "bio": "

Once upon a time, a lot longer ago then he wished Mark Hinkle was born in a small Pennsylvania town and through the most unlikely sequence of events ended up as a technologist involved in the development and evangelism of free and open source software. Mark has written extensively on open source as the former editor-in-chief of LinuxWorld Magazine and for numerous other publications (Network World, Forbes, Linux.com). He currently is the Senior Director, Open Source Solutions at Citrix Systems where he helps support the Apache CloudStack and Xen.org projects. You can find his blog at www.socializedsoftware.com and you can follow him on Twitter @mrhinkle.

" }, - + { - + "serial": 122917, "name": "Rob Hirschfeld", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_122917.jpg", @@ -10396,9 +10396,9 @@ "twitter": "zehicle", "bio": "

With a background in Mechanical and Systems Engineering, Rob Hirschfeld, Sr. Distinguished Cloud Architect at Dell, specializes in operations for large scale, integrated, and innovative cloud systems. He’s a community elected OpenStack board member, a leader of Dell’s OpenStack efforts, and founder of the Crowbar project. Rob helps Dell to set strategy for cloud computing, drives innovative cloud solutions to market, and works with customers on their cloud implementations. Rob is a graduate of Duke and Louisiana State University. In addition to cloud technologies, Rob is also known for his passion and expertise on the Agile/Lean process. You can find Rob\u2019s thoughts on cloud innovation at his blog RobHirschfeld.com or as @Zehicle. on Twitter.

" }, - + { - + "serial": 62631, "name": "Jeanne Holm", "photo": null, @@ -10408,9 +10408,9 @@ "twitter": "jeanne_jpl", "bio": "

Jeanne Holm is the Chief Knowledge Architect at the Jet Propulsion Laboratory (JPL), California Institute of Technology. Ms. Holm leads NASA\u2019s Knowledge Management Team, looking at how to access and use the knowledge gathered over the many missions of the US space agency to support missions and to drive innovation. As a lead for the award-winning NASA public and internal portals, she was at the helm of NASA\u2019s web during the largest Internet event in Government history\u2014the landing of the Mars Exploration Rovers on the surface of Mars. As the lead implementer for technologies supporting project managers at NASA, her team\u2019s solutions are helping to drive how people will manage space missions in the future, learn virtually, and share lessons learned. Her latest activities involve the transformation of NASA into a learning organization through innovative techniques in developing communities of practice and ensuring lessons are shared and embedded across the organization. Ms. Holm chairs The Federal Knowledge Management Group and a United Nations group looking at KM for Space.

\n

Her degrees are from UCLA and Claremont Graduate University. She is an instructor at UCLA where she teaches internationally and her online and ground-based courses focuses on KM strategies and social networking. She has been awarded numerous honors, including the NASA Exceptional Service Medal for leadership (twice), the NASA Achievement Award for her work on the Galileo and Voyager spacecraft, three Webby\u2019s from The International Academy of Digital Arts and Sciences, Competia\u2019s 2003 Champion of the Year, and a best practice from the APQC for \u201cUsing Knowledge Management to Drive Innovation\u201d.

\n

Specialties
\nDesigning and implementing knowledge architectures, system design and integration, knowledge management practices and systems, knowledge-based engineering, e-learning, instructional design and delivery, social networking analysis, inter-agency and inter-organizational knowledge sharing

" }, - + { - + "serial": 173380, "name": "Johnny Hughes", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_173380.jpg", @@ -10420,9 +10420,9 @@ "twitter": "", "bio": "

CentOS-4 Lead Developer, CentOS-5 updates, CentOS-6 updates. If you are looking for me in the CentOS IRC channels or Forums I am hughesjr there.

\n

I first started working with UNIX in the early 1980’s with an AT&T 3b2 server.

\n

I first started working with Linux in 1995 with a Red Hat Linux webserver.

\n

I retired from the US Navy in 1997 (I spent ~20 years as a Engineering Laboratory Technician on Nuclear Submarines while in the US Navy). Lots of that time I also spent as a UNIX systems administrator working with AT&T and HP-UX servers.

\n

Since 1997 I have been a UNIX (HP-UX, AIX, Solaris), Windows (NT, 2000, 2003), Linux (Red Hat, CentOS) systems administrator, an Oracle DBA, a Senior Systems Analyst and a Network Engineer … and finally now a Software Engineer for the CentOS Project

" }, - + { - + "serial": 77939, "name": "Nathan Humbert", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_77939.jpg", @@ -10432,9 +10432,9 @@ "twitter": "", "bio": "

Nathan Humbert is a Senior Software Engineer at New Relic where he works with an amazing group of people to build software that helps people understand how their software is functioning.

" }, - + { - + "serial": 159719, "name": "Michael Hunger", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_159719.jpg", @@ -10444,9 +10444,9 @@ "twitter": "mesirii", "bio": "

Michael Hunger has been passionate about software development for a long time. He is particularly interested in the people who develop software, software craftsmanship, programming languages, and improving code.

\n

For the last two years he has been working with Neo Technology on the Neo4j graph database. As the project lead of Spring Data Neo4j he helped developing the idea to become a convenient and complete solution for object graph mapping. He is also taking care of Neo4j cloud hosting efforts.

\n

As a developer he loves to work with many aspects of programming languages, learning new things every day, participating in exciting and ambitious open source projects and contributing to different programming related books. Michael is a frequent speaker at industry events and is an active editor and interviewer at InfoQ.

" }, - + { - + "serial": 164756, "name": "Pete Hunt", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_164756.jpg", @@ -10456,9 +10456,9 @@ "twitter": "floydophone", "bio": "

Hacker at Facebook, currently leading Instagram.com web engineering. Formerly Facebook Photos and Videos lead.

\n

Core team on http://facebook.github.io/react/ and http://github.com/facebook/huxley

" }, - + { - + "serial": 4265, "name": "Kirsten Hunter", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_4265.jpg", @@ -10468,9 +10468,9 @@ "twitter": "synedra", "bio": "

Kirsten Hunter is an unapologetic hacker and passionate advocate for the development community. Her technical interests range from graph databases to cloud services, and her experience supporting and evangelizing REST APIs has given her a unique perspective on developer success. In her copious free time she’s a gamer, fantasy reader, and all around rabble-rouser. Code samples, recipes, and philosophical musings can be found on her blog, Princess Polymath.

" }, - + { - + "serial": 152299, "name": "Vanessa Hurst", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_152299.jpg", @@ -10480,9 +10480,9 @@ "twitter": "DBNess", "bio": "

Vanessa is a data-focused developer and the CEO of CodeMontage, which empowers coders to improve their impact on the world. She believes computing is one of the most efficient and effective ways to improve the human experience. She founded Developers for Good and co-founded WriteSpeakCode and Girl Develop It. Previously, she wrangled data at Paperless Post, Capital IQ, and WealthEngine. Vanessa holds a B.S. in Computer Science with a minor in Systems and Information Engineering from the University of Virginia.

\n

Vanessa’s work in technology education and social change has appeared on the TODAY show, NPR, Al Jazeera America, Entrepreneur, The New York Times, Fast Company, and other media. She serves as a cast member for Code.org, a nonprofit dedicated to ensuring every student has the opportunity to learn to code, which reached over 20 million students in 2013 alone.

" }, - + { - + "serial": 173235, "name": "Mihail Irintchev", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_173235.jpg", @@ -10492,9 +10492,9 @@ "twitter": "irintchev", "bio": "

Born and living in Sofia, Bulgaria

\n

Lived in Ohio for a while

\n

Bachelor’s degree in CS from the American University in Bulgaria, class of 2003

\n

MSc from Sofia University – Electronic Business, class of 2006

\n

Web developer since 2003

\n

Co-founder of the BulgariaPHP user group

\n

Working at SiteGround since 2003

\n

Interested in history, military vehicles, motorcycles, science fiction, rock’n’roll

" }, - + { - + "serial": 122564, "name": "Phil Jackson", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_122564.jpg", @@ -10504,9 +10504,9 @@ "twitter": "", "bio": "

Phil Jackson is Development Community Advocate for SoftLayer. He helps customers and partners integrate with the SoftLayer’s API. He also architects the company’s Drupal websites, writes API documentation, and maintains the developer blog. Formerly, he was a Sales Engineer building internal tools and providing technical consultation for potential and existing customers. Jackson started his career in webhosting at Ev1Servers where he led the training department. With a passion for technology that started at a young age, he has developed skills in a variety of scripting and programming languages and enjoys sharing his knowledge with the tech community.

" }, - + { - + "serial": 173189, "name": "Daniel Jacobson", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_173189.jpg", @@ -10516,9 +10516,9 @@ "twitter": "daniel_jacobson", "bio": "

Daniel Jacobson is the Director of Engineering for the API and Playback Services at Netflix, responsible for delivering more than one billion streaming hours a month to Netflix users on more than 1,000 device types. Prior to Netflix, he was Director of Application Development at NPR, leading the development of NPR\u2019s custom content management system. He also created NPR\u2019s API which became the distribution channel for getting NPR content to mobile platforms, member station sites throughout the country as well as to the public developer community. Daniel also co-authored the O\u2019Reilly book, \u201cAPIs: A Strategy Guide.

" }, - + { - + "serial": 173435, "name": "Paul Kehrer", "photo": null, @@ -10528,9 +10528,9 @@ "twitter": "reaperhulk", "bio": "

Paul Kehrer is the crypto expert on the Barbican project, an open source key management platform for OpenStack. He has experience running a public certificate authority as well as doing significant open source work in cryptography by writing and maintaining r509, a ruby library for managing a certificate infrastructure and cryptography, a modern python crypto library.

" }, - + { - + "serial": 120025, "name": "Benjamin Kerensa", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_120025.jpg", @@ -10540,9 +10540,9 @@ "twitter": "bkerensa", "bio": "

Benjamin Kerensa is an internationally recognized open source evangelist, community manager, writer and speaker who currently is a Firefox Release Manager and Community Manager for Trovebox.

" }, - + { - + "serial": 182019, "name": "Saud Khan", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_182019.jpg", @@ -10552,9 +10552,9 @@ "twitter": "", "bio": "

Over the past years Saud has worked on upgrading the user experience of Twitter for Android including search, trends and collections. From bringing search suggestions to the amazing photo grid of search results, Saud has worked with the designers to bring advanced search features in a mix of Twitter and Android design paradigm.

\n

Before that Saud developed a generic call management library used to bring VoIP capabilities in various mobile devices including Android, iOS and Windows CE. Additionally he worked on porting the solution over to myriad of embedded platforms like radio gateways and call boxes.

" }, - + { - + "serial": 157931, "name": "Shashank Khandelwal", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_157931.jpg", @@ -10564,9 +10564,9 @@ "twitter": "gazeti", "bio": "

As a Design and Technology Fellow at the Consumer Financial Protection Bureau, Shashank Khandelwal develops software to make consumer financial products work for the American people. He have over eight years of software development experience and his career spans multiple domains, languages and technologies. More recently, Shashank used a data-driven approach to study human health behavior. He wrote software to collect large-scale social media data, filter, and analyze it using machine learning techniques, and released it as a disease surveillance platform for the academic, private, and government health community. One of his earlier Twitter-based studies has been featured on NPR (“What Twitter Knows about Flu” by Jordan Calmes on October 14, 2011). Previously, he wrote document management software and worked on the vacation packages team at Orbitz (in Chicago). Over the years, he has written software in Python, Java, PHP and other languages using a variety of tools, applications and libraries.

" }, - + { - + "serial": 172929, "name": "Kevin Kluge", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_172929.jpg", @@ -10576,9 +10576,9 @@ "twitter": "kevinkluge", "bio": "

Kevin is Vice President of Engineering at Elasticsearch, which produces open source search and analytics software. Previously Kevin was at Citrix Systems where he was responsible for engineering of Citrix CloudPlatform, powered by Apache CloudStack. Kevin has also held senior engineering positions at several other start-ups, including Cloud.com and Zimbra, and Sun Microsystems. Kevin has a B.S. and M.S. in Computer Science from Stanford University.

" }, - + { - + "serial": 144736, "name": "Francesca Krihely", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_144736.jpg", @@ -10588,9 +10588,9 @@ "twitter": "francium", "bio": "

Francesca is the Community Lead for MongoDB, managing the fastest growing community in Big Data. In this role she is responsible for managing acquisition and development of new community members and build out open source contributions to MongoDB’s server and project ecosystem. She holds a BA from Oberlin College in History and Sociology and is a recovering Philosophy nerd.

" }, - + { - + "serial": 125113, "name": "Spencer Krum", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_125113.jpg", @@ -10600,9 +10600,9 @@ "twitter": "", "bio": "

Spencer is a DevOps Engineer at UTi Worldwide Inc, a logistics firm. He has been using Puppet for 4 years. He is one of the co-authors of Pro Puppet 2nd edition from Apress. He lives and works in Portland and enjoys cycling and Starcraft.

" }, - + { - + "serial": 6380, "name": "Bradley Kuhn", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_6380.jpg", @@ -10612,9 +10612,9 @@ "twitter": "bkuhn_ebb_org", "bio": "

Bradley M. Kuhn is a Director of FSF, President of the Software Freedom Conservancy, and won the O’Reilly Open Source Award at OSCON 2012. Kuhn began his work in the Free, Libre and Open Source Software (FLOSS) Movement as a volunteer in 1992, when he became an early adopter of the GNU /Linux operating system, and began contributing to various FLOSS projects. He worked during the 1990s as a system administrator and software developer for various companies, and taught AP Computer Science at Walnut Hills High School in Cincinnati. Kuhn’s non-profit career began in 2000, when he was hired by the Free Software Foundation. As FSF’s Executive Director from 2001-2005, Kuhn led FSF’s GPL enforcement, launched its Associate Member program, and invented the Affero GPL. From 2005-2010, Kuhn worked as the Policy Analyst and Technology Director of the Software Freedom Law Center. Kuhn holds a summa cum laude B.S. in Computer Science from Loyola University in Maryland, and an M.S. in Computer Science from the University of Cincinnati. His Master’s thesis (an excerpt from which won the Damien Conway Award for Best Technical Paper at this conference in 2000) discussed methods for dynamic interoperability of FLOSS languages. Kuhn has a regular blog and a microblog (@bkuhn on identi.ca).

" }, - + { - + "serial": 170822, "name": "Andreas Kunz", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_170822.jpg", @@ -10624,9 +10624,9 @@ "twitter": "aku_dev", "bio": "

Working as Software Developer and Architect at SAP since 2005, focusing on user interfaces.
\nMember of the UI5 (HTML/JS UI library created by SAP) development team since its inception and spending considerable time to work around browser bugs…

" }, - + { - + "serial": 62981, "name": "Lars Kurth", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_62981.jpg", @@ -10636,9 +10636,9 @@ "twitter": "lars_kurth", "bio": "

Lars Kurth had his first contact with the open source community in 1997 when he worked on various parts of the ARM toolchain. This experience led Lars to become a passionate open source enthusiast who worked with and for many open source communities over the past 15 years. Lars contributed to projects such as GCC, Eclipse, Symbian and Xen and became the open source community manager for Xen.org in 2011. Lars is an IT generalist with a wide range of skills in software development and methodology. He is experienced in leading and building engineering teams and communities, as well as constructing marketing, product management, and change programs impacting 1000s of users. He has 17 years of industry experience in the infrastructure, tools and mobile sectors working at ARM, Citrix, Nokia and the Symbian Foundation.

" }, - + { - + "serial": 177245, "name": "Shadaj Laddad", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_177245.jpg", @@ -10648,9 +10648,9 @@ "twitter": "", "bio": "

Shadaj is a 14 year old who loves to program. He has programmed in Logo, NXT Mindstorms, Ruby, Python, C, Java, and Scala\u2014his favorite. Shadaj hosts his projects on GitHub, and has an educational channel on Youtube. He has presented at Scala Days 2013, Scala Days 2012, and the Bay Area Scala Enthusiast group showing his Scala projects. Besides programming, he likes Math and Science. Shadaj is also an active member of his school community as Student Council President. He loves spreading a love of technology, and started TechTalks\u2014a program that brings guest speakers to share their knowledge and enthusiasm with students at his school. When not doing his school work or programming, he plays guitar, sitar, and games, some of which he created.

" }, - + { - + "serial": 172658, "name": "Michael Laing", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_172658.jpg", @@ -10660,9 +10660,9 @@ "twitter": "", "bio": "

NYTimes; Systems Architect; 2011-present \u2014 Architect of nyt\u2a0da\u0431rik

\n

United Nations; IT Evangelist; 2002-2011 \u2014 Principal advisor to CIO

\n

Blue Note Technology; CEO etc; 1998-2002

\n
    \n\t
  • 2001 New England Web Design award
  • \n\t
  • 2000 Software and Information Industry Association Codie award
  • \n
\n

Harvard University Tech Eval Group; consultant; 1985-1998

\n

Education:

\n
    \n\t
  • Harvard Business School
  • \n\t
  • United States Military Academy
  • \n
\n

Personal:

\n
    \n\t
  • Sings
  • \n\t
  • Dances
  • \n
" }, - + { - + "serial": 173308, "name": "Alex Lakatos", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_173308.jpg", @@ -10672,9 +10672,9 @@ "twitter": "lakatos88", "bio": "

Alex Lakatos is a Mozilla Representative and contributor to the Mozilla project for the past three years, based in Cluj-Napoca, the heart of Transylvania. He’s a JavaScript developer building on the open web, trying to push it’s boundaries every day. You can check out his github profile or get in touch on twitter. When he\u2019s not programming, he likes to travel the world, so it\u2019s likely you\u2019ll bump into him in an airport lounge.

" }, - + { - + "serial": 137347, "name": "James Lance", "photo": null, @@ -10684,9 +10684,9 @@ "twitter": "", "bio": "

James Lance is a developer at Bluehost.com. He has been developing in Perl for over 10 years. He is also an active member in the Utah Open Source community and Core Team member of the OpenWest Conference

\n

When he\u2019s not behind a computer you can usually find a yo-yo in his hand. He also enjoys Ham radio, camping, and various forms of BBQing (smoking in particular).

" }, - + { - + "serial": 116050, "name": "Yoav Landman", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_116050.jpg", @@ -10696,9 +10696,9 @@ "twitter": "yoavlandman", "bio": "

Yoav is the CTO of JFrog, the Artifactory Binary Repository creators, JavaOne 2011 and 2013 Duke Choice Awards winner; Yoav laid the foundation to JFrog’s flagship product in 2006 when he founded Artifactory as an open source project.
\nIn 2008 he co-founded JFrog where he leads the future of products like Artifactory and, more recently, Bintray.
\nPrior to starting up JFrog, Yoav was a software architect and consultant in the field of configuration management and server-side JVM applications.
\nYoav blogs at jfrog.org and java.net.

" }, - + { - + "serial": 172864, "name": "Markus Lanthaler", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_172864.jpg", @@ -10708,9 +10708,9 @@ "twitter": "markuslanthaler", "bio": "

Markus has been programming since the age of 9. After a short journey with Perl, he quickly moved to PHP for most of his Web-related programming. He implemented his first PHP-based CMS more than 10 years ago (about the time of Drupal\u2019s first release), worked with Symfony when it was still Mojavi, and launched his first online shop in 2003. Since then he has been programming almost everything from microcontrollers on Smart Cards (in assembler) up to large-scale distributed systems. More recently Markus\u2019 focus shifted to large, distributed Web applications. He is one of the core designers of JSON-LD, the inventor of Hydra, and a W3C Invited Expert.

" }, - + { - + "serial": 183609, "name": "Walter `wxl` Lapchynski", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_183609.jpg", @@ -10720,9 +10720,9 @@ "twitter": null, "bio": "

Walter \u2665 everything about bikes & computers. He’s a Release Manager for Lubuntu & the Team Leader for Ubuntu Oregon. Bioinformatics looms in his future, as much as his obsession with Unicode allows\u2026

" }, - + { - + "serial": 182081, "name": "Chris Launey", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_182081.jpg", @@ -10732,9 +10732,9 @@ "twitter": "clauney", "bio": "

Chris Launey is the Director of Cloud Services & Architecture in the core
\narchitecture and engineering group at the Walt Disney Company. His
\norganization lives at the intersection of the quickly shifting demands of
\na media and entertainment company and the mission of a managed service
\nprovider to maintain a stable, scalable, and efficient technology
\nplatform. Chris team launched Disney\u00b9s first true on-demand IaaS offering
\nand built a service integration platform to cloudify more internal
\ntechnology services. His team is now building on that to make pubic
\nservices easier to broker and consume for the Walt Disney Company.

" }, - + { - + "serial": 151665, "name": "Mark Lavin", "photo": null, @@ -10744,9 +10744,9 @@ "twitter": "", "bio": "

Mark is a lead Python/Django developer and technical manager at Caktus Consulting Group in Carrboro, NC. He also runs a small homebrewing website written in Django called brewedbyus.com. He came to Python web development after a few years pricing derivatives on Wall Street. Mark maintains a number of open source projects primarily related to Django development and frequently contributes back to projects used by Caktus. When he isn’t programming, Mark enjoys spending time with his wife and daughter, brewing beer, and running.

" }, - + { - + "serial": 152106, "name": "Ed Leafe", "photo": null, @@ -10756,9 +10756,9 @@ "twitter": "EdLeafe", "bio": "

Ed has been a developer for over 20 years, and is one of the original developers involved with the creation of OpenStack. After leaving the Microsoft world over a decade ago he has been primarily a Python developer, and has spoken as several US PyCons, as well as PyCon Australia and PyCon Canada. He has been working with Rackspace for the past 6 years as a senior Python developer. He also throws a mean Frisbee.

" }, - + { - + "serial": 74565, "name": "Jonathan LeBlanc", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_74565.jpg", @@ -10768,9 +10768,9 @@ "twitter": "jcleblanc", "bio": "

Jonathan LeBlanc is an Emmy award winning software engineer, author of the O\u2019Reilly book “Programming Social Applications”, and the head of North American Developer Evangelism at PayPal. Specializing in user identity concepts, hardware to web interconnectivity, data mining techniques, as well as open source initiatives around social engagement, Jonathan works on the development of emerging initiatives towards building a more user-centric web.

" }, - + { - + "serial": 157509, "name": "Robert Lefkowitz", "photo": null, @@ -10780,9 +10780,9 @@ "twitter": "sharewaveteam", "bio": "

Robert “r0ml” Lefkowitz is the CTO at Sharewave, a startup building an investor management portal. This year, he was a resident at the winter session of Hacker School. He is a Distinguished Engineer of the ACM.

" }, - + { - + "serial": 124700, "name": "Janice Levenhagen-Seeley", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_124700.jpg", @@ -10792,9 +10792,9 @@ "twitter": "", "bio": "

Janice is the Executive Director of ChickTech. She has a BS in Computer Engineering from Oregon State University and an MBA from Willamette University. She believes strongly that the diversity and strengths that women can bring will push high tech to even more impressive heights. Her inspiration for creating ChickTech came from her own experiences in computer engineering and the realization that the percentage of women in engineering isn\u2019t going to get higher by itself.

" }, - + { - + "serial": 161517, "name": "Pernilla Lind", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_161517.jpg", @@ -10804,9 +10804,9 @@ "twitter": "p3rnilla", "bio": "

Pernilla is the Community Manager at Neo Technology, the company behind the open source project Neo4j. Pernilla loves to connect and spread the word of graphs and bring new ideas into life. She is an engaging speaker and teacher and hasthe mission in life to spread knowledge about technology.Pernilla spent some times in Kenya with the massai people for a year to build up their infrastructure and engage their community to do income generating activities. Before that she lived in Gambia to help up a womens organization.

\n

Pernilla is also an engaging member and organizer of Geek Girl Meetup, a network for women in tech in Sweden, where she plans and organizes conferences and meetups. She is always involved in different technoogy ecosystems and loves to meet new people and learn about new cool tech stuff. To be a role model and stand up for peoples right it\u2019s something Pernilla always do.

\n

Cats, people, philantrophy, graphs, open data, women in tech, innovation, crazy ideas and Doctor Who are subjects Pernilla can talk about forever.

" }, - + { - + "serial": 77469, "name": "Philip Lindsay", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_77469.jpg", @@ -10816,9 +10816,9 @@ "twitter": "RancidBacon", "bio": "

Philip Lindsay (also known as “follower” from rancidbacon.com ) writes documentation, creates code libraries, develops example projects and provides developer support for companies including Pebble Technology, SparkFun Electronics, Arduino and other clients.

\n

Tim O’Reilly once called Philip a “troublemaker” for his early Google Maps reverse engineering efforts.

\n

He has a particular interest in the areas where design, art, craft and technology intersect.

\n

Follow his project logs at Labradoc.

" }, - + { - + "serial": 173403, "name": "Steve Lipner", "photo": null, @@ -10828,9 +10828,9 @@ "twitter": "msftsecurity", "bio": "

Steve Lipner, Senior Director of Security Engineering Strategy, Microsoft\u2019s Trustworthy Computing Group

\n

As the senior director of security engineering strategy in Microsoft Corp.\u2019s Trustworthy Computing Group, Steve Lipner is responsible for Microsoft\u2019s Security Development Lifecycle team, including the development of programs that provide improved product security and privacy to Microsoft\u00ae customers. Additionally, Lipner is responsible for Microsoft\u2019s engineering strategies related to the company\u2019s End to End Trust initiative, aimed at extending Trustworthy Computing to the Internet.

\n

Lipner has more than 35 years experience as a researcher, development manager and general manager in information technology security, and is named as inventor on thirteen U.S. patents in the field of computer and network security. He holds both an S.B. and S.M. degree from the Massachusetts Institute of Technology, and attended the Harvard Business School\u2019s Program for Management Development.

" }, - + { - + "serial": 171564, "name": "Joshua Long", "photo": null, @@ -10840,9 +10840,9 @@ "twitter": "starbuxman", "bio": "

Josh Long is the Spring developer advocate at [Pivotal](http://gopivotal.com). Josh is the lead author on 4 books and instructor in one of Safari’s best-selling video series, all on Spring. When he’s not hacking on code, he can be found at the local Java User Group or at the local coffee shop. Josh likes solutions that push the boundaries of the technologies that enable them. His interests include cloud-computing, business-process management, big-data and high availability, mobile computing and smart systems. He blogs on [spring.io](https://spring.io/team/jlong) or on [his personal blog](http://joshlong.com) and [on Twitter (@starbuxman)](http://twitter.com/starbuxman).

" }, - + { - + "serial": 141574, "name": "Tim Mackey", "photo": null, @@ -10852,9 +10852,9 @@ "twitter": "XenServerArmy", "bio": "

Tim Mackey is a technology evangelist for XenServer and CloudStack within the Citrix Open Source Business Office and focused on server virtualization and cloud orchestration technical competencies. When he joined Citrix in 2003, he was an engineer and architect focused on low impact application performance monitoring. During his tenure at Citrix he has held roles in the XenServer team as an architect, consultant, product manager and product marketing.

" }, - + { - + "serial": 109140, "name": "Anil Madhavapeddy", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_109140.jpg", @@ -10864,9 +10864,9 @@ "twitter": "avsm", "bio": "

Dr. Anil Madhavapeddy is a Senior Research Fellow at Wolfson College and is based in the Cambridge University Computer Laboratory, investigating programming models for cloud computing. Anil was on the original team at Cambridge that developed the Xen hypervisor, and subsequently served as the senior architect and product director for XenSource/Citrix before returning to academia.

\n

Anil has a diverse background in industry at Network Appliance, Citrix, NASA, and Internet Vision. He is an active member of the open source development community with the OpenBSD operating system and more, and the co-chair of this year’s Commercial Uses of Functional Programming conference.

" }, - + { - + "serial": 173303, "name": "Rachel Magario", "photo": null, @@ -10876,9 +10876,9 @@ "twitter": "rachelmagario", "bio": "

Rachel Magario is an Assistive Technology Specialist at Pacer Simon Technology Center. As the first totally blind interaction designer, Rachel is known as a leader and visionary who has triumphed over adversity consistently throughout her life.

\n

Rachel has presented at SXSW Interactive and consulted for various companies and universities throughout the United States on accessibility. Rachel has an MBA with a concentration in Marketing and a Masters in Interaction design from the University of Kansas.

\n

Magario\u2019s dream is that usability and accessibility can be considered from the start of a project and not as an after thought. She believes this would open the door for access of information and for accessible tools. This shift would allow her and others to pursue their careers of choice and live with the dignity that should be the right of every human.

\n

Rachel has been involved with accessibility consulting and advocacy since the early 2000s. Throughout the years, Rachel served as an accessibility consultant to several university related projects and non-profit sites that were required to comply with section 508.

\n

Rachel soon realized through her experience that accessibility issues often involve problems of usability that affect anyone who accesses information. When Rachel started her Masters in Interaction Design, she experienced the lack of accessibility in the design tools that she was using as well as in end products coming out of these tools.
\nSince then, Rachel has made her mission to research and develop models and prototypes of accessible user experience. She enjoys working closely with designers and developers to ensure standards are met and to create awareness of the importance of accessible user experience.

" }, - + { - + "serial": 173324, "name": "Allison Mankin", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_173324.jpg", @@ -10888,9 +10888,9 @@ "twitter": "", "bio": "

Allison is the Director of Verisign Labs, a research organization focusing on long-term evolution of the
\nInternet infrastructure and on open standards-based prototyping. She has been active in Internet engineering
\nand research for over 25 years, including having served at the Internet Engineering Task Force as an area
\ndirector for 10 of those years. She is best known having co-led the IPng Selection Process at IETF (long ago).
\nPast open source projects include open internet conferencing, multicast and IPv6. Her research and RFCs have been primarily in the areas of TCP and DNS and their security.

" }, - + { - + "serial": 118998, "name": "Jonathon Manning", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_118998.jpg", @@ -10900,9 +10900,9 @@ "twitter": "desplesda", "bio": "

Jon Manning is the co-founder of Secret Lab, an independent game development studio based in Hobart, Tasmania, Australia. He\u2019s worked on apps of all sorts, ranging from iPad games for children to instant messaging clients. He\u2019s a Core Animation demigod, and frequently finds himself gesticulating wildly in front of classes full of eager-to-learn iOS developers.

\n

Jon is currently writing “Mobile Game Development With Unity” and “iOS Game Development Cookbook”, both for O’Reilly, and is the co-author of the books “Learning Cocoa with Objective-C Third Edition” (O’Reilly, 2012 and 2014), “iPhone and iPad Game Development For Dummies” (Wiley, 2010) and “Unity Mobile Game Development For Dummies” (Wiley, 2011).

\n

Jon is the world\u2019s biggest Horse_ebooks fan.

" }, - + { - + "serial": 6931, "name": "Joshua Marinacci", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_6931.jpg", @@ -10912,9 +10912,9 @@ "twitter": "joshmarinacci", "bio": "

Ask me about HTML Canvas, mobile apps, and visual design. Or 3D printing and wearable computing. Or just ask me to rant about Java.

\n

Josh Marinacci is a blogger and co-author of “Swing Hacks” and “Building Mobile Apps with Java” for O’Reilly. He is currently a researcher for Nokia.

\n

He previously worked on webOS at Palm and JavaFX, Swing, NetBeans, and the Java Store at Sun Microsystems.

\n

Josh lives in Eugene, Oregon and is passionate about open source technology & great user experiences.

" }, - + { - + "serial": 173388, "name": "Elaine Marino", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_173388.jpg", @@ -10924,9 +10924,9 @@ "twitter": "elaine_marino", "bio": "

Elaine Marino is a Ruby on Rails coder, marketer and entrepreneur with a 13-year background in strategic marketing and brand-building for Fortune 500 companies. Her ability to communicate effectively, manage large-scale projects, build brands and communities as well as web applications, puts her in a unique position within the software sector. There is a sweet-spot where technology enhances the lives of people and communities — that is where she thrives.

\n

She founded LadyCoders Boulder, a 2-day career career conference for 60 women software engineers, hosted at Google Boulder. LadyCoders Boulder provides tangible, real-world advice to women engineers to advance their careers.

" }, - + { - + "serial": 164144, "name": "Will Marshall", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_164144.jpg", @@ -10936,9 +10936,9 @@ "twitter": "wsm1", "bio": "

Will is responsible for setting the company\u2019s vision and and for architecting the company strategy. Previously, Will was a Scientist at NASA/USRA where he served as Co-Investigator for PhoneSat, Science Team member on the LCROSS and LADEE lunar missions. He led research projects in orbital space debris remediation. Will has published over 30 articles in scientific publications. Will received his Ph.D. in Physics from the University of Oxford and was a Postdoctoral Fellow at Harvard University.

" }, - + { - + "serial": 5199, "name": "Alex Martelli", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_5199.jpg", @@ -10948,9 +10948,9 @@ "twitter": "", "bio": "

Alex Martelli wrote “Python in a Nutshell” and co-edited
\n“Python Cookbook”. He’s a PSF member, and won the 2002 Activators’ Choice Award and the 2006 Frank Willison Award for contributions to the Python community. He works as Senior Staff Engineer for Google. You can read some PDFs and
\nwatch some videos of his past presentations.

" }, - + { - + "serial": 173465, "name": "Ritchie Martori", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_173465.jpg", @@ -10960,9 +10960,9 @@ "twitter": "", "bio": "

Ritchie Martori is Node.js and Mobile developer at StrongLoop where he focuses on LoopBack, an open source mobile backend-as-a-service. He previously authored deployd, another mBaaS in use by over 20,000 developers to create their backend APIs, and is a contributor to Angular.js.

" }, - + { - + "serial": 17378, "name": "Matt May", "photo": null, @@ -10972,9 +10972,9 @@ "twitter": null, "bio": "

Matt May
\nAccessibility Evangelist, Open Source and Accessibility
\nAdobe

\n

Matt May is a developer, technologist, and accessibility advocate who is responsible for working internally and externally with Adobe product teams and customers to address accessibility in Adobe products, ensure interoperability with assistive technologies, and make customers aware of the many accessibility features that already exist in Adobe products.

\n

Prior to joining Adobe, May worked for W3C/WAI on many of the core standards in web accessibility, led the Web Standards Project\u2019s Accessibility Task Force, helped to architect one of the first online grocery sites, HomeGrocer.com, and co-founded Blue Flavor, a respected web and mobile design consultancy. He is a member of the W3C/WAI Web Content Accessibility Guidelines Working Group and co-edited the first JavaScript techniques document for WCAG 2.0 in 2001.

\n

May is an accomplished speaker, having presented at dozens of conferences including Web 2.0 Expo, WebVisions, SXSW Interactive, CSUN Conference on Technology and Persons with Disabilities, Podcast and Portable Media Expo, Web Design World Seattle, Gilbane CMS Conference, and the International World Wide Web Conference, to name just a few.

" }, - + { - + "serial": 142336, "name": "Steve Mayzak", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_142336.jpg", @@ -10984,9 +10984,9 @@ "twitter": "", "bio": "

Steve Mayzak heads up the Systems engineering team at Elasticsearch. Having been in the software industry for over 15 years he has worked as a Developer, Architect and most recently as a Systems engineering leader. Steve loves bringing cutting edge Open Source technology to customers large and small and helping them solve some of their biggest and most interesting challenges.

" }, - + { - + "serial": 182080, "name": "Caroline McCrory", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_182080.jpg", @@ -10996,9 +10996,9 @@ "twitter": "CloudOfCaroline", "bio": "

Caroline is Senior Director at Gigaom Research. Caroline has a background in network and security engineering, enterprise operations and has served at companies such as ServiceMesh, VMware, Cisco, EMC, Siemens, CSC, the BBC and Motorola. Most recently, Caroline was Head of Product at Piston Cloud Computing, an enterprise OpenStack software company.

" }, - + { - + "serial": 74852, "name": "Matthew McCullough", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_74852.jpg", @@ -11008,9 +11008,9 @@ "twitter": "matthewmccull", "bio": "

Matthew McCullough, Training Pioneer for GitHub, is an energetic 15 year veteran of enterprise software development, world-traveling open source educator, and co-founder of a US consultancy. All of these activities provide him avenues of sharing success stories of leveraging Git and GitHub. Matthew is a contributing author to the Gradle and Jenkins O’Reilly books and creator of the Git Master Class series for O’Reilly. Matthew regularly speaks on the No Fluff Just Stuff conference tour, is the author of the DZone Git RefCard, and is President of the Denver Open Source Users Group.

" }, - + { - + "serial": 172994, "name": "Chris McEniry", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_172994.jpg", @@ -11020,9 +11020,9 @@ "twitter": "macmceniry", "bio": "

Chris “Mac” McEniry is a practicing sysadmin responsible for running a large ecommerce and gaming service. He’s been working and developing in an operational capacity for 15 years. In his free time, he builds tools and thinks about efficiency.

" }, - + { - + "serial": 152376, "name": "Patrick McFadin", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_152376.jpg", @@ -11032,9 +11032,9 @@ "twitter": null, "bio": "

Patrick McFadin is regarded as a foremost expert for Apache Cassandra and data modeling. As Chief Evangelist for Apache Cassandra and consultant working for DataStax, he has been involved in some of the biggest deployments in the world.

" }, - + { - + "serial": 183246, "name": "Patrick McGarry", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_183246.jpg", @@ -11044,9 +11044,9 @@ "twitter": "", "bio": "

Patrick is currently incarnated as a Director of Community at Red Hat working to spread the Ceph gospel. An experienced community manager, gamer, mischief maker, and all around geek, Patrick spent five years writing and managing Slashdot under the nomme du keyboard
\n‘scuttlemonkey.’ Patrick enthusiastically helps companies to understand and adopt Open Source ideals and continues to be a strong advocate of FOSS on the desktop and in the enterprise. He has strong feelings about tomatoes, longs for his deep, dark cave, and still
\nhates writing these bios.

" }, - + { - + "serial": 172824, "name": "Mark McLoughlin", "photo": null, @@ -11056,9 +11056,9 @@ "twitter": "markmc_", "bio": "

Mark McLoughlin is a consulting engineer at Red Hat and has spent over a decade contributing to and leading open source projects like GNOME, Fedora, KVM, qemu, libvirt, oVirt and, of course, OpenStack.

\n

Mark is a member of OpenStack’s technical committee and the OpenStack Foundation board of directors. He contributes mostly to Oslo, Nova and TripleO but will happily dive in to any project.

\n

Mark is responsible for Red Hat’s OpenStack technical direction from the CTO office.

" }, - + { - + "serial": 171621, "name": "Harrison Mebane", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_171621.jpg", @@ -11068,9 +11068,9 @@ "twitter": "harrisonmebane", "bio": "

After several years spent on a PhD in theoretical physics, I recently entered the “big data” community to apply what I’ve learned. I work primarily in Python but am always looking for new tools. I work closely with data scientists and engineers skilled in machine learning and data pipelining.

" }, - + { - + "serial": 153566, "name": "Gian Merlino", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_153566.jpg", @@ -11080,9 +11080,9 @@ "twitter": null, "bio": "

Gian is a contributor to the Kafka, Storm, and Druid open source projects and a developer at Metamarkets. He previously worked at Yahoo!, where he was responsible for its worldwide server deployment and configuration management platform. He holds a BS in Computer Science from California Institute of Technology

" }, - + { - + "serial": 104652, "name": "Justin Miller", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_104652.jpg", @@ -11092,9 +11092,9 @@ "twitter": "incanus77", "bio": "

Justin Miller is mobile lead at Washington, DC-based Mapbox working on the future of open mapping. He created the SQLite-based MBTiles open map tile format, contributed the leading offline map implementation for iOS, wrote the Simple KML parser, contributes to the Mac version of the TileMill map-making studio, and does it all in the open here. He lives and works here in Portland.

" }, - + { - + "serial": 175488, "name": "Katie Miller", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_175488.jpg", @@ -11104,9 +11104,9 @@ "twitter": "codemiller", "bio": "

Katie Miller is an OpenShift Developer Advocate at Red Hat, where she tries to squeeze as many different programming languages into her workdays as possible. The former newspaper journalist loves language, whether code or copy, and will pedantically edit any text you let her touch. Katie is a Co-Counder of the Lambda Ladies group for women in functional programming and one of the organisers of the Brisbane Functional Programming Group.

" }, - + { - + "serial": 171147, "name": "Michael Minella", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_171147.jpg", @@ -11116,9 +11116,9 @@ "twitter": "michaelminella", "bio": "

Michael Minella is a software engineer, teacher and author with over a decade of enterprise development experience. Michael was a member of the expert group for JSR-352 (java batch processing). He currently works for Pivotal as the project lead for the Spring Batch project as well as an instructor at DePaul University. Michael is the author of Pro Spring Batch from Apress and the popular Refcard JUnit and EasyMock.

\n

Outside of the daily grind, Michael enjoys spending time with his family and enjoys woodworking, photography, and InfoSec as hobbies.

" }, - + { - + "serial": 4378, "name": "Wade Minter", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_4378.jpg", @@ -11128,9 +11128,9 @@ "twitter": "minter", "bio": "

H. Wade Minter is the CTO and founding team member at TeamSnap, an application that makes managing sports teams and groups easy. He is also the ring announcer for a professional wrestling federation. The two may be related.

" }, - + { - + "serial": 181598, "name": "Manav Mishra", "photo": null, @@ -11140,9 +11140,9 @@ "twitter": "", "bio": "

Manav is currently the Director of Product for HP Helion where he leads the product team responsible for the developer platform and experience.

\n

Manav has been in several product and engineering leadership roles across the tech industry. Most recently, at Google, Manav led the product management team responsible for the core and enterprise products for Google Analytics. Prior to joining Google, Manav spent 10-years at Microsoft leading product teams responsible for Windows (shell user experience, search and file management, cloud integration and the developer platform), Live Mesh (SkyDrive), OneCare and machine-learning based safety and security protection in Outlook, Exchange, Hotmail and Internet Explorer. Manav started his career at Intel as a software engineer working on x86 instruction set optimizations and networking protocols for Linux. He also has several international publications and 50+ patents to his credit.

\n

Manav graduated with a M.S. in Electrical Engineering from Washington State University and is based in San Francisco.

" }, - + { - + "serial": 45968, "name": "Lorna Jane Mitchell", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_45968.jpg", @@ -11152,9 +11152,9 @@ "twitter": null, "bio": "

Lorna is an independent PHP consultant based in Leeds, UK. She leads the joind.in open source project, which provides a platform for real-time, public feedback at community events. She is an experienced event organiser and speaker herself, having hosted the Dutch PHP Conference and co-founded the PHP North West conference and user group. She has spoken at technical events across Europe and beyond, predominantly on technical topics around PHP and APIs, but also on topics around business, projects and open source. She regularly delivers technical training sessions and is also active as a mentor with PHPWomen.org. Author of the book PHP Master from Sitepoint, Lorna loves to write and is regularly published at a number of outlets including netmagazine and of course her own blog lornajane.net.

" }, - + { - + "serial": 173399, "name": "Eric Mittelette", "photo": null, @@ -11164,9 +11164,9 @@ "twitter": "ericmitt", "bio": "

Developer Evangelist since the year 2000 in Microsoft France, Eric animate sessions and keynotes about Microsoft development platform since the beginning of .NET. At this time Eric integrate open source software and framework in his work, and join recently Microsoft Open Technologies, Inc as a Senior Technical Evangelist. Passionate about code algorithm since the 90\u2019s, eric work with C, C++, C# and try to illustrate technologies breakout with simplicity and fun. His background as Agronomic researcher provide him, an different point of view and approach and a real vulgarization expertise.

" }, - + { - + "serial": 159772, "name": "Richard Mortier", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_159772.jpg", @@ -11176,9 +11176,9 @@ "twitter": "mort___", "bio": "

Richard Mortier is Horizon Transitional Fellow in Computer Science at the University of Nottingham. His research focuses on user-centred systems, investigating the challenges that arise when we design and deploy infrastructure technology with which real people must interact. Specific current projects include exokernels for secure high-performance multiscale computing (Mirage); infrastructure for building a market around privacy-preserving third-party access to personal data (Dataware); and novel approaches to deploying and managing personal network services. Prior to joining Nottingham he spent two years as a founder of Vipadia Limited designing and building the Clackpoint and Karaka real-time communications services (acquired by Voxeo Corp.), six years as a researcher with Microsoft Research Cambridge, and seven months as a visitor at Sprint ATL, CA. He received a Ph.D. from the Systems Research Group at the University of Cambridge Computer Laboratory, and a B.A. in Mathematics, also from the University of Cambridge.

" }, - + { - + "serial": 33987, "name": "Diane Mueller", "photo": null, @@ -11188,9 +11188,9 @@ "twitter": null, "bio": "

Diane Mueller is RedHat’s OpenShift Origin Open Source Community Manager. Diane has been designing and implementing products and applications embedded into mission critical systems at F500 corporations for over 20 years She is a thought leader in cloud computing, open source community building, and cat herding.

\n

.Follow her on twitter @pythondj
\n

" }, - + { - + "serial": 156427, "name": "Dan Muey", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_156427.jpg", @@ -11200,9 +11200,9 @@ "twitter": null, "bio": "

I have dedicated over ten years to cPanel and have loved every minute of it: I \u2665 cPanel! Before cPanel I spent ten years at various technology companies working on everything from server and database administration to front/back\u2013end web development and other programming. In my free time I enjoy playing music, time with my wife, and being an uncle.

" }, - + { - + "serial": 147840, "name": "Scott Murray", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_147840.jpg", @@ -11212,9 +11212,9 @@ "twitter": "alignedleft", "bio": "

Scott Murray is a code artist who writes software to create data visualizations and other interactive phenomena. His work incorporates elements of interaction design, systems design, and generative art. Scott is an Assistant Professor of Design at the University of San Francisco, where he teaches data visualization and interaction design. He is a contributor to Processing, and is author of the O’Reilly title “Interactive Data Visualization for the Web”.

" }, - + { - + "serial": 120866, "name": "Henri Muurimaa", "photo": null, @@ -11224,9 +11224,9 @@ "twitter": "henrimuurimaa", "bio": "

Henri Muurimaa, MSc. has been a professional developer and team leader since 1997. Over the years he has used many Java technologies and tools in a plethora of projects ranging from days to dozens of man-years. Lately he has been exploring building desktop-like web applications with Scala and Vaadin. He is a contributor to the Scaladin project and has been a member of the Vaadin team since 2002.

" }, - + { - + "serial": 173146, "name": "Chad Naber", "photo": null, @@ -11236,9 +11236,9 @@ "twitter": "", "bio": "

Chad is a data engineer with more than 12 years of progressive experience in leading, industry-recognized multi-national service organizations such as Intel, Nike and Edmunds.com. He has experience with leading edge technologies such as Redshift, SQL Server , Hadoop (both utilizing the streaming API and direct Java), and Hive with a baseline of experience in Agile data warehousing. Chad currently is working as a big data architect at Intel.

" }, - + { - + "serial": 143674, "name": "Rachel Nabors", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_143674.jpg", @@ -11248,9 +11248,9 @@ "twitter": "RachelNabors", "bio": "

Rachel Nabors is an interaction developer, award-winning cartoonist, and host of the Infinite Canvas Screencast. She deftly blends the art of traditional storytelling with digital media to \u201ctell better stories through code\u201d at her company Tin Magpie. You can catch her as @RachelNabors on Twitter and at rachelnabors.com.

" }, - + { - + "serial": 141881, "name": "David Nalley", "photo": null, @@ -11260,9 +11260,9 @@ "twitter": "ke4qqq", "bio": "

David is a recovering sysadmin with a decade of experience. He’s a committer on the Apache CloudStack (incubating) project, and a contributor to the Fedora Project.

" }, - + { - + "serial": 146540, "name": "Paco Nathan", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_146540.jpg", @@ -11272,9 +11272,9 @@ "twitter": "pacoid", "bio": "

O’Reilly author (Enterprise Data Workflows with Cascading) and a \u201cplayer/coach\u201d who’s led innovative Data teams building large-scale apps for 10+ yrs. Expert in machine learning, cluster computing, and Enterprise use cases for Big Data. Interests: Mesos, PMML, Open Data, Cascalog, Scalding, Python for analytics, NLP.

" }, - + { - + "serial": 109297, "name": "Wynn Netherland", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_109297.jpg", @@ -11284,9 +11284,9 @@ "twitter": "pengwynn", "bio": "

Wynn Netherland has been building the web for nearly twenty years. With a passion for API user experience, he’s a prolific creator and maintainer of Ruby API wrappers. He now spends his days hacking on the GitHub API and is author of several books, most recently Sass and Compass in Action (Manning 2013).

" }, - + { - + "serial": 109468, "name": "Christopher Neugebauer", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_109468.jpg", @@ -11296,9 +11296,9 @@ "twitter": "chrisjrn", "bio": "

Christopher is a Python programmer from Hobart, Tasmania. He\u2019s a Computer Science Honours graduate of the University of Tasmania, and he now works primarily as an Android developer. Working with Android means that his day job involves more Java than he\u2019d like. He has a strong interest in the development of the Australian Python Community \u2014 he is an immediate past convenor of PyCon Australia 2012 and 2013 in Hobart, and is a member of the Python Software Foundation.

\n

In his spare time, Christopher enjoys presenting on Mobile development at Open Source conferences, and presenting on Open Source development at Mobile conferences.

" }, - + { - + "serial": 130731, "name": "Deb Nicholson", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_130731.jpg", @@ -11308,9 +11308,9 @@ "twitter": "", "bio": "

Deb Nicholson works at the intersection of technology and social justice. She has over fifteen years of non-profit management experience and got involved in the free software movement about five years ago when she started working for the Free Software Foundation. She is currently the Community Outreach Director for the Open Invention Network – the defensive patent pool built to protect Linux projects. She is also the Community Manager for GNU MediaGoblin, a brand new federated media hosting program. In her spare time, she serves on the board of OpenHatch, a small non-profit dedicated to identifying and mentoring new free software contributors with a particular interest in building a more diverse free software movement.

" }, - + { - + "serial": 171598, "name": "Niklas Nielsen", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_171598.jpg", @@ -11320,9 +11320,9 @@ "twitter": "quarfot", "bio": "

Niklas is also a distributed systems engineer at Mesosphere and a committer in the Apache Mesos Open Source project. Before joining Mesosphere, Niklas worked at Adobe as a virtual machine and compiler engineer and completed his Master\u2019s degree doing supercomputer debugger design at Lawrence Livermore National Laboratory.

" }, - + { - + "serial": 140811, "name": "Kelley Nielsen", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_140811.jpg", @@ -11332,9 +11332,9 @@ "twitter": null, "bio": "

Kelley is a relatively new kernel developer, who is learning under the excellent guidance of mentors through the Gnome Outreach Program for Women. She is also a co-coordinator for Codechix, which organizes in-person events of all kinds for women and everybody. Before settling on the kernel, she wrote a collection of Linux screen saver demos, aptly titled Xtra Screen Hacks, and a few Android apps. She blogs here.

" }, - + { - + "serial": 173503, "name": "Jesse Noller", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_173503.jpg", @@ -11344,9 +11344,9 @@ "twitter": "jessenoller", "bio": "

Jesse Noller is a long time Python community member, developer and has contributed to everything from distributed systems to front-end interfaces. He\u2019s passionate about community, developer experience and empowering developers everywhere, in any language to build amazing applications. He currently works for Rackspace as a Developer Advocate and open source contributor.

" }, - + { - + "serial": 76338, "name": "Sarah Novotny", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_76338.jpg", @@ -11356,9 +11356,9 @@ "twitter": "sarahnovotny", "bio": "

Sarah Novotny is a technical evangelist and community manager for NGINX. Novotny has run large scale technology infrastructures as a Systems Engineer and a Database administrator for Amazon.com and the ill fated Ads.com. In 2001, she founded Blue Gecko, a remote database administration company with two peers from Amazon. Blue Gecko, was sold to DatAvail in 2012. She\u2019s also curated teams and been a leader in customer communities focused on high availability web application and platform delivery for Meteor Entertainment and Chef.

\n

Novotny regularly talks about technology infrastructure and geek lifestyle. She is additionally a program chair for O’Reilly Media’s OSCON. Her technology writing and adventures as well as her more esoteric musings are found at sarahnovotny.com.

" }, - + { - + "serial": 173452, "name": "Tim Nugent", "photo": null, @@ -11368,9 +11368,9 @@ "twitter": "The_McJones", "bio": "

Tim Nugent pretends to be a mobile app developer, game designer, PhD student and now he even pretends to be an author (he co-wrote the latest update to \u201cLearning Cocoa with Objective-C\u201d for O\u2019Reilly). When he isn\u2019t busy avoiding being found out as a fraud, he spends most of his time designing and creating little apps and games he won\u2019t let anyone see. Tim spent a disproportionately long time writing this tiny little bio, most of which was trying to stick a witty sci-fi reference in, before he simply gave up. He is, obviously, an avid board game player.

" }, - + { - + "serial": 251, "name": "Tim O'Reilly", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_251.jpg", @@ -11380,9 +11380,9 @@ "twitter": "timoreilly", "bio": "

Tim O’Reilly is the founder and CEO of O’Reilly Media. His original business plan was “interesting work for interesting people,” and that’s worked out pretty well. He publishes books, runs conferences, invests in early-stage startups, urges companies to create more value than they capture, and tries to change the world by spreading and amplifying the knowledge of innovators.

\n

Tim is also a partner at O’Reilly AlphaTech Ventures, a founder and board member of Safari Books Online and Maker Media, and on the boards of Code for America and PeerJ.

" }, - + { - + "serial": 133624, "name": "Stephen O'Sullivan", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_133624.jpg", @@ -11392,9 +11392,9 @@ "twitter": "steveos", "bio": "

A leading expert on big data architecture and Hadoop, Stephen brings over 20 years of experience creating scalable, high-availability, data and applications solutions. A veteran of WalmartLabs, Sun and Yahoo!, Stephen leads data architecture and infrastructure.

" }, - + { - + "serial": 172986, "name": "Anne Ogborn", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_172986.jpg", @@ -11404,9 +11404,9 @@ "twitter": "", "bio": "

Anne Ogborn is an avid SWI-Prolog fan, professional SWI-Prolog programmer, and sometimes contributor to the SWI-Prolog project.
\nShe uses SWI-Prolog to create games that create change for the OSU Wavicles, and to program advanced social robotics for Robokind.

" }, - + { - + "serial": 147649, "name": "Tim Palko", "photo": null, @@ -11416,9 +11416,9 @@ "twitter": "", "bio": "

Tim celebrates software development using many languages and frameworks, heeding less to past experience in choosing technologies. Spring MVC, Hibernate, Rails, .NET MVC, Django and the variety of languages that come with are in his L1 cache. Among other endeavors to keep him sharp, he currently provides coded solutions for the Software Engineering Institute at CMU.

\n

Tim received a B.S. in Computer Engineering in 2003 and resides in Pittsburgh, PA.

" }, - + { - + "serial": 26426, "name": "Rajeev Pandey", "photo": null, @@ -11428,9 +11428,9 @@ "twitter": null, "bio": "

Rajeev Pandey joined Hewlett-Packard in 1995. He has worked in a wide variety of R&D and IT positions within HP Labs, HP-IT and HP R&D prior to joining HP Cloud. He has spent the last few years using Agile methods to architect, develop, and deploy digital imaging and printing-related web services. He holds BS degrees in Computer Science and Mathematics from Montana Tech, MS and PhD degrees in Computer Science from Oregon State University, and is a senior member of the IEEE and ACM.

" }, - + { - + "serial": 113667, "name": "Manish Pandit", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_113667.jpg", @@ -11440,9 +11440,9 @@ "twitter": "lobster1234", "bio": "

A programmer at heart, I work with great teams to build great products. Over the last decade, I’ve worked with companies ranging from software, financials, to media and entertainment. Solving scale problems with leading innovations in the tech space has been an area of interest to me. From building APIs at E*Trade, IGN, and Netflix I’ve evolved both as a leader as well as an engineer focused on scalable yet flexible, and highly performant architectures.

\n

I am also active with the developer community via github, stackoverflow, meetups, and conferences.

\n

My slideshare has decks from my talks at various events. Currently I am working at Netflix as an Engineering Manager in the Streaming Platforms group, where my team builds APIs and tools around device metadata and partner products. Prior to Netflix I was Director of Engineering at IGN.com, where I helped build the next-gen API for the social and content platforms.

\n

Follow me on LinkedIn.

" }, - + { - + "serial": 173223, "name": "James Pannacciulli", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_173223.jpg", @@ -11452,9 +11452,9 @@ "twitter": "_jpnc", "bio": "

James Pannacciulli has been an enthusiast and user of GNU/Linux and related software since 1997 and is a supporter of the free (libre) software movement. He holds an MA and BA in theoretical linguistics from UCLA and Rutgers universities, respectively, and is currently a Systems Engineer at Media Temple.

\n

James has presented on Bash at SCALE and UUASC in Los Angeles.

" }, - + { - + "serial": 172630, "name": "Soohong Park", "photo": null, @@ -11464,9 +11464,9 @@ "twitter": "", "bio": "

Dr. Soohong Daniel Park is a senior member of research staff in open source office, Samsung and currently leading Internet of Things open source project collaborated with various partners. He has been working on IPv6 over Low Power Sensor Networks in IETF and advanced research topics around IoT. Also, he is currently working in W3C as Advisor Board.

" }, - + { - + "serial": 180019, "name": "Sanjay Patil", "photo": null, @@ -11476,9 +11476,9 @@ "twitter": "", "bio": "

Sanjay Patil is a member of the Industry Standards & Open Source team at SAP Labs \u2013 Palo Alto, and is responsible for driving strategic Open Source programs as well as governance of SAP\u2019s outbound Open Source projects. He has over 14 years of experience with Industry Standards and Open Source projects. He has led critical industry-wide standardization initiatives such as OASIS Web Services Reliable Messaging. He currently serves as a Director on the Board of OASIS, a standards development organization, and represents SAP on Open Source foundations such as CloudFoundry. His areas of interest include application platform technologies, Big Data and Cloud.

" }, - + { - + "serial": 171197, "name": "Josh Patterson", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_171197.jpg", @@ -11488,9 +11488,9 @@ "twitter": "jpatanooga", "bio": "

Josh Patterson currently runs a consultancy in the Big Data Machine Learning space. Previously Josh worked as a Principal Solutions Architect at Cloudera and an engineer at the Tennessee Valley Authority where he was responsible for bringing Hadoop into the smartgrid during his involvement in the openPDC project. Josh is a graduate of the University of Tennessee at Chattanooga with a Masters of Computer Science where he did research in mesh networks and social insect swarm algorithms. Josh has over 15 years in software development and continues to contribute to projects such as Apache Mahout, Metronome, IterativeReduce, openPDC, and JMotif.

" }, - + { - + "serial": 118233, "name": "James Pearce", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_118233.jpg", @@ -11500,9 +11500,9 @@ "twitter": "jamespearce", "bio": "

James manages the open source program at Facebook. He’s a developer and writer with a special passion for the web, mobile platforms of all sorts, and helping developers explore their potential.

\n

James’ mobile projects include confess.js, WhitherApps, tinySrc, ready.mobi, Device Atlas, and mobiForge. Previously at Sencha, dotMobi, Argogroup and Ernst & Young, he has also written books on the mobile web for Wrox & Wiley. He’s easy to find at /jamesgpearce or http://tripleodeon.com

" }, - + { - + "serial": 64512, "name": "Shawn Pearce", "photo": null, @@ -11512,9 +11512,9 @@ "twitter": "", "bio": "

Shawn Pearce has been actively involved in Git since early 2006. Shawn is the original author of git-gui, a Tk based graphical interface shipped with git, and git fast-import, a stream based import system often used for converting projects to git. Besides being the primary author of both git-gui and git fast-import, Shawn’s opinion, backed by his code, has influenced many decisions that form the modern git implementation.

\n

In early 2006 Shawn also founded the JGit project, creating a 100% pure Java reimplementation of the Git version control system. The JGit library can often be found in Java based products that interact with Git, including plugins for Eclipse and NetBeans IDEs, the Hudson CI server, Apache Maven, and Gerrit Code Review, a peer code review system specially designed for Git. Today he continues to develop and maintain JGit, EGit, and Gerrit Code Review.

" }, - + { - + "serial": 180056, "name": "Harry Percival", "photo": null, @@ -11524,9 +11524,9 @@ "twitter": "hjwp", "bio": "

During his childhood Harry seemed to be doing everything right — learning to program BASIC on Thomson TO-7s (whose rubber keys went “boop” when you pressed them) and Logo on a Green-screen Amstrad PCW. Something went wrong as he grew up, and Harry wasted several years studying Economics, becoming a management consultant (shudder), and programming little aside from overcomplicated Excel spreadsheets.

\n

But in 2009 Harry saw the light, let his true geek shine once again, did a new degree in Computer Science, and was lucky enough to secure an internship with Resolver Systems, the London-based company that has since morphed into PythonAnywhere. Here he was inculcated into the cult of Extreme Programming (XP), and rigorous TDD. After much moaning and dragging of feet, he finally came to see the wisdom of the approach, and now spreads the gospel of TDD through beginner’s workshops, tutorials and talks, with all the passion of a recent convert.

\n

Harry is currently writing a book for O’Reilly, provisionally titled “Test-Driven Development of Web Applications with Python”. He is trying to persuade his editor to have the title changed to “Obey the Testing Goat!”.

" }, - + { - + "serial": 173381, "name": "Jim Perrin", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_173381.jpg", @@ -11536,9 +11536,9 @@ "twitter": "BitIntegrity", "bio": "

I began my linux journey in 1999 with Mandrake 6.1 and migrated to RHL around the 7.2 release I found out about CentOS in 2004, and joined the project formally later that year. Over the years I’ve been a consultant for defense contractors as well as for the oil and gas industry, handling large scale deployment, automation, and systems integration.

\n

My current responsibilities with CentOS:

\n

Governing Board member
\nInfrastructure
\nSIG/Variant sponsor/coordinator

" }, - + { - + "serial": 151611, "name": "Jerome Petazzoni", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_151611.jpg", @@ -11548,9 +11548,9 @@ "twitter": "jpetazzo", "bio": "

Jerome is a senior engineer at Docker, where he rotates between Ops, Support and Evangelist duties. In another life he built and operated Xen clouds when EC2 was just the name of a plane, developed a GIS to deploy fiber interconnects through the French subway, managed commando deployments of large-scale video streaming systems in bandwidth-constrained environments such as conference centers, and various other feats of technical wizardry. When annoyed, he threatens to replace things with a very small shell script. His left hand cares for the dotCloud PAAS servers, while his right hand builds cool hacks around Docker.

" }, - + { - + "serial": 30412, "name": "Brandon Philips", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_30412.jpg", @@ -11560,9 +11560,9 @@ "twitter": "philips", "bio": "

Brandon Philips is helping to build modern Linux server infrastructure at CoreOS. Prior to CoreOS, he worked at Rackspace hacking on cloud monitoring and was a Linux kernel developer at SUSE. In addition to his work at CoreOS, Brandon sits on Docker’s governance board and is one of the top contributors to Docker. As a graduate of Oregon State’s Open Source Lab he is passionate about open source technologies.

" }, - + { - + "serial": 29591, "name": "Simon Phipps", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_29591.jpg", @@ -11572,9 +11572,9 @@ "twitter": "webmink", "bio": "

Simon Phipps has engaged at a strategic level in the world\u2019s leading technology companies, starting in roles such as field engineer, programmer, systems analyst and more recently taking executive leadership roles around open source. He worked with OSI standards in the 80s, on collaborative conferencing software in the 90s, helped introduce both Java and XML at IBM and was instrumental in open sourcing the whole software portfolio at Sun Microsystems.

\n

As President of the Open Source Initiative and a director of the UK’s Open Rights Group, he takes an active interest in digital rights issues and is a widely read commentator at InfoWorld, Computerworld and his own Webmink blog.

\n

He holds a BSc in electronic engineering and is a Fellow of the British Computer Society and of the Open Forum Academy.

" }, - + { - + "serial": 141661, "name": "Andy Piper", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_141661.jpg", @@ -11584,9 +11584,9 @@ "twitter": "andypiper", "bio": "

Andy Piper is a Developer Advocate for Cloud Foundry, the Open Source Platform-as-a-Service. He is an Eclipse M2M IWG Community Member and has been involved with the Eclipse Paho project since the start, particularly through his former role advocating the use of MQTT at IBM, and helping to run the mqtt.org community website. Andy has a passionate interest in Open Source, small and mobile devices, cloud, the Internet of Things, and Arduino and related technologies. He is probably best known online as a \u201csocial bridgebuilder\u201d. He was previously with IBM Software Group for more than 10 years, as a consultant, strategist, and WebSphere Messaging Community Lead.

" }, - + { - + "serial": 170158, "name": "Curtis Poe", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_170158.jpg", @@ -11594,11 +11594,11 @@ "position": "Freelance Perl expert and Agile consultant", "affiliation": "All Around The World", "twitter": "OvidPerl", - "bio": "

I’m a well-known Perl expert, better known online as Ovid. I specialize in large-scale, database driven code bases and wrote the test harness that currently ships with the Perl programming language. I’m constantly trying to create better testing tools for the Perl community.

\n

I sit on the Board of Directors of the Perl Foundation and run a consulting company with my lovely wife, Le\u00efla, from our offices in La Rochelle, a medieval port town on the west coast of France.

\n

I speak at conferences all over the world and also do private speaking engagements and training for companies. Currently I’m a specialist for hire, often focusing on complex ETL problems or making developers more productive by fixing test suites and making them run much faster.

" + "bio": "

I’m a well-known Perl expert, better known online as Ovid. I specialize in large-scale, database driven codebases and wrote the test harness that currently ships with the Perl programming language. I’m constantly trying to create better testing tools for the Perl community.

\n

I sit on the Board of Directors of the Perl Foundation and run a consulting company with my lovely wife, Le\u00efla, from our offices in La Rochelle, a medieval port town on the west coast of France.

\n

I speak at conferences all over the world and also do private speaking engagements and training for companies. Currently I’m a specialist for hire, often focusing on complex ETL problems or making developers more productive by fixing test suites and making them run much faster.

" }, - + { - + "serial": 171078, "name": "Jess Portnoy", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_171078.jpg", @@ -11608,9 +11608,9 @@ "twitter": "jess_port01", "bio": "

Jess Portnoy has been an open source developer and believer for the last 15 years. Prior to Kaltura, Jess worked, among other places, at Zend where she was responsible for porting and packaging PHP and Zend projects on all supported .*nix platforms. Jess also grows 3 pet projects hosted in sourceforge.

\n

You can view my LinkedIn profile here.

" }, - + { - + "serial": 142320, "name": "Steven Pousty", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_142320.jpg", @@ -11620,9 +11620,9 @@ "twitter": "TheSteve0", "bio": "

Steve is a PaaS Dust Spreader (aka developer evangelist) with OpenShift. He goes around and shows off all the great work the OpenShift engineers do. He can teach you about PaaS with Java, Python, PostgreSQL MongoDB, and some JavaScript. He has deep subject area expertise in GIS/Spatial, Statistics, and Ecology. He has spoken at over 50 conferences and done over 30 workshops including Monktoberfest, MongoNY, JavaOne, FOSS4G, CTIA, AjaxWorld, GeoWeb, Where2.0, and OSCON. Before OpenShift, Steve was a developer evangelist for LinkedIn, deCarta, and ESRI. Steve has a Ph.D. in Ecology from University of Connecticut. He likes building interesting applications and helping developers create great solutions.

" }, - + { - + "serial": 173111, "name": "Mark Powell", "photo": null, @@ -11632,9 +11632,9 @@ "twitter": "drmarkpowell", "bio": "

Mark Powell is a Senior Computer Scientist at the Jet Propulsion Laboratory, Pasadena, CA since 2001. Mark is the product lead for the Mars Science Laboratory mission science planning interface (MSLICE). At JPL his areas of focus are science data visualization and science planning for telerobotics. He received the 2004 NASA Software of the Year Award for his work on the Science Activity Planner science visualization and activity planning software used for MER operations. Mark is currently supporting a variety of projects at JPL including Opportunity and Curiosity rover operations and radar data processing for volcanology and seismology research.

" }, - + { - + "serial": 155107, "name": "Austin Putman", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_155107.jpg", @@ -11644,9 +11644,9 @@ "twitter": "austinfrmboston", "bio": "

I empower people to make a difference with appropriate technology. Currently I’m working to turn around the diabetes epidemic.

\n

I was a founding partner and lead dev at Radical Designs, a tech cooperative for nonprofits, and a team anchor for Pivotal Labs, training and collaborating with earth’s best agile engineers.

" }, - + { - + "serial": 173467, "name": "Steven Quella", "photo": null, @@ -11656,9 +11656,9 @@ "twitter": "", "bio": "

Steven is a senior at Saint Joseph’s College, majoring in Computer Science. He is seeking a position after he graduates as a Java developer.

" }, - + { - + "serial": 151833, "name": "Dave Quigley", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_151833.jpg", @@ -11666,11 +11666,11 @@ "position": "Computer Science Professional", "affiliation": "KEYW Corporation", "twitter": "", - "bio": "

David Quigley making a return appearance to OSCON after his \u201cDemystifying SELinux: WTF is it saying?\u201d talk started his career as a Computer Systems Researcher for the National Information Assurance Research Lab at the NSA where he worked as a member of the SELinux team. David leads the design and implementation efforts to provide Labeled-NFS support for SELinux. David has previously contributed to the open source community through maintaining the Unionfs 1.0 code base and through code contributions to various other projects. David has presented at conferences such as the Ottawa Linux Symposium, the StorageSS workshop, LinuxCon and several local Linux User Group meetings where presentation topics have included storage, file systems, and security. David currently works as a Computer Science Professional for the Operations, Analytics and Software Development (OASD) Division at Keyw Corporation.

" + "bio": "

David Quigley making a return appearance to OSCON after his \u201cDemystifying SELinux: WTF is it saying?\u201d talk started his career as a Computer Systems Researcher for the National Information Assurance Research Lab at the NSA where he worked as a member of the SELinux team. David leads the design and implementation efforts to provide Labeled-NFS support for SELinux. David has previously contributed to the open source community through maintaining the Unionfs 1.0 codebase and through code contributions to various other projects. David has presented at conferences such as the Ottawa Linux Symposium, the StorageSS workshop, LinuxCon and several local Linux User Group meetings where presentation topics have included storage, file systems, and security. David currently works as a Computer Science Professional for the Operations, Analytics and Software Development (OASD) Division at Keyw Corporation.

" }, - + { - + "serial": 161577, "name": "Carl Quinn", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_161577.jpg", @@ -11680,9 +11680,9 @@ "twitter": null, "bio": "

Carl Quinn has been developing software professionally for 34 years, starting with BASIC on an Apple II, slogging through C/C++ on DOS, Windows and embedded, and finally landing in the Java-on-Linux world. The one thread through his career has been an inexplicable attraction to developer tools, spending time building them at Borland (C++ & Java IDEs), Sun (Java RAD), Google (Java & C++ build system), Netflix (Java build and cloud deployment automation) and most recently at Riot Games (Cloud Architect). Carl also co-hosts the Java Posse podcast, the #1 ranked Java technology podcast.

" }, - + { - + "serial": 182741, "name": "Kate Rafter", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_182741.jpg", @@ -11692,9 +11692,9 @@ "twitter": "", "bio": "

As a choreographer and interdisciplinary performance artist, Kate Rafter has presented work at the Edinburgh Festival Fringe, Scotland\u2019s Dance Base, Portland’s Fertile Ground Festival, Conduit, American College Dance Festival NW, the Dance Coalition of Oregon, (a)merging 2014, and the Someday:Incubator. She heads Automal, a project-based indie dance company.

\n

Kate’s performing arts ambitions go beyond dance and theatre conventions to include audience interactivity, immersive environments, and an intent to break every wall, starting with the fourth.

" }, - + { - + "serial": 173432, "name": "Jarret Raim", "photo": null, @@ -11704,9 +11704,9 @@ "twitter": "jarretraim", "bio": "

Jarret Raim is the Security Product Manager at Rackspace Hosting. Since joining Rackspace, he has built a software assurance program for Rackspace\u2019s internal software teams as well as defined strategy for building secure systems on Rackspace\u2019s OpenStack Cloud implementation. Through his experience at Rackspace, and as a consultant for Denim Group, Jarret has assessed and remediated applications in all industries and has experience width a wide variety of both development environments and the tools used to audit them. Jarret has recently taken charge of Rackspace’s efforts to secure the Cloud through new product development, training and research. Jarret holds a Masters in Computer Science from Lehigh University and Bachelors in Computer Science from Trinity University.

" }, - + { - + "serial": 150170, "name": "Luciano Ramalho", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_150170.jpg", @@ -11716,9 +11716,9 @@ "twitter": "ramalhoorg", "bio": "

Luciano Ramalho was a Web developer before the Netscape IPO in 1995, and switched from Perl to Java to Python in 1998. Since then he worked on some of the largest news portals in Brazil using Python, and taught Python web development in the Brazilian media, banking and government sectors. His speaking credentials include OSCON 2013 (slides) and 2002, two talks at PyCon USA 2013 and 17 talks over the years at PythonBrasil (the Brazilian PyCon), FISL (the largest FLOSS conference in the Southern Hemisphere) and a keynote at the RuPy Strongly Dynamic Conference in Brazil. Ramalho is a member of the Python Software Foundation and co-founder of Garoa Hacker Clube, the first hackerspace in Brazil. He is a managing partner at Python.pro.br, a training company.

" }, - + { - + "serial": 3471, "name": "Anna Martelli Ravenscroft", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_3471.jpg", @@ -11728,9 +11728,9 @@ "twitter": "annaraven", "bio": "

Anna Martelli Ravenscroft has a background in training and mentoring. Her focus is on practical, real-world problem solving and the benefits of diversity and accessibility. Anna graduated in 2010 from Stanford University with a degree in Cognitive Science. She is a member of the Python Software Foundation, a program committee member for several open source conferences, winner of the 2013 Frank Willison Award, and co-edited the Python Cookbook 2nd edition. She has spoken at PyCon, EuroPython, OSCON, and several regional Python conferences.

" }, - + { - + "serial": 141169, "name": "Matt Ray", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_141169.jpg", @@ -11740,9 +11740,9 @@ "twitter": "mattray", "bio": "

Matt Ray is an open source hacker working as the Director of Cloud Integrations for the company and open source systems integration platform Chef. He is active in the Chef, Ruby and OpenStack communities and was the Community Manager for Zenoss Core. He has been a contributor in the open source community for well over a decade and was one of the founders of the Texas LinuxFest. He resides in Austin, blogs at LeastResistance.net and is @mattray on Twitter, IRC and GitHub.

" }, - + { - + "serial": 77350, "name": "Rob Reilly", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_77350.jpg", @@ -11752,9 +11752,9 @@ "twitter": "", "bio": "

Rob Reilly is an independent consultant, writer, and speaker specializing in Linux, Open Hardware, technology media, and the mobile professional. He\u2019s been hired for a variety of engineering, business analysis, and special projects with AT&T, Intermedia Communications, Lockheed-Martin, Pachube, and Dice. As a 10-year veteran of the tech media, Rob has posted hundreds of feature-length technology articles for LinuxPlanet.com, Linux.com, Linux Journal magazine, PC Update magazine, and Nuts & Volts. He is a co-author of \u201cPoint & Click OpenOffice.org\u201d and worked as a contributing editor for LinuxToday.com. He\u2019s also chaired speaking committees for the old LinuxWorld shows. Rob has a BS in Mechanical Technology from Purdue University and first used the Unix command line in 1981.

" }, - + { - + "serial": 160033, "name": "Ryan Richards", "photo": null, @@ -11764,9 +11764,9 @@ "twitter": null, "bio": "

I am a full stack engineer at Fastly with an interest in the front-end, game programming, and theory. In my spare time I read books, write music, play games, and drink tea.

" }, - + { - + "serial": 11886, "name": "Chris Richardson", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_11886.jpg", @@ -11776,9 +11776,9 @@ "twitter": "", "bio": "

Chris Richardson is a developer and architect with over 20 years of experience. He is a Java Champion and the author of POJOs in Action, which describes how to build enterprise Java applications with POJOs and frameworks such as Spring and Hibernate. Chris is the founder of the original CloudFoundry.com, an early Java PaaS (Platform-as-a-Service) for Amazon EC2. He spends his time investigating better ways of developing and deploying software. Chris has a computer science degree from the University of Cambridge in England and lives in Oakland, CA.

" }, - + { - + "serial": 182043, "name": "Bruce Richardson", "photo": null, @@ -11788,9 +11788,9 @@ "twitter": "", "bio": "

Bruce Richardson is the lead software engineer working on the DPDK at Intel Corporation, based out of Shannon in the West of Ireland. He has almost 10 years experience in software for telecoms, and has been working with Intel on the DPDK for over 4 of those. He\u2019s helped design many of the features now present in the DPDK, and when he\u2019s not too busy with other – less interesting \u2013 things at work, occasionally gets to have fun implementing some of them.

" }, - + { - + "serial": 1402, "name": "Bryce Roberts", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_1402.jpg", @@ -11800,9 +11800,9 @@ "twitter": "bryce", "bio": "

Bryce co-founded O’Reilly AlphaTech Ventures (OATV) in 2005. At OATV he focuses on consumer and enterprise software and services investments.

\n

Prior to OATV, Bryce sourced and lead a number of successful early stage investments at Wasatch Venture Fund, a Draper Fisher Jurvetson affiliate. In 2004, Bryce co-founded the Open Source Business Conference (sold to IDG) in order to spark a conversation around commercializing the highly disruptive technologies and services emerging from the open source community. Prior to Wasatch, Bryce was a member of a small team at vertical search pioneer Whizbang! Labs. While at WhizBang!, he defined and launched the FlipDog.com division (sold to Monster Worldwide). He began his career in technology doing large enterprise software deployments, saving his employer from the dreaded Y2K Bug.

\n

His investments at OATV include GameLayers, Get Satisfaction, OpenCandy, OpenX, Parakey (acquired by Facebook), Path Intelligence and Wesabe. He holds a B.A. in Philosophy from Brigham Young University.

" }, - + { - + "serial": 173173, "name": "Gail Roper", "photo": null, @@ -11812,9 +11812,9 @@ "twitter": "gailmroper", "bio": "

28 year IT executive, CIO, and C-level administrator. Vast experience in the area of strategic planning, IT consolidation, and large scale system implementation including ERP, GIS, VoIP, web efforts and fiber/conduit implementation projects. Understands the value of partnerships, well connected with national strategist and innovative thinkers. Fortunate to have well-rounded experience in progressive environments that influence national strategies for innovation. Highly effective in political arenas, governance strategy, and working with auditing process. Has historically brought resources to the organization in the form of grants, funding, and partnerships with public and private entities.

\n

Specialties: Strategic thinker with the idea that technology should promote efficiency in any organization. Focused on innovation and promoting the use of technology to solve problems. Significant success in the area of public/private partnerships, multi-jurisictional efforts, complex contract negotiations, RFP negotiations and evaluations. Prior experience in the development of cost models, Return on Investment strategies, and business case models including financial self funding models. Accountable leader maintaining a focus on productivity and effective practices.

" }, - + { - + "serial": 173247, "name": "Erik Rose", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_173247.jpg", @@ -11824,9 +11824,9 @@ "twitter": "ErikRose", "bio": "

Erik Rose leads Mozilla\u2019s DXR project, which does regex searches and static analysis on large codebases like Firefox. He is an Elasticsearch veteran, maintaining the pyelasticsearch library, transitioning Mozilla\u2019s support knowledgebase to ES, and building a burly cluster to do realtime fuzzy matching against the entire corpus of U.S. voters. Erik is a frequent speaker at conferences around the world, the author of \u201cPlone 3 for Education\u201d, and the nexus of the kind of animal magnetism that comes only from writing your own bio.

" }, - + { - + "serial": 124630, "name": "William A Rowe Jr", "photo": null, @@ -11836,9 +11836,9 @@ "twitter": "wrowe", "bio": "

William is a member of the Application Products engineering team at Pivotal, where he has developed and maintained Apache Web Server based products since the turn of the century. He is an active committer to several Apache Software Foundation projects and serves on the ASF security response team. Over the past dozen years, William has contributed to the Apache Software Foundation, initially as a contributor to the Apache HTTP Server and APR projects, served as a Project Chair to both, mentored a number of projects new to the Foundation, participated on the convention committee for the foundation, and served as a Director of the Foundation. He is sometimes teased as the Unix developer who happens to work on Windows, and was largely responsible for stabilizing httpd running on Windows and ensuring Windows was a first class supported platform of the APR library.

" }, - + { - + "serial": 152026, "name": "Byron Ruth", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_152026.jpg", @@ -11848,9 +11848,9 @@ "twitter": "thedevel", "bio": "

Byron Ruth is a Lead Analyst/Programmer in the Center for Biomedical Informatics at The Children’s Hospital of Philadelphia. Byron’s skills in advanced web programming environments, API, and architectural software design have enabled him to lead a variety of projects at CHOP, including the development of a highly integrated audiology research database, an electronic health record-mediated clinical decision support engine for the care of premature infants, and a data management system that helps to discover relationships between genetic markers of congenital heart defects and clinical outcomes.

" }, - + { - + "serial": 172610, "name": "Justin Ryan", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_172610.jpg", @@ -11860,9 +11860,9 @@ "twitter": "quidryan", "bio": "

Justin Ryan is a Senior Software Engineer in the Engineering Tools team at Netflix, where he applies his years of experience as a developer to the problems of build automation and dependency management. He’s consistently raising the bar for the quality of build tools and build analysis. He is tasked with finding patterns and best practices between builds and applying them back to the hundreds of projects at Netflix. Justin has worked on Web UIs to Server development to Embedded programming.

" }, - + { - + "serial": 114822, "name": "Baruch Sadogursky", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_114822.jpg", @@ -11872,9 +11872,9 @@ "twitter": "jbaruch", "bio": "

Baruch Sadogursky (a.k.a JBaruch) is the Developer Advocate of JFrog, the creators of Artifactory Binary Repository, the home of Bintray, JavaOne 2011 and 2013 Duke Choice Awards winner.

\n

For a living he hangs out with the JFrog tech leaders, writes some code around Artifactory and Bintray, and then speaks and blogs about all that. He does it repeatedly for the last 10 years and enjoys every moment of it.

\n

Baruch mostly blogs on http://blog.bintray.com and http://blogs.jfrog.org. His speaker history is on Lanyrd

" }, - + { - + "serial": 173464, "name": "Nathan Samano", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_173464.jpg", @@ -11884,9 +11884,9 @@ "twitter": "", "bio": "

Nathan is a junior Computer Science major at Saint Joseph’s College. He is minoring in biology. His computing interests include game development and cloud programming.

" }, - + { - + "serial": 169557, "name": "Peter Sand", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_169557.jpg", @@ -11896,9 +11896,9 @@ "twitter": "", "bio": "

Peter has a Ph.D. in Computer Science from MIT. He is the founder of ManyLabs, a nonprofit focused on teaching math and science using sensors and simulations. Peter also founded Modular Science, a company working on hardware and software tools for science labs. He has given talks at Science Hack Day, Launch Edu, and multiple academic conferences, including SIGGRAPH.

" }, - + { - + "serial": 173364, "name": "Karen Sandler", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_173364.jpg", @@ -11908,9 +11908,9 @@ "twitter": "o0karen0o", "bio": "

Karen M. Sandler is the Executive Director of the GNOME Foundation. She is known for her advocacy for free software, particularly in relation to the software on medical devices. Prior to joining GNOME, she was General Counsel of the Software Freedom Law Center. Karen continues to do pro bono legal work with SFLC and serves as an officer of the Software Freedom Conservancy and an advisor to the Ada Initiative. She is also pro bono General Counsel to QuestionCopyright.Org. Karen is a recipient of the O’Reilly Open Source Award.

" }, - + { - + "serial": 2699, "name": "Ed Schipul", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_2699.jpg", @@ -11920,9 +11920,9 @@ "twitter": "eschipul", "bio": "

Ed Schipul is CEO of Tendenci, formerly Schipul, a 16-year-old bootstrapped company started in Houston Texas. Ed\u2019s team created the Open Source software platform Tendenci, an all in one CMS built specifically for nonprofits, membership associations and arts organizations.

\n

Under Ed’s leadership, the company has been listed among Houston’s fastest growing companies by the Houston Business Journal, won the Fastech 50, the Aggie 100, and numerous other awards. Ed has personally been nominated for the Ernst and Young Entrepreneur of the year award twice and roasted by the AIGA.

\n

Ed has presented at OSCON, SXSW Interactive, Public Relations Society of America\u2019s International Conference, the Bulldog Reporter National Conference, the Sarasota International Design Summit, Mom 2.0 and dozens of other organizations.

\n

Ed is frequently asked to share his insights regarding online marketing and has been published in Nonprofit World, Association News, The Public Relations Strategist and PR Tactics, among others. He blogs for Hearst Newspaper\u2019s Houston City Brights and has been interviewed on NBC and ABC news as an expert in successfully using digital marketing tools to accelerate organizational growth.

\n

As a past participant and sponsor of the AIR Accessibility Internet Rally community hackathon, Ed has worked to make technology globally accessible, especially for those with disabilities.

\n

Ed is a graduate of Texas A&M University, builds aerial drones as a hobby, is an amateur photographer and a very amateur tennis player.

" }, - + { - + "serial": 128980, "name": "Aaron Schlesinger", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_128980.jpg", @@ -11932,9 +11932,9 @@ "twitter": "arschles", "bio": "

I joined PayPal as part of the StackMob acquisition in late 2013, and I’m currently a Sr. Member of the Technical Staff here. My areas of interest include large scale distributed systems, high throughput server architectures, concurrency in large scale data systems, functional programming patterns, and emerging devops patterns.

\n

In my personal life, I live to play and watch soccer. My goal is to be playing well into my 50s.

" }, - + { - + "serial": 108125, "name": "Nathaniel Schutta", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_108125.jpg", @@ -11944,9 +11944,9 @@ "twitter": "ntschutta", "bio": "

Nathaniel T. Schutta is a solution architect focussed on making usable applications. A proponent of polyglot programming, Nate has written two books on Ajax and speaks regularly at various worldwide conferences, No Fluff Just Stuff symposia, universities, and Java user groups. In addition to his day job, Nate is an adjunct professor at the University of Minnesota where he teaches students to embrace dynamic languages. Most recently, Nate coauthored the book Presentation Patterns with Neal Ford and Matthew McCullough.

" }, - + { - + "serial": 181502, "name": "Daniel Juyung Seo", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_181502.jpg", @@ -11956,9 +11956,9 @@ "twitter": "seojuyung", "bio": "

Daniel Juyung Seo is a software engineer at Samsung Electronics focusing on the development and improvements of EFL. He joined the EFL/Enlightenment community in 2010 and is actively involved the project. He is mostly working on the Ecore core library and Elementary widget set among many libraries in EFL and helps people develop EFL applications on Tizen. As an active technical writer, he contributed EFL and Tizen articles to magazines and manages his own blogs. He participated in Tizen Camera (NX300) and Tizen Wearable Device (Gear 2) product projects in the past as well.

" }, - + { - + "serial": 24052, "name": "Andrew Clay Shafer", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_24052.jpg", @@ -11968,9 +11968,9 @@ "twitter": "littleidea", "bio": "

Andrew has been a consumer and contributor to open source for many years. He is always up for an adventure with a broad background contributing to technology and business efforts. As a co-founder at Puppet Labs, followed by working in and around the CloudStack and OpenStack ecosystems, he gained a lot of perspective on building open source businesses and communities. Sometimes he has been known to talk about devops and organizational learning. Andrew recently joined Pivotal, starting another open source business adventure focused on Cloud Foundry.

" }, - + { - + "serial": 173314, "name": "Brent Shaffer", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_173314.jpg", @@ -11980,9 +11980,9 @@ "twitter": "bshaffer", "bio": "

The tale of a young musician with his head filled with hopes and dreams who finds himself burdened with a mathematical mind and with little hopes of making it in the music world, decides to jump ship and finds he loves programming as much and maybe even more than his original passion.

" }, - + { - + "serial": 126882, "name": "Gwen Shapira", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_126882.jpg", @@ -11992,9 +11992,9 @@ "twitter": "gwenshap", "bio": "

Gwen Shapira is a Solutions Architect at Cloudera and leader of IOUG Big Data SIG. Gwen Shapira studied computer science, statistics and operations research at the University of Tel Aviv, and then went on to spend the next 15 years in different technical positions in the IT industry. She specializes in scalable and resilient solutions and helps her customers build high-performance large-scale data architectures using Hadoop. Gwen Shapira is a frequent presenter at conferences and regularly publishes articles in technical magazines and her blog.

" }, - + { - + "serial": 151691, "name": "Roman Shaposhnik", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_151691.jpg", @@ -12004,9 +12004,9 @@ "twitter": "", "bio": "

Roman Shaposhnik is a committer on Apache Hadoop, and holds a Chair position for the Apache Bigtop and Apache Incubator projects. Roman has been involved in Open Source software for more than a decade and has hacked projects ranging from the Linux kernel to the flagship multimedia library known as FFmpeg. He grew up in Sun Microsystems where he had an opportunity to learn from the best software engineers in the industry. Roman’s alma mater is St. Petersburg State University, Russia where he studied to be a mathematician.

" }, - + { - + "serial": 141561, "name": "Michael Shiloh", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_141561.jpg", @@ -12016,9 +12016,9 @@ "twitter": "michaelshiloh", "bio": "

Artist, designer, tinkerer, teacher, geek; practitioner and supporter of Open Source Hardware, Open Source Software, Open Education, Linux, and Arduino, and most things in between

" }, - + { - + "serial": 169647, "name": "Egle Sigler", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_169647.jpg", @@ -12028,9 +12028,9 @@ "twitter": "eglute", "bio": "Egle Sigler is a Private Cloud Architect, who started working at Rackspace in 2008. She was one of the first to receive Rackspace OpenStack certification. Before working with OpenStack, Egle worked on MyRackspace customer control panel, software architecture and enterprise tools. In her spare time, Egle enjoys traveling, hiking, snorkeling and nature photography." }, - + { - + "serial": 3189, "name": "Ricardo Signes", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_3189.jpg", @@ -12040,9 +12040,9 @@ "twitter": "rjbs", "bio": "

Ricardo Signes was thrust into the job market with only a rudimentary humanities education, and was forced to learn to fend for himself. He is now a full-time Perl programmer, the project manager for Perl 5, and frequent contributor to the CPAN.

" }, - + { - + "serial": 180050, "name": "Alan Sill", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_180050.jpg", @@ -12052,9 +12052,9 @@ "twitter": "ogfstandards", "bio": "

Alan Sill directs the National Science Foundation Center for Cloud and Autonomic Computing at Texas Tech University, where he is also a senior scientist at the High Performance Computing Center. A particle physicist by training, he serves as VP of Standards for Open Grid Forum, co-chairs the US National Institute of Standards and Technology “Standards Acceleration to Jumpstart Adoption of Cloud Computing” working group, and co-edits the IEEE Cloud Computing magazine. He is active in many cloud standards working groups and on national and international standards roadmap committees and is committed to development of advanced distributed computing methods for real-world science and business applications.

" }, - + { - + "serial": 74368, "name": "Kyle Simpson", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_74368.jpg", @@ -12064,9 +12064,9 @@ "twitter": "getify", "bio": "

Kyle Simpson is an Open Web Evangelist from Austin, TX. He’s passionate about JavaScript, HTML5, real-time/peer-to-peer communications, and web performance. Otherwise, he’s probably bored by it. Kyle is an author, workshop trainer, tech speaker, and avid OSS community member.

" }, - + { - + "serial": 156591, "name": "Samantha Simpson", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_156591.jpg", @@ -12076,9 +12076,9 @@ "twitter": "SamHSimpson", "bio": "

Samantha Simpson moved to Washington, D.C. to work at the Consumer Financial Protection Bureau in the Office of Technology and Innovation. She work as both a Technology Portfolio Manager and Product Director. Samantha is passionate about helping consumer understand their finances. Samantha was born and raised on the South Side of Chicago and is a graduate of The Johns Hopkins University.

" }, - + { - + "serial": 165643, "name": "Thomas Smith", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_165643.jpg", @@ -12088,9 +12088,9 @@ "twitter": "projectgado", "bio": "

Thomas Smith is an inventor and entrepreneur, and the creator of the open source Gado 2 archival scanning robot. Hailed by the Wall Street Journal as “a robot which rescues black history”, the Gado 2 has been used to digitize 120,000+ images in the archives of the Afro American Newspapers, and is now in use at archives from California to Finland. Mr. Smith currently leads Project Gado, as well as several other ventures.

" }, - + { - + "serial": 131729, "name": "Garrett Smith", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_131729.jpg", @@ -12100,9 +12100,9 @@ "twitter": "gar1t", "bio": "

Garrett Smith is senior architect at CloudBees, the company behind Jenkins CI and leading Java platform-as-a-service vendor. Garrett specializes in distributed systems and reliable software. His language of choice for systems programming is Erlang, a high productivity functional language specializing in concurrency and reliability. Garrett is an Erlang instructor and the author of the e2 library, which was built from his experience teaching Erlang. Garrett is also known for the videos “MongoDB is Web Scale” and “Node.js Is Bad Ass Rock Star Tech”.

" }, - + { - + "serial": 109116, "name": "Chris Smith", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_109116.jpg", @@ -12112,9 +12112,9 @@ "twitter": "chrissmithus", "bio": "

Chris Smith is a web site architect for Xerox.com by day and transportation geek and City of Portland Planning and Sustainability Commissioner by night

" }, - + { - + "serial": 77757, "name": "Bryan Smith", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_77757.jpg", @@ -12124,9 +12124,9 @@ "twitter": "fossetcon", "bio": "

Bryan A Smith is a Debian Gnu/Linux and BSD enthusiast, hardware hacker and Systems Engineer. Bryan has used Open Source Operating Systems since the days of Red Hat 5 Hurricane.

\n

He contributes to several Open Source projects and has helped launch several startup ISP’s based in his area using Free and Open Source software as a framework.

\n

Bryan is currently the Program Director for Fossetcon Fossetcon – Free and Open Source Software Expo and Technology Conference

\n

Bryan spends his free time organizing Fossetcon, administering his free shell server, advocating Free and Open Source, proctoring BSD Associate Certification, writing poetry and playing Bossa Nova and Flamenco guitar.

" }, - + { - + "serial": 81759, "name": "Carol Smith", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_81759.jpg", @@ -12136,9 +12136,9 @@ "twitter": "fossygrl", "bio": "

Carol Smith is the Open Source Programs Manager running Google
\nSummer of Code. She’s been at Google as a program manager for 9 years.
\nShe has a degree in photojournalism from California State University,
\nNorthridge, and is an avid cyclist and bibliophile.

" }, - + { - + "serial": 2732, "name": "Nathan Sobo", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_2732.jpg", @@ -12148,9 +12148,9 @@ "twitter": "nathansobo", "bio": "

Nathan Sobo is the co-founder of the Atom open-source text editor at GitHub. He is also the author of Treetop, a Ruby-based parser generator based on parsing expression grammars.

" }, - + { - + "serial": 44753, "name": "Juhan Sonin", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_44753.jpg", @@ -12160,9 +12160,9 @@ "twitter": "", "bio": "

Juhan Sonin is the Creative Director of Involution Studios and has produced work recognized by the New York Times, Newsweek, BBC International, Billboard Magazine, and National Public Radio (NPR). He has spent time at Apple, the National Center for Supercomputing Applications (NCSA), and MITRE. Juhan lectures on design and engineering at the Massachusetts Institute of Technology (MIT).

" }, - + { - + "serial": 178738, "name": "Andrew Sorensen", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_178738.jpg", @@ -12172,9 +12172,9 @@ "twitter": "digego", "bio": "

Andrew Sorensen is an artist-programmer whose interests lie at the
\nintersection of computer science and creative practice. Andrew is well
\nknown for creating the programming languages that he uses in live
\nperformance to generate improvised audiovisual theatre. He has been
\ninvited to perform these contemporary audiovisual improvisations
\naround the world. Andrew is a Senior Research Fellow at the Queensland
\nUniversity of Technology and is the author of the Impromptu and
\nExtempore programming language environments.

" }, - + { - + "serial": 175353, "name": "Derek Sorkin", "photo": null, @@ -12184,9 +12184,9 @@ "twitter": "", "bio": "

GitHub

" }, - + { - + "serial": 151991, "name": "Francisco Souza", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_151991.jpg", @@ -12196,9 +12196,9 @@ "twitter": "franciscosouza", "bio": "

Francisco Souza is a software engineer at Globo.com, the largest media company in Latin America. He works in the cloud platform team, building an open source solution to be adopted by all Globo.com portals.

" }, - + { - + "serial": 142767, "name": "Kara Sowles", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_142767.jpg", @@ -12208,9 +12208,9 @@ "twitter": "FeyNudibranch", "bio": "

Kara Sowles is Community Initiatives Manager at Puppet Labs in Portland, OR. She’s excited to swap user group tips with you for far too long.

\n

When she’s done planning events and running community programs at Puppet Labs, she enjoys going home and making stop motion animation with actual Puppets.

" }, - + { - + "serial": 172240, "name": "Kaushik Srenevasan", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_172240.jpg", @@ -12220,9 +12220,9 @@ "twitter": "", "bio": "

Kaushik Srenevasan is the Lead on the VM Diagnostics team at Twitter,
\nwhere he hacks on the Hotspot JVM, on, among other things, improving
\nits observability. Before joining Twitter he authored the Chakra
\nJavaScript runtime’s AMD64 backend compiler and worked on the .NET CLR at
\nMicrosoft.

" }, - + { - + "serial": 131499, "name": "Raghavan Srinivas", "photo": null, @@ -12232,9 +12232,9 @@ "twitter": "ragss", "bio": "

Raghavan “Rags” Srinivas works as a Cloud Solutions Architect. His general focus area is in distributed systems, with a specialization in Cloud Computing and Big Data. He worked on Hadoop, HBase and NoSQL during its early stages. He has spoken on a variety of technical topics at conferences around the world, conducted and organized Hands-on Labs and taught graduate classes in the evening.

\n

Rags brings with him over 25 years of hands-on software development and over 15 years of architecture and technology evangelism experience.

\n

Rags holds a Masters degree in Computer Science from the Center of Advanced Computer Studies at the University of Louisiana at Lafayette. He likes to hike, run and generally be outdoors but most of all loves to eat.

" }, - + { - + "serial": 3476, "name": "Simon St. Laurent", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_3476.jpg", @@ -12244,9 +12244,9 @@ "twitter": "simonstl", "bio": "

Simon St.Laurent has spent most of his career explaining technology. He is co-chair of the Fluent and OSCON conferences, a Senior Editor at O’Reilly, and a web developer. He has written over a dozen books, including Introducing Elixir, Introducing Erlang, Learning Rails, XML Elements of Style, and XML: A Primer. He spends his spare time making objects out of wood and presenting on local history.

" }, - + { - + "serial": 170134, "name": "Mark Stanislav", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_170134.jpg", @@ -12256,9 +12256,9 @@ "twitter": "markstanislav", "bio": "

Mark Stanislav is the Security Evangelist for Duo Security, an Ann Arbor, Michigan-based startup focused on two-factor authentication and mobile security. With a career spanning over a decade, Mark has worked within small business, academia, startup, and corporate environments, primarily focused on Linux architecture, information security, and web application development.

\n

Mark has spoken nationally at over 70 events including RSA, ISSA, B-Sides, GrrCon, Infragard, and the Rochester Security Summit. Mark\u2019s security research has been featured on web sites including CSO Online, Security Ledger, and Slashdot. Additionally, Mark is an active participant of local and nationals security organizations including ISSA, Infragard, HTCIA, ArbSec, and MiSec.

\n

Mark earned his Bachelor of Science Degree in Networking and IT Administration and his Master of Science Degree in Technology Studies, focused on Information Assurance, both from Eastern Michigan University. During his time at EMU, Mark built the curriculum for two courses focused on Linux administration and taught as an Adjunct Lecturer for two years. Mark holds CISSP, Security+, Linux+, and CCSK certifications.

" }, - + { - + "serial": 172899, "name": "Nicolas Steenhout", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_172899.jpg", @@ -12268,9 +12268,9 @@ "twitter": "vavroom", "bio": "

Nicolas Steenhout is a veteran of, and passionate advocate for, web accessibility. Nic had taken the lead in building several websites prior to taking up a federally-funded position in the disability sector in the US in 1996. An international speaker, trainer and consultant, Nic works with government, corporations, and small teams, in the areas of both web and physical accessibility. Working with and for thousands of people with disabilities in North America and Australasia, while working with web technologies and their impact, has given Nic a unique insight into the challenges, solutions, nuts and bolts of web accessibility.

" }, - + { - + "serial": 138530, "name": "Boyd Stephens", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_138530.jpg", @@ -12280,9 +12280,9 @@ "twitter": "", "bio": "

Boyd Stephens – the founder of Netelysis, LLC and is currently employed within the firm’s network engineering division. Having entered his 17th year as an entrepreneur, Boyd is now a member of Netelysis’ networking services development team where his primary focus is upon the research and design of internetworking systems and network management services.

\n

For the last twenty-six years he has worked within some facet of the information technology industry having designed and managed an array of networking systems and services for educational, corporate and governmental entities. His professional efforts have been the source of immeasurable fulfillment both personally and professionally.

" }, - + { - + "serial": 172656, "name": "Simon Stewart", "photo": null, @@ -12292,9 +12292,9 @@ "twitter": "shs96c", "bio": "

Simon works at Facebook as a Software Engineer, where he works as part of the internal tools team. He’s also the inventor of WebDriver and the current lead of the Selenium project, and co-edits the W3C WebDriver specification.

" }, - + { - + "serial": 173088, "name": "Matt Stine", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_173088.jpg", @@ -12304,9 +12304,9 @@ "twitter": "mstine", "bio": "

Matt Stine is a Cloud Foundry Platform Engineer at Pivotal. He is a 13-year veteran of the enterprise IT industry, with experience spanning numerous business domains.

\n

Matt is obsessed with the idea that enterprise IT \u201cdoesn\u2019t have to suck,\u201d and spends much of his time thinking about lean/agile software development methodologies, DevOps, architectural principles/patterns/practices, and programming paradigms, in an attempt to find the perfect storm of techniques that will allow corporate IT departments to not only function like startup companies, but also create software that delights users while maintaining a high degree of conceptual integrity. He currently specializes in helping customers achieve success with Platform as a Service (PaaS) using Cloud Foundry and BOSH.

\n

Matt has spoken at conferences ranging from JavaOne to CodeMash, is a regular speaker on the No Fluff Just Stuff tour, and serves as Technical Editor of NFJS the Magazine. Matt is also the founder and past president of the Memphis/Mid-South Java User Group.

" }, - + { - + "serial": 122293, "name": "Deirdr\u00e9 Straughan", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_122293.jpg", @@ -12316,9 +12316,9 @@ "twitter": "deirdres", "bio": "

In a career spanning tech companies large and small, worldwide, Deirdr\u00e9 Straughan has constantly developed new ways to foster communication about technology between customers and companies, online and offline. She has particularly focused on innovative uses of media, in recent years producing hundreds of technical videos and live video streams for Sun Microsystems, Oracle, and Joyent, and for open source communities including OpenSolaris, illumos, SmartOS, and OpenZFS. You can learn more on beginningwithi.com.

" }, - + { - + "serial": 135908, "name": "Jason Strimpel", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_135908.jpg", @@ -12328,9 +12328,9 @@ "twitter": "", "bio": "

I engineer software.

" }, - + { - + "serial": 108840, "name": "Ruth Suehle", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_108840.jpg", @@ -12340,9 +12340,9 @@ "twitter": "suehle", "bio": "

Ruth Suehle is a community marketing manager in Red Hat\u2019s Open Source and Standards group, which supports upstream open source software communities. She also leads the Fedora Project\u2019s marketing team and is co-author of Raspberry Pi Hacks (O\u2019Reilly, December 2013). Previously an editor for Red Hat Magazine, she now leads discussions about open source principles as an editor at opensource.com. Ruth is also a senior editor at GeekMom.com, where she covers the adventures of motherhood alongside technology and sci-fi.

" }, - + { - + "serial": 173421, "name": "Marc Sugiyama", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_173421.jpg", @@ -12352,9 +12352,9 @@ "twitter": "", "bio": "

Marc Sugiyama is a Senior Architect at Erlang Solutions, Inc. A Bay Area native, he has 30 years of software development experience and has worked on everything from testing frameworks in Tcl at Sybase and Cisco, to SMP relational database engines in C at Sybase, to a MMO engine in Twisted Python for Pixverse (a company he co-founded), to a large scale real time chat system in Erlang for hi5 Networks. Prior to joining Erlang Solutions, he built a call handling service in Erlang for Ribbit/British Telecom leading a team of developers in Brazil, Sweden, the US, and the UK. A published author, he wrote his first magazine articles and books while still in high school. He has presented at Sybase User Group Meetings and the Colorado Software Summit. He holds a Bachelors of Science in Engineering and Masters of Engineering from Harvey Mudd College (Claremont, CA) and serves on the Board of Trustees of The College Preparatory School in Oakland, CA.

" }, - + { - + "serial": 164229, "name": "Nick Sullivan", "photo": null, @@ -12364,9 +12364,9 @@ "twitter": "grittygrease", "bio": "

Nick is a software engineering leader innovating in the world of Internet scale data at CloudFlare. He is also a respected digital rights management innovator with a thorough understanding of the digital media distribution process through over half a decade working on the iTunes store. He previously worked as a security analyst worked at Symantec analyzing large scale threat data. He holds an MSc in Cryptography and a BMath in Pure Mathematics.

" }, - + { - + "serial": 175040, "name": "Zach Supalla", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_175040.jpg", @@ -12376,9 +12376,9 @@ "twitter": "zsupalla", "bio": "

Zach Supalla is an entrepreneur, a Maker, and a designer. He is the founder and CEO of Spark, a start-up that’s making it easier to build internet-connected hardware. Zach juggles hardware design, front-end software development, and leading his team through the trials and tribulations of a hardware start-up.

\n

The Spark team led a successful Kickstarter campaign for their product, the Spark Core, in May 2013, raising nearly $600K in 30 days off of a goal of $10K. They\u2019re now shipping to 61 countries, with thousands of engineers and developers building new connected devices with their technology. Their products have been featured in WIRED, Engadget, Fast Company, TechCrunch, the Discovery Channel, and many other publications.

\n

Zach is a graduate of HAXLR8R, the only incubator for hardware start-ups that will teach you to order bubble tea in perfect Mandarin. He also has an MBA from Kellogg School of Management and an MEM (Masters in Engineering Management) from McCormick School of Engineering at Northwestern. Before Spark, Zach worked as a management consultant with McKinsey & Company, advising Fortune 500 companies on strategy, operations, and product development.

" }, - + { - + "serial": 171226, "name": "Jason Swartz", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_171226.jpg", @@ -12388,9 +12388,9 @@ "twitter": "swartzrock", "bio": "

Jason is a Software Engineer in the San Francisco Bay Area, developing applications and services in Scala at Netflix. Before making the switch to functional programming he managed the developer docs and support team at eBay, wrote advertising and merchandising platforms in Java and built tools and UI prototypes at Apple. His book, “Learning Scala”, will be published by O’Reilly Media in Spring 2014.

" }, - + { - + "serial": 153857, "name": "Kiyoto Tamura", "photo": null, @@ -12400,9 +12400,9 @@ "twitter": null, "bio": "

Kiyoto is one of the maintainers of Fluentd, the open source log collector with users ranging from Nintendo to Slideshare. Raised in Japan, New York and California, he brings a unique bilingual, bicultural perspective to the open source world.

\n

He spends much of his day at Treasure Data as a developer marketer/community manager for Fluentd. He also is a math nerd turned quantitative trader turned software engineer turned open source community advocate and cherishes American brunch and Japanese game shows.

" }, - + { - + "serial": 173262, "name": "Paul Tarjan", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_173262.jpg", @@ -12412,9 +12412,9 @@ "twitter": "ptarjan", "bio": "

I’m a juggling unicylcing web hacker @ Facebook.

\n

I’m on the HipHop for PHP team making HHVM able to run all existing PHP code. Before that, I build many of the Open Graph tools and features. Before that I was the Tech Lead for Yahoo! SearchMonkey.

" }, - + { - + "serial": 41059, "name": "James Tauber", "photo": null, @@ -12424,9 +12424,9 @@ "twitter": "jtauber", "bio": "

James Tauber is an Australian web developer and entrepreneur now based in Boston. He has been involved in open source, Web standards and Python for over 15 years.

" }, - + { - + "serial": 173285, "name": "Christian Ternus", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_173285.jpg", @@ -12436,9 +12436,9 @@ "twitter": "ternus", "bio": "

Christian Ternus is a security researcher on Akamai Technologies’ Adversarial Resilience team, where he works on attacks, architecture, design, analysis, and the human factors in security. He graduated from MIT and has previously worked in kernel security and mobile health-tech. He has previously spoken at industry conferences including Boston Security, SOURCE Boston, and BrainTank, as well as organizing Akamai’s Humanity in Security miniconference. In his spare time, he is an avid photographer and adventure motorcyclist.

" }, - + { - + "serial": 173472, "name": "Jeffrey Thalhammer", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_173472.jpg", @@ -12448,9 +12448,9 @@ "twitter": "thaljef", "bio": "

Jeffrey Thalhammer has been developing software for more than 15 years. He is the creator of Perl::Critic, the widely used static analyzer for Perl. Jeffrey is also a co-organizer of the San Francisco Perl Mongers.

" }, - + { - + "serial": 150, "name": "Laura Thomson", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_150.jpg", @@ -12460,9 +12460,9 @@ "twitter": null, "bio": "

Laura Thomson is a Senior Engineering Manager at Mozilla Corporation. She works with the Web Engineering team, which is responsible for the Firefox crash reporting system and other developer tools, and the Release Engineering team, which is responsible for shipping Firefox.

\n

Laura is the co-author of \u201cPHP and MySQL Web Development\u201d and \u201cMySQL Tutorial\u201d. She is a veteran speaker at Open Source conferences worldwide.

" }, - + { - + "serial": 173378, "name": "Sebastian Tiedtke", "photo": null, @@ -12472,9 +12472,9 @@ "twitter": "sourishkrout", "bio": "

Born and raised at the foot of the Alps just outside of Munich in Germany, Sebastian spent his youth listening to David Hasselhoff songs. He wore Lederhosen and eagerly anticipated the day he could legally drink his first stein of beer.

\n

When daddy put the first computer on his desk in the mid 90s, Sebastian quickly went on to figure out BASIC, Pascal and C/C++. Rainy afternoons were filled with building websites, countless iterations of Linux kernel makefile tweaks and compiler runs.

\n

Before joining Sauce, Sebastian finished a degree in Software Development (in Munich), he spent almost a decade building customer information systems making transit information more accessible for people living in large metropolitan areas worldwide. His job commitment and passions for technology eventually made him move to San Francisco in 2009.

\n

When Sebastian is not busy improving the Sauce experience he likes to take joyrides on his 1961 vespa. He’s a natural optimist in life and always sees the stein half full.

" }, - + { - + "serial": 53442, "name": "Jim Tommaney", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_53442.jpg", @@ -12484,9 +12484,9 @@ "twitter": "InfiniDB", "bio": "

Jim has extensive experience in leading the development, management, and performance for enterprise data architectures, including clustered, large SMP, and distributed systems for the retail, web, and telecom industries. He is responsible for the architecture, vision, direction, and technical evangelization of InfiniDB. Jim holds a BBA from Texas A&M and a Masters in Management Information Systems from the University of Texas at Dallas.

" }, - + { - + "serial": 172661, "name": "Sudhir Tonse", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_172661.jpg", @@ -12496,9 +12496,9 @@ "twitter": "stonse", "bio": "

Sudhir Tonse manages the Cloud Platform Infrastructure team at Netflix and is responsible for many of the services and components that form the Netflix Cloud Platform as a Service.

\n

Many of these components have been open sourced under the NetflixOSS umbrella. Open source contribution includes Archaius: a dynamic configuration/properties management library, Ribbon: a Inter Process Communications framework that includes Cloud friendly Software load balancers, Karyon: the nucleus of a PaaS service etc.
\nPrior to Netflix, Sudhir was an Architect at Netscape/AOL delivering large-scale consumer and enterprise applications in the area of Personalization, Infrastructure and Advertising Solutions.

\n

Sudhir is a weekend golfer and tries to make the most of the wonderful California weather and public courses.

" }, - + { - + "serial": 173326, "name": "Willem Toorop", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_173326.jpg", @@ -12508,9 +12508,9 @@ "twitter": "", "bio": "

Willem is a developer at NLnet Labs, a not-for-profit foundation dedicated to the development of open-source implementations of open standards. At NLnet Labs Willem is the lead developer of the C DNS utility library: ldns. Willem has implemented leading edge DNS functionality for ldns based on new open standards such as DNSSEC and DANE. Our getdns-api implementation utilizes ldns for processing DNS data. Another of NLnet Labs C-libraries, libunbound, is used for DNS resolving. Besides working on ldns Willem also maintains and develops the perl Net::DNS and Net::DNS::SEC modules and actively researches Path MTU black holes that hamper DNSSEC deployment.

" }, - + { - + "serial": 181972, "name": "Joan Touzet", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_181972.jpg", @@ -12520,9 +12520,9 @@ "twitter": "wohali", "bio": "

Joan Touzet has been using Apache CouchDB since 2008. She is now a committer and PMC member for Apache CouchDB, and acts as Senior Software Development Manager at Cloudant, an IBM Company.

\n

Her major effort for 2014 is coordinating the merge of Cloudant’s BigCouch branch back into CouchDB towards a CouchDB 2.0 release. Recently, Joan has given talks at CloudantCON 2014, CouchDB Conf 2013, ChefConf 2013 and PyCon Canada 2012, all of which are viewable on YouTube.

\n

In her spare time, Joan builds and repairs motorcycles, pilots small aircraft, and composes music for video game soundtracks.

" }, - + { - + "serial": 172990, "name": "Brian Troutwine", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_172990.jpg", @@ -12532,9 +12532,9 @@ "twitter": "bltroutwine", "bio": "

I’m a software engineer at AdRoll where I work on soft real-time system in Erlang. I deal with mission-critical systems that operate at large scale. Functional programming and Erlang in particular are my main areas of interest and I’m becoming increasingly interested in “large” embedded systems.

\n

I graduated in 2010 with a degree in Computer Science from Portland State University. I spoke at Erlang Factory 2014, Bay Area.

" }, - + { - + "serial": 172607, "name": "Eric Tschetter", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_172607.jpg", @@ -12544,9 +12544,9 @@ "twitter": "zedruid", "bio": "

Eric Tschetter is the creator and one of the main Contributors to Druid, an open source, real-time analytical data store. He is currently an individual contributor to Tidepool.org, a non-profit diabetes research organization. Eric was previously the VP of Engineering and lead architect at Metamarkets, and has held senior engineering positions at Ning and LinkedIn.He holds bachelors degrees in Computer Science and Japanese from the University of Texas at Austin, and a M.S. from the University of Tokyo in Computer Science.

" }, - + { - + "serial": 5060, "name": "James Turnbull", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_5060.jpg", @@ -12556,9 +12556,9 @@ "twitter": "kartar", "bio": "

James Turnbull is the author of seven technical books about open source software and a long-time member of the open source community. James authored the The Logstash Book and The Docker Book. He also wrote two books about Puppet (Pro Puppet\"\" and the earlier book about Puppet as well as Pro Linux System Administration, Pro Nagios 2.0, and Hardening Linux.

\n

For a real job, James is VP of Services for Docker. He likes food, wine, books, photography and cats. He is not overly keen on long walks on the beach and holding hands.

" }, - + { - + "serial": 169870, "name": "James Turner", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_169870.jpg", @@ -12568,9 +12568,9 @@ "twitter": "blackbearnh", "bio": "

James Turner, contributing editor for oreilly.com, is a freelance journalist who has written for publications as diverse as the Christian Science Monitor, Processor, Linuxworld Magazine, Developer.com and WIRED Magazine. In addition to his shorter writing, he has also written two books on Java Web Development (MySQL & JSP Web Applications" and “Struts: Kick Start”) as well as the O’Reilly title “Developing Enterprise iOS Applications”. He is the former Senior Editor of LinuxWorld Magazine and Senior Contributing Editor for Linux Today. He has also spent more than 30 years as a software engineer and system administrator, and currently works as a Senior Software Engineer for a company in the Boston area. His past employers have included the MIT Artificial Intelligence Laboratory, Xerox AI Systems, Solbourne Computer, Interleaf, the Christian Science Monitor and contracting positions at BBN and Fidelity Investments. He is a committer on the Apache Jakarta Struts project and served as the Struts 1.1B3 release manager. He lives in a 200 year old Colonial farmhouse in Derry, NH along with his wife and son. He is an open water diver and instrument-rated private pilot, as well as an avid science fiction fan.

" }, - + { - + "serial": 173406, "name": "Andrew Turner", "photo": null, @@ -12580,9 +12580,9 @@ "twitter": "ajturner", "bio": "

CTO of the Esri DC Dev Center

" }, - + { - + "serial": 175354, "name": "Tim Tyler", "photo": null, @@ -12592,9 +12592,9 @@ "twitter": "", "bio": "

Sr. Staff Engineer
\nQualcomm Open Source Portal Team

" }, - + { - + "serial": 86111, "name": "David Uhlman", "photo": null, @@ -12604,9 +12604,9 @@ "twitter": "", "bio": "

David Uhlman is CEO of ClearHealth Inc. and a has been longstanding contributor and entrepreneur in open source technology for 15 years including contributions to Linux, Java, CentOS, and Joomla. He is a frequent speaker and contributor including previous talks at OSCON, SCALE, and others.

\n

ClearHealth Inc is the company associated with the ClearHealth open source project, a practice management and electronic medical record system derived from VA VistA started in 2003 which now has over 18 million patients in more than 1,000 installations including notable large scale systems like Tarrant County TX, The Primary Care Coalition, The University of Texas Medical Branch and numerous Federally Qualified Health Centers (FQHC) nationwide.

" }, - + { - + "serial": 173056, "name": "Manish Vachharajani", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_173056.jpg", @@ -12616,9 +12616,9 @@ "twitter": "mvachhar", "bio": "

Manish was the Founder and Chief Software Architect of LineRate Systems, a high-performance software networking startup that was acquired by F5 Networks in February 2013.

\n

LineRate Systems’ core technology is based on Node.js and Manish’s research group’s work on high-performance networking at the University of Colorado at Boulder.

\n

Prior to LineRate, Manish dedicated 13 years studying
\nsoftware performance on general purpose processors. He co-authored nearly 50 publications on processor performance in a range of fields including optimizing compiler design, on-chip optics, performance modeling, parallel programming, and high performance networking. His work has been recognized by best paper and presentation awards at
\ntop-tier conferences, support from the National Science Foundation, and support from industry leaders such as Intel and NVIDIA. Manish is currently a Senior Architect at F5 Networks leading the design of the
\nLineRate product.

" }, - + { - + "serial": 76735, "name": "Jos\u00e9 Valim", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_76735.jpg", @@ -12628,9 +12628,9 @@ "twitter": "josevalim", "bio": "

Jos\u00e9 Valim is the creator of the Elixir programming language and member of the Rails Core Team. He graduated in Engineering in S\u00e3o Paulo University, Brazil and has a Master of Science by Politecnico di Torino, Italy. He is also the lead-developer of Plataformatec, a consultancy firm based in Brazil, and an active member of the Open Source community.

" }, - + { - + "serial": 99817, "name": "William Van Hevelingen", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_99817.jpg", @@ -12640,9 +12640,9 @@ "twitter": "pdx_blkperl", "bio": "

William Van Hevelingen started with Linux and configuration management as part of the Computer Action Team\u2019s Braindump program at Portland State University. He worked on the Wintel, Nix, and Networking teams as a volunteer and later as a student worker helping to manage hundreds of workstations, servers, and networking infrastructure. William now works full time for the Computer Action Team (TheCAT), which provides IT for the Maseeh College of Engineering and Computer Science at Portland State University, as the Unix Team lead. He helps teach the Unix portion of the CAT\u2019s Braindump program, covering topics like web servers, databases, storage, virtualization, and Puppet. William is a co-author of Pro Puppet 2nd Edition and Beginning Puppet book which is to be released in late 2014.

" }, - + { - + "serial": 65329, "name": "Heather VanCura", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_65329.jpg", @@ -12652,9 +12652,9 @@ "twitter": "", "bio": "

Heather VanCura manages the JCP Program Office and is responsible for the day-to-day nurturing, support, and leadership of the community. She oversees the JCP.org web site, JSR management and posting, community building, events, marketing, communications, and growth of the membership through new members and renewals. Heather has a front row seat for studying trends within the community and recommending changes. Several changes to the program in recent years have included enabling broader participation, increased transparency and agility in JSR development.

" }, - + { - + "serial": 183835, "name": "Poornima Venkatakrishnan", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_183835.jpg", @@ -12664,9 +12664,9 @@ "twitter": null, "bio": "

I am a web developer who is passionate about all things node.js and open source. I am an active contributor/ maintainer of Krakenjs – Paypal’s open sourced app framework for express. I like playing with new technologies, solving difficult problems and working on game changing projects. To sum up, I love a good challenge that keeps me on my toes.

" }, - + { - + "serial": 112672, "name": "Alvaro Videla", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_112672.jpg", @@ -12676,9 +12676,9 @@ "twitter": "old_sound", "bio": "

Alvaro Videla works as Developer Advocate for RabbitMQ/Pivotal. Before moving to Europe he used to work in Shanghai where he helped building one of Germany biggest dating websites. He co-authored the book “RabbitMQ in Action” for Manning Publishing. Some of his open source projects can be found here. Apart from code related stuff he likes traveling with his wife, listening/playing music and reading books.

" }, - + { - + "serial": 182587, "name": "Ryan Vinyard", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_182587.jpg", @@ -12688,9 +12688,9 @@ "twitter": "", "bio": "

Ryan is the Engineering Lead at Highway1, a hardware-focused startup accelerator located in San Francisco under parent company PCH International. He is a Mechanical Engineer who came to PCH through its consulting arm Lime Lab, where he developed consumer products for Fortune 500 brands. Previous to PCH, Ryan worked at startups in the cleantech and electric vehicle space where he developed novel powertrain, motor control, and thermal systems. Ryan holds a B.S. in Product Design from Stanford University.

" }, - + { - + "serial": 174073, "name": "Robert Virding", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_174073.jpg", @@ -12700,9 +12700,9 @@ "twitter": "rvirding", "bio": "

Robert is Principal Language Expert at Erlang Solutions Ltd. He was one of the early members of the Ericsson Computer Science Lab, and co-inventor of the Erlang language. He took part in the original system design and contributed much of the original libraries, as well as to the current compiler. While at the lab he also did a lot of work on the implementation of logic and functional languages, as well as garbage collection.

\n

Robert’s passion is language implementation and he created a Lisp Flavored Erlang (LFE) and Luerl, leveraging his intimate knowledge of the Erlang environment, compiler and VM. He did reactive programming long before it became a buzz word. He is also rumored to be the best Erlang teacher on this planet.

\n

Robert has worked as an entrepreneur and was one of the co-founders of one of the first Erlang startups (Bluetail). He worked a number of years at the Swedish Defence Materiel Administration (FMV) Modelling and Simulations Group. And he co-authored the first book (Prentice-Hall) on Erlang, and is regularly invited to teach and present throughout the world.

" }, - + { - + "serial": 46737, "name": "Karsten Wade", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_46737.jpg", @@ -12712,9 +12712,9 @@ "twitter": null, "bio": "

Since 2000 Karsten has been teaching and living the open source way. As a member of Red Hat’s Open Source and Standards team, he helps with community activities in projects Red Hat is involved in. As a 19 year IT industry veteran, Karsten has worked most sides of common business equations as an IS manager, professional services consultant, technical writer, and developer advocate. As of 2013, Karsten has been working on the CentOS Project as a new Board member, Red Hat liaison on the Board, and engineering team manager. You’ll see him getting involved in infrastructure, documentation, and distro building. He blogs here, microblogs here , and is found on IRC as ‘quaid’.

\n

Karsten lives in his hometown of Santa Cruz, CA with his wife and two daughters on their small urban farm, Fairy-Tale Farm, where they focus on growing their own food and nurturing sustainable community living. Most recently, Karsten has been a partner in a collectively-run business of people-powered transportation, Santa Cruz Pedicab, and some weekends you’ll find him taking tourists and late-nighters around downtown.

" }, - + { - + "serial": 39928, "name": "Ken Walker", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_39928.jpg", @@ -12724,9 +12724,9 @@ "twitter": null, "bio": "

Ken is the lead for the Open Source Orion project. He aims to make web based development tools match and exceed the capabilities of a desktop IDE and not just for Web applications. His work in developer tools includes a long history from ENVY/Smalltalk, VisualAge for Java, VisualAge Micro Edition, Eclipse and now the JavaScript based client side Orion platform. Previously he was responsible for IBM’s J9 Mobile JVM platform.

" }, - + { - + "serial": 1158, "name": "James Ward", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_1158.jpg", @@ -12736,9 +12736,9 @@ "twitter": "_JamesWard", "bio": "

James Ward works for Typesafe where he teaches developers the Typesafe Platform (Play Framework, Scala, and Akka).

" }, - + { - + "serial": 6219, "name": "Simon Wardley", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_6219.jpg", @@ -12748,9 +12748,9 @@ "twitter": "swardley", "bio": "

based in the UK, works for CSC\u2019s Leading Edge Forum, a global research and advisory programme. Simon\u2019s focus is on the intersection of strategy, economics and new technologies.

\n

As a geneticist with a love of mathematics and a fascination in economics, Simon has always found himself dealing with complex systems, whether it\u2019s in behavioural patterns, environmental risks of chemical pollution, developing novel computer systems or managing companies. He is a passionate advocate and researcher in the fields of open source, commoditization, innovation, organizational structure and cybernetics.

\n

Simon is a regular presenter at conferences worldwide, and was voted as one of the UK’s top 50 most influential people in IT in ComputerWeekly’s poll in 2011 and 2012.

" }, - + { - + "serial": 173025, "name": "Corinne Warnshuis", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_173025.jpg", @@ -12760,9 +12760,9 @@ "twitter": "corinnepw", "bio": "

Corinne is the Executive Director of Girl Develop It, a nonprofit with chapters in 36 cities that exists to offer affordable classes for women to learn web and software development. She moved to Philadelphia in 2011 after graduating from University of California, Santa Cruz.

" }, - + { - + "serial": 171565, "name": "Phil Webb", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_171565.jpg", @@ -12772,9 +12772,9 @@ "twitter": "phillip_webb", "bio": "

Phil Webb is a Spring Framework developer and co-creator of the Spring Boot project. Prior to joining Pivotal and relocating to California, Phil worked for a number of UK technology companies.

" }, - + { - + "serial": 4146, "name": "Emma Jane Westby", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_4146.jpg", @@ -12784,9 +12784,9 @@ "twitter": "emmajanehw", "bio": "

\ufeffEmma Jane Westby (n\u00e9e Hogbin) has been working as a web developer since 1996, and has been participating in FOSS communities for over a decade. She’s authored two books on Drupal (including the ever-popular Front End Drupal), and contributed technical reviews, and articles to many more publications. Passionate about information, and knowledge acquisition, Emma Jane teaches Web-based technologies online, at her local community college, and at conferences around the world. She is well-known in the Drupal community for her Drupal socks and their GPLed pattern. In her spare time, Emma Jane crafts, keeps bees, and likes to drink Scotch. You can find her at emmajane.net.

" }, - + { - + "serial": 141524, "name": "Langdon White", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_141524.jpg", @@ -12796,9 +12796,9 @@ "twitter": "langdonwhite", "bio": "

Evangelist for the Red Hat Enterprise Linux platform and it\u2019s associated Developer Program. Has spent 15 years architecting and implementing high-impact software systems for companies ranging from startups to large companies.

" }, - + { - + "serial": 142111, "name": "Sarah White", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_142111.jpg", @@ -12808,9 +12808,9 @@ "twitter": "carbonfray", "bio": "

Sarah is the co-founder of OpenDevise. She’s passionate about helping open source projects find practical yet fun ways to communicate with their users and contributors.

\n

Long ago, in a not-too-distant galaxy, she assessed hazardous waste sites and tracked pesticide routes through watersheds. So she knows a thing or two about identifying and eradicating stuff that kills projects, including poor documentation.

" }, - + { - + "serial": 54107, "name": "Dustin Whittle", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_54107.jpg", @@ -12820,9 +12820,9 @@ "twitter": "dustinwhittle", "bio": "

Dustin Whittle is a Developer Evangelist at AppDynamics focused on helping organizations manage their application performance. Before joining AppDynamics, Dustin was CTO at Kwarter, a consultant at SensioLabs, and developer evangelist at Yahoo!. He has experience building and leading engineering teams and working with developers and partners to scale up. When Dustin isn’t working he enjoys flying, sailing, diving, golfing, and travelling around the world. Find out more at dustinwhittle.com.

" }, - + { - + "serial": 172895, "name": "Glen Wiley", "photo": null, @@ -12832,9 +12832,9 @@ "twitter": "glenswiley", "bio": "

Glen spent seven years serving as the systems architect for the DNS resolution platforms for the largest domain in the world (.COM and two of the Internet root servers). He currently works in an R&D group at Verisign where he contributes to Internet standards and builds proof of concepts exploring new products and technologies. After more than 25 years in the industry Glen brings a rich blend of history and hands on experience to the talk.

" }, - + { - + "serial": 6574, "name": "Eric Wilhelm", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_6574.jpg", @@ -12844,9 +12844,9 @@ "twitter": "", "bio": "

Eric Wilhelm is a programmer at Cisco, Inc. He is a father of two, former leader of the Portland Perl Mongers, author of many CPAN modules, and a contributor to several open source projects. He has spoken numerous times at OSCON and local user groups.

" }, - + { - + "serial": 182808, "name": "Kjerstin Williams", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_182808.jpg", @@ -12856,9 +12856,9 @@ "twitter": "kjkjerstin", "bio": "

Dr. Kjerstin ‘KJ’ Williams directs the robotics and intelligent systems efforts at Applied Minds in Glendale, California. Kjerstin has delivered a wide variety of invited lectures and demonstrations on topics ranging from biologically-inspired design to computer vision in venues ranging from academic conferences to science museums
\nto the main stage at O’Reilly Media’s Maker Faire. Her current research interests include multi-modal perception strategies and the design and control of truly field-deployable, intelligent systems.

\n

Also, she\u2019s a fantastic jazz singer with KJ and the Conspirators in Los Angeles.

" }, - + { - + "serial": 183908, "name": "Cedric Williams", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_183908.jpg", @@ -12868,9 +12868,9 @@ "twitter": "AskCedricW", "bio": "

I am a technologist, advocate, and coach. I aspire to nurture products, businesses, and societies that make a difference in people’s lives.

" }, - + { - + "serial": 122424, "name": "John Willis", "photo": null, @@ -12880,9 +12880,9 @@ "twitter": null, "bio": "

John Willis is the VP of Customer Enablement for Stateless Networks. Willis, a 30-year systems management veteran, joined Stateless Networks from Dell where he was Chief DevOps evangelist. Willis, a noted expert on agile philosophies in systems management, came to Dell as part of their Enstratius acquisition. At Enstratius, Willis was the VP of Customer Enablement responsible for product support and services for the multi-cloud management platform. During his career, he has held positions at Opscode and also founded Gulf Breeze Software, an award-winning IBM business partner specializing in deploying Tivoli technology for the enterprise. Willis has authored six IBM Redbooks for IBM on enterprise systems management and was the founder and chief architect at Chain Bridge Systems. He is also co author of the \u201cDevops Cookbook\u201d and the upcoming \u201cNetwork Operations\u201d published by O\u201dReilly.

" }, - + { - + "serial": 150073, "name": "Mike Wolfson", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_150073.jpg", @@ -12892,9 +12892,9 @@ "twitter": "mikewolfson", "bio": "

Mike is a passionate mobile designer/developer working out of Phoenix. He has been working in the software field for more than 15 years, and with Android since its introduction. Currently, he develops Android applications for the health care field, and is a Lead Mobile Developer at Athenahealth\\Epocrates. He has a few successful apps in the Market and is an active contributor to the tech community, including organizing the local GDG.

\n

Mike has spoken about Android and mobile development at a variety of conferences and user groups (including Oscon). When he is not geeking out about phones, he enjoys the outdoors (snowboarding, hiking, scuba diving), collecting PEZ dispensers, and chasing his young (but quick) daughter.

" }, - + { - + "serial": 175481, "name": "Jeff Wolski", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_175481.jpg", @@ -12904,9 +12904,9 @@ "twitter": "", "bio": "

Jeff Wolski has over 10 years of experience in tech and has worked in a variety of environments: investment banking, hospital operating rooms, broadcast television graphics, flash sale retailers and now Uber. At Uber, he spends his time hacking on Node.js, learning how to Scala, building Graphite dashboards and drinking jasmine green tea. He originally hails from New York and has recently made the trek over to San Francisco, a city which he finds amazingly beautiful.

" }, - + { - + "serial": 153565, "name": "Fangjin Yang", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_153565.jpg", @@ -12916,9 +12916,9 @@ "twitter": null, "bio": "

Fangjin is one of the main Druid contributors and one of the first developers to Metamarkets. He mainly works on core infrastructure and platform development. Fangjin comes to Metamarkets from Cisco where he developed diagnostic algorithms for various routers and switches. He holds a BASc in Electrical Engineering and a MASc in Computer Engineering from the University of Waterloo, Canada.

" }, - + { - + "serial": 173466, "name": "Alex Yong", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_173466.jpg", @@ -12928,9 +12928,9 @@ "twitter": "", "bio": "

Alex is a graduating senior at Saint Joseph’s College, where he is majoring in Computer Science. After graduation, he has accepted a position working in network operations at Indiana University.

" }, - + { - + "serial": 171450, "name": "Danny Yuan", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_171450.jpg", @@ -12940,9 +12940,9 @@ "twitter": "g9yuayon", "bio": "

Danny is an architect and software developer in Netflix’s Platform Engineering team. He works on Netflix’s distributed crypto service, data pipeline, and real-time analytics. He is the owner of Netflix’s open sourced data pipeline, Suro, and also the owner of Netflix’s predictive autoscaling engine.

" }, - + { - + "serial": 169932, "name": "Tobias Zander", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_169932.jpg", @@ -12952,9 +12952,9 @@ "twitter": "airbone42", "bio": "

Tobias is the CTO and a partner at Sitewards in Frankfurt, who specialize in e-commerce solutions.
\nPreviously he was well regarded as a freelance consultant and software architect. Over the past years he has built up a development team at Sitewards that thrives to be at the cutting edge of web development.
\nWith passion of inspiring developers he has taken part in and spoken at conferences such as Meet Magento, Developers Paradise, IPC, User groups and Unconferences. He has also had articles published in t3n and PHPMagazin.

" }, - + { - + "serial": 173468, "name": "Danilo Zekovic", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_173468.jpg", @@ -12964,9 +12964,9 @@ "twitter": "", "bio": "

Danilo is a sophomore at Saint Joseph’s College, from Novi Sad, Serbia. His interests are web programming, teaching programming, and anything in general that involves programming.

" }, - + { - + "serial": 141590, "name": "Carina C. Zona", "photo": "http://cdn.oreillystatic.com/en/assets/1/eventprovider/1/_@user_141590.jpg", @@ -12976,332 +12976,332 @@ "twitter": "cczona", "bio": "

Carina C. Zona is a developer and advocate. Her day job is as the community manager for open source software ZeroVM. She has also been a teacher & organizer for many tech women\u2019s organizations. Carina is the founder of @CallbackWomen, an initiative to connect first-time speakers with conferences. She is also a certified sex educator. In her spare time, she engineers baked goods. Using git. Because she loves versioning that much.

" } - + ], "venues": [ - + { "serial": 1448, "name": "Portland Ballroom", "category": "Conference Venues" }, - + { "serial": 1449, "name": "Portland 251", "category": "Conference Venues" }, - + { "serial": 1450, "name": "Portland 252", "category": "Conference Venues" }, - + { "serial": 1452, "name": "Portland 255", "category": "Conference Venues" }, - + { "serial": 1475, "name": "Portland 256", "category": "Conference Venues" }, - + { "serial": 1454, "name": "D135", "category": "Conference Venues" }, - + { "serial": 1456, "name": "D136", "category": "Conference Venues" }, - + { "serial": 1457, "name": "D137/138", "category": "Conference Venues" }, - + { "serial": 1458, "name": "D139/140", "category": "Conference Venues" }, - + { "serial": 1470, "name": "E143/144", "category": "Conference Venues" }, - + { "serial": 1464, "name": "E144", "category": "Conference Venues" }, - + { "serial": 1465, "name": "E145", "category": "Conference Venues" }, - + { "serial": 1471, "name": "E145/146", "category": "Conference Venues" }, - + { "serial": 1466, "name": "E146", "category": "Conference Venues" }, - + { "serial": 1451, "name": "E147/148", "category": "Conference Venues" }, - + { "serial": 1587, "name": "D133/135", "category": "Conference Venues" }, - + { "serial": 1607, "name": "E 141/142", "category": "Conference Venues" }, - + { "serial": 1459, "name": "F150", "category": "Conference Venues" }, - + { "serial": 1462, "name": "F151", "category": "Conference Venues" }, - + { "serial": 1460, "name": "E141", "category": "Conference Venues" }, - + { "serial": 1461, "name": "E142", "category": "Conference Venues" }, - + { "serial": 1463, "name": "E143", "category": "Conference Venues" }, - + { "serial": 1507, "name": "D130", "category": "Conference Venues" }, - + { "serial": 1520, "name": " D135", "category": "Conference Venues" }, - + { "serial": 1525, "name": "Portland Ballroom", "category": "Conference Venues" }, - + { "serial": 1467, "name": "Exhibit Hall D", "category": "Conference Venues" }, - + { "serial": 1469, "name": "Exhibit Hall C", "category": "Conference Venues" }, - + { "serial": 1468, "name": "Exhibit Hall E", "category": "Conference Venues" }, - + { "serial": 1453, "name": "E147 / E148", "category": "Conference Venues" }, - + { "serial": 1473, "name": "Portland Ballroom Foyer", "category": "Conference Venues" }, - + { "serial": 1474, "name": "Expo Hall", "category": "Conference Venues" }, - + { "serial": 1522, "name": "See BoF Schedule Onsite for Locations", "category": "Conference Venues" }, - + { "serial": 1521, "name": "Offsite", "category": "Conference Venues" }, - + { "serial": 1523, "name": "On Your Own", "category": "Conference Venues" }, - + { "serial": 1524, "name": "Expo Hall", "category": "Conference Venues" }, - + { "serial": 1626, "name": "Puppet Labs Headquarters, 926 Northwest 13th Avenue, #210", "category": "Conference Venues" }, - + { "serial": 1526, "name": "Exhibit Hall D", "category": "Conference Venues" }, - + { "serial": 1546, "name": "Expo Hall (Table A)", "category": "Conference Venues" }, - + { "serial": 1547, "name": "Expo Hall (Table B)", "category": "Conference Venues" }, - + { "serial": 1548, "name": "Expo Hall (Table C)", "category": "Conference Venues" }, - + { "serial": 1579, "name": "Expo Hall (Table D)", "category": "Conference Venues" }, - + { "serial": 1580, "name": "Expo Hall (Table E)", "category": "Conference Venues" }, - + { "serial": 1596, "name": "Office Hours Expo Hall", "category": "Conference Venues" }, - + { "serial": 1597, "name": "Author Signings (O'Reilly Booth) ", "category": "Conference Venues" }, - + { "serial": 1549, "name": "Author Signing A", "category": "Conference Venues" }, - + { "serial": 1606, "name": "123 NE Third Ave.", "category": "Conference Venues" }, - + { "serial": 1550, "name": "Author Signing B", "category": "Conference Venues" }, - + { "serial": 1598, "name": "Author Signing C", "category": "Conference Venues" }, - + { "serial": 1583, "name": " Union Pine, 525 SE Pine St.", "category": "Conference Venues" }, - + { "serial": 1584, "name": "F 150", "category": "Conference Venues" }, - + { "serial": 1585, "name": "Exhibit Hall B", "category": "Conference Venues" }, - + { "serial": 1574, "name": "Bottom of the stairs by the E Rooms", "category": "Conference Venues" }, - + { "serial": 1578, "name": "Jupiter Hotel", "category": "Conference Venues" } - + ] }} - + diff --git a/25-class-metaprog/checked/decorator/checkeddeco.py b/25-class-metaprog/checked/decorator/checkeddeco.py index 401835d..fa6abd3 100644 --- a/25-class-metaprog/checked/decorator/checkeddeco.py +++ b/25-class-metaprog/checked/decorator/checkeddeco.py @@ -96,7 +96,7 @@ def checked(cls: type) -> type: # <1> for name, constructor in _fields(cls).items(): # <2> setattr(cls, name, Field(name, constructor)) # <3> - cls._fields = classmethod(_fields) #type: ignore # <4> + cls._fields = classmethod(_fields) # type: ignore # <4> instance_methods = ( # <5> __init__, From bd7d78deccb4d0b9e86d1729530b9d721d96939a Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Wed, 9 Jun 2021 01:00:30 -0300 Subject: [PATCH 066/166] update urllib3 due to GHSA-q2q7-5pp4-w6pg --- 21-futures/getflags/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/21-futures/getflags/requirements.txt b/21-futures/getflags/requirements.txt index 447f2e9..793679b 100644 --- a/21-futures/getflags/requirements.txt +++ b/21-futures/getflags/requirements.txt @@ -5,7 +5,7 @@ certifi==2020.12.5 chardet==4.0.0 idna==2.10 requests==2.25.1 -urllib3==1.26.4 +urllib3==1.26.5 tqdm==4.56.2 multidict==5.1.0 yarl==1.6.3 From e1121adf61a72b4df92a0891712225aaeb1ab4da Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Wed, 9 Jun 2021 01:03:07 -0300 Subject: [PATCH 067/166] Update README.md --- 18-context-mngr/lispy/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/18-context-mngr/lispy/README.md b/18-context-mngr/lispy/README.md index 1a2acfb..4f3f75d 100644 --- a/18-context-mngr/lispy/README.md +++ b/18-context-mngr/lispy/README.md @@ -6,7 +6,7 @@ for Scheme. * `original/`: Norvig's `lis.py` unchanged, `lispy.py` with [minor changes](https://github.com/norvig/pytudes/pull/106) to run on Python 3, -and the `lispytest.py` custom test suite; +and the `lispytest.py` custom test script; * `py3.9/`: `lis.py` with type hints and a few minor edits—requires Python 3.9; * `py3.10/`: `lis.py` with type hints, pattern matching, and minor edits—requires Python 3.10. From df84fe38aa3967dec733a04e68f15bcb56e0c126 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Wed, 9 Jun 2021 01:08:40 -0300 Subject: [PATCH 068/166] Update README.md --- README.md | 52 ++++++++++++++++++++++++++-------------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 7d786e1..2cfa562 100644 --- a/README.md +++ b/README.md @@ -18,36 +18,36 @@ New chapters in **Fluent Python 2e** are marked with 🆕. 🚨 This table of contents is subject to change at any time until the book goes to the printer. -Part / Chapter #|Title|Directory|Notebook|1st ed. Chapter # +Part / Chapter #|Title|Directory|1st ed. Chapter # ---:|---|---|---|:---: **I – Prologue**| -1|The Python Data Model|[01-data-model](01-data-model)|[data-model.ipynb](01-data-model/data-model.ipynb)|1 +1|The Python Data Model|[01-data-model](01-data-model)|1 **II – Data Structures**| -2|An Array of Sequences|[02-array-seq](02-array-seq)|[array-seq.ipynb](02-array-seq/array-seq.ipynb)|2 -3|Dictionaries and Sets|[03-dict-set](03-dict-set)||3 -4|Text versus Bytes|[04-text-byte](04-text-byte)||4 -🆕 5|Record-like Data Structures|[05-record-like](05-record-like)||– -6|Object References, Mutability, and Recycling|[06-obj-ref](06-obj-ref)||8 +2|An Array of Sequences|[02-array-seq](02-array-seq)|2 +3|Dictionaries and Sets|[03-dict-set](03-dict-set)|3 +4|Text versus Bytes|[04-text-byte](04-text-byte)|4 +5|Record-like Data Structures|[05-record-like](05-record-like)|🆕 +6|Object References, Mutability, and Recycling|[06-obj-ref](06-obj-ref)|8 **III – Functions as Objects**| -7|First-Class Funcions|[07-1class-func](07-1class-func)||5 -🆕 8|Type Hints in Function Definitions|[08-def-type-hints](08-def-type-hints)||– -9|Function Decorators and Closures|[09-closure-deco](09-closure-deco)||7 -10|Design Patterns with First-Class Functions|[10-dp-1class-func](10-dp-1class-func)||6 +7|First-Class Funcions|[07-1class-func](07-1class-func)|5 +8|Type Hints in Function Definitions|[08-def-type-hints](08-def-type-hints)|🆕 +9|Function Decorators and Closures|[09-closure-deco](09-closure-deco)|7 +10|Design Patterns with First-Class Functions|[10-dp-1class-func](10-dp-1class-func)|6 **IV – Object-Oriented Idioms**| -11|A Pythonic Object|[11-pythonic-obj](11-pythonic-obj)||9 -12|Sequence Hacking, Hashing, and Slicing|[12-seq-hacking](12-seq-hacking)||10 -13|Interfaces, Protocols, and ABCs|[13-protocl-abc](13-protocol-abc)||11 -14|Inheritance: For Good or For Worse|[14-inheritance](14-inheritance)||12 -🆕 15|More About Type Hints|[15-more-types](15-more-types)||– -16|Operator Overloading: Doing It Right|[16-op-overloading](16-op-overloading)||13 +11|A Pythonic Object|[11-pythonic-obj](11-pythonic-obj)|9 +12|Sequence Hacking, Hashing, and Slicing|[12-seq-hacking](12-seq-hacking)|10 +13|Interfaces, Protocols, and ABCs|[13-protocl-abc](13-protocol-abc)|11 +14|Inheritance: For Good or For Worse|[14-inheritance](14-inheritance)|12 +15|More About Type Hints|[15-more-types](15-more-types)|🆕 +16|Operator Overloading: Doing It Right|[16-op-overloading](16-op-overloading)|13 **V – Control Flow**| -17|Iterables, Iterators, and Generators|[17-it-generator](17-it-generator)||14 -18|Context Managers and else Blocks|[18-context-mngr](18-context-mngr)||15 -19|Classic Coroutines|[19-coroutine](19-coroutine)||16 -🆕 20|Concurrency Models in Python|[20-concurrency](20-concurrency)||- -21|Concurrency with Futures|[21-futures](21-futures)||17 -22|Asynchronous Programming|[22-async](22-async)||18 +17|Iterables, Iterators, and Generators|[17-it-generator](17-it-generator)|14 +18|Context Managers and else Blocks|[18-context-mngr](18-context-mngr)|15 +19|Classic Coroutines|[19-coroutine](19-coroutine)|16 +20|Concurrency Models in Python|[20-concurrency](20-concurrency)|🆕 +21|Concurrency with Futures|[21-futures](21-futures)|17 +22|Asynchronous Programming|[22-async](22-async)|18 **VI – Metaprogramming**| -23|Dynamic Attributes and Properties|[22-dyn-attr-prop](22-dyn-attr-prop)||19 -24|Attribute Descriptors|[23-descriptor](23-descriptor)||20 -25|Class Metaprogramming|[24-class-metaprog](24-class-metaprog)||21 +23|Dynamic Attributes and Properties|[22-dyn-attr-prop](22-dyn-attr-prop)|19 +24|Attribute Descriptors|[23-descriptor](23-descriptor)|20 +25|Class Metaprogramming|[24-class-metaprog](24-class-metaprog)|21 From 378a82b470ed18c18243efb4cc91d74090db5469 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Wed, 9 Jun 2021 01:10:59 -0300 Subject: [PATCH 069/166] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2cfa562..8149e80 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ New chapters in **Fluent Python 2e** are marked with 🆕. 🚨 This table of contents is subject to change at any time until the book goes to the printer. Part / Chapter #|Title|Directory|1st ed. Chapter # ----:|---|---|---|:---: +---:|---|---|:---: **I – Prologue**| 1|The Python Data Model|[01-data-model](01-data-model)|1 **II – Data Structures**| From 0f9c2970e00ca8d6aa952f11be8c0877cd0954db Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Wed, 9 Jun 2021 01:13:17 -0300 Subject: [PATCH 070/166] Update README.md --- 18-context-mngr/lispy/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/18-context-mngr/lispy/README.md b/18-context-mngr/lispy/README.md index 4f3f75d..2a3ef13 100644 --- a/18-context-mngr/lispy/README.md +++ b/18-context-mngr/lispy/README.md @@ -15,7 +15,7 @@ The `py3.9/` and `py3.10/` directories also have identical `lis_test.py` to run These files include all the [`lis_tests` suite](https://github.com/norvig/pytudes/blob/60168bce8cdfacf57c92a5b2979f0b2e95367753/py/lispytest.py#L5) from `original/lispytest.py`, -and individual tests for each expression and special form handled by `evaluate`. +and separate tests for each expression and special form handled by `evaluate`. ## Provenance, Copyright and License From 009b92950f32710dfe79f0e2bde544c7a35d16e7 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Wed, 9 Jun 2021 01:21:44 -0300 Subject: [PATCH 071/166] Update README.md --- 18-context-mngr/lispy/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/18-context-mngr/lispy/README.md b/18-context-mngr/lispy/README.md index 2a3ef13..0f98ccd 100644 --- a/18-context-mngr/lispy/README.md +++ b/18-context-mngr/lispy/README.md @@ -22,3 +22,5 @@ and separate tests for each expression and special form handled by `evaluate`. `lis.py` is published in the [norvig/pytudes](https://github.com/norvig/pytudes) repository on Github. The copyright holder is Peter Norvig and the code is licensed under the [MIT license](https://github.com/norvig/pytudes/blob/60168bce8cdfacf57c92a5b2979f0b2e95367753/LICENSE). + +Luciano Ramalho wrote the changes and additions described above. From 481f6b99ef5d0795474ff215bb593133da6ad23f Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Wed, 9 Jun 2021 01:22:31 -0300 Subject: [PATCH 072/166] Update README.md --- 18-context-mngr/lispy/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/18-context-mngr/lispy/README.md b/18-context-mngr/lispy/README.md index 0f98ccd..7edb1fa 100644 --- a/18-context-mngr/lispy/README.md +++ b/18-context-mngr/lispy/README.md @@ -23,4 +23,4 @@ and separate tests for each expression and special form handled by `evaluate`. The copyright holder is Peter Norvig and the code is licensed under the [MIT license](https://github.com/norvig/pytudes/blob/60168bce8cdfacf57c92a5b2979f0b2e95367753/LICENSE). -Luciano Ramalho wrote the changes and additions described above. +I, Luciano Ramalho, wrote the changes and additions described above. From 67c3f46dc2c3ebbd3460d3ade46d72a35b9b513c Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Wed, 9 Jun 2021 01:46:20 -0300 Subject: [PATCH 073/166] ch18: added std_env fixture to lis.py tests --- 18-context-mngr/lispy/py3.10/lis_test.py | 46 +++++++++++------------- 18-context-mngr/lispy/py3.9/lis_test.py | 46 +++++++++++------------- 2 files changed, 42 insertions(+), 50 deletions(-) diff --git a/18-context-mngr/lispy/py3.10/lis_test.py b/18-context-mngr/lispy/py3.10/lis_test.py index 591046d..f9c96ae 100755 --- a/18-context-mngr/lispy/py3.10/lis_test.py +++ b/18-context-mngr/lispy/py3.10/lis_test.py @@ -1,6 +1,6 @@ from typing import Optional -from pytest import mark +from pytest import mark, fixture from lis import parse, evaluate, Expression, Environment, standard_env @@ -39,12 +39,15 @@ ("((repeat riff-shuffle) (list 1 2 3 4 5 6 7 8))", [1, 3, 5, 7, 2, 4, 6, 8]), ("(riff-shuffle (riff-shuffle (riff-shuffle (list 1 2 3 4 5 6 7 8))))", [1,2,3,4,5,6,7,8]), ]) -@mark.skip def test_evaluate(source: str, expected: Optional[Expression]) -> None: got = evaluate(parse(source)) assert got == expected +@fixture +def std_env() -> Environment: + return standard_env() + # tests for each of the cases in evaluate def test_evaluate_variable() -> None: @@ -83,65 +86,58 @@ def test_evaluate_if_false() -> None: assert got == expected -def test_define() -> None: - env: Environment = standard_env() +def test_define(std_env: Environment) -> None: source = '(define answer (* 6 7))' - got = evaluate(parse(source), env) + got = evaluate(parse(source), std_env) assert got is None - assert env['answer'] == 42 + assert std_env['answer'] == 42 -def test_lambda() -> None: - env: Environment = standard_env() +def test_lambda(std_env: Environment) -> None: source = '(lambda (a b) (if (>= a b) a b))' - func = evaluate(parse(source), env) + func = evaluate(parse(source), std_env) assert func.parms == ['a', 'b'] assert func.body == ['if', ['>=', 'a', 'b'], 'a', 'b'] - assert func.env is env + assert func.env is std_env assert func(1, 2) == 2 assert func(3, 2) == 3 -def test_begin() -> None: - env: Environment = standard_env() +def test_begin(std_env: Environment) -> None: source = """ (begin (define x (* 2 3)) (* x 7) ) """ - got = evaluate(parse(source), env) + got = evaluate(parse(source), std_env) assert got == 42 -def test_invocation_builtin_car() -> None: - env: Environment = standard_env() +def test_invocation_builtin_car(std_env: Environment) -> None: source = '(car (quote (11 22 33)))' - got = evaluate(parse(source), env) + got = evaluate(parse(source), std_env) assert got == 11 -def test_invocation_builtin_append() -> None: - env: Environment = standard_env() +def test_invocation_builtin_append(std_env: Environment) -> None: source = '(append (quote (a b)) (quote (c d)))' - got = evaluate(parse(source), env) + got = evaluate(parse(source), std_env) assert got == ['a', 'b', 'c', 'd'] -def test_invocation_builtin_map() -> None: - env: Environment = standard_env() +def test_invocation_builtin_map(std_env: Environment) -> None: source = '(map (lambda (x) (* x 2)) (quote (1 2 3))))' - got = evaluate(parse(source), env) + got = evaluate(parse(source), std_env) assert got == [2, 4, 6] -def test_invocation_user_procedure() -> None: - env: Environment = standard_env() +def test_invocation_user_procedure(std_env: Environment) -> None: source = """ (begin (define max (lambda (a b) (if (>= a b) a b))) (max 22 11) ) """ - got = evaluate(parse(source), env) + got = evaluate(parse(source), std_env) assert got == 22 diff --git a/18-context-mngr/lispy/py3.9/lis_test.py b/18-context-mngr/lispy/py3.9/lis_test.py index 591046d..f9c96ae 100755 --- a/18-context-mngr/lispy/py3.9/lis_test.py +++ b/18-context-mngr/lispy/py3.9/lis_test.py @@ -1,6 +1,6 @@ from typing import Optional -from pytest import mark +from pytest import mark, fixture from lis import parse, evaluate, Expression, Environment, standard_env @@ -39,12 +39,15 @@ ("((repeat riff-shuffle) (list 1 2 3 4 5 6 7 8))", [1, 3, 5, 7, 2, 4, 6, 8]), ("(riff-shuffle (riff-shuffle (riff-shuffle (list 1 2 3 4 5 6 7 8))))", [1,2,3,4,5,6,7,8]), ]) -@mark.skip def test_evaluate(source: str, expected: Optional[Expression]) -> None: got = evaluate(parse(source)) assert got == expected +@fixture +def std_env() -> Environment: + return standard_env() + # tests for each of the cases in evaluate def test_evaluate_variable() -> None: @@ -83,65 +86,58 @@ def test_evaluate_if_false() -> None: assert got == expected -def test_define() -> None: - env: Environment = standard_env() +def test_define(std_env: Environment) -> None: source = '(define answer (* 6 7))' - got = evaluate(parse(source), env) + got = evaluate(parse(source), std_env) assert got is None - assert env['answer'] == 42 + assert std_env['answer'] == 42 -def test_lambda() -> None: - env: Environment = standard_env() +def test_lambda(std_env: Environment) -> None: source = '(lambda (a b) (if (>= a b) a b))' - func = evaluate(parse(source), env) + func = evaluate(parse(source), std_env) assert func.parms == ['a', 'b'] assert func.body == ['if', ['>=', 'a', 'b'], 'a', 'b'] - assert func.env is env + assert func.env is std_env assert func(1, 2) == 2 assert func(3, 2) == 3 -def test_begin() -> None: - env: Environment = standard_env() +def test_begin(std_env: Environment) -> None: source = """ (begin (define x (* 2 3)) (* x 7) ) """ - got = evaluate(parse(source), env) + got = evaluate(parse(source), std_env) assert got == 42 -def test_invocation_builtin_car() -> None: - env: Environment = standard_env() +def test_invocation_builtin_car(std_env: Environment) -> None: source = '(car (quote (11 22 33)))' - got = evaluate(parse(source), env) + got = evaluate(parse(source), std_env) assert got == 11 -def test_invocation_builtin_append() -> None: - env: Environment = standard_env() +def test_invocation_builtin_append(std_env: Environment) -> None: source = '(append (quote (a b)) (quote (c d)))' - got = evaluate(parse(source), env) + got = evaluate(parse(source), std_env) assert got == ['a', 'b', 'c', 'd'] -def test_invocation_builtin_map() -> None: - env: Environment = standard_env() +def test_invocation_builtin_map(std_env: Environment) -> None: source = '(map (lambda (x) (* x 2)) (quote (1 2 3))))' - got = evaluate(parse(source), env) + got = evaluate(parse(source), std_env) assert got == [2, 4, 6] -def test_invocation_user_procedure() -> None: - env: Environment = standard_env() +def test_invocation_user_procedure(std_env: Environment) -> None: source = """ (begin (define max (lambda (a b) (if (>= a b) a b))) (max 22 11) ) """ - got = evaluate(parse(source), env) + got = evaluate(parse(source), std_env) assert got == 22 From dab0e72ec4355294ee8d2a65ac899e849eaf0e7c Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Wed, 9 Jun 2021 01:47:22 -0300 Subject: [PATCH 074/166] ch18: added std_env fixture to lis.py tests --- 18-context-mngr/lispy/py3.10/mypy.ini | 2 ++ 18-context-mngr/lispy/py3.9/mypy.ini | 2 ++ 2 files changed, 4 insertions(+) create mode 100644 18-context-mngr/lispy/py3.10/mypy.ini create mode 100644 18-context-mngr/lispy/py3.9/mypy.ini diff --git a/18-context-mngr/lispy/py3.10/mypy.ini b/18-context-mngr/lispy/py3.10/mypy.ini new file mode 100644 index 0000000..3456561 --- /dev/null +++ b/18-context-mngr/lispy/py3.10/mypy.ini @@ -0,0 +1,2 @@ +[mypy] +python_version = 3.10 diff --git a/18-context-mngr/lispy/py3.9/mypy.ini b/18-context-mngr/lispy/py3.9/mypy.ini new file mode 100644 index 0000000..6fcaf90 --- /dev/null +++ b/18-context-mngr/lispy/py3.9/mypy.ini @@ -0,0 +1,2 @@ +[mypy] +python_version = 3.9 From fb729033fc8eafef0f6f537c77d4d9baae1d69be Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Wed, 9 Jun 2021 13:59:12 -0300 Subject: [PATCH 075/166] Update README.md --- 18-context-mngr/lispy/README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/18-context-mngr/lispy/README.md b/18-context-mngr/lispy/README.md index 7edb1fa..8c5fe94 100644 --- a/18-context-mngr/lispy/README.md +++ b/18-context-mngr/lispy/README.md @@ -23,4 +23,6 @@ and separate tests for each expression and special form handled by `evaluate`. The copyright holder is Peter Norvig and the code is licensed under the [MIT license](https://github.com/norvig/pytudes/blob/60168bce8cdfacf57c92a5b2979f0b2e95367753/LICENSE). -I, Luciano Ramalho, wrote the changes and additions described above. +I wrote the changes and additions described above. + +— Luciano Ramalho (@ramalho), 2021-06-09. From ceb63130ebe9539464c8a622a18ee78f80efa3dd Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Wed, 9 Jun 2021 13:59:51 -0300 Subject: [PATCH 076/166] Update README.md --- 18-context-mngr/lispy/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/18-context-mngr/lispy/README.md b/18-context-mngr/lispy/README.md index 8c5fe94..735f8f9 100644 --- a/18-context-mngr/lispy/README.md +++ b/18-context-mngr/lispy/README.md @@ -25,4 +25,4 @@ The copyright holder is Peter Norvig and the code is licensed under the I wrote the changes and additions described above. -— Luciano Ramalho (@ramalho), 2021-06-09. +— Luciano Ramalho ([@ramalho](https://github.com/ramalho/)), 2021-06-09. From 73900de66b6e8fc15b07e575c52d6b4aeefaaa1c Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Wed, 9 Jun 2021 14:03:24 -0300 Subject: [PATCH 077/166] Update README.md --- 18-context-mngr/lispy/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/18-context-mngr/lispy/README.md b/18-context-mngr/lispy/README.md index 735f8f9..69e042c 100644 --- a/18-context-mngr/lispy/README.md +++ b/18-context-mngr/lispy/README.md @@ -15,7 +15,7 @@ The `py3.9/` and `py3.10/` directories also have identical `lis_test.py` to run These files include all the [`lis_tests` suite](https://github.com/norvig/pytudes/blob/60168bce8cdfacf57c92a5b2979f0b2e95367753/py/lispytest.py#L5) from `original/lispytest.py`, -and separate tests for each expression and special form handled by `evaluate`. +and additional separate tests for each expression and special form handled by `evaluate`. ## Provenance, Copyright and License From b0a258da9df045040af783e2b344c9b7612ca8bd Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Wed, 9 Jun 2021 14:05:19 -0300 Subject: [PATCH 078/166] Update README.md --- 18-context-mngr/lispy/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/18-context-mngr/lispy/README.md b/18-context-mngr/lispy/README.md index 69e042c..cf18e3b 100644 --- a/18-context-mngr/lispy/README.md +++ b/18-context-mngr/lispy/README.md @@ -2,7 +2,7 @@ This directory contains 3 versions of [Peter Norvig's `lis.py` interpreter](https://norvig.com/lispy.html) -for Scheme. +for a subset of [Scheme](https://en.wikipedia.org/wiki/Scheme_(programming_language)). * `original/`: Norvig's `lis.py` unchanged, `lispy.py` with [minor changes](https://github.com/norvig/pytudes/pull/106) to run on Python 3, From 013a26295b71f7eda3663df011d2d6150fd65d07 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Wed, 9 Jun 2021 14:05:43 -0300 Subject: [PATCH 079/166] Update README.md --- 18-context-mngr/lispy/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/18-context-mngr/lispy/README.md b/18-context-mngr/lispy/README.md index cf18e3b..89526d5 100644 --- a/18-context-mngr/lispy/README.md +++ b/18-context-mngr/lispy/README.md @@ -1,6 +1,6 @@ # lis.py -This directory contains 3 versions of +This directory contains 4 versions of [Peter Norvig's `lis.py` interpreter](https://norvig.com/lispy.html) for a subset of [Scheme](https://en.wikipedia.org/wiki/Scheme_(programming_language)). From b4ffc549210c9f18de5e9979e1c91e65571b8173 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Wed, 9 Jun 2021 14:06:00 -0300 Subject: [PATCH 080/166] Update README.md --- 18-context-mngr/lispy/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/18-context-mngr/lispy/README.md b/18-context-mngr/lispy/README.md index 89526d5..cf18e3b 100644 --- a/18-context-mngr/lispy/README.md +++ b/18-context-mngr/lispy/README.md @@ -1,6 +1,6 @@ # lis.py -This directory contains 4 versions of +This directory contains 3 versions of [Peter Norvig's `lis.py` interpreter](https://norvig.com/lispy.html) for a subset of [Scheme](https://en.wikipedia.org/wiki/Scheme_(programming_language)). From 2338cb9d9824ec882ff921e9f2f7119f776d9693 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Wed, 9 Jun 2021 16:15:36 -0300 Subject: [PATCH 081/166] Update lis.py --- 18-context-mngr/lispy/py3.10/lis.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/18-context-mngr/lispy/py3.10/lis.py b/18-context-mngr/lispy/py3.10/lis.py index ca0f8e8..b5df2c0 100644 --- a/18-context-mngr/lispy/py3.10/lis.py +++ b/18-context-mngr/lispy/py3.10/lis.py @@ -25,7 +25,8 @@ def __init__(self, parms: list[str], body: Expression, env: Environment): self.parms, self.body, self.env = parms, body, env def __call__(self, *args: Expression) -> Any: - env: Environment = ChainMap(dict(zip(self.parms, args)), self.env) + local_env = dict(zip(self.parms, args)) + env: Environment = ChainMap(local_env, self.env) return evaluate(self.body, env) From 36afc017d4343b994732b177329cc8b3c1c1f47e Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Wed, 9 Jun 2021 19:41:34 -0300 Subject: [PATCH 082/166] lis.py: removed 'global_env' as default for 'evaluate(x, env)' --- 18-context-mngr/lispy/py3.10/lis.py | 2 +- 18-context-mngr/lispy/py3.10/lis_test.py | 21 ++++++++++++--------- 18-context-mngr/lispy/py3.9/lis.py | 2 +- 18-context-mngr/lispy/py3.9/lis_test.py | 21 ++++++++++++--------- 4 files changed, 26 insertions(+), 20 deletions(-) diff --git a/18-context-mngr/lispy/py3.10/lis.py b/18-context-mngr/lispy/py3.10/lis.py index b5df2c0..aa79411 100644 --- a/18-context-mngr/lispy/py3.10/lis.py +++ b/18-context-mngr/lispy/py3.10/lis.py @@ -139,7 +139,7 @@ def lispstr(exp: object) -> str: ################ eval -def evaluate(x: Expression, env: Environment = global_env) -> Any: +def evaluate(x: Expression, env: Environment) -> Any: "Evaluate an expression in an environment." match x: case str(): # variable reference diff --git a/18-context-mngr/lispy/py3.10/lis_test.py b/18-context-mngr/lispy/py3.10/lis_test.py index f9c96ae..55fd361 100755 --- a/18-context-mngr/lispy/py3.10/lis_test.py +++ b/18-context-mngr/lispy/py3.10/lis_test.py @@ -4,6 +4,9 @@ from lis import parse, evaluate, Expression, Environment, standard_env +# Norvig's tests are not isolated: they assume the +# same environment from first to last test. +ENV_FOR_FIRST_TEST = standard_env() @mark.parametrize( 'source, expected', [ ("(quote (testing 1 (2.0) -3.14e159))", ['testing', 1, [2.0], -3.14e159]), @@ -40,7 +43,7 @@ ("(riff-shuffle (riff-shuffle (riff-shuffle (list 1 2 3 4 5 6 7 8))))", [1,2,3,4,5,6,7,8]), ]) def test_evaluate(source: str, expected: Optional[Expression]) -> None: - got = evaluate(parse(source)) + got = evaluate(parse(source), ENV_FOR_FIRST_TEST) assert got == expected @@ -58,31 +61,31 @@ def test_evaluate_variable() -> None: assert got == expected -def test_evaluate_literal() -> None: +def test_evaluate_literal(std_env: Environment) -> None: source = '3.3' expected = 3.3 - got = evaluate(parse(source)) + got = evaluate(parse(source), std_env) assert got == expected -def test_evaluate_quote() -> None: +def test_evaluate_quote(std_env: Environment) -> None: source = '(quote (1.1 is not 1))' expected = [1.1, 'is', 'not', 1] - got = evaluate(parse(source)) + got = evaluate(parse(source), std_env) assert got == expected -def test_evaluate_if_true() -> None: +def test_evaluate_if_true(std_env: Environment) -> None: source = '(if 1 10 no-such-thing)' expected = 10 - got = evaluate(parse(source)) + got = evaluate(parse(source), std_env) assert got == expected -def test_evaluate_if_false() -> None: +def test_evaluate_if_false(std_env: Environment) -> None: source = '(if 0 no-such-thing 20)' expected = 20 - got = evaluate(parse(source)) + got = evaluate(parse(source), std_env) assert got == expected diff --git a/18-context-mngr/lispy/py3.9/lis.py b/18-context-mngr/lispy/py3.9/lis.py index fcedd1e..1843738 100644 --- a/18-context-mngr/lispy/py3.9/lis.py +++ b/18-context-mngr/lispy/py3.9/lis.py @@ -138,7 +138,7 @@ def lispstr(exp: object) -> str: ################ eval -def evaluate(x: Expression, env: Environment = global_env) -> Any: +def evaluate(x: Expression, env: Environment) -> Any: "Evaluate an expression in an environment." if isinstance(x, str): # variable reference return env[x] diff --git a/18-context-mngr/lispy/py3.9/lis_test.py b/18-context-mngr/lispy/py3.9/lis_test.py index f9c96ae..55fd361 100755 --- a/18-context-mngr/lispy/py3.9/lis_test.py +++ b/18-context-mngr/lispy/py3.9/lis_test.py @@ -4,6 +4,9 @@ from lis import parse, evaluate, Expression, Environment, standard_env +# Norvig's tests are not isolated: they assume the +# same environment from first to last test. +ENV_FOR_FIRST_TEST = standard_env() @mark.parametrize( 'source, expected', [ ("(quote (testing 1 (2.0) -3.14e159))", ['testing', 1, [2.0], -3.14e159]), @@ -40,7 +43,7 @@ ("(riff-shuffle (riff-shuffle (riff-shuffle (list 1 2 3 4 5 6 7 8))))", [1,2,3,4,5,6,7,8]), ]) def test_evaluate(source: str, expected: Optional[Expression]) -> None: - got = evaluate(parse(source)) + got = evaluate(parse(source), ENV_FOR_FIRST_TEST) assert got == expected @@ -58,31 +61,31 @@ def test_evaluate_variable() -> None: assert got == expected -def test_evaluate_literal() -> None: +def test_evaluate_literal(std_env: Environment) -> None: source = '3.3' expected = 3.3 - got = evaluate(parse(source)) + got = evaluate(parse(source), std_env) assert got == expected -def test_evaluate_quote() -> None: +def test_evaluate_quote(std_env: Environment) -> None: source = '(quote (1.1 is not 1))' expected = [1.1, 'is', 'not', 1] - got = evaluate(parse(source)) + got = evaluate(parse(source), std_env) assert got == expected -def test_evaluate_if_true() -> None: +def test_evaluate_if_true(std_env: Environment) -> None: source = '(if 1 10 no-such-thing)' expected = 10 - got = evaluate(parse(source)) + got = evaluate(parse(source), std_env) assert got == expected -def test_evaluate_if_false() -> None: +def test_evaluate_if_false(std_env: Environment) -> None: source = '(if 0 no-such-thing 20)' expected = 20 - got = evaluate(parse(source)) + got = evaluate(parse(source), std_env) assert got == expected From d88b17c75fa51af85deacd8679e3a512b7effb8a Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Wed, 9 Jun 2021 19:58:43 -0300 Subject: [PATCH 083/166] lis.py: explicit var in first match/case --- 18-context-mngr/lispy/py3.10/lis.py | 4 ++-- 18-context-mngr/lispy/py3.10/lis_test.py | 29 ++++++++++++++---------- 18-context-mngr/lispy/py3.9/lis_test.py | 29 ++++++++++++++---------- 3 files changed, 36 insertions(+), 26 deletions(-) diff --git a/18-context-mngr/lispy/py3.10/lis.py b/18-context-mngr/lispy/py3.10/lis.py index aa79411..160d6ec 100644 --- a/18-context-mngr/lispy/py3.10/lis.py +++ b/18-context-mngr/lispy/py3.10/lis.py @@ -142,8 +142,8 @@ def lispstr(exp: object) -> str: def evaluate(x: Expression, env: Environment) -> Any: "Evaluate an expression in an environment." match x: - case str(): # variable reference - return env[x] + case str(var): # variable reference + return env[var] case literal if not isinstance(x, list): # constant literal return literal case ['quote', exp]: # (quote exp) diff --git a/18-context-mngr/lispy/py3.10/lis_test.py b/18-context-mngr/lispy/py3.10/lis_test.py index 55fd361..44c3402 100755 --- a/18-context-mngr/lispy/py3.10/lis_test.py +++ b/18-context-mngr/lispy/py3.10/lis_test.py @@ -14,30 +14,35 @@ ("(+ (* 2 100) (* 1 10))", 210), ("(if (> 6 5) (+ 1 1) (+ 2 2))", 2), ("(if (< 6 5) (+ 1 1) (+ 2 2))", 4), - ("(define x 3)", None), ("x", 3), ("(+ x x)", 6), + ("(define x 3)", None), + ("x", 3), + ("(+ x x)", 6), ("((lambda (x) (+ x x)) 5)", 10), - ("(define twice (lambda (x) (* 2 x)))", None), ("(twice 5)", 10), + ("(define twice (lambda (x) (* 2 x)))", None), + ("(twice 5)", 10), ("(define compose (lambda (f g) (lambda (x) (f (g x)))))", None), ("((compose list twice) 5)", [10]), ("(define repeat (lambda (f) (compose f f)))", None), - ("((repeat twice) 5)", 20), ("((repeat (repeat twice)) 5)", 80), + ("((repeat twice) 5)", 20), + ("((repeat (repeat twice)) 5)", 80), ("(define fact (lambda (n) (if (<= n 1) 1 (* n (fact (- n 1))))))", None), ("(fact 3)", 6), ("(fact 50)", 30414093201713378043612608166064768844377641568960512000000000000), ("(define abs (lambda (n) ((if (> n 0) + -) 0 n)))", None), ("(list (abs -3) (abs 0) (abs 3))", [3, 0, 3]), ("""(define combine (lambda (f) - (lambda (x y) - (if (null? x) (quote ()) - (f (list (car x) (car y)) - ((combine f) (cdr x) (cdr y)))))))""", None), + (lambda (x y) + (if (null? x) (quote ()) + (f (list (car x) (car y)) + ((combine f) (cdr x) (cdr y)))))))""", None), ("(define zip (combine cons))", None), ("(zip (list 1 2 3 4) (list 5 6 7 8))", [[1, 5], [2, 6], [3, 7], [4, 8]]), - ("""(define riff-shuffle (lambda (deck) (begin - (define take (lambda (n seq) (if (<= n 0) (quote ()) (cons (car seq) (take (- n 1) (cdr seq)))))) - (define drop (lambda (n seq) (if (<= n 0) seq (drop (- n 1) (cdr seq))))) - (define mid (lambda (seq) (/ (length seq) 2))) - ((combine append) (take (mid deck) deck) (drop (mid deck) deck)))))""", None), + ("""(define riff-shuffle (lambda (deck) + (begin + (define take (lambda (n seq) (if (<= n 0) (quote ()) (cons (car seq) (take (- n 1) (cdr seq)))))) + (define drop (lambda (n seq) (if (<= n 0) seq (drop (- n 1) (cdr seq))))) + (define mid (lambda (seq) (/ (length seq) 2))) + ((combine append) (take (mid deck) deck) (drop (mid deck) deck)))))""", None), ("(riff-shuffle (list 1 2 3 4 5 6 7 8))", [1, 5, 2, 6, 3, 7, 4, 8]), ("((repeat riff-shuffle) (list 1 2 3 4 5 6 7 8))", [1, 3, 5, 7, 2, 4, 6, 8]), ("(riff-shuffle (riff-shuffle (riff-shuffle (list 1 2 3 4 5 6 7 8))))", [1,2,3,4,5,6,7,8]), diff --git a/18-context-mngr/lispy/py3.9/lis_test.py b/18-context-mngr/lispy/py3.9/lis_test.py index 55fd361..44c3402 100755 --- a/18-context-mngr/lispy/py3.9/lis_test.py +++ b/18-context-mngr/lispy/py3.9/lis_test.py @@ -14,30 +14,35 @@ ("(+ (* 2 100) (* 1 10))", 210), ("(if (> 6 5) (+ 1 1) (+ 2 2))", 2), ("(if (< 6 5) (+ 1 1) (+ 2 2))", 4), - ("(define x 3)", None), ("x", 3), ("(+ x x)", 6), + ("(define x 3)", None), + ("x", 3), + ("(+ x x)", 6), ("((lambda (x) (+ x x)) 5)", 10), - ("(define twice (lambda (x) (* 2 x)))", None), ("(twice 5)", 10), + ("(define twice (lambda (x) (* 2 x)))", None), + ("(twice 5)", 10), ("(define compose (lambda (f g) (lambda (x) (f (g x)))))", None), ("((compose list twice) 5)", [10]), ("(define repeat (lambda (f) (compose f f)))", None), - ("((repeat twice) 5)", 20), ("((repeat (repeat twice)) 5)", 80), + ("((repeat twice) 5)", 20), + ("((repeat (repeat twice)) 5)", 80), ("(define fact (lambda (n) (if (<= n 1) 1 (* n (fact (- n 1))))))", None), ("(fact 3)", 6), ("(fact 50)", 30414093201713378043612608166064768844377641568960512000000000000), ("(define abs (lambda (n) ((if (> n 0) + -) 0 n)))", None), ("(list (abs -3) (abs 0) (abs 3))", [3, 0, 3]), ("""(define combine (lambda (f) - (lambda (x y) - (if (null? x) (quote ()) - (f (list (car x) (car y)) - ((combine f) (cdr x) (cdr y)))))))""", None), + (lambda (x y) + (if (null? x) (quote ()) + (f (list (car x) (car y)) + ((combine f) (cdr x) (cdr y)))))))""", None), ("(define zip (combine cons))", None), ("(zip (list 1 2 3 4) (list 5 6 7 8))", [[1, 5], [2, 6], [3, 7], [4, 8]]), - ("""(define riff-shuffle (lambda (deck) (begin - (define take (lambda (n seq) (if (<= n 0) (quote ()) (cons (car seq) (take (- n 1) (cdr seq)))))) - (define drop (lambda (n seq) (if (<= n 0) seq (drop (- n 1) (cdr seq))))) - (define mid (lambda (seq) (/ (length seq) 2))) - ((combine append) (take (mid deck) deck) (drop (mid deck) deck)))))""", None), + ("""(define riff-shuffle (lambda (deck) + (begin + (define take (lambda (n seq) (if (<= n 0) (quote ()) (cons (car seq) (take (- n 1) (cdr seq)))))) + (define drop (lambda (n seq) (if (<= n 0) seq (drop (- n 1) (cdr seq))))) + (define mid (lambda (seq) (/ (length seq) 2))) + ((combine append) (take (mid deck) deck) (drop (mid deck) deck)))))""", None), ("(riff-shuffle (list 1 2 3 4 5 6 7 8))", [1, 5, 2, 6, 3, 7, 4, 8]), ("((repeat riff-shuffle) (list 1 2 3 4 5 6 7 8))", [1, 3, 5, 7, 2, 4, 6, 8]), ("(riff-shuffle (riff-shuffle (riff-shuffle (list 1 2 3 4 5 6 7 8))))", [1,2,3,4,5,6,7,8]), From 0cbb0c557cc73c2a01cd30bf8200b302cefe71e9 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Wed, 9 Jun 2021 23:18:50 -0300 Subject: [PATCH 084/166] lis.py: added 'define' procedure short form --- 18-context-mngr/lispy/py3.10/lis.py | 24 +++++++++++++----------- 18-context-mngr/lispy/py3.10/lis_test.py | 18 ++++++++++++++++-- 18-context-mngr/lispy/py3.9/lis.py | 20 ++++++++++---------- 3 files changed, 39 insertions(+), 23 deletions(-) mode change 100755 => 100644 18-context-mngr/lispy/py3.10/lis_test.py diff --git a/18-context-mngr/lispy/py3.10/lis.py b/18-context-mngr/lispy/py3.10/lis.py index 160d6ec..6aacaf8 100644 --- a/18-context-mngr/lispy/py3.10/lis.py +++ b/18-context-mngr/lispy/py3.10/lis.py @@ -12,16 +12,17 @@ from collections.abc import MutableMapping from typing import Any, TypeAlias -Atom: TypeAlias = float | int | str +Symbol: TypeAlias = str +Atom: TypeAlias = float | int | Symbol Expression: TypeAlias = Atom | list -Environment: TypeAlias = MutableMapping[str, object] +Environment: TypeAlias = MutableMapping[Symbol, object] class Procedure: "A user-defined Scheme procedure." - def __init__(self, parms: list[str], body: Expression, env: Environment): + def __init__(self, parms: list[Symbol], body: Expression, env: Environment): self.parms, self.body, self.env = parms, body, env def __call__(self, *args: Expression) -> Any: @@ -68,14 +69,12 @@ def standard_env() -> Environment: 'number?': lambda x: isinstance(x, (int, float)), 'procedure?': callable, 'round': round, - 'symbol?': lambda x: isinstance(x, str), + 'symbol?': lambda x: isinstance(x, Symbol), } ) return env -global_env: Environment = standard_env() - ################ Parsing: parse, tokenize, and read_from_tokens @@ -114,7 +113,7 @@ def parse_atom(token: str) -> Atom: try: return float(token) except ValueError: - return str(token) + return Symbol(token) ################ Interaction: A REPL @@ -122,8 +121,9 @@ def parse_atom(token: str) -> Atom: def repl(prompt: str = 'lis.py> ') -> None: "A prompt-read-evaluate-print loop." + global_env: Environment = standard_env() while True: - val = evaluate(parse(input(prompt))) + val = evaluate(parse(input(prompt)), global_env) if val is not None: print(lispstr(val)) @@ -142,7 +142,7 @@ def lispstr(exp: object) -> str: def evaluate(x: Expression, env: Environment) -> Any: "Evaluate an expression in an environment." match x: - case str(var): # variable reference + case Symbol(var): # variable reference return env[var] case literal if not isinstance(x, list): # constant literal return literal @@ -151,9 +151,11 @@ def evaluate(x: Expression, env: Environment) -> Any: case ['if', test, conseq, alt]: # (if test conseq alt) exp = conseq if evaluate(test, env) else alt return evaluate(exp, env) - case ['define', var, exp]: # (define var exp) + case ['define', Symbol(var), exp]: # (define var exp) env[var] = evaluate(exp, env) - case ['lambda', parms, body]: # (lambda (var...) body) + case ['define', [name, *parms], body]: # (define (fun parm...) body) + env[name] = Procedure(parms, body, env) + case ['lambda', parms, body]: # (lambda (parm...) body) return Procedure(parms, body, env) case [op, *args]: # (proc arg...) proc = evaluate(op, env) diff --git a/18-context-mngr/lispy/py3.10/lis_test.py b/18-context-mngr/lispy/py3.10/lis_test.py old mode 100755 new mode 100644 index 44c3402..b2eb28a --- a/18-context-mngr/lispy/py3.10/lis_test.py +++ b/18-context-mngr/lispy/py3.10/lis_test.py @@ -6,7 +6,7 @@ # Norvig's tests are not isolated: they assume the # same environment from first to last test. -ENV_FOR_FIRST_TEST = standard_env() +global_env_for_first_test = standard_env() @mark.parametrize( 'source, expected', [ ("(quote (testing 1 (2.0) -3.14e159))", ['testing', 1, [2.0], -3.14e159]), @@ -48,7 +48,7 @@ ("(riff-shuffle (riff-shuffle (riff-shuffle (list 1 2 3 4 5 6 7 8))))", [1,2,3,4,5,6,7,8]), ]) def test_evaluate(source: str, expected: Optional[Expression]) -> None: - got = evaluate(parse(source), ENV_FOR_FIRST_TEST) + got = evaluate(parse(source), global_env_for_first_test) assert got == expected @@ -149,3 +149,17 @@ def test_invocation_user_procedure(std_env: Environment) -> None: """ got = evaluate(parse(source), std_env) assert got == 22 + + +###################################### for py3.10/lis.py only + +def test_define_function(std_env: Environment) -> None: + source = '(define (max a b) (if (>= a b) a b))' + got = evaluate(parse(source), std_env) + assert got is None + max_fn = std_env['max'] + assert max_fn.parms == ['a', 'b'] + assert max_fn.body == ['if', ['>=', 'a', 'b'], 'a', 'b'] + assert max_fn.env is std_env + assert max_fn(1, 2) == 2 + assert max_fn(3, 2) == 3 diff --git a/18-context-mngr/lispy/py3.9/lis.py b/18-context-mngr/lispy/py3.9/lis.py index 1843738..1a14935 100644 --- a/18-context-mngr/lispy/py3.9/lis.py +++ b/18-context-mngr/lispy/py3.9/lis.py @@ -12,20 +12,21 @@ from collections.abc import MutableMapping from typing import Union, Any -Atom = Union[float, int, str] +Symbol = str +Atom = Union[float, int, Symbol] Expression = Union[Atom, list] -Environment = MutableMapping[str, object] +Environment = MutableMapping[Symbol, object] class Procedure: "A user-defined Scheme procedure." - - def __init__(self, parms: list[str], body: Expression, env: Environment): + def __init__(self, parms: list[Symbol], body: Expression, env: Environment): self.parms, self.body, self.env = parms, body, env def __call__(self, *args: Expression) -> Any: - env: Environment = ChainMap(dict(zip(self.parms, args)), self.env) + local_env = dict(zip(self.parms, args)) + env: Environment = ChainMap(local_env, self.env) return evaluate(self.body, env) @@ -67,14 +68,12 @@ def standard_env() -> Environment: 'number?': lambda x: isinstance(x, (int, float)), 'procedure?': callable, 'round': round, - 'symbol?': lambda x: isinstance(x, str), + 'symbol?': lambda x: isinstance(x, Symbol), } ) return env -global_env: Environment = standard_env() - ################ Parsing: parse, tokenize, and read_from_tokens @@ -113,7 +112,7 @@ def parse_atom(token: str) -> Atom: try: return float(token) except ValueError: - return str(token) + return Symbol(token) ################ Interaction: A REPL @@ -121,8 +120,9 @@ def parse_atom(token: str) -> Atom: def repl(prompt: str = 'lis.py> ') -> None: "A prompt-read-evaluate-print loop." + global_env: Environment = standard_env() while True: - val = evaluate(parse(input(prompt))) + val = evaluate(parse(input(prompt)), global_env) if val is not None: print(lispstr(val)) From e5173fb72ba792189cfa852e4ea1e4d600a8d173 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Wed, 9 Jun 2021 23:27:42 -0300 Subject: [PATCH 085/166] lis.py: added 'define' procedure short form --- 18-context-mngr/lispy/py3.10/lis.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/18-context-mngr/lispy/py3.10/lis.py b/18-context-mngr/lispy/py3.10/lis.py index 6aacaf8..628ddc7 100644 --- a/18-context-mngr/lispy/py3.10/lis.py +++ b/18-context-mngr/lispy/py3.10/lis.py @@ -142,22 +142,22 @@ def lispstr(exp: object) -> str: def evaluate(x: Expression, env: Environment) -> Any: "Evaluate an expression in an environment." match x: - case Symbol(var): # variable reference + case Symbol(var): # variable reference return env[var] - case literal if not isinstance(x, list): # constant literal + case literal if not isinstance(x, list): # constant literal return literal - case ['quote', exp]: # (quote exp) + case ['quote', exp]: # (quote exp) return exp - case ['if', test, conseq, alt]: # (if test conseq alt) + case ['if', test, conseq, alt]: # (if test conseq alt) exp = conseq if evaluate(test, env) else alt return evaluate(exp, env) - case ['define', Symbol(var), exp]: # (define var exp) + case ['lambda', parms, body]: # (lambda (parm...) body) + return Procedure(parms, body, env) + case ['define', Symbol(var), exp]: # (define var exp) env[var] = evaluate(exp, env) - case ['define', [name, *parms], body]: # (define (fun parm...) body) + case ['define', [name, *parms], body]: # (define (fun parm...) body) env[name] = Procedure(parms, body, env) - case ['lambda', parms, body]: # (lambda (parm...) body) - return Procedure(parms, body, env) - case [op, *args]: # (proc arg...) + case [op, *args]: # (proc arg...) proc = evaluate(op, env) values = (evaluate(arg, env) for arg in args) return proc(*values) From ca68e2e7e7dcd64efa978803e9931c092de352c7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 10 Jun 2021 15:57:02 +0000 Subject: [PATCH 086/166] build(deps): bump fastapi from 0.63.0 to 0.65.2 in /22-async/mojifinder Bumps [fastapi](https://github.com/tiangolo/fastapi) from 0.63.0 to 0.65.2. - [Release notes](https://github.com/tiangolo/fastapi/releases) - [Commits](https://github.com/tiangolo/fastapi/compare/0.63.0...0.65.2) --- updated-dependencies: - dependency-name: fastapi dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- 22-async/mojifinder/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/22-async/mojifinder/requirements.txt b/22-async/mojifinder/requirements.txt index 04ef58d..1f7c3d6 100644 --- a/22-async/mojifinder/requirements.txt +++ b/22-async/mojifinder/requirements.txt @@ -1,5 +1,5 @@ click==7.1.2 -fastapi==0.63.0 +fastapi==0.65.2 h11==0.12.0 pydantic==1.8.2 starlette==0.13.6 From 3606b1d411d0b81fc105984af8f587c035974b2e Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Thu, 17 Jun 2021 15:50:11 -0300 Subject: [PATCH 087/166] Update lis.py --- 18-context-mngr/lispy/py3.10/lis.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/18-context-mngr/lispy/py3.10/lis.py b/18-context-mngr/lispy/py3.10/lis.py index 628ddc7..ffd53c0 100644 --- a/18-context-mngr/lispy/py3.10/lis.py +++ b/18-context-mngr/lispy/py3.10/lis.py @@ -149,7 +149,10 @@ def evaluate(x: Expression, env: Environment) -> Any: case ['quote', exp]: # (quote exp) return exp case ['if', test, conseq, alt]: # (if test conseq alt) - exp = conseq if evaluate(test, env) else alt + if evaluate(test, env): + exp = conseq + else: + exp = alt return evaluate(exp, env) case ['lambda', parms, body]: # (lambda (parm...) body) return Procedure(parms, body, env) From f0f160844d097133597e5583eb47c53704a2447d Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Sat, 26 Jun 2021 13:42:28 -0300 Subject: [PATCH 088/166] sync with O'Reilly Atlas --- 02-array-seq/lispy/examples_test.py | 109 ++++++ .../py3.10 => 02-array-seq/lispy}/lis.py | 92 +++-- .../py3.10 => 02-array-seq/lispy}/lis_test.py | 17 + 02-array-seq/lispy/meta_test.py | 64 ++++ 02-array-seq/match_lat_lon.py | 32 ++ .../{metro_lat_long.py => metro_lat_lon.py} | 12 +- 04-text-byte/simplify.py | 2 +- 04-text-byte/zwj_sample.py | 3 +- 05-record-like/class/coordinates.py | 6 +- 05-record-like/dataclass/coordinates.py | 6 +- .../typing_namedtuple/coordinates.py | 8 +- .../typing_namedtuple/coordinates2.py | 6 +- .../typing_namedtuple/nocheck_demo.py | 2 +- 09-closure-deco/clockdeco.py | 8 +- 09-closure-deco/fibo_demo_cache.py | 2 +- 09-closure-deco/htmlizer.py | 4 +- 11-pythonic-obj/private/Confidential.java | 2 +- 18-context-mngr/lispy/LICENSE | 21 -- 18-context-mngr/lispy/README.md | 28 -- 18-context-mngr/lispy/original/lis.py | 132 -------- 18-context-mngr/lispy/original/lispy.py | 316 ------------------ 18-context-mngr/lispy/original/lispytest.py | 122 ------- 18-context-mngr/lispy/py3.10/mypy.ini | 2 - 18-context-mngr/lispy/py3.9/lis.py | 163 --------- 18-context-mngr/lispy/py3.9/lis_test.py | 151 --------- 18-context-mngr/lispy/py3.9/mypy.ini | 2 - 26 files changed, 308 insertions(+), 1004 deletions(-) create mode 100644 02-array-seq/lispy/examples_test.py rename {18-context-mngr/lispy/py3.10 => 02-array-seq/lispy}/lis.py (67%) rename {18-context-mngr/lispy/py3.10 => 02-array-seq/lispy}/lis_test.py (90%) create mode 100644 02-array-seq/lispy/meta_test.py create mode 100644 02-array-seq/match_lat_lon.py rename 02-array-seq/{metro_lat_long.py => metro_lat_lon.py} (61%) delete mode 100644 18-context-mngr/lispy/LICENSE delete mode 100644 18-context-mngr/lispy/README.md delete mode 100644 18-context-mngr/lispy/original/lis.py delete mode 100644 18-context-mngr/lispy/original/lispy.py delete mode 100644 18-context-mngr/lispy/original/lispytest.py delete mode 100644 18-context-mngr/lispy/py3.10/mypy.ini delete mode 100644 18-context-mngr/lispy/py3.9/lis.py delete mode 100755 18-context-mngr/lispy/py3.9/lis_test.py delete mode 100644 18-context-mngr/lispy/py3.9/mypy.ini diff --git a/02-array-seq/lispy/examples_test.py b/02-array-seq/lispy/examples_test.py new file mode 100644 index 0000000..38510a3 --- /dev/null +++ b/02-array-seq/lispy/examples_test.py @@ -0,0 +1,109 @@ +""" +Doctests for `parse`: + +>>> from lis import parse + +# tag::PARSE_DEMO[] +>>> parse('1.5') # <1> +1.5 +>>> parse('set!') # <2> +'set!' +>>> parse('(gcd 18 44)') # <3> +['gcd', 18, 44] +>>> parse('(- m (* n (// m n)))') # <4> +['-', 'm', ['*', 'n', ['//', 'm', 'n']]] + +# end::PARSE_DEMO[] + +""" + +import math + +from lis import run + + +fact_src = """ +(define (! n) + (if (< n 2) + 1 + (* n (! (- n 1))) + ) +) +(! 42) +""" +def test_factorial(): + got = run(fact_src) + assert got == 1405006117752879898543142606244511569936384000000000 + assert got == math.factorial(42) + + +gcd_src = """ +(define (mod m n) + (- m (* n (// m n)))) +(define (gcd m n) + (if (= n 0) + m + (gcd n (mod m n)))) +(gcd 18 45) +""" +def test_gcd(): + got = run(gcd_src) + assert got == 9 + + +quicksort_src = """ +(define (quicksort lst) + (if (null? lst) + lst + (begin + (define pivot (car lst)) + (define rest (cdr lst)) + (append + (quicksort + (filter (lambda (x) (< x pivot)) rest)) + (list pivot) + (quicksort + (filter (lambda (x) (>= x pivot)) rest))) + ) + ) +) +(quicksort (list 2 1 6 3 4 0 8 9 7 5)) +""" +def test_quicksort(): + got = run(quicksort_src) + assert got == [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + + +# Example from Structure and Interpretation of Computer Programs +# https://mitpress.mit.edu/sites/default/files/sicp/full-text/sicp/book/node12.html + +newton_src = """ +(define (sqrt x) + (sqrt-iter 1.0 x)) +(define (sqrt-iter guess x) + (if (good-enough? guess x) + guess + (sqrt-iter (improve guess x) x))) +(define (good-enough? guess x) + (< (abs (- (* guess guess) x)) 0.001)) +(define (improve guess x) + (average guess (/ x guess))) +(define (average x y) + (/ (+ x y) 2)) +(sqrt 123454321) +""" +def test_newton(): + got = run(newton_src) + assert math.isclose(got, 11111) + + +closure_src = """ +(define (make-adder increment) + (lambda (x) (+ increment x)) +) +(define inc (make-adder 1)) +(inc 99) +""" +def test_newton(): + got = run(closure_src) + assert got == 100 diff --git a/18-context-mngr/lispy/py3.10/lis.py b/02-array-seq/lispy/lis.py similarity index 67% rename from 18-context-mngr/lispy/py3.10/lis.py rename to 02-array-seq/lispy/lis.py index ffd53c0..e37ba23 100644 --- a/18-context-mngr/lispy/py3.10/lis.py +++ b/02-array-seq/lispy/lis.py @@ -1,15 +1,16 @@ -################ Lispy: Scheme Interpreter in Python 3.9 +################ Lispy: Scheme Interpreter in Python 3.10 ## (c) Peter Norvig, 2010-18; See http://norvig.com/lispy.html ## Minor edits for Fluent Python, Second Edition (O'Reilly, 2021) ## by Luciano Ramalho, adding type hints and pattern matching. -################ Imports and Types +################ imports and types import math import operator as op from collections import ChainMap -from collections.abc import MutableMapping +from collections.abc import MutableMapping, Iterator +from itertools import chain from typing import Any, TypeAlias Symbol: TypeAlias = str @@ -23,7 +24,9 @@ class Procedure: "A user-defined Scheme procedure." def __init__(self, parms: list[Symbol], body: Expression, env: Environment): - self.parms, self.body, self.env = parms, body, env + self.parms = parms + self.body = body + self.env = env def __call__(self, *args: Expression) -> Any: local_env = dict(zip(self.parms, args)) @@ -31,26 +34,26 @@ def __call__(self, *args: Expression) -> Any: return evaluate(self.body, env) -################ Global Environment +################ global environment def standard_env() -> Environment: "An environment with some Scheme standard procedures." env: Environment = {} env.update(vars(math)) # sin, cos, sqrt, pi, ... - env.update( - { + env.update({ '+': op.add, '-': op.sub, '*': op.mul, '/': op.truediv, + '//': op.floordiv, '>': op.gt, '<': op.lt, '>=': op.ge, '<=': op.le, '=': op.eq, 'abs': abs, - 'append': op.add, + 'append': lambda *args: list(chain(*args)), 'apply': lambda proc, args: proc(*args), 'begin': lambda *x: x[-1], 'car': lambda x: x[0], @@ -58,6 +61,7 @@ def standard_env() -> Environment: 'cons': lambda x, y: [x] + y, 'eq?': op.is_, 'equal?': op.eq, + 'filter': lambda *args: list(filter(*args)), 'length': len, 'list': lambda *x: list(x), 'list?': lambda x: isinstance(x, list), @@ -70,12 +74,11 @@ def standard_env() -> Environment: 'procedure?': callable, 'round': round, 'symbol?': lambda x: isinstance(x, Symbol), - } - ) + }) return env -################ Parsing: parse, tokenize, and read_from_tokens +################ parse, tokenize, and read_from_tokens def parse(program: str) -> Expression: @@ -94,11 +97,11 @@ def read_from_tokens(tokens: list[str]) -> Expression: raise SyntaxError('unexpected EOF while reading') token = tokens.pop(0) if '(' == token: - L = [] + exp = [] while tokens[0] != ')': - L.append(read_from_tokens(tokens)) - tokens.pop(0) # pop off ')' - return L + exp.append(read_from_tokens(tokens)) + tokens.pop(0) # discard ')' + return exp elif ')' == token: raise SyntaxError('unexpected )') else: @@ -116,7 +119,7 @@ def parse_atom(token: str) -> Atom: return Symbol(token) -################ Interaction: A REPL +################ interaction: a REPL def repl(prompt: str = 'lis.py> ') -> None: @@ -138,29 +141,50 @@ def lispstr(exp: object) -> str: ################ eval - -def evaluate(x: Expression, env: Environment) -> Any: +# tag::EVALUATE[] +def evaluate(exp: Expression, env: Environment) -> Any: "Evaluate an expression in an environment." - match x: - case Symbol(var): # variable reference + match exp: + case int(x) | float(x): + return x + case Symbol(var): return env[var] - case literal if not isinstance(x, list): # constant literal - return literal - case ['quote', exp]: # (quote exp) + case []: + return [] + case ['quote', exp]: return exp - case ['if', test, conseq, alt]: # (if test conseq alt) + case ['if', test, consequence, alternative]: if evaluate(test, env): - exp = conseq + return evaluate(consequence, env) else: - exp = alt - return evaluate(exp, env) - case ['lambda', parms, body]: # (lambda (parm...) body) - return Procedure(parms, body, env) - case ['define', Symbol(var), exp]: # (define var exp) - env[var] = evaluate(exp, env) - case ['define', [name, *parms], body]: # (define (fun parm...) body) + return evaluate(alternative, env) + case ['define', Symbol(var), value_exp]: + env[var] = evaluate(value_exp, env) + case ['define', [Symbol(name), *parms], body]: env[name] = Procedure(parms, body, env) - case [op, *args]: # (proc arg...) + case ['lambda', [*parms], body]: + return Procedure(parms, body, env) + case [op, *args]: proc = evaluate(op, env) - values = (evaluate(arg, env) for arg in args) + values = [evaluate(arg, env) for arg in args] return proc(*values) + case _: + raise SyntaxError(repr(exp)) +# end::EVALUATE[] + + +################ non-interactive execution + + +def run_lines(source: str) -> Iterator[Any]: + global_env: Environment = standard_env() + tokens = tokenize(source) + while tokens: + exp = read_from_tokens(tokens) + yield evaluate(exp, global_env) + + +def run(source: str) -> Any: + for result in run_lines(source): + pass + return result diff --git a/18-context-mngr/lispy/py3.10/lis_test.py b/02-array-seq/lispy/lis_test.py similarity index 90% rename from 18-context-mngr/lispy/py3.10/lis_test.py rename to 02-array-seq/lispy/lis_test.py index b2eb28a..e220e74 100644 --- a/18-context-mngr/lispy/py3.10/lis_test.py +++ b/02-array-seq/lispy/lis_test.py @@ -4,6 +4,23 @@ from lis import parse, evaluate, Expression, Environment, standard_env +############################################################# tests for parse + +@mark.parametrize( 'source, expected', [ + ('7', 7), + ('x', 'x'), + ('(sum 1 2 3)', ['sum', 1, 2, 3]), + ('(+ (* 2 100) (* 1 10))', ['+', ['*', 2, 100], ['*', 1, 10]]), + ('99 100', 99), # parse stops at the first complete expression + ('(a)(b)', ['a']), +]) +def test_parse(source: str, expected: Expression) -> None: + got = parse(source) + assert got == expected + + +########################################################## tests for evaluate + # Norvig's tests are not isolated: they assume the # same environment from first to last test. global_env_for_first_test = standard_env() diff --git a/02-array-seq/lispy/meta_test.py b/02-array-seq/lispy/meta_test.py new file mode 100644 index 0000000..cb3d062 --- /dev/null +++ b/02-array-seq/lispy/meta_test.py @@ -0,0 +1,64 @@ +import operator as op + +from lis import run + +env_scm = """ +(define standard-env (list + (list (quote +) +) + (list (quote -) -) +)) +standard-env +""" + +def test_env_build(): + got = run(env_scm) + assert got == [['+', op.add], ['-', op.sub]] + +scan_scm = """ +(define l (quote (a b c))) +(define (scan what where) + (if (null? where) + () + (if (eq? what (car where)) + what + (scan what (cdr where)))) +) +""" + +def test_scan(): + source = scan_scm + '(scan (quote a) l )' + got = run(source) + assert got == 'a' + + +def test_scan_not_found(): + source = scan_scm + '(scan (quote z) l )' + got = run(source) + assert got == [] + + +lookup_scm = """ +(define env (list + (list (quote +) +) + (list (quote -) -) +)) +(define (lookup what where) + (if (null? where) + () + (if (eq? what (car (car where))) + (car (cdr (car where))) + (lookup what (cdr where)))) +) +""" + +def test_lookup(): + source = lookup_scm + '(lookup (quote +) env)' + got = run(source) + assert got == op.add + + +def test_lookup_not_found(): + source = lookup_scm + '(lookup (quote z) env )' + got = run(source) + assert got == [] + diff --git a/02-array-seq/match_lat_lon.py b/02-array-seq/match_lat_lon.py new file mode 100644 index 0000000..592aa70 --- /dev/null +++ b/02-array-seq/match_lat_lon.py @@ -0,0 +1,32 @@ +""" +metro_lat_long.py + +Demonstration of nested tuple unpacking:: + + >>> main() + | latitude | longitude + Mexico City | 19.4333 | -99.1333 + New York-Newark | 40.8086 | -74.0204 + Sao Paulo | -23.5478 | -46.6358 + +""" + +# tag::MAIN[] +metro_areas = [ + ('Tokyo', 'JP', 36.933, (35.689722, 139.691667)), + ('Delhi NCR', 'IN', 21.935, (28.613889, 77.208889)), + ('Mexico City', 'MX', 20.142, (19.433333, -99.133333)), + ('New York-Newark', 'US', 20.104, (40.808611, -74.020386)), + ('Sao Paulo', 'BR', 19.649, (-23.547778, -46.635833)), +] + +def main(): + print(f'{"":15} | {"latitude":>9} | {"longitude":>9}') + for record in metro_areas: + match record: + case [name, _, _, (lat, lon)] if lon <= 0: + print(f'{name:15} | {lat:9.4f} | {lon:9.4f}') +# end::MAIN[] + +if __name__ == '__main__': + main() diff --git a/02-array-seq/metro_lat_long.py b/02-array-seq/metro_lat_lon.py similarity index 61% rename from 02-array-seq/metro_lat_long.py rename to 02-array-seq/metro_lat_lon.py index 25b2373..930a993 100644 --- a/02-array-seq/metro_lat_long.py +++ b/02-array-seq/metro_lat_lon.py @@ -4,7 +4,7 @@ Demonstration of nested tuple unpacking:: >>> main() - | lat. | long. + | latitude | longitude Mexico City | 19.4333 | -99.1333 New York-Newark | 40.8086 | -74.0204 Sao Paulo | -23.5478 | -46.6358 @@ -12,7 +12,7 @@ """ metro_areas = [ - ('Tokyo', 'JP', 36.933, (35.689722, 139.691667)), # <1> + ('Tokyo', 'JP', 36.933, (35.689722, 139.691667)), # <1> ('Delhi NCR', 'IN', 21.935, (28.613889, 77.208889)), ('Mexico City', 'MX', 20.142, (19.433333, -99.133333)), ('New York-Newark', 'US', 20.104, (40.808611, -74.020386)), @@ -20,10 +20,10 @@ ] def main(): - print(f'{"":15} | {"lat.":^9} | {"long.":^9}') - for name, cc, pop, (latitude, longitude) in metro_areas: # <2> - if longitude <= 0: # <3> - print(f'{name:15} | {latitude:9.4f} | {longitude:9.4f}') + print(f'{"":15} | {"latitude":>9} | {"longitude":>9}') + for name, _, _, (lat, lon) in metro_areas: # <2> + if lon <= 0: # <3> + print(f'{name:15} | {lat:9.4f} | {lon:9.4f}') if __name__ == '__main__': main() diff --git a/04-text-byte/simplify.py b/04-text-byte/simplify.py index ebbdcf8..0852bd2 100644 --- a/04-text-byte/simplify.py +++ b/04-text-byte/simplify.py @@ -51,7 +51,7 @@ def shave_marks_latin(txt): if unicodedata.combining(c) and latin_base: # <2> continue # ignore diacritic on Latin base char preserve.append(c) # <3> - # if it isn't combining char, it's a new base char + # if it isn't a combining char, it's a new base char if not unicodedata.combining(c): # <4> latin_base = c in string.ascii_letters shaved = ''.join(preserve) diff --git a/04-text-byte/zwj_sample.py b/04-text-byte/zwj_sample.py index 28893cc..a80f024 100644 --- a/04-text-byte/zwj_sample.py +++ b/04-text-byte/zwj_sample.py @@ -18,8 +18,7 @@ code, descr, version = (s.strip() for s in line.split('|')) chars = [chr(int(c, 16)) for c in code.split()] print(''.join(chars), version, descr, sep='\t', end='') - while chars: - char = chars.pop(0) + for char in chars: if char in markers: print(' + ' + markers[char], end='') else: diff --git a/05-record-like/class/coordinates.py b/05-record-like/class/coordinates.py index 9186c1b..683a97a 100644 --- a/05-record-like/class/coordinates.py +++ b/05-record-like/class/coordinates.py @@ -9,8 +9,8 @@ # tag::COORDINATE[] class Coordinate: - def __init__(self, lat, long): + def __init__(self, lat, lon): self.lat = lat - self.long = long + self.lon = lon -# end::COORDINATE[] \ No newline at end of file +# end::COORDINATE[] diff --git a/05-record-like/dataclass/coordinates.py b/05-record-like/dataclass/coordinates.py index baacb6e..d7ba76b 100644 --- a/05-record-like/dataclass/coordinates.py +++ b/05-record-like/dataclass/coordinates.py @@ -14,10 +14,10 @@ @dataclass(frozen=True) class Coordinate: lat: float - long: float + lon: float def __str__(self): ns = 'N' if self.lat >= 0 else 'S' - we = 'E' if self.long >= 0 else 'W' - return f'{abs(self.lat):.1f}°{ns}, {abs(self.long):.1f}°{we}' + we = 'E' if self.lon >= 0 else 'W' + return f'{abs(self.lat):.1f}°{ns}, {abs(self.lon):.1f}°{we}' # end::COORDINATE[] diff --git a/05-record-like/typing_namedtuple/coordinates.py b/05-record-like/typing_namedtuple/coordinates.py index 382d804..378a430 100644 --- a/05-record-like/typing_namedtuple/coordinates.py +++ b/05-record-like/typing_namedtuple/coordinates.py @@ -13,10 +13,10 @@ class Coordinate(NamedTuple): lat: float - long: float + lon: float def __str__(self): ns = 'N' if self.lat >= 0 else 'S' - we = 'E' if self.long >= 0 else 'W' - return f'{abs(self.lat):.1f}°{ns}, {abs(self.long):.1f}°{we}' -# end::COORDINATE[] \ No newline at end of file + we = 'E' if self.lon >= 0 else 'W' + return f'{abs(self.lat):.1f}°{ns}, {abs(self.lon):.1f}°{we}' +# end::COORDINATE[] diff --git a/05-record-like/typing_namedtuple/coordinates2.py b/05-record-like/typing_namedtuple/coordinates2.py index a251429..e523a11 100644 --- a/05-record-like/typing_namedtuple/coordinates2.py +++ b/05-record-like/typing_namedtuple/coordinates2.py @@ -5,7 +5,7 @@ >>> moscow = Coordinate(55.756, 37.617) >>> moscow - Coordinate(lat=55.756, long=37.617, reference='WGS84') + Coordinate(lat=55.756, lon=37.617, reference='WGS84') """ @@ -15,6 +15,6 @@ class Coordinate(NamedTuple): lat: float # <1> - long: float + lon: float reference: str = 'WGS84' # <2> -# end::COORDINATE[] \ No newline at end of file +# end::COORDINATE[] diff --git a/05-record-like/typing_namedtuple/nocheck_demo.py b/05-record-like/typing_namedtuple/nocheck_demo.py index e1193ec..57e8fc8 100644 --- a/05-record-like/typing_namedtuple/nocheck_demo.py +++ b/05-record-like/typing_namedtuple/nocheck_demo.py @@ -3,7 +3,7 @@ class Coordinate(typing.NamedTuple): lat: float - long: float + lon: float trash = Coordinate('foo', None) # <1> print(trash) diff --git a/09-closure-deco/clockdeco.py b/09-closure-deco/clockdeco.py index e45470d..746711b 100644 --- a/09-closure-deco/clockdeco.py +++ b/09-closure-deco/clockdeco.py @@ -9,12 +9,8 @@ def clocked(*args, **kwargs): result = func(*args, **kwargs) elapsed = time.perf_counter() - t0 name = func.__name__ - arg_lst = [] - if args: - arg_lst.append(', '.join(repr(arg) for arg in args)) - if kwargs: - pairs = [f'{k}={v!r}' for k, v in kwargs.items()] - arg_lst.append(', '.join(pairs)) + arg_lst = [repr(arg) for arg in args] + arg_lst.extend(f'{k}={v!r}' for k, v in kwargs.items()) arg_str = ', '.join(arg_lst) print(f'[{elapsed:0.8f}s] {name}({arg_str}) -> {result!r}') return result diff --git a/09-closure-deco/fibo_demo_cache.py b/09-closure-deco/fibo_demo_cache.py index 5e92ec6..2bd06e7 100644 --- a/09-closure-deco/fibo_demo_cache.py +++ b/09-closure-deco/fibo_demo_cache.py @@ -8,7 +8,7 @@ def fibonacci(n): if n < 2: return n - return fibonacci(n-2) + fibonacci(n-1) + return fibonacci(n - 2) + fibonacci(n - 1) if __name__ == '__main__': diff --git a/09-closure-deco/htmlizer.py b/09-closure-deco/htmlizer.py index cfd92b6..c660ad4 100644 --- a/09-closure-deco/htmlizer.py +++ b/09-closure-deco/htmlizer.py @@ -8,7 +8,7 @@ >>> htmlize(abs) '
<built-in function abs>
' >>> htmlize('Heimlich & Co.\n- a game') # <2> -'

Heimlich & Co.
\n- a game

' +'

Heimlich & Co.
\n- a game

' >>> htmlize(42) # <3> '
42 (0x2a)
' >>> print(htmlize(['alpha', 66, {3, 2, 1}])) # <4> @@ -45,7 +45,7 @@ def htmlize(obj: object) -> str: @htmlize.register # <2> def _(text: str) -> str: # <3> - content = html.escape(text).replace('\n', '
\n') + content = html.escape(text).replace('\n', '
\n') return f'

{content}

' @htmlize.register # <4> diff --git a/11-pythonic-obj/private/Confidential.java b/11-pythonic-obj/private/Confidential.java index 6dc740d..c3887f4 100644 --- a/11-pythonic-obj/private/Confidential.java +++ b/11-pythonic-obj/private/Confidential.java @@ -3,6 +3,6 @@ public class Confidential { private String secret = ""; public Confidential(String text) { - secret = text.toUpperCase(); + this.secret = text.toUpperCase(); } } diff --git a/18-context-mngr/lispy/LICENSE b/18-context-mngr/lispy/LICENSE deleted file mode 100644 index ca550a2..0000000 --- a/18-context-mngr/lispy/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2010-2017 Peter Norvig - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file diff --git a/18-context-mngr/lispy/README.md b/18-context-mngr/lispy/README.md deleted file mode 100644 index cf18e3b..0000000 --- a/18-context-mngr/lispy/README.md +++ /dev/null @@ -1,28 +0,0 @@ -# lis.py - -This directory contains 3 versions of -[Peter Norvig's `lis.py` interpreter](https://norvig.com/lispy.html) -for a subset of [Scheme](https://en.wikipedia.org/wiki/Scheme_(programming_language)). - -* `original/`: Norvig's `lis.py` unchanged, `lispy.py` with -[minor changes](https://github.com/norvig/pytudes/pull/106) to run on Python 3, -and the `lispytest.py` custom test script; -* `py3.9/`: `lis.py` with type hints and a few minor edits—requires Python 3.9; -* `py3.10/`: `lis.py` with type hints, pattern matching, and minor edits—requires Python 3.10. - -The `py3.9/` and `py3.10/` directories also have identical `lis_test.py` to run with -[pytest](https://docs.pytest.org). -These files include all the -[`lis_tests` suite](https://github.com/norvig/pytudes/blob/60168bce8cdfacf57c92a5b2979f0b2e95367753/py/lispytest.py#L5) -from `original/lispytest.py`, -and additional separate tests for each expression and special form handled by `evaluate`. - -## Provenance, Copyright and License - -`lis.py` is published in the [norvig/pytudes](https://github.com/norvig/pytudes) repository on Github. -The copyright holder is Peter Norvig and the code is licensed under the -[MIT license](https://github.com/norvig/pytudes/blob/60168bce8cdfacf57c92a5b2979f0b2e95367753/LICENSE). - -I wrote the changes and additions described above. - -— Luciano Ramalho ([@ramalho](https://github.com/ramalho/)), 2021-06-09. diff --git a/18-context-mngr/lispy/original/lis.py b/18-context-mngr/lispy/original/lis.py deleted file mode 100644 index f81376a..0000000 --- a/18-context-mngr/lispy/original/lis.py +++ /dev/null @@ -1,132 +0,0 @@ -################ Lispy: Scheme Interpreter in Python 3.3+ - -## (c) Peter Norvig, 2010-18; See http://norvig.com/lispy.html - -################ Imports and Types - -import math -import operator as op -from collections import ChainMap as Environment - -Symbol = str # A Lisp Symbol is implemented as a Python str -List = list # A Lisp List is implemented as a Python list -Number = (int, float) # A Lisp Number is implemented as a Python int or float - -class Procedure(object): - "A user-defined Scheme procedure." - def __init__(self, parms, body, env): - self.parms, self.body, self.env = parms, body, env - def __call__(self, *args): - env = Environment(dict(zip(self.parms, args)), self.env) - return eval(self.body, env) - -################ Global Environment - -def standard_env(): - "An environment with some Scheme standard procedures." - env = {} - env.update(vars(math)) # sin, cos, sqrt, pi, ... - env.update({ - '+':op.add, '-':op.sub, '*':op.mul, '/':op.truediv, - '>':op.gt, '<':op.lt, '>=':op.ge, '<=':op.le, '=':op.eq, - 'abs': abs, - 'append': op.add, - 'apply': lambda proc, args: proc(*args), - 'begin': lambda *x: x[-1], - 'car': lambda x: x[0], - 'cdr': lambda x: x[1:], - 'cons': lambda x,y: [x] + y, - 'eq?': op.is_, - 'equal?': op.eq, - 'length': len, - 'list': lambda *x: list(x), - 'list?': lambda x: isinstance(x,list), - 'map': lambda *args: list(map(*args)), - 'max': max, - 'min': min, - 'not': op.not_, - 'null?': lambda x: x == [], - 'number?': lambda x: isinstance(x, Number), - 'procedure?': callable, - 'round': round, - 'symbol?': lambda x: isinstance(x, Symbol), - }) - return env - -global_env = standard_env() - -################ Parsing: parse, tokenize, and read_from_tokens - -def parse(program): - "Read a Scheme expression from a string." - return read_from_tokens(tokenize(program)) - -def tokenize(s): - "Convert a string into a list of tokens." - return s.replace('(',' ( ').replace(')',' ) ').split() - -def read_from_tokens(tokens): - "Read an expression from a sequence of tokens." - if len(tokens) == 0: - raise SyntaxError('unexpected EOF while reading') - token = tokens.pop(0) - if '(' == token: - L = [] - while tokens[0] != ')': - L.append(read_from_tokens(tokens)) - tokens.pop(0) # pop off ')' - return L - elif ')' == token: - raise SyntaxError('unexpected )') - else: - return atom(token) - -def atom(token): - "Numbers become numbers; every other token is a symbol." - try: return int(token) - except ValueError: - try: return float(token) - except ValueError: - return Symbol(token) - -################ Interaction: A REPL - -def repl(prompt='lis.py> '): - "A prompt-read-eval-print loop." - while True: - val = eval(parse(input(prompt))) - if val is not None: - print(lispstr(val)) - -def lispstr(exp): - "Convert a Python object back into a Lisp-readable string." - if isinstance(exp, List): - return '(' + ' '.join(map(lispstr, exp)) + ')' - else: - return str(exp) - -################ eval - -def eval(x, env=global_env): - "Evaluate an expression in an environment." - if isinstance(x, Symbol): # variable reference - return env[x] - elif not isinstance(x, List): # constant literal - return x - elif x[0] == 'quote': # (quote exp) - (_, exp) = x - return exp - elif x[0] == 'if': # (if test conseq alt) - (_, test, conseq, alt) = x - exp = (conseq if eval(test, env) else alt) - return eval(exp, env) - elif x[0] == 'define': # (define var exp) - (_, var, exp) = x - env[var] = eval(exp, env) - elif x[0] == 'lambda': # (lambda (var...) body) - (_, parms, body) = x - return Procedure(parms, body, env) - else: # (proc arg...) - proc = eval(x[0], env) - args = [eval(exp, env) for exp in x[1:]] - return proc(*args) diff --git a/18-context-mngr/lispy/original/lispy.py b/18-context-mngr/lispy/original/lispy.py deleted file mode 100644 index b17341c..0000000 --- a/18-context-mngr/lispy/original/lispy.py +++ /dev/null @@ -1,316 +0,0 @@ -################ Scheme Interpreter in Python - -## (c) Peter Norvig, 2010; See http://norvig.com/lispy2.html - -################ Symbol, Procedure, classes - -import re, sys, io - -class Symbol(str): pass - -def Sym(s, symbol_table={}): - "Find or create unique Symbol entry for str s in symbol table." - if s not in symbol_table: symbol_table[s] = Symbol(s) - return symbol_table[s] - -_quote, _if, _set, _define, _lambda, _begin, _definemacro, = map(Sym, -"quote if set! define lambda begin define-macro".split()) - -_quasiquote, _unquote, _unquotesplicing = map(Sym, -"quasiquote unquote unquote-splicing".split()) - -class Procedure: - "A user-defined Scheme procedure." - def __init__(self, parms, exp, env): - self.parms, self.exp, self.env = parms, exp, env - def __call__(self, *args): - return eval(self.exp, Env(self.parms, args, self.env)) - -################ parse, read, and user interaction - -def parse(inport): - "Parse a program: read and expand/error-check it." - # Backwards compatibility: given a str, convert it to an InPort - if isinstance(inport, str): inport = InPort(io.StringIO(inport)) - return expand(read(inport), toplevel=True) - -eof_object = Symbol('#') # Note: uninterned; can't be read - -class InPort: - "An input port. Retains a line of chars." - tokenizer = r"""\s*(,@|[('`,)]|"(?:[\\].|[^\\"])*"|;.*|[^\s('"`,;)]*)(.*)""" - def __init__(self, file): - self.file = file; self.line = '' - def next_token(self): - "Return the next token, reading new text into line buffer if needed." - while True: - if self.line == '': self.line = self.file.readline() - if self.line == '': return eof_object - token, self.line = re.match(InPort.tokenizer, self.line).groups() - if token != '' and not token.startswith(';'): - return token - -def readchar(inport): - "Read the next character from an input port." - if inport.line != '': - ch, inport.line = inport.line[0], inport.line[1:] - return ch - else: - return inport.file.read(1) or eof_object - -def read(inport): - "Read a Scheme expression from an input port." - def read_ahead(token): - if '(' == token: - L = [] - while True: - token = inport.next_token() - if token == ')': return L - else: L.append(read_ahead(token)) - elif ')' == token: raise SyntaxError('unexpected )') - elif token in quotes: return [quotes[token], read(inport)] - elif token is eof_object: raise SyntaxError('unexpected EOF in list') - else: return atom(token) - # body of read: - token1 = inport.next_token() - return eof_object if token1 is eof_object else read_ahead(token1) - -quotes = {"'":_quote, "`":_quasiquote, ",":_unquote, ",@":_unquotesplicing} - -def atom(token): - 'Numbers become numbers; #t and #f are booleans; "..." string; otherwise Symbol.' - if token == '#t': return True - elif token == '#f': return False - elif token[0] == '"': return token[1:-1] - try: return int(token) - except ValueError: - try: return float(token) - except ValueError: - try: return complex(token.replace('i', 'j', 1)) - except ValueError: - return Sym(token) - -def to_string(x): - "Convert a Python object back into a Lisp-readable string." - if x is True: return "#t" - elif x is False: return "#f" - elif isa(x, Symbol): return x - elif isa(x, str): return repr(x) - elif isa(x, list): return '('+' '.join(map(to_string, x))+')' - elif isa(x, complex): return str(x).replace('j', 'i') - else: return str(x) - -def load(filename): - "Eval every expression from a file." - repl(None, InPort(open(filename)), None) - -def repl(prompt='lispy> ', inport=InPort(sys.stdin), out=sys.stdout): - "A prompt-read-eval-print loop." - sys.stderr.write("Lispy version 2.0\n") - while True: - try: - if prompt: sys.stderr.write(prompt) - x = parse(inport) - if x is eof_object: return - val = eval(x) - if val is not None and out: print(to_string(val), file=out) - except Exception as e: - print('%s: %s' % (type(e).__name__, e)) - -################ Environment class - -class Env(dict): - "An environment: a dict of {'var':val} pairs, with an outer Env." - def __init__(self, parms=(), args=(), outer=None): - # Bind parm list to corresponding args, or single parm to list of args - self.outer = outer - if isa(parms, Symbol): - self.update({parms:list(args)}) - else: - if len(args) != len(parms): - raise TypeError('expected %s, given %s, ' - % (to_string(parms), to_string(args))) - self.update(zip(parms,args)) - def find(self, var): - "Find the innermost Env where var appears." - if var in self: return self - elif self.outer is None: raise LookupError(var) - else: return self.outer.find(var) - -def is_pair(x): return x != [] and isa(x, list) -def cons(x, y): return [x]+y - -def callcc(proc): - "Call proc with current continuation; escape only" - ball = RuntimeWarning("Sorry, can't continue this continuation any longer.") - def throw(retval): ball.retval = retval; raise ball - try: - return proc(throw) - except RuntimeWarning as w: - if w is ball: return ball.retval - else: raise w - -def add_globals(self): - "Add some Scheme standard procedures." - import math, cmath, operator as op - self.update(vars(math)) - self.update(vars(cmath)) - self.update({ - '+':op.add, '-':op.sub, '*':op.mul, '/':op.truediv, 'not':op.not_, - '>':op.gt, '<':op.lt, '>=':op.ge, '<=':op.le, '=':op.eq, - 'equal?':op.eq, 'eq?':op.is_, 'length':len, 'cons':cons, - 'car':lambda x:x[0], 'cdr':lambda x:x[1:], 'append':op.add, - 'list':lambda *x:list(x), 'list?': lambda x:isa(x,list), - 'null?':lambda x:x==[], 'symbol?':lambda x: isa(x, Symbol), - 'boolean?':lambda x: isa(x, bool), 'pair?':is_pair, - 'port?': lambda x:isa(x,file), 'apply':lambda proc,l: proc(*l), - 'eval':lambda x: eval(expand(x)), 'load':lambda fn: load(fn), 'call/cc':callcc, - 'open-input-file':open,'close-input-port':lambda p: p.file.close(), - 'open-output-file':lambda f:open(f,'w'), 'close-output-port':lambda p: p.close(), - 'eof-object?':lambda x:x is eof_object, 'read-char':readchar, - 'read':read, 'write':lambda x,port=sys.stdout:port.write(to_string(x)), - 'display':lambda x,port=sys.stdout:port.write(x if isa(x,str) else to_string(x))}) - return self - -isa = isinstance - -global_env = add_globals(Env()) - -################ eval (tail recursive) - -def eval(x, env=global_env): - "Evaluate an expression in an environment." - while True: - if isa(x, Symbol): # variable reference - return env.find(x)[x] - elif not isa(x, list): # constant literal - return x - elif x[0] is _quote: # (quote exp) - (_, exp) = x - return exp - elif x[0] is _if: # (if test conseq alt) - (_, test, conseq, alt) = x - x = (conseq if eval(test, env) else alt) - elif x[0] is _set: # (set! var exp) - (_, var, exp) = x - env.find(var)[var] = eval(exp, env) - return None - elif x[0] is _define: # (define var exp) - (_, var, exp) = x - env[var] = eval(exp, env) - return None - elif x[0] is _lambda: # (lambda (var*) exp) - (_, vars, exp) = x - return Procedure(vars, exp, env) - elif x[0] is _begin: # (begin exp+) - for exp in x[1:-1]: - eval(exp, env) - x = x[-1] - else: # (proc exp*) - exps = [eval(exp, env) for exp in x] - proc = exps.pop(0) - if isa(proc, Procedure): - x = proc.exp - env = Env(proc.parms, exps, proc.env) - else: - return proc(*exps) - -################ expand - -def expand(x, toplevel=False): - "Walk tree of x, making optimizations/fixes, and signaling SyntaxError." - require(x, x!=[]) # () => Error - if not isa(x, list): # constant => unchanged - return x - elif x[0] is _quote: # (quote exp) - require(x, len(x)==2) - return x - elif x[0] is _if: - if len(x)==3: x = x + [None] # (if t c) => (if t c None) - require(x, len(x)==4) - return list(map(expand, x)) - elif x[0] is _set: - require(x, len(x)==3); - var = x[1] # (set! non-var exp) => Error - require(x, isa(var, Symbol), "can set! only a symbol") - return [_set, var, expand(x[2])] - elif x[0] is _define or x[0] is _definemacro: - require(x, len(x)>=3) - _def, v, body = x[0], x[1], x[2:] - if isa(v, list) and v: # (define (f args) body) - f, args = v[0], v[1:] # => (define f (lambda (args) body)) - return expand([_def, f, [_lambda, args]+body]) - else: - require(x, len(x)==3) # (define non-var/list exp) => Error - require(x, isa(v, Symbol), "can define only a symbol") - exp = expand(x[2]) - if _def is _definemacro: - require(x, toplevel, "define-macro only allowed at top level") - proc = eval(exp) - require(x, callable(proc), "macro must be a procedure") - macro_table[v] = proc # (define-macro v proc) - return None # => None; add v:proc to macro_table - return [_define, v, exp] - elif x[0] is _begin: - if len(x)==1: return None # (begin) => None - else: return [expand(xi, toplevel) for xi in x] - elif x[0] is _lambda: # (lambda (x) e1 e2) - require(x, len(x)>=3) # => (lambda (x) (begin e1 e2)) - vars, body = x[1], x[2:] - require(x, (isa(vars, list) and all(isa(v, Symbol) for v in vars)) - or isa(vars, Symbol), "illegal lambda argument list") - exp = body[0] if len(body) == 1 else [_begin] + body - return [_lambda, vars, expand(exp)] - elif x[0] is _quasiquote: # `x => expand_quasiquote(x) - require(x, len(x)==2) - return expand_quasiquote(x[1]) - elif isa(x[0], Symbol) and x[0] in macro_table: - return expand(macro_table[x[0]](*x[1:]), toplevel) # (m arg...) - else: # => macroexpand if m isa macro - return list(map(expand, x)) # (f arg...) => expand each - -def require(x, predicate, msg="wrong length"): - "Signal a syntax error if predicate is false." - if not predicate: raise SyntaxError(to_string(x)+': '+msg) - -_append, _cons, _let = map(Sym, "append cons let".split()) - -def expand_quasiquote(x): - """Expand `x => 'x; `,x => x; `(,@x y) => (append x y) """ - if not is_pair(x): - return [_quote, x] - require(x, x[0] is not _unquotesplicing, "can't splice here") - if x[0] is _unquote: - require(x, len(x)==2) - return x[1] - elif is_pair(x[0]) and x[0][0] is _unquotesplicing: - require(x[0], len(x[0])==2) - return [_append, x[0][1], expand_quasiquote(x[1:])] - else: - return [_cons, expand_quasiquote(x[0]), expand_quasiquote(x[1:])] - -def let(*args): - args = list(args) - x = cons(_let, args) - require(x, len(args)>1) - bindings, body = args[0], args[1:] - require(x, all(isa(b, list) and len(b)==2 and isa(b[0], Symbol) - for b in bindings), "illegal binding list") - vars, vals = zip(*bindings) - return [[_lambda, list(vars)]+list(map(expand, body))] + list(map(expand, vals)) - -macro_table = {_let:let} ## More macros can go here - -eval(parse("""(begin - -(define-macro and (lambda args - (if (null? args) #t - (if (= (length args) 1) (car args) - `(if ,(car args) (and ,@(cdr args)) #f))))) - -;; More macros can also go here - -)""")) - -if __name__ == '__main__': - repl() diff --git a/18-context-mngr/lispy/original/lispytest.py b/18-context-mngr/lispy/original/lispytest.py deleted file mode 100644 index b324ff3..0000000 --- a/18-context-mngr/lispy/original/lispytest.py +++ /dev/null @@ -1,122 +0,0 @@ -from __future__ import print_function - -################ Tests for lis.py and lispy.py - -lis_tests = [ - ("(quote (testing 1 (2.0) -3.14e159))", ['testing', 1, [2.0], -3.14e159]), - ("(+ 2 2)", 4), - ("(+ (* 2 100) (* 1 10))", 210), - ("(if (> 6 5) (+ 1 1) (+ 2 2))", 2), - ("(if (< 6 5) (+ 1 1) (+ 2 2))", 4), - ("(define x 3)", None), ("x", 3), ("(+ x x)", 6), - ("((lambda (x) (+ x x)) 5)", 10), - ("(define twice (lambda (x) (* 2 x)))", None), ("(twice 5)", 10), - ("(define compose (lambda (f g) (lambda (x) (f (g x)))))", None), - ("((compose list twice) 5)", [10]), - ("(define repeat (lambda (f) (compose f f)))", None), - ("((repeat twice) 5)", 20), ("((repeat (repeat twice)) 5)", 80), - ("(define fact (lambda (n) (if (<= n 1) 1 (* n (fact (- n 1))))))", None), - ("(fact 3)", 6), - ("(fact 50)", 30414093201713378043612608166064768844377641568960512000000000000), - ("(define abs (lambda (n) ((if (> n 0) + -) 0 n)))", None), - ("(list (abs -3) (abs 0) (abs 3))", [3, 0, 3]), - ("""(define combine (lambda (f) - (lambda (x y) - (if (null? x) (quote ()) - (f (list (car x) (car y)) - ((combine f) (cdr x) (cdr y)))))))""", None), - ("(define zip (combine cons))", None), - ("(zip (list 1 2 3 4) (list 5 6 7 8))", [[1, 5], [2, 6], [3, 7], [4, 8]]), - ("""(define riff-shuffle (lambda (deck) (begin - (define take (lambda (n seq) (if (<= n 0) (quote ()) (cons (car seq) (take (- n 1) (cdr seq)))))) - (define drop (lambda (n seq) (if (<= n 0) seq (drop (- n 1) (cdr seq))))) - (define mid (lambda (seq) (/ (length seq) 2))) - ((combine append) (take (mid deck) deck) (drop (mid deck) deck)))))""", None), - ("(riff-shuffle (list 1 2 3 4 5 6 7 8))", [1, 5, 2, 6, 3, 7, 4, 8]), - ("((repeat riff-shuffle) (list 1 2 3 4 5 6 7 8))", [1, 3, 5, 7, 2, 4, 6, 8]), - ("(riff-shuffle (riff-shuffle (riff-shuffle (list 1 2 3 4 5 6 7 8))))", [1,2,3,4,5,6,7,8]), - ] - -lispy_tests = [ - ("()", SyntaxError), ("(set! x)", SyntaxError), - ("(define 3 4)", SyntaxError), - ("(quote 1 2)", SyntaxError), ("(if 1 2 3 4)", SyntaxError), - ("(lambda 3 3)", SyntaxError), ("(lambda (x))", SyntaxError), - ("""(if (= 1 2) (define-macro a 'a) - (define-macro a 'b))""", SyntaxError), - ("(define (twice x) (* 2 x))", None), ("(twice 2)", 4), - ("(twice 2 2)", TypeError), - ("(define lyst (lambda items items))", None), - ("(lyst 1 2 3 (+ 2 2))", [1,2,3,4]), - ("(if 1 2)", 2), - ("(if (= 3 4) 2)", None), - ("(begin (define x 1) (set! x (+ x 1)) (+ x 1))", 3), - ("(define ((account bal) amt) (set! bal (+ bal amt)) bal)", None), - ("(define a1 (account 100))", None), - ("(a1 0)", 100), ("(a1 10)", 110), ("(a1 10)", 120), - ("""(define (newton guess function derivative epsilon) - (define guess2 (- guess (/ (function guess) (derivative guess)))) - (if (< (abs (- guess guess2)) epsilon) guess2 - (newton guess2 function derivative epsilon)))""", None), - ("""(define (square-root a) - (newton 1 (lambda (x) (- (* x x) a)) (lambda (x) (* 2 x)) 1e-8))""", None), - ("(> (square-root 200.) 14.14213)", True), - ("(< (square-root 200.) 14.14215)", True), - ("(= (square-root 200.) (sqrt 200.))", True), - ("""(define (sum-squares-range start end) - (define (sumsq-acc start end acc) - (if (> start end) acc (sumsq-acc (+ start 1) end (+ (* start start) acc)))) - (sumsq-acc start end 0))""", None), - ("(sum-squares-range 1 3000)", 9004500500), ## Tests tail recursion - ("(call/cc (lambda (throw) (+ 5 (* 10 (throw 1))))) ;; throw", 1), - ("(call/cc (lambda (throw) (+ 5 (* 10 1)))) ;; do not throw", 15), - ("""(call/cc (lambda (throw) - (+ 5 (* 10 (call/cc (lambda (escape) (* 100 (escape 3)))))))) ; 1 level""", 35), - ("""(call/cc (lambda (throw) - (+ 5 (* 10 (call/cc (lambda (escape) (* 100 (throw 3)))))))) ; 2 levels""", 3), - ("""(call/cc (lambda (throw) - (+ 5 (* 10 (call/cc (lambda (escape) (* 100 1))))))) ; 0 levels""", 1005), - ("(* 1i 1i)", -1), ("(sqrt -1)", 1j), - ("(let ((a 1) (b 2)) (+ a b))", 3), - ("(let ((a 1) (b 2 3)) (+ a b))", SyntaxError), - ("(and 1 2 3)", 3), ("(and (> 2 1) 2 3)", 3), ("(and)", True), - ("(and (> 2 1) (> 2 3))", False), - ("(define-macro unless (lambda args `(if (not ,(car args)) (begin ,@(cdr args))))) ; test `", None), - ("(unless (= 2 (+ 1 1)) (display 2) 3 4)", None), - (r'(unless (= 4 (+ 1 1)) (display 2) (display "\n") 3 4)', 4), - ("(quote x)", 'x'), - ("(quote (1 2 three))", [1, 2, 'three']), - ("'x", 'x'), - ("'(one 2 3)", ['one', 2, 3]), - ("(define L (list 1 2 3))", None), - ("`(testing ,@L testing)", ['testing',1,2,3,'testing']), - ("`(testing ,L testing)", ['testing',[1,2,3],'testing']), - ("`,@L", SyntaxError), - ("""'(1 ;test comments ' - ;skip this line - 2 ; more ; comments ; ) ) - 3) ; final comment""", [1,2,3]), - ] - -def test(tests, name=''): - "For each (exp, expected) test case, see if eval(parse(exp)) == expected." - fails = 0 - for (x, expected) in tests: - try: - result = eval(parse(x)) - print(x, '=>', lispstr(result)) - ok = (result == expected) - except Exception as e: - print(x, '=raises=>', type(e).__name__, e) - ok = isinstance(expected, type) and issubclass(expected, Exception) and isinstance(e, expected) - if not ok: - fails += 1 - print('FAIL!!! Expected', expected) - print('%s %s: %d out of %d tests fail.' % ('*'*45, name, fails, len(tests))) - -if __name__ == '__main__': - from lis import * - test(lis_tests, 'lis.py') - from lispy import * - test(lis_tests+lispy_tests, 'lispy.py') - diff --git a/18-context-mngr/lispy/py3.10/mypy.ini b/18-context-mngr/lispy/py3.10/mypy.ini deleted file mode 100644 index 3456561..0000000 --- a/18-context-mngr/lispy/py3.10/mypy.ini +++ /dev/null @@ -1,2 +0,0 @@ -[mypy] -python_version = 3.10 diff --git a/18-context-mngr/lispy/py3.9/lis.py b/18-context-mngr/lispy/py3.9/lis.py deleted file mode 100644 index 1a14935..0000000 --- a/18-context-mngr/lispy/py3.9/lis.py +++ /dev/null @@ -1,163 +0,0 @@ -################ Lispy: Scheme Interpreter in Python 3.9 - -## (c) Peter Norvig, 2010-18; See http://norvig.com/lispy.html -## Minor edits for Fluent Python, Second Edition (O'Reilly, 2021) -## by Luciano Ramalho, mostly adding type hints. - -################ Imports and Types - -import math -import operator as op -from collections import ChainMap -from collections.abc import MutableMapping -from typing import Union, Any - -Symbol = str -Atom = Union[float, int, Symbol] -Expression = Union[Atom, list] - -Environment = MutableMapping[Symbol, object] - - -class Procedure: - "A user-defined Scheme procedure." - def __init__(self, parms: list[Symbol], body: Expression, env: Environment): - self.parms, self.body, self.env = parms, body, env - - def __call__(self, *args: Expression) -> Any: - local_env = dict(zip(self.parms, args)) - env: Environment = ChainMap(local_env, self.env) - return evaluate(self.body, env) - - -################ Global Environment - - -def standard_env() -> Environment: - "An environment with some Scheme standard procedures." - env: Environment = {} - env.update(vars(math)) # sin, cos, sqrt, pi, ... - env.update( - { - '+': op.add, - '-': op.sub, - '*': op.mul, - '/': op.truediv, - '>': op.gt, - '<': op.lt, - '>=': op.ge, - '<=': op.le, - '=': op.eq, - 'abs': abs, - 'append': op.add, - 'apply': lambda proc, args: proc(*args), - 'begin': lambda *x: x[-1], - 'car': lambda x: x[0], - 'cdr': lambda x: x[1:], - 'cons': lambda x, y: [x] + y, - 'eq?': op.is_, - 'equal?': op.eq, - 'length': len, - 'list': lambda *x: list(x), - 'list?': lambda x: isinstance(x, list), - 'map': lambda *args: list(map(*args)), - 'max': max, - 'min': min, - 'not': op.not_, - 'null?': lambda x: x == [], - 'number?': lambda x: isinstance(x, (int, float)), - 'procedure?': callable, - 'round': round, - 'symbol?': lambda x: isinstance(x, Symbol), - } - ) - return env - - -################ Parsing: parse, tokenize, and read_from_tokens - - -def parse(program: str) -> Expression: - "Read a Scheme expression from a string." - return read_from_tokens(tokenize(program)) - - -def tokenize(s: str) -> list[str]: - "Convert a string into a list of tokens." - return s.replace('(', ' ( ').replace(')', ' ) ').split() - - -def read_from_tokens(tokens: list[str]) -> Expression: - "Read an expression from a sequence of tokens." - if len(tokens) == 0: - raise SyntaxError('unexpected EOF while reading') - token = tokens.pop(0) - if '(' == token: - L = [] - while tokens[0] != ')': - L.append(read_from_tokens(tokens)) - tokens.pop(0) # pop off ')' - return L - elif ')' == token: - raise SyntaxError('unexpected )') - else: - return parse_atom(token) - - -def parse_atom(token: str) -> Atom: - "Numbers become numbers; every other token is a symbol." - try: - return int(token) - except ValueError: - try: - return float(token) - except ValueError: - return Symbol(token) - - -################ Interaction: A REPL - - -def repl(prompt: str = 'lis.py> ') -> None: - "A prompt-read-evaluate-print loop." - global_env: Environment = standard_env() - while True: - val = evaluate(parse(input(prompt)), global_env) - if val is not None: - print(lispstr(val)) - - -def lispstr(exp: object) -> str: - "Convert a Python object back into a Lisp-readable string." - if isinstance(exp, list): - return '(' + ' '.join(map(lispstr, exp)) + ')' - else: - return str(exp) - - -################ eval - - -def evaluate(x: Expression, env: Environment) -> Any: - "Evaluate an expression in an environment." - if isinstance(x, str): # variable reference - return env[x] - elif not isinstance(x, list): # constant literal - return x - elif x[0] == 'quote': # (quote exp) - (_, exp) = x - return exp - elif x[0] == 'if': # (if test conseq alt) - (_, test, conseq, alt) = x - exp = conseq if evaluate(test, env) else alt - return evaluate(exp, env) - elif x[0] == 'define': # (define var exp) - (_, var, exp) = x - env[var] = evaluate(exp, env) - elif x[0] == 'lambda': # (lambda (var...) body) - (_, parms, body) = x - return Procedure(parms, body, env) - else: # (proc arg...) - proc = evaluate(x[0], env) - args = [evaluate(exp, env) for exp in x[1:]] - return proc(*args) diff --git a/18-context-mngr/lispy/py3.9/lis_test.py b/18-context-mngr/lispy/py3.9/lis_test.py deleted file mode 100755 index 44c3402..0000000 --- a/18-context-mngr/lispy/py3.9/lis_test.py +++ /dev/null @@ -1,151 +0,0 @@ -from typing import Optional - -from pytest import mark, fixture - -from lis import parse, evaluate, Expression, Environment, standard_env - -# Norvig's tests are not isolated: they assume the -# same environment from first to last test. -ENV_FOR_FIRST_TEST = standard_env() - -@mark.parametrize( 'source, expected', [ - ("(quote (testing 1 (2.0) -3.14e159))", ['testing', 1, [2.0], -3.14e159]), - ("(+ 2 2)", 4), - ("(+ (* 2 100) (* 1 10))", 210), - ("(if (> 6 5) (+ 1 1) (+ 2 2))", 2), - ("(if (< 6 5) (+ 1 1) (+ 2 2))", 4), - ("(define x 3)", None), - ("x", 3), - ("(+ x x)", 6), - ("((lambda (x) (+ x x)) 5)", 10), - ("(define twice (lambda (x) (* 2 x)))", None), - ("(twice 5)", 10), - ("(define compose (lambda (f g) (lambda (x) (f (g x)))))", None), - ("((compose list twice) 5)", [10]), - ("(define repeat (lambda (f) (compose f f)))", None), - ("((repeat twice) 5)", 20), - ("((repeat (repeat twice)) 5)", 80), - ("(define fact (lambda (n) (if (<= n 1) 1 (* n (fact (- n 1))))))", None), - ("(fact 3)", 6), - ("(fact 50)", 30414093201713378043612608166064768844377641568960512000000000000), - ("(define abs (lambda (n) ((if (> n 0) + -) 0 n)))", None), - ("(list (abs -3) (abs 0) (abs 3))", [3, 0, 3]), - ("""(define combine (lambda (f) - (lambda (x y) - (if (null? x) (quote ()) - (f (list (car x) (car y)) - ((combine f) (cdr x) (cdr y)))))))""", None), - ("(define zip (combine cons))", None), - ("(zip (list 1 2 3 4) (list 5 6 7 8))", [[1, 5], [2, 6], [3, 7], [4, 8]]), - ("""(define riff-shuffle (lambda (deck) - (begin - (define take (lambda (n seq) (if (<= n 0) (quote ()) (cons (car seq) (take (- n 1) (cdr seq)))))) - (define drop (lambda (n seq) (if (<= n 0) seq (drop (- n 1) (cdr seq))))) - (define mid (lambda (seq) (/ (length seq) 2))) - ((combine append) (take (mid deck) deck) (drop (mid deck) deck)))))""", None), - ("(riff-shuffle (list 1 2 3 4 5 6 7 8))", [1, 5, 2, 6, 3, 7, 4, 8]), - ("((repeat riff-shuffle) (list 1 2 3 4 5 6 7 8))", [1, 3, 5, 7, 2, 4, 6, 8]), - ("(riff-shuffle (riff-shuffle (riff-shuffle (list 1 2 3 4 5 6 7 8))))", [1,2,3,4,5,6,7,8]), -]) -def test_evaluate(source: str, expected: Optional[Expression]) -> None: - got = evaluate(parse(source), ENV_FOR_FIRST_TEST) - assert got == expected - - -@fixture -def std_env() -> Environment: - return standard_env() - -# tests for each of the cases in evaluate - -def test_evaluate_variable() -> None: - env: Environment = dict(x=10) - source = 'x' - expected = 10 - got = evaluate(parse(source), env) - assert got == expected - - -def test_evaluate_literal(std_env: Environment) -> None: - source = '3.3' - expected = 3.3 - got = evaluate(parse(source), std_env) - assert got == expected - - -def test_evaluate_quote(std_env: Environment) -> None: - source = '(quote (1.1 is not 1))' - expected = [1.1, 'is', 'not', 1] - got = evaluate(parse(source), std_env) - assert got == expected - - -def test_evaluate_if_true(std_env: Environment) -> None: - source = '(if 1 10 no-such-thing)' - expected = 10 - got = evaluate(parse(source), std_env) - assert got == expected - - -def test_evaluate_if_false(std_env: Environment) -> None: - source = '(if 0 no-such-thing 20)' - expected = 20 - got = evaluate(parse(source), std_env) - assert got == expected - - -def test_define(std_env: Environment) -> None: - source = '(define answer (* 6 7))' - got = evaluate(parse(source), std_env) - assert got is None - assert std_env['answer'] == 42 - - -def test_lambda(std_env: Environment) -> None: - source = '(lambda (a b) (if (>= a b) a b))' - func = evaluate(parse(source), std_env) - assert func.parms == ['a', 'b'] - assert func.body == ['if', ['>=', 'a', 'b'], 'a', 'b'] - assert func.env is std_env - assert func(1, 2) == 2 - assert func(3, 2) == 3 - - -def test_begin(std_env: Environment) -> None: - source = """ - (begin - (define x (* 2 3)) - (* x 7) - ) - """ - got = evaluate(parse(source), std_env) - assert got == 42 - - -def test_invocation_builtin_car(std_env: Environment) -> None: - source = '(car (quote (11 22 33)))' - got = evaluate(parse(source), std_env) - assert got == 11 - - -def test_invocation_builtin_append(std_env: Environment) -> None: - source = '(append (quote (a b)) (quote (c d)))' - got = evaluate(parse(source), std_env) - assert got == ['a', 'b', 'c', 'd'] - - -def test_invocation_builtin_map(std_env: Environment) -> None: - source = '(map (lambda (x) (* x 2)) (quote (1 2 3))))' - got = evaluate(parse(source), std_env) - assert got == [2, 4, 6] - - -def test_invocation_user_procedure(std_env: Environment) -> None: - source = """ - (begin - (define max (lambda (a b) (if (>= a b) a b))) - (max 22 11) - ) - """ - got = evaluate(parse(source), std_env) - assert got == 22 diff --git a/18-context-mngr/lispy/py3.9/mypy.ini b/18-context-mngr/lispy/py3.9/mypy.ini deleted file mode 100644 index 6fcaf90..0000000 --- a/18-context-mngr/lispy/py3.9/mypy.ini +++ /dev/null @@ -1,2 +0,0 @@ -[mypy] -python_version = 3.9 From 1754a511f0333ca7a96eab171348dd1d27f02c44 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Tue, 29 Jun 2021 12:21:27 -0300 Subject: [PATCH 089/166] updated lis.py examples --- 02-array-seq/lispy/LICENSE | 21 ++ 02-array-seq/lispy/README.md | 36 ++ 02-array-seq/lispy/original/lis.py | 135 ++++++++ 02-array-seq/lispy/original/lispy.py | 316 ++++++++++++++++++ 02-array-seq/lispy/original/lispytest.py | 122 +++++++ .../lispy/{ => py3.10}/examples_test.py | 0 02-array-seq/lispy/{ => py3.10}/lis.py | 10 +- 02-array-seq/lispy/{ => py3.10}/lis_test.py | 6 +- 02-array-seq/lispy/{ => py3.10}/meta_test.py | 0 9 files changed, 639 insertions(+), 7 deletions(-) create mode 100644 02-array-seq/lispy/LICENSE create mode 100644 02-array-seq/lispy/README.md create mode 100644 02-array-seq/lispy/original/lis.py create mode 100644 02-array-seq/lispy/original/lispy.py create mode 100644 02-array-seq/lispy/original/lispytest.py rename 02-array-seq/lispy/{ => py3.10}/examples_test.py (100%) rename 02-array-seq/lispy/{ => py3.10}/lis.py (95%) rename 02-array-seq/lispy/{ => py3.10}/lis_test.py (97%) rename 02-array-seq/lispy/{ => py3.10}/meta_test.py (100%) diff --git a/02-array-seq/lispy/LICENSE b/02-array-seq/lispy/LICENSE new file mode 100644 index 0000000..ca550a2 --- /dev/null +++ b/02-array-seq/lispy/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2010-2017 Peter Norvig + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/02-array-seq/lispy/README.md b/02-array-seq/lispy/README.md new file mode 100644 index 0000000..0c11fd8 --- /dev/null +++ b/02-array-seq/lispy/README.md @@ -0,0 +1,36 @@ +# Norvig's originals and updates + +This directory contains: + +* `original/`: +Norvig's [`lis.py`](https://github.com/norvig/pytudes/blob/c33cd6835a506a57d9fe73e3a8317d49babb13e8/py/lis.py), +[`lispy.py`](https://github.com/norvig/pytudes/blob/c33cd6835a506a57d9fe73e3a8317d49babb13e8/py/lispy.py), and the `lispytest.py` custom test script for testing both; +* `py3.10/`: `lis.py` with type hints, pattern matching, and minor edits—requires Python 3.10. + +The `py3.10/` directory also has `lis_test.py` to run with +[pytest](https://docs.pytest.org), including all the +[`lis_tests` suite](https://github.com/norvig/pytudes/blob/60168bce8cdfacf57c92a5b2979f0b2e95367753/py/lispytest.py#L5) +from `original/lispytest.py`, +and additional separate tests for each expression and special form handled by `evaluate`. + + +## Provenance, Copyright and License + +`lis.py` is +[published](https://github.com/norvig/pytudes/blob/c33cd6835a506a57d9fe73e3a8317d49babb13e8/py/lis.py) +in the [norvig/pytudes](https://github.com/norvig/pytudes) repository on Github. +The copyright holder is Peter Norvig and the code is licensed under the +[MIT license](https://github.com/norvig/pytudes/blob/60168bce8cdfacf57c92a5b2979f0b2e95367753/LICENSE). + + +## Changes to Norvig's code + +I made small changes to the programs in `original/`: + +* In `lis.py`: + * The `Procedure` class accepts a list of expressions as the `body`, and `__call__` evaluates all those expressions in order, returning the value of the last. This is consistent with Scheme's `lambda` syntax and provided a useful example for pattern matching. + * In the `elif` block for `'lambda'`, I added the `*` in front of the `*body` variable in the tuple unpacking to capture the expressions as a list, before calling the `Procedure` constructor. + +* In `lispy.py` I made [changes and a pull request](https://github.com/norvig/pytudes/pull/106) to make it run on Python 3. + +_Luciano Ramalho
June 29, 2021_ \ No newline at end of file diff --git a/02-array-seq/lispy/original/lis.py b/02-array-seq/lispy/original/lis.py new file mode 100644 index 0000000..6e43fa2 --- /dev/null +++ b/02-array-seq/lispy/original/lis.py @@ -0,0 +1,135 @@ +################ Lispy: Scheme Interpreter in Python 3.3+ + +## (c) Peter Norvig, 2010-18; See http://norvig.com/lispy.html + +################ Imports and Types + +import math +import operator as op +from collections import ChainMap as Environment + +Symbol = str # A Lisp Symbol is implemented as a Python str +List = list # A Lisp List is implemented as a Python list +Number = (int, float) # A Lisp Number is implemented as a Python int or float + +class Procedure(object): + "A user-defined Scheme procedure." + def __init__(self, parms, body, env): + self.parms, self.body, self.env = parms, body, env + def __call__(self, *args): + env = Environment(dict(zip(self.parms, args)), self.env) + for exp in self.body: + result = eval(exp, env) + return result + + +################ Global Environment + +def standard_env(): + "An environment with some Scheme standard procedures." + env = {} + env.update(vars(math)) # sin, cos, sqrt, pi, ... + env.update({ + '+':op.add, '-':op.sub, '*':op.mul, '/':op.truediv, + '>':op.gt, '<':op.lt, '>=':op.ge, '<=':op.le, '=':op.eq, + 'abs': abs, + 'append': op.add, + 'apply': lambda proc, args: proc(*args), + 'begin': lambda *x: x[-1], + 'car': lambda x: x[0], + 'cdr': lambda x: x[1:], + 'cons': lambda x,y: [x] + y, + 'eq?': op.is_, + 'equal?': op.eq, + 'length': len, + 'list': lambda *x: list(x), + 'list?': lambda x: isinstance(x,list), + 'map': lambda *args: list(map(*args)), + 'max': max, + 'min': min, + 'not': op.not_, + 'null?': lambda x: x == [], + 'number?': lambda x: isinstance(x, Number), + 'procedure?': callable, + 'round': round, + 'symbol?': lambda x: isinstance(x, Symbol), + }) + return env + +global_env = standard_env() + +################ Parsing: parse, tokenize, and read_from_tokens + +def parse(program): + "Read a Scheme expression from a string." + return read_from_tokens(tokenize(program)) + +def tokenize(s): + "Convert a string into a list of tokens." + return s.replace('(',' ( ').replace(')',' ) ').split() + +def read_from_tokens(tokens): + "Read an expression from a sequence of tokens." + if len(tokens) == 0: + raise SyntaxError('unexpected EOF while reading') + token = tokens.pop(0) + if '(' == token: + L = [] + while tokens[0] != ')': + L.append(read_from_tokens(tokens)) + tokens.pop(0) # pop off ')' + return L + elif ')' == token: + raise SyntaxError('unexpected )') + else: + return atom(token) + +def atom(token): + "Numbers become numbers; every other token is a symbol." + try: return int(token) + except ValueError: + try: return float(token) + except ValueError: + return Symbol(token) + +################ Interaction: A REPL + +def repl(prompt='lis.py> '): + "A prompt-read-eval-print loop." + while True: + val = eval(parse(input(prompt))) + if val is not None: + print(lispstr(val)) + +def lispstr(exp): + "Convert a Python object back into a Lisp-readable string." + if isinstance(exp, List): + return '(' + ' '.join(map(lispstr, exp)) + ')' + else: + return str(exp) + +################ eval + +def eval(x, env=global_env): + "Evaluate an expression in an environment." + if isinstance(x, Symbol): # variable reference + return env[x] + elif not isinstance(x, List): # constant literal + return x + elif x[0] == 'quote': # (quote exp) + (_, exp) = x + return exp + elif x[0] == 'if': # (if test conseq alt) + (_, test, conseq, alt) = x + exp = (conseq if eval(test, env) else alt) + return eval(exp, env) + elif x[0] == 'define': # (define var exp) + (_, var, exp) = x + env[var] = eval(exp, env) + elif x[0] == 'lambda': # (lambda (var...) body) + (_, parms, *body) = x + return Procedure(parms, body, env) + else: # (proc arg...) + proc = eval(x[0], env) + args = [eval(exp, env) for exp in x[1:]] + return proc(*args) diff --git a/02-array-seq/lispy/original/lispy.py b/02-array-seq/lispy/original/lispy.py new file mode 100644 index 0000000..b17341c --- /dev/null +++ b/02-array-seq/lispy/original/lispy.py @@ -0,0 +1,316 @@ +################ Scheme Interpreter in Python + +## (c) Peter Norvig, 2010; See http://norvig.com/lispy2.html + +################ Symbol, Procedure, classes + +import re, sys, io + +class Symbol(str): pass + +def Sym(s, symbol_table={}): + "Find or create unique Symbol entry for str s in symbol table." + if s not in symbol_table: symbol_table[s] = Symbol(s) + return symbol_table[s] + +_quote, _if, _set, _define, _lambda, _begin, _definemacro, = map(Sym, +"quote if set! define lambda begin define-macro".split()) + +_quasiquote, _unquote, _unquotesplicing = map(Sym, +"quasiquote unquote unquote-splicing".split()) + +class Procedure: + "A user-defined Scheme procedure." + def __init__(self, parms, exp, env): + self.parms, self.exp, self.env = parms, exp, env + def __call__(self, *args): + return eval(self.exp, Env(self.parms, args, self.env)) + +################ parse, read, and user interaction + +def parse(inport): + "Parse a program: read and expand/error-check it." + # Backwards compatibility: given a str, convert it to an InPort + if isinstance(inport, str): inport = InPort(io.StringIO(inport)) + return expand(read(inport), toplevel=True) + +eof_object = Symbol('#') # Note: uninterned; can't be read + +class InPort: + "An input port. Retains a line of chars." + tokenizer = r"""\s*(,@|[('`,)]|"(?:[\\].|[^\\"])*"|;.*|[^\s('"`,;)]*)(.*)""" + def __init__(self, file): + self.file = file; self.line = '' + def next_token(self): + "Return the next token, reading new text into line buffer if needed." + while True: + if self.line == '': self.line = self.file.readline() + if self.line == '': return eof_object + token, self.line = re.match(InPort.tokenizer, self.line).groups() + if token != '' and not token.startswith(';'): + return token + +def readchar(inport): + "Read the next character from an input port." + if inport.line != '': + ch, inport.line = inport.line[0], inport.line[1:] + return ch + else: + return inport.file.read(1) or eof_object + +def read(inport): + "Read a Scheme expression from an input port." + def read_ahead(token): + if '(' == token: + L = [] + while True: + token = inport.next_token() + if token == ')': return L + else: L.append(read_ahead(token)) + elif ')' == token: raise SyntaxError('unexpected )') + elif token in quotes: return [quotes[token], read(inport)] + elif token is eof_object: raise SyntaxError('unexpected EOF in list') + else: return atom(token) + # body of read: + token1 = inport.next_token() + return eof_object if token1 is eof_object else read_ahead(token1) + +quotes = {"'":_quote, "`":_quasiquote, ",":_unquote, ",@":_unquotesplicing} + +def atom(token): + 'Numbers become numbers; #t and #f are booleans; "..." string; otherwise Symbol.' + if token == '#t': return True + elif token == '#f': return False + elif token[0] == '"': return token[1:-1] + try: return int(token) + except ValueError: + try: return float(token) + except ValueError: + try: return complex(token.replace('i', 'j', 1)) + except ValueError: + return Sym(token) + +def to_string(x): + "Convert a Python object back into a Lisp-readable string." + if x is True: return "#t" + elif x is False: return "#f" + elif isa(x, Symbol): return x + elif isa(x, str): return repr(x) + elif isa(x, list): return '('+' '.join(map(to_string, x))+')' + elif isa(x, complex): return str(x).replace('j', 'i') + else: return str(x) + +def load(filename): + "Eval every expression from a file." + repl(None, InPort(open(filename)), None) + +def repl(prompt='lispy> ', inport=InPort(sys.stdin), out=sys.stdout): + "A prompt-read-eval-print loop." + sys.stderr.write("Lispy version 2.0\n") + while True: + try: + if prompt: sys.stderr.write(prompt) + x = parse(inport) + if x is eof_object: return + val = eval(x) + if val is not None and out: print(to_string(val), file=out) + except Exception as e: + print('%s: %s' % (type(e).__name__, e)) + +################ Environment class + +class Env(dict): + "An environment: a dict of {'var':val} pairs, with an outer Env." + def __init__(self, parms=(), args=(), outer=None): + # Bind parm list to corresponding args, or single parm to list of args + self.outer = outer + if isa(parms, Symbol): + self.update({parms:list(args)}) + else: + if len(args) != len(parms): + raise TypeError('expected %s, given %s, ' + % (to_string(parms), to_string(args))) + self.update(zip(parms,args)) + def find(self, var): + "Find the innermost Env where var appears." + if var in self: return self + elif self.outer is None: raise LookupError(var) + else: return self.outer.find(var) + +def is_pair(x): return x != [] and isa(x, list) +def cons(x, y): return [x]+y + +def callcc(proc): + "Call proc with current continuation; escape only" + ball = RuntimeWarning("Sorry, can't continue this continuation any longer.") + def throw(retval): ball.retval = retval; raise ball + try: + return proc(throw) + except RuntimeWarning as w: + if w is ball: return ball.retval + else: raise w + +def add_globals(self): + "Add some Scheme standard procedures." + import math, cmath, operator as op + self.update(vars(math)) + self.update(vars(cmath)) + self.update({ + '+':op.add, '-':op.sub, '*':op.mul, '/':op.truediv, 'not':op.not_, + '>':op.gt, '<':op.lt, '>=':op.ge, '<=':op.le, '=':op.eq, + 'equal?':op.eq, 'eq?':op.is_, 'length':len, 'cons':cons, + 'car':lambda x:x[0], 'cdr':lambda x:x[1:], 'append':op.add, + 'list':lambda *x:list(x), 'list?': lambda x:isa(x,list), + 'null?':lambda x:x==[], 'symbol?':lambda x: isa(x, Symbol), + 'boolean?':lambda x: isa(x, bool), 'pair?':is_pair, + 'port?': lambda x:isa(x,file), 'apply':lambda proc,l: proc(*l), + 'eval':lambda x: eval(expand(x)), 'load':lambda fn: load(fn), 'call/cc':callcc, + 'open-input-file':open,'close-input-port':lambda p: p.file.close(), + 'open-output-file':lambda f:open(f,'w'), 'close-output-port':lambda p: p.close(), + 'eof-object?':lambda x:x is eof_object, 'read-char':readchar, + 'read':read, 'write':lambda x,port=sys.stdout:port.write(to_string(x)), + 'display':lambda x,port=sys.stdout:port.write(x if isa(x,str) else to_string(x))}) + return self + +isa = isinstance + +global_env = add_globals(Env()) + +################ eval (tail recursive) + +def eval(x, env=global_env): + "Evaluate an expression in an environment." + while True: + if isa(x, Symbol): # variable reference + return env.find(x)[x] + elif not isa(x, list): # constant literal + return x + elif x[0] is _quote: # (quote exp) + (_, exp) = x + return exp + elif x[0] is _if: # (if test conseq alt) + (_, test, conseq, alt) = x + x = (conseq if eval(test, env) else alt) + elif x[0] is _set: # (set! var exp) + (_, var, exp) = x + env.find(var)[var] = eval(exp, env) + return None + elif x[0] is _define: # (define var exp) + (_, var, exp) = x + env[var] = eval(exp, env) + return None + elif x[0] is _lambda: # (lambda (var*) exp) + (_, vars, exp) = x + return Procedure(vars, exp, env) + elif x[0] is _begin: # (begin exp+) + for exp in x[1:-1]: + eval(exp, env) + x = x[-1] + else: # (proc exp*) + exps = [eval(exp, env) for exp in x] + proc = exps.pop(0) + if isa(proc, Procedure): + x = proc.exp + env = Env(proc.parms, exps, proc.env) + else: + return proc(*exps) + +################ expand + +def expand(x, toplevel=False): + "Walk tree of x, making optimizations/fixes, and signaling SyntaxError." + require(x, x!=[]) # () => Error + if not isa(x, list): # constant => unchanged + return x + elif x[0] is _quote: # (quote exp) + require(x, len(x)==2) + return x + elif x[0] is _if: + if len(x)==3: x = x + [None] # (if t c) => (if t c None) + require(x, len(x)==4) + return list(map(expand, x)) + elif x[0] is _set: + require(x, len(x)==3); + var = x[1] # (set! non-var exp) => Error + require(x, isa(var, Symbol), "can set! only a symbol") + return [_set, var, expand(x[2])] + elif x[0] is _define or x[0] is _definemacro: + require(x, len(x)>=3) + _def, v, body = x[0], x[1], x[2:] + if isa(v, list) and v: # (define (f args) body) + f, args = v[0], v[1:] # => (define f (lambda (args) body)) + return expand([_def, f, [_lambda, args]+body]) + else: + require(x, len(x)==3) # (define non-var/list exp) => Error + require(x, isa(v, Symbol), "can define only a symbol") + exp = expand(x[2]) + if _def is _definemacro: + require(x, toplevel, "define-macro only allowed at top level") + proc = eval(exp) + require(x, callable(proc), "macro must be a procedure") + macro_table[v] = proc # (define-macro v proc) + return None # => None; add v:proc to macro_table + return [_define, v, exp] + elif x[0] is _begin: + if len(x)==1: return None # (begin) => None + else: return [expand(xi, toplevel) for xi in x] + elif x[0] is _lambda: # (lambda (x) e1 e2) + require(x, len(x)>=3) # => (lambda (x) (begin e1 e2)) + vars, body = x[1], x[2:] + require(x, (isa(vars, list) and all(isa(v, Symbol) for v in vars)) + or isa(vars, Symbol), "illegal lambda argument list") + exp = body[0] if len(body) == 1 else [_begin] + body + return [_lambda, vars, expand(exp)] + elif x[0] is _quasiquote: # `x => expand_quasiquote(x) + require(x, len(x)==2) + return expand_quasiquote(x[1]) + elif isa(x[0], Symbol) and x[0] in macro_table: + return expand(macro_table[x[0]](*x[1:]), toplevel) # (m arg...) + else: # => macroexpand if m isa macro + return list(map(expand, x)) # (f arg...) => expand each + +def require(x, predicate, msg="wrong length"): + "Signal a syntax error if predicate is false." + if not predicate: raise SyntaxError(to_string(x)+': '+msg) + +_append, _cons, _let = map(Sym, "append cons let".split()) + +def expand_quasiquote(x): + """Expand `x => 'x; `,x => x; `(,@x y) => (append x y) """ + if not is_pair(x): + return [_quote, x] + require(x, x[0] is not _unquotesplicing, "can't splice here") + if x[0] is _unquote: + require(x, len(x)==2) + return x[1] + elif is_pair(x[0]) and x[0][0] is _unquotesplicing: + require(x[0], len(x[0])==2) + return [_append, x[0][1], expand_quasiquote(x[1:])] + else: + return [_cons, expand_quasiquote(x[0]), expand_quasiquote(x[1:])] + +def let(*args): + args = list(args) + x = cons(_let, args) + require(x, len(args)>1) + bindings, body = args[0], args[1:] + require(x, all(isa(b, list) and len(b)==2 and isa(b[0], Symbol) + for b in bindings), "illegal binding list") + vars, vals = zip(*bindings) + return [[_lambda, list(vars)]+list(map(expand, body))] + list(map(expand, vals)) + +macro_table = {_let:let} ## More macros can go here + +eval(parse("""(begin + +(define-macro and (lambda args + (if (null? args) #t + (if (= (length args) 1) (car args) + `(if ,(car args) (and ,@(cdr args)) #f))))) + +;; More macros can also go here + +)""")) + +if __name__ == '__main__': + repl() diff --git a/02-array-seq/lispy/original/lispytest.py b/02-array-seq/lispy/original/lispytest.py new file mode 100644 index 0000000..b324ff3 --- /dev/null +++ b/02-array-seq/lispy/original/lispytest.py @@ -0,0 +1,122 @@ +from __future__ import print_function + +################ Tests for lis.py and lispy.py + +lis_tests = [ + ("(quote (testing 1 (2.0) -3.14e159))", ['testing', 1, [2.0], -3.14e159]), + ("(+ 2 2)", 4), + ("(+ (* 2 100) (* 1 10))", 210), + ("(if (> 6 5) (+ 1 1) (+ 2 2))", 2), + ("(if (< 6 5) (+ 1 1) (+ 2 2))", 4), + ("(define x 3)", None), ("x", 3), ("(+ x x)", 6), + ("((lambda (x) (+ x x)) 5)", 10), + ("(define twice (lambda (x) (* 2 x)))", None), ("(twice 5)", 10), + ("(define compose (lambda (f g) (lambda (x) (f (g x)))))", None), + ("((compose list twice) 5)", [10]), + ("(define repeat (lambda (f) (compose f f)))", None), + ("((repeat twice) 5)", 20), ("((repeat (repeat twice)) 5)", 80), + ("(define fact (lambda (n) (if (<= n 1) 1 (* n (fact (- n 1))))))", None), + ("(fact 3)", 6), + ("(fact 50)", 30414093201713378043612608166064768844377641568960512000000000000), + ("(define abs (lambda (n) ((if (> n 0) + -) 0 n)))", None), + ("(list (abs -3) (abs 0) (abs 3))", [3, 0, 3]), + ("""(define combine (lambda (f) + (lambda (x y) + (if (null? x) (quote ()) + (f (list (car x) (car y)) + ((combine f) (cdr x) (cdr y)))))))""", None), + ("(define zip (combine cons))", None), + ("(zip (list 1 2 3 4) (list 5 6 7 8))", [[1, 5], [2, 6], [3, 7], [4, 8]]), + ("""(define riff-shuffle (lambda (deck) (begin + (define take (lambda (n seq) (if (<= n 0) (quote ()) (cons (car seq) (take (- n 1) (cdr seq)))))) + (define drop (lambda (n seq) (if (<= n 0) seq (drop (- n 1) (cdr seq))))) + (define mid (lambda (seq) (/ (length seq) 2))) + ((combine append) (take (mid deck) deck) (drop (mid deck) deck)))))""", None), + ("(riff-shuffle (list 1 2 3 4 5 6 7 8))", [1, 5, 2, 6, 3, 7, 4, 8]), + ("((repeat riff-shuffle) (list 1 2 3 4 5 6 7 8))", [1, 3, 5, 7, 2, 4, 6, 8]), + ("(riff-shuffle (riff-shuffle (riff-shuffle (list 1 2 3 4 5 6 7 8))))", [1,2,3,4,5,6,7,8]), + ] + +lispy_tests = [ + ("()", SyntaxError), ("(set! x)", SyntaxError), + ("(define 3 4)", SyntaxError), + ("(quote 1 2)", SyntaxError), ("(if 1 2 3 4)", SyntaxError), + ("(lambda 3 3)", SyntaxError), ("(lambda (x))", SyntaxError), + ("""(if (= 1 2) (define-macro a 'a) + (define-macro a 'b))""", SyntaxError), + ("(define (twice x) (* 2 x))", None), ("(twice 2)", 4), + ("(twice 2 2)", TypeError), + ("(define lyst (lambda items items))", None), + ("(lyst 1 2 3 (+ 2 2))", [1,2,3,4]), + ("(if 1 2)", 2), + ("(if (= 3 4) 2)", None), + ("(begin (define x 1) (set! x (+ x 1)) (+ x 1))", 3), + ("(define ((account bal) amt) (set! bal (+ bal amt)) bal)", None), + ("(define a1 (account 100))", None), + ("(a1 0)", 100), ("(a1 10)", 110), ("(a1 10)", 120), + ("""(define (newton guess function derivative epsilon) + (define guess2 (- guess (/ (function guess) (derivative guess)))) + (if (< (abs (- guess guess2)) epsilon) guess2 + (newton guess2 function derivative epsilon)))""", None), + ("""(define (square-root a) + (newton 1 (lambda (x) (- (* x x) a)) (lambda (x) (* 2 x)) 1e-8))""", None), + ("(> (square-root 200.) 14.14213)", True), + ("(< (square-root 200.) 14.14215)", True), + ("(= (square-root 200.) (sqrt 200.))", True), + ("""(define (sum-squares-range start end) + (define (sumsq-acc start end acc) + (if (> start end) acc (sumsq-acc (+ start 1) end (+ (* start start) acc)))) + (sumsq-acc start end 0))""", None), + ("(sum-squares-range 1 3000)", 9004500500), ## Tests tail recursion + ("(call/cc (lambda (throw) (+ 5 (* 10 (throw 1))))) ;; throw", 1), + ("(call/cc (lambda (throw) (+ 5 (* 10 1)))) ;; do not throw", 15), + ("""(call/cc (lambda (throw) + (+ 5 (* 10 (call/cc (lambda (escape) (* 100 (escape 3)))))))) ; 1 level""", 35), + ("""(call/cc (lambda (throw) + (+ 5 (* 10 (call/cc (lambda (escape) (* 100 (throw 3)))))))) ; 2 levels""", 3), + ("""(call/cc (lambda (throw) + (+ 5 (* 10 (call/cc (lambda (escape) (* 100 1))))))) ; 0 levels""", 1005), + ("(* 1i 1i)", -1), ("(sqrt -1)", 1j), + ("(let ((a 1) (b 2)) (+ a b))", 3), + ("(let ((a 1) (b 2 3)) (+ a b))", SyntaxError), + ("(and 1 2 3)", 3), ("(and (> 2 1) 2 3)", 3), ("(and)", True), + ("(and (> 2 1) (> 2 3))", False), + ("(define-macro unless (lambda args `(if (not ,(car args)) (begin ,@(cdr args))))) ; test `", None), + ("(unless (= 2 (+ 1 1)) (display 2) 3 4)", None), + (r'(unless (= 4 (+ 1 1)) (display 2) (display "\n") 3 4)', 4), + ("(quote x)", 'x'), + ("(quote (1 2 three))", [1, 2, 'three']), + ("'x", 'x'), + ("'(one 2 3)", ['one', 2, 3]), + ("(define L (list 1 2 3))", None), + ("`(testing ,@L testing)", ['testing',1,2,3,'testing']), + ("`(testing ,L testing)", ['testing',[1,2,3],'testing']), + ("`,@L", SyntaxError), + ("""'(1 ;test comments ' + ;skip this line + 2 ; more ; comments ; ) ) + 3) ; final comment""", [1,2,3]), + ] + +def test(tests, name=''): + "For each (exp, expected) test case, see if eval(parse(exp)) == expected." + fails = 0 + for (x, expected) in tests: + try: + result = eval(parse(x)) + print(x, '=>', lispstr(result)) + ok = (result == expected) + except Exception as e: + print(x, '=raises=>', type(e).__name__, e) + ok = isinstance(expected, type) and issubclass(expected, Exception) and isinstance(e, expected) + if not ok: + fails += 1 + print('FAIL!!! Expected', expected) + print('%s %s: %d out of %d tests fail.' % ('*'*45, name, fails, len(tests))) + +if __name__ == '__main__': + from lis import * + test(lis_tests, 'lis.py') + from lispy import * + test(lis_tests+lispy_tests, 'lispy.py') + diff --git a/02-array-seq/lispy/examples_test.py b/02-array-seq/lispy/py3.10/examples_test.py similarity index 100% rename from 02-array-seq/lispy/examples_test.py rename to 02-array-seq/lispy/py3.10/examples_test.py diff --git a/02-array-seq/lispy/lis.py b/02-array-seq/lispy/py3.10/lis.py similarity index 95% rename from 02-array-seq/lispy/lis.py rename to 02-array-seq/lispy/py3.10/lis.py index e37ba23..7ae0a9d 100644 --- a/02-array-seq/lispy/lis.py +++ b/02-array-seq/lispy/py3.10/lis.py @@ -23,7 +23,7 @@ class Procedure: "A user-defined Scheme procedure." - def __init__(self, parms: list[Symbol], body: Expression, env: Environment): + def __init__(self, parms: list[Symbol], body: list[Expression], env: Environment): self.parms = parms self.body = body self.env = env @@ -31,7 +31,9 @@ def __init__(self, parms: list[Symbol], body: Expression, env: Environment): def __call__(self, *args: Expression) -> Any: local_env = dict(zip(self.parms, args)) env: Environment = ChainMap(local_env, self.env) - return evaluate(self.body, env) + for exp in self.body: + result = evaluate(exp, env) + return result ################ global environment @@ -160,9 +162,9 @@ def evaluate(exp: Expression, env: Environment) -> Any: return evaluate(alternative, env) case ['define', Symbol(var), value_exp]: env[var] = evaluate(value_exp, env) - case ['define', [Symbol(name), *parms], body]: + case ['define', [Symbol(name), *parms], *body]: env[name] = Procedure(parms, body, env) - case ['lambda', [*parms], body]: + case ['lambda', [*parms], *body]: return Procedure(parms, body, env) case [op, *args]: proc = evaluate(op, env) diff --git a/02-array-seq/lispy/lis_test.py b/02-array-seq/lispy/py3.10/lis_test.py similarity index 97% rename from 02-array-seq/lispy/lis_test.py rename to 02-array-seq/lispy/py3.10/lis_test.py index e220e74..3688888 100644 --- a/02-array-seq/lispy/lis_test.py +++ b/02-array-seq/lispy/py3.10/lis_test.py @@ -12,7 +12,7 @@ ('(sum 1 2 3)', ['sum', 1, 2, 3]), ('(+ (* 2 100) (* 1 10))', ['+', ['*', 2, 100], ['*', 1, 10]]), ('99 100', 99), # parse stops at the first complete expression - ('(a)(b)', ['a']), + ('(a)(b)', ['a']), ]) def test_parse(source: str, expected: Expression) -> None: got = parse(source) @@ -122,7 +122,7 @@ def test_lambda(std_env: Environment) -> None: source = '(lambda (a b) (if (>= a b) a b))' func = evaluate(parse(source), std_env) assert func.parms == ['a', 'b'] - assert func.body == ['if', ['>=', 'a', 'b'], 'a', 'b'] + assert func.body == [['if', ['>=', 'a', 'b'], 'a', 'b']] assert func.env is std_env assert func(1, 2) == 2 assert func(3, 2) == 3 @@ -176,7 +176,7 @@ def test_define_function(std_env: Environment) -> None: assert got is None max_fn = std_env['max'] assert max_fn.parms == ['a', 'b'] - assert max_fn.body == ['if', ['>=', 'a', 'b'], 'a', 'b'] + assert max_fn.body == [['if', ['>=', 'a', 'b'], 'a', 'b']] assert max_fn.env is std_env assert max_fn(1, 2) == 2 assert max_fn(3, 2) == 3 diff --git a/02-array-seq/lispy/meta_test.py b/02-array-seq/lispy/py3.10/meta_test.py similarity index 100% rename from 02-array-seq/lispy/meta_test.py rename to 02-array-seq/lispy/py3.10/meta_test.py From de1fbfbcee6238f4c33cf9d3ea2e5afe2c6313d4 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Tue, 29 Jun 2021 12:24:32 -0300 Subject: [PATCH 090/166] Update README.md --- 02-array-seq/lispy/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/02-array-seq/lispy/README.md b/02-array-seq/lispy/README.md index 0c11fd8..b8f769a 100644 --- a/02-array-seq/lispy/README.md +++ b/02-array-seq/lispy/README.md @@ -1,4 +1,4 @@ -# Norvig's originals and updates +# Norvig's `lis.py` and updates This directory contains: @@ -33,4 +33,4 @@ I made small changes to the programs in `original/`: * In `lispy.py` I made [changes and a pull request](https://github.com/norvig/pytudes/pull/106) to make it run on Python 3. -_Luciano Ramalho
June 29, 2021_ \ No newline at end of file +_Luciano Ramalho
June 29, 2021_ From 88a92385528b67c3c9e90c7ef2466aef18850791 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Tue, 29 Jun 2021 12:26:50 -0300 Subject: [PATCH 091/166] Update README.md --- 02-array-seq/lispy/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/02-array-seq/lispy/README.md b/02-array-seq/lispy/README.md index b8f769a..be251f2 100644 --- a/02-array-seq/lispy/README.md +++ b/02-array-seq/lispy/README.md @@ -8,7 +8,7 @@ Norvig's [`lis.py`](https://github.com/norvig/pytudes/blob/c33cd6835a506a57d9fe7 * `py3.10/`: `lis.py` with type hints, pattern matching, and minor edits—requires Python 3.10. The `py3.10/` directory also has `lis_test.py` to run with -[pytest](https://docs.pytest.org), including all the +[pytest](https://docs.pytest.org), including the [`lis_tests` suite](https://github.com/norvig/pytudes/blob/60168bce8cdfacf57c92a5b2979f0b2e95367753/py/lispytest.py#L5) from `original/lispytest.py`, and additional separate tests for each expression and special form handled by `evaluate`. @@ -28,7 +28,7 @@ The copyright holder is Peter Norvig and the code is licensed under the I made small changes to the programs in `original/`: * In `lis.py`: - * The `Procedure` class accepts a list of expressions as the `body`, and `__call__` evaluates all those expressions in order, returning the value of the last. This is consistent with Scheme's `lambda` syntax and provided a useful example for pattern matching. + * The `Procedure` class accepts a list of expressions as the `body`, and `__call__` evaluates those expressions in order, and returns the value of the last. This is consistent with Scheme's `lambda` syntax and provided a useful example for pattern matching. * In the `elif` block for `'lambda'`, I added the `*` in front of the `*body` variable in the tuple unpacking to capture the expressions as a list, before calling the `Procedure` constructor. * In `lispy.py` I made [changes and a pull request](https://github.com/norvig/pytudes/pull/106) to make it run on Python 3. From 23e78eeb82e04731fbb6063d1075d2f6400cc26c Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Wed, 7 Jul 2021 23:45:54 -0300 Subject: [PATCH 092/166] sync with O'Reilly Atlas --- 02-array-seq/lispy/LICENSE | 21 ++ 02-array-seq/lispy/README.md | 36 ++ 02-array-seq/lispy/original/LICENSE | 21 ++ 02-array-seq/lispy/original/README.md | 8 + 02-array-seq/lispy/original/lis.py | 132 ++++++++ 02-array-seq/lispy/original/lispy.py | 316 ++++++++++++++++++ 02-array-seq/lispy/original/lispytest.py | 122 +++++++ .../lispy/{ => py3.10}/examples_test.py | 0 02-array-seq/lispy/py3.10/lis.py | 188 +++++++++++ 02-array-seq/lispy/{ => py3.10}/lis_test.py | 6 +- 02-array-seq/lispy/py3.10/meta_test.py | 69 ++++ 02-array-seq/lispy/py3.9-no-hints/README.md | 33 ++ 02-array-seq/lispy/py3.9-no-hints/lis.py | 142 ++++++++ 02-array-seq/lispy/py3.9-no-hints/lis_test.py | 166 +++++++++ 02-array-seq/lispy/py3.9/README.md | 33 ++ 02-array-seq/lispy/py3.9/lis.py | 155 +++++++++ 02-array-seq/lispy/py3.9/lis_test.py | 168 ++++++++++ 02-array-seq/match_lat_lon.py | 4 +- 02-array-seq/metro_lat_lon.py | 8 +- 03-dict-set/py3.10/creator.py | 40 +++ 04-text-byte/categories.py | 4 +- 04-text-byte/stdout_check.py | 6 +- .../README.asciidoc | 0 .../cards.doctest | 0 {05-record-like => 05-data-classes}/cards.py | 0 .../cards_enum.py | 0 .../class/coordinates.py | 2 +- .../dataclass/club.py | 0 .../dataclass/club_generic.py | 0 .../dataclass/club_wrong.py | 0 .../dataclass/coordinates.py | 0 .../dataclass/hackerclub.py | 0 .../dataclass/hackerclub_annotated.py | 0 .../dataclass/resource.py | 0 .../dataclass/resource_repr.py | 0 .../frenchdeck.doctest | 0 .../frenchdeck.py | 0 05-data-classes/match_cities.py | 92 +++++ .../meaning/demo_dc.py | 0 .../meaning/demo_nt.py | 0 .../meaning/demo_plain.py | 0 .../typing_namedtuple/coordinates.py | 0 .../typing_namedtuple/coordinates2.py | 2 +- .../typing_namedtuple/nocheck_demo.py | 2 +- 05-record-like/struct/README | 17 - 05-record-like/struct/metro | Bin 8764 -> 0 bytes 05-record-like/struct/metro_areas.bin | Bin 72 -> 0 bytes 05-record-like/struct/metro_read.py | 15 - 05-record-like/struct/metro_write.c | 46 --- 07-1class-func/clip.py | 30 +- 07-1class-func/clip_signature.rst | 4 +- 07-1class-func/tagger.py | 9 +- {18-context-mngr => 18-with-match}/README.rst | 0 18-with-match/lisplus/examples_test.py | 109 ++++++ .../lispy => 18-with-match/lisplus}/lis.py | 10 +- 18-with-match/lisplus/lis_test.py | 182 ++++++++++ .../lisplus}/meta_test.py | 0 {18-context-mngr => 18-with-match}/mirror.py | 0 .../mirror_gen.py | 0 .../mirror_gen_exc.py | 0 .../checked/decorator/checkeddeco.py | 3 +- .../checked/initsub/checkedlib.py | 3 +- .../checked/metaclass/checkedlib.py | 3 +- README.md | 4 +- 64 files changed, 2087 insertions(+), 124 deletions(-) create mode 100644 02-array-seq/lispy/LICENSE create mode 100644 02-array-seq/lispy/README.md create mode 100644 02-array-seq/lispy/original/LICENSE create mode 100644 02-array-seq/lispy/original/README.md create mode 100644 02-array-seq/lispy/original/lis.py create mode 100644 02-array-seq/lispy/original/lispy.py create mode 100644 02-array-seq/lispy/original/lispytest.py rename 02-array-seq/lispy/{ => py3.10}/examples_test.py (100%) create mode 100644 02-array-seq/lispy/py3.10/lis.py rename 02-array-seq/lispy/{ => py3.10}/lis_test.py (97%) create mode 100644 02-array-seq/lispy/py3.10/meta_test.py create mode 100644 02-array-seq/lispy/py3.9-no-hints/README.md create mode 100644 02-array-seq/lispy/py3.9-no-hints/lis.py create mode 100644 02-array-seq/lispy/py3.9-no-hints/lis_test.py create mode 100644 02-array-seq/lispy/py3.9/README.md create mode 100644 02-array-seq/lispy/py3.9/lis.py create mode 100644 02-array-seq/lispy/py3.9/lis_test.py create mode 100644 03-dict-set/py3.10/creator.py rename {05-record-like => 05-data-classes}/README.asciidoc (100%) rename {05-record-like => 05-data-classes}/cards.doctest (100%) rename {05-record-like => 05-data-classes}/cards.py (100%) rename {05-record-like => 05-data-classes}/cards_enum.py (100%) rename {05-record-like => 05-data-classes}/class/coordinates.py (94%) rename {05-record-like => 05-data-classes}/dataclass/club.py (100%) rename {05-record-like => 05-data-classes}/dataclass/club_generic.py (100%) rename {05-record-like => 05-data-classes}/dataclass/club_wrong.py (100%) rename {05-record-like => 05-data-classes}/dataclass/coordinates.py (100%) rename {05-record-like => 05-data-classes}/dataclass/hackerclub.py (100%) rename {05-record-like => 05-data-classes}/dataclass/hackerclub_annotated.py (100%) rename {05-record-like => 05-data-classes}/dataclass/resource.py (100%) rename {05-record-like => 05-data-classes}/dataclass/resource_repr.py (100%) rename {05-record-like => 05-data-classes}/frenchdeck.doctest (100%) rename {05-record-like => 05-data-classes}/frenchdeck.py (100%) create mode 100644 05-data-classes/match_cities.py rename {05-record-like => 05-data-classes}/meaning/demo_dc.py (100%) rename {05-record-like => 05-data-classes}/meaning/demo_nt.py (100%) rename {05-record-like => 05-data-classes}/meaning/demo_plain.py (100%) rename {05-record-like => 05-data-classes}/typing_namedtuple/coordinates.py (100%) rename {05-record-like => 05-data-classes}/typing_namedtuple/coordinates2.py (95%) rename {05-record-like => 05-data-classes}/typing_namedtuple/nocheck_demo.py (71%) delete mode 100644 05-record-like/struct/README delete mode 100755 05-record-like/struct/metro delete mode 100644 05-record-like/struct/metro_areas.bin delete mode 100644 05-record-like/struct/metro_read.py delete mode 100644 05-record-like/struct/metro_write.c rename {18-context-mngr => 18-with-match}/README.rst (100%) create mode 100644 18-with-match/lisplus/examples_test.py rename {02-array-seq/lispy => 18-with-match/lisplus}/lis.py (95%) create mode 100644 18-with-match/lisplus/lis_test.py rename {02-array-seq/lispy => 18-with-match/lisplus}/meta_test.py (100%) rename {18-context-mngr => 18-with-match}/mirror.py (100%) rename {18-context-mngr => 18-with-match}/mirror_gen.py (100%) rename {18-context-mngr => 18-with-match}/mirror_gen_exc.py (100%) diff --git a/02-array-seq/lispy/LICENSE b/02-array-seq/lispy/LICENSE new file mode 100644 index 0000000..ca550a2 --- /dev/null +++ b/02-array-seq/lispy/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2010-2017 Peter Norvig + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/02-array-seq/lispy/README.md b/02-array-seq/lispy/README.md new file mode 100644 index 0000000..0c11fd8 --- /dev/null +++ b/02-array-seq/lispy/README.md @@ -0,0 +1,36 @@ +# Norvig's originals and updates + +This directory contains: + +* `original/`: +Norvig's [`lis.py`](https://github.com/norvig/pytudes/blob/c33cd6835a506a57d9fe73e3a8317d49babb13e8/py/lis.py), +[`lispy.py`](https://github.com/norvig/pytudes/blob/c33cd6835a506a57d9fe73e3a8317d49babb13e8/py/lispy.py), and the `lispytest.py` custom test script for testing both; +* `py3.10/`: `lis.py` with type hints, pattern matching, and minor edits—requires Python 3.10. + +The `py3.10/` directory also has `lis_test.py` to run with +[pytest](https://docs.pytest.org), including all the +[`lis_tests` suite](https://github.com/norvig/pytudes/blob/60168bce8cdfacf57c92a5b2979f0b2e95367753/py/lispytest.py#L5) +from `original/lispytest.py`, +and additional separate tests for each expression and special form handled by `evaluate`. + + +## Provenance, Copyright and License + +`lis.py` is +[published](https://github.com/norvig/pytudes/blob/c33cd6835a506a57d9fe73e3a8317d49babb13e8/py/lis.py) +in the [norvig/pytudes](https://github.com/norvig/pytudes) repository on Github. +The copyright holder is Peter Norvig and the code is licensed under the +[MIT license](https://github.com/norvig/pytudes/blob/60168bce8cdfacf57c92a5b2979f0b2e95367753/LICENSE). + + +## Changes to Norvig's code + +I made small changes to the programs in `original/`: + +* In `lis.py`: + * The `Procedure` class accepts a list of expressions as the `body`, and `__call__` evaluates all those expressions in order, returning the value of the last. This is consistent with Scheme's `lambda` syntax and provided a useful example for pattern matching. + * In the `elif` block for `'lambda'`, I added the `*` in front of the `*body` variable in the tuple unpacking to capture the expressions as a list, before calling the `Procedure` constructor. + +* In `lispy.py` I made [changes and a pull request](https://github.com/norvig/pytudes/pull/106) to make it run on Python 3. + +_Luciano Ramalho
June 29, 2021_ \ No newline at end of file diff --git a/02-array-seq/lispy/original/LICENSE b/02-array-seq/lispy/original/LICENSE new file mode 100644 index 0000000..ca550a2 --- /dev/null +++ b/02-array-seq/lispy/original/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2010-2017 Peter Norvig + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/02-array-seq/lispy/original/README.md b/02-array-seq/lispy/original/README.md new file mode 100644 index 0000000..bb4d4e0 --- /dev/null +++ b/02-array-seq/lispy/original/README.md @@ -0,0 +1,8 @@ + +# Source of the originals + +* [lis.py](https://raw.githubusercontent.com/norvig/pytudes/705c0a335c1811a203e79587d7d41865cf7f41c7/py/lis.py) + +* [lispy.py](https://raw.githubusercontent.com/norvig/pytudes/705c0a335c1811a203e79587d7d41865cf7f41c7/py/lispy.py) + +* [lispytest.py](https://raw.githubusercontent.com/norvig/pytudes/705c0a335c1811a203e79587d7d41865cf7f41c7/py/lispytest.py) diff --git a/02-array-seq/lispy/original/lis.py b/02-array-seq/lispy/original/lis.py new file mode 100644 index 0000000..f81376a --- /dev/null +++ b/02-array-seq/lispy/original/lis.py @@ -0,0 +1,132 @@ +################ Lispy: Scheme Interpreter in Python 3.3+ + +## (c) Peter Norvig, 2010-18; See http://norvig.com/lispy.html + +################ Imports and Types + +import math +import operator as op +from collections import ChainMap as Environment + +Symbol = str # A Lisp Symbol is implemented as a Python str +List = list # A Lisp List is implemented as a Python list +Number = (int, float) # A Lisp Number is implemented as a Python int or float + +class Procedure(object): + "A user-defined Scheme procedure." + def __init__(self, parms, body, env): + self.parms, self.body, self.env = parms, body, env + def __call__(self, *args): + env = Environment(dict(zip(self.parms, args)), self.env) + return eval(self.body, env) + +################ Global Environment + +def standard_env(): + "An environment with some Scheme standard procedures." + env = {} + env.update(vars(math)) # sin, cos, sqrt, pi, ... + env.update({ + '+':op.add, '-':op.sub, '*':op.mul, '/':op.truediv, + '>':op.gt, '<':op.lt, '>=':op.ge, '<=':op.le, '=':op.eq, + 'abs': abs, + 'append': op.add, + 'apply': lambda proc, args: proc(*args), + 'begin': lambda *x: x[-1], + 'car': lambda x: x[0], + 'cdr': lambda x: x[1:], + 'cons': lambda x,y: [x] + y, + 'eq?': op.is_, + 'equal?': op.eq, + 'length': len, + 'list': lambda *x: list(x), + 'list?': lambda x: isinstance(x,list), + 'map': lambda *args: list(map(*args)), + 'max': max, + 'min': min, + 'not': op.not_, + 'null?': lambda x: x == [], + 'number?': lambda x: isinstance(x, Number), + 'procedure?': callable, + 'round': round, + 'symbol?': lambda x: isinstance(x, Symbol), + }) + return env + +global_env = standard_env() + +################ Parsing: parse, tokenize, and read_from_tokens + +def parse(program): + "Read a Scheme expression from a string." + return read_from_tokens(tokenize(program)) + +def tokenize(s): + "Convert a string into a list of tokens." + return s.replace('(',' ( ').replace(')',' ) ').split() + +def read_from_tokens(tokens): + "Read an expression from a sequence of tokens." + if len(tokens) == 0: + raise SyntaxError('unexpected EOF while reading') + token = tokens.pop(0) + if '(' == token: + L = [] + while tokens[0] != ')': + L.append(read_from_tokens(tokens)) + tokens.pop(0) # pop off ')' + return L + elif ')' == token: + raise SyntaxError('unexpected )') + else: + return atom(token) + +def atom(token): + "Numbers become numbers; every other token is a symbol." + try: return int(token) + except ValueError: + try: return float(token) + except ValueError: + return Symbol(token) + +################ Interaction: A REPL + +def repl(prompt='lis.py> '): + "A prompt-read-eval-print loop." + while True: + val = eval(parse(input(prompt))) + if val is not None: + print(lispstr(val)) + +def lispstr(exp): + "Convert a Python object back into a Lisp-readable string." + if isinstance(exp, List): + return '(' + ' '.join(map(lispstr, exp)) + ')' + else: + return str(exp) + +################ eval + +def eval(x, env=global_env): + "Evaluate an expression in an environment." + if isinstance(x, Symbol): # variable reference + return env[x] + elif not isinstance(x, List): # constant literal + return x + elif x[0] == 'quote': # (quote exp) + (_, exp) = x + return exp + elif x[0] == 'if': # (if test conseq alt) + (_, test, conseq, alt) = x + exp = (conseq if eval(test, env) else alt) + return eval(exp, env) + elif x[0] == 'define': # (define var exp) + (_, var, exp) = x + env[var] = eval(exp, env) + elif x[0] == 'lambda': # (lambda (var...) body) + (_, parms, body) = x + return Procedure(parms, body, env) + else: # (proc arg...) + proc = eval(x[0], env) + args = [eval(exp, env) for exp in x[1:]] + return proc(*args) diff --git a/02-array-seq/lispy/original/lispy.py b/02-array-seq/lispy/original/lispy.py new file mode 100644 index 0000000..b17341c --- /dev/null +++ b/02-array-seq/lispy/original/lispy.py @@ -0,0 +1,316 @@ +################ Scheme Interpreter in Python + +## (c) Peter Norvig, 2010; See http://norvig.com/lispy2.html + +################ Symbol, Procedure, classes + +import re, sys, io + +class Symbol(str): pass + +def Sym(s, symbol_table={}): + "Find or create unique Symbol entry for str s in symbol table." + if s not in symbol_table: symbol_table[s] = Symbol(s) + return symbol_table[s] + +_quote, _if, _set, _define, _lambda, _begin, _definemacro, = map(Sym, +"quote if set! define lambda begin define-macro".split()) + +_quasiquote, _unquote, _unquotesplicing = map(Sym, +"quasiquote unquote unquote-splicing".split()) + +class Procedure: + "A user-defined Scheme procedure." + def __init__(self, parms, exp, env): + self.parms, self.exp, self.env = parms, exp, env + def __call__(self, *args): + return eval(self.exp, Env(self.parms, args, self.env)) + +################ parse, read, and user interaction + +def parse(inport): + "Parse a program: read and expand/error-check it." + # Backwards compatibility: given a str, convert it to an InPort + if isinstance(inport, str): inport = InPort(io.StringIO(inport)) + return expand(read(inport), toplevel=True) + +eof_object = Symbol('#') # Note: uninterned; can't be read + +class InPort: + "An input port. Retains a line of chars." + tokenizer = r"""\s*(,@|[('`,)]|"(?:[\\].|[^\\"])*"|;.*|[^\s('"`,;)]*)(.*)""" + def __init__(self, file): + self.file = file; self.line = '' + def next_token(self): + "Return the next token, reading new text into line buffer if needed." + while True: + if self.line == '': self.line = self.file.readline() + if self.line == '': return eof_object + token, self.line = re.match(InPort.tokenizer, self.line).groups() + if token != '' and not token.startswith(';'): + return token + +def readchar(inport): + "Read the next character from an input port." + if inport.line != '': + ch, inport.line = inport.line[0], inport.line[1:] + return ch + else: + return inport.file.read(1) or eof_object + +def read(inport): + "Read a Scheme expression from an input port." + def read_ahead(token): + if '(' == token: + L = [] + while True: + token = inport.next_token() + if token == ')': return L + else: L.append(read_ahead(token)) + elif ')' == token: raise SyntaxError('unexpected )') + elif token in quotes: return [quotes[token], read(inport)] + elif token is eof_object: raise SyntaxError('unexpected EOF in list') + else: return atom(token) + # body of read: + token1 = inport.next_token() + return eof_object if token1 is eof_object else read_ahead(token1) + +quotes = {"'":_quote, "`":_quasiquote, ",":_unquote, ",@":_unquotesplicing} + +def atom(token): + 'Numbers become numbers; #t and #f are booleans; "..." string; otherwise Symbol.' + if token == '#t': return True + elif token == '#f': return False + elif token[0] == '"': return token[1:-1] + try: return int(token) + except ValueError: + try: return float(token) + except ValueError: + try: return complex(token.replace('i', 'j', 1)) + except ValueError: + return Sym(token) + +def to_string(x): + "Convert a Python object back into a Lisp-readable string." + if x is True: return "#t" + elif x is False: return "#f" + elif isa(x, Symbol): return x + elif isa(x, str): return repr(x) + elif isa(x, list): return '('+' '.join(map(to_string, x))+')' + elif isa(x, complex): return str(x).replace('j', 'i') + else: return str(x) + +def load(filename): + "Eval every expression from a file." + repl(None, InPort(open(filename)), None) + +def repl(prompt='lispy> ', inport=InPort(sys.stdin), out=sys.stdout): + "A prompt-read-eval-print loop." + sys.stderr.write("Lispy version 2.0\n") + while True: + try: + if prompt: sys.stderr.write(prompt) + x = parse(inport) + if x is eof_object: return + val = eval(x) + if val is not None and out: print(to_string(val), file=out) + except Exception as e: + print('%s: %s' % (type(e).__name__, e)) + +################ Environment class + +class Env(dict): + "An environment: a dict of {'var':val} pairs, with an outer Env." + def __init__(self, parms=(), args=(), outer=None): + # Bind parm list to corresponding args, or single parm to list of args + self.outer = outer + if isa(parms, Symbol): + self.update({parms:list(args)}) + else: + if len(args) != len(parms): + raise TypeError('expected %s, given %s, ' + % (to_string(parms), to_string(args))) + self.update(zip(parms,args)) + def find(self, var): + "Find the innermost Env where var appears." + if var in self: return self + elif self.outer is None: raise LookupError(var) + else: return self.outer.find(var) + +def is_pair(x): return x != [] and isa(x, list) +def cons(x, y): return [x]+y + +def callcc(proc): + "Call proc with current continuation; escape only" + ball = RuntimeWarning("Sorry, can't continue this continuation any longer.") + def throw(retval): ball.retval = retval; raise ball + try: + return proc(throw) + except RuntimeWarning as w: + if w is ball: return ball.retval + else: raise w + +def add_globals(self): + "Add some Scheme standard procedures." + import math, cmath, operator as op + self.update(vars(math)) + self.update(vars(cmath)) + self.update({ + '+':op.add, '-':op.sub, '*':op.mul, '/':op.truediv, 'not':op.not_, + '>':op.gt, '<':op.lt, '>=':op.ge, '<=':op.le, '=':op.eq, + 'equal?':op.eq, 'eq?':op.is_, 'length':len, 'cons':cons, + 'car':lambda x:x[0], 'cdr':lambda x:x[1:], 'append':op.add, + 'list':lambda *x:list(x), 'list?': lambda x:isa(x,list), + 'null?':lambda x:x==[], 'symbol?':lambda x: isa(x, Symbol), + 'boolean?':lambda x: isa(x, bool), 'pair?':is_pair, + 'port?': lambda x:isa(x,file), 'apply':lambda proc,l: proc(*l), + 'eval':lambda x: eval(expand(x)), 'load':lambda fn: load(fn), 'call/cc':callcc, + 'open-input-file':open,'close-input-port':lambda p: p.file.close(), + 'open-output-file':lambda f:open(f,'w'), 'close-output-port':lambda p: p.close(), + 'eof-object?':lambda x:x is eof_object, 'read-char':readchar, + 'read':read, 'write':lambda x,port=sys.stdout:port.write(to_string(x)), + 'display':lambda x,port=sys.stdout:port.write(x if isa(x,str) else to_string(x))}) + return self + +isa = isinstance + +global_env = add_globals(Env()) + +################ eval (tail recursive) + +def eval(x, env=global_env): + "Evaluate an expression in an environment." + while True: + if isa(x, Symbol): # variable reference + return env.find(x)[x] + elif not isa(x, list): # constant literal + return x + elif x[0] is _quote: # (quote exp) + (_, exp) = x + return exp + elif x[0] is _if: # (if test conseq alt) + (_, test, conseq, alt) = x + x = (conseq if eval(test, env) else alt) + elif x[0] is _set: # (set! var exp) + (_, var, exp) = x + env.find(var)[var] = eval(exp, env) + return None + elif x[0] is _define: # (define var exp) + (_, var, exp) = x + env[var] = eval(exp, env) + return None + elif x[0] is _lambda: # (lambda (var*) exp) + (_, vars, exp) = x + return Procedure(vars, exp, env) + elif x[0] is _begin: # (begin exp+) + for exp in x[1:-1]: + eval(exp, env) + x = x[-1] + else: # (proc exp*) + exps = [eval(exp, env) for exp in x] + proc = exps.pop(0) + if isa(proc, Procedure): + x = proc.exp + env = Env(proc.parms, exps, proc.env) + else: + return proc(*exps) + +################ expand + +def expand(x, toplevel=False): + "Walk tree of x, making optimizations/fixes, and signaling SyntaxError." + require(x, x!=[]) # () => Error + if not isa(x, list): # constant => unchanged + return x + elif x[0] is _quote: # (quote exp) + require(x, len(x)==2) + return x + elif x[0] is _if: + if len(x)==3: x = x + [None] # (if t c) => (if t c None) + require(x, len(x)==4) + return list(map(expand, x)) + elif x[0] is _set: + require(x, len(x)==3); + var = x[1] # (set! non-var exp) => Error + require(x, isa(var, Symbol), "can set! only a symbol") + return [_set, var, expand(x[2])] + elif x[0] is _define or x[0] is _definemacro: + require(x, len(x)>=3) + _def, v, body = x[0], x[1], x[2:] + if isa(v, list) and v: # (define (f args) body) + f, args = v[0], v[1:] # => (define f (lambda (args) body)) + return expand([_def, f, [_lambda, args]+body]) + else: + require(x, len(x)==3) # (define non-var/list exp) => Error + require(x, isa(v, Symbol), "can define only a symbol") + exp = expand(x[2]) + if _def is _definemacro: + require(x, toplevel, "define-macro only allowed at top level") + proc = eval(exp) + require(x, callable(proc), "macro must be a procedure") + macro_table[v] = proc # (define-macro v proc) + return None # => None; add v:proc to macro_table + return [_define, v, exp] + elif x[0] is _begin: + if len(x)==1: return None # (begin) => None + else: return [expand(xi, toplevel) for xi in x] + elif x[0] is _lambda: # (lambda (x) e1 e2) + require(x, len(x)>=3) # => (lambda (x) (begin e1 e2)) + vars, body = x[1], x[2:] + require(x, (isa(vars, list) and all(isa(v, Symbol) for v in vars)) + or isa(vars, Symbol), "illegal lambda argument list") + exp = body[0] if len(body) == 1 else [_begin] + body + return [_lambda, vars, expand(exp)] + elif x[0] is _quasiquote: # `x => expand_quasiquote(x) + require(x, len(x)==2) + return expand_quasiquote(x[1]) + elif isa(x[0], Symbol) and x[0] in macro_table: + return expand(macro_table[x[0]](*x[1:]), toplevel) # (m arg...) + else: # => macroexpand if m isa macro + return list(map(expand, x)) # (f arg...) => expand each + +def require(x, predicate, msg="wrong length"): + "Signal a syntax error if predicate is false." + if not predicate: raise SyntaxError(to_string(x)+': '+msg) + +_append, _cons, _let = map(Sym, "append cons let".split()) + +def expand_quasiquote(x): + """Expand `x => 'x; `,x => x; `(,@x y) => (append x y) """ + if not is_pair(x): + return [_quote, x] + require(x, x[0] is not _unquotesplicing, "can't splice here") + if x[0] is _unquote: + require(x, len(x)==2) + return x[1] + elif is_pair(x[0]) and x[0][0] is _unquotesplicing: + require(x[0], len(x[0])==2) + return [_append, x[0][1], expand_quasiquote(x[1:])] + else: + return [_cons, expand_quasiquote(x[0]), expand_quasiquote(x[1:])] + +def let(*args): + args = list(args) + x = cons(_let, args) + require(x, len(args)>1) + bindings, body = args[0], args[1:] + require(x, all(isa(b, list) and len(b)==2 and isa(b[0], Symbol) + for b in bindings), "illegal binding list") + vars, vals = zip(*bindings) + return [[_lambda, list(vars)]+list(map(expand, body))] + list(map(expand, vals)) + +macro_table = {_let:let} ## More macros can go here + +eval(parse("""(begin + +(define-macro and (lambda args + (if (null? args) #t + (if (= (length args) 1) (car args) + `(if ,(car args) (and ,@(cdr args)) #f))))) + +;; More macros can also go here + +)""")) + +if __name__ == '__main__': + repl() diff --git a/02-array-seq/lispy/original/lispytest.py b/02-array-seq/lispy/original/lispytest.py new file mode 100644 index 0000000..b324ff3 --- /dev/null +++ b/02-array-seq/lispy/original/lispytest.py @@ -0,0 +1,122 @@ +from __future__ import print_function + +################ Tests for lis.py and lispy.py + +lis_tests = [ + ("(quote (testing 1 (2.0) -3.14e159))", ['testing', 1, [2.0], -3.14e159]), + ("(+ 2 2)", 4), + ("(+ (* 2 100) (* 1 10))", 210), + ("(if (> 6 5) (+ 1 1) (+ 2 2))", 2), + ("(if (< 6 5) (+ 1 1) (+ 2 2))", 4), + ("(define x 3)", None), ("x", 3), ("(+ x x)", 6), + ("((lambda (x) (+ x x)) 5)", 10), + ("(define twice (lambda (x) (* 2 x)))", None), ("(twice 5)", 10), + ("(define compose (lambda (f g) (lambda (x) (f (g x)))))", None), + ("((compose list twice) 5)", [10]), + ("(define repeat (lambda (f) (compose f f)))", None), + ("((repeat twice) 5)", 20), ("((repeat (repeat twice)) 5)", 80), + ("(define fact (lambda (n) (if (<= n 1) 1 (* n (fact (- n 1))))))", None), + ("(fact 3)", 6), + ("(fact 50)", 30414093201713378043612608166064768844377641568960512000000000000), + ("(define abs (lambda (n) ((if (> n 0) + -) 0 n)))", None), + ("(list (abs -3) (abs 0) (abs 3))", [3, 0, 3]), + ("""(define combine (lambda (f) + (lambda (x y) + (if (null? x) (quote ()) + (f (list (car x) (car y)) + ((combine f) (cdr x) (cdr y)))))))""", None), + ("(define zip (combine cons))", None), + ("(zip (list 1 2 3 4) (list 5 6 7 8))", [[1, 5], [2, 6], [3, 7], [4, 8]]), + ("""(define riff-shuffle (lambda (deck) (begin + (define take (lambda (n seq) (if (<= n 0) (quote ()) (cons (car seq) (take (- n 1) (cdr seq)))))) + (define drop (lambda (n seq) (if (<= n 0) seq (drop (- n 1) (cdr seq))))) + (define mid (lambda (seq) (/ (length seq) 2))) + ((combine append) (take (mid deck) deck) (drop (mid deck) deck)))))""", None), + ("(riff-shuffle (list 1 2 3 4 5 6 7 8))", [1, 5, 2, 6, 3, 7, 4, 8]), + ("((repeat riff-shuffle) (list 1 2 3 4 5 6 7 8))", [1, 3, 5, 7, 2, 4, 6, 8]), + ("(riff-shuffle (riff-shuffle (riff-shuffle (list 1 2 3 4 5 6 7 8))))", [1,2,3,4,5,6,7,8]), + ] + +lispy_tests = [ + ("()", SyntaxError), ("(set! x)", SyntaxError), + ("(define 3 4)", SyntaxError), + ("(quote 1 2)", SyntaxError), ("(if 1 2 3 4)", SyntaxError), + ("(lambda 3 3)", SyntaxError), ("(lambda (x))", SyntaxError), + ("""(if (= 1 2) (define-macro a 'a) + (define-macro a 'b))""", SyntaxError), + ("(define (twice x) (* 2 x))", None), ("(twice 2)", 4), + ("(twice 2 2)", TypeError), + ("(define lyst (lambda items items))", None), + ("(lyst 1 2 3 (+ 2 2))", [1,2,3,4]), + ("(if 1 2)", 2), + ("(if (= 3 4) 2)", None), + ("(begin (define x 1) (set! x (+ x 1)) (+ x 1))", 3), + ("(define ((account bal) amt) (set! bal (+ bal amt)) bal)", None), + ("(define a1 (account 100))", None), + ("(a1 0)", 100), ("(a1 10)", 110), ("(a1 10)", 120), + ("""(define (newton guess function derivative epsilon) + (define guess2 (- guess (/ (function guess) (derivative guess)))) + (if (< (abs (- guess guess2)) epsilon) guess2 + (newton guess2 function derivative epsilon)))""", None), + ("""(define (square-root a) + (newton 1 (lambda (x) (- (* x x) a)) (lambda (x) (* 2 x)) 1e-8))""", None), + ("(> (square-root 200.) 14.14213)", True), + ("(< (square-root 200.) 14.14215)", True), + ("(= (square-root 200.) (sqrt 200.))", True), + ("""(define (sum-squares-range start end) + (define (sumsq-acc start end acc) + (if (> start end) acc (sumsq-acc (+ start 1) end (+ (* start start) acc)))) + (sumsq-acc start end 0))""", None), + ("(sum-squares-range 1 3000)", 9004500500), ## Tests tail recursion + ("(call/cc (lambda (throw) (+ 5 (* 10 (throw 1))))) ;; throw", 1), + ("(call/cc (lambda (throw) (+ 5 (* 10 1)))) ;; do not throw", 15), + ("""(call/cc (lambda (throw) + (+ 5 (* 10 (call/cc (lambda (escape) (* 100 (escape 3)))))))) ; 1 level""", 35), + ("""(call/cc (lambda (throw) + (+ 5 (* 10 (call/cc (lambda (escape) (* 100 (throw 3)))))))) ; 2 levels""", 3), + ("""(call/cc (lambda (throw) + (+ 5 (* 10 (call/cc (lambda (escape) (* 100 1))))))) ; 0 levels""", 1005), + ("(* 1i 1i)", -1), ("(sqrt -1)", 1j), + ("(let ((a 1) (b 2)) (+ a b))", 3), + ("(let ((a 1) (b 2 3)) (+ a b))", SyntaxError), + ("(and 1 2 3)", 3), ("(and (> 2 1) 2 3)", 3), ("(and)", True), + ("(and (> 2 1) (> 2 3))", False), + ("(define-macro unless (lambda args `(if (not ,(car args)) (begin ,@(cdr args))))) ; test `", None), + ("(unless (= 2 (+ 1 1)) (display 2) 3 4)", None), + (r'(unless (= 4 (+ 1 1)) (display 2) (display "\n") 3 4)', 4), + ("(quote x)", 'x'), + ("(quote (1 2 three))", [1, 2, 'three']), + ("'x", 'x'), + ("'(one 2 3)", ['one', 2, 3]), + ("(define L (list 1 2 3))", None), + ("`(testing ,@L testing)", ['testing',1,2,3,'testing']), + ("`(testing ,L testing)", ['testing',[1,2,3],'testing']), + ("`,@L", SyntaxError), + ("""'(1 ;test comments ' + ;skip this line + 2 ; more ; comments ; ) ) + 3) ; final comment""", [1,2,3]), + ] + +def test(tests, name=''): + "For each (exp, expected) test case, see if eval(parse(exp)) == expected." + fails = 0 + for (x, expected) in tests: + try: + result = eval(parse(x)) + print(x, '=>', lispstr(result)) + ok = (result == expected) + except Exception as e: + print(x, '=raises=>', type(e).__name__, e) + ok = isinstance(expected, type) and issubclass(expected, Exception) and isinstance(e, expected) + if not ok: + fails += 1 + print('FAIL!!! Expected', expected) + print('%s %s: %d out of %d tests fail.' % ('*'*45, name, fails, len(tests))) + +if __name__ == '__main__': + from lis import * + test(lis_tests, 'lis.py') + from lispy import * + test(lis_tests+lispy_tests, 'lispy.py') + diff --git a/02-array-seq/lispy/examples_test.py b/02-array-seq/lispy/py3.10/examples_test.py similarity index 100% rename from 02-array-seq/lispy/examples_test.py rename to 02-array-seq/lispy/py3.10/examples_test.py diff --git a/02-array-seq/lispy/py3.10/lis.py b/02-array-seq/lispy/py3.10/lis.py new file mode 100644 index 0000000..035481f --- /dev/null +++ b/02-array-seq/lispy/py3.10/lis.py @@ -0,0 +1,188 @@ +################ Lispy: Scheme Interpreter in Python 3.10 + +## (c) Peter Norvig, 2010-18; See http://norvig.com/lispy.html +## Minor edits for Fluent Python, Second Edition (O'Reilly, 2021) +## by Luciano Ramalho, adding type hints and pattern matching. + +################ Imports and Types + +import math +import operator as op +from collections import ChainMap +from collections.abc import MutableMapping, Iterator +from itertools import chain +from typing import Any, TypeAlias + +Symbol: TypeAlias = str +Atom: TypeAlias = float | int | Symbol +Expression: TypeAlias = Atom | list + +Environment: TypeAlias = MutableMapping[Symbol, object] + + +class Procedure: + "A user-defined Scheme procedure." + + def __init__(self, parms: list[Symbol], body: list[Expression], env: Environment): + self.parms = parms + self.body = body + self.env = env + + def __call__(self, *args: Expression) -> Any: + local_env = dict(zip(self.parms, args)) + env: Environment = ChainMap(local_env, self.env) + for exp in self.body: + result = evaluate(exp, env) + return result + + +################ Global Environment + +def standard_env() -> Environment: + "An environment with some Scheme standard procedures." + env: Environment = {} + env.update(vars(math)) # sin, cos, sqrt, pi, ... + env.update({ + '+': op.add, + '-': op.sub, + '*': op.mul, + '/': op.truediv, + '//': op.floordiv, + '>': op.gt, + '<': op.lt, + '>=': op.ge, + '<=': op.le, + '=': op.eq, + 'abs': abs, + 'append': lambda *args: list(chain(*args)), + 'apply': lambda proc, args: proc(*args), + 'begin': lambda *x: x[-1], + 'car': lambda x: x[0], + 'cdr': lambda x: x[1:], + 'cons': lambda x, y: [x] + y, + 'eq?': op.is_, + 'equal?': op.eq, + 'filter': lambda *args: list(filter(*args)), + 'length': len, + 'list': lambda *x: list(x), + 'list?': lambda x: isinstance(x, list), + 'map': lambda *args: list(map(*args)), + 'max': max, + 'min': min, + 'not': op.not_, + 'null?': lambda x: x == [], + 'number?': lambda x: isinstance(x, (int, float)), + 'procedure?': callable, + 'round': round, + 'symbol?': lambda x: isinstance(x, Symbol), + }) + return env + +################ Parsing: parse, tokenize, and read_from_tokens + +def parse(program: str) -> Expression: + "Read a Scheme expression from a string." + return read_from_tokens(tokenize(program)) + + +def tokenize(s: str) -> list[str]: + "Convert a string into a list of tokens." + return s.replace('(', ' ( ').replace(')', ' ) ').split() + + +def read_from_tokens(tokens: list[str]) -> Expression: + "Read an expression from a sequence of tokens." + if len(tokens) == 0: + raise SyntaxError('unexpected EOF while reading') + token = tokens.pop(0) + if '(' == token: + exp = [] + while tokens[0] != ')': + exp.append(read_from_tokens(tokens)) + tokens.pop(0) # discard ')' + return exp + elif ')' == token: + raise SyntaxError('unexpected )') + else: + return parse_atom(token) + + +def parse_atom(token: str) -> Atom: + "Numbers become numbers; every other token is a symbol." + try: + return int(token) + except ValueError: + try: + return float(token) + except ValueError: + return Symbol(token) + + +################ Interaction: A REPL + +def repl(prompt: str = 'lis.py> ') -> None: + "A prompt-read-evaluate-print loop." + global_env = standard_env() + while True: + val = evaluate(parse(input(prompt)), global_env) + if val is not None: + print(lispstr(val)) + + +def lispstr(exp: object) -> str: + "Convert a Python object back into a Lisp-readable string." + if isinstance(exp, list): + return '(' + ' '.join(map(lispstr, exp)) + ')' + else: + return str(exp) + + +################ eval + +# tag::EVALUATE[] +def evaluate(exp: Expression, env: Environment) -> Any: + "Evaluate an expression in an environment." + match exp: + case int(x) | float(x): + return x + case Symbol(var): + return env[var] + case []: + return [] + case ['quote', exp]: + return exp + case ['if', test, consequence, alternative]: + if evaluate(test, env): + return evaluate(consequence, env) + else: + return evaluate(alternative, env) + case ['define', Symbol(var), value_exp]: + env[var] = evaluate(value_exp, env) + case ['define', [Symbol(name), *parms], *body]: + env[name] = Procedure(parms, body, env) + case ['lambda', [*parms], *body]: + return Procedure(parms, body, env) + case [op, *args]: + proc = evaluate(op, env) + values = [evaluate(arg, env) for arg in args] + return proc(*values) + case _: + raise SyntaxError(repr(exp)) +# end::EVALUATE[] + + +################ non-interactive execution + + +def run_lines(source: str) -> Iterator[Any]: + global_env: Environment = standard_env() + tokens = tokenize(source) + while tokens: + exp = read_from_tokens(tokens) + yield evaluate(exp, global_env) + + +def run(source: str) -> Any: + for result in run_lines(source): + pass + return result diff --git a/02-array-seq/lispy/lis_test.py b/02-array-seq/lispy/py3.10/lis_test.py similarity index 97% rename from 02-array-seq/lispy/lis_test.py rename to 02-array-seq/lispy/py3.10/lis_test.py index e220e74..3688888 100644 --- a/02-array-seq/lispy/lis_test.py +++ b/02-array-seq/lispy/py3.10/lis_test.py @@ -12,7 +12,7 @@ ('(sum 1 2 3)', ['sum', 1, 2, 3]), ('(+ (* 2 100) (* 1 10))', ['+', ['*', 2, 100], ['*', 1, 10]]), ('99 100', 99), # parse stops at the first complete expression - ('(a)(b)', ['a']), + ('(a)(b)', ['a']), ]) def test_parse(source: str, expected: Expression) -> None: got = parse(source) @@ -122,7 +122,7 @@ def test_lambda(std_env: Environment) -> None: source = '(lambda (a b) (if (>= a b) a b))' func = evaluate(parse(source), std_env) assert func.parms == ['a', 'b'] - assert func.body == ['if', ['>=', 'a', 'b'], 'a', 'b'] + assert func.body == [['if', ['>=', 'a', 'b'], 'a', 'b']] assert func.env is std_env assert func(1, 2) == 2 assert func(3, 2) == 3 @@ -176,7 +176,7 @@ def test_define_function(std_env: Environment) -> None: assert got is None max_fn = std_env['max'] assert max_fn.parms == ['a', 'b'] - assert max_fn.body == ['if', ['>=', 'a', 'b'], 'a', 'b'] + assert max_fn.body == [['if', ['>=', 'a', 'b'], 'a', 'b']] assert max_fn.env is std_env assert max_fn(1, 2) == 2 assert max_fn(3, 2) == 3 diff --git a/02-array-seq/lispy/py3.10/meta_test.py b/02-array-seq/lispy/py3.10/meta_test.py new file mode 100644 index 0000000..3ddc3f3 --- /dev/null +++ b/02-array-seq/lispy/py3.10/meta_test.py @@ -0,0 +1,69 @@ +""" +Tests for developing a meta-circular interpreter, step-by-step. +""" + + +import operator as op + +from lis import run + +env_scm = """ +(define standard-env (list + (list (quote +) +) + (list (quote -) -) +)) +standard-env +""" + +def test_env_build(): + got = run(env_scm) + assert got == [['+', op.add], ['-', op.sub]] + +scan_scm = """ +(define l (quote (a b c))) +(define (scan what where) + (if (null? where) + () + (if (eq? what (car where)) + what + (scan what (cdr where)))) +) +""" + +def test_scan(): + source = scan_scm + '(scan (quote a) l )' + got = run(source) + assert got == 'a' + + +def test_scan_not_found(): + source = scan_scm + '(scan (quote z) l )' + got = run(source) + assert got == [] + + +lookup_scm = """ +(define env (list + (list (quote +) +) + (list (quote -) -) +)) +(define (lookup what where) + (if (null? where) + () + (if (eq? what (car (car where))) + (car (cdr (car where))) + (lookup what (cdr where)))) +) +""" + +def test_lookup(): + source = lookup_scm + '(lookup (quote +) env)' + got = run(source) + assert got == op.add + + +def test_lookup_not_found(): + source = lookup_scm + '(lookup (quote z) env )' + got = run(source) + assert got == [] + diff --git a/02-array-seq/lispy/py3.9-no-hints/README.md b/02-array-seq/lispy/py3.9-no-hints/README.md new file mode 100644 index 0000000..db6a11b --- /dev/null +++ b/02-array-seq/lispy/py3.9-no-hints/README.md @@ -0,0 +1,33 @@ +# Changes from the original + +While adapting Peter Norvig's [lis.py](https://github.com/norvig/pytudes/blob/705c0a335c1811a203e79587d7d41865cf7f41c7/py/lis.py) for +use in _Fluent Python, Second Edition_, I made a few changes for didactic reasons. + +_Luciano Ramalho_ + +## Major changes + +* Make the `lambda` form accept more than one expression as the body. This is consistent with _Scheme_ syntax, and provides a useful example for the book. To implement this: + * In `Procedure.__call__`: evaluate `self.body` as a list of expressions, instead of a single expression. Return the value of the last expression. + * In `evaluate()`: when processing `lambda`, unpack expression into `(_, parms, *body)`, to accept a list of expressions as the body. +* Remove the `global_env` global `dict`. It is only used as a default value for the `env` parameter in `evaluate()`, but it is unsafe to use mutable data structures as parameter default values. To implement this: + * In `repl()`: create local variable `global_env` and pass it as the `env` paramater of `evaluate()`. + * In `evaluate()`, remove `global_env` default value for `env`. +* Rewrite the custom test script +[lispytest.py](https://github.com/norvig/pytudes/blob/705c0a335c1811a203e79587d7d41865cf7f41c7/py/lispytest.py) as +[lis_test.py](https://github.com/fluentpython/example-code-2e/blob/master/02-array-seq/lispy/py3.9/lis_test.py): +a standard [pytest](https://docs.pytest.org) test suite including new test cases, preserving all Norvig's the test cases for +[lis.py](https://github.com/norvig/pytudes/blob/705c0a335c1811a203e79587d7d41865cf7f41c7/py/lis.py) +but removing the test cases for the features implemented only in +[lispy.py](https://github.com/norvig/pytudes/blob/705c0a335c1811a203e79587d7d41865cf7f41c7/py/lispy.py) + + +## Minor changes + +Cosmetic changes to make the code look more familiar to +Python programmers, the audience of _Fluent Python_. + +* Rename `eval()` to `evaluate()`, to avoid confusion with Python's `eval` built-in function. +* Refer to the list class as `list` instead of aliasing as `List`, to avoid confusion with `typing.List` which is often imported as `List`. +* Import `collections.ChainMap` as `ChainMap` instead of `Environment`. + diff --git a/02-array-seq/lispy/py3.9-no-hints/lis.py b/02-array-seq/lispy/py3.9-no-hints/lis.py new file mode 100644 index 0000000..c74c3ae --- /dev/null +++ b/02-array-seq/lispy/py3.9-no-hints/lis.py @@ -0,0 +1,142 @@ +################ Lispy: Scheme Interpreter in Python 3.9 + +## (c) Peter Norvig, 2010-18; See http://norvig.com/lispy.html +## Minor edits for Fluent Python, Second Edition (O'Reilly, 2021) +## by Luciano Ramalho, adding type hints and pattern matching. + +################ Imports and Types + +import math +import operator as op +from collections import ChainMap +from typing import Any + +Symbol = str # A Lisp Symbol is implemented as a Python str +Number = (int, float) # A Lisp Number is implemented as a Python int or float + +class Procedure: + "A user-defined Scheme procedure." + def __init__(self, parms, body, env): + self.parms, self.body, self.env = parms, body, env + def __call__(self, *args): + env = ChainMap(dict(zip(self.parms, args)), self.env) + for exp in self.body: + result = evaluate(exp, env) + return result + + +################ Global Environment + +def standard_env(): + "An environment with some Scheme standard procedures." + env = {} + env.update(vars(math)) # sin, cos, sqrt, pi, ... + env.update({ + '+':op.add, '-':op.sub, '*':op.mul, '/':op.truediv, + '>':op.gt, '<':op.lt, '>=':op.ge, '<=':op.le, '=':op.eq, + 'abs': abs, + 'append': op.add, + 'apply': lambda proc, args: proc(*args), + 'begin': lambda *x: x[-1], + 'car': lambda x: x[0], + 'cdr': lambda x: x[1:], + 'cons': lambda x,y: [x] + y, + 'eq?': op.is_, + 'equal?': op.eq, + 'length': len, + 'list': lambda *x: list(x), + 'list?': lambda x: isinstance(x,list), + 'map': lambda *args: list(map(*args)), + 'max': max, + 'min': min, + 'not': op.not_, + 'null?': lambda x: x == [], + 'number?': lambda x: isinstance(x, Number), + 'procedure?': callable, + 'round': round, + 'symbol?': lambda x: isinstance(x, Symbol), + }) + return env + +################ Parsing: parse, tokenize, and read_from_tokens + +def parse(program): + "Read a Scheme expression from a string." + return read_from_tokens(tokenize(program)) + +def tokenize(s): + "Convert a string into a list of tokens." + return s.replace('(',' ( ').replace(')',' ) ').split() + +def read_from_tokens(tokens): + "Read an expression from a sequence of tokens." + if len(tokens) == 0: + raise SyntaxError('unexpected EOF while reading') + token = tokens.pop(0) + if '(' == token: + exp = [] + while tokens[0] != ')': + exp.append(read_from_tokens(tokens)) + tokens.pop(0) # discard ')' + return exp + elif ')' == token: + raise SyntaxError('unexpected )') + else: + return parse_atom(token) + + +def parse_atom(token: str): + "Numbers become numbers; every other token is a symbol." + try: + return int(token) + except ValueError: + try: + return float(token) + except ValueError: + return Symbol(token) + + +################ Interaction: A REPL + +def repl(prompt: str = 'lis.py> ') -> None: + "A prompt-read-eval-print loop." + global_env = standard_env() + while True: + val = evaluate(parse(input(prompt)), global_env) + if val is not None: + print(lispstr(val)) + + +def lispstr(exp): + "Convert a Python object back into a Lisp-readable string." + if isinstance(exp, list): + return '(' + ' '.join(map(lispstr, exp)) + ')' + else: + return str(exp) + + +################ eval + +def evaluate(x, env): + "Evaluate an expression in an environment." + if isinstance(x, Symbol): # variable reference + return env[x] + elif not isinstance(x, list): # constant literal + return x + elif x[0] == 'quote': # (quote exp) + (_, exp) = x + return exp + elif x[0] == 'if': # (if test conseq alt) + (_, test, conseq, alt) = x + exp = (conseq if evaluate(test, env) else alt) + return evaluate(exp, env) + elif x[0] == 'define': # (define var exp) + (_, var, exp) = x + env[var] = evaluate(exp, env) + elif x[0] == 'lambda': # (lambda (var...) body) + (_, parms, *body) = x + return Procedure(parms, body, env) + else: # (proc arg...) + proc = evaluate(x[0], env) + args = [evaluate(exp, env) for exp in x[1:]] + return proc(*args) diff --git a/02-array-seq/lispy/py3.9-no-hints/lis_test.py b/02-array-seq/lispy/py3.9-no-hints/lis_test.py new file mode 100644 index 0000000..f5f4f8e --- /dev/null +++ b/02-array-seq/lispy/py3.9-no-hints/lis_test.py @@ -0,0 +1,166 @@ +from pytest import mark, fixture + +from lis import parse, evaluate, standard_env + +############################################################# tests for parse + +@mark.parametrize( 'source, expected', [ + ('7', 7), + ('x', 'x'), + ('(sum 1 2 3)', ['sum', 1, 2, 3]), + ('(+ (* 2 100) (* 1 10))', ['+', ['*', 2, 100], ['*', 1, 10]]), + ('99 100', 99), # parse stops at the first complete expression + ('(a)(b)', ['a']), +]) +def test_parse(source: str, expected): + got = parse(source) + assert got == expected + + +########################################################## tests for evaluate + +# Norvig's tests are not isolated: they assume the +# same environment from first to last test. +global_env_for_first_test = standard_env() + +@mark.parametrize( 'source, expected', [ + ("(quote (testing 1 (2.0) -3.14e159))", ['testing', 1, [2.0], -3.14e159]), + ("(+ 2 2)", 4), + ("(+ (* 2 100) (* 1 10))", 210), + ("(if (> 6 5) (+ 1 1) (+ 2 2))", 2), + ("(if (< 6 5) (+ 1 1) (+ 2 2))", 4), + ("(define x 3)", None), + ("x", 3), + ("(+ x x)", 6), + ("((lambda (x) (+ x x)) 5)", 10), + ("(define twice (lambda (x) (* 2 x)))", None), + ("(twice 5)", 10), + ("(define compose (lambda (f g) (lambda (x) (f (g x)))))", None), + ("((compose list twice) 5)", [10]), + ("(define repeat (lambda (f) (compose f f)))", None), + ("((repeat twice) 5)", 20), + ("((repeat (repeat twice)) 5)", 80), + ("(define fact (lambda (n) (if (<= n 1) 1 (* n (fact (- n 1))))))", None), + ("(fact 3)", 6), + ("(fact 50)", 30414093201713378043612608166064768844377641568960512000000000000), + ("(define abs (lambda (n) ((if (> n 0) + -) 0 n)))", None), + ("(list (abs -3) (abs 0) (abs 3))", [3, 0, 3]), + ("""(define combine (lambda (f) + (lambda (x y) + (if (null? x) (quote ()) + (f (list (car x) (car y)) + ((combine f) (cdr x) (cdr y)))))))""", None), + ("(define zip (combine cons))", None), + ("(zip (list 1 2 3 4) (list 5 6 7 8))", [[1, 5], [2, 6], [3, 7], [4, 8]]), + ("""(define riff-shuffle (lambda (deck) + (begin + (define take (lambda (n seq) (if (<= n 0) (quote ()) (cons (car seq) (take (- n 1) (cdr seq)))))) + (define drop (lambda (n seq) (if (<= n 0) seq (drop (- n 1) (cdr seq))))) + (define mid (lambda (seq) (/ (length seq) 2))) + ((combine append) (take (mid deck) deck) (drop (mid deck) deck)))))""", None), + ("(riff-shuffle (list 1 2 3 4 5 6 7 8))", [1, 5, 2, 6, 3, 7, 4, 8]), + ("((repeat riff-shuffle) (list 1 2 3 4 5 6 7 8))", [1, 3, 5, 7, 2, 4, 6, 8]), + ("(riff-shuffle (riff-shuffle (riff-shuffle (list 1 2 3 4 5 6 7 8))))", [1,2,3,4,5,6,7,8]), +]) +def test_evaluate(source, expected): + got = evaluate(parse(source), global_env_for_first_test) + assert got == expected + + +@fixture +def std_env(): + return standard_env() + +# tests for each of the cases in evaluate + +def test_evaluate_variable(): + env = dict(x=10) + source = 'x' + expected = 10 + got = evaluate(parse(source), env) + assert got == expected + + +def test_evaluate_literal(std_env): + source = '3.3' + expected = 3.3 + got = evaluate(parse(source), std_env) + assert got == expected + + +def test_evaluate_quote(std_env): + source = '(quote (1.1 is not 1))' + expected = [1.1, 'is', 'not', 1] + got = evaluate(parse(source), std_env) + assert got == expected + + +def test_evaluate_if_true(std_env) -> None: + source = '(if 1 10 no-such-thing)' + expected = 10 + got = evaluate(parse(source), std_env) + assert got == expected + + +def test_evaluate_if_false(std_env) -> None: + source = '(if 0 no-such-thing 20)' + expected = 20 + got = evaluate(parse(source), std_env) + assert got == expected + + +def test_define(std_env) -> None: + source = '(define answer (* 6 7))' + got = evaluate(parse(source), std_env) + assert got is None + assert std_env['answer'] == 42 + + +def test_lambda(std_env) -> None: + source = '(lambda (a b) (if (>= a b) a b))' + func = evaluate(parse(source), std_env) + assert func.parms == ['a', 'b'] + assert func.body == [['if', ['>=', 'a', 'b'], 'a', 'b']] + assert func.env is std_env + assert func(1, 2) == 2 + assert func(3, 2) == 3 + + +def test_begin(std_env) -> None: + source = """ + (begin + (define x (* 2 3)) + (* x 7) + ) + """ + got = evaluate(parse(source), std_env) + assert got == 42 + + +def test_invocation_builtin_car(std_env) -> None: + source = '(car (quote (11 22 33)))' + got = evaluate(parse(source), std_env) + assert got == 11 + + +def test_invocation_builtin_append(std_env) -> None: + source = '(append (quote (a b)) (quote (c d)))' + got = evaluate(parse(source), std_env) + assert got == ['a', 'b', 'c', 'd'] + + +def test_invocation_builtin_map(std_env) -> None: + source = '(map (lambda (x) (* x 2)) (quote (1 2 3))))' + got = evaluate(parse(source), std_env) + assert got == [2, 4, 6] + + +def test_invocation_user_procedure(std_env): + source = """ + (begin + (define max (lambda (a b) (if (>= a b) a b))) + (max 22 11) + ) + """ + got = evaluate(parse(source), std_env) + assert got == 22 diff --git a/02-array-seq/lispy/py3.9/README.md b/02-array-seq/lispy/py3.9/README.md new file mode 100644 index 0000000..db6a11b --- /dev/null +++ b/02-array-seq/lispy/py3.9/README.md @@ -0,0 +1,33 @@ +# Changes from the original + +While adapting Peter Norvig's [lis.py](https://github.com/norvig/pytudes/blob/705c0a335c1811a203e79587d7d41865cf7f41c7/py/lis.py) for +use in _Fluent Python, Second Edition_, I made a few changes for didactic reasons. + +_Luciano Ramalho_ + +## Major changes + +* Make the `lambda` form accept more than one expression as the body. This is consistent with _Scheme_ syntax, and provides a useful example for the book. To implement this: + * In `Procedure.__call__`: evaluate `self.body` as a list of expressions, instead of a single expression. Return the value of the last expression. + * In `evaluate()`: when processing `lambda`, unpack expression into `(_, parms, *body)`, to accept a list of expressions as the body. +* Remove the `global_env` global `dict`. It is only used as a default value for the `env` parameter in `evaluate()`, but it is unsafe to use mutable data structures as parameter default values. To implement this: + * In `repl()`: create local variable `global_env` and pass it as the `env` paramater of `evaluate()`. + * In `evaluate()`, remove `global_env` default value for `env`. +* Rewrite the custom test script +[lispytest.py](https://github.com/norvig/pytudes/blob/705c0a335c1811a203e79587d7d41865cf7f41c7/py/lispytest.py) as +[lis_test.py](https://github.com/fluentpython/example-code-2e/blob/master/02-array-seq/lispy/py3.9/lis_test.py): +a standard [pytest](https://docs.pytest.org) test suite including new test cases, preserving all Norvig's the test cases for +[lis.py](https://github.com/norvig/pytudes/blob/705c0a335c1811a203e79587d7d41865cf7f41c7/py/lis.py) +but removing the test cases for the features implemented only in +[lispy.py](https://github.com/norvig/pytudes/blob/705c0a335c1811a203e79587d7d41865cf7f41c7/py/lispy.py) + + +## Minor changes + +Cosmetic changes to make the code look more familiar to +Python programmers, the audience of _Fluent Python_. + +* Rename `eval()` to `evaluate()`, to avoid confusion with Python's `eval` built-in function. +* Refer to the list class as `list` instead of aliasing as `List`, to avoid confusion with `typing.List` which is often imported as `List`. +* Import `collections.ChainMap` as `ChainMap` instead of `Environment`. + diff --git a/02-array-seq/lispy/py3.9/lis.py b/02-array-seq/lispy/py3.9/lis.py new file mode 100644 index 0000000..11f4402 --- /dev/null +++ b/02-array-seq/lispy/py3.9/lis.py @@ -0,0 +1,155 @@ +################ Lispy: Scheme Interpreter in Python 3.9 + +## (c) Peter Norvig, 2010-18; See http://norvig.com/lispy.html +## Minor edits for Fluent Python, Second Edition (O'Reilly, 2021) +## by Luciano Ramalho, adding type hints and pattern matching. + +################ Imports and Types + +import math +import operator as op +from collections import ChainMap +from collections.abc import MutableMapping, Iterator +from itertools import chain +from typing import Any, Union + +Symbol = str +Atom = Union[float, int, Symbol] +Expression = Union[Atom, list] + +Environment = MutableMapping[Symbol, object] + + +class Procedure: + "A user-defined Scheme procedure." + + def __init__(self, parms: list[Symbol], body: list[Expression], env: Environment): + self.parms = parms + self.body = body + self.env = env + + def __call__(self, *args: Expression) -> Any: + local_env = dict(zip(self.parms, args)) + env: Environment = ChainMap(local_env, self.env) + for exp in self.body: + result = evaluate(exp, env) + return result + + +################ Global Environment + +def standard_env() -> Environment: + "An environment with some Scheme standard procedures." + env: Environment = {} + env.update(vars(math)) # sin, cos, sqrt, pi, ... + env.update({ + '+':op.add, '-':op.sub, '*':op.mul, '/':op.truediv, + '>':op.gt, '<':op.lt, '>=':op.ge, '<=':op.le, '=':op.eq, + 'abs': abs, + 'append': op.add, + 'apply': lambda proc, args: proc(*args), + 'begin': lambda *x: x[-1], + 'car': lambda x: x[0], + 'cdr': lambda x: x[1:], + 'cons': lambda x,y: [x] + y, + 'eq?': op.is_, + 'equal?': op.eq, + 'length': len, + 'list': lambda *x: list(x), + 'list?': lambda x: isinstance(x,list), + 'map': lambda *args: list(map(*args)), + 'max': max, + 'min': min, + 'not': op.not_, + 'null?': lambda x: x == [], + 'number?': lambda x: isinstance(x, (int, float)), + 'procedure?': callable, + 'round': round, + 'symbol?': lambda x: isinstance(x, Symbol), + }) + return env + +################ Parsing: parse, tokenize, and read_from_tokens + +def parse(program: str) -> Expression: + "Read a Scheme expression from a string." + return read_from_tokens(tokenize(program)) + + +def tokenize(s: str) -> list[str]: + "Convert a string into a list of tokens." + return s.replace('(', ' ( ').replace(')', ' ) ').split() + + +def read_from_tokens(tokens: list[str]) -> Expression: + "Read an expression from a sequence of tokens." + if len(tokens) == 0: + raise SyntaxError('unexpected EOF while reading') + token = tokens.pop(0) + if '(' == token: + exp = [] + while tokens[0] != ')': + exp.append(read_from_tokens(tokens)) + tokens.pop(0) # discard ')' + return exp + elif ')' == token: + raise SyntaxError('unexpected )') + else: + return parse_atom(token) + + +def parse_atom(token: str) -> Atom: + "Numbers become numbers; every other token is a symbol." + try: + return int(token) + except ValueError: + try: + return float(token) + except ValueError: + return Symbol(token) + + +################ Interaction: A REPL + +def repl(prompt: str = 'lis.py> ') -> None: + "A prompt-read-eval-print loop." + global_env = standard_env() + while True: + val = evaluate(parse(input(prompt)), global_env) + if val is not None: + print(lispstr(val)) + + +def lispstr(exp: object) -> str: + "Convert a Python object back into a Lisp-readable string." + if isinstance(exp, list): + return '(' + ' '.join(map(lispstr, exp)) + ')' + else: + return str(exp) + + +################ eval + +def evaluate(x: Expression, env: Environment) -> Any: + "Evaluate an expression in an environment." + if isinstance(x, Symbol): # variable reference + return env[x] + elif not isinstance(x, list): # constant literal + return x + elif x[0] == 'quote': # (quote exp) + (_, exp) = x + return exp + elif x[0] == 'if': # (if test conseq alt) + (_, test, conseq, alt) = x + exp = (conseq if evaluate(test, env) else alt) + return evaluate(exp, env) + elif x[0] == 'define': # (define var exp) + (_, var, exp) = x + env[var] = evaluate(exp, env) + elif x[0] == 'lambda': # (lambda (var...) body) + (_, parms, *body) = x + return Procedure(parms, body, env) + else: # (proc arg...) + proc = evaluate(x[0], env) + args = [evaluate(exp, env) for exp in x[1:]] + return proc(*args) diff --git a/02-array-seq/lispy/py3.9/lis_test.py b/02-array-seq/lispy/py3.9/lis_test.py new file mode 100644 index 0000000..106ac24 --- /dev/null +++ b/02-array-seq/lispy/py3.9/lis_test.py @@ -0,0 +1,168 @@ +from typing import Any, Optional + +from pytest import mark, fixture + +from lis import parse, evaluate, standard_env, Symbol, Environment, Expression + +############################################################# tests for parse + +@mark.parametrize( 'source, expected', [ + ('7', 7), + ('x', 'x'), + ('(sum 1 2 3)', ['sum', 1, 2, 3]), + ('(+ (* 2 100) (* 1 10))', ['+', ['*', 2, 100], ['*', 1, 10]]), + ('99 100', 99), # parse stops at the first complete expression + ('(a)(b)', ['a']), +]) +def test_parse(source: str, expected: Expression) -> None: + got = parse(source) + assert got == expected + + +########################################################## tests for evaluate + +# Norvig's tests are not isolated: they assume the +# same environment from first to last test. +global_env_for_first_test = standard_env() + +@mark.parametrize( 'source, expected', [ + ("(quote (testing 1 (2.0) -3.14e159))", ['testing', 1, [2.0], -3.14e159]), + ("(+ 2 2)", 4), + ("(+ (* 2 100) (* 1 10))", 210), + ("(if (> 6 5) (+ 1 1) (+ 2 2))", 2), + ("(if (< 6 5) (+ 1 1) (+ 2 2))", 4), + ("(define x 3)", None), + ("x", 3), + ("(+ x x)", 6), + ("((lambda (x) (+ x x)) 5)", 10), + ("(define twice (lambda (x) (* 2 x)))", None), + ("(twice 5)", 10), + ("(define compose (lambda (f g) (lambda (x) (f (g x)))))", None), + ("((compose list twice) 5)", [10]), + ("(define repeat (lambda (f) (compose f f)))", None), + ("((repeat twice) 5)", 20), + ("((repeat (repeat twice)) 5)", 80), + ("(define fact (lambda (n) (if (<= n 1) 1 (* n (fact (- n 1))))))", None), + ("(fact 3)", 6), + ("(fact 50)", 30414093201713378043612608166064768844377641568960512000000000000), + ("(define abs (lambda (n) ((if (> n 0) + -) 0 n)))", None), + ("(list (abs -3) (abs 0) (abs 3))", [3, 0, 3]), + ("""(define combine (lambda (f) + (lambda (x y) + (if (null? x) (quote ()) + (f (list (car x) (car y)) + ((combine f) (cdr x) (cdr y)))))))""", None), + ("(define zip (combine cons))", None), + ("(zip (list 1 2 3 4) (list 5 6 7 8))", [[1, 5], [2, 6], [3, 7], [4, 8]]), + ("""(define riff-shuffle (lambda (deck) + (begin + (define take (lambda (n seq) (if (<= n 0) (quote ()) (cons (car seq) (take (- n 1) (cdr seq)))))) + (define drop (lambda (n seq) (if (<= n 0) seq (drop (- n 1) (cdr seq))))) + (define mid (lambda (seq) (/ (length seq) 2))) + ((combine append) (take (mid deck) deck) (drop (mid deck) deck)))))""", None), + ("(riff-shuffle (list 1 2 3 4 5 6 7 8))", [1, 5, 2, 6, 3, 7, 4, 8]), + ("((repeat riff-shuffle) (list 1 2 3 4 5 6 7 8))", [1, 3, 5, 7, 2, 4, 6, 8]), + ("(riff-shuffle (riff-shuffle (riff-shuffle (list 1 2 3 4 5 6 7 8))))", [1,2,3,4,5,6,7,8]), +]) +def test_evaluate(source: str, expected: Optional[Expression]) -> None: + got = evaluate(parse(source), global_env_for_first_test) + assert got == expected + + +@fixture +def std_env() -> Environment: + return standard_env() + +# tests for each of the cases in evaluate + +def test_evaluate_variable() -> None: + env: Environment = dict(x=10) + source = 'x' + expected = 10 + got = evaluate(parse(source), env) + assert got == expected + + +def test_evaluate_literal(std_env: Environment) -> None: + source = '3.3' + expected = 3.3 + got = evaluate(parse(source), std_env) + assert got == expected + + +def test_evaluate_quote(std_env: Environment) -> None: + source = '(quote (1.1 is not 1))' + expected = [1.1, 'is', 'not', 1] + got = evaluate(parse(source), std_env) + assert got == expected + + +def test_evaluate_if_true(std_env: Environment) -> None: + source = '(if 1 10 no-such-thing)' + expected = 10 + got = evaluate(parse(source), std_env) + assert got == expected + + +def test_evaluate_if_false(std_env: Environment) -> None: + source = '(if 0 no-such-thing 20)' + expected = 20 + got = evaluate(parse(source), std_env) + assert got == expected + + +def test_define(std_env: Environment) -> None: + source = '(define answer (* 6 7))' + got = evaluate(parse(source), std_env) + assert got is None + assert std_env['answer'] == 42 + + +def test_lambda(std_env: Environment) -> None: + source = '(lambda (a b) (if (>= a b) a b))' + func = evaluate(parse(source), std_env) + assert func.parms == ['a', 'b'] + assert func.body == [['if', ['>=', 'a', 'b'], 'a', 'b']] + assert func.env is std_env + assert func(1, 2) == 2 + assert func(3, 2) == 3 + + +def test_begin(std_env: Environment) -> None: + source = """ + (begin + (define x (* 2 3)) + (* x 7) + ) + """ + got = evaluate(parse(source), std_env) + assert got == 42 + + +def test_invocation_builtin_car(std_env: Environment) -> None: + source = '(car (quote (11 22 33)))' + got = evaluate(parse(source), std_env) + assert got == 11 + + +def test_invocation_builtin_append(std_env: Environment) -> None: + source = '(append (quote (a b)) (quote (c d)))' + got = evaluate(parse(source), std_env) + assert got == ['a', 'b', 'c', 'd'] + + +def test_invocation_builtin_map(std_env: Environment) -> None: + source = '(map (lambda (x) (* x 2)) (quote (1 2 3))))' + got = evaluate(parse(source), std_env) + assert got == [2, 4, 6] + + +def test_invocation_user_procedure(std_env: Environment) -> None: + source = """ + (begin + (define max (lambda (a b) (if (>= a b) a b))) + (max 22 11) + ) + """ + got = evaluate(parse(source), std_env) + assert got == 22 diff --git a/02-array-seq/match_lat_lon.py b/02-array-seq/match_lat_lon.py index 592aa70..20c7131 100644 --- a/02-array-seq/match_lat_lon.py +++ b/02-array-seq/match_lat_lon.py @@ -7,7 +7,7 @@ | latitude | longitude Mexico City | 19.4333 | -99.1333 New York-Newark | 40.8086 | -74.0204 - Sao Paulo | -23.5478 | -46.6358 + São Paulo | -23.5478 | -46.6358 """ @@ -17,7 +17,7 @@ ('Delhi NCR', 'IN', 21.935, (28.613889, 77.208889)), ('Mexico City', 'MX', 20.142, (19.433333, -99.133333)), ('New York-Newark', 'US', 20.104, (40.808611, -74.020386)), - ('Sao Paulo', 'BR', 19.649, (-23.547778, -46.635833)), + ('São Paulo', 'BR', 19.649, (-23.547778, -46.635833)), ] def main(): diff --git a/02-array-seq/metro_lat_lon.py b/02-array-seq/metro_lat_lon.py index 930a993..899bcb8 100644 --- a/02-array-seq/metro_lat_lon.py +++ b/02-array-seq/metro_lat_lon.py @@ -1,5 +1,5 @@ """ -metro_lat_long.py +metro_lat_lon.py Demonstration of nested tuple unpacking:: @@ -7,16 +7,17 @@ | latitude | longitude Mexico City | 19.4333 | -99.1333 New York-Newark | 40.8086 | -74.0204 - Sao Paulo | -23.5478 | -46.6358 + São Paulo | -23.5478 | -46.6358 """ +# tag::MAIN[] metro_areas = [ ('Tokyo', 'JP', 36.933, (35.689722, 139.691667)), # <1> ('Delhi NCR', 'IN', 21.935, (28.613889, 77.208889)), ('Mexico City', 'MX', 20.142, (19.433333, -99.133333)), ('New York-Newark', 'US', 20.104, (40.808611, -74.020386)), - ('Sao Paulo', 'BR', 19.649, (-23.547778, -46.635833)), + ('São Paulo', 'BR', 19.649, (-23.547778, -46.635833)), ] def main(): @@ -27,3 +28,4 @@ def main(): if __name__ == '__main__': main() +# end::MAIN[] \ No newline at end of file diff --git a/03-dict-set/py3.10/creator.py b/03-dict-set/py3.10/creator.py new file mode 100644 index 0000000..97d6bde --- /dev/null +++ b/03-dict-set/py3.10/creator.py @@ -0,0 +1,40 @@ +""" +Pattern matching with mapping—requires Python ≥ 3.10 + +# tag::DICT_MATCH_TEST[] +>>> b1 = dict(api=1, author='Douglas Hofstadter', +... type='book', title='Gödel, Escher, Bach') +>>> get_creators(b1) +['Douglas Hofstadter'] +>>> from collections import OrderedDict +>>> b2 = OrderedDict(api=2, type='book', +... title='Python in a Nutshell', +... authors='Martelli Ravenscroft Holden'.split()) +>>> get_creators(b2) +['Martelli', 'Ravenscroft', 'Holden'] +>>> get_creators({'type': 'book', 'pages': 770}) +Traceback (most recent call last): + ... +ValueError: Invalid 'book' record: {'type': 'book', 'pages': 770} +>>> get_creators('Spam, spam, spam') +Traceback (most recent call last): + ... +ValueError: Invalid record: 'Spam, spam, spam' + +# end::DICT_MATCH_TEST[] +""" + +# tag::DICT_MATCH[] +def get_creators(record: dict) -> list: + match record: + case {'type': 'book', 'api': 2, 'authors': [*names]}: # <1> + return names + case {'type': 'book', 'api': 1, 'author': name}: # <2> + return [name] + case {'type': 'book'}: # <3> + raise ValueError(f"Invalid 'book' record: {record!r}") + case {'type': 'movie', 'director': name}: # <4> + return [name] + case _: # <5> + raise ValueError(f'Invalid record: {record!r}') +# end::DICT_MATCH[] diff --git a/04-text-byte/categories.py b/04-text-byte/categories.py index 11cc28e..ae8dedf 100644 --- a/04-text-byte/categories.py +++ b/04-text-byte/categories.py @@ -34,11 +34,11 @@ def main(args): print(count, 'characters shown') else: counts, firsts = category_stats() - for cat, count in counts.most_common(): + for i, (cat, count) in enumerate(counts.most_common(), 1): first = firsts[cat] if cat == 'Cs': first = f'(surrogate U+{ord(first):04X})' - print(f'{count:6} {cat} {first}') + print(f'{i:2} {count:6} {cat} {first}') if __name__ == '__main__': diff --git a/04-text-byte/stdout_check.py b/04-text-byte/stdout_check.py index dcef224..c20139f 100644 --- a/04-text-byte/stdout_check.py +++ b/04-text-byte/stdout_check.py @@ -8,9 +8,9 @@ print() test_chars = [ - '\u2026', # HORIZONTAL ELLIPSIS (in cp1252) - '\u221E', # INFINITY (in cp437) - '\u32B7', # CIRCLED NUMBER FORTY TWO + '\N{HORIZONTAL ELLIPSIS}', # exists in cp1252, not in cp437 + '\N{INFINITY}', # exists in cp437, not in cp1252 + '\N{CIRCLED NUMBER FORTY TWO}', # not in cp437 or in cp1252 ] for char in test_chars: diff --git a/05-record-like/README.asciidoc b/05-data-classes/README.asciidoc similarity index 100% rename from 05-record-like/README.asciidoc rename to 05-data-classes/README.asciidoc diff --git a/05-record-like/cards.doctest b/05-data-classes/cards.doctest similarity index 100% rename from 05-record-like/cards.doctest rename to 05-data-classes/cards.doctest diff --git a/05-record-like/cards.py b/05-data-classes/cards.py similarity index 100% rename from 05-record-like/cards.py rename to 05-data-classes/cards.py diff --git a/05-record-like/cards_enum.py b/05-data-classes/cards_enum.py similarity index 100% rename from 05-record-like/cards_enum.py rename to 05-data-classes/cards_enum.py diff --git a/05-record-like/class/coordinates.py b/05-data-classes/class/coordinates.py similarity index 94% rename from 05-record-like/class/coordinates.py rename to 05-data-classes/class/coordinates.py index 683a97a..84dcd1d 100644 --- a/05-record-like/class/coordinates.py +++ b/05-data-classes/class/coordinates.py @@ -13,4 +13,4 @@ def __init__(self, lat, lon): self.lat = lat self.lon = lon -# end::COORDINATE[] +# end::COORDINATE[] \ No newline at end of file diff --git a/05-record-like/dataclass/club.py b/05-data-classes/dataclass/club.py similarity index 100% rename from 05-record-like/dataclass/club.py rename to 05-data-classes/dataclass/club.py diff --git a/05-record-like/dataclass/club_generic.py b/05-data-classes/dataclass/club_generic.py similarity index 100% rename from 05-record-like/dataclass/club_generic.py rename to 05-data-classes/dataclass/club_generic.py diff --git a/05-record-like/dataclass/club_wrong.py b/05-data-classes/dataclass/club_wrong.py similarity index 100% rename from 05-record-like/dataclass/club_wrong.py rename to 05-data-classes/dataclass/club_wrong.py diff --git a/05-record-like/dataclass/coordinates.py b/05-data-classes/dataclass/coordinates.py similarity index 100% rename from 05-record-like/dataclass/coordinates.py rename to 05-data-classes/dataclass/coordinates.py diff --git a/05-record-like/dataclass/hackerclub.py b/05-data-classes/dataclass/hackerclub.py similarity index 100% rename from 05-record-like/dataclass/hackerclub.py rename to 05-data-classes/dataclass/hackerclub.py diff --git a/05-record-like/dataclass/hackerclub_annotated.py b/05-data-classes/dataclass/hackerclub_annotated.py similarity index 100% rename from 05-record-like/dataclass/hackerclub_annotated.py rename to 05-data-classes/dataclass/hackerclub_annotated.py diff --git a/05-record-like/dataclass/resource.py b/05-data-classes/dataclass/resource.py similarity index 100% rename from 05-record-like/dataclass/resource.py rename to 05-data-classes/dataclass/resource.py diff --git a/05-record-like/dataclass/resource_repr.py b/05-data-classes/dataclass/resource_repr.py similarity index 100% rename from 05-record-like/dataclass/resource_repr.py rename to 05-data-classes/dataclass/resource_repr.py diff --git a/05-record-like/frenchdeck.doctest b/05-data-classes/frenchdeck.doctest similarity index 100% rename from 05-record-like/frenchdeck.doctest rename to 05-data-classes/frenchdeck.doctest diff --git a/05-record-like/frenchdeck.py b/05-data-classes/frenchdeck.py similarity index 100% rename from 05-record-like/frenchdeck.py rename to 05-data-classes/frenchdeck.py diff --git a/05-data-classes/match_cities.py b/05-data-classes/match_cities.py new file mode 100644 index 0000000..e795c50 --- /dev/null +++ b/05-data-classes/match_cities.py @@ -0,0 +1,92 @@ +""" +match_cities.py +""" + +# tag::CITY[] +import typing + +class City(typing.NamedTuple): + continent: str + name: str + country: str + + +cities = [ + City('Asia', 'Tokyo', 'JP'), + City('Asia', 'Delhi', 'IN'), + City('North America', 'Mexico City', 'MX'), + City('North America', 'New York', 'US'), + City('South America', 'São Paulo', 'BR'), +] +# end::CITY[] + +# tag::ASIA[] +def match_asian_cities(): + results = [] + for city in cities: + match city: + case City(continent='Asia'): + results.append(city) + return results +# end::ASIA[] + +# tag::ASIA_POSITIONAL[] +def match_asian_cities_pos(): + results = [] + for city in cities: + match city: + case City('Asia'): + results.append(city) + return results +# end::ASIA_POSITIONAL[] + + +# tag::ASIA_COUNTRIES[] +def match_asian_countries(): + results = [] + for city in cities: + match city: + case City(continent='Asia', country=cc): + results.append(cc) + return results +# end::ASIA_COUNTRIES[] + +# tag::ASIA_COUNTRIES_POSITIONAL[] +def match_asian_countries_pos(): + results = [] + for city in cities: + match city: + case City('Asia', _, country): + results.append(country) + return results +# end::ASIA_COUNTRIES_POSITIONAL[] + + +def match_india(): + results = [] + for city in cities: + match city: + case City(_, name, 'IN'): + results.append(name) + return results + + +def match_brazil(): + results = [] + for city in cities: + match city: + case City(country='BR', name=name): + results.append(name) + return results + + + +def main(): + tests = ((n, f) for n, f in globals().items() if n.startswith('match_')) + + for name, func in tests: + print(f'{name:15}\t{func()}') + + +if __name__ == '__main__': + main() diff --git a/05-record-like/meaning/demo_dc.py b/05-data-classes/meaning/demo_dc.py similarity index 100% rename from 05-record-like/meaning/demo_dc.py rename to 05-data-classes/meaning/demo_dc.py diff --git a/05-record-like/meaning/demo_nt.py b/05-data-classes/meaning/demo_nt.py similarity index 100% rename from 05-record-like/meaning/demo_nt.py rename to 05-data-classes/meaning/demo_nt.py diff --git a/05-record-like/meaning/demo_plain.py b/05-data-classes/meaning/demo_plain.py similarity index 100% rename from 05-record-like/meaning/demo_plain.py rename to 05-data-classes/meaning/demo_plain.py diff --git a/05-record-like/typing_namedtuple/coordinates.py b/05-data-classes/typing_namedtuple/coordinates.py similarity index 100% rename from 05-record-like/typing_namedtuple/coordinates.py rename to 05-data-classes/typing_namedtuple/coordinates.py diff --git a/05-record-like/typing_namedtuple/coordinates2.py b/05-data-classes/typing_namedtuple/coordinates2.py similarity index 95% rename from 05-record-like/typing_namedtuple/coordinates2.py rename to 05-data-classes/typing_namedtuple/coordinates2.py index e523a11..2032311 100644 --- a/05-record-like/typing_namedtuple/coordinates2.py +++ b/05-data-classes/typing_namedtuple/coordinates2.py @@ -17,4 +17,4 @@ class Coordinate(NamedTuple): lat: float # <1> lon: float reference: str = 'WGS84' # <2> -# end::COORDINATE[] +# end::COORDINATE[] \ No newline at end of file diff --git a/05-record-like/typing_namedtuple/nocheck_demo.py b/05-data-classes/typing_namedtuple/nocheck_demo.py similarity index 71% rename from 05-record-like/typing_namedtuple/nocheck_demo.py rename to 05-data-classes/typing_namedtuple/nocheck_demo.py index 57e8fc8..8ca5dc1 100644 --- a/05-record-like/typing_namedtuple/nocheck_demo.py +++ b/05-data-classes/typing_namedtuple/nocheck_demo.py @@ -5,5 +5,5 @@ class Coordinate(typing.NamedTuple): lat: float lon: float -trash = Coordinate('foo', None) # <1> +trash = Coordinate('Ni!', None) # <1> print(trash) diff --git a/05-record-like/struct/README b/05-record-like/struct/README deleted file mode 100644 index 65e16f2..0000000 --- a/05-record-like/struct/README +++ /dev/null @@ -1,17 +0,0 @@ -To compile `metro_write.c` on MacOS 10.14 with XCode: - -$ clang metro_write.c -o metro - -Output: - -$ xxd metro_areas.bin -00000000: e207 0000 546f 6b79 6f00 0000 0000 0000 ....Tokyo....... -00000010: 0000 0000 0000 0000 4a50 01e7 1158 274c ........JP...X'L -00000020: df07 0000 5368 616e 6768 6169 0000 0000 ....Shanghai.... -00000030: 0100 0000 0e00 0000 434e 0000 f8a0 134c ........CN.....L -00000040: df07 0000 4a61 6b61 7274 6100 0000 0000 ....Jakarta..... -00000050: 0000 0000 0000 0000 4944 0000 bcc5 f14b ........ID.....K -$ python3 metro_read.py -2018 Tokyo, JP 43,868,228 -2015 Shanghai, CN 38,700,000 -2015 Jakarta, ID 31,689,592 diff --git a/05-record-like/struct/metro b/05-record-like/struct/metro deleted file mode 100755 index 69a29133a0771710a7d1fb6b0ca83b2187a0261c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8764 zcmeHNU1(fI7@chrZS2n`C=&l*TaucVl0>l(iwQ~1r8n(1i6*H*s*{_|Cc9>{8}4pM z@=!<#3ogq--t@7L1q%lBp+c)|tx3^`qE8j7hG1n27Fy~eCl(h*PC-BPB)^Qls-o3CpRUIyJOc2dOErEr49 zawugoH9M9pHo5s8?6vqB?Fa`u19sYcrToYzBjMOg*hG+Zp)PgnvF?BE6mi$7A zc(U}uZoW~QZ?{cAT)|f)wj3!#$&?w5PulHnzL?F|Y4;PCo10t8+4x*EJ}#s2i39@O zd>3s#t|1z+o5Sj{o?IvG5J<|N?xF6o9;^p7rOrBW)m$f7!>VwUl<}BME=-RlVsa*B zZp-J;aJ_N7W1LTCPT3~qWTG?+Tl1^Kc;>g0!7ySspVP;FDPzXJ<=0^IwJ{h*T+YY$ ztyMkNqx3xu_V%6BdU~DjSiq$lFzpR{63C-;Rjoqo$KL_#`&e}WM^Ilt z%{bCDpsWKwh5GV75TM?LGJsNUjAbd;p}e2|BF4LxoC?9$Py|r8&Rb`bW@{`u);hir z8waMnfWkcbLP!7n`QR%XS2{n=epDOjcol5{6d#J%iwsfdD=n+8`wqc}v_)%13gqG6 zjdJ7XQU~_&9ej^}ttM+R7vwXzkW7WATaL9Lujm+pU-5k_dy%41AFEv;3?oK z;3?oK;3@F`RA5-o+||$hzJly8agUI>uIEmz1hX}tVV7_!D}otqb4|O*m&=^Jpl4>! zuW2hA*mdbk*Q)i*Ej@SSF7_k&5Af*zf($*GZy)*VYQ96ms&@6ns&;MQk-uKMv8LVp zh2ubvu??m1pCD4tXdmVexH*;-$L%*)wIBP)ac50iFXLz{am-j8i}?*?z>n`yLm%|6 zY7cuj^bbAPG033YkuxebJzppk@?RkwpS`RvZ3=%nK$Pp~M8Jy3e+qQhf`Vjo^zu{aw|df}#QngIkm)mM$!Zt%?4BT#$=2%G z;Z6PZ?=$HU^!U@UkK?VD6>+ z2<{#%H2s8nnfqOF?>8_5w$A4;piD)d$ zy){x3FQU-OYrYxxtFNJEdC1K&j6P-FjgfU~G+RrKLfbqVAt67n#je}!`j}k@?fSG` zZ$slXdkT08cnWw5cnWw5cnWw5cnWw5cnWw5cnWw5{09`M-`{YWY1DT~UWFT>DH)1P z$;ny692akyb<176WK);q5{DDxxYS(dC-FOg9dbG%!}HKK0 z(!@iMDslCBJKS;9sgI90aMpRov#9+z*l>O)z;hHp9(5>d!LuB9yKuh?zvII1yYPey z$6c8D;Ucm48wcLU@_w`y1=hv$o9$u(xJBNohg5o0%%+-?iWX~rRa0?-oT;%V#hDIg JBDSmp@i+LF;Jg3; diff --git a/05-record-like/struct/metro_areas.bin b/05-record-like/struct/metro_areas.bin deleted file mode 100644 index 1e738222c9d003f54adb49c2222f45f05d8ba747..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 72 zcmaFF&cG0opIw>HaFmsifq}s*fPq0ULfz*+P&_yzF)uwMF%u}v;Oxi1@MD26SllZy UJF%!F5hTpu>GJF4o}(YV0h6{AqW}N^ diff --git a/05-record-like/struct/metro_read.py b/05-record-like/struct/metro_read.py deleted file mode 100644 index 22f315a..0000000 --- a/05-record-like/struct/metro_read.py +++ /dev/null @@ -1,15 +0,0 @@ -from struct import iter_unpack - -FORMAT = 'i12s2sf' # <1> - -def text(field: bytes) -> str: # <2> - octets = field.split(b'\0', 1)[0] # <3> - return octets.decode('cp437') # <4> - -with open('metro_areas.bin', 'rb') as fp: # <5> - data = fp.read() - -for fields in iter_unpack(FORMAT, data): # <6> - year, name, country, pop = fields - place = text(name) + ', ' + text(country) # <7> - print(f'{year}\t{place}\t{pop:,.0f}') diff --git a/05-record-like/struct/metro_write.c b/05-record-like/struct/metro_write.c deleted file mode 100644 index f179370..0000000 --- a/05-record-like/struct/metro_write.c +++ /dev/null @@ -1,46 +0,0 @@ -#include -#include -#include - -#define LEN 3 - -struct MetroArea { - int year; - char name[12]; - char country[2]; - float population; -}; - -int main(int argc, char* argv[]) { - struct MetroArea metro[LEN]; - int rank; - - metro[0].year = 2018; - strcpy(metro[0].name, "Tokyo"); - metro[0].country[0] = 'J'; - metro[0].country[1] = 'P'; - metro[0].population = 43868229.0; - - metro[1].year = 2015; - strcpy(metro[1].name, "Shanghai"); - metro[1].country[0] = 'C'; - metro[1].country[1] = 'N'; - metro[1].population = 38700000.0; - - metro[2].year = 2015; - strcpy(metro[2].name, "Jakarta"); - metro[2].country[0] = 'I'; - metro[2].country[1] = 'D'; - metro[2].population = 31689592.0; - - FILE* data; - if ( (data = fopen("metro_areas.bin", "wb")) == NULL ) { - printf("Error opening file\n"); - return 1; - } - - fwrite(metro, sizeof(struct MetroArea), LEN, data); - fclose(data); - - return 0; -} diff --git a/07-1class-func/clip.py b/07-1class-func/clip.py index 2ca3f43..79411de 100644 --- a/07-1class-func/clip.py +++ b/07-1class-func/clip.py @@ -15,22 +15,26 @@ 'banana' >>> clip('banana split', 12) 'banana split' + >>> clip('bananasplit', 5) + 'bananasplit' + >>> clip('banana split', 8) + 'banana' """ # tag::CLIP[] def clip(text, max_len=80): - """Return text clipped at the last space before or after max_len - """ - end = None - if len(text) > max_len: - space_before = text.rfind(' ', 0, max_len) - if space_before >= 0: - end = space_before - else: - space_after = text.rfind(' ', max_len) - if space_after >= 0: - end = space_after - if end is None: # no spaces were found - return text.rstrip() + """Return text clipped at the last space before or after max_len""" + text = text.rstrip() + end = len(text) + if end <= max_len: + return text + space_before = text.rfind(' ', 0, max_len) + if space_before >= 0: + end = space_before + else: + space_after = text.find(' ', max_len) + if space_after >= 0: + end = space_after return text[:end].rstrip() # end::CLIP[] + diff --git a/07-1class-func/clip_signature.rst b/07-1class-func/clip_signature.rst index 43a5cb4..9bc2dee 100644 --- a/07-1class-func/clip_signature.rst +++ b/07-1class-func/clip_signature.rst @@ -1,8 +1,8 @@ >>> from clip import clip >>> from inspect import signature >>> sig = signature(clip) ->>> sig # doctest: +ELLIPSIS - +>>> sig + >>> str(sig) '(text, max_len=80)' >>> for name, param in sig.parameters.items(): diff --git a/07-1class-func/tagger.py b/07-1class-func/tagger.py index f132bef..b610676 100644 --- a/07-1class-func/tagger.py +++ b/07-1class-func/tagger.py @@ -28,12 +28,9 @@ def tag(name, *content, class_=None, **attrs): """Generate one or more HTML tags""" if class_ is not None: attrs['class'] = class_ - if attrs: - attr_pairs = (f' {attr}="{value}"' for attr, value - in sorted(attrs.items())) - attr_str = ''.join(attr_pairs) - else: - attr_str = '' + attr_pairs = (f' {attr}="{value}"' for attr, value + in sorted(attrs.items())) + attr_str = ''.join(attr_pairs) if content: elements = (f'<{name}{attr_str}>{c}' for c in content) diff --git a/18-context-mngr/README.rst b/18-with-match/README.rst similarity index 100% rename from 18-context-mngr/README.rst rename to 18-with-match/README.rst diff --git a/18-with-match/lisplus/examples_test.py b/18-with-match/lisplus/examples_test.py new file mode 100644 index 0000000..38510a3 --- /dev/null +++ b/18-with-match/lisplus/examples_test.py @@ -0,0 +1,109 @@ +""" +Doctests for `parse`: + +>>> from lis import parse + +# tag::PARSE_DEMO[] +>>> parse('1.5') # <1> +1.5 +>>> parse('set!') # <2> +'set!' +>>> parse('(gcd 18 44)') # <3> +['gcd', 18, 44] +>>> parse('(- m (* n (// m n)))') # <4> +['-', 'm', ['*', 'n', ['//', 'm', 'n']]] + +# end::PARSE_DEMO[] + +""" + +import math + +from lis import run + + +fact_src = """ +(define (! n) + (if (< n 2) + 1 + (* n (! (- n 1))) + ) +) +(! 42) +""" +def test_factorial(): + got = run(fact_src) + assert got == 1405006117752879898543142606244511569936384000000000 + assert got == math.factorial(42) + + +gcd_src = """ +(define (mod m n) + (- m (* n (// m n)))) +(define (gcd m n) + (if (= n 0) + m + (gcd n (mod m n)))) +(gcd 18 45) +""" +def test_gcd(): + got = run(gcd_src) + assert got == 9 + + +quicksort_src = """ +(define (quicksort lst) + (if (null? lst) + lst + (begin + (define pivot (car lst)) + (define rest (cdr lst)) + (append + (quicksort + (filter (lambda (x) (< x pivot)) rest)) + (list pivot) + (quicksort + (filter (lambda (x) (>= x pivot)) rest))) + ) + ) +) +(quicksort (list 2 1 6 3 4 0 8 9 7 5)) +""" +def test_quicksort(): + got = run(quicksort_src) + assert got == [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + + +# Example from Structure and Interpretation of Computer Programs +# https://mitpress.mit.edu/sites/default/files/sicp/full-text/sicp/book/node12.html + +newton_src = """ +(define (sqrt x) + (sqrt-iter 1.0 x)) +(define (sqrt-iter guess x) + (if (good-enough? guess x) + guess + (sqrt-iter (improve guess x) x))) +(define (good-enough? guess x) + (< (abs (- (* guess guess) x)) 0.001)) +(define (improve guess x) + (average guess (/ x guess))) +(define (average x y) + (/ (+ x y) 2)) +(sqrt 123454321) +""" +def test_newton(): + got = run(newton_src) + assert math.isclose(got, 11111) + + +closure_src = """ +(define (make-adder increment) + (lambda (x) (+ increment x)) +) +(define inc (make-adder 1)) +(inc 99) +""" +def test_newton(): + got = run(closure_src) + assert got == 100 diff --git a/02-array-seq/lispy/lis.py b/18-with-match/lisplus/lis.py similarity index 95% rename from 02-array-seq/lispy/lis.py rename to 18-with-match/lisplus/lis.py index e37ba23..7ae0a9d 100644 --- a/02-array-seq/lispy/lis.py +++ b/18-with-match/lisplus/lis.py @@ -23,7 +23,7 @@ class Procedure: "A user-defined Scheme procedure." - def __init__(self, parms: list[Symbol], body: Expression, env: Environment): + def __init__(self, parms: list[Symbol], body: list[Expression], env: Environment): self.parms = parms self.body = body self.env = env @@ -31,7 +31,9 @@ def __init__(self, parms: list[Symbol], body: Expression, env: Environment): def __call__(self, *args: Expression) -> Any: local_env = dict(zip(self.parms, args)) env: Environment = ChainMap(local_env, self.env) - return evaluate(self.body, env) + for exp in self.body: + result = evaluate(exp, env) + return result ################ global environment @@ -160,9 +162,9 @@ def evaluate(exp: Expression, env: Environment) -> Any: return evaluate(alternative, env) case ['define', Symbol(var), value_exp]: env[var] = evaluate(value_exp, env) - case ['define', [Symbol(name), *parms], body]: + case ['define', [Symbol(name), *parms], *body]: env[name] = Procedure(parms, body, env) - case ['lambda', [*parms], body]: + case ['lambda', [*parms], *body]: return Procedure(parms, body, env) case [op, *args]: proc = evaluate(op, env) diff --git a/18-with-match/lisplus/lis_test.py b/18-with-match/lisplus/lis_test.py new file mode 100644 index 0000000..3688888 --- /dev/null +++ b/18-with-match/lisplus/lis_test.py @@ -0,0 +1,182 @@ +from typing import Optional + +from pytest import mark, fixture + +from lis import parse, evaluate, Expression, Environment, standard_env + +############################################################# tests for parse + +@mark.parametrize( 'source, expected', [ + ('7', 7), + ('x', 'x'), + ('(sum 1 2 3)', ['sum', 1, 2, 3]), + ('(+ (* 2 100) (* 1 10))', ['+', ['*', 2, 100], ['*', 1, 10]]), + ('99 100', 99), # parse stops at the first complete expression + ('(a)(b)', ['a']), +]) +def test_parse(source: str, expected: Expression) -> None: + got = parse(source) + assert got == expected + + +########################################################## tests for evaluate + +# Norvig's tests are not isolated: they assume the +# same environment from first to last test. +global_env_for_first_test = standard_env() + +@mark.parametrize( 'source, expected', [ + ("(quote (testing 1 (2.0) -3.14e159))", ['testing', 1, [2.0], -3.14e159]), + ("(+ 2 2)", 4), + ("(+ (* 2 100) (* 1 10))", 210), + ("(if (> 6 5) (+ 1 1) (+ 2 2))", 2), + ("(if (< 6 5) (+ 1 1) (+ 2 2))", 4), + ("(define x 3)", None), + ("x", 3), + ("(+ x x)", 6), + ("((lambda (x) (+ x x)) 5)", 10), + ("(define twice (lambda (x) (* 2 x)))", None), + ("(twice 5)", 10), + ("(define compose (lambda (f g) (lambda (x) (f (g x)))))", None), + ("((compose list twice) 5)", [10]), + ("(define repeat (lambda (f) (compose f f)))", None), + ("((repeat twice) 5)", 20), + ("((repeat (repeat twice)) 5)", 80), + ("(define fact (lambda (n) (if (<= n 1) 1 (* n (fact (- n 1))))))", None), + ("(fact 3)", 6), + ("(fact 50)", 30414093201713378043612608166064768844377641568960512000000000000), + ("(define abs (lambda (n) ((if (> n 0) + -) 0 n)))", None), + ("(list (abs -3) (abs 0) (abs 3))", [3, 0, 3]), + ("""(define combine (lambda (f) + (lambda (x y) + (if (null? x) (quote ()) + (f (list (car x) (car y)) + ((combine f) (cdr x) (cdr y)))))))""", None), + ("(define zip (combine cons))", None), + ("(zip (list 1 2 3 4) (list 5 6 7 8))", [[1, 5], [2, 6], [3, 7], [4, 8]]), + ("""(define riff-shuffle (lambda (deck) + (begin + (define take (lambda (n seq) (if (<= n 0) (quote ()) (cons (car seq) (take (- n 1) (cdr seq)))))) + (define drop (lambda (n seq) (if (<= n 0) seq (drop (- n 1) (cdr seq))))) + (define mid (lambda (seq) (/ (length seq) 2))) + ((combine append) (take (mid deck) deck) (drop (mid deck) deck)))))""", None), + ("(riff-shuffle (list 1 2 3 4 5 6 7 8))", [1, 5, 2, 6, 3, 7, 4, 8]), + ("((repeat riff-shuffle) (list 1 2 3 4 5 6 7 8))", [1, 3, 5, 7, 2, 4, 6, 8]), + ("(riff-shuffle (riff-shuffle (riff-shuffle (list 1 2 3 4 5 6 7 8))))", [1,2,3,4,5,6,7,8]), +]) +def test_evaluate(source: str, expected: Optional[Expression]) -> None: + got = evaluate(parse(source), global_env_for_first_test) + assert got == expected + + +@fixture +def std_env() -> Environment: + return standard_env() + +# tests for each of the cases in evaluate + +def test_evaluate_variable() -> None: + env: Environment = dict(x=10) + source = 'x' + expected = 10 + got = evaluate(parse(source), env) + assert got == expected + + +def test_evaluate_literal(std_env: Environment) -> None: + source = '3.3' + expected = 3.3 + got = evaluate(parse(source), std_env) + assert got == expected + + +def test_evaluate_quote(std_env: Environment) -> None: + source = '(quote (1.1 is not 1))' + expected = [1.1, 'is', 'not', 1] + got = evaluate(parse(source), std_env) + assert got == expected + + +def test_evaluate_if_true(std_env: Environment) -> None: + source = '(if 1 10 no-such-thing)' + expected = 10 + got = evaluate(parse(source), std_env) + assert got == expected + + +def test_evaluate_if_false(std_env: Environment) -> None: + source = '(if 0 no-such-thing 20)' + expected = 20 + got = evaluate(parse(source), std_env) + assert got == expected + + +def test_define(std_env: Environment) -> None: + source = '(define answer (* 6 7))' + got = evaluate(parse(source), std_env) + assert got is None + assert std_env['answer'] == 42 + + +def test_lambda(std_env: Environment) -> None: + source = '(lambda (a b) (if (>= a b) a b))' + func = evaluate(parse(source), std_env) + assert func.parms == ['a', 'b'] + assert func.body == [['if', ['>=', 'a', 'b'], 'a', 'b']] + assert func.env is std_env + assert func(1, 2) == 2 + assert func(3, 2) == 3 + + +def test_begin(std_env: Environment) -> None: + source = """ + (begin + (define x (* 2 3)) + (* x 7) + ) + """ + got = evaluate(parse(source), std_env) + assert got == 42 + + +def test_invocation_builtin_car(std_env: Environment) -> None: + source = '(car (quote (11 22 33)))' + got = evaluate(parse(source), std_env) + assert got == 11 + + +def test_invocation_builtin_append(std_env: Environment) -> None: + source = '(append (quote (a b)) (quote (c d)))' + got = evaluate(parse(source), std_env) + assert got == ['a', 'b', 'c', 'd'] + + +def test_invocation_builtin_map(std_env: Environment) -> None: + source = '(map (lambda (x) (* x 2)) (quote (1 2 3))))' + got = evaluate(parse(source), std_env) + assert got == [2, 4, 6] + + +def test_invocation_user_procedure(std_env: Environment) -> None: + source = """ + (begin + (define max (lambda (a b) (if (>= a b) a b))) + (max 22 11) + ) + """ + got = evaluate(parse(source), std_env) + assert got == 22 + + +###################################### for py3.10/lis.py only + +def test_define_function(std_env: Environment) -> None: + source = '(define (max a b) (if (>= a b) a b))' + got = evaluate(parse(source), std_env) + assert got is None + max_fn = std_env['max'] + assert max_fn.parms == ['a', 'b'] + assert max_fn.body == [['if', ['>=', 'a', 'b'], 'a', 'b']] + assert max_fn.env is std_env + assert max_fn(1, 2) == 2 + assert max_fn(3, 2) == 3 diff --git a/02-array-seq/lispy/meta_test.py b/18-with-match/lisplus/meta_test.py similarity index 100% rename from 02-array-seq/lispy/meta_test.py rename to 18-with-match/lisplus/meta_test.py diff --git a/18-context-mngr/mirror.py b/18-with-match/mirror.py similarity index 100% rename from 18-context-mngr/mirror.py rename to 18-with-match/mirror.py diff --git a/18-context-mngr/mirror_gen.py b/18-with-match/mirror_gen.py similarity index 100% rename from 18-context-mngr/mirror_gen.py rename to 18-with-match/mirror_gen.py diff --git a/18-context-mngr/mirror_gen_exc.py b/18-with-match/mirror_gen_exc.py similarity index 100% rename from 18-context-mngr/mirror_gen_exc.py rename to 18-with-match/mirror_gen_exc.py diff --git a/25-class-metaprog/checked/decorator/checkeddeco.py b/25-class-metaprog/checked/decorator/checkeddeco.py index fa6abd3..c7d4be1 100644 --- a/25-class-metaprog/checked/decorator/checkeddeco.py +++ b/25-class-metaprog/checked/decorator/checkeddeco.py @@ -59,7 +59,8 @@ ... AttributeError: 'Movie' has no attribute 'director' -The `_as_dict` instance creates a `dict` from the attributes of a `Movie` object:: +The `_asdict` instance method creates a `dict` from the attributes +of a `Movie` object:: >>> movie._asdict() {'title': 'The Godfather', 'year': 1972, 'box_office': 137.0} diff --git a/25-class-metaprog/checked/initsub/checkedlib.py b/25-class-metaprog/checked/initsub/checkedlib.py index 01bc0a8..9b6bf7f 100644 --- a/25-class-metaprog/checked/initsub/checkedlib.py +++ b/25-class-metaprog/checked/initsub/checkedlib.py @@ -58,7 +58,8 @@ ... AttributeError: 'Movie' object has no attribute 'director' -The `_as_dict` instance creates a `dict` from the attributes of a `Movie` object:: +The `_asdict` instance method creates a `dict` from the attributes +of a `Movie` object:: >>> movie._asdict() {'title': 'The Godfather', 'year': 1972, 'box_office': 137.0} diff --git a/25-class-metaprog/checked/metaclass/checkedlib.py b/25-class-metaprog/checked/metaclass/checkedlib.py index 34484ac..8207dbe 100644 --- a/25-class-metaprog/checked/metaclass/checkedlib.py +++ b/25-class-metaprog/checked/metaclass/checkedlib.py @@ -58,7 +58,8 @@ ... AttributeError: 'Movie' object has no attribute 'director' -The `_as_dict` instance creates a `dict` from the attributes of a `Movie` object:: +The `_asdict` instance method creates a `dict` from the attributes +of a `Movie` object:: >>> movie._asdict() {'title': 'The Godfather', 'year': 1972, 'box_office': 137.0} diff --git a/README.md b/README.md index 8149e80..18eaaa2 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ Part / Chapter #|Title|Directory|1st ed. Chapter # 2|An Array of Sequences|[02-array-seq](02-array-seq)|2 3|Dictionaries and Sets|[03-dict-set](03-dict-set)|3 4|Text versus Bytes|[04-text-byte](04-text-byte)|4 -5|Record-like Data Structures|[05-record-like](05-record-like)|🆕 +5|Record-like Data Structures|[05-data-classes](05-data-classes)|🆕 6|Object References, Mutability, and Recycling|[06-obj-ref](06-obj-ref)|8 **III – Functions as Objects**| 7|First-Class Funcions|[07-1class-func](07-1class-func)|5 @@ -42,7 +42,7 @@ Part / Chapter #|Title|Directory|1st ed. Chapter # 16|Operator Overloading: Doing It Right|[16-op-overloading](16-op-overloading)|13 **V – Control Flow**| 17|Iterables, Iterators, and Generators|[17-it-generator](17-it-generator)|14 -18|Context Managers and else Blocks|[18-context-mngr](18-context-mngr)|15 +18|Context Managers and else Blocks|[18-with-match](18-with-match)|15 19|Classic Coroutines|[19-coroutine](19-coroutine)|16 20|Concurrency Models in Python|[20-concurrency](20-concurrency)|🆕 21|Concurrency with Futures|[21-futures](21-futures)|17 From fba87b1cffb6ac9b92efb0d58aea35ebe70b5685 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Thu, 8 Jul 2021 00:22:48 -0300 Subject: [PATCH 093/166] Update README.md --- 02-array-seq/lispy/py3.9/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/02-array-seq/lispy/py3.9/README.md b/02-array-seq/lispy/py3.9/README.md index db6a11b..75bc6bb 100644 --- a/02-array-seq/lispy/py3.9/README.md +++ b/02-array-seq/lispy/py3.9/README.md @@ -7,7 +7,7 @@ _Luciano Ramalho_ ## Major changes -* Make the `lambda` form accept more than one expression as the body. This is consistent with _Scheme_ syntax, and provides a useful example for the book. To implement this: +* Make the `lambda` form accept more than one expression as the body. This is consistent with [_Scheme_ syntax](https://web.mit.edu/scheme_v9.2/doc/mit-scheme-ref/Lambda-Expressions.html), and provides a useful example for the book. To implement this: * In `Procedure.__call__`: evaluate `self.body` as a list of expressions, instead of a single expression. Return the value of the last expression. * In `evaluate()`: when processing `lambda`, unpack expression into `(_, parms, *body)`, to accept a list of expressions as the body. * Remove the `global_env` global `dict`. It is only used as a default value for the `env` parameter in `evaluate()`, but it is unsafe to use mutable data structures as parameter default values. To implement this: @@ -16,7 +16,7 @@ _Luciano Ramalho_ * Rewrite the custom test script [lispytest.py](https://github.com/norvig/pytudes/blob/705c0a335c1811a203e79587d7d41865cf7f41c7/py/lispytest.py) as [lis_test.py](https://github.com/fluentpython/example-code-2e/blob/master/02-array-seq/lispy/py3.9/lis_test.py): -a standard [pytest](https://docs.pytest.org) test suite including new test cases, preserving all Norvig's the test cases for +a standard [pytest](https://docs.pytest.org) test suite including new test cases, preserving all Norvig's test cases for [lis.py](https://github.com/norvig/pytudes/blob/705c0a335c1811a203e79587d7d41865cf7f41c7/py/lis.py) but removing the test cases for the features implemented only in [lispy.py](https://github.com/norvig/pytudes/blob/705c0a335c1811a203e79587d7d41865cf7f41c7/py/lispy.py) From a77e6d1e9797a5d7f7b8e32ed39c094109c8b36e Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Thu, 8 Jul 2021 00:23:56 -0300 Subject: [PATCH 094/166] Update README.md --- 02-array-seq/lispy/py3.9/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/02-array-seq/lispy/py3.9/README.md b/02-array-seq/lispy/py3.9/README.md index 75bc6bb..fdb1f08 100644 --- a/02-array-seq/lispy/py3.9/README.md +++ b/02-array-seq/lispy/py3.9/README.md @@ -19,7 +19,7 @@ _Luciano Ramalho_ a standard [pytest](https://docs.pytest.org) test suite including new test cases, preserving all Norvig's test cases for [lis.py](https://github.com/norvig/pytudes/blob/705c0a335c1811a203e79587d7d41865cf7f41c7/py/lis.py) but removing the test cases for the features implemented only in -[lispy.py](https://github.com/norvig/pytudes/blob/705c0a335c1811a203e79587d7d41865cf7f41c7/py/lispy.py) +[lispy.py](https://github.com/norvig/pytudes/blob/705c0a335c1811a203e79587d7d41865cf7f41c7/py/lispy.py). ## Minor changes From dfef30981edc398c6b7cc1ecc5f71612ab0832e7 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Thu, 8 Jul 2021 12:41:18 -0300 Subject: [PATCH 095/166] Update README.md --- 02-array-seq/lispy/README.md | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/02-array-seq/lispy/README.md b/02-array-seq/lispy/README.md index 38458bb..9ac6f56 100644 --- a/02-array-seq/lispy/README.md +++ b/02-array-seq/lispy/README.md @@ -23,14 +23,4 @@ The copyright holder is Peter Norvig and the code is licensed under the [MIT license](https://github.com/norvig/pytudes/blob/60168bce8cdfacf57c92a5b2979f0b2e95367753/LICENSE). -## Changes to Norvig's code - -I made small changes to the programs in `original/`: - -* In `lis.py`: - * The `Procedure` class accepts a list of expressions as the `body`, and `__call__` evaluates those expressions in order, and returns the value of the last. This is consistent with Scheme's `lambda` syntax and provided a useful example for pattern matching. - * In the `elif` block for `'lambda'`, I added the `*` in front of the `*body` variable in the tuple unpacking to capture the expressions as a list, before calling the `Procedure` constructor. - -* In `lispy.py` I made [changes and a pull request](https://github.com/norvig/pytudes/pull/106) to make it run on Python 3. - _Luciano Ramalho
June 29, 2021_ From 1283115921091cbe11e22d44279556da59b96e2f Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Sat, 10 Jul 2021 11:58:24 -0300 Subject: [PATCH 096/166] sync with Atlas --- 02-array-seq/match_lat_lon.py | 4 +- 04-text-byte/charfinder/README.rst | 72 +++++++++++++++++++++++++++ 04-text-byte/charfinder/cf.py | 6 --- 04-text-byte/charfinder/cf_tests.rst | 36 -------------- 04-text-byte/charfinder/test.sh | 2 +- 07-1class-func/clip.py | 48 ++++++++++-------- 07-1class-func/clip_introspection.rst | 2 +- 15-more-types/typeddict/books.py | 8 +-- 8 files changed, 108 insertions(+), 70 deletions(-) create mode 100644 04-text-byte/charfinder/README.rst delete mode 100644 04-text-byte/charfinder/cf_tests.rst diff --git a/02-array-seq/match_lat_lon.py b/02-array-seq/match_lat_lon.py index 20c7131..d5a5463 100644 --- a/02-array-seq/match_lat_lon.py +++ b/02-array-seq/match_lat_lon.py @@ -23,8 +23,8 @@ def main(): print(f'{"":15} | {"latitude":>9} | {"longitude":>9}') for record in metro_areas: - match record: - case [name, _, _, (lat, lon)] if lon <= 0: + match record: # <1> + case [name, _, _, (lat, lon)] if lon <= 0: # <2> print(f'{name:15} | {lat:9.4f} | {lon:9.4f}') # end::MAIN[] diff --git a/04-text-byte/charfinder/README.rst b/04-text-byte/charfinder/README.rst new file mode 100644 index 0000000..46a5d70 --- /dev/null +++ b/04-text-byte/charfinder/README.rst @@ -0,0 +1,72 @@ +======================== +Character Finder Utility +======================== + +Usage tips +========== + +`cf.py` works as an executable on Unix-like systems, +if you have `python3` on your `$PATH`:: + + $ chmod +x cf.py + $ ./cf.py cat eyes + U+1F638 😸 GRINNING CAT FACE WITH SMILING EYES + U+1F63B 😻 SMILING CAT FACE WITH HEART-SHAPED EYES + U+1F63D 😽 KISSING CAT FACE WITH CLOSED EYES + +Use `wc -l` to count the number of hits:: + + $ ./cf.py hieroglyph | wc -l + 1663 + +With `tee` you can get the output and the count:: + + $ ./cf.py trigram | tee >(wc -l) + U+2630 ☰ TRIGRAM FOR HEAVEN + U+2631 ☱ TRIGRAM FOR LAKE + U+2632 ☲ TRIGRAM FOR FIRE + U+2633 ☳ TRIGRAM FOR THUNDER + U+2634 ☴ TRIGRAM FOR WIND + U+2635 ☵ TRIGRAM FOR WATER + U+2636 ☶ TRIGRAM FOR MOUNTAIN + U+2637 ☷ TRIGRAM FOR EARTH + 8 + + +Running the tests +================= + +Run the ``doctest`` module from the command line on +this README.rst file (using ``-v`` to make tests visible):: + + $ python3 -m doctest README.rst -v + +That's what the ``test.sh`` script does. + + +Tests +----- + +Import functions for testing:: + + >>> from cf import find, main + +Test ``find`` with single result:: + + >>> find('sign', 'registered') # doctest:+NORMALIZE_WHITESPACE + U+00AE ® REGISTERED SIGN + +Test ``find`` with two results:: + + >>> find('chess', 'queen', last=0xFFFF) # doctest:+NORMALIZE_WHITESPACE + U+2655 ♕ WHITE CHESS QUEEN + U+265B ♛ BLACK CHESS QUEEN + +Test ``find`` with no results:: + + >>> find('no_such_character') + +Test ``main`` with no words:: + + >>> main([]) + Please provide words to find. diff --git a/04-text-byte/charfinder/cf.py b/04-text-byte/charfinder/cf.py index 8976a66..28f20d0 100755 --- a/04-text-byte/charfinder/cf.py +++ b/04-text-byte/charfinder/cf.py @@ -4,18 +4,13 @@ FIRST, LAST = ord(' '), sys.maxunicode # <1> - def find(*query_words, first=FIRST, last=LAST): # <2> query = {w.upper() for w in query_words} # <3> - count = 0 for code in range(first, last + 1): char = chr(code) # <4> name = unicodedata.name(char, None) # <5> if name and query.issubset(name.split()): # <6> print(f'U+{code:04X}\t{char}\t{name}') # <7> - count += 1 - print(f'({count} found)') - def main(words): if words: @@ -23,6 +18,5 @@ def main(words): else: print('Please provide words to find.') - if __name__ == '__main__': main(sys.argv[1:]) diff --git a/04-text-byte/charfinder/cf_tests.rst b/04-text-byte/charfinder/cf_tests.rst deleted file mode 100644 index 57028e0..0000000 --- a/04-text-byte/charfinder/cf_tests.rst +++ /dev/null @@ -1,36 +0,0 @@ -Doctests for ``cf.py`` -====================== - -How to run the tests ----------------------- - -Run the ``doctest`` module from the command line:: - - $ python3 -m doctest cf_tests.rst - - -Tests ------ - -Import functions for testing:: - - >>> from cf import find, main - -Test ``find`` with single result:: - - >>> find("sign", "registered") # doctest:+NORMALIZE_WHITESPACE - U+00AE ® REGISTERED SIGN - (1 found) - - -Test ``find`` with two results:: - - >>> find("chess", "queen", last=0xFFFF) # doctest:+NORMALIZE_WHITESPACE - U+2655 ♕ WHITE CHESS QUEEN - U+265B ♛ BLACK CHESS QUEEN - (2 found) - -Test ``main`` with no words:: - - >>> main([]) - Please provide words to find. diff --git a/04-text-byte/charfinder/test.sh b/04-text-byte/charfinder/test.sh index 055fa84..23d19cd 100755 --- a/04-text-byte/charfinder/test.sh +++ b/04-text-byte/charfinder/test.sh @@ -1,2 +1,2 @@ #!/bin/bash -python3 -m doctest cf_tests.rst $1 +python3 -m doctest README.rst $1 diff --git a/07-1class-func/clip.py b/07-1class-func/clip.py index 79411de..2f97c66 100644 --- a/07-1class-func/clip.py +++ b/07-1class-func/clip.py @@ -1,40 +1,48 @@ """ - >>> clip('banana ', 6) - 'banana' - >>> clip('banana ', 7) - 'banana' - >>> clip('banana ', 5) + >>> clip('banana split', 5) 'banana' >>> clip('banana split', 6) 'banana' >>> clip('banana split', 7) 'banana' - >>> clip('banana split', 10) + >>> clip('banana split', 8) 'banana' >>> clip('banana split', 11) 'banana' >>> clip('banana split', 12) 'banana split' - >>> clip('bananasplit', 5) - 'bananasplit' - >>> clip('banana split', 8) - 'banana' + >>> clip('banana-split', 3) + 'banana-split' + +Jess' tests: + + >>> text = 'The quick brown fox jumps over the lazy dog.' + >>> clip14 = clip(text, max_len=14) + >>> clip14 + 'The quick' + >>> len(clip14) + 9 + >>> clip15 = clip(text, max_len=15) + >>> clip15 + 'The quick brown' + >>> len(clip15) + 15 + """ # tag::CLIP[] def clip(text, max_len=80): - """Return text clipped at the last space before or after max_len""" + """Return max_len characters clipped at space if possible""" text = text.rstrip() - end = len(text) - if end <= max_len: + if len(text) <= max_len or ' ' not in text: return text - space_before = text.rfind(' ', 0, max_len) - if space_before >= 0: - end = space_before + end = len(text) + space_at = text.rfind(' ', 0, max_len + 1) + if space_at >= 0: + end = space_at else: - space_after = text.find(' ', max_len) - if space_after >= 0: - end = space_after + space_at = text.find(' ', max_len) + if space_at >= 0: + end = space_at return text[:end].rstrip() # end::CLIP[] - diff --git a/07-1class-func/clip_introspection.rst b/07-1class-func/clip_introspection.rst index aae210a..0b4334d 100644 --- a/07-1class-func/clip_introspection.rst +++ b/07-1class-func/clip_introspection.rst @@ -4,6 +4,6 @@ >>> clip.__code__ # doctest: +ELLIPSIS >>> clip.__code__.co_varnames -('text', 'max_len', 'end', 'space_before', 'space_after') +('text', 'max_len', 'end', 'space_at') >>> clip.__code__.co_argcount 2 diff --git a/15-more-types/typeddict/books.py b/15-more-types/typeddict/books.py index 5a0bc82..e33e21e 100644 --- a/15-more-types/typeddict/books.py +++ b/15-more-types/typeddict/books.py @@ -1,11 +1,11 @@ # tag::BOOKDICT[] -from typing import TypedDict, List +from typing import TypedDict import json class BookDict(TypedDict): isbn: str title: str - authors: List[str] + authors: list[str] pagecount: int # end::BOOKDICT[] @@ -13,7 +13,7 @@ class BookDict(TypedDict): AUTHOR_EL = '{}' def to_xml(book: BookDict) -> str: # <1> - elements: List[str] = [] # <2> + elements: list[str] = [] # <2> for key, value in book.items(): if isinstance(value, list): # <3> elements.extend( @@ -29,4 +29,4 @@ def to_xml(book: BookDict) -> str: # <1> def from_json(data: str) -> BookDict: whatever: BookDict = json.loads(data) # <1> return whatever # <2> -# end::FROMJSON[] \ No newline at end of file +# end::FROMJSON[] From 3f7c926067cd1c66429639b3a12f637556b314a9 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Sat, 10 Jul 2021 12:03:04 -0300 Subject: [PATCH 097/166] Update README.md --- 02-array-seq/lispy/py3.9-no-hints/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/02-array-seq/lispy/py3.9-no-hints/README.md b/02-array-seq/lispy/py3.9-no-hints/README.md index db6a11b..2b6f4ea 100644 --- a/02-array-seq/lispy/py3.9-no-hints/README.md +++ b/02-array-seq/lispy/py3.9-no-hints/README.md @@ -5,7 +5,7 @@ use in _Fluent Python, Second Edition_, I made a few changes for didactic reason _Luciano Ramalho_ -## Major changes +## Significant changes * Make the `lambda` form accept more than one expression as the body. This is consistent with _Scheme_ syntax, and provides a useful example for the book. To implement this: * In `Procedure.__call__`: evaluate `self.body` as a list of expressions, instead of a single expression. Return the value of the last expression. @@ -22,7 +22,7 @@ but removing the test cases for the features implemented only in [lispy.py](https://github.com/norvig/pytudes/blob/705c0a335c1811a203e79587d7d41865cf7f41c7/py/lispy.py) -## Minor changes +## Name changes Cosmetic changes to make the code look more familiar to Python programmers, the audience of _Fluent Python_. From 837b5a36bf2a0b4d16e01502f4ed53ca95d34a6a Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Sat, 10 Jul 2021 12:15:02 -0300 Subject: [PATCH 098/166] New titles for chapters 4, 5, and 7 --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 18eaaa2..102993a 100644 --- a/README.md +++ b/README.md @@ -25,11 +25,11 @@ Part / Chapter #|Title|Directory|1st ed. Chapter # **II – Data Structures**| 2|An Array of Sequences|[02-array-seq](02-array-seq)|2 3|Dictionaries and Sets|[03-dict-set](03-dict-set)|3 -4|Text versus Bytes|[04-text-byte](04-text-byte)|4 -5|Record-like Data Structures|[05-data-classes](05-data-classes)|🆕 +4|Unicode Text versus Bytes|[04-text-byte](04-text-byte)|4 +5|Data Class Builders|[05-data-classes](05-data-classes)|🆕 6|Object References, Mutability, and Recycling|[06-obj-ref](06-obj-ref)|8 **III – Functions as Objects**| -7|First-Class Funcions|[07-1class-func](07-1class-func)|5 +7|Funcions as First-Class Objects|[07-1class-func](07-1class-func)|5 8|Type Hints in Function Definitions|[08-def-type-hints](08-def-type-hints)|🆕 9|Function Decorators and Closures|[09-closure-deco](09-closure-deco)|7 10|Design Patterns with First-Class Functions|[10-dp-1class-func](10-dp-1class-func)|6 From fa1c0a9fe393bb05b21fd71c44b8567796592b22 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Fri, 30 Jul 2021 14:15:38 -0300 Subject: [PATCH 099/166] updated example, thanks to @leorochael --- 14-inheritance/strkeydict_dictsub.py | 93 ++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 14-inheritance/strkeydict_dictsub.py diff --git a/14-inheritance/strkeydict_dictsub.py b/14-inheritance/strkeydict_dictsub.py new file mode 100644 index 0000000..68d7f00 --- /dev/null +++ b/14-inheritance/strkeydict_dictsub.py @@ -0,0 +1,93 @@ +"""StrKeyDict always converts non-string keys to `str` + +This is a variation of `strkeydict.StrKeyDict` implemented +as a `dict` built-in subclass (instead of a `UserDict` subclass) + +Test for initializer: keys are converted to `str`. + + >>> d = StrKeyDict([(2, 'two'), ('4', 'four')]) + >>> sorted(d.keys()) + ['2', '4'] + +Tests for item retrieval using `d[key]` notation:: + + >>> d['2'] + 'two' + >>> d[4] + 'four' + >>> d[1] + Traceback (most recent call last): + ... + KeyError: '1' + +Tests for item retrieval using `d.get(key)` notation:: + + >>> d.get('2') + 'two' + >>> d.get(4) + 'four' + >>> d.get(1, 'N/A') + 'N/A' + +Tests for the `in` operator:: + + >>> 2 in d + True + >>> 1 in d + False + +Test for item assignment using non-string key:: + + >>> d[0] = 'zero' + >>> d['0'] + 'zero' + +Tests for update using a `dict` or a sequence of pairs:: + + >>> d.update({6:'six', '8':'eight'}) + >>> sorted(d.keys()) + ['0', '2', '4', '6', '8'] + >>> d.update([(10, 'ten'), ('12', 'twelve')]) + >>> sorted(d.keys()) + ['0', '10', '12', '2', '4', '6', '8'] + >>> d.update([1, 3, 5]) + Traceback (most recent call last): + ... + TypeError: cannot unpack non-iterable int object + +""" + + +class StrKeyDict(dict): + + def __init__(self, iterable=None, **kwds): + super().__init__() + self.update(iterable, **kwds) + + def __missing__(self, key): + if isinstance(key, str): + raise KeyError(key) + return self[str(key)] + + def __contains__(self, key): + return key in self.keys() or str(key) in self.keys() + + def __setitem__(self, key, item): + super().__setitem__(str(key), item) + + def get(self, key, default=None): + try: + return self[key] + except KeyError: + return default + + def update(self, iterable=None, **kwds): + if iterable is not None: + try: # duck typing FTW! + pairs = iterable.items() + except AttributeError: + pairs = iterable + for key, value in pairs: + self[key] = value + if kwds: + self.update(kwds) From b4ad845d12faf5923370cc47e9e5889acf2f3a72 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Mon, 2 Aug 2021 22:00:17 -0300 Subject: [PATCH 100/166] missing.py example --- 03-dict-set/missing.py | 122 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 03-dict-set/missing.py diff --git a/03-dict-set/missing.py b/03-dict-set/missing.py new file mode 100644 index 0000000..609d39e --- /dev/null +++ b/03-dict-set/missing.py @@ -0,0 +1,122 @@ +""" +Semantics of ``__missing__`` across mappings. + +✅ = indicates ``__missing__`` was called + +Subclass of ``dict``:: + + >>> d = DictSub(A = 'letter A') + >>> d['a'] # ✅ + 'letter A' + >>> d.get('a', '') + '' + >>> 'a' in d + False + +Subclass of ``UserDict``:: + + >>> ud = UserDictSub(A = 'letter A') + >>> ud['a'] # ✅ + 'letter A' + >>> ud.get('a', '') # ✅ + 'letter A' + >>> 'a' in ud + False + + +Simple subclass of ``abc.Mapping``:: + + >>> sms = SimpleMappingSub(A = 'letter A') + >>> sms['a'] + Traceback (most recent call last): + ... + KeyError: 'a' + >>> sms.get('a', '') + '' + >>> 'a' in sms + False + + +Subclass of ``abc.Mapping`` with support for ``__missing__``:: + + >>> mms = MappingMissingSub(A = 'letter A') + >>> mms['a'] # ✅ + 'letter A' + >>> mms.get('a', '') # ✅ + 'letter A' + >>> 'a' in mms # ✅ + True + +Subclass of ``abc.Mapping`` with support for ``__missing__``:: + + >>> dms = DictLikeMappingSub(A = 'letter A') + >>> dms['a'] # ✅ + 'letter A' + >>> dms.get('a', '') + '' + >>> 'a' in dms + False + + +""" + +from collections import UserDict +from collections import abc + + +def _upper(x): + try: + return x.upper() + except AttributeError: + return x + + +class DictSub(dict): + def __missing__(self, key): + return self[_upper(key)] + + +class UserDictSub(UserDict): + def __missing__(self, key): + return self[_upper(key)] + + +class SimpleMappingSub(abc.Mapping): + def __init__(self, *args, **kwargs): + self._data = dict(*args, **kwargs) + + # next three methods: abstract in ABC + def __getitem__(self, key): + return self._data[key] + + def __len__(self): + return len(self._data) + + def __iter__(self): + return iter(self._data) + + # never called by instances of this class + def __missing__(self, key): + return self[_upper(key)] + + +class MappingMissingSub(SimpleMappingSub): + def __getitem__(self, key): + try: + return self._data[key] + except KeyError: + return self[_upper(key)] + + +class DictLikeMappingSub(SimpleMappingSub): + def __getitem__(self, key): + try: + return self._data[key] + except KeyError: + return self[_upper(key)] + + def get(self, key, default=None): + return self._data.get(key, default) + + def __contains__(self, key): + return key in self._data From cbd13885fc1a7903be433277791ff9d1868be752 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Fri, 6 Aug 2021 19:51:22 -0300 Subject: [PATCH 101/166] updates to ch.14 --- 14-inheritance/README.rst | 2 +- 14-inheritance/diamond.py | 65 +++++++++++++++----- 14-inheritance/diamond2.py | 67 ++++++++++++++++++++ 14-inheritance/uppermixin.py | 115 +++++++++++++++++++++++++++++++++++ 4 files changed, 233 insertions(+), 16 deletions(-) create mode 100644 14-inheritance/diamond2.py create mode 100644 14-inheritance/uppermixin.py diff --git a/14-inheritance/README.rst b/14-inheritance/README.rst index 2f7426c..496ffb2 100644 --- a/14-inheritance/README.rst +++ b/14-inheritance/README.rst @@ -1,4 +1,4 @@ -Sample code for Chapter 14 - "Inheritance: for good or for worse" +Sample code for Chapter 14 - "Inheritance: for better or for worse" From the book "Fluent Python, Second Edition" by Luciano Ramalho (O'Reilly, 2021) https://learning.oreilly.com/library/view/fluent-python-2nd/9781492056348/ diff --git a/14-inheritance/diamond.py b/14-inheritance/diamond.py index 5aaac18..1df747b 100644 --- a/14-inheritance/diamond.py +++ b/14-inheritance/diamond.py @@ -1,27 +1,62 @@ -class A: - def ping(self): - print('ping:', self) +""" +diamond1.py: Demo of diamond-shaped class graph. +# tag::LEAF_MRO[] +>>> Leaf.__mro__ # doctest:+NORMALIZE_WHITESPACE + (, , , + , ) -class B(A): - def pong(self): - print('pong:', self) +# end::LEAF_MRO[] + +# tag::DIAMOND_CALLS[] + >>> leaf1 = Leaf() # <1> + >>> leaf1.ping() # <2> + .ping() in Leaf + .ping() in A + .ping() in B + .ping() in Root + >>> leaf1.pong() # <3> + .pong() in A + .pong() in B + +# end::DIAMOND_CALLS[] +""" + +# tag::DIAMOND_CLASSES[] +class Root: # <1> + def ping(self): + print(f'{self}.ping() in Root') -class C(A): def pong(self): - print('PONG:', self) + print(f'{self}.pong() in Root') + def __repr__(self): + cls_name = type(self).__name__ + return f'' -class D(B, C): +class A(Root): # <2> def ping(self): + print(f'{self}.ping() in A') super().ping() - print('post-ping:', self) - def pingpong(self): - self.ping() - super().ping() - self.pong() + def pong(self): + print(f'{self}.pong() in A') super().pong() - C.pong(self) + + +class B(Root): # <3> + def ping(self): + print(f'{self}.ping() in B') + super().ping() + + def pong(self): + print(f'{self}.pong() in B') + + +class Leaf(A, B): # <4> + def ping(self): + print(f'{self}.ping() in Leaf') + super().ping() +# end::DIAMOND_CLASSES[] diff --git a/14-inheritance/diamond2.py b/14-inheritance/diamond2.py new file mode 100644 index 0000000..d92db69 --- /dev/null +++ b/14-inheritance/diamond2.py @@ -0,0 +1,67 @@ +""" +unrelated.py: examples with ``super()`` in a sibling class. + +``U`` is unrelated (does not subclass ``Root``) + +Calling ``ping`` on an instance of ``U`` fails:: + +# tag::UNRELATED_DEMO_1[] + >>> u = U() + >>> u.ping() + Traceback (most recent call last): + ... + AttributeError: 'super' object has no attribute 'ping' + +# end::UNRELATED_DEMO_1[] + + +But if ``U`` is part of a cooperative arrangement of base classes, +its ``ping`` method works:: + +# tag::UNRELATED_DEMO_2[] + + >>> leaf2 = LeafUA() + >>> leaf2.ping() + .ping() in LeafUA + .ping() in U + .ping() in A + .ping() in Root + >>> LeafUA.__mro__ # doctest:+NORMALIZE_WHITESPACE + (, , + , , ) + +# end::UNRELATED_DEMO_2[] + + +Here ``U.ping`` is never called because ``Root.ping`` does not call ``super``. + + >>> o6 = LeafAU() + >>> o6.ping() + .ping() in LeafAU + .ping() in A + .ping() in Root + >>> LeafAU.__mro__ # doctest:+NORMALIZE_WHITESPACE + (, , , + , ) + +""" + +# tag::DIAMOND_CLASSES[] +from diamond import A # <1> + +class U(): # <2> + def ping(self): + print(f'{self}.ping() in U') + super().ping() # <3> + +class LeafUA(U, A): # <4> + def ping(self): + print(f'{self}.ping() in LeafUA') + super().ping() +# end::DIAMOND_CLASSES[] + +class LeafAU(A, U): + def ping(self): + print(f'{self}.ping() in LeafAU') + super().ping() + diff --git a/14-inheritance/uppermixin.py b/14-inheritance/uppermixin.py new file mode 100644 index 0000000..d86ecea --- /dev/null +++ b/14-inheritance/uppermixin.py @@ -0,0 +1,115 @@ +"""UpperDict uppercases all string keys. + +Test for initializer. `str` keys are uppercased:: + + >>> d = UpperDict([('a', 'letter A'), ('B', 'letter B'), (2, 'digit two')]) + >>> list(d.keys()) + ['A', 'B', 2] + +Tests for item retrieval using `d[key]` notation:: + + >>> d['A'] + 'letter A' + >>> d['b'] + 'letter B' + >>> d[2] + 'digit two' + + +Tests for missing key:: + + >>> d['z'] + Traceback (most recent call last): + ... + KeyError: 'Z' + >>> d[99] + Traceback (most recent call last): + ... + KeyError: 99 + + +Tests for item retrieval using `d.get(key)` notation:: + + >>> d.get('a') + 'letter A' + >>> d.get('B') + 'letter B' + >>> d.get(2) + 'digit two' + >>> d.get('z', '(not found)') + '(not found)' + +Tests for the `in` operator:: + + >>> ('a' in d, 'B' in d, 'z' in d) + (True, True, False) + +Test for item assignment using lowercase key:: + + >>> d['c'] = 'letter C' + >>> d['C'] + 'letter C' + +Tests for update using a `dict` or a sequence of pairs:: + + >>> d.update({'D': 'letter D', 'e': 'letter E'}) + >>> list(d.keys()) + ['A', 'B', 2, 'C', 'D', 'E'] + >>> d.update([('f', 'letter F'), ('G', 'letter G')]) + >>> list(d.keys()) + ['A', 'B', 2, 'C', 'D', 'E', 'F', 'G'] + >>> d # doctest:+NORMALIZE_WHITESPACE + {'A': 'letter A', 'B': 'letter B', 2: 'digit two', + 'C': 'letter C', 'D': 'letter D', 'E': 'letter E', + 'F': 'letter F', 'G': 'letter G'} + +UpperCounter uppercases all `str` keys. + +Test for initializer: keys are uppercased. + + >>> d = UpperCounter('AbracAdaBrA') + >>> sorted(d.keys()) + ['A', 'B', 'C', 'D', 'R'] + +Tests for count retrieval using `d[key]` notation:: + + >>> d['a'] + 5 + >>> d['z'] + 0 + +""" +# tag::UPPERCASE_MIXIN[] +import collections + + +def _upper(key): # <1> + try: + return key.upper() + except AttributeError: + return key + + +class UpperCaseMixin: # <2> + def __setitem__(self, key, item): + super().__setitem__(_upper(key), item) + + def __getitem__(self, key): + return super().__getitem__(_upper(key)) + + def get(self, key, default=None): + return super().get(_upper(key), default) + + def __contains__(self, key): + return super().__contains__(_upper(key)) +# end::UPPERCASE_MIXIN[] + +# tag::UPPERDICT[] +class UpperDict(UpperCaseMixin, collections.UserDict): # <1> + pass + + +class UpperCounter(UpperCaseMixin, collections.Counter): # <2> + """Specialized 'Counter' that uppercases string keys""" # <3> + +# end::UPPERDICT[] From 01e717b60a9a14bc1bca4bffd915f7ed2c637e5e Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Sat, 7 Aug 2021 00:44:01 -0300 Subject: [PATCH 102/166] updated from Atlas --- 02-array-seq/lispy/README.md | 10 ++ 04-text-byte/charfinder/README.rst | 2 +- 04-text-byte/charfinder/cf.py | 6 +- 05-data-classes/dataclass/club.py | 2 - 05-data-classes/dataclass/club_wrong.py | 1 - 05-data-classes/dataclass/hackerclub.py | 2 - .../dataclass/hackerclub_annotated.py | 2 - 05-data-classes/dataclass/resource.py | 2 +- 05-data-classes/meaning/demo_dc.py | 1 - 05-data-classes/meaning/demo_nt.py | 1 - 05-data-classes/meaning/demo_plain.py | 1 - .../typing_namedtuple/coordinates.py | 1 - .../typing_namedtuple/coordinates2.py | 3 +- .../typing_namedtuple/nocheck_demo.py | 1 - 07-1class-func/clip.py | 48 --------- 07-1class-func/clip_introspection.rst | 9 -- 07-1class-func/clip_signature.rst | 12 --- 08-def-type-hints/RPN_calc/calc.py | 67 ------------ 08-def-type-hints/RPN_calc/calc_test.py | 51 --------- 08-def-type-hints/charindex.py | 6 +- 08-def-type-hints/colors.py | 6 +- 08-def-type-hints/columnize.py | 4 +- 08-def-type-hints/columnize2.py | 35 ------ 08-def-type-hints/columnize_alias.py | 26 ----- 08-def-type-hints/comparable/top.py | 9 +- 08-def-type-hints/comparable/top_test.py | 25 +++-- 08-def-type-hints/coordinates/coordinates.py | 4 +- .../coordinates/coordinates_named.py | 13 ++- 08-def-type-hints/list.py | 10 -- .../messages/hints_1/messages.py | 11 +- .../messages/hints_1/messages_test.py | 4 +- .../messages/hints_2/messages.py | 19 ++-- .../messages/hints_2/messages_test.py | 10 +- .../messages/no_hints/messages.py | 11 +- .../messages/no_hints/messages_test.py | 2 +- 08-def-type-hints/mode/mode_T.py | 26 ----- 08-def-type-hints/mode/mode_float.py | 4 +- 08-def-type-hints/mode/mode_hashable.py | 3 +- 08-def-type-hints/mode/mode_hashable_wrong.py | 24 ----- 08-def-type-hints/mode/mode_number.py | 26 ----- .../{messages/hints_1 => }/mypy.ini | 4 +- 08-def-type-hints/passdrill.py | 101 ------------------ 08-def-type-hints/replacer.py | 4 +- 08-def-type-hints/replacer2.py | 39 ------- 08-def-type-hints/reveal_array.py | 8 -- 08-def-type-hints/sample.py | 5 +- 08-def-type-hints/typevar_bounded.py | 11 ++ 08-def-type-hints/typevars_constrained.py | 4 +- 09-closure-deco/{ => clock}/clockdeco.py | 0 09-closure-deco/{ => clock}/clockdeco0.py | 0 09-closure-deco/{ => clock}/clockdeco_cls.py | 0 09-closure-deco/{ => clock}/clockdeco_demo.py | 5 +- .../{ => clock}/clockdeco_param.py | 0 .../{ => clock}/clockdeco_param_demo1.py | 0 .../{ => clock}/clockdeco_param_demo2.py | 0 10-dp-1class-func/classic_strategy.py | 82 +++++++------- 10-dp-1class-func/classic_strategy_test.py | 44 ++++---- .../monkeytype/classic_strategy.py | 4 +- 10-dp-1class-func/promotions.py | 21 ++-- 10-dp-1class-func/pytypes/classic_strategy.py | 4 +- 10-dp-1class-func/requirements.txt | 15 +-- 10-dp-1class-func/strategy.py | 82 +++++++------- 10-dp-1class-func/strategy_best.py | 24 +++-- 10-dp-1class-func/strategy_best2.py | 99 ++++------------- 10-dp-1class-func/strategy_best3.py | 67 +++--------- 10-dp-1class-func/strategy_best4.py | 101 ++++++------------ 10-dp-1class-func/strategy_param.py | 4 +- 10-dp-1class-func/strategy_param_test.py | 2 +- 10-dp-1class-func/strategy_test.py | 45 ++++---- 10-dp-1class-func/untyped/classic_strategy.py | 4 +- 10-dp-1class-func/untyped/strategy.py | 4 +- 10-dp-1class-func/untyped/strategy_best.py | 6 +- 10-dp-1class-func/untyped/strategy_best2.py | 6 +- 10-dp-1class-func/untyped/strategy_best3.py | 6 +- 10-dp-1class-func/untyped/strategy_best4.py | 6 +- 10-dp-1class-func/untyped/strategy_param.py | 4 +- 10-dp-1class-func/untyped/strategy_param2.py | 4 +- 11-pythonic-obj/mem_test.py | 15 ++- 11-pythonic-obj/patterns.py | 53 +++++++++ 11-pythonic-obj/slots.rst | 52 +++++++++ 11-pythonic-obj/vector2d_v3.py | 4 +- 11-pythonic-obj/vector2d_v3_prophash.py | 4 +- 11-pythonic-obj/vector2d_v3_slots.py | 7 +- 12-seq-hacking/vector_v3.py | 20 ++-- 12-seq-hacking/vector_v4.py | 12 ++- 12-seq-hacking/vector_v5.py | 12 ++- 13-protocol-abc/frenchdeck2.py | 6 +- 13-protocol-abc/typing/randompick_test.py | 8 +- 13-protocol-abc/typing/vector2d_v4.py | 2 +- 13-protocol-abc/typing/vector2d_v5.py | 2 +- 14-inheritance/uppermixin.py | 41 +++++-- 15-more-types/protocol/abs_demo.py | 2 + 16-op-overloading/vector2d_v3.py | 8 +- 16-op-overloading/vector_v6.py | 12 ++- 16-op-overloading/vector_v7.py | 12 ++- 16-op-overloading/vector_v8.py | 12 ++- 96 files changed, 579 insertions(+), 1020 deletions(-) delete mode 100644 07-1class-func/clip.py delete mode 100644 07-1class-func/clip_introspection.rst delete mode 100644 07-1class-func/clip_signature.rst delete mode 100755 08-def-type-hints/RPN_calc/calc.py delete mode 100644 08-def-type-hints/RPN_calc/calc_test.py delete mode 100644 08-def-type-hints/columnize2.py delete mode 100644 08-def-type-hints/columnize_alias.py delete mode 100644 08-def-type-hints/list.py delete mode 100644 08-def-type-hints/mode/mode_T.py delete mode 100644 08-def-type-hints/mode/mode_hashable_wrong.py delete mode 100644 08-def-type-hints/mode/mode_number.py rename 08-def-type-hints/{messages/hints_1 => }/mypy.ini (50%) delete mode 100755 08-def-type-hints/passdrill.py delete mode 100644 08-def-type-hints/replacer2.py delete mode 100644 08-def-type-hints/reveal_array.py create mode 100644 08-def-type-hints/typevar_bounded.py rename 09-closure-deco/{ => clock}/clockdeco.py (100%) rename 09-closure-deco/{ => clock}/clockdeco0.py (100%) rename 09-closure-deco/{ => clock}/clockdeco_cls.py (100%) rename 09-closure-deco/{ => clock}/clockdeco_demo.py (90%) rename 09-closure-deco/{ => clock}/clockdeco_param.py (100%) rename 09-closure-deco/{ => clock}/clockdeco_param_demo1.py (100%) rename 09-closure-deco/{ => clock}/clockdeco_param_demo2.py (100%) create mode 100644 11-pythonic-obj/patterns.py create mode 100644 11-pythonic-obj/slots.rst diff --git a/02-array-seq/lispy/README.md b/02-array-seq/lispy/README.md index 9ac6f56..38458bb 100644 --- a/02-array-seq/lispy/README.md +++ b/02-array-seq/lispy/README.md @@ -23,4 +23,14 @@ The copyright holder is Peter Norvig and the code is licensed under the [MIT license](https://github.com/norvig/pytudes/blob/60168bce8cdfacf57c92a5b2979f0b2e95367753/LICENSE). +## Changes to Norvig's code + +I made small changes to the programs in `original/`: + +* In `lis.py`: + * The `Procedure` class accepts a list of expressions as the `body`, and `__call__` evaluates those expressions in order, and returns the value of the last. This is consistent with Scheme's `lambda` syntax and provided a useful example for pattern matching. + * In the `elif` block for `'lambda'`, I added the `*` in front of the `*body` variable in the tuple unpacking to capture the expressions as a list, before calling the `Procedure` constructor. + +* In `lispy.py` I made [changes and a pull request](https://github.com/norvig/pytudes/pull/106) to make it run on Python 3. + _Luciano Ramalho
June 29, 2021_ diff --git a/04-text-byte/charfinder/README.rst b/04-text-byte/charfinder/README.rst index 46a5d70..15a613d 100644 --- a/04-text-byte/charfinder/README.rst +++ b/04-text-byte/charfinder/README.rst @@ -58,7 +58,7 @@ Test ``find`` with single result:: Test ``find`` with two results:: - >>> find('chess', 'queen', last=0xFFFF) # doctest:+NORMALIZE_WHITESPACE + >>> find('chess', 'queen', end=0xFFFF) # doctest:+NORMALIZE_WHITESPACE U+2655 ♕ WHITE CHESS QUEEN U+265B ♛ BLACK CHESS QUEEN diff --git a/04-text-byte/charfinder/cf.py b/04-text-byte/charfinder/cf.py index 28f20d0..db982bc 100755 --- a/04-text-byte/charfinder/cf.py +++ b/04-text-byte/charfinder/cf.py @@ -2,11 +2,11 @@ import sys import unicodedata -FIRST, LAST = ord(' '), sys.maxunicode # <1> +START, END = ord(' '), sys.maxunicode + 1 # <1> -def find(*query_words, first=FIRST, last=LAST): # <2> +def find(*query_words, start=START, end=END): # <2> query = {w.upper() for w in query_words} # <3> - for code in range(first, last + 1): + for code in range(start, end): char = chr(code) # <4> name = unicodedata.name(char, None) # <5> if name and query.issubset(name.split()): # <6> diff --git a/05-data-classes/dataclass/club.py b/05-data-classes/dataclass/club.py index cd8ff46..7af49c8 100644 --- a/05-data-classes/dataclass/club.py +++ b/05-data-classes/dataclass/club.py @@ -1,9 +1,7 @@ from dataclasses import dataclass, field - @dataclass class ClubMember: - name: str guests: list = field(default_factory=list) diff --git a/05-data-classes/dataclass/club_wrong.py b/05-data-classes/dataclass/club_wrong.py index 3d73d6a..8521a9d 100644 --- a/05-data-classes/dataclass/club_wrong.py +++ b/05-data-classes/dataclass/club_wrong.py @@ -3,7 +3,6 @@ # tag::CLUBMEMBER[] @dataclass class ClubMember: - name: str guests: list = [] # end::CLUBMEMBER[] diff --git a/05-data-classes/dataclass/hackerclub.py b/05-data-classes/dataclass/hackerclub.py index 4d9112e..762c2cd 100644 --- a/05-data-classes/dataclass/hackerclub.py +++ b/05-data-classes/dataclass/hackerclub.py @@ -34,9 +34,7 @@ @dataclass class HackerClubMember(ClubMember): # <1> - all_handles = set() # <2> - handle: str = '' # <3> def __post_init__(self): diff --git a/05-data-classes/dataclass/hackerclub_annotated.py b/05-data-classes/dataclass/hackerclub_annotated.py index 5cf90fc..2394796 100644 --- a/05-data-classes/dataclass/hackerclub_annotated.py +++ b/05-data-classes/dataclass/hackerclub_annotated.py @@ -35,9 +35,7 @@ @dataclass class HackerClubMember(ClubMember): - all_handles: ClassVar[set[str]] = set() - handle: str = '' def __post_init__(self): diff --git a/05-data-classes/dataclass/resource.py b/05-data-classes/dataclass/resource.py index 5190055..f332a11 100644 --- a/05-data-classes/dataclass/resource.py +++ b/05-data-classes/dataclass/resource.py @@ -32,7 +32,7 @@ from datetime import date -class ResourceType(Enum): # <1> +class ResourceType(Enum): # <1> BOOK = auto() EBOOK = auto() VIDEO = auto() diff --git a/05-data-classes/meaning/demo_dc.py b/05-data-classes/meaning/demo_dc.py index 3cc45ce..fa45bb8 100644 --- a/05-data-classes/meaning/demo_dc.py +++ b/05-data-classes/meaning/demo_dc.py @@ -2,7 +2,6 @@ @dataclass class DemoDataClass: - a: int # <1> b: float = 1.1 # <2> c = 'spam' # <3> diff --git a/05-data-classes/meaning/demo_nt.py b/05-data-classes/meaning/demo_nt.py index 317fb82..8f52354 100644 --- a/05-data-classes/meaning/demo_nt.py +++ b/05-data-classes/meaning/demo_nt.py @@ -1,7 +1,6 @@ import typing class DemoNTClass(typing.NamedTuple): - a: int # <1> b: float = 1.1 # <2> c = 'spam' # <3> diff --git a/05-data-classes/meaning/demo_plain.py b/05-data-classes/meaning/demo_plain.py index 6376959..98c3e40 100644 --- a/05-data-classes/meaning/demo_plain.py +++ b/05-data-classes/meaning/demo_plain.py @@ -1,5 +1,4 @@ class DemoPlainClass: - a: int # <1> b: float = 1.1 # <2> c = 'spam' # <3> diff --git a/05-data-classes/typing_namedtuple/coordinates.py b/05-data-classes/typing_namedtuple/coordinates.py index 378a430..5e4d879 100644 --- a/05-data-classes/typing_namedtuple/coordinates.py +++ b/05-data-classes/typing_namedtuple/coordinates.py @@ -11,7 +11,6 @@ from typing import NamedTuple class Coordinate(NamedTuple): - lat: float lon: float diff --git a/05-data-classes/typing_namedtuple/coordinates2.py b/05-data-classes/typing_namedtuple/coordinates2.py index 2032311..efcd6be 100644 --- a/05-data-classes/typing_namedtuple/coordinates2.py +++ b/05-data-classes/typing_namedtuple/coordinates2.py @@ -13,8 +13,7 @@ from typing import NamedTuple class Coordinate(NamedTuple): - lat: float # <1> lon: float reference: str = 'WGS84' # <2> -# end::COORDINATE[] \ No newline at end of file +# end::COORDINATE[] diff --git a/05-data-classes/typing_namedtuple/nocheck_demo.py b/05-data-classes/typing_namedtuple/nocheck_demo.py index 8ca5dc1..43c1b96 100644 --- a/05-data-classes/typing_namedtuple/nocheck_demo.py +++ b/05-data-classes/typing_namedtuple/nocheck_demo.py @@ -1,7 +1,6 @@ import typing class Coordinate(typing.NamedTuple): - lat: float lon: float diff --git a/07-1class-func/clip.py b/07-1class-func/clip.py deleted file mode 100644 index 2f97c66..0000000 --- a/07-1class-func/clip.py +++ /dev/null @@ -1,48 +0,0 @@ -""" - >>> clip('banana split', 5) - 'banana' - >>> clip('banana split', 6) - 'banana' - >>> clip('banana split', 7) - 'banana' - >>> clip('banana split', 8) - 'banana' - >>> clip('banana split', 11) - 'banana' - >>> clip('banana split', 12) - 'banana split' - >>> clip('banana-split', 3) - 'banana-split' - -Jess' tests: - - >>> text = 'The quick brown fox jumps over the lazy dog.' - >>> clip14 = clip(text, max_len=14) - >>> clip14 - 'The quick' - >>> len(clip14) - 9 - >>> clip15 = clip(text, max_len=15) - >>> clip15 - 'The quick brown' - >>> len(clip15) - 15 - -""" - -# tag::CLIP[] -def clip(text, max_len=80): - """Return max_len characters clipped at space if possible""" - text = text.rstrip() - if len(text) <= max_len or ' ' not in text: - return text - end = len(text) - space_at = text.rfind(' ', 0, max_len + 1) - if space_at >= 0: - end = space_at - else: - space_at = text.find(' ', max_len) - if space_at >= 0: - end = space_at - return text[:end].rstrip() -# end::CLIP[] diff --git a/07-1class-func/clip_introspection.rst b/07-1class-func/clip_introspection.rst deleted file mode 100644 index 0b4334d..0000000 --- a/07-1class-func/clip_introspection.rst +++ /dev/null @@ -1,9 +0,0 @@ ->>> from clip import clip ->>> clip.__defaults__ -(80,) ->>> clip.__code__ # doctest: +ELLIPSIS - ->>> clip.__code__.co_varnames -('text', 'max_len', 'end', 'space_at') ->>> clip.__code__.co_argcount -2 diff --git a/07-1class-func/clip_signature.rst b/07-1class-func/clip_signature.rst deleted file mode 100644 index 9bc2dee..0000000 --- a/07-1class-func/clip_signature.rst +++ /dev/null @@ -1,12 +0,0 @@ ->>> from clip import clip ->>> from inspect import signature ->>> sig = signature(clip) ->>> sig - ->>> str(sig) -'(text, max_len=80)' ->>> for name, param in sig.parameters.items(): -... print(param.kind, ':', name, '=', param.default) -... -POSITIONAL_OR_KEYWORD : text = -POSITIONAL_OR_KEYWORD : max_len = 80 diff --git a/08-def-type-hints/RPN_calc/calc.py b/08-def-type-hints/RPN_calc/calc.py deleted file mode 100755 index 967b094..0000000 --- a/08-def-type-hints/RPN_calc/calc.py +++ /dev/null @@ -1,67 +0,0 @@ -#!/usr/bin/env python3 - -import sys -from array import array -from typing import Mapping, MutableSequence, Callable, Iterable, Union, Any - - -OPERATORS: Mapping[str, Callable[[float, float], float]] = { - '+': lambda a, b: a + b, - '-': lambda a, b: a - b, - '*': lambda a, b: a * b, - '/': lambda a, b: a / b, - '^': lambda a, b: a ** b, -} - - -Stack = MutableSequence[float] - - -def parse_token(token: str) -> Union[str, float]: - try: - return float(token) - except ValueError: - return token - - -def evaluate(tokens: Iterable[str], stack: Stack) -> None: - for token in tokens: - atom = parse_token(token) - if isinstance(atom, float): - stack.append(atom) - else: # not float, must be operator - op = OPERATORS[atom] - x, y = stack.pop(), stack.pop() - result = op(y, x) - stack.append(result) - - -def display(s: Stack) -> str: - items = (repr(n) for n in s) - return ' │ '.join(items) + ' →' - - -def repl(input_fn: Callable[[Any], str] = input) -> None: - """Read-Eval-Print-Loop""" - - print('Use CTRL+C to quit.', file=sys.stderr) - stack: Stack = array('d') - - while True: - try: - line = input_fn('> ') # Read - except (EOFError, KeyboardInterrupt): - break - try: - evaluate(line.split(), stack) # Eval - except IndexError: - print('*** Not enough arguments.', file=sys.stderr) - except KeyError as exc: - print('*** Unknown operator:', exc.args[0], file=sys.stderr) - print(display(stack)) # Print - - print() - - -if __name__ == '__main__': - repl() diff --git a/08-def-type-hints/RPN_calc/calc_test.py b/08-def-type-hints/RPN_calc/calc_test.py deleted file mode 100644 index c2d144c..0000000 --- a/08-def-type-hints/RPN_calc/calc_test.py +++ /dev/null @@ -1,51 +0,0 @@ -from pytest import mark, approx # type: ignore - -from dialogue import Dialogue # type: ignore - -from calc import evaluate, repl, display, Stack - -TOLERANCE = .0001 - -@mark.parametrize("source, want", [ - ('2', 2), - ('2 3 +', 5), - ('5 3 -', 2), - ('3 5 * 2 +', 17), - ('2 3 4 5 * * *', 120), - ('1.1 1.1 1.1 + +', approx(3.3, TOLERANCE)), - ('100 32 - 5 * 9 /', approx(37.78, TOLERANCE)), -]) -def test_evaluate(source, want) -> None: - stack: Stack = [] - evaluate(source.split(), stack) - assert want == stack[-1] - - -@mark.parametrize("value, want", [ - ([], ' →'), - ([3.], '3.0 →'), - ([3., 4., 5.], '3.0 │ 4.0 │ 5.0 →'), -]) -def test_display(value, want) -> None: - assert want == display(value) - - -@mark.parametrize("session", [ - """ - > 3 - 3.0 → - """, - """ - > 3 5 6 - 3.0 │ 5.0 │ 6.0 → - > * - 3.0 │ 30.0 → - > - - -27.0 → - """, -]) -def test_repl(capsys, session) -> None: - dlg = Dialogue(session) - repl(dlg.fake_input) - captured = capsys.readouterr() - assert dlg.session.strip() == captured.out.strip() diff --git a/08-def-type-hints/charindex.py b/08-def-type-hints/charindex.py index 36d20e3..c368c37 100644 --- a/08-def-type-hints/charindex.py +++ b/08-def-type-hints/charindex.py @@ -15,7 +15,7 @@ import sys import re import unicodedata -from typing import Dict, Set, Iterator +from collections.abc import Iterator RE_WORD = re.compile(r'\w+') STOP_CODE = sys.maxunicode + 1 @@ -25,8 +25,8 @@ def tokenize(text: str) -> Iterator[str]: # <1> for match in RE_WORD.finditer(text): yield match.group().upper() -def name_index(start: int = 32, end: int = STOP_CODE) -> Dict[str, Set[str]]: - index: Dict[str, Set[str]] = {} # <2> +def name_index(start: int = 32, end: int = STOP_CODE) -> dict[str, set[str]]: + index: dict[str, set[str]] = {} # <2> for char in (chr(i) for i in range(start, end)): if name := unicodedata.name(char, ''): # <3> for word in tokenize(name): diff --git a/08-def-type-hints/colors.py b/08-def-type-hints/colors.py index b57413c..c0d0665 100644 --- a/08-def-type-hints/colors.py +++ b/08-def-type-hints/colors.py @@ -1,4 +1,4 @@ -from typing import Tuple, Mapping +from collections.abc import Mapping NAMES = { 'aqua': 65535, @@ -19,7 +19,7 @@ 'yellow': 16776960, } -def rgb2hex(color=Tuple[int, int, int]) -> str: +def rgb2hex(color: tuple[int, int, int]) -> str: if any(c not in range(256) for c in color): raise ValueError('Color components must be in range(256)') values = (f'{n % 256:02x}' for n in color) @@ -27,7 +27,7 @@ def rgb2hex(color=Tuple[int, int, int]) -> str: HEX_ERROR = "Color must use format '#0099ff', got: {!r}" -def hex2rgb(color=str) -> Tuple[int, int, int]: +def hex2rgb(color: str) -> tuple[int, int, int]: if len(color) != 7 or color[0] != '#': raise ValueError(HEX_ERROR.format(color)) try: diff --git a/08-def-type-hints/columnize.py b/08-def-type-hints/columnize.py index ef995ae..66b72d8 100644 --- a/08-def-type-hints/columnize.py +++ b/08-def-type-hints/columnize.py @@ -1,7 +1,7 @@ # tag::COLUMNIZE[] -from typing import Sequence, List, Tuple +from collections.abc import Sequence -def columnize(sequence: Sequence[str], num_columns: int = 0) -> List[Tuple[str, ...]]: +def columnize(sequence: Sequence[str], num_columns: int = 0) -> list[tuple[str, ...]]: if num_columns == 0: num_columns = round(len(sequence) ** .5) num_rows, reminder = divmod(len(sequence), num_columns) diff --git a/08-def-type-hints/columnize2.py b/08-def-type-hints/columnize2.py deleted file mode 100644 index 2524fd3..0000000 --- a/08-def-type-hints/columnize2.py +++ /dev/null @@ -1,35 +0,0 @@ -from typing import Sequence, List, Tuple, TypeVar - -T = TypeVar('T') - -def columnize(sequence: Sequence[T], num_columns: int = 0) -> List[Tuple[T, ...]]: - if num_columns == 0: - num_columns = round(len(sequence) ** .5) - num_rows, reminder = divmod(len(sequence), num_columns) - num_rows += bool(reminder) - return [tuple(sequence[i::num_rows]) for i in range(num_rows)] - - -def demo() -> None: - nato = ('Alfa Bravo Charlie Delta Echo Foxtrot Golf Hotel India' - ' Juliett Kilo Lima Mike November Oscar Papa Quebec Romeo' - ' Sierra Tango Uniform Victor Whiskey X-ray Yankee Zulu' - ).split() - - for line in columnize(nato): - for word in line: - print(f'{word:15}', end='') - print() - - print() - for length in range(2, 21, 6): - values = list(range(1, length + 1)) - for row in columnize(values): - for cell in row: - print(f'{cell:5}', end='') - print() - print() - - -if __name__ == '__main__': - demo() diff --git a/08-def-type-hints/columnize_alias.py b/08-def-type-hints/columnize_alias.py deleted file mode 100644 index b783469..0000000 --- a/08-def-type-hints/columnize_alias.py +++ /dev/null @@ -1,26 +0,0 @@ -from typing import Sequence, List, Tuple - -Row = Tuple[str, ...] - -def columnize(sequence: Sequence[str], num_columns: int) -> List[Row]: - if num_columns == 0: - num_columns = round(len(sequence) ** .5) - num_rows, reminder = divmod(len(sequence), num_columns) - num_rows += bool(reminder) - return [tuple(sequence[i::num_rows]) for i in range(num_rows)] - - -def demo() -> None: - nato = ('Alfa Bravo Charlie Delta Echo Foxtrot Golf Hotel India' - ' Juliett Kilo Lima Mike November Oscar Papa Quebec Romeo' - ' Sierra Tango Uniform Victor Whiskey X-ray Yankee Zulu' - ).split() - - for row in columnize(nato, 4): - for word in row: - print(f'{word:15}', end='') - print() - - -if __name__ == '__main__': - demo() diff --git a/08-def-type-hints/comparable/top.py b/08-def-type-hints/comparable/top.py index c552890..2851f62 100644 --- a/08-def-type-hints/comparable/top.py +++ b/08-def-type-hints/comparable/top.py @@ -20,11 +20,14 @@ """ # tag::TOP[] -from typing import TypeVar, Iterable, List +from collections.abc import Iterable +from typing import TypeVar + from comparable import SupportsLessThan LT = TypeVar('LT', bound=SupportsLessThan) -def top(series: Iterable[LT], length: int) -> List[LT]: - return sorted(series, reverse=True)[:length] +def top(series: Iterable[LT], length: int) -> list[LT]: + ordered = sorted(series, reverse=True) + return ordered[:length] # end::TOP[] diff --git a/08-def-type-hints/comparable/top_test.py b/08-def-type-hints/comparable/top_test.py index baff36b..c8c39ac 100644 --- a/08-def-type-hints/comparable/top_test.py +++ b/08-def-type-hints/comparable/top_test.py @@ -1,6 +1,11 @@ -from typing import Tuple, List, Iterator, TYPE_CHECKING -import pytest # type: ignore +# tag::TOP_IMPORT[] +from collections.abc import Iterator +from typing import TYPE_CHECKING # <1> + +import pytest + from top import top +# end::TOP_IMPORT[] @pytest.mark.parametrize('series, length, expected', [ ((1, 2, 3), 2, [3, 2]), @@ -8,9 +13,9 @@ ((3, 3, 3), 1, [3]), ]) def test_top( - series: Tuple[float, ...], + series: tuple[float, ...], length: int, - expected: List[float], + expected: list[float], ) -> None: result = top(series, length) assert expected == result @@ -18,13 +23,13 @@ def test_top( # tag::TOP_TEST[] def test_top_tuples() -> None: fruit = 'mango pear apple kiwi banana'.split() - series: Iterator[Tuple[int, str]] = ( + series: Iterator[tuple[int, str]] = ( # <2> (len(s), s) for s in fruit) length = 3 expected = [(6, 'banana'), (5, 'mango'), (5, 'apple')] result = top(series, length) - if TYPE_CHECKING: - reveal_type(series) + if TYPE_CHECKING: # <3> + reveal_type(series) # <4> reveal_type(expected) reveal_type(result) assert result == expected @@ -34,7 +39,7 @@ def test_top_objects_error() -> None: series = [object() for _ in range(4)] if TYPE_CHECKING: reveal_type(series) - with pytest.raises(TypeError) as exc: - top(series, 3) - assert "'<' not supported" in str(exc) + with pytest.raises(TypeError) as excinfo: + top(series, 3) # <5> + assert "'<' not supported" in str(excinfo.value) # end::TOP_TEST[] diff --git a/08-def-type-hints/coordinates/coordinates.py b/08-def-type-hints/coordinates/coordinates.py index d48b390..fb594eb 100644 --- a/08-def-type-hints/coordinates/coordinates.py +++ b/08-def-type-hints/coordinates/coordinates.py @@ -8,10 +8,10 @@ """ # tag::GEOHASH[] -from geolib import geohash as gh # type: ignore +from geolib import geohash as gh # type: ignore # <1> PRECISION = 9 -def geohash(lat_lon: tuple[float, float]) -> str: +def geohash(lat_lon: tuple[float, float]) -> str: # <2> return gh.encode(*lat_lon, PRECISION) # end::GEOHASH[] diff --git a/08-def-type-hints/coordinates/coordinates_named.py b/08-def-type-hints/coordinates/coordinates_named.py index 87cac72..76bd9bb 100644 --- a/08-def-type-hints/coordinates/coordinates_named.py +++ b/08-def-type-hints/coordinates/coordinates_named.py @@ -9,7 +9,7 @@ """ # tag::GEOHASH[] -from typing import Tuple, NamedTuple +from typing import NamedTuple from geolib import geohash as gh # type: ignore @@ -21,16 +21,21 @@ class Coordinate(NamedTuple): def geohash(lat_lon: Coordinate) -> str: return gh.encode(*lat_lon, PRECISION) +# end::GEOHASH[] -def display(lat_lon: Tuple[float, float]) -> str: +# tag::DISPLAY[] +def display(lat_lon: tuple[float, float]) -> str: lat, lon = lat_lon ns = 'N' if lat >= 0 else 'S' ew = 'E' if lon >= 0 else 'W' return f'{abs(lat):0.1f}°{ns}, {abs(lon):0.1f}°{ew}' - -# end::GEOHASH[] +# end::DISPLAY[] def demo(): shanghai = 31.2304, 121.4737 + print(display(shanghai)) s = geohash(shanghai) print(s) + +if __name__ == '__main__': + demo() diff --git a/08-def-type-hints/list.py b/08-def-type-hints/list.py deleted file mode 100644 index 6d122e7..0000000 --- a/08-def-type-hints/list.py +++ /dev/null @@ -1,10 +0,0 @@ -from typing import List, Tuple - -def tokenize(text: str) -> List[str]: - return text.upper().split() - -l: List[str] = [] -l.append(1) -print(l) - -t: Tuple[str, float] = ('São Paulo', 12_176_866) diff --git a/08-def-type-hints/messages/hints_1/messages.py b/08-def-type-hints/messages/hints_1/messages.py index c00e706..59b432e 100644 --- a/08-def-type-hints/messages/hints_1/messages.py +++ b/08-def-type-hints/messages/hints_1/messages.py @@ -5,16 +5,15 @@ >>> show_count(1, 'bird') '1 bird' >>> show_count(0, 'bird') -'no bird' +'no birds' # end::SHOW_COUNT_DOCTEST[] """ # tag::SHOW_COUNT[] def show_count(count: int, word: str) -> str: - if count == 0: - return f'no {word}' - elif count == 1: - return f'{count} {word}' - return f'{count} {word}s' + if count == 1: + return f'1 {word}' + count_str = str(count) if count else 'no' + return f'{count_str} {word}s' # end::SHOW_COUNT[] diff --git a/08-def-type-hints/messages/hints_1/messages_test.py b/08-def-type-hints/messages/hints_1/messages_test.py index 2a16f25..3688da1 100644 --- a/08-def-type-hints/messages/hints_1/messages_test.py +++ b/08-def-type-hints/messages/hints_1/messages_test.py @@ -7,11 +7,11 @@ (1, '1 part'), (2, '2 parts'), ]) -def test_show_count(qty, expected): +def test_show_count(qty: int, expected: str) -> None: got = show_count(qty, 'part') assert got == expected def test_show_count_zero(): got = show_count(0, 'part') - assert got == 'no part' + assert got == 'no parts' diff --git a/08-def-type-hints/messages/hints_2/messages.py b/08-def-type-hints/messages/hints_2/messages.py index c43d85f..fd2a331 100644 --- a/08-def-type-hints/messages/hints_2/messages.py +++ b/08-def-type-hints/messages/hints_2/messages.py @@ -4,21 +4,22 @@ >>> show_count(1, 'bird') '1 bird' >>> show_count(0, 'bird') -'no bird' +'no birds' >>> show_count(3, 'virus', 'viruses') '3 viruses' +>>> show_count(1, 'virus', 'viruses') +'1 virus' +>>> show_count(0, 'virus', 'viruses') +'no viruses' """ # tag::SHOW_COUNT[] def show_count(count: int, singular: str, plural: str = '') -> str: - if count == 0: - return f'no {singular}' - elif count == 1: + if count == 1: return f'1 {singular}' - else: - if plural: - return f'{count} {plural}' - else: - return f'{count} {singular}s' + count_str = str(count) if count else 'no' + if not plural: + plural = singular + 's' + return f'{count_str} {plural}' # end::SHOW_COUNT[] diff --git a/08-def-type-hints/messages/hints_2/messages_test.py b/08-def-type-hints/messages/hints_2/messages_test.py index f7b2fe1..2678d9b 100644 --- a/08-def-type-hints/messages/hints_2/messages_test.py +++ b/08-def-type-hints/messages/hints_2/messages_test.py @@ -1,4 +1,4 @@ -from pytest import mark # type: ignore +from pytest import mark from messages import show_count @@ -6,9 +6,9 @@ @mark.parametrize('qty, expected', [ (1, '1 part'), (2, '2 parts'), - (0, 'no part'), + (0, 'no parts'), ]) -def test_show_count(qty, expected): +def test_show_count(qty: int, expected: str) -> None: got = show_count(qty, 'part') assert got == expected @@ -17,9 +17,9 @@ def test_show_count(qty, expected): @mark.parametrize('qty, expected', [ (1, '1 child'), (2, '2 children'), - (0, 'no child'), + (0, 'no children'), ]) -def test_irregular(qty, expected) -> None: +def test_irregular(qty: int, expected: str) -> None: got = show_count(qty, 'child', 'children') assert got == expected # end::TEST_IRREGULAR[] diff --git a/08-def-type-hints/messages/no_hints/messages.py b/08-def-type-hints/messages/no_hints/messages.py index d7898c5..df037e7 100644 --- a/08-def-type-hints/messages/no_hints/messages.py +++ b/08-def-type-hints/messages/no_hints/messages.py @@ -5,16 +5,15 @@ >>> show_count(1, 'bird') '1 bird' >>> show_count(0, 'bird') -'no bird' +'no birds' # end::SHOW_COUNT_DOCTEST[] """ # tag::SHOW_COUNT[] def show_count(count, word): - if count == 0: - return f'no {word}' - elif count == 1: - return f'{count} {word}' - return f'{count} {word}s' + if count == 1: + return f'1 {word}' + count_str = str(count) if count else 'no' + return f'{count_str} {word}s' # end::SHOW_COUNT[] diff --git a/08-def-type-hints/messages/no_hints/messages_test.py b/08-def-type-hints/messages/no_hints/messages_test.py index a8ec100..09532d3 100644 --- a/08-def-type-hints/messages/no_hints/messages_test.py +++ b/08-def-type-hints/messages/no_hints/messages_test.py @@ -12,4 +12,4 @@ def test_show_count(qty, expected): def test_show_count_zero(): got = show_count(0, 'part') - assert got == 'no part' + assert got == 'no parts' diff --git a/08-def-type-hints/mode/mode_T.py b/08-def-type-hints/mode/mode_T.py deleted file mode 100644 index cf88bbb..0000000 --- a/08-def-type-hints/mode/mode_T.py +++ /dev/null @@ -1,26 +0,0 @@ -from collections import Counter -from typing import Iterable, TypeVar - -T = TypeVar('T') - -def mode(data: Iterable[T]) -> T: - data = iter(data) - pairs = Counter(data).most_common(1) - if len(pairs) == 0: - raise ValueError('no mode for empty data') - return pairs[0][0] - - -def demo() -> None: - from typing import TYPE_CHECKING - pop: list[set] = [set(), set()] - m = mode(pop) - if TYPE_CHECKING: - reveal_type(pop) - reveal_type(m) - print(pop) - print(repr(m), type(m)) - - -if __name__ == '__main__': - demo() diff --git a/08-def-type-hints/mode/mode_float.py b/08-def-type-hints/mode/mode_float.py index 0684202..79308be 100644 --- a/08-def-type-hints/mode/mode_float.py +++ b/08-def-type-hints/mode/mode_float.py @@ -1,6 +1,6 @@ # tag::MODE_FLOAT[] from collections import Counter -from typing import Iterable +from collections.abc import Iterable def mode(data: Iterable[float]) -> float: pairs = Counter(data).most_common(1) @@ -20,4 +20,4 @@ def demo() -> None: print(repr(m), type(m)) if __name__ == '__main__': - demo() \ No newline at end of file + demo() diff --git a/08-def-type-hints/mode/mode_hashable.py b/08-def-type-hints/mode/mode_hashable.py index aa7f313..57c3a30 100644 --- a/08-def-type-hints/mode/mode_hashable.py +++ b/08-def-type-hints/mode/mode_hashable.py @@ -1,6 +1,7 @@ # tag::MODE_HASHABLE_T[] from collections import Counter -from typing import Iterable, Hashable, TypeVar +from collections.abc import Iterable, Hashable +from typing import TypeVar HashableT = TypeVar('HashableT', bound=Hashable) diff --git a/08-def-type-hints/mode/mode_hashable_wrong.py b/08-def-type-hints/mode/mode_hashable_wrong.py deleted file mode 100644 index ff770a7..0000000 --- a/08-def-type-hints/mode/mode_hashable_wrong.py +++ /dev/null @@ -1,24 +0,0 @@ -# tag::MODE_FLOAT[] -from collections import Counter -from typing import Iterable, Hashable - -def mode(data: Iterable[Hashable]) -> Hashable: - data = iter(data) - pairs = Counter(data).most_common(1) - if len(pairs) == 0: - raise ValueError('no mode for empty data') - return pairs[0][0] -# end::MODE_FLOAT[] - -def demo() -> None: - import typing - pop = 'abracadabra' - m = mode(pop) - if typing.TYPE_CHECKING: - reveal_type(pop) - reveal_type(m) - print(pop) - print(m.upper(), type(m)) - -if __name__ == '__main__': - demo() \ No newline at end of file diff --git a/08-def-type-hints/mode/mode_number.py b/08-def-type-hints/mode/mode_number.py deleted file mode 100644 index 999387f..0000000 --- a/08-def-type-hints/mode/mode_number.py +++ /dev/null @@ -1,26 +0,0 @@ -from collections import Counter -from typing import Iterable, TypeVar -from decimal import Decimal -from fractions import Fraction - -NumberT = TypeVar('NumberT', float, Decimal, Fraction) - -def mode(data: Iterable[NumberT]) -> NumberT: - pairs = Counter(data).most_common(1) - if len(pairs) == 0: - raise ValueError('no mode for empty data') - return pairs[0][0] - - -def demo() -> None: - from typing import TYPE_CHECKING - pop = [Fraction(1, 2), Fraction(1, 3), Fraction(1, 4), Fraction(1, 2)] - m = mode(pop) - if TYPE_CHECKING: - reveal_type(pop) - reveal_type(m) - print(pop) - print(repr(m), type(m)) - -if __name__ == '__main__': - demo() \ No newline at end of file diff --git a/08-def-type-hints/messages/hints_1/mypy.ini b/08-def-type-hints/mypy.ini similarity index 50% rename from 08-def-type-hints/messages/hints_1/mypy.ini rename to 08-def-type-hints/mypy.ini index f658867..ab42819 100644 --- a/08-def-type-hints/messages/hints_1/mypy.ini +++ b/08-def-type-hints/mypy.ini @@ -1,6 +1,4 @@ [mypy] -python_version = 3.8 +python_version = 3.9 warn_unused_configs = True disallow_incomplete_defs = True -[mypy-pytest] -ignore_missing_imports = True diff --git a/08-def-type-hints/passdrill.py b/08-def-type-hints/passdrill.py deleted file mode 100755 index 83b1be1..0000000 --- a/08-def-type-hints/passdrill.py +++ /dev/null @@ -1,101 +0,0 @@ -#!/usr/bin/env python3 - -"""passdrill: typing drills for practicing passphrases -""" - -import os -import sys -from base64 import b64encode, b64decode -from getpass import getpass -from hashlib import scrypt -from typing import Sequence, Tuple - -HASH_FILENAME = 'passdrill.hash' -HELP = 'Use -s to save passphrase hash for practice.' - - -def prompt() -> str: - print('WARNING: the passphrase WILL BE SHOWN so that you can check it!') - confirmed = '' - while confirmed != 'y': - passphrase = input('Type passphrase to hash (it will be echoed): ') - if passphrase in ('', 'q'): - print('ERROR: the passphrase cannot be empty or "q".') - continue - print(f'Passphrase to be hashed -> {passphrase}') - confirmed = input('Confirm (y/n): ').lower() - return passphrase - - -def crypto_hash(salt: bytes, passphrase: str) -> bytes: - octets = passphrase.encode('utf-8') - # Recommended parameters for interactive logins as of 2017: - # N=32768, r=8 and p=1 (https://godoc.org/golang.org/x/crypto/scrypt) - return scrypt(octets, salt=salt, n=32768, r=8, p=1, maxmem=2 ** 26) - - -def build_hash(passphrase: str) -> bytes: - salt = os.urandom(32) - payload = crypto_hash(salt, passphrase) - return b64encode(salt) + b':' + b64encode(payload) - - -def save_hash() -> None: - salted_hash = build_hash(prompt()) - with open(HASH_FILENAME, 'wb') as fp: - fp.write(salted_hash) - print(f'Passphrase hash saved to {HASH_FILENAME}') - - -def load_hash() -> Tuple[bytes, bytes]: - try: - with open(HASH_FILENAME, 'rb') as fp: - salted_hash = fp.read() - except FileNotFoundError: - print('ERROR: passphrase hash file not found.', HELP) - # "standard" exit status codes: - # https://stackoverflow.com/questions/1101957/are-there-any-standard-exit-status-codes-in-linux/40484670#40484670 - sys.exit(74) # input/output error - - salt, stored_hash = salted_hash.split(b':') - return b64decode(salt), b64decode(stored_hash) - - -def practice() -> None: - salt, stored_hash = load_hash() - print('Type q to end practice.') - turn = 0 - correct = 0 - while True: - turn += 1 - response = getpass(f'{turn}:') - if response == '': - print('Type q to quit.') - turn -= 1 # don't count this response - continue - elif response == 'q': - turn -= 1 # don't count this response - break - if crypto_hash(salt, response) == stored_hash: - correct += 1 - answer = 'OK' - else: - answer = 'wrong' - print(f' {answer}\thits={correct}\tmisses={turn-correct}') - - if turn: - print(f'\n{turn} turns. {correct / turn:.1%} correct.') - - -def main(argv: Sequence[str]) -> None: - if len(argv) < 2: - practice() - elif len(argv) == 2 and argv[1] == '-s': - save_hash() - else: - print('ERROR: invalid argument.', HELP) - sys.exit(2) # command line usage error - - -if __name__ == '__main__': - main(sys.argv) diff --git a/08-def-type-hints/replacer.py b/08-def-type-hints/replacer.py index 73ec77d..553a927 100644 --- a/08-def-type-hints/replacer.py +++ b/08-def-type-hints/replacer.py @@ -13,9 +13,9 @@ """ # tag::ZIP_REPLACE[] -from typing import Iterable, Tuple +from collections.abc import Iterable -FromTo = Tuple[str, str] # <1> +FromTo = tuple[str, str] # <1> def zip_replace(text: str, changes: Iterable[FromTo]) -> str: # <2> for from_, to in changes: diff --git a/08-def-type-hints/replacer2.py b/08-def-type-hints/replacer2.py deleted file mode 100644 index 33cdaeb..0000000 --- a/08-def-type-hints/replacer2.py +++ /dev/null @@ -1,39 +0,0 @@ -""" -``zip_replace`` replaces multiple calls to ``str.replace``:: - - >>> changes = [ - ... ('(', ' ( '), - ... (')', ' ) '), - ... (' ', ' '), - ... ] - >>> expr = '(+ 2 (* 3 7))' - >>> zip_replace(expr, changes) - ' ( + 2 ( * 3 7 ) ) ' - -""" - -from typing import Iterable, NamedTuple - - -class FromTo(NamedTuple): - from_: str - to: str - - -def zip_replace(text: str, changes: Iterable[FromTo], count: int = -1) -> str: - for from_, to in changes: - text = text.replace(from_, to, count) - return text - - -def demo() -> None: - import doctest - failed, count = doctest.testmod() - print(f'{count-failed} of {count} doctests OK') - l33t = [FromTo(*p) for p in 'a4 e3 i1 o0'.split()] - text = 'mad skilled noob powned leet' - print(zip_replace(text, l33t)) - - -if __name__ == '__main__': - demo() diff --git a/08-def-type-hints/reveal_array.py b/08-def-type-hints/reveal_array.py deleted file mode 100644 index 149c85a..0000000 --- a/08-def-type-hints/reveal_array.py +++ /dev/null @@ -1,8 +0,0 @@ -from array import array -from typing import MutableSequence - -a = array('d') -reveal_type(a) -b: MutableSequence[float] = array('b') -reveal_type(b) - diff --git a/08-def-type-hints/sample.py b/08-def-type-hints/sample.py index 0e7e2e9..04b0319 100644 --- a/08-def-type-hints/sample.py +++ b/08-def-type-hints/sample.py @@ -1,10 +1,11 @@ # tag::SAMPLE[] +from collections.abc import Sequence from random import shuffle -from typing import Sequence, List, TypeVar +from typing import TypeVar T = TypeVar('T') -def sample(population: Sequence[T], size: int) -> List[T]: +def sample(population: Sequence[T], size: int) -> list[T]: if size < 1: raise ValueError('size must be >= 1') result = list(population) diff --git a/08-def-type-hints/typevar_bounded.py b/08-def-type-hints/typevar_bounded.py new file mode 100644 index 0000000..5c2adf0 --- /dev/null +++ b/08-def-type-hints/typevar_bounded.py @@ -0,0 +1,11 @@ +from typing import TypeVar, TYPE_CHECKING + +BT = TypeVar('BT', bound=float) + +def triple2(a: BT) -> BT: + return a * 3 + +res2 = triple2(2) + +if TYPE_CHECKING: + reveal_type(res2) diff --git a/08-def-type-hints/typevars_constrained.py b/08-def-type-hints/typevars_constrained.py index 2bfea13..8fb8d8e 100644 --- a/08-def-type-hints/typevars_constrained.py +++ b/08-def-type-hints/typevars_constrained.py @@ -7,7 +7,7 @@ def triple1(a: RT) -> RT: return a * 3 -res1 = triple1(1, 2) +res1 = triple1(2) if TYPE_CHECKING: reveal_type(res1) @@ -19,7 +19,7 @@ def triple1(a: RT) -> RT: def triple2(a: BT) -> BT: return a * 3 -res2 = triple2(1, 2) +res2 = triple2(2) if TYPE_CHECKING: reveal_type(res2) diff --git a/09-closure-deco/clockdeco.py b/09-closure-deco/clock/clockdeco.py similarity index 100% rename from 09-closure-deco/clockdeco.py rename to 09-closure-deco/clock/clockdeco.py diff --git a/09-closure-deco/clockdeco0.py b/09-closure-deco/clock/clockdeco0.py similarity index 100% rename from 09-closure-deco/clockdeco0.py rename to 09-closure-deco/clock/clockdeco0.py diff --git a/09-closure-deco/clockdeco_cls.py b/09-closure-deco/clock/clockdeco_cls.py similarity index 100% rename from 09-closure-deco/clockdeco_cls.py rename to 09-closure-deco/clock/clockdeco_cls.py diff --git a/09-closure-deco/clockdeco_demo.py b/09-closure-deco/clock/clockdeco_demo.py similarity index 90% rename from 09-closure-deco/clockdeco_demo.py rename to 09-closure-deco/clock/clockdeco_demo.py index 121b52f..bd41b5c 100644 --- a/09-closure-deco/clockdeco_demo.py +++ b/09-closure-deco/clock/clockdeco_demo.py @@ -1,17 +1,14 @@ import time -from clockdeco import clock - +from clockdeco0 import clock @clock def snooze(seconds): time.sleep(seconds) - @clock def factorial(n): return 1 if n < 2 else n*factorial(n-1) - if __name__ == '__main__': print('*' * 40, 'Calling snooze(.123)') snooze(.123) diff --git a/09-closure-deco/clockdeco_param.py b/09-closure-deco/clock/clockdeco_param.py similarity index 100% rename from 09-closure-deco/clockdeco_param.py rename to 09-closure-deco/clock/clockdeco_param.py diff --git a/09-closure-deco/clockdeco_param_demo1.py b/09-closure-deco/clock/clockdeco_param_demo1.py similarity index 100% rename from 09-closure-deco/clockdeco_param_demo1.py rename to 09-closure-deco/clock/clockdeco_param_demo1.py diff --git a/09-closure-deco/clockdeco_param_demo2.py b/09-closure-deco/clock/clockdeco_param_demo2.py similarity index 100% rename from 09-closure-deco/clockdeco_param_demo2.py rename to 09-closure-deco/clock/clockdeco_param_demo2.py diff --git a/10-dp-1class-func/classic_strategy.py b/10-dp-1class-func/classic_strategy.py index dc72e19..d796a79 100644 --- a/10-dp-1class-func/classic_strategy.py +++ b/10-dp-1class-func/classic_strategy.py @@ -6,20 +6,20 @@ >>> joe = Customer('John Doe', 0) # <1> >>> ann = Customer('Ann Smith', 1100) - >>> cart = [LineItem('banana', 4, .5), # <2> - ... LineItem('apple', 10, 1.5), - ... LineItem('watermelon', 5, 5.0)] + >>> cart = (LineItem('banana', 4, Decimal('.5')), # <2> + ... LineItem('apple', 10, Decimal('1.5')), + ... LineItem('watermelon', 5, Decimal(5))) >>> Order(joe, cart, FidelityPromo()) # <3> >>> Order(ann, cart, FidelityPromo()) # <4> - >>> banana_cart = [LineItem('banana', 30, .5), # <5> - ... LineItem('apple', 10, 1.5)] + >>> banana_cart = (LineItem('banana', 30, Decimal('.5')), # <5> + ... LineItem('apple', 10, Decimal('1.5'))) >>> Order(joe, banana_cart, BulkItemPromo()) # <6> - >>> big_cart = [LineItem(str(item_code), 1, 1.0) # <7> - ... for item_code in range(10)] - >>> Order(joe, big_cart, LargeOrderPromo()) # <8> + >>> long_cart = tuple(LineItem(str(sku), 1, Decimal(1)) # <7> + ... for sku in range(10)) + >>> Order(joe, long_cart, LargeOrderPromo()) # <8> >>> Order(joe, cart, LargeOrderPromo()) @@ -29,44 +29,37 @@ # tag::CLASSIC_STRATEGY[] from abc import ABC, abstractmethod -import typing -from typing import Sequence, Optional +from collections.abc import Sequence +from decimal import Decimal +from typing import NamedTuple, Optional -class Customer(typing.NamedTuple): +class Customer(NamedTuple): name: str fidelity: int -class LineItem: - def __init__(self, product: str, quantity: int, price: float): - self.product = product - self.quantity = quantity - self.price = price +class LineItem(NamedTuple): + product: str + quantity: int + price: Decimal - def total(self): + def total(self) -> Decimal: return self.price * self.quantity -class Order: # the Context - def __init__( - self, - customer: Customer, - cart: Sequence[LineItem], - promotion: Optional['Promotion'] = None, - ): - self.customer = customer - self.cart = list(cart) - self.promotion = promotion +class Order(NamedTuple): # the Context + customer: Customer + cart: Sequence[LineItem] + promotion: Optional['Promotion'] = None - def total(self) -> float: - if not hasattr(self, '__total'): - self.__total = sum(item.total() for item in self.cart) - return self.__total + def total(self) -> Decimal: + totals = (item.total() for item in self.cart) + return sum(totals, start=Decimal(0)) - def due(self) -> float: + def due(self) -> Decimal: if self.promotion is None: - discount = 0.0 + discount = Decimal(0) else: discount = self.promotion.discount(self) return self.total() - discount @@ -77,36 +70,37 @@ def __repr__(self): class Promotion(ABC): # the Strategy: an abstract base class @abstractmethod - def discount(self, order: Order) -> float: + def discount(self, order: Order) -> Decimal: """Return discount as a positive dollar amount""" class FidelityPromo(Promotion): # first Concrete Strategy """5% discount for customers with 1000 or more fidelity points""" - def discount(self, order: Order) -> float: - return order.total() * 0.05 if order.customer.fidelity >= 1000 else 0 + def discount(self, order: Order) -> Decimal: + rate = Decimal('0.05') + if order.customer.fidelity >= 1000: + return order.total() * rate + return Decimal(0) class BulkItemPromo(Promotion): # second Concrete Strategy """10% discount for each LineItem with 20 or more units""" - def discount(self, order: Order) -> float: - discount = 0 + def discount(self, order: Order) -> Decimal: + discount = Decimal(0) for item in order.cart: if item.quantity >= 20: - discount += item.total() * 0.1 + discount += item.total() * Decimal('0.1') return discount class LargeOrderPromo(Promotion): # third Concrete Strategy """7% discount for orders with 10 or more distinct items""" - def discount(self, order: Order) -> float: + def discount(self, order: Order) -> Decimal: distinct_items = {item.product for item in order.cart} if len(distinct_items) >= 10: - return order.total() * 0.07 - return 0 - - + return order.total() * Decimal('0.07') + return Decimal(0) # end::CLASSIC_STRATEGY[] diff --git a/10-dp-1class-func/classic_strategy_test.py b/10-dp-1class-func/classic_strategy_test.py index 8735811..143cd0c 100644 --- a/10-dp-1class-func/classic_strategy_test.py +++ b/10-dp-1class-func/classic_strategy_test.py @@ -1,4 +1,4 @@ -from typing import List +from decimal import Decimal import pytest # type: ignore @@ -17,47 +17,49 @@ def customer_fidelity_1100() -> Customer: @pytest.fixture -def cart_plain() -> List[LineItem]: - return [ - LineItem('banana', 4, 0.5), - LineItem('apple', 10, 1.5), - LineItem('watermelon', 5, 5.0), - ] +def cart_plain() -> tuple[LineItem, ...]: + return ( + LineItem('banana', 4, Decimal('0.5')), + LineItem('apple', 10, Decimal('1.5')), + LineItem('watermelon', 5, Decimal('5.0')), + ) def test_fidelity_promo_no_discount(customer_fidelity_0, cart_plain) -> None: order = Order(customer_fidelity_0, cart_plain, FidelityPromo()) - assert order.total() == 42.0 - assert order.due() == 42.0 + assert order.total() == 42 + assert order.due() == 42 def test_fidelity_promo_with_discount(customer_fidelity_1100, cart_plain) -> None: order = Order(customer_fidelity_1100, cart_plain, FidelityPromo()) - assert order.total() == 42.0 - assert order.due() == 39.9 + assert order.total() == 42 + assert order.due() == Decimal('39.9') def test_bulk_item_promo_no_discount(customer_fidelity_0, cart_plain) -> None: order = Order(customer_fidelity_0, cart_plain, BulkItemPromo()) - assert order.total() == 42.0 - assert order.due() == 42.0 + assert order.total() == 42 + assert order.due() == 42 def test_bulk_item_promo_with_discount(customer_fidelity_0) -> None: - cart = [LineItem('banana', 30, 0.5), LineItem('apple', 10, 1.5)] + cart = [LineItem('banana', 30, Decimal('0.5')), + LineItem('apple', 10, Decimal('1.5'))] order = Order(customer_fidelity_0, cart, BulkItemPromo()) - assert order.total() == 30.0 - assert order.due() == 28.5 + assert order.total() == 30 + assert order.due() == Decimal('28.5') def test_large_order_promo_no_discount(customer_fidelity_0, cart_plain) -> None: order = Order(customer_fidelity_0, cart_plain, LargeOrderPromo()) - assert order.total() == 42.0 - assert order.due() == 42.0 + assert order.total() == 42 + assert order.due() == 42 def test_large_order_promo_with_discount(customer_fidelity_0) -> None: - cart = [LineItem(str(item_code), 1, 1.0) for item_code in range(10)] + cart = [LineItem(str(item_code), 1, Decimal(1)) + for item_code in range(10)] order = Order(customer_fidelity_0, cart, LargeOrderPromo()) - assert order.total() == 10.0 - assert order.due() == 9.3 + assert order.total() == 10 + assert order.due() == Decimal('9.3') diff --git a/10-dp-1class-func/monkeytype/classic_strategy.py b/10-dp-1class-func/monkeytype/classic_strategy.py index 0057d96..670c4cb 100644 --- a/10-dp-1class-func/monkeytype/classic_strategy.py +++ b/10-dp-1class-func/monkeytype/classic_strategy.py @@ -17,9 +17,9 @@ ... LineItem('apple', 10, 1.5)] >>> Order(joe, banana_cart, BulkItemPromo()) # <6> - >>> long_order = [LineItem(str(item_code), 1, 1.0) # <7> + >>> long_cart = [LineItem(str(item_code), 1, 1.0) # <7> ... for item_code in range(10)] - >>> Order(joe, long_order, LargeOrderPromo()) # <8> + >>> Order(joe, long_cart, LargeOrderPromo()) # <8> >>> Order(joe, cart, LargeOrderPromo()) diff --git a/10-dp-1class-func/promotions.py b/10-dp-1class-func/promotions.py index 0f8a823..ee64b20 100644 --- a/10-dp-1class-func/promotions.py +++ b/10-dp-1class-func/promotions.py @@ -1,20 +1,25 @@ -def fidelity_promo(order): +from decimal import Decimal +from strategy import Order + +def fidelity_promo(order: Order) -> Decimal: # <3> """5% discount for customers with 1000 or more fidelity points""" - return order.total() * 0.05 if order.customer.fidelity >= 1000 else 0 + if order.customer.fidelity >= 1000: + return order.total() * Decimal('0.05') + return Decimal(0) -def bulk_item_promo(order): +def bulk_item_promo(order: Order) -> Decimal: """10% discount for each LineItem with 20 or more units""" - discount = 0 + discount = Decimal(0) for item in order.cart: if item.quantity >= 20: - discount += item.total() * 0.1 + discount += item.total() * Decimal('0.1') return discount -def large_order_promo(order): +def large_order_promo(order: Order) -> Decimal: """7% discount for orders with 10 or more distinct items""" distinct_items = {item.product for item in order.cart} if len(distinct_items) >= 10: - return order.total() * 0.07 - return 0 + return order.total() * Decimal('0.07') + return Decimal(0) diff --git a/10-dp-1class-func/pytypes/classic_strategy.py b/10-dp-1class-func/pytypes/classic_strategy.py index d929ffa..710fd26 100644 --- a/10-dp-1class-func/pytypes/classic_strategy.py +++ b/10-dp-1class-func/pytypes/classic_strategy.py @@ -17,9 +17,9 @@ ... LineItem('apple', 10, 1.5)] >>> Order(joe, banana_cart, BulkItemPromo()) # <6> - >>> long_order = [LineItem(str(item_code), 1, 1.0) # <7> + >>> long_cart = [LineItem(str(item_code), 1, 1.0) # <7> ... for item_code in range(10)] - >>> Order(joe, long_order, LargeOrderPromo()) # <8> + >>> Order(joe, long_cart, LargeOrderPromo()) # <8> >>> Order(joe, cart, LargeOrderPromo()) diff --git a/10-dp-1class-func/requirements.txt b/10-dp-1class-func/requirements.txt index 1826922..4c4f0e1 100644 --- a/10-dp-1class-func/requirements.txt +++ b/10-dp-1class-func/requirements.txt @@ -1,13 +1,2 @@ -mypy==0.770 -mypy-extensions==0.4.3 -typed-ast==1.4.1 -typing-extensions==3.7.4.1 -attrs==19.3.0 -more-itertools==8.2.0 -packaging==20.3 -pluggy==0.13.1 -py==1.10.0 -pyparsing==2.4.6 -pytest==5.4.1 -six==1.14.0 -wcwidth==0.1.9 +mypy==0.910 +pytest==6.2.4 diff --git a/10-dp-1class-func/strategy.py b/10-dp-1class-func/strategy.py index 2ab49d1..89a93ba 100644 --- a/10-dp-1class-func/strategy.py +++ b/10-dp-1class-func/strategy.py @@ -6,20 +6,20 @@ >>> joe = Customer('John Doe', 0) # <1> >>> ann = Customer('Ann Smith', 1100) - >>> cart = [LineItem('banana', 4, .5), - ... LineItem('apple', 10, 1.5), - ... LineItem('watermelon', 5, 5.0)] + >>> cart = [LineItem('banana', 4, Decimal('.5')), + ... LineItem('apple', 10, Decimal('1.5')), + ... LineItem('watermelon', 5, Decimal(5))] >>> Order(joe, cart, fidelity_promo) # <2> >>> Order(ann, cart, fidelity_promo) - >>> banana_cart = [LineItem('banana', 30, .5), - ... LineItem('apple', 10, 1.5)] + >>> banana_cart = [LineItem('banana', 30, Decimal('.5')), + ... LineItem('apple', 10, Decimal('1.5'))] >>> Order(joe, banana_cart, bulk_item_promo) # <3> - >>> big_cart = [LineItem(str(item_code), 1, 1.0) + >>> long_cart = [LineItem(str(item_code), 1, Decimal(1)) ... for item_code in range(10)] - >>> Order(joe, big_cart, large_order_promo) + >>> Order(joe, long_cart, large_order_promo) >>> Order(joe, cart, large_order_promo) @@ -28,75 +28,71 @@ """ # tag::STRATEGY[] -import typing -from typing import Sequence, Optional, Callable +from collections.abc import Sequence +from dataclasses import dataclass +from decimal import Decimal +from typing import Optional, Callable, NamedTuple -class Customer(typing.NamedTuple): +class Customer(NamedTuple): name: str fidelity: int -class LineItem: - def __init__(self, product: str, quantity: int, price: float): - self.product = product - self.quantity = quantity - self.price = price +class LineItem(NamedTuple): + product: str + quantity: int + price: Decimal def total(self): return self.price * self.quantity - +@dataclass(frozen=True) class Order: # the Context - def __init__( - self, - customer: Customer, - cart: Sequence[LineItem], - promotion: Optional[Callable[['Order'], float]] = None, - ) -> None: - self.customer = customer - self.cart = list(cart) - self.promotion = promotion - - def total(self) -> float: - if not hasattr(self, '__total'): - self.__total = sum(item.total() for item in self.cart) - return self.__total - - def due(self) -> float: + customer: Customer + cart: Sequence[LineItem] + promotion: Optional[Callable[['Order'], Decimal]] = None # <1> + + def total(self) -> Decimal: + totals = (item.total() for item in self.cart) + return sum(totals, start=Decimal(0)) + + def due(self) -> Decimal: if self.promotion is None: - discount = 0.0 + discount = Decimal(0) else: - discount = self.promotion(self) # <1> + discount = self.promotion(self) # <2> return self.total() - discount def __repr__(self): return f'' -# <2> +# <3> -def fidelity_promo(order: Order) -> float: # <3> +def fidelity_promo(order: Order) -> Decimal: # <4> """5% discount for customers with 1000 or more fidelity points""" - return order.total() * 0.05 if order.customer.fidelity >= 1000 else 0 + if order.customer.fidelity >= 1000: + return order.total() * Decimal('0.05') + return Decimal(0) -def bulk_item_promo(order: Order): +def bulk_item_promo(order: Order) -> Decimal: """10% discount for each LineItem with 20 or more units""" - discount = 0 + discount = Decimal(0) for item in order.cart: if item.quantity >= 20: - discount += item.total() * 0.1 + discount += item.total() * Decimal('0.1') return discount -def large_order_promo(order: Order): +def large_order_promo(order: Order) -> Decimal: """7% discount for orders with 10 or more distinct items""" distinct_items = {item.product for item in order.cart} if len(distinct_items) >= 10: - return order.total() * 0.07 - return 0 + return order.total() * Decimal('0.07') + return Decimal(0) # end::STRATEGY[] diff --git a/10-dp-1class-func/strategy_best.py b/10-dp-1class-func/strategy_best.py index a7c4d74..68cab48 100644 --- a/10-dp-1class-func/strategy_best.py +++ b/10-dp-1class-func/strategy_best.py @@ -3,19 +3,20 @@ # selecting best promotion from static list of functions """ + >>> from strategy import Customer, LineItem >>> joe = Customer('John Doe', 0) >>> ann = Customer('Ann Smith', 1100) - >>> cart = [LineItem('banana', 4, .5), - ... LineItem('apple', 10, 1.5), - ... LineItem('watermelon', 5, 5.0)] - >>> banana_cart = [LineItem('banana', 30, .5), - ... LineItem('apple', 10, 1.5)] - >>> big_cart = [LineItem(str(item_code), 1, 1.0) + >>> cart = [LineItem('banana', 4, Decimal('.5')), + ... LineItem('apple', 10, Decimal('1.5')), + ... LineItem('watermelon', 5, Decimal(5))] + >>> banana_cart = [LineItem('banana', 30, Decimal('.5')), + ... LineItem('apple', 10, Decimal('1.5'))] + >>> long_cart = [LineItem(str(item_code), 1, Decimal(1)) ... for item_code in range(10)] # tag::STRATEGY_BEST_TESTS[] - >>> Order(joe, big_cart, best_promo) # <1> + >>> Order(joe, long_cart, best_promo) # <1> >>> Order(joe, banana_cart, best_promo) # <2> @@ -25,7 +26,9 @@ # end::STRATEGY_BEST_TESTS[] """ -from strategy import Customer, LineItem, Order +from decimal import Decimal + +from strategy import Order from strategy import fidelity_promo, bulk_item_promo, large_order_promo # tag::STRATEGY_BEST[] @@ -33,9 +36,8 @@ promos = [fidelity_promo, bulk_item_promo, large_order_promo] # <1> -def best_promo(order) -> float: # <2> - """Select best discount available - """ +def best_promo(order: Order) -> Decimal: # <2> + """Compute the best discount available""" return max(promo(order) for promo in promos) # <3> diff --git a/10-dp-1class-func/strategy_best2.py b/10-dp-1class-func/strategy_best2.py index 62a993e..1c6ad9c 100644 --- a/10-dp-1class-func/strategy_best2.py +++ b/10-dp-1class-func/strategy_best2.py @@ -3,29 +3,31 @@ # selecting best promotion from current module globals """ + >>> from decimal import Decimal + >>> from strategy import Customer, LineItem, Order >>> joe = Customer('John Doe', 0) >>> ann = Customer('Ann Smith', 1100) - >>> cart = [LineItem('banana', 4, .5), - ... LineItem('apple', 10, 1.5), - ... LineItem('watermelon', 5, 5.0)] + >>> cart = [LineItem('banana', 4, Decimal('.5')), + ... LineItem('apple', 10, Decimal('1.5')), + ... LineItem('watermelon', 5, Decimal(5))] >>> Order(joe, cart, fidelity_promo) >>> Order(ann, cart, fidelity_promo) - >>> banana_cart = [LineItem('banana', 30, .5), - ... LineItem('apple', 10, 1.5)] + >>> banana_cart = [LineItem('banana', 30, Decimal('.5')), + ... LineItem('apple', 10, Decimal('1.5'))] >>> Order(joe, banana_cart, bulk_item_promo) - >>> long_order = [LineItem(str(item_code), 1, 1.0) + >>> long_cart = [LineItem(str(item_code), 1, Decimal(1)) ... for item_code in range(10)] - >>> Order(joe, long_order, large_order_promo) + >>> Order(joe, long_cart, large_order_promo) >>> Order(joe, cart, large_order_promo) # tag::STRATEGY_BEST_TESTS[] - >>> Order(joe, long_order, best_promo) + >>> Order(joe, long_cart, best_promo) >>> Order(joe, banana_cart, best_promo) @@ -35,78 +37,21 @@ # end::STRATEGY_BEST_TESTS[] """ -from collections import namedtuple - -Customer = namedtuple('Customer', 'name fidelity') - - -class LineItem: - def __init__(self, product, quantity, price): - self.product = product - self.quantity = quantity - self.price = price - - def total(self): - return self.price * self.quantity - - -class Order: # the Context - def __init__(self, customer, cart, promotion=None): - self.customer = customer - self.cart = list(cart) - self.promotion = promotion - - def total(self): - if not hasattr(self, '__total'): - self.__total = sum(item.total() for item in self.cart) - return self.__total - - def due(self): - if self.promotion is None: - discount = 0 - else: - discount = self.promotion(self) - return self.total() - discount - - def __repr__(self): - return f'' - - -def fidelity_promo(order): - """5% discount for customers with 1000 or more fidelity points""" - return order.total() * 0.05 if order.customer.fidelity >= 1000 else 0 - - -def bulk_item_promo(order): - """10% discount for each LineItem with 20 or more units""" - discount = 0 - for item in order.cart: - if item.quantity >= 20: - discount += item.total() * 0.1 - return discount - - -def large_order_promo(order): - """7% discount for orders with 10 or more distinct items""" - distinct_items = {item.product for item in order.cart} - if len(distinct_items) >= 10: - return order.total() * 0.07 - return 0 - - # tag::STRATEGY_BEST2[] +from decimal import Decimal +from strategy import Order +from strategy import ( + fidelity_promo, bulk_item_promo, large_order_promo # <1> +) -promos = [ - globals()[name] - for name in globals() # <1> - if name.endswith('_promo') and name != 'best_promo' # <2> -] # <3> - +promos = [promo for name, promo in globals().items() # <2> + if name.endswith('_promo') and # <3> + name != 'best_promo' # <4> +] -def best_promo(order): - """Select best discount available - """ - return max(promo(order) for promo in promos) # <4> +def best_promo(order: Order) -> Decimal: # <5> + """Compute the best discount available""" + return max(promo(order) for promo in promos) # end::STRATEGY_BEST2[] diff --git a/10-dp-1class-func/strategy_best3.py b/10-dp-1class-func/strategy_best3.py index 39ce3bf..8b16ceb 100644 --- a/10-dp-1class-func/strategy_best3.py +++ b/10-dp-1class-func/strategy_best3.py @@ -3,30 +3,32 @@ # selecting best promotion from imported module """ + >>> from decimal import Decimal + >>> from strategy import Customer, LineItem, Order >>> from promotions import * >>> joe = Customer('John Doe', 0) >>> ann = Customer('Ann Smith', 1100) - >>> cart = [LineItem('banana', 4, .5), - ... LineItem('apple', 10, 1.5), - ... LineItem('watermelon', 5, 5.0)] + >>> cart = [LineItem('banana', 4, Decimal('.5')), + ... LineItem('apple', 10, Decimal('1.5')), + ... LineItem('watermelon', 5, Decimal(5))] >>> Order(joe, cart, fidelity_promo) >>> Order(ann, cart, fidelity_promo) - >>> banana_cart = [LineItem('banana', 30, .5), - ... LineItem('apple', 10, 1.5)] + >>> banana_cart = [LineItem('banana', 30, Decimal('.5')), + ... LineItem('apple', 10, Decimal('1.5'))] >>> Order(joe, banana_cart, bulk_item_promo) - >>> long_order = [LineItem(str(item_code), 1, 1.0) + >>> long_cart = [LineItem(str(item_code), 1, Decimal(1)) ... for item_code in range(10)] - >>> Order(joe, long_order, large_order_promo) + >>> Order(joe, long_cart, large_order_promo) >>> Order(joe, cart, large_order_promo) # tag::STRATEGY_BEST_TESTS[] - >>> Order(joe, long_order, best_promo) + >>> Order(joe, long_cart, best_promo) >>> Order(joe, banana_cart, best_promo) @@ -36,55 +38,20 @@ # end::STRATEGY_BEST_TESTS[] """ -from collections import namedtuple +# tag::STRATEGY_BEST3[] + +from decimal import Decimal import inspect +from strategy import Order import promotions -Customer = namedtuple('Customer', 'name fidelity') - - -class LineItem: - def __init__(self, product, quantity, price): - self.product = product - self.quantity = quantity - self.price = price - - def total(self): - return self.price * self.quantity - - -class Order: # the Context - def __init__(self, customer, cart, promotion=None): - self.customer = customer - self.cart = list(cart) - self.promotion = promotion - def total(self): - if not hasattr(self, '__total'): - self.__total = sum(item.total() for item in self.cart) - return self.__total +promos = [func for _, func in inspect.getmembers(promotions, inspect.isfunction)] - def due(self): - if self.promotion is None: - discount = 0 - else: - discount = self.promotion(self) - return self.total() - discount - def __repr__(self): - return f'' - - -# tag::STRATEGY_BEST3[] - -promos = [func for name, func in inspect.getmembers(promotions, inspect.isfunction)] - - -def best_promo(order): - """Select best discount available - """ +def best_promo(order: Order) -> Decimal: + """Compute the best discount available""" return max(promo(order) for promo in promos) - # end::STRATEGY_BEST3[] diff --git a/10-dp-1class-func/strategy_best4.py b/10-dp-1class-func/strategy_best4.py index 955ac3a..8e124bb 100644 --- a/10-dp-1class-func/strategy_best4.py +++ b/10-dp-1class-func/strategy_best4.py @@ -4,29 +4,32 @@ # registered by a decorator """ + >>> from decimal import Decimal + >>> from strategy import Customer, LineItem, Order + >>> from promotions import * >>> joe = Customer('John Doe', 0) >>> ann = Customer('Ann Smith', 1100) - >>> cart = [LineItem('banana', 4, .5), - ... LineItem('apple', 10, 1.5), - ... LineItem('watermelon', 5, 5.0)] - >>> Order(joe, cart, fidelity) + >>> cart = [LineItem('banana', 4, Decimal('.5')), + ... LineItem('apple', 10, Decimal('1.5')), + ... LineItem('watermelon', 5, Decimal(5))] + >>> Order(joe, cart, fidelity_promo) - >>> Order(ann, cart, fidelity) + >>> Order(ann, cart, fidelity_promo) - >>> banana_cart = [LineItem('banana', 30, .5), - ... LineItem('apple', 10, 1.5)] - >>> Order(joe, banana_cart, bulk_item) + >>> banana_cart = [LineItem('banana', 30, Decimal('.5')), + ... LineItem('apple', 10, Decimal('1.5'))] + >>> Order(joe, banana_cart, bulk_item_promo) - >>> long_order = [LineItem(str(item_code), 1, 1.0) + >>> long_cart = [LineItem(str(item_code), 1, Decimal(1)) ... for item_code in range(10)] - >>> Order(joe, long_order, large_order) + >>> Order(joe, long_cart, large_order_promo) - >>> Order(joe, cart, large_order) + >>> Order(joe, cart, large_order_promo) # tag::STRATEGY_BEST_TESTS[] - >>> Order(joe, long_order, best_promo) + >>> Order(joe, long_cart, best_promo) >>> Order(joe, banana_cart, best_promo) @@ -36,47 +39,16 @@ # end::STRATEGY_BEST_TESTS[] """ -from collections import namedtuple -from typing import Callable, List - -Customer = namedtuple('Customer', 'name fidelity') - - -class LineItem: - def __init__(self, product, quantity, price): - self.product = product - self.quantity = quantity - self.price = price - - def total(self): - return self.price * self.quantity - - -class Order: # the Context - def __init__(self, customer, cart, promotion=None): - self.customer = customer - self.cart = list(cart) - self.promotion = promotion - - def total(self): - if not hasattr(self, '__total'): - self.__total = sum(item.total() for item in self.cart) - return self.__total - - def due(self): - if self.promotion is None: - discount = 0 - else: - discount = self.promotion(self) - return self.total() - discount - - def __repr__(self): - return f'' +from decimal import Decimal +from typing import Callable +from strategy import Order # tag::STRATEGY_BEST4[] -Promotion = Callable[[Order], float] # <2> +Promotion = Callable[[Order], Decimal] + +promos: list[Promotion] = [] # <1> def promotion(promo: Promotion) -> Promotion: # <2> @@ -84,38 +56,35 @@ def promotion(promo: Promotion) -> Promotion: # <2> return promo -promos: List[Promotion] = [] # <1> +def best_promo(order: Order) -> Decimal: + """Compute the best discount available""" + return max(promo(order) for promo in promos) # <3> -@promotion # <3> -def fidelity(order: Order) -> float: +@promotion # <4> +def fidelity(order: Order) -> Decimal: """5% discount for customers with 1000 or more fidelity points""" - return order.total() * 0.05 if order.customer.fidelity >= 1000 else 0 + if order.customer.fidelity >= 1000: + return order.total() * Decimal('0.05') + return Decimal(0) @promotion -def bulk_item(order: Order) -> float: +def bulk_item(order: Order) -> Decimal: """10% discount for each LineItem with 20 or more units""" - discount = 0 + discount = Decimal(0) for item in order.cart: if item.quantity >= 20: - discount += item.total() * 0.1 + discount += item.total() * Decimal('0.1') return discount @promotion -def large_order(order: Order) -> float: +def large_order(order: Order) -> Decimal: """7% discount for orders with 10 or more distinct items""" distinct_items = {item.product for item in order.cart} if len(distinct_items) >= 10: - return order.total() * 0.07 - return 0 - - -def best_promo(order: Order) -> float: # <4> - """Select best discount available - """ - return max(promo(order) for promo in promos) - + return order.total() * Decimal('0.07') + return Decimal(0) # end::STRATEGY_BEST4[] diff --git a/10-dp-1class-func/strategy_param.py b/10-dp-1class-func/strategy_param.py index 7318530..5490666 100644 --- a/10-dp-1class-func/strategy_param.py +++ b/10-dp-1class-func/strategy_param.py @@ -15,9 +15,9 @@ ... LineItem('apple', 10, 1.5)] >>> Order(joe, banana_cart, bulk_item_promo(10)) - >>> big_cart = [LineItem(str(item_code), 1, 1.0) + >>> long_cart = [LineItem(str(item_code), 1, 1.0) ... for item_code in range(10)] - >>> Order(joe, big_cart, LargeOrderPromo(7)) + >>> Order(joe, long_cart, LargeOrderPromo(7)) >>> Order(joe, cart, LargeOrderPromo(7)) diff --git a/10-dp-1class-func/strategy_param_test.py b/10-dp-1class-func/strategy_param_test.py index d550e2b..cfb965e 100644 --- a/10-dp-1class-func/strategy_param_test.py +++ b/10-dp-1class-func/strategy_param_test.py @@ -47,7 +47,7 @@ def test_large_order_promo_with_discount(customer_fidelity_0) -> None: assert order.due() == 9.3 -def test_general_discount(customer_fidelity_0, cart_plain) -> None: +def test_general_discount(customer_fidelity_1100, cart_plain) -> None: general_promo: Promotion = functools.partial(general_discount, 5) order = Order(customer_fidelity_1100, cart_plain, general_promo) assert order.total() == 42.0 diff --git a/10-dp-1class-func/strategy_test.py b/10-dp-1class-func/strategy_test.py index bf225ae..c47440e 100644 --- a/10-dp-1class-func/strategy_test.py +++ b/10-dp-1class-func/strategy_test.py @@ -1,4 +1,4 @@ -from typing import List +from decimal import Decimal import pytest # type: ignore @@ -17,47 +17,50 @@ def customer_fidelity_1100() -> Customer: @pytest.fixture -def cart_plain() -> List[LineItem]: - return [ - LineItem('banana', 4, 0.5), - LineItem('apple', 10, 1.5), - LineItem('watermelon', 5, 5.0), - ] +def cart_plain() -> tuple[LineItem, ...]: + return ( + LineItem('banana', 4, Decimal('0.5')), + LineItem('apple', 10, Decimal('1.5')), + LineItem('watermelon', 5, Decimal('5.0')), + ) def test_fidelity_promo_no_discount(customer_fidelity_0, cart_plain) -> None: order = Order(customer_fidelity_0, cart_plain, fidelity_promo) - assert order.total() == 42.0 - assert order.due() == 42.0 + assert order.total() == 42 + assert order.due() == 42 def test_fidelity_promo_with_discount(customer_fidelity_1100, cart_plain) -> None: order = Order(customer_fidelity_1100, cart_plain, fidelity_promo) - assert order.total() == 42.0 - assert order.due() == 39.9 + assert order.total() == 42 + assert order.due() == Decimal('39.9') def test_bulk_item_promo_no_discount(customer_fidelity_0, cart_plain) -> None: order = Order(customer_fidelity_0, cart_plain, bulk_item_promo) - assert order.total() == 42.0 - assert order.due() == 42.0 + assert order.total() == 42 + assert order.due() == 42 def test_bulk_item_promo_with_discount(customer_fidelity_0) -> None: - cart = [LineItem('banana', 30, 0.5), LineItem('apple', 10, 1.5)] + cart = [LineItem('banana', 30, Decimal('0.5')), + LineItem('apple', 10, Decimal('1.5'))] order = Order(customer_fidelity_0, cart, bulk_item_promo) - assert order.total() == 30.0 - assert order.due() == 28.5 + assert order.total() == 30 + assert order.due() == Decimal('28.5') def test_large_order_promo_no_discount(customer_fidelity_0, cart_plain) -> None: order = Order(customer_fidelity_0, cart_plain, large_order_promo) - assert order.total() == 42.0 - assert order.due() == 42.0 + assert order.total() == 42 + assert order.due() == 42 def test_large_order_promo_with_discount(customer_fidelity_0) -> None: - cart = [LineItem(str(item_code), 1, 1.0) for item_code in range(10)] + + cart = [LineItem(str(item_code), 1, Decimal(1)) + for item_code in range(10)] order = Order(customer_fidelity_0, cart, large_order_promo) - assert order.total() == 10.0 - assert order.due() == 9.3 + assert order.total() == 10 + assert order.due() == Decimal('9.3') diff --git a/10-dp-1class-func/untyped/classic_strategy.py b/10-dp-1class-func/untyped/classic_strategy.py index b969780..61cccd5 100644 --- a/10-dp-1class-func/untyped/classic_strategy.py +++ b/10-dp-1class-func/untyped/classic_strategy.py @@ -17,9 +17,9 @@ ... LineItem('apple', 10, 1.5)] >>> Order(joe, banana_cart, BulkItemPromo()) # <6> - >>> long_order = [LineItem(str(item_code), 1, 1.0) # <7> + >>> long_cart = [LineItem(str(item_code), 1, 1.0) # <7> ... for item_code in range(10)] - >>> Order(joe, long_order, LargeOrderPromo()) # <8> + >>> Order(joe, long_cart, LargeOrderPromo()) # <8> >>> Order(joe, cart, LargeOrderPromo()) diff --git a/10-dp-1class-func/untyped/strategy.py b/10-dp-1class-func/untyped/strategy.py index 1f8ad4c..518c69d 100644 --- a/10-dp-1class-func/untyped/strategy.py +++ b/10-dp-1class-func/untyped/strategy.py @@ -17,9 +17,9 @@ ... LineItem('apple', 10, 1.5)] >>> Order(joe, banana_cart, bulk_item_promo) # <3> - >>> long_order = [LineItem(str(item_code), 1, 1.0) + >>> long_cart = [LineItem(str(item_code), 1, 1.0) ... for item_code in range(10)] - >>> Order(joe, long_order, large_order_promo) + >>> Order(joe, long_cart, large_order_promo) >>> Order(joe, cart, large_order_promo) diff --git a/10-dp-1class-func/untyped/strategy_best.py b/10-dp-1class-func/untyped/strategy_best.py index c0585f7..718b672 100644 --- a/10-dp-1class-func/untyped/strategy_best.py +++ b/10-dp-1class-func/untyped/strategy_best.py @@ -16,16 +16,16 @@ ... LineItem('apple', 10, 1.5)] >>> Order(joe, banana_cart, bulk_item_promo) - >>> long_order = [LineItem(str(item_code), 1, 1.0) + >>> long_cart = [LineItem(str(item_code), 1, 1.0) ... for item_code in range(10)] - >>> Order(joe, long_order, large_order_promo) + >>> Order(joe, long_cart, large_order_promo) >>> Order(joe, cart, large_order_promo) # tag::STRATEGY_BEST_TESTS[] - >>> Order(joe, long_order, best_promo) # <1> + >>> Order(joe, long_cart, best_promo) # <1> >>> Order(joe, banana_cart, best_promo) # <2> diff --git a/10-dp-1class-func/untyped/strategy_best2.py b/10-dp-1class-func/untyped/strategy_best2.py index 1f4700f..bfcd839 100644 --- a/10-dp-1class-func/untyped/strategy_best2.py +++ b/10-dp-1class-func/untyped/strategy_best2.py @@ -16,16 +16,16 @@ ... LineItem('apple', 10, 1.5)] >>> Order(joe, banana_cart, bulk_item_promo) - >>> long_order = [LineItem(str(item_code), 1, 1.0) + >>> long_cart = [LineItem(str(item_code), 1, 1.0) ... for item_code in range(10)] - >>> Order(joe, long_order, large_order_promo) + >>> Order(joe, long_cart, large_order_promo) >>> Order(joe, cart, large_order_promo) # tag::STRATEGY_BEST_TESTS[] - >>> Order(joe, long_order, best_promo) + >>> Order(joe, long_cart, best_promo) >>> Order(joe, banana_cart, best_promo) diff --git a/10-dp-1class-func/untyped/strategy_best3.py b/10-dp-1class-func/untyped/strategy_best3.py index 8d21ffc..f911ce3 100644 --- a/10-dp-1class-func/untyped/strategy_best3.py +++ b/10-dp-1class-func/untyped/strategy_best3.py @@ -17,16 +17,16 @@ ... LineItem('apple', 10, 1.5)] >>> Order(joe, banana_cart, bulk_item_promo) - >>> long_order = [LineItem(str(item_code), 1, 1.0) + >>> long_cart = [LineItem(str(item_code), 1, 1.0) ... for item_code in range(10)] - >>> Order(joe, long_order, large_order_promo) + >>> Order(joe, long_cart, large_order_promo) >>> Order(joe, cart, large_order_promo) # tag::STRATEGY_BEST_TESTS[] - >>> Order(joe, long_order, best_promo) + >>> Order(joe, long_cart, best_promo) >>> Order(joe, banana_cart, best_promo) diff --git a/10-dp-1class-func/untyped/strategy_best4.py b/10-dp-1class-func/untyped/strategy_best4.py index b523752..9573176 100644 --- a/10-dp-1class-func/untyped/strategy_best4.py +++ b/10-dp-1class-func/untyped/strategy_best4.py @@ -17,16 +17,16 @@ ... LineItem('apple', 10, 1.5)] >>> Order(joe, banana_cart, bulk_item) - >>> long_order = [LineItem(str(item_code), 1, 1.0) + >>> long_cart = [LineItem(str(item_code), 1, 1.0) ... for item_code in range(10)] - >>> Order(joe, long_order, large_order) + >>> Order(joe, long_cart, large_order) >>> Order(joe, cart, large_order) # tag::STRATEGY_BEST_TESTS[] - >>> Order(joe, long_order, best_promo) + >>> Order(joe, long_cart, best_promo) >>> Order(joe, banana_cart, best_promo) diff --git a/10-dp-1class-func/untyped/strategy_param.py b/10-dp-1class-func/untyped/strategy_param.py index d5cc931..ab07132 100644 --- a/10-dp-1class-func/untyped/strategy_param.py +++ b/10-dp-1class-func/untyped/strategy_param.py @@ -15,9 +15,9 @@ ... LineItem('apple', 10, 1.5)] >>> Order(joe, banana_cart, bulk_item_promo(10)) - >>> long_order = [LineItem(str(item_code), 1, 1.0) + >>> long_cart = [LineItem(str(item_code), 1, 1.0) ... for item_code in range(10)] - >>> Order(joe, long_order, large_order_promo(7)) + >>> Order(joe, long_cart, large_order_promo(7)) >>> Order(joe, cart, large_order_promo(7)) diff --git a/10-dp-1class-func/untyped/strategy_param2.py b/10-dp-1class-func/untyped/strategy_param2.py index 625bbca..332f49d 100644 --- a/10-dp-1class-func/untyped/strategy_param2.py +++ b/10-dp-1class-func/untyped/strategy_param2.py @@ -15,9 +15,9 @@ ... LineItem('apple', 10, 1.5)] >>> Order(joe, banana_cart, BulkItemPromo(10)) - >>> long_order = [LineItem(str(item_code), 1, 1.0) + >>> long_cart = [LineItem(str(item_code), 1, 1.0) ... for item_code in range(10)] - >>> Order(joe, long_order, LargeOrderPromo(7)) + >>> Order(joe, long_cart, LargeOrderPromo(7)) >>> Order(joe, cart, LargeOrderPromo(7)) diff --git a/11-pythonic-obj/mem_test.py b/11-pythonic-obj/mem_test.py index 0d745f2..12fc54e 100644 --- a/11-pythonic-obj/mem_test.py +++ b/11-pythonic-obj/mem_test.py @@ -4,20 +4,25 @@ NUM_VECTORS = 10**7 +module = None if len(sys.argv) == 2: module_name = sys.argv[1].replace('.py', '') module = importlib.import_module(module_name) else: print(f'Usage: {sys.argv[0]} ') - sys.exit(2) # command line usage error -fmt = 'Selected Vector2d type: {.__name__}.{.__name__}' -print(fmt.format(module, module.Vector2d)) +if module is None: + print('Running test with built-in `complex`') + cls = complex +else: + fmt = 'Selected Vector2d type: {.__name__}.{.__name__}' + print(fmt.format(module, module.Vector2d)) + cls = module.Vector2d mem_init = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss -print(f'Creating {NUM_VECTORS:,} Vector2d instances') +print(f'Creating {NUM_VECTORS:,} {cls.__qualname__!r} instances') -vectors = [module.Vector2d(3.0, 4.0) for i in range(NUM_VECTORS)] +vectors = [cls(3.0, 4.0) for i in range(NUM_VECTORS)] mem_final = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss print(f'Initial RAM usage: {mem_init:14,}') diff --git a/11-pythonic-obj/patterns.py b/11-pythonic-obj/patterns.py new file mode 100644 index 0000000..9317a4c --- /dev/null +++ b/11-pythonic-obj/patterns.py @@ -0,0 +1,53 @@ +from vector2d_v3 import Vector2d + +# tag::KEYWORD_PATTERNS[] +def keyword_pattern_demo(v: Vector2d) -> None: + match v: + case Vector2d(x=0, y=0): + print(f'{v!r} is null') + case Vector2d(x=0): + print(f'{v!r} is vertical') + case Vector2d(y=0): + print(f'{v!r} is horizontal') + case Vector2d(x=x, y=y) if x==y: + print(f'{v!r} is diagonal') + case _: + print(f'{v!r} is awesome') +# end::KEYWORD_PATTERNS[] + +# tag::POSITIONAL_PATTERNS[] +def positional_pattern_demo(v: Vector2d) -> None: + match v: + case Vector2d(0, 0): + print(f'{v!r} is null') + case Vector2d(0): + print(f'{v!r} is vertical') + case Vector2d(_, 0): + print(f'{v!r} is horizontal') + case Vector2d(x, y) if x==y: + print(f'{v!r} is diagonal') + case _: + print(f'{v!r} is awesome') +# end::POSITIONAL_PATTERNS[] + + +def main(): + vectors = ( + Vector2d(1, 1), + Vector2d(0, 1), + Vector2d(1, 0), + Vector2d(1, 2), + Vector2d(0, 0), + ) + print('KEYWORD PATTERNS:') + for vector in vectors: + keyword_pattern_demo(vector) + + print('POSITIONAL PATTERNS:') + for vector in vectors: + positional_pattern_demo(vector) + + + +if __name__ == '__main__': + main() diff --git a/11-pythonic-obj/slots.rst b/11-pythonic-obj/slots.rst new file mode 100644 index 0000000..351c9de --- /dev/null +++ b/11-pythonic-obj/slots.rst @@ -0,0 +1,52 @@ +# tag::PIXEL[] +>>> class Pixel: +... __slots__ = ('x', 'y') # <1> +... +>>> p = Pixel() # <2> +>>> p.__dict__ # <3> +Traceback (most recent call last): + ... +AttributeError: 'Pixel' object has no attribute '__dict__' +>>> p.x = 10 # <4> +>>> p.y = 20 +>>> p.color = 'red' # <5> +Traceback (most recent call last): + ... +AttributeError: 'Pixel' object has no attribute 'color' + +# end::PIXEL[] + +# tag::OPEN_PIXEL[] +>>> class OpenPixel(Pixel): # <1> +... pass +... +>>> op = OpenPixel() +>>> op.__dict__ # <2> +{} +>>> op.x = 8 # <3> +>>> op.__dict__ # <4> +{} +>>> op.x # <5> +8 +>>> op.color = 'green' # <6> +>>> op.__dict__ # <7> +{'color': 'green'} + +# end::OPEN_PIXEL[] + +# tag::COLOR_PIXEL[] +>>> class ColorPixel(Pixel): +... __slots__ = ('color',) # <1> +>>> cp = ColorPixel() +>>> cp.__dict__ # <2> +Traceback (most recent call last): + ... +AttributeError: 'ColorPixel' object has no attribute '__dict__' +>>> cp.x = 2 +>>> cp.color = 'blue' # <3> +>>> cp.flavor = 'banana' +Traceback (most recent call last): + ... +AttributeError: 'ColorPixel' object has no attribute 'flavor' + +# end::COLOR_PIXEL[] diff --git a/11-pythonic-obj/vector2d_v3.py b/11-pythonic-obj/vector2d_v3.py index 376a8a4..9ee716c 100644 --- a/11-pythonic-obj/vector2d_v3.py +++ b/11-pythonic-obj/vector2d_v3.py @@ -72,7 +72,7 @@ >>> v1.x = 123 Traceback (most recent call last): ... - AttributeError: can't set attribute + AttributeError: can't set attribute 'x' Tests of hashing: @@ -90,6 +90,8 @@ import math class Vector2d: + __match_args__ = ('x', 'y') + typecode = 'd' def __init__(self, x, y): diff --git a/11-pythonic-obj/vector2d_v3_prophash.py b/11-pythonic-obj/vector2d_v3_prophash.py index 3552530..6d7dceb 100644 --- a/11-pythonic-obj/vector2d_v3_prophash.py +++ b/11-pythonic-obj/vector2d_v3_prophash.py @@ -72,7 +72,7 @@ >>> v1.x = 123 Traceback (most recent call last): ... - AttributeError: can't set attribute + AttributeError: can't set attribute 'x' # end::VECTOR2D_V3_HASH_DEMO[] @@ -112,7 +112,7 @@ def y(self): def __iter__(self): return (i for i in (self.x, self.y)) # <6> - # remaining methods follow (omitted in book listing) + # remaining methods: same as previous Vector2d # end::VECTOR2D_V3_PROP[] def __repr__(self): diff --git a/11-pythonic-obj/vector2d_v3_slots.py b/11-pythonic-obj/vector2d_v3_slots.py index 20b6da4..c48fb5b 100644 --- a/11-pythonic-obj/vector2d_v3_slots.py +++ b/11-pythonic-obj/vector2d_v3_slots.py @@ -72,7 +72,7 @@ >>> v1.x = 123 Traceback (most recent call last): ... - AttributeError: can't set attribute + AttributeError: can't set attribute 'x' Tests of hashing: @@ -90,11 +90,10 @@ # tag::VECTOR2D_V3_SLOTS[] class Vector2d: - __slots__ = ('__x', '__y') + __match_args__ = ('x', 'y') # <1> + __slots__ = ('__x', '__y') # <2> typecode = 'd' - - # methods follow (omitted in book listing) # end::VECTOR2D_V3_SLOTS[] def __init__(self, x, y): diff --git a/12-seq-hacking/vector_v3.py b/12-seq-hacking/vector_v3.py index 6ee18b5..c1f7859 100644 --- a/12-seq-hacking/vector_v3.py +++ b/12-seq-hacking/vector_v3.py @@ -199,15 +199,17 @@ def __getitem__(self, key): return self._components[index] # tag::VECTOR_V3_GETATTR[] - shortcut_names = 'xyzt' + __match_args__ = ('x', 'y', 'z', 't') # <1> def __getattr__(self, name): - cls = type(self) # <1> - if len(name) == 1: # <2> - pos = cls.shortcut_names.find(name) # <3> - if 0 <= pos < len(self._components): # <4> - return self._components[pos] - msg = f'{cls.__name__!r} object has no attribute {name!r}' # <5> + cls = type(self) # <2> + try: + pos = cls.__match_args__.index(name) # <3> + except ValueError: # <4> + pos = -1 + if 0 <= pos < len(self._components): # <5> + return self._components[pos] + msg = f'{cls.__name__!r} object has no attribute {name!r}' # <6> raise AttributeError(msg) # end::VECTOR_V3_GETATTR[] @@ -215,8 +217,8 @@ def __getattr__(self, name): def __setattr__(self, name, value): cls = type(self) if len(name) == 1: # <1> - if name in cls.shortcut_names: # <2> - error = 'read-only attribute {attr_name!r}' + if name in cls.__match_args__: # <2> + error = 'readonly attribute {attr_name!r}' elif name.islower(): # <3> error = "can't set attributes 'a' to 'z' in {cls_name!r}" else: diff --git a/12-seq-hacking/vector_v4.py b/12-seq-hacking/vector_v4.py index 95530eb..856f338 100644 --- a/12-seq-hacking/vector_v4.py +++ b/12-seq-hacking/vector_v4.py @@ -199,14 +199,16 @@ def __getitem__(self, key): index = operator.index(key) return self._components[index] - shortcut_names = 'xyzt' + __match_args__ = ('x', 'y', 'z', 't') def __getattr__(self, name): cls = type(self) - if len(name) == 1: - pos = cls.shortcut_names.find(name) - if 0 <= pos < len(self._components): - return self._components[pos] + try: + pos = cls.__match_args__.index(name) + except ValueError: + pos = -1 + if 0 <= pos < len(self._components): + return self._components[pos] msg = f'{cls.__name__!r} object has no attribute {name!r}' raise AttributeError(msg) diff --git a/12-seq-hacking/vector_v5.py b/12-seq-hacking/vector_v5.py index ebbd523..09b3044 100644 --- a/12-seq-hacking/vector_v5.py +++ b/12-seq-hacking/vector_v5.py @@ -242,14 +242,16 @@ def __getitem__(self, key): index = operator.index(key) return self._components[index] - shortcut_names = 'xyzt' + __match_args__ = ('x', 'y', 'z', 't') def __getattr__(self, name): cls = type(self) - if len(name) == 1: - pos = cls.shortcut_names.find(name) - if 0 <= pos < len(self._components): - return self._components[pos] + try: + pos = cls.__match_args__.index(name) + except ValueError: + pos = -1 + if 0 <= pos < len(self._components): + return self._components[pos] msg = f'{cls.__name__!r} object has no attribute {name!r}' raise AttributeError(msg) diff --git a/13-protocol-abc/frenchdeck2.py b/13-protocol-abc/frenchdeck2.py index e3ccc2c..612581d 100644 --- a/13-protocol-abc/frenchdeck2.py +++ b/13-protocol-abc/frenchdeck2.py @@ -1,8 +1,8 @@ -import collections +from collections import namedtuple, abc -Card = collections.namedtuple('Card', ['rank', 'suit']) +Card = namedtuple('Card', ['rank', 'suit']) -class FrenchDeck2(collections.MutableSequence): +class FrenchDeck2(abc.MutableSequence): ranks = [str(n) for n in range(2, 11)] + list('JQKA') suits = 'spades diamonds clubs hearts'.split() diff --git a/13-protocol-abc/typing/randompick_test.py b/13-protocol-abc/typing/randompick_test.py index f090022..115c4d3 100644 --- a/13-protocol-abc/typing/randompick_test.py +++ b/13-protocol-abc/typing/randompick_test.py @@ -12,14 +12,14 @@ def pick(self) -> Any: # <3> return self._items.pop() def test_isinstance() -> None: # <4> - popper = SimplePicker([1]) - assert isinstance(popper, RandomPicker) + popper: RandomPicker = SimplePicker([1]) # <5> + assert isinstance(popper, RandomPicker) # <6> -def test_item_type() -> None: # <5> +def test_item_type() -> None: # <7> items = [1, 2] popper = SimplePicker(items) item = popper.pick() assert item in items if TYPE_CHECKING: - reveal_type(item) # <6> + reveal_type(item) # <8> assert isinstance(item, int) diff --git a/13-protocol-abc/typing/vector2d_v4.py b/13-protocol-abc/typing/vector2d_v4.py index 1e9d4ac..deaa824 100644 --- a/13-protocol-abc/typing/vector2d_v4.py +++ b/13-protocol-abc/typing/vector2d_v4.py @@ -168,5 +168,5 @@ def __complex__(self): @classmethod def fromcomplex(cls, datum): - return Vector2d(datum.real, datum.imag) # <1> + return cls(datum.real, datum.imag) # <1> # end::VECTOR2D_V4_COMPLEX[] diff --git a/13-protocol-abc/typing/vector2d_v5.py b/13-protocol-abc/typing/vector2d_v5.py index ceda21c..378b826 100644 --- a/13-protocol-abc/typing/vector2d_v5.py +++ b/13-protocol-abc/typing/vector2d_v5.py @@ -170,5 +170,5 @@ def __complex__(self) -> complex: # <2> @classmethod def fromcomplex(cls, datum: SupportsComplex) -> Vector2d: # <3> c = complex(datum) # <4> - return Vector2d(c.real, c.imag) + return cls(c.real, c.imag) # end::VECTOR2D_V5_COMPLEX[] diff --git a/14-inheritance/uppermixin.py b/14-inheritance/uppermixin.py index d86ecea..12c4a17 100644 --- a/14-inheritance/uppermixin.py +++ b/14-inheritance/uppermixin.py @@ -1,10 +1,39 @@ -"""UpperDict uppercases all string keys. +""" +Short demos +=========== -Test for initializer. `str` keys are uppercased:: +``UpperDict`` behaves like a case-insensitive mapping`:: - >>> d = UpperDict([('a', 'letter A'), ('B', 'letter B'), (2, 'digit two')]) +# tag::UPPERDICT_DEMO[] + >>> d = UpperDict([('a', 'letter A'), (2, 'digit two')]) >>> list(d.keys()) - ['A', 'B', 2] + ['A', 2] + >>> d['b'] = 'letter B' + >>> 'b' in d + True + >>> d['a'], d.get('B') + ('letter A', 'letter B') + >>> list(d.keys()) + ['A', 2, 'B'] + +# end::UPPERDICT_DEMO[] + +And ``UpperCounter`` is also case-insensitive:: + +# tag::UPPERCOUNTER_DEMO[] + >>> c = UpperCounter('BaNanA') + >>> c.most_common() + [('A', 3), ('N', 2), ('B', 1)] + +# end::UPPERCOUNTER_DEMO[] + +Detailed tests +============== + +UpperDict uppercases all string keys. + + >>> d = UpperDict([('a', 'letter A'), ('B', 'letter B'), (2, 'digit two')]) + Tests for item retrieval using `d[key]` notation:: @@ -82,14 +111,12 @@ # tag::UPPERCASE_MIXIN[] import collections - def _upper(key): # <1> try: return key.upper() except AttributeError: return key - class UpperCaseMixin: # <2> def __setitem__(self, key, item): super().__setitem__(_upper(key), item) @@ -108,8 +135,6 @@ def __contains__(self, key): class UpperDict(UpperCaseMixin, collections.UserDict): # <1> pass - class UpperCounter(UpperCaseMixin, collections.Counter): # <2> """Specialized 'Counter' that uppercases string keys""" # <3> - # end::UPPERDICT[] diff --git a/15-more-types/protocol/abs_demo.py b/15-more-types/protocol/abs_demo.py index 9cb7f80..6af78ea 100755 --- a/15-more-types/protocol/abs_demo.py +++ b/15-more-types/protocol/abs_demo.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 +# tag::ABS_DEMO[] import math from typing import NamedTuple, SupportsAbs @@ -30,3 +31,4 @@ def is_unit(v: SupportsAbs[float]) -> bool: # <2> assert is_unit(v4) print('OK') +# end::ABS_DEMO[] diff --git a/16-op-overloading/vector2d_v3.py b/16-op-overloading/vector2d_v3.py index 5812dcf..9ee716c 100644 --- a/16-op-overloading/vector2d_v3.py +++ b/16-op-overloading/vector2d_v3.py @@ -1,5 +1,5 @@ """ -A 2-dimensional vector class +A two-dimensional vector class >>> v1 = Vector2d(3, 4) >>> print(v1.x, v1.y) @@ -72,7 +72,7 @@ >>> v1.x = 123 Traceback (most recent call last): ... - AttributeError: can't set attribute + AttributeError: can't set attribute 'x' Tests of hashing: @@ -81,7 +81,7 @@ >>> v2 = Vector2d(3.1, 4.2) >>> hash(v1), hash(v2) (7, 384307168202284039) - >>> len(set([v1, v2])) + >>> len({v1, v2}) 2 """ @@ -90,6 +90,8 @@ import math class Vector2d: + __match_args__ = ('x', 'y') + typecode = 'd' def __init__(self, x, y): diff --git a/16-op-overloading/vector_v6.py b/16-op-overloading/vector_v6.py index 7154851..6ae4dcf 100644 --- a/16-op-overloading/vector_v6.py +++ b/16-op-overloading/vector_v6.py @@ -305,14 +305,16 @@ def __getitem__(self, key): index = operator.index(key) return self._components[index] - shortcut_names = 'xyzt' + __match_args__ = ('x', 'y', 'z', 't') def __getattr__(self, name): cls = type(self) - if len(name) == 1: - pos = cls.shortcut_names.find(name) - if 0 <= pos < len(self._components): - return self._components[pos] + try: + pos = cls.__match_args__.index(name) + except ValueError: + pos = -1 + if 0 <= pos < len(self._components): + return self._components[pos] msg = f'{cls.__name__!r} object has no attribute {name!r}' raise AttributeError(msg) diff --git a/16-op-overloading/vector_v7.py b/16-op-overloading/vector_v7.py index e59c896..953622e 100644 --- a/16-op-overloading/vector_v7.py +++ b/16-op-overloading/vector_v7.py @@ -355,14 +355,16 @@ def __getitem__(self, key): index = operator.index(key) return self._components[index] - shortcut_names = 'xyzt' + __match_args__ = ('x', 'y', 'z', 't') def __getattr__(self, name): cls = type(self) - if len(name) == 1: - pos = cls.shortcut_names.find(name) - if 0 <= pos < len(self._components): - return self._components[pos] + try: + pos = cls.__match_args__.index(name) + except ValueError: + pos = -1 + if 0 <= pos < len(self._components): + return self._components[pos] msg = f'{cls.__name__!r} object has no attribute {name!r}' raise AttributeError(msg) diff --git a/16-op-overloading/vector_v8.py b/16-op-overloading/vector_v8.py index ee2cb48..44c05fd 100644 --- a/16-op-overloading/vector_v8.py +++ b/16-op-overloading/vector_v8.py @@ -361,14 +361,16 @@ def __getitem__(self, key): index = operator.index(key) return self._components[index] - shortcut_names = 'xyzt' + __match_args__ = ('x', 'y', 'z', 't') def __getattr__(self, name): cls = type(self) - if len(name) == 1: - pos = cls.shortcut_names.find(name) - if 0 <= pos < len(self._components): - return self._components[pos] + try: + pos = cls.__match_args__.index(name) + except ValueError: + pos = -1 + if 0 <= pos < len(self._components): + return self._components[pos] msg = f'{cls.__name__!r} object has no attribute {name!r}' raise AttributeError(msg) From fa1cd6bd5ef63e3623e71debe3f218a6befdbc64 Mon Sep 17 00:00:00 2001 From: kbaikov Date: Sun, 22 Aug 2021 16:29:22 +0200 Subject: [PATCH 103/166] Use fixture instead of yield_fixture The old @pytest.yield_fixture causes a deprecation warning: Use @pytest.fixture instead; they are the same. def records(): --- 23-dyn-attr-prop/oscon/test_schedule_v1.py | 2 +- 23-dyn-attr-prop/oscon/test_schedule_v2.py | 2 +- 23-dyn-attr-prop/oscon/test_schedule_v3.py | 2 +- 23-dyn-attr-prop/oscon/test_schedule_v4.py | 2 +- 23-dyn-attr-prop/oscon/test_schedule_v5.py | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/23-dyn-attr-prop/oscon/test_schedule_v1.py b/23-dyn-attr-prop/oscon/test_schedule_v1.py index 643f04f..cfd7dd0 100644 --- a/23-dyn-attr-prop/oscon/test_schedule_v1.py +++ b/23-dyn-attr-prop/oscon/test_schedule_v1.py @@ -3,7 +3,7 @@ import schedule_v1 as schedule -@pytest.yield_fixture +@pytest.fixture def records(): yield schedule.load(schedule.JSON_PATH) diff --git a/23-dyn-attr-prop/oscon/test_schedule_v2.py b/23-dyn-attr-prop/oscon/test_schedule_v2.py index a11b24d..e15d640 100644 --- a/23-dyn-attr-prop/oscon/test_schedule_v2.py +++ b/23-dyn-attr-prop/oscon/test_schedule_v2.py @@ -2,7 +2,7 @@ import schedule_v2 as schedule -@pytest.yield_fixture +@pytest.fixture def records(): yield schedule.load(schedule.JSON_PATH) diff --git a/23-dyn-attr-prop/oscon/test_schedule_v3.py b/23-dyn-attr-prop/oscon/test_schedule_v3.py index 1b7ea75..d9436d4 100644 --- a/23-dyn-attr-prop/oscon/test_schedule_v3.py +++ b/23-dyn-attr-prop/oscon/test_schedule_v3.py @@ -2,7 +2,7 @@ import schedule_v3 as schedule -@pytest.yield_fixture +@pytest.fixture def records(): yield schedule.load(schedule.JSON_PATH) diff --git a/23-dyn-attr-prop/oscon/test_schedule_v4.py b/23-dyn-attr-prop/oscon/test_schedule_v4.py index 1e5560a..4980bbd 100644 --- a/23-dyn-attr-prop/oscon/test_schedule_v4.py +++ b/23-dyn-attr-prop/oscon/test_schedule_v4.py @@ -2,7 +2,7 @@ import schedule_v4 as schedule -@pytest.yield_fixture +@pytest.fixture def records(): yield schedule.load(schedule.JSON_PATH) diff --git a/23-dyn-attr-prop/oscon/test_schedule_v5.py b/23-dyn-attr-prop/oscon/test_schedule_v5.py index 2151844..e3a5206 100644 --- a/23-dyn-attr-prop/oscon/test_schedule_v5.py +++ b/23-dyn-attr-prop/oscon/test_schedule_v5.py @@ -2,7 +2,7 @@ import schedule_v5 as schedule -@pytest.yield_fixture +@pytest.fixture def records(): yield schedule.load(schedule.JSON_PATH) From dd535abcf76739d451c2baaf7813b8390a7dfe88 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Wed, 25 Aug 2021 14:46:57 -0300 Subject: [PATCH 104/166] sync from Atlas --- 08-def-type-hints/columnize.py | 15 +++++++------ 15-more-types/cast/find.py | 2 +- 15-more-types/protocol/abs_demo.py | 4 ---- 15-more-types/protocol/mymax/mymax.py | 3 ++- 15-more-types/typeddict/books.py | 6 +++--- 15-more-types/typeddict/books_any.py | 4 ++-- 16-op-overloading/bingoaddable.py | 8 +++---- 17-it-generator/aritprog_v1.py | 2 +- 17-it-generator/aritprog_v2.py | 2 +- 17-it-generator/columnize_iter.py | 21 +++++++++--------- 17-it-generator/iter_gen_type.py | 13 +++++++++++ 17-it-generator/tree/step2/tree.py | 2 +- 23-dyn-attr-prop/oscon/data/osconfeed.json | 2 +- 23-dyn-attr-prop/oscon/demo_schedule2.py | 25 ---------------------- 23-dyn-attr-prop/oscon/test_schedule_v3.py | 2 +- 23-dyn-attr-prop/pseudo_construction.py | 2 +- 16 files changed, 51 insertions(+), 62 deletions(-) create mode 100644 17-it-generator/iter_gen_type.py delete mode 100755 23-dyn-attr-prop/oscon/demo_schedule2.py diff --git a/08-def-type-hints/columnize.py b/08-def-type-hints/columnize.py index 66b72d8..8f4e92b 100644 --- a/08-def-type-hints/columnize.py +++ b/08-def-type-hints/columnize.py @@ -1,9 +1,11 @@ # tag::COLUMNIZE[] from collections.abc import Sequence -def columnize(sequence: Sequence[str], num_columns: int = 0) -> list[tuple[str, ...]]: +def columnize( + sequence: Sequence[str], num_columns: int = 0 +) -> list[tuple[str, ...]]: if num_columns == 0: - num_columns = round(len(sequence) ** .5) + num_columns = round(len(sequence) ** 0.5) num_rows, reminder = divmod(len(sequence), num_columns) num_rows += bool(reminder) return [tuple(sequence[i::num_rows]) for i in range(num_rows)] @@ -11,10 +13,11 @@ def columnize(sequence: Sequence[str], num_columns: int = 0) -> list[tuple[str, def demo() -> None: - nato = ('Alfa Bravo Charlie Delta Echo Foxtrot Golf Hotel India' - ' Juliett Kilo Lima Mike November Oscar Papa Quebec Romeo' - ' Sierra Tango Uniform Victor Whiskey X-ray Yankee Zulu' - ).split() + nato = ( + 'Alfa Bravo Charlie Delta Echo Foxtrot Golf Hotel India' + ' Juliett Kilo Lima Mike November Oscar Papa Quebec Romeo' + ' Sierra Tango Uniform Victor Whiskey X-ray Yankee Zulu' + ).split() for row in columnize(nato, 4): for word in row: diff --git a/15-more-types/cast/find.py b/15-more-types/cast/find.py index c853ea8..e79fe5b 100644 --- a/15-more-types/cast/find.py +++ b/15-more-types/cast/find.py @@ -3,7 +3,7 @@ def find_first_str(a: list[object]) -> str: index = next(i for i, x in enumerate(a) if isinstance(x, str)) - # We only get here if there's at least one string in a + # We only get here if there's at least one string return cast(str, a[index]) # end::CAST[] diff --git a/15-more-types/protocol/abs_demo.py b/15-more-types/protocol/abs_demo.py index 6af78ea..9c0b74c 100755 --- a/15-more-types/protocol/abs_demo.py +++ b/15-more-types/protocol/abs_demo.py @@ -1,6 +1,3 @@ -#!/usr/bin/env python3 - -# tag::ABS_DEMO[] import math from typing import NamedTuple, SupportsAbs @@ -31,4 +28,3 @@ def is_unit(v: SupportsAbs[float]) -> bool: # <2> assert is_unit(v4) print('OK') -# end::ABS_DEMO[] diff --git a/15-more-types/protocol/mymax/mymax.py b/15-more-types/protocol/mymax/mymax.py index 5b9f4b2..6096985 100644 --- a/15-more-types/protocol/mymax/mymax.py +++ b/15-more-types/protocol/mymax/mymax.py @@ -1,5 +1,6 @@ # tag::MYMAX_TYPES[] -from typing import Protocol, Any, TypeVar, overload, Callable, Iterable, Union +from collections.abc import Callable, Iterable +from typing import Protocol, Any, TypeVar, overload, Union class SupportsLessThan(Protocol): def __lt__(self, other: Any) -> bool: ... diff --git a/15-more-types/typeddict/books.py b/15-more-types/typeddict/books.py index e33e21e..8caca46 100644 --- a/15-more-types/typeddict/books.py +++ b/15-more-types/typeddict/books.py @@ -1,6 +1,6 @@ +import json # tag::BOOKDICT[] from typing import TypedDict -import json class BookDict(TypedDict): isbn: str @@ -10,14 +10,14 @@ class BookDict(TypedDict): # end::BOOKDICT[] # tag::TOXML[] -AUTHOR_EL = '{}' +AUTHOR_ELEMENT = '{}' def to_xml(book: BookDict) -> str: # <1> elements: list[str] = [] # <2> for key, value in book.items(): if isinstance(value, list): # <3> elements.extend( - AUTHOR_EL.format(n) for n in value) # <4> + AUTHOR_ELEMENT.format(n) for n in value) # <4> else: tag = key.upper() elements.append(f'<{tag}>{value}') diff --git a/15-more-types/typeddict/books_any.py b/15-more-types/typeddict/books_any.py index 49a544e..0a5e1e4 100644 --- a/15-more-types/typeddict/books_any.py +++ b/15-more-types/typeddict/books_any.py @@ -10,13 +10,13 @@ class BookDict(TypedDict): # end::BOOKDICT[] # tag::TOXML[] -AUTHOR_EL = '{}' +AUTHOR_ELEMENT = '{}' def to_xml(book: BookDict) -> str: # <1> elements: List[str] = [] # <2> for key, value in book.items(): if isinstance(value, list): # <3> - elements.extend(AUTHOR_EL.format(n) + elements.extend(AUTHOR_ELEMENT.format(n) for n in value) else: tag = key.upper() diff --git a/16-op-overloading/bingoaddable.py b/16-op-overloading/bingoaddable.py index 38a70c7..da667d4 100644 --- a/16-op-overloading/bingoaddable.py +++ b/16-op-overloading/bingoaddable.py @@ -46,7 +46,7 @@ >>> globe += 1 # <6> Traceback (most recent call last): ... - TypeError: right operand in += must be 'AddableBingoCage' or an iterable + TypeError: right operand in += must be 'Tombola' or an iterable # end::ADDABLE_BINGO_IADD_DEMO[] @@ -72,9 +72,9 @@ def __iadd__(self, other): try: other_iterable = iter(other) # <4> except TypeError: # <5> - self_cls = type(self).__name__ - msg = "right operand in += must be {!r} or an iterable" - raise TypeError(msg.format(self_cls)) + msg = ('right operand in += must be ' + "'Tombola' or an iterable") + raise TypeError(msg) self.load(other_iterable) # <6> return self # <7> diff --git a/17-it-generator/aritprog_v1.py b/17-it-generator/aritprog_v1.py index 2ae66c4..69259bc 100644 --- a/17-it-generator/aritprog_v1.py +++ b/17-it-generator/aritprog_v1.py @@ -19,7 +19,7 @@ >>> from decimal import Decimal >>> ap = ArithmeticProgression(0, Decimal('.1'), .3) >>> list(ap) - [Decimal('0.0'), Decimal('0.1'), Decimal('0.2')] + [Decimal('0'), Decimal('0.1'), Decimal('0.2')] # end::ARITPROG_CLASS_DEMO[] """ diff --git a/17-it-generator/aritprog_v2.py b/17-it-generator/aritprog_v2.py index be211d8..42e27f8 100644 --- a/17-it-generator/aritprog_v2.py +++ b/17-it-generator/aritprog_v2.py @@ -14,7 +14,7 @@ >>> from decimal import Decimal >>> ap = aritprog_gen(0, Decimal('.1'), .3) >>> list(ap) - [Decimal('0.0'), Decimal('0.1'), Decimal('0.2')] + [Decimal('0'), Decimal('0.1'), Decimal('0.2')] """ diff --git a/17-it-generator/columnize_iter.py b/17-it-generator/columnize_iter.py index c9a6f7d..cee0f13 100644 --- a/17-it-generator/columnize_iter.py +++ b/17-it-generator/columnize_iter.py @@ -1,26 +1,27 @@ # tag::COLUMNIZE[] -from typing import Sequence, Tuple, Iterator +from collections.abc import Sequence, Iterator -def columnize(sequence: Sequence[str], num_columns: int = 0) -> Iterator[Tuple[str, ...]]: +def columnize( + sequence: Sequence[str], num_columns: int = 0 +) -> Iterator[tuple[str, ...]]: # <1> if num_columns == 0: - num_columns = round(len(sequence) ** .5) + num_columns = round(len(sequence) ** 0.5) num_rows, reminder = divmod(len(sequence), num_columns) num_rows += bool(reminder) - return (tuple(sequence[i::num_rows]) for i in range(num_rows)) + return (tuple(sequence[i::num_rows]) for i in range(num_rows)) # <2> # end::COLUMNIZE[] - def demo() -> None: - nato = ('Alfa Bravo Charlie Delta Echo Foxtrot Golf Hotel India' - ' Juliett Kilo Lima Mike November Oscar Papa Quebec Romeo' - ' Sierra Tango Uniform Victor Whiskey X-ray Yankee Zulu' - ).split() + nato = ( + 'Alfa Bravo Charlie Delta Echo Foxtrot Golf Hotel India' + ' Juliett Kilo Lima Mike November Oscar Papa Quebec Romeo' + ' Sierra Tango Uniform Victor Whiskey X-ray Yankee Zulu' + ).split() for row in columnize(nato, 4): for word in row: print(f'{word:15}', end='') print() - if __name__ == '__main__': demo() diff --git a/17-it-generator/iter_gen_type.py b/17-it-generator/iter_gen_type.py new file mode 100644 index 0000000..1730d87 --- /dev/null +++ b/17-it-generator/iter_gen_type.py @@ -0,0 +1,13 @@ +from collections.abc import Iterator +from keyword import kwlist +from typing import TYPE_CHECKING + +short_kw = (k for k in kwlist if len(k) < 5) # <1> + +if TYPE_CHECKING: + reveal_type(short_kw) # <2> + +long_kw: Iterator[str] = (k for k in kwlist if len(k) >= 4) # <3> + +if TYPE_CHECKING: # <4> + reveal_type(long_kw) diff --git a/17-it-generator/tree/step2/tree.py b/17-it-generator/tree/step2/tree.py index d4078b4..6a0e1fa 100644 --- a/17-it-generator/tree/step2/tree.py +++ b/17-it-generator/tree/step2/tree.py @@ -9,7 +9,7 @@ def sub_tree(cls): def display(cls): - for cls_name, level in tree(cls): + for cls_name, level in tree(cls): # <3> indent = ' ' * 4 * level print(f'{indent}{cls_name}') diff --git a/23-dyn-attr-prop/oscon/data/osconfeed.json b/23-dyn-attr-prop/oscon/data/osconfeed.json index 50ed313..7bea08d 100644 --- a/23-dyn-attr-prop/oscon/data/osconfeed.json +++ b/23-dyn-attr-prop/oscon/data/osconfeed.json @@ -1243,7 +1243,7 @@ "time_start": "2014-07-21 13:30:00", "time_stop": "2014-07-21 17:00:00", "venue_serial": 1451, - "description": "Metaprograming in Python is fun and profitable thanks to its rich Data Model \u2013 APIs that let you handle functions, modules and even classes as objects that you can create, inspect and modify at runtime. The Data Model also enables your own objects to support infix operators, become iterable and emulate collections. This workshop shows how, through a diverse selection of examples and exercises.", + "description": "Metaprogramming in Python is fun and profitable thanks to its rich Data Model \u2013 APIs that let you handle functions, modules and even classes as objects that you can create, inspect and modify at runtime. The Data Model also enables your own objects to support infix operators, become iterable and emulate collections. This workshop shows how, through a diverse selection of examples and exercises.", "website_url": "http://oscon.com/oscon2014/public/schedule/detail/34107", "speakers": [150170], "categories": [ diff --git a/23-dyn-attr-prop/oscon/demo_schedule2.py b/23-dyn-attr-prop/oscon/demo_schedule2.py deleted file mode 100755 index 12fd440..0000000 --- a/23-dyn-attr-prop/oscon/demo_schedule2.py +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env python3 - -import shelve - -from schedule_v2 import DB_NAME, CONFERENCE, load_db -from schedule_v2 import DbRecord, Event - -with shelve.open(DB_NAME) as db: - if CONFERENCE not in db: - load_db(db) - - DbRecord.set_db(db) - event = DbRecord.fetch('event.33950') - print(event) - print(event.venue) - print(event.venue.name) - for spkr in event.speakers: - print(f'{spkr.serial}:', spkr.name) - - print(repr(Event.venue)) - - event2 = DbRecord.fetch('event.33451') - print(event2) - print(event2.fetch) - print(event2.venue) \ No newline at end of file diff --git a/23-dyn-attr-prop/oscon/test_schedule_v3.py b/23-dyn-attr-prop/oscon/test_schedule_v3.py index d9436d4..12332c4 100644 --- a/23-dyn-attr-prop/oscon/test_schedule_v3.py +++ b/23-dyn-attr-prop/oscon/test_schedule_v3.py @@ -56,4 +56,4 @@ def test_event_speakers(): def test_event_no_speakers(): event = schedule.Record.fetch('event.36848') - assert event.speakers == [] \ No newline at end of file + assert event.speakers == [] diff --git a/23-dyn-attr-prop/pseudo_construction.py b/23-dyn-attr-prop/pseudo_construction.py index 5656192..af651a2 100644 --- a/23-dyn-attr-prop/pseudo_construction.py +++ b/23-dyn-attr-prop/pseudo_construction.py @@ -1,4 +1,4 @@ -# pseudo-code for object construction +# pseudocode for object construction def make(the_class, some_arg): new_object = the_class.__new__(some_arg) if isinstance(new_object, the_class): From 4ae4096c4cb8848d0c1dcf24ff2cb7efb72e3b2b Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Fri, 10 Sep 2021 12:34:39 -0300 Subject: [PATCH 105/166] renumbering chapters >= 19 --- .../primes/log-procs.txt | 0 .../primes/primes.py | 0 .../primes/procs.py | 0 .../primes/py36/primes.py | 0 .../primes/py36/procs.py | 0 .../primes/run_procs.sh | 0 .../primes/sequential.py | 0 .../primes/spinner_prime_async_broken.py | 0 .../primes/spinner_prime_async_nap.py | 0 .../primes/spinner_prime_proc.py | 0 .../primes/spinner_prime_thread.py | 0 .../primes/stats-procs.ipynb | 0 .../primes/threads.py | 0 .../spinner_async.py | 0 .../spinner_async_experiment.py | 0 .../spinner_proc.py | 0 .../spinner_thread.py | 0 19-coroutine/README.rst | 4 - 19-coroutine/coro_exc_demo.py | 66 ----- 19-coroutine/coro_finally_demo.py | 61 ----- 19-coroutine/coroaverager0.py | 28 -- 19-coroutine/coroaverager1.py | 30 --- 19-coroutine/coroaverager2.py | 61 ----- 19-coroutine/coroaverager3.py | 107 -------- 19-coroutine/coroutil.py | 12 - 19-coroutine/taxi_sim.py | 203 -------------- 19-coroutine/taxi_sim0.py | 255 ------------------ 19-coroutine/taxi_sim_delay.py | 215 --------------- 19-coroutine/yield_from_expansion.py | 52 ---- .../yield_from_expansion_simplified.py | 32 --- .../demo_executor_map.py | 0 .../getflags/.gitignore | 0 .../getflags/country_codes.txt | 0 .../getflags/downloaded/.gitignore | 0 {21-futures => 20-futures}/getflags/flags.py | 0 {21-futures => 20-futures}/getflags/flags.zip | Bin .../getflags/flags2_asyncio.py | 0 .../getflags/flags2_asyncio_executor.py | 0 .../getflags/flags2_common.py | 0 .../getflags/flags2_sequential.py | 0 .../getflags/flags2_threadpool.py | 0 .../getflags/flags3_asyncio.py | 0 .../getflags/flags_asyncio.py | 0 .../getflags/flags_threadpool.py | 0 .../getflags/flags_threadpool_futures.py | 0 .../getflags/requirements.txt | 0 .../getflags/slow_server.py | 0 {21-futures => 20-futures}/primes/primes.py | 0 .../primes/proc_pool.py | 0 {22-async => 21-async}/README.rst | 0 {22-async => 21-async}/domains/README.rst | 0 .../domains/asyncio/blogdom.py | 0 .../domains/asyncio/domaincheck.py | 0 .../domains/asyncio/domainlib.py | 0 .../domains/curio/blogdom.py | 0 .../domains/curio/domaincheck.py | 0 .../domains/curio/domainlib.py | 0 .../domains/curio/requirements.txt | 0 {22-async => 21-async}/mojifinder/README.md | 0 {22-async => 21-async}/mojifinder/bottle.py | 0 .../mojifinder/charindex.py | 0 .../mojifinder/requirements.txt | 0 .../mojifinder/static/form.html | 0 .../mojifinder/tcp_mojifinder.py | 0 .../mojifinder/web_mojifinder.py | 0 .../mojifinder/web_mojifinder_bottle.py | 0 .../README.rst | 0 .../blackknight.py | 0 .../bulkfood/bulkfood_v1.py | 0 .../bulkfood/bulkfood_v2.py | 0 .../bulkfood/bulkfood_v2b.py | 0 .../bulkfood/bulkfood_v2prop.py | 0 .../doc_property.py | 0 .../oscon/data/osconfeed.json | 0 .../oscon/demo_schedule2.py | 0 .../oscon/explore0.py | 0 .../oscon/explore1.py | 0 .../oscon/explore2.py | 0 .../oscon/osconfeed-sample.json | 0 .../oscon/osconfeed_explore.rst | 0 .../oscon/runtests.sh | 0 .../oscon/schedule_v1.py | 0 .../oscon/schedule_v2.py | 0 .../oscon/schedule_v3.py | 0 .../oscon/schedule_v4.py | 0 .../oscon/schedule_v4_hasattr.py | 0 .../oscon/schedule_v5.py | 0 .../oscon/test_schedule_v1.py | 0 .../oscon/test_schedule_v2.py | 0 .../oscon/test_schedule_v3.py | 0 .../oscon/test_schedule_v4.py | 0 .../oscon/test_schedule_v5.py | 0 .../pseudo_construction.py | 0 {24-descriptor => 23-descriptor}/README.rst | 0 .../bulkfood/bulkfood_v3.py | 0 .../bulkfood/bulkfood_v4.py | 0 .../bulkfood/bulkfood_v4c.py | 0 .../bulkfood/bulkfood_v5.py | 0 .../bulkfood/model_v4c.py | 0 .../bulkfood/model_v5.py | 0 .../descriptorkinds.py | 0 .../descriptorkinds_dump.py | 0 .../method_is_descriptor.py | 0 .../autoconst/autoconst.py | 0 .../autoconst/autoconst_demo.py | 0 .../bulkfood/README.md | 0 .../bulkfood/bulkfood_v6.py | 0 .../bulkfood/bulkfood_v7.py | 0 .../bulkfood/bulkfood_v8.py | 0 .../bulkfood/model_v6.py | 0 .../bulkfood/model_v7.py | 0 .../bulkfood/model_v8.py | 0 .../checked/decorator/checkeddeco.py | 0 .../checked/decorator/checkeddeco_demo.py | 0 .../checked/decorator/checkeddeco_test.py | 0 .../checked/initsub/checked_demo.py | 0 .../checked/initsub/checkedlib.py | 0 .../checked/initsub/checkedlib_test.py | 0 .../checked/metaclass/checked_demo.py | 0 .../checked/metaclass/checkedlib.py | 0 .../checked/metaclass/checkedlib_test.py | 0 .../evaltime/builderlib.py | 0 .../evaltime/evaldemo.py | 0 .../evaltime/evaldemo_meta.py | 0 .../evaltime/metalib.py | 0 .../factories.py | 0 .../factories_ducktyped.py | 0 .../hours/hours.py | 0 .../hours/hours_test.py | 0 .../metabunch/README.md | 0 .../metabunch/from3.6/bunch.py | 0 .../metabunch/from3.6/bunch_test.py | 0 .../metabunch/nutshell3e/bunch.py | 0 .../metabunch/nutshell3e/bunch_test.py | 0 .../metabunch/original/bunch.py | 0 .../metabunch/original/bunch_test.py | 0 .../metabunch/pre3.6/bunch.py | 0 .../metabunch/pre3.6/bunch_test.py | 0 .../persistent/.gitignore | 0 .../persistent/dblib.py | 0 .../persistent/dblib_test.py | 0 .../persistent/persistlib.py | 0 .../persistent/persistlib_test.py | 0 .../qualname/fakedjango.py | 0 .../qualname/models.py | 0 .../sentinel/sentinel.py | 0 .../sentinel/sentinel_test.py | 0 .../setattr/example_from_leo.py | 0 .../slots/slots_timing.py | 0 .../tinyenums/microenum.py | 0 .../tinyenums/microenum_demo.py | 0 .../tinyenums/nanoenum.py | 0 .../tinyenums/nanoenum_demo.py | 0 README.md | 15 +- 154 files changed, 7 insertions(+), 1134 deletions(-) rename {20-concurrency => 19-concurrency}/primes/log-procs.txt (100%) rename {20-concurrency => 19-concurrency}/primes/primes.py (100%) rename {20-concurrency => 19-concurrency}/primes/procs.py (100%) rename {20-concurrency => 19-concurrency}/primes/py36/primes.py (100%) rename {20-concurrency => 19-concurrency}/primes/py36/procs.py (100%) rename {20-concurrency => 19-concurrency}/primes/run_procs.sh (100%) rename {20-concurrency => 19-concurrency}/primes/sequential.py (100%) rename {20-concurrency => 19-concurrency}/primes/spinner_prime_async_broken.py (100%) rename {20-concurrency => 19-concurrency}/primes/spinner_prime_async_nap.py (100%) rename {20-concurrency => 19-concurrency}/primes/spinner_prime_proc.py (100%) rename {20-concurrency => 19-concurrency}/primes/spinner_prime_thread.py (100%) rename {20-concurrency => 19-concurrency}/primes/stats-procs.ipynb (100%) rename {20-concurrency => 19-concurrency}/primes/threads.py (100%) rename {20-concurrency => 19-concurrency}/spinner_async.py (100%) rename {20-concurrency => 19-concurrency}/spinner_async_experiment.py (100%) rename {20-concurrency => 19-concurrency}/spinner_proc.py (100%) rename {20-concurrency => 19-concurrency}/spinner_thread.py (100%) delete mode 100644 19-coroutine/README.rst delete mode 100644 19-coroutine/coro_exc_demo.py delete mode 100644 19-coroutine/coro_finally_demo.py delete mode 100644 19-coroutine/coroaverager0.py delete mode 100644 19-coroutine/coroaverager1.py delete mode 100644 19-coroutine/coroaverager2.py delete mode 100644 19-coroutine/coroaverager3.py delete mode 100644 19-coroutine/coroutil.py delete mode 100644 19-coroutine/taxi_sim.py delete mode 100644 19-coroutine/taxi_sim0.py delete mode 100644 19-coroutine/taxi_sim_delay.py delete mode 100644 19-coroutine/yield_from_expansion.py delete mode 100644 19-coroutine/yield_from_expansion_simplified.py rename {21-futures => 20-futures}/demo_executor_map.py (100%) rename {21-futures => 20-futures}/getflags/.gitignore (100%) rename {21-futures => 20-futures}/getflags/country_codes.txt (100%) rename {21-futures => 20-futures}/getflags/downloaded/.gitignore (100%) rename {21-futures => 20-futures}/getflags/flags.py (100%) rename {21-futures => 20-futures}/getflags/flags.zip (100%) rename {21-futures => 20-futures}/getflags/flags2_asyncio.py (100%) rename {21-futures => 20-futures}/getflags/flags2_asyncio_executor.py (100%) rename {21-futures => 20-futures}/getflags/flags2_common.py (100%) rename {21-futures => 20-futures}/getflags/flags2_sequential.py (100%) rename {21-futures => 20-futures}/getflags/flags2_threadpool.py (100%) rename {21-futures => 20-futures}/getflags/flags3_asyncio.py (100%) rename {21-futures => 20-futures}/getflags/flags_asyncio.py (100%) rename {21-futures => 20-futures}/getflags/flags_threadpool.py (100%) rename {21-futures => 20-futures}/getflags/flags_threadpool_futures.py (100%) rename {21-futures => 20-futures}/getflags/requirements.txt (100%) rename {21-futures => 20-futures}/getflags/slow_server.py (100%) rename {21-futures => 20-futures}/primes/primes.py (100%) rename {21-futures => 20-futures}/primes/proc_pool.py (100%) rename {22-async => 21-async}/README.rst (100%) rename {22-async => 21-async}/domains/README.rst (100%) rename {22-async => 21-async}/domains/asyncio/blogdom.py (100%) rename {22-async => 21-async}/domains/asyncio/domaincheck.py (100%) rename {22-async => 21-async}/domains/asyncio/domainlib.py (100%) rename {22-async => 21-async}/domains/curio/blogdom.py (100%) rename {22-async => 21-async}/domains/curio/domaincheck.py (100%) rename {22-async => 21-async}/domains/curio/domainlib.py (100%) rename {22-async => 21-async}/domains/curio/requirements.txt (100%) rename {22-async => 21-async}/mojifinder/README.md (100%) rename {22-async => 21-async}/mojifinder/bottle.py (100%) rename {22-async => 21-async}/mojifinder/charindex.py (100%) rename {22-async => 21-async}/mojifinder/requirements.txt (100%) rename {22-async => 21-async}/mojifinder/static/form.html (100%) rename {22-async => 21-async}/mojifinder/tcp_mojifinder.py (100%) rename {22-async => 21-async}/mojifinder/web_mojifinder.py (100%) rename {22-async => 21-async}/mojifinder/web_mojifinder_bottle.py (100%) rename {23-dyn-attr-prop => 22-dyn-attr-prop}/README.rst (100%) rename {23-dyn-attr-prop => 22-dyn-attr-prop}/blackknight.py (100%) rename {23-dyn-attr-prop => 22-dyn-attr-prop}/bulkfood/bulkfood_v1.py (100%) rename {23-dyn-attr-prop => 22-dyn-attr-prop}/bulkfood/bulkfood_v2.py (100%) rename {23-dyn-attr-prop => 22-dyn-attr-prop}/bulkfood/bulkfood_v2b.py (100%) rename {23-dyn-attr-prop => 22-dyn-attr-prop}/bulkfood/bulkfood_v2prop.py (100%) rename {23-dyn-attr-prop => 22-dyn-attr-prop}/doc_property.py (100%) rename {23-dyn-attr-prop => 22-dyn-attr-prop}/oscon/data/osconfeed.json (100%) rename {23-dyn-attr-prop => 22-dyn-attr-prop}/oscon/demo_schedule2.py (100%) rename {23-dyn-attr-prop => 22-dyn-attr-prop}/oscon/explore0.py (100%) rename {23-dyn-attr-prop => 22-dyn-attr-prop}/oscon/explore1.py (100%) rename {23-dyn-attr-prop => 22-dyn-attr-prop}/oscon/explore2.py (100%) rename {23-dyn-attr-prop => 22-dyn-attr-prop}/oscon/osconfeed-sample.json (100%) rename {23-dyn-attr-prop => 22-dyn-attr-prop}/oscon/osconfeed_explore.rst (100%) rename {23-dyn-attr-prop => 22-dyn-attr-prop}/oscon/runtests.sh (100%) rename {23-dyn-attr-prop => 22-dyn-attr-prop}/oscon/schedule_v1.py (100%) rename {23-dyn-attr-prop => 22-dyn-attr-prop}/oscon/schedule_v2.py (100%) rename {23-dyn-attr-prop => 22-dyn-attr-prop}/oscon/schedule_v3.py (100%) rename {23-dyn-attr-prop => 22-dyn-attr-prop}/oscon/schedule_v4.py (100%) rename {23-dyn-attr-prop => 22-dyn-attr-prop}/oscon/schedule_v4_hasattr.py (100%) rename {23-dyn-attr-prop => 22-dyn-attr-prop}/oscon/schedule_v5.py (100%) rename {23-dyn-attr-prop => 22-dyn-attr-prop}/oscon/test_schedule_v1.py (100%) rename {23-dyn-attr-prop => 22-dyn-attr-prop}/oscon/test_schedule_v2.py (100%) rename {23-dyn-attr-prop => 22-dyn-attr-prop}/oscon/test_schedule_v3.py (100%) rename {23-dyn-attr-prop => 22-dyn-attr-prop}/oscon/test_schedule_v4.py (100%) rename {23-dyn-attr-prop => 22-dyn-attr-prop}/oscon/test_schedule_v5.py (100%) rename {23-dyn-attr-prop => 22-dyn-attr-prop}/pseudo_construction.py (100%) rename {24-descriptor => 23-descriptor}/README.rst (100%) rename {24-descriptor => 23-descriptor}/bulkfood/bulkfood_v3.py (100%) rename {24-descriptor => 23-descriptor}/bulkfood/bulkfood_v4.py (100%) rename {24-descriptor => 23-descriptor}/bulkfood/bulkfood_v4c.py (100%) rename {24-descriptor => 23-descriptor}/bulkfood/bulkfood_v5.py (100%) rename {24-descriptor => 23-descriptor}/bulkfood/model_v4c.py (100%) rename {24-descriptor => 23-descriptor}/bulkfood/model_v5.py (100%) rename {24-descriptor => 23-descriptor}/descriptorkinds.py (100%) rename {24-descriptor => 23-descriptor}/descriptorkinds_dump.py (100%) rename {24-descriptor => 23-descriptor}/method_is_descriptor.py (100%) rename {25-class-metaprog => 24-class-metaprog}/autoconst/autoconst.py (100%) rename {25-class-metaprog => 24-class-metaprog}/autoconst/autoconst_demo.py (100%) rename {25-class-metaprog => 24-class-metaprog}/bulkfood/README.md (100%) rename {25-class-metaprog => 24-class-metaprog}/bulkfood/bulkfood_v6.py (100%) rename {25-class-metaprog => 24-class-metaprog}/bulkfood/bulkfood_v7.py (100%) rename {25-class-metaprog => 24-class-metaprog}/bulkfood/bulkfood_v8.py (100%) rename {25-class-metaprog => 24-class-metaprog}/bulkfood/model_v6.py (100%) rename {25-class-metaprog => 24-class-metaprog}/bulkfood/model_v7.py (100%) rename {25-class-metaprog => 24-class-metaprog}/bulkfood/model_v8.py (100%) rename {25-class-metaprog => 24-class-metaprog}/checked/decorator/checkeddeco.py (100%) rename {25-class-metaprog => 24-class-metaprog}/checked/decorator/checkeddeco_demo.py (100%) rename {25-class-metaprog => 24-class-metaprog}/checked/decorator/checkeddeco_test.py (100%) rename {25-class-metaprog => 24-class-metaprog}/checked/initsub/checked_demo.py (100%) rename {25-class-metaprog => 24-class-metaprog}/checked/initsub/checkedlib.py (100%) rename {25-class-metaprog => 24-class-metaprog}/checked/initsub/checkedlib_test.py (100%) rename {25-class-metaprog => 24-class-metaprog}/checked/metaclass/checked_demo.py (100%) rename {25-class-metaprog => 24-class-metaprog}/checked/metaclass/checkedlib.py (100%) rename {25-class-metaprog => 24-class-metaprog}/checked/metaclass/checkedlib_test.py (100%) rename {25-class-metaprog => 24-class-metaprog}/evaltime/builderlib.py (100%) rename {25-class-metaprog => 24-class-metaprog}/evaltime/evaldemo.py (100%) rename {25-class-metaprog => 24-class-metaprog}/evaltime/evaldemo_meta.py (100%) rename {25-class-metaprog => 24-class-metaprog}/evaltime/metalib.py (100%) rename {25-class-metaprog => 24-class-metaprog}/factories.py (100%) rename {25-class-metaprog => 24-class-metaprog}/factories_ducktyped.py (100%) rename {25-class-metaprog => 24-class-metaprog}/hours/hours.py (100%) rename {25-class-metaprog => 24-class-metaprog}/hours/hours_test.py (100%) rename {25-class-metaprog => 24-class-metaprog}/metabunch/README.md (100%) rename {25-class-metaprog => 24-class-metaprog}/metabunch/from3.6/bunch.py (100%) rename {25-class-metaprog => 24-class-metaprog}/metabunch/from3.6/bunch_test.py (100%) rename {25-class-metaprog => 24-class-metaprog}/metabunch/nutshell3e/bunch.py (100%) rename {25-class-metaprog => 24-class-metaprog}/metabunch/nutshell3e/bunch_test.py (100%) rename {25-class-metaprog => 24-class-metaprog}/metabunch/original/bunch.py (100%) rename {25-class-metaprog => 24-class-metaprog}/metabunch/original/bunch_test.py (100%) rename {25-class-metaprog => 24-class-metaprog}/metabunch/pre3.6/bunch.py (100%) rename {25-class-metaprog => 24-class-metaprog}/metabunch/pre3.6/bunch_test.py (100%) rename {25-class-metaprog => 24-class-metaprog}/persistent/.gitignore (100%) rename {25-class-metaprog => 24-class-metaprog}/persistent/dblib.py (100%) rename {25-class-metaprog => 24-class-metaprog}/persistent/dblib_test.py (100%) rename {25-class-metaprog => 24-class-metaprog}/persistent/persistlib.py (100%) rename {25-class-metaprog => 24-class-metaprog}/persistent/persistlib_test.py (100%) rename {25-class-metaprog => 24-class-metaprog}/qualname/fakedjango.py (100%) rename {25-class-metaprog => 24-class-metaprog}/qualname/models.py (100%) rename {25-class-metaprog => 24-class-metaprog}/sentinel/sentinel.py (100%) rename {25-class-metaprog => 24-class-metaprog}/sentinel/sentinel_test.py (100%) rename {25-class-metaprog => 24-class-metaprog}/setattr/example_from_leo.py (100%) rename {25-class-metaprog => 24-class-metaprog}/slots/slots_timing.py (100%) rename {25-class-metaprog => 24-class-metaprog}/tinyenums/microenum.py (100%) rename {25-class-metaprog => 24-class-metaprog}/tinyenums/microenum_demo.py (100%) rename {25-class-metaprog => 24-class-metaprog}/tinyenums/nanoenum.py (100%) rename {25-class-metaprog => 24-class-metaprog}/tinyenums/nanoenum_demo.py (100%) diff --git a/20-concurrency/primes/log-procs.txt b/19-concurrency/primes/log-procs.txt similarity index 100% rename from 20-concurrency/primes/log-procs.txt rename to 19-concurrency/primes/log-procs.txt diff --git a/20-concurrency/primes/primes.py b/19-concurrency/primes/primes.py similarity index 100% rename from 20-concurrency/primes/primes.py rename to 19-concurrency/primes/primes.py diff --git a/20-concurrency/primes/procs.py b/19-concurrency/primes/procs.py similarity index 100% rename from 20-concurrency/primes/procs.py rename to 19-concurrency/primes/procs.py diff --git a/20-concurrency/primes/py36/primes.py b/19-concurrency/primes/py36/primes.py similarity index 100% rename from 20-concurrency/primes/py36/primes.py rename to 19-concurrency/primes/py36/primes.py diff --git a/20-concurrency/primes/py36/procs.py b/19-concurrency/primes/py36/procs.py similarity index 100% rename from 20-concurrency/primes/py36/procs.py rename to 19-concurrency/primes/py36/procs.py diff --git a/20-concurrency/primes/run_procs.sh b/19-concurrency/primes/run_procs.sh similarity index 100% rename from 20-concurrency/primes/run_procs.sh rename to 19-concurrency/primes/run_procs.sh diff --git a/20-concurrency/primes/sequential.py b/19-concurrency/primes/sequential.py similarity index 100% rename from 20-concurrency/primes/sequential.py rename to 19-concurrency/primes/sequential.py diff --git a/20-concurrency/primes/spinner_prime_async_broken.py b/19-concurrency/primes/spinner_prime_async_broken.py similarity index 100% rename from 20-concurrency/primes/spinner_prime_async_broken.py rename to 19-concurrency/primes/spinner_prime_async_broken.py diff --git a/20-concurrency/primes/spinner_prime_async_nap.py b/19-concurrency/primes/spinner_prime_async_nap.py similarity index 100% rename from 20-concurrency/primes/spinner_prime_async_nap.py rename to 19-concurrency/primes/spinner_prime_async_nap.py diff --git a/20-concurrency/primes/spinner_prime_proc.py b/19-concurrency/primes/spinner_prime_proc.py similarity index 100% rename from 20-concurrency/primes/spinner_prime_proc.py rename to 19-concurrency/primes/spinner_prime_proc.py diff --git a/20-concurrency/primes/spinner_prime_thread.py b/19-concurrency/primes/spinner_prime_thread.py similarity index 100% rename from 20-concurrency/primes/spinner_prime_thread.py rename to 19-concurrency/primes/spinner_prime_thread.py diff --git a/20-concurrency/primes/stats-procs.ipynb b/19-concurrency/primes/stats-procs.ipynb similarity index 100% rename from 20-concurrency/primes/stats-procs.ipynb rename to 19-concurrency/primes/stats-procs.ipynb diff --git a/20-concurrency/primes/threads.py b/19-concurrency/primes/threads.py similarity index 100% rename from 20-concurrency/primes/threads.py rename to 19-concurrency/primes/threads.py diff --git a/20-concurrency/spinner_async.py b/19-concurrency/spinner_async.py similarity index 100% rename from 20-concurrency/spinner_async.py rename to 19-concurrency/spinner_async.py diff --git a/20-concurrency/spinner_async_experiment.py b/19-concurrency/spinner_async_experiment.py similarity index 100% rename from 20-concurrency/spinner_async_experiment.py rename to 19-concurrency/spinner_async_experiment.py diff --git a/20-concurrency/spinner_proc.py b/19-concurrency/spinner_proc.py similarity index 100% rename from 20-concurrency/spinner_proc.py rename to 19-concurrency/spinner_proc.py diff --git a/20-concurrency/spinner_thread.py b/19-concurrency/spinner_thread.py similarity index 100% rename from 20-concurrency/spinner_thread.py rename to 19-concurrency/spinner_thread.py diff --git a/19-coroutine/README.rst b/19-coroutine/README.rst deleted file mode 100644 index 2771530..0000000 --- a/19-coroutine/README.rst +++ /dev/null @@ -1,4 +0,0 @@ -Sample code for Chapter 16 - "Coroutines" - -From the book "Fluent Python" by Luciano Ramalho (O'Reilly, 2015) -http://shop.oreilly.com/product/0636920032519.do diff --git a/19-coroutine/coro_exc_demo.py b/19-coroutine/coro_exc_demo.py deleted file mode 100644 index a68684f..0000000 --- a/19-coroutine/coro_exc_demo.py +++ /dev/null @@ -1,66 +0,0 @@ -""" -Coroutine closing demonstration:: - -# tag::DEMO_CORO_EXC_1[] - >>> exc_coro = demo_exc_handling() - >>> next(exc_coro) - -> coroutine started - >>> exc_coro.send(11) - -> coroutine received: 11 - >>> exc_coro.send(22) - -> coroutine received: 22 - >>> exc_coro.close() - >>> from inspect import getgeneratorstate - >>> getgeneratorstate(exc_coro) - 'GEN_CLOSED' - -# end::DEMO_CORO_EXC_1[] - -Coroutine handling exception:: - -# tag::DEMO_CORO_EXC_2[] - >>> exc_coro = demo_exc_handling() - >>> next(exc_coro) - -> coroutine started - >>> exc_coro.send(11) - -> coroutine received: 11 - >>> exc_coro.throw(DemoException) - *** DemoException handled. Continuing... - >>> getgeneratorstate(exc_coro) - 'GEN_SUSPENDED' - -# end::DEMO_CORO_EXC_2[] - -Coroutine not handling exception:: - -# tag::DEMO_CORO_EXC_3[] - >>> exc_coro = demo_exc_handling() - >>> next(exc_coro) - -> coroutine started - >>> exc_coro.send(11) - -> coroutine received: 11 - >>> exc_coro.throw(ZeroDivisionError) - Traceback (most recent call last): - ... - ZeroDivisionError - >>> getgeneratorstate(exc_coro) - 'GEN_CLOSED' - -# end::DEMO_CORO_EXC_3[] -""" - -# tag::EX_CORO_EXC[] -class DemoException(Exception): - """An exception type for the demonstration.""" - -def demo_exc_handling(): - print('-> coroutine started') - while True: - try: - x = yield - except DemoException: # <1> - print('*** DemoException handled. Continuing...') - else: # <2> - print(f'-> coroutine received: {x!r}') - raise RuntimeError('This line should never run.') # <3> -# end::EX_CORO_EXC[] diff --git a/19-coroutine/coro_finally_demo.py b/19-coroutine/coro_finally_demo.py deleted file mode 100644 index fd4545d..0000000 --- a/19-coroutine/coro_finally_demo.py +++ /dev/null @@ -1,61 +0,0 @@ -""" -Second coroutine closing demonstration:: - - >>> fin_coro = demo_finally() - >>> next(fin_coro) - -> coroutine started - >>> fin_coro.send(11) - -> coroutine received: 11 - >>> fin_coro.send(22) - -> coroutine received: 22 - >>> fin_coro.close() - -> coroutine ending - - -Second coroutine not handling exception:: - - >>> fin_coro = demo_finally() - >>> next(fin_coro) - -> coroutine started - >>> fin_coro.send(11) - -> coroutine received: 11 - >>> fin_coro.throw(ZeroDivisionError) # doctest: +SKIP - -> coroutine ending - Traceback (most recent call last): - File "", line 1, in - File "coro_exception_demos.py", line 109, in demo_finally - print(f'-> coroutine received: {x!r}') - ZeroDivisionError - - -The last test above must be skipped because the output '-> coroutine ending' -is not detected by doctest, which raises a false error. However, if you -run this file as shown below, you'll see that output "leak" into standard -output:: - - - $ python3 -m doctest coro_exception_demo.py - -> coroutine ending - -""" - - -# tag::EX_CORO_FINALLY[] -class DemoException(Exception): - """An exception type for the demonstration.""" - - -def demo_finally(): - print('-> coroutine started') - try: - while True: - try: - x = yield - except DemoException: - print('*** DemoException handled. Continuing...') - else: - print(f'-> coroutine received: {x!r}') - finally: - print('-> coroutine ending') - -# end::EX_CORO_FINALLY[] diff --git a/19-coroutine/coroaverager0.py b/19-coroutine/coroaverager0.py deleted file mode 100644 index 10ccfdc..0000000 --- a/19-coroutine/coroaverager0.py +++ /dev/null @@ -1,28 +0,0 @@ -""" -A coroutine to compute a running average - -# tag::CORO_AVERAGER_TEST[] - >>> coro_avg = averager() # <1> - >>> next(coro_avg) # <2> - >>> coro_avg.send(10) # <3> - 10.0 - >>> coro_avg.send(30) - 20.0 - >>> coro_avg.send(5) - 15.0 - -# end::CORO_AVERAGER_TEST[] - -""" - -# tag::CORO_AVERAGER[] -def averager(): - total = 0.0 - count = 0 - average = None - while True: # <1> - term = yield average # <2> - total += term - count += 1 - average = total/count -# end::CORO_AVERAGER[] diff --git a/19-coroutine/coroaverager1.py b/19-coroutine/coroaverager1.py deleted file mode 100644 index 3b3c5e9..0000000 --- a/19-coroutine/coroaverager1.py +++ /dev/null @@ -1,30 +0,0 @@ -# tag::DECORATED_AVERAGER[] -""" -A coroutine to compute a running average - - >>> coro_avg = averager() # <1> - >>> from inspect import getgeneratorstate - >>> getgeneratorstate(coro_avg) # <2> - 'GEN_SUSPENDED' - >>> coro_avg.send(10) # <3> - 10.0 - >>> coro_avg.send(30) - 20.0 - >>> coro_avg.send(5) - 15.0 - -""" - -from coroutil import coroutine # <4> - -@coroutine # <5> -def averager(): # <6> - total = 0.0 - count = 0 - average = None - while True: - term = yield average - total += term - count += 1 - average = total/count -# end::DECORATED_AVERAGER[] diff --git a/19-coroutine/coroaverager2.py b/19-coroutine/coroaverager2.py deleted file mode 100644 index 9d724c0..0000000 --- a/19-coroutine/coroaverager2.py +++ /dev/null @@ -1,61 +0,0 @@ -""" -A coroutine to compute a running average. - -Testing ``averager`` by itself:: - -# tag::RETURNING_AVERAGER_DEMO1[] - - >>> coro_avg = averager() - >>> next(coro_avg) - >>> coro_avg.send(10) # <1> - >>> coro_avg.send(30) - >>> coro_avg.send(6.5) - >>> coro_avg.send(None) # <2> - Traceback (most recent call last): - ... - StopIteration: Result(count=3, average=15.5) - -# end::RETURNING_AVERAGER_DEMO1[] - -Catching `StopIteration` to extract the value returned by -the coroutine:: - -# tag::RETURNING_AVERAGER_DEMO2[] - - >>> coro_avg = averager() - >>> next(coro_avg) - >>> coro_avg.send(10) - >>> coro_avg.send(30) - >>> coro_avg.send(6.5) - >>> try: - ... coro_avg.send(None) - ... except StopIteration as exc: - ... result = exc.value - ... - >>> result - Result(count=3, average=15.5) - -# end::RETURNING_AVERAGER_DEMO2[] - - -""" - -# tag::RETURNING_AVERAGER[] -from collections import namedtuple - -Result = namedtuple('Result', 'count average') - - -def averager(): - total = 0.0 - count = 0 - average = None - while True: - term = yield - if term is None: - break # <1> - total += term - count += 1 - average = total/count - return Result(count, average) # <2> -# end::RETURNING_AVERAGER[] diff --git a/19-coroutine/coroaverager3.py b/19-coroutine/coroaverager3.py deleted file mode 100644 index 2b24df9..0000000 --- a/19-coroutine/coroaverager3.py +++ /dev/null @@ -1,107 +0,0 @@ -""" -A coroutine to compute a running average. - -Testing ``averager`` by itself:: - - >>> coro_avg = averager() - >>> next(coro_avg) - >>> coro_avg.send(10) - >>> coro_avg.send(30) - >>> coro_avg.send(6.5) - >>> coro_avg.send(None) - Traceback (most recent call last): - ... - StopIteration: Result(count=3, average=15.5) - - -Driving it with ``yield from``:: - - >>> def summarize(results): - ... while True: - ... result = yield from averager() - ... results.append(result) - ... - >>> results = [] - >>> summary = summarize(results) - >>> next(summary) - >>> for height in data['girls;m']: - ... summary.send(height) - ... - >>> summary.send(None) - >>> for height in data['boys;m']: - ... summary.send(height) - ... - >>> summary.send(None) - >>> results == [ - ... Result(count=10, average=1.4279999999999997), - ... Result(count=9, average=1.3888888888888888) - ... ] - True - -""" - -# tag::YIELD_FROM_AVERAGER[] -from collections import namedtuple - -Result = namedtuple('Result', 'count average') - - -# the subgenerator -def averager(): # <1> - total = 0.0 - count = 0 - average = None - while True: - term = yield # <2> - if term is None: # <3> - break - total += term - count += 1 - average = total/count - return Result(count, average) # <4> - - -# the delegating generator -def grouper(results, key): # <5> - while True: # <6> - results[key] = yield from averager() # <7> - - -# the client code, a.k.a. the caller -def main(data): # <8> - results = {} - for key, values in data.items(): - group = grouper(results, key) # <9> - next(group) # <10> - for value in values: - group.send(value) # <11> - group.send(None) # important! <12> - - # print(results) # uncomment to debug - report(results) - - -# output report -def report(results): - for key, result in sorted(results.items()): - group, unit = key.split(';') - print(f'{result.count:2} {group:5}', - f'averaging {result.average:.2f}{unit}') - - -data = { - 'girls;kg': - [40.9, 38.5, 44.3, 42.2, 45.2, 41.7, 44.5, 38.0, 40.6, 44.5], - 'girls;m': - [1.6, 1.51, 1.4, 1.3, 1.41, 1.39, 1.33, 1.46, 1.45, 1.43], - 'boys;kg': - [39.0, 40.8, 43.2, 40.8, 43.1, 38.6, 41.4, 40.6, 36.3], - 'boys;m': - [1.38, 1.5, 1.32, 1.25, 1.37, 1.48, 1.25, 1.49, 1.46], -} - - -if __name__ == '__main__': - main(data) - -# end::YIELD_FROM_AVERAGER[] diff --git a/19-coroutine/coroutil.py b/19-coroutine/coroutil.py deleted file mode 100644 index 4420db8..0000000 --- a/19-coroutine/coroutil.py +++ /dev/null @@ -1,12 +0,0 @@ -# tag::CORO_DECO[] -from functools import wraps - -def coroutine(func): - """Decorator: primes `func` by advancing to first `yield`""" - @wraps(func) - def primer(*args, **kwargs): # <1> - gen = func(*args, **kwargs) # <2> - next(gen) # <3> - return gen # <4> - return primer -# end::CORO_DECO[] diff --git a/19-coroutine/taxi_sim.py b/19-coroutine/taxi_sim.py deleted file mode 100644 index 6bf0ce9..0000000 --- a/19-coroutine/taxi_sim.py +++ /dev/null @@ -1,203 +0,0 @@ - -""" -Taxi simulator -============== - -Driving a taxi from the console:: - - >>> from taxi_sim import taxi_process - >>> taxi = taxi_process(ident=13, trips=2, start_time=0) - >>> next(taxi) - Event(time=0, proc=13, action='leave garage') - >>> taxi.send(_.time + 7) - Event(time=7, proc=13, action='pick up passenger') - >>> taxi.send(_.time + 23) - Event(time=30, proc=13, action='drop off passenger') - >>> taxi.send(_.time + 5) - Event(time=35, proc=13, action='pick up passenger') - >>> taxi.send(_.time + 48) - Event(time=83, proc=13, action='drop off passenger') - >>> taxi.send(_.time + 1) - Event(time=84, proc=13, action='going home') - >>> taxi.send(_.time + 10) - Traceback (most recent call last): - File "", line 1, in - StopIteration - -Sample run with two cars, random seed 10. This is a valid doctest:: - - >>> main(num_taxis=2, seed=10) - taxi: 0 Event(time=0, proc=0, action='leave garage') - taxi: 0 Event(time=5, proc=0, action='pick up passenger') - taxi: 1 Event(time=5, proc=1, action='leave garage') - taxi: 1 Event(time=10, proc=1, action='pick up passenger') - taxi: 1 Event(time=15, proc=1, action='drop off passenger') - taxi: 0 Event(time=17, proc=0, action='drop off passenger') - taxi: 1 Event(time=24, proc=1, action='pick up passenger') - taxi: 0 Event(time=26, proc=0, action='pick up passenger') - taxi: 0 Event(time=30, proc=0, action='drop off passenger') - taxi: 0 Event(time=34, proc=0, action='going home') - taxi: 1 Event(time=46, proc=1, action='drop off passenger') - taxi: 1 Event(time=48, proc=1, action='pick up passenger') - taxi: 1 Event(time=110, proc=1, action='drop off passenger') - taxi: 1 Event(time=139, proc=1, action='pick up passenger') - taxi: 1 Event(time=140, proc=1, action='drop off passenger') - taxi: 1 Event(time=150, proc=1, action='going home') - *** end of events *** - -See longer sample run at the end of this module. - -""" - -import argparse -import collections -import random -import queue - - -DEFAULT_NUMBER_OF_TAXIS = 3 -DEFAULT_END_TIME = 180 -SEARCH_DURATION = 5 -TRIP_DURATION = 20 -DEPARTURE_INTERVAL = 5 - -Event = collections.namedtuple('Event', 'time proc action') - - -# tag::TAXI_PROCESS[] -def taxi_process(ident, trips, start_time=0): # <1> - """Yield to simulator issuing event at each state change""" - time = yield Event(start_time, ident, 'leave garage') # <2> - for i in range(trips): # <3> - time = yield Event(time, ident, 'pick up passenger') # <4> - time = yield Event(time, ident, 'drop off passenger') # <5> - - yield Event(time, ident, 'going home') # <6> - # end of taxi process # <7> -# end::TAXI_PROCESS[] - - -# tag::TAXI_SIMULATOR[] -class Simulator: - - def __init__(self, procs_map): - self.events = queue.PriorityQueue() - self.procs = dict(procs_map) - - def run(self, end_time): # <1> - """Schedule and display events until time is up""" - # schedule the first event for each cab - for _, proc in sorted(self.procs.items()): # <2> - first_event = next(proc) # <3> - self.events.put(first_event) # <4> - - # main loop of the simulation - sim_time = 0 # <5> - while sim_time < end_time: # <6> - if self.events.empty(): # <7> - print('*** end of events ***') - break - - current_event = self.events.get() # <8> - sim_time, proc_id, previous_action = current_event # <9> - print('taxi:', proc_id, proc_id * ' ', current_event) # <10> - active_proc = self.procs[proc_id] # <11> - next_time = sim_time + compute_duration(previous_action) # <12> - try: - next_event = active_proc.send(next_time) # <13> - except StopIteration: - del self.procs[proc_id] # <14> - else: - self.events.put(next_event) # <15> - else: # <16> - msg = '*** end of simulation time: {} events pending ***' - print(msg.format(self.events.qsize())) -# end::TAXI_SIMULATOR[] - - -def compute_duration(previous_action): - """Compute action duration using exponential distribution""" - if previous_action in ['leave garage', 'drop off passenger']: - # new state is prowling - interval = SEARCH_DURATION - elif previous_action == 'pick up passenger': - # new state is trip - interval = TRIP_DURATION - elif previous_action == 'going home': - interval = 1 - else: - raise ValueError(f'Unknown previous_action: {previous_action}') - return int(random.expovariate(1 / interval)) + 1 - - -def main(end_time=DEFAULT_END_TIME, num_taxis=DEFAULT_NUMBER_OF_TAXIS, - seed=None): - """Initialize random generator, build procs and run simulation""" - if seed is not None: - random.seed(seed) # get reproducible results - - taxis = {i: taxi_process(i, (i + 1) * 2, i * DEPARTURE_INTERVAL) - for i in range(num_taxis)} - sim = Simulator(taxis) - sim.run(end_time) - - -if __name__ == '__main__': - - parser = argparse.ArgumentParser( - description='Taxi fleet simulator.') - parser.add_argument('-e', '--end-time', type=int, - default=DEFAULT_END_TIME, - help='simulation end time; default = %s' - % DEFAULT_END_TIME) - parser.add_argument('-t', '--taxis', type=int, - default=DEFAULT_NUMBER_OF_TAXIS, - help='number of taxis running; default = %s' - % DEFAULT_NUMBER_OF_TAXIS) - parser.add_argument('-s', '--seed', type=int, default=None, - help='random generator seed (for testing)') - - args = parser.parse_args() - main(args.end_time, args.taxis, args.seed) - - -""" - -Sample run from the command line, seed=3, maximum elapsed time=120:: - -# tag::TAXI_SAMPLE_RUN[] -$ python3 taxi_sim.py -s 3 -e 120 -taxi: 0 Event(time=0, proc=0, action='leave garage') -taxi: 0 Event(time=2, proc=0, action='pick up passenger') -taxi: 1 Event(time=5, proc=1, action='leave garage') -taxi: 1 Event(time=8, proc=1, action='pick up passenger') -taxi: 2 Event(time=10, proc=2, action='leave garage') -taxi: 2 Event(time=15, proc=2, action='pick up passenger') -taxi: 2 Event(time=17, proc=2, action='drop off passenger') -taxi: 0 Event(time=18, proc=0, action='drop off passenger') -taxi: 2 Event(time=18, proc=2, action='pick up passenger') -taxi: 2 Event(time=25, proc=2, action='drop off passenger') -taxi: 1 Event(time=27, proc=1, action='drop off passenger') -taxi: 2 Event(time=27, proc=2, action='pick up passenger') -taxi: 0 Event(time=28, proc=0, action='pick up passenger') -taxi: 2 Event(time=40, proc=2, action='drop off passenger') -taxi: 2 Event(time=44, proc=2, action='pick up passenger') -taxi: 1 Event(time=55, proc=1, action='pick up passenger') -taxi: 1 Event(time=59, proc=1, action='drop off passenger') -taxi: 0 Event(time=65, proc=0, action='drop off passenger') -taxi: 1 Event(time=65, proc=1, action='pick up passenger') -taxi: 2 Event(time=65, proc=2, action='drop off passenger') -taxi: 2 Event(time=72, proc=2, action='pick up passenger') -taxi: 0 Event(time=76, proc=0, action='going home') -taxi: 1 Event(time=80, proc=1, action='drop off passenger') -taxi: 1 Event(time=88, proc=1, action='pick up passenger') -taxi: 2 Event(time=95, proc=2, action='drop off passenger') -taxi: 2 Event(time=97, proc=2, action='pick up passenger') -taxi: 2 Event(time=98, proc=2, action='drop off passenger') -taxi: 1 Event(time=106, proc=1, action='drop off passenger') -taxi: 2 Event(time=109, proc=2, action='going home') -taxi: 1 Event(time=110, proc=1, action='going home') -*** end of events *** -# end::TAXI_SAMPLE_RUN[] - -""" diff --git a/19-coroutine/taxi_sim0.py b/19-coroutine/taxi_sim0.py deleted file mode 100644 index ed988f5..0000000 --- a/19-coroutine/taxi_sim0.py +++ /dev/null @@ -1,255 +0,0 @@ - -""" -Taxi simulator - -Sample run with two cars, random seed 10. This is a valid doctest. - - >>> main(num_taxis=2, seed=10) - taxi: 0 Event(time=0, proc=0, action='leave garage') - taxi: 0 Event(time=4, proc=0, action='pick up passenger') - taxi: 1 Event(time=5, proc=1, action='leave garage') - taxi: 1 Event(time=9, proc=1, action='pick up passenger') - taxi: 0 Event(time=10, proc=0, action='drop off passenger') - taxi: 1 Event(time=12, proc=1, action='drop off passenger') - taxi: 0 Event(time=17, proc=0, action='pick up passenger') - taxi: 1 Event(time=19, proc=1, action='pick up passenger') - taxi: 1 Event(time=21, proc=1, action='drop off passenger') - taxi: 1 Event(time=24, proc=1, action='pick up passenger') - taxi: 0 Event(time=28, proc=0, action='drop off passenger') - taxi: 1 Event(time=28, proc=1, action='drop off passenger') - taxi: 0 Event(time=29, proc=0, action='going home') - taxi: 1 Event(time=30, proc=1, action='pick up passenger') - taxi: 1 Event(time=61, proc=1, action='drop off passenger') - taxi: 1 Event(time=62, proc=1, action='going home') - *** end of events *** - -See explanation and longer sample run at the end of this module. - -""" - -import argparse -import collections -import queue -import random - -DEFAULT_NUMBER_OF_TAXIS = 3 -DEFAULT_END_TIME = 80 -SEARCH_DURATION = 4 -TRIP_DURATION = 10 -DEPARTURE_INTERVAL = 5 - -Event = collections.namedtuple('Event', 'time proc action') - - -def compute_delay(interval): - """Compute action delay using exponential distribution""" - return int(random.expovariate(1 / interval)) + 1 - -# BEGIN TAXI_PROCESS -def taxi_process(ident, trips, start_time=0): # <1> - """Yield to simulator issuing event at each state change""" - time = yield Event(start_time, ident, 'leave garage') # <2> - for i in range(trips): # <3> - prowling_ends = time + compute_delay(SEARCH_DURATION) # <4> - time = yield Event(prowling_ends, ident, 'pick up passenger') # <5> - - trip_ends = time + compute_delay(TRIP_DURATION) # <6> - time = yield Event(trip_ends, ident, 'drop off passenger') # <7> - - yield Event(time + 1, ident, 'going home') # <8> - # end of taxi process # <9> -# END TAXI_PROCESS - -# BEGIN TAXI_SIMULATOR -class Simulator: - - def __init__(self, procs_map): - self.events = queue.PriorityQueue() - self.procs = dict(procs_map) - - def run(self, end_time): # <1> - """Schedule and display events until time is up""" - # schedule the first event for each cab - for _, proc in sorted(self.procs.items()): # <2> - first_event = next(proc) # <3> - self.events.put(first_event) # <4> - - # main loop of the simulation - time = 0 - while time < end_time: # <5> - if self.events.empty(): # <6> - print('*** end of events ***') - break - - # get and display current event - current_event = self.events.get() # <7> - print('taxi:', current_event.proc, # <8> - current_event.proc * ' ', current_event) - - # schedule next action for current proc - time = current_event.time # <9> - proc = self.procs[current_event.proc] # <10> - try: - next_event = proc.send(time) # <11> - except StopIteration: - del self.procs[current_event.proc] # <12> - else: - self.events.put(next_event) # <13> - else: # <14> - msg = '*** end of simulation time: {} events pending ***' - print(msg.format(self.events.qsize())) -# END TAXI_SIMULATOR - -def main(end_time=DEFAULT_END_TIME, num_taxis=DEFAULT_NUMBER_OF_TAXIS, - seed=None): - """Initialize random generator, build procs and run simulation""" - if seed is not None: - random.seed(seed) # get reproducible results - - taxis = {i: taxi_process(i, (i + 1) * 2, i * DEPARTURE_INTERVAL) - for i in range(num_taxis)} - sim = Simulator(taxis) - sim.run(end_time) - - -if __name__ == '__main__': - - parser = argparse.ArgumentParser( - description='Taxi fleet simulator.') - parser.add_argument('-e', '--end-time', type=int, - default=DEFAULT_END_TIME, - help='simulation end time; default = %s' - % DEFAULT_END_TIME) - parser.add_argument('-t', '--taxis', type=int, - default=DEFAULT_NUMBER_OF_TAXIS, - help='number of taxis running; default = %s' - % DEFAULT_NUMBER_OF_TAXIS) - parser.add_argument('-s', '--seed', type=int, default=None, - help='random generator seed (for testing)') - - args = parser.parse_args() - main(args.end_time, args.taxis, args.seed) - - -""" -Notes for the ``taxi_process`` coroutine:: - -<1> `taxi_process` will be called once per taxi, creating a generator - object to represent its operations. `ident` is the number of the taxi - (eg. 0, 1, 2 in the sample run); `trips` is the number of trips this - taxi will make before going home; `start_time` is when the taxi - leaves the garage. - -<2> The first `Event` yielded is `'leave garage'`. This suspends the - coroutine, and lets the simulation main loop proceed to the next - scheduled event. When it's time to reactivate this process, the main - loop will `send` the current simulation time, which is assigned to - `time`. - -<3> This block will be repeated once for each trip. - -<4> The ending time of the search for a passenger is computed. - -<5> An `Event` signaling passenger pick up is yielded. The coroutine - pauses here. When the time comes to reactivate this coroutine, - the main loop will again `send` the current time. - -<6> The ending time of the trip is computed, taking into account the - current `time`. - -<7> An `Event` signaling passenger drop off is yielded. Coroutine - suspended again, waiting for the main loop to send the time of when - it's time to continue. - -<8> The `for` loop ends after the given number of trips, and a final - `'going home'` event is yielded, to happen 1 minute after the current - time. The coroutine will suspend for the last time. When reactivated, - it will be sent the time from the simulation main loop, but here I - don't assign it to any variable because it will not be useful. - -<9> When the coroutine falls off the end, the coroutine object raises - `StopIteration`. - - -Notes for the ``Simulator.run`` method:: - -<1> The simulation `end_time` is the only required argument for `run`. - -<2> Use `sorted` to retrieve the `self.procs` items ordered by the - integer key; we don't care about the key, so assign it to `_`. - -<3> `next(proc)` primes each coroutine by advancing it to the first - yield, so it's ready to be sent data. An `Event` is yielded. - -<4> Add each event to the `self.events` `PriorityQueue`. The first - event for each taxi is `'leave garage'`, as seen in the sample run - (ex_taxi_process>>). - -<5> Main loop of the simulation: run until the current `time` equals - or exceeds the `end_time`. - -<6> The main loop may also exit if there are no pending events in the - queue. - -<7> Get `Event` with the smallest `time` in the queue; this is the - `current_event`. - -<8> Display the `Event`, identifying the taxi and adding indentation - according to the taxi id. - -<9> Update the simulation time with the time of the `current_event`. - -<10> Retrieve the coroutine for this taxi from the `self.procs` - dictionary. - -<11> Send the `time` to the coroutine. The coroutine will yield the - `next_event` or raise `StopIteration` it's finished. - -<12> If `StopIteration` was raised, delete the coroutine from the - `self.procs` dictionary. - -<13> Otherwise, put the `next_event` in the queue. - -<14> If the loop exits because the simulation time passed, display the - number of events pending (which may be zero by coincidence, - sometimes). - - -Sample run from the command line, seed=24, total elapsed time=160:: - -# BEGIN TAXI_SAMPLE_RUN -$ python3 taxi_sim.py -s 24 -e 160 -taxi: 0 Event(time=0, proc=0, action='leave garage') -taxi: 0 Event(time=5, proc=0, action='pick up passenger') -taxi: 1 Event(time=5, proc=1, action='leave garage') -taxi: 1 Event(time=6, proc=1, action='pick up passenger') -taxi: 2 Event(time=10, proc=2, action='leave garage') -taxi: 2 Event(time=11, proc=2, action='pick up passenger') -taxi: 2 Event(time=23, proc=2, action='drop off passenger') -taxi: 0 Event(time=24, proc=0, action='drop off passenger') -taxi: 2 Event(time=24, proc=2, action='pick up passenger') -taxi: 2 Event(time=26, proc=2, action='drop off passenger') -taxi: 0 Event(time=30, proc=0, action='pick up passenger') -taxi: 2 Event(time=31, proc=2, action='pick up passenger') -taxi: 0 Event(time=43, proc=0, action='drop off passenger') -taxi: 0 Event(time=44, proc=0, action='going home') -taxi: 2 Event(time=46, proc=2, action='drop off passenger') -taxi: 2 Event(time=49, proc=2, action='pick up passenger') -taxi: 1 Event(time=70, proc=1, action='drop off passenger') -taxi: 2 Event(time=70, proc=2, action='drop off passenger') -taxi: 2 Event(time=71, proc=2, action='pick up passenger') -taxi: 2 Event(time=79, proc=2, action='drop off passenger') -taxi: 1 Event(time=88, proc=1, action='pick up passenger') -taxi: 2 Event(time=92, proc=2, action='pick up passenger') -taxi: 2 Event(time=98, proc=2, action='drop off passenger') -taxi: 2 Event(time=99, proc=2, action='going home') -taxi: 1 Event(time=102, proc=1, action='drop off passenger') -taxi: 1 Event(time=104, proc=1, action='pick up passenger') -taxi: 1 Event(time=135, proc=1, action='drop off passenger') -taxi: 1 Event(time=136, proc=1, action='pick up passenger') -taxi: 1 Event(time=151, proc=1, action='drop off passenger') -taxi: 1 Event(time=152, proc=1, action='going home') -*** end of events *** -# END TAXI_SAMPLE_RUN - -""" diff --git a/19-coroutine/taxi_sim_delay.py b/19-coroutine/taxi_sim_delay.py deleted file mode 100644 index 8f38e4f..0000000 --- a/19-coroutine/taxi_sim_delay.py +++ /dev/null @@ -1,215 +0,0 @@ - -""" -Taxi simulator with delay on output -=================================== - -This is a variation of ``taxi_sim.py`` which adds a ``-d`` comand-line -option. When given, that option adds a delay in the main loop, pausing -the simulation for .5s for each "minute" of simulation time. - - -Driving a taxi from the console:: - - >>> from taxi_sim import taxi_process - >>> taxi = taxi_process(ident=13, trips=2, start_time=0) - >>> next(taxi) - Event(time=0, proc=13, action='leave garage') - >>> taxi.send(_.time + 7) - Event(time=7, proc=13, action='pick up passenger') - >>> taxi.send(_.time + 23) - Event(time=30, proc=13, action='drop off passenger') - >>> taxi.send(_.time + 5) - Event(time=35, proc=13, action='pick up passenger') - >>> taxi.send(_.time + 48) - Event(time=83, proc=13, action='drop off passenger') - >>> taxi.send(_.time + 1) - Event(time=84, proc=13, action='going home') - >>> taxi.send(_.time + 10) - Traceback (most recent call last): - File "", line 1, in - StopIteration - -Sample run with two cars, random seed 10. This is a valid doctest:: - - >>> main(num_taxis=2, seed=10) - taxi: 0 Event(time=0, proc=0, action='leave garage') - taxi: 0 Event(time=5, proc=0, action='pick up passenger') - taxi: 1 Event(time=5, proc=1, action='leave garage') - taxi: 1 Event(time=10, proc=1, action='pick up passenger') - taxi: 1 Event(time=15, proc=1, action='drop off passenger') - taxi: 0 Event(time=17, proc=0, action='drop off passenger') - taxi: 1 Event(time=24, proc=1, action='pick up passenger') - taxi: 0 Event(time=26, proc=0, action='pick up passenger') - taxi: 0 Event(time=30, proc=0, action='drop off passenger') - taxi: 0 Event(time=34, proc=0, action='going home') - taxi: 1 Event(time=46, proc=1, action='drop off passenger') - taxi: 1 Event(time=48, proc=1, action='pick up passenger') - taxi: 1 Event(time=110, proc=1, action='drop off passenger') - taxi: 1 Event(time=139, proc=1, action='pick up passenger') - taxi: 1 Event(time=140, proc=1, action='drop off passenger') - taxi: 1 Event(time=150, proc=1, action='going home') - *** end of events *** - -See longer sample run at the end of this module. - -""" - -import random -import collections -import queue -import argparse -import time - -DEFAULT_NUMBER_OF_TAXIS = 3 -DEFAULT_END_TIME = 180 -SEARCH_DURATION = 5 -TRIP_DURATION = 20 -DEPARTURE_INTERVAL = 5 - -Event = collections.namedtuple('Event', 'time proc action') - - -# BEGIN TAXI_PROCESS -def taxi_process(ident, trips, start_time=0): # <1> - """Yield to simulator issuing event at each state change""" - time = yield Event(start_time, ident, 'leave garage') # <2> - for i in range(trips): # <3> - time = yield Event(time, ident, 'pick up passenger') # <4> - time = yield Event(time, ident, 'drop off passenger') # <5> - - yield Event(time, ident, 'going home') # <6> - # end of taxi process # <7> -# END TAXI_PROCESS - - -# BEGIN TAXI_SIMULATOR -class Simulator: - - def __init__(self, procs_map): - self.events = queue.PriorityQueue() - self.procs = dict(procs_map) - - def run(self, end_time, delay=False): # <1> - """Schedule and display events until time is up""" - # schedule the first event for each cab - for _, proc in sorted(self.procs.items()): # <2> - first_event = next(proc) # <3> - self.events.put(first_event) # <4> - - # main loop of the simulation - sim_time = 0 # <5> - while sim_time < end_time: # <6> - if self.events.empty(): # <7> - print('*** end of events ***') - break - - # get and display current event - current_event = self.events.get() # <8> - if delay: - time.sleep((current_event.time - sim_time) / 2) - # update the simulation time - sim_time, proc_id, previous_action = current_event - print('taxi:', proc_id, proc_id * ' ', current_event) - active_proc = self.procs[proc_id] - # schedule next action for current proc - next_time = sim_time + compute_duration(previous_action) - try: - next_event = active_proc.send(next_time) # <12> - except StopIteration: - del self.procs[proc_id] # <13> - else: - self.events.put(next_event) # <14> - else: # <15> - msg = '*** end of simulation time: {} events pending ***' - print(msg.format(self.events.qsize())) -# END TAXI_SIMULATOR - - -def compute_duration(previous_action): - """Compute action duration using exponential distribution""" - if previous_action in ['leave garage', 'drop off passenger']: - # new state is prowling - interval = SEARCH_DURATION - elif previous_action == 'pick up passenger': - # new state is trip - interval = TRIP_DURATION - elif previous_action == 'going home': - interval = 1 - else: - raise ValueError('Unknown previous_action: %s' % previous_action) - return int(random.expovariate(1/interval)) + 1 - - -def main(end_time=DEFAULT_END_TIME, num_taxis=DEFAULT_NUMBER_OF_TAXIS, - seed=None, delay=False): - """Initialize random generator, build procs and run simulation""" - if seed is not None: - random.seed(seed) # get reproducible results - - taxis = {i: taxi_process(i, (i+1)*2, i*DEPARTURE_INTERVAL) - for i in range(num_taxis)} - sim = Simulator(taxis) - sim.run(end_time, delay) - - -if __name__ == '__main__': - - parser = argparse.ArgumentParser( - description='Taxi fleet simulator.') - parser.add_argument('-e', '--end-time', type=int, - default=DEFAULT_END_TIME, - help='simulation end time; default = %s' - % DEFAULT_END_TIME) - parser.add_argument('-t', '--taxis', type=int, - default=DEFAULT_NUMBER_OF_TAXIS, - help='number of taxis running; default = %s' - % DEFAULT_NUMBER_OF_TAXIS) - parser.add_argument('-s', '--seed', type=int, default=None, - help='random generator seed (for testing)') - parser.add_argument('-d', '--delay', action='store_true', - help='introduce delay proportional to simulation time') - - args = parser.parse_args() - main(args.end_time, args.taxis, args.seed, args.delay) - - -""" - -Sample run from the command line, seed=3, maximum elapsed time=120:: - -# BEGIN TAXI_SAMPLE_RUN -$ python3 taxi_sim.py -s 3 -e 120 -taxi: 0 Event(time=0, proc=0, action='leave garage') -taxi: 0 Event(time=2, proc=0, action='pick up passenger') -taxi: 1 Event(time=5, proc=1, action='leave garage') -taxi: 1 Event(time=8, proc=1, action='pick up passenger') -taxi: 2 Event(time=10, proc=2, action='leave garage') -taxi: 2 Event(time=15, proc=2, action='pick up passenger') -taxi: 2 Event(time=17, proc=2, action='drop off passenger') -taxi: 0 Event(time=18, proc=0, action='drop off passenger') -taxi: 2 Event(time=18, proc=2, action='pick up passenger') -taxi: 2 Event(time=25, proc=2, action='drop off passenger') -taxi: 1 Event(time=27, proc=1, action='drop off passenger') -taxi: 2 Event(time=27, proc=2, action='pick up passenger') -taxi: 0 Event(time=28, proc=0, action='pick up passenger') -taxi: 2 Event(time=40, proc=2, action='drop off passenger') -taxi: 2 Event(time=44, proc=2, action='pick up passenger') -taxi: 1 Event(time=55, proc=1, action='pick up passenger') -taxi: 1 Event(time=59, proc=1, action='drop off passenger') -taxi: 0 Event(time=65, proc=0, action='drop off passenger') -taxi: 1 Event(time=65, proc=1, action='pick up passenger') -taxi: 2 Event(time=65, proc=2, action='drop off passenger') -taxi: 2 Event(time=72, proc=2, action='pick up passenger') -taxi: 0 Event(time=76, proc=0, action='going home') -taxi: 1 Event(time=80, proc=1, action='drop off passenger') -taxi: 1 Event(time=88, proc=1, action='pick up passenger') -taxi: 2 Event(time=95, proc=2, action='drop off passenger') -taxi: 2 Event(time=97, proc=2, action='pick up passenger') -taxi: 2 Event(time=98, proc=2, action='drop off passenger') -taxi: 1 Event(time=106, proc=1, action='drop off passenger') -taxi: 2 Event(time=109, proc=2, action='going home') -taxi: 1 Event(time=110, proc=1, action='going home') -*** end of events *** -# END TAXI_SAMPLE_RUN - -""" diff --git a/19-coroutine/yield_from_expansion.py b/19-coroutine/yield_from_expansion.py deleted file mode 100644 index 837925f..0000000 --- a/19-coroutine/yield_from_expansion.py +++ /dev/null @@ -1,52 +0,0 @@ -# Code below is the expansion of the statement: -# -# RESULT = yield from EXPR -# -# Copied verbatim from the Formal Semantics section of -# PEP 380 -- Syntax for Delegating to a Subgenerator -# -# https://www.python.org/dev/peps/pep-0380/#formal-semantics - - -# tag::YIELD_FROM_EXPANSION[] -_i = iter(EXPR) # <1> -try: - _y = next(_i) # <2> -except StopIteration as _e: - _r = _e.value # <3> -else: - while 1: # <4> - try: - _s = yield _y # <5> - except GeneratorExit as _e: # <6> - try: - _m = _i.close - except AttributeError: - pass - else: - _m() - raise _e - except BaseException as _e: # <7> - _x = sys.exc_info() - try: - _m = _i.throw - except AttributeError: - raise _e - else: # <8> - try: - _y = _m(*_x) - except StopIteration as _e: - _r = _e.value - break - else: # <9> - try: # <10> - if _s is None: # <11> - _y = next(_i) - else: - _y = _i.send(_s) - except StopIteration as _e: # <12> - _r = _e.value - break - -RESULT = _r # <13> -# end::YIELD_FROM_EXPANSION[] diff --git a/19-coroutine/yield_from_expansion_simplified.py b/19-coroutine/yield_from_expansion_simplified.py deleted file mode 100644 index 7648da5..0000000 --- a/19-coroutine/yield_from_expansion_simplified.py +++ /dev/null @@ -1,32 +0,0 @@ -# Code below is a very simplified expansion of the statement: -# -# RESULT = yield from EXPR -# -# This code assumes that the subgenerator will run to completion, -# without the client ever calling ``.throw()`` or ``.close()``. -# Also, this code makes no distinction between the client -# calling ``next(subgen)`` or ``subgen.send(...)`` -# -# The full expansion is in: -# PEP 380 -- Syntax for Delegating to a Subgenerator -# -# https://www.python.org/dev/peps/pep-0380/#formal-semantics - - -# tag::YIELD_FROM_EXPANSION_SIMPLIFIED[] -_i = iter(EXPR) # <1> -try: - _y = next(_i) # <2> -except StopIteration as _e: - _r = _e.value # <3> -else: - while 1: # <4> - _s = yield _y # <5> - try: - _y = _i.send(_s) # <6> - except StopIteration as _e: # <7> - _r = _e.value - break - -RESULT = _r # <8> -# end::YIELD_FROM_EXPANSION_SIMPLIFIED[] diff --git a/21-futures/demo_executor_map.py b/20-futures/demo_executor_map.py similarity index 100% rename from 21-futures/demo_executor_map.py rename to 20-futures/demo_executor_map.py diff --git a/21-futures/getflags/.gitignore b/20-futures/getflags/.gitignore similarity index 100% rename from 21-futures/getflags/.gitignore rename to 20-futures/getflags/.gitignore diff --git a/21-futures/getflags/country_codes.txt b/20-futures/getflags/country_codes.txt similarity index 100% rename from 21-futures/getflags/country_codes.txt rename to 20-futures/getflags/country_codes.txt diff --git a/21-futures/getflags/downloaded/.gitignore b/20-futures/getflags/downloaded/.gitignore similarity index 100% rename from 21-futures/getflags/downloaded/.gitignore rename to 20-futures/getflags/downloaded/.gitignore diff --git a/21-futures/getflags/flags.py b/20-futures/getflags/flags.py similarity index 100% rename from 21-futures/getflags/flags.py rename to 20-futures/getflags/flags.py diff --git a/21-futures/getflags/flags.zip b/20-futures/getflags/flags.zip similarity index 100% rename from 21-futures/getflags/flags.zip rename to 20-futures/getflags/flags.zip diff --git a/21-futures/getflags/flags2_asyncio.py b/20-futures/getflags/flags2_asyncio.py similarity index 100% rename from 21-futures/getflags/flags2_asyncio.py rename to 20-futures/getflags/flags2_asyncio.py diff --git a/21-futures/getflags/flags2_asyncio_executor.py b/20-futures/getflags/flags2_asyncio_executor.py similarity index 100% rename from 21-futures/getflags/flags2_asyncio_executor.py rename to 20-futures/getflags/flags2_asyncio_executor.py diff --git a/21-futures/getflags/flags2_common.py b/20-futures/getflags/flags2_common.py similarity index 100% rename from 21-futures/getflags/flags2_common.py rename to 20-futures/getflags/flags2_common.py diff --git a/21-futures/getflags/flags2_sequential.py b/20-futures/getflags/flags2_sequential.py similarity index 100% rename from 21-futures/getflags/flags2_sequential.py rename to 20-futures/getflags/flags2_sequential.py diff --git a/21-futures/getflags/flags2_threadpool.py b/20-futures/getflags/flags2_threadpool.py similarity index 100% rename from 21-futures/getflags/flags2_threadpool.py rename to 20-futures/getflags/flags2_threadpool.py diff --git a/21-futures/getflags/flags3_asyncio.py b/20-futures/getflags/flags3_asyncio.py similarity index 100% rename from 21-futures/getflags/flags3_asyncio.py rename to 20-futures/getflags/flags3_asyncio.py diff --git a/21-futures/getflags/flags_asyncio.py b/20-futures/getflags/flags_asyncio.py similarity index 100% rename from 21-futures/getflags/flags_asyncio.py rename to 20-futures/getflags/flags_asyncio.py diff --git a/21-futures/getflags/flags_threadpool.py b/20-futures/getflags/flags_threadpool.py similarity index 100% rename from 21-futures/getflags/flags_threadpool.py rename to 20-futures/getflags/flags_threadpool.py diff --git a/21-futures/getflags/flags_threadpool_futures.py b/20-futures/getflags/flags_threadpool_futures.py similarity index 100% rename from 21-futures/getflags/flags_threadpool_futures.py rename to 20-futures/getflags/flags_threadpool_futures.py diff --git a/21-futures/getflags/requirements.txt b/20-futures/getflags/requirements.txt similarity index 100% rename from 21-futures/getflags/requirements.txt rename to 20-futures/getflags/requirements.txt diff --git a/21-futures/getflags/slow_server.py b/20-futures/getflags/slow_server.py similarity index 100% rename from 21-futures/getflags/slow_server.py rename to 20-futures/getflags/slow_server.py diff --git a/21-futures/primes/primes.py b/20-futures/primes/primes.py similarity index 100% rename from 21-futures/primes/primes.py rename to 20-futures/primes/primes.py diff --git a/21-futures/primes/proc_pool.py b/20-futures/primes/proc_pool.py similarity index 100% rename from 21-futures/primes/proc_pool.py rename to 20-futures/primes/proc_pool.py diff --git a/22-async/README.rst b/21-async/README.rst similarity index 100% rename from 22-async/README.rst rename to 21-async/README.rst diff --git a/22-async/domains/README.rst b/21-async/domains/README.rst similarity index 100% rename from 22-async/domains/README.rst rename to 21-async/domains/README.rst diff --git a/22-async/domains/asyncio/blogdom.py b/21-async/domains/asyncio/blogdom.py similarity index 100% rename from 22-async/domains/asyncio/blogdom.py rename to 21-async/domains/asyncio/blogdom.py diff --git a/22-async/domains/asyncio/domaincheck.py b/21-async/domains/asyncio/domaincheck.py similarity index 100% rename from 22-async/domains/asyncio/domaincheck.py rename to 21-async/domains/asyncio/domaincheck.py diff --git a/22-async/domains/asyncio/domainlib.py b/21-async/domains/asyncio/domainlib.py similarity index 100% rename from 22-async/domains/asyncio/domainlib.py rename to 21-async/domains/asyncio/domainlib.py diff --git a/22-async/domains/curio/blogdom.py b/21-async/domains/curio/blogdom.py similarity index 100% rename from 22-async/domains/curio/blogdom.py rename to 21-async/domains/curio/blogdom.py diff --git a/22-async/domains/curio/domaincheck.py b/21-async/domains/curio/domaincheck.py similarity index 100% rename from 22-async/domains/curio/domaincheck.py rename to 21-async/domains/curio/domaincheck.py diff --git a/22-async/domains/curio/domainlib.py b/21-async/domains/curio/domainlib.py similarity index 100% rename from 22-async/domains/curio/domainlib.py rename to 21-async/domains/curio/domainlib.py diff --git a/22-async/domains/curio/requirements.txt b/21-async/domains/curio/requirements.txt similarity index 100% rename from 22-async/domains/curio/requirements.txt rename to 21-async/domains/curio/requirements.txt diff --git a/22-async/mojifinder/README.md b/21-async/mojifinder/README.md similarity index 100% rename from 22-async/mojifinder/README.md rename to 21-async/mojifinder/README.md diff --git a/22-async/mojifinder/bottle.py b/21-async/mojifinder/bottle.py similarity index 100% rename from 22-async/mojifinder/bottle.py rename to 21-async/mojifinder/bottle.py diff --git a/22-async/mojifinder/charindex.py b/21-async/mojifinder/charindex.py similarity index 100% rename from 22-async/mojifinder/charindex.py rename to 21-async/mojifinder/charindex.py diff --git a/22-async/mojifinder/requirements.txt b/21-async/mojifinder/requirements.txt similarity index 100% rename from 22-async/mojifinder/requirements.txt rename to 21-async/mojifinder/requirements.txt diff --git a/22-async/mojifinder/static/form.html b/21-async/mojifinder/static/form.html similarity index 100% rename from 22-async/mojifinder/static/form.html rename to 21-async/mojifinder/static/form.html diff --git a/22-async/mojifinder/tcp_mojifinder.py b/21-async/mojifinder/tcp_mojifinder.py similarity index 100% rename from 22-async/mojifinder/tcp_mojifinder.py rename to 21-async/mojifinder/tcp_mojifinder.py diff --git a/22-async/mojifinder/web_mojifinder.py b/21-async/mojifinder/web_mojifinder.py similarity index 100% rename from 22-async/mojifinder/web_mojifinder.py rename to 21-async/mojifinder/web_mojifinder.py diff --git a/22-async/mojifinder/web_mojifinder_bottle.py b/21-async/mojifinder/web_mojifinder_bottle.py similarity index 100% rename from 22-async/mojifinder/web_mojifinder_bottle.py rename to 21-async/mojifinder/web_mojifinder_bottle.py diff --git a/23-dyn-attr-prop/README.rst b/22-dyn-attr-prop/README.rst similarity index 100% rename from 23-dyn-attr-prop/README.rst rename to 22-dyn-attr-prop/README.rst diff --git a/23-dyn-attr-prop/blackknight.py b/22-dyn-attr-prop/blackknight.py similarity index 100% rename from 23-dyn-attr-prop/blackknight.py rename to 22-dyn-attr-prop/blackknight.py diff --git a/23-dyn-attr-prop/bulkfood/bulkfood_v1.py b/22-dyn-attr-prop/bulkfood/bulkfood_v1.py similarity index 100% rename from 23-dyn-attr-prop/bulkfood/bulkfood_v1.py rename to 22-dyn-attr-prop/bulkfood/bulkfood_v1.py diff --git a/23-dyn-attr-prop/bulkfood/bulkfood_v2.py b/22-dyn-attr-prop/bulkfood/bulkfood_v2.py similarity index 100% rename from 23-dyn-attr-prop/bulkfood/bulkfood_v2.py rename to 22-dyn-attr-prop/bulkfood/bulkfood_v2.py diff --git a/23-dyn-attr-prop/bulkfood/bulkfood_v2b.py b/22-dyn-attr-prop/bulkfood/bulkfood_v2b.py similarity index 100% rename from 23-dyn-attr-prop/bulkfood/bulkfood_v2b.py rename to 22-dyn-attr-prop/bulkfood/bulkfood_v2b.py diff --git a/23-dyn-attr-prop/bulkfood/bulkfood_v2prop.py b/22-dyn-attr-prop/bulkfood/bulkfood_v2prop.py similarity index 100% rename from 23-dyn-attr-prop/bulkfood/bulkfood_v2prop.py rename to 22-dyn-attr-prop/bulkfood/bulkfood_v2prop.py diff --git a/23-dyn-attr-prop/doc_property.py b/22-dyn-attr-prop/doc_property.py similarity index 100% rename from 23-dyn-attr-prop/doc_property.py rename to 22-dyn-attr-prop/doc_property.py diff --git a/23-dyn-attr-prop/oscon/data/osconfeed.json b/22-dyn-attr-prop/oscon/data/osconfeed.json similarity index 100% rename from 23-dyn-attr-prop/oscon/data/osconfeed.json rename to 22-dyn-attr-prop/oscon/data/osconfeed.json diff --git a/23-dyn-attr-prop/oscon/demo_schedule2.py b/22-dyn-attr-prop/oscon/demo_schedule2.py similarity index 100% rename from 23-dyn-attr-prop/oscon/demo_schedule2.py rename to 22-dyn-attr-prop/oscon/demo_schedule2.py diff --git a/23-dyn-attr-prop/oscon/explore0.py b/22-dyn-attr-prop/oscon/explore0.py similarity index 100% rename from 23-dyn-attr-prop/oscon/explore0.py rename to 22-dyn-attr-prop/oscon/explore0.py diff --git a/23-dyn-attr-prop/oscon/explore1.py b/22-dyn-attr-prop/oscon/explore1.py similarity index 100% rename from 23-dyn-attr-prop/oscon/explore1.py rename to 22-dyn-attr-prop/oscon/explore1.py diff --git a/23-dyn-attr-prop/oscon/explore2.py b/22-dyn-attr-prop/oscon/explore2.py similarity index 100% rename from 23-dyn-attr-prop/oscon/explore2.py rename to 22-dyn-attr-prop/oscon/explore2.py diff --git a/23-dyn-attr-prop/oscon/osconfeed-sample.json b/22-dyn-attr-prop/oscon/osconfeed-sample.json similarity index 100% rename from 23-dyn-attr-prop/oscon/osconfeed-sample.json rename to 22-dyn-attr-prop/oscon/osconfeed-sample.json diff --git a/23-dyn-attr-prop/oscon/osconfeed_explore.rst b/22-dyn-attr-prop/oscon/osconfeed_explore.rst similarity index 100% rename from 23-dyn-attr-prop/oscon/osconfeed_explore.rst rename to 22-dyn-attr-prop/oscon/osconfeed_explore.rst diff --git a/23-dyn-attr-prop/oscon/runtests.sh b/22-dyn-attr-prop/oscon/runtests.sh similarity index 100% rename from 23-dyn-attr-prop/oscon/runtests.sh rename to 22-dyn-attr-prop/oscon/runtests.sh diff --git a/23-dyn-attr-prop/oscon/schedule_v1.py b/22-dyn-attr-prop/oscon/schedule_v1.py similarity index 100% rename from 23-dyn-attr-prop/oscon/schedule_v1.py rename to 22-dyn-attr-prop/oscon/schedule_v1.py diff --git a/23-dyn-attr-prop/oscon/schedule_v2.py b/22-dyn-attr-prop/oscon/schedule_v2.py similarity index 100% rename from 23-dyn-attr-prop/oscon/schedule_v2.py rename to 22-dyn-attr-prop/oscon/schedule_v2.py diff --git a/23-dyn-attr-prop/oscon/schedule_v3.py b/22-dyn-attr-prop/oscon/schedule_v3.py similarity index 100% rename from 23-dyn-attr-prop/oscon/schedule_v3.py rename to 22-dyn-attr-prop/oscon/schedule_v3.py diff --git a/23-dyn-attr-prop/oscon/schedule_v4.py b/22-dyn-attr-prop/oscon/schedule_v4.py similarity index 100% rename from 23-dyn-attr-prop/oscon/schedule_v4.py rename to 22-dyn-attr-prop/oscon/schedule_v4.py diff --git a/23-dyn-attr-prop/oscon/schedule_v4_hasattr.py b/22-dyn-attr-prop/oscon/schedule_v4_hasattr.py similarity index 100% rename from 23-dyn-attr-prop/oscon/schedule_v4_hasattr.py rename to 22-dyn-attr-prop/oscon/schedule_v4_hasattr.py diff --git a/23-dyn-attr-prop/oscon/schedule_v5.py b/22-dyn-attr-prop/oscon/schedule_v5.py similarity index 100% rename from 23-dyn-attr-prop/oscon/schedule_v5.py rename to 22-dyn-attr-prop/oscon/schedule_v5.py diff --git a/23-dyn-attr-prop/oscon/test_schedule_v1.py b/22-dyn-attr-prop/oscon/test_schedule_v1.py similarity index 100% rename from 23-dyn-attr-prop/oscon/test_schedule_v1.py rename to 22-dyn-attr-prop/oscon/test_schedule_v1.py diff --git a/23-dyn-attr-prop/oscon/test_schedule_v2.py b/22-dyn-attr-prop/oscon/test_schedule_v2.py similarity index 100% rename from 23-dyn-attr-prop/oscon/test_schedule_v2.py rename to 22-dyn-attr-prop/oscon/test_schedule_v2.py diff --git a/23-dyn-attr-prop/oscon/test_schedule_v3.py b/22-dyn-attr-prop/oscon/test_schedule_v3.py similarity index 100% rename from 23-dyn-attr-prop/oscon/test_schedule_v3.py rename to 22-dyn-attr-prop/oscon/test_schedule_v3.py diff --git a/23-dyn-attr-prop/oscon/test_schedule_v4.py b/22-dyn-attr-prop/oscon/test_schedule_v4.py similarity index 100% rename from 23-dyn-attr-prop/oscon/test_schedule_v4.py rename to 22-dyn-attr-prop/oscon/test_schedule_v4.py diff --git a/23-dyn-attr-prop/oscon/test_schedule_v5.py b/22-dyn-attr-prop/oscon/test_schedule_v5.py similarity index 100% rename from 23-dyn-attr-prop/oscon/test_schedule_v5.py rename to 22-dyn-attr-prop/oscon/test_schedule_v5.py diff --git a/23-dyn-attr-prop/pseudo_construction.py b/22-dyn-attr-prop/pseudo_construction.py similarity index 100% rename from 23-dyn-attr-prop/pseudo_construction.py rename to 22-dyn-attr-prop/pseudo_construction.py diff --git a/24-descriptor/README.rst b/23-descriptor/README.rst similarity index 100% rename from 24-descriptor/README.rst rename to 23-descriptor/README.rst diff --git a/24-descriptor/bulkfood/bulkfood_v3.py b/23-descriptor/bulkfood/bulkfood_v3.py similarity index 100% rename from 24-descriptor/bulkfood/bulkfood_v3.py rename to 23-descriptor/bulkfood/bulkfood_v3.py diff --git a/24-descriptor/bulkfood/bulkfood_v4.py b/23-descriptor/bulkfood/bulkfood_v4.py similarity index 100% rename from 24-descriptor/bulkfood/bulkfood_v4.py rename to 23-descriptor/bulkfood/bulkfood_v4.py diff --git a/24-descriptor/bulkfood/bulkfood_v4c.py b/23-descriptor/bulkfood/bulkfood_v4c.py similarity index 100% rename from 24-descriptor/bulkfood/bulkfood_v4c.py rename to 23-descriptor/bulkfood/bulkfood_v4c.py diff --git a/24-descriptor/bulkfood/bulkfood_v5.py b/23-descriptor/bulkfood/bulkfood_v5.py similarity index 100% rename from 24-descriptor/bulkfood/bulkfood_v5.py rename to 23-descriptor/bulkfood/bulkfood_v5.py diff --git a/24-descriptor/bulkfood/model_v4c.py b/23-descriptor/bulkfood/model_v4c.py similarity index 100% rename from 24-descriptor/bulkfood/model_v4c.py rename to 23-descriptor/bulkfood/model_v4c.py diff --git a/24-descriptor/bulkfood/model_v5.py b/23-descriptor/bulkfood/model_v5.py similarity index 100% rename from 24-descriptor/bulkfood/model_v5.py rename to 23-descriptor/bulkfood/model_v5.py diff --git a/24-descriptor/descriptorkinds.py b/23-descriptor/descriptorkinds.py similarity index 100% rename from 24-descriptor/descriptorkinds.py rename to 23-descriptor/descriptorkinds.py diff --git a/24-descriptor/descriptorkinds_dump.py b/23-descriptor/descriptorkinds_dump.py similarity index 100% rename from 24-descriptor/descriptorkinds_dump.py rename to 23-descriptor/descriptorkinds_dump.py diff --git a/24-descriptor/method_is_descriptor.py b/23-descriptor/method_is_descriptor.py similarity index 100% rename from 24-descriptor/method_is_descriptor.py rename to 23-descriptor/method_is_descriptor.py diff --git a/25-class-metaprog/autoconst/autoconst.py b/24-class-metaprog/autoconst/autoconst.py similarity index 100% rename from 25-class-metaprog/autoconst/autoconst.py rename to 24-class-metaprog/autoconst/autoconst.py diff --git a/25-class-metaprog/autoconst/autoconst_demo.py b/24-class-metaprog/autoconst/autoconst_demo.py similarity index 100% rename from 25-class-metaprog/autoconst/autoconst_demo.py rename to 24-class-metaprog/autoconst/autoconst_demo.py diff --git a/25-class-metaprog/bulkfood/README.md b/24-class-metaprog/bulkfood/README.md similarity index 100% rename from 25-class-metaprog/bulkfood/README.md rename to 24-class-metaprog/bulkfood/README.md diff --git a/25-class-metaprog/bulkfood/bulkfood_v6.py b/24-class-metaprog/bulkfood/bulkfood_v6.py similarity index 100% rename from 25-class-metaprog/bulkfood/bulkfood_v6.py rename to 24-class-metaprog/bulkfood/bulkfood_v6.py diff --git a/25-class-metaprog/bulkfood/bulkfood_v7.py b/24-class-metaprog/bulkfood/bulkfood_v7.py similarity index 100% rename from 25-class-metaprog/bulkfood/bulkfood_v7.py rename to 24-class-metaprog/bulkfood/bulkfood_v7.py diff --git a/25-class-metaprog/bulkfood/bulkfood_v8.py b/24-class-metaprog/bulkfood/bulkfood_v8.py similarity index 100% rename from 25-class-metaprog/bulkfood/bulkfood_v8.py rename to 24-class-metaprog/bulkfood/bulkfood_v8.py diff --git a/25-class-metaprog/bulkfood/model_v6.py b/24-class-metaprog/bulkfood/model_v6.py similarity index 100% rename from 25-class-metaprog/bulkfood/model_v6.py rename to 24-class-metaprog/bulkfood/model_v6.py diff --git a/25-class-metaprog/bulkfood/model_v7.py b/24-class-metaprog/bulkfood/model_v7.py similarity index 100% rename from 25-class-metaprog/bulkfood/model_v7.py rename to 24-class-metaprog/bulkfood/model_v7.py diff --git a/25-class-metaprog/bulkfood/model_v8.py b/24-class-metaprog/bulkfood/model_v8.py similarity index 100% rename from 25-class-metaprog/bulkfood/model_v8.py rename to 24-class-metaprog/bulkfood/model_v8.py diff --git a/25-class-metaprog/checked/decorator/checkeddeco.py b/24-class-metaprog/checked/decorator/checkeddeco.py similarity index 100% rename from 25-class-metaprog/checked/decorator/checkeddeco.py rename to 24-class-metaprog/checked/decorator/checkeddeco.py diff --git a/25-class-metaprog/checked/decorator/checkeddeco_demo.py b/24-class-metaprog/checked/decorator/checkeddeco_demo.py similarity index 100% rename from 25-class-metaprog/checked/decorator/checkeddeco_demo.py rename to 24-class-metaprog/checked/decorator/checkeddeco_demo.py diff --git a/25-class-metaprog/checked/decorator/checkeddeco_test.py b/24-class-metaprog/checked/decorator/checkeddeco_test.py similarity index 100% rename from 25-class-metaprog/checked/decorator/checkeddeco_test.py rename to 24-class-metaprog/checked/decorator/checkeddeco_test.py diff --git a/25-class-metaprog/checked/initsub/checked_demo.py b/24-class-metaprog/checked/initsub/checked_demo.py similarity index 100% rename from 25-class-metaprog/checked/initsub/checked_demo.py rename to 24-class-metaprog/checked/initsub/checked_demo.py diff --git a/25-class-metaprog/checked/initsub/checkedlib.py b/24-class-metaprog/checked/initsub/checkedlib.py similarity index 100% rename from 25-class-metaprog/checked/initsub/checkedlib.py rename to 24-class-metaprog/checked/initsub/checkedlib.py diff --git a/25-class-metaprog/checked/initsub/checkedlib_test.py b/24-class-metaprog/checked/initsub/checkedlib_test.py similarity index 100% rename from 25-class-metaprog/checked/initsub/checkedlib_test.py rename to 24-class-metaprog/checked/initsub/checkedlib_test.py diff --git a/25-class-metaprog/checked/metaclass/checked_demo.py b/24-class-metaprog/checked/metaclass/checked_demo.py similarity index 100% rename from 25-class-metaprog/checked/metaclass/checked_demo.py rename to 24-class-metaprog/checked/metaclass/checked_demo.py diff --git a/25-class-metaprog/checked/metaclass/checkedlib.py b/24-class-metaprog/checked/metaclass/checkedlib.py similarity index 100% rename from 25-class-metaprog/checked/metaclass/checkedlib.py rename to 24-class-metaprog/checked/metaclass/checkedlib.py diff --git a/25-class-metaprog/checked/metaclass/checkedlib_test.py b/24-class-metaprog/checked/metaclass/checkedlib_test.py similarity index 100% rename from 25-class-metaprog/checked/metaclass/checkedlib_test.py rename to 24-class-metaprog/checked/metaclass/checkedlib_test.py diff --git a/25-class-metaprog/evaltime/builderlib.py b/24-class-metaprog/evaltime/builderlib.py similarity index 100% rename from 25-class-metaprog/evaltime/builderlib.py rename to 24-class-metaprog/evaltime/builderlib.py diff --git a/25-class-metaprog/evaltime/evaldemo.py b/24-class-metaprog/evaltime/evaldemo.py similarity index 100% rename from 25-class-metaprog/evaltime/evaldemo.py rename to 24-class-metaprog/evaltime/evaldemo.py diff --git a/25-class-metaprog/evaltime/evaldemo_meta.py b/24-class-metaprog/evaltime/evaldemo_meta.py similarity index 100% rename from 25-class-metaprog/evaltime/evaldemo_meta.py rename to 24-class-metaprog/evaltime/evaldemo_meta.py diff --git a/25-class-metaprog/evaltime/metalib.py b/24-class-metaprog/evaltime/metalib.py similarity index 100% rename from 25-class-metaprog/evaltime/metalib.py rename to 24-class-metaprog/evaltime/metalib.py diff --git a/25-class-metaprog/factories.py b/24-class-metaprog/factories.py similarity index 100% rename from 25-class-metaprog/factories.py rename to 24-class-metaprog/factories.py diff --git a/25-class-metaprog/factories_ducktyped.py b/24-class-metaprog/factories_ducktyped.py similarity index 100% rename from 25-class-metaprog/factories_ducktyped.py rename to 24-class-metaprog/factories_ducktyped.py diff --git a/25-class-metaprog/hours/hours.py b/24-class-metaprog/hours/hours.py similarity index 100% rename from 25-class-metaprog/hours/hours.py rename to 24-class-metaprog/hours/hours.py diff --git a/25-class-metaprog/hours/hours_test.py b/24-class-metaprog/hours/hours_test.py similarity index 100% rename from 25-class-metaprog/hours/hours_test.py rename to 24-class-metaprog/hours/hours_test.py diff --git a/25-class-metaprog/metabunch/README.md b/24-class-metaprog/metabunch/README.md similarity index 100% rename from 25-class-metaprog/metabunch/README.md rename to 24-class-metaprog/metabunch/README.md diff --git a/25-class-metaprog/metabunch/from3.6/bunch.py b/24-class-metaprog/metabunch/from3.6/bunch.py similarity index 100% rename from 25-class-metaprog/metabunch/from3.6/bunch.py rename to 24-class-metaprog/metabunch/from3.6/bunch.py diff --git a/25-class-metaprog/metabunch/from3.6/bunch_test.py b/24-class-metaprog/metabunch/from3.6/bunch_test.py similarity index 100% rename from 25-class-metaprog/metabunch/from3.6/bunch_test.py rename to 24-class-metaprog/metabunch/from3.6/bunch_test.py diff --git a/25-class-metaprog/metabunch/nutshell3e/bunch.py b/24-class-metaprog/metabunch/nutshell3e/bunch.py similarity index 100% rename from 25-class-metaprog/metabunch/nutshell3e/bunch.py rename to 24-class-metaprog/metabunch/nutshell3e/bunch.py diff --git a/25-class-metaprog/metabunch/nutshell3e/bunch_test.py b/24-class-metaprog/metabunch/nutshell3e/bunch_test.py similarity index 100% rename from 25-class-metaprog/metabunch/nutshell3e/bunch_test.py rename to 24-class-metaprog/metabunch/nutshell3e/bunch_test.py diff --git a/25-class-metaprog/metabunch/original/bunch.py b/24-class-metaprog/metabunch/original/bunch.py similarity index 100% rename from 25-class-metaprog/metabunch/original/bunch.py rename to 24-class-metaprog/metabunch/original/bunch.py diff --git a/25-class-metaprog/metabunch/original/bunch_test.py b/24-class-metaprog/metabunch/original/bunch_test.py similarity index 100% rename from 25-class-metaprog/metabunch/original/bunch_test.py rename to 24-class-metaprog/metabunch/original/bunch_test.py diff --git a/25-class-metaprog/metabunch/pre3.6/bunch.py b/24-class-metaprog/metabunch/pre3.6/bunch.py similarity index 100% rename from 25-class-metaprog/metabunch/pre3.6/bunch.py rename to 24-class-metaprog/metabunch/pre3.6/bunch.py diff --git a/25-class-metaprog/metabunch/pre3.6/bunch_test.py b/24-class-metaprog/metabunch/pre3.6/bunch_test.py similarity index 100% rename from 25-class-metaprog/metabunch/pre3.6/bunch_test.py rename to 24-class-metaprog/metabunch/pre3.6/bunch_test.py diff --git a/25-class-metaprog/persistent/.gitignore b/24-class-metaprog/persistent/.gitignore similarity index 100% rename from 25-class-metaprog/persistent/.gitignore rename to 24-class-metaprog/persistent/.gitignore diff --git a/25-class-metaprog/persistent/dblib.py b/24-class-metaprog/persistent/dblib.py similarity index 100% rename from 25-class-metaprog/persistent/dblib.py rename to 24-class-metaprog/persistent/dblib.py diff --git a/25-class-metaprog/persistent/dblib_test.py b/24-class-metaprog/persistent/dblib_test.py similarity index 100% rename from 25-class-metaprog/persistent/dblib_test.py rename to 24-class-metaprog/persistent/dblib_test.py diff --git a/25-class-metaprog/persistent/persistlib.py b/24-class-metaprog/persistent/persistlib.py similarity index 100% rename from 25-class-metaprog/persistent/persistlib.py rename to 24-class-metaprog/persistent/persistlib.py diff --git a/25-class-metaprog/persistent/persistlib_test.py b/24-class-metaprog/persistent/persistlib_test.py similarity index 100% rename from 25-class-metaprog/persistent/persistlib_test.py rename to 24-class-metaprog/persistent/persistlib_test.py diff --git a/25-class-metaprog/qualname/fakedjango.py b/24-class-metaprog/qualname/fakedjango.py similarity index 100% rename from 25-class-metaprog/qualname/fakedjango.py rename to 24-class-metaprog/qualname/fakedjango.py diff --git a/25-class-metaprog/qualname/models.py b/24-class-metaprog/qualname/models.py similarity index 100% rename from 25-class-metaprog/qualname/models.py rename to 24-class-metaprog/qualname/models.py diff --git a/25-class-metaprog/sentinel/sentinel.py b/24-class-metaprog/sentinel/sentinel.py similarity index 100% rename from 25-class-metaprog/sentinel/sentinel.py rename to 24-class-metaprog/sentinel/sentinel.py diff --git a/25-class-metaprog/sentinel/sentinel_test.py b/24-class-metaprog/sentinel/sentinel_test.py similarity index 100% rename from 25-class-metaprog/sentinel/sentinel_test.py rename to 24-class-metaprog/sentinel/sentinel_test.py diff --git a/25-class-metaprog/setattr/example_from_leo.py b/24-class-metaprog/setattr/example_from_leo.py similarity index 100% rename from 25-class-metaprog/setattr/example_from_leo.py rename to 24-class-metaprog/setattr/example_from_leo.py diff --git a/25-class-metaprog/slots/slots_timing.py b/24-class-metaprog/slots/slots_timing.py similarity index 100% rename from 25-class-metaprog/slots/slots_timing.py rename to 24-class-metaprog/slots/slots_timing.py diff --git a/25-class-metaprog/tinyenums/microenum.py b/24-class-metaprog/tinyenums/microenum.py similarity index 100% rename from 25-class-metaprog/tinyenums/microenum.py rename to 24-class-metaprog/tinyenums/microenum.py diff --git a/25-class-metaprog/tinyenums/microenum_demo.py b/24-class-metaprog/tinyenums/microenum_demo.py similarity index 100% rename from 25-class-metaprog/tinyenums/microenum_demo.py rename to 24-class-metaprog/tinyenums/microenum_demo.py diff --git a/25-class-metaprog/tinyenums/nanoenum.py b/24-class-metaprog/tinyenums/nanoenum.py similarity index 100% rename from 25-class-metaprog/tinyenums/nanoenum.py rename to 24-class-metaprog/tinyenums/nanoenum.py diff --git a/25-class-metaprog/tinyenums/nanoenum_demo.py b/24-class-metaprog/tinyenums/nanoenum_demo.py similarity index 100% rename from 25-class-metaprog/tinyenums/nanoenum_demo.py rename to 24-class-metaprog/tinyenums/nanoenum_demo.py diff --git a/README.md b/README.md index 102993a..f0854e9 100644 --- a/README.md +++ b/README.md @@ -41,13 +41,12 @@ Part / Chapter #|Title|Directory|1st ed. Chapter # 15|More About Type Hints|[15-more-types](15-more-types)|🆕 16|Operator Overloading: Doing It Right|[16-op-overloading](16-op-overloading)|13 **V – Control Flow**| -17|Iterables, Iterators, and Generators|[17-it-generator](17-it-generator)|14 +17|Iterators, Generators, and Classic Coroutines|[17-it-generator](17-it-generator)|14 18|Context Managers and else Blocks|[18-with-match](18-with-match)|15 -19|Classic Coroutines|[19-coroutine](19-coroutine)|16 -20|Concurrency Models in Python|[20-concurrency](20-concurrency)|🆕 -21|Concurrency with Futures|[21-futures](21-futures)|17 -22|Asynchronous Programming|[22-async](22-async)|18 +19|Concurrency Models in Python|[19-concurrency](19-concurrency)|🆕 +20|Concurrency with Futures|[20-futures](20-futures)|17 +21|Asynchronous Programming|[21-async](21-async)|18 **VI – Metaprogramming**| -23|Dynamic Attributes and Properties|[22-dyn-attr-prop](22-dyn-attr-prop)|19 -24|Attribute Descriptors|[23-descriptor](23-descriptor)|20 -25|Class Metaprogramming|[24-class-metaprog](24-class-metaprog)|21 +22|Dynamic Attributes and Properties|[22-dyn-attr-prop](22-dyn-attr-prop)|19 +23|Attribute Descriptors|[23-descriptor](23-descriptor)|20 +24|Class Metaprogramming|[24-class-metaprog](24-class-metaprog)|21 From 6527037ae7319ba370a1ee2d9fe79214d0ed9452 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Wed, 15 Sep 2021 22:48:08 -0300 Subject: [PATCH 106/166] moved lispy from 02 to 18 --- 02-array-seq/lispy/py3.10/examples_test.py | 109 -------- 02-array-seq/lispy/py3.10/meta_test.py | 69 ----- 02-array-seq/lispy/py3.9-no-hints/README.md | 33 --- 02-array-seq/lispy/py3.9-no-hints/lis.py | 142 ---------- 02-array-seq/lispy/py3.9-no-hints/lis_test.py | 166 ----------- 05-data-classes/dataclass/hackerclub.py | 2 +- .../dataclass/hackerclub_annotated.py | 2 +- 17-it-generator/aritprog_v3.py | 6 +- 17-it-generator/coroaverager.py | 43 +++ 17-it-generator/coroaverager2.py | 96 +++++++ 17-it-generator/fibo_gen.py | 7 + 17-it-generator/sentence_gen.py | 2 +- 18-with-match/lisplus/examples_test.py | 109 -------- 18-with-match/lisplus/lis.py | 192 ------------- 18-with-match/lisplus/lis_test.py | 182 ------------ 18-with-match/lisplus/meta_test.py | 64 ----- {02-array-seq => 18-with-match}/lispy/LICENSE | 0 .../lispy/README.md | 0 .../lispy/original/LICENSE | 0 .../lispy/original/README.md | 0 .../lispy/original/lis.py | 0 .../lispy/original/lispy.py | 0 .../lispy/original/lispytest.py | 0 18-with-match/lispy/py3.10/examples_test.py | 259 ++++++++++++++++++ .../lispy/py3.10/lis.py | 178 +++++++----- .../lispy/py3.10/lis_test.py | 0 18-with-match/lispy/py3.10/quicksort.scm | 17 ++ .../lispy/py3.9/README.md | 0 .../lispy/py3.9/lis.py | 18 +- .../lispy/py3.9/lis_test.py | 0 18-with-match/mirror.py | 28 +- 18-with-match/mirror_gen.py | 22 +- 18-with-match/mirror_gen_exc.py | 9 +- 20-futures/getflags/flags2_asyncio.py | 4 +- 20-futures/getflags/flags3_asyncio.py | 4 +- 20-futures/getflags/requirements.txt | 16 +- 20-futures/getflags/slow_server.py | 1 + 22-dyn-attr-prop/oscon/demo_schedule2.py | 25 -- 38 files changed, 588 insertions(+), 1217 deletions(-) delete mode 100644 02-array-seq/lispy/py3.10/examples_test.py delete mode 100644 02-array-seq/lispy/py3.10/meta_test.py delete mode 100644 02-array-seq/lispy/py3.9-no-hints/README.md delete mode 100644 02-array-seq/lispy/py3.9-no-hints/lis.py delete mode 100644 02-array-seq/lispy/py3.9-no-hints/lis_test.py create mode 100644 17-it-generator/coroaverager.py create mode 100644 17-it-generator/coroaverager2.py create mode 100644 17-it-generator/fibo_gen.py delete mode 100644 18-with-match/lisplus/examples_test.py delete mode 100644 18-with-match/lisplus/lis.py delete mode 100644 18-with-match/lisplus/lis_test.py delete mode 100644 18-with-match/lisplus/meta_test.py rename {02-array-seq => 18-with-match}/lispy/LICENSE (100%) rename {02-array-seq => 18-with-match}/lispy/README.md (100%) rename {02-array-seq => 18-with-match}/lispy/original/LICENSE (100%) rename {02-array-seq => 18-with-match}/lispy/original/README.md (100%) rename {02-array-seq => 18-with-match}/lispy/original/lis.py (100%) rename {02-array-seq => 18-with-match}/lispy/original/lispy.py (100%) rename {02-array-seq => 18-with-match}/lispy/original/lispytest.py (100%) create mode 100644 18-with-match/lispy/py3.10/examples_test.py rename {02-array-seq => 18-with-match}/lispy/py3.10/lis.py (70%) mode change 100644 => 100755 rename {02-array-seq => 18-with-match}/lispy/py3.10/lis_test.py (100%) create mode 100644 18-with-match/lispy/py3.10/quicksort.scm rename {02-array-seq => 18-with-match}/lispy/py3.9/README.md (100%) rename {02-array-seq => 18-with-match}/lispy/py3.9/lis.py (91%) rename {02-array-seq => 18-with-match}/lispy/py3.9/lis_test.py (100%) delete mode 100755 22-dyn-attr-prop/oscon/demo_schedule2.py diff --git a/02-array-seq/lispy/py3.10/examples_test.py b/02-array-seq/lispy/py3.10/examples_test.py deleted file mode 100644 index 38510a3..0000000 --- a/02-array-seq/lispy/py3.10/examples_test.py +++ /dev/null @@ -1,109 +0,0 @@ -""" -Doctests for `parse`: - ->>> from lis import parse - -# tag::PARSE_DEMO[] ->>> parse('1.5') # <1> -1.5 ->>> parse('set!') # <2> -'set!' ->>> parse('(gcd 18 44)') # <3> -['gcd', 18, 44] ->>> parse('(- m (* n (// m n)))') # <4> -['-', 'm', ['*', 'n', ['//', 'm', 'n']]] - -# end::PARSE_DEMO[] - -""" - -import math - -from lis import run - - -fact_src = """ -(define (! n) - (if (< n 2) - 1 - (* n (! (- n 1))) - ) -) -(! 42) -""" -def test_factorial(): - got = run(fact_src) - assert got == 1405006117752879898543142606244511569936384000000000 - assert got == math.factorial(42) - - -gcd_src = """ -(define (mod m n) - (- m (* n (// m n)))) -(define (gcd m n) - (if (= n 0) - m - (gcd n (mod m n)))) -(gcd 18 45) -""" -def test_gcd(): - got = run(gcd_src) - assert got == 9 - - -quicksort_src = """ -(define (quicksort lst) - (if (null? lst) - lst - (begin - (define pivot (car lst)) - (define rest (cdr lst)) - (append - (quicksort - (filter (lambda (x) (< x pivot)) rest)) - (list pivot) - (quicksort - (filter (lambda (x) (>= x pivot)) rest))) - ) - ) -) -(quicksort (list 2 1 6 3 4 0 8 9 7 5)) -""" -def test_quicksort(): - got = run(quicksort_src) - assert got == [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] - - -# Example from Structure and Interpretation of Computer Programs -# https://mitpress.mit.edu/sites/default/files/sicp/full-text/sicp/book/node12.html - -newton_src = """ -(define (sqrt x) - (sqrt-iter 1.0 x)) -(define (sqrt-iter guess x) - (if (good-enough? guess x) - guess - (sqrt-iter (improve guess x) x))) -(define (good-enough? guess x) - (< (abs (- (* guess guess) x)) 0.001)) -(define (improve guess x) - (average guess (/ x guess))) -(define (average x y) - (/ (+ x y) 2)) -(sqrt 123454321) -""" -def test_newton(): - got = run(newton_src) - assert math.isclose(got, 11111) - - -closure_src = """ -(define (make-adder increment) - (lambda (x) (+ increment x)) -) -(define inc (make-adder 1)) -(inc 99) -""" -def test_newton(): - got = run(closure_src) - assert got == 100 diff --git a/02-array-seq/lispy/py3.10/meta_test.py b/02-array-seq/lispy/py3.10/meta_test.py deleted file mode 100644 index 3ddc3f3..0000000 --- a/02-array-seq/lispy/py3.10/meta_test.py +++ /dev/null @@ -1,69 +0,0 @@ -""" -Tests for developing a meta-circular interpreter, step-by-step. -""" - - -import operator as op - -from lis import run - -env_scm = """ -(define standard-env (list - (list (quote +) +) - (list (quote -) -) -)) -standard-env -""" - -def test_env_build(): - got = run(env_scm) - assert got == [['+', op.add], ['-', op.sub]] - -scan_scm = """ -(define l (quote (a b c))) -(define (scan what where) - (if (null? where) - () - (if (eq? what (car where)) - what - (scan what (cdr where)))) -) -""" - -def test_scan(): - source = scan_scm + '(scan (quote a) l )' - got = run(source) - assert got == 'a' - - -def test_scan_not_found(): - source = scan_scm + '(scan (quote z) l )' - got = run(source) - assert got == [] - - -lookup_scm = """ -(define env (list - (list (quote +) +) - (list (quote -) -) -)) -(define (lookup what where) - (if (null? where) - () - (if (eq? what (car (car where))) - (car (cdr (car where))) - (lookup what (cdr where)))) -) -""" - -def test_lookup(): - source = lookup_scm + '(lookup (quote +) env)' - got = run(source) - assert got == op.add - - -def test_lookup_not_found(): - source = lookup_scm + '(lookup (quote z) env )' - got = run(source) - assert got == [] - diff --git a/02-array-seq/lispy/py3.9-no-hints/README.md b/02-array-seq/lispy/py3.9-no-hints/README.md deleted file mode 100644 index 2b6f4ea..0000000 --- a/02-array-seq/lispy/py3.9-no-hints/README.md +++ /dev/null @@ -1,33 +0,0 @@ -# Changes from the original - -While adapting Peter Norvig's [lis.py](https://github.com/norvig/pytudes/blob/705c0a335c1811a203e79587d7d41865cf7f41c7/py/lis.py) for -use in _Fluent Python, Second Edition_, I made a few changes for didactic reasons. - -_Luciano Ramalho_ - -## Significant changes - -* Make the `lambda` form accept more than one expression as the body. This is consistent with _Scheme_ syntax, and provides a useful example for the book. To implement this: - * In `Procedure.__call__`: evaluate `self.body` as a list of expressions, instead of a single expression. Return the value of the last expression. - * In `evaluate()`: when processing `lambda`, unpack expression into `(_, parms, *body)`, to accept a list of expressions as the body. -* Remove the `global_env` global `dict`. It is only used as a default value for the `env` parameter in `evaluate()`, but it is unsafe to use mutable data structures as parameter default values. To implement this: - * In `repl()`: create local variable `global_env` and pass it as the `env` paramater of `evaluate()`. - * In `evaluate()`, remove `global_env` default value for `env`. -* Rewrite the custom test script -[lispytest.py](https://github.com/norvig/pytudes/blob/705c0a335c1811a203e79587d7d41865cf7f41c7/py/lispytest.py) as -[lis_test.py](https://github.com/fluentpython/example-code-2e/blob/master/02-array-seq/lispy/py3.9/lis_test.py): -a standard [pytest](https://docs.pytest.org) test suite including new test cases, preserving all Norvig's the test cases for -[lis.py](https://github.com/norvig/pytudes/blob/705c0a335c1811a203e79587d7d41865cf7f41c7/py/lis.py) -but removing the test cases for the features implemented only in -[lispy.py](https://github.com/norvig/pytudes/blob/705c0a335c1811a203e79587d7d41865cf7f41c7/py/lispy.py) - - -## Name changes - -Cosmetic changes to make the code look more familiar to -Python programmers, the audience of _Fluent Python_. - -* Rename `eval()` to `evaluate()`, to avoid confusion with Python's `eval` built-in function. -* Refer to the list class as `list` instead of aliasing as `List`, to avoid confusion with `typing.List` which is often imported as `List`. -* Import `collections.ChainMap` as `ChainMap` instead of `Environment`. - diff --git a/02-array-seq/lispy/py3.9-no-hints/lis.py b/02-array-seq/lispy/py3.9-no-hints/lis.py deleted file mode 100644 index c74c3ae..0000000 --- a/02-array-seq/lispy/py3.9-no-hints/lis.py +++ /dev/null @@ -1,142 +0,0 @@ -################ Lispy: Scheme Interpreter in Python 3.9 - -## (c) Peter Norvig, 2010-18; See http://norvig.com/lispy.html -## Minor edits for Fluent Python, Second Edition (O'Reilly, 2021) -## by Luciano Ramalho, adding type hints and pattern matching. - -################ Imports and Types - -import math -import operator as op -from collections import ChainMap -from typing import Any - -Symbol = str # A Lisp Symbol is implemented as a Python str -Number = (int, float) # A Lisp Number is implemented as a Python int or float - -class Procedure: - "A user-defined Scheme procedure." - def __init__(self, parms, body, env): - self.parms, self.body, self.env = parms, body, env - def __call__(self, *args): - env = ChainMap(dict(zip(self.parms, args)), self.env) - for exp in self.body: - result = evaluate(exp, env) - return result - - -################ Global Environment - -def standard_env(): - "An environment with some Scheme standard procedures." - env = {} - env.update(vars(math)) # sin, cos, sqrt, pi, ... - env.update({ - '+':op.add, '-':op.sub, '*':op.mul, '/':op.truediv, - '>':op.gt, '<':op.lt, '>=':op.ge, '<=':op.le, '=':op.eq, - 'abs': abs, - 'append': op.add, - 'apply': lambda proc, args: proc(*args), - 'begin': lambda *x: x[-1], - 'car': lambda x: x[0], - 'cdr': lambda x: x[1:], - 'cons': lambda x,y: [x] + y, - 'eq?': op.is_, - 'equal?': op.eq, - 'length': len, - 'list': lambda *x: list(x), - 'list?': lambda x: isinstance(x,list), - 'map': lambda *args: list(map(*args)), - 'max': max, - 'min': min, - 'not': op.not_, - 'null?': lambda x: x == [], - 'number?': lambda x: isinstance(x, Number), - 'procedure?': callable, - 'round': round, - 'symbol?': lambda x: isinstance(x, Symbol), - }) - return env - -################ Parsing: parse, tokenize, and read_from_tokens - -def parse(program): - "Read a Scheme expression from a string." - return read_from_tokens(tokenize(program)) - -def tokenize(s): - "Convert a string into a list of tokens." - return s.replace('(',' ( ').replace(')',' ) ').split() - -def read_from_tokens(tokens): - "Read an expression from a sequence of tokens." - if len(tokens) == 0: - raise SyntaxError('unexpected EOF while reading') - token = tokens.pop(0) - if '(' == token: - exp = [] - while tokens[0] != ')': - exp.append(read_from_tokens(tokens)) - tokens.pop(0) # discard ')' - return exp - elif ')' == token: - raise SyntaxError('unexpected )') - else: - return parse_atom(token) - - -def parse_atom(token: str): - "Numbers become numbers; every other token is a symbol." - try: - return int(token) - except ValueError: - try: - return float(token) - except ValueError: - return Symbol(token) - - -################ Interaction: A REPL - -def repl(prompt: str = 'lis.py> ') -> None: - "A prompt-read-eval-print loop." - global_env = standard_env() - while True: - val = evaluate(parse(input(prompt)), global_env) - if val is not None: - print(lispstr(val)) - - -def lispstr(exp): - "Convert a Python object back into a Lisp-readable string." - if isinstance(exp, list): - return '(' + ' '.join(map(lispstr, exp)) + ')' - else: - return str(exp) - - -################ eval - -def evaluate(x, env): - "Evaluate an expression in an environment." - if isinstance(x, Symbol): # variable reference - return env[x] - elif not isinstance(x, list): # constant literal - return x - elif x[0] == 'quote': # (quote exp) - (_, exp) = x - return exp - elif x[0] == 'if': # (if test conseq alt) - (_, test, conseq, alt) = x - exp = (conseq if evaluate(test, env) else alt) - return evaluate(exp, env) - elif x[0] == 'define': # (define var exp) - (_, var, exp) = x - env[var] = evaluate(exp, env) - elif x[0] == 'lambda': # (lambda (var...) body) - (_, parms, *body) = x - return Procedure(parms, body, env) - else: # (proc arg...) - proc = evaluate(x[0], env) - args = [evaluate(exp, env) for exp in x[1:]] - return proc(*args) diff --git a/02-array-seq/lispy/py3.9-no-hints/lis_test.py b/02-array-seq/lispy/py3.9-no-hints/lis_test.py deleted file mode 100644 index f5f4f8e..0000000 --- a/02-array-seq/lispy/py3.9-no-hints/lis_test.py +++ /dev/null @@ -1,166 +0,0 @@ -from pytest import mark, fixture - -from lis import parse, evaluate, standard_env - -############################################################# tests for parse - -@mark.parametrize( 'source, expected', [ - ('7', 7), - ('x', 'x'), - ('(sum 1 2 3)', ['sum', 1, 2, 3]), - ('(+ (* 2 100) (* 1 10))', ['+', ['*', 2, 100], ['*', 1, 10]]), - ('99 100', 99), # parse stops at the first complete expression - ('(a)(b)', ['a']), -]) -def test_parse(source: str, expected): - got = parse(source) - assert got == expected - - -########################################################## tests for evaluate - -# Norvig's tests are not isolated: they assume the -# same environment from first to last test. -global_env_for_first_test = standard_env() - -@mark.parametrize( 'source, expected', [ - ("(quote (testing 1 (2.0) -3.14e159))", ['testing', 1, [2.0], -3.14e159]), - ("(+ 2 2)", 4), - ("(+ (* 2 100) (* 1 10))", 210), - ("(if (> 6 5) (+ 1 1) (+ 2 2))", 2), - ("(if (< 6 5) (+ 1 1) (+ 2 2))", 4), - ("(define x 3)", None), - ("x", 3), - ("(+ x x)", 6), - ("((lambda (x) (+ x x)) 5)", 10), - ("(define twice (lambda (x) (* 2 x)))", None), - ("(twice 5)", 10), - ("(define compose (lambda (f g) (lambda (x) (f (g x)))))", None), - ("((compose list twice) 5)", [10]), - ("(define repeat (lambda (f) (compose f f)))", None), - ("((repeat twice) 5)", 20), - ("((repeat (repeat twice)) 5)", 80), - ("(define fact (lambda (n) (if (<= n 1) 1 (* n (fact (- n 1))))))", None), - ("(fact 3)", 6), - ("(fact 50)", 30414093201713378043612608166064768844377641568960512000000000000), - ("(define abs (lambda (n) ((if (> n 0) + -) 0 n)))", None), - ("(list (abs -3) (abs 0) (abs 3))", [3, 0, 3]), - ("""(define combine (lambda (f) - (lambda (x y) - (if (null? x) (quote ()) - (f (list (car x) (car y)) - ((combine f) (cdr x) (cdr y)))))))""", None), - ("(define zip (combine cons))", None), - ("(zip (list 1 2 3 4) (list 5 6 7 8))", [[1, 5], [2, 6], [3, 7], [4, 8]]), - ("""(define riff-shuffle (lambda (deck) - (begin - (define take (lambda (n seq) (if (<= n 0) (quote ()) (cons (car seq) (take (- n 1) (cdr seq)))))) - (define drop (lambda (n seq) (if (<= n 0) seq (drop (- n 1) (cdr seq))))) - (define mid (lambda (seq) (/ (length seq) 2))) - ((combine append) (take (mid deck) deck) (drop (mid deck) deck)))))""", None), - ("(riff-shuffle (list 1 2 3 4 5 6 7 8))", [1, 5, 2, 6, 3, 7, 4, 8]), - ("((repeat riff-shuffle) (list 1 2 3 4 5 6 7 8))", [1, 3, 5, 7, 2, 4, 6, 8]), - ("(riff-shuffle (riff-shuffle (riff-shuffle (list 1 2 3 4 5 6 7 8))))", [1,2,3,4,5,6,7,8]), -]) -def test_evaluate(source, expected): - got = evaluate(parse(source), global_env_for_first_test) - assert got == expected - - -@fixture -def std_env(): - return standard_env() - -# tests for each of the cases in evaluate - -def test_evaluate_variable(): - env = dict(x=10) - source = 'x' - expected = 10 - got = evaluate(parse(source), env) - assert got == expected - - -def test_evaluate_literal(std_env): - source = '3.3' - expected = 3.3 - got = evaluate(parse(source), std_env) - assert got == expected - - -def test_evaluate_quote(std_env): - source = '(quote (1.1 is not 1))' - expected = [1.1, 'is', 'not', 1] - got = evaluate(parse(source), std_env) - assert got == expected - - -def test_evaluate_if_true(std_env) -> None: - source = '(if 1 10 no-such-thing)' - expected = 10 - got = evaluate(parse(source), std_env) - assert got == expected - - -def test_evaluate_if_false(std_env) -> None: - source = '(if 0 no-such-thing 20)' - expected = 20 - got = evaluate(parse(source), std_env) - assert got == expected - - -def test_define(std_env) -> None: - source = '(define answer (* 6 7))' - got = evaluate(parse(source), std_env) - assert got is None - assert std_env['answer'] == 42 - - -def test_lambda(std_env) -> None: - source = '(lambda (a b) (if (>= a b) a b))' - func = evaluate(parse(source), std_env) - assert func.parms == ['a', 'b'] - assert func.body == [['if', ['>=', 'a', 'b'], 'a', 'b']] - assert func.env is std_env - assert func(1, 2) == 2 - assert func(3, 2) == 3 - - -def test_begin(std_env) -> None: - source = """ - (begin - (define x (* 2 3)) - (* x 7) - ) - """ - got = evaluate(parse(source), std_env) - assert got == 42 - - -def test_invocation_builtin_car(std_env) -> None: - source = '(car (quote (11 22 33)))' - got = evaluate(parse(source), std_env) - assert got == 11 - - -def test_invocation_builtin_append(std_env) -> None: - source = '(append (quote (a b)) (quote (c d)))' - got = evaluate(parse(source), std_env) - assert got == ['a', 'b', 'c', 'd'] - - -def test_invocation_builtin_map(std_env) -> None: - source = '(map (lambda (x) (* x 2)) (quote (1 2 3))))' - got = evaluate(parse(source), std_env) - assert got == [2, 4, 6] - - -def test_invocation_user_procedure(std_env): - source = """ - (begin - (define max (lambda (a b) (if (>= a b) a b))) - (max 22 11) - ) - """ - got = evaluate(parse(source), std_env) - assert got == 22 diff --git a/05-data-classes/dataclass/hackerclub.py b/05-data-classes/dataclass/hackerclub.py index 762c2cd..60f8234 100644 --- a/05-data-classes/dataclass/hackerclub.py +++ b/05-data-classes/dataclass/hackerclub.py @@ -6,7 +6,7 @@ >>> anna HackerClubMember(name='Anna Ravenscroft', guests=[], handle='AnnaRaven') -If ``handle`` is ommitted, it's set to the first part of the member's name:: +If ``handle`` is omitted, it's set to the first part of the member's name:: >>> leo = HackerClubMember('Leo Rochael') >>> leo diff --git a/05-data-classes/dataclass/hackerclub_annotated.py b/05-data-classes/dataclass/hackerclub_annotated.py index 2394796..0b87c50 100644 --- a/05-data-classes/dataclass/hackerclub_annotated.py +++ b/05-data-classes/dataclass/hackerclub_annotated.py @@ -6,7 +6,7 @@ >>> anna HackerClubMember(name='Anna Ravenscroft', guests=[], handle='AnnaRaven') -If ``handle`` is ommitted, it's set to the first part of the member's name:: +If ``handle`` is omitted, it's set to the first part of the member's name:: >>> leo = HackerClubMember('Leo Rochael') >>> leo diff --git a/17-it-generator/aritprog_v3.py b/17-it-generator/aritprog_v3.py index 3dd8e18..914f5c9 100644 --- a/17-it-generator/aritprog_v3.py +++ b/17-it-generator/aritprog_v3.py @@ -5,7 +5,7 @@ def aritprog_gen(begin, step, end=None): first = type(begin + step)(begin) ap_gen = itertools.count(first, step) - if end is not None: - ap_gen = itertools.takewhile(lambda n: n < end, ap_gen) - return ap_gen + if end is None: + return ap_gen + return itertools.takewhile(lambda n: n < end, ap_gen) # end::ARITPROG_ITERTOOLS[] diff --git a/17-it-generator/coroaverager.py b/17-it-generator/coroaverager.py new file mode 100644 index 0000000..d976760 --- /dev/null +++ b/17-it-generator/coroaverager.py @@ -0,0 +1,43 @@ +""" +A coroutine to compute a running average + +# tag::CORO_AVERAGER_TEST[] + >>> coro_avg = averager() # <1> + >>> next(coro_avg) # <2> + 0.0 + >>> coro_avg.send(10) # <3> + 10.0 + >>> coro_avg.send(30) + 20.0 + >>> coro_avg.send(5) + 15.0 + +# end::CORO_AVERAGER_TEST[] +# tag::CORO_AVERAGER_TEST_CONT[] + + >>> coro_avg.send(20) # <1> + 16.25 + >>> coro_avg.close() # <2> + >>> coro_avg.close() # <3> + >>> coro_avg.send(5) # <4> + Traceback (most recent call last): + ... + StopIteration + +# end::CORO_AVERAGER_TEST_CONT[] + +""" + +# tag::CORO_AVERAGER[] +from collections.abc import Generator + +def averager() -> Generator[float, float, None]: # <1> + total = 0.0 + count = 0 + average = 0.0 + while True: # <2> + term = yield average # <3> + total += term + count += 1 + average = total/count +# end::CORO_AVERAGER[] diff --git a/17-it-generator/coroaverager2.py b/17-it-generator/coroaverager2.py new file mode 100644 index 0000000..3a15f5e --- /dev/null +++ b/17-it-generator/coroaverager2.py @@ -0,0 +1,96 @@ +""" +A coroutine to compute a running average. + +Testing ``averager2`` by itself:: + +# tag::RETURNING_AVERAGER_DEMO_1[] + + >>> coro_avg = averager2() + >>> next(coro_avg) + >>> coro_avg.send(10) # <1> + >>> coro_avg.send(30) + >>> coro_avg.send(6.5) + >>> coro_avg.close() # <2> + +# end::RETURNING_AVERAGER_DEMO_1[] + +Catching `StopIteration` to extract the value returned by +the coroutine:: + +# tag::RETURNING_AVERAGER_DEMO_2[] + + >>> coro_avg = averager2() + >>> next(coro_avg) + >>> coro_avg.send(10) + >>> coro_avg.send(30) + >>> coro_avg.send(6.5) + >>> try: + ... coro_avg.send(STOP) # <1> + ... except StopIteration as exc: + ... result = exc.value # <2> + ... + >>> result # <3> + Result(count=3, average=15.5) + +# end::RETURNING_AVERAGER_DEMO_2[] + +Using `yield from`: + + +# tag::RETURNING_AVERAGER_DEMO_3[] + + >>> def compute(): + ... res = yield from averager2(True) # <1> + ... print('computed:', res) # <2> + ... return res # <3> + ... + >>> comp = compute() # <4> + >>> for v in [None, 10, 20, 30, STOP]: # <5> + ... try: + ... comp.send(v) # <6> + ... except StopIteration as exc: # <7> + ... result = exc.value + received: 10 + received: 20 + received: 30 + received: + computed: Result(count=3, average=20.0) + >>> result # <8> + Result(count=3, average=20.0) + +# end::RETURNING_AVERAGER_DEMO_3[] +""" + +# tag::RETURNING_AVERAGER_TOP[] +from collections.abc import Generator +from typing import Union, NamedTuple + +class Result(NamedTuple): # <1> + count: int # type: ignore # <2> + average: float + +class Sentinel: # <3> + def __repr__(self): + return f'' + +STOP = Sentinel() # <4> + +SendType = Union[float, Sentinel] # <5> +# end::RETURNING_AVERAGER_TOP[] +# tag::RETURNING_AVERAGER[] +def averager2(verbose: bool = False) -> Generator[None, SendType, Result]: # <1> + total = 0.0 + count = 0 + average = 0.0 + while True: + term = yield # <2> + if verbose: + print('received:', term) + if isinstance(term, Sentinel): # <3> + break + total += term # <4> + count += 1 + average = total / count + return Result(count, average) # <5> + +# end::RETURNING_AVERAGER[] diff --git a/17-it-generator/fibo_gen.py b/17-it-generator/fibo_gen.py new file mode 100644 index 0000000..d7296ac --- /dev/null +++ b/17-it-generator/fibo_gen.py @@ -0,0 +1,7 @@ +from collections.abc import Iterator + +def fibonacci() -> Iterator[int]: + a, b = 0, 1 + while True: + yield a + a, b = b, a + b diff --git a/17-it-generator/sentence_gen.py b/17-it-generator/sentence_gen.py index 3dbc606..1f7ebeb 100644 --- a/17-it-generator/sentence_gen.py +++ b/17-it-generator/sentence_gen.py @@ -21,7 +21,7 @@ def __repr__(self): def __iter__(self): for word in self.words: # <1> yield word # <2> - return # <3> + # <3> # done! <4> diff --git a/18-with-match/lisplus/examples_test.py b/18-with-match/lisplus/examples_test.py deleted file mode 100644 index 38510a3..0000000 --- a/18-with-match/lisplus/examples_test.py +++ /dev/null @@ -1,109 +0,0 @@ -""" -Doctests for `parse`: - ->>> from lis import parse - -# tag::PARSE_DEMO[] ->>> parse('1.5') # <1> -1.5 ->>> parse('set!') # <2> -'set!' ->>> parse('(gcd 18 44)') # <3> -['gcd', 18, 44] ->>> parse('(- m (* n (// m n)))') # <4> -['-', 'm', ['*', 'n', ['//', 'm', 'n']]] - -# end::PARSE_DEMO[] - -""" - -import math - -from lis import run - - -fact_src = """ -(define (! n) - (if (< n 2) - 1 - (* n (! (- n 1))) - ) -) -(! 42) -""" -def test_factorial(): - got = run(fact_src) - assert got == 1405006117752879898543142606244511569936384000000000 - assert got == math.factorial(42) - - -gcd_src = """ -(define (mod m n) - (- m (* n (// m n)))) -(define (gcd m n) - (if (= n 0) - m - (gcd n (mod m n)))) -(gcd 18 45) -""" -def test_gcd(): - got = run(gcd_src) - assert got == 9 - - -quicksort_src = """ -(define (quicksort lst) - (if (null? lst) - lst - (begin - (define pivot (car lst)) - (define rest (cdr lst)) - (append - (quicksort - (filter (lambda (x) (< x pivot)) rest)) - (list pivot) - (quicksort - (filter (lambda (x) (>= x pivot)) rest))) - ) - ) -) -(quicksort (list 2 1 6 3 4 0 8 9 7 5)) -""" -def test_quicksort(): - got = run(quicksort_src) - assert got == [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] - - -# Example from Structure and Interpretation of Computer Programs -# https://mitpress.mit.edu/sites/default/files/sicp/full-text/sicp/book/node12.html - -newton_src = """ -(define (sqrt x) - (sqrt-iter 1.0 x)) -(define (sqrt-iter guess x) - (if (good-enough? guess x) - guess - (sqrt-iter (improve guess x) x))) -(define (good-enough? guess x) - (< (abs (- (* guess guess) x)) 0.001)) -(define (improve guess x) - (average guess (/ x guess))) -(define (average x y) - (/ (+ x y) 2)) -(sqrt 123454321) -""" -def test_newton(): - got = run(newton_src) - assert math.isclose(got, 11111) - - -closure_src = """ -(define (make-adder increment) - (lambda (x) (+ increment x)) -) -(define inc (make-adder 1)) -(inc 99) -""" -def test_newton(): - got = run(closure_src) - assert got == 100 diff --git a/18-with-match/lisplus/lis.py b/18-with-match/lisplus/lis.py deleted file mode 100644 index 7ae0a9d..0000000 --- a/18-with-match/lisplus/lis.py +++ /dev/null @@ -1,192 +0,0 @@ -################ Lispy: Scheme Interpreter in Python 3.10 - -## (c) Peter Norvig, 2010-18; See http://norvig.com/lispy.html -## Minor edits for Fluent Python, Second Edition (O'Reilly, 2021) -## by Luciano Ramalho, adding type hints and pattern matching. - -################ imports and types - -import math -import operator as op -from collections import ChainMap -from collections.abc import MutableMapping, Iterator -from itertools import chain -from typing import Any, TypeAlias - -Symbol: TypeAlias = str -Atom: TypeAlias = float | int | Symbol -Expression: TypeAlias = Atom | list - -Environment: TypeAlias = MutableMapping[Symbol, object] - - -class Procedure: - "A user-defined Scheme procedure." - - def __init__(self, parms: list[Symbol], body: list[Expression], env: Environment): - self.parms = parms - self.body = body - self.env = env - - def __call__(self, *args: Expression) -> Any: - local_env = dict(zip(self.parms, args)) - env: Environment = ChainMap(local_env, self.env) - for exp in self.body: - result = evaluate(exp, env) - return result - - -################ global environment - - -def standard_env() -> Environment: - "An environment with some Scheme standard procedures." - env: Environment = {} - env.update(vars(math)) # sin, cos, sqrt, pi, ... - env.update({ - '+': op.add, - '-': op.sub, - '*': op.mul, - '/': op.truediv, - '//': op.floordiv, - '>': op.gt, - '<': op.lt, - '>=': op.ge, - '<=': op.le, - '=': op.eq, - 'abs': abs, - 'append': lambda *args: list(chain(*args)), - 'apply': lambda proc, args: proc(*args), - 'begin': lambda *x: x[-1], - 'car': lambda x: x[0], - 'cdr': lambda x: x[1:], - 'cons': lambda x, y: [x] + y, - 'eq?': op.is_, - 'equal?': op.eq, - 'filter': lambda *args: list(filter(*args)), - 'length': len, - 'list': lambda *x: list(x), - 'list?': lambda x: isinstance(x, list), - 'map': lambda *args: list(map(*args)), - 'max': max, - 'min': min, - 'not': op.not_, - 'null?': lambda x: x == [], - 'number?': lambda x: isinstance(x, (int, float)), - 'procedure?': callable, - 'round': round, - 'symbol?': lambda x: isinstance(x, Symbol), - }) - return env - - -################ parse, tokenize, and read_from_tokens - - -def parse(program: str) -> Expression: - "Read a Scheme expression from a string." - return read_from_tokens(tokenize(program)) - - -def tokenize(s: str) -> list[str]: - "Convert a string into a list of tokens." - return s.replace('(', ' ( ').replace(')', ' ) ').split() - - -def read_from_tokens(tokens: list[str]) -> Expression: - "Read an expression from a sequence of tokens." - if len(tokens) == 0: - raise SyntaxError('unexpected EOF while reading') - token = tokens.pop(0) - if '(' == token: - exp = [] - while tokens[0] != ')': - exp.append(read_from_tokens(tokens)) - tokens.pop(0) # discard ')' - return exp - elif ')' == token: - raise SyntaxError('unexpected )') - else: - return parse_atom(token) - - -def parse_atom(token: str) -> Atom: - "Numbers become numbers; every other token is a symbol." - try: - return int(token) - except ValueError: - try: - return float(token) - except ValueError: - return Symbol(token) - - -################ interaction: a REPL - - -def repl(prompt: str = 'lis.py> ') -> None: - "A prompt-read-evaluate-print loop." - global_env: Environment = standard_env() - while True: - val = evaluate(parse(input(prompt)), global_env) - if val is not None: - print(lispstr(val)) - - -def lispstr(exp: object) -> str: - "Convert a Python object back into a Lisp-readable string." - if isinstance(exp, list): - return '(' + ' '.join(map(lispstr, exp)) + ')' - else: - return str(exp) - - -################ eval - -# tag::EVALUATE[] -def evaluate(exp: Expression, env: Environment) -> Any: - "Evaluate an expression in an environment." - match exp: - case int(x) | float(x): - return x - case Symbol(var): - return env[var] - case []: - return [] - case ['quote', exp]: - return exp - case ['if', test, consequence, alternative]: - if evaluate(test, env): - return evaluate(consequence, env) - else: - return evaluate(alternative, env) - case ['define', Symbol(var), value_exp]: - env[var] = evaluate(value_exp, env) - case ['define', [Symbol(name), *parms], *body]: - env[name] = Procedure(parms, body, env) - case ['lambda', [*parms], *body]: - return Procedure(parms, body, env) - case [op, *args]: - proc = evaluate(op, env) - values = [evaluate(arg, env) for arg in args] - return proc(*values) - case _: - raise SyntaxError(repr(exp)) -# end::EVALUATE[] - - -################ non-interactive execution - - -def run_lines(source: str) -> Iterator[Any]: - global_env: Environment = standard_env() - tokens = tokenize(source) - while tokens: - exp = read_from_tokens(tokens) - yield evaluate(exp, global_env) - - -def run(source: str) -> Any: - for result in run_lines(source): - pass - return result diff --git a/18-with-match/lisplus/lis_test.py b/18-with-match/lisplus/lis_test.py deleted file mode 100644 index 3688888..0000000 --- a/18-with-match/lisplus/lis_test.py +++ /dev/null @@ -1,182 +0,0 @@ -from typing import Optional - -from pytest import mark, fixture - -from lis import parse, evaluate, Expression, Environment, standard_env - -############################################################# tests for parse - -@mark.parametrize( 'source, expected', [ - ('7', 7), - ('x', 'x'), - ('(sum 1 2 3)', ['sum', 1, 2, 3]), - ('(+ (* 2 100) (* 1 10))', ['+', ['*', 2, 100], ['*', 1, 10]]), - ('99 100', 99), # parse stops at the first complete expression - ('(a)(b)', ['a']), -]) -def test_parse(source: str, expected: Expression) -> None: - got = parse(source) - assert got == expected - - -########################################################## tests for evaluate - -# Norvig's tests are not isolated: they assume the -# same environment from first to last test. -global_env_for_first_test = standard_env() - -@mark.parametrize( 'source, expected', [ - ("(quote (testing 1 (2.0) -3.14e159))", ['testing', 1, [2.0], -3.14e159]), - ("(+ 2 2)", 4), - ("(+ (* 2 100) (* 1 10))", 210), - ("(if (> 6 5) (+ 1 1) (+ 2 2))", 2), - ("(if (< 6 5) (+ 1 1) (+ 2 2))", 4), - ("(define x 3)", None), - ("x", 3), - ("(+ x x)", 6), - ("((lambda (x) (+ x x)) 5)", 10), - ("(define twice (lambda (x) (* 2 x)))", None), - ("(twice 5)", 10), - ("(define compose (lambda (f g) (lambda (x) (f (g x)))))", None), - ("((compose list twice) 5)", [10]), - ("(define repeat (lambda (f) (compose f f)))", None), - ("((repeat twice) 5)", 20), - ("((repeat (repeat twice)) 5)", 80), - ("(define fact (lambda (n) (if (<= n 1) 1 (* n (fact (- n 1))))))", None), - ("(fact 3)", 6), - ("(fact 50)", 30414093201713378043612608166064768844377641568960512000000000000), - ("(define abs (lambda (n) ((if (> n 0) + -) 0 n)))", None), - ("(list (abs -3) (abs 0) (abs 3))", [3, 0, 3]), - ("""(define combine (lambda (f) - (lambda (x y) - (if (null? x) (quote ()) - (f (list (car x) (car y)) - ((combine f) (cdr x) (cdr y)))))))""", None), - ("(define zip (combine cons))", None), - ("(zip (list 1 2 3 4) (list 5 6 7 8))", [[1, 5], [2, 6], [3, 7], [4, 8]]), - ("""(define riff-shuffle (lambda (deck) - (begin - (define take (lambda (n seq) (if (<= n 0) (quote ()) (cons (car seq) (take (- n 1) (cdr seq)))))) - (define drop (lambda (n seq) (if (<= n 0) seq (drop (- n 1) (cdr seq))))) - (define mid (lambda (seq) (/ (length seq) 2))) - ((combine append) (take (mid deck) deck) (drop (mid deck) deck)))))""", None), - ("(riff-shuffle (list 1 2 3 4 5 6 7 8))", [1, 5, 2, 6, 3, 7, 4, 8]), - ("((repeat riff-shuffle) (list 1 2 3 4 5 6 7 8))", [1, 3, 5, 7, 2, 4, 6, 8]), - ("(riff-shuffle (riff-shuffle (riff-shuffle (list 1 2 3 4 5 6 7 8))))", [1,2,3,4,5,6,7,8]), -]) -def test_evaluate(source: str, expected: Optional[Expression]) -> None: - got = evaluate(parse(source), global_env_for_first_test) - assert got == expected - - -@fixture -def std_env() -> Environment: - return standard_env() - -# tests for each of the cases in evaluate - -def test_evaluate_variable() -> None: - env: Environment = dict(x=10) - source = 'x' - expected = 10 - got = evaluate(parse(source), env) - assert got == expected - - -def test_evaluate_literal(std_env: Environment) -> None: - source = '3.3' - expected = 3.3 - got = evaluate(parse(source), std_env) - assert got == expected - - -def test_evaluate_quote(std_env: Environment) -> None: - source = '(quote (1.1 is not 1))' - expected = [1.1, 'is', 'not', 1] - got = evaluate(parse(source), std_env) - assert got == expected - - -def test_evaluate_if_true(std_env: Environment) -> None: - source = '(if 1 10 no-such-thing)' - expected = 10 - got = evaluate(parse(source), std_env) - assert got == expected - - -def test_evaluate_if_false(std_env: Environment) -> None: - source = '(if 0 no-such-thing 20)' - expected = 20 - got = evaluate(parse(source), std_env) - assert got == expected - - -def test_define(std_env: Environment) -> None: - source = '(define answer (* 6 7))' - got = evaluate(parse(source), std_env) - assert got is None - assert std_env['answer'] == 42 - - -def test_lambda(std_env: Environment) -> None: - source = '(lambda (a b) (if (>= a b) a b))' - func = evaluate(parse(source), std_env) - assert func.parms == ['a', 'b'] - assert func.body == [['if', ['>=', 'a', 'b'], 'a', 'b']] - assert func.env is std_env - assert func(1, 2) == 2 - assert func(3, 2) == 3 - - -def test_begin(std_env: Environment) -> None: - source = """ - (begin - (define x (* 2 3)) - (* x 7) - ) - """ - got = evaluate(parse(source), std_env) - assert got == 42 - - -def test_invocation_builtin_car(std_env: Environment) -> None: - source = '(car (quote (11 22 33)))' - got = evaluate(parse(source), std_env) - assert got == 11 - - -def test_invocation_builtin_append(std_env: Environment) -> None: - source = '(append (quote (a b)) (quote (c d)))' - got = evaluate(parse(source), std_env) - assert got == ['a', 'b', 'c', 'd'] - - -def test_invocation_builtin_map(std_env: Environment) -> None: - source = '(map (lambda (x) (* x 2)) (quote (1 2 3))))' - got = evaluate(parse(source), std_env) - assert got == [2, 4, 6] - - -def test_invocation_user_procedure(std_env: Environment) -> None: - source = """ - (begin - (define max (lambda (a b) (if (>= a b) a b))) - (max 22 11) - ) - """ - got = evaluate(parse(source), std_env) - assert got == 22 - - -###################################### for py3.10/lis.py only - -def test_define_function(std_env: Environment) -> None: - source = '(define (max a b) (if (>= a b) a b))' - got = evaluate(parse(source), std_env) - assert got is None - max_fn = std_env['max'] - assert max_fn.parms == ['a', 'b'] - assert max_fn.body == [['if', ['>=', 'a', 'b'], 'a', 'b']] - assert max_fn.env is std_env - assert max_fn(1, 2) == 2 - assert max_fn(3, 2) == 3 diff --git a/18-with-match/lisplus/meta_test.py b/18-with-match/lisplus/meta_test.py deleted file mode 100644 index cb3d062..0000000 --- a/18-with-match/lisplus/meta_test.py +++ /dev/null @@ -1,64 +0,0 @@ -import operator as op - -from lis import run - -env_scm = """ -(define standard-env (list - (list (quote +) +) - (list (quote -) -) -)) -standard-env -""" - -def test_env_build(): - got = run(env_scm) - assert got == [['+', op.add], ['-', op.sub]] - -scan_scm = """ -(define l (quote (a b c))) -(define (scan what where) - (if (null? where) - () - (if (eq? what (car where)) - what - (scan what (cdr where)))) -) -""" - -def test_scan(): - source = scan_scm + '(scan (quote a) l )' - got = run(source) - assert got == 'a' - - -def test_scan_not_found(): - source = scan_scm + '(scan (quote z) l )' - got = run(source) - assert got == [] - - -lookup_scm = """ -(define env (list - (list (quote +) +) - (list (quote -) -) -)) -(define (lookup what where) - (if (null? where) - () - (if (eq? what (car (car where))) - (car (cdr (car where))) - (lookup what (cdr where)))) -) -""" - -def test_lookup(): - source = lookup_scm + '(lookup (quote +) env)' - got = run(source) - assert got == op.add - - -def test_lookup_not_found(): - source = lookup_scm + '(lookup (quote z) env )' - got = run(source) - assert got == [] - diff --git a/02-array-seq/lispy/LICENSE b/18-with-match/lispy/LICENSE similarity index 100% rename from 02-array-seq/lispy/LICENSE rename to 18-with-match/lispy/LICENSE diff --git a/02-array-seq/lispy/README.md b/18-with-match/lispy/README.md similarity index 100% rename from 02-array-seq/lispy/README.md rename to 18-with-match/lispy/README.md diff --git a/02-array-seq/lispy/original/LICENSE b/18-with-match/lispy/original/LICENSE similarity index 100% rename from 02-array-seq/lispy/original/LICENSE rename to 18-with-match/lispy/original/LICENSE diff --git a/02-array-seq/lispy/original/README.md b/18-with-match/lispy/original/README.md similarity index 100% rename from 02-array-seq/lispy/original/README.md rename to 18-with-match/lispy/original/README.md diff --git a/02-array-seq/lispy/original/lis.py b/18-with-match/lispy/original/lis.py similarity index 100% rename from 02-array-seq/lispy/original/lis.py rename to 18-with-match/lispy/original/lis.py diff --git a/02-array-seq/lispy/original/lispy.py b/18-with-match/lispy/original/lispy.py similarity index 100% rename from 02-array-seq/lispy/original/lispy.py rename to 18-with-match/lispy/original/lispy.py diff --git a/02-array-seq/lispy/original/lispytest.py b/18-with-match/lispy/original/lispytest.py similarity index 100% rename from 02-array-seq/lispy/original/lispytest.py rename to 18-with-match/lispy/original/lispytest.py diff --git a/18-with-match/lispy/py3.10/examples_test.py b/18-with-match/lispy/py3.10/examples_test.py new file mode 100644 index 0000000..ed4b68a --- /dev/null +++ b/18-with-match/lispy/py3.10/examples_test.py @@ -0,0 +1,259 @@ +""" +Doctests for `parse` +-------------------- + +# tag::PARSE_ATOM[] +>>> from lis import parse +>>> parse('1.5') +1.5 +>>> parse('ni!') +'ni!' + +# end::PARSE_ATOM[] + +# tag::PARSE_LIST[] +>>> parse('(gcd 18 45)') +['gcd', 18, 45] +>>> parse(''' +... (define double +... (lambda (n) +... (* n 2))) +... ''') +['define', 'double', ['lambda', ['n'], ['*', 'n', 2]]] + +# end::PARSE_LIST[] + +Doctest for `Environment` +------------------------- + +# tag::ENVIRONMENT[] +>>> from lis import Environment +>>> outer_env = {'a': 0, 'b': 1} +>>> inner_env = {'a': 2} +>>> env = Environment(inner_env, outer_env) +>>> env['a'] = 111 # <1> +>>> env['c'] = 222 +>>> env +Environment({'a': 111, 'c': 222}, {'a': 0, 'b': 1}) +>>> env.change('b', 333) # <2> +>>> env +Environment({'a': 111, 'c': 222}, {'a': 0, 'b': 333}) + +# end::ENVIRONMENT[] + +Doctests for `evaluate` +----------------------- + +# tag::EVAL_NUMBER[] +>>> from lis import parse, evaluate, standard_env +>>> evaluate(parse('1.5'), {}) +1.5 + +# end::EVAL_NUMBER[] + +# tag::EVAL_SYMBOL[] +>>> from lis import standard_env +>>> evaluate(parse('+'), standard_env()) + +>>> evaluate(parse('ni!'), standard_env()) +Traceback (most recent call last): + ... +KeyError: 'ni!' + +# end::EVAL_SYMBOL[] + + +# tag::EVAL_QUOTE[] +>>> evaluate(parse('(quote no-such-name)'), {}) +'no-such-name' +>>> evaluate(parse('(quote (99 bottles of beer))'), {}) +[99, 'bottles', 'of', 'beer'] +>>> evaluate(parse('(quote (/ 10 0))'), {}) +['/', 10, 0] + +# end::EVAL_QUOTE[] + +# tag::EVAL_IF[] +>>> evaluate(parse('(if (= 3 3) 1 0))'), standard_env()) +1 +>>> evaluate(parse('(if (= 3 4) 1 0))'), standard_env()) +0 + +# end::EVAL_IF[] + + +# tag::EVAL_LAMBDA[] +>>> expr = '(lambda (a b) (* (/ a b) 100))' +>>> f = evaluate(parse(expr), standard_env()) +>>> f # doctest: +ELLIPSIS + +>>> f(15, 20) +75.0 + +# end::EVAL_LAMBDA[] + +# tag::EVAL_DEFINE[] +>>> global_env = standard_env() +>>> evaluate(parse('(define answer (* 7 6))'), global_env) +>>> global_env['answer'] +42 + +# end::EVAL_DEFINE[] + +# tag::EVAL_DEFUN[] +>>> global_env = standard_env() +>>> percent = '(define (% a b) (* (/ a b) 100))' +>>> evaluate(parse(percent), global_env) +>>> global_env['%'] # doctest: +ELLIPSIS + +>>> global_env['%'](170, 200) +85.0 + +# end::EVAL_DEFUN[] + +function call: + +# tag::EVAL_CALL[] +>>> evaluate(parse('(% (* 12 14) (- 500 100))'), global_env) +42.0 + +# end::EVAL_CALL[] + +# tag::EVAL_SYNTAX_ERROR[] +>>> evaluate(parse('(lambda is not like this)'), standard_env()) +Traceback (most recent call last): + ... +SyntaxError: (lambda is not like this) + +# end::EVAL_SYNTAX_ERROR[] + +""" + +import math + +from lis import run + + +fact_src = """ +(define (! n) + (if (< n 2) + 1 + (* n (! (- n 1))) + ) +) +(! 42) +""" +def test_factorial(): + got = run(fact_src) + assert got == 1405006117752879898543142606244511569936384000000000 + assert got == math.factorial(42) + + +gcd_src = """ +(define (mod m n) + (- m (* n (// m n)))) +(define (gcd m n) + (if (= n 0) + m + (gcd n (mod m n)))) +(gcd 18 45) +""" +def test_gcd(): + got = run(gcd_src) + assert got == 9 + + +quicksort_src = """ +(define (quicksort lst) + (if (null? lst) + lst + (begin + (define pivot (car lst)) + (define rest (cdr lst)) + (append + (quicksort + (filter (lambda (x) (< x pivot)) rest)) + (list pivot) + (quicksort + (filter (lambda (x) (>= x pivot)) rest))) + ) + ) +) +(quicksort (list 2 1 6 3 4 0 8 9 7 5)) +""" +def test_quicksort(): + got = run(quicksort_src) + assert got == [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + + +# Example from Structure and Interpretation of Computer Programs +# https://mitpress.mit.edu/sites/default/files/sicp/full-text/sicp/book/node12.html + +newton_src = """ +(define (sqrt x) + (sqrt-iter 1.0 x)) +(define (sqrt-iter guess x) + (if (good-enough? guess x) + guess + (sqrt-iter (improve guess x) x))) +(define (good-enough? guess x) + (< (abs (- (* guess guess) x)) 0.001)) +(define (improve guess x) + (average guess (/ x guess))) +(define (average x y) + (/ (+ x y) 2)) +(sqrt 123454321) +""" +def test_newton(): + got = run(newton_src) + assert math.isclose(got, 11111) + + +closure_src = """ +(define (make-adder increment) + (lambda (x) (+ increment x)) +) +(define inc (make-adder 1)) +(inc 99) +""" +def test_newton(): + got = run(closure_src) + assert got == 100 + +closure_with_change_src = """ +(define (make-counter) + (define n 0) + (lambda () + (set! n (+ n 1)) + n) +) +(define counter (make-counter)) +(counter) +(counter) +(counter) +""" +def test_closure_with_change(): + got = run(closure_with_change_src) + assert got == 3 + + +# tag::RUN_AVERAGER[] +closure_averager_src = """ +(define (make-averager) + (define count 0) + (define total 0) + (lambda (new-value) + (set! count (+ count 1)) + (set! total (+ total new-value)) + (/ total count) + ) +) +(define avg (make-averager)) +(avg 10) +(avg 11) +(avg 15) +""" +def test_closure_averager(): + got = run(closure_averager_src) + assert got == 12.0 +# end::RUN_AVERAGER[] \ No newline at end of file diff --git a/02-array-seq/lispy/py3.10/lis.py b/18-with-match/lispy/py3.10/lis.py old mode 100644 new mode 100755 similarity index 70% rename from 02-array-seq/lispy/py3.10/lis.py rename to 18-with-match/lispy/py3.10/lis.py index 035481f..4a5195c --- a/02-array-seq/lispy/py3.10/lis.py +++ b/18-with-match/lispy/py3.10/lis.py @@ -1,46 +1,82 @@ +#!/usr/bin/env python + ################ Lispy: Scheme Interpreter in Python 3.10 ## (c) Peter Norvig, 2010-18; See http://norvig.com/lispy.html ## Minor edits for Fluent Python, Second Edition (O'Reilly, 2021) ## by Luciano Ramalho, adding type hints and pattern matching. + ################ Imports and Types +# tag::IMPORTS[] import math import operator as op from collections import ChainMap -from collections.abc import MutableMapping, Iterator from itertools import chain -from typing import Any, TypeAlias +from typing import Any, TypeAlias, NoReturn Symbol: TypeAlias = str Atom: TypeAlias = float | int | Symbol Expression: TypeAlias = Atom | list +# end::IMPORTS[] -Environment: TypeAlias = MutableMapping[Symbol, object] +################ Parsing: parse, tokenize, and read_from_tokens -class Procedure: - "A user-defined Scheme procedure." +def parse(program: str) -> Expression: + "Read a Scheme expression from a string." + return read_from_tokens(tokenize(program)) - def __init__(self, parms: list[Symbol], body: list[Expression], env: Environment): - self.parms = parms - self.body = body - self.env = env +def tokenize(s: str) -> list[str]: + "Convert a string into a list of tokens." + return s.replace('(', ' ( ').replace(')', ' ) ').split() - def __call__(self, *args: Expression) -> Any: - local_env = dict(zip(self.parms, args)) - env: Environment = ChainMap(local_env, self.env) - for exp in self.body: - result = evaluate(exp, env) - return result +def read_from_tokens(tokens: list[str]) -> Expression: + "Read an expression from a sequence of tokens." + if len(tokens) == 0: + raise SyntaxError('unexpected EOF while reading') + token = tokens.pop(0) + if '(' == token: + exp = [] + while tokens[0] != ')': + exp.append(read_from_tokens(tokens)) + tokens.pop(0) # discard ')' + return exp + elif ')' == token: + raise SyntaxError('unexpected )') + else: + return parse_atom(token) +def parse_atom(token: str) -> Atom: + "Numbers become numbers; every other token is a symbol." + try: + return int(token) + except ValueError: + try: + return float(token) + except ValueError: + return Symbol(token) ################ Global Environment +# tag::ENV_CLASS[] +class Environment(ChainMap): + "A ChainMap that allows changing an item in-place." + + def change(self, key: Symbol, value: object) -> None: + "Find where key is defined and change the value there." + for map in self.maps: + if key in map: + map[key] = value + return + raise KeyError(key) +# end::ENV_CLASS[] + + def standard_env() -> Environment: "An environment with some Scheme standard procedures." - env: Environment = {} + env = Environment() env.update(vars(math)) # sin, cos, sqrt, pi, ... env.update({ '+': op.add, @@ -60,6 +96,7 @@ def standard_env() -> Environment: 'car': lambda x: x[0], 'cdr': lambda x: x[1:], 'cons': lambda x, y: [x] + y, + 'display': lambda x: print(lispstr(x)), 'eq?': op.is_, 'equal?': op.eq, 'filter': lambda *args: list(filter(*args)), @@ -78,68 +115,33 @@ def standard_env() -> Environment: }) return env -################ Parsing: parse, tokenize, and read_from_tokens - -def parse(program: str) -> Expression: - "Read a Scheme expression from a string." - return read_from_tokens(tokenize(program)) - - -def tokenize(s: str) -> list[str]: - "Convert a string into a list of tokens." - return s.replace('(', ' ( ').replace(')', ' ) ').split() - - -def read_from_tokens(tokens: list[str]) -> Expression: - "Read an expression from a sequence of tokens." - if len(tokens) == 0: - raise SyntaxError('unexpected EOF while reading') - token = tokens.pop(0) - if '(' == token: - exp = [] - while tokens[0] != ')': - exp.append(read_from_tokens(tokens)) - tokens.pop(0) # discard ')' - return exp - elif ')' == token: - raise SyntaxError('unexpected )') - else: - return parse_atom(token) - - -def parse_atom(token: str) -> Atom: - "Numbers become numbers; every other token is a symbol." - try: - return int(token) - except ValueError: - try: - return float(token) - except ValueError: - return Symbol(token) - ################ Interaction: A REPL -def repl(prompt: str = 'lis.py> ') -> None: - "A prompt-read-evaluate-print loop." +# tag::REPL[] +def repl() -> NoReturn: + "A prompt-read-eval-print loop." global_env = standard_env() while True: - val = evaluate(parse(input(prompt)), global_env) + ast = parse(input('lis.py> ')) + val = evaluate(ast, global_env) if val is not None: print(lispstr(val)) - def lispstr(exp: object) -> str: "Convert a Python object back into a Lisp-readable string." if isinstance(exp, list): return '(' + ' '.join(map(lispstr, exp)) + ')' else: return str(exp) +# end::REPL[] -################ eval +################ Evaluator # tag::EVALUATE[] +KEYWORDS = ['quote', 'if', 'lambda', 'define', 'set!'] + def evaluate(exp: Expression, env: Environment) -> Any: "Evaluate an expression in an environment." match exp: @@ -149,40 +151,66 @@ def evaluate(exp: Expression, env: Environment) -> Any: return env[var] case []: return [] - case ['quote', exp]: - return exp + case ['quote', x]: + return x case ['if', test, consequence, alternative]: if evaluate(test, env): return evaluate(consequence, env) else: return evaluate(alternative, env) + case ['lambda', [*parms], *body] if body: + return Procedure(parms, body, env) case ['define', Symbol(var), value_exp]: env[var] = evaluate(value_exp, env) - case ['define', [Symbol(name), *parms], *body]: + case ['define', [Symbol(name), *parms], *body] if body: env[name] = Procedure(parms, body, env) - case ['lambda', [*parms], *body]: - return Procedure(parms, body, env) - case [op, *args]: + case ['set!', Symbol(var), value_exp]: + env.change(var, evaluate(value_exp, env)) + case [op, *args] if op not in KEYWORDS: proc = evaluate(op, env) values = [evaluate(arg, env) for arg in args] return proc(*values) case _: - raise SyntaxError(repr(exp)) + raise SyntaxError(lispstr(exp)) # end::EVALUATE[] +# tag::PROCEDURE[] +class Procedure: + "A user-defined Scheme procedure." + + def __init__( # <1> + self, parms: list[Symbol], body: list[Expression], env: Environment + ): + self.parms = parms # <2> + self.body = body + self.env = env + + def __call__(self, *args: Expression) -> Any: # <3> + local_env = dict(zip(self.parms, args)) # <4> + env = Environment(local_env, self.env) # <5> + for exp in self.body: # <6> + result = evaluate(exp, env) + return result # <7> +# end::PROCEDURE[] -################ non-interactive execution +################ command-line interface -def run_lines(source: str) -> Iterator[Any]: - global_env: Environment = standard_env() +def run(source: str) -> Any: + global_env = standard_env() tokens = tokenize(source) while tokens: exp = read_from_tokens(tokens) - yield evaluate(exp, global_env) + result = evaluate(exp, global_env) + return result +def main(args: list[str]) -> None: + if len(args) == 1: + with open(args[0]) as fp: + run(fp.read()) + else: + repl() -def run(source: str) -> Any: - for result in run_lines(source): - pass - return result +if __name__ == '__main__': + import sys + main(sys.argv[1:]) diff --git a/02-array-seq/lispy/py3.10/lis_test.py b/18-with-match/lispy/py3.10/lis_test.py similarity index 100% rename from 02-array-seq/lispy/py3.10/lis_test.py rename to 18-with-match/lispy/py3.10/lis_test.py diff --git a/18-with-match/lispy/py3.10/quicksort.scm b/18-with-match/lispy/py3.10/quicksort.scm new file mode 100644 index 0000000..08dd596 --- /dev/null +++ b/18-with-match/lispy/py3.10/quicksort.scm @@ -0,0 +1,17 @@ +(define (quicksort lst) + (if (null? lst) + lst + (begin + (define pivot (car lst)) + (define rest (cdr lst)) + (append + (quicksort + (filter (lambda (x) (< x pivot)) rest)) + (list pivot) + (quicksort + (filter (lambda (x) (>= x pivot)) rest))) + ) + ) +) +(display + (quicksort (list 2 1 6 3 4 0 8 9 7 5))) diff --git a/02-array-seq/lispy/py3.9/README.md b/18-with-match/lispy/py3.9/README.md similarity index 100% rename from 02-array-seq/lispy/py3.9/README.md rename to 18-with-match/lispy/py3.9/README.md diff --git a/02-array-seq/lispy/py3.9/lis.py b/18-with-match/lispy/py3.9/lis.py similarity index 91% rename from 02-array-seq/lispy/py3.9/lis.py rename to 18-with-match/lispy/py3.9/lis.py index 11f4402..9e4dec1 100644 --- a/02-array-seq/lispy/py3.9/lis.py +++ b/18-with-match/lispy/py3.9/lis.py @@ -140,16 +140,18 @@ def evaluate(x: Expression, env: Environment) -> Any: (_, exp) = x return exp elif x[0] == 'if': # (if test conseq alt) - (_, test, conseq, alt) = x - exp = (conseq if evaluate(test, env) else alt) - return evaluate(exp, env) - elif x[0] == 'define': # (define var exp) - (_, var, exp) = x - env[var] = evaluate(exp, env) - elif x[0] == 'lambda': # (lambda (var...) body) + (_, test, consequence, alternative) = x + if evaluate(test, env): + return evaluate(consequence, env) + else: + return evaluate(alternative, env) + elif x[0] == 'define': # (define name exp) + (_, name, exp) = x + env[name] = evaluate(exp, env) + elif x[0] == 'lambda': # (lambda (parm…) body) (_, parms, *body) = x return Procedure(parms, body, env) - else: # (proc arg...) + else: # (proc arg…) proc = evaluate(x[0], env) args = [evaluate(exp, env) for exp in x[1:]] return proc(*args) diff --git a/02-array-seq/lispy/py3.9/lis_test.py b/18-with-match/lispy/py3.9/lis_test.py similarity index 100% rename from 02-array-seq/lispy/py3.9/lis_test.py rename to 18-with-match/lispy/py3.9/lis_test.py diff --git a/18-with-match/mirror.py b/18-with-match/mirror.py index ba31944..841c6ab 100644 --- a/18-with-match/mirror.py +++ b/18-with-match/mirror.py @@ -11,11 +11,11 @@ ... print('Alice, Kitty and Snowdrop') # <2> ... print(what) ... - pordwonS dna yttiK ,ecilA # <3> + pordwonS dna yttiK ,ecilA YKCOWREBBAJ - >>> what # <4> + >>> what # <3> 'JABBERWOCKY' - >>> print('Back to normal.') # <5> + >>> print('Back to normal.') # <4> Back to normal. # end::MIRROR_DEMO_1[] @@ -27,15 +27,15 @@ >>> from mirror import LookingGlass >>> manager = LookingGlass() # <1> - >>> manager - + >>> manager # doctest: +ELLIPSIS + >>> monster = manager.__enter__() # <2> >>> monster == 'JABBERWOCKY' # <3> eurT >>> monster 'YKCOWREBBAJ' - >>> manager - >ca875a2x0 ta tcejbo ssalGgnikooL.rorrim< + >>> manager # doctest: +ELLIPSIS + >... ta tcejbo ssalGgnikooL.rorrim< >>> manager.__exit__(None, None, None) # <4> >>> monster 'JABBERWOCKY' @@ -69,10 +69,11 @@ # tag::MIRROR_EX[] +import sys + class LookingGlass: def __enter__(self): # <1> - import sys self.original_write = sys.stdout.write # <2> sys.stdout.write = self.reverse_write # <3> return 'JABBERWOCKY' # <4> @@ -81,12 +82,9 @@ def reverse_write(self, text): # <5> self.original_write(text[::-1]) def __exit__(self, exc_type, exc_value, traceback): # <6> - import sys # <7> - sys.stdout.write = self.original_write # <8> - if exc_type is ZeroDivisionError: # <9> + sys.stdout.write = self.original_write # <7> + if exc_type is ZeroDivisionError: # <8> print('Please DO NOT divide by zero!') - return True # <10> - # <11> - - + return True # <9> + # <10> # end::MIRROR_EX[] diff --git a/18-with-match/mirror_gen.py b/18-with-match/mirror_gen.py index 457955a..6dfaf02 100644 --- a/18-with-match/mirror_gen.py +++ b/18-with-match/mirror_gen.py @@ -35,22 +35,36 @@ >>> manager # doctest: +ELLIPSIS >...x0 ta tcejbo reganaMtxetnoCrotareneG_.biltxetnoc< >>> manager.__exit__(None, None, None) # <4> + False >>> monster 'JABBERWOCKY' # end::MIRROR_GEN_DEMO_2[] +The decorated generator also works as a decorator: + + +# tag::MIRROR_GEN_DECO[] + >>> @looking_glass() + ... def verse(): + ... print('The time has come') + ... + >>> verse() # <1> + emoc sah emit ehT + >>> print('back to normal') # <2> + back to normal + +# end::MIRROR_GEN_DECO[] + """ # tag::MIRROR_GEN_EX[] - import contextlib - +import sys @contextlib.contextmanager # <1> def looking_glass(): - import sys original_write = sys.stdout.write # <2> def reverse_write(text): # <3> @@ -59,6 +73,4 @@ def reverse_write(text): # <3> sys.stdout.write = reverse_write # <4> yield 'JABBERWOCKY' # <5> sys.stdout.write = original_write # <6> - - # end::MIRROR_GEN_EX[] diff --git a/18-with-match/mirror_gen_exc.py b/18-with-match/mirror_gen_exc.py index c446918..0c20a3b 100644 --- a/18-with-match/mirror_gen_exc.py +++ b/18-with-match/mirror_gen_exc.py @@ -35,6 +35,7 @@ >>> manager # doctest: +ELLIPSIS >...x0 ta tcejbo reganaMtxetnoCrotareneG_.biltxetnoc< >>> manager.__exit__(None, None, None) # <4> + False >>> monster 'JABBERWOCKY' @@ -48,7 +49,7 @@ # tag::MIRROR_GEN_DEMO_3[] - >>> from mirror_gen import looking_glass + >>> from mirror_gen_exc import looking_glass >>> with looking_glass(): ... print('Humpty Dumpty') ... x = 1/0 # <1> @@ -74,13 +75,11 @@ # tag::MIRROR_GEN_EXC[] - import contextlib - +import sys @contextlib.contextmanager def looking_glass(): - import sys original_write = sys.stdout.write def reverse_write(text): @@ -96,6 +95,4 @@ def reverse_write(text): sys.stdout.write = original_write # <3> if msg: print(msg) # <4> - - # end::MIRROR_GEN_EXC[] diff --git a/20-futures/getflags/flags2_asyncio.py b/20-futures/getflags/flags2_asyncio.py index e7a73ac..88a18ed 100755 --- a/20-futures/getflags/flags2_asyncio.py +++ b/20-futures/getflags/flags2_asyncio.py @@ -36,10 +36,10 @@ async def get_flag(session: aiohttp.ClientSession, # <2> resp.raise_for_status() # <3> return bytes() -async def download_one(session: aiohttp.ClientSession, # <4> +async def download_one(session: aiohttp.ClientSession, cc: str, base_url: str, - semaphore: asyncio.Semaphore, + semaphore: asyncio.Semaphore, # <4> verbose: bool) -> Result: try: async with semaphore: # <5> diff --git a/20-futures/getflags/flags3_asyncio.py b/20-futures/getflags/flags3_asyncio.py index 37b5957..04a01e2 100755 --- a/20-futures/getflags/flags3_asyncio.py +++ b/20-futures/getflags/flags3_asyncio.py @@ -36,9 +36,9 @@ async def get_flag(session: aiohttp.ClientSession, return bytes() # tag::FLAGS3_ASYNCIO_GET_COUNTRY[] -async def get_country(session: aiohttp.ClientSession, # <1> +async def get_country(session: aiohttp.ClientSession, base_url: str, - cc: str) -> str: + cc: str) -> str: # <1> url = f'{base_url}/{cc}/metadata.json' async with session.get(url) as resp: if resp.status == 200: diff --git a/20-futures/getflags/requirements.txt b/20-futures/getflags/requirements.txt index 793679b..37baa8d 100644 --- a/20-futures/getflags/requirements.txt +++ b/20-futures/getflags/requirements.txt @@ -1,11 +1,13 @@ -aiohttp==3.7.4 +aiohttp==3.7.4.post0 async-timeout==3.0.1 -attrs==20.3.0 -certifi==2020.12.5 +attrs==21.2.0 +certifi==2021.5.30 chardet==4.0.0 -idna==2.10 -requests==2.25.1 -urllib3==1.26.5 -tqdm==4.56.2 +charset-normalizer==2.0.4 +idna==3.2 multidict==5.1.0 +requests==2.26.0 +tqdm==4.62.2 +typing-extensions==3.10.0.2 +urllib3==1.26.6 yarl==1.6.3 diff --git a/20-futures/getflags/slow_server.py b/20-futures/getflags/slow_server.py index 9d8142b..bede010 100755 --- a/20-futures/getflags/slow_server.py +++ b/20-futures/getflags/slow_server.py @@ -38,6 +38,7 @@ def do_GET(self): """Serve a GET request.""" time.sleep(.5) if random() < self.error_rate: + # HTTPStatus.IM_A_TEAPOT requires Python >= 3.9 self.send_error(HTTPStatus.IM_A_TEAPOT, "I'm a Teapot") else: f = self.send_head() diff --git a/22-dyn-attr-prop/oscon/demo_schedule2.py b/22-dyn-attr-prop/oscon/demo_schedule2.py deleted file mode 100755 index 12fd440..0000000 --- a/22-dyn-attr-prop/oscon/demo_schedule2.py +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env python3 - -import shelve - -from schedule_v2 import DB_NAME, CONFERENCE, load_db -from schedule_v2 import DbRecord, Event - -with shelve.open(DB_NAME) as db: - if CONFERENCE not in db: - load_db(db) - - DbRecord.set_db(db) - event = DbRecord.fetch('event.33950') - print(event) - print(event.venue) - print(event.venue.name) - for spkr in event.speakers: - print(f'{spkr.serial}:', spkr.name) - - print(repr(Event.venue)) - - event2 = DbRecord.fetch('event.33451') - print(event2) - print(event2.fetch) - print(event2.venue) \ No newline at end of file From 3ecfb212c6273122797c76876d6b373b2cb94fa6 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Sat, 18 Sep 2021 13:18:17 -0300 Subject: [PATCH 107/166] updated from Atlas --- 18-with-match/lispy/py3.10/examples_test.py | 43 ++-- 18-with-match/lispy/py3.10/lis.py | 18 +- 18-with-match/lispy/py3.10/lis_test.py | 6 +- 18-with-match/lispy/py3.9/examples_test.py | 258 ++++++++++++++++++++ 18-with-match/lispy/py3.9/lis.py | 207 ++++++++++------ 18-with-match/lispy/py3.9/lis_test.py | 20 +- 6 files changed, 438 insertions(+), 114 deletions(-) create mode 100644 18-with-match/lispy/py3.9/examples_test.py diff --git a/18-with-match/lispy/py3.10/examples_test.py b/18-with-match/lispy/py3.10/examples_test.py index ed4b68a..ba6bd10 100644 --- a/18-with-match/lispy/py3.10/examples_test.py +++ b/18-with-match/lispy/py3.10/examples_test.py @@ -2,16 +2,12 @@ Doctests for `parse` -------------------- -# tag::PARSE_ATOM[] +# tag::PARSE[] >>> from lis import parse >>> parse('1.5') 1.5 >>> parse('ni!') 'ni!' - -# end::PARSE_ATOM[] - -# tag::PARSE_LIST[] >>> parse('(gcd 18 45)') ['gcd', 18, 45] >>> parse(''' @@ -21,15 +17,15 @@ ... ''') ['define', 'double', ['lambda', ['n'], ['*', 'n', 2]]] -# end::PARSE_LIST[] +# end::PARSE[] Doctest for `Environment` ------------------------- # tag::ENVIRONMENT[] >>> from lis import Environment ->>> outer_env = {'a': 0, 'b': 1} >>> inner_env = {'a': 2} +>>> outer_env = {'a': 0, 'b': 1} >>> env = Environment(inner_env, outer_env) >>> env['a'] = 111 # <1> >>> env['c'] = 222 @@ -64,11 +60,11 @@ # tag::EVAL_QUOTE[] ->>> evaluate(parse('(quote no-such-name)'), {}) +>>> evaluate(parse('(quote no-such-name)'), standard_env()) 'no-such-name' ->>> evaluate(parse('(quote (99 bottles of beer))'), {}) +>>> evaluate(parse('(quote (99 bottles of beer))'), standard_env()) [99, 'bottles', 'of', 'beer'] ->>> evaluate(parse('(quote (/ 10 0))'), {}) +>>> evaluate(parse('(quote (/ 10 0))'), standard_env()) ['/', 10, 0] # end::EVAL_QUOTE[] @@ -156,11 +152,12 @@ def test_factorial(): (if (= n 0) m (gcd n (mod m n)))) -(gcd 18 45) +(display (gcd 18 45)) """ -def test_gcd(): - got = run(gcd_src) - assert got == 9 +def test_gcd(capsys): + run(gcd_src) + captured = capsys.readouterr() + assert captured.out == '9\n' quicksort_src = """ @@ -216,7 +213,7 @@ def test_newton(): (define inc (make-adder 1)) (inc 99) """ -def test_newton(): +def test_closure(): got = run(closure_src) assert got == 100 @@ -228,13 +225,15 @@ def test_newton(): n) ) (define counter (make-counter)) -(counter) -(counter) -(counter) +(display (counter)) +(display (counter)) +(display (counter)) """ -def test_closure_with_change(): - got = run(closure_with_change_src) - assert got == 3 +def test_closure_with_change(capsys): + run(closure_with_change_src) + captured = capsys.readouterr() + assert captured.out == '1\n2\n3\n' + # tag::RUN_AVERAGER[] @@ -256,4 +255,4 @@ def test_closure_with_change(): def test_closure_averager(): got = run(closure_averager_src) assert got == 12.0 -# end::RUN_AVERAGER[] \ No newline at end of file +# end::RUN_AVERAGER[] diff --git a/18-with-match/lispy/py3.10/lis.py b/18-with-match/lispy/py3.10/lis.py index 4a5195c..ff494d1 100755 --- a/18-with-match/lispy/py3.10/lis.py +++ b/18-with-match/lispy/py3.10/lis.py @@ -58,10 +58,11 @@ def parse_atom(token: str) -> Atom: except ValueError: return Symbol(token) + ################ Global Environment # tag::ENV_CLASS[] -class Environment(ChainMap): +class Environment(ChainMap[Symbol, Any]): "A ChainMap that allows changing an item in-place." def change(self, key: Symbol, value: object) -> None: @@ -119,11 +120,11 @@ def standard_env() -> Environment: ################ Interaction: A REPL # tag::REPL[] -def repl() -> NoReturn: +def repl(prompt: str = 'lis.py> ') -> NoReturn: "A prompt-read-eval-print loop." global_env = standard_env() while True: - ast = parse(input('lis.py> ')) + ast = parse(input(prompt)) val = evaluate(ast, global_env) if val is not None: print(lispstr(val)) @@ -140,7 +141,10 @@ def lispstr(exp: object) -> str: ################ Evaluator # tag::EVALUATE[] -KEYWORDS = ['quote', 'if', 'lambda', 'define', 'set!'] +KEYWORDS = {'quote', 'if', 'lambda', 'define', 'set!'} + +def is_keyword(s: Any) -> bool: + return isinstance(s, Symbol) and s in KEYWORDS def evaluate(exp: Expression, env: Environment) -> Any: "Evaluate an expression in an environment." @@ -149,8 +153,6 @@ def evaluate(exp: Expression, env: Environment) -> Any: return x case Symbol(var): return env[var] - case []: - return [] case ['quote', x]: return x case ['if', test, consequence, alternative]: @@ -166,8 +168,8 @@ def evaluate(exp: Expression, env: Environment) -> Any: env[name] = Procedure(parms, body, env) case ['set!', Symbol(var), value_exp]: env.change(var, evaluate(value_exp, env)) - case [op, *args] if op not in KEYWORDS: - proc = evaluate(op, env) + case [func_exp, *args] if not is_keyword(func_exp): + proc = evaluate(func_exp, env) values = [evaluate(arg, env) for arg in args] return proc(*values) case _: diff --git a/18-with-match/lispy/py3.10/lis_test.py b/18-with-match/lispy/py3.10/lis_test.py index 3688888..03dc15c 100644 --- a/18-with-match/lispy/py3.10/lis_test.py +++ b/18-with-match/lispy/py3.10/lis_test.py @@ -73,10 +73,10 @@ def test_evaluate(source: str, expected: Optional[Expression]) -> None: def std_env() -> Environment: return standard_env() -# tests for each of the cases in evaluate +# tests for cases in evaluate def test_evaluate_variable() -> None: - env: Environment = dict(x=10) + env = Environment({'x': 10}) source = 'x' expected = 10 got = evaluate(parse(source), env) @@ -168,8 +168,6 @@ def test_invocation_user_procedure(std_env: Environment) -> None: assert got == 22 -###################################### for py3.10/lis.py only - def test_define_function(std_env: Environment) -> None: source = '(define (max a b) (if (>= a b) a b))' got = evaluate(parse(source), std_env) diff --git a/18-with-match/lispy/py3.9/examples_test.py b/18-with-match/lispy/py3.9/examples_test.py new file mode 100644 index 0000000..c632662 --- /dev/null +++ b/18-with-match/lispy/py3.9/examples_test.py @@ -0,0 +1,258 @@ +""" +Doctests for `parse` +-------------------- + +# tag::PARSE[] +>>> from lis import parse +>>> parse('1.5') +1.5 +>>> parse('ni!') +'ni!' +>>> parse('(gcd 18 45)') +['gcd', 18, 45] +>>> parse(''' +... (define double +... (lambda (n) +... (* n 2))) +... ''') +['define', 'double', ['lambda', ['n'], ['*', 'n', 2]]] + +# end::PARSE[] + +Doctest for `Environment` +------------------------- + +# tag::ENVIRONMENT[] +>>> from lis import Environment +>>> inner_env = {'a': 2} +>>> outer_env = {'a': 0, 'b': 1} +>>> env = Environment(inner_env, outer_env) +>>> env['a'] = 111 # <1> +>>> env['c'] = 222 +>>> env +Environment({'a': 111, 'c': 222}, {'a': 0, 'b': 1}) +>>> env.change('b', 333) # <2> +>>> env +Environment({'a': 111, 'c': 222}, {'a': 0, 'b': 333}) + +# end::ENVIRONMENT[] + +Doctests for `evaluate` +----------------------- + +# tag::EVAL_NUMBER[] +>>> from lis import parse, evaluate, standard_env +>>> evaluate(parse('1.5'), {}) +1.5 + +# end::EVAL_NUMBER[] + +# tag::EVAL_SYMBOL[] +>>> from lis import standard_env +>>> evaluate(parse('+'), standard_env()) + +>>> evaluate(parse('ni!'), standard_env()) +Traceback (most recent call last): + ... +KeyError: 'ni!' + +# end::EVAL_SYMBOL[] + + +# tag::EVAL_QUOTE[] +>>> evaluate(parse('(quote no-such-name)'), standard_env()) +'no-such-name' +>>> evaluate(parse('(quote (99 bottles of beer))'), standard_env()) +[99, 'bottles', 'of', 'beer'] +>>> evaluate(parse('(quote (/ 10 0))'), standard_env()) +['/', 10, 0] + +# end::EVAL_QUOTE[] + +# tag::EVAL_IF[] +>>> evaluate(parse('(if (= 3 3) 1 0))'), standard_env()) +1 +>>> evaluate(parse('(if (= 3 4) 1 0))'), standard_env()) +0 + +# end::EVAL_IF[] + + +# tag::EVAL_LAMBDA[] +>>> expr = '(lambda (a b) (* (/ a b) 100))' +>>> f = evaluate(parse(expr), standard_env()) +>>> f # doctest: +ELLIPSIS + +>>> f(15, 20) +75.0 + +# end::EVAL_LAMBDA[] + +# tag::EVAL_DEFINE[] +>>> global_env = standard_env() +>>> evaluate(parse('(define answer (* 7 6))'), global_env) +>>> global_env['answer'] +42 + +# end::EVAL_DEFINE[] + +# tag::EVAL_DEFUN[] +>>> global_env = standard_env() +>>> percent = '(define (% a b) (* (/ a b) 100))' +>>> evaluate(parse(percent), global_env) +>>> global_env['%'] # doctest: +ELLIPSIS + +>>> global_env['%'](170, 200) +85.0 + +# end::EVAL_DEFUN[] + +function call: + +# tag::EVAL_CALL[] +>>> evaluate(parse('(% (* 12 14) (- 500 100))'), global_env) +42.0 + +# end::EVAL_CALL[] + +# tag::EVAL_SYNTAX_ERROR[] +>>> evaluate(parse('(lambda is not like this)'), standard_env()) +Traceback (most recent call last): + ... +SyntaxError: (lambda is not like this) + +# end::EVAL_SYNTAX_ERROR[] + +""" + +import math + +from lis import run + + +fact_src = """ +(define (! n) + (if (< n 2) + 1 + (* n (! (- n 1))) + ) +) +(! 42) +""" +def test_factorial(): + got = run(fact_src) + assert got == 1405006117752879898543142606244511569936384000000000 + assert got == math.factorial(42) + + +gcd_src = """ +(define (mod m n) + (- m (* n (// m n)))) +(define (gcd m n) + (if (= n 0) + m + (gcd n (mod m n)))) +(display (gcd 18 45)) +""" +def test_gcd(capsys): + run(gcd_src) + captured = capsys.readouterr() + assert captured.out == '9\n' + + +quicksort_src = """ +(define (quicksort lst) + (if (null? lst) + lst + (begin + (define pivot (car lst)) + (define rest (cdr lst)) + (append + (quicksort + (filter (lambda (x) (< x pivot)) rest)) + (list pivot) + (quicksort + (filter (lambda (x) (>= x pivot)) rest))) + ) + ) +) +(quicksort (list 2 1 6 3 4 0 8 9 7 5)) +""" +def test_quicksort(): + got = run(quicksort_src) + assert got == [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + + +# Example from Structure and Interpretation of Computer Programs +# https://mitpress.mit.edu/sites/default/files/sicp/full-text/sicp/book/node12.html + +newton_src = """ +(define (sqrt x) + (sqrt-iter 1.0 x)) +(define (sqrt-iter guess x) + (if (good-enough? guess x) + guess + (sqrt-iter (improve guess x) x))) +(define (good-enough? guess x) + (< (abs (- (* guess guess) x)) 0.001)) +(define (improve guess x) + (average guess (/ x guess))) +(define (average x y) + (/ (+ x y) 2)) +(sqrt 123454321) +""" +def test_newton(): + got = run(newton_src) + assert math.isclose(got, 11111) + + +closure_src = """ +(define (make-adder increment) + (lambda (x) (+ increment x)) +) +(define inc (make-adder 1)) +(inc 99) +""" +def test_closure(): + got = run(closure_src) + assert got == 100 + +closure_with_change_src = """ +(define (make-counter) + (define n 0) + (lambda () + (set! n (+ n 1)) + n) +) +(define counter (make-counter)) +(display (counter)) +(display (counter)) +(display (counter)) +""" +def test_closure_with_change(capsys): + run(closure_with_change_src) + captured = capsys.readouterr() + assert captured.out == '1\n2\n3\n' + + + +# tag::RUN_AVERAGER[] +closure_averager_src = """ +(define (make-averager) + (define count 0) + (define total 0) + (lambda (new-value) + (set! count (+ count 1)) + (set! total (+ total new-value)) + (/ total count) + ) +) +(define avg (make-averager)) +(avg 10) +(avg 11) +(avg 15) +""" +def test_closure_averager(): + got = run(closure_averager_src) + assert got == 12.0 +# end::RUN_AVERAGER[] \ No newline at end of file diff --git a/18-with-match/lispy/py3.9/lis.py b/18-with-match/lispy/py3.9/lis.py index 9e4dec1..e8881cd 100644 --- a/18-with-match/lispy/py3.9/lis.py +++ b/18-with-match/lispy/py3.9/lis.py @@ -1,73 +1,24 @@ +#!/usr/bin/env python + ################ Lispy: Scheme Interpreter in Python 3.9 ## (c) Peter Norvig, 2010-18; See http://norvig.com/lispy.html ## Minor edits for Fluent Python, Second Edition (O'Reilly, 2021) ## by Luciano Ramalho, adding type hints and pattern matching. + ################ Imports and Types import math import operator as op from collections import ChainMap -from collections.abc import MutableMapping, Iterator from itertools import chain -from typing import Any, Union +from typing import Any, Union, NoReturn Symbol = str Atom = Union[float, int, Symbol] Expression = Union[Atom, list] -Environment = MutableMapping[Symbol, object] - - -class Procedure: - "A user-defined Scheme procedure." - - def __init__(self, parms: list[Symbol], body: list[Expression], env: Environment): - self.parms = parms - self.body = body - self.env = env - - def __call__(self, *args: Expression) -> Any: - local_env = dict(zip(self.parms, args)) - env: Environment = ChainMap(local_env, self.env) - for exp in self.body: - result = evaluate(exp, env) - return result - - -################ Global Environment - -def standard_env() -> Environment: - "An environment with some Scheme standard procedures." - env: Environment = {} - env.update(vars(math)) # sin, cos, sqrt, pi, ... - env.update({ - '+':op.add, '-':op.sub, '*':op.mul, '/':op.truediv, - '>':op.gt, '<':op.lt, '>=':op.ge, '<=':op.le, '=':op.eq, - 'abs': abs, - 'append': op.add, - 'apply': lambda proc, args: proc(*args), - 'begin': lambda *x: x[-1], - 'car': lambda x: x[0], - 'cdr': lambda x: x[1:], - 'cons': lambda x,y: [x] + y, - 'eq?': op.is_, - 'equal?': op.eq, - 'length': len, - 'list': lambda *x: list(x), - 'list?': lambda x: isinstance(x,list), - 'map': lambda *args: list(map(*args)), - 'max': max, - 'min': min, - 'not': op.not_, - 'null?': lambda x: x == [], - 'number?': lambda x: isinstance(x, (int, float)), - 'procedure?': callable, - 'round': round, - 'symbol?': lambda x: isinstance(x, Symbol), - }) - return env ################ Parsing: parse, tokenize, and read_from_tokens @@ -75,12 +26,10 @@ def parse(program: str) -> Expression: "Read a Scheme expression from a string." return read_from_tokens(tokenize(program)) - def tokenize(s: str) -> list[str]: "Convert a string into a list of tokens." return s.replace('(', ' ( ').replace(')', ' ) ').split() - def read_from_tokens(tokens: list[str]) -> Expression: "Read an expression from a sequence of tokens." if len(tokens) == 0: @@ -97,7 +46,6 @@ def read_from_tokens(tokens: list[str]) -> Expression: else: return parse_atom(token) - def parse_atom(token: str) -> Atom: "Numbers become numbers; every other token is a symbol." try: @@ -109,17 +57,73 @@ def parse_atom(token: str) -> Atom: return Symbol(token) +################ Global Environment + +class Environment(ChainMap[Symbol, Any]): + "A ChainMap that allows changing an item in-place." + + def change(self, key: Symbol, value: object) -> None: + "Find where key is defined and change the value there." + for map in self.maps: + if key in map: + map[key] = value # type: ignore[index] + return + raise KeyError(key) + + +def standard_env() -> Environment: + "An environment with some Scheme standard procedures." + env = Environment() + env.update(vars(math)) # sin, cos, sqrt, pi, ... + env.update({ + '+': op.add, + '-': op.sub, + '*': op.mul, + '/': op.truediv, + '//': op.floordiv, + '>': op.gt, + '<': op.lt, + '>=': op.ge, + '<=': op.le, + '=': op.eq, + 'abs': abs, + 'append': lambda *args: list(chain(*args)), + 'apply': lambda proc, args: proc(*args), + 'begin': lambda *x: x[-1], + 'car': lambda x: x[0], + 'cdr': lambda x: x[1:], + 'cons': lambda x, y: [x] + y, + 'display': lambda x: print(lispstr(x)), + 'eq?': op.is_, + 'equal?': op.eq, + 'filter': lambda *args: list(filter(*args)), + 'length': len, + 'list': lambda *x: list(x), + 'list?': lambda x: isinstance(x, list), + 'map': lambda *args: list(map(*args)), + 'max': max, + 'min': min, + 'not': op.not_, + 'null?': lambda x: x == [], + 'number?': lambda x: isinstance(x, (int, float)), + 'procedure?': callable, + 'round': round, + 'symbol?': lambda x: isinstance(x, Symbol), + }) + return env + + ################ Interaction: A REPL -def repl(prompt: str = 'lis.py> ') -> None: +def repl(prompt: str = 'lis.py> ') -> NoReturn: "A prompt-read-eval-print loop." global_env = standard_env() while True: - val = evaluate(parse(input(prompt)), global_env) + ast = parse(input(prompt)) + val = evaluate(ast, global_env) if val is not None: print(lispstr(val)) - def lispstr(exp: object) -> str: "Convert a Python object back into a Lisp-readable string." if isinstance(exp, list): @@ -128,30 +132,81 @@ def lispstr(exp: object) -> str: return str(exp) -################ eval +################ Evaluator -def evaluate(x: Expression, env: Environment) -> Any: +def evaluate(exp: Expression, env: Environment) -> Any: "Evaluate an expression in an environment." - if isinstance(x, Symbol): # variable reference - return env[x] - elif not isinstance(x, list): # constant literal - return x - elif x[0] == 'quote': # (quote exp) - (_, exp) = x + if isinstance(exp, Symbol): # variable reference + return env[exp] + elif not isinstance(exp, list): # constant literal return exp - elif x[0] == 'if': # (if test conseq alt) - (_, test, consequence, alternative) = x + elif exp[0] == 'quote': # (quote exp) + (_, x) = exp + return x + elif exp[0] == 'if': # (if test conseq alt) + (_, test, consequence, alternative) = exp if evaluate(test, env): return evaluate(consequence, env) else: return evaluate(alternative, env) - elif x[0] == 'define': # (define name exp) - (_, name, exp) = x - env[name] = evaluate(exp, env) - elif x[0] == 'lambda': # (lambda (parm…) body) - (_, parms, *body) = x + elif exp[0] == 'lambda': # (lambda (parm…) body…) + (_, parms, *body) = exp + if not isinstance(parms, list): + raise SyntaxError(lispstr(exp)) return Procedure(parms, body, env) + elif exp[0] == 'define': + (_, name_exp, *rest) = exp + if isinstance(name_exp, Symbol): # (define name exp) + value_exp = rest[0] + env[name_exp] = evaluate(value_exp, env) + else: # (define (name parm…) body…) + name, *parms = name_exp + env[name] = Procedure(parms, rest, env) + elif exp[0] == 'set!': + (_, var, value_exp) = exp + env.change(var, evaluate(value_exp, env)) else: # (proc arg…) - proc = evaluate(x[0], env) - args = [evaluate(exp, env) for exp in x[1:]] + (func_exp, *args) = exp + proc = evaluate(func_exp, env) + args = [evaluate(arg, env) for arg in args] return proc(*args) + + +class Procedure: + "A user-defined Scheme procedure." + + def __init__( + self, parms: list[Symbol], body: list[Expression], env: Environment + ): + self.parms = parms + self.body = body + self.env = env + + def __call__(self, *args: Expression) -> Any: + local_env = dict(zip(self.parms, args)) + env = Environment(local_env, self.env) + for exp in self.body: + result = evaluate(exp, env) + return result + + +################ command-line interface + +def run(source: str) -> Any: + global_env = standard_env() + tokens = tokenize(source) + while tokens: + exp = read_from_tokens(tokens) + result = evaluate(exp, global_env) + return result + +def main(args: list[str]) -> None: + if len(args) == 1: + with open(args[0]) as fp: + run(fp.read()) + else: + repl() + +if __name__ == '__main__': + import sys + main(sys.argv[1:]) diff --git a/18-with-match/lispy/py3.9/lis_test.py b/18-with-match/lispy/py3.9/lis_test.py index 106ac24..03dc15c 100644 --- a/18-with-match/lispy/py3.9/lis_test.py +++ b/18-with-match/lispy/py3.9/lis_test.py @@ -1,8 +1,8 @@ -from typing import Any, Optional +from typing import Optional from pytest import mark, fixture -from lis import parse, evaluate, standard_env, Symbol, Environment, Expression +from lis import parse, evaluate, Expression, Environment, standard_env ############################################################# tests for parse @@ -73,10 +73,10 @@ def test_evaluate(source: str, expected: Optional[Expression]) -> None: def std_env() -> Environment: return standard_env() -# tests for each of the cases in evaluate +# tests for cases in evaluate def test_evaluate_variable() -> None: - env: Environment = dict(x=10) + env = Environment({'x': 10}) source = 'x' expected = 10 got = evaluate(parse(source), env) @@ -166,3 +166,15 @@ def test_invocation_user_procedure(std_env: Environment) -> None: """ got = evaluate(parse(source), std_env) assert got == 22 + + +def test_define_function(std_env: Environment) -> None: + source = '(define (max a b) (if (>= a b) a b))' + got = evaluate(parse(source), std_env) + assert got is None + max_fn = std_env['max'] + assert max_fn.parms == ['a', 'b'] + assert max_fn.body == [['if', ['>=', 'a', 'b'], 'a', 'b']] + assert max_fn.env is std_env + assert max_fn(1, 2) == 2 + assert max_fn(3, 2) == 3 From 2f2f87d4fb297869cb5763ec9b2ffcad3e841d20 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Mon, 20 Sep 2021 10:37:26 -0300 Subject: [PATCH 108/166] sync from Atlas --- 02-array-seq/lispy/py3.10/examples_test.py | 258 ++++++++++++++++++++ 02-array-seq/lispy/py3.10/lis.py | 219 +++++++++++++++++ 02-array-seq/lispy/py3.10/lis_test.py | 180 ++++++++++++++ 02-array-seq/lispy/py3.10/quicksort.scm | 17 ++ 02-array-seq/lispy/py3.9/README.md | 33 +++ 02-array-seq/lispy/py3.9/examples_test.py | 196 +++++++++++++++ 02-array-seq/lispy/py3.9/lis.py | 209 ++++++++++++++++ 02-array-seq/lispy/py3.9/lis_test.py | 180 ++++++++++++++ 18-with-match/lispy/py3.10/examples_test.py | 43 ++-- 18-with-match/lispy/py3.10/lis.py | 14 +- 18-with-match/lispy/py3.10/lis_test.py | 6 +- 18-with-match/lispy/py3.9/examples_test.py | 258 ++++++++++++++++++++ 18-with-match/lispy/py3.9/lis.py | 207 ++++++++++------ 18-with-match/lispy/py3.9/lis_test.py | 20 +- 18-with-match/mirror_gen.py | 3 + 22-dyn-attr-prop/doc_property.py | 2 +- 16 files changed, 1730 insertions(+), 115 deletions(-) create mode 100644 02-array-seq/lispy/py3.10/examples_test.py create mode 100755 02-array-seq/lispy/py3.10/lis.py create mode 100644 02-array-seq/lispy/py3.10/lis_test.py create mode 100644 02-array-seq/lispy/py3.10/quicksort.scm create mode 100644 02-array-seq/lispy/py3.9/README.md create mode 100644 02-array-seq/lispy/py3.9/examples_test.py create mode 100644 02-array-seq/lispy/py3.9/lis.py create mode 100644 02-array-seq/lispy/py3.9/lis_test.py create mode 100644 18-with-match/lispy/py3.9/examples_test.py diff --git a/02-array-seq/lispy/py3.10/examples_test.py b/02-array-seq/lispy/py3.10/examples_test.py new file mode 100644 index 0000000..ba6bd10 --- /dev/null +++ b/02-array-seq/lispy/py3.10/examples_test.py @@ -0,0 +1,258 @@ +""" +Doctests for `parse` +-------------------- + +# tag::PARSE[] +>>> from lis import parse +>>> parse('1.5') +1.5 +>>> parse('ni!') +'ni!' +>>> parse('(gcd 18 45)') +['gcd', 18, 45] +>>> parse(''' +... (define double +... (lambda (n) +... (* n 2))) +... ''') +['define', 'double', ['lambda', ['n'], ['*', 'n', 2]]] + +# end::PARSE[] + +Doctest for `Environment` +------------------------- + +# tag::ENVIRONMENT[] +>>> from lis import Environment +>>> inner_env = {'a': 2} +>>> outer_env = {'a': 0, 'b': 1} +>>> env = Environment(inner_env, outer_env) +>>> env['a'] = 111 # <1> +>>> env['c'] = 222 +>>> env +Environment({'a': 111, 'c': 222}, {'a': 0, 'b': 1}) +>>> env.change('b', 333) # <2> +>>> env +Environment({'a': 111, 'c': 222}, {'a': 0, 'b': 333}) + +# end::ENVIRONMENT[] + +Doctests for `evaluate` +----------------------- + +# tag::EVAL_NUMBER[] +>>> from lis import parse, evaluate, standard_env +>>> evaluate(parse('1.5'), {}) +1.5 + +# end::EVAL_NUMBER[] + +# tag::EVAL_SYMBOL[] +>>> from lis import standard_env +>>> evaluate(parse('+'), standard_env()) + +>>> evaluate(parse('ni!'), standard_env()) +Traceback (most recent call last): + ... +KeyError: 'ni!' + +# end::EVAL_SYMBOL[] + + +# tag::EVAL_QUOTE[] +>>> evaluate(parse('(quote no-such-name)'), standard_env()) +'no-such-name' +>>> evaluate(parse('(quote (99 bottles of beer))'), standard_env()) +[99, 'bottles', 'of', 'beer'] +>>> evaluate(parse('(quote (/ 10 0))'), standard_env()) +['/', 10, 0] + +# end::EVAL_QUOTE[] + +# tag::EVAL_IF[] +>>> evaluate(parse('(if (= 3 3) 1 0))'), standard_env()) +1 +>>> evaluate(parse('(if (= 3 4) 1 0))'), standard_env()) +0 + +# end::EVAL_IF[] + + +# tag::EVAL_LAMBDA[] +>>> expr = '(lambda (a b) (* (/ a b) 100))' +>>> f = evaluate(parse(expr), standard_env()) +>>> f # doctest: +ELLIPSIS + +>>> f(15, 20) +75.0 + +# end::EVAL_LAMBDA[] + +# tag::EVAL_DEFINE[] +>>> global_env = standard_env() +>>> evaluate(parse('(define answer (* 7 6))'), global_env) +>>> global_env['answer'] +42 + +# end::EVAL_DEFINE[] + +# tag::EVAL_DEFUN[] +>>> global_env = standard_env() +>>> percent = '(define (% a b) (* (/ a b) 100))' +>>> evaluate(parse(percent), global_env) +>>> global_env['%'] # doctest: +ELLIPSIS + +>>> global_env['%'](170, 200) +85.0 + +# end::EVAL_DEFUN[] + +function call: + +# tag::EVAL_CALL[] +>>> evaluate(parse('(% (* 12 14) (- 500 100))'), global_env) +42.0 + +# end::EVAL_CALL[] + +# tag::EVAL_SYNTAX_ERROR[] +>>> evaluate(parse('(lambda is not like this)'), standard_env()) +Traceback (most recent call last): + ... +SyntaxError: (lambda is not like this) + +# end::EVAL_SYNTAX_ERROR[] + +""" + +import math + +from lis import run + + +fact_src = """ +(define (! n) + (if (< n 2) + 1 + (* n (! (- n 1))) + ) +) +(! 42) +""" +def test_factorial(): + got = run(fact_src) + assert got == 1405006117752879898543142606244511569936384000000000 + assert got == math.factorial(42) + + +gcd_src = """ +(define (mod m n) + (- m (* n (// m n)))) +(define (gcd m n) + (if (= n 0) + m + (gcd n (mod m n)))) +(display (gcd 18 45)) +""" +def test_gcd(capsys): + run(gcd_src) + captured = capsys.readouterr() + assert captured.out == '9\n' + + +quicksort_src = """ +(define (quicksort lst) + (if (null? lst) + lst + (begin + (define pivot (car lst)) + (define rest (cdr lst)) + (append + (quicksort + (filter (lambda (x) (< x pivot)) rest)) + (list pivot) + (quicksort + (filter (lambda (x) (>= x pivot)) rest))) + ) + ) +) +(quicksort (list 2 1 6 3 4 0 8 9 7 5)) +""" +def test_quicksort(): + got = run(quicksort_src) + assert got == [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + + +# Example from Structure and Interpretation of Computer Programs +# https://mitpress.mit.edu/sites/default/files/sicp/full-text/sicp/book/node12.html + +newton_src = """ +(define (sqrt x) + (sqrt-iter 1.0 x)) +(define (sqrt-iter guess x) + (if (good-enough? guess x) + guess + (sqrt-iter (improve guess x) x))) +(define (good-enough? guess x) + (< (abs (- (* guess guess) x)) 0.001)) +(define (improve guess x) + (average guess (/ x guess))) +(define (average x y) + (/ (+ x y) 2)) +(sqrt 123454321) +""" +def test_newton(): + got = run(newton_src) + assert math.isclose(got, 11111) + + +closure_src = """ +(define (make-adder increment) + (lambda (x) (+ increment x)) +) +(define inc (make-adder 1)) +(inc 99) +""" +def test_closure(): + got = run(closure_src) + assert got == 100 + +closure_with_change_src = """ +(define (make-counter) + (define n 0) + (lambda () + (set! n (+ n 1)) + n) +) +(define counter (make-counter)) +(display (counter)) +(display (counter)) +(display (counter)) +""" +def test_closure_with_change(capsys): + run(closure_with_change_src) + captured = capsys.readouterr() + assert captured.out == '1\n2\n3\n' + + + +# tag::RUN_AVERAGER[] +closure_averager_src = """ +(define (make-averager) + (define count 0) + (define total 0) + (lambda (new-value) + (set! count (+ count 1)) + (set! total (+ total new-value)) + (/ total count) + ) +) +(define avg (make-averager)) +(avg 10) +(avg 11) +(avg 15) +""" +def test_closure_averager(): + got = run(closure_averager_src) + assert got == 12.0 +# end::RUN_AVERAGER[] diff --git a/02-array-seq/lispy/py3.10/lis.py b/02-array-seq/lispy/py3.10/lis.py new file mode 100755 index 0000000..cffb9a1 --- /dev/null +++ b/02-array-seq/lispy/py3.10/lis.py @@ -0,0 +1,219 @@ +#!/usr/bin/env python + +################ Lispy: Scheme Interpreter in Python 3.10 + +## (c) Peter Norvig, 2010-18; See http://norvig.com/lispy.html +## Minor edits for Fluent Python, Second Edition (O'Reilly, 2021) +## by Luciano Ramalho, adding type hints and pattern matching. + + +################ Imports and Types + +# tag::IMPORTS[] +import math +import operator as op +from collections import ChainMap +from itertools import chain +from typing import Any, TypeAlias, NoReturn + +Symbol: TypeAlias = str +Atom: TypeAlias = float | int | Symbol +Expression: TypeAlias = Atom | list +# end::IMPORTS[] + + +################ Parsing: parse, tokenize, and read_from_tokens + +def parse(program: str) -> Expression: + "Read a Scheme expression from a string." + return read_from_tokens(tokenize(program)) + +def tokenize(s: str) -> list[str]: + "Convert a string into a list of tokens." + return s.replace('(', ' ( ').replace(')', ' ) ').split() + +def read_from_tokens(tokens: list[str]) -> Expression: + "Read an expression from a sequence of tokens." + if len(tokens) == 0: + raise SyntaxError('unexpected EOF while reading') + token = tokens.pop(0) + if '(' == token: + exp = [] + while tokens[0] != ')': + exp.append(read_from_tokens(tokens)) + tokens.pop(0) # discard ')' + return exp + elif ')' == token: + raise SyntaxError('unexpected )') + else: + return parse_atom(token) + +def parse_atom(token: str) -> Atom: + "Numbers become numbers; every other token is a symbol." + try: + return int(token) + except ValueError: + try: + return float(token) + except ValueError: + return Symbol(token) + + +################ Global Environment + +# tag::ENV_CLASS[] +class Environment(ChainMap[Symbol, Any]): + "A ChainMap that allows changing an item in-place." + + def change(self, key: Symbol, value: object) -> None: + "Find where key is defined and change the value there." + for map in self.maps: + if key in map: + map[key] = value + return + raise KeyError(key) +# end::ENV_CLASS[] + + +def standard_env() -> Environment: + "An environment with some Scheme standard procedures." + env = Environment() + env.update(vars(math)) # sin, cos, sqrt, pi, ... + env.update({ + '+': op.add, + '-': op.sub, + '*': op.mul, + '/': op.truediv, + '//': op.floordiv, + '>': op.gt, + '<': op.lt, + '>=': op.ge, + '<=': op.le, + '=': op.eq, + 'abs': abs, + 'append': lambda *args: list(chain(*args)), + 'apply': lambda proc, args: proc(*args), + 'begin': lambda *x: x[-1], + 'car': lambda x: x[0], + 'cdr': lambda x: x[1:], + 'cons': lambda x, y: [x] + y, + 'display': lambda x: print(lispstr(x)), + 'eq?': op.is_, + 'equal?': op.eq, + 'filter': lambda *args: list(filter(*args)), + 'length': len, + 'list': lambda *x: list(x), + 'list?': lambda x: isinstance(x, list), + 'map': lambda *args: list(map(*args)), + 'max': max, + 'min': min, + 'not': op.not_, + 'null?': lambda x: x == [], + 'number?': lambda x: isinstance(x, (int, float)), + 'procedure?': callable, + 'round': round, + 'symbol?': lambda x: isinstance(x, Symbol), + }) + return env + + +################ Interaction: A REPL + +# tag::REPL[] +def repl(prompt: str = 'lis.py> ') -> NoReturn: + "A prompt-read-eval-print loop." + global_env = standard_env() + while True: + ast = parse(input(prompt)) + val = evaluate(ast, global_env) + if val is not None: + print(lispstr(val)) + +def lispstr(exp: object) -> str: + "Convert a Python object back into a Lisp-readable string." + if isinstance(exp, list): + return '(' + ' '.join(map(lispstr, exp)) + ')' + else: + return str(exp) +# end::REPL[] + + +################ Evaluator + +KEYWORDS = ['quote', 'if', 'lambda', 'define', 'set!'] + +# tag::EVAL_MATCH_TOP[] +def evaluate(exp: Expression, env: Environment) -> Any: + "Evaluate an expression in an environment." + match exp: +# end::EVAL_MATCH_TOP[] + case int(x) | float(x): + return x + case Symbol(name): + return env[name] +# tag::EVAL_MATCH_MIDDLE[] + case ['quote', x]: # <1> + return x + case ['if', test, consequence, alternative]: # <2> + if evaluate(test, env): + return evaluate(consequence, env) + else: + return evaluate(alternative, env) + case ['lambda', [*parms], *body] if body: # <3> + return Procedure(parms, body, env) + case ['define', Symbol(name), value_exp]: # <4> + env[name] = evaluate(value_exp, env) +# end::EVAL_MATCH_MIDDLE[] + case ['define', [Symbol(name), *parms], *body] if body: + env[name] = Procedure(parms, body, env) + case ['set!', Symbol(name), value_exp]: + env.change(name, evaluate(value_exp, env)) + case [func_exp, *args] if func_exp not in KEYWORDS: + proc = evaluate(func_exp, env) + values = [evaluate(arg, env) for arg in args] + return proc(*values) +# tag::EVAL_MATCH_BOTTOM[] + case _: # <5> + raise SyntaxError(lispstr(exp)) +# end::EVAL_MATCH_BOTTOM[] + +# tag::PROCEDURE[] +class Procedure: + "A user-defined Scheme procedure." + + def __init__( # <1> + self, parms: list[Symbol], body: list[Expression], env: Environment + ): + self.parms = parms # <2> + self.body = body + self.env = env + + def __call__(self, *args: Expression) -> Any: # <3> + local_env = dict(zip(self.parms, args)) # <4> + env = Environment(local_env, self.env) # <5> + for exp in self.body: # <6> + result = evaluate(exp, env) + return result # <7> +# end::PROCEDURE[] + + +################ command-line interface + +def run(source: str) -> Any: + global_env = standard_env() + tokens = tokenize(source) + while tokens: + exp = read_from_tokens(tokens) + result = evaluate(exp, global_env) + return result + +def main(args: list[str]) -> None: + if len(args) == 1: + with open(args[0]) as fp: + run(fp.read()) + else: + repl() + +if __name__ == '__main__': + import sys + main(sys.argv[1:]) diff --git a/02-array-seq/lispy/py3.10/lis_test.py b/02-array-seq/lispy/py3.10/lis_test.py new file mode 100644 index 0000000..03dc15c --- /dev/null +++ b/02-array-seq/lispy/py3.10/lis_test.py @@ -0,0 +1,180 @@ +from typing import Optional + +from pytest import mark, fixture + +from lis import parse, evaluate, Expression, Environment, standard_env + +############################################################# tests for parse + +@mark.parametrize( 'source, expected', [ + ('7', 7), + ('x', 'x'), + ('(sum 1 2 3)', ['sum', 1, 2, 3]), + ('(+ (* 2 100) (* 1 10))', ['+', ['*', 2, 100], ['*', 1, 10]]), + ('99 100', 99), # parse stops at the first complete expression + ('(a)(b)', ['a']), +]) +def test_parse(source: str, expected: Expression) -> None: + got = parse(source) + assert got == expected + + +########################################################## tests for evaluate + +# Norvig's tests are not isolated: they assume the +# same environment from first to last test. +global_env_for_first_test = standard_env() + +@mark.parametrize( 'source, expected', [ + ("(quote (testing 1 (2.0) -3.14e159))", ['testing', 1, [2.0], -3.14e159]), + ("(+ 2 2)", 4), + ("(+ (* 2 100) (* 1 10))", 210), + ("(if (> 6 5) (+ 1 1) (+ 2 2))", 2), + ("(if (< 6 5) (+ 1 1) (+ 2 2))", 4), + ("(define x 3)", None), + ("x", 3), + ("(+ x x)", 6), + ("((lambda (x) (+ x x)) 5)", 10), + ("(define twice (lambda (x) (* 2 x)))", None), + ("(twice 5)", 10), + ("(define compose (lambda (f g) (lambda (x) (f (g x)))))", None), + ("((compose list twice) 5)", [10]), + ("(define repeat (lambda (f) (compose f f)))", None), + ("((repeat twice) 5)", 20), + ("((repeat (repeat twice)) 5)", 80), + ("(define fact (lambda (n) (if (<= n 1) 1 (* n (fact (- n 1))))))", None), + ("(fact 3)", 6), + ("(fact 50)", 30414093201713378043612608166064768844377641568960512000000000000), + ("(define abs (lambda (n) ((if (> n 0) + -) 0 n)))", None), + ("(list (abs -3) (abs 0) (abs 3))", [3, 0, 3]), + ("""(define combine (lambda (f) + (lambda (x y) + (if (null? x) (quote ()) + (f (list (car x) (car y)) + ((combine f) (cdr x) (cdr y)))))))""", None), + ("(define zip (combine cons))", None), + ("(zip (list 1 2 3 4) (list 5 6 7 8))", [[1, 5], [2, 6], [3, 7], [4, 8]]), + ("""(define riff-shuffle (lambda (deck) + (begin + (define take (lambda (n seq) (if (<= n 0) (quote ()) (cons (car seq) (take (- n 1) (cdr seq)))))) + (define drop (lambda (n seq) (if (<= n 0) seq (drop (- n 1) (cdr seq))))) + (define mid (lambda (seq) (/ (length seq) 2))) + ((combine append) (take (mid deck) deck) (drop (mid deck) deck)))))""", None), + ("(riff-shuffle (list 1 2 3 4 5 6 7 8))", [1, 5, 2, 6, 3, 7, 4, 8]), + ("((repeat riff-shuffle) (list 1 2 3 4 5 6 7 8))", [1, 3, 5, 7, 2, 4, 6, 8]), + ("(riff-shuffle (riff-shuffle (riff-shuffle (list 1 2 3 4 5 6 7 8))))", [1,2,3,4,5,6,7,8]), +]) +def test_evaluate(source: str, expected: Optional[Expression]) -> None: + got = evaluate(parse(source), global_env_for_first_test) + assert got == expected + + +@fixture +def std_env() -> Environment: + return standard_env() + +# tests for cases in evaluate + +def test_evaluate_variable() -> None: + env = Environment({'x': 10}) + source = 'x' + expected = 10 + got = evaluate(parse(source), env) + assert got == expected + + +def test_evaluate_literal(std_env: Environment) -> None: + source = '3.3' + expected = 3.3 + got = evaluate(parse(source), std_env) + assert got == expected + + +def test_evaluate_quote(std_env: Environment) -> None: + source = '(quote (1.1 is not 1))' + expected = [1.1, 'is', 'not', 1] + got = evaluate(parse(source), std_env) + assert got == expected + + +def test_evaluate_if_true(std_env: Environment) -> None: + source = '(if 1 10 no-such-thing)' + expected = 10 + got = evaluate(parse(source), std_env) + assert got == expected + + +def test_evaluate_if_false(std_env: Environment) -> None: + source = '(if 0 no-such-thing 20)' + expected = 20 + got = evaluate(parse(source), std_env) + assert got == expected + + +def test_define(std_env: Environment) -> None: + source = '(define answer (* 6 7))' + got = evaluate(parse(source), std_env) + assert got is None + assert std_env['answer'] == 42 + + +def test_lambda(std_env: Environment) -> None: + source = '(lambda (a b) (if (>= a b) a b))' + func = evaluate(parse(source), std_env) + assert func.parms == ['a', 'b'] + assert func.body == [['if', ['>=', 'a', 'b'], 'a', 'b']] + assert func.env is std_env + assert func(1, 2) == 2 + assert func(3, 2) == 3 + + +def test_begin(std_env: Environment) -> None: + source = """ + (begin + (define x (* 2 3)) + (* x 7) + ) + """ + got = evaluate(parse(source), std_env) + assert got == 42 + + +def test_invocation_builtin_car(std_env: Environment) -> None: + source = '(car (quote (11 22 33)))' + got = evaluate(parse(source), std_env) + assert got == 11 + + +def test_invocation_builtin_append(std_env: Environment) -> None: + source = '(append (quote (a b)) (quote (c d)))' + got = evaluate(parse(source), std_env) + assert got == ['a', 'b', 'c', 'd'] + + +def test_invocation_builtin_map(std_env: Environment) -> None: + source = '(map (lambda (x) (* x 2)) (quote (1 2 3))))' + got = evaluate(parse(source), std_env) + assert got == [2, 4, 6] + + +def test_invocation_user_procedure(std_env: Environment) -> None: + source = """ + (begin + (define max (lambda (a b) (if (>= a b) a b))) + (max 22 11) + ) + """ + got = evaluate(parse(source), std_env) + assert got == 22 + + +def test_define_function(std_env: Environment) -> None: + source = '(define (max a b) (if (>= a b) a b))' + got = evaluate(parse(source), std_env) + assert got is None + max_fn = std_env['max'] + assert max_fn.parms == ['a', 'b'] + assert max_fn.body == [['if', ['>=', 'a', 'b'], 'a', 'b']] + assert max_fn.env is std_env + assert max_fn(1, 2) == 2 + assert max_fn(3, 2) == 3 diff --git a/02-array-seq/lispy/py3.10/quicksort.scm b/02-array-seq/lispy/py3.10/quicksort.scm new file mode 100644 index 0000000..08dd596 --- /dev/null +++ b/02-array-seq/lispy/py3.10/quicksort.scm @@ -0,0 +1,17 @@ +(define (quicksort lst) + (if (null? lst) + lst + (begin + (define pivot (car lst)) + (define rest (cdr lst)) + (append + (quicksort + (filter (lambda (x) (< x pivot)) rest)) + (list pivot) + (quicksort + (filter (lambda (x) (>= x pivot)) rest))) + ) + ) +) +(display + (quicksort (list 2 1 6 3 4 0 8 9 7 5))) diff --git a/02-array-seq/lispy/py3.9/README.md b/02-array-seq/lispy/py3.9/README.md new file mode 100644 index 0000000..fdb1f08 --- /dev/null +++ b/02-array-seq/lispy/py3.9/README.md @@ -0,0 +1,33 @@ +# Changes from the original + +While adapting Peter Norvig's [lis.py](https://github.com/norvig/pytudes/blob/705c0a335c1811a203e79587d7d41865cf7f41c7/py/lis.py) for +use in _Fluent Python, Second Edition_, I made a few changes for didactic reasons. + +_Luciano Ramalho_ + +## Major changes + +* Make the `lambda` form accept more than one expression as the body. This is consistent with [_Scheme_ syntax](https://web.mit.edu/scheme_v9.2/doc/mit-scheme-ref/Lambda-Expressions.html), and provides a useful example for the book. To implement this: + * In `Procedure.__call__`: evaluate `self.body` as a list of expressions, instead of a single expression. Return the value of the last expression. + * In `evaluate()`: when processing `lambda`, unpack expression into `(_, parms, *body)`, to accept a list of expressions as the body. +* Remove the `global_env` global `dict`. It is only used as a default value for the `env` parameter in `evaluate()`, but it is unsafe to use mutable data structures as parameter default values. To implement this: + * In `repl()`: create local variable `global_env` and pass it as the `env` paramater of `evaluate()`. + * In `evaluate()`, remove `global_env` default value for `env`. +* Rewrite the custom test script +[lispytest.py](https://github.com/norvig/pytudes/blob/705c0a335c1811a203e79587d7d41865cf7f41c7/py/lispytest.py) as +[lis_test.py](https://github.com/fluentpython/example-code-2e/blob/master/02-array-seq/lispy/py3.9/lis_test.py): +a standard [pytest](https://docs.pytest.org) test suite including new test cases, preserving all Norvig's test cases for +[lis.py](https://github.com/norvig/pytudes/blob/705c0a335c1811a203e79587d7d41865cf7f41c7/py/lis.py) +but removing the test cases for the features implemented only in +[lispy.py](https://github.com/norvig/pytudes/blob/705c0a335c1811a203e79587d7d41865cf7f41c7/py/lispy.py). + + +## Minor changes + +Cosmetic changes to make the code look more familiar to +Python programmers, the audience of _Fluent Python_. + +* Rename `eval()` to `evaluate()`, to avoid confusion with Python's `eval` built-in function. +* Refer to the list class as `list` instead of aliasing as `List`, to avoid confusion with `typing.List` which is often imported as `List`. +* Import `collections.ChainMap` as `ChainMap` instead of `Environment`. + diff --git a/02-array-seq/lispy/py3.9/examples_test.py b/02-array-seq/lispy/py3.9/examples_test.py new file mode 100644 index 0000000..bdcda48 --- /dev/null +++ b/02-array-seq/lispy/py3.9/examples_test.py @@ -0,0 +1,196 @@ +""" +Doctests for `parse` +-------------------- + +# tag::PARSE[] +>>> from lis import parse +>>> parse('1.5') +1.5 +>>> parse('ni!') +'ni!' +>>> parse('(gcd 18 45)') +['gcd', 18, 45] +>>> parse(''' +... (define double +... (lambda (n) +... (* n 2))) +... ''') +['define', 'double', ['lambda', ['n'], ['*', 'n', 2]]] + +# end::PARSE[] + +Doctest for `Environment` +------------------------- + +# tag::ENVIRONMENT[] +>>> from lis import Environment +>>> inner_env = {'a': 2} +>>> outer_env = {'a': 0, 'b': 1} +>>> env = Environment(inner_env, outer_env) +>>> env['a'] = 111 # <1> +>>> env['c'] = 222 +>>> env +Environment({'a': 111, 'c': 222}, {'a': 0, 'b': 1}) +>>> env.change('b', 333) # <2> +>>> env +Environment({'a': 111, 'c': 222}, {'a': 0, 'b': 333}) + +# end::ENVIRONMENT[] + +Doctests for `evaluate` +----------------------- + +# tag::EVAL_NUMBER[] +>>> from lis import parse, evaluate, standard_env +>>> evaluate(parse('1.5'), {}) +1.5 + +# end::EVAL_NUMBER[] + +# tag::EVAL_SYMBOL[] +>>> from lis import standard_env +>>> evaluate(parse('+'), standard_env()) + +>>> evaluate(parse('ni!'), standard_env()) +Traceback (most recent call last): + ... +KeyError: 'ni!' + +# end::EVAL_SYMBOL[] + + +# tag::EVAL_QUOTE[] +>>> evaluate(parse('(quote no-such-name)'), standard_env()) +'no-such-name' +>>> evaluate(parse('(quote (99 bottles of beer))'), standard_env()) +[99, 'bottles', 'of', 'beer'] +>>> evaluate(parse('(quote (/ 10 0))'), standard_env()) +['/', 10, 0] + +# end::EVAL_QUOTE[] + +# tag::EVAL_IF[] +>>> evaluate(parse('(if (= 3 3) 1 0))'), standard_env()) +1 +>>> evaluate(parse('(if (= 3 4) 1 0))'), standard_env()) +0 + +# end::EVAL_IF[] + + +# tag::EVAL_LAMBDA[] +>>> expr = '(lambda (a b) (* (/ a b) 100))' +>>> f = evaluate(parse(expr), standard_env()) +>>> f # doctest: +ELLIPSIS + +>>> f(15, 20) +75.0 + +# end::EVAL_LAMBDA[] + +# tag::EVAL_DEFINE[] +>>> global_env = standard_env() +>>> evaluate(parse('(define answer (* 7 6))'), global_env) +>>> global_env['answer'] +42 + +# end::EVAL_DEFINE[] + +# tag::EVAL_DEFUN[] +>>> global_env = standard_env() +>>> percent = '(define % (lambda (a b) (* (/ a b) 100)))' +>>> evaluate(parse(percent), global_env) +>>> global_env['%'] # doctest: +ELLIPSIS + +>>> global_env['%'](170, 200) +85.0 + +# end::EVAL_DEFUN[] + +function call: + +# tag::EVAL_CALL[] +>>> evaluate(parse('(% (* 12 14) (- 500 100))'), global_env) +42.0 + +# end::EVAL_CALL[] + +""" + +import math + +from lis import run + + +fact_src = """ +(define ! + (lambda (n) + (if (< n 2) + 1 + (* n (! (- n 1))) + ) + ) +) +(! 42) +""" +def test_factorial(): + got = run(fact_src) + assert got == 1405006117752879898543142606244511569936384000000000 + assert got == math.factorial(42) + +closure_src = """ +(define make-adder + (lambda (increment) + (lambda (x) (+ increment x)) + ) +) +(define inc (make-adder 1)) +(inc 99) +""" +def test_closure(): + got = run(closure_src) + assert got == 100 + +closure_with_change_src = """ +(define make-counter + (lambda () + (define n 0) + (lambda () + (set! n (+ n 1)) + n) + ) +) +(define counter (make-counter)) +(display (counter)) +(display (counter)) +(display (counter)) +""" +def test_closure_with_change(capsys): + run(closure_with_change_src) + captured = capsys.readouterr() + assert captured.out == '1\n2\n3\n' + + + +# tag::RUN_AVERAGER[] +closure_averager_src = """ +(define make-averager + (lambda () + (define count 0) + (define total 0) + (lambda (new-value) + (set! count (+ count 1)) + (set! total (+ total new-value)) + (/ total count) + ) + ) +) +(define avg (make-averager)) +(avg 10) +(avg 11) +(avg 15) +""" +def test_closure_averager(): + got = run(closure_averager_src) + assert got == 12.0 +# end::RUN_AVERAGER[] \ No newline at end of file diff --git a/02-array-seq/lispy/py3.9/lis.py b/02-array-seq/lispy/py3.9/lis.py new file mode 100644 index 0000000..843d4cc --- /dev/null +++ b/02-array-seq/lispy/py3.9/lis.py @@ -0,0 +1,209 @@ +#!/usr/bin/env python + +################ Lispy: Scheme Interpreter in Python 3.9 + +## (c) Peter Norvig, 2010-18; See http://norvig.com/lispy.html +## Minor edits for Fluent Python, Second Edition (O'Reilly, 2021) +## by Luciano Ramalho, adding type hints and pattern matching. + + +################ Imports and Types + +import math +import operator as op +from collections import ChainMap +from itertools import chain +from typing import Any, Union, NoReturn + +Symbol = str +Atom = Union[float, int, Symbol] +Expression = Union[Atom, list] + + +################ Parsing: parse, tokenize, and read_from_tokens + +def parse(program: str) -> Expression: + "Read a Scheme expression from a string." + return read_from_tokens(tokenize(program)) + +def tokenize(s: str) -> list[str]: + "Convert a string into a list of tokens." + return s.replace('(', ' ( ').replace(')', ' ) ').split() + +def read_from_tokens(tokens: list[str]) -> Expression: + "Read an expression from a sequence of tokens." + if len(tokens) == 0: + raise SyntaxError('unexpected EOF while reading') + token = tokens.pop(0) + if '(' == token: + exp = [] + while tokens[0] != ')': + exp.append(read_from_tokens(tokens)) + tokens.pop(0) # discard ')' + return exp + elif ')' == token: + raise SyntaxError('unexpected )') + else: + return parse_atom(token) + +def parse_atom(token: str) -> Atom: + "Numbers become numbers; every other token is a symbol." + try: + return int(token) + except ValueError: + try: + return float(token) + except ValueError: + return Symbol(token) + + +################ Global Environment + +class Environment(ChainMap[Symbol, Any]): + "A ChainMap that allows changing an item in-place." + + def change(self, key: Symbol, value: object) -> None: + "Find where key is defined and change the value there." + for map in self.maps: + if key in map: + map[key] = value # type: ignore[index] + return + raise KeyError(key) + + +def standard_env() -> Environment: + "An environment with some Scheme standard procedures." + env = Environment() + env.update(vars(math)) # sin, cos, sqrt, pi, ... + env.update({ + '+': op.add, + '-': op.sub, + '*': op.mul, + '/': op.truediv, + '//': op.floordiv, + '>': op.gt, + '<': op.lt, + '>=': op.ge, + '<=': op.le, + '=': op.eq, + 'abs': abs, + 'append': lambda *args: list(chain(*args)), + 'apply': lambda proc, args: proc(*args), + 'begin': lambda *x: x[-1], + 'car': lambda x: x[0], + 'cdr': lambda x: x[1:], + 'cons': lambda x, y: [x] + y, + 'display': lambda x: print(lispstr(x)), + 'eq?': op.is_, + 'equal?': op.eq, + 'filter': lambda *args: list(filter(*args)), + 'length': len, + 'list': lambda *x: list(x), + 'list?': lambda x: isinstance(x, list), + 'map': lambda *args: list(map(*args)), + 'max': max, + 'min': min, + 'not': op.not_, + 'null?': lambda x: x == [], + 'number?': lambda x: isinstance(x, (int, float)), + 'procedure?': callable, + 'round': round, + 'symbol?': lambda x: isinstance(x, Symbol), + }) + return env + + +################ Interaction: A REPL + +def repl(prompt: str = 'lis.py> ') -> NoReturn: + "A prompt-read-eval-print loop." + global_env = standard_env() + while True: + ast = parse(input(prompt)) + val = evaluate(ast, global_env) + if val is not None: + print(lispstr(val)) + +def lispstr(exp: object) -> str: + "Convert a Python object back into a Lisp-readable string." + if isinstance(exp, list): + return '(' + ' '.join(map(lispstr, exp)) + ')' + else: + return str(exp) + + +################ Evaluator + +# tag::EVAL_IF_TOP[] +def evaluate(exp: Expression, env: Environment) -> Any: + "Evaluate an expression in an environment." + if isinstance(exp, Symbol): # variable reference + return env[exp] +# end::EVAL_IF_TOP[] + elif not isinstance(exp, list): # constant literal + return exp +# tag::EVAL_IF_MIDDLE[] + elif exp[0] == 'quote': # (quote exp) + (_, x) = exp + return x + elif exp[0] == 'if': # (if test conseq alt) + (_, test, consequence, alternative) = exp + if evaluate(test, env): + return evaluate(consequence, env) + else: + return evaluate(alternative, env) + elif exp[0] == 'lambda': # (lambda (parm…) body…) + (_, parms, *body) = exp + return Procedure(parms, body, env) + elif exp[0] == 'define': + (_, name, value_exp) = exp + env[name] = evaluate(value_exp, env) +# end::EVAL_IF_MIDDLE[] + elif exp[0] == 'set!': + (_, name, value_exp) = exp + env.change(name, evaluate(value_exp, env)) + else: # (proc arg…) + (func_exp, *args) = exp + proc = evaluate(func_exp, env) + args = [evaluate(arg, env) for arg in args] + return proc(*args) + + +class Procedure: + "A user-defined Scheme procedure." + + def __init__( + self, parms: list[Symbol], body: list[Expression], env: Environment + ): + self.parms = parms + self.body = body + self.env = env + + def __call__(self, *args: Expression) -> Any: + local_env = dict(zip(self.parms, args)) + env = Environment(local_env, self.env) + for exp in self.body: + result = evaluate(exp, env) + return result + + +################ command-line interface + +def run(source: str) -> Any: + global_env = standard_env() + tokens = tokenize(source) + while tokens: + exp = read_from_tokens(tokens) + result = evaluate(exp, global_env) + return result + +def main(args: list[str]) -> None: + if len(args) == 1: + with open(args[0]) as fp: + run(fp.read()) + else: + repl() + +if __name__ == '__main__': + import sys + main(sys.argv[1:]) diff --git a/02-array-seq/lispy/py3.9/lis_test.py b/02-array-seq/lispy/py3.9/lis_test.py new file mode 100644 index 0000000..64bafb4 --- /dev/null +++ b/02-array-seq/lispy/py3.9/lis_test.py @@ -0,0 +1,180 @@ +from typing import Optional + +from pytest import mark, fixture + +from lis import parse, evaluate, Expression, Environment, standard_env + +############################################################# tests for parse + +@mark.parametrize( 'source, expected', [ + ('7', 7), + ('x', 'x'), + ('(sum 1 2 3)', ['sum', 1, 2, 3]), + ('(+ (* 2 100) (* 1 10))', ['+', ['*', 2, 100], ['*', 1, 10]]), + ('99 100', 99), # parse stops at the first complete expression + ('(a)(b)', ['a']), +]) +def test_parse(source: str, expected: Expression) -> None: + got = parse(source) + assert got == expected + + +########################################################## tests for evaluate + +# Norvig's tests are not isolated: they assume the +# same environment from first to last test. +global_env_for_first_test = standard_env() + +@mark.parametrize( 'source, expected', [ + ("(quote (testing 1 (2.0) -3.14e159))", ['testing', 1, [2.0], -3.14e159]), + ("(+ 2 2)", 4), + ("(+ (* 2 100) (* 1 10))", 210), + ("(if (> 6 5) (+ 1 1) (+ 2 2))", 2), + ("(if (< 6 5) (+ 1 1) (+ 2 2))", 4), + ("(define x 3)", None), + ("x", 3), + ("(+ x x)", 6), + ("((lambda (x) (+ x x)) 5)", 10), + ("(define twice (lambda (x) (* 2 x)))", None), + ("(twice 5)", 10), + ("(define compose (lambda (f g) (lambda (x) (f (g x)))))", None), + ("((compose list twice) 5)", [10]), + ("(define repeat (lambda (f) (compose f f)))", None), + ("((repeat twice) 5)", 20), + ("((repeat (repeat twice)) 5)", 80), + ("(define fact (lambda (n) (if (<= n 1) 1 (* n (fact (- n 1))))))", None), + ("(fact 3)", 6), + ("(fact 50)", 30414093201713378043612608166064768844377641568960512000000000000), + ("(define abs (lambda (n) ((if (> n 0) + -) 0 n)))", None), + ("(list (abs -3) (abs 0) (abs 3))", [3, 0, 3]), + ("""(define combine (lambda (f) + (lambda (x y) + (if (null? x) (quote ()) + (f (list (car x) (car y)) + ((combine f) (cdr x) (cdr y)))))))""", None), + ("(define zip (combine cons))", None), + ("(zip (list 1 2 3 4) (list 5 6 7 8))", [[1, 5], [2, 6], [3, 7], [4, 8]]), + ("""(define riff-shuffle (lambda (deck) + (begin + (define take (lambda (n seq) (if (<= n 0) (quote ()) (cons (car seq) (take (- n 1) (cdr seq)))))) + (define drop (lambda (n seq) (if (<= n 0) seq (drop (- n 1) (cdr seq))))) + (define mid (lambda (seq) (/ (length seq) 2))) + ((combine append) (take (mid deck) deck) (drop (mid deck) deck)))))""", None), + ("(riff-shuffle (list 1 2 3 4 5 6 7 8))", [1, 5, 2, 6, 3, 7, 4, 8]), + ("((repeat riff-shuffle) (list 1 2 3 4 5 6 7 8))", [1, 3, 5, 7, 2, 4, 6, 8]), + ("(riff-shuffle (riff-shuffle (riff-shuffle (list 1 2 3 4 5 6 7 8))))", [1,2,3,4,5,6,7,8]), +]) +def test_evaluate(source: str, expected: Optional[Expression]) -> None: + got = evaluate(parse(source), global_env_for_first_test) + assert got == expected + + +@fixture +def std_env() -> Environment: + return standard_env() + +# tests for cases in evaluate + +def test_evaluate_variable() -> None: + env = Environment({'x': 10}) + source = 'x' + expected = 10 + got = evaluate(parse(source), env) + assert got == expected + + +def test_evaluate_literal(std_env: Environment) -> None: + source = '3.3' + expected = 3.3 + got = evaluate(parse(source), std_env) + assert got == expected + + +def test_evaluate_quote(std_env: Environment) -> None: + source = '(quote (1.1 is not 1))' + expected = [1.1, 'is', 'not', 1] + got = evaluate(parse(source), std_env) + assert got == expected + + +def test_evaluate_if_true(std_env: Environment) -> None: + source = '(if 1 10 no-such-thing)' + expected = 10 + got = evaluate(parse(source), std_env) + assert got == expected + + +def test_evaluate_if_false(std_env: Environment) -> None: + source = '(if 0 no-such-thing 20)' + expected = 20 + got = evaluate(parse(source), std_env) + assert got == expected + + +def test_define(std_env: Environment) -> None: + source = '(define answer (* 6 7))' + got = evaluate(parse(source), std_env) + assert got is None + assert std_env['answer'] == 42 + + +def test_lambda(std_env: Environment) -> None: + source = '(lambda (a b) (if (>= a b) a b))' + func = evaluate(parse(source), std_env) + assert func.parms == ['a', 'b'] + assert func.body == [['if', ['>=', 'a', 'b'], 'a', 'b']] + assert func.env is std_env + assert func(1, 2) == 2 + assert func(3, 2) == 3 + + +def test_begin(std_env: Environment) -> None: + source = """ + (begin + (define x (* 2 3)) + (* x 7) + ) + """ + got = evaluate(parse(source), std_env) + assert got == 42 + + +def test_invocation_builtin_car(std_env: Environment) -> None: + source = '(car (quote (11 22 33)))' + got = evaluate(parse(source), std_env) + assert got == 11 + + +def test_invocation_builtin_append(std_env: Environment) -> None: + source = '(append (quote (a b)) (quote (c d)))' + got = evaluate(parse(source), std_env) + assert got == ['a', 'b', 'c', 'd'] + + +def test_invocation_builtin_map(std_env: Environment) -> None: + source = '(map (lambda (x) (* x 2)) (quote (1 2 3))))' + got = evaluate(parse(source), std_env) + assert got == [2, 4, 6] + + +def test_invocation_user_procedure(std_env: Environment) -> None: + source = """ + (begin + (define max (lambda (a b) (if (>= a b) a b))) + (max 22 11) + ) + """ + got = evaluate(parse(source), std_env) + assert got == 22 + + +def test_define_function(std_env: Environment) -> None: + source = '(define max (lambda (a b) (if (>= a b) a b)))' + got = evaluate(parse(source), std_env) + assert got is None + max_fn = std_env['max'] + assert max_fn.parms == ['a', 'b'] + assert max_fn.body == [['if', ['>=', 'a', 'b'], 'a', 'b']] + assert max_fn.env is std_env + assert max_fn(1, 2) == 2 + assert max_fn(3, 2) == 3 diff --git a/18-with-match/lispy/py3.10/examples_test.py b/18-with-match/lispy/py3.10/examples_test.py index ed4b68a..ba6bd10 100644 --- a/18-with-match/lispy/py3.10/examples_test.py +++ b/18-with-match/lispy/py3.10/examples_test.py @@ -2,16 +2,12 @@ Doctests for `parse` -------------------- -# tag::PARSE_ATOM[] +# tag::PARSE[] >>> from lis import parse >>> parse('1.5') 1.5 >>> parse('ni!') 'ni!' - -# end::PARSE_ATOM[] - -# tag::PARSE_LIST[] >>> parse('(gcd 18 45)') ['gcd', 18, 45] >>> parse(''' @@ -21,15 +17,15 @@ ... ''') ['define', 'double', ['lambda', ['n'], ['*', 'n', 2]]] -# end::PARSE_LIST[] +# end::PARSE[] Doctest for `Environment` ------------------------- # tag::ENVIRONMENT[] >>> from lis import Environment ->>> outer_env = {'a': 0, 'b': 1} >>> inner_env = {'a': 2} +>>> outer_env = {'a': 0, 'b': 1} >>> env = Environment(inner_env, outer_env) >>> env['a'] = 111 # <1> >>> env['c'] = 222 @@ -64,11 +60,11 @@ # tag::EVAL_QUOTE[] ->>> evaluate(parse('(quote no-such-name)'), {}) +>>> evaluate(parse('(quote no-such-name)'), standard_env()) 'no-such-name' ->>> evaluate(parse('(quote (99 bottles of beer))'), {}) +>>> evaluate(parse('(quote (99 bottles of beer))'), standard_env()) [99, 'bottles', 'of', 'beer'] ->>> evaluate(parse('(quote (/ 10 0))'), {}) +>>> evaluate(parse('(quote (/ 10 0))'), standard_env()) ['/', 10, 0] # end::EVAL_QUOTE[] @@ -156,11 +152,12 @@ def test_factorial(): (if (= n 0) m (gcd n (mod m n)))) -(gcd 18 45) +(display (gcd 18 45)) """ -def test_gcd(): - got = run(gcd_src) - assert got == 9 +def test_gcd(capsys): + run(gcd_src) + captured = capsys.readouterr() + assert captured.out == '9\n' quicksort_src = """ @@ -216,7 +213,7 @@ def test_newton(): (define inc (make-adder 1)) (inc 99) """ -def test_newton(): +def test_closure(): got = run(closure_src) assert got == 100 @@ -228,13 +225,15 @@ def test_newton(): n) ) (define counter (make-counter)) -(counter) -(counter) -(counter) +(display (counter)) +(display (counter)) +(display (counter)) """ -def test_closure_with_change(): - got = run(closure_with_change_src) - assert got == 3 +def test_closure_with_change(capsys): + run(closure_with_change_src) + captured = capsys.readouterr() + assert captured.out == '1\n2\n3\n' + # tag::RUN_AVERAGER[] @@ -256,4 +255,4 @@ def test_closure_with_change(): def test_closure_averager(): got = run(closure_averager_src) assert got == 12.0 -# end::RUN_AVERAGER[] \ No newline at end of file +# end::RUN_AVERAGER[] diff --git a/18-with-match/lispy/py3.10/lis.py b/18-with-match/lispy/py3.10/lis.py index 4a5195c..e05ee9c 100755 --- a/18-with-match/lispy/py3.10/lis.py +++ b/18-with-match/lispy/py3.10/lis.py @@ -58,10 +58,11 @@ def parse_atom(token: str) -> Atom: except ValueError: return Symbol(token) + ################ Global Environment # tag::ENV_CLASS[] -class Environment(ChainMap): +class Environment(ChainMap[Symbol, Any]): "A ChainMap that allows changing an item in-place." def change(self, key: Symbol, value: object) -> None: @@ -73,7 +74,6 @@ def change(self, key: Symbol, value: object) -> None: raise KeyError(key) # end::ENV_CLASS[] - def standard_env() -> Environment: "An environment with some Scheme standard procedures." env = Environment() @@ -119,11 +119,11 @@ def standard_env() -> Environment: ################ Interaction: A REPL # tag::REPL[] -def repl() -> NoReturn: +def repl(prompt: str = 'lis.py> ') -> NoReturn: "A prompt-read-eval-print loop." global_env = standard_env() while True: - ast = parse(input('lis.py> ')) + ast = parse(input(prompt)) val = evaluate(ast, global_env) if val is not None: print(lispstr(val)) @@ -149,8 +149,6 @@ def evaluate(exp: Expression, env: Environment) -> Any: return x case Symbol(var): return env[var] - case []: - return [] case ['quote', x]: return x case ['if', test, consequence, alternative]: @@ -166,8 +164,8 @@ def evaluate(exp: Expression, env: Environment) -> Any: env[name] = Procedure(parms, body, env) case ['set!', Symbol(var), value_exp]: env.change(var, evaluate(value_exp, env)) - case [op, *args] if op not in KEYWORDS: - proc = evaluate(op, env) + case [func_exp, *args] if func_exp not in KEYWORDS: + proc = evaluate(func_exp, env) values = [evaluate(arg, env) for arg in args] return proc(*values) case _: diff --git a/18-with-match/lispy/py3.10/lis_test.py b/18-with-match/lispy/py3.10/lis_test.py index 3688888..03dc15c 100644 --- a/18-with-match/lispy/py3.10/lis_test.py +++ b/18-with-match/lispy/py3.10/lis_test.py @@ -73,10 +73,10 @@ def test_evaluate(source: str, expected: Optional[Expression]) -> None: def std_env() -> Environment: return standard_env() -# tests for each of the cases in evaluate +# tests for cases in evaluate def test_evaluate_variable() -> None: - env: Environment = dict(x=10) + env = Environment({'x': 10}) source = 'x' expected = 10 got = evaluate(parse(source), env) @@ -168,8 +168,6 @@ def test_invocation_user_procedure(std_env: Environment) -> None: assert got == 22 -###################################### for py3.10/lis.py only - def test_define_function(std_env: Environment) -> None: source = '(define (max a b) (if (>= a b) a b))' got = evaluate(parse(source), std_env) diff --git a/18-with-match/lispy/py3.9/examples_test.py b/18-with-match/lispy/py3.9/examples_test.py new file mode 100644 index 0000000..c632662 --- /dev/null +++ b/18-with-match/lispy/py3.9/examples_test.py @@ -0,0 +1,258 @@ +""" +Doctests for `parse` +-------------------- + +# tag::PARSE[] +>>> from lis import parse +>>> parse('1.5') +1.5 +>>> parse('ni!') +'ni!' +>>> parse('(gcd 18 45)') +['gcd', 18, 45] +>>> parse(''' +... (define double +... (lambda (n) +... (* n 2))) +... ''') +['define', 'double', ['lambda', ['n'], ['*', 'n', 2]]] + +# end::PARSE[] + +Doctest for `Environment` +------------------------- + +# tag::ENVIRONMENT[] +>>> from lis import Environment +>>> inner_env = {'a': 2} +>>> outer_env = {'a': 0, 'b': 1} +>>> env = Environment(inner_env, outer_env) +>>> env['a'] = 111 # <1> +>>> env['c'] = 222 +>>> env +Environment({'a': 111, 'c': 222}, {'a': 0, 'b': 1}) +>>> env.change('b', 333) # <2> +>>> env +Environment({'a': 111, 'c': 222}, {'a': 0, 'b': 333}) + +# end::ENVIRONMENT[] + +Doctests for `evaluate` +----------------------- + +# tag::EVAL_NUMBER[] +>>> from lis import parse, evaluate, standard_env +>>> evaluate(parse('1.5'), {}) +1.5 + +# end::EVAL_NUMBER[] + +# tag::EVAL_SYMBOL[] +>>> from lis import standard_env +>>> evaluate(parse('+'), standard_env()) + +>>> evaluate(parse('ni!'), standard_env()) +Traceback (most recent call last): + ... +KeyError: 'ni!' + +# end::EVAL_SYMBOL[] + + +# tag::EVAL_QUOTE[] +>>> evaluate(parse('(quote no-such-name)'), standard_env()) +'no-such-name' +>>> evaluate(parse('(quote (99 bottles of beer))'), standard_env()) +[99, 'bottles', 'of', 'beer'] +>>> evaluate(parse('(quote (/ 10 0))'), standard_env()) +['/', 10, 0] + +# end::EVAL_QUOTE[] + +# tag::EVAL_IF[] +>>> evaluate(parse('(if (= 3 3) 1 0))'), standard_env()) +1 +>>> evaluate(parse('(if (= 3 4) 1 0))'), standard_env()) +0 + +# end::EVAL_IF[] + + +# tag::EVAL_LAMBDA[] +>>> expr = '(lambda (a b) (* (/ a b) 100))' +>>> f = evaluate(parse(expr), standard_env()) +>>> f # doctest: +ELLIPSIS + +>>> f(15, 20) +75.0 + +# end::EVAL_LAMBDA[] + +# tag::EVAL_DEFINE[] +>>> global_env = standard_env() +>>> evaluate(parse('(define answer (* 7 6))'), global_env) +>>> global_env['answer'] +42 + +# end::EVAL_DEFINE[] + +# tag::EVAL_DEFUN[] +>>> global_env = standard_env() +>>> percent = '(define (% a b) (* (/ a b) 100))' +>>> evaluate(parse(percent), global_env) +>>> global_env['%'] # doctest: +ELLIPSIS + +>>> global_env['%'](170, 200) +85.0 + +# end::EVAL_DEFUN[] + +function call: + +# tag::EVAL_CALL[] +>>> evaluate(parse('(% (* 12 14) (- 500 100))'), global_env) +42.0 + +# end::EVAL_CALL[] + +# tag::EVAL_SYNTAX_ERROR[] +>>> evaluate(parse('(lambda is not like this)'), standard_env()) +Traceback (most recent call last): + ... +SyntaxError: (lambda is not like this) + +# end::EVAL_SYNTAX_ERROR[] + +""" + +import math + +from lis import run + + +fact_src = """ +(define (! n) + (if (< n 2) + 1 + (* n (! (- n 1))) + ) +) +(! 42) +""" +def test_factorial(): + got = run(fact_src) + assert got == 1405006117752879898543142606244511569936384000000000 + assert got == math.factorial(42) + + +gcd_src = """ +(define (mod m n) + (- m (* n (// m n)))) +(define (gcd m n) + (if (= n 0) + m + (gcd n (mod m n)))) +(display (gcd 18 45)) +""" +def test_gcd(capsys): + run(gcd_src) + captured = capsys.readouterr() + assert captured.out == '9\n' + + +quicksort_src = """ +(define (quicksort lst) + (if (null? lst) + lst + (begin + (define pivot (car lst)) + (define rest (cdr lst)) + (append + (quicksort + (filter (lambda (x) (< x pivot)) rest)) + (list pivot) + (quicksort + (filter (lambda (x) (>= x pivot)) rest))) + ) + ) +) +(quicksort (list 2 1 6 3 4 0 8 9 7 5)) +""" +def test_quicksort(): + got = run(quicksort_src) + assert got == [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + + +# Example from Structure and Interpretation of Computer Programs +# https://mitpress.mit.edu/sites/default/files/sicp/full-text/sicp/book/node12.html + +newton_src = """ +(define (sqrt x) + (sqrt-iter 1.0 x)) +(define (sqrt-iter guess x) + (if (good-enough? guess x) + guess + (sqrt-iter (improve guess x) x))) +(define (good-enough? guess x) + (< (abs (- (* guess guess) x)) 0.001)) +(define (improve guess x) + (average guess (/ x guess))) +(define (average x y) + (/ (+ x y) 2)) +(sqrt 123454321) +""" +def test_newton(): + got = run(newton_src) + assert math.isclose(got, 11111) + + +closure_src = """ +(define (make-adder increment) + (lambda (x) (+ increment x)) +) +(define inc (make-adder 1)) +(inc 99) +""" +def test_closure(): + got = run(closure_src) + assert got == 100 + +closure_with_change_src = """ +(define (make-counter) + (define n 0) + (lambda () + (set! n (+ n 1)) + n) +) +(define counter (make-counter)) +(display (counter)) +(display (counter)) +(display (counter)) +""" +def test_closure_with_change(capsys): + run(closure_with_change_src) + captured = capsys.readouterr() + assert captured.out == '1\n2\n3\n' + + + +# tag::RUN_AVERAGER[] +closure_averager_src = """ +(define (make-averager) + (define count 0) + (define total 0) + (lambda (new-value) + (set! count (+ count 1)) + (set! total (+ total new-value)) + (/ total count) + ) +) +(define avg (make-averager)) +(avg 10) +(avg 11) +(avg 15) +""" +def test_closure_averager(): + got = run(closure_averager_src) + assert got == 12.0 +# end::RUN_AVERAGER[] \ No newline at end of file diff --git a/18-with-match/lispy/py3.9/lis.py b/18-with-match/lispy/py3.9/lis.py index 9e4dec1..e8881cd 100644 --- a/18-with-match/lispy/py3.9/lis.py +++ b/18-with-match/lispy/py3.9/lis.py @@ -1,73 +1,24 @@ +#!/usr/bin/env python + ################ Lispy: Scheme Interpreter in Python 3.9 ## (c) Peter Norvig, 2010-18; See http://norvig.com/lispy.html ## Minor edits for Fluent Python, Second Edition (O'Reilly, 2021) ## by Luciano Ramalho, adding type hints and pattern matching. + ################ Imports and Types import math import operator as op from collections import ChainMap -from collections.abc import MutableMapping, Iterator from itertools import chain -from typing import Any, Union +from typing import Any, Union, NoReturn Symbol = str Atom = Union[float, int, Symbol] Expression = Union[Atom, list] -Environment = MutableMapping[Symbol, object] - - -class Procedure: - "A user-defined Scheme procedure." - - def __init__(self, parms: list[Symbol], body: list[Expression], env: Environment): - self.parms = parms - self.body = body - self.env = env - - def __call__(self, *args: Expression) -> Any: - local_env = dict(zip(self.parms, args)) - env: Environment = ChainMap(local_env, self.env) - for exp in self.body: - result = evaluate(exp, env) - return result - - -################ Global Environment - -def standard_env() -> Environment: - "An environment with some Scheme standard procedures." - env: Environment = {} - env.update(vars(math)) # sin, cos, sqrt, pi, ... - env.update({ - '+':op.add, '-':op.sub, '*':op.mul, '/':op.truediv, - '>':op.gt, '<':op.lt, '>=':op.ge, '<=':op.le, '=':op.eq, - 'abs': abs, - 'append': op.add, - 'apply': lambda proc, args: proc(*args), - 'begin': lambda *x: x[-1], - 'car': lambda x: x[0], - 'cdr': lambda x: x[1:], - 'cons': lambda x,y: [x] + y, - 'eq?': op.is_, - 'equal?': op.eq, - 'length': len, - 'list': lambda *x: list(x), - 'list?': lambda x: isinstance(x,list), - 'map': lambda *args: list(map(*args)), - 'max': max, - 'min': min, - 'not': op.not_, - 'null?': lambda x: x == [], - 'number?': lambda x: isinstance(x, (int, float)), - 'procedure?': callable, - 'round': round, - 'symbol?': lambda x: isinstance(x, Symbol), - }) - return env ################ Parsing: parse, tokenize, and read_from_tokens @@ -75,12 +26,10 @@ def parse(program: str) -> Expression: "Read a Scheme expression from a string." return read_from_tokens(tokenize(program)) - def tokenize(s: str) -> list[str]: "Convert a string into a list of tokens." return s.replace('(', ' ( ').replace(')', ' ) ').split() - def read_from_tokens(tokens: list[str]) -> Expression: "Read an expression from a sequence of tokens." if len(tokens) == 0: @@ -97,7 +46,6 @@ def read_from_tokens(tokens: list[str]) -> Expression: else: return parse_atom(token) - def parse_atom(token: str) -> Atom: "Numbers become numbers; every other token is a symbol." try: @@ -109,17 +57,73 @@ def parse_atom(token: str) -> Atom: return Symbol(token) +################ Global Environment + +class Environment(ChainMap[Symbol, Any]): + "A ChainMap that allows changing an item in-place." + + def change(self, key: Symbol, value: object) -> None: + "Find where key is defined and change the value there." + for map in self.maps: + if key in map: + map[key] = value # type: ignore[index] + return + raise KeyError(key) + + +def standard_env() -> Environment: + "An environment with some Scheme standard procedures." + env = Environment() + env.update(vars(math)) # sin, cos, sqrt, pi, ... + env.update({ + '+': op.add, + '-': op.sub, + '*': op.mul, + '/': op.truediv, + '//': op.floordiv, + '>': op.gt, + '<': op.lt, + '>=': op.ge, + '<=': op.le, + '=': op.eq, + 'abs': abs, + 'append': lambda *args: list(chain(*args)), + 'apply': lambda proc, args: proc(*args), + 'begin': lambda *x: x[-1], + 'car': lambda x: x[0], + 'cdr': lambda x: x[1:], + 'cons': lambda x, y: [x] + y, + 'display': lambda x: print(lispstr(x)), + 'eq?': op.is_, + 'equal?': op.eq, + 'filter': lambda *args: list(filter(*args)), + 'length': len, + 'list': lambda *x: list(x), + 'list?': lambda x: isinstance(x, list), + 'map': lambda *args: list(map(*args)), + 'max': max, + 'min': min, + 'not': op.not_, + 'null?': lambda x: x == [], + 'number?': lambda x: isinstance(x, (int, float)), + 'procedure?': callable, + 'round': round, + 'symbol?': lambda x: isinstance(x, Symbol), + }) + return env + + ################ Interaction: A REPL -def repl(prompt: str = 'lis.py> ') -> None: +def repl(prompt: str = 'lis.py> ') -> NoReturn: "A prompt-read-eval-print loop." global_env = standard_env() while True: - val = evaluate(parse(input(prompt)), global_env) + ast = parse(input(prompt)) + val = evaluate(ast, global_env) if val is not None: print(lispstr(val)) - def lispstr(exp: object) -> str: "Convert a Python object back into a Lisp-readable string." if isinstance(exp, list): @@ -128,30 +132,81 @@ def lispstr(exp: object) -> str: return str(exp) -################ eval +################ Evaluator -def evaluate(x: Expression, env: Environment) -> Any: +def evaluate(exp: Expression, env: Environment) -> Any: "Evaluate an expression in an environment." - if isinstance(x, Symbol): # variable reference - return env[x] - elif not isinstance(x, list): # constant literal - return x - elif x[0] == 'quote': # (quote exp) - (_, exp) = x + if isinstance(exp, Symbol): # variable reference + return env[exp] + elif not isinstance(exp, list): # constant literal return exp - elif x[0] == 'if': # (if test conseq alt) - (_, test, consequence, alternative) = x + elif exp[0] == 'quote': # (quote exp) + (_, x) = exp + return x + elif exp[0] == 'if': # (if test conseq alt) + (_, test, consequence, alternative) = exp if evaluate(test, env): return evaluate(consequence, env) else: return evaluate(alternative, env) - elif x[0] == 'define': # (define name exp) - (_, name, exp) = x - env[name] = evaluate(exp, env) - elif x[0] == 'lambda': # (lambda (parm…) body) - (_, parms, *body) = x + elif exp[0] == 'lambda': # (lambda (parm…) body…) + (_, parms, *body) = exp + if not isinstance(parms, list): + raise SyntaxError(lispstr(exp)) return Procedure(parms, body, env) + elif exp[0] == 'define': + (_, name_exp, *rest) = exp + if isinstance(name_exp, Symbol): # (define name exp) + value_exp = rest[0] + env[name_exp] = evaluate(value_exp, env) + else: # (define (name parm…) body…) + name, *parms = name_exp + env[name] = Procedure(parms, rest, env) + elif exp[0] == 'set!': + (_, var, value_exp) = exp + env.change(var, evaluate(value_exp, env)) else: # (proc arg…) - proc = evaluate(x[0], env) - args = [evaluate(exp, env) for exp in x[1:]] + (func_exp, *args) = exp + proc = evaluate(func_exp, env) + args = [evaluate(arg, env) for arg in args] return proc(*args) + + +class Procedure: + "A user-defined Scheme procedure." + + def __init__( + self, parms: list[Symbol], body: list[Expression], env: Environment + ): + self.parms = parms + self.body = body + self.env = env + + def __call__(self, *args: Expression) -> Any: + local_env = dict(zip(self.parms, args)) + env = Environment(local_env, self.env) + for exp in self.body: + result = evaluate(exp, env) + return result + + +################ command-line interface + +def run(source: str) -> Any: + global_env = standard_env() + tokens = tokenize(source) + while tokens: + exp = read_from_tokens(tokens) + result = evaluate(exp, global_env) + return result + +def main(args: list[str]) -> None: + if len(args) == 1: + with open(args[0]) as fp: + run(fp.read()) + else: + repl() + +if __name__ == '__main__': + import sys + main(sys.argv[1:]) diff --git a/18-with-match/lispy/py3.9/lis_test.py b/18-with-match/lispy/py3.9/lis_test.py index 106ac24..03dc15c 100644 --- a/18-with-match/lispy/py3.9/lis_test.py +++ b/18-with-match/lispy/py3.9/lis_test.py @@ -1,8 +1,8 @@ -from typing import Any, Optional +from typing import Optional from pytest import mark, fixture -from lis import parse, evaluate, standard_env, Symbol, Environment, Expression +from lis import parse, evaluate, Expression, Environment, standard_env ############################################################# tests for parse @@ -73,10 +73,10 @@ def test_evaluate(source: str, expected: Optional[Expression]) -> None: def std_env() -> Environment: return standard_env() -# tests for each of the cases in evaluate +# tests for cases in evaluate def test_evaluate_variable() -> None: - env: Environment = dict(x=10) + env = Environment({'x': 10}) source = 'x' expected = 10 got = evaluate(parse(source), env) @@ -166,3 +166,15 @@ def test_invocation_user_procedure(std_env: Environment) -> None: """ got = evaluate(parse(source), std_env) assert got == 22 + + +def test_define_function(std_env: Environment) -> None: + source = '(define (max a b) (if (>= a b) a b))' + got = evaluate(parse(source), std_env) + assert got is None + max_fn = std_env['max'] + assert max_fn.parms == ['a', 'b'] + assert max_fn.body == [['if', ['>=', 'a', 'b'], 'a', 'b']] + assert max_fn.env is std_env + assert max_fn(1, 2) == 2 + assert max_fn(3, 2) == 3 diff --git a/18-with-match/mirror_gen.py b/18-with-match/mirror_gen.py index 6dfaf02..2758045 100644 --- a/18-with-match/mirror_gen.py +++ b/18-with-match/mirror_gen.py @@ -15,6 +15,9 @@ YKCOWREBBAJ >>> what 'JABBERWOCKY' + >>> print('back to normal') + back to normal + # end::MIRROR_GEN_DEMO_1[] diff --git a/22-dyn-attr-prop/doc_property.py b/22-dyn-attr-prop/doc_property.py index b1ba351..5b653ef 100644 --- a/22-dyn-attr-prop/doc_property.py +++ b/22-dyn-attr-prop/doc_property.py @@ -14,7 +14,7 @@ class Foo: @property def bar(self): - '''The bar attribute''' + """The bar attribute""" return self.__dict__['bar'] @bar.setter From ade9577a55f1337ee5ca2b85d40f8eafc161d648 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Mon, 20 Sep 2021 23:31:45 -0300 Subject: [PATCH 109/166] sync from Atlas --- 02-array-seq/lispy/py3.10/lis.py | 4 ++-- 02-array-seq/lispy/py3.9/lis.py | 4 ++-- 18-with-match/lispy/py3.10/examples_test.py | 1 - 18-with-match/lispy/py3.10/lis.py | 21 +++++++------------ 18-with-match/lispy/py3.9/lis.py | 4 ++-- .../primes/spinner_prime_async_nap.py | 15 ++++++------- 6 files changed, 21 insertions(+), 28 deletions(-) diff --git a/02-array-seq/lispy/py3.10/lis.py b/02-array-seq/lispy/py3.10/lis.py index cffb9a1..150fca7 100755 --- a/02-array-seq/lispy/py3.10/lis.py +++ b/02-array-seq/lispy/py3.10/lis.py @@ -122,7 +122,7 @@ def standard_env() -> Environment: # tag::REPL[] def repl(prompt: str = 'lis.py> ') -> NoReturn: "A prompt-read-eval-print loop." - global_env = standard_env() + global_env = Environment({}, standard_env()) while True: ast = parse(input(prompt)) val = evaluate(ast, global_env) @@ -200,7 +200,7 @@ def __call__(self, *args: Expression) -> Any: # <3> ################ command-line interface def run(source: str) -> Any: - global_env = standard_env() + global_env = Environment({}, standard_env()) tokens = tokenize(source) while tokens: exp = read_from_tokens(tokens) diff --git a/02-array-seq/lispy/py3.9/lis.py b/02-array-seq/lispy/py3.9/lis.py index 843d4cc..201b41e 100644 --- a/02-array-seq/lispy/py3.9/lis.py +++ b/02-array-seq/lispy/py3.9/lis.py @@ -117,7 +117,7 @@ def standard_env() -> Environment: def repl(prompt: str = 'lis.py> ') -> NoReturn: "A prompt-read-eval-print loop." - global_env = standard_env() + global_env = Environment({}, standard_env()) while True: ast = parse(input(prompt)) val = evaluate(ast, global_env) @@ -190,7 +190,7 @@ def __call__(self, *args: Expression) -> Any: ################ command-line interface def run(source: str) -> Any: - global_env = standard_env() + global_env = Environment({}, standard_env()) tokens = tokenize(source) while tokens: exp = read_from_tokens(tokens) diff --git a/18-with-match/lispy/py3.10/examples_test.py b/18-with-match/lispy/py3.10/examples_test.py index ba6bd10..2807596 100644 --- a/18-with-match/lispy/py3.10/examples_test.py +++ b/18-with-match/lispy/py3.10/examples_test.py @@ -48,7 +48,6 @@ # end::EVAL_NUMBER[] # tag::EVAL_SYMBOL[] ->>> from lis import standard_env >>> evaluate(parse('+'), standard_env()) >>> evaluate(parse('ni!'), standard_env()) diff --git a/18-with-match/lispy/py3.10/lis.py b/18-with-match/lispy/py3.10/lis.py index 0feeadc..30ad8a8 100755 --- a/18-with-match/lispy/py3.10/lis.py +++ b/18-with-match/lispy/py3.10/lis.py @@ -121,7 +121,7 @@ def standard_env() -> Environment: # tag::REPL[] def repl(prompt: str = 'lis.py> ') -> NoReturn: "A prompt-read-eval-print loop." - global_env = standard_env() + global_env = Environment({}, standard_env()) while True: ast = parse(input(prompt)) val = evaluate(ast, global_env) @@ -140,10 +140,7 @@ def lispstr(exp: object) -> str: ################ Evaluator # tag::EVALUATE[] -KEYWORDS = {'quote', 'if', 'lambda', 'define', 'set!'} - -def is_keyword(s: Any) -> bool: - return isinstance(s, Symbol) and s in KEYWORDS +KEYWORDS = ['quote', 'if', 'lambda', 'define', 'set!'] def evaluate(exp: Expression, env: Environment) -> Any: "Evaluate an expression in an environment." @@ -161,17 +158,13 @@ def evaluate(exp: Expression, env: Environment) -> Any: return evaluate(alternative, env) case ['lambda', [*parms], *body] if body: return Procedure(parms, body, env) - case ['define', Symbol(var), value_exp]: - env[var] = evaluate(value_exp, env) + case ['define', Symbol(name), value_exp]: + env[name] = evaluate(value_exp, env) case ['define', [Symbol(name), *parms], *body] if body: env[name] = Procedure(parms, body, env) - case ['set!', Symbol(var), value_exp]: - env.change(var, evaluate(value_exp, env)) -<<<<<<< HEAD + case ['set!', Symbol(name), value_exp]: + env.change(name, evaluate(value_exp, env)) case [func_exp, *args] if func_exp not in KEYWORDS: -======= - case [func_exp, *args] if not is_keyword(func_exp): ->>>>>>> 3ecfb212c6273122797c76876d6b373b2cb94fa6 proc = evaluate(func_exp, env) values = [evaluate(arg, env) for arg in args] return proc(*values) @@ -202,7 +195,7 @@ def __call__(self, *args: Expression) -> Any: # <3> ################ command-line interface def run(source: str) -> Any: - global_env = standard_env() + global_env = Environment({}, standard_env()) tokens = tokenize(source) while tokens: exp = read_from_tokens(tokens) diff --git a/18-with-match/lispy/py3.9/lis.py b/18-with-match/lispy/py3.9/lis.py index e8881cd..6ab8a19 100644 --- a/18-with-match/lispy/py3.9/lis.py +++ b/18-with-match/lispy/py3.9/lis.py @@ -117,7 +117,7 @@ def standard_env() -> Environment: def repl(prompt: str = 'lis.py> ') -> NoReturn: "A prompt-read-eval-print loop." - global_env = standard_env() + global_env = Environment({}, standard_env()) while True: ast = parse(input(prompt)) val = evaluate(ast, global_env) @@ -193,7 +193,7 @@ def __call__(self, *args: Expression) -> Any: ################ command-line interface def run(source: str) -> Any: - global_env = standard_env() + global_env = Environment({}, standard_env()) tokens = tokenize(source) while tokens: exp = read_from_tokens(tokens) diff --git a/19-concurrency/primes/spinner_prime_async_nap.py b/19-concurrency/primes/spinner_prime_async_nap.py index 4d6fa47..b6e6518 100644 --- a/19-concurrency/primes/spinner_prime_async_nap.py +++ b/19-concurrency/primes/spinner_prime_async_nap.py @@ -7,6 +7,7 @@ import asyncio import itertools import math +import functools # tag::PRIME_NAP[] async def is_prime(n): @@ -21,8 +22,8 @@ async def is_prime(n): for i in range(3, root + 1, 2): if n % i == 0: return False - if i % 100_000 == 1: # <2> - await asyncio.sleep(0) + if i % 100_000 == 1: + await asyncio.sleep(0) # <1> return True # end::PRIME_NAP[] @@ -39,13 +40,13 @@ async def spin(msg: str) -> None: print(f'\r{blanks}\r', end='') async def check(n: int) -> int: - return await is_prime(n) # <4> + return await is_prime(n) async def supervisor(n: int) -> int: - spinner = asyncio.create_task(spin('thinking!')) # <1> - print('spinner object:', spinner) # <2> - result = await check(n) # <3> - spinner.cancel() # <5> + spinner = asyncio.create_task(spin('thinking!')) + print('spinner object:', spinner) + result = await check(n) + spinner.cancel() return result def main() -> None: From 2c1230579db99738a5e5e6802063bda585f6476d Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Tue, 21 Sep 2021 18:03:34 -0300 Subject: [PATCH 110/166] ch19: fixed race condition in procs.py --- 19-concurrency/primes/procs.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/19-concurrency/primes/procs.py b/19-concurrency/primes/procs.py index fc84049..8624958 100644 --- a/19-concurrency/primes/procs.py +++ b/19-concurrency/primes/procs.py @@ -30,6 +30,7 @@ def check(n: int) -> PrimeResult: # <6> def worker(jobs: JobQueue, results: ResultQueue) -> None: # <7> while n := jobs.get(): # <8> results.put(check(n)) # <9> + results.put(PrimeResult(0, False, 0.0)) # <10> # end::PRIMES_PROC_TOP[] # tag::PRIMES_PROC_MAIN[] @@ -53,15 +54,19 @@ def main() -> None: proc.start() # <5> jobs.put(0) # <6> - while True: - n, prime, elapsed = results.get() # <7> - label = 'P' if prime else ' ' - print(f'{n:16} {label} {elapsed:9.6f}s') # <8> - if jobs.empty(): # <9> - break + workers_done = 0 + checked = 0 + while workers_done < workers: # <7> + n, prime, elapsed = results.get() # <8> + if n == 0: + workers_done += 1 # <9> + else: + checked += 1 + label = 'P' if prime else ' ' + print(f'{n:16} {label} {elapsed:9.6f}s') # <10> elapsed = perf_counter() - t0 - print(f'Total time: {elapsed:.2f}s') + print(f'{checked} checks in {elapsed:.2f}s') # <11> if __name__ == '__main__': main() From 4f4f759229cca84b10170809607849f463891358 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Tue, 21 Sep 2021 23:31:27 -0300 Subject: [PATCH 111/166] sync from Atlas --- 18-with-match/lispy/py3.10/lis.py | 2 +- 19-concurrency/primes/procs.py | 54 +++++++++++++------------ 19-concurrency/primes/threads.py | 66 +++++++++++++++++-------------- 3 files changed, 67 insertions(+), 55 deletions(-) diff --git a/18-with-match/lispy/py3.10/lis.py b/18-with-match/lispy/py3.10/lis.py index 30ad8a8..0faf6d2 100755 --- a/18-with-match/lispy/py3.10/lis.py +++ b/18-with-match/lispy/py3.10/lis.py @@ -69,7 +69,7 @@ def change(self, key: Symbol, value: object) -> None: "Find where key is defined and change the value there." for map in self.maps: if key in map: - map[key] = value + map[key] = value # type: ignore[index] return raise KeyError(key) # end::ENV_CLASS[] diff --git a/19-concurrency/primes/procs.py b/19-concurrency/primes/procs.py index 8624958..09f75e3 100644 --- a/19-concurrency/primes/procs.py +++ b/19-concurrency/primes/procs.py @@ -33,40 +33,44 @@ def worker(jobs: JobQueue, results: ResultQueue) -> None: # <7> results.put(PrimeResult(0, False, 0.0)) # <10> # end::PRIMES_PROC_TOP[] -# tag::PRIMES_PROC_MAIN[] -def main() -> None: - if len(sys.argv) < 2: # <1> - workers = cpu_count() - else: - workers = int(sys.argv[1]) - - print(f'Checking {len(NUMBERS)} numbers with {workers} processes:') - - jobs: JobQueue = SimpleQueue() # <2> - results: ResultQueue = SimpleQueue() - t0 = perf_counter() - - for n in NUMBERS: # <3> - jobs.put(n) - +# tag::PRIMES_PROC_MIDDLE[] +def start_jobs(workers: int, jobs: JobQueue, results: ResultQueue) -> None: + for n in NUMBERS: + jobs.put(n) # <1> for _ in range(workers): - proc = Process(target=worker, args=(jobs, results)) # <4> - proc.start() # <5> - jobs.put(0) # <6> + proc = Process(target=worker, args=(jobs, results)) # <2> + proc.start() # <3> + jobs.put(0) # <4> - workers_done = 0 +def report(workers: int, results: ResultQueue) -> int: checked = 0 - while workers_done < workers: # <7> - n, prime, elapsed = results.get() # <8> + workers_done = 0 + while workers_done < workers: + n, prime, elapsed = results.get() if n == 0: - workers_done += 1 # <9> + workers_done += 1 else: checked += 1 label = 'P' if prime else ' ' - print(f'{n:16} {label} {elapsed:9.6f}s') # <10> + print(f'{n:16} {label} {elapsed:9.6f}s') + return checked +# end::PRIMES_PROC_MIDDLE[] +# tag::PRIMES_PROC_MAIN[] +def main() -> None: + if len(sys.argv) < 2: + workers = cpu_count() + else: + workers = int(sys.argv[1]) + + print(f'Checking {len(NUMBERS)} numbers with {workers} processes:') + t0 = perf_counter() + jobs: JobQueue = SimpleQueue() + results: ResultQueue = SimpleQueue() + start_jobs(workers, jobs, results) + checked = report(workers, results) elapsed = perf_counter() - t0 - print(f'{checked} checks in {elapsed:.2f}s') # <11> + print(f'{checked} checks in {elapsed:.2f}s') if __name__ == '__main__': main() diff --git a/19-concurrency/primes/threads.py b/19-concurrency/primes/threads.py index 0161dd7..ea00bbe 100644 --- a/19-concurrency/primes/threads.py +++ b/19-concurrency/primes/threads.py @@ -1,8 +1,8 @@ #!/usr/bin/env python3 """ -threads.py: shows that Python threads are slower than -sequential code for CPU-intensive work. +threads.py: shows that Python threads are slower +than sequential code for CPU-intensive work. """ import os @@ -14,52 +14,60 @@ from primes import is_prime, NUMBERS -class PrimeResult(NamedTuple): # <3> +class PrimeResult(NamedTuple): n: int prime: bool elapsed: float -JobQueue = SimpleQueue[int] -ResultQueue = SimpleQueue[PrimeResult] +JobQueue = SimpleQueue[int] # <4> +ResultQueue = SimpleQueue[PrimeResult] # <5> -def check(n: int) -> PrimeResult: +def check(n: int) -> PrimeResult: # <6> t0 = perf_counter() res = is_prime(n) return PrimeResult(n, res, perf_counter() - t0) -def worker(jobs: JobQueue, results: ResultQueue) -> None: - while n := jobs.get(): - results.put(check(n)) - -def main() -> None: - if len(sys.argv) < 2: # <1> - workers = os.cpu_count() or 1 # make mypy happy - else: - workers = int(sys.argv[1]) - - print(f'Checking {len(NUMBERS)} numbers with {workers} threads:') - - jobs: JobQueue = SimpleQueue() # <2> - results: ResultQueue = SimpleQueue() - t0 = perf_counter() +def worker(jobs: JobQueue, results: ResultQueue) -> None: # <7> + while n := jobs.get(): # <8> + results.put(check(n)) # <9> + results.put(PrimeResult(0, False, 0.0)) +def start_jobs(workers: int, jobs: JobQueue, results: ResultQueue) -> None: for n in NUMBERS: # <3> jobs.put(n) - for _ in range(workers): proc = Thread(target=worker, args=(jobs, results)) # <4> proc.start() # <5> jobs.put(0) # <6> - while True: - n, prime, elapsed = results.get() # <7> - label = 'P' if prime else ' ' - print(f'{n:16} {label} {elapsed:9.6f}s') - if jobs.empty(): # <8> - break +def report(workers: int, results: ResultQueue) -> int: + checked = 0 + workers_done = 0 + while workers_done < workers: + n, prime, elapsed = results.get() + if n == 0: + workers_done += 1 + else: + checked += 1 + label = 'P' if prime else ' ' + print(f'{n:16} {label} {elapsed:9.6f}s') + return checked + +def main() -> None: + if len(sys.argv) < 2: + workers = os.cpu_count() + else: + workers = int(sys.argv[1]) + print(f'Checking {len(NUMBERS)} numbers with {workers} threads:') + t0 = perf_counter() + jobs: JobQueue = SimpleQueue() + results: ResultQueue = SimpleQueue() + start_jobs(workers, jobs, results) + checked = report(workers, results) elapsed = perf_counter() - t0 - print(f'Total time: {elapsed:.2f}s') + print(f'{checked} checks in {elapsed:.2f}s') if __name__ == '__main__': main() + From 4fad21e6b0434d814fab582440a44add48456f04 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Thu, 23 Sep 2021 03:07:15 -0300 Subject: [PATCH 112/166] added README.md to 19-concurrency/primes/ --- 19-concurrency/primes/README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 19-concurrency/primes/README.md diff --git a/19-concurrency/primes/README.md b/19-concurrency/primes/README.md new file mode 100644 index 0000000..772592a --- /dev/null +++ b/19-concurrency/primes/README.md @@ -0,0 +1,16 @@ +# Race condition in orignal procs.py + +Thanks to reader Michael Albert who noticed the code I published during the Early Release had a race condition in `proc.py`. + +If you are curious, +[this diff](https://github.com/fluentpython/example-code-2e/commit/2c1230579db99738a5e5e6802063bda585f6476d) +shows the bug and how I fixed it—but note that I later refactored +the example to delegate parts of `main` to the `start_jobs` and `report` functions. + +The problem was that I ended the `while` loop that retrieved the results when the `jobs` queue was empty. +However, it was possible that the queue was empty but there were still processes working. +If that happened, one or more results would not be reported. +I did not notice the problem when I tested my original code, +but Albert showed that adding a `sleep(1)` call before the `if jobs.empty()` line made the bug occur frequently. +I adopted one of his solutions: have the `worker` function send back a `PrimeResult` with `n = 0` as a sentinel, +to let the main loop know that the process had completed, ending the loop when all processes were done. \ No newline at end of file From 4002c57728bd5b4874b320ca91284697f708743c Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Mon, 27 Sep 2021 15:05:01 -0300 Subject: [PATCH 113/166] added README.adoc --- 20-futures/getflags/README.adoc | 72 +++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 20-futures/getflags/README.adoc diff --git a/20-futures/getflags/README.adoc b/20-futures/getflags/README.adoc new file mode 100644 index 0000000..14ecec4 --- /dev/null +++ b/20-futures/getflags/README.adoc @@ -0,0 +1,72 @@ += Running the flags2* examples + +== Setting up a test server + +If you don't already have a local HTTP server for testing, +here are the steps to experiment using only Python ≥ 3.9—no external libraries: + +. Clone or download the https://github.com/fluentpython/example-code-2e[_Fluent Python 2e_ code repository]. +. Open your shell and go to the _20-futures/getflags/_ directory of your local copy of the repository. +. Unzip the _flags.zip_ file, creating a _flags_ directory at _20-futures/getflags/flags/_. +. Open a second shell, go to the _20-futures/getflags/_ directory and run `python3 -m http.server`. This will start a `ThreadingHTTPServer` listening to port 8000, serving the local files. If you open the URL http://localhost:8000/flags/[http://localhost:8000/flags/] with your browser, you'll see a long list of directories named with two-letter country codes from `ad/` to `zw/`. +. Now you can go back to the first shell and run the _flags2*.py_ examples with the default `--server LOCAL` option. +. To test with the `--server DELAY` option, go to _20-futures/getflags/_ and run `python3 slow_server.py 8001`. This will add a .5s delay before each response. +. To test with the `--server ERROR` option, go to _20-futures/getflags/_ and run `python3 slow_server.py 8002 --error-rate .25`. Each request will have a 25% probability of getting a https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/418[418 I'm a teapot] response, and all responses will be delayed .5s. + +I wrote _slow_server.py_ reusing code from Python's +https://github.com/python/cpython/blob/917eca700aa341f8544ace43b75d41b477e98b72/Lib/http/server.py[`http.server`] standard library module, +which "is not recommended for production"—according to the +https://docs.python.org/3/library/http.server.html[documentation]. +To set up a more reliable testing environment, I recommend configuring +https://www.nginx.com/[NGINX] and +https://github.com/shopify/toxiproxy[Toxiproxy] with equivalent parameters. + +== Running a flags2* example + +The `flags2*` examples provide a command-line interface. +All three scripts accept the same options, +and you can see them by running any of the scripts with the `-h` option. +<> shows the help text. + +[[flags2_help_demo]] +.Help screen for the scripts in the flags2 series +==== +[source, text] +---- +$ python3 flags2_threadpool.py -h +usage: flags2_threadpool.py [-h] [-a] [-e] [-l N] [-m CONCURRENT] [-s LABEL] + [-v] + [CC [CC ...]] + +Download flags for country codes. Default: top 20 countries by population. + +positional arguments: + CC country code or 1st letter (eg. B for BA...BZ) + +optional arguments: + -h, --help show this help message and exit + -a, --all get all available flags (AD to ZW) + -e, --every get flags for every possible code (AA...ZZ) + -l N, --limit N limit to N first codes + -m CONCURRENT, --max_req CONCURRENT + maximum concurrent requests (default=30) + -s LABEL, --server LABEL + Server to hit; one of DELAY, ERROR, LOCAL, REMOTE + (default=LOCAL) + -v, --verbose output detailed progress info + +---- +==== + +All arguments are optional. The most important arguments are discussed next. + +One option you can't ignore is `-s/--server`: it lets you choose which HTTP server and base URL will be used in the test. You can pass one of four strings to determine where the script will look for the flags (the strings are case insensitive): + +`LOCAL`:: Use `http://localhost:8000/flags`; this is the default. You should configure a local HTTP server to answer at port 8000. See <> for instructions. + +`REMOTE`:: Use `http://fluentpython.com/data/flags`; that is a public website owned by me, hosted on a shared server. Please do not pound it with too many concurrent requests. The `fluentpython.com` domain is handled by the http://www.cloudflare.com/[Cloudflare] CDN (Content Delivery Network) so you may notice that the first downloads are slower, but they get faster when the CDN cache warms up.footnote:[Before configuring Cloudflare, I got HTTP 503 errors--Service Temporarily Unavailable--when testing the scripts with a few dozen concurrent requests on my inexpensive shared host account. Now those errors are gone.] + +`DELAY`:: Use `http://localhost:8001/flags`; a server delaying HTTP responses should be listening to port 8001. I wrote _slow_server.py_ to make it easier to experiment. You'll find it in the _20-futures/getflags/_ directory of the https://github.com/fluentpython/example-code-2e[_Fluent Python 2e_ code repository]. See <> for instructions. + +`ERROR`:: Use `http://localhost:8002/flags`; a server introducing HTTP errors and delaying responses should be installed at port 8002. Running _slow_server.py_ is an easy way to do it. See <>. + From f7bf5b0d2ad4fb0f5156bc2bb7ca43991fc6719a Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Mon, 27 Sep 2021 15:06:49 -0300 Subject: [PATCH 114/166] added README.adoc --- 20-futures/getflags/README.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/20-futures/getflags/README.adoc b/20-futures/getflags/README.adoc index 14ecec4..38cf3df 100644 --- a/20-futures/getflags/README.adoc +++ b/20-futures/getflags/README.adoc @@ -1,4 +1,4 @@ -= Running the flags2* examples += Experimenting with the `flags2*` examples == Setting up a test server @@ -21,7 +21,7 @@ To set up a more reliable testing environment, I recommend configuring https://www.nginx.com/[NGINX] and https://github.com/shopify/toxiproxy[Toxiproxy] with equivalent parameters. -== Running a flags2* example +== Running a `flags2*` script The `flags2*` examples provide a command-line interface. All three scripts accept the same options, From 1e5940056e21b350f2601e5df3f0b24b829d351c Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Mon, 27 Sep 2021 19:35:38 -0300 Subject: [PATCH 115/166] ch20: README.adoc --- 20-futures/getflags/README.adoc | 38 +++++++++++++++++++++++++++------ 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/20-futures/getflags/README.adoc b/20-futures/getflags/README.adoc index 38cf3df..ed0b5e9 100644 --- a/20-futures/getflags/README.adoc +++ b/20-futures/getflags/README.adoc @@ -1,25 +1,51 @@ = Experimenting with the `flags2*` examples +== Install SSL Certificates (for MacOS) + +On Macos, depending on how in installed Python you may need to manually run a command +after Python's installer finishes, to install SSL certificates for HTTPS connections. + +Using the Finder, open the `Python 3.X` folder inside `/Applications` folder +and double-click "Install Certificates" or "Install Certificates.command". + +Using the terminal, you can type for example: + +[source, text] +---- +$ open /Applications/Python 3.10/"Install Certificates.command" +---- + + == Setting up a test server If you don't already have a local HTTP server for testing, -here are the steps to experiment using only Python ≥ 3.9—no external libraries: +here are the steps to experiment using only Python ≥ 3.9: -. Clone or download the https://github.com/fluentpython/example-code-2e[_Fluent Python 2e_ code repository]. -. Open your shell and go to the _20-futures/getflags/_ directory of your local copy of the repository. +. Clone or download the https://github.com/fluentpython/example-code-2e[_Fluent Python 2e_ code repository] (this repo!). +. Open your shell and go to the _20-futures/getflags/_ directory of your local copy of the repository (this directory!) . Unzip the _flags.zip_ file, creating a _flags_ directory at _20-futures/getflags/flags/_. . Open a second shell, go to the _20-futures/getflags/_ directory and run `python3 -m http.server`. This will start a `ThreadingHTTPServer` listening to port 8000, serving the local files. If you open the URL http://localhost:8000/flags/[http://localhost:8000/flags/] with your browser, you'll see a long list of directories named with two-letter country codes from `ad/` to `zw/`. . Now you can go back to the first shell and run the _flags2*.py_ examples with the default `--server LOCAL` option. -. To test with the `--server DELAY` option, go to _20-futures/getflags/_ and run `python3 slow_server.py 8001`. This will add a .5s delay before each response. -. To test with the `--server ERROR` option, go to _20-futures/getflags/_ and run `python3 slow_server.py 8002 --error-rate .25`. Each request will have a 25% probability of getting a https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/418[418 I'm a teapot] response, and all responses will be delayed .5s. +. To test with the `--server DELAY` option, go to _20-futures/getflags/_ and run `python3 slow_server.py`. This bind to port 8001 by default. It will add a .5s delay before each response. +. To test with the `--server ERROR` option, go to _20-futures/getflags/_ and run `python3 slow_server.py 8002 --error-rate .25`. +Each request will have a 25% probability of getting a +https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/418[418 I'm a teapot] response, +and all responses will be delayed .5s. I wrote _slow_server.py_ reusing code from Python's https://github.com/python/cpython/blob/917eca700aa341f8544ace43b75d41b477e98b72/Lib/http/server.py[`http.server`] standard library module, which "is not recommended for production"—according to the https://docs.python.org/3/library/http.server.html[documentation]. -To set up a more reliable testing environment, I recommend configuring + +[NOTE] +==== +This is a simple testing environment that does nor require any external libraries or +tools—apart from the libraries used in the `flags2*` scripts themselves, as discussed in the book. + +For a more robust testing environment, I recommend configuring https://www.nginx.com/[NGINX] and https://github.com/shopify/toxiproxy[Toxiproxy] with equivalent parameters. +==== == Running a `flags2*` script From f45806361f7f7d958c6a61dfb62c04bfb39e8c60 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Mon, 27 Sep 2021 19:46:25 -0300 Subject: [PATCH 116/166] ch20: README.adoc --- 20-futures/getflags/README.adoc | 55 +++++++++++++++++++++++---------- 1 file changed, 39 insertions(+), 16 deletions(-) diff --git a/20-futures/getflags/README.adoc b/20-futures/getflags/README.adoc index ed0b5e9..b744c15 100644 --- a/20-futures/getflags/README.adoc +++ b/20-futures/getflags/README.adoc @@ -1,25 +1,25 @@ = Experimenting with the `flags2*` examples -== Install SSL Certificates (for MacOS) +The `flags2*` examples enhance the `flags*` examples with error handling and reporting. +Therefore, we need a server that generates errors and delays to test them. -On Macos, depending on how in installed Python you may need to manually run a command -after Python's installer finishes, to install SSL certificates for HTTPS connections. +The main reason for these instructions is to document how to configure one such server +in your machine, and how to run the clients to use it. -Using the Finder, open the `Python 3.X` folder inside `/Applications` folder -and double-click "Install Certificates" or "Install Certificates.command". +The other reason is to alert of an installation step that MacOS users sometimes overlook. -Using the terminal, you can type for example: - -[source, text] ----- -$ open /Applications/Python 3.10/"Install Certificates.command" ----- +Contents: +* <> +* <> +* <> +[[server_setup]] == Setting up a test server If you don't already have a local HTTP server for testing, -here are the steps to experiment using only Python ≥ 3.9: +here are the steps to experiment with the `flags2*` examples +using just the Python ≥ 3.9 distribution and . Clone or download the https://github.com/fluentpython/example-code-2e[_Fluent Python 2e_ code repository] (this repo!). . Open your shell and go to the _20-futures/getflags/_ directory of your local copy of the repository (this directory!) @@ -39,7 +39,7 @@ https://docs.python.org/3/library/http.server.html[documentation]. [NOTE] ==== -This is a simple testing environment that does nor require any external libraries or +This is a simple testing environment that does not require any external libraries or tools—apart from the libraries used in the `flags2*` scripts themselves, as discussed in the book. For a more robust testing environment, I recommend configuring @@ -47,6 +47,7 @@ https://www.nginx.com/[NGINX] and https://github.com/shopify/toxiproxy[Toxiproxy] with equivalent parameters. ==== +[[client_setup]] == Running a `flags2*` script The `flags2*` examples provide a command-line interface. @@ -86,13 +87,35 @@ optional arguments: All arguments are optional. The most important arguments are discussed next. -One option you can't ignore is `-s/--server`: it lets you choose which HTTP server and base URL will be used in the test. You can pass one of four strings to determine where the script will look for the flags (the strings are case insensitive): +One option you can't ignore is `-s/--server`: it lets you choose which HTTP server and base URL will be used in the test. +You can pass one of four strings to determine where the script will look for the flags (the strings are case insensitive): -`LOCAL`:: Use `http://localhost:8000/flags`; this is the default. You should configure a local HTTP server to answer at port 8000. See <> for instructions. +`LOCAL`:: Use `http://localhost:8000/flags`; this is the default. +You should configure a local HTTP server to answer at port 8000. See <> for instructions. -`REMOTE`:: Use `http://fluentpython.com/data/flags`; that is a public website owned by me, hosted on a shared server. Please do not pound it with too many concurrent requests. The `fluentpython.com` domain is handled by the http://www.cloudflare.com/[Cloudflare] CDN (Content Delivery Network) so you may notice that the first downloads are slower, but they get faster when the CDN cache warms up.footnote:[Before configuring Cloudflare, I got HTTP 503 errors--Service Temporarily Unavailable--when testing the scripts with a few dozen concurrent requests on my inexpensive shared host account. Now those errors are gone.] +`REMOTE`:: Use `http://fluentpython.com/data/flags`; that is a public website owned by me, hosted on a shared server. +Please do not pound it with too many concurrent requests. +The `fluentpython.com` domain is handled by the http://www.cloudflare.com/[Cloudflare] CDN (Content Delivery Network) +so you may notice that the first downloads are slower, but they get faster when the CDN cache warms +up.footnote:[Before configuring Cloudflare, I got HTTP 503 errors--Service Temporarily Unavailable--when +testing the scripts with a few dozen concurrent requests on my inexpensive shared host account. Now those errors are gone.] `DELAY`:: Use `http://localhost:8001/flags`; a server delaying HTTP responses should be listening to port 8001. I wrote _slow_server.py_ to make it easier to experiment. You'll find it in the _20-futures/getflags/_ directory of the https://github.com/fluentpython/example-code-2e[_Fluent Python 2e_ code repository]. See <> for instructions. `ERROR`:: Use `http://localhost:8002/flags`; a server introducing HTTP errors and delaying responses should be installed at port 8002. Running _slow_server.py_ is an easy way to do it. See <>. +[[macos_certificates]] +== Install SSL Certificates (for MacOS) + +On Macos, depending on how in installed Python you may need to manually run a command +after Python's installer finishes, to install SSL certificates for HTTPS connections. + +Using the Finder, open the `Python 3.X` folder inside `/Applications` folder +and double-click "Install Certificates" or "Install Certificates.command". + +Using the terminal, you can type for example: + +[source, text] +---- +$ open /Applications/Python 3.10/"Install Certificates.command" +---- From 69f326dd3dfef93565a185ee9daa9cbf9a69d09a Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Mon, 27 Sep 2021 19:55:48 -0300 Subject: [PATCH 117/166] ch20: README.adoc --- 20-futures/getflags/README.adoc | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/20-futures/getflags/README.adoc b/20-futures/getflags/README.adoc index b744c15..cfbb255 100644 --- a/20-futures/getflags/README.adoc +++ b/20-futures/getflags/README.adoc @@ -1,11 +1,10 @@ = Experimenting with the `flags2*` examples The `flags2*` examples enhance the `flags*` examples with error handling and reporting. -Therefore, we need a server that generates errors and delays to test them. +Therefore, we need a server that generates errors and delays to experiment with them. The main reason for these instructions is to document how to configure one such server -in your machine, and how to run the clients to use it. - +in your machine, and how to tell the `flags2*` clients to access it. The other reason is to alert of an installation step that MacOS users sometimes overlook. Contents: @@ -19,14 +18,14 @@ Contents: If you don't already have a local HTTP server for testing, here are the steps to experiment with the `flags2*` examples -using just the Python ≥ 3.9 distribution and +using just the Python ≥ 3.9 distribution: . Clone or download the https://github.com/fluentpython/example-code-2e[_Fluent Python 2e_ code repository] (this repo!). . Open your shell and go to the _20-futures/getflags/_ directory of your local copy of the repository (this directory!) . Unzip the _flags.zip_ file, creating a _flags_ directory at _20-futures/getflags/flags/_. . Open a second shell, go to the _20-futures/getflags/_ directory and run `python3 -m http.server`. This will start a `ThreadingHTTPServer` listening to port 8000, serving the local files. If you open the URL http://localhost:8000/flags/[http://localhost:8000/flags/] with your browser, you'll see a long list of directories named with two-letter country codes from `ad/` to `zw/`. . Now you can go back to the first shell and run the _flags2*.py_ examples with the default `--server LOCAL` option. -. To test with the `--server DELAY` option, go to _20-futures/getflags/_ and run `python3 slow_server.py`. This bind to port 8001 by default. It will add a .5s delay before each response. +. To test with the `--server DELAY` option, go to _20-futures/getflags/_ and run `python3 slow_server.py`. This binds to port 8001 by default. It will add a .5s delay before each response. . To test with the `--server ERROR` option, go to _20-futures/getflags/_ and run `python3 slow_server.py 8002 --error-rate .25`. Each request will have a 25% probability of getting a https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/418[418 I'm a teapot] response, @@ -52,8 +51,7 @@ https://github.com/shopify/toxiproxy[Toxiproxy] with equivalent parameters. The `flags2*` examples provide a command-line interface. All three scripts accept the same options, -and you can see them by running any of the scripts with the `-h` option. -<> shows the help text. +and you can see them by running any of the scripts with the `-h` option: [[flags2_help_demo]] .Help screen for the scripts in the flags2 series @@ -91,18 +89,21 @@ One option you can't ignore is `-s/--server`: it lets you choose which HTTP serv You can pass one of four strings to determine where the script will look for the flags (the strings are case insensitive): `LOCAL`:: Use `http://localhost:8000/flags`; this is the default. -You should configure a local HTTP server to answer at port 8000. See <> for instructions. +You should configure a local HTTP server to answer at port 8000. See <> for instructions. +Feel free to hit this as hard as you can. It's your machine! `REMOTE`:: Use `http://fluentpython.com/data/flags`; that is a public website owned by me, hosted on a shared server. -Please do not pound it with too many concurrent requests. +Please do not hit it with too many concurrent requests. The `fluentpython.com` domain is handled by the http://www.cloudflare.com/[Cloudflare] CDN (Content Delivery Network) so you may notice that the first downloads are slower, but they get faster when the CDN cache warms up.footnote:[Before configuring Cloudflare, I got HTTP 503 errors--Service Temporarily Unavailable--when testing the scripts with a few dozen concurrent requests on my inexpensive shared host account. Now those errors are gone.] -`DELAY`:: Use `http://localhost:8001/flags`; a server delaying HTTP responses should be listening to port 8001. I wrote _slow_server.py_ to make it easier to experiment. You'll find it in the _20-futures/getflags/_ directory of the https://github.com/fluentpython/example-code-2e[_Fluent Python 2e_ code repository]. See <> for instructions. +`DELAY`:: Use `http://localhost:8001/flags`; a server delaying HTTP responses should be listening to port 8001. +I wrote _slow_server.py_ to make it easier to experiment. See <> for instructions. -`ERROR`:: Use `http://localhost:8002/flags`; a server introducing HTTP errors and delaying responses should be installed at port 8002. Running _slow_server.py_ is an easy way to do it. See <>. +`ERROR`:: Use `http://localhost:8002/flags`; a server introducing HTTP errors and delaying responses should be installed at port 8002. +Running _slow_server.py_ is an easy way to do it. See <>. [[macos_certificates]] == Install SSL Certificates (for MacOS) From 7985fda09f4c2f644540a258d141be92161c5eaf Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Mon, 27 Sep 2021 19:58:16 -0300 Subject: [PATCH 118/166] ch20: README.adoc --- 20-futures/getflags/README.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/20-futures/getflags/README.adoc b/20-futures/getflags/README.adoc index cfbb255..b4b3cc2 100644 --- a/20-futures/getflags/README.adoc +++ b/20-futures/getflags/README.adoc @@ -108,8 +108,8 @@ Running _slow_server.py_ is an easy way to do it. See <>. [[macos_certificates]] == Install SSL Certificates (for MacOS) -On Macos, depending on how in installed Python you may need to manually run a command -after Python's installer finishes, to install SSL certificates for HTTPS connections. +On Macos, depending on how you installed Python you may need to manually run a command +after Python's installer finishes, to install the SSL certificates Python uses to make HTTPS connections. Using the Finder, open the `Python 3.X` folder inside `/Applications` folder and double-click "Install Certificates" or "Install Certificates.command". From 4f1392d21c87e2519bd212fef8d9cbe280f7533a Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Sat, 2 Oct 2021 17:12:42 -0300 Subject: [PATCH 119/166] sequential, threaded & async HTTP clients using HTTPX --- 20-futures/getflags/README.adoc | 6 +- 20-futures/getflags/flags.py | 21 ++-- 20-futures/getflags/flags2_asyncio.py | 89 +++++++------- .../getflags/flags2_asyncio_executor.py | 115 +++++++++--------- 20-futures/getflags/flags2_common.py | 30 ++--- 20-futures/getflags/flags2_sequential.py | 59 ++++----- 20-futures/getflags/flags2_threadpool.py | 51 ++++---- 20-futures/getflags/flags3_asyncio.py | 25 ++-- 20-futures/getflags/flags_asyncio.py | 26 ++-- 20-futures/getflags/requirements.txt | 19 ++- 20-futures/getflags/slow_server.py | 20 ++- 20-futures/getflags/tree.py | 38 ++++++ 12 files changed, 273 insertions(+), 226 deletions(-) create mode 100644 20-futures/getflags/tree.py diff --git a/20-futures/getflags/README.adoc b/20-futures/getflags/README.adoc index b4b3cc2..a649994 100644 --- a/20-futures/getflags/README.adoc +++ b/20-futures/getflags/README.adoc @@ -14,7 +14,7 @@ Contents: * <> [[server_setup]] -== Setting up a test server +== Setting up test servers If you don't already have a local HTTP server for testing, here are the steps to experiment with the `flags2*` examples @@ -25,7 +25,7 @@ using just the Python ≥ 3.9 distribution: . Unzip the _flags.zip_ file, creating a _flags_ directory at _20-futures/getflags/flags/_. . Open a second shell, go to the _20-futures/getflags/_ directory and run `python3 -m http.server`. This will start a `ThreadingHTTPServer` listening to port 8000, serving the local files. If you open the URL http://localhost:8000/flags/[http://localhost:8000/flags/] with your browser, you'll see a long list of directories named with two-letter country codes from `ad/` to `zw/`. . Now you can go back to the first shell and run the _flags2*.py_ examples with the default `--server LOCAL` option. -. To test with the `--server DELAY` option, go to _20-futures/getflags/_ and run `python3 slow_server.py`. This binds to port 8001 by default. It will add a .5s delay before each response. +. To test with the `--server DELAY` option, go to _20-futures/getflags/_ and run `python3 slow_server.py`. This binds to port 8001 by default. It will add a random delay of .5s to 5s before each response. . To test with the `--server ERROR` option, go to _20-futures/getflags/_ and run `python3 slow_server.py 8002 --error-rate .25`. Each request will have a 25% probability of getting a https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/418[418 I'm a teapot] response, @@ -86,7 +86,7 @@ optional arguments: All arguments are optional. The most important arguments are discussed next. One option you can't ignore is `-s/--server`: it lets you choose which HTTP server and base URL will be used in the test. -You can pass one of four strings to determine where the script will look for the flags (the strings are case insensitive): +You can pass one of four labels to determine where the script will look for the flags (the labels are case-insensitive): `LOCAL`:: Use `http://localhost:8000/flags`; this is the default. You should configure a local HTTP server to answer at port 8000. See <> for instructions. diff --git a/20-futures/getflags/flags.py b/20-futures/getflags/flags.py index 3d6d36f..2311125 100755 --- a/20-futures/getflags/flags.py +++ b/20-futures/getflags/flags.py @@ -21,12 +21,12 @@ from pathlib import Path from typing import Callable -import requests # <1> +import httpx # <1> POP20_CC = ('CN IN US ID BR PK NG BD RU JP ' 'MX PH VN ET EG DE IR TR CD FR').split() # <2> -BASE_URL = 'http://fluentpython.com/data/flags' # <3> +BASE_URL = 'https://www.fluentpython.com/data/flags' # <3> DEST_DIR = Path('downloaded') # <4> def save_flag(img: bytes, filename: str) -> None: # <5> @@ -34,22 +34,25 @@ def save_flag(img: bytes, filename: str) -> None: # <5> def get_flag(cc: str) -> bytes: # <6> url = f'{BASE_URL}/{cc}/{cc}.gif'.lower() - resp = requests.get(url) + resp = httpx.get(url, timeout=6.1, # <7> + follow_redirects=True) # <8> + resp.raise_for_status() # <9> return resp.content -def download_many(cc_list: list[str]) -> int: # <7> - for cc in sorted(cc_list): # <8> +def download_many(cc_list: list[str]) -> int: # <10> + for cc in sorted(cc_list): # <11> image = get_flag(cc) save_flag(image, f'{cc}.gif') - print(cc, end=' ', flush=True) # <9> + print(cc, end=' ', flush=True) # <12> return len(cc_list) -def main(downloader: Callable[[list[str]], int]) -> None: # <10> - t0 = time.perf_counter() # <11> +def main(downloader: Callable[[list[str]], int]) -> None: # <13> + DEST_DIR.mkdir(exist_ok=True) # <14> + t0 = time.perf_counter() # <15> count = downloader(POP20_CC) elapsed = time.perf_counter() - t0 print(f'\n{count} downloads in {elapsed:.2f}s') if __name__ == '__main__': - main(download_many) # <12> + main(download_many) # <16> # end::FLAGS_PY[] diff --git a/20-futures/getflags/flags2_asyncio.py b/20-futures/getflags/flags2_asyncio.py index 88a18ed..a2bb6ff 100755 --- a/20-futures/getflags/flags2_asyncio.py +++ b/20-futures/getflags/flags2_asyncio.py @@ -8,65 +8,60 @@ # tag::FLAGS2_ASYNCIO_TOP[] import asyncio from collections import Counter +from http import HTTPStatus +from pathlib import Path -import aiohttp +import httpx import tqdm # type: ignore -from flags2_common import main, HTTPStatus, Result, save_flag +from flags2_common import main, DownloadStatus, save_flag # default set low to avoid errors from remote site, such as # 503 - Service Temporarily Unavailable DEFAULT_CONCUR_REQ = 5 MAX_CONCUR_REQ = 1000 - -class FetchError(Exception): # <1> - def __init__(self, country_code: str): - self.country_code = country_code - - -async def get_flag(session: aiohttp.ClientSession, # <2> +async def get_flag(session: httpx.AsyncClient, # <2> base_url: str, cc: str) -> bytes: url = f'{base_url}/{cc}/{cc}.gif'.lower() - async with session.get(url) as resp: - if resp.status == 200: - return await resp.read() - else: - resp.raise_for_status() # <3> - return bytes() + resp = await session.get(url, timeout=3.1, follow_redirects=True) # <3> + resp.raise_for_status() + return resp.content -async def download_one(session: aiohttp.ClientSession, +async def download_one(session: httpx.AsyncClient, cc: str, base_url: str, semaphore: asyncio.Semaphore, # <4> - verbose: bool) -> Result: + verbose: bool) -> DownloadStatus: try: async with semaphore: # <5> image = await get_flag(session, base_url, cc) - except aiohttp.ClientResponseError as exc: - if exc.status == 404: # <6> - status = HTTPStatus.not_found - msg = 'not found' + except httpx.HTTPStatusError as exc: # <4> + res = exc.response + if res.status_code == HTTPStatus.NOT_FOUND: + status = DownloadStatus.NOT_FOUND # <5> + msg = f'not found: {res.url}' else: - raise FetchError(cc) from exc # <7> + raise + else: - save_flag(image, f'{cc}.gif') - status = HTTPStatus.ok + await asyncio.to_thread(save_flag, image, f'{cc}.gif') + status = DownloadStatus.OK msg = 'OK' if verbose and msg: print(cc, msg) - return Result(status, cc) + return status # end::FLAGS2_ASYNCIO_TOP[] # tag::FLAGS2_ASYNCIO_START[] async def supervisor(cc_list: list[str], base_url: str, verbose: bool, - concur_req: int) -> Counter[HTTPStatus]: # <1> - counter: Counter[HTTPStatus] = Counter() + concur_req: int) -> Counter[DownloadStatus]: # <1> + counter: Counter[DownloadStatus] = Counter() semaphore = asyncio.Semaphore(concur_req) # <2> - async with aiohttp.ClientSession() as session: + async with httpx.AsyncClient() as session: to_do = [download_one(session, cc, base_url, semaphore, verbose) for cc in sorted(cc_list)] # <3> to_do_iter = asyncio.as_completed(to_do) # <4> @@ -74,25 +69,33 @@ async def supervisor(cc_list: list[str], to_do_iter = tqdm.tqdm(to_do_iter, total=len(cc_list)) # <5> for coro in to_do_iter: # <6> try: - res = await coro # <7> - except FetchError as exc: # <8> - country_code = exc.country_code # <9> - try: - error_msg = exc.__cause__.message # type: ignore # <10> - except AttributeError: - error_msg = 'Unknown cause' # <11> - if verbose and error_msg: - print(f'*** Error for {country_code}: {error_msg}') - status = HTTPStatus.error - else: - status = res.status - counter[status] += 1 # <12> - return counter # <13> + status = await coro # <7> + except httpx.HTTPStatusError as exc: # <8> + error_msg = 'HTTP error {resp.status_code} - {resp.reason_phrase}' + error_msg = error_msg.format(resp=exc.response) + error = exc + except httpx.RequestError as exc: # <9> + error_msg = f'{exc} {type(exc)}'.strip() + error = exc + except KeyboardInterrupt: # <10> + break + else: # <11> + error = None + + if error: + status = DownloadStatus.ERROR # <12> + if verbose: + url = str(error.request.url) # <13> + cc = Path(url).stem.upper() # <14> + print(f'{cc} error: {error_msg}') + counter[status] += 1 + + return counter def download_many(cc_list: list[str], base_url: str, verbose: bool, - concur_req: int) -> Counter[HTTPStatus]: + concur_req: int) -> Counter[DownloadStatus]: coro = supervisor(cc_list, base_url, verbose, concur_req) counts = asyncio.run(coro) # <14> diff --git a/20-futures/getflags/flags2_asyncio_executor.py b/20-futures/getflags/flags2_asyncio_executor.py index 5b75d59..7143b46 100755 --- a/20-futures/getflags/flags2_asyncio_executor.py +++ b/20-futures/getflags/flags2_asyncio_executor.py @@ -2,17 +2,19 @@ """Download flags of countries (with error handling). -asyncio async/await version using run_in_executor for save_flag. +asyncio async/await version """ - +# tag::FLAGS2_ASYNCIO_TOP[] import asyncio from collections import Counter +from http import HTTPStatus +from pathlib import Path -import aiohttp +import httpx import tqdm # type: ignore -from flags2_common import main, HTTPStatus, Result, save_flag +from flags2_common import main, DownloadStatus, save_flag # default set low to avoid errors from remote site, such as # 503 - Service Temporarily Unavailable @@ -20,90 +22,87 @@ MAX_CONCUR_REQ = 1000 -class FetchError(Exception): - def __init__(self, country_code: str): - self.country_code = country_code - - -async def get_flag(session: aiohttp.ClientSession, +async def get_flag(session: httpx.AsyncClient, # <2> base_url: str, cc: str) -> bytes: url = f'{base_url}/{cc}/{cc}.gif'.lower() - async with session.get(url) as resp: - if resp.status == 200: - return await resp.read() - else: - resp.raise_for_status() - return bytes() + resp = await session.get(url, timeout=3.1, follow_redirects=True) # <3> + resp.raise_for_status() + return resp.content -# tag::FLAGS2_ASYNCIO_EXECUTOR[] -async def download_one(session: aiohttp.ClientSession, + +async def download_one(session: httpx.AsyncClient, cc: str, base_url: str, semaphore: asyncio.Semaphore, - verbose: bool) -> Result: + verbose: bool) -> DownloadStatus: try: async with semaphore: image = await get_flag(session, base_url, cc) - except aiohttp.ClientResponseError as exc: - if exc.status == 404: - status = HTTPStatus.not_found - msg = 'not found' + except httpx.HTTPStatusError as exc: + res = exc.response + if res.status_code == HTTPStatus.NOT_FOUND: + status = DownloadStatus.NOT_FOUND + msg = f'not found: {res.url}' else: - raise FetchError(cc) from exc + raise else: - loop = asyncio.get_running_loop() # <1> - loop.run_in_executor(None, # <2> - save_flag, image, f'{cc}.gif') # <3> - status = HTTPStatus.ok +# tag::FLAGS2_ASYNCIO_EXECUTOR[] + loop = asyncio.get_running_loop() # <1> + loop.run_in_executor(None, save_flag, # <2> + image, f'{cc}.gif') # <3> +# end::FLAGS2_ASYNCIO_EXECUTOR[] + status = DownloadStatus.OK msg = 'OK' if verbose and msg: print(cc, msg) - return Result(status, cc) -# end::FLAGS2_ASYNCIO_EXECUTOR[] + return status async def supervisor(cc_list: list[str], base_url: str, verbose: bool, - concur_req: int) -> Counter[HTTPStatus]: - counter: Counter[HTTPStatus] = Counter() - semaphore = asyncio.Semaphore(concur_req) - async with aiohttp.ClientSession() as session: + concur_req: int) -> Counter[DownloadStatus]: # <1> + counter: Counter[DownloadStatus] = Counter() + semaphore = asyncio.Semaphore(concur_req) # <2> + async with httpx.AsyncClient() as session: to_do = [download_one(session, cc, base_url, semaphore, verbose) - for cc in sorted(cc_list)] - - to_do_iter = asyncio.as_completed(to_do) + for cc in sorted(cc_list)] # <3> + to_do_iter = asyncio.as_completed(to_do) # <4> if not verbose: - to_do_iter = tqdm.tqdm(to_do_iter, total=len(cc_list)) - for coro in to_do_iter: + to_do_iter = tqdm.tqdm(to_do_iter, total=len(cc_list)) # <5> + for coro in to_do_iter: # <6> try: - res = await coro - except FetchError as exc: - country_code = exc.country_code - try: - error_msg = exc.__cause__.message # type: ignore - except AttributeError: - error_msg = 'Unknown cause' - if verbose and error_msg: - print(f'*** Error for {country_code}: {error_msg}') - status = HTTPStatus.error - else: - status = res.status - - counter[status] += 1 - - return counter - + status = await coro # <7> + except httpx.HTTPStatusError as exc: # <13> + error_msg = 'HTTP error {resp.status_code} - {resp.reason_phrase}' + error_msg = error_msg.format(resp=exc.response) + error = exc + except httpx.RequestError as exc: # <15> + error_msg = f'{exc} {type(exc)}'.strip() + error = exc + except KeyboardInterrupt: # <7> + break + else: # <8> + error = None + + if error: + status = DownloadStatus.ERROR # <9> + if verbose: # <11> + cc = Path(str(error.request.url)).stem.upper() + print(f'{cc} error: {error_msg}') + counter[status] += 1 # <10> + + return counter # <12> def download_many(cc_list: list[str], base_url: str, verbose: bool, - concur_req: int) -> Counter[HTTPStatus]: + concur_req: int) -> Counter[DownloadStatus]: coro = supervisor(cc_list, base_url, verbose, concur_req) counts = asyncio.run(coro) # <14> return counts - if __name__ == '__main__': main(download_many, DEFAULT_CONCUR_REQ, MAX_CONCUR_REQ) +# end::FLAGS2_ASYNCIO_START[] diff --git a/20-futures/getflags/flags2_common.py b/20-futures/getflags/flags2_common.py index 74cd888..1ad05d3 100644 --- a/20-futures/getflags/flags2_common.py +++ b/20-futures/getflags/flags2_common.py @@ -5,13 +5,11 @@ import string import sys import time -from collections import namedtuple, Counter +from collections import Counter from enum import Enum from pathlib import Path -Result = namedtuple('Result', 'status data') - -HTTPStatus = Enum('HTTPStatus', 'ok not_found error') +DownloadStatus = Enum('DownloadStatus', 'OK NOT_FOUND ERROR') POP20_CC = ('CN IN US ID BR PK NG BD RU JP ' 'MX PH VN ET EG DE IR TR CD FR').split() @@ -20,7 +18,7 @@ MAX_CONCUR_REQ = 1 SERVERS = { - 'REMOTE': 'http://fluentpython.com/data/flags', + 'REMOTE': 'https://www.fluentpython.com/data/flags', 'LOCAL': 'http://localhost:8000/flags', 'DELAY': 'http://localhost:8001/flags', 'ERROR': 'http://localhost:8002/flags', @@ -52,17 +50,17 @@ def initial_report(cc_list: list[str], def final_report(cc_list: list[str], - counter: Counter[HTTPStatus], + counter: Counter[DownloadStatus], start_time: float) -> None: elapsed = time.perf_counter() - start_time print('-' * 20) - plural = 's' if counter[HTTPStatus.ok] != 1 else '' - print(f'{counter[HTTPStatus.ok]} flag{plural} downloaded.') - if counter[HTTPStatus.not_found]: - print(f'{counter[HTTPStatus.not_found]} not found.') - if counter[HTTPStatus.error]: - plural = 's' if counter[HTTPStatus.error] != 1 else '' - print(f'{counter[HTTPStatus.error]} error{plural}.') + plural = 's' if counter[DownloadStatus.OK] != 1 else '' + print(f'{counter[DownloadStatus.OK]:3} flag{plural} downloaded.') + if counter[DownloadStatus.NOT_FOUND]: + print(f'{counter[DownloadStatus.NOT_FOUND]:3} not found.') + if counter[DownloadStatus.ERROR]: + plural = 's' if counter[DownloadStatus.ERROR] != 1 else '' + print(f'{counter[DownloadStatus.ERROR]:3} error{plural}.') print(f'Elapsed time: {elapsed:.2f}s') @@ -142,7 +140,7 @@ def process_args(default_concur_req): sys.exit(2) # command line usage error if not cc_list: - cc_list = sorted(POP20_CC) + cc_list = sorted(POP20_CC)[:args.limit] return args, cc_list @@ -151,9 +149,7 @@ def main(download_many, default_concur_req, max_concur_req): actual_req = min(args.max_req, max_concur_req, len(cc_list)) initial_report(cc_list, actual_req, args.server) base_url = SERVERS[args.server] + DEST_DIR.mkdir(exist_ok=True) t0 = time.perf_counter() counter = download_many(cc_list, base_url, args.verbose, actual_req) - assert sum(counter.values()) == len(cc_list), ( - 'some downloads are unaccounted for' - ) final_report(cc_list, counter, t0) diff --git a/20-futures/getflags/flags2_sequential.py b/20-futures/getflags/flags2_sequential.py index 2e30856..e4358a3 100755 --- a/20-futures/getflags/flags2_sequential.py +++ b/20-futures/getflags/flags2_sequential.py @@ -17,71 +17,72 @@ """ +# tag::FLAGS2_BASIC_HTTP_FUNCTIONS[] from collections import Counter +from http import HTTPStatus -import requests -import tqdm # type: ignore +import httpx +import tqdm # type: ignore # <1> -from flags2_common import main, save_flag, HTTPStatus, Result +from flags2_common import main, save_flag, DownloadStatus # <2> DEFAULT_CONCUR_REQ = 1 MAX_CONCUR_REQ = 1 -# tag::FLAGS2_BASIC_HTTP_FUNCTIONS[] def get_flag(base_url: str, cc: str) -> bytes: url = f'{base_url}/{cc}/{cc}.gif'.lower() - resp = requests.get(url) - if resp.status_code != 200: # <1> - resp.raise_for_status() + resp = httpx.get(url, timeout=3.1, follow_redirects=True) + resp.raise_for_status() # <3> return resp.content -def download_one(cc: str, base_url: str, verbose: bool = False): +def download_one(cc: str, base_url: str, verbose: bool = False) -> DownloadStatus: try: image = get_flag(base_url, cc) - except requests.exceptions.HTTPError as exc: # <2> + except httpx.HTTPStatusError as exc: # <4> res = exc.response - if res.status_code == 404: - status = HTTPStatus.not_found # <3> - msg = 'not found' - else: # <4> - raise + if res.status_code == HTTPStatus.NOT_FOUND: + status = DownloadStatus.NOT_FOUND # <5> + msg = f'not found: {res.url}' + else: + raise # <6> else: save_flag(image, f'{cc}.gif') - status = HTTPStatus.ok + status = DownloadStatus.OK msg = 'OK' - if verbose: # <5> + if verbose: # <7> print(cc, msg) - return Result(status, cc) # <6> + return status # end::FLAGS2_BASIC_HTTP_FUNCTIONS[] # tag::FLAGS2_DOWNLOAD_MANY_SEQUENTIAL[] def download_many(cc_list: list[str], base_url: str, verbose: bool, - _unused_concur_req: int) -> Counter[int]: - counter: Counter[int] = Counter() # <1> + _unused_concur_req: int) -> Counter[DownloadStatus]: + counter: Counter[DownloadStatus] = Counter() # <1> cc_iter = sorted(cc_list) # <2> if not verbose: cc_iter = tqdm.tqdm(cc_iter) # <3> - for cc in cc_iter: # <4> + for cc in cc_iter: try: - res = download_one(cc, base_url, verbose) # <5> - except requests.exceptions.HTTPError as exc: # <6> - error_msg = 'HTTP error {res.status_code} - {res.reason}' - error_msg = error_msg.format(res=exc.response) - except requests.exceptions.ConnectionError: # <7> - error_msg = 'Connection error' + status = download_one(cc, base_url, verbose) # <4> + except httpx.HTTPStatusError as exc: # <5> + error_msg = 'HTTP error {resp.status_code} - {resp.reason_phrase}' + error_msg = error_msg.format(resp=exc.response) + except httpx.RequestError as exc: # <6> + error_msg = f'{exc} {type(exc)}'.strip() + except KeyboardInterrupt: # <7> + break else: # <8> error_msg = '' - status = res.status if error_msg: - status = HTTPStatus.error # <9> + status = DownloadStatus.ERROR # <9> counter[status] += 1 # <10> if verbose and error_msg: # <11> - print(f'*** Error for {cc}: {error_msg}') + print(f'{cc} error: {error_msg}') return counter # <12> # end::FLAGS2_DOWNLOAD_MANY_SEQUENTIAL[] diff --git a/20-futures/getflags/flags2_threadpool.py b/20-futures/getflags/flags2_threadpool.py index bbe71cb..8955c7c 100755 --- a/20-futures/getflags/flags2_threadpool.py +++ b/20-futures/getflags/flags2_threadpool.py @@ -22,48 +22,49 @@ from collections import Counter from concurrent import futures -import requests -import tqdm # type: ignore # <1> +import httpx +import tqdm # type: ignore -from flags2_common import main, HTTPStatus # <2> -from flags2_sequential import download_one # <3> +from flags2_common import main, DownloadStatus +from flags2_sequential import download_one # <1> -DEFAULT_CONCUR_REQ = 30 # <4> -MAX_CONCUR_REQ = 1000 # <5> +DEFAULT_CONCUR_REQ = 30 # <2> +MAX_CONCUR_REQ = 1000 # <3> def download_many(cc_list: list[str], base_url: str, verbose: bool, - concur_req: int) -> Counter[int]: - counter: Counter[int] = Counter() - with futures.ThreadPoolExecutor(max_workers=concur_req) as executor: # <6> - to_do_map = {} # <7> - for cc in sorted(cc_list): # <8> + concur_req: int) -> Counter[DownloadStatus]: + counter: Counter[DownloadStatus] = Counter() + with futures.ThreadPoolExecutor(max_workers=concur_req) as executor: # <4> + to_do_map = {} # <5> + for cc in sorted(cc_list): # <6> future = executor.submit(download_one, cc, - base_url, verbose) # <9> - to_do_map[future] = cc # <10> - done_iter = futures.as_completed(to_do_map) # <11> + base_url, verbose) # <7> + to_do_map[future] = cc # <8> + done_iter = futures.as_completed(to_do_map) # <9> if not verbose: - done_iter = tqdm.tqdm(done_iter, total=len(cc_list)) # <12> - for future in done_iter: # <13> + done_iter = tqdm.tqdm(done_iter, total=len(cc_list)) # <10> + for future in done_iter: # <11> try: - res = future.result() # <14> - except requests.exceptions.HTTPError as exc: # <15> - error_fmt = 'HTTP {res.status_code} - {res.reason}' - error_msg = error_fmt.format(res=exc.response) - except requests.exceptions.ConnectionError: - error_msg = 'Connection error' + status = future.result() # <12> + except httpx.HTTPStatusError as exc: # <13> + error_msg = 'HTTP error {resp.status_code} - {resp.reason_phrase}' + error_msg = error_msg.format(resp=exc.response) + except httpx.RequestError as exc: # <15> + error_msg = f'{exc} {type(exc)}'.strip() + except KeyboardInterrupt: + break else: error_msg = '' - status = res.status if error_msg: - status = HTTPStatus.error + status = DownloadStatus.ERROR counter[status] += 1 if verbose and error_msg: cc = to_do_map[future] # <16> - print(f'*** Error for {cc}: {error_msg}') + print(f'{cc} error: {error_msg}') return counter diff --git a/20-futures/getflags/flags3_asyncio.py b/20-futures/getflags/flags3_asyncio.py index 04a01e2..b698d4d 100755 --- a/20-futures/getflags/flags3_asyncio.py +++ b/20-futures/getflags/flags3_asyncio.py @@ -8,11 +8,12 @@ import asyncio from collections import Counter +from http import HTTPStatus import aiohttp import tqdm # type: ignore -from flags2_common import main, HTTPStatus, Result, save_flag +from flags2_common import main, DownloadStatus, save_flag # default set low to avoid errors from remote site, such as # 503 - Service Temporarily Unavailable @@ -54,15 +55,15 @@ async def download_one(session: aiohttp.ClientSession, cc: str, base_url: str, semaphore: asyncio.Semaphore, - verbose: bool) -> Result: + verbose: bool) -> DownloadStatus: try: async with semaphore: image = await get_flag(session, base_url, cc) # <1> async with semaphore: country = await get_country(session, base_url, cc) # <2> except aiohttp.ClientResponseError as exc: - if exc.status == 404: - status = HTTPStatus.not_found + if exc.status == HTTPStatus.NOT_FOUND: + status = DownloadStatus.NOT_FOUND msg = 'not found' else: raise FetchError(cc) from exc @@ -72,18 +73,18 @@ async def download_one(session: aiohttp.ClientSession, loop = asyncio.get_running_loop() loop.run_in_executor(None, save_flag, image, filename) - status = HTTPStatus.ok + status = DownloadStatus.OK msg = 'OK' if verbose and msg: print(cc, msg) - return Result(status, cc) + return status # end::FLAGS3_ASYNCIO_DOWNLOAD_ONE[] async def supervisor(cc_list: list[str], base_url: str, verbose: bool, - concur_req: int) -> Counter[HTTPStatus]: - counter: Counter[HTTPStatus] = Counter() + concur_req: int) -> Counter[DownloadStatus]: + counter: Counter[DownloadStatus] = Counter() semaphore = asyncio.Semaphore(concur_req) async with aiohttp.ClientSession() as session: to_do = [download_one(session, cc, base_url, @@ -95,7 +96,7 @@ async def supervisor(cc_list: list[str], to_do_iter = tqdm.tqdm(to_do_iter, total=len(cc_list)) for coro in to_do_iter: try: - res = await coro + status = await coro except FetchError as exc: country_code = exc.country_code try: @@ -104,9 +105,7 @@ async def supervisor(cc_list: list[str], error_msg = 'Unknown cause' if verbose and error_msg: print(f'*** Error for {country_code}: {error_msg}') - status = HTTPStatus.error - else: - status = res.status + status = DownloadStatus.ERROR counter[status] += 1 @@ -116,7 +115,7 @@ async def supervisor(cc_list: list[str], def download_many(cc_list: list[str], base_url: str, verbose: bool, - concur_req: int) -> Counter[HTTPStatus]: + concur_req: int) -> Counter[DownloadStatus]: coro = supervisor(cc_list, base_url, verbose, concur_req) counts = asyncio.run(coro) # <14> diff --git a/20-futures/getflags/flags_asyncio.py b/20-futures/getflags/flags_asyncio.py index ee431d0..ba28065 100755 --- a/20-futures/getflags/flags_asyncio.py +++ b/20-futures/getflags/flags_asyncio.py @@ -9,38 +9,38 @@ $ python3 flags_asyncio.py EG VN IN TR RU ID US DE CN MX JP BD NG ET FR BR PH PK CD IR 20 flags downloaded in 1.07s - """ # tag::FLAGS_ASYNCIO_TOP[] import asyncio -from aiohttp import ClientSession # <1> +from httpx import AsyncClient # <1> from flags import BASE_URL, save_flag, main # <2> -async def download_one(session: ClientSession, cc: str): # <3> +async def download_one(session: AsyncClient, cc: str): # <3> image = await get_flag(session, cc) save_flag(image, f'{cc}.gif') print(cc, end=' ', flush=True) return cc -async def get_flag(session: ClientSession, cc: str) -> bytes: # <4> +async def get_flag(session: AsyncClient, cc: str) -> bytes: # <4> url = f'{BASE_URL}/{cc}/{cc}.gif'.lower() - async with session.get(url) as resp: # <5> - return await resp.read() # <6> + resp = await session.get(url, timeout=6.1, + follow_redirects=True) # <5> + return resp.read() # <6> # end::FLAGS_ASYNCIO_TOP[] # tag::FLAGS_ASYNCIO_START[] -def download_many(cc_list: list[str]) -> int: # <1> - return asyncio.run(supervisor(cc_list)) # <2> +def download_many(cc_list: list[str]) -> int: # <1> + return asyncio.run(supervisor(cc_list)) # <2> async def supervisor(cc_list: list[str]) -> int: - async with ClientSession() as session: # <3> - to_do = [download_one(session, cc) # <4> - for cc in sorted(cc_list)] - res = await asyncio.gather(*to_do) # <5> + async with AsyncClient() as session: # <3> + to_do = [download_one(session, cc) + for cc in sorted(cc_list)] # <4> + res = await asyncio.gather(*to_do) # <5> - return len(res) # <6> + return len(res) # <6> if __name__ == '__main__': main(download_many) diff --git a/20-futures/getflags/requirements.txt b/20-futures/getflags/requirements.txt index 37baa8d..b8bb630 100644 --- a/20-futures/getflags/requirements.txt +++ b/20-futures/getflags/requirements.txt @@ -1,13 +1,10 @@ -aiohttp==3.7.4.post0 -async-timeout==3.0.1 -attrs==21.2.0 +anyio==3.3.2 certifi==2021.5.30 -chardet==4.0.0 -charset-normalizer==2.0.4 +charset-normalizer==2.0.6 +h11==0.12.0 +httpcore==0.13.7 +httpx==1.0.0b0 idna==3.2 -multidict==5.1.0 -requests==2.26.0 -tqdm==4.62.2 -typing-extensions==3.10.0.2 -urllib3==1.26.6 -yarl==1.6.3 +rfc3986==1.5.0 +sniffio==1.2.0 +tqdm==4.62.3 diff --git a/20-futures/getflags/slow_server.py b/20-futures/getflags/slow_server.py index bede010..90e6672 100755 --- a/20-futures/getflags/slow_server.py +++ b/20-futures/getflags/slow_server.py @@ -15,8 +15,10 @@ from functools import partial from http import server, HTTPStatus from http.server import ThreadingHTTPServer, SimpleHTTPRequestHandler -from random import random +from random import random, uniform +MIN_DELAY = 0.5 # minimum delay for do_GET (seconds) +MAX_DELAY = 5.0 # maximum delay for do_GET (seconds) class SlowHTTPRequestHandler(SimpleHTTPRequestHandler): """SlowHTTPRequestHandler adds delays and errors to test HTTP clients. @@ -36,15 +38,23 @@ def __init__(self, *args, error_rate=0.0, **kwargs): def do_GET(self): """Serve a GET request.""" - time.sleep(.5) + delay = uniform(MIN_DELAY, MAX_DELAY) + cc = self.path[-6:-4].upper() + print(f'{cc} delay: {delay:0.2}s') + time.sleep(delay) if random() < self.error_rate: # HTTPStatus.IM_A_TEAPOT requires Python >= 3.9 - self.send_error(HTTPStatus.IM_A_TEAPOT, "I'm a Teapot") + try: + self.send_error(HTTPStatus.IM_A_TEAPOT, "I'm a Teapot") + except BrokenPipeError as exc: + print(f'{cc} *** BrokenPipeError: client closed') else: f = self.send_head() if f: try: self.copyfile(f, self.wfile) + except BrokenPipeError as exc: + print(f'{cc} *** BrokenPipeError: client closed') finally: f.close() @@ -67,9 +77,9 @@ def do_GET(self): help='Error rate; e.g. use .25 for 25%% probability ' '[default:0.0]') parser.add_argument('port', action='store', - default=8000, type=int, + default=8001, type=int, nargs='?', - help='Specify alternate port [default: 8000]') + help='Specify alternate port [default: 8001]') args = parser.parse_args() handler_class = partial(SlowHTTPRequestHandler, directory=args.directory, diff --git a/20-futures/getflags/tree.py b/20-futures/getflags/tree.py new file mode 100644 index 0000000..4f65697 --- /dev/null +++ b/20-futures/getflags/tree.py @@ -0,0 +1,38 @@ +import httpx + +def tree(cls, level=0): + yield cls.__name__, level + for sub_cls in cls.__subclasses__(): + yield from tree(sub_cls, level+1) + + +def display(cls): + for cls_name, level in tree(cls): + indent = ' ' * 4 * level + print(f'{indent}{cls_name}') + + +def find_roots(module): + exceptions = [] + for name in dir(module): + obj = getattr(module, name) + if isinstance(obj, type) and issubclass(obj, BaseException): + exceptions.append(obj) + roots = [] + for exc in exceptions: + root = True + for other in exceptions: + if exc is not other and issubclass(exc, other): + root = False + break + if root: + roots.append(exc) + return roots + + +def main(): + for exc in find_roots(httpx): + display(exc) + +if __name__ == '__main__': + main() From 0f91193c9f0cc6cf200c22e3828f2599fa8a67ed Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Sat, 2 Oct 2021 17:13:14 -0300 Subject: [PATCH 120/166] sequential, threaded & async HTTP clients using HTTPX --- 20-futures/getflags/flags3_asyncio.py | 126 -------------------------- 1 file changed, 126 deletions(-) delete mode 100755 20-futures/getflags/flags3_asyncio.py diff --git a/20-futures/getflags/flags3_asyncio.py b/20-futures/getflags/flags3_asyncio.py deleted file mode 100755 index b698d4d..0000000 --- a/20-futures/getflags/flags3_asyncio.py +++ /dev/null @@ -1,126 +0,0 @@ -#!/usr/bin/env python3 - -"""Download flags of countries (with error handling). - -asyncio async/await version using run_in_executor for save_flag. - -""" - -import asyncio -from collections import Counter -from http import HTTPStatus - -import aiohttp -import tqdm # type: ignore - -from flags2_common import main, DownloadStatus, save_flag - -# default set low to avoid errors from remote site, such as -# 503 - Service Temporarily Unavailable -DEFAULT_CONCUR_REQ = 5 -MAX_CONCUR_REQ = 1000 - - -class FetchError(Exception): - def __init__(self, country_code: str): - self.country_code = country_code - -async def get_flag(session: aiohttp.ClientSession, - base_url: str, - cc: str) -> bytes: - url = f'{base_url}/{cc}/{cc}.gif' - async with session.get(url) as resp: - if resp.status == 200: - return await resp.read() - else: - resp.raise_for_status() - return bytes() - -# tag::FLAGS3_ASYNCIO_GET_COUNTRY[] -async def get_country(session: aiohttp.ClientSession, - base_url: str, - cc: str) -> str: # <1> - url = f'{base_url}/{cc}/metadata.json' - async with session.get(url) as resp: - if resp.status == 200: - metadata = await resp.json() # <2> - return metadata.get('country', 'no name') # <3> - else: - resp.raise_for_status() - return '' -# end::FLAGS3_ASYNCIO_GET_COUNTRY[] - -# tag::FLAGS3_ASYNCIO_DOWNLOAD_ONE[] -async def download_one(session: aiohttp.ClientSession, - cc: str, - base_url: str, - semaphore: asyncio.Semaphore, - verbose: bool) -> DownloadStatus: - try: - async with semaphore: - image = await get_flag(session, base_url, cc) # <1> - async with semaphore: - country = await get_country(session, base_url, cc) # <2> - except aiohttp.ClientResponseError as exc: - if exc.status == HTTPStatus.NOT_FOUND: - status = DownloadStatus.NOT_FOUND - msg = 'not found' - else: - raise FetchError(cc) from exc - else: - filename = country.replace(' ', '_') # <3> - filename = f'{filename}.gif' - loop = asyncio.get_running_loop() - loop.run_in_executor(None, - save_flag, image, filename) - status = DownloadStatus.OK - msg = 'OK' - if verbose and msg: - print(cc, msg) - return status -# end::FLAGS3_ASYNCIO_DOWNLOAD_ONE[] - -async def supervisor(cc_list: list[str], - base_url: str, - verbose: bool, - concur_req: int) -> Counter[DownloadStatus]: - counter: Counter[DownloadStatus] = Counter() - semaphore = asyncio.Semaphore(concur_req) - async with aiohttp.ClientSession() as session: - to_do = [download_one(session, cc, base_url, - semaphore, verbose) - for cc in sorted(cc_list)] - - to_do_iter = asyncio.as_completed(to_do) - if not verbose: - to_do_iter = tqdm.tqdm(to_do_iter, total=len(cc_list)) - for coro in to_do_iter: - try: - status = await coro - except FetchError as exc: - country_code = exc.country_code - try: - error_msg = exc.__cause__.message # type: ignore - except AttributeError: - error_msg = 'Unknown cause' - if verbose and error_msg: - print(f'*** Error for {country_code}: {error_msg}') - status = DownloadStatus.ERROR - - counter[status] += 1 - - return counter - - -def download_many(cc_list: list[str], - base_url: str, - verbose: bool, - concur_req: int) -> Counter[DownloadStatus]: - coro = supervisor(cc_list, base_url, verbose, concur_req) - counts = asyncio.run(coro) # <14> - - return counts - - -if __name__ == '__main__': - main(download_many, DEFAULT_CONCUR_REQ, MAX_CONCUR_REQ) From 6359a4de0c7f3b155b5445512fe99972ed7c5f43 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Sat, 2 Oct 2021 17:27:22 -0300 Subject: [PATCH 121/166] sequential, threaded & async HTTP clients using HTTPX --- 20-futures/getflags/downloaded/.gitignore | 1 - 1 file changed, 1 deletion(-) delete mode 100644 20-futures/getflags/downloaded/.gitignore diff --git a/20-futures/getflags/downloaded/.gitignore b/20-futures/getflags/downloaded/.gitignore deleted file mode 100644 index d8c78e1..0000000 --- a/20-futures/getflags/downloaded/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.gif \ No newline at end of file From 980f75032685921e08d5200513a261e83ef5f858 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Sat, 2 Oct 2021 17:48:34 -0300 Subject: [PATCH 122/166] sequential, threaded & async HTTP clients using HTTPX --- 20-futures/getflags/flags2_asyncio.py | 1 + 20-futures/getflags/flags2_asyncio_executor.py | 1 + 2 files changed, 2 insertions(+) diff --git a/20-futures/getflags/flags2_asyncio.py b/20-futures/getflags/flags2_asyncio.py index a2bb6ff..4825db4 100755 --- a/20-futures/getflags/flags2_asyncio.py +++ b/20-futures/getflags/flags2_asyncio.py @@ -67,6 +67,7 @@ async def supervisor(cc_list: list[str], to_do_iter = asyncio.as_completed(to_do) # <4> if not verbose: to_do_iter = tqdm.tqdm(to_do_iter, total=len(cc_list)) # <5> + error: httpx.HTTPError | None = None for coro in to_do_iter: # <6> try: status = await coro # <7> diff --git a/20-futures/getflags/flags2_asyncio_executor.py b/20-futures/getflags/flags2_asyncio_executor.py index 7143b46..752b60f 100755 --- a/20-futures/getflags/flags2_asyncio_executor.py +++ b/20-futures/getflags/flags2_asyncio_executor.py @@ -70,6 +70,7 @@ async def supervisor(cc_list: list[str], to_do_iter = asyncio.as_completed(to_do) # <4> if not verbose: to_do_iter = tqdm.tqdm(to_do_iter, total=len(cc_list)) # <5> + error: httpx.HTTPError | None = None for coro in to_do_iter: # <6> try: status = await coro # <7> From 5d6b15604782c715c85236d6de30b90add3f8d32 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Tue, 5 Oct 2021 09:44:12 -0300 Subject: [PATCH 123/166] ch20: renamed directory --- {20-futures => 20-executors}/demo_executor_map.py | 0 {20-futures => 20-executors}/getflags/.gitignore | 0 {20-futures => 20-executors}/getflags/README.adoc | 0 .../getflags/country_codes.txt | 0 {20-futures => 20-executors}/getflags/flags.py | 0 {20-futures => 20-executors}/getflags/flags.zip | Bin .../getflags/flags2_asyncio.py | 0 .../getflags/flags2_asyncio_executor.py | 0 .../getflags/flags2_common.py | 0 .../getflags/flags2_sequential.py | 0 .../getflags/flags2_threadpool.py | 0 .../getflags/flags_asyncio.py | 0 .../getflags/flags_threadpool.py | 0 .../getflags/flags_threadpool_futures.py | 0 .../getflags/requirements.txt | 0 .../getflags/slow_server.py | 0 {20-futures => 20-executors}/getflags/tree.py | 0 {20-futures => 20-executors}/primes/primes.py | 0 {20-futures => 20-executors}/primes/proc_pool.py | 0 19 files changed, 0 insertions(+), 0 deletions(-) rename {20-futures => 20-executors}/demo_executor_map.py (100%) rename {20-futures => 20-executors}/getflags/.gitignore (100%) rename {20-futures => 20-executors}/getflags/README.adoc (100%) rename {20-futures => 20-executors}/getflags/country_codes.txt (100%) rename {20-futures => 20-executors}/getflags/flags.py (100%) rename {20-futures => 20-executors}/getflags/flags.zip (100%) rename {20-futures => 20-executors}/getflags/flags2_asyncio.py (100%) rename {20-futures => 20-executors}/getflags/flags2_asyncio_executor.py (100%) rename {20-futures => 20-executors}/getflags/flags2_common.py (100%) rename {20-futures => 20-executors}/getflags/flags2_sequential.py (100%) rename {20-futures => 20-executors}/getflags/flags2_threadpool.py (100%) rename {20-futures => 20-executors}/getflags/flags_asyncio.py (100%) rename {20-futures => 20-executors}/getflags/flags_threadpool.py (100%) rename {20-futures => 20-executors}/getflags/flags_threadpool_futures.py (100%) rename {20-futures => 20-executors}/getflags/requirements.txt (100%) rename {20-futures => 20-executors}/getflags/slow_server.py (100%) rename {20-futures => 20-executors}/getflags/tree.py (100%) rename {20-futures => 20-executors}/primes/primes.py (100%) rename {20-futures => 20-executors}/primes/proc_pool.py (100%) diff --git a/20-futures/demo_executor_map.py b/20-executors/demo_executor_map.py similarity index 100% rename from 20-futures/demo_executor_map.py rename to 20-executors/demo_executor_map.py diff --git a/20-futures/getflags/.gitignore b/20-executors/getflags/.gitignore similarity index 100% rename from 20-futures/getflags/.gitignore rename to 20-executors/getflags/.gitignore diff --git a/20-futures/getflags/README.adoc b/20-executors/getflags/README.adoc similarity index 100% rename from 20-futures/getflags/README.adoc rename to 20-executors/getflags/README.adoc diff --git a/20-futures/getflags/country_codes.txt b/20-executors/getflags/country_codes.txt similarity index 100% rename from 20-futures/getflags/country_codes.txt rename to 20-executors/getflags/country_codes.txt diff --git a/20-futures/getflags/flags.py b/20-executors/getflags/flags.py similarity index 100% rename from 20-futures/getflags/flags.py rename to 20-executors/getflags/flags.py diff --git a/20-futures/getflags/flags.zip b/20-executors/getflags/flags.zip similarity index 100% rename from 20-futures/getflags/flags.zip rename to 20-executors/getflags/flags.zip diff --git a/20-futures/getflags/flags2_asyncio.py b/20-executors/getflags/flags2_asyncio.py similarity index 100% rename from 20-futures/getflags/flags2_asyncio.py rename to 20-executors/getflags/flags2_asyncio.py diff --git a/20-futures/getflags/flags2_asyncio_executor.py b/20-executors/getflags/flags2_asyncio_executor.py similarity index 100% rename from 20-futures/getflags/flags2_asyncio_executor.py rename to 20-executors/getflags/flags2_asyncio_executor.py diff --git a/20-futures/getflags/flags2_common.py b/20-executors/getflags/flags2_common.py similarity index 100% rename from 20-futures/getflags/flags2_common.py rename to 20-executors/getflags/flags2_common.py diff --git a/20-futures/getflags/flags2_sequential.py b/20-executors/getflags/flags2_sequential.py similarity index 100% rename from 20-futures/getflags/flags2_sequential.py rename to 20-executors/getflags/flags2_sequential.py diff --git a/20-futures/getflags/flags2_threadpool.py b/20-executors/getflags/flags2_threadpool.py similarity index 100% rename from 20-futures/getflags/flags2_threadpool.py rename to 20-executors/getflags/flags2_threadpool.py diff --git a/20-futures/getflags/flags_asyncio.py b/20-executors/getflags/flags_asyncio.py similarity index 100% rename from 20-futures/getflags/flags_asyncio.py rename to 20-executors/getflags/flags_asyncio.py diff --git a/20-futures/getflags/flags_threadpool.py b/20-executors/getflags/flags_threadpool.py similarity index 100% rename from 20-futures/getflags/flags_threadpool.py rename to 20-executors/getflags/flags_threadpool.py diff --git a/20-futures/getflags/flags_threadpool_futures.py b/20-executors/getflags/flags_threadpool_futures.py similarity index 100% rename from 20-futures/getflags/flags_threadpool_futures.py rename to 20-executors/getflags/flags_threadpool_futures.py diff --git a/20-futures/getflags/requirements.txt b/20-executors/getflags/requirements.txt similarity index 100% rename from 20-futures/getflags/requirements.txt rename to 20-executors/getflags/requirements.txt diff --git a/20-futures/getflags/slow_server.py b/20-executors/getflags/slow_server.py similarity index 100% rename from 20-futures/getflags/slow_server.py rename to 20-executors/getflags/slow_server.py diff --git a/20-futures/getflags/tree.py b/20-executors/getflags/tree.py similarity index 100% rename from 20-futures/getflags/tree.py rename to 20-executors/getflags/tree.py diff --git a/20-futures/primes/primes.py b/20-executors/primes/primes.py similarity index 100% rename from 20-futures/primes/primes.py rename to 20-executors/primes/primes.py diff --git a/20-futures/primes/proc_pool.py b/20-executors/primes/proc_pool.py similarity index 100% rename from 20-futures/primes/proc_pool.py rename to 20-executors/primes/proc_pool.py From 43f1bf23b3ba020597b455e740fe553f1923fca9 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Tue, 5 Oct 2021 09:52:43 -0300 Subject: [PATCH 124/166] sync from Atlas --- 19-concurrency/primes/procs.py | 60 +++++++------- 19-concurrency/primes/procs_race_condition.py | 80 +++++++++++++++++++ 20-executors/getflags/.gitignore | 3 +- 20-executors/getflags/flags2_threadpool.py | 10 +-- README.md | 2 +- 5 files changed, 118 insertions(+), 37 deletions(-) create mode 100755 19-concurrency/primes/procs_race_condition.py diff --git a/19-concurrency/primes/procs.py b/19-concurrency/primes/procs.py index 09f75e3..50e3d9f 100644 --- a/19-concurrency/primes/procs.py +++ b/19-concurrency/primes/procs.py @@ -31,46 +31,46 @@ def worker(jobs: JobQueue, results: ResultQueue) -> None: # <7> while n := jobs.get(): # <8> results.put(check(n)) # <9> results.put(PrimeResult(0, False, 0.0)) # <10> -# end::PRIMES_PROC_TOP[] -# tag::PRIMES_PROC_MIDDLE[] -def start_jobs(workers: int, jobs: JobQueue, results: ResultQueue) -> None: +def start_jobs( + procs: int, jobs: JobQueue, results: ResultQueue # <11> +) -> None: for n in NUMBERS: - jobs.put(n) # <1> - for _ in range(workers): - proc = Process(target=worker, args=(jobs, results)) # <2> - proc.start() # <3> - jobs.put(0) # <4> - -def report(workers: int, results: ResultQueue) -> int: - checked = 0 - workers_done = 0 - while workers_done < workers: - n, prime, elapsed = results.get() - if n == 0: - workers_done += 1 - else: - checked += 1 - label = 'P' if prime else ' ' - print(f'{n:16} {label} {elapsed:9.6f}s') - return checked -# end::PRIMES_PROC_MIDDLE[] + jobs.put(n) # <12> + for _ in range(procs): + proc = Process(target=worker, args=(jobs, results)) # <13> + proc.start() # <14> + jobs.put(0) # <15> +# end::PRIMES_PROC_TOP[] # tag::PRIMES_PROC_MAIN[] def main() -> None: - if len(sys.argv) < 2: - workers = cpu_count() + if len(sys.argv) < 2: # <1> + procs = cpu_count() else: - workers = int(sys.argv[1]) + procs = int(sys.argv[1]) - print(f'Checking {len(NUMBERS)} numbers with {workers} processes:') + print(f'Checking {len(NUMBERS)} numbers with {procs} processes:') t0 = perf_counter() - jobs: JobQueue = SimpleQueue() + jobs: JobQueue = SimpleQueue() # <2> results: ResultQueue = SimpleQueue() - start_jobs(workers, jobs, results) - checked = report(workers, results) + start_jobs(procs, jobs, results) # <3> + checked = report(procs, results) # <4> elapsed = perf_counter() - t0 - print(f'{checked} checks in {elapsed:.2f}s') + print(f'{checked} checks in {elapsed:.2f}s') # <5> + +def report(procs: int, results: ResultQueue) -> int: # <6> + checked = 0 + procs_done = 0 + while procs_done < procs: # <7> + n, prime, elapsed = results.get() # <8> + if n == 0: # <9> + procs_done += 1 + else: + checked += 1 # <10> + label = 'P' if prime else ' ' + print(f'{n:16} {label} {elapsed:9.6f}s') + return checked if __name__ == '__main__': main() diff --git a/19-concurrency/primes/procs_race_condition.py b/19-concurrency/primes/procs_race_condition.py new file mode 100755 index 0000000..aa5e172 --- /dev/null +++ b/19-concurrency/primes/procs_race_condition.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python3 + +""" +procs.py: shows that multiprocessing on a multicore machine +can be faster than sequential code for CPU-intensive work. +""" + +# tag::PRIMES_PROC_TOP[] +import sys +from time import perf_counter +from typing import NamedTuple +from multiprocessing import Process, SimpleQueue, cpu_count # <1> +from multiprocessing import queues # <2> + +from primes import is_prime, NUMBERS + +class PrimeResult(NamedTuple): # <3> + n: int + prime: bool + elapsed: float + +JobQueue = queues.SimpleQueue[int] # <4> +ResultQueue = queues.SimpleQueue[PrimeResult] # <5> + +def check(n: int) -> PrimeResult: # <6> + t0 = perf_counter() + res = is_prime(n) + return PrimeResult(n, res, perf_counter() - t0) + +def worker(jobs: JobQueue, results: ResultQueue) -> None: # <7> + while n := jobs.get(): # <8> + results.put(check(n)) # <9> + results.put(PrimeResult(0, False, 0.0)) +# end::PRIMES_PROC_TOP[] + +def start_jobs(workers: int) -> ResultQueue: + jobs: JobQueue = SimpleQueue() # <2> + results: ResultQueue = SimpleQueue() + + for n in NUMBERS: # <3> + jobs.put(n) + + for _ in range(workers): + proc = Process(target=worker, args=(jobs, results)) # <4> + proc.start() # <5> + jobs.put(0) # <6> + + return results + +def report(workers: int, results: ResultQueue) -> int: + workers_done = 0 + checked = 0 + while workers_done < workers: + n, prime, elapsed = results.get() # <7> + if n == 0: + workers_done += 1 + else: + checked += 1 + label = 'P' if prime else ' ' + print(f'{n:16} {label} {elapsed:9.6f}s') # <8> + return checked + + +# tag::PRIMES_PROC_MAIN[] +def main() -> None: + if len(sys.argv) < 2: # <1> + workers = cpu_count() + else: + workers = int(sys.argv[1]) + + print(f'Checking {len(NUMBERS)} numbers with {workers} processes:') + t0 = perf_counter() + results = start_jobs(workers) + checked = report(workers, results) + elapsed = perf_counter() - t0 + print(f'{checked} checks in {elapsed:.2f}s') + +if __name__ == '__main__': + main() +# end::PRIMES_PROC_MAIN[] diff --git a/20-executors/getflags/.gitignore b/20-executors/getflags/.gitignore index 8484300..aad8463 100644 --- a/20-executors/getflags/.gitignore +++ b/20-executors/getflags/.gitignore @@ -1 +1,2 @@ -flags/ \ No newline at end of file +flags/ +downloaded/ diff --git a/20-executors/getflags/flags2_threadpool.py b/20-executors/getflags/flags2_threadpool.py index 8955c7c..62f6c3b 100755 --- a/20-executors/getflags/flags2_threadpool.py +++ b/20-executors/getflags/flags2_threadpool.py @@ -20,7 +20,7 @@ # tag::FLAGS2_THREADPOOL[] from collections import Counter -from concurrent import futures +from concurrent.futures import ThreadPoolExecutor, as_completed import httpx import tqdm # type: ignore @@ -37,13 +37,13 @@ def download_many(cc_list: list[str], verbose: bool, concur_req: int) -> Counter[DownloadStatus]: counter: Counter[DownloadStatus] = Counter() - with futures.ThreadPoolExecutor(max_workers=concur_req) as executor: # <4> + with ThreadPoolExecutor(max_workers=concur_req) as executor: # <4> to_do_map = {} # <5> for cc in sorted(cc_list): # <6> future = executor.submit(download_one, cc, base_url, verbose) # <7> to_do_map[future] = cc # <8> - done_iter = futures.as_completed(to_do_map) # <9> + done_iter = as_completed(to_do_map) # <9> if not verbose: done_iter = tqdm.tqdm(done_iter, total=len(cc_list)) # <10> for future in done_iter: # <11> @@ -52,7 +52,7 @@ def download_many(cc_list: list[str], except httpx.HTTPStatusError as exc: # <13> error_msg = 'HTTP error {resp.status_code} - {resp.reason_phrase}' error_msg = error_msg.format(resp=exc.response) - except httpx.RequestError as exc: # <15> + except httpx.RequestError as exc: error_msg = f'{exc} {type(exc)}'.strip() except KeyboardInterrupt: break @@ -63,7 +63,7 @@ def download_many(cc_list: list[str], status = DownloadStatus.ERROR counter[status] += 1 if verbose and error_msg: - cc = to_do_map[future] # <16> + cc = to_do_map[future] # <14> print(f'{cc} error: {error_msg}') return counter diff --git a/README.md b/README.md index f0854e9..96273ac 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ Part / Chapter #|Title|Directory|1st ed. Chapter # 17|Iterators, Generators, and Classic Coroutines|[17-it-generator](17-it-generator)|14 18|Context Managers and else Blocks|[18-with-match](18-with-match)|15 19|Concurrency Models in Python|[19-concurrency](19-concurrency)|🆕 -20|Concurrency with Futures|[20-futures](20-futures)|17 +20|Concurrent Executors|[20-executors](20-executors)|17 21|Asynchronous Programming|[21-async](21-async)|18 **VI – Metaprogramming**| 22|Dynamic Attributes and Properties|[22-dyn-attr-prop](22-dyn-attr-prop)|19 From 6fb0832c4041ec8f46f80d0a8bac5af5a69f32f4 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Tue, 12 Oct 2021 22:37:28 -0300 Subject: [PATCH 125/166] sync from Atlas --- 02-array-seq/lispy/py3.10/examples_test.py | 2 +- 02-array-seq/lispy/py3.9/lis.py | 2 +- 18-with-match/lispy/py3.10/examples_test.py | 2 +- 18-with-match/lispy/py3.10/lis.py | 2 +- 18-with-match/lispy/py3.9/examples_test.py | 4 +- 18-with-match/lispy/py3.9/lis.py | 2 +- 20-executors/getflags/flags2_asyncio.py | 51 ++++++++++----------- 7 files changed, 31 insertions(+), 34 deletions(-) diff --git a/02-array-seq/lispy/py3.10/examples_test.py b/02-array-seq/lispy/py3.10/examples_test.py index ba6bd10..603ccd3 100644 --- a/02-array-seq/lispy/py3.10/examples_test.py +++ b/02-array-seq/lispy/py3.10/examples_test.py @@ -147,7 +147,7 @@ def test_factorial(): gcd_src = """ (define (mod m n) - (- m (* n (// m n)))) + (- m (* n (quotient m n)))) (define (gcd m n) (if (= n 0) m diff --git a/02-array-seq/lispy/py3.9/lis.py b/02-array-seq/lispy/py3.9/lis.py index 201b41e..dd80096 100644 --- a/02-array-seq/lispy/py3.9/lis.py +++ b/02-array-seq/lispy/py3.9/lis.py @@ -80,7 +80,7 @@ def standard_env() -> Environment: '-': op.sub, '*': op.mul, '/': op.truediv, - '//': op.floordiv, + 'quotient': op.floordiv, '>': op.gt, '<': op.lt, '>=': op.ge, diff --git a/18-with-match/lispy/py3.10/examples_test.py b/18-with-match/lispy/py3.10/examples_test.py index 2807596..d207df0 100644 --- a/18-with-match/lispy/py3.10/examples_test.py +++ b/18-with-match/lispy/py3.10/examples_test.py @@ -146,7 +146,7 @@ def test_factorial(): gcd_src = """ (define (mod m n) - (- m (* n (// m n)))) + (- m (* n (quotient m n)))) (define (gcd m n) (if (= n 0) m diff --git a/18-with-match/lispy/py3.10/lis.py b/18-with-match/lispy/py3.10/lis.py index 0faf6d2..04cc28d 100755 --- a/18-with-match/lispy/py3.10/lis.py +++ b/18-with-match/lispy/py3.10/lis.py @@ -83,7 +83,7 @@ def standard_env() -> Environment: '-': op.sub, '*': op.mul, '/': op.truediv, - '//': op.floordiv, + 'quotient': op.floordiv, '>': op.gt, '<': op.lt, '>=': op.ge, diff --git a/18-with-match/lispy/py3.9/examples_test.py b/18-with-match/lispy/py3.9/examples_test.py index c632662..603ccd3 100644 --- a/18-with-match/lispy/py3.9/examples_test.py +++ b/18-with-match/lispy/py3.9/examples_test.py @@ -147,7 +147,7 @@ def test_factorial(): gcd_src = """ (define (mod m n) - (- m (* n (// m n)))) + (- m (* n (quotient m n)))) (define (gcd m n) (if (= n 0) m @@ -255,4 +255,4 @@ def test_closure_with_change(capsys): def test_closure_averager(): got = run(closure_averager_src) assert got == 12.0 -# end::RUN_AVERAGER[] \ No newline at end of file +# end::RUN_AVERAGER[] diff --git a/18-with-match/lispy/py3.9/lis.py b/18-with-match/lispy/py3.9/lis.py index 6ab8a19..e868a14 100644 --- a/18-with-match/lispy/py3.9/lis.py +++ b/18-with-match/lispy/py3.9/lis.py @@ -80,7 +80,7 @@ def standard_env() -> Environment: '-': op.sub, '*': op.mul, '/': op.truediv, - '//': op.floordiv, + 'quotient': op.floordiv, '>': op.gt, '<': op.lt, '>=': op.ge, diff --git a/20-executors/getflags/flags2_asyncio.py b/20-executors/getflags/flags2_asyncio.py index 4825db4..a922db5 100755 --- a/20-executors/getflags/flags2_asyncio.py +++ b/20-executors/getflags/flags2_asyncio.py @@ -16,37 +16,36 @@ from flags2_common import main, DownloadStatus, save_flag -# default set low to avoid errors from remote site, such as -# 503 - Service Temporarily Unavailable +# low concurrency default to avoid errors from remote site, +# such as 503 - Service Temporarily Unavailable DEFAULT_CONCUR_REQ = 5 MAX_CONCUR_REQ = 1000 -async def get_flag(session: httpx.AsyncClient, # <2> +async def get_flag(client: httpx.AsyncClient, # <1> base_url: str, cc: str) -> bytes: url = f'{base_url}/{cc}/{cc}.gif'.lower() - resp = await session.get(url, timeout=3.1, follow_redirects=True) # <3> + resp = await client.get(url, timeout=3.1, follow_redirects=True) # <2> resp.raise_for_status() return resp.content -async def download_one(session: httpx.AsyncClient, +async def download_one(client: httpx.AsyncClient, cc: str, base_url: str, - semaphore: asyncio.Semaphore, # <4> + semaphore: asyncio.Semaphore, verbose: bool) -> DownloadStatus: try: - async with semaphore: # <5> - image = await get_flag(session, base_url, cc) - except httpx.HTTPStatusError as exc: # <4> + async with semaphore: # <3> + image = await get_flag(client, base_url, cc) + except httpx.HTTPStatusError as exc: # <5> res = exc.response if res.status_code == HTTPStatus.NOT_FOUND: - status = DownloadStatus.NOT_FOUND # <5> + status = DownloadStatus.NOT_FOUND msg = f'not found: {res.url}' else: raise - else: - await asyncio.to_thread(save_flag, image, f'{cc}.gif') + await asyncio.to_thread(save_flag, image, f'{cc}.gif') # <6> status = DownloadStatus.OK msg = 'OK' if verbose and msg: @@ -61,33 +60,31 @@ async def supervisor(cc_list: list[str], concur_req: int) -> Counter[DownloadStatus]: # <1> counter: Counter[DownloadStatus] = Counter() semaphore = asyncio.Semaphore(concur_req) # <2> - async with httpx.AsyncClient() as session: - to_do = [download_one(session, cc, base_url, semaphore, verbose) + async with httpx.AsyncClient() as client: + to_do = [download_one(client, cc, base_url, semaphore, verbose) for cc in sorted(cc_list)] # <3> to_do_iter = asyncio.as_completed(to_do) # <4> if not verbose: to_do_iter = tqdm.tqdm(to_do_iter, total=len(cc_list)) # <5> - error: httpx.HTTPError | None = None - for coro in to_do_iter: # <6> + error: httpx.HTTPError | None = None # <6> + for coro in to_do_iter: # <7> try: - status = await coro # <7> - except httpx.HTTPStatusError as exc: # <8> + status = await coro # <8> + except httpx.HTTPStatusError as exc: error_msg = 'HTTP error {resp.status_code} - {resp.reason_phrase}' error_msg = error_msg.format(resp=exc.response) - error = exc - except httpx.RequestError as exc: # <9> + error = exc # <9> + except httpx.RequestError as exc: error_msg = f'{exc} {type(exc)}'.strip() - error = exc - except KeyboardInterrupt: # <10> + error = exc # <10> + except KeyboardInterrupt: break - else: # <11> - error = None if error: - status = DownloadStatus.ERROR # <12> + status = DownloadStatus.ERROR # <11> if verbose: - url = str(error.request.url) # <13> - cc = Path(url).stem.upper() # <14> + url = str(error.request.url) # <12> + cc = Path(url).stem.upper() # <13> print(f'{cc} error: {error_msg}') counter[status] += 1 From f1524171df7fd44204d33212b4af3e86b58a212a Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Mon, 18 Oct 2021 14:42:55 -0300 Subject: [PATCH 126/166] refactored drawtree.py --- 17-it-generator/tree/extra/drawtree.py | 28 +++++++++++++++ 17-it-generator/tree/extra/pretty_tree.py | 35 ------------------- .../{test_pretty_tree.py => test_drawtree.py} | 6 ++-- 3 files changed, 31 insertions(+), 38 deletions(-) create mode 100644 17-it-generator/tree/extra/drawtree.py delete mode 100644 17-it-generator/tree/extra/pretty_tree.py rename 17-it-generator/tree/extra/{test_pretty_tree.py => test_drawtree.py} (94%) diff --git a/17-it-generator/tree/extra/drawtree.py b/17-it-generator/tree/extra/drawtree.py new file mode 100644 index 0000000..043e53b --- /dev/null +++ b/17-it-generator/tree/extra/drawtree.py @@ -0,0 +1,28 @@ +from tree import tree + +SP = '\N{SPACE}' +HLIN = '\N{BOX DRAWINGS LIGHT HORIZONTAL}' # ─ +ELBOW = f'\N{BOX DRAWINGS LIGHT UP AND RIGHT}{HLIN*2}{SP}' # └── +TEE = f'\N{BOX DRAWINGS LIGHT VERTICAL AND RIGHT}{HLIN*2}{SP}' # ├── +PIPE = f'\N{BOX DRAWINGS LIGHT VERTICAL}{SP*3}' # │ + + +def render_lines(tree_iter): + name, _, _ = next(tree_iter) + yield name + prefix = '' + + for name, level, last in tree_iter: + prefix = prefix[:4 * (level-1)] + prefix = prefix.replace(TEE, PIPE).replace(ELBOW, SP*4) + prefix += ELBOW if last else TEE + yield prefix + name + + +def draw(cls): + for line in render_lines(tree(cls)): + print(line) + + +if __name__ == '__main__': + draw(BaseException) diff --git a/17-it-generator/tree/extra/pretty_tree.py b/17-it-generator/tree/extra/pretty_tree.py deleted file mode 100644 index 6d6d91a..0000000 --- a/17-it-generator/tree/extra/pretty_tree.py +++ /dev/null @@ -1,35 +0,0 @@ -from tree import tree - -SPACES = ' ' * 4 -HLINE = '\u2500' # ─ BOX DRAWINGS LIGHT HORIZONTAL -HLINE2 = HLINE * 2 -ELBOW = f'\u2514{HLINE2} ' # └ BOX DRAWINGS LIGHT UP AND RIGHT -TEE = f'\u251C{HLINE2} ' # ├ BOX DRAWINGS LIGHT VERTICAL AND RIGHT -PIPE = '\u2502 ' # │ BOX DRAWINGS LIGHT VERTICAL - - -def render_lines(tree_iter): - name, _, _ = next(tree_iter) - yield name - prefix = '' - - for name, level, last in tree_iter: - if last: - connector = ELBOW - else: - connector = TEE - - prefix = prefix[:4 * (level-1)] - prefix = prefix.replace(TEE, PIPE).replace(ELBOW, SPACES) - prefix += connector - - yield prefix + name - - -def display(cls): - for line in render_lines(tree(cls)): - print(line) - - -if __name__ == '__main__': - display(BaseException) diff --git a/17-it-generator/tree/extra/test_pretty_tree.py b/17-it-generator/tree/extra/test_drawtree.py similarity index 94% rename from 17-it-generator/tree/extra/test_pretty_tree.py rename to 17-it-generator/tree/extra/test_drawtree.py index 70019a3..f66dfa4 100644 --- a/17-it-generator/tree/extra/test_pretty_tree.py +++ b/17-it-generator/tree/extra/test_drawtree.py @@ -1,4 +1,4 @@ -from pretty_tree import tree, render_lines +from drawtree import tree, render_lines def test_1_level(): result = list(render_lines(tree(BrokenPipeError))) @@ -59,7 +59,7 @@ class Leaf2(Branch): pass assert expected == result -def test_3_levels_2_leaves(): +def test_3_levels_2_leaves_dedent(): class A: pass class B(A): pass class C(B): pass @@ -77,7 +77,7 @@ class E(D): pass assert expected == result -def test_4_levels_4_leaves(): +def test_4_levels_4_leaves_dedent(): class A: pass class B1(A): pass class C1(B1): pass From c41611668dbc9431370b1b61dfdab80497794432 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Mon, 18 Oct 2021 14:57:06 -0300 Subject: [PATCH 127/166] refactored drawtree.py --- 17-it-generator/tree/extra/drawtree.py | 8 ++++---- 17-it-generator/tree/extra/test_tree.py | 18 ++++++++++++------ 17-it-generator/tree/extra/tree.py | 8 ++++---- 3 files changed, 20 insertions(+), 14 deletions(-) diff --git a/17-it-generator/tree/extra/drawtree.py b/17-it-generator/tree/extra/drawtree.py index 043e53b..522666a 100644 --- a/17-it-generator/tree/extra/drawtree.py +++ b/17-it-generator/tree/extra/drawtree.py @@ -8,15 +8,15 @@ def render_lines(tree_iter): - name, _, _ = next(tree_iter) - yield name + cls, _, _ = next(tree_iter) + yield cls.__name__ prefix = '' - for name, level, last in tree_iter: + for cls, level, last in tree_iter: prefix = prefix[:4 * (level-1)] prefix = prefix.replace(TEE, PIPE).replace(ELBOW, SP*4) prefix += ELBOW if last else TEE - yield prefix + name + yield prefix + cls.__name__ def draw(cls): diff --git a/17-it-generator/tree/extra/test_tree.py b/17-it-generator/tree/extra/test_tree.py index 4c6404c..b06cf3b 100644 --- a/17-it-generator/tree/extra/test_tree.py +++ b/17-it-generator/tree/extra/test_tree.py @@ -4,7 +4,8 @@ def test_1_level(): class One: pass expected = [('One', 0, True)] - result = list(tree(One)) + result = [(cls.__name__, level, last) + for cls, level, last in tree(One)] assert expected == result @@ -17,7 +18,8 @@ class Leaf2(Branch): pass ('Leaf1', 1, False), ('Leaf2', 1, True), ] - result = list(tree(Branch)) + result = [(cls.__name__, level, last) + for cls, level, last in tree(Branch)] assert expected == result @@ -30,7 +32,8 @@ class Z(Y): pass ('Y', 1, True), ('Z', 2, True), ] - result = list(tree(X)) + result = [(cls.__name__, level, last) + for cls, level, last in tree(X)] assert expected == result @@ -46,7 +49,8 @@ class Level3(Level2): pass ('Level3', 3, True), ] - result = list(tree(Level0)) + result = [(cls.__name__, level, last) + for cls, level, last in tree(Level0)] assert expected == result @@ -68,7 +72,8 @@ class D2(C1): pass ('C2', 2, True), ] - result = list(tree(A)) + result = [(cls.__name__, level, last) + for cls, level, last in tree(A)] assert expected == result @@ -83,7 +88,8 @@ class Root: pass expected.append((name, level, True)) parent = cls - result = list(tree(Root)) + result = [(cls.__name__, level, last) + for cls, level, last in tree(Root)] assert len(result) == level_count assert result[0] == ('Root', 0, True) assert result[-1] == ('Sub99', 99, True) diff --git a/17-it-generator/tree/extra/tree.py b/17-it-generator/tree/extra/tree.py index 7169a28..aeebd37 100644 --- a/17-it-generator/tree/extra/tree.py +++ b/17-it-generator/tree/extra/tree.py @@ -1,5 +1,5 @@ -def tree(cls, level=0, last_in_level=True): - yield cls.__name__, level, last_in_level +def tree(cls, level=0, last_sibling=True): + yield cls, level, last_sibling subclasses = cls.__subclasses__() if subclasses: last = subclasses[-1] @@ -8,9 +8,9 @@ def tree(cls, level=0, last_in_level=True): def display(cls): - for cls_name, level, _ in tree(cls): + for cls, level, _ in tree(cls): indent = ' ' * 4 * level - print(f'{indent}{cls_name}') + print(f'{indent}{cls.__name__}') if __name__ == '__main__': From 7c155cb33725666349867020ec022b78aa3e531e Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Mon, 18 Oct 2021 15:22:46 -0300 Subject: [PATCH 128/166] draw tree of HTTPX exceptions --- .../getflags/httpx-error-tree/drawtree.py | 35 +++++++++++++++++++ .../getflags/httpx-error-tree/tree.py | 24 +++++++++++++ 2 files changed, 59 insertions(+) create mode 100755 20-executors/getflags/httpx-error-tree/drawtree.py create mode 100755 20-executors/getflags/httpx-error-tree/tree.py diff --git a/20-executors/getflags/httpx-error-tree/drawtree.py b/20-executors/getflags/httpx-error-tree/drawtree.py new file mode 100755 index 0000000..f2d9fea --- /dev/null +++ b/20-executors/getflags/httpx-error-tree/drawtree.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 + +from tree import tree + + +SP = '\N{SPACE}' +HLIN = '\N{BOX DRAWINGS LIGHT HORIZONTAL}' # ─ +ELBOW = f'\N{BOX DRAWINGS LIGHT UP AND RIGHT}{HLIN*2}{SP}' # └── +TEE = f'\N{BOX DRAWINGS LIGHT VERTICAL AND RIGHT}{HLIN*2}{SP}' # ├── +PIPE = f'\N{BOX DRAWINGS LIGHT VERTICAL}{SP*3}' # │ + + +def cls_name(cls): + module = 'builtins.' if cls.__module__ == 'builtins' else '' + return module + cls.__name__ + +def render_lines(tree_iter): + cls, _, _ = next(tree_iter) + yield cls_name(cls) + prefix = '' + + for cls, level, last in tree_iter: + prefix = prefix[:4 * (level-1)] + prefix = prefix.replace(TEE, PIPE).replace(ELBOW, SP*4) + prefix += ELBOW if last else TEE + yield prefix + cls_name(cls) + + +def draw(cls): + for line in render_lines(tree(cls)): + print(line) + + +if __name__ == '__main__': + draw(Exception) diff --git a/20-executors/getflags/httpx-error-tree/tree.py b/20-executors/getflags/httpx-error-tree/tree.py new file mode 100755 index 0000000..9b905f2 --- /dev/null +++ b/20-executors/getflags/httpx-error-tree/tree.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python3 + +import httpx # make httpx classes available to .__subclasses__() + + +def tree(cls, level=0, last_sibling=True): + yield cls, level, last_sibling + subclasses = [c for c in cls.__subclasses__() + if c.__module__ == 'httpx' or c is RuntimeError] + if subclasses: + last = subclasses[-1] + for sub_cls in subclasses: + yield from tree(sub_cls, level+1, sub_cls is last) + + +def display(cls): + for cls, level, _ in tree(cls): + indent = ' ' * 4 * level + module = 'builtins.' if cls.__module__ == 'builtins' else '' + print(f'{indent}{module}{cls.__name__}') + + +if __name__ == '__main__': + display(Exception) From ce7d45154ff29048e2002e4f4491f199807e3281 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Mon, 18 Oct 2021 16:42:59 -0300 Subject: [PATCH 129/166] draw tree of HTTPX exceptions --- 20-executors/getflags/httpx-error-tree/tree.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/20-executors/getflags/httpx-error-tree/tree.py b/20-executors/getflags/httpx-error-tree/tree.py index 9b905f2..7b82975 100755 --- a/20-executors/getflags/httpx-error-tree/tree.py +++ b/20-executors/getflags/httpx-error-tree/tree.py @@ -5,12 +5,13 @@ def tree(cls, level=0, last_sibling=True): yield cls, level, last_sibling - subclasses = [c for c in cls.__subclasses__() - if c.__module__ == 'httpx' or c is RuntimeError] + # get RuntimeError and exceptions defined in httpx + subclasses = [sub for sub in cls.__subclasses__() + if sub is RuntimeError or sub.__module__ == 'httpx'] if subclasses: last = subclasses[-1] - for sub_cls in subclasses: - yield from tree(sub_cls, level+1, sub_cls is last) + for sub in subclasses: + yield from tree(sub, level+1, sub is last) def display(cls): From 07083b8240c9886bb0b4615c81f4d89ecaede1ba Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Mon, 18 Oct 2021 16:45:15 -0300 Subject: [PATCH 130/166] draw tree of HTTPX exceptions --- 20-executors/getflags/httpx-error-tree/tree.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/20-executors/getflags/httpx-error-tree/tree.py b/20-executors/getflags/httpx-error-tree/tree.py index 7b82975..eff7870 100755 --- a/20-executors/getflags/httpx-error-tree/tree.py +++ b/20-executors/getflags/httpx-error-tree/tree.py @@ -10,8 +10,8 @@ def tree(cls, level=0, last_sibling=True): if sub is RuntimeError or sub.__module__ == 'httpx'] if subclasses: last = subclasses[-1] - for sub in subclasses: - yield from tree(sub, level+1, sub is last) + for sub in subclasses: + yield from tree(sub, level+1, sub is last) def display(cls): From f5e3cb8ad32bdf183cee85a17c6105d51538b751 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Fri, 22 Oct 2021 14:59:04 -0300 Subject: [PATCH 131/166] minor refactorings --- 17-it-generator/tree/classtree/classtree.py | 75 ++++++++ .../tree/classtree/classtree_test.py | 181 ++++++++++++++++++ 17-it-generator/tree/extra/drawtree.py | 30 ++- 3 files changed, 279 insertions(+), 7 deletions(-) create mode 100755 17-it-generator/tree/classtree/classtree.py create mode 100644 17-it-generator/tree/classtree/classtree_test.py diff --git a/17-it-generator/tree/classtree/classtree.py b/17-it-generator/tree/classtree/classtree.py new file mode 100755 index 0000000..56bbec3 --- /dev/null +++ b/17-it-generator/tree/classtree/classtree.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python3 + +from importlib import import_module +import sys + + +SP = '\N{SPACE}' +HLIN = '\N{BOX DRAWINGS LIGHT HORIZONTAL}' * 2 + SP # ── +VLIN = '\N{BOX DRAWINGS LIGHT VERTICAL}' + SP * 3 # │ +TEE = '\N{BOX DRAWINGS LIGHT VERTICAL AND RIGHT}' + HLIN # ├── +ELBOW = '\N{BOX DRAWINGS LIGHT UP AND RIGHT}' + HLIN # └── + + +def subclasses(cls): + try: + return cls.__subclasses__() + except TypeError: # handle the `type` type + return cls.__subclasses__(cls) + + +def tree(cls, level=0, last_sibling=True): + yield cls, level, last_sibling + chidren = subclasses(cls) + if chidren: + last = chidren[-1] + for child in chidren: + yield from tree(child, level + 1, child is last) + + +def render_lines(tree_generator): + cls, _, _ = next(tree_generator) + yield cls.__name__ + prefix = '' + for cls, level, last in tree_generator: + prefix = prefix[: 4 * (level - 1)] + prefix = prefix.replace(TEE, VLIN).replace(ELBOW, SP * 4) + prefix += ELBOW if last else TEE + yield prefix + cls.__name__ + + +def draw(cls): + for line in render_lines(tree(cls)): + print(line) + + +def parse(name): + if '.' in name: + return name.rsplit('.', 1) + else: + return 'builtins', name + + +def main(name): + module_name, cls_name = parse(name) + try: + cls = getattr(import_module(module_name), cls_name) + except ModuleNotFoundError: + print(f'*** Could not import {module_name!r}.') + except AttributeError: + print(f'*** {cls_name!r} not found in {module_name!r}.') + else: + if isinstance(cls, type): + draw(cls) + else: + print(f'*** {cls_name!r} is not a class.') + + +if __name__ == '__main__': + if len(sys.argv) == 2: + main(sys.argv[1]) + else: + print('Usage:' + f'\t{sys.argv[0]} Class # for builtin classes\n' + f'\t{sys.argv[0]} package.Class # for other classes' + ) diff --git a/17-it-generator/tree/classtree/classtree_test.py b/17-it-generator/tree/classtree/classtree_test.py new file mode 100644 index 0000000..4535747 --- /dev/null +++ b/17-it-generator/tree/classtree/classtree_test.py @@ -0,0 +1,181 @@ +from textwrap import dedent +from typing import SupportsBytes +from classtree import tree, render_lines, main, subclasses + +from abc import ABCMeta + + +def test_subclasses(): + result = subclasses(UnicodeError) + assert set(result) >= {UnicodeEncodeError, UnicodeDecodeError} + + +def test_subclasses_of_type(): + """ + The `type` class is a special case because `type.__subclasses__()` + is an unbound method when called on it, so we must call it as + `type.__subclasses__(type)` just for `type`. + + This test does not verify the full list of results, but just + checks that `abc.ABCMeta` is included, because that's the only + subclass of `type` (i.e. metaclass) I we get when I run + `$ classtree.py type` at the command line. + + However, the Python console and `pytest` both load other modules, + so `subclasses` may find more subclasses of `type`—for example, + `enum.EnumMeta`. + """ + result = subclasses(type) + assert ABCMeta in result + + +def test_tree_1_level(): + result = list(tree(TabError)) + assert result == [(TabError, 0, True)] + + +def test_tree_2_levels(): + result = list(tree(IndentationError)) + assert result == [ + (IndentationError, 0, True), + (TabError, 1, True), + ] + + +def test_render_lines_1_level(): + result = list(render_lines(tree(TabError))) + assert result == ['TabError'] + + +def test_render_lines_2_levels_1_leaf(): + result = list(render_lines(tree(IndentationError))) + expected = [ + 'IndentationError', + '└── TabError', + ] + assert expected == result + + +def test_render_lines_3_levels_1_leaf(): + class X: pass + class Y(X): pass + class Z(Y): pass + result = list(render_lines(tree(X))) + expected = [ + 'X', + '└── Y', + ' └── Z', + ] + assert expected == result + + +def test_render_lines_4_levels_1_leaf(): + class Level0: pass + class Level1(Level0): pass + class Level2(Level1): pass + class Level3(Level2): pass + + result = list(render_lines(tree(Level0))) + expected = [ + 'Level0', + '└── Level1', + ' └── Level2', + ' └── Level3', + ] + assert expected == result + + +def test_render_lines_2_levels_2_leaves(): + class Branch: pass + class Leaf1(Branch): pass + class Leaf2(Branch): pass + result = list(render_lines(tree(Branch))) + expected = [ + 'Branch', + '├── Leaf1', + '└── Leaf2', + ] + assert expected == result + + +def test_render_lines_3_levels_2_leaves_dedent(): + class A: pass + class B(A): pass + class C(B): pass + class D(A): pass + class E(D): pass + + result = list(render_lines(tree(A))) + expected = [ + 'A', + '├── B', + '│ └── C', + '└── D', + ' └── E', + ] + assert expected == result + + +def test_render_lines_4_levels_4_leaves_dedent(): + class A: pass + class B1(A): pass + class C1(B1): pass + class D1(C1): pass + class D2(C1): pass + class C2(B1): pass + class B2(A): pass + expected = [ + 'A', + '├── B1', + '│ ├── C1', + '│ │ ├── D1', + '│ │ └── D2', + '│ └── C2', + '└── B2', + ] + + result = list(render_lines(tree(A))) + assert expected == result + + +def test_main_simple(capsys): + main('IndentationError') + expected = dedent(""" + IndentationError + └── TabError + """).lstrip() + captured = capsys.readouterr() + assert captured.out == expected + + +def test_main_dotted(capsys): + main('collections.abc.Sequence') + expected = dedent(""" + Sequence + ├── ByteString + ├── MutableSequence + │ └── UserList + """).lstrip() + captured = capsys.readouterr() + assert captured.out.startswith(expected) + + +def test_main_class_not_found(capsys): + main('NoSuchClass') + expected = "*** 'NoSuchClass' not found in 'builtins'.\n" + captured = capsys.readouterr() + assert captured.out == expected + + +def test_main_module_not_found(capsys): + main('nosuch.module') + expected = "*** Could not import 'nosuch'.\n" + captured = capsys.readouterr() + assert captured.out == expected + + +def test_main_not_a_class(capsys): + main('collections.abc') + expected = "*** 'abc' is not a class.\n" + captured = capsys.readouterr() + assert captured.out == expected diff --git a/17-it-generator/tree/extra/drawtree.py b/17-it-generator/tree/extra/drawtree.py index 522666a..b74cd33 100644 --- a/17-it-generator/tree/extra/drawtree.py +++ b/17-it-generator/tree/extra/drawtree.py @@ -1,10 +1,26 @@ from tree import tree -SP = '\N{SPACE}' -HLIN = '\N{BOX DRAWINGS LIGHT HORIZONTAL}' # ─ -ELBOW = f'\N{BOX DRAWINGS LIGHT UP AND RIGHT}{HLIN*2}{SP}' # └── -TEE = f'\N{BOX DRAWINGS LIGHT VERTICAL AND RIGHT}{HLIN*2}{SP}' # ├── -PIPE = f'\N{BOX DRAWINGS LIGHT VERTICAL}{SP*3}' # │ +SP = '\N{SPACE}' +HLIN = '\N{BOX DRAWINGS LIGHT HORIZONTAL}' * 2 + SP # ── +VLIN = '\N{BOX DRAWINGS LIGHT VERTICAL}' + SP * 3 # │ +TEE = '\N{BOX DRAWINGS LIGHT VERTICAL AND RIGHT}' + HLIN # ├── +ELBOW = '\N{BOX DRAWINGS LIGHT UP AND RIGHT}' + HLIN # └── + + +def subclasses(cls): + try: + return cls.__subclasses__() + except TypeError: # handle the `type` type + return cls.__subclasses__(cls) + + +def tree(cls, level=0, last_sibling=True): + yield cls, level, last_sibling + children = subclasses(cls) + if children: + last = children[-1] + for child in children: + yield from tree(child, level+1, child is last) def render_lines(tree_iter): @@ -13,8 +29,8 @@ def render_lines(tree_iter): prefix = '' for cls, level, last in tree_iter: - prefix = prefix[:4 * (level-1)] - prefix = prefix.replace(TEE, PIPE).replace(ELBOW, SP*4) + prefix = prefix[:4 * (level - 1)] + prefix = prefix.replace(TEE, VLIN).replace(ELBOW, SP * 4) prefix += ELBOW if last else TEE yield prefix + cls.__name__ From 80f7f84274a47579e59c29a4657691525152c9d5 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Fri, 12 Nov 2021 11:33:12 -0300 Subject: [PATCH 132/166] 2e reviewed manuscript --- 02-array-seq/lispy/py3.10/lis.py | 10 +- 11-pythonic-obj/vector2d_v3.py | 4 +- 11-pythonic-obj/vector2d_v3_prophash.py | 4 +- 11-pythonic-obj/vector2d_v3_slots.py | 4 +- 18-with-match/lispy/py3.10/examples_test.py | 6 +- 18-with-match/lispy/py3.10/lis.py | 2 +- 18-with-match/lispy/py3.9/lis.py | 2 +- 20-executors/getflags/flags2_asyncio.py | 4 +- .../getflags/flags2_asyncio_executor.py | 12 +- 20-executors/getflags/flags3_asyncio.py | 119 ++++++++++++++++++ 20-executors/getflags/flags_asyncio.py | 12 +- .../getflags/httpx-error-tree/tree.py | 1 + 20-executors/getflags/tree.py | 38 ------ 21-async/mojifinder/tcp_mojifinder.py | 24 ++-- 21-async/mojifinder/web_mojifinder.py | 13 +- 22-dyn-attr-prop/bulkfood/bulkfood_v2prop.py | 4 +- 22-dyn-attr-prop/oscon/explore0.py | 11 +- 22-dyn-attr-prop/oscon/explore1.py | 7 +- 22-dyn-attr-prop/oscon/explore2.py | 7 +- 22-dyn-attr-prop/oscon/schedule_v1.py | 3 +- 22-dyn-attr-prop/oscon/schedule_v2.py | 10 +- 22-dyn-attr-prop/oscon/schedule_v3.py | 12 +- 22-dyn-attr-prop/oscon/schedule_v4.py | 12 +- 22-dyn-attr-prop/oscon/schedule_v4_hasattr.py | 12 +- 22-dyn-attr-prop/oscon/schedule_v5.py | 10 +- 23-descriptor/bulkfood/model_v5.py | 4 +- 23-descriptor/descriptorkinds.py | 25 ++-- .../checked/metaclass/checkedlib.py | 4 +- 24-class-metaprog/factories.py | 5 +- 24-class-metaprog/metabunch/from3.6/bunch.py | 5 +- .../metabunch/from3.6/bunch_test.py | 2 +- 24-class-metaprog/timeslice.py | 91 ++++++++++++++ 32 files changed, 323 insertions(+), 156 deletions(-) create mode 100755 20-executors/getflags/flags3_asyncio.py delete mode 100644 20-executors/getflags/tree.py create mode 100644 24-class-metaprog/timeslice.py diff --git a/02-array-seq/lispy/py3.10/lis.py b/02-array-seq/lispy/py3.10/lis.py index 150fca7..88d9345 100755 --- a/02-array-seq/lispy/py3.10/lis.py +++ b/02-array-seq/lispy/py3.10/lis.py @@ -84,7 +84,7 @@ def standard_env() -> Environment: '-': op.sub, '*': op.mul, '/': op.truediv, - '//': op.floordiv, + 'quotient': op.floordiv, '>': op.gt, '<': op.lt, '>=': op.ge, @@ -149,7 +149,7 @@ def evaluate(exp: Expression, env: Environment) -> Any: # end::EVAL_MATCH_TOP[] case int(x) | float(x): return x - case Symbol(name): + case Symbol() as name: return env[name] # tag::EVAL_MATCH_MIDDLE[] case ['quote', x]: # <1> @@ -161,12 +161,12 @@ def evaluate(exp: Expression, env: Environment) -> Any: return evaluate(alternative, env) case ['lambda', [*parms], *body] if body: # <3> return Procedure(parms, body, env) - case ['define', Symbol(name), value_exp]: # <4> + case ['define', Symbol() as name, value_exp]: # <4> env[name] = evaluate(value_exp, env) # end::EVAL_MATCH_MIDDLE[] - case ['define', [Symbol(name), *parms], *body] if body: + case ['define', [Symbol() as name, *parms], *body] if body: env[name] = Procedure(parms, body, env) - case ['set!', Symbol(name), value_exp]: + case ['set!', Symbol() as name, value_exp]: env.change(name, evaluate(value_exp, env)) case [func_exp, *args] if func_exp not in KEYWORDS: proc = evaluate(func_exp, env) diff --git a/11-pythonic-obj/vector2d_v3.py b/11-pythonic-obj/vector2d_v3.py index 9ee716c..0edffb7 100644 --- a/11-pythonic-obj/vector2d_v3.py +++ b/11-pythonic-obj/vector2d_v3.py @@ -79,8 +79,6 @@ >>> v1 = Vector2d(3, 4) >>> v2 = Vector2d(3.1, 4.2) - >>> hash(v1), hash(v2) - (7, 384307168202284039) >>> len({v1, v2}) 2 @@ -124,7 +122,7 @@ def __eq__(self, other): return tuple(self) == tuple(other) def __hash__(self): - return hash(self.x) ^ hash(self.y) + return hash((self.x, self.y)) def __abs__(self): return math.hypot(self.x, self.y) diff --git a/11-pythonic-obj/vector2d_v3_prophash.py b/11-pythonic-obj/vector2d_v3_prophash.py index 6d7dceb..1652cd4 100644 --- a/11-pythonic-obj/vector2d_v3_prophash.py +++ b/11-pythonic-obj/vector2d_v3_prophash.py @@ -81,8 +81,6 @@ >>> v1 = Vector2d(3, 4) >>> v2 = Vector2d(3.1, 4.2) - >>> hash(v1), hash(v2) - (7, 384307168202284039) >>> len({v1, v2}) 2 @@ -131,7 +129,7 @@ def __eq__(self, other): # tag::VECTOR_V3_HASH[] def __hash__(self): - return hash(self.x) ^ hash(self.y) + return hash((self.x, self.y)) # end::VECTOR_V3_HASH[] def __abs__(self): diff --git a/11-pythonic-obj/vector2d_v3_slots.py b/11-pythonic-obj/vector2d_v3_slots.py index c48fb5b..89da973 100644 --- a/11-pythonic-obj/vector2d_v3_slots.py +++ b/11-pythonic-obj/vector2d_v3_slots.py @@ -78,8 +78,6 @@ >>> v1 = Vector2d(3, 4) >>> v2 = Vector2d(3.1, 4.2) - >>> hash(v1), hash(v2) - (7, 384307168202284039) >>> len({v1, v2}) 2 @@ -126,7 +124,7 @@ def __eq__(self, other): return tuple(self) == tuple(other) def __hash__(self): - return hash(self.x) ^ hash(self.y) + return hash((self.x, self.y)) def __abs__(self): return math.hypot(self.x, self.y) diff --git a/18-with-match/lispy/py3.10/examples_test.py b/18-with-match/lispy/py3.10/examples_test.py index d207df0..7025dad 100644 --- a/18-with-match/lispy/py3.10/examples_test.py +++ b/18-with-match/lispy/py3.10/examples_test.py @@ -27,11 +27,13 @@ >>> inner_env = {'a': 2} >>> outer_env = {'a': 0, 'b': 1} >>> env = Environment(inner_env, outer_env) ->>> env['a'] = 111 # <1> +>>> env['a'] # <1> +2 +>>> env['a'] = 111 # <2> >>> env['c'] = 222 >>> env Environment({'a': 111, 'c': 222}, {'a': 0, 'b': 1}) ->>> env.change('b', 333) # <2> +>>> env.change('b', 333) # <3> >>> env Environment({'a': 111, 'c': 222}, {'a': 0, 'b': 333}) diff --git a/18-with-match/lispy/py3.10/lis.py b/18-with-match/lispy/py3.10/lis.py index 04cc28d..f2d982c 100755 --- a/18-with-match/lispy/py3.10/lis.py +++ b/18-with-match/lispy/py3.10/lis.py @@ -65,7 +65,7 @@ def parse_atom(token: str) -> Atom: class Environment(ChainMap[Symbol, Any]): "A ChainMap that allows changing an item in-place." - def change(self, key: Symbol, value: object) -> None: + def change(self, key: Symbol, value: Any) -> None: "Find where key is defined and change the value there." for map in self.maps: if key in map: diff --git a/18-with-match/lispy/py3.9/lis.py b/18-with-match/lispy/py3.9/lis.py index e868a14..e467211 100644 --- a/18-with-match/lispy/py3.9/lis.py +++ b/18-with-match/lispy/py3.9/lis.py @@ -62,7 +62,7 @@ def parse_atom(token: str) -> Atom: class Environment(ChainMap[Symbol, Any]): "A ChainMap that allows changing an item in-place." - def change(self, key: Symbol, value: object) -> None: + def change(self, key: Symbol, value: Any) -> None: "Find where key is defined and change the value there." for map in self.maps: if key in map: diff --git a/20-executors/getflags/flags2_asyncio.py b/20-executors/getflags/flags2_asyncio.py index a922db5..4f1849a 100755 --- a/20-executors/getflags/flags2_asyncio.py +++ b/20-executors/getflags/flags2_asyncio.py @@ -37,7 +37,7 @@ async def download_one(client: httpx.AsyncClient, try: async with semaphore: # <3> image = await get_flag(client, base_url, cc) - except httpx.HTTPStatusError as exc: # <5> + except httpx.HTTPStatusError as exc: # <4> res = exc.response if res.status_code == HTTPStatus.NOT_FOUND: status = DownloadStatus.NOT_FOUND @@ -45,7 +45,7 @@ async def download_one(client: httpx.AsyncClient, else: raise else: - await asyncio.to_thread(save_flag, image, f'{cc}.gif') # <6> + await asyncio.to_thread(save_flag, image, f'{cc}.gif') # <5> status = DownloadStatus.OK msg = 'OK' if verbose and msg: diff --git a/20-executors/getflags/flags2_asyncio_executor.py b/20-executors/getflags/flags2_asyncio_executor.py index 752b60f..62516b3 100755 --- a/20-executors/getflags/flags2_asyncio_executor.py +++ b/20-executors/getflags/flags2_asyncio_executor.py @@ -22,23 +22,23 @@ MAX_CONCUR_REQ = 1000 -async def get_flag(session: httpx.AsyncClient, # <2> +async def get_flag(client: httpx.AsyncClient, # <2> base_url: str, cc: str) -> bytes: url = f'{base_url}/{cc}/{cc}.gif'.lower() - resp = await session.get(url, timeout=3.1, follow_redirects=True) # <3> + resp = await client.get(url, timeout=3.1, follow_redirects=True) # <3> resp.raise_for_status() return resp.content -async def download_one(session: httpx.AsyncClient, +async def download_one(client: httpx.AsyncClient, cc: str, base_url: str, semaphore: asyncio.Semaphore, verbose: bool) -> DownloadStatus: try: async with semaphore: - image = await get_flag(session, base_url, cc) + image = await get_flag(client, base_url, cc) except httpx.HTTPStatusError as exc: res = exc.response if res.status_code == HTTPStatus.NOT_FOUND: @@ -64,8 +64,8 @@ async def supervisor(cc_list: list[str], concur_req: int) -> Counter[DownloadStatus]: # <1> counter: Counter[DownloadStatus] = Counter() semaphore = asyncio.Semaphore(concur_req) # <2> - async with httpx.AsyncClient() as session: - to_do = [download_one(session, cc, base_url, semaphore, verbose) + async with httpx.AsyncClient() as client: + to_do = [download_one(client, cc, base_url, semaphore, verbose) for cc in sorted(cc_list)] # <3> to_do_iter = asyncio.as_completed(to_do) # <4> if not verbose: diff --git a/20-executors/getflags/flags3_asyncio.py b/20-executors/getflags/flags3_asyncio.py new file mode 100755 index 0000000..9554dd2 --- /dev/null +++ b/20-executors/getflags/flags3_asyncio.py @@ -0,0 +1,119 @@ +#!/usr/bin/env python3 + +"""Download flags of countries (with error handling). + +asyncio async/await version + +""" +# tag::FLAGS2_ASYNCIO_TOP[] +import asyncio +from collections import Counter +from http import HTTPStatus +from pathlib import Path + +import httpx +import tqdm # type: ignore + +from flags2_common import main, DownloadStatus, save_flag + +# low concurrency default to avoid errors from remote site, +# such as 503 - Service Temporarily Unavailable +DEFAULT_CONCUR_REQ = 5 +MAX_CONCUR_REQ = 1000 + +async def get_flag(client: httpx.AsyncClient, # <1> + base_url: str, + cc: str) -> bytes: + url = f'{base_url}/{cc}/{cc}.gif'.lower() + resp = await client.get(url, timeout=3.1, follow_redirects=True) # <2> + resp.raise_for_status() + return resp.content + +# tag::FLAGS3_ASYNCIO_GET_COUNTRY[] +async def get_country(client: httpx.AsyncClient, + base_url: str, + cc: str) -> str: # <1> + url = f'{base_url}/{cc}/metadata.json'.lower() + resp = await client.get(url, timeout=3.1, follow_redirects=True) + resp.raise_for_status() + metadata = resp.json() # <2> + return metadata['country'] # <3> +# end::FLAGS3_ASYNCIO_GET_COUNTRY[] + +# tag::FLAGS3_ASYNCIO_DOWNLOAD_ONE[] +async def download_one(client: httpx.AsyncClient, + cc: str, + base_url: str, + semaphore: asyncio.Semaphore, + verbose: bool) -> DownloadStatus: + try: + async with semaphore: # <1> + image = await get_flag(client, base_url, cc) + async with semaphore: # <2> + country = await get_country(client, base_url, cc) + except httpx.HTTPStatusError as exc: + res = exc.response + if res.status_code == HTTPStatus.NOT_FOUND: + status = DownloadStatus.NOT_FOUND + msg = f'not found: {res.url}' + else: + raise + else: + filename = country.replace(' ', '_') # <3> + await asyncio.to_thread(save_flag, image, f'{filename}.gif') + status = DownloadStatus.OK + msg = 'OK' + if verbose and msg: + print(cc, msg) + return status +# end::FLAGS3_ASYNCIO_DOWNLOAD_ONE[] + +# tag::FLAGS2_ASYNCIO_START[] +async def supervisor(cc_list: list[str], + base_url: str, + verbose: bool, + concur_req: int) -> Counter[DownloadStatus]: # <1> + counter: Counter[DownloadStatus] = Counter() + semaphore = asyncio.Semaphore(concur_req) # <2> + async with httpx.AsyncClient() as client: + to_do = [download_one(client, cc, base_url, semaphore, verbose) + for cc in sorted(cc_list)] # <3> + to_do_iter = asyncio.as_completed(to_do) # <4> + if not verbose: + to_do_iter = tqdm.tqdm(to_do_iter, total=len(cc_list)) # <5> + error: httpx.HTTPError | None = None # <6> + for coro in to_do_iter: # <7> + try: + status = await coro # <8> + except httpx.HTTPStatusError as exc: + error_msg = 'HTTP error {resp.status_code} - {resp.reason_phrase}' + error_msg = error_msg.format(resp=exc.response) + error = exc # <9> + except httpx.RequestError as exc: + error_msg = f'{exc} {type(exc)}'.strip() + error = exc # <10> + except KeyboardInterrupt: + break + + if error: + status = DownloadStatus.ERROR # <11> + if verbose: + url = str(error.request.url) # <12> + cc = Path(url).stem.upper() # <13> + print(f'{cc} error: {error_msg}') + counter[status] += 1 + + return counter + +def download_many(cc_list: list[str], + base_url: str, + verbose: bool, + concur_req: int) -> Counter[DownloadStatus]: + coro = supervisor(cc_list, base_url, verbose, concur_req) + counts = asyncio.run(coro) # <14> + + return counts + +if __name__ == '__main__': + main(download_many, DEFAULT_CONCUR_REQ, MAX_CONCUR_REQ) +# end::FLAGS2_ASYNCIO_START[] diff --git a/20-executors/getflags/flags_asyncio.py b/20-executors/getflags/flags_asyncio.py index ba28065..588d166 100755 --- a/20-executors/getflags/flags_asyncio.py +++ b/20-executors/getflags/flags_asyncio.py @@ -17,15 +17,15 @@ from flags import BASE_URL, save_flag, main # <2> -async def download_one(session: AsyncClient, cc: str): # <3> - image = await get_flag(session, cc) +async def download_one(client: AsyncClient, cc: str): # <3> + image = await get_flag(client, cc) save_flag(image, f'{cc}.gif') print(cc, end=' ', flush=True) return cc -async def get_flag(session: AsyncClient, cc: str) -> bytes: # <4> +async def get_flag(client: AsyncClient, cc: str) -> bytes: # <4> url = f'{BASE_URL}/{cc}/{cc}.gif'.lower() - resp = await session.get(url, timeout=6.1, + resp = await client.get(url, timeout=6.1, follow_redirects=True) # <5> return resp.read() # <6> # end::FLAGS_ASYNCIO_TOP[] @@ -35,8 +35,8 @@ def download_many(cc_list: list[str]) -> int: # <1> return asyncio.run(supervisor(cc_list)) # <2> async def supervisor(cc_list: list[str]) -> int: - async with AsyncClient() as session: # <3> - to_do = [download_one(session, cc) + async with AsyncClient() as client: # <3> + to_do = [download_one(client, cc) for cc in sorted(cc_list)] # <4> res = await asyncio.gather(*to_do) # <5> diff --git a/20-executors/getflags/httpx-error-tree/tree.py b/20-executors/getflags/httpx-error-tree/tree.py index eff7870..950bbfe 100755 --- a/20-executors/getflags/httpx-error-tree/tree.py +++ b/20-executors/getflags/httpx-error-tree/tree.py @@ -5,6 +5,7 @@ def tree(cls, level=0, last_sibling=True): yield cls, level, last_sibling + # get RuntimeError and exceptions defined in httpx subclasses = [sub for sub in cls.__subclasses__() if sub is RuntimeError or sub.__module__ == 'httpx'] diff --git a/20-executors/getflags/tree.py b/20-executors/getflags/tree.py deleted file mode 100644 index 4f65697..0000000 --- a/20-executors/getflags/tree.py +++ /dev/null @@ -1,38 +0,0 @@ -import httpx - -def tree(cls, level=0): - yield cls.__name__, level - for sub_cls in cls.__subclasses__(): - yield from tree(sub_cls, level+1) - - -def display(cls): - for cls_name, level in tree(cls): - indent = ' ' * 4 * level - print(f'{indent}{cls_name}') - - -def find_roots(module): - exceptions = [] - for name in dir(module): - obj = getattr(module, name) - if isinstance(obj, type) and issubclass(obj, BaseException): - exceptions.append(obj) - roots = [] - for exc in exceptions: - root = True - for other in exceptions: - if exc is not other and issubclass(exc, other): - root = False - break - if root: - roots.append(exc) - return roots - - -def main(): - for exc in find_roots(httpx): - display(exc) - -if __name__ == '__main__': - main() diff --git a/21-async/mojifinder/tcp_mojifinder.py b/21-async/mojifinder/tcp_mojifinder.py index 8ff4e50..4da7188 100755 --- a/21-async/mojifinder/tcp_mojifinder.py +++ b/21-async/mojifinder/tcp_mojifinder.py @@ -14,26 +14,28 @@ async def finder(index: InvertedIndex, # <2> reader: asyncio.StreamReader, - writer: asyncio.StreamWriter): + writer: asyncio.StreamWriter) -> None: client = writer.get_extra_info('peername') # <3> while True: # <4> writer.write(PROMPT) # can't await! # <5> await writer.drain() # must await! # <6> data = await reader.readline() # <7> + if not data: # <8> + break try: - query = data.decode().strip() # <8> - except UnicodeDecodeError: # <9> + query = data.decode().strip() # <9> + except UnicodeDecodeError: # <10> query = '\x00' - print(f' From {client}: {query!r}') # <10> + print(f' From {client}: {query!r}') # <11> if query: - if ord(query[:1]) < 32: # <11> + if ord(query[:1]) < 32: # <12> break - results = await search(query, index, writer) # <12> - print(f' To {client}: {results} results.') # <13> + results = await search(query, index, writer) # <13> + print(f' To {client}: {results} results.') # <14> - writer.close() # <14> - await writer.wait_closed() # <15> - print(f'Close {client}.') # <16> + writer.close() # <15> + await writer.wait_closed() # <16> + print(f'Close {client}.') # <17> # end::TCP_MOJIFINDER_TOP[] # tag::TCP_MOJIFINDER_SEARCH[] @@ -52,7 +54,7 @@ async def search(query: str, # <1> # end::TCP_MOJIFINDER_SEARCH[] # tag::TCP_MOJIFINDER_MAIN[] -async def supervisor(index: InvertedIndex, host: str, port: int): +async def supervisor(index: InvertedIndex, host: str, port: int) -> None: server = await asyncio.start_server( # <1> functools.partial(finder, index), # <2> host, port) # <3> diff --git a/21-async/mojifinder/web_mojifinder.py b/21-async/mojifinder/web_mojifinder.py index 7e0ff96..5f0cf5c 100644 --- a/21-async/mojifinder/web_mojifinder.py +++ b/21-async/mojifinder/web_mojifinder.py @@ -7,25 +7,26 @@ from charindex import InvertedIndex -app = FastAPI( # <1> +STATIC_PATH = Path(__file__).parent.absolute() / 'static' # <1> + +app = FastAPI( # <2> title='Mojifinder Web', description='Search for Unicode characters by name.', ) -class CharName(BaseModel): # <2> +class CharName(BaseModel): # <3> char: str name: str -def init(app): # <3> +def init(app): # <4> app.state.index = InvertedIndex() - static = Path(__file__).parent.absolute() / 'static' # <4> - app.state.form = (static / 'form.html').read_text() + app.state.form = (STATIC_PATH / 'form.html').read_text() init(app) # <5> @app.get('/search', response_model=list[CharName]) # <6> async def search(q: str): # <7> - chars = app.state.index.search(q) + chars = sorted(app.state.index.search(q)) return ({'char': c, 'name': name(c)} for c in chars) # <8> @app.get('/', response_class=HTMLResponse, include_in_schema=False) diff --git a/22-dyn-attr-prop/bulkfood/bulkfood_v2prop.py b/22-dyn-attr-prop/bulkfood/bulkfood_v2prop.py index e34a7c3..b2cffab 100644 --- a/22-dyn-attr-prop/bulkfood/bulkfood_v2prop.py +++ b/22-dyn-attr-prop/bulkfood/bulkfood_v2prop.py @@ -30,8 +30,8 @@ >>> nutmeg = LineItem('Moluccan nutmeg', 8, 13.95) >>> nutmeg.weight, nutmeg.price # <1> (8, 13.95) - >>> sorted(vars(nutmeg).items()) # <2> - [('description', 'Moluccan nutmeg'), ('price', 13.95), ('weight', 8)] + >>> nutmeg.__dict__ # <2> + {'description': 'Moluccan nutmeg', 'weight': 8, 'price': 13.95} # end::LINEITEM_V2_PROP_DEMO[] diff --git a/22-dyn-attr-prop/oscon/explore0.py b/22-dyn-attr-prop/oscon/explore0.py index 1ea7843..c881d16 100644 --- a/22-dyn-attr-prop/oscon/explore0.py +++ b/22-dyn-attr-prop/oscon/explore0.py @@ -54,12 +54,15 @@ def __getattr__(self, name): # <2> except AttributeError: return FrozenJSON.build(self.__data[name]) # <4> + def __dir__(self): # <5> + return self.__data.keys() + @classmethod - def build(cls, obj): # <5> - if isinstance(obj, abc.Mapping): # <6> + def build(cls, obj): # <6> + if isinstance(obj, abc.Mapping): # <7> return cls(obj) - elif isinstance(obj, abc.MutableSequence): # <7> + elif isinstance(obj, abc.MutableSequence): # <8> return [cls.build(item) for item in obj] - else: # <8> + else: # <9> return obj # end::EXPLORE0[] diff --git a/22-dyn-attr-prop/oscon/explore1.py b/22-dyn-attr-prop/oscon/explore1.py index ecb53b0..6942079 100644 --- a/22-dyn-attr-prop/oscon/explore1.py +++ b/22-dyn-attr-prop/oscon/explore1.py @@ -62,11 +62,14 @@ def __init__(self, mapping): # end::EXPLORE1[] def __getattr__(self, name): - if hasattr(self.__data, name): + try: return getattr(self.__data, name) - else: + except AttributeError: return FrozenJSON.build(self.__data[name]) + def __dir__(self): # <5> + return self.__data.keys() + @classmethod def build(cls, obj): if isinstance(obj, abc.Mapping): diff --git a/22-dyn-attr-prop/oscon/explore2.py b/22-dyn-attr-prop/oscon/explore2.py index 8cb9348..b50d49f 100644 --- a/22-dyn-attr-prop/oscon/explore2.py +++ b/22-dyn-attr-prop/oscon/explore2.py @@ -47,8 +47,11 @@ def __init__(self, mapping): self.__data[key] = value def __getattr__(self, name): - if hasattr(self.__data, name): + try: return getattr(self.__data, name) - else: + except AttributeError: return FrozenJSON(self.__data[name]) # <4> + + def __dir__(self): + return self.__data.keys() # end::EXPLORE2[] diff --git a/22-dyn-attr-prop/oscon/schedule_v1.py b/22-dyn-attr-prop/oscon/schedule_v1.py index 1be6c41..12aab71 100644 --- a/22-dyn-attr-prop/oscon/schedule_v1.py +++ b/22-dyn-attr-prop/oscon/schedule_v1.py @@ -23,8 +23,7 @@ def __init__(self, **kwargs): self.__dict__.update(kwargs) # <1> def __repr__(self): - cls_name = self.__class__.__name__ - return f'<{cls_name} serial={self.serial!r}>' # <2> + return f'<{self.__class__.__name__} serial={self.serial!r}>' # <2> def load(path=JSON_PATH): records = {} # <3> diff --git a/22-dyn-attr-prop/oscon/schedule_v2.py b/22-dyn-attr-prop/oscon/schedule_v2.py index 35827eb..5f264f7 100644 --- a/22-dyn-attr-prop/oscon/schedule_v2.py +++ b/22-dyn-attr-prop/oscon/schedule_v2.py @@ -29,8 +29,7 @@ def __init__(self, **kwargs): self.__dict__.update(kwargs) def __repr__(self): - cls_name = self.__class__.__name__ - return f'<{cls_name} serial={self.serial!r}>' + return f'<{self.__class__.__name__} serial={self.serial!r}>' @staticmethod # <3> def fetch(key): @@ -44,10 +43,9 @@ def fetch(key): class Event(Record): # <1> def __repr__(self): - if hasattr(self, 'name'): # <2> - cls_name = self.__class__.__name__ - return f'<{cls_name} {self.name!r}>' - else: + try: + return f'<{self.__class__.__name__} {self.name!r}>' # <2> + except AttributeError: return super().__repr__() @property diff --git a/22-dyn-attr-prop/oscon/schedule_v3.py b/22-dyn-attr-prop/oscon/schedule_v3.py index 786a412..7683b2d 100644 --- a/22-dyn-attr-prop/oscon/schedule_v3.py +++ b/22-dyn-attr-prop/oscon/schedule_v3.py @@ -33,8 +33,7 @@ def __init__(self, **kwargs): self.__dict__.update(kwargs) def __repr__(self): - cls_name = self.__class__.__name__ - return f'<{cls_name} serial={self.serial!r}>' + return f'<{self.__class__.__name__} serial={self.serial!r}>' @staticmethod def fetch(key): @@ -46,11 +45,10 @@ def fetch(key): class Event(Record): def __repr__(self): - if hasattr(self, 'name'): # <3> - cls_name = self.__class__.__name__ - return f'<{cls_name} {self.name!r}>' - else: - return super().__repr__() # <4> + try: + return f'<{self.__class__.__name__} {self.name!r}>' + except AttributeError: + return super().__repr__() @property def venue(self): diff --git a/22-dyn-attr-prop/oscon/schedule_v4.py b/22-dyn-attr-prop/oscon/schedule_v4.py index a609917..5b968e1 100644 --- a/22-dyn-attr-prop/oscon/schedule_v4.py +++ b/22-dyn-attr-prop/oscon/schedule_v4.py @@ -32,8 +32,7 @@ def __init__(self, **kwargs): self.__dict__.update(kwargs) def __repr__(self): - cls_name = self.__class__.__name__ - return f'<{cls_name} serial={self.serial!r}>' + return f'<{self.__class__.__name__} serial={self.serial!r}>' @staticmethod def fetch(key): @@ -52,11 +51,10 @@ def __init__(self, **kwargs): # end::SCHEDULE4_INIT[] def __repr__(self): - if hasattr(self, 'name'): - cls_name = self.__class__.__name__ - return f'<{cls_name} {self.name!r}>' - else: - return super().__repr__() # <4> + try: + return f'<{self.__class__.__name__} {self.name!r}>' + except AttributeError: + return super().__repr__() @property def venue(self): diff --git a/22-dyn-attr-prop/oscon/schedule_v4_hasattr.py b/22-dyn-attr-prop/oscon/schedule_v4_hasattr.py index ac5d5de..abc72bf 100644 --- a/22-dyn-attr-prop/oscon/schedule_v4_hasattr.py +++ b/22-dyn-attr-prop/oscon/schedule_v4_hasattr.py @@ -31,8 +31,7 @@ def __init__(self, **kwargs): self.__dict__.update(kwargs) def __repr__(self): - cls_name = self.__class__.__name__ - return f'<{cls_name} serial={self.serial!r}>' + return f'<{self.__class__.__name__} serial={self.serial!r}>' @staticmethod def fetch(key): @@ -44,11 +43,10 @@ def fetch(key): class Event(Record): def __repr__(self): - if hasattr(self, 'name'): - cls_name = self.__class__.__name__ - return f'<{cls_name} {self.name!r}>' - else: - return super().__repr__() # <4> + try: + return f'<{self.__class__.__name__} {self.name!r}>' + except AttributeError: + return super().__repr__() @property def venue(self): diff --git a/22-dyn-attr-prop/oscon/schedule_v5.py b/22-dyn-attr-prop/oscon/schedule_v5.py index 2517e57..7a9543b 100644 --- a/22-dyn-attr-prop/oscon/schedule_v5.py +++ b/22-dyn-attr-prop/oscon/schedule_v5.py @@ -36,8 +36,7 @@ def __init__(self, **kwargs): self.__dict__.update(kwargs) def __repr__(self): - cls_name = self.__class__.__name__ - return f'<{cls_name} serial={self.serial!r}>' + return f'<{self.__class__.__name__} serial={self.serial!r}>' @staticmethod def fetch(key): @@ -49,10 +48,9 @@ def fetch(key): class Event(Record): def __repr__(self): - if hasattr(self, 'name'): - cls_name = self.__class__.__name__ - return f'<{cls_name} {self.name!r}>' - else: + try: + return f'<{self.__class__.__name__} {self.name!r}>' + except AttributeError: return super().__repr__() # tag::SCHEDULE5_CACHED_PROPERTY[] diff --git a/23-descriptor/bulkfood/model_v5.py b/23-descriptor/bulkfood/model_v5.py index 018afcd..4c35698 100644 --- a/23-descriptor/bulkfood/model_v5.py +++ b/23-descriptor/bulkfood/model_v5.py @@ -30,7 +30,7 @@ class NonBlank(Validated): def validate(self, name, value): value = value.strip() - if len(value) == 0: + if not value: # <2> raise ValueError(f'{name} cannot be blank') - return value # <2> + return value # <3> # end::MODEL_V5_VALIDATED_SUB[] diff --git a/23-descriptor/descriptorkinds.py b/23-descriptor/descriptorkinds.py index 44f4018..13df709 100644 --- a/23-descriptor/descriptorkinds.py +++ b/23-descriptor/descriptorkinds.py @@ -5,21 +5,18 @@ >>> obj = Managed() # <1> >>> obj.over # <2> - -> Overriding.__get__(, , - ) + -> Overriding.__get__(, , ) >>> Managed.over # <3> -> Overriding.__get__(, None, ) >>> obj.over = 7 # <4> -> Overriding.__set__(, , 7) >>> obj.over # <5> - -> Overriding.__get__(, , - ) + -> Overriding.__get__(, , ) >>> obj.__dict__['over'] = 8 # <6> >>> vars(obj) # <7> {'over': 8} >>> obj.over # <8> - -> Overriding.__get__(, , - ) + -> Overriding.__get__(, , ) # end::DESCR_KINDS_DEMO1[] @@ -50,8 +47,7 @@ >>> obj = Managed() >>> obj.non_over # <1> - -> NonOverriding.__get__(, , - ) + -> NonOverriding.__get__(, , ) >>> obj.non_over = 7 # <2> >>> obj.non_over # <3> 7 @@ -59,8 +55,7 @@ -> NonOverriding.__get__(, None, ) >>> del obj.non_over # <5> >>> obj.non_over # <6> - -> NonOverriding.__get__(, , - ) + -> NonOverriding.__get__(, , ) # end::DESCR_KINDS_DEMO3[] @@ -88,7 +83,7 @@ >>> Managed.spam() Traceback (most recent call last): ... - TypeError: spam() missing 1 required positional argument: 'self' + TypeError: Managed.spam() missing 1 required positional argument: 'self' >>> Managed.spam(obj) -> Managed.spam() >>> Managed.spam.__get__(obj) # doctest: +ELLIPSIS @@ -156,15 +151,15 @@ def cls_name(obj_or_cls): def display(obj): cls = type(obj) if cls is type: - return ''.format(obj.__name__) + return f'' elif cls in [type(None), int]: return repr(obj) else: - return '<{} object>'.format(cls_name(obj)) + return f'<{cls_name(obj)} object>' def print_args(name, *args): pseudo_args = ', '.join(display(x) for x in args) - print('-> {}.__{}__({})'.format(cls_name(args[0]), name, pseudo_args)) + print(f'-> {cls_name(args[0])}.__{name}__({pseudo_args})') ### essential classes for this example ### @@ -199,6 +194,6 @@ class Managed: # <5> non_over = NonOverriding() def spam(self): # <6> - print('-> Managed.spam({})'.format(display(self))) + print(f'-> Managed.spam({display(self)})') # end::DESCR_KINDS[] diff --git a/24-class-metaprog/checked/metaclass/checkedlib.py b/24-class-metaprog/checked/metaclass/checkedlib.py index 8207dbe..768a0f6 100644 --- a/24-class-metaprog/checked/metaclass/checkedlib.py +++ b/24-class-metaprog/checked/metaclass/checkedlib.py @@ -78,7 +78,9 @@ def __init__(self, name: str, constructor: Callable) -> None: self.storage_name = '_' + name # <1> self.constructor = constructor - def __get__(self, instance, owner=None): # <2> + def __get__(self, instance, owner=None): + if instance is None: # <2> + return self return getattr(instance, self.storage_name) # <3> def __set__(self, instance: Any, value: Any) -> None: diff --git a/24-class-metaprog/factories.py b/24-class-metaprog/factories.py index 38ec763..cf61d89 100644 --- a/24-class-metaprog/factories.py +++ b/24-class-metaprog/factories.py @@ -49,9 +49,8 @@ def __iter__(self) -> Iterator[Any]: # <5> yield getattr(self, name) def __repr__(self): # <6> - values = ', '.join( - '{}={!r}'.format(*i) for i in zip(self.__slots__, self) - ) + values = ', '.join(f'{name}={value!r}' + for name, value in zip(self.__slots__, self)) cls_name = self.__class__.__name__ return f'{cls_name}({values})' diff --git a/24-class-metaprog/metabunch/from3.6/bunch.py b/24-class-metaprog/metabunch/from3.6/bunch.py index a8b3d5a..aa0a65e 100644 --- a/24-class-metaprog/metabunch/from3.6/bunch.py +++ b/24-class-metaprog/metabunch/from3.6/bunch.py @@ -28,7 +28,7 @@ >>> Point(x=1, y=2, z=3) Traceback (most recent call last): ... - AttributeError: 'Point' object has no attribute 'z' + AttributeError: No slots left for: 'z' >>> p = Point(x=21) >>> p.y = 42 >>> p @@ -51,7 +51,8 @@ def __init__(self, **kwargs): # <4> for name, default in defaults.items(): # <5> setattr(self, name, kwargs.pop(name, default)) if kwargs: # <6> - setattr(self, *kwargs.popitem()) + extra = ', '.join(kwargs) + raise AttributeError(f'No slots left for: {extra!r}') def __repr__(self): # <7> rep = ', '.join(f'{name}={value!r}' diff --git a/24-class-metaprog/metabunch/from3.6/bunch_test.py b/24-class-metaprog/metabunch/from3.6/bunch_test.py index 322487a..166e600 100644 --- a/24-class-metaprog/metabunch/from3.6/bunch_test.py +++ b/24-class-metaprog/metabunch/from3.6/bunch_test.py @@ -26,7 +26,7 @@ def test_init(): def test_init_wrong_argument(): with pytest.raises(AttributeError) as exc: p = Point(x=1.2, y=3.4, flavor='coffee') - assert "no attribute 'flavor'" in str(exc.value) + assert 'flavor' in str(exc.value) def test_slots(): diff --git a/24-class-metaprog/timeslice.py b/24-class-metaprog/timeslice.py new file mode 100644 index 0000000..5a52457 --- /dev/null +++ b/24-class-metaprog/timeslice.py @@ -0,0 +1,91 @@ + +""" +Could this be valid Python? + + if now >= T[4:20:PM]: chill() + + +>>> t = T[4:20] +>>> t +T[4:20] +>>> h, m, s = t +>>> h, m, s +(4, 20, 0) +>>> t[11:59:AM] +T[11:59:AM] +>>> start = t[9:O1:PM] +>>> start +T[9:O1:PM] +>>> start.h, start.m, start.s, start.pm +(9, 1, 0, True) +>>> now = T[7:O1:PM] +>>> T[4:OO:PM] +T[4:OO:PM] +>>> now > T[4:20:PM] +True +""" + +import functools + +AM = -2 +PM = -1 + +for n in range(10): + globals()[f'O{n}'] = n +OO = 0 + +@functools.total_ordering +class T(): + + def __init__(self, arg): + if isinstance(arg, slice): + h = arg.start or 0 + m = arg.stop or 0 + s = arg.step or 0 + else: + h, m, s = 0, 0, arg + if m in (AM, PM): + self.pm = m == PM + m = 0 + elif s in (AM, PM): + self.pm = s == PM + s = 0 + else: + self.pm = None + self.h, self.m, self.s = h, m, s + + def __class_getitem__(cls, arg): + return cls(arg) + + def __getitem__(self, arg): + return(type(self)(arg)) + + def __repr__(self): + h, m, s = self.h, self.m, self.s or None + if m == 0: + m = f'OO' + elif m < 10: + m = f'O{m}' + s = '' if s is None else s + if self.pm is None: + pm = '' + else: + pm = ':' + ('AM', 'PM')[self.pm] + return f'T[{h}:{m}{s}{pm}]' + + def __iter__(self): + yield from (self.h, self.m, self.s) + + def __eq__(self, other): + return tuple(self) == tuple(other) + + def __lt__(self, other): + return tuple(self) < tuple(other) + + def __add__(self, other): + """ + >>> T[11:O5:AM] + 15 # TODO: preserve pm field + T[11:20] + """ + if isinstance(other, int): + return self[self.h:self.m + other:self.pm] From 6709e755ee4b7edc6fc3b183c72d76a8cb7a83d5 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Sat, 15 Jan 2022 13:24:21 -0300 Subject: [PATCH 133/166] added links and README.md --- links/FPY.LI.htaccess | 982 ++++++++++++++++++++++++++++++++++++++++++ links/README.md | 37 ++ links/custom.htaccess | 109 +++++ 3 files changed, 1128 insertions(+) create mode 100644 links/FPY.LI.htaccess create mode 100644 links/README.md create mode 100644 links/custom.htaccess diff --git a/links/FPY.LI.htaccess b/links/FPY.LI.htaccess new file mode 100644 index 0000000..579d7e4 --- /dev/null +++ b/links/FPY.LI.htaccess @@ -0,0 +1,982 @@ +ErrorDocument 404 /404.html + +# main resources +RedirectTemp /code https://github.com/fluentpython/example-code-2e +RedirectTemp /home https://www.fluentpython.com/ + +# URLs mentioned at least three times +RedirectTemp /bisect https://www.fluentpython.com/extra/ordered-sequences-with-bisect/ +RedirectTemp /cardxvi https://www.python.org/dev/peps/pep-0484/#the-numeric-tower +RedirectTemp /collec https://docs.python.org/3/library/collections.html +RedirectTemp /dask https://dask.org/ +RedirectTemp /dtmodel https://docs.python.org/3/reference/datamodel.html +RedirectTemp /descr101 https://www.python.org/download/releases/2.2.3/descrintro/ +RedirectTemp /descrhow https://docs.python.org/3/howto/descriptor.html +RedirectTemp /doctest https://docs.python.org/3/library/doctest.html +RedirectTemp /effectpy https://effectivepython.com/ +RedirectTemp /fmtspec https://docs.python.org/3/library/string.html#formatspec +RedirectTemp /gunicorn https://gunicorn.org/ +RedirectTemp /hashint https://www.fluentpython.com/extra/internals-of-sets-and-dicts/ +RedirectTemp /hattingh https://www.oreilly.com/library/view/using-asyncio-in/9781492075325/ +RedirectTemp /httpx https://www.python-httpx.org/ +RedirectTemp /initvar https://docs.python.org/3/library/dataclasses.html#init-only-variables +RedirectTemp /mypy https://mypy.readthedocs.io/en/stable/ +RedirectTemp /norvigdp http://norvig.com/design-patterns/ +RedirectTemp /nsphere https://en.wikipedia.org/wiki/N-sphere +RedirectTemp /oldcoro https://www.fluentpython.com/extra/classic-coroutines/ +RedirectTemp /pandas https://pandas.pydata.org/ +RedirectTemp /pep218 https://www.python.org/dev/peps/pep-0218/ +RedirectTemp /pep227 https://www.python.org/dev/peps/pep-0227/ +RedirectTemp /pep255 https://www.python.org/dev/peps/pep-0255/ +RedirectTemp /pep342 https://www.python.org/dev/peps/pep-0342/ +RedirectTemp /pep343 https://www.python.org/dev/peps/pep-0343/ +RedirectTemp /pep357 https://www.python.org/dev/peps/pep-0357/ +RedirectTemp /pep362 https://www.python.org/dev/peps/pep-0362/ +RedirectTemp /pep371 https://www.python.org/dev/peps/pep-0371/ +RedirectTemp /pep380 https://www.python.org/dev/peps/pep-0380/ +RedirectTemp /pep393 https://www.python.org/dev/peps/pep-0393/ +RedirectTemp /pep412 https://www.python.org/dev/peps/pep-0412/ +RedirectTemp /pep442 https://www.python.org/dev/peps/pep-0442/ +RedirectTemp /pep443 https://www.python.org/dev/peps/pep-0443/ +RedirectTemp /pep448 https://www.python.org/dev/peps/pep-0448/ +RedirectTemp /pep455 https://www.python.org/dev/peps/pep-0455/ +RedirectTemp /pep456 https://www.python.org/dev/peps/pep-0456/ +RedirectTemp /pep461 https://www.python.org/dev/peps/pep-0461/ +RedirectTemp /pep465 https://www.python.org/dev/peps/pep-0465/ +RedirectTemp /pep467 https://www.python.org/dev/peps/pep-0467/ +RedirectTemp /pep482 https://www.python.org/dev/peps/pep-0482/ +RedirectTemp /pep483 https://www.python.org/dev/peps/pep-0483/ +RedirectTemp /pep484 https://www.python.org/dev/peps/pep-0484/ +RedirectTemp /pep487 https://www.python.org/dev/peps/pep-0487/ +RedirectTemp /pep492 https://www.python.org/dev/peps/pep-0492/ +RedirectTemp /pep519 https://www.python.org/dev/peps/pep-0519/ +RedirectTemp /pep525 https://www.python.org/dev/peps/pep-0525/ +RedirectTemp /pep526 https://www.python.org/dev/peps/pep-0526/ +RedirectTemp /pep528 https://www.python.org/dev/peps/pep-0528/ +RedirectTemp /pep529 https://www.python.org/dev/peps/pep-0529/ +RedirectTemp /pep530 https://www.python.org/dev/peps/pep-0530/ +RedirectTemp /pep544 https://www.python.org/dev/peps/pep-0544/ +RedirectTemp /pep554 https://www.python.org/dev/peps/pep-0554/ +RedirectTemp /pep557 https://www.python.org/dev/peps/pep-0557/ +RedirectTemp /pep560 https://www.python.org/dev/peps/pep-0560/ +RedirectTemp /pep561 https://www.python.org/dev/peps/pep-0561/ +RedirectTemp /pep563 https://www.python.org/dev/peps/pep-0563/ +RedirectTemp /pep570 https://www.python.org/dev/peps/pep-0570/ +RedirectTemp /pep572 https://www.python.org/dev/peps/pep-0572/ +RedirectTemp /pep584 https://www.python.org/dev/peps/pep-0584/ +RedirectTemp /pep585 https://www.python.org/dev/peps/pep-0585/ +RedirectTemp /pep586 https://www.python.org/dev/peps/pep-0586/ +RedirectTemp /pep589 https://www.python.org/dev/peps/pep-0589/ +RedirectTemp /pep591 https://www.python.org/dev/peps/pep-0591/ +RedirectTemp /pep593 https://www.python.org/dev/peps/pep-0593/ +RedirectTemp /pep604 https://www.python.org/dev/peps/pep-0604/ +RedirectTemp /pep612 https://www.python.org/dev/peps/pep-0612/ +RedirectTemp /pep613 https://www.python.org/dev/peps/pep-0613/ +RedirectTemp /pep616 https://www.python.org/dev/peps/pep-0616/ +RedirectTemp /pep617 https://www.python.org/dev/peps/pep-0617/ +RedirectTemp /pep618 https://www.python.org/dev/peps/pep-0618/ +RedirectTemp /pep634 https://www.python.org/dev/peps/pep-0634/ +RedirectTemp /pep635 https://www.python.org/dev/peps/pep-0635/ +RedirectTemp /pep636 https://www.python.org/dev/peps/pep-0636/ +RedirectTemp /pep638 https://www.python.org/dev/peps/pep-0638/ +RedirectTemp /pep645 https://www.python.org/dev/peps/pep-0645/ +RedirectTemp /pep646 https://www.python.org/dev/peps/pep-0646/ +RedirectTemp /pep647 https://www.python.org/dev/peps/pep-0647/ +RedirectTemp /pep649 https://www.python.org/dev/peps/pep-0649/ +RedirectTemp /pep654 https://www.python.org/dev/peps/pep-0654/ +RedirectTemp /pep655 https://www.python.org/dev/peps/pep-0655/ +RedirectTemp /pep661 https://www.python.org/dev/peps/pep-0661/ +RedirectTemp /pep3099 https://www.python.org/dev/peps/pep-3099/ +RedirectTemp /pep3102 https://www.python.org/dev/peps/pep-3102/ +RedirectTemp /pep3104 https://www.python.org/dev/peps/pep-3104/ +RedirectTemp /pep3106 https://www.python.org/dev/peps/pep-3106/ +RedirectTemp /pep3107 https://www.python.org/dev/peps/pep-3107/ +RedirectTemp /pep3115 https://www.python.org/dev/peps/pep-3115/ +RedirectTemp /pep3118 https://www.python.org/dev/peps/pep-3118/ +RedirectTemp /pep3119 https://www.python.org/dev/peps/pep-3119/ +RedirectTemp /pep3129 https://www.python.org/dev/peps/pep-3129/ +RedirectTemp /pep3132 https://www.python.org/dev/peps/pep-3132/ +RedirectTemp /pep3141 https://www.python.org/dev/peps/pep-3141/ +RedirectTemp /pep3148 https://www.python.org/dev/peps/pep-3148/ +RedirectTemp /pep3155 https://www.python.org/dev/peps/pep-3155/ +RedirectTemp /pep3333 https://www.python.org/dev/peps/pep-3333/ +RedirectTemp /pypydif https://doc.pypy.org/en/latest/cpython_differences.html#subclasses-of-built-in-types +RedirectTemp /shed4051 https://github.com/python/typeshed/issues/4051 +RedirectTemp /slatkin https://effectivepython.com/ +RedirectTemp /specattr https://docs.python.org/3/library/stdtypes.html#special-attributes +RedirectTemp /typecoro https://docs.python.org/3.10/library/typing.html#typing.Coroutine +RedirectTemp /typing https://docs.python.org/3/library/typing.html +RedirectTemp /weakref https://www.fluentpython.com/extra/weak-references/ + +# Remaining URLs by chapter + +############################################################ p +RedirectTemp /p-1 https://mail.python.org/pipermail/python-list/2002-December/134521.html +RedirectTemp /p-2 https://docs.python.org/3.10/tutorial/ +RedirectTemp /p-3 https://docs.python.org/3/tutorial/ +RedirectTemp /p-9 https://www.oreilly.com/library/view/fluent-python-2nd/9781492056348/ +RedirectTemp /p-10 https://www.oreilly.com/online-learning/try-now.html +RedirectTemp /p-13 https://www.oreilly.com/library/view/fluent-python-2nd/9781492056348/ +RedirectTemp /p-14 https://www.oreilly.com/ +RedirectTemp /p-15 https://www.facebook.com/OReilly/ +RedirectTemp /p-16 https://twitter.com/oreillymedia +RedirectTemp /p-17 https://www.youtube.com/oreillymedia +RedirectTemp /p-18 https://stackoverflow.com/users/95810/alex-martelli +RedirectTemp /p-19 https://pythonpro.com.br +RedirectTemp /p-20 https://groups.google.com/g/python-brasil +RedirectTemp /p-21 https://www.coffeelab.com.br/ +RedirectTemp /p-22 https://garoa.net.br/wiki/P%C3%A1gina_principal +############################################################ a +RedirectTemp /a-1 https://groups.google.com/forum/#!topic/python-tulip/Y4bhLNbKs74 +RedirectTemp /a-2 https://docs.python.org/3/library/asyncio-eventloop.html#executor +RedirectTemp /a-3 https://www.youtube.com/watch?v=x-kB2o8sd5c +RedirectTemp /a-4 https://www.youtube.com/watch?v=OSGv2VnC0go +RedirectTemp /a-5 https://mail.python.org/pipermail/python-ideas/2015-March/032557.html +RedirectTemp /a-6 https://pypi.org/project/pep8/ +RedirectTemp /a-7 https://pypi.org/project/flake8/ +RedirectTemp /a-8 https://pypi.org/project/pyflakes/ +RedirectTemp /a-9 https://pypi.org/project/mccabe/ +RedirectTemp /a-10 https://google.github.io/styleguide/pyguide.html +RedirectTemp /a-11 https://flask.palletsprojects.com/en/1.1.x/styleguide/ +RedirectTemp /a-12 https://docs.python-guide.org/ +RedirectTemp /a-13 https://david.goodger.org/projects/pycon/2007/idiomatic/handout.html +RedirectTemp /a-14 https://docs.mongodb.com/manual/about/#about-the-documentation-process +RedirectTemp /a-15 https://blog.startifact.com/posts/older/what-is-pythonic.html +RedirectTemp /a-16 https://mail.python.org/pipermail/tutor/2003-October/thread.html#25930 +RedirectTemp /a-17 https://mail.python.org/pipermail/python-list/2003-April/192027.html +RedirectTemp /a-19 https://www.python.org/doc/essays/ +############################################################ 01 +RedirectTemp /1-1 http://hugunin.net/story_of_jython.html +RedirectTemp /1-3 https://docs.python.org/2/library/string.html#format-string-syntax +RedirectTemp /1-4 https://stackoverflow.com/questions/1436703/what-is-the-difference-between-str-and-repr +RedirectTemp /1-5 https://docs.python.org/3/library/stdtypes.html#truth +RedirectTemp /1-6 https://docs.python.org/3/tutorial/controlflow.html#unpacking-argument-lists +RedirectTemp /1-8 https://www.python.org/doc/humor/#the-zen-of-python +RedirectTemp /1-10 https://stackoverflow.com/users/95810/alex-martelli +RedirectTemp /1-11 https://en.wikipedia.org/wiki/Object_model +RedirectTemp /1-13 https://www.dourish.com/goodies/jargon.html +RedirectTemp /1-14 https://zopeinterface.readthedocs.io/en/latest/ +RedirectTemp /1-15 https://plone.org/ +############################################################ 02 +RedirectTemp /2-4 https://github.com/fluentpython/example-code-2e/blob/master/02-array-seq/listcomp_speed.py +RedirectTemp /2-6 https://www.python.org/dev/peps/pep-3132/ +RedirectTemp /2-8 https://docs.python.org/3/whatsnew/3.5.html#pep-448-additional-unpacking-generalizations +RedirectTemp /2-9 https://docs.python.org/3/whatsnew/3.5.html#pep-448-additional-unpacking-generalizations +RedirectTemp /2-11 https://docs.python.org/3.10/whatsnew/3.10.html#pep-634-structural-pattern-matching +RedirectTemp /2-12 https://docs.python.org/3.10/whatsnew/3.10.html +RedirectTemp /2-13 https://en.wikipedia.org/wiki/Switch_statement#Fallthrough +RedirectTemp /2-14 https://en.wikipedia.org/wiki/Dangling_else +RedirectTemp /2-15 https://github.com/gvanrossum/patma/blob/3ece6444ef70122876fd9f0099eb9490a2d630df/EXAMPLES.md#case-6-a-very-deep-iterable-and-type-match-with-extraction +RedirectTemp /2-16 https://github.com/fluentpython/lispy/blob/main/original/norvig/lis.py +RedirectTemp /2-17 https://norvig.com/lispy.html +RedirectTemp /2-18 https://numpy.org/doc/stable/user/quickstart.html#indexing-slicing-and-iterating +RedirectTemp /2-19 https://pythontutor.com/ +RedirectTemp /2-20 https://en.wikipedia.org/wiki/Fluent_interface +RedirectTemp /2-23 https://docs.python.org/3/library/bisect.html#bisect.insort +RedirectTemp /2-24 https://stackoverflow.com/questions/4845418/when-should-a-memoryview-be-used/ +RedirectTemp /2-26 https://www.fluentpython.com/extra/parsing-binary-struct/ +RedirectTemp /2-27 http://www.netlib.org +RedirectTemp /2-28 https://pandas.pydata.org/ +RedirectTemp /2-29 https://scikit-learn.org/stable/ +RedirectTemp /2-32 https://docs.python.org/3/howto/sorting.html +RedirectTemp /2-33 https://www.python.org/dev/peps/pep-3132/ +RedirectTemp /2-34 https://bugs.python.org/issue2292 +RedirectTemp /2-36 https://docs.python.org/3.10/whatsnew/3.10.html#pep-634-structural-pattern-matching +RedirectTemp /2-37 https://docs.python.org/3.10/whatsnew/3.10.html +RedirectTemp /2-39 https://www.python.org/dev/peps/pep-0636/#appendix-a-quick-intro +RedirectTemp /2-41 https://eli.thegreenplace.net/2011/11/28/less-copies-in-python-with-the-buffer-protocol-and-memoryviews/ +RedirectTemp /2-42 https://jakevdp.github.io/PythonDataScienceHandbook/ +RedirectTemp /2-43 https://www.labri.fr/perso/nrougier/from-python-to-numpy/ +RedirectTemp /2-44 https://www.cs.utexas.edu/users/EWD/transcriptions/EWD08xx/EWD831.html +RedirectTemp /2-45 http://www.fonts101.com/fonts/view/Uncategorized/34398/Dijkstra +RedirectTemp /2-46 https://docs.python.org/3/reference/datamodel.html#objects-values-and-types +RedirectTemp /2-47 https://en.wikipedia.org/wiki/Timsort +RedirectTemp /2-48 http://www.groklaw.net/pdf3/OraGoogle-1202.pdf +RedirectTemp /2-49 https://www.python.org/doc/humor/#id9 +############################################################ 03 +RedirectTemp /3-5 https://www.python.org/dev/peps/pep-0584/#motivation +RedirectTemp /3-7 https://docs.python.org/3.10/c-api/typeobj.html#Py_TPFLAGS_MAPPING +RedirectTemp /3-8 https://docs.python.org/3/glossary.html#term-hashable +RedirectTemp /3-9 https://docs.python.org/3/glossary.html#term-hashable +RedirectTemp /3-11 http://www.aleax.it/Python/accu04_Relearn_Python_alex.pdf +RedirectTemp /3-12 https://github.com/pingo-io/pingo-py +RedirectTemp /3-13 https://github.com/fluentpython/example-code-2e/blob/master/03-dict-set/missing.py +RedirectTemp /3-14 https://docs.python.org/3/library/collections.html#collections.ChainMap +RedirectTemp /3-15 https://docs.python.org/3/library/collections.html#collections.Counter +RedirectTemp /3-16 https://docs.python.org/3/library/shelve.html +RedirectTemp /3-17 https://docs.python.org/3/library/dbm.html +RedirectTemp /3-18 https://docs.python.org/3/library/pickle.html +RedirectTemp /3-19 https://nedbatchelder.com/blog/202006/pickles_nine_flaws.html +RedirectTemp /3-20 https://github.com/python/cpython/blob/0bbf30e2b910bc9c5899134ae9d73a8df968da35/Lib/_collections_abc.py#L813 +RedirectTemp /3-22 https://mail.python.org/pipermail/python-dev/2015-May/140003.html +RedirectTemp /3-23 https://bugs.python.org/issue18986 +RedirectTemp /3-24 https://github.com/fluentpython/example-code-2e/blob/master/03-dict-set/transformdict.py +RedirectTemp /3-33 http://gandenberger.org/2018/03/10/ordered-dicts-vs-ordereddict/ +RedirectTemp /3-35 https://www.pypy.org/ +RedirectTemp /3-36 https://morepypy.blogspot.com/2015/01/faster-more-memory-efficient-and-more.html +RedirectTemp /3-37 https://www.npopov.com/2014/12/22/PHPs-new-hashtable-implementation.html +RedirectTemp /3-38 https://www.youtube.com/watch?v=66P5FMkWoVU +RedirectTemp /3-39 https://pyvideo.org/video/276/the-mighty-dictionary-55/ +RedirectTemp /3-40 https://www.youtube.com/watch?v=p33CVV29OG8 +RedirectTemp /3-41 https://docs.python.org/3/whatsnew/3.6.html#new-dict-implementation +RedirectTemp /3-43 https://www.youtube.com/watch?v=tGAngdU_8D8 +RedirectTemp /3-44 https://speakerdeck.com/ramalho/python-set-practice-at-pycon +RedirectTemp /3-45 https://github.com/standupdev/uintset +RedirectTemp /3-46 http://www.json.org/fatfree.html +RedirectTemp /3-47 https://twitter.com/mitsuhiko/status/1229385843585974272 +############################################################ 04 +RedirectTemp /4-1 https://www.slideshare.net/fischertrav/character-encoding-unicode-how-to-with-dignity-33352863 +RedirectTemp /4-2 https://pyvideo.org/video/2625/character-encoding-and-unicode-in-python/ +RedirectTemp /4-3 https://www.fluentpython.com/extra/parsing-binary-struct/ +RedirectTemp /4-5 https://www.fluentpython.com/extra/multi-character-emojis/ +RedirectTemp /4-8 https://w3techs.com/technologies/overview/character_encoding +RedirectTemp /4-9 https://docs.python.org/3/library/codecs.html#codecs.register_error +RedirectTemp /4-10 https://docs.python.org/3/library/stdtypes.html#str.isascii +RedirectTemp /4-11 https://pypi.org/project/chardet/ +RedirectTemp /4-12 https://docs.python.org/3/library/codecs.html#encodings-and-unicode +RedirectTemp /4-13 https://nedbatchelder.com/text/unipain/unipain.html +RedirectTemp /4-16 https://docs.python.org/3/using/cmdline.html#envvar-PYTHONIOENCODING +RedirectTemp /4-17 https://docs.python.org/3/using/cmdline.html#envvar-PYTHONLEGACYWINDOWSSTDIO +RedirectTemp /4-18 https://docs.python.org/3/library/locale.html#locale.getpreferredencoding +RedirectTemp /4-19 http://www.w3.org/TR/charmod-norm/ +RedirectTemp /4-20 https://docs.python.org/3/library/locale.html?highlight=strxfrm#locale.strxfrm +RedirectTemp /4-21 https://github.com/jtauber/pyuca +RedirectTemp /4-22 http://www.unicode.org/Public/UCA/6.3.0/allkeys.txt +RedirectTemp /4-23 https://pypi.org/project/PyICU/ +RedirectTemp /4-24 https://docs.python.org/3.10/library/stdtypes.html#str.isalpha +RedirectTemp /4-25 https://en.wikipedia.org/wiki/Unicode_character_property#General_Category +RedirectTemp /4-26 https://en.wikipedia.org/wiki/Unicode_character_property +RedirectTemp /4-27 https://github.com/microsoft/terminal +RedirectTemp /4-28 https://docs.python.org/3/library/unicodedata.html +RedirectTemp /4-29 https://docs.python.org/3/reference/lexical_analysis.html#string-literal-concatenation +RedirectTemp /4-30 https://docs.python.org/3/library/re.html +RedirectTemp /4-31 https://nedbatchelder.com/text/unipain.html +RedirectTemp /4-32 https://www.slideshare.net/fischertrav/character-encoding-unicode-how-to-with-dignity-33352863 +RedirectTemp /4-33 https://pyvideo.org/video/2625/character-encoding-and-unicode-in-python/ +RedirectTemp /4-34 https://regebro.wordpress.com/2011/03/23/unconfusing-unicode-what-is-unicode/ +RedirectTemp /4-35 https://docs.python.org/3/howto/unicode.html +RedirectTemp /4-36 https://diveintopython3.net/strings.html +RedirectTemp /4-37 https://diveintopython3.net/ +RedirectTemp /4-38 https://finderiko.com/python-book +RedirectTemp /4-39 https://docs.python.org/3.0/whatsnew/3.0.html#text-vs-data-instead-of-unicode-vs-8-bit +RedirectTemp /4-40 https://lucumr.pocoo.org/2013/7/2/the-updated-guide-to-unicode/ +RedirectTemp /4-41 http://python-notes.curiousefficiency.org/en/latest/python3/binary_protocols.html +RedirectTemp /4-42 http://python-notes.curiousefficiency.org/en/latest/python3/text_file_processing.html +RedirectTemp /4-43 https://docs.python.org/3/library/codecs.html#standard-encodings +RedirectTemp /4-44 https://www.informit.com/store/unicode-demystified-a-practical-programmers-guide-to-9780201700527 +RedirectTemp /4-45 https://unicodebook.readthedocs.io/index.html +RedirectTemp /4-46 https://www.w3.org/International/wiki/Case_folding +RedirectTemp /4-47 http://www.w3.org/TR/charmod-norm/ +RedirectTemp /4-48 http://unicode.org/reports/tr15/ +RedirectTemp /4-49 http://www.unicode.org/faq/normalization.html +RedirectTemp /4-50 http://www.unicode.org/ +RedirectTemp /4-51 http://www.macchiato.com/unicode/nfc-faq +RedirectTemp /4-52 https://emojipedia.org/ +RedirectTemp /4-53 https://blog.emojipedia.org/correcting-the-record-on-the-first-emoji-set/ +RedirectTemp /4-54 http://emojitracker.com/ +RedirectTemp /4-55 http://www.unicode.org/glossary/#plain_text +RedirectTemp /4-56 http://www.methods.co.nz/asciidoc/ +RedirectTemp /4-57 https://atlas.oreilly.com/ +############################################################ 05 +RedirectTemp /5-1 https://docs.python.org/3/library/typing.html#typing.TypedDict +RedirectTemp /5-4 https://docs.python.org/3.10/library/inspect.html#inspect.get_annotations +RedirectTemp /5-5 https://docs.python.org/3/library/typing.html#typing.get_type_hints +RedirectTemp /5-6 https://docs.python.org/3.8/library/collections.html#collections.somenamedtuple._asdict +RedirectTemp /5-8 https://www.jetbrains.com/pycharm/ +RedirectTemp /5-10 https://www.python.org/dev/peps/pep-0484/#acceptable-type-hints +RedirectTemp /5-11 https://docs.python.org/3/library/dataclasses.html#dataclasses.dataclass +RedirectTemp /5-13 https://docs.python.org/3/library/dataclasses.html#dataclasses.dataclass +RedirectTemp /5-14 https://docs.python.org/3/library/dataclasses.html +RedirectTemp /5-15 https://docs.python.org/3/library/dataclasses.html#inheritance +RedirectTemp /5-16 https://www.python.org/dev/peps/pep-0526/#class-and-instance-variable-annotations +RedirectTemp /5-22 https://dublincore.org/specifications/dublin-core/ +RedirectTemp /5-23 https://en.wikipedia.org/wiki/Dublin_Core +RedirectTemp /5-24 https://martinfowler.com/bliki/CodeSmell.html +RedirectTemp /5-25 https://martinfowler.com/books/refactoring.html +RedirectTemp /5-26 https://www.python.org/dev/peps/pep-0634/#class-patterns +RedirectTemp /5-30 https://docs.python.org/3/library/dataclasses.html +RedirectTemp /5-32 https://www.python.org/dev/peps/pep-0557/#id47 +RedirectTemp /5-33 https://www.python.org/dev/peps/pep-0557/#id48 +RedirectTemp /5-34 https://www.python.org/dev/peps/pep-0557/#id33 +RedirectTemp /5-35 https://realpython.com +RedirectTemp /5-36 https://realpython.com/python-data-classes/ +RedirectTemp /5-37 https://www.youtube.com/watch?v=T-TwcmT6Rcw +RedirectTemp /5-38 https://www.attrs.org/en/stable/ +RedirectTemp /5-39 https://glyph.twistedmatrix.com/2016/08/attrs.html +RedirectTemp /5-40 https://www.attrs.org/en/stable/why.html +RedirectTemp /5-41 https://github.com/dabeaz/cluegen +RedirectTemp /5-42 https://refactoring.guru/ +RedirectTemp /5-43 https://refactoring.guru/smells/data-class +RedirectTemp /5-44 https://web.archive.org/web/20190204130328/http://catb.org/esr/jargon/html/G/Guido.html +RedirectTemp /5-45 https://web.archive.org/web/20190211161610/http://catb.org/esr/jargon/html/index.html +RedirectTemp /5-47 https://www.attrs.org/en/stable/ +############################################################ 06 +RedirectTemp /6-3 https://www.olin.edu/faculty/profile/lynn-andrea-stein/ +RedirectTemp /6-4 https://docs.python.org/3/reference/datamodel.html#objects-values-and-types +RedirectTemp /6-5 https://pythontutor.com/ +RedirectTemp /6-6 https://docs.python.org/3/library/copy.html +RedirectTemp /6-7 https://en.wikipedia.org/wiki/Principle_of_least_astonishment +RedirectTemp /6-8 https://docs.python.org/3/reference/datamodel.html#object.%5C_%5C_del__ +RedirectTemp /6-9 https://emptysqua.re/blog/pypy-garbage-collection-and-a-deadlock/ +RedirectTemp /6-15 https://www.youtube.com/watch?v=HHFCFJSPWrI&feature=youtu.be +RedirectTemp /6-16 http://pymotw.com/3/copy/ +RedirectTemp /6-17 http://pymotw.com/3/weakref/ +RedirectTemp /6-18 https://docs.python.org/3/library/gc.html +RedirectTemp /6-20 https://devguide.python.org/garbage_collector/ +RedirectTemp /6-21 https://devguide.python.org/ +RedirectTemp /6-22 https://www.python.org/dev/peps/pep-0442/ +RedirectTemp /6-23 https://en.wikipedia.org/wiki/String_interning +RedirectTemp /6-24 https://en.wikipedia.org/wiki/Haddocks%27_Eyes +RedirectTemp /6-25 https://thp.io/2012/python-gc/python_gc_final_2012-01-22.pdf +############################################################ 07 +RedirectTemp /7-1 http://python-history.blogspot.com/2009/04/origins-of-pythons-functional-features.html +RedirectTemp /7-3 https://www.fluentpython.com/extra/function-introspection/ +RedirectTemp /7-5 https://docs.python.org/3/library/functions.html#map +RedirectTemp /7-6 https://en.wikipedia.org/wiki/Functional_programming +RedirectTemp /7-7 https://docs.python.org/3/howto/functional.html +RedirectTemp /7-8 https://docs.python.org/3/reference/datamodel.html#the-standard-type-hierarchy +RedirectTemp /7-9 https://docs.python.org/3/whatsnew/3.8.html#positional-only-parameters +RedirectTemp /7-10 https://docs.python.org/3/whatsnew/3.8.html#positional-only-parameters +RedirectTemp /7-12 https://docs.python.org/3/reference/datamodel.html#the-standard-type-hierarchy +RedirectTemp /7-14 https://docs.python.org/3/howto/functional.html +RedirectTemp /7-15 https://stackoverflow.com/questions/3252228/python-why-is-functools-partial-necessary +RedirectTemp /7-16 https://speakerdeck.com/ramalho/beyond-paradigms-berlin-edition +RedirectTemp /7-17 https://www.youtube.com/watch?v=bF3a2VYXxa0 +RedirectTemp /7-18 http://cs.brown.edu/~sk/Publications/Papers/Published/sk-teach-pl-post-linnaean/ +RedirectTemp /7-19 http://python-history.blogspot.com/2009/04/origins-of-pythons-functional-features.html +RedirectTemp /7-20 https://raw.githubusercontent.com/python/cpython/main/Misc/HISTORY +RedirectTemp /7-21 http://neopythonic.blogspot.com/2009/04/tail-recursion-elimination.html +############################################################ 08 +RedirectTemp /8-1 https://www.python.org/dev/peps/pep-0484/#non-goals +RedirectTemp /8-4 https://github.com/python/typing/issues/182 +RedirectTemp /8-5 https://github.com/python/mypy/issues/731 +RedirectTemp /8-6 https://github.com/google/pytype +RedirectTemp /8-7 https://github.com/Microsoft/pyright +RedirectTemp /8-8 https://pyre-check.org/ +RedirectTemp /8-10 https://mypy.readthedocs.io/en/stable/introduction.html +RedirectTemp /8-11 https://mypy.readthedocs.io/en/stable/config_file.html +RedirectTemp /8-12 https://pypi.org/project/flake8/ +RedirectTemp /8-13 https://pypi.org/project/blue/ +RedirectTemp /8-14 https://pypi.org/project/black/ +RedirectTemp /8-16 https://wefearchange.org/2020/11/steeringcouncil.rst.html +RedirectTemp /8-17 https://docs.python.org/3/library/collections.abc.html#collections-abstract-base-classes +RedirectTemp /8-18 https://en.wikipedia.org/wiki/Barbara_Liskov +RedirectTemp /8-19 https://en.wikipedia.org/wiki/Behavioral_subtyping +RedirectTemp /8-22 https://www.python.org/dev/peps/pep-0585/#implementation +RedirectTemp /8-25 https://docs.python.org/3/library/typing.html#module-contents +RedirectTemp /8-26 https://en.wikipedia.org/wiki/Geohash +RedirectTemp /8-27 https://en.wikipedia.org/wiki/Inverted_index +RedirectTemp /8-29 https://docs.python.org/3/library/typing.html#typing.List +RedirectTemp /8-30 https://docs.python.org/3/library/typing.html#typing.Dict +RedirectTemp /8-31 https://docs.python.org/3/library/typing.html#typing.Set +RedirectTemp /8-32 https://www.python.org/dev/peps/pep-0585/#implementation +RedirectTemp /8-34 https://docs.python.org/3/library/numbers.html +RedirectTemp /8-37 https://docs.python.org/3/library/typing.html#typing.List +RedirectTemp /8-38 https://github.com/python/typeshed +RedirectTemp /8-39 https://github.com/python/typeshed/blob/66cd36268a6a667714efaa27198a41d0d7f89477/stdlib/2and3/math.pyi#L45 +RedirectTemp /8-41 https://docs.python.org/3/library/statistics.html#statistics.mode +RedirectTemp /8-42 https://docs.python.org/3/library/statistics.html#statistics.mode +RedirectTemp /8-46 https://docs.python.org/3/library/typing.html#typing.Callable +RedirectTemp /8-47 https://pypi.org/project/blue/ +RedirectTemp /8-48 https://www.python.org/dev/peps/pep-0484/#id38 +RedirectTemp /8-49 https://docs.google.com/document/d/1aXs1tpwzPjW9MdsG5dI7clNFyYayFBkcXwRDo-qvbIk/preview +RedirectTemp /8-50 https://www.oreilly.com/library/view/the-best-software/9781590595008/ +RedirectTemp /8-51 https://www.youtube.com/watch?v=YFexUDjHO6w +RedirectTemp /8-52 https://www.youtube.com/watch?v=YFexUDjHO6w&t=13m40s +RedirectTemp /8-53 https://bernat.tech/posts/the-state-of-type-hints-in-python/ +RedirectTemp /8-54 https://realpython.com/python-type-checking/ +RedirectTemp /8-55 https://cjolowicz.github.io/posts/hypermodern-python-04-typing/ +RedirectTemp /8-56 https://mypy.readthedocs.io/en/stable/index.html +RedirectTemp /8-57 https://mypy.readthedocs.io/en/stable/cheat_sheet_py3.html +RedirectTemp /8-58 https://mypy.readthedocs.io/en/stable/common_issues.html +RedirectTemp /8-60 https://github.com/typeddjango/awesome-python-typing +RedirectTemp /8-61 https://docs.python.org/3/library/functions.html#max +RedirectTemp /8-62 https://en.wikipedia.org/wiki/Linguistic_relativity +RedirectTemp /8-63 https://pypistats.org/top +RedirectTemp /8-64 https://github.com/psf/requests/issues/3855 +RedirectTemp /8-65 https://lwn.net/Articles/643399/ +RedirectTemp /8-66 https://docs.python-requests.org/en/master/api/#requests.request +RedirectTemp /8-67 https://queue.acm.org/detail.cfm?id=1039523 +############################################################ 09 +RedirectTemp /9-1 https://docs.python.org/3/library/dis.html +RedirectTemp /9-2 https://en.wikipedia.org/wiki/Memoization +RedirectTemp /9-3 https://numpy.org/doc/stable/user/basics.types.html +RedirectTemp /9-5 https://docs.python.org/3/library/functools.html#functools.singledispatch +RedirectTemp /9-7 https://github.com/GrahamDumpleton/wrapt/blob/develop/blog/README.md +RedirectTemp /9-8 https://github.com/GrahamDumpleton/wrapt/blob/develop/blog/01-how-you-implemented-your-python-decorator-is-wrong.md +RedirectTemp /9-9 https://wrapt.readthedocs.io/en/latest/ +RedirectTemp /9-10 https://www.oreilly.com/library/view/python-cookbook-3rd/9781449357337/ch09.html +RedirectTemp /9-11 https://pypi.org/project/decorator/ +RedirectTemp /9-12 https://wiki.python.org/moin/PythonDecoratorLibrary +RedirectTemp /9-13 http://web.archive.org/web/20201109032203/http://effbot.org/zone/closure.htm +RedirectTemp /9-14 https://www.python.org/dev/peps/pep-3104/ +RedirectTemp /9-15 https://www.python.org/dev/peps/pep-0227/ +RedirectTemp /9-16 https://www.python.org/dev/peps/pep-0443/ +RedirectTemp /9-17 https://www.artima.com/weblogs/viewpost.jsp?thread=101605 +RedirectTemp /9-18 https://reg.readthedocs.io/en/latest/ +RedirectTemp /9-19 https://morepath.readthedocs.io/en/latest/ +RedirectTemp /9-20 https://www.gnu.org/software/emacs/manual/html_node/elisp/Dynamic-Binding.html +RedirectTemp /9-21 http://www.paulgraham.com/rootsoflisp.html +RedirectTemp /9-22 http://www-formal.stanford.edu/jmc/recursive/recursive.html +RedirectTemp /9-23 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this +############################################################ 10 +RedirectTemp /10-1 https://en.wikipedia.org/wiki/Software_design_pattern +RedirectTemp /10-2 https://en.wikipedia.org/wiki/Iterator_pattern +RedirectTemp /10-4 https://github.com/python/mypy/issues/9397 +RedirectTemp /10-5 http://www.norvig.com/design-patterns/index.htm +RedirectTemp /10-6 https://pyvideo.org/video/1110/python-design-patterns/ +RedirectTemp /10-7 http://www.aleax.it/gdd_pydp.pdf +RedirectTemp /10-9 https://perl.plover.com/yak/design/ +RedirectTemp /10-11 https://en.wikipedia.org/wiki/Turtles_all_the_way_down +############################################################ 11 +RedirectTemp /11-1 https://blog.startifact.com/posts/older/what-is-pythonic.html +RedirectTemp /11-2 https://julien.danjou.info/guide-python-static-class-abstract-methods/ +RedirectTemp /11-3 https://docs.python.org/3/library/string.html#formatspec +RedirectTemp /11-5 https://docs.python.org/3/reference/lexical_analysis.html#f-strings +RedirectTemp /11-6 https://docs.python.org/3/library/string.html#format-string-syntax +RedirectTemp /11-7 https://docs.python.org/3/library/string.html#formatspec +RedirectTemp /11-8 https://docs.python.org/3/reference/datamodel.html#object.__hash__ +RedirectTemp /11-9 https://web.archive.org/web/20161025185040/http://pythonpaste.org/StyleGuide.html +RedirectTemp /11-10 https://docs.python.org/3/tutorial/modules.html#more-on-modules +RedirectTemp /11-11 https://docs.python.org/3/library/gettext.html#gettext.NullTranslations +RedirectTemp /11-12 https://github.com/fluentpython/example-code-2e/blob/master/11-pythonic-obj/mem_test.py +RedirectTemp /11-16 https://docs.python.org/3/reference/datamodel.html#basic-customization +RedirectTemp /11-18 http://esug.org/data/HistoricalDocuments/TheSmalltalkReport/ST07/04wo.pdf +RedirectTemp /11-19 https://docs.oracle.com/javase/tutorial/essential/environment/security.html +RedirectTemp /11-21 https://docs.oracle.com/javase/tutorial/essential/environment/security.html +############################################################ 12 +RedirectTemp /12-1 https://en.wikipedia.org/wiki/Vector_space_model +RedirectTemp /12-2 https://pypi.org/project/gensim/ +RedirectTemp /12-6 https://docs.python.org/3/library/functions.html#enumerate +RedirectTemp /12-7 https://mathworld.wolfram.com/Hypersphere.html +RedirectTemp /12-12 https://en.wikipedia.org/wiki/Fold_(higher-order_function) +RedirectTemp /12-13 https://docs.python.org/2.5/whatsnew/pep-357.html +RedirectTemp /12-15 https://docs.python.org/3/reference/datamodel.html#special-method-names +RedirectTemp /12-16 https://en.wikipedia.org/wiki/KISS_principle +RedirectTemp /12-17 https://mail.python.org/pipermail/python-list/2000-July/046184.html +RedirectTemp /12-18 https://en.wikipedia.org/wiki/Duck_typing +RedirectTemp /12-19 https://mail.python.org/mailman/listinfo/python-list +RedirectTemp /12-20 https://mail.python.org/pipermail/python-list/2003-April/218568.html +############################################################ 13 +RedirectTemp /13-2 https://docs.python.org/3/c-api/index.html +RedirectTemp /13-3 https://docs.python.org/3/c-api/sequence.html +RedirectTemp /13-7 https://github.com/python/cpython/blob/31ceccb2c77854893f3a754aca04bedd74bedb10/Lib/_collections_abc.py#L870 +RedirectTemp /13-8 https://en.wikipedia.org/wiki/Monkey_patch +RedirectTemp /13-9 https://www.gevent.org/api/gevent.monkey.html +RedirectTemp /13-10 https://docs.python.org/3/library/random.html#random.shuffle +RedirectTemp /13-11 https://docs.python.org/3/reference/datamodel.html#emulating-container-types +RedirectTemp /13-12 https://docs.python.org/3/library/collections.html#collections.namedtuple +RedirectTemp /13-13 https://github.com/python/typeshed/blob/24afb531ffd07083d6a74be917342195062f7277/stdlib/collections/__init__.pyi +RedirectTemp /13-14 https://docs.python.org/3/glossary.html#term-abstract-base-class +RedirectTemp /13-15 https://en.wikipedia.org/wiki/Duck_typing#History +RedirectTemp /13-16 http://ptgmedia.pearsoncmg.com/images/020163371x/items/item33.html +RedirectTemp /13-17 https://docs.python.org/3/library/bisect.html#bisect.bisect +RedirectTemp /13-20 https://github.com/python/cpython/blob/main/Lib/_collections_abc.py +RedirectTemp /13-21 https://github.com/python/cpython/blob/main/Lib/abc.py +RedirectTemp /13-22 https://docs.python.org/3/library/collections.abc.html#collections-abstract-base-classes +RedirectTemp /13-23 https://docs.python.org/3/library/collections.abc.html#collections.abc.Iterable +RedirectTemp /13-24 https://docs.python.org/3/library/abc.html +RedirectTemp /13-25 https://docs.python.org/dev/library/abc.html#abc.abstractmethod +RedirectTemp /13-26 https://docs.python.org/dev/library/abc.html +RedirectTemp /13-27 https://docs.python.org/3/library/os.html#os.urandom +RedirectTemp /13-28 https://github.com/python/mypy/issues/2922 +RedirectTemp /13-29 https://docs.python.org/3/library/stdtypes.html#truth +RedirectTemp /13-30 https://github.com/python/cpython/blob/0bbf30e2b910bc9c5899134ae9d73a8df968da35/Lib/_collections_abc.py +RedirectTemp /13-31 https://github.com/python/cpython/blob/0fbddb14dc03f61738af01af88e7d8aa8df07336/Lib/_collections_abc.py#L369 +RedirectTemp /13-32 https://bugs.python.org/issue31333 +RedirectTemp /13-33 https://github.com/python/cpython/blob/3635388f52b42e5280229104747962117104c453/Modules/_abc.c#L605 +RedirectTemp /13-34 https://github.com/python/cpython/blob/0fbddb14dc03f61738af01af88e7d8aa8df07336/Lib/_collections_abc.py#L881 +RedirectTemp /13-37 https://docs.python.org/3/library/typing.html#protocols +RedirectTemp /13-40 https://martinfowler.com/bliki/RoleInterface.html +RedirectTemp /13-41 https://en.wikipedia.org/wiki/Interface_segregation_principle +RedirectTemp /13-42 https://github.com/python/typeshed/blob/master/CONTRIBUTING.md +RedirectTemp /13-43 https://gist.github.com/asukakenji/ac8a05644a2e98f1d5ea8c299541fce9 +RedirectTemp /13-44 https://www.python.org/dev/peps/pep-0544/#merging-and-extending-protocols +RedirectTemp /13-45 https://numpy.org/devdocs/user/basics.types.html +RedirectTemp /13-46 https://github.com/python/typeshed/blob/master/stdlib/statistics.pyi +RedirectTemp /13-47 https://bugs.python.org/issue41974 +RedirectTemp /13-49 https://glyph.twistedmatrix.com/2020/07/new-duck.html +RedirectTemp /13-50 https://glyph.twistedmatrix.com/2021/03/interfaces-and-protocols.html +RedirectTemp /13-51 https://plone.org/ +RedirectTemp /13-52 https://trypyramid.com/ +RedirectTemp /13-53 https://twistedmatrix.com/trac/ +RedirectTemp /13-54 https://www.artima.com/articles/contracts-in-python +RedirectTemp /13-55 https://martinfowler.com/bliki/DynamicTyping.html +RedirectTemp /13-56 https://martinfowler.com/bliki/RoleInterface.html +RedirectTemp /13-57 https://mypy.readthedocs.io/en/stable/protocols.html +RedirectTemp /13-58 https://pymotw.com/3/abc/index.html +RedirectTemp /13-59 https://www.python.org/dev/peps/pep-3119/ +RedirectTemp /13-60 https://www.python.org/dev/peps/pep-3141/ +RedirectTemp /13-61 https://docs.python.org/3/library/numbers.html +RedirectTemp /13-62 https://github.com/python/mypy/issues/3186 +RedirectTemp /13-63 https://github.com/python/mypy/issues/3186 +RedirectTemp /13-64 https://martinfowler.com/articles/lean-inception/ +RedirectTemp /13-65 https://martinfowler.com +RedirectTemp /13-68 https://www.jetbrains.com/pycharm/ +RedirectTemp /13-69 https://wingware.com/ +RedirectTemp /13-70 https://code.visualstudio.com/ +############################################################ 14 +RedirectTemp /14-1 http://worrydream.com/EarlyHistoryOfSmalltalk/ +RedirectTemp /14-2 https://docs.python.org/3/tutorial/classes.html +RedirectTemp /14-3 https://docs.python.org/3/library/collections.html#ordereddict-examples-and-recipes +RedirectTemp /14-4 https://discuss.python.org/t/is-it-time-to-deprecate-unbound-super-methods/1833 +RedirectTemp /14-6 https://doc.pypy.org/en/latest/cpython_differences.html#subclasses-of-built-in-types +RedirectTemp /14-7 https://docs.python.org/3/library/collections.html +RedirectTemp /14-9 https://doc.pypy.org/en/latest/cpython_differences.html#subclasses-of-built-in-types +RedirectTemp /14-10 https://en.wikipedia.org/wiki/Breadth-first_search +RedirectTemp /14-11 https://www.python.org/download/releases/2.3/mro/ +RedirectTemp /14-12 https://github.com/fluentpython/example-code-2e/blob/master/14-inheritance/uppermixin.py +RedirectTemp /14-13 https://docs.oracle.com/javase/tutorial/java/IandI/defaultmethods.html +RedirectTemp /14-14 https://docs.python.org/3/library/collections.abc.html +RedirectTemp /14-15 https://github.com/python/cpython/blob/8ece98a7e418c3c68a4c61bc47a2d0931b59a889/Lib/collections/__init__.py#L1084 +RedirectTemp /14-16 https://docs.python.org/3/library/http.server.html +RedirectTemp /14-17 https://docs.python.org/3/library/socketserver.html#socketserver.ForkingMixIn +RedirectTemp /14-18 https://docs.python.org/3/library/os.html#os.fork +RedirectTemp /14-19 https://en.wikipedia.org/wiki/POSIX +RedirectTemp /14-20 http://ccbv.co.uk/ +RedirectTemp /14-21 https://github.com/django/django/tree/main/django/views/generic +RedirectTemp /14-22 https://en.wikipedia.org/wiki/Template_method_pattern +RedirectTemp /14-23 https://docs.python.org/3/library/tkinter.html +RedirectTemp /14-24 https://docs.python.org/3/library/tkinter.ttk.html +RedirectTemp /14-25 https://docs.oracle.com/javase/10/docs/api/java/awt/package-tree.html +RedirectTemp /14-26 https://docs.oracle.com/javase/10/docs/api/javax/swing/package-tree.html +RedirectTemp /14-27 https://squeak.org/ +RedirectTemp /14-28 https://github.com/python/cpython/blob/8ed183391241f0c73e7ba7f42b1d49fc02985f7b/Lib/tkinter/__init__.py#L2618 +RedirectTemp /14-29 https://docs.python.org/3/library/socketserver.html +RedirectTemp /14-30 https://docs.python.org/3/library/socketserver.html#socketserver.BaseServer +RedirectTemp /14-32 https://docs.python.org/3/library/typing.html#typing.final +RedirectTemp /14-33 https://docs.python.org/3/library/typing.html#typing.Final +RedirectTemp /14-35 https://docs.python.org/3/library/collections.abc.html +RedirectTemp /14-36 https://hynek.me/articles/python-subclassing-redux/ +RedirectTemp /14-38 https://rhettinger.wordpress.com/2011/05/26/super-considered-super/ +RedirectTemp /14-39 https://fuhm.net/super-harmful/ +RedirectTemp /14-40 https://www.artima.com/weblogs/viewpost.jsp?thread=246488 +RedirectTemp /14-41 https://www.artima.com/weblogs/viewpost.jsp?thread=281127 +RedirectTemp /14-42 https://www.artima.com/weblogs/viewpost.jsp?thread=246341 +RedirectTemp /14-43 https://www.artima.com/weblogs/viewpost.jsp?thread=246483 +RedirectTemp /14-44 https://www.artima.com/weblogs/viewpost.jsp?thread=236275 +RedirectTemp /14-45 https://www.artima.com/weblogs/viewpost.jsp?thread=236278 +RedirectTemp /14-46 https://www.artima.com/weblogs/viewpost.jsp?thread=237121 +RedirectTemp /14-47 https://python-patterns.guide/gang-of-four/composition-over-inheritance/ +RedirectTemp /14-48 https://python-patterns.guide/ +RedirectTemp /14-49 https://www.youtube.com/watch?v=3MNVP9-hglc +RedirectTemp /14-50 http://worrydream.com/EarlyHistoryOfSmalltalk/ +RedirectTemp /14-51 https://en.wikipedia.org/wiki/Polymorphism_(computer_science) +############################################################ 15 +RedirectTemp /15-1 https://www.youtube.com/watch?v=csL8DLXGNlU&t=92m5s +RedirectTemp /15-4 https://twitter.com/gwidion/status/1265384692464967680 +RedirectTemp /15-5 https://pypi.org/project/pydantic/ +RedirectTemp /15-7 https://google.github.io/pytype/faq.html +RedirectTemp /15-8 https://google.github.io/pytype/faq.html +RedirectTemp /15-9 https://lxml.de/ +RedirectTemp /15-10 https://docs.python.org/3/library/xml.etree.elementtree.html +RedirectTemp /15-11 https://mypy.readthedocs.io/en/stable/common_issues.html +RedirectTemp /15-12 https://mypy.readthedocs.io/en/stable/common_issues.html#types-of-empty-collections +RedirectTemp /15-13 https://github.com/python/typing/issues/182 +RedirectTemp /15-14 https://pypi.org/project/pydantic/ +RedirectTemp /15-16 https://mypy.readthedocs.io/en/stable/type_narrowing.html#casts +RedirectTemp /15-17 https://www.python.org/dev/peps/pep-0484/#casts +RedirectTemp /15-18 https://github.com/python/typeshed/issues/5535 +RedirectTemp /15-19 https://docs.python.org/3/library/asyncio-stream.html#tcp-echo-server-using-streams +RedirectTemp /15-20 https://en.wikipedia.org/wiki/Code_smell +RedirectTemp /15-21 https://mypy.readthedocs.io/en/stable/error_codes.html#error-codes +RedirectTemp /15-22 https://github.com/fluentpython/example-code-2e/blob/master/15-more-types/clip_annot.py +RedirectTemp /15-24 https://docs.python.org/3/library/typing.html#introspection-helpers +RedirectTemp /15-25 https://docs.python.org/3.10/library/inspect.html#inspect.get_annotations +RedirectTemp /15-27 https://www.python.org/dev/peps/pep-0563/#abstract +RedirectTemp /15-29 https://docs.python.org/3.10/howto/annotations.html +RedirectTemp /15-32 https://docs.python.org/3/library/typing.html#user-defined-generic-types +RedirectTemp /15-33 https://docs.python.org/3.10/library/typing.html#typing.FrozenSet +RedirectTemp /15-34 https://docs.python.org/3.10/library/typing.html#typing.Generator +RedirectTemp /15-36 https://docs.python.org/3.10/library/typing.html#typing.AsyncGenerator +RedirectTemp /15-62 https://www.oreilly.com/library/view/robust-python/9781098100650/ +RedirectTemp /15-63 https://www.python.org/dev/peps/pep-0484/#covariance-and-contravariance +RedirectTemp /15-64 https://mypy.readthedocs.io/en/stable/generics.html#variance-of-generic-types +RedirectTemp /15-65 https://mypy.readthedocs.io/en/stable/common_issues.html#variance +RedirectTemp /15-67 https://www.artima.com/weblogs/viewpost.jsp?thread=85551 +RedirectTemp /15-68 https://dl.acm.org/action/cookieAbsent +RedirectTemp /15-69 http://bracha.org/pluggableTypesPosition.pdf +RedirectTemp /15-70 https://www.atomickotlin.com/atomickotlin/ +RedirectTemp /15-71 https://www.informit.com/store/effective-java-9780134685991 +RedirectTemp /15-72 https://www.manning.com/books/programming-with-types +RedirectTemp /15-73 https://www.oreilly.com/library/view/programming-typescript/9781492037644/ +RedirectTemp /15-74 https://www.informit.com/store/dart-programming-language-9780321927705 +RedirectTemp /15-75 https://www.yodaiken.com/2017/09/15/bad-ideas-in-type-theory/ +RedirectTemp /15-76 https://www.yodaiken.com/2017/11/30/types-considered-harmful-ii/ +RedirectTemp /15-77 https://web.archive.org/web/20071010002142/http://weblogs.java.net/blog/arnold/archive/2005/06/generics_consid_1.html +RedirectTemp /15-78 https://www.python.org/dev/peps/pep-0484/#covariance-and-contravariance +############################################################ 16 +RedirectTemp /16-1 http://www.gotw.ca/publications/c_family_interview.htm +RedirectTemp /16-2 https://docs.python.org/3/reference/expressions.html#unary-arithmetic-and-bitwise-operations +RedirectTemp /16-4 https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#boolean-indexing +RedirectTemp /16-5 https://docs.python.org/3/library/collections.html#collections.Counter +RedirectTemp /16-6 https://docs.python.org/3/reference/datamodel.html#emulating-container-types +RedirectTemp /16-8 https://docs.python.org/3/library/numbers.html#implementing-the-arithmetic-operations +RedirectTemp /16-10 https://www.fluentpython.com/lingo/#fail-fast +RedirectTemp /16-11 https://neopythonic.blogspot.com/2019/03/why-operators-are-useful.html +RedirectTemp /16-12 https://treyhunner.com/2019/03/python-deep-comparisons-and-code-readability/ +RedirectTemp /16-14 https://docs.python.org/3/library/numbers.html#implementing-the-arithmetic-operations +RedirectTemp /16-15 https://docs.python.org/3/library/pathlib.html +RedirectTemp /16-16 https://pypi.org/project/scapy/ +RedirectTemp /16-17 https://scapy.readthedocs.io/en/latest/usage.html#stacking-layers +RedirectTemp /16-18 https://docs.python.org/3/library/functools.html#functools.total_ordering +RedirectTemp /16-19 https://wiki.illinois.edu//wiki/download/attachments/273416327/ingalls.pdf +RedirectTemp /16-20 https://wiki.illinois.edu//wiki/download/attachments/273416327/double-dispatch.pdf +RedirectTemp /16-21 http://www.gotw.ca/publications/c_family_interview.htm +RedirectTemp /16-22 https://doc.rust-lang.org/std/ops/index.html +RedirectTemp /16-23 https://www.fluentpython.com/lingo/#lazy +############################################################ 17 +RedirectTemp /17-1 http://www.paulgraham.com/icad.html +RedirectTemp /17-5 https://en.wikipedia.org/wiki/Sentinel_value +RedirectTemp /17-6 https://docs.python.org/3.10/library/functions.html#iter +RedirectTemp /17-7 https://docs.python.org/3.10/library/functions.html#iter +RedirectTemp /17-8 https://github.com/python/cpython/blob/b1930bf75f276cd7ca08c4455298128d89adf7d1/Lib/_collections_abc.py#L271 +RedirectTemp /17-9 https://github.com/python/cpython/blob/main/Lib/types.py#L6 +RedirectTemp /17-10 https://en.wikipedia.org/wiki/CLU_(programming_language) +RedirectTemp /17-12 https://docs.python.org/3/glossary.html +RedirectTemp /17-13 https://docs.python.org/3/glossary.html#term-generator-iterator +RedirectTemp /17-14 https://docs.python.org/3/glossary.html#term-generator-expression +RedirectTemp /17-15 https://marc.info/?l=python-list&m=141826925106951&w=2 +RedirectTemp /17-17 https://docs.python.org/3/library/itertools.html +RedirectTemp /17-18 https://docs.python.org/3/library/exceptions.html#exception-hierarchy +RedirectTemp /17-19 https://en.wikipedia.org/wiki/Depth-first_search +RedirectTemp /17-20 https://docs.python.org/3.10/library/typing.html#typing.TypeAlias +RedirectTemp /17-22 https://docs.python.org/3/library/typing.html#typing.Generator +RedirectTemp /17-25 http://www.dabeaz.com/coroutines/Coroutines.pdf +RedirectTemp /17-26 http://www.dabeaz.com/coroutines/Coroutines.pdf +RedirectTemp /17-27 https://mail.python.org/pipermail/python-ideas/2009-April/003841.html +RedirectTemp /17-28 https://mail.python.org/pipermail/python-ideas/2009-April/003912.html +RedirectTemp /17-31 https://docs.python.org/3/library/exceptions.html#StopIteration +RedirectTemp /17-32 https://docs.python.org/3/reference/expressions.html#yield-expressions +RedirectTemp /17-33 https://docs.python.org/3/reference/index.html +RedirectTemp /17-36 http://catb.org/~esr/jargon/html/G/grok.html +RedirectTemp /17-37 https://docs.python.org/3/reference/expressions.html#yieldexpr +RedirectTemp /17-39 https://docs.python.org/3/library/itertools.html#itertools-recipes +RedirectTemp /17-40 https://more-itertools.readthedocs.io/en/stable/index.html +RedirectTemp /17-41 https://rittau.org/2006/11/java-iterators-are-not-iterable/ +RedirectTemp /17-42 https://docs.python.org/3/whatsnew/3.3.html#pep-380-syntax-for-delegating-to-a-subgenerator +RedirectTemp /17-45 http://www.dabeaz.com/generators/ +RedirectTemp /17-46 http://www.dabeaz.com/coroutines/ +RedirectTemp /17-47 https://archive.org/details/pyvideo_213___pycon-2009-a-curious-course-on-coroutines-and-concurrency-part-1-of-3 +RedirectTemp /17-48 https://archive.org/details/pyvideo_215___pycon-2009-a-curious-course-on-coroutines-and-concurrency-part-2-of-3 +RedirectTemp /17-49 https://archive.org/details/pyvideo_214___pycon-2009-a-curious-course-on-coroutines-and-concurrency-part-3-of-3 +RedirectTemp /17-50 http://www.dabeaz.com/finalgenerator/ +RedirectTemp /17-51 https://web.archive.org/web/20200218150637/http://seriously.dontusethiscode.com/2013/05/01/greedy-coroutine.html +RedirectTemp /17-52 https://effectivepython.com/ +RedirectTemp /17-53 https://effectivepython.com/2015/03/10/consider-coroutines-to-run-many-functions-concurrently +RedirectTemp /17-54 https://en.wikipedia.org/wiki/Conway's_Game_of_Life +RedirectTemp /17-55 https://gist.github.com/ramalho/da5590bc38c973408839 +RedirectTemp /17-56 https://gist.github.com/ramalho/da5590bc38c973408839 +RedirectTemp /17-57 https://journal.code4lib.org/articles/4893 +RedirectTemp /17-58 https://github.com/fluentpython/isis2json +RedirectTemp /17-59 https://github.com/fluentpython/isis2json/blob/master/README.rst +############################################################ 18 +RedirectTemp /18-1 https://pyvideo.org/video/1669/keynote-3/ +RedirectTemp /18-2 https://docs.python.org/3/library/sqlite3.html#using-the-connection-as-a-context-manager +RedirectTemp /18-3 https://docs.python.org/3/library/threading.html#using-locks-conditions-and-semaphores-in-the-with-statement +RedirectTemp /18-4 https://docs.python.org/3/library/decimal.html#decimal.localcontext +RedirectTemp /18-5 https://docs.python.org/3/library/unittest.mock.html#patch +RedirectTemp /18-6 https://docs.python.org/3/library/contextlib.html#contextlib.redirect_stdout +RedirectTemp /18-7 https://docs.python.org/3/library/sys.html#sys.exc_info +RedirectTemp /18-9 https://en.wikipedia.org/wiki/LL_parser +RedirectTemp /18-10 https://docs.python.org/3/library/contextlib.html +RedirectTemp /18-11 https://www.zopatista.com/python/2013/11/26/inplace-file-rewriting/ +RedirectTemp /18-12 https://docs.python.org/3/library/fileinput.html#fileinput.input +RedirectTemp /18-13 https://www.zopatista.com/python/2013/11/26/inplace-file-rewriting/ +RedirectTemp /18-14 https://en.wikipedia.org/wiki/Euclidean_algorithm +RedirectTemp /18-15 https://github.com/fluentpython/example-code-2e/tree/master/18-with-match/lispy/py3.10/ +RedirectTemp /18-17 https://github.com/python/typeshed/issues/6042 +RedirectTemp /18-18 https://github.com/fluentpython/lispy/tree/main/mylis +RedirectTemp /18-19 https://mitpress.mit.edu/sites/default/files/sicp/index.html +RedirectTemp /18-20 https://www.python.org/dev/peps/pep-0634/#or-patterns +RedirectTemp /18-21 https://en.wikipedia.org/wiki/Lambda#Character_encodings +RedirectTemp /18-22 https://docs.python.org/3/reference/compound_stmts.html +RedirectTemp /18-23 https://docs.python.org/3/glossary.html#term-eafp +RedirectTemp /18-24 https://speakerdeck.com/pyconslides/pycon-keynote-python-is-awesome-by-raymond-hettinger?slide=21 +RedirectTemp /18-25 https://docs.python.org/3/reference/compound_stmts.html +RedirectTemp /18-26 https://stackoverflow.com/questions/16138232/is-it-a-good-practice-to-use-try-except-else-in-python +RedirectTemp /18-27 https://docs.python.org/3/library/stdtypes.html#typecontextmanager +RedirectTemp /18-28 https://docs.python.org/3/reference/datamodel.html#with-statement-context-managers +RedirectTemp /18-30 https://speakerdeck.com/pyconslides/pycon-keynote-python-is-awesome-by-raymond-hettinger?slide=21 +RedirectTemp /18-31 https://speakerdeck.com/pyconslides/transforming-code-into-beautiful-idiomatic-python-by-raymond-hettinger-1?slide=34 +RedirectTemp /18-32 https://preshing.com/20110920/the-python-with-statement-by-example/ +RedirectTemp /18-33 https://www.rath.org/on-the-beauty-of-pythons-exitstack.html +RedirectTemp /18-34 https://github.com/norvig/pytudes +RedirectTemp /18-35 https://github.com/fluentpython/lispy +RedirectTemp /18-36 https://racket-lang.org/ +RedirectTemp /18-37 https://pyvideo.org/video/1669/keynote-3/ +RedirectTemp /18-38 https://en.wikipedia.org/wiki/Tail_call +RedirectTemp /18-39 https://2ality.com/2015/06/tail-call-optimization.html +RedirectTemp /18-40 http://neopythonic.blogspot.com/2009/04/final-words-on-tail-calls.html +RedirectTemp /18-41 https://webkit.org/blog/6240/ecmascript-6-proper-tail-calls-in-webkit/ +RedirectTemp /18-42 http://kangax.github.io/compat-table/es6/ +RedirectTemp /18-43 https://world.hey.com/mgmarlow/what-happened-to-proper-tail-calls-in-javascript-5494c256 +RedirectTemp /18-44 http://neopythonic.blogspot.com/2009/04/tail-recursion-elimination.html +RedirectTemp /18-45 https://github.com/fluentpython/lispy/blob/main/mylis/mylis_2/lis.py +RedirectTemp /18-46 https://github.com/fluentpython/lispy/tree/main/mylis +############################################################ 19 +RedirectTemp /19-1 https://go.dev/blog/waza-talk +RedirectTemp /19-2 https://en.wikipedia.org/wiki/Graphics_processing_unit +RedirectTemp /19-3 https://docs.python.org/3/library/sys.html#sys.getswitchinterval +RedirectTemp /19-4 https://docs.python.org/3/library/sys.html#sys.setswitchinterval +RedirectTemp /19-5 https://en.wikipedia.org/wiki/System_call +RedirectTemp /19-6 https://mail.python.org/pipermail/python-dev/2009-October/093356.html +RedirectTemp /19-8 http://www.dabeaz.com/finalgenerator/ +RedirectTemp /19-9 https://docs.python.org/3/library/threading.html#thread-objects +RedirectTemp /19-10 https://www.pypy.org/ +RedirectTemp /19-11 https://mail.python.org/pipermail/python-list/2009-February/675659.html +RedirectTemp /19-12 https://en.wikipedia.org/wiki/Braille_Patterns +RedirectTemp /19-13 https://docs.python.org/3/library/multiprocessing.shared_memory.html +RedirectTemp /19-14 https://greenlet.readthedocs.io/en/latest/ +RedirectTemp /19-15 https://docs.sqlalchemy.org/en/14/orm/extensions/asyncio.html +RedirectTemp /19-16 http://www.gevent.org/ +RedirectTemp /19-17 https://github.com/gevent/gevent/wiki/Projects +RedirectTemp /19-19 https://docs.python.org/3/library/concurrent.futures.html#processpoolexecutor-example +RedirectTemp /19-20 https://github.com/python/asyncio/issues/284 +RedirectTemp /19-21 https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.loop.run_in_executor +RedirectTemp /19-23 https://docs.python.org/3/library/queue.html#queue.SimpleQueue.get +RedirectTemp /19-24 https://en.wikipedia.org/wiki/Race_condition +RedirectTemp /19-26 https://en.wikipedia.org/wiki/Context_switch +RedirectTemp /19-27 http://www.gotw.ca/publications/concurrency-ddj.htm +RedirectTemp /19-28 https://www.ansible.com/ +RedirectTemp /19-29 https://saltproject.io/ +RedirectTemp /19-30 https://www.fabfile.org/ +RedirectTemp /19-31 https://jupyter.org/ +RedirectTemp /19-32 https://docs.bokeh.org/en/latest/index.html +RedirectTemp /19-33 https://www.tensorflow.org/ +RedirectTemp /19-34 https://pytorch.org/ +RedirectTemp /19-35 https://www.oreilly.com/radar/where-programming-ops-ai-and-the-cloud-are-headed-in-2021/ +RedirectTemp /19-38 https://www.youtube.com/watch?v=ods97a5Pzw0 +RedirectTemp /19-39 https://www.thoughtworks.com/radar/techniques/high-performance-envy-web-scale-envy +RedirectTemp /19-41 https://modwsgi.readthedocs.io/en/master/ +RedirectTemp /19-42 https://uwsgi-docs.readthedocs.io/en/latest/ +RedirectTemp /19-44 https://unit.nginx.org/ +RedirectTemp /19-45 https://www.techatbloomberg.com/blog/configuring-uwsgi-production-deployment/ +RedirectTemp /19-46 https://www.youtube.com/watch?v=p6R1h2Nn468 +RedirectTemp /19-47 https://asgi.readthedocs.io/en/latest/index.html +RedirectTemp /19-48 https://docs.celeryproject.org/en/stable/getting-started/introduction.html +RedirectTemp /19-49 https://python-rq.org/ +RedirectTemp /19-50 https://redis.io/ +RedirectTemp /19-51 https://realpython.com/intro-to-python-threading/ +RedirectTemp /19-52 https://pymotw.com/3/concurrency.html +RedirectTemp /19-54 https://docs.python.org/3/library/multiprocessing.html#programming-guidelines +RedirectTemp /19-56 https://docs.python.org/3/library/multiprocessing.html +RedirectTemp /19-57 https://www.oreilly.com/library/view/high-performance-python/9781492055013/ +RedirectTemp /19-58 https://link.springer.com/book/10.1007/978-1-4842-5793-7?error=cookies_not_supported&code=2ed5d61d-ae9f-4f3d-94ac-0f68cf45ea4f +RedirectTemp /19-59 https://www.packtpub.com/product/parallel-programming-with-python/9781783288397 +RedirectTemp /19-61 https://greenteapress.com/wp/semaphores/ +RedirectTemp /19-62 https://docs.python.org/3/c-api/init.html#thread-state-and-the-global-interpreter-lock +RedirectTemp /19-63 https://www.artima.com/weblogs/viewpost.jsp?thread=214235 +RedirectTemp /19-64 http://jessenoller.com/blog/2009/02/01/python-threads-and-the-global-interpreter-lock +RedirectTemp /19-65 https://realpython.com/products/cpython-internals-book/ +RedirectTemp /19-66 http://www.dabeaz.com/GIL/ +RedirectTemp /19-67 http://www.dabeaz.com/python/UnderstandingGIL.pdf +RedirectTemp /19-68 https://bugs.python.org/issue7946#msg223110 +RedirectTemp /19-69 https://bugs.python.org/issue7946 +RedirectTemp /19-70 https://www.fullstackpython.com/ +RedirectTemp /19-71 https://www.oreilly.com/library/view/high-performance-python/9781492055013/ +RedirectTemp /19-72 https://www.packtpub.com/product/parallel-programming-with-python/9781783288397 +RedirectTemp /19-73 https://www.packtpub.com/product/distributed-computing-with-python/9781785889691 +RedirectTemp /19-74 https://towardsdatascience.com/python-performance-and-gpus-1be860ffd58d?gi=6a57a172ab5e +RedirectTemp /19-75 https://www.oreilly.com/library/view/architecture-patterns-with/9781492052197/ +RedirectTemp /19-76 https://www.cosmicpython.com/ +RedirectTemp /19-77 https://pypi.org/project/lelo/ +RedirectTemp /19-78 https://github.com/npryce/python-parallelize +RedirectTemp /19-79 https://github.com/ericsnowcurrently/multi-core-python/wiki +RedirectTemp /19-81 https://gist.github.com/markshannon/79cace3656b40e21b7021504daee950c +RedirectTemp /19-82 https://en.wikipedia.org/wiki/Communicating_sequential_processes +RedirectTemp /19-83 https://github.com/stackless-dev/stackless/wiki +RedirectTemp /19-84 https://www.eveonline.com +RedirectTemp /19-85 https://www.ccpgames.com/ +RedirectTemp /19-86 https://stackless.readthedocs.io/en/3.6-slp/stackless-python.html#history +RedirectTemp /19-87 https://doc.pypy.org/en/latest/stackless.html +RedirectTemp /19-88 https://greenlet.readthedocs.io/en/latest/ +RedirectTemp /19-89 http://www.gevent.org/ +RedirectTemp /19-91 http://thespianpy.com/doc/ +RedirectTemp /19-92 https://pykka.readthedocs.io/en/latest/ +RedirectTemp /19-93 https://www.manning.com/books/rabbitmq-in-action +RedirectTemp /19-94 https://pragprog.com/titles/pb7con/seven-concurrency-models-in-seven-weeks/ +RedirectTemp /19-95 https://en.wikipedia.org/wiki/OpenCL +RedirectTemp /19-96 https://media.pragprog.com/titles/pb7con/Bonus_Chapter.pdf +RedirectTemp /19-97 https://martinfowler.com/ +RedirectTemp /19-98 https://martinfowler.com/articles/patterns-of-distributed-systems/ +RedirectTemp /19-99 https://www.oreilly.com/library/view/oscon-2016-video/9781491965153/video247021.html +RedirectTemp /19-100 https://www.oreilly.com/library/view/designing-for-scalability/9781449361556/ +RedirectTemp /19-101 https://www.thoughtworks.com/radar/techniques/high-performance-envy-web-scale-envy +RedirectTemp /19-102 https://en.wikipedia.org/wiki/KISS_principle +RedirectTemp /19-103 https://www.usenix.org/conference/hotos15/workshop-program/presentation/mcsherry +############################################################ 20 +RedirectTemp /20-1 https://www.artima.com/weblogs/viewpost.jsp?thread=299551 +RedirectTemp /20-3 https://docs.python.org/3/library/http.server.html +RedirectTemp /20-4 https://www.youtube.com/watch?v=A9e9Cy1UkME +RedirectTemp /20-5 https://www.cia.gov/the-world-factbook/ +RedirectTemp /20-7 https://docs.python-requests.org/en/latest/ +RedirectTemp /20-8 https://docs.python.org/3/library/concurrent.futures.html#concurrent.futures.as_completed +RedirectTemp /20-9 https://docs.python.org/3/library/concurrent.futures.html +RedirectTemp /20-10 https://docs.python.org/3.10/library/concurrent.futures.html#concurrent.futures.Executor +RedirectTemp /20-12 https://github.com/noamraph/tqdm +RedirectTemp /20-13 https://www.youtube.com/watch?v=M8Z65tAl5l4 +RedirectTemp /20-14 https://github.com/noamraph/tqdm/blob/master/README.md +RedirectTemp /20-15 https://docs.python.org/3/library/concurrent.futures.html#concurrent.futures.as_completed +RedirectTemp /20-16 https://docs.python.org/3/library/asyncio-task.html#asyncio.as_completed +RedirectTemp /20-17 https://www.cloudflare.com/ +RedirectTemp /20-19 https://github.com/fluentpython/example-code-2e/tree/master/20-executors/getflags +RedirectTemp /20-21 https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/418 +RedirectTemp /20-22 https://en.wikipedia.org/wiki/Embarrassingly_parallel +RedirectTemp /20-23 https://pyvideo.org/video/480/pyconau-2010--the-future-is-soon/ +RedirectTemp /20-25 http://www.dabeaz.com/coroutines/ +RedirectTemp /20-26 https://en.wikipedia.org/wiki/POSIX_Threads +RedirectTemp /20-27 https://en.wikipedia.org/wiki/C_dynamic_memory_allocation +RedirectTemp /20-28 https://pragprog.com/titles/pb7con/seven-concurrency-models-in-seven-weeks/ +RedirectTemp /20-29 https://hexdocs.pm/ecto/getting-started.html +############################################################ 21 +RedirectTemp /21-1 https://docs.python.org/3/library/asyncio.html +RedirectTemp /21-6 https://bugs.python.org/issue43216 +RedirectTemp /21-7 https://bugs.python.org/issue36921 +RedirectTemp /21-8 https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.loop.getaddrinfo +RedirectTemp /21-9 https://docs.python.org/3/library/socket.html#socket.getaddrinfo +RedirectTemp /21-10 https://docs.python.org/3.10/library/asyncio-eventloop.html#asyncio.get_event_loop +RedirectTemp /21-11 https://www.python.org/dev/peps/pep-0492/#await-expression +RedirectTemp /21-14 https://www.fluentpython.com/extra/classic-coroutines/#yield_from_meaning_sec +RedirectTemp /21-17 https://github.com/fluentpython/example-code-2e/tree/master/20-executors/getflags +RedirectTemp /21-18 https://magicstack.github.io/asyncpg/current/ +RedirectTemp /21-19 https://magicstack.github.io/asyncpg/current/api/index.html#transactions +RedirectTemp /21-21 https://magicstack.github.io/asyncpg/current/api/index.html#transactions +RedirectTemp /21-22 https://magicstack.github.io/asyncpg/current/usage.html#connection-pools +RedirectTemp /21-23 https://gist.github.com/jboner/2841832 +RedirectTemp /21-24 https://en.wikipedia.org/wiki/Network-attached_storage +RedirectTemp /21-25 https://en.wikipedia.org/wiki/Semaphore_(programming) +RedirectTemp /21-26 https://en.wikipedia.org/wiki/Semaphore_(programming) +RedirectTemp /21-27 https://groups.google.com/forum/#!msg/python-tulip/PdAEtwpaJHs/7fqb-Qj2zJoJ +RedirectTemp /21-28 https://tritarget.org/#blog/2012/11/28/the-pyramid-of-doom-a-javascript-style-trap +RedirectTemp /21-29 https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.loop.run_in_executor +RedirectTemp /21-30 https://motor.readthedocs.io/en/stable/ +RedirectTemp /21-31 https://emptysqua.re/blog/response-to-asynchronous-python-and-databases/ +RedirectTemp /21-33 https://docs.python.org/3/library/asyncio-stream.html#tcp-echo-server-using-streams +RedirectTemp /21-35 https://en.wikipedia.org/wiki/Phaistos_Disc +RedirectTemp /21-36 https://en.wikipedia.org/wiki/Inverted_index +RedirectTemp /21-37 https://fastapi.tiangolo.com/ +RedirectTemp /21-38 https://swagger.io/specification/ +RedirectTemp /21-40 https://asgi.readthedocs.io/en/latest/implementations.html +RedirectTemp /21-41 https://pydantic-docs.helpmanual.io/ +RedirectTemp /21-42 https://doc.traefik.io/traefik/ +RedirectTemp /21-43 https://fastapi.tiangolo.com/project-generation/ +RedirectTemp /21-44 https://fastapi.tiangolo.com/tutorial/response-model/ +RedirectTemp /21-45 https://docs.python.org/3/library/asyncio-stream.html#asyncio.start_server +RedirectTemp /21-46 https://github.com/python/typeshed/issues/5535 +RedirectTemp /21-47 https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.Server.serve_forever +RedirectTemp /21-48 https://docs.python.org/3/library/asyncio-stream.html#asyncio.StreamWriter.close +RedirectTemp /21-49 https://docs.python.org/3/library/asyncio-stream.html#streamwriter +RedirectTemp /21-50 https://docs.python.org/3/library/asyncio-stream.html +RedirectTemp /21-51 https://docs.python.org/3/library/asyncio-protocol.html +RedirectTemp /21-52 https://docs.python.org/3/library/asyncio-protocol.html#tcp-echo-server +RedirectTemp /21-53 https://github.com/aio-libs/aiopg +RedirectTemp /21-54 https://docs.python.org/3/whatsnew/3.8.html#asyncio +RedirectTemp /21-56 https://datatracker.ietf.org/doc/html/rfc6761 +RedirectTemp /21-57 https://docs.python.org/3/library/contextlib.html#contextlib.asynccontextmanager +RedirectTemp /21-59 https://docs.python.org/3/library/contextlib.html#contextlib.asynccontextmanager +RedirectTemp /21-61 https://docs.python.org/3/library/asyncio-task.html#asyncio.gather +RedirectTemp /21-62 https://curio.readthedocs.io/en/latest/index.html +RedirectTemp /21-63 https://curio.readthedocs.io/en/latest/reference.html#task-groups +RedirectTemp /21-64 https://en.wikipedia.org/wiki/Structured_concurrency +RedirectTemp /21-66 https://www.python.org/dev/peps/pep-0654/#motivation +RedirectTemp /21-67 https://curio.readthedocs.io/en/latest/reference.html#AWAIT +RedirectTemp /21-68 https://www.python-httpx.org/async/#curio +RedirectTemp /21-69 https://github.com/dabeaz/curio/tree/78bca8a6ad677ef51e1568ac7b3e51441ab49c42/examples +RedirectTemp /21-70 https://datatracker.ietf.org/doc/html/rfc8305 +RedirectTemp /21-71 https://trio.readthedocs.io/en/stable/ +RedirectTemp /21-72 https://www.youtube.com/watch?v=M-sc73Y-zQA +RedirectTemp /21-73 https://en.wikipedia.org/wiki/Technical_debt +RedirectTemp /21-74 https://www.youtube.com/watch?v=E-1Y4kSsAFc +RedirectTemp /21-76 https://github.com/dabeaz/curio +RedirectTemp /21-77 https://trio.readthedocs.io/en/stable/ +RedirectTemp /21-78 https://curio.readthedocs.io/en/latest/#curio-university +RedirectTemp /21-79 https://docs.python.org/3/library/asyncio.html +RedirectTemp /21-80 https://bugs.python.org/issue33649 +RedirectTemp /21-82 https://docs.python.org/3/library/asyncio-dev.html +RedirectTemp /21-83 https://www.youtube.com/watch?v=iG6fr81xHKA +RedirectTemp /21-84 https://www.youtube.com/watch?v=F19R_M4Nay4 +RedirectTemp /21-85 https://asherman.io/projects/unsync.html +RedirectTemp /21-86 https://pyladies.com/ +RedirectTemp /21-87 https://www.youtube.com/watch?v=sW76-pRkZk8 +RedirectTemp /21-88 https://www.youtube.com/watch?v=Xbl7XjFYsN4 +RedirectTemp /21-89 https://www.youtube.com/watch?v=02CLD-42VdI +RedirectTemp /21-90 https://micropython.org/ +RedirectTemp /21-91 https://docs.micropython.org/en/latest/library/uasyncio.html +RedirectTemp /21-92 https://www.encode.io/articles/python-async-frameworks-beyond-developer-tribalism +RedirectTemp /21-93 https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/ +RedirectTemp /21-94 https://github.com/MagicStack/uvloop +RedirectTemp /21-95 http://magic.io/blog/uvloop-blazing-fast-python-networking/ +RedirectTemp /21-96 https://github.com/MagicStack/httptools +RedirectTemp /21-97 https://docs.aiohttp.org/en/stable/ +RedirectTemp /21-98 https://github.com/wg/wrk +RedirectTemp /21-99 https://twistedmatrix.com/trac/ +############################################################ 22 +RedirectTemp /22-4 https://pypi.org/project/attrdict/ +RedirectTemp /22-5 https://pypi.org/project/addict/ +RedirectTemp /22-7 https://github.com/ActiveState/code/tree/master/recipes/Python/52308_simple_but_handy_collector_bunch_named_stuff +RedirectTemp /22-8 https://docs.python.org/3/library/types.html#types.SimpleNamespace +RedirectTemp /22-9 https://docs.python.org/3/library/argparse.html#argparse.Namespace +RedirectTemp /22-12 https://docs.python.org/3/library/functools.html#functools.cached_property +RedirectTemp /22-13 https://docs.python.org/3/library/functools.html#functools.cached_property +RedirectTemp /22-14 https://bugs.python.org/issue42781 +RedirectTemp /22-15 https://docs.python.org/3/howto/descriptor.html +RedirectTemp /22-16 https://docs.python.org/3/library/threading.html#rlock-objects +RedirectTemp /22-17 https://docs.python.org/3.10/library/functools.html#functools.cached_property +RedirectTemp /22-18 https://www.wsj.com/articles/SB10001424052970203914304576627102996831200 +RedirectTemp /22-19 https://www.youtube.com/watch?v=s35rVw1zskA&feature=youtu.be +RedirectTemp /22-20 https://docs.python.org/3/library/functions.html#dir +RedirectTemp /22-21 https://docs.python.org/3/library/functions.html#hasattr +RedirectTemp /22-22 https://docs.python.org/3.10/reference/datamodel.html#special-method-lookup +RedirectTemp /22-23 https://docs.python.org/3/library/functions.html +RedirectTemp /22-24 https://docs.python.org/3/reference/datamodel.html#customizing-attribute-access +RedirectTemp /22-25 https://docs.python.org/3/reference/datamodel.html#special-method-lookup +RedirectTemp /22-26 https://docs.python.org/3/library/stdtypes.html#special-attributes +RedirectTemp /22-27 http://wiki.c2.com/?WelcomeVisitors +RedirectTemp /22-28 http://wiki.c2.com/?UniformAccessPrinciple +RedirectTemp /22-29 https://docs.oracle.com/javase/tutorial/java/javaOO/accesscontrol.html +RedirectTemp /22-30 http://www.pingo.io/docs/ +RedirectTemp /22-31 https://www.drdobbs.com/javas-new-considered-harmful/184405016 +RedirectTemp /22-32 https://www.python.org/dev/peps/pep-0008/#class-names +############################################################ 23 +RedirectTemp /23-2 http://www.aleax.it/goo_pydp.pdf +RedirectTemp /23-3 https://docs.python.org/3.10/reference/datamodel.html#implementing-descriptors +RedirectTemp /23-6 https://docs.python.org/3/howto/descriptor.html +RedirectTemp /23-7 https://docs.python.org/3/howto/ +RedirectTemp /23-8 http://www.aleax.it/Python/nylug05_om.pdf +RedirectTemp /23-9 https://www.youtube.com/watch?v=VOzvpHoYQoo +RedirectTemp /23-11 https://www.python.org/dev/peps/pep-0487/#trait-descriptors +RedirectTemp /23-12 https://dreamsongs.com/RiseOfWorseIsBetter.html +RedirectTemp /23-13 http://web.archive.org/web/20031002184114/www.amk.ca/python/writing/warts.html +RedirectTemp /23-14 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this +RedirectTemp /23-15 http://python-history.blogspot.com/2009/02/adding-support-for-user-defined-classes.html +############################################################ 24 +RedirectTemp /24-2 https://docs.python.org/3/library/stdtypes.html#special-attributes +RedirectTemp /24-3 https://docs.djangoproject.com/en/3.2/topics/db/models/#meta-options +RedirectTemp /24-4 https://www.python.org/dev/peps/pep-3155/ +RedirectTemp /24-7 https://en.wikipedia.org/wiki/Tony_Hoare#Apologies_and_retractions +RedirectTemp /24-8 https://go.dev/tour/basics/12 +RedirectTemp /24-9 https://bugs.python.org/issue42102 +RedirectTemp /24-11 https://www.python.org/dev/peps/pep-0557/#abstract +RedirectTemp /24-12 https://github.com/python/cpython/blob/3.9/Lib/dataclasses.py +RedirectTemp /24-13 https://docs.python.org/3/reference/datamodel.html#creating-the-class-object +RedirectTemp /24-15 https://mail.python.org/pipermail/python-list/2002-December/134521.html +RedirectTemp /24-16 https://www.oreilly.com/library/view/python-in-a/9781491913833/ +RedirectTemp /24-17 https://mail.python.org/pipermail/python-list/2002-July/162558.html +RedirectTemp /24-18 https://github.com/fluentpython/example-code/tree/master/21-class-metaprog/bulkfood +RedirectTemp /24-19 https://en.wikipedia.org/wiki/Principle_of_least_astonishment +RedirectTemp /24-21 https://en.wikipedia.org/wiki/Trait_(computer_programming) +RedirectTemp /24-22 https://en.wikipedia.org/wiki/Aspect-oriented_programming +RedirectTemp /24-23 https://dhh.dk/arc/000416.html +RedirectTemp /24-24 https://github.com/cjrh/autoslot +RedirectTemp /24-25 https://docs.python.org/3/reference/datamodel.html#customizing-class-creation +RedirectTemp /24-26 https://docs.python.org/3/library/functions.html#type +RedirectTemp /24-27 https://docs.python.org/3/library/stdtypes.html#special-attributes +RedirectTemp /24-28 https://docs.python.org/3/library/types.html +RedirectTemp /24-29 https://www.python.org/dev/peps/pep-3129/ +RedirectTemp /24-30 https://www.youtube.com/watch?v=cAGliEJV9_o +RedirectTemp /24-31 https://docs.python.org/3/library/functools.html#functools.total_ordering +RedirectTemp /24-33 https://www.oreilly.com/library/view/python-in-a/9781491913833/ +RedirectTemp /24-36 https://www.python.org/download/releases/2.2.3/descrintro/ +RedirectTemp /24-42 https://github.com/lihaoyi/macropy +RedirectTemp /24-43 https://people.eecs.berkeley.edu/~bh/ss-toc2.html diff --git a/links/README.md b/links/README.md new file mode 100644 index 0000000..6ec844c --- /dev/null +++ b/links/README.md @@ -0,0 +1,37 @@ +# Short links for URLs in the book + +## The problem: link rot + +_Fluent Python, Second Edition_ has more than 1000 links to external resources. +Inevitably, some of those links will rot as time passes. +But I can't change the URLs in the print book! + +## The solution: indirection + +I replaced almost all URLs in the book with shortened versions that go through the `fpy.li` site which I control. +The site has an `.htaccess` file with *temporary* redirects. + +When I find out a link is stale, I can thange the redirect in `.htaccess` to a new target, +so the link in the book is back in service via the updated redirect. + +## Help wanted + +Please report broken links as bugs in the [`FPY.LI.htaccess`](FPY.LI.htaccess) file. +Also, feel free to send pull requests with fixes to that file. +When I accept a PR, I will redeploy it to `fpy.li/.htaccess`. + +## Details + +Almost all URLs in the book are replaced with shortened versions like +[`http://fpy.li/1-3`](http://fpy.li/1-3)—for chapter 1, link #3. + +There are also custom short URLs like +[`https://fpy.li/code`](https://fpy.li/code) which redirects to the example code repository. +I used custom short URLs for URLs with 3 or more mentions, or links to PEPs. + +Exceptions: + +- URLs with `oreilly` in them are unchanged; +- `fluentpython.com` URL (with no path) is unchanged; + +The `FPY.LI.htaccess` is deployed at the root folder in `http://fpy.li`. diff --git a/links/custom.htaccess b/links/custom.htaccess new file mode 100644 index 0000000..52649db --- /dev/null +++ b/links/custom.htaccess @@ -0,0 +1,109 @@ +ErrorDocument 404 /404.html + +# main resources +RedirectTemp /code https://github.com/fluentpython/example-code-2e +RedirectTemp /home https://www.fluentpython.com/ + +# URLs mentioned at least three times +RedirectTemp /bisect https://www.fluentpython.com/extra/ordered-sequences-with-bisect/ +RedirectTemp /cardxvi https://www.python.org/dev/peps/pep-0484/#the-numeric-tower +RedirectTemp /collec https://docs.python.org/3/library/collections.html +RedirectTemp /dask https://dask.org/ +RedirectTemp /dtmodel https://docs.python.org/3/reference/datamodel.html +RedirectTemp /descr101 https://www.python.org/download/releases/2.2.3/descrintro/ +RedirectTemp /descrhow https://docs.python.org/3/howto/descriptor.html +RedirectTemp /doctest https://docs.python.org/3/library/doctest.html +RedirectTemp /effectpy https://effectivepython.com/ +RedirectTemp /fmtspec https://docs.python.org/3/library/string.html#formatspec +RedirectTemp /gunicorn https://gunicorn.org/ +RedirectTemp /hashint https://www.fluentpython.com/extra/internals-of-sets-and-dicts/ +RedirectTemp /hattingh https://www.oreilly.com/library/view/using-asyncio-in/9781492075325/ +RedirectTemp /httpx https://www.python-httpx.org/ +RedirectTemp /initvar https://docs.python.org/3/library/dataclasses.html#init-only-variables +RedirectTemp /mypy https://mypy.readthedocs.io/en/stable/ +RedirectTemp /norvigdp http://norvig.com/design-patterns/ +RedirectTemp /nsphere https://en.wikipedia.org/wiki/N-sphere +RedirectTemp /oldcoro https://www.fluentpython.com/extra/classic-coroutines/ +RedirectTemp /pandas https://pandas.pydata.org/ +RedirectTemp /pep218 https://www.python.org/dev/peps/pep-0218/ +RedirectTemp /pep227 https://www.python.org/dev/peps/pep-0227/ +RedirectTemp /pep255 https://www.python.org/dev/peps/pep-0255/ +RedirectTemp /pep342 https://www.python.org/dev/peps/pep-0342/ +RedirectTemp /pep343 https://www.python.org/dev/peps/pep-0343/ +RedirectTemp /pep357 https://www.python.org/dev/peps/pep-0357/ +RedirectTemp /pep362 https://www.python.org/dev/peps/pep-0362/ +RedirectTemp /pep371 https://www.python.org/dev/peps/pep-0371/ +RedirectTemp /pep380 https://www.python.org/dev/peps/pep-0380/ +RedirectTemp /pep393 https://www.python.org/dev/peps/pep-0393/ +RedirectTemp /pep412 https://www.python.org/dev/peps/pep-0412/ +RedirectTemp /pep442 https://www.python.org/dev/peps/pep-0442/ +RedirectTemp /pep443 https://www.python.org/dev/peps/pep-0443/ +RedirectTemp /pep448 https://www.python.org/dev/peps/pep-0448/ +RedirectTemp /pep455 https://www.python.org/dev/peps/pep-0455/ +RedirectTemp /pep456 https://www.python.org/dev/peps/pep-0456/ +RedirectTemp /pep461 https://www.python.org/dev/peps/pep-0461/ +RedirectTemp /pep465 https://www.python.org/dev/peps/pep-0465/ +RedirectTemp /pep467 https://www.python.org/dev/peps/pep-0467/ +RedirectTemp /pep482 https://www.python.org/dev/peps/pep-0482/ +RedirectTemp /pep483 https://www.python.org/dev/peps/pep-0483/ +RedirectTemp /pep484 https://www.python.org/dev/peps/pep-0484/ +RedirectTemp /pep487 https://www.python.org/dev/peps/pep-0487/ +RedirectTemp /pep492 https://www.python.org/dev/peps/pep-0492/ +RedirectTemp /pep519 https://www.python.org/dev/peps/pep-0519/ +RedirectTemp /pep525 https://www.python.org/dev/peps/pep-0525/ +RedirectTemp /pep526 https://www.python.org/dev/peps/pep-0526/ +RedirectTemp /pep528 https://www.python.org/dev/peps/pep-0528/ +RedirectTemp /pep529 https://www.python.org/dev/peps/pep-0529/ +RedirectTemp /pep530 https://www.python.org/dev/peps/pep-0530/ +RedirectTemp /pep544 https://www.python.org/dev/peps/pep-0544/ +RedirectTemp /pep554 https://www.python.org/dev/peps/pep-0554/ +RedirectTemp /pep557 https://www.python.org/dev/peps/pep-0557/ +RedirectTemp /pep560 https://www.python.org/dev/peps/pep-0560/ +RedirectTemp /pep561 https://www.python.org/dev/peps/pep-0561/ +RedirectTemp /pep563 https://www.python.org/dev/peps/pep-0563/ +RedirectTemp /pep570 https://www.python.org/dev/peps/pep-0570/ +RedirectTemp /pep572 https://www.python.org/dev/peps/pep-0572/ +RedirectTemp /pep584 https://www.python.org/dev/peps/pep-0584/ +RedirectTemp /pep585 https://www.python.org/dev/peps/pep-0585/ +RedirectTemp /pep586 https://www.python.org/dev/peps/pep-0586/ +RedirectTemp /pep589 https://www.python.org/dev/peps/pep-0589/ +RedirectTemp /pep591 https://www.python.org/dev/peps/pep-0591/ +RedirectTemp /pep593 https://www.python.org/dev/peps/pep-0593/ +RedirectTemp /pep604 https://www.python.org/dev/peps/pep-0604/ +RedirectTemp /pep612 https://www.python.org/dev/peps/pep-0612/ +RedirectTemp /pep613 https://www.python.org/dev/peps/pep-0613/ +RedirectTemp /pep616 https://www.python.org/dev/peps/pep-0616/ +RedirectTemp /pep617 https://www.python.org/dev/peps/pep-0617/ +RedirectTemp /pep618 https://www.python.org/dev/peps/pep-0618/ +RedirectTemp /pep634 https://www.python.org/dev/peps/pep-0634/ +RedirectTemp /pep635 https://www.python.org/dev/peps/pep-0635/ +RedirectTemp /pep636 https://www.python.org/dev/peps/pep-0636/ +RedirectTemp /pep638 https://www.python.org/dev/peps/pep-0638/ +RedirectTemp /pep645 https://www.python.org/dev/peps/pep-0645/ +RedirectTemp /pep646 https://www.python.org/dev/peps/pep-0646/ +RedirectTemp /pep647 https://www.python.org/dev/peps/pep-0647/ +RedirectTemp /pep649 https://www.python.org/dev/peps/pep-0649/ +RedirectTemp /pep654 https://www.python.org/dev/peps/pep-0654/ +RedirectTemp /pep655 https://www.python.org/dev/peps/pep-0655/ +RedirectTemp /pep661 https://www.python.org/dev/peps/pep-0661/ +RedirectTemp /pep3099 https://www.python.org/dev/peps/pep-3099/ +RedirectTemp /pep3102 https://www.python.org/dev/peps/pep-3102/ +RedirectTemp /pep3104 https://www.python.org/dev/peps/pep-3104/ +RedirectTemp /pep3106 https://www.python.org/dev/peps/pep-3106/ +RedirectTemp /pep3107 https://www.python.org/dev/peps/pep-3107/ +RedirectTemp /pep3115 https://www.python.org/dev/peps/pep-3115/ +RedirectTemp /pep3118 https://www.python.org/dev/peps/pep-3118/ +RedirectTemp /pep3119 https://www.python.org/dev/peps/pep-3119/ +RedirectTemp /pep3129 https://www.python.org/dev/peps/pep-3129/ +RedirectTemp /pep3132 https://www.python.org/dev/peps/pep-3132/ +RedirectTemp /pep3141 https://www.python.org/dev/peps/pep-3141/ +RedirectTemp /pep3148 https://www.python.org/dev/peps/pep-3148/ +RedirectTemp /pep3155 https://www.python.org/dev/peps/pep-3155/ +RedirectTemp /pep3333 https://www.python.org/dev/peps/pep-3333/ +RedirectTemp /pypydif https://doc.pypy.org/en/latest/cpython_differences.html#subclasses-of-built-in-types +RedirectTemp /shed4051 https://github.com/python/typeshed/issues/4051 +RedirectTemp /slatkin https://effectivepython.com/ +RedirectTemp /specattr https://docs.python.org/3/library/stdtypes.html#special-attributes +RedirectTemp /typecoro https://docs.python.org/3.10/library/typing.html#typing.Coroutine +RedirectTemp /typing https://docs.python.org/3/library/typing.html +RedirectTemp /weakref https://www.fluentpython.com/extra/weak-references/ From 82b4fe2163174bfee23527e68d418eed2ae4f46b Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Sat, 15 Jan 2022 14:04:45 -0300 Subject: [PATCH 134/166] added links and README.md --- links/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/links/README.md b/links/README.md index 6ec844c..09c808a 100644 --- a/links/README.md +++ b/links/README.md @@ -1,18 +1,18 @@ # Short links for URLs in the book -## The problem: link rot +## Problem: link rot _Fluent Python, Second Edition_ has more than 1000 links to external resources. Inevitably, some of those links will rot as time passes. -But I can't change the URLs in the print book! +But I can't change the URLs in the print book... -## The solution: indirection +## Solution: indirection I replaced almost all URLs in the book with shortened versions that go through the `fpy.li` site which I control. The site has an `.htaccess` file with *temporary* redirects. When I find out a link is stale, I can thange the redirect in `.htaccess` to a new target, -so the link in the book is back in service via the updated redirect. +so the link in the book is back in service through the updated redirect. ## Help wanted From 646427f393a8d3463d8dc95f8eb0bc1744d2d002 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Sun, 16 Jan 2022 19:05:00 -0300 Subject: [PATCH 135/166] updated .htaccess --- links/FPY.LI.htaccess | 1567 ++++++++++++++++++++++------------------- links/custom.htaccess | 18 +- 2 files changed, 838 insertions(+), 747 deletions(-) diff --git a/links/FPY.LI.htaccess b/links/FPY.LI.htaccess index 579d7e4..9f960f1 100644 --- a/links/FPY.LI.htaccess +++ b/links/FPY.LI.htaccess @@ -25,6 +25,17 @@ RedirectTemp /norvigdp http://norvig.com/design-patterns/ RedirectTemp /nsphere https://en.wikipedia.org/wiki/N-sphere RedirectTemp /oldcoro https://www.fluentpython.com/extra/classic-coroutines/ RedirectTemp /pandas https://pandas.pydata.org/ +RedirectTemp /pycook3 https://www.oreilly.com/library/view/python-cookbook-3rd/9781449357337/ +RedirectTemp /pynut3 https://www.oreilly.com/library/view/python-in-a/9781491913833/ +RedirectTemp /pypydif https://doc.pypy.org/en/latest/cpython_differences.html#subclasses-of-built-in-types +RedirectTemp /shed4051 https://github.com/python/typeshed/issues/4051 +RedirectTemp /slatkin https://effectivepython.com/ +RedirectTemp /specattr https://docs.python.org/3/library/stdtypes.html#special-attributes +RedirectTemp /typecoro https://docs.python.org/3.10/library/typing.html#typing.Coroutine +RedirectTemp /typing https://docs.python.org/3/library/typing.html +RedirectTemp /weakref https://www.fluentpython.com/extra/weak-references/ + +# Python Enhancement Proposals RedirectTemp /pep218 https://www.python.org/dev/peps/pep-0218/ RedirectTemp /pep227 https://www.python.org/dev/peps/pep-0227/ RedirectTemp /pep255 https://www.python.org/dev/peps/pep-0255/ @@ -100,13 +111,6 @@ RedirectTemp /pep3141 https://www.python.org/dev/peps/pep-3141/ RedirectTemp /pep3148 https://www.python.org/dev/peps/pep-3148/ RedirectTemp /pep3155 https://www.python.org/dev/peps/pep-3155/ RedirectTemp /pep3333 https://www.python.org/dev/peps/pep-3333/ -RedirectTemp /pypydif https://doc.pypy.org/en/latest/cpython_differences.html#subclasses-of-built-in-types -RedirectTemp /shed4051 https://github.com/python/typeshed/issues/4051 -RedirectTemp /slatkin https://effectivepython.com/ -RedirectTemp /specattr https://docs.python.org/3/library/stdtypes.html#special-attributes -RedirectTemp /typecoro https://docs.python.org/3.10/library/typing.html#typing.Coroutine -RedirectTemp /typing https://docs.python.org/3/library/typing.html -RedirectTemp /weakref https://www.fluentpython.com/extra/weak-references/ # Remaining URLs by chapter @@ -114,18 +118,14 @@ RedirectTemp /weakref https://www.fluentpython.com/extra/weak-references/ RedirectTemp /p-1 https://mail.python.org/pipermail/python-list/2002-December/134521.html RedirectTemp /p-2 https://docs.python.org/3.10/tutorial/ RedirectTemp /p-3 https://docs.python.org/3/tutorial/ -RedirectTemp /p-9 https://www.oreilly.com/library/view/fluent-python-2nd/9781492056348/ -RedirectTemp /p-10 https://www.oreilly.com/online-learning/try-now.html -RedirectTemp /p-13 https://www.oreilly.com/library/view/fluent-python-2nd/9781492056348/ -RedirectTemp /p-14 https://www.oreilly.com/ -RedirectTemp /p-15 https://www.facebook.com/OReilly/ -RedirectTemp /p-16 https://twitter.com/oreillymedia -RedirectTemp /p-17 https://www.youtube.com/oreillymedia -RedirectTemp /p-18 https://stackoverflow.com/users/95810/alex-martelli -RedirectTemp /p-19 https://pythonpro.com.br -RedirectTemp /p-20 https://groups.google.com/g/python-brasil -RedirectTemp /p-21 https://www.coffeelab.com.br/ -RedirectTemp /p-22 https://garoa.net.br/wiki/P%C3%A1gina_principal +RedirectTemp /p-4 https://www.oreilly.com/library/view/fluent-python-2nd/9781492056348/ +RedirectTemp /p-5 https://www.oreilly.com/online-learning/try-now.html +RedirectTemp /p-6 https://www.oreilly.com/library/view/fluent-python-2nd/9781492056348/ +RedirectTemp /p-7 https://stackoverflow.com/users/95810/alex-martelli +RedirectTemp /p-8 https://pythonpro.com.br +RedirectTemp /p-9 https://groups.google.com/g/python-brasil +RedirectTemp /p-10 https://www.coffeelab.com.br/ +RedirectTemp /p-11 https://garoa.net.br/wiki/P%C3%A1gina_principal ############################################################ a RedirectTemp /a-1 https://groups.google.com/forum/#!topic/python-tulip/Y4bhLNbKs74 RedirectTemp /a-2 https://docs.python.org/3/library/asyncio-eventloop.html#executor @@ -144,133 +144,145 @@ RedirectTemp /a-14 https://docs.mongodb.com/manual/about/#about-the-documentatio RedirectTemp /a-15 https://blog.startifact.com/posts/older/what-is-pythonic.html RedirectTemp /a-16 https://mail.python.org/pipermail/tutor/2003-October/thread.html#25930 RedirectTemp /a-17 https://mail.python.org/pipermail/python-list/2003-April/192027.html -RedirectTemp /a-19 https://www.python.org/doc/essays/ +RedirectTemp /a-18 https://www.python.org/doc/essays/ ############################################################ 01 RedirectTemp /1-1 http://hugunin.net/story_of_jython.html -RedirectTemp /1-3 https://docs.python.org/2/library/string.html#format-string-syntax -RedirectTemp /1-4 https://stackoverflow.com/questions/1436703/what-is-the-difference-between-str-and-repr -RedirectTemp /1-5 https://docs.python.org/3/library/stdtypes.html#truth -RedirectTemp /1-6 https://docs.python.org/3/tutorial/controlflow.html#unpacking-argument-lists +RedirectTemp /1-2 https://www.oreilly.com/library/view/jython-essentials/9781449397364/ +RedirectTemp /1-3 https://docs.python.org/3/reference/lexical_analysis.html#reserved-classes-of-identifiers +RedirectTemp /1-4 https://docs.python.org/2/library/string.html#format-string-syntax +RedirectTemp /1-5 https://stackoverflow.com/questions/1436703/what-is-the-difference-between-str-and-repr +RedirectTemp /1-6 https://docs.python.org/3/library/stdtypes.html#truth +RedirectTemp /1-7 https://docs.python.org/3/tutorial/controlflow.html#unpacking-argument-lists RedirectTemp /1-8 https://www.python.org/doc/humor/#the-zen-of-python -RedirectTemp /1-10 https://stackoverflow.com/users/95810/alex-martelli -RedirectTemp /1-11 https://en.wikipedia.org/wiki/Object_model -RedirectTemp /1-13 https://www.dourish.com/goodies/jargon.html -RedirectTemp /1-14 https://zopeinterface.readthedocs.io/en/latest/ -RedirectTemp /1-15 https://plone.org/ +RedirectTemp /1-9 https://stackoverflow.com/users/95810/alex-martelli +RedirectTemp /1-10 https://en.wikipedia.org/wiki/Object_model +RedirectTemp /1-11 https://www.dourish.com/goodies/jargon.html +RedirectTemp /1-12 https://zopeinterface.readthedocs.io/en/latest/ +RedirectTemp /1-13 https://plone.org/ ############################################################ 02 -RedirectTemp /2-4 https://github.com/fluentpython/example-code-2e/blob/master/02-array-seq/listcomp_speed.py -RedirectTemp /2-6 https://www.python.org/dev/peps/pep-3132/ -RedirectTemp /2-8 https://docs.python.org/3/whatsnew/3.5.html#pep-448-additional-unpacking-generalizations -RedirectTemp /2-9 https://docs.python.org/3/whatsnew/3.5.html#pep-448-additional-unpacking-generalizations -RedirectTemp /2-11 https://docs.python.org/3.10/whatsnew/3.10.html#pep-634-structural-pattern-matching -RedirectTemp /2-12 https://docs.python.org/3.10/whatsnew/3.10.html -RedirectTemp /2-13 https://en.wikipedia.org/wiki/Switch_statement#Fallthrough -RedirectTemp /2-14 https://en.wikipedia.org/wiki/Dangling_else -RedirectTemp /2-15 https://github.com/gvanrossum/patma/blob/3ece6444ef70122876fd9f0099eb9490a2d630df/EXAMPLES.md#case-6-a-very-deep-iterable-and-type-match-with-extraction -RedirectTemp /2-16 https://github.com/fluentpython/lispy/blob/main/original/norvig/lis.py -RedirectTemp /2-17 https://norvig.com/lispy.html -RedirectTemp /2-18 https://numpy.org/doc/stable/user/quickstart.html#indexing-slicing-and-iterating -RedirectTemp /2-19 https://pythontutor.com/ -RedirectTemp /2-20 https://en.wikipedia.org/wiki/Fluent_interface -RedirectTemp /2-23 https://docs.python.org/3/library/bisect.html#bisect.insort -RedirectTemp /2-24 https://stackoverflow.com/questions/4845418/when-should-a-memoryview-be-used/ -RedirectTemp /2-26 https://www.fluentpython.com/extra/parsing-binary-struct/ -RedirectTemp /2-27 http://www.netlib.org -RedirectTemp /2-28 https://pandas.pydata.org/ -RedirectTemp /2-29 https://scikit-learn.org/stable/ -RedirectTemp /2-32 https://docs.python.org/3/howto/sorting.html -RedirectTemp /2-33 https://www.python.org/dev/peps/pep-3132/ -RedirectTemp /2-34 https://bugs.python.org/issue2292 -RedirectTemp /2-36 https://docs.python.org/3.10/whatsnew/3.10.html#pep-634-structural-pattern-matching -RedirectTemp /2-37 https://docs.python.org/3.10/whatsnew/3.10.html -RedirectTemp /2-39 https://www.python.org/dev/peps/pep-0636/#appendix-a-quick-intro -RedirectTemp /2-41 https://eli.thegreenplace.net/2011/11/28/less-copies-in-python-with-the-buffer-protocol-and-memoryviews/ -RedirectTemp /2-42 https://jakevdp.github.io/PythonDataScienceHandbook/ -RedirectTemp /2-43 https://www.labri.fr/perso/nrougier/from-python-to-numpy/ -RedirectTemp /2-44 https://www.cs.utexas.edu/users/EWD/transcriptions/EWD08xx/EWD831.html -RedirectTemp /2-45 http://www.fonts101.com/fonts/view/Uncategorized/34398/Dijkstra -RedirectTemp /2-46 https://docs.python.org/3/reference/datamodel.html#objects-values-and-types -RedirectTemp /2-47 https://en.wikipedia.org/wiki/Timsort -RedirectTemp /2-48 http://www.groklaw.net/pdf3/OraGoogle-1202.pdf -RedirectTemp /2-49 https://www.python.org/doc/humor/#id9 +RedirectTemp /2-1 https://github.com/fluentpython/example-code-2e/blob/master/02-array-seq/listcomp_speed.py +RedirectTemp /2-2 https://www.python.org/dev/peps/pep-3132/ +RedirectTemp /2-3 https://stackoverflow.com/questions/68630/are-tuples-more-efficient-than-lists-in-python/22140115#22140115 +RedirectTemp /2-4 https://docs.python.org/3/whatsnew/3.5.html#pep-448-additional-unpacking-generalizations +RedirectTemp /2-5 https://docs.python.org/3/whatsnew/3.5.html#pep-448-additional-unpacking-generalizations +RedirectTemp /2-6 https://docs.python.org/3.10/whatsnew/3.10.html#pep-634-structural-pattern-matching +RedirectTemp /2-7 https://docs.python.org/3.10/whatsnew/3.10.html +RedirectTemp /2-8 https://en.wikipedia.org/wiki/Switch_statement#Fallthrough +RedirectTemp /2-9 https://en.wikipedia.org/wiki/Dangling_else +RedirectTemp /2-10 https://github.com/gvanrossum/patma/blob/3ece6444ef70122876fd9f0099eb9490a2d630df/EXAMPLES.md#case-6-a-very-deep-iterable-and-type-match-with-extraction +RedirectTemp /2-11 https://github.com/fluentpython/lispy/blob/main/original/norvig/lis.py +RedirectTemp /2-12 https://norvig.com/lispy.html +RedirectTemp /2-13 https://numpy.org/doc/stable/user/quickstart.html#indexing-slicing-and-iterating +RedirectTemp /2-14 https://pythontutor.com/ +RedirectTemp /2-15 https://en.wikipedia.org/wiki/Fluent_interface +RedirectTemp /2-16 https://docs.python.org/3/library/bisect.html#bisect.insort +RedirectTemp /2-17 https://stackoverflow.com/questions/4845418/when-should-a-memoryview-be-used/ +RedirectTemp /2-18 https://www.fluentpython.com/extra/parsing-binary-struct/ +RedirectTemp /2-19 http://www.netlib.org +RedirectTemp /2-20 https://pandas.pydata.org/ +RedirectTemp /2-21 https://scikit-learn.org/stable/ +RedirectTemp /2-22 https://docs.python.org/3/howto/sorting.html +RedirectTemp /2-23 https://www.python.org/dev/peps/pep-3132/ +RedirectTemp /2-24 https://bugs.python.org/issue2292 +RedirectTemp /2-25 https://docs.python.org/3.10/whatsnew/3.10.html#pep-634-structural-pattern-matching +RedirectTemp /2-26 https://docs.python.org/3.10/whatsnew/3.10.html +RedirectTemp /2-27 https://www.python.org/dev/peps/pep-0636/#appendix-a-quick-intro +RedirectTemp /2-28 https://eli.thegreenplace.net/2011/11/28/less-copies-in-python-with-the-buffer-protocol-and-memoryviews/ +RedirectTemp /2-29 https://jakevdp.github.io/PythonDataScienceHandbook/ +RedirectTemp /2-30 https://www.oreilly.com/library/view/python-for-data/9781491957653/ +RedirectTemp /2-31 https://www.labri.fr/perso/nrougier/from-python-to-numpy/ +RedirectTemp /2-32 https://www.cs.utexas.edu/users/EWD/transcriptions/EWD08xx/EWD831.html +RedirectTemp /2-33 http://www.fonts101.com/fonts/view/Uncategorized/34398/Dijkstra +RedirectTemp /2-34 https://docs.python.org/3/reference/datamodel.html#objects-values-and-types +RedirectTemp /2-35 https://en.wikipedia.org/wiki/Timsort +RedirectTemp /2-36 http://www.groklaw.net/pdf3/OraGoogle-1202.pdf +RedirectTemp /2-37 https://www.python.org/doc/humor/#id9 ############################################################ 03 -RedirectTemp /3-5 https://www.python.org/dev/peps/pep-0584/#motivation -RedirectTemp /3-7 https://docs.python.org/3.10/c-api/typeobj.html#Py_TPFLAGS_MAPPING -RedirectTemp /3-8 https://docs.python.org/3/glossary.html#term-hashable -RedirectTemp /3-9 https://docs.python.org/3/glossary.html#term-hashable -RedirectTemp /3-11 http://www.aleax.it/Python/accu04_Relearn_Python_alex.pdf -RedirectTemp /3-12 https://github.com/pingo-io/pingo-py -RedirectTemp /3-13 https://github.com/fluentpython/example-code-2e/blob/master/03-dict-set/missing.py -RedirectTemp /3-14 https://docs.python.org/3/library/collections.html#collections.ChainMap -RedirectTemp /3-15 https://docs.python.org/3/library/collections.html#collections.Counter -RedirectTemp /3-16 https://docs.python.org/3/library/shelve.html -RedirectTemp /3-17 https://docs.python.org/3/library/dbm.html -RedirectTemp /3-18 https://docs.python.org/3/library/pickle.html -RedirectTemp /3-19 https://nedbatchelder.com/blog/202006/pickles_nine_flaws.html -RedirectTemp /3-20 https://github.com/python/cpython/blob/0bbf30e2b910bc9c5899134ae9d73a8df968da35/Lib/_collections_abc.py#L813 -RedirectTemp /3-22 https://mail.python.org/pipermail/python-dev/2015-May/140003.html -RedirectTemp /3-23 https://bugs.python.org/issue18986 -RedirectTemp /3-24 https://github.com/fluentpython/example-code-2e/blob/master/03-dict-set/transformdict.py -RedirectTemp /3-33 http://gandenberger.org/2018/03/10/ordered-dicts-vs-ordereddict/ -RedirectTemp /3-35 https://www.pypy.org/ -RedirectTemp /3-36 https://morepypy.blogspot.com/2015/01/faster-more-memory-efficient-and-more.html -RedirectTemp /3-37 https://www.npopov.com/2014/12/22/PHPs-new-hashtable-implementation.html -RedirectTemp /3-38 https://www.youtube.com/watch?v=66P5FMkWoVU -RedirectTemp /3-39 https://pyvideo.org/video/276/the-mighty-dictionary-55/ -RedirectTemp /3-40 https://www.youtube.com/watch?v=p33CVV29OG8 -RedirectTemp /3-41 https://docs.python.org/3/whatsnew/3.6.html#new-dict-implementation -RedirectTemp /3-43 https://www.youtube.com/watch?v=tGAngdU_8D8 -RedirectTemp /3-44 https://speakerdeck.com/ramalho/python-set-practice-at-pycon -RedirectTemp /3-45 https://github.com/standupdev/uintset -RedirectTemp /3-46 http://www.json.org/fatfree.html -RedirectTemp /3-47 https://twitter.com/mitsuhiko/status/1229385843585974272 +RedirectTemp /3-1 https://www.python.org/dev/peps/pep-0584/#motivation +RedirectTemp /3-2 https://docs.python.org/3.10/c-api/typeobj.html#Py_TPFLAGS_MAPPING +RedirectTemp /3-3 https://docs.python.org/3/glossary.html#term-hashable +RedirectTemp /3-4 https://docs.python.org/3/glossary.html#term-hashable +RedirectTemp /3-5 http://www.aleax.it/Python/accu04_Relearn_Python_alex.pdf +RedirectTemp /3-6 https://github.com/pingo-io/pingo-py +RedirectTemp /3-7 https://github.com/fluentpython/example-code-2e/blob/master/03-dict-set/missing.py +RedirectTemp /3-8 https://docs.python.org/3/library/collections.html#collections.ChainMap +RedirectTemp /3-9 https://docs.python.org/3/library/collections.html#collections.Counter +RedirectTemp /3-10 https://docs.python.org/3/library/shelve.html +RedirectTemp /3-11 https://docs.python.org/3/library/dbm.html +RedirectTemp /3-12 https://docs.python.org/3/library/pickle.html +RedirectTemp /3-13 https://nedbatchelder.com/blog/202006/pickles_nine_flaws.html +RedirectTemp /3-14 https://github.com/python/cpython/blob/0bbf30e2b910bc9c5899134ae9d73a8df968da35/Lib/_collections_abc.py#L813 +RedirectTemp /3-15 https://mail.python.org/pipermail/python-dev/2015-May/140003.html +RedirectTemp /3-16 https://bugs.python.org/issue18986 +RedirectTemp /3-17 https://github.com/fluentpython/example-code-2e/blob/master/03-dict-set/transformdict.py +RedirectTemp /3-18 http://gandenberger.org/2018/03/10/ordered-dicts-vs-ordereddict/ +RedirectTemp /3-19 https://www.pypy.org/ +RedirectTemp /3-20 https://morepypy.blogspot.com/2015/01/faster-more-memory-efficient-and-more.html +RedirectTemp /3-21 https://www.npopov.com/2014/12/22/PHPs-new-hashtable-implementation.html +RedirectTemp /3-22 https://www.youtube.com/watch?v=66P5FMkWoVU +RedirectTemp /3-23 https://pyvideo.org/video/276/the-mighty-dictionary-55/ +RedirectTemp /3-24 https://www.youtube.com/watch?v=p33CVV29OG8 +RedirectTemp /3-25 https://docs.python.org/3/whatsnew/3.6.html#new-dict-implementation +RedirectTemp /3-26 https://github.com/python/cpython/blob/cf7eaa4617295747ee5646c4e2b7e7a16d7c64ab/Objects/dictobject.c +RedirectTemp /3-27 https://github.com/python/cpython/blob/cf7eaa4617295747ee5646c4e2b7e7a16d7c64ab/Objects/dictnotes.txt +RedirectTemp /3-28 https://www.youtube.com/watch?v=tGAngdU_8D8 +RedirectTemp /3-29 https://speakerdeck.com/ramalho/python-set-practice-at-pycon +RedirectTemp /3-30 https://github.com/standupdev/uintset +RedirectTemp /3-31 https://spectrum.ieee.org/hans-peter-luhn-and-the-birth-of-the-hashing-algorithm +RedirectTemp /3-32 http://www.json.org/fatfree.html +RedirectTemp /3-33 https://twitter.com/mitsuhiko/status/1229385843585974272 ############################################################ 04 RedirectTemp /4-1 https://www.slideshare.net/fischertrav/character-encoding-unicode-how-to-with-dignity-33352863 RedirectTemp /4-2 https://pyvideo.org/video/2625/character-encoding-and-unicode-in-python/ RedirectTemp /4-3 https://www.fluentpython.com/extra/parsing-binary-struct/ -RedirectTemp /4-5 https://www.fluentpython.com/extra/multi-character-emojis/ -RedirectTemp /4-8 https://w3techs.com/technologies/overview/character_encoding -RedirectTemp /4-9 https://docs.python.org/3/library/codecs.html#codecs.register_error -RedirectTemp /4-10 https://docs.python.org/3/library/stdtypes.html#str.isascii -RedirectTemp /4-11 https://pypi.org/project/chardet/ -RedirectTemp /4-12 https://docs.python.org/3/library/codecs.html#encodings-and-unicode -RedirectTemp /4-13 https://nedbatchelder.com/text/unipain/unipain.html -RedirectTemp /4-16 https://docs.python.org/3/using/cmdline.html#envvar-PYTHONIOENCODING -RedirectTemp /4-17 https://docs.python.org/3/using/cmdline.html#envvar-PYTHONLEGACYWINDOWSSTDIO -RedirectTemp /4-18 https://docs.python.org/3/library/locale.html#locale.getpreferredencoding -RedirectTemp /4-19 http://www.w3.org/TR/charmod-norm/ -RedirectTemp /4-20 https://docs.python.org/3/library/locale.html?highlight=strxfrm#locale.strxfrm -RedirectTemp /4-21 https://github.com/jtauber/pyuca -RedirectTemp /4-22 http://www.unicode.org/Public/UCA/6.3.0/allkeys.txt -RedirectTemp /4-23 https://pypi.org/project/PyICU/ -RedirectTemp /4-24 https://docs.python.org/3.10/library/stdtypes.html#str.isalpha -RedirectTemp /4-25 https://en.wikipedia.org/wiki/Unicode_character_property#General_Category -RedirectTemp /4-26 https://en.wikipedia.org/wiki/Unicode_character_property -RedirectTemp /4-27 https://github.com/microsoft/terminal -RedirectTemp /4-28 https://docs.python.org/3/library/unicodedata.html -RedirectTemp /4-29 https://docs.python.org/3/reference/lexical_analysis.html#string-literal-concatenation -RedirectTemp /4-30 https://docs.python.org/3/library/re.html -RedirectTemp /4-31 https://nedbatchelder.com/text/unipain.html -RedirectTemp /4-32 https://www.slideshare.net/fischertrav/character-encoding-unicode-how-to-with-dignity-33352863 -RedirectTemp /4-33 https://pyvideo.org/video/2625/character-encoding-and-unicode-in-python/ -RedirectTemp /4-34 https://regebro.wordpress.com/2011/03/23/unconfusing-unicode-what-is-unicode/ -RedirectTemp /4-35 https://docs.python.org/3/howto/unicode.html -RedirectTemp /4-36 https://diveintopython3.net/strings.html -RedirectTemp /4-37 https://diveintopython3.net/ -RedirectTemp /4-38 https://finderiko.com/python-book -RedirectTemp /4-39 https://docs.python.org/3.0/whatsnew/3.0.html#text-vs-data-instead-of-unicode-vs-8-bit -RedirectTemp /4-40 https://lucumr.pocoo.org/2013/7/2/the-updated-guide-to-unicode/ -RedirectTemp /4-41 http://python-notes.curiousefficiency.org/en/latest/python3/binary_protocols.html -RedirectTemp /4-42 http://python-notes.curiousefficiency.org/en/latest/python3/text_file_processing.html -RedirectTemp /4-43 https://docs.python.org/3/library/codecs.html#standard-encodings -RedirectTemp /4-44 https://www.informit.com/store/unicode-demystified-a-practical-programmers-guide-to-9780201700527 -RedirectTemp /4-45 https://unicodebook.readthedocs.io/index.html -RedirectTemp /4-46 https://www.w3.org/International/wiki/Case_folding -RedirectTemp /4-47 http://www.w3.org/TR/charmod-norm/ -RedirectTemp /4-48 http://unicode.org/reports/tr15/ -RedirectTemp /4-49 http://www.unicode.org/faq/normalization.html -RedirectTemp /4-50 http://www.unicode.org/ -RedirectTemp /4-51 http://www.macchiato.com/unicode/nfc-faq +RedirectTemp /4-4 https://www.fluentpython.com/extra/multi-character-emojis/ +RedirectTemp /4-5 https://w3techs.com/technologies/overview/character_encoding +RedirectTemp /4-6 https://docs.python.org/3/library/codecs.html#codecs.register_error +RedirectTemp /4-7 https://docs.python.org/3/library/stdtypes.html#str.isascii +RedirectTemp /4-8 https://pypi.org/project/chardet/ +RedirectTemp /4-9 https://docs.python.org/3/library/codecs.html#encodings-and-unicode +RedirectTemp /4-10 https://nedbatchelder.com/text/unipain/unipain.html +RedirectTemp /4-11 https://devblogs.microsoft.com/commandline/windows-command-line-unicode-and-utf-8-output-text-buffer/ +RedirectTemp /4-12 https://docs.python.org/3/using/cmdline.html#envvar-PYTHONIOENCODING +RedirectTemp /4-13 https://docs.python.org/3/using/cmdline.html#envvar-PYTHONLEGACYWINDOWSSTDIO +RedirectTemp /4-14 https://docs.python.org/3/library/locale.html#locale.getpreferredencoding +RedirectTemp /4-15 http://www.w3.org/TR/charmod-norm/ +RedirectTemp /4-16 https://docs.python.org/3/library/locale.html?highlight=strxfrm#locale.strxfrm +RedirectTemp /4-17 https://pypi.org/project/pyuca/ +RedirectTemp /4-18 https://github.com/jtauber/pyuca +RedirectTemp /4-19 http://www.unicode.org/Public/UCA/6.3.0/allkeys.txt +RedirectTemp /4-20 https://pypi.org/project/PyICU/ +RedirectTemp /4-21 https://docs.python.org/3.10/library/stdtypes.html#str.isalpha +RedirectTemp /4-22 https://en.wikipedia.org/wiki/Unicode_character_property#General_Category +RedirectTemp /4-23 https://en.wikipedia.org/wiki/Unicode_character_property +RedirectTemp /4-24 https://github.com/microsoft/terminal +RedirectTemp /4-25 https://docs.python.org/3/library/unicodedata.html +RedirectTemp /4-26 https://docs.python.org/3/reference/lexical_analysis.html#string-literal-concatenation +RedirectTemp /4-27 https://docs.python.org/3/library/re.html +RedirectTemp /4-28 https://nedbatchelder.com/text/unipain.html +RedirectTemp /4-29 https://www.slideshare.net/fischertrav/character-encoding-unicode-how-to-with-dignity-33352863 +RedirectTemp /4-30 https://pyvideo.org/video/2625/character-encoding-and-unicode-in-python/ +RedirectTemp /4-31 https://regebro.wordpress.com/2011/03/23/unconfusing-unicode-what-is-unicode/ +RedirectTemp /4-32 https://docs.python.org/3/howto/unicode.html +RedirectTemp /4-33 https://diveintopython3.net/strings.html +RedirectTemp /4-34 https://diveintopython3.net/ +RedirectTemp /4-35 https://finderiko.com/python-book +RedirectTemp /4-36 https://docs.python.org/3.0/whatsnew/3.0.html#text-vs-data-instead-of-unicode-vs-8-bit +RedirectTemp /4-37 https://lucumr.pocoo.org/2013/7/2/the-updated-guide-to-unicode/ +RedirectTemp /4-38 http://python-notes.curiousefficiency.org/en/latest/python3/binary_protocols.html +RedirectTemp /4-39 http://python-notes.curiousefficiency.org/en/latest/python3/text_file_processing.html +RedirectTemp /4-40 https://docs.python.org/3/library/codecs.html#standard-encodings +RedirectTemp /4-41 https://github.com/python/cpython/blob/0bbf30e2b910bc9c5899134ae9d73a8df968da35/Tools/unicode/listcodecs.py +RedirectTemp /4-42 https://www.oreilly.com/library/view/unicode-explained/059610121X/ +RedirectTemp /4-43 https://www.informit.com/store/unicode-demystified-a-practical-programmers-guide-to-9780201700527 +RedirectTemp /4-44 https://unicodebook.readthedocs.io/index.html +RedirectTemp /4-45 https://www.w3.org/International/wiki/Case_folding +RedirectTemp /4-46 http://www.w3.org/TR/charmod-norm/ +RedirectTemp /4-47 http://unicode.org/reports/tr15/ +RedirectTemp /4-48 http://www.unicode.org/faq/normalization.html +RedirectTemp /4-49 http://www.unicode.org/ +RedirectTemp /4-50 http://www.macchiato.com/unicode/nfc-faq +RedirectTemp /4-51 https://stories.moma.org/the-original-emoji-set-has-been-added-to-the-museum-of-modern-arts-collection-c6060e141f61?gi=c403f5a840a5 RedirectTemp /4-52 https://emojipedia.org/ RedirectTemp /4-53 https://blog.emojipedia.org/correcting-the-record-on-the-first-emoji-set/ RedirectTemp /4-54 http://emojitracker.com/ @@ -279,397 +291,433 @@ RedirectTemp /4-56 http://www.methods.co.nz/asciidoc/ RedirectTemp /4-57 https://atlas.oreilly.com/ ############################################################ 05 RedirectTemp /5-1 https://docs.python.org/3/library/typing.html#typing.TypedDict -RedirectTemp /5-4 https://docs.python.org/3.10/library/inspect.html#inspect.get_annotations -RedirectTemp /5-5 https://docs.python.org/3/library/typing.html#typing.get_type_hints -RedirectTemp /5-6 https://docs.python.org/3.8/library/collections.html#collections.somenamedtuple._asdict -RedirectTemp /5-8 https://www.jetbrains.com/pycharm/ -RedirectTemp /5-10 https://www.python.org/dev/peps/pep-0484/#acceptable-type-hints -RedirectTemp /5-11 https://docs.python.org/3/library/dataclasses.html#dataclasses.dataclass -RedirectTemp /5-13 https://docs.python.org/3/library/dataclasses.html#dataclasses.dataclass -RedirectTemp /5-14 https://docs.python.org/3/library/dataclasses.html -RedirectTemp /5-15 https://docs.python.org/3/library/dataclasses.html#inheritance -RedirectTemp /5-16 https://www.python.org/dev/peps/pep-0526/#class-and-instance-variable-annotations -RedirectTemp /5-22 https://dublincore.org/specifications/dublin-core/ -RedirectTemp /5-23 https://en.wikipedia.org/wiki/Dublin_Core -RedirectTemp /5-24 https://martinfowler.com/bliki/CodeSmell.html -RedirectTemp /5-25 https://martinfowler.com/books/refactoring.html -RedirectTemp /5-26 https://www.python.org/dev/peps/pep-0634/#class-patterns -RedirectTemp /5-30 https://docs.python.org/3/library/dataclasses.html -RedirectTemp /5-32 https://www.python.org/dev/peps/pep-0557/#id47 -RedirectTemp /5-33 https://www.python.org/dev/peps/pep-0557/#id48 -RedirectTemp /5-34 https://www.python.org/dev/peps/pep-0557/#id33 -RedirectTemp /5-35 https://realpython.com -RedirectTemp /5-36 https://realpython.com/python-data-classes/ -RedirectTemp /5-37 https://www.youtube.com/watch?v=T-TwcmT6Rcw -RedirectTemp /5-38 https://www.attrs.org/en/stable/ -RedirectTemp /5-39 https://glyph.twistedmatrix.com/2016/08/attrs.html -RedirectTemp /5-40 https://www.attrs.org/en/stable/why.html -RedirectTemp /5-41 https://github.com/dabeaz/cluegen -RedirectTemp /5-42 https://refactoring.guru/ -RedirectTemp /5-43 https://refactoring.guru/smells/data-class -RedirectTemp /5-44 https://web.archive.org/web/20190204130328/http://catb.org/esr/jargon/html/G/Guido.html -RedirectTemp /5-45 https://web.archive.org/web/20190211161610/http://catb.org/esr/jargon/html/index.html -RedirectTemp /5-47 https://www.attrs.org/en/stable/ +RedirectTemp /5-2 https://docs.python.org/3.10/library/inspect.html#inspect.get_annotations +RedirectTemp /5-3 https://docs.python.org/3/library/typing.html#typing.get_type_hints +RedirectTemp /5-4 https://docs.python.org/3.8/library/collections.html#collections.somenamedtuple._asdict +RedirectTemp /5-5 https://www.jetbrains.com/pycharm/ +RedirectTemp /5-6 https://www.python.org/dev/peps/pep-0484/#acceptable-type-hints +RedirectTemp /5-7 https://docs.python.org/3/library/dataclasses.html#dataclasses.dataclass +RedirectTemp /5-8 https://docs.python.org/3/library/dataclasses.html#dataclasses.dataclass +RedirectTemp /5-9 https://docs.python.org/3/library/dataclasses.html +RedirectTemp /5-10 https://docs.python.org/3/library/dataclasses.html#inheritance +RedirectTemp /5-11 https://www.python.org/dev/peps/pep-0526/#class-and-instance-variable-annotations +RedirectTemp /5-12 https://dublincore.org/specifications/dublin-core/ +RedirectTemp /5-13 https://en.wikipedia.org/wiki/Dublin_Core +RedirectTemp /5-14 https://martinfowler.com/bliki/CodeSmell.html +RedirectTemp /5-15 https://martinfowler.com/books/refactoring.html +RedirectTemp /5-16 https://www.python.org/dev/peps/pep-0634/#class-patterns +RedirectTemp /5-17 https://docs.python.org/3/library/dataclasses.html +RedirectTemp /5-18 https://www.python.org/dev/peps/pep-0557/#id47 +RedirectTemp /5-19 https://www.python.org/dev/peps/pep-0557/#id48 +RedirectTemp /5-20 https://www.python.org/dev/peps/pep-0557/#id33 +RedirectTemp /5-21 https://realpython.com +RedirectTemp /5-22 https://realpython.com/python-data-classes/ +RedirectTemp /5-23 https://www.youtube.com/watch?v=T-TwcmT6Rcw +RedirectTemp /5-24 https://www.attrs.org/en/stable/ +RedirectTemp /5-25 https://glyph.twistedmatrix.com/2016/08/attrs.html +RedirectTemp /5-26 https://www.attrs.org/en/stable/why.html +RedirectTemp /5-27 https://github.com/dabeaz/cluegen +RedirectTemp /5-28 https://refactoring.guru/ +RedirectTemp /5-29 https://refactoring.guru/smells/data-class +RedirectTemp /5-30 https://web.archive.org/web/20190204130328/http://catb.org/esr/jargon/html/G/Guido.html +RedirectTemp /5-31 https://web.archive.org/web/20190211161610/http://catb.org/esr/jargon/html/index.html +RedirectTemp /5-32 https://www.attrs.org/en/stable/ ############################################################ 06 -RedirectTemp /6-3 https://www.olin.edu/faculty/profile/lynn-andrea-stein/ -RedirectTemp /6-4 https://docs.python.org/3/reference/datamodel.html#objects-values-and-types -RedirectTemp /6-5 https://pythontutor.com/ -RedirectTemp /6-6 https://docs.python.org/3/library/copy.html -RedirectTemp /6-7 https://en.wikipedia.org/wiki/Principle_of_least_astonishment -RedirectTemp /6-8 https://docs.python.org/3/reference/datamodel.html#object.%5C_%5C_del__ -RedirectTemp /6-9 https://emptysqua.re/blog/pypy-garbage-collection-and-a-deadlock/ -RedirectTemp /6-15 https://www.youtube.com/watch?v=HHFCFJSPWrI&feature=youtu.be -RedirectTemp /6-16 http://pymotw.com/3/copy/ -RedirectTemp /6-17 http://pymotw.com/3/weakref/ -RedirectTemp /6-18 https://docs.python.org/3/library/gc.html -RedirectTemp /6-20 https://devguide.python.org/garbage_collector/ -RedirectTemp /6-21 https://devguide.python.org/ -RedirectTemp /6-22 https://www.python.org/dev/peps/pep-0442/ -RedirectTemp /6-23 https://en.wikipedia.org/wiki/String_interning -RedirectTemp /6-24 https://en.wikipedia.org/wiki/Haddocks%27_Eyes -RedirectTemp /6-25 https://thp.io/2012/python-gc/python_gc_final_2012-01-22.pdf +RedirectTemp /6-1 https://www.olin.edu/faculty/profile/lynn-andrea-stein/ +RedirectTemp /6-2 https://docs.python.org/3/reference/datamodel.html#objects-values-and-types +RedirectTemp /6-3 https://pythontutor.com/ +RedirectTemp /6-4 https://docs.python.org/3/library/copy.html +RedirectTemp /6-5 https://en.wikipedia.org/wiki/Principle_of_least_astonishment +RedirectTemp /6-6 https://docs.python.org/3/reference/datamodel.html#object.%5C_%5C_del__ +RedirectTemp /6-7 https://emptysqua.re/blog/pypy-garbage-collection-and-a-deadlock/ +RedirectTemp /6-8 https://www.youtube.com/watch?v=HHFCFJSPWrI&feature=youtu.be +RedirectTemp /6-9 http://pymotw.com/3/copy/ +RedirectTemp /6-10 http://pymotw.com/3/weakref/ +RedirectTemp /6-11 https://docs.python.org/3/library/gc.html +RedirectTemp /6-12 https://devguide.python.org/garbage_collector/ +RedirectTemp /6-13 https://devguide.python.org/ +RedirectTemp /6-14 https://www.python.org/dev/peps/pep-0442/ +RedirectTemp /6-15 https://en.wikipedia.org/wiki/String_interning +RedirectTemp /6-16 https://en.wikipedia.org/wiki/Haddocks%27_Eyes +RedirectTemp /6-17 https://thp.io/2012/python-gc/python_gc_final_2012-01-22.pdf ############################################################ 07 RedirectTemp /7-1 http://python-history.blogspot.com/2009/04/origins-of-pythons-functional-features.html -RedirectTemp /7-3 https://www.fluentpython.com/extra/function-introspection/ -RedirectTemp /7-5 https://docs.python.org/3/library/functions.html#map -RedirectTemp /7-6 https://en.wikipedia.org/wiki/Functional_programming -RedirectTemp /7-7 https://docs.python.org/3/howto/functional.html -RedirectTemp /7-8 https://docs.python.org/3/reference/datamodel.html#the-standard-type-hierarchy -RedirectTemp /7-9 https://docs.python.org/3/whatsnew/3.8.html#positional-only-parameters -RedirectTemp /7-10 https://docs.python.org/3/whatsnew/3.8.html#positional-only-parameters -RedirectTemp /7-12 https://docs.python.org/3/reference/datamodel.html#the-standard-type-hierarchy -RedirectTemp /7-14 https://docs.python.org/3/howto/functional.html -RedirectTemp /7-15 https://stackoverflow.com/questions/3252228/python-why-is-functools-partial-necessary -RedirectTemp /7-16 https://speakerdeck.com/ramalho/beyond-paradigms-berlin-edition -RedirectTemp /7-17 https://www.youtube.com/watch?v=bF3a2VYXxa0 -RedirectTemp /7-18 http://cs.brown.edu/~sk/Publications/Papers/Published/sk-teach-pl-post-linnaean/ -RedirectTemp /7-19 http://python-history.blogspot.com/2009/04/origins-of-pythons-functional-features.html -RedirectTemp /7-20 https://raw.githubusercontent.com/python/cpython/main/Misc/HISTORY -RedirectTemp /7-21 http://neopythonic.blogspot.com/2009/04/tail-recursion-elimination.html +RedirectTemp /7-2 https://www.fluentpython.com/extra/function-introspection/ +RedirectTemp /7-3 https://docs.python.org/3/library/functions.html#map +RedirectTemp /7-4 https://en.wikipedia.org/wiki/Functional_programming +RedirectTemp /7-5 https://docs.python.org/3/howto/functional.html +RedirectTemp /7-6 https://docs.python.org/3/reference/datamodel.html#the-standard-type-hierarchy +RedirectTemp /7-7 https://docs.python.org/3/whatsnew/3.8.html#positional-only-parameters +RedirectTemp /7-8 https://docs.python.org/3/whatsnew/3.8.html#positional-only-parameters +RedirectTemp /7-9 https://github.com/python/cpython/blob/0bbf30e2b910bc9c5899134ae9d73a8df968da35/Lib/functools.py#L341 +RedirectTemp /7-10 https://docs.python.org/3/reference/datamodel.html#the-standard-type-hierarchy +RedirectTemp /7-11 https://docs.python.org/3/howto/functional.html +RedirectTemp /7-12 https://stackoverflow.com/questions/3252228/python-why-is-functools-partial-necessary +RedirectTemp /7-13 https://speakerdeck.com/ramalho/beyond-paradigms-berlin-edition +RedirectTemp /7-14 https://www.youtube.com/watch?v=bF3a2VYXxa0 +RedirectTemp /7-15 http://cs.brown.edu/~sk/Publications/Papers/Published/sk-teach-pl-post-linnaean/ +RedirectTemp /7-16 http://python-history.blogspot.com/2009/04/origins-of-pythons-functional-features.html +RedirectTemp /7-17 https://raw.githubusercontent.com/python/cpython/main/Misc/HISTORY +RedirectTemp /7-18 http://neopythonic.blogspot.com/2009/04/tail-recursion-elimination.html ############################################################ 08 RedirectTemp /8-1 https://www.python.org/dev/peps/pep-0484/#non-goals -RedirectTemp /8-4 https://github.com/python/typing/issues/182 -RedirectTemp /8-5 https://github.com/python/mypy/issues/731 -RedirectTemp /8-6 https://github.com/google/pytype -RedirectTemp /8-7 https://github.com/Microsoft/pyright -RedirectTemp /8-8 https://pyre-check.org/ -RedirectTemp /8-10 https://mypy.readthedocs.io/en/stable/introduction.html -RedirectTemp /8-11 https://mypy.readthedocs.io/en/stable/config_file.html -RedirectTemp /8-12 https://pypi.org/project/flake8/ -RedirectTemp /8-13 https://pypi.org/project/blue/ -RedirectTemp /8-14 https://pypi.org/project/black/ -RedirectTemp /8-16 https://wefearchange.org/2020/11/steeringcouncil.rst.html -RedirectTemp /8-17 https://docs.python.org/3/library/collections.abc.html#collections-abstract-base-classes -RedirectTemp /8-18 https://en.wikipedia.org/wiki/Barbara_Liskov -RedirectTemp /8-19 https://en.wikipedia.org/wiki/Behavioral_subtyping -RedirectTemp /8-22 https://www.python.org/dev/peps/pep-0585/#implementation -RedirectTemp /8-25 https://docs.python.org/3/library/typing.html#module-contents -RedirectTemp /8-26 https://en.wikipedia.org/wiki/Geohash -RedirectTemp /8-27 https://en.wikipedia.org/wiki/Inverted_index -RedirectTemp /8-29 https://docs.python.org/3/library/typing.html#typing.List -RedirectTemp /8-30 https://docs.python.org/3/library/typing.html#typing.Dict -RedirectTemp /8-31 https://docs.python.org/3/library/typing.html#typing.Set -RedirectTemp /8-32 https://www.python.org/dev/peps/pep-0585/#implementation -RedirectTemp /8-34 https://docs.python.org/3/library/numbers.html -RedirectTemp /8-37 https://docs.python.org/3/library/typing.html#typing.List -RedirectTemp /8-38 https://github.com/python/typeshed -RedirectTemp /8-39 https://github.com/python/typeshed/blob/66cd36268a6a667714efaa27198a41d0d7f89477/stdlib/2and3/math.pyi#L45 -RedirectTemp /8-41 https://docs.python.org/3/library/statistics.html#statistics.mode -RedirectTemp /8-42 https://docs.python.org/3/library/statistics.html#statistics.mode -RedirectTemp /8-46 https://docs.python.org/3/library/typing.html#typing.Callable -RedirectTemp /8-47 https://pypi.org/project/blue/ -RedirectTemp /8-48 https://www.python.org/dev/peps/pep-0484/#id38 -RedirectTemp /8-49 https://docs.google.com/document/d/1aXs1tpwzPjW9MdsG5dI7clNFyYayFBkcXwRDo-qvbIk/preview -RedirectTemp /8-50 https://www.oreilly.com/library/view/the-best-software/9781590595008/ -RedirectTemp /8-51 https://www.youtube.com/watch?v=YFexUDjHO6w -RedirectTemp /8-52 https://www.youtube.com/watch?v=YFexUDjHO6w&t=13m40s -RedirectTemp /8-53 https://bernat.tech/posts/the-state-of-type-hints-in-python/ -RedirectTemp /8-54 https://realpython.com/python-type-checking/ -RedirectTemp /8-55 https://cjolowicz.github.io/posts/hypermodern-python-04-typing/ -RedirectTemp /8-56 https://mypy.readthedocs.io/en/stable/index.html -RedirectTemp /8-57 https://mypy.readthedocs.io/en/stable/cheat_sheet_py3.html -RedirectTemp /8-58 https://mypy.readthedocs.io/en/stable/common_issues.html -RedirectTemp /8-60 https://github.com/typeddjango/awesome-python-typing -RedirectTemp /8-61 https://docs.python.org/3/library/functions.html#max -RedirectTemp /8-62 https://en.wikipedia.org/wiki/Linguistic_relativity -RedirectTemp /8-63 https://pypistats.org/top -RedirectTemp /8-64 https://github.com/psf/requests/issues/3855 -RedirectTemp /8-65 https://lwn.net/Articles/643399/ -RedirectTemp /8-66 https://docs.python-requests.org/en/master/api/#requests.request -RedirectTemp /8-67 https://queue.acm.org/detail.cfm?id=1039523 +RedirectTemp /8-2 https://github.com/python/typing/issues/182 +RedirectTemp /8-3 https://github.com/python/mypy/issues/731 +RedirectTemp /8-4 https://github.com/google/pytype +RedirectTemp /8-5 https://github.com/Microsoft/pyright +RedirectTemp /8-6 https://pyre-check.org/ +RedirectTemp /8-7 https://mypy.readthedocs.io/en/stable/introduction.html +RedirectTemp /8-8 https://mypy.readthedocs.io/en/stable/config_file.html +RedirectTemp /8-9 https://pypi.org/project/flake8/ +RedirectTemp /8-10 https://pypi.org/project/blue/ +RedirectTemp /8-11 https://pypi.org/project/black/ +RedirectTemp /8-12 https://wefearchange.org/2020/11/steeringcouncil.rst.html +RedirectTemp /8-13 https://docs.python.org/3/library/collections.abc.html#collections-abstract-base-classes +RedirectTemp /8-14 https://en.wikipedia.org/wiki/Barbara_Liskov +RedirectTemp /8-15 https://en.wikipedia.org/wiki/Behavioral_subtyping +RedirectTemp /8-16 https://www.python.org/dev/peps/pep-0585/#implementation +RedirectTemp /8-17 https://docs.python.org/3/library/typing.html#module-contents +RedirectTemp /8-18 https://en.wikipedia.org/wiki/Geohash +RedirectTemp /8-19 https://en.wikipedia.org/wiki/Inverted_index +RedirectTemp /8-20 https://docs.python.org/3/library/typing.html#typing.List +RedirectTemp /8-21 https://docs.python.org/3/library/typing.html#typing.Dict +RedirectTemp /8-22 https://docs.python.org/3/library/typing.html#typing.Set +RedirectTemp /8-23 https://www.python.org/dev/peps/pep-0585/#implementation +RedirectTemp /8-24 https://docs.python.org/3/library/numbers.html +RedirectTemp /8-25 https://docs.python.org/3/library/typing.html#typing.List +RedirectTemp /8-26 https://github.com/python/typeshed +RedirectTemp /8-27 https://github.com/python/typeshed/blob/66cd36268a6a667714efaa27198a41d0d7f89477/stdlib/2and3/math.pyi#L45 +RedirectTemp /8-28 https://docs.python.org/3/library/statistics.html#statistics.mode +RedirectTemp /8-29 https://github.com/python/cpython/blob/822efa5695b5ba6c2316c1400e4e9ec2546f7ea5/Lib/statistics.py#L534 +RedirectTemp /8-30 https://github.com/python/typeshed/blob/e1e99245bb46223928eba68d4fc74962240ba5b4/stdlib/3/statistics.pyi +RedirectTemp /8-31 https://docs.python.org/3/library/statistics.html#statistics.mode +RedirectTemp /8-32 https://github.com/python/typeshed/blob/a8834fcd46339e17fc8add82b5803a1ce53d3d60/stdlib/3/statistics.pyi#L32 +RedirectTemp /8-33 https://mail.python.org/archives/list/python-dev@python.org/thread/CLVXXPQ2T2LQ5MP2Y53VVQFCXYWQJHKZ/ +RedirectTemp /8-34 https://docs.python.org/3/library/typing.html#typing.Callable +RedirectTemp /8-35 https://pypi.org/project/blue/ +RedirectTemp /8-36 https://www.python.org/dev/peps/pep-0484/#id38 +RedirectTemp /8-37 https://docs.google.com/document/d/1aXs1tpwzPjW9MdsG5dI7clNFyYayFBkcXwRDo-qvbIk/preview +RedirectTemp /8-38 https://www.oreilly.com/library/view/the-best-software/9781590595008/ +RedirectTemp /8-39 https://www.youtube.com/watch?v=YFexUDjHO6w +RedirectTemp /8-40 https://www.youtube.com/watch?v=YFexUDjHO6w&t=13m40s +RedirectTemp /8-41 https://bernat.tech/posts/the-state-of-type-hints-in-python/ +RedirectTemp /8-42 https://realpython.com/python-type-checking/ +RedirectTemp /8-43 https://cjolowicz.github.io/posts/hypermodern-python-04-typing/ +RedirectTemp /8-44 https://mypy.readthedocs.io/en/stable/index.html +RedirectTemp /8-45 https://mypy.readthedocs.io/en/stable/cheat_sheet_py3.html +RedirectTemp /8-46 https://mypy.readthedocs.io/en/stable/common_issues.html +RedirectTemp /8-47 https://github.com/typeddjango/awesome-python-typing +RedirectTemp /8-48 https://docs.python.org/3/library/functions.html#max +RedirectTemp /8-49 https://en.wikipedia.org/wiki/Linguistic_relativity +RedirectTemp /8-50 https://pypistats.org/top +RedirectTemp /8-51 https://github.com/psf/requests/issues/3855 +RedirectTemp /8-52 https://lwn.net/Articles/643399/ +RedirectTemp /8-53 https://docs.python-requests.org/en/master/api/#requests.request +RedirectTemp /8-54 https://queue.acm.org/detail.cfm?id=1039523 ############################################################ 09 RedirectTemp /9-1 https://docs.python.org/3/library/dis.html RedirectTemp /9-2 https://en.wikipedia.org/wiki/Memoization RedirectTemp /9-3 https://numpy.org/doc/stable/user/basics.types.html -RedirectTemp /9-5 https://docs.python.org/3/library/functools.html#functools.singledispatch -RedirectTemp /9-7 https://github.com/GrahamDumpleton/wrapt/blob/develop/blog/README.md -RedirectTemp /9-8 https://github.com/GrahamDumpleton/wrapt/blob/develop/blog/01-how-you-implemented-your-python-decorator-is-wrong.md -RedirectTemp /9-9 https://wrapt.readthedocs.io/en/latest/ -RedirectTemp /9-10 https://www.oreilly.com/library/view/python-cookbook-3rd/9781449357337/ch09.html -RedirectTemp /9-11 https://pypi.org/project/decorator/ -RedirectTemp /9-12 https://wiki.python.org/moin/PythonDecoratorLibrary -RedirectTemp /9-13 http://web.archive.org/web/20201109032203/http://effbot.org/zone/closure.htm -RedirectTemp /9-14 https://www.python.org/dev/peps/pep-3104/ -RedirectTemp /9-15 https://www.python.org/dev/peps/pep-0227/ -RedirectTemp /9-16 https://www.python.org/dev/peps/pep-0443/ -RedirectTemp /9-17 https://www.artima.com/weblogs/viewpost.jsp?thread=101605 -RedirectTemp /9-18 https://reg.readthedocs.io/en/latest/ -RedirectTemp /9-19 https://morepath.readthedocs.io/en/latest/ -RedirectTemp /9-20 https://www.gnu.org/software/emacs/manual/html_node/elisp/Dynamic-Binding.html -RedirectTemp /9-21 http://www.paulgraham.com/rootsoflisp.html -RedirectTemp /9-22 http://www-formal.stanford.edu/jmc/recursive/recursive.html -RedirectTemp /9-23 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this +RedirectTemp /9-4 https://docs.python.org/3/library/functools.html#functools.singledispatch +RedirectTemp /9-5 https://github.com/GrahamDumpleton/wrapt/blob/develop/blog/README.md +RedirectTemp /9-6 https://github.com/GrahamDumpleton/wrapt/blob/develop/blog/01-how-you-implemented-your-python-decorator-is-wrong.md +RedirectTemp /9-7 https://wrapt.readthedocs.io/en/latest/ +RedirectTemp /9-8 https://www.oreilly.com/library/view/python-cookbook-3rd/9781449357337/ch09.html +RedirectTemp /9-9 https://pypi.org/project/decorator/ +RedirectTemp /9-10 https://wiki.python.org/moin/PythonDecoratorLibrary +RedirectTemp /9-11 http://web.archive.org/web/20201109032203/http://effbot.org/zone/closure.htm +RedirectTemp /9-12 https://www.python.org/dev/peps/pep-3104/ +RedirectTemp /9-13 https://www.python.org/dev/peps/pep-0227/ +RedirectTemp /9-14 https://www.python.org/dev/peps/pep-0443/ +RedirectTemp /9-15 https://www.artima.com/weblogs/viewpost.jsp?thread=101605 +RedirectTemp /9-16 https://reg.readthedocs.io/en/latest/ +RedirectTemp /9-17 https://morepath.readthedocs.io/en/latest/ +RedirectTemp /9-18 https://www.gnu.org/software/emacs/manual/html_node/elisp/Dynamic-Binding.html +RedirectTemp /9-19 http://www.paulgraham.com/rootsoflisp.html +RedirectTemp /9-20 http://www-formal.stanford.edu/jmc/recursive/recursive.html +RedirectTemp /9-21 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this ############################################################ 10 RedirectTemp /10-1 https://en.wikipedia.org/wiki/Software_design_pattern RedirectTemp /10-2 https://en.wikipedia.org/wiki/Iterator_pattern -RedirectTemp /10-4 https://github.com/python/mypy/issues/9397 -RedirectTemp /10-5 http://www.norvig.com/design-patterns/index.htm -RedirectTemp /10-6 https://pyvideo.org/video/1110/python-design-patterns/ -RedirectTemp /10-7 http://www.aleax.it/gdd_pydp.pdf -RedirectTemp /10-9 https://perl.plover.com/yak/design/ -RedirectTemp /10-11 https://en.wikipedia.org/wiki/Turtles_all_the_way_down +RedirectTemp /10-3 https://github.com/python/mypy/issues/9397 +RedirectTemp /10-4 http://www.norvig.com/design-patterns/index.htm +RedirectTemp /10-5 https://pyvideo.org/video/1110/python-design-patterns/ +RedirectTemp /10-6 http://www.aleax.it/gdd_pydp.pdf +RedirectTemp /10-7 https://perl.plover.com/yak/design/ +RedirectTemp /10-8 https://en.wikipedia.org/wiki/Turtles_all_the_way_down ############################################################ 11 RedirectTemp /11-1 https://blog.startifact.com/posts/older/what-is-pythonic.html RedirectTemp /11-2 https://julien.danjou.info/guide-python-static-class-abstract-methods/ RedirectTemp /11-3 https://docs.python.org/3/library/string.html#formatspec -RedirectTemp /11-5 https://docs.python.org/3/reference/lexical_analysis.html#f-strings -RedirectTemp /11-6 https://docs.python.org/3/library/string.html#format-string-syntax -RedirectTemp /11-7 https://docs.python.org/3/library/string.html#formatspec -RedirectTemp /11-8 https://docs.python.org/3/reference/datamodel.html#object.__hash__ -RedirectTemp /11-9 https://web.archive.org/web/20161025185040/http://pythonpaste.org/StyleGuide.html -RedirectTemp /11-10 https://docs.python.org/3/tutorial/modules.html#more-on-modules -RedirectTemp /11-11 https://docs.python.org/3/library/gettext.html#gettext.NullTranslations -RedirectTemp /11-12 https://github.com/fluentpython/example-code-2e/blob/master/11-pythonic-obj/mem_test.py -RedirectTemp /11-16 https://docs.python.org/3/reference/datamodel.html#basic-customization -RedirectTemp /11-18 http://esug.org/data/HistoricalDocuments/TheSmalltalkReport/ST07/04wo.pdf -RedirectTemp /11-19 https://docs.oracle.com/javase/tutorial/essential/environment/security.html -RedirectTemp /11-21 https://docs.oracle.com/javase/tutorial/essential/environment/security.html +RedirectTemp /11-4 https://docs.python.org/3/reference/lexical_analysis.html#f-strings +RedirectTemp /11-5 https://docs.python.org/3/library/string.html#format-string-syntax +RedirectTemp /11-6 https://docs.python.org/3/library/string.html#formatspec +RedirectTemp /11-7 https://docs.python.org/3/reference/datamodel.html#object.__hash__ +RedirectTemp /11-8 https://web.archive.org/web/20161025185040/http://pythonpaste.org/StyleGuide.html +RedirectTemp /11-9 https://docs.python.org/3/tutorial/modules.html#more-on-modules +RedirectTemp /11-10 https://docs.python.org/3/library/gettext.html#gettext.NullTranslations +RedirectTemp /11-11 https://github.com/fluentpython/example-code-2e/blob/master/11-pythonic-obj/mem_test.py +RedirectTemp /11-12 https://docs.python.org/3/reference/datamodel.html#basic-customization +RedirectTemp /11-13 http://esug.org/data/HistoricalDocuments/TheSmalltalkReport/ST07/04wo.pdf +RedirectTemp /11-14 https://www.artima.com/articles/the-simplest-thing-that-could-possibly-work#part3 +RedirectTemp /11-15 https://docs.oracle.com/javase/tutorial/essential/environment/security.html +RedirectTemp /11-16 https://github.com/fluentpython/example-code-2e/blob/master/11-pythonic-obj/private/Expose.java +RedirectTemp /11-17 https://docs.oracle.com/javase/tutorial/essential/environment/security.html ############################################################ 12 RedirectTemp /12-1 https://en.wikipedia.org/wiki/Vector_space_model RedirectTemp /12-2 https://pypi.org/project/gensim/ -RedirectTemp /12-6 https://docs.python.org/3/library/functions.html#enumerate -RedirectTemp /12-7 https://mathworld.wolfram.com/Hypersphere.html -RedirectTemp /12-12 https://en.wikipedia.org/wiki/Fold_(higher-order_function) -RedirectTemp /12-13 https://docs.python.org/2.5/whatsnew/pep-357.html -RedirectTemp /12-15 https://docs.python.org/3/reference/datamodel.html#special-method-names -RedirectTemp /12-16 https://en.wikipedia.org/wiki/KISS_principle -RedirectTemp /12-17 https://mail.python.org/pipermail/python-list/2000-July/046184.html -RedirectTemp /12-18 https://en.wikipedia.org/wiki/Duck_typing -RedirectTemp /12-19 https://mail.python.org/mailman/listinfo/python-list -RedirectTemp /12-20 https://mail.python.org/pipermail/python-list/2003-April/218568.html +RedirectTemp /12-3 https://docs.python.org/3/library/functions.html#enumerate +RedirectTemp /12-4 https://mathworld.wolfram.com/Hypersphere.html +RedirectTemp /12-5 https://en.wikipedia.org/wiki/Fold_(higher-order_function) +RedirectTemp /12-6 https://docs.python.org/2.5/whatsnew/pep-357.html +RedirectTemp /12-7 https://docs.python.org/3/reference/datamodel.html#special-method-names +RedirectTemp /12-8 https://en.wikipedia.org/wiki/KISS_principle +RedirectTemp /12-9 https://mail.python.org/pipermail/python-list/2000-July/046184.html +RedirectTemp /12-10 https://en.wikipedia.org/wiki/Duck_typing +RedirectTemp /12-11 https://mail.python.org/mailman/listinfo/python-list +RedirectTemp /12-12 https://mail.python.org/pipermail/python-list/2003-April/218568.html ############################################################ 13 -RedirectTemp /13-2 https://docs.python.org/3/c-api/index.html -RedirectTemp /13-3 https://docs.python.org/3/c-api/sequence.html -RedirectTemp /13-7 https://github.com/python/cpython/blob/31ceccb2c77854893f3a754aca04bedd74bedb10/Lib/_collections_abc.py#L870 -RedirectTemp /13-8 https://en.wikipedia.org/wiki/Monkey_patch -RedirectTemp /13-9 https://www.gevent.org/api/gevent.monkey.html -RedirectTemp /13-10 https://docs.python.org/3/library/random.html#random.shuffle -RedirectTemp /13-11 https://docs.python.org/3/reference/datamodel.html#emulating-container-types -RedirectTemp /13-12 https://docs.python.org/3/library/collections.html#collections.namedtuple -RedirectTemp /13-13 https://github.com/python/typeshed/blob/24afb531ffd07083d6a74be917342195062f7277/stdlib/collections/__init__.pyi -RedirectTemp /13-14 https://docs.python.org/3/glossary.html#term-abstract-base-class -RedirectTemp /13-15 https://en.wikipedia.org/wiki/Duck_typing#History -RedirectTemp /13-16 http://ptgmedia.pearsoncmg.com/images/020163371x/items/item33.html -RedirectTemp /13-17 https://docs.python.org/3/library/bisect.html#bisect.bisect -RedirectTemp /13-20 https://github.com/python/cpython/blob/main/Lib/_collections_abc.py -RedirectTemp /13-21 https://github.com/python/cpython/blob/main/Lib/abc.py -RedirectTemp /13-22 https://docs.python.org/3/library/collections.abc.html#collections-abstract-base-classes -RedirectTemp /13-23 https://docs.python.org/3/library/collections.abc.html#collections.abc.Iterable -RedirectTemp /13-24 https://docs.python.org/3/library/abc.html -RedirectTemp /13-25 https://docs.python.org/dev/library/abc.html#abc.abstractmethod -RedirectTemp /13-26 https://docs.python.org/dev/library/abc.html -RedirectTemp /13-27 https://docs.python.org/3/library/os.html#os.urandom -RedirectTemp /13-28 https://github.com/python/mypy/issues/2922 -RedirectTemp /13-29 https://docs.python.org/3/library/stdtypes.html#truth -RedirectTemp /13-30 https://github.com/python/cpython/blob/0bbf30e2b910bc9c5899134ae9d73a8df968da35/Lib/_collections_abc.py -RedirectTemp /13-31 https://github.com/python/cpython/blob/0fbddb14dc03f61738af01af88e7d8aa8df07336/Lib/_collections_abc.py#L369 -RedirectTemp /13-32 https://bugs.python.org/issue31333 -RedirectTemp /13-33 https://github.com/python/cpython/blob/3635388f52b42e5280229104747962117104c453/Modules/_abc.c#L605 -RedirectTemp /13-34 https://github.com/python/cpython/blob/0fbddb14dc03f61738af01af88e7d8aa8df07336/Lib/_collections_abc.py#L881 -RedirectTemp /13-37 https://docs.python.org/3/library/typing.html#protocols -RedirectTemp /13-40 https://martinfowler.com/bliki/RoleInterface.html -RedirectTemp /13-41 https://en.wikipedia.org/wiki/Interface_segregation_principle -RedirectTemp /13-42 https://github.com/python/typeshed/blob/master/CONTRIBUTING.md -RedirectTemp /13-43 https://gist.github.com/asukakenji/ac8a05644a2e98f1d5ea8c299541fce9 -RedirectTemp /13-44 https://www.python.org/dev/peps/pep-0544/#merging-and-extending-protocols -RedirectTemp /13-45 https://numpy.org/devdocs/user/basics.types.html -RedirectTemp /13-46 https://github.com/python/typeshed/blob/master/stdlib/statistics.pyi -RedirectTemp /13-47 https://bugs.python.org/issue41974 -RedirectTemp /13-49 https://glyph.twistedmatrix.com/2020/07/new-duck.html -RedirectTemp /13-50 https://glyph.twistedmatrix.com/2021/03/interfaces-and-protocols.html -RedirectTemp /13-51 https://plone.org/ -RedirectTemp /13-52 https://trypyramid.com/ -RedirectTemp /13-53 https://twistedmatrix.com/trac/ -RedirectTemp /13-54 https://www.artima.com/articles/contracts-in-python -RedirectTemp /13-55 https://martinfowler.com/bliki/DynamicTyping.html -RedirectTemp /13-56 https://martinfowler.com/bliki/RoleInterface.html -RedirectTemp /13-57 https://mypy.readthedocs.io/en/stable/protocols.html -RedirectTemp /13-58 https://pymotw.com/3/abc/index.html -RedirectTemp /13-59 https://www.python.org/dev/peps/pep-3119/ -RedirectTemp /13-60 https://www.python.org/dev/peps/pep-3141/ -RedirectTemp /13-61 https://docs.python.org/3/library/numbers.html -RedirectTemp /13-62 https://github.com/python/mypy/issues/3186 -RedirectTemp /13-63 https://github.com/python/mypy/issues/3186 -RedirectTemp /13-64 https://martinfowler.com/articles/lean-inception/ -RedirectTemp /13-65 https://martinfowler.com -RedirectTemp /13-68 https://www.jetbrains.com/pycharm/ -RedirectTemp /13-69 https://wingware.com/ -RedirectTemp /13-70 https://code.visualstudio.com/ +RedirectTemp /13-1 https://docs.python.org/3/c-api/index.html +RedirectTemp /13-2 https://docs.python.org/3/c-api/sequence.html +RedirectTemp /13-3 https://github.com/python/cpython/blob/31ceccb2c77854893f3a754aca04bedd74bedb10/Lib/_collections_abc.py#L870 +RedirectTemp /13-4 https://en.wikipedia.org/wiki/Monkey_patch +RedirectTemp /13-5 https://www.gevent.org/api/gevent.monkey.html +RedirectTemp /13-6 https://docs.python.org/3/library/random.html#random.shuffle +RedirectTemp /13-7 https://docs.python.org/3/reference/datamodel.html#emulating-container-types +RedirectTemp /13-8 https://docs.python.org/3/library/collections.html#collections.namedtuple +RedirectTemp /13-9 https://github.com/python/typeshed/blob/24afb531ffd07083d6a74be917342195062f7277/stdlib/collections/__init__.pyi +RedirectTemp /13-10 https://docs.python.org/3/glossary.html#term-abstract-base-class +RedirectTemp /13-11 https://en.wikipedia.org/wiki/Duck_typing#History +RedirectTemp /13-12 http://ptgmedia.pearsoncmg.com/images/020163371x/items/item33.html +RedirectTemp /13-13 https://docs.python.org/3/library/bisect.html#bisect.bisect +RedirectTemp /13-14 https://github.com/python/cpython/blob/main/Lib/_collections_abc.py +RedirectTemp /13-15 https://github.com/python/cpython/blob/main/Lib/abc.py +RedirectTemp /13-16 https://docs.python.org/3/library/collections.abc.html#collections-abstract-base-classes +RedirectTemp /13-17 https://docs.python.org/3/library/collections.abc.html#collections.abc.Iterable +RedirectTemp /13-18 https://docs.python.org/3/library/abc.html +RedirectTemp /13-19 https://docs.python.org/dev/library/abc.html#abc.abstractmethod +RedirectTemp /13-20 https://docs.python.org/dev/library/abc.html +RedirectTemp /13-21 https://docs.python.org/3/library/os.html#os.urandom +RedirectTemp /13-22 https://github.com/python/mypy/issues/2922 +RedirectTemp /13-23 https://docs.python.org/3/library/stdtypes.html#truth +RedirectTemp /13-24 https://github.com/python/cpython/blob/0bbf30e2b910bc9c5899134ae9d73a8df968da35/Lib/_collections_abc.py +RedirectTemp /13-25 https://github.com/python/cpython/blob/0fbddb14dc03f61738af01af88e7d8aa8df07336/Lib/_collections_abc.py#L369 +RedirectTemp /13-26 https://github.com/python/cpython/blob/c0a9afe2ac1820409e6173bd1893ebee2cf50270/Lib/abc.py#L196 +RedirectTemp /13-27 https://bugs.python.org/issue31333 +RedirectTemp /13-28 https://github.com/python/cpython/blob/3635388f52b42e5280229104747962117104c453/Modules/_abc.c#L605 +RedirectTemp /13-29 https://github.com/python/cpython/blob/0fbddb14dc03f61738af01af88e7d8aa8df07336/Lib/_collections_abc.py#L881 +RedirectTemp /13-30 https://docs.python.org/3/library/typing.html#protocols +RedirectTemp /13-31 https://github.com/python/cpython/blob/3635388f52b42e5280229104747962117104c453/Lib/typing.py#L1751 +RedirectTemp /13-32 https://mail.python.org/archives/list/python-dev@python.org/thread/CLVXXPQ2T2LQ5MP2Y53VVQFCXYWQJHKZ/ +RedirectTemp /13-33 https://martinfowler.com/bliki/RoleInterface.html +RedirectTemp /13-34 https://en.wikipedia.org/wiki/Interface_segregation_principle +RedirectTemp /13-35 https://github.com/python/typeshed/blob/master/CONTRIBUTING.md +RedirectTemp /13-36 https://gist.github.com/asukakenji/ac8a05644a2e98f1d5ea8c299541fce9 +RedirectTemp /13-37 https://www.python.org/dev/peps/pep-0544/#runtime-checkable-decorator-and-narrowing-types-by-isinstance +RedirectTemp /13-38 https://www.python.org/dev/peps/pep-0544/#merging-and-extending-protocols +RedirectTemp /13-39 https://numpy.org/devdocs/user/basics.types.html +RedirectTemp /13-40 https://github.com/python/typeshed/blob/master/stdlib/statistics.pyi +RedirectTemp /13-41 https://bugs.python.org/issue41974 +RedirectTemp /13-42 https://glyph.twistedmatrix.com/2020/07/new-duck.html +RedirectTemp /13-43 https://glyph.twistedmatrix.com/2021/03/interfaces-and-protocols.html +RedirectTemp /13-44 https://plone.org/ +RedirectTemp /13-45 https://trypyramid.com/ +RedirectTemp /13-46 https://twistedmatrix.com/trac/ +RedirectTemp /13-47 https://www.artima.com/articles/contracts-in-python +RedirectTemp /13-48 https://martinfowler.com/bliki/DynamicTyping.html +RedirectTemp /13-49 https://martinfowler.com/bliki/RoleInterface.html +RedirectTemp /13-50 https://mypy.readthedocs.io/en/stable/protocols.html +RedirectTemp /13-51 https://pymotw.com/3/abc/index.html +RedirectTemp /13-52 https://www.python.org/dev/peps/pep-3119/ +RedirectTemp /13-53 https://www.python.org/dev/peps/pep-3141/ +RedirectTemp /13-54 https://docs.python.org/3/library/numbers.html +RedirectTemp /13-55 https://github.com/python/mypy/issues/3186 +RedirectTemp /13-56 https://stackoverflow.com/questions/69334475/how-to-hint-at-number-types-i-e-subclasses-of-number-not-numbers-themselv/69383462#69383462 +RedirectTemp /13-57 https://github.com/python/mypy/issues/3186 +RedirectTemp /13-58 https://martinfowler.com/articles/lean-inception/ +RedirectTemp /13-59 https://martinfowler.com +RedirectTemp /13-60 https://www.jetbrains.com/pycharm/ +RedirectTemp /13-61 https://wingware.com/ +RedirectTemp /13-62 https://code.visualstudio.com/ ############################################################ 14 RedirectTemp /14-1 http://worrydream.com/EarlyHistoryOfSmalltalk/ RedirectTemp /14-2 https://docs.python.org/3/tutorial/classes.html RedirectTemp /14-3 https://docs.python.org/3/library/collections.html#ordereddict-examples-and-recipes RedirectTemp /14-4 https://discuss.python.org/t/is-it-time-to-deprecate-unbound-super-methods/1833 -RedirectTemp /14-6 https://doc.pypy.org/en/latest/cpython_differences.html#subclasses-of-built-in-types -RedirectTemp /14-7 https://docs.python.org/3/library/collections.html -RedirectTemp /14-9 https://doc.pypy.org/en/latest/cpython_differences.html#subclasses-of-built-in-types -RedirectTemp /14-10 https://en.wikipedia.org/wiki/Breadth-first_search -RedirectTemp /14-11 https://www.python.org/download/releases/2.3/mro/ -RedirectTemp /14-12 https://github.com/fluentpython/example-code-2e/blob/master/14-inheritance/uppermixin.py -RedirectTemp /14-13 https://docs.oracle.com/javase/tutorial/java/IandI/defaultmethods.html -RedirectTemp /14-14 https://docs.python.org/3/library/collections.abc.html -RedirectTemp /14-15 https://github.com/python/cpython/blob/8ece98a7e418c3c68a4c61bc47a2d0931b59a889/Lib/collections/__init__.py#L1084 -RedirectTemp /14-16 https://docs.python.org/3/library/http.server.html -RedirectTemp /14-17 https://docs.python.org/3/library/socketserver.html#socketserver.ForkingMixIn -RedirectTemp /14-18 https://docs.python.org/3/library/os.html#os.fork -RedirectTemp /14-19 https://en.wikipedia.org/wiki/POSIX -RedirectTemp /14-20 http://ccbv.co.uk/ -RedirectTemp /14-21 https://github.com/django/django/tree/main/django/views/generic -RedirectTemp /14-22 https://en.wikipedia.org/wiki/Template_method_pattern -RedirectTemp /14-23 https://docs.python.org/3/library/tkinter.html -RedirectTemp /14-24 https://docs.python.org/3/library/tkinter.ttk.html -RedirectTemp /14-25 https://docs.oracle.com/javase/10/docs/api/java/awt/package-tree.html -RedirectTemp /14-26 https://docs.oracle.com/javase/10/docs/api/javax/swing/package-tree.html -RedirectTemp /14-27 https://squeak.org/ -RedirectTemp /14-28 https://github.com/python/cpython/blob/8ed183391241f0c73e7ba7f42b1d49fc02985f7b/Lib/tkinter/__init__.py#L2618 -RedirectTemp /14-29 https://docs.python.org/3/library/socketserver.html -RedirectTemp /14-30 https://docs.python.org/3/library/socketserver.html#socketserver.BaseServer -RedirectTemp /14-32 https://docs.python.org/3/library/typing.html#typing.final -RedirectTemp /14-33 https://docs.python.org/3/library/typing.html#typing.Final -RedirectTemp /14-35 https://docs.python.org/3/library/collections.abc.html -RedirectTemp /14-36 https://hynek.me/articles/python-subclassing-redux/ -RedirectTemp /14-38 https://rhettinger.wordpress.com/2011/05/26/super-considered-super/ -RedirectTemp /14-39 https://fuhm.net/super-harmful/ -RedirectTemp /14-40 https://www.artima.com/weblogs/viewpost.jsp?thread=246488 -RedirectTemp /14-41 https://www.artima.com/weblogs/viewpost.jsp?thread=281127 -RedirectTemp /14-42 https://www.artima.com/weblogs/viewpost.jsp?thread=246341 -RedirectTemp /14-43 https://www.artima.com/weblogs/viewpost.jsp?thread=246483 -RedirectTemp /14-44 https://www.artima.com/weblogs/viewpost.jsp?thread=236275 -RedirectTemp /14-45 https://www.artima.com/weblogs/viewpost.jsp?thread=236278 -RedirectTemp /14-46 https://www.artima.com/weblogs/viewpost.jsp?thread=237121 -RedirectTemp /14-47 https://python-patterns.guide/gang-of-four/composition-over-inheritance/ -RedirectTemp /14-48 https://python-patterns.guide/ -RedirectTemp /14-49 https://www.youtube.com/watch?v=3MNVP9-hglc -RedirectTemp /14-50 http://worrydream.com/EarlyHistoryOfSmalltalk/ -RedirectTemp /14-51 https://en.wikipedia.org/wiki/Polymorphism_(computer_science) +RedirectTemp /14-5 https://doc.pypy.org/en/latest/cpython_differences.html#subclasses-of-built-in-types +RedirectTemp /14-6 https://docs.python.org/3/library/collections.html +RedirectTemp /14-7 https://github.com/fluentpython/example-code-2e/blob/master/14-inheritance/strkeydict_dictsub.py +RedirectTemp /14-8 https://doc.pypy.org/en/latest/cpython_differences.html#subclasses-of-built-in-types +RedirectTemp /14-9 https://en.wikipedia.org/wiki/Breadth-first_search +RedirectTemp /14-10 https://www.python.org/download/releases/2.3/mro/ +RedirectTemp /14-11 https://github.com/fluentpython/example-code-2e/blob/master/14-inheritance/uppermixin.py +RedirectTemp /14-12 https://docs.oracle.com/javase/tutorial/java/IandI/defaultmethods.html +RedirectTemp /14-13 https://docs.python.org/3/library/collections.abc.html +RedirectTemp /14-14 https://github.com/python/cpython/blob/8ece98a7e418c3c68a4c61bc47a2d0931b59a889/Lib/collections/__init__.py#L1084 +RedirectTemp /14-15 https://docs.python.org/3/library/http.server.html +RedirectTemp /14-16 https://github.com/python/cpython/blob/17c23167942498296f0bdfffe52e72d53d66d693/Lib/http/server.py#L144 +RedirectTemp /14-17 https://github.com/python/cpython/blob/699ee016af5736ffc80f68359617611a22b72943/Lib/socketserver.py#L664 +RedirectTemp /14-18 https://docs.python.org/3/library/socketserver.html#socketserver.ForkingMixIn +RedirectTemp /14-19 https://docs.python.org/3/library/os.html#os.fork +RedirectTemp /14-20 https://en.wikipedia.org/wiki/POSIX +RedirectTemp /14-21 http://ccbv.co.uk/ +RedirectTemp /14-22 https://github.com/django/django/tree/main/django/views/generic +RedirectTemp /14-23 https://en.wikipedia.org/wiki/Template_method_pattern +RedirectTemp /14-24 https://docs.python.org/3/library/tkinter.html +RedirectTemp /14-25 https://docs.python.org/3/library/tkinter.ttk.html +RedirectTemp /14-26 https://docs.oracle.com/javase/10/docs/api/java/awt/package-tree.html +RedirectTemp /14-27 https://docs.oracle.com/javase/10/docs/api/javax/swing/package-tree.html +RedirectTemp /14-28 https://squeak.org/ +RedirectTemp /14-29 https://github.com/django/django/blob/b64db05b9cedd96905d637a2d824cbbf428e40e7/django/views/generic/list.py#L194 +RedirectTemp /14-30 https://github.com/python/cpython/blob/8ed183391241f0c73e7ba7f42b1d49fc02985f7b/Lib/tkinter/__init__.py#L2618 +RedirectTemp /14-31 https://docs.python.org/3/library/socketserver.html +RedirectTemp /14-32 https://docs.python.org/3/library/socketserver.html#socketserver.BaseServer +RedirectTemp /14-33 https://github.com/python/cpython/blob/699ee016af5736ffc80f68359617611a22b72943/Lib/socketserver.py#L153 +RedirectTemp /14-34 https://docs.python.org/3/library/typing.html#typing.final +RedirectTemp /14-35 https://docs.python.org/3/library/typing.html#typing.Final +RedirectTemp /14-36 https://docs.python.org/3/library/collections.abc.html +RedirectTemp /14-37 https://hynek.me/articles/python-subclassing-redux/ +RedirectTemp /14-38 https://www.oreilly.com/library/view/python-cookbook-3rd/9781449357337/ch08.html#super +RedirectTemp /14-39 https://rhettinger.wordpress.com/2011/05/26/super-considered-super/ +RedirectTemp /14-40 https://fuhm.net/super-harmful/ +RedirectTemp /14-41 https://stackoverflow.com/questions/30190185/how-to-use-super-with-one-argument/30190341#30190341 +RedirectTemp /14-42 https://www.artima.com/weblogs/viewpost.jsp?thread=246488 +RedirectTemp /14-43 https://www.artima.com/weblogs/viewpost.jsp?thread=281127 +RedirectTemp /14-44 https://www.artima.com/weblogs/viewpost.jsp?thread=246341 +RedirectTemp /14-45 https://www.artima.com/weblogs/viewpost.jsp?thread=246483 +RedirectTemp /14-46 https://www.artima.com/weblogs/viewpost.jsp?thread=236275 +RedirectTemp /14-47 https://www.artima.com/weblogs/viewpost.jsp?thread=236278 +RedirectTemp /14-48 https://www.artima.com/weblogs/viewpost.jsp?thread=237121 +RedirectTemp /14-49 https://python-patterns.guide/gang-of-four/composition-over-inheritance/ +RedirectTemp /14-50 https://python-patterns.guide/ +RedirectTemp /14-51 https://www.youtube.com/watch?v=3MNVP9-hglc +RedirectTemp /14-52 http://worrydream.com/EarlyHistoryOfSmalltalk/ +RedirectTemp /14-53 https://en.wikipedia.org/wiki/Polymorphism_(computer_science) ############################################################ 15 RedirectTemp /15-1 https://www.youtube.com/watch?v=csL8DLXGNlU&t=92m5s +RedirectTemp /15-2 https://github.com/python/typeshed/blob/a8834fcd46339e17fc8add82b5803a1ce53d3d60/stdlib/2and3/builtins.pyi#L1434 +RedirectTemp /15-3 https://github.com/python/typeshed/blob/a8834fcd46339e17fc8add82b5803a1ce53d3d60/stdlib/2and3/builtins.pyi RedirectTemp /15-4 https://twitter.com/gwidion/status/1265384692464967680 RedirectTemp /15-5 https://pypi.org/project/pydantic/ +RedirectTemp /15-6 https://google.github.io/pytype/faq.html RedirectTemp /15-7 https://google.github.io/pytype/faq.html -RedirectTemp /15-8 https://google.github.io/pytype/faq.html -RedirectTemp /15-9 https://lxml.de/ -RedirectTemp /15-10 https://docs.python.org/3/library/xml.etree.elementtree.html -RedirectTemp /15-11 https://mypy.readthedocs.io/en/stable/common_issues.html -RedirectTemp /15-12 https://mypy.readthedocs.io/en/stable/common_issues.html#types-of-empty-collections -RedirectTemp /15-13 https://github.com/python/typing/issues/182 -RedirectTemp /15-14 https://pypi.org/project/pydantic/ -RedirectTemp /15-16 https://mypy.readthedocs.io/en/stable/type_narrowing.html#casts -RedirectTemp /15-17 https://www.python.org/dev/peps/pep-0484/#casts -RedirectTemp /15-18 https://github.com/python/typeshed/issues/5535 -RedirectTemp /15-19 https://docs.python.org/3/library/asyncio-stream.html#tcp-echo-server-using-streams +RedirectTemp /15-8 https://lxml.de/ +RedirectTemp /15-9 https://docs.python.org/3/library/xml.etree.elementtree.html +RedirectTemp /15-10 https://mypy.readthedocs.io/en/stable/common_issues.html +RedirectTemp /15-11 https://mypy.readthedocs.io/en/stable/common_issues.html#types-of-empty-collections +RedirectTemp /15-12 https://github.com/python/typing/issues/182 +RedirectTemp /15-13 https://pypi.org/project/pydantic/ +RedirectTemp /15-14 https://mypy.readthedocs.io/en/stable/type_narrowing.html#casts +RedirectTemp /15-15 https://github.com/python/cpython/blob/bee66d3cb98e740f9d8057eb7f503122052ca5d8/Lib/typing.py#L1340 +RedirectTemp /15-16 https://www.python.org/dev/peps/pep-0484/#casts +RedirectTemp /15-17 https://github.com/python/typeshed/issues/5535 +RedirectTemp /15-18 https://docs.python.org/3/library/asyncio-stream.html#tcp-echo-server-using-streams +RedirectTemp /15-19 https://github.com/python/cpython/blob/b798ab06937f8bb24b444a49dd42e11fff15e654/Lib/test/test_asyncio/test_server.py#L55 RedirectTemp /15-20 https://en.wikipedia.org/wiki/Code_smell -RedirectTemp /15-21 https://mypy.readthedocs.io/en/stable/error_codes.html#error-codes -RedirectTemp /15-22 https://github.com/fluentpython/example-code-2e/blob/master/15-more-types/clip_annot.py +RedirectTemp /15-21 https://mail.python.org/archives/list/typing-sig@python.org/message/5LCWMN2UY2UQNLC5Z47GHBZKSPZW4I63/ +RedirectTemp /15-22 https://mypy.readthedocs.io/en/stable/error_codes.html#error-codes +RedirectTemp /15-23 https://github.com/fluentpython/example-code-2e/blob/master/15-more-types/clip_annot.py RedirectTemp /15-24 https://docs.python.org/3/library/typing.html#introspection-helpers RedirectTemp /15-25 https://docs.python.org/3.10/library/inspect.html#inspect.get_annotations -RedirectTemp /15-27 https://www.python.org/dev/peps/pep-0563/#abstract -RedirectTemp /15-29 https://docs.python.org/3.10/howto/annotations.html -RedirectTemp /15-32 https://docs.python.org/3/library/typing.html#user-defined-generic-types -RedirectTemp /15-33 https://docs.python.org/3.10/library/typing.html#typing.FrozenSet -RedirectTemp /15-34 https://docs.python.org/3.10/library/typing.html#typing.Generator -RedirectTemp /15-36 https://docs.python.org/3.10/library/typing.html#typing.AsyncGenerator -RedirectTemp /15-62 https://www.oreilly.com/library/view/robust-python/9781098100650/ -RedirectTemp /15-63 https://www.python.org/dev/peps/pep-0484/#covariance-and-contravariance -RedirectTemp /15-64 https://mypy.readthedocs.io/en/stable/generics.html#variance-of-generic-types -RedirectTemp /15-65 https://mypy.readthedocs.io/en/stable/common_issues.html#variance -RedirectTemp /15-67 https://www.artima.com/weblogs/viewpost.jsp?thread=85551 -RedirectTemp /15-68 https://dl.acm.org/action/cookieAbsent -RedirectTemp /15-69 http://bracha.org/pluggableTypesPosition.pdf -RedirectTemp /15-70 https://www.atomickotlin.com/atomickotlin/ -RedirectTemp /15-71 https://www.informit.com/store/effective-java-9780134685991 -RedirectTemp /15-72 https://www.manning.com/books/programming-with-types -RedirectTemp /15-73 https://www.oreilly.com/library/view/programming-typescript/9781492037644/ -RedirectTemp /15-74 https://www.informit.com/store/dart-programming-language-9780321927705 -RedirectTemp /15-75 https://www.yodaiken.com/2017/09/15/bad-ideas-in-type-theory/ -RedirectTemp /15-76 https://www.yodaiken.com/2017/11/30/types-considered-harmful-ii/ -RedirectTemp /15-77 https://web.archive.org/web/20071010002142/http://weblogs.java.net/blog/arnold/archive/2005/06/generics_consid_1.html -RedirectTemp /15-78 https://www.python.org/dev/peps/pep-0484/#covariance-and-contravariance +RedirectTemp /15-26 https://www.python.org/dev/peps/pep-0563/#abstract +RedirectTemp /15-27 https://mail.python.org/archives/list/python-dev@python.org/message/ZBJ7MD6CSGM6LZAOTET7GXAVBZB7O77O/ +RedirectTemp /15-28 https://docs.python.org/3.10/howto/annotations.html +RedirectTemp /15-29 https://docs.python.org/3/library/typing.html#user-defined-generic-types +RedirectTemp /15-30 https://github.com/python/typeshed/blob/bfc83c365a0b26ab16586beac77ff16729d0e473/stdlib/builtins.pyi#L743 +RedirectTemp /15-31 https://docs.python.org/3.10/library/typing.html#typing.FrozenSet +RedirectTemp /15-32 https://docs.python.org/3.10/library/typing.html#typing.Generator +RedirectTemp /15-33 https://docs.python.org/3.10/library/typing.html#typing.AsyncGenerator +RedirectTemp /15-34 https://github.com/python/cpython/blob/46b16d0bdbb1722daed10389e27226a2370f1635/Lib/typing.py#L1786 +RedirectTemp /15-35 https://github.com/python/typeshed/blob/2a9f081abbf01134e4e04ced6a750107db904d70/stdlib/builtins.pyi#L239 +RedirectTemp /15-36 https://www.oreilly.com/library/view/robust-python/9781098100650/ +RedirectTemp /15-37 https://www.python.org/dev/peps/pep-0484/#covariance-and-contravariance +RedirectTemp /15-38 https://mypy.readthedocs.io/en/stable/generics.html#variance-of-generic-types +RedirectTemp /15-39 https://mypy.readthedocs.io/en/stable/common_issues.html#variance +RedirectTemp /15-40 https://www.artima.com/weblogs/viewpost.jsp?thread=85551 +RedirectTemp /15-41 https://dl.acm.org/action/cookieAbsent +RedirectTemp /15-42 http://bracha.org/pluggableTypesPosition.pdf +RedirectTemp /15-43 https://www.researchgate.net/publication/213886116_Static_Typing_Where_Possible_Dynamic_Typing_When_Needed_The_End_of_the_Cold_War_Between_Programming_Languages +RedirectTemp /15-44 https://www.atomickotlin.com/atomickotlin/ +RedirectTemp /15-45 https://www.informit.com/store/effective-java-9780134685991 +RedirectTemp /15-46 https://www.manning.com/books/programming-with-types +RedirectTemp /15-47 https://www.oreilly.com/library/view/programming-typescript/9781492037644/ +RedirectTemp /15-48 https://www.informit.com/store/dart-programming-language-9780321927705 +RedirectTemp /15-49 https://www.yodaiken.com/2017/09/15/bad-ideas-in-type-theory/ +RedirectTemp /15-50 https://www.yodaiken.com/2017/11/30/types-considered-harmful-ii/ +RedirectTemp /15-51 https://web.archive.org/web/20071010002142/http://weblogs.java.net/blog/arnold/archive/2005/06/generics_consid_1.html +RedirectTemp /15-52 https://github.com/python/cpython/blob/3e7ee02327db13e4337374597cdc4458ecb9e3ad/Lib/asyncio/trsock.py#L5 +RedirectTemp /15-53 https://www.python.org/dev/peps/pep-0484/#covariance-and-contravariance ############################################################ 16 RedirectTemp /16-1 http://www.gotw.ca/publications/c_family_interview.htm RedirectTemp /16-2 https://docs.python.org/3/reference/expressions.html#unary-arithmetic-and-bitwise-operations +RedirectTemp /16-3 https://docs.python.org/3/reference/datamodel.html#object.__neg__ RedirectTemp /16-4 https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#boolean-indexing RedirectTemp /16-5 https://docs.python.org/3/library/collections.html#collections.Counter RedirectTemp /16-6 https://docs.python.org/3/reference/datamodel.html#emulating-container-types -RedirectTemp /16-8 https://docs.python.org/3/library/numbers.html#implementing-the-arithmetic-operations -RedirectTemp /16-10 https://www.fluentpython.com/lingo/#fail-fast -RedirectTemp /16-11 https://neopythonic.blogspot.com/2019/03/why-operators-are-useful.html -RedirectTemp /16-12 https://treyhunner.com/2019/03/python-deep-comparisons-and-code-readability/ -RedirectTemp /16-14 https://docs.python.org/3/library/numbers.html#implementing-the-arithmetic-operations -RedirectTemp /16-15 https://docs.python.org/3/library/pathlib.html -RedirectTemp /16-16 https://pypi.org/project/scapy/ -RedirectTemp /16-17 https://scapy.readthedocs.io/en/latest/usage.html#stacking-layers -RedirectTemp /16-18 https://docs.python.org/3/library/functools.html#functools.total_ordering -RedirectTemp /16-19 https://wiki.illinois.edu//wiki/download/attachments/273416327/ingalls.pdf -RedirectTemp /16-20 https://wiki.illinois.edu//wiki/download/attachments/273416327/double-dispatch.pdf -RedirectTemp /16-21 http://www.gotw.ca/publications/c_family_interview.htm -RedirectTemp /16-22 https://doc.rust-lang.org/std/ops/index.html -RedirectTemp /16-23 https://www.fluentpython.com/lingo/#lazy +RedirectTemp /16-7 https://docs.python.org/3/library/numbers.html#implementing-the-arithmetic-operations +RedirectTemp /16-8 https://www.fluentpython.com/lingo/#fail-fast +RedirectTemp /16-9 https://github.com/python/cpython/blob/0bbf30e2b910bc9c5899134ae9d73a8df968da35/Objects/typeobject.c#L4598 +RedirectTemp /16-10 https://neopythonic.blogspot.com/2019/03/why-operators-are-useful.html +RedirectTemp /16-11 https://treyhunner.com/2019/03/python-deep-comparisons-and-code-readability/ +RedirectTemp /16-12 https://docs.python.org/3/library/numbers.html#implementing-the-arithmetic-operations +RedirectTemp /16-13 https://docs.python.org/3/library/pathlib.html +RedirectTemp /16-14 https://pypi.org/project/scapy/ +RedirectTemp /16-15 https://scapy.readthedocs.io/en/latest/usage.html#stacking-layers +RedirectTemp /16-16 https://docs.python.org/3/library/functools.html#functools.total_ordering +RedirectTemp /16-17 https://wiki.illinois.edu//wiki/download/attachments/273416327/ingalls.pdf +RedirectTemp /16-18 https://wiki.illinois.edu//wiki/download/attachments/273416327/double-dispatch.pdf +RedirectTemp /16-19 http://www.gotw.ca/publications/c_family_interview.htm +RedirectTemp /16-20 http://www.gotw.ca/publications/c_family_interview.htm +RedirectTemp /16-21 https://doc.rust-lang.org/std/ops/index.html +RedirectTemp /16-22 https://www.fluentpython.com/lingo/#lazy ############################################################ 17 RedirectTemp /17-1 http://www.paulgraham.com/icad.html -RedirectTemp /17-5 https://en.wikipedia.org/wiki/Sentinel_value -RedirectTemp /17-6 https://docs.python.org/3.10/library/functions.html#iter -RedirectTemp /17-7 https://docs.python.org/3.10/library/functions.html#iter -RedirectTemp /17-8 https://github.com/python/cpython/blob/b1930bf75f276cd7ca08c4455298128d89adf7d1/Lib/_collections_abc.py#L271 -RedirectTemp /17-9 https://github.com/python/cpython/blob/main/Lib/types.py#L6 -RedirectTemp /17-10 https://en.wikipedia.org/wiki/CLU_(programming_language) -RedirectTemp /17-12 https://docs.python.org/3/glossary.html -RedirectTemp /17-13 https://docs.python.org/3/glossary.html#term-generator-iterator -RedirectTemp /17-14 https://docs.python.org/3/glossary.html#term-generator-expression -RedirectTemp /17-15 https://marc.info/?l=python-list&m=141826925106951&w=2 -RedirectTemp /17-17 https://docs.python.org/3/library/itertools.html -RedirectTemp /17-18 https://docs.python.org/3/library/exceptions.html#exception-hierarchy -RedirectTemp /17-19 https://en.wikipedia.org/wiki/Depth-first_search -RedirectTemp /17-20 https://docs.python.org/3.10/library/typing.html#typing.TypeAlias -RedirectTemp /17-22 https://docs.python.org/3/library/typing.html#typing.Generator -RedirectTemp /17-25 http://www.dabeaz.com/coroutines/Coroutines.pdf -RedirectTemp /17-26 http://www.dabeaz.com/coroutines/Coroutines.pdf -RedirectTemp /17-27 https://mail.python.org/pipermail/python-ideas/2009-April/003841.html -RedirectTemp /17-28 https://mail.python.org/pipermail/python-ideas/2009-April/003912.html -RedirectTemp /17-31 https://docs.python.org/3/library/exceptions.html#StopIteration -RedirectTemp /17-32 https://docs.python.org/3/reference/expressions.html#yield-expressions -RedirectTemp /17-33 https://docs.python.org/3/reference/index.html -RedirectTemp /17-36 http://catb.org/~esr/jargon/html/G/grok.html -RedirectTemp /17-37 https://docs.python.org/3/reference/expressions.html#yieldexpr -RedirectTemp /17-39 https://docs.python.org/3/library/itertools.html#itertools-recipes -RedirectTemp /17-40 https://more-itertools.readthedocs.io/en/stable/index.html -RedirectTemp /17-41 https://rittau.org/2006/11/java-iterators-are-not-iterable/ -RedirectTemp /17-42 https://docs.python.org/3/whatsnew/3.3.html#pep-380-syntax-for-delegating-to-a-subgenerator -RedirectTemp /17-45 http://www.dabeaz.com/generators/ -RedirectTemp /17-46 http://www.dabeaz.com/coroutines/ -RedirectTemp /17-47 https://archive.org/details/pyvideo_213___pycon-2009-a-curious-course-on-coroutines-and-concurrency-part-1-of-3 -RedirectTemp /17-48 https://archive.org/details/pyvideo_215___pycon-2009-a-curious-course-on-coroutines-and-concurrency-part-2-of-3 -RedirectTemp /17-49 https://archive.org/details/pyvideo_214___pycon-2009-a-curious-course-on-coroutines-and-concurrency-part-3-of-3 -RedirectTemp /17-50 http://www.dabeaz.com/finalgenerator/ -RedirectTemp /17-51 https://web.archive.org/web/20200218150637/http://seriously.dontusethiscode.com/2013/05/01/greedy-coroutine.html -RedirectTemp /17-52 https://effectivepython.com/ -RedirectTemp /17-53 https://effectivepython.com/2015/03/10/consider-coroutines-to-run-many-functions-concurrently -RedirectTemp /17-54 https://en.wikipedia.org/wiki/Conway's_Game_of_Life -RedirectTemp /17-55 https://gist.github.com/ramalho/da5590bc38c973408839 -RedirectTemp /17-56 https://gist.github.com/ramalho/da5590bc38c973408839 -RedirectTemp /17-57 https://journal.code4lib.org/articles/4893 -RedirectTemp /17-58 https://github.com/fluentpython/isis2json -RedirectTemp /17-59 https://github.com/fluentpython/isis2json/blob/master/README.rst +RedirectTemp /17-2 https://en.wikipedia.org/wiki/Sentinel_value +RedirectTemp /17-3 https://docs.python.org/3.10/library/functions.html#iter +RedirectTemp /17-4 https://docs.python.org/3.10/library/functions.html#iter +RedirectTemp /17-5 https://github.com/python/cpython/blob/b1930bf75f276cd7ca08c4455298128d89adf7d1/Lib/_collections_abc.py#L271 +RedirectTemp /17-6 https://github.com/python/cpython/blob/main/Lib/types.py#L6 +RedirectTemp /17-7 https://en.wikipedia.org/wiki/CLU_(programming_language) +RedirectTemp /17-8 https://docs.python.org/3/glossary.html +RedirectTemp /17-9 https://docs.python.org/3/glossary.html#term-generator-iterator +RedirectTemp /17-10 https://docs.python.org/3/glossary.html#term-generator-expression +RedirectTemp /17-11 https://marc.info/?l=python-list&m=141826925106951&w=2 +RedirectTemp /17-12 https://docs.python.org/3/library/os.html#os.walk +RedirectTemp /17-13 https://docs.python.org/3/library/itertools.html +RedirectTemp /17-14 https://docs.python.org/3/library/exceptions.html#exception-hierarchy +RedirectTemp /17-15 https://en.wikipedia.org/wiki/Depth-first_search +RedirectTemp /17-16 https://docs.python.org/3.10/library/typing.html#typing.TypeAlias +RedirectTemp /17-17 https://docs.python.org/3/library/typing.html#typing.Generator +RedirectTemp /17-18 http://www.dabeaz.com/coroutines/Coroutines.pdf +RedirectTemp /17-19 http://www.dabeaz.com/coroutines/Coroutines.pdf +RedirectTemp /17-20 https://mail.python.org/pipermail/python-ideas/2009-April/003841.html +RedirectTemp /17-21 https://mail.python.org/pipermail/python-ideas/2009-April/003912.html +RedirectTemp /17-22 https://docs.python.org/3/library/exceptions.html#StopIteration +RedirectTemp /17-23 https://docs.python.org/3/reference/expressions.html#yield-expressions +RedirectTemp /17-24 https://docs.python.org/3/reference/index.html +RedirectTemp /17-25 https://github.com/python/cpython/blob/6f743e7a4da904f61dfa84cc7d7385e4dcc79ac5/Lib/typing.py#L2060 +RedirectTemp /17-26 http://catb.org/~esr/jargon/html/G/grok.html +RedirectTemp /17-27 https://docs.python.org/3/reference/expressions.html#yieldexpr +RedirectTemp /17-28 https://docs.python.org/3/library/itertools.html +RedirectTemp /17-29 https://docs.python.org/3/library/itertools.html#itertools-recipes +RedirectTemp /17-30 https://more-itertools.readthedocs.io/en/stable/index.html +RedirectTemp /17-31 https://rittau.org/2006/11/java-iterators-are-not-iterable/ +RedirectTemp /17-32 https://docs.python.org/3/whatsnew/3.3.html#pep-380-syntax-for-delegating-to-a-subgenerator +RedirectTemp /17-33 http://www.dabeaz.com/generators/ +RedirectTemp /17-34 http://www.dabeaz.com/coroutines/ +RedirectTemp /17-35 https://archive.org/details/pyvideo_213___pycon-2009-a-curious-course-on-coroutines-and-concurrency-part-1-of-3 +RedirectTemp /17-36 https://archive.org/details/pyvideo_215___pycon-2009-a-curious-course-on-coroutines-and-concurrency-part-2-of-3 +RedirectTemp /17-37 https://archive.org/details/pyvideo_214___pycon-2009-a-curious-course-on-coroutines-and-concurrency-part-3-of-3 +RedirectTemp /17-38 http://www.dabeaz.com/finalgenerator/ +RedirectTemp /17-39 https://web.archive.org/web/20200218150637/http://seriously.dontusethiscode.com/2013/05/01/greedy-coroutine.html +RedirectTemp /17-40 https://effectivepython.com/ +RedirectTemp /17-41 https://effectivepython.com/2015/03/10/consider-coroutines-to-run-many-functions-concurrently +RedirectTemp /17-42 https://en.wikipedia.org/wiki/Conway's_Game_of_Life +RedirectTemp /17-43 https://gist.github.com/ramalho/da5590bc38c973408839 +RedirectTemp /17-44 https://gist.github.com/ramalho/da5590bc38c973408839 +RedirectTemp /17-45 https://journal.code4lib.org/articles/4893 +RedirectTemp /17-46 https://github.com/fluentpython/isis2json +RedirectTemp /17-47 https://github.com/fluentpython/isis2json/blob/master/README.rst ############################################################ 18 RedirectTemp /18-1 https://pyvideo.org/video/1669/keynote-3/ RedirectTemp /18-2 https://docs.python.org/3/library/sqlite3.html#using-the-connection-as-a-context-manager @@ -678,42 +726,53 @@ RedirectTemp /18-4 https://docs.python.org/3/library/decimal.html#decimal.localc RedirectTemp /18-5 https://docs.python.org/3/library/unittest.mock.html#patch RedirectTemp /18-6 https://docs.python.org/3/library/contextlib.html#contextlib.redirect_stdout RedirectTemp /18-7 https://docs.python.org/3/library/sys.html#sys.exc_info -RedirectTemp /18-9 https://en.wikipedia.org/wiki/LL_parser -RedirectTemp /18-10 https://docs.python.org/3/library/contextlib.html +RedirectTemp /18-8 https://en.wikipedia.org/wiki/LL_parser +RedirectTemp /18-9 https://docs.python.org/3/library/contextlib.html +RedirectTemp /18-10 https://github.com/python/cpython/blob/8afab2ebbc1b343cd88d058914cf622fe687a2be/Lib/contextlib.py#L123 RedirectTemp /18-11 https://www.zopatista.com/python/2013/11/26/inplace-file-rewriting/ RedirectTemp /18-12 https://docs.python.org/3/library/fileinput.html#fileinput.input RedirectTemp /18-13 https://www.zopatista.com/python/2013/11/26/inplace-file-rewriting/ RedirectTemp /18-14 https://en.wikipedia.org/wiki/Euclidean_algorithm RedirectTemp /18-15 https://github.com/fluentpython/example-code-2e/tree/master/18-with-match/lispy/py3.10/ -RedirectTemp /18-17 https://github.com/python/typeshed/issues/6042 -RedirectTemp /18-18 https://github.com/fluentpython/lispy/tree/main/mylis -RedirectTemp /18-19 https://mitpress.mit.edu/sites/default/files/sicp/index.html -RedirectTemp /18-20 https://www.python.org/dev/peps/pep-0634/#or-patterns -RedirectTemp /18-21 https://en.wikipedia.org/wiki/Lambda#Character_encodings -RedirectTemp /18-22 https://docs.python.org/3/reference/compound_stmts.html -RedirectTemp /18-23 https://docs.python.org/3/glossary.html#term-eafp -RedirectTemp /18-24 https://speakerdeck.com/pyconslides/pycon-keynote-python-is-awesome-by-raymond-hettinger?slide=21 -RedirectTemp /18-25 https://docs.python.org/3/reference/compound_stmts.html -RedirectTemp /18-26 https://stackoverflow.com/questions/16138232/is-it-a-good-practice-to-use-try-except-else-in-python -RedirectTemp /18-27 https://docs.python.org/3/library/stdtypes.html#typecontextmanager -RedirectTemp /18-28 https://docs.python.org/3/reference/datamodel.html#with-statement-context-managers -RedirectTemp /18-30 https://speakerdeck.com/pyconslides/pycon-keynote-python-is-awesome-by-raymond-hettinger?slide=21 -RedirectTemp /18-31 https://speakerdeck.com/pyconslides/transforming-code-into-beautiful-idiomatic-python-by-raymond-hettinger-1?slide=34 -RedirectTemp /18-32 https://preshing.com/20110920/the-python-with-statement-by-example/ -RedirectTemp /18-33 https://www.rath.org/on-the-beauty-of-pythons-exitstack.html -RedirectTemp /18-34 https://github.com/norvig/pytudes -RedirectTemp /18-35 https://github.com/fluentpython/lispy -RedirectTemp /18-36 https://racket-lang.org/ -RedirectTemp /18-37 https://pyvideo.org/video/1669/keynote-3/ -RedirectTemp /18-38 https://en.wikipedia.org/wiki/Tail_call -RedirectTemp /18-39 https://2ality.com/2015/06/tail-call-optimization.html -RedirectTemp /18-40 http://neopythonic.blogspot.com/2009/04/final-words-on-tail-calls.html -RedirectTemp /18-41 https://webkit.org/blog/6240/ecmascript-6-proper-tail-calls-in-webkit/ -RedirectTemp /18-42 http://kangax.github.io/compat-table/es6/ -RedirectTemp /18-43 https://world.hey.com/mgmarlow/what-happened-to-proper-tail-calls-in-javascript-5494c256 -RedirectTemp /18-44 http://neopythonic.blogspot.com/2009/04/tail-recursion-elimination.html -RedirectTemp /18-45 https://github.com/fluentpython/lispy/blob/main/mylis/mylis_2/lis.py -RedirectTemp /18-46 https://github.com/fluentpython/lispy/tree/main/mylis +RedirectTemp /18-16 https://github.com/fluentpython/example-code-2e/blob/master/18-with-match/lispy/original/lispy.py +RedirectTemp /18-17 https://github.com/fluentpython/example-code-2e/blob/6527037ae7319ba370a1ee2d9fe79214d0ed9452/18-with-match/lispy/py3.10/lis.py#L35 +RedirectTemp /18-18 https://github.com/fluentpython/example-code-2e/blob/master/18-with-match/lispy/py3.10/examples_test.py +RedirectTemp /18-19 https://github.com/python/typeshed/issues/6042 +RedirectTemp /18-20 https://github.com/fluentpython/lispy/tree/main/mylis +RedirectTemp /18-21 https://github.com/fluentpython/example-code-2e/blob/00e4741926e1b771ee7c753148b1415c0bd12e39/02-array-seq/lispy/py3.10/examples_test.py +RedirectTemp /18-22 https://mitpress.mit.edu/sites/default/files/sicp/index.html +RedirectTemp /18-23 https://github.com/fluentpython/example-code-2e/blob/master/18-with-match/lispy/py3.10/examples_test.py +RedirectTemp /18-24 https://github.com/fluentpython/example-code-2e/blob/master/18-with-match/lispy/py3.10/lis.py +RedirectTemp /18-25 https://www.python.org/dev/peps/pep-0634/#or-patterns +RedirectTemp /18-26 https://en.wikipedia.org/wiki/Lambda#Character_encodings +RedirectTemp /18-27 https://docs.python.org/3/reference/compound_stmts.html +RedirectTemp /18-28 https://docs.python.org/3/glossary.html#term-eafp +RedirectTemp /18-29 https://speakerdeck.com/pyconslides/pycon-keynote-python-is-awesome-by-raymond-hettinger?slide=21 +RedirectTemp /18-30 https://docs.python.org/3/reference/compound_stmts.html +RedirectTemp /18-31 https://stackoverflow.com/questions/16138232/is-it-a-good-practice-to-use-try-except-else-in-python +RedirectTemp /18-32 https://docs.python.org/3/library/stdtypes.html#typecontextmanager +RedirectTemp /18-33 https://docs.python.org/3/reference/datamodel.html#with-statement-context-managers +RedirectTemp /18-34 https://speakerdeck.com/pyconslides/pycon-keynote-python-is-awesome-by-raymond-hettinger?slide=21 +RedirectTemp /18-35 https://speakerdeck.com/pyconslides/transforming-code-into-beautiful-idiomatic-python-by-raymond-hettinger-1?slide=34 +RedirectTemp /18-36 https://preshing.com/20110920/the-python-with-statement-by-example/ +RedirectTemp /18-37 https://www.rath.org/on-the-beauty-of-pythons-exitstack.html +RedirectTemp /18-38 https://norvig.com/lispy.html +RedirectTemp /18-39 https://norvig.com/lispy2.html +RedirectTemp /18-40 https://github.com/norvig/pytudes +RedirectTemp /18-41 https://github.com/fluentpython/lispy +RedirectTemp /18-42 https://racket-lang.org/ +RedirectTemp /18-43 https://pyvideo.org/video/1669/keynote-3/ +RedirectTemp /18-44 https://en.wikipedia.org/wiki/Tail_call +RedirectTemp /18-45 https://2ality.com/2015/06/tail-call-optimization.html +RedirectTemp /18-46 https://github.com/fluentpython/example-code-2e/blob/master/18-with-match/lispy/original/lis.py +RedirectTemp /18-47 https://github.com/fluentpython/example-code-2e/blob/master/18-with-match/lispy/original/lispy.py +RedirectTemp /18-48 http://neopythonic.blogspot.com/2009/04/final-words-on-tail-calls.html +RedirectTemp /18-49 https://webkit.org/blog/6240/ecmascript-6-proper-tail-calls-in-webkit/ +RedirectTemp /18-50 http://kangax.github.io/compat-table/es6/ +RedirectTemp /18-51 https://world.hey.com/mgmarlow/what-happened-to-proper-tail-calls-in-javascript-5494c256 +RedirectTemp /18-52 http://neopythonic.blogspot.com/2009/04/tail-recursion-elimination.html +RedirectTemp /18-53 https://github.com/fluentpython/lispy/blob/main/mylis/mylis_2/lis.py +RedirectTemp /18-54 https://github.com/fluentpython/lispy/tree/main/mylis ############################################################ 19 RedirectTemp /19-1 https://go.dev/blog/waza-talk RedirectTemp /19-2 https://en.wikipedia.org/wiki/Graphics_processing_unit @@ -721,69 +780,81 @@ RedirectTemp /19-3 https://docs.python.org/3/library/sys.html#sys.getswitchinter RedirectTemp /19-4 https://docs.python.org/3/library/sys.html#sys.setswitchinterval RedirectTemp /19-5 https://en.wikipedia.org/wiki/System_call RedirectTemp /19-6 https://mail.python.org/pipermail/python-dev/2009-October/093356.html -RedirectTemp /19-8 http://www.dabeaz.com/finalgenerator/ -RedirectTemp /19-9 https://docs.python.org/3/library/threading.html#thread-objects -RedirectTemp /19-10 https://www.pypy.org/ -RedirectTemp /19-11 https://mail.python.org/pipermail/python-list/2009-February/675659.html -RedirectTemp /19-12 https://en.wikipedia.org/wiki/Braille_Patterns -RedirectTemp /19-13 https://docs.python.org/3/library/multiprocessing.shared_memory.html +RedirectTemp /19-7 http://www.dabeaz.com/finalgenerator/ +RedirectTemp /19-8 https://docs.python.org/3/library/threading.html#thread-objects +RedirectTemp /19-9 https://www.pypy.org/ +RedirectTemp /19-10 https://mail.python.org/pipermail/python-list/2009-February/675659.html +RedirectTemp /19-11 https://en.wikipedia.org/wiki/Braille_Patterns +RedirectTemp /19-12 https://docs.python.org/3/library/multiprocessing.shared_memory.html +RedirectTemp /19-13 https://docs.python.org/3/library/multiprocessing.shared_memory.html#multiprocessing.shared_memory.ShareableList RedirectTemp /19-14 https://greenlet.readthedocs.io/en/latest/ -RedirectTemp /19-15 https://docs.sqlalchemy.org/en/14/orm/extensions/asyncio.html -RedirectTemp /19-16 http://www.gevent.org/ -RedirectTemp /19-17 https://github.com/gevent/gevent/wiki/Projects +RedirectTemp /19-15 https://docs.sqlalchemy.org/en/14/changelog/migration_14.html#asynchronous-io-support-for-core-and-orm +RedirectTemp /19-16 https://docs.sqlalchemy.org/en/14/orm/extensions/asyncio.html +RedirectTemp /19-17 http://www.gevent.org/ +RedirectTemp /19-18 https://github.com/gevent/gevent/wiki/Projects RedirectTemp /19-19 https://docs.python.org/3/library/concurrent.futures.html#processpoolexecutor-example RedirectTemp /19-20 https://github.com/python/asyncio/issues/284 RedirectTemp /19-21 https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.loop.run_in_executor +RedirectTemp /19-22 https://mail.python.org/archives/list/python-dev@python.org/message/JBYXQH3NV3YBF7P2HLHB5CD6V3GVTY55/ RedirectTemp /19-23 https://docs.python.org/3/library/queue.html#queue.SimpleQueue.get RedirectTemp /19-24 https://en.wikipedia.org/wiki/Race_condition -RedirectTemp /19-26 https://en.wikipedia.org/wiki/Context_switch -RedirectTemp /19-27 http://www.gotw.ca/publications/concurrency-ddj.htm -RedirectTemp /19-28 https://www.ansible.com/ -RedirectTemp /19-29 https://saltproject.io/ -RedirectTemp /19-30 https://www.fabfile.org/ -RedirectTemp /19-31 https://jupyter.org/ -RedirectTemp /19-32 https://docs.bokeh.org/en/latest/index.html -RedirectTemp /19-33 https://www.tensorflow.org/ -RedirectTemp /19-34 https://pytorch.org/ -RedirectTemp /19-35 https://www.oreilly.com/radar/where-programming-ops-ai-and-the-cloud-are-headed-in-2021/ -RedirectTemp /19-38 https://www.youtube.com/watch?v=ods97a5Pzw0 -RedirectTemp /19-39 https://www.thoughtworks.com/radar/techniques/high-performance-envy-web-scale-envy +RedirectTemp /19-25 https://github.com/fluentpython/example-code-2e/commit/2c1230579db99738a5e5e6802063bda585f6476d +RedirectTemp /19-26 https://github.com/fluentpython/example-code-2e/blob/master/19-concurrency/primes/README.md +RedirectTemp /19-27 https://github.com/fluentpython/example-code-2e/blob/master/19-concurrency/primes/threads.py +RedirectTemp /19-28 https://en.wikipedia.org/wiki/Context_switch +RedirectTemp /19-29 http://www.gotw.ca/publications/concurrency-ddj.htm +RedirectTemp /19-30 https://www.ansible.com/ +RedirectTemp /19-31 https://saltproject.io/ +RedirectTemp /19-32 https://www.fabfile.org/ +RedirectTemp /19-33 https://engineering.fb.com/2016/05/27/production-engineering/python-in-production-engineering/ +RedirectTemp /19-34 https://jupyter.org/ +RedirectTemp /19-35 https://docs.bokeh.org/en/latest/index.html +RedirectTemp /19-36 https://www.tensorflow.org/ +RedirectTemp /19-37 https://pytorch.org/ +RedirectTemp /19-38 https://www.oreilly.com/radar/where-programming-ops-ai-and-the-cloud-are-headed-in-2021/ +RedirectTemp /19-39 https://www.youtube.com/watch?v=ods97a5Pzw0 +RedirectTemp /19-40 https://www.thoughtworks.com/radar/techniques/high-performance-envy-web-scale-envy RedirectTemp /19-41 https://modwsgi.readthedocs.io/en/master/ RedirectTemp /19-42 https://uwsgi-docs.readthedocs.io/en/latest/ -RedirectTemp /19-44 https://unit.nginx.org/ -RedirectTemp /19-45 https://www.techatbloomberg.com/blog/configuring-uwsgi-production-deployment/ -RedirectTemp /19-46 https://www.youtube.com/watch?v=p6R1h2Nn468 -RedirectTemp /19-47 https://asgi.readthedocs.io/en/latest/index.html -RedirectTemp /19-48 https://docs.celeryproject.org/en/stable/getting-started/introduction.html -RedirectTemp /19-49 https://python-rq.org/ +RedirectTemp /19-43 https://unit.nginx.org/ +RedirectTemp /19-44 https://www.techatbloomberg.com/blog/configuring-uwsgi-production-deployment/ +RedirectTemp /19-45 https://www.youtube.com/watch?v=p6R1h2Nn468 +RedirectTemp /19-46 https://asgi.readthedocs.io/en/latest/index.html +RedirectTemp /19-47 https://docs.celeryproject.org/en/stable/getting-started/introduction.html +RedirectTemp /19-48 https://python-rq.org/ +RedirectTemp /19-49 https://docs.celeryproject.org/en/stable/faq.html#what-kinds-of-things-should-i-use-celery-for RedirectTemp /19-50 https://redis.io/ RedirectTemp /19-51 https://realpython.com/intro-to-python-threading/ RedirectTemp /19-52 https://pymotw.com/3/concurrency.html +RedirectTemp /19-53 https://www.pearson.com/us/higher-education/program/Hellmann-Python-3-Standard-Library-by-Example-The/PGM328871.html RedirectTemp /19-54 https://docs.python.org/3/library/multiprocessing.html#programming-guidelines -RedirectTemp /19-56 https://docs.python.org/3/library/multiprocessing.html -RedirectTemp /19-57 https://www.oreilly.com/library/view/high-performance-python/9781492055013/ -RedirectTemp /19-58 https://link.springer.com/book/10.1007/978-1-4842-5793-7?error=cookies_not_supported&code=2ed5d61d-ae9f-4f3d-94ac-0f68cf45ea4f -RedirectTemp /19-59 https://www.packtpub.com/product/parallel-programming-with-python/9781783288397 -RedirectTemp /19-61 https://greenteapress.com/wp/semaphores/ -RedirectTemp /19-62 https://docs.python.org/3/c-api/init.html#thread-state-and-the-global-interpreter-lock -RedirectTemp /19-63 https://www.artima.com/weblogs/viewpost.jsp?thread=214235 -RedirectTemp /19-64 http://jessenoller.com/blog/2009/02/01/python-threads-and-the-global-interpreter-lock -RedirectTemp /19-65 https://realpython.com/products/cpython-internals-book/ -RedirectTemp /19-66 http://www.dabeaz.com/GIL/ -RedirectTemp /19-67 http://www.dabeaz.com/python/UnderstandingGIL.pdf -RedirectTemp /19-68 https://bugs.python.org/issue7946#msg223110 -RedirectTemp /19-69 https://bugs.python.org/issue7946 -RedirectTemp /19-70 https://www.fullstackpython.com/ -RedirectTemp /19-71 https://www.oreilly.com/library/view/high-performance-python/9781492055013/ -RedirectTemp /19-72 https://www.packtpub.com/product/parallel-programming-with-python/9781783288397 -RedirectTemp /19-73 https://www.packtpub.com/product/distributed-computing-with-python/9781785889691 -RedirectTemp /19-74 https://towardsdatascience.com/python-performance-and-gpus-1be860ffd58d?gi=6a57a172ab5e +RedirectTemp /19-55 https://docs.python.org/3/library/multiprocessing.html +RedirectTemp /19-56 https://www.oreilly.com/library/view/high-performance-python/9781492055013/ +RedirectTemp /19-57 https://link.springer.com/book/10.1007/978-1-4842-5793-7?error=cookies_not_supported&code=2ed5d61d-ae9f-4f3d-94ac-0f68cf45ea4f +RedirectTemp /19-58 https://www.packtpub.com/product/parallel-programming-with-python/9781783288397 +RedirectTemp /19-59 https://greenteapress.com/wp/semaphores/ +RedirectTemp /19-60 https://docs.python.org/3/c-api/init.html#thread-state-and-the-global-interpreter-lock +RedirectTemp /19-61 https://docs.python.org/3/faq/library.html#can-t-we-get-rid-of-the-global-interpreter-lock +RedirectTemp /19-62 https://www.artima.com/weblogs/viewpost.jsp?thread=214235 +RedirectTemp /19-63 http://jessenoller.com/blog/2009/02/01/python-threads-and-the-global-interpreter-lock +RedirectTemp /19-64 https://realpython.com/products/cpython-internals-book/ +RedirectTemp /19-65 http://www.dabeaz.com/GIL/ +RedirectTemp /19-66 http://www.dabeaz.com/python/UnderstandingGIL.pdf +RedirectTemp /19-67 https://bugs.python.org/issue7946#msg223110 +RedirectTemp /19-68 https://bugs.python.org/issue7946 +RedirectTemp /19-69 https://www.fullstackpython.com/ +RedirectTemp /19-70 https://www.oreilly.com/library/view/high-performance-python/9781492055013/ +RedirectTemp /19-71 https://www.packtpub.com/product/parallel-programming-with-python/9781783288397 +RedirectTemp /19-72 https://www.packtpub.com/product/distributed-computing-with-python/9781785889691 +RedirectTemp /19-73 https://towardsdatascience.com/python-performance-and-gpus-1be860ffd58d?gi=a7d537cc2fb4 +RedirectTemp /19-74 https://instagram-engineering.com/web-service-efficiency-at-instagram-with-python-4976d078e366?gi=12a441991c88 RedirectTemp /19-75 https://www.oreilly.com/library/view/architecture-patterns-with/9781492052197/ RedirectTemp /19-76 https://www.cosmicpython.com/ RedirectTemp /19-77 https://pypi.org/project/lelo/ RedirectTemp /19-78 https://github.com/npryce/python-parallelize RedirectTemp /19-79 https://github.com/ericsnowcurrently/multi-core-python/wiki -RedirectTemp /19-81 https://gist.github.com/markshannon/79cace3656b40e21b7021504daee950c +RedirectTemp /19-80 https://gist.github.com/markshannon/79cace3656b40e21b7021504daee950c +RedirectTemp /19-81 https://mail.python.org/archives/list/python-dev@python.org/message/YOOQZCFOKEPQ24YHWWLQSJ3RCXFMS7D7/ RedirectTemp /19-82 https://en.wikipedia.org/wiki/Communicating_sequential_processes RedirectTemp /19-83 https://github.com/stackless-dev/stackless/wiki RedirectTemp /19-84 https://www.eveonline.com @@ -792,14 +863,15 @@ RedirectTemp /19-86 https://stackless.readthedocs.io/en/3.6-slp/stackless-python RedirectTemp /19-87 https://doc.pypy.org/en/latest/stackless.html RedirectTemp /19-88 https://greenlet.readthedocs.io/en/latest/ RedirectTemp /19-89 http://www.gevent.org/ -RedirectTemp /19-91 http://thespianpy.com/doc/ -RedirectTemp /19-92 https://pykka.readthedocs.io/en/latest/ -RedirectTemp /19-93 https://www.manning.com/books/rabbitmq-in-action -RedirectTemp /19-94 https://pragprog.com/titles/pb7con/seven-concurrency-models-in-seven-weeks/ -RedirectTemp /19-95 https://en.wikipedia.org/wiki/OpenCL -RedirectTemp /19-96 https://media.pragprog.com/titles/pb7con/Bonus_Chapter.pdf -RedirectTemp /19-97 https://martinfowler.com/ -RedirectTemp /19-98 https://martinfowler.com/articles/patterns-of-distributed-systems/ +RedirectTemp /19-90 http://thespianpy.com/doc/ +RedirectTemp /19-91 https://pykka.readthedocs.io/en/latest/ +RedirectTemp /19-92 https://www.manning.com/books/rabbitmq-in-action +RedirectTemp /19-93 https://pragprog.com/titles/pb7con/seven-concurrency-models-in-seven-weeks/ +RedirectTemp /19-94 https://en.wikipedia.org/wiki/OpenCL +RedirectTemp /19-95 https://media.pragprog.com/titles/pb7con/Bonus_Chapter.pdf +RedirectTemp /19-96 https://martinfowler.com/ +RedirectTemp /19-97 https://martinfowler.com/articles/patterns-of-distributed-systems/ +RedirectTemp /19-98 https://www.oreilly.com/library/view/designing-data-intensive-applications/9781491903063/ RedirectTemp /19-99 https://www.oreilly.com/library/view/oscon-2016-video/9781491965153/video247021.html RedirectTemp /19-100 https://www.oreilly.com/library/view/designing-for-scalability/9781449361556/ RedirectTemp /19-101 https://www.thoughtworks.com/radar/techniques/high-performance-envy-web-scale-envy @@ -807,176 +879,191 @@ RedirectTemp /19-102 https://en.wikipedia.org/wiki/KISS_principle RedirectTemp /19-103 https://www.usenix.org/conference/hotos15/workshop-program/presentation/mcsherry ############################################################ 20 RedirectTemp /20-1 https://www.artima.com/weblogs/viewpost.jsp?thread=299551 -RedirectTemp /20-3 https://docs.python.org/3/library/http.server.html -RedirectTemp /20-4 https://www.youtube.com/watch?v=A9e9Cy1UkME -RedirectTemp /20-5 https://www.cia.gov/the-world-factbook/ -RedirectTemp /20-7 https://docs.python-requests.org/en/latest/ -RedirectTemp /20-8 https://docs.python.org/3/library/concurrent.futures.html#concurrent.futures.as_completed -RedirectTemp /20-9 https://docs.python.org/3/library/concurrent.futures.html -RedirectTemp /20-10 https://docs.python.org/3.10/library/concurrent.futures.html#concurrent.futures.Executor -RedirectTemp /20-12 https://github.com/noamraph/tqdm -RedirectTemp /20-13 https://www.youtube.com/watch?v=M8Z65tAl5l4 -RedirectTemp /20-14 https://github.com/noamraph/tqdm/blob/master/README.md -RedirectTemp /20-15 https://docs.python.org/3/library/concurrent.futures.html#concurrent.futures.as_completed -RedirectTemp /20-16 https://docs.python.org/3/library/asyncio-task.html#asyncio.as_completed -RedirectTemp /20-17 https://www.cloudflare.com/ -RedirectTemp /20-19 https://github.com/fluentpython/example-code-2e/tree/master/20-executors/getflags -RedirectTemp /20-21 https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/418 -RedirectTemp /20-22 https://en.wikipedia.org/wiki/Embarrassingly_parallel -RedirectTemp /20-23 https://pyvideo.org/video/480/pyconau-2010--the-future-is-soon/ -RedirectTemp /20-25 http://www.dabeaz.com/coroutines/ -RedirectTemp /20-26 https://en.wikipedia.org/wiki/POSIX_Threads -RedirectTemp /20-27 https://en.wikipedia.org/wiki/C_dynamic_memory_allocation -RedirectTemp /20-28 https://pragprog.com/titles/pb7con/seven-concurrency-models-in-seven-weeks/ -RedirectTemp /20-29 https://hexdocs.pm/ecto/getting-started.html +RedirectTemp /20-2 https://docs.python.org/3/library/http.server.html +RedirectTemp /20-3 https://www.youtube.com/watch?v=A9e9Cy1UkME +RedirectTemp /20-4 https://www.cia.gov/the-world-factbook/ +RedirectTemp /20-5 https://docs.python-requests.org/en/latest/ +RedirectTemp /20-6 https://docs.python.org/3.10/library/concurrent.futures.html#concurrent.futures.ThreadPoolExecutor +RedirectTemp /20-7 https://docs.python.org/3/library/concurrent.futures.html#concurrent.futures.as_completed +RedirectTemp /20-8 https://docs.python.org/3/library/concurrent.futures.html +RedirectTemp /20-9 https://docs.python.org/3.10/library/concurrent.futures.html#concurrent.futures.Executor +RedirectTemp /20-10 https://github.com/fluentpython/example-code-2e/blob/master/20-executors/getflags/flags2_common.py +RedirectTemp /20-11 https://github.com/noamraph/tqdm +RedirectTemp /20-12 https://www.youtube.com/watch?v=M8Z65tAl5l4 +RedirectTemp /20-13 https://github.com/noamraph/tqdm/blob/master/README.md +RedirectTemp /20-14 https://docs.python.org/3/library/concurrent.futures.html#concurrent.futures.as_completed +RedirectTemp /20-15 https://docs.python.org/3/library/asyncio-task.html#asyncio.as_completed +RedirectTemp /20-16 https://www.cloudflare.com/ +RedirectTemp /20-17 https://github.com/fluentpython/example-code-2e/tree/master/20-executors/getflags +RedirectTemp /20-18 https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/418 +RedirectTemp /20-19 https://en.wikipedia.org/wiki/Embarrassingly_parallel +RedirectTemp /20-20 https://pyvideo.org/video/480/pyconau-2010--the-future-is-soon/ +RedirectTemp /20-21 http://www.dabeaz.com/coroutines/ +RedirectTemp /20-22 https://en.wikipedia.org/wiki/POSIX_Threads +RedirectTemp /20-23 https://en.wikipedia.org/wiki/C_dynamic_memory_allocation +RedirectTemp /20-24 https://pragprog.com/titles/pb7con/seven-concurrency-models-in-seven-weeks/ +RedirectTemp /20-25 https://hexdocs.pm/ecto/getting-started.html ############################################################ 21 RedirectTemp /21-1 https://docs.python.org/3/library/asyncio.html -RedirectTemp /21-6 https://bugs.python.org/issue43216 -RedirectTemp /21-7 https://bugs.python.org/issue36921 -RedirectTemp /21-8 https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.loop.getaddrinfo -RedirectTemp /21-9 https://docs.python.org/3/library/socket.html#socket.getaddrinfo -RedirectTemp /21-10 https://docs.python.org/3.10/library/asyncio-eventloop.html#asyncio.get_event_loop -RedirectTemp /21-11 https://www.python.org/dev/peps/pep-0492/#await-expression -RedirectTemp /21-14 https://www.fluentpython.com/extra/classic-coroutines/#yield_from_meaning_sec -RedirectTemp /21-17 https://github.com/fluentpython/example-code-2e/tree/master/20-executors/getflags -RedirectTemp /21-18 https://magicstack.github.io/asyncpg/current/ -RedirectTemp /21-19 https://magicstack.github.io/asyncpg/current/api/index.html#transactions -RedirectTemp /21-21 https://magicstack.github.io/asyncpg/current/api/index.html#transactions -RedirectTemp /21-22 https://magicstack.github.io/asyncpg/current/usage.html#connection-pools -RedirectTemp /21-23 https://gist.github.com/jboner/2841832 -RedirectTemp /21-24 https://en.wikipedia.org/wiki/Network-attached_storage -RedirectTemp /21-25 https://en.wikipedia.org/wiki/Semaphore_(programming) -RedirectTemp /21-26 https://en.wikipedia.org/wiki/Semaphore_(programming) -RedirectTemp /21-27 https://groups.google.com/forum/#!msg/python-tulip/PdAEtwpaJHs/7fqb-Qj2zJoJ -RedirectTemp /21-28 https://tritarget.org/#blog/2012/11/28/the-pyramid-of-doom-a-javascript-style-trap -RedirectTemp /21-29 https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.loop.run_in_executor -RedirectTemp /21-30 https://motor.readthedocs.io/en/stable/ -RedirectTemp /21-31 https://emptysqua.re/blog/response-to-asynchronous-python-and-databases/ -RedirectTemp /21-33 https://docs.python.org/3/library/asyncio-stream.html#tcp-echo-server-using-streams -RedirectTemp /21-35 https://en.wikipedia.org/wiki/Phaistos_Disc -RedirectTemp /21-36 https://en.wikipedia.org/wiki/Inverted_index -RedirectTemp /21-37 https://fastapi.tiangolo.com/ -RedirectTemp /21-38 https://swagger.io/specification/ -RedirectTemp /21-40 https://asgi.readthedocs.io/en/latest/implementations.html -RedirectTemp /21-41 https://pydantic-docs.helpmanual.io/ -RedirectTemp /21-42 https://doc.traefik.io/traefik/ -RedirectTemp /21-43 https://fastapi.tiangolo.com/project-generation/ -RedirectTemp /21-44 https://fastapi.tiangolo.com/tutorial/response-model/ -RedirectTemp /21-45 https://docs.python.org/3/library/asyncio-stream.html#asyncio.start_server -RedirectTemp /21-46 https://github.com/python/typeshed/issues/5535 -RedirectTemp /21-47 https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.Server.serve_forever -RedirectTemp /21-48 https://docs.python.org/3/library/asyncio-stream.html#asyncio.StreamWriter.close -RedirectTemp /21-49 https://docs.python.org/3/library/asyncio-stream.html#streamwriter -RedirectTemp /21-50 https://docs.python.org/3/library/asyncio-stream.html -RedirectTemp /21-51 https://docs.python.org/3/library/asyncio-protocol.html -RedirectTemp /21-52 https://docs.python.org/3/library/asyncio-protocol.html#tcp-echo-server -RedirectTemp /21-53 https://github.com/aio-libs/aiopg -RedirectTemp /21-54 https://docs.python.org/3/whatsnew/3.8.html#asyncio -RedirectTemp /21-56 https://datatracker.ietf.org/doc/html/rfc6761 -RedirectTemp /21-57 https://docs.python.org/3/library/contextlib.html#contextlib.asynccontextmanager -RedirectTemp /21-59 https://docs.python.org/3/library/contextlib.html#contextlib.asynccontextmanager -RedirectTemp /21-61 https://docs.python.org/3/library/asyncio-task.html#asyncio.gather -RedirectTemp /21-62 https://curio.readthedocs.io/en/latest/index.html -RedirectTemp /21-63 https://curio.readthedocs.io/en/latest/reference.html#task-groups -RedirectTemp /21-64 https://en.wikipedia.org/wiki/Structured_concurrency -RedirectTemp /21-66 https://www.python.org/dev/peps/pep-0654/#motivation -RedirectTemp /21-67 https://curio.readthedocs.io/en/latest/reference.html#AWAIT -RedirectTemp /21-68 https://www.python-httpx.org/async/#curio -RedirectTemp /21-69 https://github.com/dabeaz/curio/tree/78bca8a6ad677ef51e1568ac7b3e51441ab49c42/examples -RedirectTemp /21-70 https://datatracker.ietf.org/doc/html/rfc8305 -RedirectTemp /21-71 https://trio.readthedocs.io/en/stable/ -RedirectTemp /21-72 https://www.youtube.com/watch?v=M-sc73Y-zQA -RedirectTemp /21-73 https://en.wikipedia.org/wiki/Technical_debt -RedirectTemp /21-74 https://www.youtube.com/watch?v=E-1Y4kSsAFc -RedirectTemp /21-76 https://github.com/dabeaz/curio -RedirectTemp /21-77 https://trio.readthedocs.io/en/stable/ -RedirectTemp /21-78 https://curio.readthedocs.io/en/latest/#curio-university -RedirectTemp /21-79 https://docs.python.org/3/library/asyncio.html -RedirectTemp /21-80 https://bugs.python.org/issue33649 -RedirectTemp /21-82 https://docs.python.org/3/library/asyncio-dev.html -RedirectTemp /21-83 https://www.youtube.com/watch?v=iG6fr81xHKA -RedirectTemp /21-84 https://www.youtube.com/watch?v=F19R_M4Nay4 -RedirectTemp /21-85 https://asherman.io/projects/unsync.html -RedirectTemp /21-86 https://pyladies.com/ -RedirectTemp /21-87 https://www.youtube.com/watch?v=sW76-pRkZk8 -RedirectTemp /21-88 https://www.youtube.com/watch?v=Xbl7XjFYsN4 -RedirectTemp /21-89 https://www.youtube.com/watch?v=02CLD-42VdI -RedirectTemp /21-90 https://micropython.org/ -RedirectTemp /21-91 https://docs.micropython.org/en/latest/library/uasyncio.html -RedirectTemp /21-92 https://www.encode.io/articles/python-async-frameworks-beyond-developer-tribalism -RedirectTemp /21-93 https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/ -RedirectTemp /21-94 https://github.com/MagicStack/uvloop -RedirectTemp /21-95 http://magic.io/blog/uvloop-blazing-fast-python-networking/ -RedirectTemp /21-96 https://github.com/MagicStack/httptools -RedirectTemp /21-97 https://docs.aiohttp.org/en/stable/ -RedirectTemp /21-98 https://github.com/wg/wrk -RedirectTemp /21-99 https://twistedmatrix.com/trac/ +RedirectTemp /21-2 https://bugs.python.org/issue43216 +RedirectTemp /21-3 https://bugs.python.org/issue36921 +RedirectTemp /21-4 https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.loop.getaddrinfo +RedirectTemp /21-5 https://docs.python.org/3/library/socket.html#socket.getaddrinfo +RedirectTemp /21-6 https://docs.python.org/3.10/library/asyncio-eventloop.html#asyncio.get_event_loop +RedirectTemp /21-7 https://www.python.org/dev/peps/pep-0492/#await-expression +RedirectTemp /21-8 https://www.fluentpython.com/extra/classic-coroutines/#yield_from_meaning_sec +RedirectTemp /21-9 https://github.com/fluentpython/example-code-2e/tree/master/20-executors/getflags +RedirectTemp /21-10 https://magicstack.github.io/asyncpg/current/ +RedirectTemp /21-11 https://magicstack.github.io/asyncpg/current/api/index.html#transactions +RedirectTemp /21-12 https://magicstack.github.io/asyncpg/current/api/index.html#transactions +RedirectTemp /21-13 https://github.com/MagicStack/asyncpg/blob/4d39a05268ce4cc01b00458223a767542da048b8/asyncpg/transaction.py#L57 +RedirectTemp /21-14 https://magicstack.github.io/asyncpg/current/usage.html#connection-pools +RedirectTemp /21-15 https://gist.github.com/jboner/2841832 +RedirectTemp /21-16 https://en.wikipedia.org/wiki/Network-attached_storage +RedirectTemp /21-17 https://en.wikipedia.org/wiki/Semaphore_(programming) +RedirectTemp /21-18 https://en.wikipedia.org/wiki/Semaphore_(programming) +RedirectTemp /21-19 https://groups.google.com/forum/#!msg/python-tulip/PdAEtwpaJHs/7fqb-Qj2zJoJ +RedirectTemp /21-20 https://web.archive.org/web/20151209151711/http://tritarget.org/blog/2012/11/28/the-pyramid-of-doom-a-javascript-style-trap +RedirectTemp /21-21 https://stackoverflow.com/questions/53701841/what-is-the-use-case-for-future-add-done-callback/53710563 +RedirectTemp /21-22 https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.loop.run_in_executor +RedirectTemp /21-23 https://motor.readthedocs.io/en/stable/ +RedirectTemp /21-24 https://emptysqua.re/blog/response-to-asynchronous-python-and-databases/ +RedirectTemp /21-25 https://docs.python.org/3/library/asyncio-stream.html#tcp-echo-server-using-streams +RedirectTemp /21-26 https://en.wikipedia.org/wiki/Phaistos_Disc +RedirectTemp /21-27 https://en.wikipedia.org/wiki/Inverted_index +RedirectTemp /21-28 https://fastapi.tiangolo.com/ +RedirectTemp /21-29 https://swagger.io/specification/ +RedirectTemp /21-30 https://asgi.readthedocs.io/en/latest/implementations.html +RedirectTemp /21-31 https://pydantic-docs.helpmanual.io/ +RedirectTemp /21-32 https://doc.traefik.io/traefik/ +RedirectTemp /21-33 https://fastapi.tiangolo.com/project-generation/ +RedirectTemp /21-34 https://fastapi.tiangolo.com/tutorial/response-model/ +RedirectTemp /21-35 https://docs.python.org/3/library/asyncio-stream.html#asyncio.start_server +RedirectTemp /21-36 https://github.com/python/typeshed/issues/5535 +RedirectTemp /21-37 https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.Server.serve_forever +RedirectTemp /21-38 https://docs.python.org/3/library/asyncio-stream.html#asyncio.StreamWriter.close +RedirectTemp /21-39 https://docs.python.org/3/library/asyncio-stream.html#streamwriter +RedirectTemp /21-40 https://docs.python.org/3/library/asyncio-stream.html +RedirectTemp /21-41 https://docs.python.org/3/library/asyncio-protocol.html +RedirectTemp /21-42 https://docs.python.org/3/library/asyncio-protocol.html#tcp-echo-server +RedirectTemp /21-43 https://github.com/aio-libs/aiopg +RedirectTemp /21-44 https://docs.python.org/3/whatsnew/3.8.html#asyncio +RedirectTemp /21-45 https://datatracker.ietf.org/doc/html/rfc6761 +RedirectTemp /21-46 https://docs.python.org/3/library/contextlib.html#contextlib.asynccontextmanager +RedirectTemp /21-47 https://docs.python.org/3/library/contextlib.html#contextlib.asynccontextmanager +RedirectTemp /21-48 https://docs.python.org/3/library/asyncio-task.html#asyncio.gather +RedirectTemp /21-49 https://curio.readthedocs.io/en/latest/index.html +RedirectTemp /21-50 https://curio.readthedocs.io/en/latest/reference.html#task-groups +RedirectTemp /21-51 https://en.wikipedia.org/wiki/Structured_concurrency +RedirectTemp /21-52 https://mail.python.org/archives/list/python-dev@python.org/thread/2ORDAW74LGE3ZI2QETPJRT2ZL7MCCPG2/ +RedirectTemp /21-53 https://www.python.org/dev/peps/pep-0654/#motivation +RedirectTemp /21-54 https://curio.readthedocs.io/en/latest/reference.html#AWAIT +RedirectTemp /21-55 https://www.python-httpx.org/async/#curio +RedirectTemp /21-56 https://github.com/dabeaz/curio/tree/78bca8a6ad677ef51e1568ac7b3e51441ab49c42/examples +RedirectTemp /21-57 https://datatracker.ietf.org/doc/html/rfc8305 +RedirectTemp /21-58 https://trio.readthedocs.io/en/stable/ +RedirectTemp /21-59 https://www.youtube.com/watch?v=M-sc73Y-zQA +RedirectTemp /21-60 https://en.wikipedia.org/wiki/Technical_debt +RedirectTemp /21-61 https://www.youtube.com/watch?v=E-1Y4kSsAFc +RedirectTemp /21-62 https://github.com/dabeaz/curio +RedirectTemp /21-63 https://trio.readthedocs.io/en/stable/ +RedirectTemp /21-64 https://curio.readthedocs.io/en/latest/#curio-university +RedirectTemp /21-65 https://vorpus.org/blog/some-thoughts-on-asynchronous-api-design-in-a-post-asyncawait-world/ +RedirectTemp /21-66 https://vorpus.org/blog/notes-on-structured-concurrency-or-go-statement-considered-harmful/ +RedirectTemp /21-67 https://stackoverflow.com/questions/49482969/what-is-the-core-difference-between-asyncio-and-trio +RedirectTemp /21-68 https://docs.python.org/3/library/asyncio.html +RedirectTemp /21-69 https://bugs.python.org/issue33649 +RedirectTemp /21-70 https://docs.python.org/3/library/asyncio-dev.html +RedirectTemp /21-71 https://www.youtube.com/watch?v=iG6fr81xHKA +RedirectTemp /21-72 https://www.youtube.com/watch?v=F19R_M4Nay4 +RedirectTemp /21-73 https://asherman.io/projects/unsync.html +RedirectTemp /21-74 https://pyladies.com/ +RedirectTemp /21-75 https://www.youtube.com/watch?v=sW76-pRkZk8 +RedirectTemp /21-76 https://www.youtube.com/watch?v=Xbl7XjFYsN4 +RedirectTemp /21-77 https://www.youtube.com/watch?v=02CLD-42VdI +RedirectTemp /21-78 https://micropython.org/ +RedirectTemp /21-79 https://docs.micropython.org/en/latest/library/uasyncio.html +RedirectTemp /21-80 https://www.encode.io/articles/python-async-frameworks-beyond-developer-tribalism +RedirectTemp /21-81 https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/ +RedirectTemp /21-82 https://vorpus.org/blog/notes-on-structured-concurrency-or-go-statement-considered-harmful/ +RedirectTemp /21-83 https://github.com/MagicStack/uvloop +RedirectTemp /21-84 http://magic.io/blog/uvloop-blazing-fast-python-networking/ +RedirectTemp /21-85 https://github.com/MagicStack/httptools +RedirectTemp /21-86 https://docs.aiohttp.org/en/stable/ +RedirectTemp /21-87 https://github.com/wg/wrk +RedirectTemp /21-88 https://twistedmatrix.com/trac/ ############################################################ 22 -RedirectTemp /22-4 https://pypi.org/project/attrdict/ -RedirectTemp /22-5 https://pypi.org/project/addict/ -RedirectTemp /22-7 https://github.com/ActiveState/code/tree/master/recipes/Python/52308_simple_but_handy_collector_bunch_named_stuff -RedirectTemp /22-8 https://docs.python.org/3/library/types.html#types.SimpleNamespace -RedirectTemp /22-9 https://docs.python.org/3/library/argparse.html#argparse.Namespace -RedirectTemp /22-12 https://docs.python.org/3/library/functools.html#functools.cached_property -RedirectTemp /22-13 https://docs.python.org/3/library/functools.html#functools.cached_property -RedirectTemp /22-14 https://bugs.python.org/issue42781 -RedirectTemp /22-15 https://docs.python.org/3/howto/descriptor.html -RedirectTemp /22-16 https://docs.python.org/3/library/threading.html#rlock-objects -RedirectTemp /22-17 https://docs.python.org/3.10/library/functools.html#functools.cached_property -RedirectTemp /22-18 https://www.wsj.com/articles/SB10001424052970203914304576627102996831200 -RedirectTemp /22-19 https://www.youtube.com/watch?v=s35rVw1zskA&feature=youtu.be -RedirectTemp /22-20 https://docs.python.org/3/library/functions.html#dir -RedirectTemp /22-21 https://docs.python.org/3/library/functions.html#hasattr -RedirectTemp /22-22 https://docs.python.org/3.10/reference/datamodel.html#special-method-lookup -RedirectTemp /22-23 https://docs.python.org/3/library/functions.html -RedirectTemp /22-24 https://docs.python.org/3/reference/datamodel.html#customizing-attribute-access -RedirectTemp /22-25 https://docs.python.org/3/reference/datamodel.html#special-method-lookup -RedirectTemp /22-26 https://docs.python.org/3/library/stdtypes.html#special-attributes -RedirectTemp /22-27 http://wiki.c2.com/?WelcomeVisitors -RedirectTemp /22-28 http://wiki.c2.com/?UniformAccessPrinciple -RedirectTemp /22-29 https://docs.oracle.com/javase/tutorial/java/javaOO/accesscontrol.html -RedirectTemp /22-30 http://www.pingo.io/docs/ -RedirectTemp /22-31 https://www.drdobbs.com/javas-new-considered-harmful/184405016 -RedirectTemp /22-32 https://www.python.org/dev/peps/pep-0008/#class-names +RedirectTemp /22-1 https://github.com/fluentpython/example-code-2e/blob/master/22-dyn-attr-prop/oscon/data/osconfeed.json +RedirectTemp /22-2 https://pypi.org/project/attrdict/ +RedirectTemp /22-3 https://pypi.org/project/addict/ +RedirectTemp /22-4 https://github.com/ActiveState/code/tree/master/recipes/Python/52308_simple_but_handy_collector_bunch_named_stuff +RedirectTemp /22-5 https://docs.python.org/3/library/types.html#types.SimpleNamespace +RedirectTemp /22-6 https://docs.python.org/3/library/argparse.html#argparse.Namespace +RedirectTemp /22-7 https://docs.python.org/3/library/multiprocessing.html#multiprocessing.managers.Namespace +RedirectTemp /22-8 https://github.com/fluentpython/example-code-2e/blob/master/22-dyn-attr-prop/oscon/schedule_v2.py +RedirectTemp /22-9 https://docs.python.org/3/library/functools.html#functools.cached_property +RedirectTemp /22-10 https://docs.python.org/3/library/functools.html#functools.cached_property +RedirectTemp /22-11 https://bugs.python.org/issue42781 +RedirectTemp /22-12 https://docs.python.org/3/howto/descriptor.html +RedirectTemp /22-13 https://github.com/python/cpython/blob/e6d0107e13ed957109e79b796984d3d026a8660d/Lib/functools.py#L926 +RedirectTemp /22-14 https://docs.python.org/3/library/threading.html#rlock-objects +RedirectTemp /22-15 https://docs.python.org/3.10/library/functools.html#functools.cached_property +RedirectTemp /22-16 https://www.wsj.com/articles/SB10001424052970203914304576627102996831200 +RedirectTemp /22-17 https://www.youtube.com/watch?v=s35rVw1zskA&feature=youtu.be +RedirectTemp /22-18 https://docs.python.org/3/library/functions.html#dir +RedirectTemp /22-19 https://github.com/python/cpython/blob/19903085c3ad7a17c8047e1556c700f2eb109931/Lib/cmd.py#L214 +RedirectTemp /22-20 https://docs.python.org/3/library/functions.html#hasattr +RedirectTemp /22-21 https://docs.python.org/3.10/reference/datamodel.html#special-method-lookup +RedirectTemp /22-22 https://docs.python.org/3/library/functions.html +RedirectTemp /22-23 https://docs.python.org/3/reference/datamodel.html#customizing-attribute-access +RedirectTemp /22-24 https://docs.python.org/3/reference/datamodel.html#special-method-lookup +RedirectTemp /22-25 https://docs.python.org/3/library/stdtypes.html#special-attributes +RedirectTemp /22-26 http://wiki.c2.com/?WelcomeVisitors +RedirectTemp /22-27 http://wiki.c2.com/?UniformAccessPrinciple +RedirectTemp /22-28 https://docs.oracle.com/javase/tutorial/java/javaOO/accesscontrol.html +RedirectTemp /22-29 http://www.pingo.io/docs/ +RedirectTemp /22-30 https://www.drdobbs.com/javas-new-considered-harmful/184405016 +RedirectTemp /22-31 https://www.python.org/dev/peps/pep-0008/#class-names ############################################################ 23 -RedirectTemp /23-2 http://www.aleax.it/goo_pydp.pdf -RedirectTemp /23-3 https://docs.python.org/3.10/reference/datamodel.html#implementing-descriptors -RedirectTemp /23-6 https://docs.python.org/3/howto/descriptor.html -RedirectTemp /23-7 https://docs.python.org/3/howto/ -RedirectTemp /23-8 http://www.aleax.it/Python/nylug05_om.pdf -RedirectTemp /23-9 https://www.youtube.com/watch?v=VOzvpHoYQoo -RedirectTemp /23-11 https://www.python.org/dev/peps/pep-0487/#trait-descriptors -RedirectTemp /23-12 https://dreamsongs.com/RiseOfWorseIsBetter.html -RedirectTemp /23-13 http://web.archive.org/web/20031002184114/www.amk.ca/python/writing/warts.html -RedirectTemp /23-14 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this -RedirectTemp /23-15 http://python-history.blogspot.com/2009/02/adding-support-for-user-defined-classes.html +RedirectTemp /23-1 http://www.aleax.it/goo_pydp.pdf +RedirectTemp /23-2 https://docs.python.org/3.10/reference/datamodel.html#implementing-descriptors +RedirectTemp /23-3 https://docs.python.org/3/howto/descriptor.html +RedirectTemp /23-4 https://docs.python.org/3/howto/ +RedirectTemp /23-5 http://www.aleax.it/Python/nylug05_om.pdf +RedirectTemp /23-6 https://www.youtube.com/watch?v=VOzvpHoYQoo +RedirectTemp /23-7 https://www.python.org/dev/peps/pep-0487/#trait-descriptors +RedirectTemp /23-8 https://dreamsongs.com/RiseOfWorseIsBetter.html +RedirectTemp /23-9 http://web.archive.org/web/20031002184114/www.amk.ca/python/writing/warts.html +RedirectTemp /23-10 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this +RedirectTemp /23-11 http://python-history.blogspot.com/2009/02/adding-support-for-user-defined-classes.html ############################################################ 24 -RedirectTemp /24-2 https://docs.python.org/3/library/stdtypes.html#special-attributes -RedirectTemp /24-3 https://docs.djangoproject.com/en/3.2/topics/db/models/#meta-options -RedirectTemp /24-4 https://www.python.org/dev/peps/pep-3155/ -RedirectTemp /24-7 https://en.wikipedia.org/wiki/Tony_Hoare#Apologies_and_retractions -RedirectTemp /24-8 https://go.dev/tour/basics/12 -RedirectTemp /24-9 https://bugs.python.org/issue42102 -RedirectTemp /24-11 https://www.python.org/dev/peps/pep-0557/#abstract -RedirectTemp /24-12 https://github.com/python/cpython/blob/3.9/Lib/dataclasses.py -RedirectTemp /24-13 https://docs.python.org/3/reference/datamodel.html#creating-the-class-object -RedirectTemp /24-15 https://mail.python.org/pipermail/python-list/2002-December/134521.html -RedirectTemp /24-16 https://www.oreilly.com/library/view/python-in-a/9781491913833/ -RedirectTemp /24-17 https://mail.python.org/pipermail/python-list/2002-July/162558.html -RedirectTemp /24-18 https://github.com/fluentpython/example-code/tree/master/21-class-metaprog/bulkfood -RedirectTemp /24-19 https://en.wikipedia.org/wiki/Principle_of_least_astonishment -RedirectTemp /24-21 https://en.wikipedia.org/wiki/Trait_(computer_programming) -RedirectTemp /24-22 https://en.wikipedia.org/wiki/Aspect-oriented_programming -RedirectTemp /24-23 https://dhh.dk/arc/000416.html -RedirectTemp /24-24 https://github.com/cjrh/autoslot -RedirectTemp /24-25 https://docs.python.org/3/reference/datamodel.html#customizing-class-creation -RedirectTemp /24-26 https://docs.python.org/3/library/functions.html#type -RedirectTemp /24-27 https://docs.python.org/3/library/stdtypes.html#special-attributes -RedirectTemp /24-28 https://docs.python.org/3/library/types.html -RedirectTemp /24-29 https://www.python.org/dev/peps/pep-3129/ -RedirectTemp /24-30 https://www.youtube.com/watch?v=cAGliEJV9_o -RedirectTemp /24-31 https://docs.python.org/3/library/functools.html#functools.total_ordering -RedirectTemp /24-33 https://www.oreilly.com/library/view/python-in-a/9781491913833/ -RedirectTemp /24-36 https://www.python.org/download/releases/2.2.3/descrintro/ -RedirectTemp /24-42 https://github.com/lihaoyi/macropy -RedirectTemp /24-43 https://people.eecs.berkeley.edu/~bh/ss-toc2.html +RedirectTemp /24-1 https://docs.python.org/3/library/stdtypes.html#special-attributes +RedirectTemp /24-2 https://docs.djangoproject.com/en/3.2/topics/db/models/#meta-options +RedirectTemp /24-3 https://www.python.org/dev/peps/pep-3155/ +RedirectTemp /24-4 https://github.com/python/cpython/blob/3.9/Lib/collections/__init__.py +RedirectTemp /24-5 https://en.wikipedia.org/wiki/Tony_Hoare#Apologies_and_retractions +RedirectTemp /24-6 https://go.dev/tour/basics/12 +RedirectTemp /24-7 https://bugs.python.org/issue42102 +RedirectTemp /24-8 https://docs.python.org/3/reference/datamodel.html#object.__init_subclass__ +RedirectTemp /24-9 https://www.python.org/dev/peps/pep-0557/#abstract +RedirectTemp /24-10 https://github.com/python/cpython/blob/3.9/Lib/dataclasses.py +RedirectTemp /24-11 https://docs.python.org/3/reference/datamodel.html#creating-the-class-object +RedirectTemp /24-12 https://mail.python.org/pipermail/python-list/2002-December/134521.html +RedirectTemp /24-13 https://mail.python.org/pipermail/python-list/2002-July/162558.html +RedirectTemp /24-14 https://github.com/fluentpython/example-code/tree/master/21-class-metaprog/bulkfood +RedirectTemp /24-15 https://en.wikipedia.org/wiki/Principle_of_least_astonishment +RedirectTemp /24-16 https://docs.python.org/3/reference/datamodel.html#object.__class_getitem__ +RedirectTemp /24-17 https://en.wikipedia.org/wiki/Trait_(computer_programming) +RedirectTemp /24-18 https://en.wikipedia.org/wiki/Aspect-oriented_programming +RedirectTemp /24-19 https://dhh.dk/arc/000416.html +RedirectTemp /24-20 https://github.com/cjrh/autoslot +RedirectTemp /24-21 https://docs.python.org/3/reference/datamodel.html#customizing-class-creation +RedirectTemp /24-22 https://docs.python.org/3/library/functions.html#type +RedirectTemp /24-23 https://docs.python.org/3/library/stdtypes.html#special-attributes +RedirectTemp /24-24 https://docs.python.org/3/library/types.html +RedirectTemp /24-25 https://www.python.org/dev/peps/pep-3129/ +RedirectTemp /24-26 https://www.youtube.com/watch?v=cAGliEJV9_o +RedirectTemp /24-27 https://docs.python.org/3/library/functools.html#functools.total_ordering +RedirectTemp /24-28 https://www.python.org/download/releases/2.2.3/descrintro/ +RedirectTemp /24-29 https://github.com/lihaoyi/macropy +RedirectTemp /24-30 https://people.eecs.berkeley.edu/~bh/ss-toc2.html diff --git a/links/custom.htaccess b/links/custom.htaccess index 52649db..b9bc509 100644 --- a/links/custom.htaccess +++ b/links/custom.htaccess @@ -25,6 +25,17 @@ RedirectTemp /norvigdp http://norvig.com/design-patterns/ RedirectTemp /nsphere https://en.wikipedia.org/wiki/N-sphere RedirectTemp /oldcoro https://www.fluentpython.com/extra/classic-coroutines/ RedirectTemp /pandas https://pandas.pydata.org/ +RedirectTemp /pycook3 https://www.oreilly.com/library/view/python-cookbook-3rd/9781449357337/ +RedirectTemp /pynut3 https://www.oreilly.com/library/view/python-in-a/9781491913833/ +RedirectTemp /pypydif https://doc.pypy.org/en/latest/cpython_differences.html#subclasses-of-built-in-types +RedirectTemp /shed4051 https://github.com/python/typeshed/issues/4051 +RedirectTemp /slatkin https://effectivepython.com/ +RedirectTemp /specattr https://docs.python.org/3/library/stdtypes.html#special-attributes +RedirectTemp /typecoro https://docs.python.org/3.10/library/typing.html#typing.Coroutine +RedirectTemp /typing https://docs.python.org/3/library/typing.html +RedirectTemp /weakref https://www.fluentpython.com/extra/weak-references/ + +# Python Enhancement Proposals RedirectTemp /pep218 https://www.python.org/dev/peps/pep-0218/ RedirectTemp /pep227 https://www.python.org/dev/peps/pep-0227/ RedirectTemp /pep255 https://www.python.org/dev/peps/pep-0255/ @@ -100,10 +111,3 @@ RedirectTemp /pep3141 https://www.python.org/dev/peps/pep-3141/ RedirectTemp /pep3148 https://www.python.org/dev/peps/pep-3148/ RedirectTemp /pep3155 https://www.python.org/dev/peps/pep-3155/ RedirectTemp /pep3333 https://www.python.org/dev/peps/pep-3333/ -RedirectTemp /pypydif https://doc.pypy.org/en/latest/cpython_differences.html#subclasses-of-built-in-types -RedirectTemp /shed4051 https://github.com/python/typeshed/issues/4051 -RedirectTemp /slatkin https://effectivepython.com/ -RedirectTemp /specattr https://docs.python.org/3/library/stdtypes.html#special-attributes -RedirectTemp /typecoro https://docs.python.org/3.10/library/typing.html#typing.Coroutine -RedirectTemp /typing https://docs.python.org/3/library/typing.html -RedirectTemp /weakref https://www.fluentpython.com/extra/weak-references/ From 08318a657a683c7d80b06a94aeee3f8f36ffbb25 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Mon, 17 Jan 2022 15:22:20 -0300 Subject: [PATCH 136/166] harmless edits in .htaccess --- links/FPY.LI.htaccess | 12 ++++++------ links/custom.htaccess | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/links/FPY.LI.htaccess b/links/FPY.LI.htaccess index 9f960f1..6e324d0 100644 --- a/links/FPY.LI.htaccess +++ b/links/FPY.LI.htaccess @@ -1,19 +1,20 @@ ErrorDocument 404 /404.html # main resources +RedirectTemp /book https://www.oreilly.com/library/view/fluent-python-2nd/9781492056348/ RedirectTemp /code https://github.com/fluentpython/example-code-2e RedirectTemp /home https://www.fluentpython.com/ # URLs mentioned at least three times RedirectTemp /bisect https://www.fluentpython.com/extra/ordered-sequences-with-bisect/ -RedirectTemp /cardxvi https://www.python.org/dev/peps/pep-0484/#the-numeric-tower +RedirectTemp /cardxvi https://www.python.org/dev/peps/pep-0484/#the-numeric-tower RedirectTemp /collec https://docs.python.org/3/library/collections.html RedirectTemp /dask https://dask.org/ RedirectTemp /dtmodel https://docs.python.org/3/reference/datamodel.html RedirectTemp /descr101 https://www.python.org/download/releases/2.2.3/descrintro/ RedirectTemp /descrhow https://docs.python.org/3/howto/descriptor.html RedirectTemp /doctest https://docs.python.org/3/library/doctest.html -RedirectTemp /effectpy https://effectivepython.com/ +RedirectTemp /effectpy https://effectivepython.com/ RedirectTemp /fmtspec https://docs.python.org/3/library/string.html#formatspec RedirectTemp /gunicorn https://gunicorn.org/ RedirectTemp /hashint https://www.fluentpython.com/extra/internals-of-sets-and-dicts/ @@ -25,13 +26,12 @@ RedirectTemp /norvigdp http://norvig.com/design-patterns/ RedirectTemp /nsphere https://en.wikipedia.org/wiki/N-sphere RedirectTemp /oldcoro https://www.fluentpython.com/extra/classic-coroutines/ RedirectTemp /pandas https://pandas.pydata.org/ -RedirectTemp /pycook3 https://www.oreilly.com/library/view/python-cookbook-3rd/9781449357337/ -RedirectTemp /pynut3 https://www.oreilly.com/library/view/python-in-a/9781491913833/ +RedirectTemp /pycook3 https://www.oreilly.com/library/view/python-cookbook-3rd/9781449357337/ +RedirectTemp /pynut3 https://www.oreilly.com/library/view/python-in-a/9781491913833/ RedirectTemp /pypydif https://doc.pypy.org/en/latest/cpython_differences.html#subclasses-of-built-in-types RedirectTemp /shed4051 https://github.com/python/typeshed/issues/4051 -RedirectTemp /slatkin https://effectivepython.com/ RedirectTemp /specattr https://docs.python.org/3/library/stdtypes.html#special-attributes -RedirectTemp /typecoro https://docs.python.org/3.10/library/typing.html#typing.Coroutine +RedirectTemp /typecoro https://docs.python.org/3.10/library/typing.html#typing.Coroutine RedirectTemp /typing https://docs.python.org/3/library/typing.html RedirectTemp /weakref https://www.fluentpython.com/extra/weak-references/ diff --git a/links/custom.htaccess b/links/custom.htaccess index b9bc509..a13b9f8 100644 --- a/links/custom.htaccess +++ b/links/custom.htaccess @@ -1,19 +1,20 @@ ErrorDocument 404 /404.html # main resources +RedirectTemp /book https://www.oreilly.com/library/view/fluent-python-2nd/9781492056348/ RedirectTemp /code https://github.com/fluentpython/example-code-2e RedirectTemp /home https://www.fluentpython.com/ # URLs mentioned at least three times RedirectTemp /bisect https://www.fluentpython.com/extra/ordered-sequences-with-bisect/ -RedirectTemp /cardxvi https://www.python.org/dev/peps/pep-0484/#the-numeric-tower +RedirectTemp /cardxvi https://www.python.org/dev/peps/pep-0484/#the-numeric-tower RedirectTemp /collec https://docs.python.org/3/library/collections.html RedirectTemp /dask https://dask.org/ RedirectTemp /dtmodel https://docs.python.org/3/reference/datamodel.html RedirectTemp /descr101 https://www.python.org/download/releases/2.2.3/descrintro/ RedirectTemp /descrhow https://docs.python.org/3/howto/descriptor.html RedirectTemp /doctest https://docs.python.org/3/library/doctest.html -RedirectTemp /effectpy https://effectivepython.com/ +RedirectTemp /effectpy https://effectivepython.com/ RedirectTemp /fmtspec https://docs.python.org/3/library/string.html#formatspec RedirectTemp /gunicorn https://gunicorn.org/ RedirectTemp /hashint https://www.fluentpython.com/extra/internals-of-sets-and-dicts/ @@ -25,13 +26,12 @@ RedirectTemp /norvigdp http://norvig.com/design-patterns/ RedirectTemp /nsphere https://en.wikipedia.org/wiki/N-sphere RedirectTemp /oldcoro https://www.fluentpython.com/extra/classic-coroutines/ RedirectTemp /pandas https://pandas.pydata.org/ -RedirectTemp /pycook3 https://www.oreilly.com/library/view/python-cookbook-3rd/9781449357337/ -RedirectTemp /pynut3 https://www.oreilly.com/library/view/python-in-a/9781491913833/ +RedirectTemp /pycook3 https://www.oreilly.com/library/view/python-cookbook-3rd/9781449357337/ +RedirectTemp /pynut3 https://www.oreilly.com/library/view/python-in-a/9781491913833/ RedirectTemp /pypydif https://doc.pypy.org/en/latest/cpython_differences.html#subclasses-of-built-in-types RedirectTemp /shed4051 https://github.com/python/typeshed/issues/4051 -RedirectTemp /slatkin https://effectivepython.com/ RedirectTemp /specattr https://docs.python.org/3/library/stdtypes.html#special-attributes -RedirectTemp /typecoro https://docs.python.org/3.10/library/typing.html#typing.Coroutine +RedirectTemp /typecoro https://docs.python.org/3.10/library/typing.html#typing.Coroutine RedirectTemp /typing https://docs.python.org/3/library/typing.html RedirectTemp /weakref https://www.fluentpython.com/extra/weak-references/ From b39c1c1f049ec5b0c6e1686cda20b39f45072824 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Wed, 16 Feb 2022 14:40:49 -0300 Subject: [PATCH 137/166] added bdfl short URL --- links/FPY.LI.htaccess | 3 +++ links/README.md | 5 ++++- links/custom.htaccess | 3 +++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/links/FPY.LI.htaccess b/links/FPY.LI.htaccess index 6e324d0..d38c973 100644 --- a/links/FPY.LI.htaccess +++ b/links/FPY.LI.htaccess @@ -35,6 +35,9 @@ RedirectTemp /typecoro https://docs.python.org/3.10/library/typing.html#typing.C RedirectTemp /typing https://docs.python.org/3/library/typing.html RedirectTemp /weakref https://www.fluentpython.com/extra/weak-references/ +# URLs added during QA of the Second Edition +RedirectTemp /bdfl https://www.artima.com/weblogs/viewpost.jsp?thread=235725 + # Python Enhancement Proposals RedirectTemp /pep218 https://www.python.org/dev/peps/pep-0218/ RedirectTemp /pep227 https://www.python.org/dev/peps/pep-0227/ diff --git a/links/README.md b/links/README.md index 09c808a..6da595c 100644 --- a/links/README.md +++ b/links/README.md @@ -12,7 +12,10 @@ I replaced almost all URLs in the book with shortened versions that go through t The site has an `.htaccess` file with *temporary* redirects. When I find out a link is stale, I can thange the redirect in `.htaccess` to a new target, -so the link in the book is back in service through the updated redirect. +which may be a link to copy in the Internet Archive's +[Wayback Machine](https://archive.org/web/) +o the link in the book is back in service through the updated redirect. + ## Help wanted diff --git a/links/custom.htaccess b/links/custom.htaccess index a13b9f8..9e070f1 100644 --- a/links/custom.htaccess +++ b/links/custom.htaccess @@ -35,6 +35,9 @@ RedirectTemp /typecoro https://docs.python.org/3.10/library/typing.html#typing.C RedirectTemp /typing https://docs.python.org/3/library/typing.html RedirectTemp /weakref https://www.fluentpython.com/extra/weak-references/ +# URLs added during QA of the Second Edition +RedirectTemp /bdfl https://www.artima.com/weblogs/viewpost.jsp?thread=235725 + # Python Enhancement Proposals RedirectTemp /pep218 https://www.python.org/dev/peps/pep-0218/ RedirectTemp /pep227 https://www.python.org/dev/peps/pep-0227/ From 5f2c3abfc76c906003cb35dbb4e50bd0cb6e0206 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Wed, 16 Feb 2022 14:46:28 -0300 Subject: [PATCH 138/166] added bdfl short URL --- links/FPY.LI.htaccess | 2 +- links/custom.htaccess | 2 +- links/deploy.sh | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) create mode 100755 links/deploy.sh diff --git a/links/FPY.LI.htaccess b/links/FPY.LI.htaccess index d38c973..44d2530 100644 --- a/links/FPY.LI.htaccess +++ b/links/FPY.LI.htaccess @@ -35,7 +35,7 @@ RedirectTemp /typecoro https://docs.python.org/3.10/library/typing.html#typing.C RedirectTemp /typing https://docs.python.org/3/library/typing.html RedirectTemp /weakref https://www.fluentpython.com/extra/weak-references/ -# URLs added during QA of the Second Edition +# URL added during QA of the Second Edition RedirectTemp /bdfl https://www.artima.com/weblogs/viewpost.jsp?thread=235725 # Python Enhancement Proposals diff --git a/links/custom.htaccess b/links/custom.htaccess index 9e070f1..ad10802 100644 --- a/links/custom.htaccess +++ b/links/custom.htaccess @@ -35,7 +35,7 @@ RedirectTemp /typecoro https://docs.python.org/3.10/library/typing.html#typing.C RedirectTemp /typing https://docs.python.org/3/library/typing.html RedirectTemp /weakref https://www.fluentpython.com/extra/weak-references/ -# URLs added during QA of the Second Edition +# URL added during QA of the Second Edition RedirectTemp /bdfl https://www.artima.com/weblogs/viewpost.jsp?thread=235725 # Python Enhancement Proposals diff --git a/links/deploy.sh b/links/deploy.sh new file mode 100755 index 0000000..ced9bd9 --- /dev/null +++ b/links/deploy.sh @@ -0,0 +1,2 @@ +#!/bin/bash +scp FPY.LI.htaccess dh_i4p2ka@fpy.li:~/fpy.li/.htaccess From 536e68686fed2c51a29b6dc0e2acbff7aeb3ee3f Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Mon, 21 Feb 2022 18:21:28 -0300 Subject: [PATCH 139/166] updated link to String Format String Syntax in Python 3 --- links/FPY.LI.htaccess | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/links/FPY.LI.htaccess b/links/FPY.LI.htaccess index 44d2530..cc779db 100644 --- a/links/FPY.LI.htaccess +++ b/links/FPY.LI.htaccess @@ -152,7 +152,7 @@ RedirectTemp /a-18 https://www.python.org/doc/essays/ RedirectTemp /1-1 http://hugunin.net/story_of_jython.html RedirectTemp /1-2 https://www.oreilly.com/library/view/jython-essentials/9781449397364/ RedirectTemp /1-3 https://docs.python.org/3/reference/lexical_analysis.html#reserved-classes-of-identifiers -RedirectTemp /1-4 https://docs.python.org/2/library/string.html#format-string-syntax +RedirectTemp /1-4 https://docs.python.org/3.10/library/string.html#format-string-syntax RedirectTemp /1-5 https://stackoverflow.com/questions/1436703/what-is-the-difference-between-str-and-repr RedirectTemp /1-6 https://docs.python.org/3/library/stdtypes.html#truth RedirectTemp /1-7 https://docs.python.org/3/tutorial/controlflow.html#unpacking-argument-lists From a40bd03b0eb9e88884b93377dea7238038893916 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Wed, 9 Mar 2022 18:35:41 -0300 Subject: [PATCH 140/166] renumbered book parts --- README.md | 35 ++++++++++++++--------------------- 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 96273ac..234c8da 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,7 @@ # Fluent Python 2e example code -Example code for the book **Fluent Python, 2nd edition** by Luciano Ramalho (O'Reilly, 2021). +Example code for the book **Fluent Python, Second Edition** by Luciano Ramalho (O'Reilly, 2022). -> **BEWARE**: This is a work in progress! -> -> * Code here may change and disappear without warning. -> -> * Major reorganizations may happen at any time. -> -> * No promises. No guarantees. Use at own risk. ## Table of Contents @@ -16,37 +9,37 @@ All chapters are undergoing review and updates, including significant rewrites i New chapters in **Fluent Python 2e** are marked with 🆕. -🚨 This table of contents is subject to change at any time until the book goes to the printer. +> 🚨  This table of contents is subject to change at any time until the book goes to the printer.
+Latest change: Old **Part I—Prologue** merged into new **Part I—Data Structures**; parts renumbered accordingly; chapter numbers unchanged. Part / Chapter #|Title|Directory|1st ed. Chapter # ---:|---|---|:---: -**I – Prologue**| +**I – Data Structures**| 1|The Python Data Model|[01-data-model](01-data-model)|1 -**II – Data Structures**| 2|An Array of Sequences|[02-array-seq](02-array-seq)|2 3|Dictionaries and Sets|[03-dict-set](03-dict-set)|3 4|Unicode Text versus Bytes|[04-text-byte](04-text-byte)|4 5|Data Class Builders|[05-data-classes](05-data-classes)|🆕 6|Object References, Mutability, and Recycling|[06-obj-ref](06-obj-ref)|8 -**III – Functions as Objects**| +**II – Functions as Objects**| 7|Funcions as First-Class Objects|[07-1class-func](07-1class-func)|5 -8|Type Hints in Function Definitions|[08-def-type-hints](08-def-type-hints)|🆕 -9|Function Decorators and Closures|[09-closure-deco](09-closure-deco)|7 +8|Type Hints in Functions|[08-def-type-hints](08-def-type-hints)|🆕 +9|Decorators and Closures|[09-closure-deco](09-closure-deco)|7 10|Design Patterns with First-Class Functions|[10-dp-1class-func](10-dp-1class-func)|6 -**IV – Object-Oriented Idioms**| +**III – Object-Oriented Idioms**| 11|A Pythonic Object|[11-pythonic-obj](11-pythonic-obj)|9 -12|Sequence Hacking, Hashing, and Slicing|[12-seq-hacking](12-seq-hacking)|10 +12|Special Methods for Sequences|[12-seq-hacking](12-seq-hacking)|10 13|Interfaces, Protocols, and ABCs|[13-protocl-abc](13-protocol-abc)|11 -14|Inheritance: For Good or For Worse|[14-inheritance](14-inheritance)|12 +14|Inheritance: For Better or For Worse|[14-inheritance](14-inheritance)|12 15|More About Type Hints|[15-more-types](15-more-types)|🆕 -16|Operator Overloading: Doing It Right|[16-op-overloading](16-op-overloading)|13 -**V – Control Flow**| +16|Operator Overloading|[16-op-overloading](16-op-overloading)|13 +**IV – Control Flow**| 17|Iterators, Generators, and Classic Coroutines|[17-it-generator](17-it-generator)|14 -18|Context Managers and else Blocks|[18-with-match](18-with-match)|15 +18|with, match, and else Blocks|[18-with-match](18-with-match)|15 19|Concurrency Models in Python|[19-concurrency](19-concurrency)|🆕 20|Concurrent Executors|[20-executors](20-executors)|17 21|Asynchronous Programming|[21-async](21-async)|18 -**VI – Metaprogramming**| +**V – Metaprogramming**| 22|Dynamic Attributes and Properties|[22-dyn-attr-prop](22-dyn-attr-prop)|19 23|Attribute Descriptors|[23-descriptor](23-descriptor)|20 24|Class Metaprogramming|[24-class-metaprog](24-class-metaprog)|21 From 8158222f2224c95ae57a4f8599073245ee94dc06 Mon Sep 17 00:00:00 2001 From: Scott McCormack Date: Mon, 14 Mar 2022 21:58:48 +0800 Subject: [PATCH 141/166] 2nd edition updates --- 02-array-seq/array-seq.ipynb | 1834 ++++++++++++++++++++++------------ 1 file changed, 1201 insertions(+), 633 deletions(-) diff --git a/02-array-seq/array-seq.ipynb b/02-array-seq/array-seq.ipynb index 032d811..afdbf6b 100644 --- a/02-array-seq/array-seq.ipynb +++ b/02-array-seq/array-seq.ipynb @@ -9,12 +9,14 @@ "**Sections with code snippets in this chapter:**\n", "\n", "* [List Comprehensions and Generator Expressions](#List-Comprehensions-and-Generator-Expressions)\n", + "* [Tuples Are Not Just Immutable Lists](#Tuples-Are-Not-Just-Immutable-Lists)\n", + "* [Unpacking sequences and iterables](#Unpacking-sequences-and-iterables)\n", + "* [Pattern Matching with Sequences](#Pattern-Matching-with-Sequences)\n", "* [Slicing](#Slicing)\n", - "* [Building Lists of Lists](#Building-Lists-of-Lists)\n", + "* [Using + and * with Sequences](#Using-+-and-*-with-Sequences)\n", "* [Augmented Assignment with Sequences](#Augmented-Assignment-with-Sequences)\n", "* [list.sort and the sorted Built-In Function](#list.sort-and-the-sorted-Built-In-Function)\n", - "* [Managing Ordered Sequences with bisect](#Managing-Ordered-Sequences-with-bisect)\n", - "* [Arrays](#Arrays)\n", + "* [When a List Is Not the Answer](#When-a-List-Is-Not-the-Answer)\n", "* [Memory Views](#Memory-Views)\n", "* [NumPy and SciPy](#NumPy-and-SciPy)\n", "* [Deques and Other Queues](#Deques-and-Other-Queues)\n", @@ -42,9 +44,7 @@ "outputs": [ { "data": { - "text/plain": [ - "[36, 162, 163, 165, 8364, 164]" - ] + "text/plain": "[36, 162, 163, 165, 8364, 164]" }, "execution_count": 1, "metadata": {}, @@ -65,7 +65,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "#### Example 2-2. Build a list of Unicode codepoints from a string, take 2" + "#### Example 2-2. Build a list of Unicode codepoints from a string, using a listcomp" ] }, { @@ -75,9 +75,7 @@ "outputs": [ { "data": { - "text/plain": [ - "[36, 162, 163, 165, 8364, 164]" - ] + "text/plain": "[36, 162, 163, 165, 8364, 164]" }, "execution_count": 2, "metadata": {}, @@ -106,9 +104,7 @@ "outputs": [ { "data": { - "text/plain": [ - "'ABC'" - ] + "text/plain": "'ABC'" }, "execution_count": 3, "metadata": {}, @@ -128,9 +124,7 @@ "outputs": [ { "data": { - "text/plain": [ - "[65, 66, 67]" - ] + "text/plain": "[65, 66, 67]" }, "execution_count": 4, "metadata": {}, @@ -141,6 +135,30 @@ "codes" ] }, + { + "cell_type": "code", + "execution_count": 5, + "outputs": [ + { + "data": { + "text/plain": "67" + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "codes = [last := ord(c) for c in x]\n", + "last" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, { "cell_type": "markdown", "metadata": {}, @@ -150,214 +168,958 @@ }, { "cell_type": "code", - "execution_count": 5, - "metadata": {}, + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": "[162, 163, 165, 8364, 164]" + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "symbols = '$¢£¥€¤'\n", + "beyond_ascii = [ord(s) for s in symbols if ord(s) > 127]\n", + "beyond_ascii" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": "[162, 163, 165, 8364, 164]" + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "beyond_ascii = list(filter(lambda c: c > 127, map(ord, symbols)))\n", + "beyond_ascii" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Example 2-4. Cartesian product using a list comprehension" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": "[('black', 'S'),\n ('black', 'M'),\n ('black', 'L'),\n ('white', 'S'),\n ('white', 'M'),\n ('white', 'L')]" + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "colors = ['black', 'white']\n", + "sizes = ['S', 'M', 'L']\n", + "tshirts = [(color, size) for color in colors for size in sizes]\n", + "tshirts" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "('black', 'S')\n", + "('black', 'M')\n", + "('black', 'L')\n", + "('white', 'S')\n", + "('white', 'M')\n", + "('white', 'L')\n" + ] + } + ], + "source": [ + "for color in colors:\n", + " for size in sizes:\n", + " print((color, size))" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": "[('black', 'S'),\n ('black', 'M'),\n ('black', 'L'),\n ('white', 'S'),\n ('white', 'M'),\n ('white', 'L')]" + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "shirts = [(color, size) for size in sizes\n", + " for color in colors]\n", + "tshirts" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Example 2-5. Initializing a tuple and an array from a generator expression" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": "(36, 162, 163, 165, 8364, 164)" + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "symbols = '$¢£¥€¤'\n", + "tuple(ord(symbol) for symbol in symbols)" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": "array('I', [36, 162, 163, 165, 8364, 164])" + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import array\n", + "\n", + "array.array('I', (ord(symbol) for symbol in symbols))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Example 2-6. Cartesian product in a generator expression" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "black S\n", + "black M\n", + "black L\n", + "white S\n", + "white M\n", + "white L\n" + ] + } + ], + "source": [ + "colors = ['black', 'white']\n", + "sizes = ['S', 'M', 'L']\n", + "\n", + "for tshirt in ('%s %s' % (c, s) for c in colors for s in sizes):\n", + " print(tshirt)" + ] + }, + { + "cell_type": "markdown", + "source": [ + "## Tuples Are Not Just Immutable Lists" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + }, + "execution_count": 73 + }, + { + "cell_type": "markdown", + "source": [ + "#### Example 2-7. Tuples used as records" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 14, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "BRA/CE342567\n", + "ESP/XDA205856\n", + "USA/31195855\n" + ] + } + ], + "source": [ + "lax_coordinates = (33.9425, -118.408056)\n", + "city, year, pop, chg, area = ('Tokyo', 2003, 32_450, 0.66, 8014)\n", + "traveler_ids = [('USA', '31195855'), ('BRA', 'CE342567'), ('ESP', 'XDA205856')]\n", + "\n", + "for passport in sorted(traveler_ids):\n", + " print('%s/%s' % passport)" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 15, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "USA\n", + "BRA\n", + "ESP\n" + ] + } + ], + "source": [ + "for country, _ in traveler_ids:\n", + " print(country)" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "### Tuples as Immutable Lists" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 16, + "outputs": [ + { + "data": { + "text/plain": "True" + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "a = (10, 'alpha', [1, 2])\n", + "b = (10, 'alpha', [1, 2])\n", + "a == b" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 17, + "outputs": [ + { + "data": { + "text/plain": "False" + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "b[-1].append(99)\n", + "a == b" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 18, + "outputs": [ + { + "data": { + "text/plain": "(10, 'alpha', [1, 2, 99])" + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "b" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 19, + "outputs": [ + { + "data": { + "text/plain": "True" + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def fixed(o):\n", + " try:\n", + " hash(o)\n", + " except TypeError:\n", + " return False\n", + " return True\n", + "\n", + "\n", + "tf = (10, 'alpha', (1, 2)) # Contains no mutable items\n", + "tm = (10, 'alpha', [1, 2]) # Contains a mutable item (list)\n", + "fixed(tf)" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 20, + "outputs": [ + { + "data": { + "text/plain": "False" + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "fixed(tm)" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "## Unpacking sequences and iterables" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 21, + "outputs": [ + { + "data": { + "text/plain": "33.9425" + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "lax_coordinates = (33.9425, -118.408056)\n", + "latitude, longitude = lax_coordinates # unpacking\n", + "latitude" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 22, + "outputs": [ + { + "data": { + "text/plain": "-118.408056" + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "longitude" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 23, + "outputs": [ + { + "data": { + "text/plain": "(2, 4)" + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "divmod(20, 8)" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 24, + "outputs": [ + { + "data": { + "text/plain": "(2, 4)" + }, + "execution_count": 24, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "t = (20, 8)\n", + "divmod(*t)" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 25, + "outputs": [ + { + "data": { + "text/plain": "(2, 4)" + }, + "execution_count": 25, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "quotient, remainder = divmod(*t)\n", + "quotient, remainder" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 26, + "outputs": [ + { + "data": { + "text/plain": "'id_rsa.pub'" + }, + "execution_count": 26, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import os\n", + "\n", + "_, filename = os.path.split('/home/luciano/.ssh/id_rsa.pub')\n", + "filename" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "### Using * to grab excess items" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 27, + "outputs": [ + { + "data": { + "text/plain": "(0, 1, [2, 3, 4])" + }, + "execution_count": 27, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "a, b, *rest = range(5)\n", + "a, b, rest" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 28, + "outputs": [ + { + "data": { + "text/plain": "(0, 1, [2])" + }, + "execution_count": 28, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "a, b, *rest = range(3)\n", + "a, b, rest" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 29, "outputs": [ { "data": { - "text/plain": [ - "[162, 163, 165, 8364, 164]" - ] + "text/plain": "(0, 1, [])" }, - "execution_count": 5, + "execution_count": 29, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "symbols = '$¢£¥€¤'\n", - "beyond_ascii = [ord(s) for s in symbols if ord(s) > 127]\n", - "beyond_ascii" - ] + "a, b, *rest = range(2)\n", + "a, b, rest" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } }, { "cell_type": "code", - "execution_count": 6, - "metadata": {}, + "execution_count": 30, "outputs": [ { "data": { - "text/plain": [ - "[162, 163, 165, 8364, 164]" - ] + "text/plain": "(0, [1, 2], 3, 4)" }, - "execution_count": 6, + "execution_count": 30, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "beyond_ascii = list(filter(lambda c: c > 127, map(ord, symbols)))\n", - "beyond_ascii" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Example 2-4. Cartesian product using a list comprehension" - ] + "a, *body, c, d = range(5)\n", + "a, body, c, d" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } }, { "cell_type": "code", - "execution_count": 7, - "metadata": {}, + "execution_count": 31, "outputs": [ { "data": { - "text/plain": [ - "[('black', 'S'),\n", - " ('black', 'M'),\n", - " ('black', 'L'),\n", - " ('white', 'S'),\n", - " ('white', 'M'),\n", - " ('white', 'L')]" - ] + "text/plain": "([0, 1], 2, 3, 4)" }, - "execution_count": 7, + "execution_count": 31, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "colors = ['black', 'white']\n", - "sizes = ['S', 'M', 'L']\n", - "tshirts = [(color, size) for color in colors for size in sizes]\n", - "tshirts" - ] + "*head, b, c, d = range(5)\n", + "head, b, c, d" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "### Unpacking with * in function calls and sequence literals" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } }, { "cell_type": "code", - "execution_count": 8, - "metadata": {}, + "execution_count": 32, "outputs": [ { - "name": "stdout", - "output_type": "stream", - "text": [ - "('black', 'S')\n", - "('black', 'M')\n", - "('black', 'L')\n", - "('white', 'S')\n", - "('white', 'M')\n", - "('white', 'L')\n" - ] + "data": { + "text/plain": "(1, 2, 3, 4, (5, 6))" + }, + "execution_count": 32, + "metadata": {}, + "output_type": "execute_result" } ], "source": [ - "for color in colors:\n", - " for size in sizes:\n", - " print((color, size))" - ] + "def fun(a, b, c, d, *rest):\n", + " return a, b, c, d, rest\n", + "\n", + "\n", + "fun(*[1, 2], 3, *range(4, 7))" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } }, { "cell_type": "code", - "execution_count": 9, - "metadata": {}, + "execution_count": 33, "outputs": [ { "data": { - "text/plain": [ - "[('black', 'S'),\n", - " ('black', 'M'),\n", - " ('black', 'L'),\n", - " ('white', 'S'),\n", - " ('white', 'M'),\n", - " ('white', 'L')]" - ] + "text/plain": "(0, 1, 2, 3, 4)" }, - "execution_count": 9, + "execution_count": 33, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "shirts = [(color, size) for size in sizes\n", - " for color in colors]\n", - "tshirts" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Example 2-5. Initializing a tuple and an array from a generator expression" - ] + "*range(4), 4" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } }, { "cell_type": "code", - "execution_count": 10, - "metadata": {}, + "execution_count": 34, "outputs": [ { "data": { - "text/plain": [ - "(36, 162, 163, 165, 8364, 164)" - ] + "text/plain": "[0, 1, 2, 3, 4]" }, - "execution_count": 10, + "execution_count": 34, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "symbols = '$¢£¥€¤'\n", - "tuple(ord(symbol) for symbol in symbols)" - ] + "[*range(4), 4]" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } }, { "cell_type": "code", - "execution_count": 11, - "metadata": {}, + "execution_count": 35, "outputs": [ { "data": { - "text/plain": [ - "array('I', [36, 162, 163, 165, 8364, 164])" - ] + "text/plain": "{0, 1, 2, 3, 4, 5, 6, 7}" }, - "execution_count": 11, + "execution_count": 35, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "import array\n", - "array.array('I', (ord(symbol) for symbol in symbols))" - ] + "{*range(4), 4, *(5, 6, 7)}" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } }, { "cell_type": "markdown", - "metadata": {}, "source": [ - "#### Example 2-6. Cartesian product in a generator expression" - ] + "### Nested unpacking\n", + "#### Example 2-8. Unpacking nested tuples to access the longitude\n", + "\n", + "[02-array-seq/metro_lat_lon.py](02-array-seq/metro_lat_lon.py)" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "## Pattern Matching with Sequences\n", + "#### Example 2-9. Method from an imaginary Robot class" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } }, { "cell_type": "code", - "execution_count": 12, - "metadata": {}, + "execution_count": 36, + "outputs": [], + "source": [ + "# def handle_command(self, message):\n", + "# match message:\n", + "# case ['BEEPER', frequency, times]:\n", + "# self.beep(times, frequency)\n", + "# case ['NECK', angle]:\n", + "# self.rotate_neck(angle)\n", + "# case ['LED', ident, intensity]:\n", + "# self.leds[ident].set_brightness(ident, intensity)\n", + "# case ['LED', ident, red, green, blue]:\n", + "# self.leds[ident].set_color(ident, red, green, blue)\n", + "# case _:\n", + "# raise InvalidCommand(message)" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "#### Example 2-10. Destructuring nested tuples—requires Python ≥ 3.10.\n", + "[02-array-seq/match_lat_lon.py](02-array-seq/match_lat_lon.py)" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 37, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "black S\n", - "black M\n", - "black L\n", - "white S\n", - "white M\n", - "white L\n" + " | latitude | longitude\n", + "Mexico City | 19.4333 | -99.1333\n", + "New York-Newark | 40.8086 | -74.0204\n", + "São Paulo | -23.5478 | -46.6358\n" ] } ], "source": [ - "colors = ['black', 'white']\n", - "sizes = ['S', 'M', 'L']\n", + "metro_areas = [\n", + " ('Tokyo', 'JP', 36.933, (35.689722, 139.691667)),\n", + " ('Delhi NCR', 'IN', 21.935, (28.613889, 77.208889)),\n", + " ('Mexico City', 'MX', 20.142, (19.433333, -99.133333)),\n", + " ('New York-Newark', 'US', 20.104, (40.808611, -74.020386)),\n", + " ('São Paulo', 'BR', 19.649, (-23.547778, -46.635833)),\n", + "]\n", "\n", - "for tshirt in ('%s %s' % (c, s) for c in colors for s in sizes):\n", - " print(tshirt)" - ] + "def main():\n", + " print(f'{\"\":15} | {\"latitude\":>9} | {\"longitude\":>9}')\n", + " for record in metro_areas:\n", + " match record:\n", + " case [name, _, _, (lat, lon)] if lon <= 0:\n", + " print(f'{name:15} | {lat:9.4f} | {lon:9.4f}')\n", + "main()" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "markdown", + "source": [], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "### Pattern Matching Sequences in an Interpreter\n", + "#### Example 2-11. Matching patterns without match/case.\n", + "[02-array-seq/lispy/py3.9/lis.py](02-array-seq/lispy/py3.9/lis.py)" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "#### Example 2-12. Pattern matching with match/case—requires Python ≥ 3.10.\n", + "[02-array-seq/lispy/py3.10/lis.py](02-array-seq/lispy/py3.10/lis.py)" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + }, + { + "cell_type": "markdown", + "source": [], + "metadata": { + "collapsed": false + } }, { "cell_type": "markdown", @@ -375,16 +1137,14 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 38, "metadata": {}, "outputs": [ { "data": { - "text/plain": [ - "[10, 20]" - ] + "text/plain": "[10, 20]" }, - "execution_count": 13, + "execution_count": 38, "metadata": {}, "output_type": "execute_result" } @@ -397,16 +1157,14 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 39, "metadata": {}, "outputs": [ { "data": { - "text/plain": [ - "[30, 40, 50, 60]" - ] + "text/plain": "[30, 40, 50, 60]" }, - "execution_count": 14, + "execution_count": 39, "metadata": {}, "output_type": "execute_result" } @@ -417,16 +1175,14 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 40, "metadata": {}, "outputs": [ { "data": { - "text/plain": [ - "[10, 20, 30]" - ] + "text/plain": "[10, 20, 30]" }, - "execution_count": 15, + "execution_count": 40, "metadata": {}, "output_type": "execute_result" } @@ -437,16 +1193,14 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 41, "metadata": {}, "outputs": [ { "data": { - "text/plain": [ - "[40, 50, 60]" - ] + "text/plain": "[40, 50, 60]" }, - "execution_count": 16, + "execution_count": 41, "metadata": {}, "output_type": "execute_result" } @@ -464,16 +1218,14 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 42, "metadata": {}, "outputs": [ { "data": { - "text/plain": [ - "'bye'" - ] + "text/plain": "'bye'" }, - "execution_count": 17, + "execution_count": 42, "metadata": {}, "output_type": "execute_result" } @@ -485,16 +1237,14 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 43, "metadata": {}, "outputs": [ { "data": { - "text/plain": [ - "'elcycib'" - ] + "text/plain": "'elcycib'" }, - "execution_count": 18, + "execution_count": 43, "metadata": {}, "output_type": "execute_result" } @@ -505,16 +1255,14 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 44, "metadata": {}, "outputs": [ { "data": { - "text/plain": [ - "'eccb'" - ] + "text/plain": "'eccb'" }, - "execution_count": 19, + "execution_count": 44, "metadata": {}, "output_type": "execute_result" } @@ -527,12 +1275,12 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "#### Example 2-9. Line items from a flat-file invoice" + "#### Example 2-13. Line items from a flat-file invoice" ] }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 45, "metadata": {}, "outputs": [ { @@ -577,16 +1325,14 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 46, "metadata": {}, "outputs": [ { "data": { - "text/plain": [ - "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]" - ] + "text/plain": "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]" }, - "execution_count": 21, + "execution_count": 46, "metadata": {}, "output_type": "execute_result" } @@ -598,16 +1344,14 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 47, "metadata": {}, "outputs": [ { "data": { - "text/plain": [ - "[0, 1, 20, 30, 5, 6, 7, 8, 9]" - ] + "text/plain": "[0, 1, 20, 30, 5, 6, 7, 8, 9]" }, - "execution_count": 22, + "execution_count": 47, "metadata": {}, "output_type": "execute_result" } @@ -619,16 +1363,14 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 48, "metadata": {}, "outputs": [ { "data": { - "text/plain": [ - "[0, 1, 20, 30, 5, 8, 9]" - ] + "text/plain": "[0, 1, 20, 30, 5, 8, 9]" }, - "execution_count": 23, + "execution_count": 48, "metadata": {}, "output_type": "execute_result" } @@ -640,16 +1382,14 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 49, "metadata": {}, "outputs": [ { "data": { - "text/plain": [ - "[0, 1, 20, 11, 5, 22, 9]" - ] + "text/plain": "[0, 1, 20, 11, 5, 22, 9]" }, - "execution_count": 24, + "execution_count": 49, "metadata": {}, "output_type": "execute_result" } @@ -668,7 +1408,7 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 50, "metadata": {}, "outputs": [ { @@ -688,16 +1428,14 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 51, "metadata": {}, "outputs": [ { "data": { - "text/plain": [ - "[0, 1, 100, 22, 9]" - ] + "text/plain": "[0, 1, 100, 22, 9]" }, - "execution_count": 26, + "execution_count": 51, "metadata": {}, "output_type": "execute_result" } @@ -711,21 +1449,19 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### Using + and * with Sequences" + "## Using + and * with Sequences" ] }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 52, "metadata": {}, "outputs": [ { "data": { - "text/plain": [ - "[1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3]" - ] + "text/plain": "[1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3]" }, - "execution_count": 27, + "execution_count": 52, "metadata": {}, "output_type": "execute_result" } @@ -737,16 +1473,14 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 53, "metadata": {}, "outputs": [ { "data": { - "text/plain": [ - "'abcdabcdabcdabcdabcd'" - ] + "text/plain": "'abcdabcdabcdabcdabcd'" }, - "execution_count": 28, + "execution_count": 53, "metadata": {}, "output_type": "execute_result" } @@ -766,21 +1500,19 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "#### Example 2-10. A list with three lists of length 3 can represent a tic-tac-toe board" + "#### Example 2-14. A list with three lists of length 3 can represent a tic-tac-toe board" ] }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 54, "metadata": {}, "outputs": [ { "data": { - "text/plain": [ - "[['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]" - ] + "text/plain": "[['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]" }, - "execution_count": 29, + "execution_count": 54, "metadata": {}, "output_type": "execute_result" } @@ -792,16 +1524,14 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": 55, "metadata": {}, "outputs": [ { "data": { - "text/plain": [ - "[['_', '_', '_'], ['_', '_', 'X'], ['_', '_', '_']]" - ] + "text/plain": "[['_', '_', '_'], ['_', '_', 'X'], ['_', '_', '_']]" }, - "execution_count": 30, + "execution_count": 55, "metadata": {}, "output_type": "execute_result" } @@ -815,21 +1545,19 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "#### Example 2-11. A list with three references to the same list is useless" + "#### Example 2-15. A list with three references to the same list is useless" ] }, { "cell_type": "code", - "execution_count": 31, + "execution_count": 56, "metadata": {}, "outputs": [ { "data": { - "text/plain": [ - "[['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]" - ] + "text/plain": "[['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]" }, - "execution_count": 31, + "execution_count": 56, "metadata": {}, "output_type": "execute_result" } @@ -841,16 +1569,14 @@ }, { "cell_type": "code", - "execution_count": 32, + "execution_count": 57, "metadata": {}, "outputs": [ { "data": { - "text/plain": [ - "[['_', '_', 'O'], ['_', '_', 'O'], ['_', '_', 'O']]" - ] + "text/plain": "[['_', '_', 'O'], ['_', '_', 'O'], ['_', '_', 'O']]" }, - "execution_count": 32, + "execution_count": 57, "metadata": {}, "output_type": "execute_result" } @@ -869,16 +1595,14 @@ }, { "cell_type": "code", - "execution_count": 33, + "execution_count": 58, "metadata": {}, "outputs": [ { "data": { - "text/plain": [ - "[['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]" - ] + "text/plain": "[['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]" }, - "execution_count": 33, + "execution_count": 58, "metadata": {}, "output_type": "execute_result" } @@ -893,16 +1617,14 @@ }, { "cell_type": "code", - "execution_count": 34, + "execution_count": 59, "metadata": {}, "outputs": [ { "data": { - "text/plain": [ - "[['_', '_', '_'], ['_', '_', '_'], ['X', '_', '_']]" - ] + "text/plain": "[['_', '_', '_'], ['_', '_', '_'], ['X', '_', '_']]" }, - "execution_count": 34, + "execution_count": 59, "metadata": {}, "output_type": "execute_result" } @@ -921,7 +1643,7 @@ }, { "cell_type": "code", - "execution_count": 35, + "execution_count": 60, "metadata": {}, "outputs": [], "source": [ @@ -931,16 +1653,14 @@ }, { "cell_type": "code", - "execution_count": 36, + "execution_count": 61, "metadata": {}, "outputs": [ { "data": { - "text/plain": [ - "4414271936" - ] + "text/plain": "140694277263808" }, - "execution_count": 36, + "execution_count": 61, "metadata": {}, "output_type": "execute_result" } @@ -952,16 +1672,14 @@ }, { "cell_type": "code", - "execution_count": 37, + "execution_count": 62, "metadata": {}, "outputs": [ { "data": { - "text/plain": [ - "[1, 2, 3, 1, 2, 3]" - ] + "text/plain": "[1, 2, 3, 1, 2, 3]" }, - "execution_count": 37, + "execution_count": 62, "metadata": {}, "output_type": "execute_result" } @@ -973,16 +1691,14 @@ }, { "cell_type": "code", - "execution_count": 38, + "execution_count": 63, "metadata": {}, "outputs": [ { "data": { - "text/plain": [ - "True" - ] + "text/plain": "True" }, - "execution_count": 38, + "execution_count": 63, "metadata": {}, "output_type": "execute_result" } @@ -993,7 +1709,7 @@ }, { "cell_type": "code", - "execution_count": 39, + "execution_count": 64, "metadata": {}, "outputs": [], "source": [ @@ -1003,16 +1719,14 @@ }, { "cell_type": "code", - "execution_count": 40, + "execution_count": 65, "metadata": {}, "outputs": [ { "data": { - "text/plain": [ - "4414275328" - ] + "text/plain": "140694329335488" }, - "execution_count": 40, + "execution_count": 65, "metadata": {}, "output_type": "execute_result" } @@ -1024,35 +1738,34 @@ }, { "cell_type": "code", - "execution_count": 41, + "execution_count": 66, "metadata": {}, "outputs": [ { "data": { - "text/plain": [ - "False" - ] + "text/plain": "False" }, - "execution_count": 41, + "execution_count": 66, "metadata": {}, "output_type": "execute_result" } ], "source": [ "t *= 2\n", - "id(t) == idt # new tuple" + "id(t) == idt # new tuple" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### A += Assignment Puzzler" + "### A += Assignment Puzzler\n", + "#### Example 2-16. A riddle" ] }, { "cell_type": "code", - "execution_count": 42, + "execution_count": 67, "metadata": {}, "outputs": [ { @@ -1071,18 +1784,28 @@ " print(repr(e))" ] }, + { + "cell_type": "markdown", + "source": [ + "#### Example 2-17. The unexpected result: item t2 is changed and an exception is raised" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + }, { "cell_type": "code", - "execution_count": 43, + "execution_count": 68, "metadata": {}, "outputs": [ { "data": { - "text/plain": [ - "(1, 2, [30, 40, 50, 60])" - ] + "text/plain": "(1, 2, [30, 40, 50, 60])" }, - "execution_count": 43, + "execution_count": 68, "metadata": {}, "output_type": "execute_result" } @@ -1095,12 +1818,12 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "#### Example 2-14. Bytecode for the expression s[a] += b" + "#### Example 2-18. Bytecode for the expression s[a] += b" ] }, { "cell_type": "code", - "execution_count": 44, + "execution_count": 69, "metadata": {}, "outputs": [ { @@ -1135,16 +1858,14 @@ }, { "cell_type": "code", - "execution_count": 45, + "execution_count": 70, "metadata": {}, "outputs": [ { "data": { - "text/plain": [ - "['apple', 'banana', 'grape', 'raspberry']" - ] + "text/plain": "['apple', 'banana', 'grape', 'raspberry']" }, - "execution_count": 45, + "execution_count": 70, "metadata": {}, "output_type": "execute_result" } @@ -1156,16 +1877,14 @@ }, { "cell_type": "code", - "execution_count": 46, + "execution_count": 71, "metadata": {}, "outputs": [ { "data": { - "text/plain": [ - "['grape', 'raspberry', 'apple', 'banana']" - ] + "text/plain": "['grape', 'raspberry', 'apple', 'banana']" }, - "execution_count": 46, + "execution_count": 71, "metadata": {}, "output_type": "execute_result" } @@ -1176,16 +1895,14 @@ }, { "cell_type": "code", - "execution_count": 47, + "execution_count": 72, "metadata": {}, "outputs": [ { "data": { - "text/plain": [ - "['raspberry', 'grape', 'banana', 'apple']" - ] + "text/plain": "['raspberry', 'grape', 'banana', 'apple']" }, - "execution_count": 47, + "execution_count": 72, "metadata": {}, "output_type": "execute_result" } @@ -1196,16 +1913,14 @@ }, { "cell_type": "code", - "execution_count": 48, + "execution_count": 73, "metadata": {}, "outputs": [ { "data": { - "text/plain": [ - "['grape', 'apple', 'banana', 'raspberry']" - ] + "text/plain": "['grape', 'apple', 'banana', 'raspberry']" }, - "execution_count": 48, + "execution_count": 73, "metadata": {}, "output_type": "execute_result" } @@ -1216,16 +1931,14 @@ }, { "cell_type": "code", - "execution_count": 49, + "execution_count": 74, "metadata": {}, "outputs": [ { "data": { - "text/plain": [ - "['raspberry', 'banana', 'grape', 'apple']" - ] + "text/plain": "['raspberry', 'banana', 'grape', 'apple']" }, - "execution_count": 49, + "execution_count": 74, "metadata": {}, "output_type": "execute_result" } @@ -1236,16 +1949,14 @@ }, { "cell_type": "code", - "execution_count": 50, + "execution_count": 75, "metadata": {}, "outputs": [ { "data": { - "text/plain": [ - "['grape', 'raspberry', 'apple', 'banana']" - ] + "text/plain": "['grape', 'raspberry', 'apple', 'banana']" }, - "execution_count": 50, + "execution_count": 75, "metadata": {}, "output_type": "execute_result" } @@ -1256,16 +1967,14 @@ }, { "cell_type": "code", - "execution_count": 51, + "execution_count": 76, "metadata": {}, "outputs": [ { "data": { - "text/plain": [ - "['apple', 'banana', 'grape', 'raspberry']" - ] + "text/plain": "['apple', 'banana', 'grape', 'raspberry']" }, - "execution_count": 51, + "execution_count": 76, "metadata": {}, "output_type": "execute_result" } @@ -1279,322 +1988,231 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Managing Ordered Sequences with bisect" + "## When a List Is Not the Answer" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "#### Example 2-15. bisect finds insertion points for items in a sorted sequence" - ] - }, - { - "cell_type": "code", - "execution_count": 52, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "DEMO: bisect_right\n", - "haystack -> 1 4 5 6 8 12 15 20 21 23 23 26 29 30\n", - "31 @ 14 | | | | | | | | | | | | | |31\n", - "30 @ 14 | | | | | | | | | | | | | |30\n", - "29 @ 13 | | | | | | | | | | | | |29\n", - "23 @ 11 | | | | | | | | | | |23\n", - "22 @ 9 | | | | | | | | |22\n", - "10 @ 5 | | | | |10\n", - " 8 @ 5 | | | | |8 \n", - " 5 @ 3 | | |5 \n", - " 2 @ 1 |2 \n", - " 1 @ 1 |1 \n", - " 0 @ 0 0 \n" - ] - } - ], - "source": [ - "# BEGIN BISECT_DEMO\n", - "import bisect\n", - "import sys\n", - "\n", - "HAYSTACK = [1, 4, 5, 6, 8, 12, 15, 20, 21, 23, 23, 26, 29, 30]\n", - "NEEDLES = [0, 1, 2, 5, 8, 10, 22, 23, 29, 30, 31]\n", - "\n", - "ROW_FMT = '{0:2d} @ {1:2d} {2}{0:<2d}'\n", - "\n", - "def demo(haystack, needles, bisect_fn):\n", - " print('DEMO:', bisect_fn.__name__) # <1>\n", - " print('haystack ->', ' '.join('%2d' % n for n in haystack))\n", - " for needle in reversed(needles):\n", - " position = bisect_fn(haystack, needle) # <2>\n", - " offset = position * ' |' # <3>\n", - " print(ROW_FMT.format(needle, position, offset)) # <4>\n", - "\n", - "demo(HAYSTACK, NEEDLES, bisect.bisect) # <5>\n", - "# END BISECT_DEMO" - ] - }, - { - "cell_type": "code", - "execution_count": 53, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "DEMO: bisect_left\n", - "haystack -> 1 4 5 6 8 12 15 20 21 23 23 26 29 30\n", - "31 @ 14 | | | | | | | | | | | | | |31\n", - "30 @ 13 | | | | | | | | | | | | |30\n", - "29 @ 12 | | | | | | | | | | | |29\n", - "23 @ 9 | | | | | | | | |23\n", - "22 @ 9 | | | | | | | | |22\n", - "10 @ 5 | | | | |10\n", - " 8 @ 4 | | | |8 \n", - " 5 @ 2 | |5 \n", - " 2 @ 1 |2 \n", - " 1 @ 0 1 \n", - " 0 @ 0 0 \n" - ] - } - ], - "source": [ - "demo(HAYSTACK, NEEDLES, bisect.bisect_left)" + "### Arrays" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "#### Example 2-16. Given a test score, grade returns the corresponding letter grade" + "#### Example 2-19. Creating, saving, and loading a large array of floats" ] }, { "cell_type": "code", - "execution_count": 54, + "execution_count": 77, "metadata": {}, "outputs": [ { "data": { - "text/plain": [ - "['F', 'D', 'D', 'C', 'C', 'B', 'B', 'A', 'A']" - ] + "text/plain": "0.8190492979077034" }, - "execution_count": 54, + "execution_count": 77, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "def grade(score, breakpoints=[60, 70, 80, 90], grades='FDCBA'):\n", - " i = bisect.bisect(breakpoints, score)\n", - " return grades[i]\n", + "from array import array\n", + "from random import random, seed\n", + "seed(10) # Use seed to make the output consistent\n", "\n", - "[grade(score) for score in [55, 60, 65, 70, 75, 80, 85, 90, 95]]" + "floats = array('d', (random() for i in range(10 ** 7)))\n", + "floats[-1]" ] }, { - "cell_type": "markdown", + "cell_type": "code", + "execution_count": 78, "metadata": {}, + "outputs": [], "source": [ - "#### Example 2-17. bisect_left maps a score of 60 to grade F, not D as in Example 2-16." + "with open('floats.bin', 'wb') as fp:\n", + " floats.tofile(fp)" ] }, { "cell_type": "code", - "execution_count": 55, + "execution_count": 79, "metadata": {}, "outputs": [ { "data": { - "text/plain": [ - "['F', 'F', 'D', 'D', 'C', 'C', 'B', 'B', 'A']" - ] + "text/plain": "0.8190492979077034" }, - "execution_count": 55, + "execution_count": 79, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "def grade(score, breakpoints=[60, 70, 80, 90], grades='FDCBA'):\n", - " i = bisect.bisect_left(breakpoints, score)\n", - " return grades[i]\n", + "floats2 = array('d')\n", "\n", - "[grade(score) for score in [55, 60, 65, 70, 75, 80, 85, 90, 95]]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Example 2-18. Insort keeps a sorted sequence always sorted" + "with open('floats.bin', 'rb') as fp:\n", + " floats2.fromfile(fp, 10 ** 7)\n", + "\n", + "floats2[-1]" ] }, { "cell_type": "code", - "execution_count": 56, + "execution_count": 80, "metadata": {}, "outputs": [ { - "name": "stdout", - "output_type": "stream", - "text": [ - "insert 10 -> [10]\n", - "insert 0 -> [0, 10]\n", - "insert 6 -> [0, 6, 10]\n", - "insert 8 -> [0, 6, 8, 10]\n", - "insert 7 -> [0, 6, 7, 8, 10]\n", - "insert 2 -> [0, 2, 6, 7, 8, 10]\n", - "insert 10 -> [0, 2, 6, 7, 8, 10, 10]\n" - ] + "data": { + "text/plain": "True" + }, + "execution_count": 80, + "metadata": {}, + "output_type": "execute_result" } ], "source": [ - "import bisect\n", - "import random\n", - "\n", - "SIZE = 7\n", - "\n", - "random.seed(1729)\n", - "\n", - "my_list = []\n", - "\n", - "for i in range(SIZE):\n", - " new_item = random.randrange(SIZE*2)\n", - " bisect.insort(my_list, new_item)\n", - " print(f'insert {new_item:2d} -> {my_list}')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## When a List Is Not the Answer" + "floats2 == floats" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### Arrays" + "### Memory Views" ] }, { "cell_type": "markdown", - "metadata": {}, "source": [ - "#### Example 2-19. Creating, saving, and loading a large array of floats" - ] + "#### Example 2-20. Handling 6 bytes memory of as 1×6, 2×3, and 3×2 views" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } }, { "cell_type": "code", - "execution_count": 57, - "metadata": {}, + "execution_count": 81, "outputs": [ { "data": { - "text/plain": [ - "0.5963321947530882" - ] + "text/plain": "[0, 1, 2, 3, 4, 5]" }, - "execution_count": 57, + "execution_count": 81, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "from array import array\n", - "from random import random\n", - "\n", - "floats = array('d', (random() for i in range(10**7)))\n", - "floats[-1]" - ] + "octets = array('B', range(6))\n", + "m1 = memoryview(octets)\n", + "m1.tolist()" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } }, { "cell_type": "code", - "execution_count": 58, - "metadata": {}, - "outputs": [], + "execution_count": 82, + "outputs": [ + { + "data": { + "text/plain": "[[0, 1, 2], [3, 4, 5]]" + }, + "execution_count": 82, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "with open('floats.bin', 'wb') as fp:\n", - " floats.tofile(fp)" - ] + "m2 = m1.cast('B', [2, 3])\n", + "m2.tolist()" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } }, { "cell_type": "code", - "execution_count": 59, - "metadata": {}, + "execution_count": 83, "outputs": [ { "data": { - "text/plain": [ - "0.5963321947530882" - ] + "text/plain": "[[0, 1], [2, 3], [4, 5]]" }, - "execution_count": 59, + "execution_count": 83, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "floats2 = array('d')\n", - "\n", - "with open('floats.bin', 'rb') as fp:\n", - " floats2.fromfile(fp, 10**7)\n", - "\n", - "floats2[-1]" - ] + "m3 = m1.cast('B', [3, 2])\n", + "m3.tolist()" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } }, { "cell_type": "code", - "execution_count": 60, - "metadata": {}, + "execution_count": 84, "outputs": [ { "data": { - "text/plain": [ - "True" - ] + "text/plain": "array('B', [0, 1, 2, 33, 22, 5])" }, - "execution_count": 60, + "execution_count": 84, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "floats2 == floats" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Memory Views" - ] + "m2[1,1] = 22\n", + "m3[1,1] = 33\n", + "octets" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } }, { "cell_type": "markdown", "metadata": {}, "source": [ - "#### Example 2-20. Changing the value of an array item by poking one of its bytes" + "#### Example 2-21. Changing the value of an 16-bit integer array item by poking one of its bytes" ] }, { "cell_type": "code", - "execution_count": 61, + "execution_count": 85, "metadata": {}, "outputs": [ { "data": { - "text/plain": [ - "5" - ] + "text/plain": "5" }, - "execution_count": 61, + "execution_count": 85, "metadata": {}, "output_type": "execute_result" } @@ -1607,16 +2225,14 @@ }, { "cell_type": "code", - "execution_count": 62, + "execution_count": 86, "metadata": {}, "outputs": [ { "data": { - "text/plain": [ - "-2" - ] + "text/plain": "-2" }, - "execution_count": 62, + "execution_count": 86, "metadata": {}, "output_type": "execute_result" } @@ -1627,16 +2243,14 @@ }, { "cell_type": "code", - "execution_count": 63, + "execution_count": 87, "metadata": {}, "outputs": [ { "data": { - "text/plain": [ - "[254, 255, 255, 255, 0, 0, 1, 0, 2, 0]" - ] + "text/plain": "[254, 255, 255, 255, 0, 0, 1, 0, 2, 0]" }, - "execution_count": 63, + "execution_count": 87, "metadata": {}, "output_type": "execute_result" } @@ -1648,16 +2262,14 @@ }, { "cell_type": "code", - "execution_count": 64, + "execution_count": 88, "metadata": {}, "outputs": [ { "data": { - "text/plain": [ - "array('h', [-2, -1, 1024, 1, 2])" - ] + "text/plain": "array('h', [-2, -1, 1024, 1, 2])" }, - "execution_count": 64, + "execution_count": 88, "metadata": {}, "output_type": "execute_result" } @@ -1671,50 +2283,47 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### NumPy and SciPy" + "### NumPy" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "#### Example 2-21. Basic operations with rows and columns in a numpy.ndarray" + "#### Example 2-22. Basic operations with rows and columns in a numpy.ndarray" ] }, { "cell_type": "code", - "execution_count": 65, + "execution_count": 89, "metadata": {}, "outputs": [ { "data": { - "text/plain": [ - "array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])" - ] + "text/plain": "array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])" }, - "execution_count": 65, + "execution_count": 89, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import numpy as np\n", + "\n", "a = np.arange(12)\n", "a" ] }, { "cell_type": "code", - "execution_count": 66, + "execution_count": 90, "metadata": {}, "outputs": [ { "data": { - "text/plain": [ - "numpy.ndarray" - ] + "text/plain": "numpy.ndarray" }, - "execution_count": 66, + "execution_count": 90, "metadata": {}, "output_type": "execute_result" } @@ -1725,16 +2334,14 @@ }, { "cell_type": "code", - "execution_count": 67, + "execution_count": 91, "metadata": {}, "outputs": [ { "data": { - "text/plain": [ - "(12,)" - ] + "text/plain": "(12,)" }, - "execution_count": 67, + "execution_count": 91, "metadata": {}, "output_type": "execute_result" } @@ -1745,18 +2352,14 @@ }, { "cell_type": "code", - "execution_count": 68, + "execution_count": 92, "metadata": {}, "outputs": [ { "data": { - "text/plain": [ - "array([[ 0, 1, 2, 3],\n", - " [ 4, 5, 6, 7],\n", - " [ 8, 9, 10, 11]])" - ] + "text/plain": "array([[ 0, 1, 2, 3],\n [ 4, 5, 6, 7],\n [ 8, 9, 10, 11]])" }, - "execution_count": 68, + "execution_count": 92, "metadata": {}, "output_type": "execute_result" } @@ -1768,16 +2371,14 @@ }, { "cell_type": "code", - "execution_count": 69, + "execution_count": 93, "metadata": {}, "outputs": [ { "data": { - "text/plain": [ - "array([ 8, 9, 10, 11])" - ] + "text/plain": "array([ 8, 9, 10, 11])" }, - "execution_count": 69, + "execution_count": 93, "metadata": {}, "output_type": "execute_result" } @@ -1788,16 +2389,14 @@ }, { "cell_type": "code", - "execution_count": 70, + "execution_count": 94, "metadata": {}, "outputs": [ { "data": { - "text/plain": [ - "9" - ] + "text/plain": "9" }, - "execution_count": 70, + "execution_count": 94, "metadata": {}, "output_type": "execute_result" } @@ -1808,16 +2407,14 @@ }, { "cell_type": "code", - "execution_count": 71, + "execution_count": 95, "metadata": {}, "outputs": [ { "data": { - "text/plain": [ - "array([1, 5, 9])" - ] + "text/plain": "array([1, 5, 9])" }, - "execution_count": 71, + "execution_count": 95, "metadata": {}, "output_type": "execute_result" } @@ -1828,25 +2425,20 @@ }, { "cell_type": "code", - "execution_count": 72, + "execution_count": 96, "metadata": {}, "outputs": [ { "data": { - "text/plain": [ - "array([[ 0, 4, 8],\n", - " [ 1, 5, 9],\n", - " [ 2, 6, 10],\n", - " [ 3, 7, 11]])" - ] + "text/plain": "array([[ 0, 4, 8],\n [ 1, 5, 9],\n [ 2, 6, 10],\n [ 3, 7, 11]])" }, - "execution_count": 72, + "execution_count": 96, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "a.transpose()\n" + "a.transpose()" ] }, { @@ -1858,7 +2450,7 @@ }, { "cell_type": "code", - "execution_count": 73, + "execution_count": 97, "metadata": {}, "outputs": [], "source": [ @@ -1869,7 +2461,7 @@ }, { "cell_type": "code", - "execution_count": 74, + "execution_count": 98, "metadata": {}, "outputs": [], "source": [ @@ -1878,16 +2470,14 @@ }, { "cell_type": "code", - "execution_count": 75, + "execution_count": 99, "metadata": {}, "outputs": [ { "data": { - "text/plain": [ - "array([0.29150425, 0.33893554, 0.08112756])" - ] + "text/plain": "array([0.06078257, 0.61741189, 0.84349987])" }, - "execution_count": 75, + "execution_count": 99, "metadata": {}, "output_type": "execute_result" } @@ -1898,16 +2488,14 @@ }, { "cell_type": "code", - "execution_count": 76, + "execution_count": 100, "metadata": {}, "outputs": [ { "data": { - "text/plain": [ - "array([0.14575213, 0.16946777, 0.04056378])" - ] + "text/plain": "array([0.03039128, 0.30870594, 0.42174994])" }, - "execution_count": 76, + "execution_count": 100, "metadata": {}, "output_type": "execute_result" } @@ -1919,16 +2507,14 @@ }, { "cell_type": "code", - "execution_count": 77, + "execution_count": 101, "metadata": {}, "outputs": [ { "data": { - "text/plain": [ - "True" - ] + "text/plain": "True" }, - "execution_count": 77, + "execution_count": 101, "metadata": {}, "output_type": "execute_result" } @@ -1943,7 +2529,7 @@ }, { "cell_type": "code", - "execution_count": 78, + "execution_count": 102, "metadata": {}, "outputs": [], "source": [ @@ -1954,16 +2540,14 @@ }, { "cell_type": "code", - "execution_count": 79, + "execution_count": 103, "metadata": {}, "outputs": [ { "data": { - "text/plain": [ - "memmap([0.29150425, 0.33893554, 0.08112756])" - ] + "text/plain": "memmap([0.06078257, 0.61741189, 0.84349987])" }, - "execution_count": 79, + "execution_count": 103, "metadata": {}, "output_type": "execute_result" } @@ -1983,21 +2567,19 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "#### Example 2-22. Working with a deque" + "#### Example 2-23. Working with a deque" ] }, { "cell_type": "code", - "execution_count": 80, + "execution_count": 104, "metadata": {}, "outputs": [ { "data": { - "text/plain": [ - "deque([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])" - ] + "text/plain": "deque([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])" }, - "execution_count": 80, + "execution_count": 104, "metadata": {}, "output_type": "execute_result" } @@ -2011,16 +2593,14 @@ }, { "cell_type": "code", - "execution_count": 81, + "execution_count": 105, "metadata": {}, "outputs": [ { "data": { - "text/plain": [ - "deque([7, 8, 9, 0, 1, 2, 3, 4, 5, 6])" - ] + "text/plain": "deque([7, 8, 9, 0, 1, 2, 3, 4, 5, 6])" }, - "execution_count": 81, + "execution_count": 105, "metadata": {}, "output_type": "execute_result" } @@ -2032,16 +2612,14 @@ }, { "cell_type": "code", - "execution_count": 82, + "execution_count": 106, "metadata": {}, "outputs": [ { "data": { - "text/plain": [ - "deque([1, 2, 3, 4, 5, 6, 7, 8, 9, 0])" - ] + "text/plain": "deque([1, 2, 3, 4, 5, 6, 7, 8, 9, 0])" }, - "execution_count": 82, + "execution_count": 106, "metadata": {}, "output_type": "execute_result" } @@ -2053,16 +2631,14 @@ }, { "cell_type": "code", - "execution_count": 83, + "execution_count": 107, "metadata": {}, "outputs": [ { "data": { - "text/plain": [ - "deque([-1, 1, 2, 3, 4, 5, 6, 7, 8, 9])" - ] + "text/plain": "deque([-1, 1, 2, 3, 4, 5, 6, 7, 8, 9])" }, - "execution_count": 83, + "execution_count": 107, "metadata": {}, "output_type": "execute_result" } @@ -2074,16 +2650,14 @@ }, { "cell_type": "code", - "execution_count": 84, + "execution_count": 108, "metadata": {}, "outputs": [ { "data": { - "text/plain": [ - "deque([3, 4, 5, 6, 7, 8, 9, 11, 22, 33])" - ] + "text/plain": "deque([3, 4, 5, 6, 7, 8, 9, 11, 22, 33])" }, - "execution_count": 84, + "execution_count": 108, "metadata": {}, "output_type": "execute_result" } @@ -2095,16 +2669,14 @@ }, { "cell_type": "code", - "execution_count": 85, + "execution_count": 109, "metadata": {}, "outputs": [ { "data": { - "text/plain": [ - "deque([40, 30, 20, 10, 3, 4, 5, 6, 7, 8])" - ] + "text/plain": "deque([40, 30, 20, 10, 3, 4, 5, 6, 7, 8])" }, - "execution_count": 85, + "execution_count": 109, "metadata": {}, "output_type": "execute_result" } @@ -2130,7 +2702,7 @@ }, { "cell_type": "code", - "execution_count": 86, + "execution_count": 110, "metadata": {}, "outputs": [], "source": [ @@ -2139,7 +2711,7 @@ }, { "cell_type": "code", - "execution_count": 87, + "execution_count": 111, "metadata": {}, "outputs": [ { @@ -2166,16 +2738,14 @@ }, { "cell_type": "code", - "execution_count": 88, + "execution_count": 112, "metadata": {}, "outputs": [ { "data": { - "text/plain": [ - "[0, '1', 5, 6, '9', 14, 19, '23', 28, '28']" - ] + "text/plain": "[0, '1', 5, 6, '9', 14, 19, '23', 28, '28']" }, - "execution_count": 88, + "execution_count": 112, "metadata": {}, "output_type": "execute_result" } @@ -2188,16 +2758,14 @@ }, { "cell_type": "code", - "execution_count": 89, + "execution_count": 113, "metadata": {}, "outputs": [ { "data": { - "text/plain": [ - "[0, '1', 14, 19, '23', 28, '28', 5, 6, '9']" - ] + "text/plain": "[0, '1', 14, 19, '23', 28, '28', 5, 6, '9']" }, - "execution_count": 89, + "execution_count": 113, "metadata": {}, "output_type": "execute_result" } @@ -2208,7 +2776,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 113, "metadata": {}, "outputs": [], "source": [] @@ -2235,4 +2803,4 @@ }, "nbformat": 4, "nbformat_minor": 2 -} +} \ No newline at end of file From 434f8d26b6a11e6f59aba288933192af92f211ad Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Fri, 1 Apr 2022 17:23:12 -0300 Subject: [PATCH 142/166] covariance v. contravariance in Callable --- 08-def-type-hints/callable/variance.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 08-def-type-hints/callable/variance.py diff --git a/08-def-type-hints/callable/variance.py b/08-def-type-hints/callable/variance.py new file mode 100644 index 0000000..fcddbde --- /dev/null +++ b/08-def-type-hints/callable/variance.py @@ -0,0 +1,22 @@ +from collections.abc import Callable + +def update( # <1> + probe: Callable[[], float], # <2> + display: Callable[[float], None] # <3> + ) -> None: + temperature = probe() + # imagine lots of control code here + display(temperature) + +def probe_ok() -> int: # <4> + return 42 + +def display_wrong(temperature: int) -> None: # <5> + print(hex(temperature)) + +update(probe_ok, display_wrong) # type error # <6> + +def display_ok(temperature: complex) -> None: # <7> + print(temperature) + +update(probe_ok, display_ok) # OK # <8> From 08230737cc9b32a87510b517c448a2ca71a0fa18 Mon Sep 17 00:00:00 2001 From: Eduardo Scartezini Date: Fri, 8 Jul 2022 17:44:09 +0100 Subject: [PATCH 143/166] Fix chapter number to 2ed edition On 2ed the chapter "Object references, mutability and recycling" from 8 to 6 This commit fixes the readme with the new chapter number --- 06-obj-ref/README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/06-obj-ref/README.rst b/06-obj-ref/README.rst index deac2fa..0d705e6 100644 --- a/06-obj-ref/README.rst +++ b/06-obj-ref/README.rst @@ -1,4 +1,4 @@ -Sample code for Chapter 8 - "Object references, mutability and recycling" +Sample code for Chapter 6 - "Object references, mutability and recycling" From the book "Fluent Python" by Luciano Ramalho (O'Reilly, 2015) http://shop.oreilly.com/product/0636920032519.do From d00908d553a9746ea3c855c99c78a9d7308e1b23 Mon Sep 17 00:00:00 2001 From: Vlad Grin <67241634+grinvlad@users.noreply.github.com> Date: Sun, 31 Jul 2022 18:41:59 +0300 Subject: [PATCH 144/166] Fix filename in script --- 02-array-seq/test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/02-array-seq/test.sh b/02-array-seq/test.sh index 4747320..906717d 100755 --- a/02-array-seq/test.sh +++ b/02-array-seq/test.sh @@ -1,4 +1,4 @@ #!/bin/bash python3 -m doctest bisect_demo.py -python3 -m doctest metro_lat_long.py +python3 -m doctest metro_lat_lon.py pytest -q --nbval From 3f9c89554aa5a14837e6f1977317b415ffab4ea1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 9 Dec 2022 05:48:20 +0000 Subject: [PATCH 145/166] build(deps): bump certifi in /20-executors/getflags Bumps [certifi](https://github.com/certifi/python-certifi) from 2021.5.30 to 2022.12.7. - [Release notes](https://github.com/certifi/python-certifi/releases) - [Commits](https://github.com/certifi/python-certifi/compare/2021.05.30...2022.12.07) --- updated-dependencies: - dependency-name: certifi dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- 20-executors/getflags/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/20-executors/getflags/requirements.txt b/20-executors/getflags/requirements.txt index b8bb630..5dc5007 100644 --- a/20-executors/getflags/requirements.txt +++ b/20-executors/getflags/requirements.txt @@ -1,5 +1,5 @@ anyio==3.3.2 -certifi==2021.5.30 +certifi==2022.12.7 charset-normalizer==2.0.6 h11==0.12.0 httpcore==0.13.7 From d3309f03e88ce449c8f45beee25621221db86217 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Feb 2023 22:09:42 +0000 Subject: [PATCH 146/166] build(deps): bump starlette in /21-async/mojifinder Bumps [starlette](https://github.com/encode/starlette) from 0.13.6 to 0.25.0. - [Release notes](https://github.com/encode/starlette/releases) - [Changelog](https://github.com/encode/starlette/blob/master/docs/release-notes.md) - [Commits](https://github.com/encode/starlette/compare/0.13.6...0.25.0) --- updated-dependencies: - dependency-name: starlette dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- 21-async/mojifinder/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/21-async/mojifinder/requirements.txt b/21-async/mojifinder/requirements.txt index 1f7c3d6..4a1c97a 100644 --- a/21-async/mojifinder/requirements.txt +++ b/21-async/mojifinder/requirements.txt @@ -2,6 +2,6 @@ click==7.1.2 fastapi==0.65.2 h11==0.12.0 pydantic==1.8.2 -starlette==0.13.6 +starlette==0.25.0 typing-extensions==3.7.4.3 uvicorn==0.13.4 From 51055887dd6cca6c0b67e1f0072ec33d1eaa6351 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Andr=C3=A9s=20=C3=81lvarez=20Restrepo?= Date: Sun, 26 Mar 2023 17:28:08 -0500 Subject: [PATCH 147/166] Update flags2_asyncio.py Fix `error` variable not being reset when a successful coroutine call --- 20-executors/getflags/flags2_asyncio.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/20-executors/getflags/flags2_asyncio.py b/20-executors/getflags/flags2_asyncio.py index 4f1849a..b697840 100755 --- a/20-executors/getflags/flags2_asyncio.py +++ b/20-executors/getflags/flags2_asyncio.py @@ -79,6 +79,8 @@ async def supervisor(cc_list: list[str], error = exc # <10> except KeyboardInterrupt: break + else: + error = None if error: status = DownloadStatus.ERROR # <11> From 166a388ac84cbdea1c27404acfe1453c7bb6eb51 Mon Sep 17 00:00:00 2001 From: Mehdi Abbassi Date: Fri, 29 Sep 2023 07:46:37 +0330 Subject: [PATCH 148/166] using f-strings instead of % operator --- 01-data-model/data-model.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/01-data-model/data-model.ipynb b/01-data-model/data-model.ipynb index f525899..5b30397 100644 --- a/01-data-model/data-model.ipynb +++ b/01-data-model/data-model.ipynb @@ -482,7 +482,7 @@ " self.y = y\n", "\n", " def __repr__(self):\n", - " return 'Vector(%r, %r)' % (self.x, self.y)\n", + " return f'Vector({self.x!r}, {self.y!r})'\n", "\n", " def __abs__(self):\n", " return math.hypot(self.x, self.y)\n", From 567f3529e35fccc0087c9555f9fcd72c90043564 Mon Sep 17 00:00:00 2001 From: Tudor Capusan Date: Sun, 16 Jun 2024 11:37:29 -0400 Subject: [PATCH 149/166] fix spelling change from 'reminder' to 'remainder' to match intent --- 17-it-generator/columnize_iter.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/17-it-generator/columnize_iter.py b/17-it-generator/columnize_iter.py index cee0f13..4362bc1 100644 --- a/17-it-generator/columnize_iter.py +++ b/17-it-generator/columnize_iter.py @@ -6,8 +6,8 @@ def columnize( ) -> Iterator[tuple[str, ...]]: # <1> if num_columns == 0: num_columns = round(len(sequence) ** 0.5) - num_rows, reminder = divmod(len(sequence), num_columns) - num_rows += bool(reminder) + num_rows, remainder = divmod(len(sequence), num_columns) + num_rows += bool(remainder) return (tuple(sequence[i::num_rows]) for i in range(num_rows)) # <2> # end::COLUMNIZE[] From 74ee1d4915d91b0af9013eb8fecf0302ca7f40c9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 May 2025 19:51:04 +0000 Subject: [PATCH 150/166] build(deps): bump idna from 3.2 to 3.7 in /20-executors/getflags Bumps [idna](https://github.com/kjd/idna) from 3.2 to 3.7. - [Release notes](https://github.com/kjd/idna/releases) - [Changelog](https://github.com/kjd/idna/blob/master/HISTORY.rst) - [Commits](https://github.com/kjd/idna/compare/v3.2...v3.7) --- updated-dependencies: - dependency-name: idna dependency-version: '3.7' dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- 20-executors/getflags/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/20-executors/getflags/requirements.txt b/20-executors/getflags/requirements.txt index 5dc5007..c5fb66c 100644 --- a/20-executors/getflags/requirements.txt +++ b/20-executors/getflags/requirements.txt @@ -4,7 +4,7 @@ charset-normalizer==2.0.6 h11==0.12.0 httpcore==0.13.7 httpx==1.0.0b0 -idna==3.2 +idna==3.7 rfc3986==1.5.0 sniffio==1.2.0 tqdm==4.62.3 From 1b44dfaf11bf9bc8d87339f9fb8eddb294014f07 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 May 2025 19:51:09 +0000 Subject: [PATCH 151/166] build(deps): bump starlette in /21-async/mojifinder Bumps [starlette](https://github.com/encode/starlette) from 0.25.0 to 0.40.0. - [Release notes](https://github.com/encode/starlette/releases) - [Changelog](https://github.com/encode/starlette/blob/master/docs/release-notes.md) - [Commits](https://github.com/encode/starlette/compare/0.25.0...0.40.0) --- updated-dependencies: - dependency-name: starlette dependency-version: 0.40.0 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- 21-async/mojifinder/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/21-async/mojifinder/requirements.txt b/21-async/mojifinder/requirements.txt index 4a1c97a..72231bf 100644 --- a/21-async/mojifinder/requirements.txt +++ b/21-async/mojifinder/requirements.txt @@ -2,6 +2,6 @@ click==7.1.2 fastapi==0.65.2 h11==0.12.0 pydantic==1.8.2 -starlette==0.25.0 +starlette==0.40.0 typing-extensions==3.7.4.3 uvicorn==0.13.4 From d14c7bed18737930eebb0d398c609b4b9657753a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 May 2025 19:51:11 +0000 Subject: [PATCH 152/166] build(deps): bump certifi in /20-executors/getflags Bumps [certifi](https://github.com/certifi/python-certifi) from 2022.12.7 to 2024.7.4. - [Commits](https://github.com/certifi/python-certifi/compare/2022.12.07...2024.07.04) --- updated-dependencies: - dependency-name: certifi dependency-version: 2024.7.4 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- 20-executors/getflags/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/20-executors/getflags/requirements.txt b/20-executors/getflags/requirements.txt index 5dc5007..06da86c 100644 --- a/20-executors/getflags/requirements.txt +++ b/20-executors/getflags/requirements.txt @@ -1,5 +1,5 @@ anyio==3.3.2 -certifi==2022.12.7 +certifi==2024.7.4 charset-normalizer==2.0.6 h11==0.12.0 httpcore==0.13.7 From 8e911b19be0159c8d5ff6e36c56ddc7a799de788 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 May 2025 19:51:51 +0000 Subject: [PATCH 153/166] build(deps): bump tqdm from 4.62.3 to 4.66.3 in /20-executors/getflags Bumps [tqdm](https://github.com/tqdm/tqdm) from 4.62.3 to 4.66.3. - [Release notes](https://github.com/tqdm/tqdm/releases) - [Commits](https://github.com/tqdm/tqdm/compare/v4.62.3...v4.66.3) --- updated-dependencies: - dependency-name: tqdm dependency-version: 4.66.3 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- 20-executors/getflags/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/20-executors/getflags/requirements.txt b/20-executors/getflags/requirements.txt index 5dc5007..5412046 100644 --- a/20-executors/getflags/requirements.txt +++ b/20-executors/getflags/requirements.txt @@ -7,4 +7,4 @@ httpx==1.0.0b0 idna==3.2 rfc3986==1.5.0 sniffio==1.2.0 -tqdm==4.62.3 +tqdm==4.66.3 From 68de220bd1950731b6cd50c0a0834647bbf6bd29 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 May 2025 19:51:51 +0000 Subject: [PATCH 154/166] build(deps): bump future in /08-def-type-hints/coordinates Bumps [future](https://github.com/PythonCharmers/python-future) from 0.18.2 to 0.18.3. - [Release notes](https://github.com/PythonCharmers/python-future/releases) - [Changelog](https://github.com/PythonCharmers/python-future/blob/master/docs/changelog.rst) - [Commits](https://github.com/PythonCharmers/python-future/compare/v0.18.2...v0.18.3) --- updated-dependencies: - dependency-name: future dependency-version: 0.18.3 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- 08-def-type-hints/coordinates/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/08-def-type-hints/coordinates/requirements.txt b/08-def-type-hints/coordinates/requirements.txt index fb11094..7b7b424 100644 --- a/08-def-type-hints/coordinates/requirements.txt +++ b/08-def-type-hints/coordinates/requirements.txt @@ -1,2 +1,2 @@ geolib==1.0.7 -future==0.18.2 +future==0.18.3 From 74c52779b4fc88a217a96e0ec5a1c0029516096a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 May 2025 19:53:20 +0000 Subject: [PATCH 155/166] build(deps): bump h11 from 0.12.0 to 0.16.0 in /20-executors/getflags Bumps [h11](https://github.com/python-hyper/h11) from 0.12.0 to 0.16.0. - [Commits](https://github.com/python-hyper/h11/compare/v0.12.0...v0.16.0) --- updated-dependencies: - dependency-name: h11 dependency-version: 0.16.0 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- 20-executors/getflags/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/20-executors/getflags/requirements.txt b/20-executors/getflags/requirements.txt index 464188b..6d74412 100644 --- a/20-executors/getflags/requirements.txt +++ b/20-executors/getflags/requirements.txt @@ -1,7 +1,7 @@ anyio==3.3.2 certifi==2024.7.4 charset-normalizer==2.0.6 -h11==0.12.0 +h11==0.16.0 httpcore==0.13.7 httpx==1.0.0b0 idna==3.2 From ab8a774924029233cfe1f6116d7c7a838c3485c7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 May 2025 19:55:09 +0000 Subject: [PATCH 156/166] build(deps): bump pydantic from 1.8.2 to 1.10.13 in /21-async/mojifinder Bumps [pydantic](https://github.com/pydantic/pydantic) from 1.8.2 to 1.10.13. - [Release notes](https://github.com/pydantic/pydantic/releases) - [Changelog](https://github.com/pydantic/pydantic/blob/main/HISTORY.md) - [Commits](https://github.com/pydantic/pydantic/compare/v1.8.2...v1.10.13) --- updated-dependencies: - dependency-name: pydantic dependency-version: 1.10.13 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- 21-async/mojifinder/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/21-async/mojifinder/requirements.txt b/21-async/mojifinder/requirements.txt index 72231bf..f7de911 100644 --- a/21-async/mojifinder/requirements.txt +++ b/21-async/mojifinder/requirements.txt @@ -1,7 +1,7 @@ click==7.1.2 fastapi==0.65.2 h11==0.12.0 -pydantic==1.8.2 +pydantic==1.10.13 starlette==0.40.0 typing-extensions==3.7.4.3 uvicorn==0.13.4 From 1bd4b1e7be896069225e72ac89303a26a8be46f3 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Wed, 21 May 2025 12:00:57 -0300 Subject: [PATCH 157/166] documentando FPY.LI.htacess --- links/README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/links/README.md b/links/README.md index 6da595c..6c9cfa8 100644 --- a/links/README.md +++ b/links/README.md @@ -23,6 +23,7 @@ Please report broken links as bugs in the [`FPY.LI.htaccess`](FPY.LI.htaccess) f Also, feel free to send pull requests with fixes to that file. When I accept a PR, I will redeploy it to `fpy.li/.htaccess`. + ## Details Almost all URLs in the book are replaced with shortened versions like @@ -37,4 +38,8 @@ Exceptions: - URLs with `oreilly` in them are unchanged; - `fluentpython.com` URL (with no path) is unchanged; +The `custom.htacess` file contains the top redirects, which have custom names. +`FPY.LI.htaccess` has the same content, plus numbered URLs generated +from the links in each chapter in the book. + The `FPY.LI.htaccess` is deployed at the root folder in `http://fpy.li`. From 3d5588b75ee44ce650b82b87922bc40b6ea6615c Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Thu, 22 May 2025 01:11:34 -0300 Subject: [PATCH 158/166] gerador de URLs curtas --- links/FPY.LI.htaccess | 1074 +---------------------------------------- links/README.md | 15 +- links/custom.htaccess | 956 ++++++++++++++++++++++++++++++++++++ links/short.htaccess | 1 + links/short.py | 81 ++++ 5 files changed, 1051 insertions(+), 1076 deletions(-) create mode 100644 links/short.htaccess create mode 100755 links/short.py diff --git a/links/FPY.LI.htaccess b/links/FPY.LI.htaccess index cc779db..7338157 100644 --- a/links/FPY.LI.htaccess +++ b/links/FPY.LI.htaccess @@ -1,1072 +1,2 @@ -ErrorDocument 404 /404.html - -# main resources -RedirectTemp /book https://www.oreilly.com/library/view/fluent-python-2nd/9781492056348/ -RedirectTemp /code https://github.com/fluentpython/example-code-2e -RedirectTemp /home https://www.fluentpython.com/ - -# URLs mentioned at least three times -RedirectTemp /bisect https://www.fluentpython.com/extra/ordered-sequences-with-bisect/ -RedirectTemp /cardxvi https://www.python.org/dev/peps/pep-0484/#the-numeric-tower -RedirectTemp /collec https://docs.python.org/3/library/collections.html -RedirectTemp /dask https://dask.org/ -RedirectTemp /dtmodel https://docs.python.org/3/reference/datamodel.html -RedirectTemp /descr101 https://www.python.org/download/releases/2.2.3/descrintro/ -RedirectTemp /descrhow https://docs.python.org/3/howto/descriptor.html -RedirectTemp /doctest https://docs.python.org/3/library/doctest.html -RedirectTemp /effectpy https://effectivepython.com/ -RedirectTemp /fmtspec https://docs.python.org/3/library/string.html#formatspec -RedirectTemp /gunicorn https://gunicorn.org/ -RedirectTemp /hashint https://www.fluentpython.com/extra/internals-of-sets-and-dicts/ -RedirectTemp /hattingh https://www.oreilly.com/library/view/using-asyncio-in/9781492075325/ -RedirectTemp /httpx https://www.python-httpx.org/ -RedirectTemp /initvar https://docs.python.org/3/library/dataclasses.html#init-only-variables -RedirectTemp /mypy https://mypy.readthedocs.io/en/stable/ -RedirectTemp /norvigdp http://norvig.com/design-patterns/ -RedirectTemp /nsphere https://en.wikipedia.org/wiki/N-sphere -RedirectTemp /oldcoro https://www.fluentpython.com/extra/classic-coroutines/ -RedirectTemp /pandas https://pandas.pydata.org/ -RedirectTemp /pycook3 https://www.oreilly.com/library/view/python-cookbook-3rd/9781449357337/ -RedirectTemp /pynut3 https://www.oreilly.com/library/view/python-in-a/9781491913833/ -RedirectTemp /pypydif https://doc.pypy.org/en/latest/cpython_differences.html#subclasses-of-built-in-types -RedirectTemp /shed4051 https://github.com/python/typeshed/issues/4051 -RedirectTemp /specattr https://docs.python.org/3/library/stdtypes.html#special-attributes -RedirectTemp /typecoro https://docs.python.org/3.10/library/typing.html#typing.Coroutine -RedirectTemp /typing https://docs.python.org/3/library/typing.html -RedirectTemp /weakref https://www.fluentpython.com/extra/weak-references/ - -# URL added during QA of the Second Edition -RedirectTemp /bdfl https://www.artima.com/weblogs/viewpost.jsp?thread=235725 - -# Python Enhancement Proposals -RedirectTemp /pep218 https://www.python.org/dev/peps/pep-0218/ -RedirectTemp /pep227 https://www.python.org/dev/peps/pep-0227/ -RedirectTemp /pep255 https://www.python.org/dev/peps/pep-0255/ -RedirectTemp /pep342 https://www.python.org/dev/peps/pep-0342/ -RedirectTemp /pep343 https://www.python.org/dev/peps/pep-0343/ -RedirectTemp /pep357 https://www.python.org/dev/peps/pep-0357/ -RedirectTemp /pep362 https://www.python.org/dev/peps/pep-0362/ -RedirectTemp /pep371 https://www.python.org/dev/peps/pep-0371/ -RedirectTemp /pep380 https://www.python.org/dev/peps/pep-0380/ -RedirectTemp /pep393 https://www.python.org/dev/peps/pep-0393/ -RedirectTemp /pep412 https://www.python.org/dev/peps/pep-0412/ -RedirectTemp /pep442 https://www.python.org/dev/peps/pep-0442/ -RedirectTemp /pep443 https://www.python.org/dev/peps/pep-0443/ -RedirectTemp /pep448 https://www.python.org/dev/peps/pep-0448/ -RedirectTemp /pep455 https://www.python.org/dev/peps/pep-0455/ -RedirectTemp /pep456 https://www.python.org/dev/peps/pep-0456/ -RedirectTemp /pep461 https://www.python.org/dev/peps/pep-0461/ -RedirectTemp /pep465 https://www.python.org/dev/peps/pep-0465/ -RedirectTemp /pep467 https://www.python.org/dev/peps/pep-0467/ -RedirectTemp /pep482 https://www.python.org/dev/peps/pep-0482/ -RedirectTemp /pep483 https://www.python.org/dev/peps/pep-0483/ -RedirectTemp /pep484 https://www.python.org/dev/peps/pep-0484/ -RedirectTemp /pep487 https://www.python.org/dev/peps/pep-0487/ -RedirectTemp /pep492 https://www.python.org/dev/peps/pep-0492/ -RedirectTemp /pep519 https://www.python.org/dev/peps/pep-0519/ -RedirectTemp /pep525 https://www.python.org/dev/peps/pep-0525/ -RedirectTemp /pep526 https://www.python.org/dev/peps/pep-0526/ -RedirectTemp /pep528 https://www.python.org/dev/peps/pep-0528/ -RedirectTemp /pep529 https://www.python.org/dev/peps/pep-0529/ -RedirectTemp /pep530 https://www.python.org/dev/peps/pep-0530/ -RedirectTemp /pep544 https://www.python.org/dev/peps/pep-0544/ -RedirectTemp /pep554 https://www.python.org/dev/peps/pep-0554/ -RedirectTemp /pep557 https://www.python.org/dev/peps/pep-0557/ -RedirectTemp /pep560 https://www.python.org/dev/peps/pep-0560/ -RedirectTemp /pep561 https://www.python.org/dev/peps/pep-0561/ -RedirectTemp /pep563 https://www.python.org/dev/peps/pep-0563/ -RedirectTemp /pep570 https://www.python.org/dev/peps/pep-0570/ -RedirectTemp /pep572 https://www.python.org/dev/peps/pep-0572/ -RedirectTemp /pep584 https://www.python.org/dev/peps/pep-0584/ -RedirectTemp /pep585 https://www.python.org/dev/peps/pep-0585/ -RedirectTemp /pep586 https://www.python.org/dev/peps/pep-0586/ -RedirectTemp /pep589 https://www.python.org/dev/peps/pep-0589/ -RedirectTemp /pep591 https://www.python.org/dev/peps/pep-0591/ -RedirectTemp /pep593 https://www.python.org/dev/peps/pep-0593/ -RedirectTemp /pep604 https://www.python.org/dev/peps/pep-0604/ -RedirectTemp /pep612 https://www.python.org/dev/peps/pep-0612/ -RedirectTemp /pep613 https://www.python.org/dev/peps/pep-0613/ -RedirectTemp /pep616 https://www.python.org/dev/peps/pep-0616/ -RedirectTemp /pep617 https://www.python.org/dev/peps/pep-0617/ -RedirectTemp /pep618 https://www.python.org/dev/peps/pep-0618/ -RedirectTemp /pep634 https://www.python.org/dev/peps/pep-0634/ -RedirectTemp /pep635 https://www.python.org/dev/peps/pep-0635/ -RedirectTemp /pep636 https://www.python.org/dev/peps/pep-0636/ -RedirectTemp /pep638 https://www.python.org/dev/peps/pep-0638/ -RedirectTemp /pep645 https://www.python.org/dev/peps/pep-0645/ -RedirectTemp /pep646 https://www.python.org/dev/peps/pep-0646/ -RedirectTemp /pep647 https://www.python.org/dev/peps/pep-0647/ -RedirectTemp /pep649 https://www.python.org/dev/peps/pep-0649/ -RedirectTemp /pep654 https://www.python.org/dev/peps/pep-0654/ -RedirectTemp /pep655 https://www.python.org/dev/peps/pep-0655/ -RedirectTemp /pep661 https://www.python.org/dev/peps/pep-0661/ -RedirectTemp /pep3099 https://www.python.org/dev/peps/pep-3099/ -RedirectTemp /pep3102 https://www.python.org/dev/peps/pep-3102/ -RedirectTemp /pep3104 https://www.python.org/dev/peps/pep-3104/ -RedirectTemp /pep3106 https://www.python.org/dev/peps/pep-3106/ -RedirectTemp /pep3107 https://www.python.org/dev/peps/pep-3107/ -RedirectTemp /pep3115 https://www.python.org/dev/peps/pep-3115/ -RedirectTemp /pep3118 https://www.python.org/dev/peps/pep-3118/ -RedirectTemp /pep3119 https://www.python.org/dev/peps/pep-3119/ -RedirectTemp /pep3129 https://www.python.org/dev/peps/pep-3129/ -RedirectTemp /pep3132 https://www.python.org/dev/peps/pep-3132/ -RedirectTemp /pep3141 https://www.python.org/dev/peps/pep-3141/ -RedirectTemp /pep3148 https://www.python.org/dev/peps/pep-3148/ -RedirectTemp /pep3155 https://www.python.org/dev/peps/pep-3155/ -RedirectTemp /pep3333 https://www.python.org/dev/peps/pep-3333/ - -# Remaining URLs by chapter - -############################################################ p -RedirectTemp /p-1 https://mail.python.org/pipermail/python-list/2002-December/134521.html -RedirectTemp /p-2 https://docs.python.org/3.10/tutorial/ -RedirectTemp /p-3 https://docs.python.org/3/tutorial/ -RedirectTemp /p-4 https://www.oreilly.com/library/view/fluent-python-2nd/9781492056348/ -RedirectTemp /p-5 https://www.oreilly.com/online-learning/try-now.html -RedirectTemp /p-6 https://www.oreilly.com/library/view/fluent-python-2nd/9781492056348/ -RedirectTemp /p-7 https://stackoverflow.com/users/95810/alex-martelli -RedirectTemp /p-8 https://pythonpro.com.br -RedirectTemp /p-9 https://groups.google.com/g/python-brasil -RedirectTemp /p-10 https://www.coffeelab.com.br/ -RedirectTemp /p-11 https://garoa.net.br/wiki/P%C3%A1gina_principal -############################################################ a -RedirectTemp /a-1 https://groups.google.com/forum/#!topic/python-tulip/Y4bhLNbKs74 -RedirectTemp /a-2 https://docs.python.org/3/library/asyncio-eventloop.html#executor -RedirectTemp /a-3 https://www.youtube.com/watch?v=x-kB2o8sd5c -RedirectTemp /a-4 https://www.youtube.com/watch?v=OSGv2VnC0go -RedirectTemp /a-5 https://mail.python.org/pipermail/python-ideas/2015-March/032557.html -RedirectTemp /a-6 https://pypi.org/project/pep8/ -RedirectTemp /a-7 https://pypi.org/project/flake8/ -RedirectTemp /a-8 https://pypi.org/project/pyflakes/ -RedirectTemp /a-9 https://pypi.org/project/mccabe/ -RedirectTemp /a-10 https://google.github.io/styleguide/pyguide.html -RedirectTemp /a-11 https://flask.palletsprojects.com/en/1.1.x/styleguide/ -RedirectTemp /a-12 https://docs.python-guide.org/ -RedirectTemp /a-13 https://david.goodger.org/projects/pycon/2007/idiomatic/handout.html -RedirectTemp /a-14 https://docs.mongodb.com/manual/about/#about-the-documentation-process -RedirectTemp /a-15 https://blog.startifact.com/posts/older/what-is-pythonic.html -RedirectTemp /a-16 https://mail.python.org/pipermail/tutor/2003-October/thread.html#25930 -RedirectTemp /a-17 https://mail.python.org/pipermail/python-list/2003-April/192027.html -RedirectTemp /a-18 https://www.python.org/doc/essays/ -############################################################ 01 -RedirectTemp /1-1 http://hugunin.net/story_of_jython.html -RedirectTemp /1-2 https://www.oreilly.com/library/view/jython-essentials/9781449397364/ -RedirectTemp /1-3 https://docs.python.org/3/reference/lexical_analysis.html#reserved-classes-of-identifiers -RedirectTemp /1-4 https://docs.python.org/3.10/library/string.html#format-string-syntax -RedirectTemp /1-5 https://stackoverflow.com/questions/1436703/what-is-the-difference-between-str-and-repr -RedirectTemp /1-6 https://docs.python.org/3/library/stdtypes.html#truth -RedirectTemp /1-7 https://docs.python.org/3/tutorial/controlflow.html#unpacking-argument-lists -RedirectTemp /1-8 https://www.python.org/doc/humor/#the-zen-of-python -RedirectTemp /1-9 https://stackoverflow.com/users/95810/alex-martelli -RedirectTemp /1-10 https://en.wikipedia.org/wiki/Object_model -RedirectTemp /1-11 https://www.dourish.com/goodies/jargon.html -RedirectTemp /1-12 https://zopeinterface.readthedocs.io/en/latest/ -RedirectTemp /1-13 https://plone.org/ -############################################################ 02 -RedirectTemp /2-1 https://github.com/fluentpython/example-code-2e/blob/master/02-array-seq/listcomp_speed.py -RedirectTemp /2-2 https://www.python.org/dev/peps/pep-3132/ -RedirectTemp /2-3 https://stackoverflow.com/questions/68630/are-tuples-more-efficient-than-lists-in-python/22140115#22140115 -RedirectTemp /2-4 https://docs.python.org/3/whatsnew/3.5.html#pep-448-additional-unpacking-generalizations -RedirectTemp /2-5 https://docs.python.org/3/whatsnew/3.5.html#pep-448-additional-unpacking-generalizations -RedirectTemp /2-6 https://docs.python.org/3.10/whatsnew/3.10.html#pep-634-structural-pattern-matching -RedirectTemp /2-7 https://docs.python.org/3.10/whatsnew/3.10.html -RedirectTemp /2-8 https://en.wikipedia.org/wiki/Switch_statement#Fallthrough -RedirectTemp /2-9 https://en.wikipedia.org/wiki/Dangling_else -RedirectTemp /2-10 https://github.com/gvanrossum/patma/blob/3ece6444ef70122876fd9f0099eb9490a2d630df/EXAMPLES.md#case-6-a-very-deep-iterable-and-type-match-with-extraction -RedirectTemp /2-11 https://github.com/fluentpython/lispy/blob/main/original/norvig/lis.py -RedirectTemp /2-12 https://norvig.com/lispy.html -RedirectTemp /2-13 https://numpy.org/doc/stable/user/quickstart.html#indexing-slicing-and-iterating -RedirectTemp /2-14 https://pythontutor.com/ -RedirectTemp /2-15 https://en.wikipedia.org/wiki/Fluent_interface -RedirectTemp /2-16 https://docs.python.org/3/library/bisect.html#bisect.insort -RedirectTemp /2-17 https://stackoverflow.com/questions/4845418/when-should-a-memoryview-be-used/ -RedirectTemp /2-18 https://www.fluentpython.com/extra/parsing-binary-struct/ -RedirectTemp /2-19 http://www.netlib.org -RedirectTemp /2-20 https://pandas.pydata.org/ -RedirectTemp /2-21 https://scikit-learn.org/stable/ -RedirectTemp /2-22 https://docs.python.org/3/howto/sorting.html -RedirectTemp /2-23 https://www.python.org/dev/peps/pep-3132/ -RedirectTemp /2-24 https://bugs.python.org/issue2292 -RedirectTemp /2-25 https://docs.python.org/3.10/whatsnew/3.10.html#pep-634-structural-pattern-matching -RedirectTemp /2-26 https://docs.python.org/3.10/whatsnew/3.10.html -RedirectTemp /2-27 https://www.python.org/dev/peps/pep-0636/#appendix-a-quick-intro -RedirectTemp /2-28 https://eli.thegreenplace.net/2011/11/28/less-copies-in-python-with-the-buffer-protocol-and-memoryviews/ -RedirectTemp /2-29 https://jakevdp.github.io/PythonDataScienceHandbook/ -RedirectTemp /2-30 https://www.oreilly.com/library/view/python-for-data/9781491957653/ -RedirectTemp /2-31 https://www.labri.fr/perso/nrougier/from-python-to-numpy/ -RedirectTemp /2-32 https://www.cs.utexas.edu/users/EWD/transcriptions/EWD08xx/EWD831.html -RedirectTemp /2-33 http://www.fonts101.com/fonts/view/Uncategorized/34398/Dijkstra -RedirectTemp /2-34 https://docs.python.org/3/reference/datamodel.html#objects-values-and-types -RedirectTemp /2-35 https://en.wikipedia.org/wiki/Timsort -RedirectTemp /2-36 http://www.groklaw.net/pdf3/OraGoogle-1202.pdf -RedirectTemp /2-37 https://www.python.org/doc/humor/#id9 -############################################################ 03 -RedirectTemp /3-1 https://www.python.org/dev/peps/pep-0584/#motivation -RedirectTemp /3-2 https://docs.python.org/3.10/c-api/typeobj.html#Py_TPFLAGS_MAPPING -RedirectTemp /3-3 https://docs.python.org/3/glossary.html#term-hashable -RedirectTemp /3-4 https://docs.python.org/3/glossary.html#term-hashable -RedirectTemp /3-5 http://www.aleax.it/Python/accu04_Relearn_Python_alex.pdf -RedirectTemp /3-6 https://github.com/pingo-io/pingo-py -RedirectTemp /3-7 https://github.com/fluentpython/example-code-2e/blob/master/03-dict-set/missing.py -RedirectTemp /3-8 https://docs.python.org/3/library/collections.html#collections.ChainMap -RedirectTemp /3-9 https://docs.python.org/3/library/collections.html#collections.Counter -RedirectTemp /3-10 https://docs.python.org/3/library/shelve.html -RedirectTemp /3-11 https://docs.python.org/3/library/dbm.html -RedirectTemp /3-12 https://docs.python.org/3/library/pickle.html -RedirectTemp /3-13 https://nedbatchelder.com/blog/202006/pickles_nine_flaws.html -RedirectTemp /3-14 https://github.com/python/cpython/blob/0bbf30e2b910bc9c5899134ae9d73a8df968da35/Lib/_collections_abc.py#L813 -RedirectTemp /3-15 https://mail.python.org/pipermail/python-dev/2015-May/140003.html -RedirectTemp /3-16 https://bugs.python.org/issue18986 -RedirectTemp /3-17 https://github.com/fluentpython/example-code-2e/blob/master/03-dict-set/transformdict.py -RedirectTemp /3-18 http://gandenberger.org/2018/03/10/ordered-dicts-vs-ordereddict/ -RedirectTemp /3-19 https://www.pypy.org/ -RedirectTemp /3-20 https://morepypy.blogspot.com/2015/01/faster-more-memory-efficient-and-more.html -RedirectTemp /3-21 https://www.npopov.com/2014/12/22/PHPs-new-hashtable-implementation.html -RedirectTemp /3-22 https://www.youtube.com/watch?v=66P5FMkWoVU -RedirectTemp /3-23 https://pyvideo.org/video/276/the-mighty-dictionary-55/ -RedirectTemp /3-24 https://www.youtube.com/watch?v=p33CVV29OG8 -RedirectTemp /3-25 https://docs.python.org/3/whatsnew/3.6.html#new-dict-implementation -RedirectTemp /3-26 https://github.com/python/cpython/blob/cf7eaa4617295747ee5646c4e2b7e7a16d7c64ab/Objects/dictobject.c -RedirectTemp /3-27 https://github.com/python/cpython/blob/cf7eaa4617295747ee5646c4e2b7e7a16d7c64ab/Objects/dictnotes.txt -RedirectTemp /3-28 https://www.youtube.com/watch?v=tGAngdU_8D8 -RedirectTemp /3-29 https://speakerdeck.com/ramalho/python-set-practice-at-pycon -RedirectTemp /3-30 https://github.com/standupdev/uintset -RedirectTemp /3-31 https://spectrum.ieee.org/hans-peter-luhn-and-the-birth-of-the-hashing-algorithm -RedirectTemp /3-32 http://www.json.org/fatfree.html -RedirectTemp /3-33 https://twitter.com/mitsuhiko/status/1229385843585974272 -############################################################ 04 -RedirectTemp /4-1 https://www.slideshare.net/fischertrav/character-encoding-unicode-how-to-with-dignity-33352863 -RedirectTemp /4-2 https://pyvideo.org/video/2625/character-encoding-and-unicode-in-python/ -RedirectTemp /4-3 https://www.fluentpython.com/extra/parsing-binary-struct/ -RedirectTemp /4-4 https://www.fluentpython.com/extra/multi-character-emojis/ -RedirectTemp /4-5 https://w3techs.com/technologies/overview/character_encoding -RedirectTemp /4-6 https://docs.python.org/3/library/codecs.html#codecs.register_error -RedirectTemp /4-7 https://docs.python.org/3/library/stdtypes.html#str.isascii -RedirectTemp /4-8 https://pypi.org/project/chardet/ -RedirectTemp /4-9 https://docs.python.org/3/library/codecs.html#encodings-and-unicode -RedirectTemp /4-10 https://nedbatchelder.com/text/unipain/unipain.html -RedirectTemp /4-11 https://devblogs.microsoft.com/commandline/windows-command-line-unicode-and-utf-8-output-text-buffer/ -RedirectTemp /4-12 https://docs.python.org/3/using/cmdline.html#envvar-PYTHONIOENCODING -RedirectTemp /4-13 https://docs.python.org/3/using/cmdline.html#envvar-PYTHONLEGACYWINDOWSSTDIO -RedirectTemp /4-14 https://docs.python.org/3/library/locale.html#locale.getpreferredencoding -RedirectTemp /4-15 http://www.w3.org/TR/charmod-norm/ -RedirectTemp /4-16 https://docs.python.org/3/library/locale.html?highlight=strxfrm#locale.strxfrm -RedirectTemp /4-17 https://pypi.org/project/pyuca/ -RedirectTemp /4-18 https://github.com/jtauber/pyuca -RedirectTemp /4-19 http://www.unicode.org/Public/UCA/6.3.0/allkeys.txt -RedirectTemp /4-20 https://pypi.org/project/PyICU/ -RedirectTemp /4-21 https://docs.python.org/3.10/library/stdtypes.html#str.isalpha -RedirectTemp /4-22 https://en.wikipedia.org/wiki/Unicode_character_property#General_Category -RedirectTemp /4-23 https://en.wikipedia.org/wiki/Unicode_character_property -RedirectTemp /4-24 https://github.com/microsoft/terminal -RedirectTemp /4-25 https://docs.python.org/3/library/unicodedata.html -RedirectTemp /4-26 https://docs.python.org/3/reference/lexical_analysis.html#string-literal-concatenation -RedirectTemp /4-27 https://docs.python.org/3/library/re.html -RedirectTemp /4-28 https://nedbatchelder.com/text/unipain.html -RedirectTemp /4-29 https://www.slideshare.net/fischertrav/character-encoding-unicode-how-to-with-dignity-33352863 -RedirectTemp /4-30 https://pyvideo.org/video/2625/character-encoding-and-unicode-in-python/ -RedirectTemp /4-31 https://regebro.wordpress.com/2011/03/23/unconfusing-unicode-what-is-unicode/ -RedirectTemp /4-32 https://docs.python.org/3/howto/unicode.html -RedirectTemp /4-33 https://diveintopython3.net/strings.html -RedirectTemp /4-34 https://diveintopython3.net/ -RedirectTemp /4-35 https://finderiko.com/python-book -RedirectTemp /4-36 https://docs.python.org/3.0/whatsnew/3.0.html#text-vs-data-instead-of-unicode-vs-8-bit -RedirectTemp /4-37 https://lucumr.pocoo.org/2013/7/2/the-updated-guide-to-unicode/ -RedirectTemp /4-38 http://python-notes.curiousefficiency.org/en/latest/python3/binary_protocols.html -RedirectTemp /4-39 http://python-notes.curiousefficiency.org/en/latest/python3/text_file_processing.html -RedirectTemp /4-40 https://docs.python.org/3/library/codecs.html#standard-encodings -RedirectTemp /4-41 https://github.com/python/cpython/blob/0bbf30e2b910bc9c5899134ae9d73a8df968da35/Tools/unicode/listcodecs.py -RedirectTemp /4-42 https://www.oreilly.com/library/view/unicode-explained/059610121X/ -RedirectTemp /4-43 https://www.informit.com/store/unicode-demystified-a-practical-programmers-guide-to-9780201700527 -RedirectTemp /4-44 https://unicodebook.readthedocs.io/index.html -RedirectTemp /4-45 https://www.w3.org/International/wiki/Case_folding -RedirectTemp /4-46 http://www.w3.org/TR/charmod-norm/ -RedirectTemp /4-47 http://unicode.org/reports/tr15/ -RedirectTemp /4-48 http://www.unicode.org/faq/normalization.html -RedirectTemp /4-49 http://www.unicode.org/ -RedirectTemp /4-50 http://www.macchiato.com/unicode/nfc-faq -RedirectTemp /4-51 https://stories.moma.org/the-original-emoji-set-has-been-added-to-the-museum-of-modern-arts-collection-c6060e141f61?gi=c403f5a840a5 -RedirectTemp /4-52 https://emojipedia.org/ -RedirectTemp /4-53 https://blog.emojipedia.org/correcting-the-record-on-the-first-emoji-set/ -RedirectTemp /4-54 http://emojitracker.com/ -RedirectTemp /4-55 http://www.unicode.org/glossary/#plain_text -RedirectTemp /4-56 http://www.methods.co.nz/asciidoc/ -RedirectTemp /4-57 https://atlas.oreilly.com/ -############################################################ 05 -RedirectTemp /5-1 https://docs.python.org/3/library/typing.html#typing.TypedDict -RedirectTemp /5-2 https://docs.python.org/3.10/library/inspect.html#inspect.get_annotations -RedirectTemp /5-3 https://docs.python.org/3/library/typing.html#typing.get_type_hints -RedirectTemp /5-4 https://docs.python.org/3.8/library/collections.html#collections.somenamedtuple._asdict -RedirectTemp /5-5 https://www.jetbrains.com/pycharm/ -RedirectTemp /5-6 https://www.python.org/dev/peps/pep-0484/#acceptable-type-hints -RedirectTemp /5-7 https://docs.python.org/3/library/dataclasses.html#dataclasses.dataclass -RedirectTemp /5-8 https://docs.python.org/3/library/dataclasses.html#dataclasses.dataclass -RedirectTemp /5-9 https://docs.python.org/3/library/dataclasses.html -RedirectTemp /5-10 https://docs.python.org/3/library/dataclasses.html#inheritance -RedirectTemp /5-11 https://www.python.org/dev/peps/pep-0526/#class-and-instance-variable-annotations -RedirectTemp /5-12 https://dublincore.org/specifications/dublin-core/ -RedirectTemp /5-13 https://en.wikipedia.org/wiki/Dublin_Core -RedirectTemp /5-14 https://martinfowler.com/bliki/CodeSmell.html -RedirectTemp /5-15 https://martinfowler.com/books/refactoring.html -RedirectTemp /5-16 https://www.python.org/dev/peps/pep-0634/#class-patterns -RedirectTemp /5-17 https://docs.python.org/3/library/dataclasses.html -RedirectTemp /5-18 https://www.python.org/dev/peps/pep-0557/#id47 -RedirectTemp /5-19 https://www.python.org/dev/peps/pep-0557/#id48 -RedirectTemp /5-20 https://www.python.org/dev/peps/pep-0557/#id33 -RedirectTemp /5-21 https://realpython.com -RedirectTemp /5-22 https://realpython.com/python-data-classes/ -RedirectTemp /5-23 https://www.youtube.com/watch?v=T-TwcmT6Rcw -RedirectTemp /5-24 https://www.attrs.org/en/stable/ -RedirectTemp /5-25 https://glyph.twistedmatrix.com/2016/08/attrs.html -RedirectTemp /5-26 https://www.attrs.org/en/stable/why.html -RedirectTemp /5-27 https://github.com/dabeaz/cluegen -RedirectTemp /5-28 https://refactoring.guru/ -RedirectTemp /5-29 https://refactoring.guru/smells/data-class -RedirectTemp /5-30 https://web.archive.org/web/20190204130328/http://catb.org/esr/jargon/html/G/Guido.html -RedirectTemp /5-31 https://web.archive.org/web/20190211161610/http://catb.org/esr/jargon/html/index.html -RedirectTemp /5-32 https://www.attrs.org/en/stable/ -############################################################ 06 -RedirectTemp /6-1 https://www.olin.edu/faculty/profile/lynn-andrea-stein/ -RedirectTemp /6-2 https://docs.python.org/3/reference/datamodel.html#objects-values-and-types -RedirectTemp /6-3 https://pythontutor.com/ -RedirectTemp /6-4 https://docs.python.org/3/library/copy.html -RedirectTemp /6-5 https://en.wikipedia.org/wiki/Principle_of_least_astonishment -RedirectTemp /6-6 https://docs.python.org/3/reference/datamodel.html#object.%5C_%5C_del__ -RedirectTemp /6-7 https://emptysqua.re/blog/pypy-garbage-collection-and-a-deadlock/ -RedirectTemp /6-8 https://www.youtube.com/watch?v=HHFCFJSPWrI&feature=youtu.be -RedirectTemp /6-9 http://pymotw.com/3/copy/ -RedirectTemp /6-10 http://pymotw.com/3/weakref/ -RedirectTemp /6-11 https://docs.python.org/3/library/gc.html -RedirectTemp /6-12 https://devguide.python.org/garbage_collector/ -RedirectTemp /6-13 https://devguide.python.org/ -RedirectTemp /6-14 https://www.python.org/dev/peps/pep-0442/ -RedirectTemp /6-15 https://en.wikipedia.org/wiki/String_interning -RedirectTemp /6-16 https://en.wikipedia.org/wiki/Haddocks%27_Eyes -RedirectTemp /6-17 https://thp.io/2012/python-gc/python_gc_final_2012-01-22.pdf -############################################################ 07 -RedirectTemp /7-1 http://python-history.blogspot.com/2009/04/origins-of-pythons-functional-features.html -RedirectTemp /7-2 https://www.fluentpython.com/extra/function-introspection/ -RedirectTemp /7-3 https://docs.python.org/3/library/functions.html#map -RedirectTemp /7-4 https://en.wikipedia.org/wiki/Functional_programming -RedirectTemp /7-5 https://docs.python.org/3/howto/functional.html -RedirectTemp /7-6 https://docs.python.org/3/reference/datamodel.html#the-standard-type-hierarchy -RedirectTemp /7-7 https://docs.python.org/3/whatsnew/3.8.html#positional-only-parameters -RedirectTemp /7-8 https://docs.python.org/3/whatsnew/3.8.html#positional-only-parameters -RedirectTemp /7-9 https://github.com/python/cpython/blob/0bbf30e2b910bc9c5899134ae9d73a8df968da35/Lib/functools.py#L341 -RedirectTemp /7-10 https://docs.python.org/3/reference/datamodel.html#the-standard-type-hierarchy -RedirectTemp /7-11 https://docs.python.org/3/howto/functional.html -RedirectTemp /7-12 https://stackoverflow.com/questions/3252228/python-why-is-functools-partial-necessary -RedirectTemp /7-13 https://speakerdeck.com/ramalho/beyond-paradigms-berlin-edition -RedirectTemp /7-14 https://www.youtube.com/watch?v=bF3a2VYXxa0 -RedirectTemp /7-15 http://cs.brown.edu/~sk/Publications/Papers/Published/sk-teach-pl-post-linnaean/ -RedirectTemp /7-16 http://python-history.blogspot.com/2009/04/origins-of-pythons-functional-features.html -RedirectTemp /7-17 https://raw.githubusercontent.com/python/cpython/main/Misc/HISTORY -RedirectTemp /7-18 http://neopythonic.blogspot.com/2009/04/tail-recursion-elimination.html -############################################################ 08 -RedirectTemp /8-1 https://www.python.org/dev/peps/pep-0484/#non-goals -RedirectTemp /8-2 https://github.com/python/typing/issues/182 -RedirectTemp /8-3 https://github.com/python/mypy/issues/731 -RedirectTemp /8-4 https://github.com/google/pytype -RedirectTemp /8-5 https://github.com/Microsoft/pyright -RedirectTemp /8-6 https://pyre-check.org/ -RedirectTemp /8-7 https://mypy.readthedocs.io/en/stable/introduction.html -RedirectTemp /8-8 https://mypy.readthedocs.io/en/stable/config_file.html -RedirectTemp /8-9 https://pypi.org/project/flake8/ -RedirectTemp /8-10 https://pypi.org/project/blue/ -RedirectTemp /8-11 https://pypi.org/project/black/ -RedirectTemp /8-12 https://wefearchange.org/2020/11/steeringcouncil.rst.html -RedirectTemp /8-13 https://docs.python.org/3/library/collections.abc.html#collections-abstract-base-classes -RedirectTemp /8-14 https://en.wikipedia.org/wiki/Barbara_Liskov -RedirectTemp /8-15 https://en.wikipedia.org/wiki/Behavioral_subtyping -RedirectTemp /8-16 https://www.python.org/dev/peps/pep-0585/#implementation -RedirectTemp /8-17 https://docs.python.org/3/library/typing.html#module-contents -RedirectTemp /8-18 https://en.wikipedia.org/wiki/Geohash -RedirectTemp /8-19 https://en.wikipedia.org/wiki/Inverted_index -RedirectTemp /8-20 https://docs.python.org/3/library/typing.html#typing.List -RedirectTemp /8-21 https://docs.python.org/3/library/typing.html#typing.Dict -RedirectTemp /8-22 https://docs.python.org/3/library/typing.html#typing.Set -RedirectTemp /8-23 https://www.python.org/dev/peps/pep-0585/#implementation -RedirectTemp /8-24 https://docs.python.org/3/library/numbers.html -RedirectTemp /8-25 https://docs.python.org/3/library/typing.html#typing.List -RedirectTemp /8-26 https://github.com/python/typeshed -RedirectTemp /8-27 https://github.com/python/typeshed/blob/66cd36268a6a667714efaa27198a41d0d7f89477/stdlib/2and3/math.pyi#L45 -RedirectTemp /8-28 https://docs.python.org/3/library/statistics.html#statistics.mode -RedirectTemp /8-29 https://github.com/python/cpython/blob/822efa5695b5ba6c2316c1400e4e9ec2546f7ea5/Lib/statistics.py#L534 -RedirectTemp /8-30 https://github.com/python/typeshed/blob/e1e99245bb46223928eba68d4fc74962240ba5b4/stdlib/3/statistics.pyi -RedirectTemp /8-31 https://docs.python.org/3/library/statistics.html#statistics.mode -RedirectTemp /8-32 https://github.com/python/typeshed/blob/a8834fcd46339e17fc8add82b5803a1ce53d3d60/stdlib/3/statistics.pyi#L32 -RedirectTemp /8-33 https://mail.python.org/archives/list/python-dev@python.org/thread/CLVXXPQ2T2LQ5MP2Y53VVQFCXYWQJHKZ/ -RedirectTemp /8-34 https://docs.python.org/3/library/typing.html#typing.Callable -RedirectTemp /8-35 https://pypi.org/project/blue/ -RedirectTemp /8-36 https://www.python.org/dev/peps/pep-0484/#id38 -RedirectTemp /8-37 https://docs.google.com/document/d/1aXs1tpwzPjW9MdsG5dI7clNFyYayFBkcXwRDo-qvbIk/preview -RedirectTemp /8-38 https://www.oreilly.com/library/view/the-best-software/9781590595008/ -RedirectTemp /8-39 https://www.youtube.com/watch?v=YFexUDjHO6w -RedirectTemp /8-40 https://www.youtube.com/watch?v=YFexUDjHO6w&t=13m40s -RedirectTemp /8-41 https://bernat.tech/posts/the-state-of-type-hints-in-python/ -RedirectTemp /8-42 https://realpython.com/python-type-checking/ -RedirectTemp /8-43 https://cjolowicz.github.io/posts/hypermodern-python-04-typing/ -RedirectTemp /8-44 https://mypy.readthedocs.io/en/stable/index.html -RedirectTemp /8-45 https://mypy.readthedocs.io/en/stable/cheat_sheet_py3.html -RedirectTemp /8-46 https://mypy.readthedocs.io/en/stable/common_issues.html -RedirectTemp /8-47 https://github.com/typeddjango/awesome-python-typing -RedirectTemp /8-48 https://docs.python.org/3/library/functions.html#max -RedirectTemp /8-49 https://en.wikipedia.org/wiki/Linguistic_relativity -RedirectTemp /8-50 https://pypistats.org/top -RedirectTemp /8-51 https://github.com/psf/requests/issues/3855 -RedirectTemp /8-52 https://lwn.net/Articles/643399/ -RedirectTemp /8-53 https://docs.python-requests.org/en/master/api/#requests.request -RedirectTemp /8-54 https://queue.acm.org/detail.cfm?id=1039523 -############################################################ 09 -RedirectTemp /9-1 https://docs.python.org/3/library/dis.html -RedirectTemp /9-2 https://en.wikipedia.org/wiki/Memoization -RedirectTemp /9-3 https://numpy.org/doc/stable/user/basics.types.html -RedirectTemp /9-4 https://docs.python.org/3/library/functools.html#functools.singledispatch -RedirectTemp /9-5 https://github.com/GrahamDumpleton/wrapt/blob/develop/blog/README.md -RedirectTemp /9-6 https://github.com/GrahamDumpleton/wrapt/blob/develop/blog/01-how-you-implemented-your-python-decorator-is-wrong.md -RedirectTemp /9-7 https://wrapt.readthedocs.io/en/latest/ -RedirectTemp /9-8 https://www.oreilly.com/library/view/python-cookbook-3rd/9781449357337/ch09.html -RedirectTemp /9-9 https://pypi.org/project/decorator/ -RedirectTemp /9-10 https://wiki.python.org/moin/PythonDecoratorLibrary -RedirectTemp /9-11 http://web.archive.org/web/20201109032203/http://effbot.org/zone/closure.htm -RedirectTemp /9-12 https://www.python.org/dev/peps/pep-3104/ -RedirectTemp /9-13 https://www.python.org/dev/peps/pep-0227/ -RedirectTemp /9-14 https://www.python.org/dev/peps/pep-0443/ -RedirectTemp /9-15 https://www.artima.com/weblogs/viewpost.jsp?thread=101605 -RedirectTemp /9-16 https://reg.readthedocs.io/en/latest/ -RedirectTemp /9-17 https://morepath.readthedocs.io/en/latest/ -RedirectTemp /9-18 https://www.gnu.org/software/emacs/manual/html_node/elisp/Dynamic-Binding.html -RedirectTemp /9-19 http://www.paulgraham.com/rootsoflisp.html -RedirectTemp /9-20 http://www-formal.stanford.edu/jmc/recursive/recursive.html -RedirectTemp /9-21 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this -############################################################ 10 -RedirectTemp /10-1 https://en.wikipedia.org/wiki/Software_design_pattern -RedirectTemp /10-2 https://en.wikipedia.org/wiki/Iterator_pattern -RedirectTemp /10-3 https://github.com/python/mypy/issues/9397 -RedirectTemp /10-4 http://www.norvig.com/design-patterns/index.htm -RedirectTemp /10-5 https://pyvideo.org/video/1110/python-design-patterns/ -RedirectTemp /10-6 http://www.aleax.it/gdd_pydp.pdf -RedirectTemp /10-7 https://perl.plover.com/yak/design/ -RedirectTemp /10-8 https://en.wikipedia.org/wiki/Turtles_all_the_way_down -############################################################ 11 -RedirectTemp /11-1 https://blog.startifact.com/posts/older/what-is-pythonic.html -RedirectTemp /11-2 https://julien.danjou.info/guide-python-static-class-abstract-methods/ -RedirectTemp /11-3 https://docs.python.org/3/library/string.html#formatspec -RedirectTemp /11-4 https://docs.python.org/3/reference/lexical_analysis.html#f-strings -RedirectTemp /11-5 https://docs.python.org/3/library/string.html#format-string-syntax -RedirectTemp /11-6 https://docs.python.org/3/library/string.html#formatspec -RedirectTemp /11-7 https://docs.python.org/3/reference/datamodel.html#object.__hash__ -RedirectTemp /11-8 https://web.archive.org/web/20161025185040/http://pythonpaste.org/StyleGuide.html -RedirectTemp /11-9 https://docs.python.org/3/tutorial/modules.html#more-on-modules -RedirectTemp /11-10 https://docs.python.org/3/library/gettext.html#gettext.NullTranslations -RedirectTemp /11-11 https://github.com/fluentpython/example-code-2e/blob/master/11-pythonic-obj/mem_test.py -RedirectTemp /11-12 https://docs.python.org/3/reference/datamodel.html#basic-customization -RedirectTemp /11-13 http://esug.org/data/HistoricalDocuments/TheSmalltalkReport/ST07/04wo.pdf -RedirectTemp /11-14 https://www.artima.com/articles/the-simplest-thing-that-could-possibly-work#part3 -RedirectTemp /11-15 https://docs.oracle.com/javase/tutorial/essential/environment/security.html -RedirectTemp /11-16 https://github.com/fluentpython/example-code-2e/blob/master/11-pythonic-obj/private/Expose.java -RedirectTemp /11-17 https://docs.oracle.com/javase/tutorial/essential/environment/security.html -############################################################ 12 -RedirectTemp /12-1 https://en.wikipedia.org/wiki/Vector_space_model -RedirectTemp /12-2 https://pypi.org/project/gensim/ -RedirectTemp /12-3 https://docs.python.org/3/library/functions.html#enumerate -RedirectTemp /12-4 https://mathworld.wolfram.com/Hypersphere.html -RedirectTemp /12-5 https://en.wikipedia.org/wiki/Fold_(higher-order_function) -RedirectTemp /12-6 https://docs.python.org/2.5/whatsnew/pep-357.html -RedirectTemp /12-7 https://docs.python.org/3/reference/datamodel.html#special-method-names -RedirectTemp /12-8 https://en.wikipedia.org/wiki/KISS_principle -RedirectTemp /12-9 https://mail.python.org/pipermail/python-list/2000-July/046184.html -RedirectTemp /12-10 https://en.wikipedia.org/wiki/Duck_typing -RedirectTemp /12-11 https://mail.python.org/mailman/listinfo/python-list -RedirectTemp /12-12 https://mail.python.org/pipermail/python-list/2003-April/218568.html -############################################################ 13 -RedirectTemp /13-1 https://docs.python.org/3/c-api/index.html -RedirectTemp /13-2 https://docs.python.org/3/c-api/sequence.html -RedirectTemp /13-3 https://github.com/python/cpython/blob/31ceccb2c77854893f3a754aca04bedd74bedb10/Lib/_collections_abc.py#L870 -RedirectTemp /13-4 https://en.wikipedia.org/wiki/Monkey_patch -RedirectTemp /13-5 https://www.gevent.org/api/gevent.monkey.html -RedirectTemp /13-6 https://docs.python.org/3/library/random.html#random.shuffle -RedirectTemp /13-7 https://docs.python.org/3/reference/datamodel.html#emulating-container-types -RedirectTemp /13-8 https://docs.python.org/3/library/collections.html#collections.namedtuple -RedirectTemp /13-9 https://github.com/python/typeshed/blob/24afb531ffd07083d6a74be917342195062f7277/stdlib/collections/__init__.pyi -RedirectTemp /13-10 https://docs.python.org/3/glossary.html#term-abstract-base-class -RedirectTemp /13-11 https://en.wikipedia.org/wiki/Duck_typing#History -RedirectTemp /13-12 http://ptgmedia.pearsoncmg.com/images/020163371x/items/item33.html -RedirectTemp /13-13 https://docs.python.org/3/library/bisect.html#bisect.bisect -RedirectTemp /13-14 https://github.com/python/cpython/blob/main/Lib/_collections_abc.py -RedirectTemp /13-15 https://github.com/python/cpython/blob/main/Lib/abc.py -RedirectTemp /13-16 https://docs.python.org/3/library/collections.abc.html#collections-abstract-base-classes -RedirectTemp /13-17 https://docs.python.org/3/library/collections.abc.html#collections.abc.Iterable -RedirectTemp /13-18 https://docs.python.org/3/library/abc.html -RedirectTemp /13-19 https://docs.python.org/dev/library/abc.html#abc.abstractmethod -RedirectTemp /13-20 https://docs.python.org/dev/library/abc.html -RedirectTemp /13-21 https://docs.python.org/3/library/os.html#os.urandom -RedirectTemp /13-22 https://github.com/python/mypy/issues/2922 -RedirectTemp /13-23 https://docs.python.org/3/library/stdtypes.html#truth -RedirectTemp /13-24 https://github.com/python/cpython/blob/0bbf30e2b910bc9c5899134ae9d73a8df968da35/Lib/_collections_abc.py -RedirectTemp /13-25 https://github.com/python/cpython/blob/0fbddb14dc03f61738af01af88e7d8aa8df07336/Lib/_collections_abc.py#L369 -RedirectTemp /13-26 https://github.com/python/cpython/blob/c0a9afe2ac1820409e6173bd1893ebee2cf50270/Lib/abc.py#L196 -RedirectTemp /13-27 https://bugs.python.org/issue31333 -RedirectTemp /13-28 https://github.com/python/cpython/blob/3635388f52b42e5280229104747962117104c453/Modules/_abc.c#L605 -RedirectTemp /13-29 https://github.com/python/cpython/blob/0fbddb14dc03f61738af01af88e7d8aa8df07336/Lib/_collections_abc.py#L881 -RedirectTemp /13-30 https://docs.python.org/3/library/typing.html#protocols -RedirectTemp /13-31 https://github.com/python/cpython/blob/3635388f52b42e5280229104747962117104c453/Lib/typing.py#L1751 -RedirectTemp /13-32 https://mail.python.org/archives/list/python-dev@python.org/thread/CLVXXPQ2T2LQ5MP2Y53VVQFCXYWQJHKZ/ -RedirectTemp /13-33 https://martinfowler.com/bliki/RoleInterface.html -RedirectTemp /13-34 https://en.wikipedia.org/wiki/Interface_segregation_principle -RedirectTemp /13-35 https://github.com/python/typeshed/blob/master/CONTRIBUTING.md -RedirectTemp /13-36 https://gist.github.com/asukakenji/ac8a05644a2e98f1d5ea8c299541fce9 -RedirectTemp /13-37 https://www.python.org/dev/peps/pep-0544/#runtime-checkable-decorator-and-narrowing-types-by-isinstance -RedirectTemp /13-38 https://www.python.org/dev/peps/pep-0544/#merging-and-extending-protocols -RedirectTemp /13-39 https://numpy.org/devdocs/user/basics.types.html -RedirectTemp /13-40 https://github.com/python/typeshed/blob/master/stdlib/statistics.pyi -RedirectTemp /13-41 https://bugs.python.org/issue41974 -RedirectTemp /13-42 https://glyph.twistedmatrix.com/2020/07/new-duck.html -RedirectTemp /13-43 https://glyph.twistedmatrix.com/2021/03/interfaces-and-protocols.html -RedirectTemp /13-44 https://plone.org/ -RedirectTemp /13-45 https://trypyramid.com/ -RedirectTemp /13-46 https://twistedmatrix.com/trac/ -RedirectTemp /13-47 https://www.artima.com/articles/contracts-in-python -RedirectTemp /13-48 https://martinfowler.com/bliki/DynamicTyping.html -RedirectTemp /13-49 https://martinfowler.com/bliki/RoleInterface.html -RedirectTemp /13-50 https://mypy.readthedocs.io/en/stable/protocols.html -RedirectTemp /13-51 https://pymotw.com/3/abc/index.html -RedirectTemp /13-52 https://www.python.org/dev/peps/pep-3119/ -RedirectTemp /13-53 https://www.python.org/dev/peps/pep-3141/ -RedirectTemp /13-54 https://docs.python.org/3/library/numbers.html -RedirectTemp /13-55 https://github.com/python/mypy/issues/3186 -RedirectTemp /13-56 https://stackoverflow.com/questions/69334475/how-to-hint-at-number-types-i-e-subclasses-of-number-not-numbers-themselv/69383462#69383462 -RedirectTemp /13-57 https://github.com/python/mypy/issues/3186 -RedirectTemp /13-58 https://martinfowler.com/articles/lean-inception/ -RedirectTemp /13-59 https://martinfowler.com -RedirectTemp /13-60 https://www.jetbrains.com/pycharm/ -RedirectTemp /13-61 https://wingware.com/ -RedirectTemp /13-62 https://code.visualstudio.com/ -############################################################ 14 -RedirectTemp /14-1 http://worrydream.com/EarlyHistoryOfSmalltalk/ -RedirectTemp /14-2 https://docs.python.org/3/tutorial/classes.html -RedirectTemp /14-3 https://docs.python.org/3/library/collections.html#ordereddict-examples-and-recipes -RedirectTemp /14-4 https://discuss.python.org/t/is-it-time-to-deprecate-unbound-super-methods/1833 -RedirectTemp /14-5 https://doc.pypy.org/en/latest/cpython_differences.html#subclasses-of-built-in-types -RedirectTemp /14-6 https://docs.python.org/3/library/collections.html -RedirectTemp /14-7 https://github.com/fluentpython/example-code-2e/blob/master/14-inheritance/strkeydict_dictsub.py -RedirectTemp /14-8 https://doc.pypy.org/en/latest/cpython_differences.html#subclasses-of-built-in-types -RedirectTemp /14-9 https://en.wikipedia.org/wiki/Breadth-first_search -RedirectTemp /14-10 https://www.python.org/download/releases/2.3/mro/ -RedirectTemp /14-11 https://github.com/fluentpython/example-code-2e/blob/master/14-inheritance/uppermixin.py -RedirectTemp /14-12 https://docs.oracle.com/javase/tutorial/java/IandI/defaultmethods.html -RedirectTemp /14-13 https://docs.python.org/3/library/collections.abc.html -RedirectTemp /14-14 https://github.com/python/cpython/blob/8ece98a7e418c3c68a4c61bc47a2d0931b59a889/Lib/collections/__init__.py#L1084 -RedirectTemp /14-15 https://docs.python.org/3/library/http.server.html -RedirectTemp /14-16 https://github.com/python/cpython/blob/17c23167942498296f0bdfffe52e72d53d66d693/Lib/http/server.py#L144 -RedirectTemp /14-17 https://github.com/python/cpython/blob/699ee016af5736ffc80f68359617611a22b72943/Lib/socketserver.py#L664 -RedirectTemp /14-18 https://docs.python.org/3/library/socketserver.html#socketserver.ForkingMixIn -RedirectTemp /14-19 https://docs.python.org/3/library/os.html#os.fork -RedirectTemp /14-20 https://en.wikipedia.org/wiki/POSIX -RedirectTemp /14-21 http://ccbv.co.uk/ -RedirectTemp /14-22 https://github.com/django/django/tree/main/django/views/generic -RedirectTemp /14-23 https://en.wikipedia.org/wiki/Template_method_pattern -RedirectTemp /14-24 https://docs.python.org/3/library/tkinter.html -RedirectTemp /14-25 https://docs.python.org/3/library/tkinter.ttk.html -RedirectTemp /14-26 https://docs.oracle.com/javase/10/docs/api/java/awt/package-tree.html -RedirectTemp /14-27 https://docs.oracle.com/javase/10/docs/api/javax/swing/package-tree.html -RedirectTemp /14-28 https://squeak.org/ -RedirectTemp /14-29 https://github.com/django/django/blob/b64db05b9cedd96905d637a2d824cbbf428e40e7/django/views/generic/list.py#L194 -RedirectTemp /14-30 https://github.com/python/cpython/blob/8ed183391241f0c73e7ba7f42b1d49fc02985f7b/Lib/tkinter/__init__.py#L2618 -RedirectTemp /14-31 https://docs.python.org/3/library/socketserver.html -RedirectTemp /14-32 https://docs.python.org/3/library/socketserver.html#socketserver.BaseServer -RedirectTemp /14-33 https://github.com/python/cpython/blob/699ee016af5736ffc80f68359617611a22b72943/Lib/socketserver.py#L153 -RedirectTemp /14-34 https://docs.python.org/3/library/typing.html#typing.final -RedirectTemp /14-35 https://docs.python.org/3/library/typing.html#typing.Final -RedirectTemp /14-36 https://docs.python.org/3/library/collections.abc.html -RedirectTemp /14-37 https://hynek.me/articles/python-subclassing-redux/ -RedirectTemp /14-38 https://www.oreilly.com/library/view/python-cookbook-3rd/9781449357337/ch08.html#super -RedirectTemp /14-39 https://rhettinger.wordpress.com/2011/05/26/super-considered-super/ -RedirectTemp /14-40 https://fuhm.net/super-harmful/ -RedirectTemp /14-41 https://stackoverflow.com/questions/30190185/how-to-use-super-with-one-argument/30190341#30190341 -RedirectTemp /14-42 https://www.artima.com/weblogs/viewpost.jsp?thread=246488 -RedirectTemp /14-43 https://www.artima.com/weblogs/viewpost.jsp?thread=281127 -RedirectTemp /14-44 https://www.artima.com/weblogs/viewpost.jsp?thread=246341 -RedirectTemp /14-45 https://www.artima.com/weblogs/viewpost.jsp?thread=246483 -RedirectTemp /14-46 https://www.artima.com/weblogs/viewpost.jsp?thread=236275 -RedirectTemp /14-47 https://www.artima.com/weblogs/viewpost.jsp?thread=236278 -RedirectTemp /14-48 https://www.artima.com/weblogs/viewpost.jsp?thread=237121 -RedirectTemp /14-49 https://python-patterns.guide/gang-of-four/composition-over-inheritance/ -RedirectTemp /14-50 https://python-patterns.guide/ -RedirectTemp /14-51 https://www.youtube.com/watch?v=3MNVP9-hglc -RedirectTemp /14-52 http://worrydream.com/EarlyHistoryOfSmalltalk/ -RedirectTemp /14-53 https://en.wikipedia.org/wiki/Polymorphism_(computer_science) -############################################################ 15 -RedirectTemp /15-1 https://www.youtube.com/watch?v=csL8DLXGNlU&t=92m5s -RedirectTemp /15-2 https://github.com/python/typeshed/blob/a8834fcd46339e17fc8add82b5803a1ce53d3d60/stdlib/2and3/builtins.pyi#L1434 -RedirectTemp /15-3 https://github.com/python/typeshed/blob/a8834fcd46339e17fc8add82b5803a1ce53d3d60/stdlib/2and3/builtins.pyi -RedirectTemp /15-4 https://twitter.com/gwidion/status/1265384692464967680 -RedirectTemp /15-5 https://pypi.org/project/pydantic/ -RedirectTemp /15-6 https://google.github.io/pytype/faq.html -RedirectTemp /15-7 https://google.github.io/pytype/faq.html -RedirectTemp /15-8 https://lxml.de/ -RedirectTemp /15-9 https://docs.python.org/3/library/xml.etree.elementtree.html -RedirectTemp /15-10 https://mypy.readthedocs.io/en/stable/common_issues.html -RedirectTemp /15-11 https://mypy.readthedocs.io/en/stable/common_issues.html#types-of-empty-collections -RedirectTemp /15-12 https://github.com/python/typing/issues/182 -RedirectTemp /15-13 https://pypi.org/project/pydantic/ -RedirectTemp /15-14 https://mypy.readthedocs.io/en/stable/type_narrowing.html#casts -RedirectTemp /15-15 https://github.com/python/cpython/blob/bee66d3cb98e740f9d8057eb7f503122052ca5d8/Lib/typing.py#L1340 -RedirectTemp /15-16 https://www.python.org/dev/peps/pep-0484/#casts -RedirectTemp /15-17 https://github.com/python/typeshed/issues/5535 -RedirectTemp /15-18 https://docs.python.org/3/library/asyncio-stream.html#tcp-echo-server-using-streams -RedirectTemp /15-19 https://github.com/python/cpython/blob/b798ab06937f8bb24b444a49dd42e11fff15e654/Lib/test/test_asyncio/test_server.py#L55 -RedirectTemp /15-20 https://en.wikipedia.org/wiki/Code_smell -RedirectTemp /15-21 https://mail.python.org/archives/list/typing-sig@python.org/message/5LCWMN2UY2UQNLC5Z47GHBZKSPZW4I63/ -RedirectTemp /15-22 https://mypy.readthedocs.io/en/stable/error_codes.html#error-codes -RedirectTemp /15-23 https://github.com/fluentpython/example-code-2e/blob/master/15-more-types/clip_annot.py -RedirectTemp /15-24 https://docs.python.org/3/library/typing.html#introspection-helpers -RedirectTemp /15-25 https://docs.python.org/3.10/library/inspect.html#inspect.get_annotations -RedirectTemp /15-26 https://www.python.org/dev/peps/pep-0563/#abstract -RedirectTemp /15-27 https://mail.python.org/archives/list/python-dev@python.org/message/ZBJ7MD6CSGM6LZAOTET7GXAVBZB7O77O/ -RedirectTemp /15-28 https://docs.python.org/3.10/howto/annotations.html -RedirectTemp /15-29 https://docs.python.org/3/library/typing.html#user-defined-generic-types -RedirectTemp /15-30 https://github.com/python/typeshed/blob/bfc83c365a0b26ab16586beac77ff16729d0e473/stdlib/builtins.pyi#L743 -RedirectTemp /15-31 https://docs.python.org/3.10/library/typing.html#typing.FrozenSet -RedirectTemp /15-32 https://docs.python.org/3.10/library/typing.html#typing.Generator -RedirectTemp /15-33 https://docs.python.org/3.10/library/typing.html#typing.AsyncGenerator -RedirectTemp /15-34 https://github.com/python/cpython/blob/46b16d0bdbb1722daed10389e27226a2370f1635/Lib/typing.py#L1786 -RedirectTemp /15-35 https://github.com/python/typeshed/blob/2a9f081abbf01134e4e04ced6a750107db904d70/stdlib/builtins.pyi#L239 -RedirectTemp /15-36 https://www.oreilly.com/library/view/robust-python/9781098100650/ -RedirectTemp /15-37 https://www.python.org/dev/peps/pep-0484/#covariance-and-contravariance -RedirectTemp /15-38 https://mypy.readthedocs.io/en/stable/generics.html#variance-of-generic-types -RedirectTemp /15-39 https://mypy.readthedocs.io/en/stable/common_issues.html#variance -RedirectTemp /15-40 https://www.artima.com/weblogs/viewpost.jsp?thread=85551 -RedirectTemp /15-41 https://dl.acm.org/action/cookieAbsent -RedirectTemp /15-42 http://bracha.org/pluggableTypesPosition.pdf -RedirectTemp /15-43 https://www.researchgate.net/publication/213886116_Static_Typing_Where_Possible_Dynamic_Typing_When_Needed_The_End_of_the_Cold_War_Between_Programming_Languages -RedirectTemp /15-44 https://www.atomickotlin.com/atomickotlin/ -RedirectTemp /15-45 https://www.informit.com/store/effective-java-9780134685991 -RedirectTemp /15-46 https://www.manning.com/books/programming-with-types -RedirectTemp /15-47 https://www.oreilly.com/library/view/programming-typescript/9781492037644/ -RedirectTemp /15-48 https://www.informit.com/store/dart-programming-language-9780321927705 -RedirectTemp /15-49 https://www.yodaiken.com/2017/09/15/bad-ideas-in-type-theory/ -RedirectTemp /15-50 https://www.yodaiken.com/2017/11/30/types-considered-harmful-ii/ -RedirectTemp /15-51 https://web.archive.org/web/20071010002142/http://weblogs.java.net/blog/arnold/archive/2005/06/generics_consid_1.html -RedirectTemp /15-52 https://github.com/python/cpython/blob/3e7ee02327db13e4337374597cdc4458ecb9e3ad/Lib/asyncio/trsock.py#L5 -RedirectTemp /15-53 https://www.python.org/dev/peps/pep-0484/#covariance-and-contravariance -############################################################ 16 -RedirectTemp /16-1 http://www.gotw.ca/publications/c_family_interview.htm -RedirectTemp /16-2 https://docs.python.org/3/reference/expressions.html#unary-arithmetic-and-bitwise-operations -RedirectTemp /16-3 https://docs.python.org/3/reference/datamodel.html#object.__neg__ -RedirectTemp /16-4 https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#boolean-indexing -RedirectTemp /16-5 https://docs.python.org/3/library/collections.html#collections.Counter -RedirectTemp /16-6 https://docs.python.org/3/reference/datamodel.html#emulating-container-types -RedirectTemp /16-7 https://docs.python.org/3/library/numbers.html#implementing-the-arithmetic-operations -RedirectTemp /16-8 https://www.fluentpython.com/lingo/#fail-fast -RedirectTemp /16-9 https://github.com/python/cpython/blob/0bbf30e2b910bc9c5899134ae9d73a8df968da35/Objects/typeobject.c#L4598 -RedirectTemp /16-10 https://neopythonic.blogspot.com/2019/03/why-operators-are-useful.html -RedirectTemp /16-11 https://treyhunner.com/2019/03/python-deep-comparisons-and-code-readability/ -RedirectTemp /16-12 https://docs.python.org/3/library/numbers.html#implementing-the-arithmetic-operations -RedirectTemp /16-13 https://docs.python.org/3/library/pathlib.html -RedirectTemp /16-14 https://pypi.org/project/scapy/ -RedirectTemp /16-15 https://scapy.readthedocs.io/en/latest/usage.html#stacking-layers -RedirectTemp /16-16 https://docs.python.org/3/library/functools.html#functools.total_ordering -RedirectTemp /16-17 https://wiki.illinois.edu//wiki/download/attachments/273416327/ingalls.pdf -RedirectTemp /16-18 https://wiki.illinois.edu//wiki/download/attachments/273416327/double-dispatch.pdf -RedirectTemp /16-19 http://www.gotw.ca/publications/c_family_interview.htm -RedirectTemp /16-20 http://www.gotw.ca/publications/c_family_interview.htm -RedirectTemp /16-21 https://doc.rust-lang.org/std/ops/index.html -RedirectTemp /16-22 https://www.fluentpython.com/lingo/#lazy -############################################################ 17 -RedirectTemp /17-1 http://www.paulgraham.com/icad.html -RedirectTemp /17-2 https://en.wikipedia.org/wiki/Sentinel_value -RedirectTemp /17-3 https://docs.python.org/3.10/library/functions.html#iter -RedirectTemp /17-4 https://docs.python.org/3.10/library/functions.html#iter -RedirectTemp /17-5 https://github.com/python/cpython/blob/b1930bf75f276cd7ca08c4455298128d89adf7d1/Lib/_collections_abc.py#L271 -RedirectTemp /17-6 https://github.com/python/cpython/blob/main/Lib/types.py#L6 -RedirectTemp /17-7 https://en.wikipedia.org/wiki/CLU_(programming_language) -RedirectTemp /17-8 https://docs.python.org/3/glossary.html -RedirectTemp /17-9 https://docs.python.org/3/glossary.html#term-generator-iterator -RedirectTemp /17-10 https://docs.python.org/3/glossary.html#term-generator-expression -RedirectTemp /17-11 https://marc.info/?l=python-list&m=141826925106951&w=2 -RedirectTemp /17-12 https://docs.python.org/3/library/os.html#os.walk -RedirectTemp /17-13 https://docs.python.org/3/library/itertools.html -RedirectTemp /17-14 https://docs.python.org/3/library/exceptions.html#exception-hierarchy -RedirectTemp /17-15 https://en.wikipedia.org/wiki/Depth-first_search -RedirectTemp /17-16 https://docs.python.org/3.10/library/typing.html#typing.TypeAlias -RedirectTemp /17-17 https://docs.python.org/3/library/typing.html#typing.Generator -RedirectTemp /17-18 http://www.dabeaz.com/coroutines/Coroutines.pdf -RedirectTemp /17-19 http://www.dabeaz.com/coroutines/Coroutines.pdf -RedirectTemp /17-20 https://mail.python.org/pipermail/python-ideas/2009-April/003841.html -RedirectTemp /17-21 https://mail.python.org/pipermail/python-ideas/2009-April/003912.html -RedirectTemp /17-22 https://docs.python.org/3/library/exceptions.html#StopIteration -RedirectTemp /17-23 https://docs.python.org/3/reference/expressions.html#yield-expressions -RedirectTemp /17-24 https://docs.python.org/3/reference/index.html -RedirectTemp /17-25 https://github.com/python/cpython/blob/6f743e7a4da904f61dfa84cc7d7385e4dcc79ac5/Lib/typing.py#L2060 -RedirectTemp /17-26 http://catb.org/~esr/jargon/html/G/grok.html -RedirectTemp /17-27 https://docs.python.org/3/reference/expressions.html#yieldexpr -RedirectTemp /17-28 https://docs.python.org/3/library/itertools.html -RedirectTemp /17-29 https://docs.python.org/3/library/itertools.html#itertools-recipes -RedirectTemp /17-30 https://more-itertools.readthedocs.io/en/stable/index.html -RedirectTemp /17-31 https://rittau.org/2006/11/java-iterators-are-not-iterable/ -RedirectTemp /17-32 https://docs.python.org/3/whatsnew/3.3.html#pep-380-syntax-for-delegating-to-a-subgenerator -RedirectTemp /17-33 http://www.dabeaz.com/generators/ -RedirectTemp /17-34 http://www.dabeaz.com/coroutines/ -RedirectTemp /17-35 https://archive.org/details/pyvideo_213___pycon-2009-a-curious-course-on-coroutines-and-concurrency-part-1-of-3 -RedirectTemp /17-36 https://archive.org/details/pyvideo_215___pycon-2009-a-curious-course-on-coroutines-and-concurrency-part-2-of-3 -RedirectTemp /17-37 https://archive.org/details/pyvideo_214___pycon-2009-a-curious-course-on-coroutines-and-concurrency-part-3-of-3 -RedirectTemp /17-38 http://www.dabeaz.com/finalgenerator/ -RedirectTemp /17-39 https://web.archive.org/web/20200218150637/http://seriously.dontusethiscode.com/2013/05/01/greedy-coroutine.html -RedirectTemp /17-40 https://effectivepython.com/ -RedirectTemp /17-41 https://effectivepython.com/2015/03/10/consider-coroutines-to-run-many-functions-concurrently -RedirectTemp /17-42 https://en.wikipedia.org/wiki/Conway's_Game_of_Life -RedirectTemp /17-43 https://gist.github.com/ramalho/da5590bc38c973408839 -RedirectTemp /17-44 https://gist.github.com/ramalho/da5590bc38c973408839 -RedirectTemp /17-45 https://journal.code4lib.org/articles/4893 -RedirectTemp /17-46 https://github.com/fluentpython/isis2json -RedirectTemp /17-47 https://github.com/fluentpython/isis2json/blob/master/README.rst -############################################################ 18 -RedirectTemp /18-1 https://pyvideo.org/video/1669/keynote-3/ -RedirectTemp /18-2 https://docs.python.org/3/library/sqlite3.html#using-the-connection-as-a-context-manager -RedirectTemp /18-3 https://docs.python.org/3/library/threading.html#using-locks-conditions-and-semaphores-in-the-with-statement -RedirectTemp /18-4 https://docs.python.org/3/library/decimal.html#decimal.localcontext -RedirectTemp /18-5 https://docs.python.org/3/library/unittest.mock.html#patch -RedirectTemp /18-6 https://docs.python.org/3/library/contextlib.html#contextlib.redirect_stdout -RedirectTemp /18-7 https://docs.python.org/3/library/sys.html#sys.exc_info -RedirectTemp /18-8 https://en.wikipedia.org/wiki/LL_parser -RedirectTemp /18-9 https://docs.python.org/3/library/contextlib.html -RedirectTemp /18-10 https://github.com/python/cpython/blob/8afab2ebbc1b343cd88d058914cf622fe687a2be/Lib/contextlib.py#L123 -RedirectTemp /18-11 https://www.zopatista.com/python/2013/11/26/inplace-file-rewriting/ -RedirectTemp /18-12 https://docs.python.org/3/library/fileinput.html#fileinput.input -RedirectTemp /18-13 https://www.zopatista.com/python/2013/11/26/inplace-file-rewriting/ -RedirectTemp /18-14 https://en.wikipedia.org/wiki/Euclidean_algorithm -RedirectTemp /18-15 https://github.com/fluentpython/example-code-2e/tree/master/18-with-match/lispy/py3.10/ -RedirectTemp /18-16 https://github.com/fluentpython/example-code-2e/blob/master/18-with-match/lispy/original/lispy.py -RedirectTemp /18-17 https://github.com/fluentpython/example-code-2e/blob/6527037ae7319ba370a1ee2d9fe79214d0ed9452/18-with-match/lispy/py3.10/lis.py#L35 -RedirectTemp /18-18 https://github.com/fluentpython/example-code-2e/blob/master/18-with-match/lispy/py3.10/examples_test.py -RedirectTemp /18-19 https://github.com/python/typeshed/issues/6042 -RedirectTemp /18-20 https://github.com/fluentpython/lispy/tree/main/mylis -RedirectTemp /18-21 https://github.com/fluentpython/example-code-2e/blob/00e4741926e1b771ee7c753148b1415c0bd12e39/02-array-seq/lispy/py3.10/examples_test.py -RedirectTemp /18-22 https://mitpress.mit.edu/sites/default/files/sicp/index.html -RedirectTemp /18-23 https://github.com/fluentpython/example-code-2e/blob/master/18-with-match/lispy/py3.10/examples_test.py -RedirectTemp /18-24 https://github.com/fluentpython/example-code-2e/blob/master/18-with-match/lispy/py3.10/lis.py -RedirectTemp /18-25 https://www.python.org/dev/peps/pep-0634/#or-patterns -RedirectTemp /18-26 https://en.wikipedia.org/wiki/Lambda#Character_encodings -RedirectTemp /18-27 https://docs.python.org/3/reference/compound_stmts.html -RedirectTemp /18-28 https://docs.python.org/3/glossary.html#term-eafp -RedirectTemp /18-29 https://speakerdeck.com/pyconslides/pycon-keynote-python-is-awesome-by-raymond-hettinger?slide=21 -RedirectTemp /18-30 https://docs.python.org/3/reference/compound_stmts.html -RedirectTemp /18-31 https://stackoverflow.com/questions/16138232/is-it-a-good-practice-to-use-try-except-else-in-python -RedirectTemp /18-32 https://docs.python.org/3/library/stdtypes.html#typecontextmanager -RedirectTemp /18-33 https://docs.python.org/3/reference/datamodel.html#with-statement-context-managers -RedirectTemp /18-34 https://speakerdeck.com/pyconslides/pycon-keynote-python-is-awesome-by-raymond-hettinger?slide=21 -RedirectTemp /18-35 https://speakerdeck.com/pyconslides/transforming-code-into-beautiful-idiomatic-python-by-raymond-hettinger-1?slide=34 -RedirectTemp /18-36 https://preshing.com/20110920/the-python-with-statement-by-example/ -RedirectTemp /18-37 https://www.rath.org/on-the-beauty-of-pythons-exitstack.html -RedirectTemp /18-38 https://norvig.com/lispy.html -RedirectTemp /18-39 https://norvig.com/lispy2.html -RedirectTemp /18-40 https://github.com/norvig/pytudes -RedirectTemp /18-41 https://github.com/fluentpython/lispy -RedirectTemp /18-42 https://racket-lang.org/ -RedirectTemp /18-43 https://pyvideo.org/video/1669/keynote-3/ -RedirectTemp /18-44 https://en.wikipedia.org/wiki/Tail_call -RedirectTemp /18-45 https://2ality.com/2015/06/tail-call-optimization.html -RedirectTemp /18-46 https://github.com/fluentpython/example-code-2e/blob/master/18-with-match/lispy/original/lis.py -RedirectTemp /18-47 https://github.com/fluentpython/example-code-2e/blob/master/18-with-match/lispy/original/lispy.py -RedirectTemp /18-48 http://neopythonic.blogspot.com/2009/04/final-words-on-tail-calls.html -RedirectTemp /18-49 https://webkit.org/blog/6240/ecmascript-6-proper-tail-calls-in-webkit/ -RedirectTemp /18-50 http://kangax.github.io/compat-table/es6/ -RedirectTemp /18-51 https://world.hey.com/mgmarlow/what-happened-to-proper-tail-calls-in-javascript-5494c256 -RedirectTemp /18-52 http://neopythonic.blogspot.com/2009/04/tail-recursion-elimination.html -RedirectTemp /18-53 https://github.com/fluentpython/lispy/blob/main/mylis/mylis_2/lis.py -RedirectTemp /18-54 https://github.com/fluentpython/lispy/tree/main/mylis -############################################################ 19 -RedirectTemp /19-1 https://go.dev/blog/waza-talk -RedirectTemp /19-2 https://en.wikipedia.org/wiki/Graphics_processing_unit -RedirectTemp /19-3 https://docs.python.org/3/library/sys.html#sys.getswitchinterval -RedirectTemp /19-4 https://docs.python.org/3/library/sys.html#sys.setswitchinterval -RedirectTemp /19-5 https://en.wikipedia.org/wiki/System_call -RedirectTemp /19-6 https://mail.python.org/pipermail/python-dev/2009-October/093356.html -RedirectTemp /19-7 http://www.dabeaz.com/finalgenerator/ -RedirectTemp /19-8 https://docs.python.org/3/library/threading.html#thread-objects -RedirectTemp /19-9 https://www.pypy.org/ -RedirectTemp /19-10 https://mail.python.org/pipermail/python-list/2009-February/675659.html -RedirectTemp /19-11 https://en.wikipedia.org/wiki/Braille_Patterns -RedirectTemp /19-12 https://docs.python.org/3/library/multiprocessing.shared_memory.html -RedirectTemp /19-13 https://docs.python.org/3/library/multiprocessing.shared_memory.html#multiprocessing.shared_memory.ShareableList -RedirectTemp /19-14 https://greenlet.readthedocs.io/en/latest/ -RedirectTemp /19-15 https://docs.sqlalchemy.org/en/14/changelog/migration_14.html#asynchronous-io-support-for-core-and-orm -RedirectTemp /19-16 https://docs.sqlalchemy.org/en/14/orm/extensions/asyncio.html -RedirectTemp /19-17 http://www.gevent.org/ -RedirectTemp /19-18 https://github.com/gevent/gevent/wiki/Projects -RedirectTemp /19-19 https://docs.python.org/3/library/concurrent.futures.html#processpoolexecutor-example -RedirectTemp /19-20 https://github.com/python/asyncio/issues/284 -RedirectTemp /19-21 https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.loop.run_in_executor -RedirectTemp /19-22 https://mail.python.org/archives/list/python-dev@python.org/message/JBYXQH3NV3YBF7P2HLHB5CD6V3GVTY55/ -RedirectTemp /19-23 https://docs.python.org/3/library/queue.html#queue.SimpleQueue.get -RedirectTemp /19-24 https://en.wikipedia.org/wiki/Race_condition -RedirectTemp /19-25 https://github.com/fluentpython/example-code-2e/commit/2c1230579db99738a5e5e6802063bda585f6476d -RedirectTemp /19-26 https://github.com/fluentpython/example-code-2e/blob/master/19-concurrency/primes/README.md -RedirectTemp /19-27 https://github.com/fluentpython/example-code-2e/blob/master/19-concurrency/primes/threads.py -RedirectTemp /19-28 https://en.wikipedia.org/wiki/Context_switch -RedirectTemp /19-29 http://www.gotw.ca/publications/concurrency-ddj.htm -RedirectTemp /19-30 https://www.ansible.com/ -RedirectTemp /19-31 https://saltproject.io/ -RedirectTemp /19-32 https://www.fabfile.org/ -RedirectTemp /19-33 https://engineering.fb.com/2016/05/27/production-engineering/python-in-production-engineering/ -RedirectTemp /19-34 https://jupyter.org/ -RedirectTemp /19-35 https://docs.bokeh.org/en/latest/index.html -RedirectTemp /19-36 https://www.tensorflow.org/ -RedirectTemp /19-37 https://pytorch.org/ -RedirectTemp /19-38 https://www.oreilly.com/radar/where-programming-ops-ai-and-the-cloud-are-headed-in-2021/ -RedirectTemp /19-39 https://www.youtube.com/watch?v=ods97a5Pzw0 -RedirectTemp /19-40 https://www.thoughtworks.com/radar/techniques/high-performance-envy-web-scale-envy -RedirectTemp /19-41 https://modwsgi.readthedocs.io/en/master/ -RedirectTemp /19-42 https://uwsgi-docs.readthedocs.io/en/latest/ -RedirectTemp /19-43 https://unit.nginx.org/ -RedirectTemp /19-44 https://www.techatbloomberg.com/blog/configuring-uwsgi-production-deployment/ -RedirectTemp /19-45 https://www.youtube.com/watch?v=p6R1h2Nn468 -RedirectTemp /19-46 https://asgi.readthedocs.io/en/latest/index.html -RedirectTemp /19-47 https://docs.celeryproject.org/en/stable/getting-started/introduction.html -RedirectTemp /19-48 https://python-rq.org/ -RedirectTemp /19-49 https://docs.celeryproject.org/en/stable/faq.html#what-kinds-of-things-should-i-use-celery-for -RedirectTemp /19-50 https://redis.io/ -RedirectTemp /19-51 https://realpython.com/intro-to-python-threading/ -RedirectTemp /19-52 https://pymotw.com/3/concurrency.html -RedirectTemp /19-53 https://www.pearson.com/us/higher-education/program/Hellmann-Python-3-Standard-Library-by-Example-The/PGM328871.html -RedirectTemp /19-54 https://docs.python.org/3/library/multiprocessing.html#programming-guidelines -RedirectTemp /19-55 https://docs.python.org/3/library/multiprocessing.html -RedirectTemp /19-56 https://www.oreilly.com/library/view/high-performance-python/9781492055013/ -RedirectTemp /19-57 https://link.springer.com/book/10.1007/978-1-4842-5793-7?error=cookies_not_supported&code=2ed5d61d-ae9f-4f3d-94ac-0f68cf45ea4f -RedirectTemp /19-58 https://www.packtpub.com/product/parallel-programming-with-python/9781783288397 -RedirectTemp /19-59 https://greenteapress.com/wp/semaphores/ -RedirectTemp /19-60 https://docs.python.org/3/c-api/init.html#thread-state-and-the-global-interpreter-lock -RedirectTemp /19-61 https://docs.python.org/3/faq/library.html#can-t-we-get-rid-of-the-global-interpreter-lock -RedirectTemp /19-62 https://www.artima.com/weblogs/viewpost.jsp?thread=214235 -RedirectTemp /19-63 http://jessenoller.com/blog/2009/02/01/python-threads-and-the-global-interpreter-lock -RedirectTemp /19-64 https://realpython.com/products/cpython-internals-book/ -RedirectTemp /19-65 http://www.dabeaz.com/GIL/ -RedirectTemp /19-66 http://www.dabeaz.com/python/UnderstandingGIL.pdf -RedirectTemp /19-67 https://bugs.python.org/issue7946#msg223110 -RedirectTemp /19-68 https://bugs.python.org/issue7946 -RedirectTemp /19-69 https://www.fullstackpython.com/ -RedirectTemp /19-70 https://www.oreilly.com/library/view/high-performance-python/9781492055013/ -RedirectTemp /19-71 https://www.packtpub.com/product/parallel-programming-with-python/9781783288397 -RedirectTemp /19-72 https://www.packtpub.com/product/distributed-computing-with-python/9781785889691 -RedirectTemp /19-73 https://towardsdatascience.com/python-performance-and-gpus-1be860ffd58d?gi=a7d537cc2fb4 -RedirectTemp /19-74 https://instagram-engineering.com/web-service-efficiency-at-instagram-with-python-4976d078e366?gi=12a441991c88 -RedirectTemp /19-75 https://www.oreilly.com/library/view/architecture-patterns-with/9781492052197/ -RedirectTemp /19-76 https://www.cosmicpython.com/ -RedirectTemp /19-77 https://pypi.org/project/lelo/ -RedirectTemp /19-78 https://github.com/npryce/python-parallelize -RedirectTemp /19-79 https://github.com/ericsnowcurrently/multi-core-python/wiki -RedirectTemp /19-80 https://gist.github.com/markshannon/79cace3656b40e21b7021504daee950c -RedirectTemp /19-81 https://mail.python.org/archives/list/python-dev@python.org/message/YOOQZCFOKEPQ24YHWWLQSJ3RCXFMS7D7/ -RedirectTemp /19-82 https://en.wikipedia.org/wiki/Communicating_sequential_processes -RedirectTemp /19-83 https://github.com/stackless-dev/stackless/wiki -RedirectTemp /19-84 https://www.eveonline.com -RedirectTemp /19-85 https://www.ccpgames.com/ -RedirectTemp /19-86 https://stackless.readthedocs.io/en/3.6-slp/stackless-python.html#history -RedirectTemp /19-87 https://doc.pypy.org/en/latest/stackless.html -RedirectTemp /19-88 https://greenlet.readthedocs.io/en/latest/ -RedirectTemp /19-89 http://www.gevent.org/ -RedirectTemp /19-90 http://thespianpy.com/doc/ -RedirectTemp /19-91 https://pykka.readthedocs.io/en/latest/ -RedirectTemp /19-92 https://www.manning.com/books/rabbitmq-in-action -RedirectTemp /19-93 https://pragprog.com/titles/pb7con/seven-concurrency-models-in-seven-weeks/ -RedirectTemp /19-94 https://en.wikipedia.org/wiki/OpenCL -RedirectTemp /19-95 https://media.pragprog.com/titles/pb7con/Bonus_Chapter.pdf -RedirectTemp /19-96 https://martinfowler.com/ -RedirectTemp /19-97 https://martinfowler.com/articles/patterns-of-distributed-systems/ -RedirectTemp /19-98 https://www.oreilly.com/library/view/designing-data-intensive-applications/9781491903063/ -RedirectTemp /19-99 https://www.oreilly.com/library/view/oscon-2016-video/9781491965153/video247021.html -RedirectTemp /19-100 https://www.oreilly.com/library/view/designing-for-scalability/9781449361556/ -RedirectTemp /19-101 https://www.thoughtworks.com/radar/techniques/high-performance-envy-web-scale-envy -RedirectTemp /19-102 https://en.wikipedia.org/wiki/KISS_principle -RedirectTemp /19-103 https://www.usenix.org/conference/hotos15/workshop-program/presentation/mcsherry -############################################################ 20 -RedirectTemp /20-1 https://www.artima.com/weblogs/viewpost.jsp?thread=299551 -RedirectTemp /20-2 https://docs.python.org/3/library/http.server.html -RedirectTemp /20-3 https://www.youtube.com/watch?v=A9e9Cy1UkME -RedirectTemp /20-4 https://www.cia.gov/the-world-factbook/ -RedirectTemp /20-5 https://docs.python-requests.org/en/latest/ -RedirectTemp /20-6 https://docs.python.org/3.10/library/concurrent.futures.html#concurrent.futures.ThreadPoolExecutor -RedirectTemp /20-7 https://docs.python.org/3/library/concurrent.futures.html#concurrent.futures.as_completed -RedirectTemp /20-8 https://docs.python.org/3/library/concurrent.futures.html -RedirectTemp /20-9 https://docs.python.org/3.10/library/concurrent.futures.html#concurrent.futures.Executor -RedirectTemp /20-10 https://github.com/fluentpython/example-code-2e/blob/master/20-executors/getflags/flags2_common.py -RedirectTemp /20-11 https://github.com/noamraph/tqdm -RedirectTemp /20-12 https://www.youtube.com/watch?v=M8Z65tAl5l4 -RedirectTemp /20-13 https://github.com/noamraph/tqdm/blob/master/README.md -RedirectTemp /20-14 https://docs.python.org/3/library/concurrent.futures.html#concurrent.futures.as_completed -RedirectTemp /20-15 https://docs.python.org/3/library/asyncio-task.html#asyncio.as_completed -RedirectTemp /20-16 https://www.cloudflare.com/ -RedirectTemp /20-17 https://github.com/fluentpython/example-code-2e/tree/master/20-executors/getflags -RedirectTemp /20-18 https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/418 -RedirectTemp /20-19 https://en.wikipedia.org/wiki/Embarrassingly_parallel -RedirectTemp /20-20 https://pyvideo.org/video/480/pyconau-2010--the-future-is-soon/ -RedirectTemp /20-21 http://www.dabeaz.com/coroutines/ -RedirectTemp /20-22 https://en.wikipedia.org/wiki/POSIX_Threads -RedirectTemp /20-23 https://en.wikipedia.org/wiki/C_dynamic_memory_allocation -RedirectTemp /20-24 https://pragprog.com/titles/pb7con/seven-concurrency-models-in-seven-weeks/ -RedirectTemp /20-25 https://hexdocs.pm/ecto/getting-started.html -############################################################ 21 -RedirectTemp /21-1 https://docs.python.org/3/library/asyncio.html -RedirectTemp /21-2 https://bugs.python.org/issue43216 -RedirectTemp /21-3 https://bugs.python.org/issue36921 -RedirectTemp /21-4 https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.loop.getaddrinfo -RedirectTemp /21-5 https://docs.python.org/3/library/socket.html#socket.getaddrinfo -RedirectTemp /21-6 https://docs.python.org/3.10/library/asyncio-eventloop.html#asyncio.get_event_loop -RedirectTemp /21-7 https://www.python.org/dev/peps/pep-0492/#await-expression -RedirectTemp /21-8 https://www.fluentpython.com/extra/classic-coroutines/#yield_from_meaning_sec -RedirectTemp /21-9 https://github.com/fluentpython/example-code-2e/tree/master/20-executors/getflags -RedirectTemp /21-10 https://magicstack.github.io/asyncpg/current/ -RedirectTemp /21-11 https://magicstack.github.io/asyncpg/current/api/index.html#transactions -RedirectTemp /21-12 https://magicstack.github.io/asyncpg/current/api/index.html#transactions -RedirectTemp /21-13 https://github.com/MagicStack/asyncpg/blob/4d39a05268ce4cc01b00458223a767542da048b8/asyncpg/transaction.py#L57 -RedirectTemp /21-14 https://magicstack.github.io/asyncpg/current/usage.html#connection-pools -RedirectTemp /21-15 https://gist.github.com/jboner/2841832 -RedirectTemp /21-16 https://en.wikipedia.org/wiki/Network-attached_storage -RedirectTemp /21-17 https://en.wikipedia.org/wiki/Semaphore_(programming) -RedirectTemp /21-18 https://en.wikipedia.org/wiki/Semaphore_(programming) -RedirectTemp /21-19 https://groups.google.com/forum/#!msg/python-tulip/PdAEtwpaJHs/7fqb-Qj2zJoJ -RedirectTemp /21-20 https://web.archive.org/web/20151209151711/http://tritarget.org/blog/2012/11/28/the-pyramid-of-doom-a-javascript-style-trap -RedirectTemp /21-21 https://stackoverflow.com/questions/53701841/what-is-the-use-case-for-future-add-done-callback/53710563 -RedirectTemp /21-22 https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.loop.run_in_executor -RedirectTemp /21-23 https://motor.readthedocs.io/en/stable/ -RedirectTemp /21-24 https://emptysqua.re/blog/response-to-asynchronous-python-and-databases/ -RedirectTemp /21-25 https://docs.python.org/3/library/asyncio-stream.html#tcp-echo-server-using-streams -RedirectTemp /21-26 https://en.wikipedia.org/wiki/Phaistos_Disc -RedirectTemp /21-27 https://en.wikipedia.org/wiki/Inverted_index -RedirectTemp /21-28 https://fastapi.tiangolo.com/ -RedirectTemp /21-29 https://swagger.io/specification/ -RedirectTemp /21-30 https://asgi.readthedocs.io/en/latest/implementations.html -RedirectTemp /21-31 https://pydantic-docs.helpmanual.io/ -RedirectTemp /21-32 https://doc.traefik.io/traefik/ -RedirectTemp /21-33 https://fastapi.tiangolo.com/project-generation/ -RedirectTemp /21-34 https://fastapi.tiangolo.com/tutorial/response-model/ -RedirectTemp /21-35 https://docs.python.org/3/library/asyncio-stream.html#asyncio.start_server -RedirectTemp /21-36 https://github.com/python/typeshed/issues/5535 -RedirectTemp /21-37 https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.Server.serve_forever -RedirectTemp /21-38 https://docs.python.org/3/library/asyncio-stream.html#asyncio.StreamWriter.close -RedirectTemp /21-39 https://docs.python.org/3/library/asyncio-stream.html#streamwriter -RedirectTemp /21-40 https://docs.python.org/3/library/asyncio-stream.html -RedirectTemp /21-41 https://docs.python.org/3/library/asyncio-protocol.html -RedirectTemp /21-42 https://docs.python.org/3/library/asyncio-protocol.html#tcp-echo-server -RedirectTemp /21-43 https://github.com/aio-libs/aiopg -RedirectTemp /21-44 https://docs.python.org/3/whatsnew/3.8.html#asyncio -RedirectTemp /21-45 https://datatracker.ietf.org/doc/html/rfc6761 -RedirectTemp /21-46 https://docs.python.org/3/library/contextlib.html#contextlib.asynccontextmanager -RedirectTemp /21-47 https://docs.python.org/3/library/contextlib.html#contextlib.asynccontextmanager -RedirectTemp /21-48 https://docs.python.org/3/library/asyncio-task.html#asyncio.gather -RedirectTemp /21-49 https://curio.readthedocs.io/en/latest/index.html -RedirectTemp /21-50 https://curio.readthedocs.io/en/latest/reference.html#task-groups -RedirectTemp /21-51 https://en.wikipedia.org/wiki/Structured_concurrency -RedirectTemp /21-52 https://mail.python.org/archives/list/python-dev@python.org/thread/2ORDAW74LGE3ZI2QETPJRT2ZL7MCCPG2/ -RedirectTemp /21-53 https://www.python.org/dev/peps/pep-0654/#motivation -RedirectTemp /21-54 https://curio.readthedocs.io/en/latest/reference.html#AWAIT -RedirectTemp /21-55 https://www.python-httpx.org/async/#curio -RedirectTemp /21-56 https://github.com/dabeaz/curio/tree/78bca8a6ad677ef51e1568ac7b3e51441ab49c42/examples -RedirectTemp /21-57 https://datatracker.ietf.org/doc/html/rfc8305 -RedirectTemp /21-58 https://trio.readthedocs.io/en/stable/ -RedirectTemp /21-59 https://www.youtube.com/watch?v=M-sc73Y-zQA -RedirectTemp /21-60 https://en.wikipedia.org/wiki/Technical_debt -RedirectTemp /21-61 https://www.youtube.com/watch?v=E-1Y4kSsAFc -RedirectTemp /21-62 https://github.com/dabeaz/curio -RedirectTemp /21-63 https://trio.readthedocs.io/en/stable/ -RedirectTemp /21-64 https://curio.readthedocs.io/en/latest/#curio-university -RedirectTemp /21-65 https://vorpus.org/blog/some-thoughts-on-asynchronous-api-design-in-a-post-asyncawait-world/ -RedirectTemp /21-66 https://vorpus.org/blog/notes-on-structured-concurrency-or-go-statement-considered-harmful/ -RedirectTemp /21-67 https://stackoverflow.com/questions/49482969/what-is-the-core-difference-between-asyncio-and-trio -RedirectTemp /21-68 https://docs.python.org/3/library/asyncio.html -RedirectTemp /21-69 https://bugs.python.org/issue33649 -RedirectTemp /21-70 https://docs.python.org/3/library/asyncio-dev.html -RedirectTemp /21-71 https://www.youtube.com/watch?v=iG6fr81xHKA -RedirectTemp /21-72 https://www.youtube.com/watch?v=F19R_M4Nay4 -RedirectTemp /21-73 https://asherman.io/projects/unsync.html -RedirectTemp /21-74 https://pyladies.com/ -RedirectTemp /21-75 https://www.youtube.com/watch?v=sW76-pRkZk8 -RedirectTemp /21-76 https://www.youtube.com/watch?v=Xbl7XjFYsN4 -RedirectTemp /21-77 https://www.youtube.com/watch?v=02CLD-42VdI -RedirectTemp /21-78 https://micropython.org/ -RedirectTemp /21-79 https://docs.micropython.org/en/latest/library/uasyncio.html -RedirectTemp /21-80 https://www.encode.io/articles/python-async-frameworks-beyond-developer-tribalism -RedirectTemp /21-81 https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/ -RedirectTemp /21-82 https://vorpus.org/blog/notes-on-structured-concurrency-or-go-statement-considered-harmful/ -RedirectTemp /21-83 https://github.com/MagicStack/uvloop -RedirectTemp /21-84 http://magic.io/blog/uvloop-blazing-fast-python-networking/ -RedirectTemp /21-85 https://github.com/MagicStack/httptools -RedirectTemp /21-86 https://docs.aiohttp.org/en/stable/ -RedirectTemp /21-87 https://github.com/wg/wrk -RedirectTemp /21-88 https://twistedmatrix.com/trac/ -############################################################ 22 -RedirectTemp /22-1 https://github.com/fluentpython/example-code-2e/blob/master/22-dyn-attr-prop/oscon/data/osconfeed.json -RedirectTemp /22-2 https://pypi.org/project/attrdict/ -RedirectTemp /22-3 https://pypi.org/project/addict/ -RedirectTemp /22-4 https://github.com/ActiveState/code/tree/master/recipes/Python/52308_simple_but_handy_collector_bunch_named_stuff -RedirectTemp /22-5 https://docs.python.org/3/library/types.html#types.SimpleNamespace -RedirectTemp /22-6 https://docs.python.org/3/library/argparse.html#argparse.Namespace -RedirectTemp /22-7 https://docs.python.org/3/library/multiprocessing.html#multiprocessing.managers.Namespace -RedirectTemp /22-8 https://github.com/fluentpython/example-code-2e/blob/master/22-dyn-attr-prop/oscon/schedule_v2.py -RedirectTemp /22-9 https://docs.python.org/3/library/functools.html#functools.cached_property -RedirectTemp /22-10 https://docs.python.org/3/library/functools.html#functools.cached_property -RedirectTemp /22-11 https://bugs.python.org/issue42781 -RedirectTemp /22-12 https://docs.python.org/3/howto/descriptor.html -RedirectTemp /22-13 https://github.com/python/cpython/blob/e6d0107e13ed957109e79b796984d3d026a8660d/Lib/functools.py#L926 -RedirectTemp /22-14 https://docs.python.org/3/library/threading.html#rlock-objects -RedirectTemp /22-15 https://docs.python.org/3.10/library/functools.html#functools.cached_property -RedirectTemp /22-16 https://www.wsj.com/articles/SB10001424052970203914304576627102996831200 -RedirectTemp /22-17 https://www.youtube.com/watch?v=s35rVw1zskA&feature=youtu.be -RedirectTemp /22-18 https://docs.python.org/3/library/functions.html#dir -RedirectTemp /22-19 https://github.com/python/cpython/blob/19903085c3ad7a17c8047e1556c700f2eb109931/Lib/cmd.py#L214 -RedirectTemp /22-20 https://docs.python.org/3/library/functions.html#hasattr -RedirectTemp /22-21 https://docs.python.org/3.10/reference/datamodel.html#special-method-lookup -RedirectTemp /22-22 https://docs.python.org/3/library/functions.html -RedirectTemp /22-23 https://docs.python.org/3/reference/datamodel.html#customizing-attribute-access -RedirectTemp /22-24 https://docs.python.org/3/reference/datamodel.html#special-method-lookup -RedirectTemp /22-25 https://docs.python.org/3/library/stdtypes.html#special-attributes -RedirectTemp /22-26 http://wiki.c2.com/?WelcomeVisitors -RedirectTemp /22-27 http://wiki.c2.com/?UniformAccessPrinciple -RedirectTemp /22-28 https://docs.oracle.com/javase/tutorial/java/javaOO/accesscontrol.html -RedirectTemp /22-29 http://www.pingo.io/docs/ -RedirectTemp /22-30 https://www.drdobbs.com/javas-new-considered-harmful/184405016 -RedirectTemp /22-31 https://www.python.org/dev/peps/pep-0008/#class-names -############################################################ 23 -RedirectTemp /23-1 http://www.aleax.it/goo_pydp.pdf -RedirectTemp /23-2 https://docs.python.org/3.10/reference/datamodel.html#implementing-descriptors -RedirectTemp /23-3 https://docs.python.org/3/howto/descriptor.html -RedirectTemp /23-4 https://docs.python.org/3/howto/ -RedirectTemp /23-5 http://www.aleax.it/Python/nylug05_om.pdf -RedirectTemp /23-6 https://www.youtube.com/watch?v=VOzvpHoYQoo -RedirectTemp /23-7 https://www.python.org/dev/peps/pep-0487/#trait-descriptors -RedirectTemp /23-8 https://dreamsongs.com/RiseOfWorseIsBetter.html -RedirectTemp /23-9 http://web.archive.org/web/20031002184114/www.amk.ca/python/writing/warts.html -RedirectTemp /23-10 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this -RedirectTemp /23-11 http://python-history.blogspot.com/2009/02/adding-support-for-user-defined-classes.html -############################################################ 24 -RedirectTemp /24-1 https://docs.python.org/3/library/stdtypes.html#special-attributes -RedirectTemp /24-2 https://docs.djangoproject.com/en/3.2/topics/db/models/#meta-options -RedirectTemp /24-3 https://www.python.org/dev/peps/pep-3155/ -RedirectTemp /24-4 https://github.com/python/cpython/blob/3.9/Lib/collections/__init__.py -RedirectTemp /24-5 https://en.wikipedia.org/wiki/Tony_Hoare#Apologies_and_retractions -RedirectTemp /24-6 https://go.dev/tour/basics/12 -RedirectTemp /24-7 https://bugs.python.org/issue42102 -RedirectTemp /24-8 https://docs.python.org/3/reference/datamodel.html#object.__init_subclass__ -RedirectTemp /24-9 https://www.python.org/dev/peps/pep-0557/#abstract -RedirectTemp /24-10 https://github.com/python/cpython/blob/3.9/Lib/dataclasses.py -RedirectTemp /24-11 https://docs.python.org/3/reference/datamodel.html#creating-the-class-object -RedirectTemp /24-12 https://mail.python.org/pipermail/python-list/2002-December/134521.html -RedirectTemp /24-13 https://mail.python.org/pipermail/python-list/2002-July/162558.html -RedirectTemp /24-14 https://github.com/fluentpython/example-code/tree/master/21-class-metaprog/bulkfood -RedirectTemp /24-15 https://en.wikipedia.org/wiki/Principle_of_least_astonishment -RedirectTemp /24-16 https://docs.python.org/3/reference/datamodel.html#object.__class_getitem__ -RedirectTemp /24-17 https://en.wikipedia.org/wiki/Trait_(computer_programming) -RedirectTemp /24-18 https://en.wikipedia.org/wiki/Aspect-oriented_programming -RedirectTemp /24-19 https://dhh.dk/arc/000416.html -RedirectTemp /24-20 https://github.com/cjrh/autoslot -RedirectTemp /24-21 https://docs.python.org/3/reference/datamodel.html#customizing-class-creation -RedirectTemp /24-22 https://docs.python.org/3/library/functions.html#type -RedirectTemp /24-23 https://docs.python.org/3/library/stdtypes.html#special-attributes -RedirectTemp /24-24 https://docs.python.org/3/library/types.html -RedirectTemp /24-25 https://www.python.org/dev/peps/pep-3129/ -RedirectTemp /24-26 https://www.youtube.com/watch?v=cAGliEJV9_o -RedirectTemp /24-27 https://docs.python.org/3/library/functools.html#functools.total_ordering -RedirectTemp /24-28 https://www.python.org/download/releases/2.2.3/descrintro/ -RedirectTemp /24-29 https://github.com/lihaoyi/macropy -RedirectTemp /24-30 https://people.eecs.berkeley.edu/~bh/ss-toc2.html +# to update this file: +# cat custom.htaccess short.htaccess >> FPY.LI.htaccess diff --git a/links/README.md b/links/README.md index 6c9cfa8..2a3d80a 100644 --- a/links/README.md +++ b/links/README.md @@ -38,8 +38,15 @@ Exceptions: - URLs with `oreilly` in them are unchanged; - `fluentpython.com` URL (with no path) is unchanged; -The `custom.htacess` file contains the top redirects, which have custom names. -`FPY.LI.htaccess` has the same content, plus numbered URLs generated -from the links in each chapter in the book. +The `custom.htaccess` file contains redirects with custom names +plus numbered URLs generated from the links in each chapter in +the Second Edition in English. -The `FPY.LI.htaccess` is deployed at the root folder in `http://fpy.li`. +`short.htaccess` has redirects made by `short.py`, starting +with the Second Edition in Brazilian Portuguese. + +```shell +cat custom.htaccess short.htaccess > FPY.LI.htaccess +``` + +`FPY.LI.htaccess` is deployed at the root folder in `http://fpy.li`. diff --git a/links/custom.htaccess b/links/custom.htaccess index ad10802..cc779db 100644 --- a/links/custom.htaccess +++ b/links/custom.htaccess @@ -114,3 +114,959 @@ RedirectTemp /pep3141 https://www.python.org/dev/peps/pep-3141/ RedirectTemp /pep3148 https://www.python.org/dev/peps/pep-3148/ RedirectTemp /pep3155 https://www.python.org/dev/peps/pep-3155/ RedirectTemp /pep3333 https://www.python.org/dev/peps/pep-3333/ + +# Remaining URLs by chapter + +############################################################ p +RedirectTemp /p-1 https://mail.python.org/pipermail/python-list/2002-December/134521.html +RedirectTemp /p-2 https://docs.python.org/3.10/tutorial/ +RedirectTemp /p-3 https://docs.python.org/3/tutorial/ +RedirectTemp /p-4 https://www.oreilly.com/library/view/fluent-python-2nd/9781492056348/ +RedirectTemp /p-5 https://www.oreilly.com/online-learning/try-now.html +RedirectTemp /p-6 https://www.oreilly.com/library/view/fluent-python-2nd/9781492056348/ +RedirectTemp /p-7 https://stackoverflow.com/users/95810/alex-martelli +RedirectTemp /p-8 https://pythonpro.com.br +RedirectTemp /p-9 https://groups.google.com/g/python-brasil +RedirectTemp /p-10 https://www.coffeelab.com.br/ +RedirectTemp /p-11 https://garoa.net.br/wiki/P%C3%A1gina_principal +############################################################ a +RedirectTemp /a-1 https://groups.google.com/forum/#!topic/python-tulip/Y4bhLNbKs74 +RedirectTemp /a-2 https://docs.python.org/3/library/asyncio-eventloop.html#executor +RedirectTemp /a-3 https://www.youtube.com/watch?v=x-kB2o8sd5c +RedirectTemp /a-4 https://www.youtube.com/watch?v=OSGv2VnC0go +RedirectTemp /a-5 https://mail.python.org/pipermail/python-ideas/2015-March/032557.html +RedirectTemp /a-6 https://pypi.org/project/pep8/ +RedirectTemp /a-7 https://pypi.org/project/flake8/ +RedirectTemp /a-8 https://pypi.org/project/pyflakes/ +RedirectTemp /a-9 https://pypi.org/project/mccabe/ +RedirectTemp /a-10 https://google.github.io/styleguide/pyguide.html +RedirectTemp /a-11 https://flask.palletsprojects.com/en/1.1.x/styleguide/ +RedirectTemp /a-12 https://docs.python-guide.org/ +RedirectTemp /a-13 https://david.goodger.org/projects/pycon/2007/idiomatic/handout.html +RedirectTemp /a-14 https://docs.mongodb.com/manual/about/#about-the-documentation-process +RedirectTemp /a-15 https://blog.startifact.com/posts/older/what-is-pythonic.html +RedirectTemp /a-16 https://mail.python.org/pipermail/tutor/2003-October/thread.html#25930 +RedirectTemp /a-17 https://mail.python.org/pipermail/python-list/2003-April/192027.html +RedirectTemp /a-18 https://www.python.org/doc/essays/ +############################################################ 01 +RedirectTemp /1-1 http://hugunin.net/story_of_jython.html +RedirectTemp /1-2 https://www.oreilly.com/library/view/jython-essentials/9781449397364/ +RedirectTemp /1-3 https://docs.python.org/3/reference/lexical_analysis.html#reserved-classes-of-identifiers +RedirectTemp /1-4 https://docs.python.org/3.10/library/string.html#format-string-syntax +RedirectTemp /1-5 https://stackoverflow.com/questions/1436703/what-is-the-difference-between-str-and-repr +RedirectTemp /1-6 https://docs.python.org/3/library/stdtypes.html#truth +RedirectTemp /1-7 https://docs.python.org/3/tutorial/controlflow.html#unpacking-argument-lists +RedirectTemp /1-8 https://www.python.org/doc/humor/#the-zen-of-python +RedirectTemp /1-9 https://stackoverflow.com/users/95810/alex-martelli +RedirectTemp /1-10 https://en.wikipedia.org/wiki/Object_model +RedirectTemp /1-11 https://www.dourish.com/goodies/jargon.html +RedirectTemp /1-12 https://zopeinterface.readthedocs.io/en/latest/ +RedirectTemp /1-13 https://plone.org/ +############################################################ 02 +RedirectTemp /2-1 https://github.com/fluentpython/example-code-2e/blob/master/02-array-seq/listcomp_speed.py +RedirectTemp /2-2 https://www.python.org/dev/peps/pep-3132/ +RedirectTemp /2-3 https://stackoverflow.com/questions/68630/are-tuples-more-efficient-than-lists-in-python/22140115#22140115 +RedirectTemp /2-4 https://docs.python.org/3/whatsnew/3.5.html#pep-448-additional-unpacking-generalizations +RedirectTemp /2-5 https://docs.python.org/3/whatsnew/3.5.html#pep-448-additional-unpacking-generalizations +RedirectTemp /2-6 https://docs.python.org/3.10/whatsnew/3.10.html#pep-634-structural-pattern-matching +RedirectTemp /2-7 https://docs.python.org/3.10/whatsnew/3.10.html +RedirectTemp /2-8 https://en.wikipedia.org/wiki/Switch_statement#Fallthrough +RedirectTemp /2-9 https://en.wikipedia.org/wiki/Dangling_else +RedirectTemp /2-10 https://github.com/gvanrossum/patma/blob/3ece6444ef70122876fd9f0099eb9490a2d630df/EXAMPLES.md#case-6-a-very-deep-iterable-and-type-match-with-extraction +RedirectTemp /2-11 https://github.com/fluentpython/lispy/blob/main/original/norvig/lis.py +RedirectTemp /2-12 https://norvig.com/lispy.html +RedirectTemp /2-13 https://numpy.org/doc/stable/user/quickstart.html#indexing-slicing-and-iterating +RedirectTemp /2-14 https://pythontutor.com/ +RedirectTemp /2-15 https://en.wikipedia.org/wiki/Fluent_interface +RedirectTemp /2-16 https://docs.python.org/3/library/bisect.html#bisect.insort +RedirectTemp /2-17 https://stackoverflow.com/questions/4845418/when-should-a-memoryview-be-used/ +RedirectTemp /2-18 https://www.fluentpython.com/extra/parsing-binary-struct/ +RedirectTemp /2-19 http://www.netlib.org +RedirectTemp /2-20 https://pandas.pydata.org/ +RedirectTemp /2-21 https://scikit-learn.org/stable/ +RedirectTemp /2-22 https://docs.python.org/3/howto/sorting.html +RedirectTemp /2-23 https://www.python.org/dev/peps/pep-3132/ +RedirectTemp /2-24 https://bugs.python.org/issue2292 +RedirectTemp /2-25 https://docs.python.org/3.10/whatsnew/3.10.html#pep-634-structural-pattern-matching +RedirectTemp /2-26 https://docs.python.org/3.10/whatsnew/3.10.html +RedirectTemp /2-27 https://www.python.org/dev/peps/pep-0636/#appendix-a-quick-intro +RedirectTemp /2-28 https://eli.thegreenplace.net/2011/11/28/less-copies-in-python-with-the-buffer-protocol-and-memoryviews/ +RedirectTemp /2-29 https://jakevdp.github.io/PythonDataScienceHandbook/ +RedirectTemp /2-30 https://www.oreilly.com/library/view/python-for-data/9781491957653/ +RedirectTemp /2-31 https://www.labri.fr/perso/nrougier/from-python-to-numpy/ +RedirectTemp /2-32 https://www.cs.utexas.edu/users/EWD/transcriptions/EWD08xx/EWD831.html +RedirectTemp /2-33 http://www.fonts101.com/fonts/view/Uncategorized/34398/Dijkstra +RedirectTemp /2-34 https://docs.python.org/3/reference/datamodel.html#objects-values-and-types +RedirectTemp /2-35 https://en.wikipedia.org/wiki/Timsort +RedirectTemp /2-36 http://www.groklaw.net/pdf3/OraGoogle-1202.pdf +RedirectTemp /2-37 https://www.python.org/doc/humor/#id9 +############################################################ 03 +RedirectTemp /3-1 https://www.python.org/dev/peps/pep-0584/#motivation +RedirectTemp /3-2 https://docs.python.org/3.10/c-api/typeobj.html#Py_TPFLAGS_MAPPING +RedirectTemp /3-3 https://docs.python.org/3/glossary.html#term-hashable +RedirectTemp /3-4 https://docs.python.org/3/glossary.html#term-hashable +RedirectTemp /3-5 http://www.aleax.it/Python/accu04_Relearn_Python_alex.pdf +RedirectTemp /3-6 https://github.com/pingo-io/pingo-py +RedirectTemp /3-7 https://github.com/fluentpython/example-code-2e/blob/master/03-dict-set/missing.py +RedirectTemp /3-8 https://docs.python.org/3/library/collections.html#collections.ChainMap +RedirectTemp /3-9 https://docs.python.org/3/library/collections.html#collections.Counter +RedirectTemp /3-10 https://docs.python.org/3/library/shelve.html +RedirectTemp /3-11 https://docs.python.org/3/library/dbm.html +RedirectTemp /3-12 https://docs.python.org/3/library/pickle.html +RedirectTemp /3-13 https://nedbatchelder.com/blog/202006/pickles_nine_flaws.html +RedirectTemp /3-14 https://github.com/python/cpython/blob/0bbf30e2b910bc9c5899134ae9d73a8df968da35/Lib/_collections_abc.py#L813 +RedirectTemp /3-15 https://mail.python.org/pipermail/python-dev/2015-May/140003.html +RedirectTemp /3-16 https://bugs.python.org/issue18986 +RedirectTemp /3-17 https://github.com/fluentpython/example-code-2e/blob/master/03-dict-set/transformdict.py +RedirectTemp /3-18 http://gandenberger.org/2018/03/10/ordered-dicts-vs-ordereddict/ +RedirectTemp /3-19 https://www.pypy.org/ +RedirectTemp /3-20 https://morepypy.blogspot.com/2015/01/faster-more-memory-efficient-and-more.html +RedirectTemp /3-21 https://www.npopov.com/2014/12/22/PHPs-new-hashtable-implementation.html +RedirectTemp /3-22 https://www.youtube.com/watch?v=66P5FMkWoVU +RedirectTemp /3-23 https://pyvideo.org/video/276/the-mighty-dictionary-55/ +RedirectTemp /3-24 https://www.youtube.com/watch?v=p33CVV29OG8 +RedirectTemp /3-25 https://docs.python.org/3/whatsnew/3.6.html#new-dict-implementation +RedirectTemp /3-26 https://github.com/python/cpython/blob/cf7eaa4617295747ee5646c4e2b7e7a16d7c64ab/Objects/dictobject.c +RedirectTemp /3-27 https://github.com/python/cpython/blob/cf7eaa4617295747ee5646c4e2b7e7a16d7c64ab/Objects/dictnotes.txt +RedirectTemp /3-28 https://www.youtube.com/watch?v=tGAngdU_8D8 +RedirectTemp /3-29 https://speakerdeck.com/ramalho/python-set-practice-at-pycon +RedirectTemp /3-30 https://github.com/standupdev/uintset +RedirectTemp /3-31 https://spectrum.ieee.org/hans-peter-luhn-and-the-birth-of-the-hashing-algorithm +RedirectTemp /3-32 http://www.json.org/fatfree.html +RedirectTemp /3-33 https://twitter.com/mitsuhiko/status/1229385843585974272 +############################################################ 04 +RedirectTemp /4-1 https://www.slideshare.net/fischertrav/character-encoding-unicode-how-to-with-dignity-33352863 +RedirectTemp /4-2 https://pyvideo.org/video/2625/character-encoding-and-unicode-in-python/ +RedirectTemp /4-3 https://www.fluentpython.com/extra/parsing-binary-struct/ +RedirectTemp /4-4 https://www.fluentpython.com/extra/multi-character-emojis/ +RedirectTemp /4-5 https://w3techs.com/technologies/overview/character_encoding +RedirectTemp /4-6 https://docs.python.org/3/library/codecs.html#codecs.register_error +RedirectTemp /4-7 https://docs.python.org/3/library/stdtypes.html#str.isascii +RedirectTemp /4-8 https://pypi.org/project/chardet/ +RedirectTemp /4-9 https://docs.python.org/3/library/codecs.html#encodings-and-unicode +RedirectTemp /4-10 https://nedbatchelder.com/text/unipain/unipain.html +RedirectTemp /4-11 https://devblogs.microsoft.com/commandline/windows-command-line-unicode-and-utf-8-output-text-buffer/ +RedirectTemp /4-12 https://docs.python.org/3/using/cmdline.html#envvar-PYTHONIOENCODING +RedirectTemp /4-13 https://docs.python.org/3/using/cmdline.html#envvar-PYTHONLEGACYWINDOWSSTDIO +RedirectTemp /4-14 https://docs.python.org/3/library/locale.html#locale.getpreferredencoding +RedirectTemp /4-15 http://www.w3.org/TR/charmod-norm/ +RedirectTemp /4-16 https://docs.python.org/3/library/locale.html?highlight=strxfrm#locale.strxfrm +RedirectTemp /4-17 https://pypi.org/project/pyuca/ +RedirectTemp /4-18 https://github.com/jtauber/pyuca +RedirectTemp /4-19 http://www.unicode.org/Public/UCA/6.3.0/allkeys.txt +RedirectTemp /4-20 https://pypi.org/project/PyICU/ +RedirectTemp /4-21 https://docs.python.org/3.10/library/stdtypes.html#str.isalpha +RedirectTemp /4-22 https://en.wikipedia.org/wiki/Unicode_character_property#General_Category +RedirectTemp /4-23 https://en.wikipedia.org/wiki/Unicode_character_property +RedirectTemp /4-24 https://github.com/microsoft/terminal +RedirectTemp /4-25 https://docs.python.org/3/library/unicodedata.html +RedirectTemp /4-26 https://docs.python.org/3/reference/lexical_analysis.html#string-literal-concatenation +RedirectTemp /4-27 https://docs.python.org/3/library/re.html +RedirectTemp /4-28 https://nedbatchelder.com/text/unipain.html +RedirectTemp /4-29 https://www.slideshare.net/fischertrav/character-encoding-unicode-how-to-with-dignity-33352863 +RedirectTemp /4-30 https://pyvideo.org/video/2625/character-encoding-and-unicode-in-python/ +RedirectTemp /4-31 https://regebro.wordpress.com/2011/03/23/unconfusing-unicode-what-is-unicode/ +RedirectTemp /4-32 https://docs.python.org/3/howto/unicode.html +RedirectTemp /4-33 https://diveintopython3.net/strings.html +RedirectTemp /4-34 https://diveintopython3.net/ +RedirectTemp /4-35 https://finderiko.com/python-book +RedirectTemp /4-36 https://docs.python.org/3.0/whatsnew/3.0.html#text-vs-data-instead-of-unicode-vs-8-bit +RedirectTemp /4-37 https://lucumr.pocoo.org/2013/7/2/the-updated-guide-to-unicode/ +RedirectTemp /4-38 http://python-notes.curiousefficiency.org/en/latest/python3/binary_protocols.html +RedirectTemp /4-39 http://python-notes.curiousefficiency.org/en/latest/python3/text_file_processing.html +RedirectTemp /4-40 https://docs.python.org/3/library/codecs.html#standard-encodings +RedirectTemp /4-41 https://github.com/python/cpython/blob/0bbf30e2b910bc9c5899134ae9d73a8df968da35/Tools/unicode/listcodecs.py +RedirectTemp /4-42 https://www.oreilly.com/library/view/unicode-explained/059610121X/ +RedirectTemp /4-43 https://www.informit.com/store/unicode-demystified-a-practical-programmers-guide-to-9780201700527 +RedirectTemp /4-44 https://unicodebook.readthedocs.io/index.html +RedirectTemp /4-45 https://www.w3.org/International/wiki/Case_folding +RedirectTemp /4-46 http://www.w3.org/TR/charmod-norm/ +RedirectTemp /4-47 http://unicode.org/reports/tr15/ +RedirectTemp /4-48 http://www.unicode.org/faq/normalization.html +RedirectTemp /4-49 http://www.unicode.org/ +RedirectTemp /4-50 http://www.macchiato.com/unicode/nfc-faq +RedirectTemp /4-51 https://stories.moma.org/the-original-emoji-set-has-been-added-to-the-museum-of-modern-arts-collection-c6060e141f61?gi=c403f5a840a5 +RedirectTemp /4-52 https://emojipedia.org/ +RedirectTemp /4-53 https://blog.emojipedia.org/correcting-the-record-on-the-first-emoji-set/ +RedirectTemp /4-54 http://emojitracker.com/ +RedirectTemp /4-55 http://www.unicode.org/glossary/#plain_text +RedirectTemp /4-56 http://www.methods.co.nz/asciidoc/ +RedirectTemp /4-57 https://atlas.oreilly.com/ +############################################################ 05 +RedirectTemp /5-1 https://docs.python.org/3/library/typing.html#typing.TypedDict +RedirectTemp /5-2 https://docs.python.org/3.10/library/inspect.html#inspect.get_annotations +RedirectTemp /5-3 https://docs.python.org/3/library/typing.html#typing.get_type_hints +RedirectTemp /5-4 https://docs.python.org/3.8/library/collections.html#collections.somenamedtuple._asdict +RedirectTemp /5-5 https://www.jetbrains.com/pycharm/ +RedirectTemp /5-6 https://www.python.org/dev/peps/pep-0484/#acceptable-type-hints +RedirectTemp /5-7 https://docs.python.org/3/library/dataclasses.html#dataclasses.dataclass +RedirectTemp /5-8 https://docs.python.org/3/library/dataclasses.html#dataclasses.dataclass +RedirectTemp /5-9 https://docs.python.org/3/library/dataclasses.html +RedirectTemp /5-10 https://docs.python.org/3/library/dataclasses.html#inheritance +RedirectTemp /5-11 https://www.python.org/dev/peps/pep-0526/#class-and-instance-variable-annotations +RedirectTemp /5-12 https://dublincore.org/specifications/dublin-core/ +RedirectTemp /5-13 https://en.wikipedia.org/wiki/Dublin_Core +RedirectTemp /5-14 https://martinfowler.com/bliki/CodeSmell.html +RedirectTemp /5-15 https://martinfowler.com/books/refactoring.html +RedirectTemp /5-16 https://www.python.org/dev/peps/pep-0634/#class-patterns +RedirectTemp /5-17 https://docs.python.org/3/library/dataclasses.html +RedirectTemp /5-18 https://www.python.org/dev/peps/pep-0557/#id47 +RedirectTemp /5-19 https://www.python.org/dev/peps/pep-0557/#id48 +RedirectTemp /5-20 https://www.python.org/dev/peps/pep-0557/#id33 +RedirectTemp /5-21 https://realpython.com +RedirectTemp /5-22 https://realpython.com/python-data-classes/ +RedirectTemp /5-23 https://www.youtube.com/watch?v=T-TwcmT6Rcw +RedirectTemp /5-24 https://www.attrs.org/en/stable/ +RedirectTemp /5-25 https://glyph.twistedmatrix.com/2016/08/attrs.html +RedirectTemp /5-26 https://www.attrs.org/en/stable/why.html +RedirectTemp /5-27 https://github.com/dabeaz/cluegen +RedirectTemp /5-28 https://refactoring.guru/ +RedirectTemp /5-29 https://refactoring.guru/smells/data-class +RedirectTemp /5-30 https://web.archive.org/web/20190204130328/http://catb.org/esr/jargon/html/G/Guido.html +RedirectTemp /5-31 https://web.archive.org/web/20190211161610/http://catb.org/esr/jargon/html/index.html +RedirectTemp /5-32 https://www.attrs.org/en/stable/ +############################################################ 06 +RedirectTemp /6-1 https://www.olin.edu/faculty/profile/lynn-andrea-stein/ +RedirectTemp /6-2 https://docs.python.org/3/reference/datamodel.html#objects-values-and-types +RedirectTemp /6-3 https://pythontutor.com/ +RedirectTemp /6-4 https://docs.python.org/3/library/copy.html +RedirectTemp /6-5 https://en.wikipedia.org/wiki/Principle_of_least_astonishment +RedirectTemp /6-6 https://docs.python.org/3/reference/datamodel.html#object.%5C_%5C_del__ +RedirectTemp /6-7 https://emptysqua.re/blog/pypy-garbage-collection-and-a-deadlock/ +RedirectTemp /6-8 https://www.youtube.com/watch?v=HHFCFJSPWrI&feature=youtu.be +RedirectTemp /6-9 http://pymotw.com/3/copy/ +RedirectTemp /6-10 http://pymotw.com/3/weakref/ +RedirectTemp /6-11 https://docs.python.org/3/library/gc.html +RedirectTemp /6-12 https://devguide.python.org/garbage_collector/ +RedirectTemp /6-13 https://devguide.python.org/ +RedirectTemp /6-14 https://www.python.org/dev/peps/pep-0442/ +RedirectTemp /6-15 https://en.wikipedia.org/wiki/String_interning +RedirectTemp /6-16 https://en.wikipedia.org/wiki/Haddocks%27_Eyes +RedirectTemp /6-17 https://thp.io/2012/python-gc/python_gc_final_2012-01-22.pdf +############################################################ 07 +RedirectTemp /7-1 http://python-history.blogspot.com/2009/04/origins-of-pythons-functional-features.html +RedirectTemp /7-2 https://www.fluentpython.com/extra/function-introspection/ +RedirectTemp /7-3 https://docs.python.org/3/library/functions.html#map +RedirectTemp /7-4 https://en.wikipedia.org/wiki/Functional_programming +RedirectTemp /7-5 https://docs.python.org/3/howto/functional.html +RedirectTemp /7-6 https://docs.python.org/3/reference/datamodel.html#the-standard-type-hierarchy +RedirectTemp /7-7 https://docs.python.org/3/whatsnew/3.8.html#positional-only-parameters +RedirectTemp /7-8 https://docs.python.org/3/whatsnew/3.8.html#positional-only-parameters +RedirectTemp /7-9 https://github.com/python/cpython/blob/0bbf30e2b910bc9c5899134ae9d73a8df968da35/Lib/functools.py#L341 +RedirectTemp /7-10 https://docs.python.org/3/reference/datamodel.html#the-standard-type-hierarchy +RedirectTemp /7-11 https://docs.python.org/3/howto/functional.html +RedirectTemp /7-12 https://stackoverflow.com/questions/3252228/python-why-is-functools-partial-necessary +RedirectTemp /7-13 https://speakerdeck.com/ramalho/beyond-paradigms-berlin-edition +RedirectTemp /7-14 https://www.youtube.com/watch?v=bF3a2VYXxa0 +RedirectTemp /7-15 http://cs.brown.edu/~sk/Publications/Papers/Published/sk-teach-pl-post-linnaean/ +RedirectTemp /7-16 http://python-history.blogspot.com/2009/04/origins-of-pythons-functional-features.html +RedirectTemp /7-17 https://raw.githubusercontent.com/python/cpython/main/Misc/HISTORY +RedirectTemp /7-18 http://neopythonic.blogspot.com/2009/04/tail-recursion-elimination.html +############################################################ 08 +RedirectTemp /8-1 https://www.python.org/dev/peps/pep-0484/#non-goals +RedirectTemp /8-2 https://github.com/python/typing/issues/182 +RedirectTemp /8-3 https://github.com/python/mypy/issues/731 +RedirectTemp /8-4 https://github.com/google/pytype +RedirectTemp /8-5 https://github.com/Microsoft/pyright +RedirectTemp /8-6 https://pyre-check.org/ +RedirectTemp /8-7 https://mypy.readthedocs.io/en/stable/introduction.html +RedirectTemp /8-8 https://mypy.readthedocs.io/en/stable/config_file.html +RedirectTemp /8-9 https://pypi.org/project/flake8/ +RedirectTemp /8-10 https://pypi.org/project/blue/ +RedirectTemp /8-11 https://pypi.org/project/black/ +RedirectTemp /8-12 https://wefearchange.org/2020/11/steeringcouncil.rst.html +RedirectTemp /8-13 https://docs.python.org/3/library/collections.abc.html#collections-abstract-base-classes +RedirectTemp /8-14 https://en.wikipedia.org/wiki/Barbara_Liskov +RedirectTemp /8-15 https://en.wikipedia.org/wiki/Behavioral_subtyping +RedirectTemp /8-16 https://www.python.org/dev/peps/pep-0585/#implementation +RedirectTemp /8-17 https://docs.python.org/3/library/typing.html#module-contents +RedirectTemp /8-18 https://en.wikipedia.org/wiki/Geohash +RedirectTemp /8-19 https://en.wikipedia.org/wiki/Inverted_index +RedirectTemp /8-20 https://docs.python.org/3/library/typing.html#typing.List +RedirectTemp /8-21 https://docs.python.org/3/library/typing.html#typing.Dict +RedirectTemp /8-22 https://docs.python.org/3/library/typing.html#typing.Set +RedirectTemp /8-23 https://www.python.org/dev/peps/pep-0585/#implementation +RedirectTemp /8-24 https://docs.python.org/3/library/numbers.html +RedirectTemp /8-25 https://docs.python.org/3/library/typing.html#typing.List +RedirectTemp /8-26 https://github.com/python/typeshed +RedirectTemp /8-27 https://github.com/python/typeshed/blob/66cd36268a6a667714efaa27198a41d0d7f89477/stdlib/2and3/math.pyi#L45 +RedirectTemp /8-28 https://docs.python.org/3/library/statistics.html#statistics.mode +RedirectTemp /8-29 https://github.com/python/cpython/blob/822efa5695b5ba6c2316c1400e4e9ec2546f7ea5/Lib/statistics.py#L534 +RedirectTemp /8-30 https://github.com/python/typeshed/blob/e1e99245bb46223928eba68d4fc74962240ba5b4/stdlib/3/statistics.pyi +RedirectTemp /8-31 https://docs.python.org/3/library/statistics.html#statistics.mode +RedirectTemp /8-32 https://github.com/python/typeshed/blob/a8834fcd46339e17fc8add82b5803a1ce53d3d60/stdlib/3/statistics.pyi#L32 +RedirectTemp /8-33 https://mail.python.org/archives/list/python-dev@python.org/thread/CLVXXPQ2T2LQ5MP2Y53VVQFCXYWQJHKZ/ +RedirectTemp /8-34 https://docs.python.org/3/library/typing.html#typing.Callable +RedirectTemp /8-35 https://pypi.org/project/blue/ +RedirectTemp /8-36 https://www.python.org/dev/peps/pep-0484/#id38 +RedirectTemp /8-37 https://docs.google.com/document/d/1aXs1tpwzPjW9MdsG5dI7clNFyYayFBkcXwRDo-qvbIk/preview +RedirectTemp /8-38 https://www.oreilly.com/library/view/the-best-software/9781590595008/ +RedirectTemp /8-39 https://www.youtube.com/watch?v=YFexUDjHO6w +RedirectTemp /8-40 https://www.youtube.com/watch?v=YFexUDjHO6w&t=13m40s +RedirectTemp /8-41 https://bernat.tech/posts/the-state-of-type-hints-in-python/ +RedirectTemp /8-42 https://realpython.com/python-type-checking/ +RedirectTemp /8-43 https://cjolowicz.github.io/posts/hypermodern-python-04-typing/ +RedirectTemp /8-44 https://mypy.readthedocs.io/en/stable/index.html +RedirectTemp /8-45 https://mypy.readthedocs.io/en/stable/cheat_sheet_py3.html +RedirectTemp /8-46 https://mypy.readthedocs.io/en/stable/common_issues.html +RedirectTemp /8-47 https://github.com/typeddjango/awesome-python-typing +RedirectTemp /8-48 https://docs.python.org/3/library/functions.html#max +RedirectTemp /8-49 https://en.wikipedia.org/wiki/Linguistic_relativity +RedirectTemp /8-50 https://pypistats.org/top +RedirectTemp /8-51 https://github.com/psf/requests/issues/3855 +RedirectTemp /8-52 https://lwn.net/Articles/643399/ +RedirectTemp /8-53 https://docs.python-requests.org/en/master/api/#requests.request +RedirectTemp /8-54 https://queue.acm.org/detail.cfm?id=1039523 +############################################################ 09 +RedirectTemp /9-1 https://docs.python.org/3/library/dis.html +RedirectTemp /9-2 https://en.wikipedia.org/wiki/Memoization +RedirectTemp /9-3 https://numpy.org/doc/stable/user/basics.types.html +RedirectTemp /9-4 https://docs.python.org/3/library/functools.html#functools.singledispatch +RedirectTemp /9-5 https://github.com/GrahamDumpleton/wrapt/blob/develop/blog/README.md +RedirectTemp /9-6 https://github.com/GrahamDumpleton/wrapt/blob/develop/blog/01-how-you-implemented-your-python-decorator-is-wrong.md +RedirectTemp /9-7 https://wrapt.readthedocs.io/en/latest/ +RedirectTemp /9-8 https://www.oreilly.com/library/view/python-cookbook-3rd/9781449357337/ch09.html +RedirectTemp /9-9 https://pypi.org/project/decorator/ +RedirectTemp /9-10 https://wiki.python.org/moin/PythonDecoratorLibrary +RedirectTemp /9-11 http://web.archive.org/web/20201109032203/http://effbot.org/zone/closure.htm +RedirectTemp /9-12 https://www.python.org/dev/peps/pep-3104/ +RedirectTemp /9-13 https://www.python.org/dev/peps/pep-0227/ +RedirectTemp /9-14 https://www.python.org/dev/peps/pep-0443/ +RedirectTemp /9-15 https://www.artima.com/weblogs/viewpost.jsp?thread=101605 +RedirectTemp /9-16 https://reg.readthedocs.io/en/latest/ +RedirectTemp /9-17 https://morepath.readthedocs.io/en/latest/ +RedirectTemp /9-18 https://www.gnu.org/software/emacs/manual/html_node/elisp/Dynamic-Binding.html +RedirectTemp /9-19 http://www.paulgraham.com/rootsoflisp.html +RedirectTemp /9-20 http://www-formal.stanford.edu/jmc/recursive/recursive.html +RedirectTemp /9-21 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this +############################################################ 10 +RedirectTemp /10-1 https://en.wikipedia.org/wiki/Software_design_pattern +RedirectTemp /10-2 https://en.wikipedia.org/wiki/Iterator_pattern +RedirectTemp /10-3 https://github.com/python/mypy/issues/9397 +RedirectTemp /10-4 http://www.norvig.com/design-patterns/index.htm +RedirectTemp /10-5 https://pyvideo.org/video/1110/python-design-patterns/ +RedirectTemp /10-6 http://www.aleax.it/gdd_pydp.pdf +RedirectTemp /10-7 https://perl.plover.com/yak/design/ +RedirectTemp /10-8 https://en.wikipedia.org/wiki/Turtles_all_the_way_down +############################################################ 11 +RedirectTemp /11-1 https://blog.startifact.com/posts/older/what-is-pythonic.html +RedirectTemp /11-2 https://julien.danjou.info/guide-python-static-class-abstract-methods/ +RedirectTemp /11-3 https://docs.python.org/3/library/string.html#formatspec +RedirectTemp /11-4 https://docs.python.org/3/reference/lexical_analysis.html#f-strings +RedirectTemp /11-5 https://docs.python.org/3/library/string.html#format-string-syntax +RedirectTemp /11-6 https://docs.python.org/3/library/string.html#formatspec +RedirectTemp /11-7 https://docs.python.org/3/reference/datamodel.html#object.__hash__ +RedirectTemp /11-8 https://web.archive.org/web/20161025185040/http://pythonpaste.org/StyleGuide.html +RedirectTemp /11-9 https://docs.python.org/3/tutorial/modules.html#more-on-modules +RedirectTemp /11-10 https://docs.python.org/3/library/gettext.html#gettext.NullTranslations +RedirectTemp /11-11 https://github.com/fluentpython/example-code-2e/blob/master/11-pythonic-obj/mem_test.py +RedirectTemp /11-12 https://docs.python.org/3/reference/datamodel.html#basic-customization +RedirectTemp /11-13 http://esug.org/data/HistoricalDocuments/TheSmalltalkReport/ST07/04wo.pdf +RedirectTemp /11-14 https://www.artima.com/articles/the-simplest-thing-that-could-possibly-work#part3 +RedirectTemp /11-15 https://docs.oracle.com/javase/tutorial/essential/environment/security.html +RedirectTemp /11-16 https://github.com/fluentpython/example-code-2e/blob/master/11-pythonic-obj/private/Expose.java +RedirectTemp /11-17 https://docs.oracle.com/javase/tutorial/essential/environment/security.html +############################################################ 12 +RedirectTemp /12-1 https://en.wikipedia.org/wiki/Vector_space_model +RedirectTemp /12-2 https://pypi.org/project/gensim/ +RedirectTemp /12-3 https://docs.python.org/3/library/functions.html#enumerate +RedirectTemp /12-4 https://mathworld.wolfram.com/Hypersphere.html +RedirectTemp /12-5 https://en.wikipedia.org/wiki/Fold_(higher-order_function) +RedirectTemp /12-6 https://docs.python.org/2.5/whatsnew/pep-357.html +RedirectTemp /12-7 https://docs.python.org/3/reference/datamodel.html#special-method-names +RedirectTemp /12-8 https://en.wikipedia.org/wiki/KISS_principle +RedirectTemp /12-9 https://mail.python.org/pipermail/python-list/2000-July/046184.html +RedirectTemp /12-10 https://en.wikipedia.org/wiki/Duck_typing +RedirectTemp /12-11 https://mail.python.org/mailman/listinfo/python-list +RedirectTemp /12-12 https://mail.python.org/pipermail/python-list/2003-April/218568.html +############################################################ 13 +RedirectTemp /13-1 https://docs.python.org/3/c-api/index.html +RedirectTemp /13-2 https://docs.python.org/3/c-api/sequence.html +RedirectTemp /13-3 https://github.com/python/cpython/blob/31ceccb2c77854893f3a754aca04bedd74bedb10/Lib/_collections_abc.py#L870 +RedirectTemp /13-4 https://en.wikipedia.org/wiki/Monkey_patch +RedirectTemp /13-5 https://www.gevent.org/api/gevent.monkey.html +RedirectTemp /13-6 https://docs.python.org/3/library/random.html#random.shuffle +RedirectTemp /13-7 https://docs.python.org/3/reference/datamodel.html#emulating-container-types +RedirectTemp /13-8 https://docs.python.org/3/library/collections.html#collections.namedtuple +RedirectTemp /13-9 https://github.com/python/typeshed/blob/24afb531ffd07083d6a74be917342195062f7277/stdlib/collections/__init__.pyi +RedirectTemp /13-10 https://docs.python.org/3/glossary.html#term-abstract-base-class +RedirectTemp /13-11 https://en.wikipedia.org/wiki/Duck_typing#History +RedirectTemp /13-12 http://ptgmedia.pearsoncmg.com/images/020163371x/items/item33.html +RedirectTemp /13-13 https://docs.python.org/3/library/bisect.html#bisect.bisect +RedirectTemp /13-14 https://github.com/python/cpython/blob/main/Lib/_collections_abc.py +RedirectTemp /13-15 https://github.com/python/cpython/blob/main/Lib/abc.py +RedirectTemp /13-16 https://docs.python.org/3/library/collections.abc.html#collections-abstract-base-classes +RedirectTemp /13-17 https://docs.python.org/3/library/collections.abc.html#collections.abc.Iterable +RedirectTemp /13-18 https://docs.python.org/3/library/abc.html +RedirectTemp /13-19 https://docs.python.org/dev/library/abc.html#abc.abstractmethod +RedirectTemp /13-20 https://docs.python.org/dev/library/abc.html +RedirectTemp /13-21 https://docs.python.org/3/library/os.html#os.urandom +RedirectTemp /13-22 https://github.com/python/mypy/issues/2922 +RedirectTemp /13-23 https://docs.python.org/3/library/stdtypes.html#truth +RedirectTemp /13-24 https://github.com/python/cpython/blob/0bbf30e2b910bc9c5899134ae9d73a8df968da35/Lib/_collections_abc.py +RedirectTemp /13-25 https://github.com/python/cpython/blob/0fbddb14dc03f61738af01af88e7d8aa8df07336/Lib/_collections_abc.py#L369 +RedirectTemp /13-26 https://github.com/python/cpython/blob/c0a9afe2ac1820409e6173bd1893ebee2cf50270/Lib/abc.py#L196 +RedirectTemp /13-27 https://bugs.python.org/issue31333 +RedirectTemp /13-28 https://github.com/python/cpython/blob/3635388f52b42e5280229104747962117104c453/Modules/_abc.c#L605 +RedirectTemp /13-29 https://github.com/python/cpython/blob/0fbddb14dc03f61738af01af88e7d8aa8df07336/Lib/_collections_abc.py#L881 +RedirectTemp /13-30 https://docs.python.org/3/library/typing.html#protocols +RedirectTemp /13-31 https://github.com/python/cpython/blob/3635388f52b42e5280229104747962117104c453/Lib/typing.py#L1751 +RedirectTemp /13-32 https://mail.python.org/archives/list/python-dev@python.org/thread/CLVXXPQ2T2LQ5MP2Y53VVQFCXYWQJHKZ/ +RedirectTemp /13-33 https://martinfowler.com/bliki/RoleInterface.html +RedirectTemp /13-34 https://en.wikipedia.org/wiki/Interface_segregation_principle +RedirectTemp /13-35 https://github.com/python/typeshed/blob/master/CONTRIBUTING.md +RedirectTemp /13-36 https://gist.github.com/asukakenji/ac8a05644a2e98f1d5ea8c299541fce9 +RedirectTemp /13-37 https://www.python.org/dev/peps/pep-0544/#runtime-checkable-decorator-and-narrowing-types-by-isinstance +RedirectTemp /13-38 https://www.python.org/dev/peps/pep-0544/#merging-and-extending-protocols +RedirectTemp /13-39 https://numpy.org/devdocs/user/basics.types.html +RedirectTemp /13-40 https://github.com/python/typeshed/blob/master/stdlib/statistics.pyi +RedirectTemp /13-41 https://bugs.python.org/issue41974 +RedirectTemp /13-42 https://glyph.twistedmatrix.com/2020/07/new-duck.html +RedirectTemp /13-43 https://glyph.twistedmatrix.com/2021/03/interfaces-and-protocols.html +RedirectTemp /13-44 https://plone.org/ +RedirectTemp /13-45 https://trypyramid.com/ +RedirectTemp /13-46 https://twistedmatrix.com/trac/ +RedirectTemp /13-47 https://www.artima.com/articles/contracts-in-python +RedirectTemp /13-48 https://martinfowler.com/bliki/DynamicTyping.html +RedirectTemp /13-49 https://martinfowler.com/bliki/RoleInterface.html +RedirectTemp /13-50 https://mypy.readthedocs.io/en/stable/protocols.html +RedirectTemp /13-51 https://pymotw.com/3/abc/index.html +RedirectTemp /13-52 https://www.python.org/dev/peps/pep-3119/ +RedirectTemp /13-53 https://www.python.org/dev/peps/pep-3141/ +RedirectTemp /13-54 https://docs.python.org/3/library/numbers.html +RedirectTemp /13-55 https://github.com/python/mypy/issues/3186 +RedirectTemp /13-56 https://stackoverflow.com/questions/69334475/how-to-hint-at-number-types-i-e-subclasses-of-number-not-numbers-themselv/69383462#69383462 +RedirectTemp /13-57 https://github.com/python/mypy/issues/3186 +RedirectTemp /13-58 https://martinfowler.com/articles/lean-inception/ +RedirectTemp /13-59 https://martinfowler.com +RedirectTemp /13-60 https://www.jetbrains.com/pycharm/ +RedirectTemp /13-61 https://wingware.com/ +RedirectTemp /13-62 https://code.visualstudio.com/ +############################################################ 14 +RedirectTemp /14-1 http://worrydream.com/EarlyHistoryOfSmalltalk/ +RedirectTemp /14-2 https://docs.python.org/3/tutorial/classes.html +RedirectTemp /14-3 https://docs.python.org/3/library/collections.html#ordereddict-examples-and-recipes +RedirectTemp /14-4 https://discuss.python.org/t/is-it-time-to-deprecate-unbound-super-methods/1833 +RedirectTemp /14-5 https://doc.pypy.org/en/latest/cpython_differences.html#subclasses-of-built-in-types +RedirectTemp /14-6 https://docs.python.org/3/library/collections.html +RedirectTemp /14-7 https://github.com/fluentpython/example-code-2e/blob/master/14-inheritance/strkeydict_dictsub.py +RedirectTemp /14-8 https://doc.pypy.org/en/latest/cpython_differences.html#subclasses-of-built-in-types +RedirectTemp /14-9 https://en.wikipedia.org/wiki/Breadth-first_search +RedirectTemp /14-10 https://www.python.org/download/releases/2.3/mro/ +RedirectTemp /14-11 https://github.com/fluentpython/example-code-2e/blob/master/14-inheritance/uppermixin.py +RedirectTemp /14-12 https://docs.oracle.com/javase/tutorial/java/IandI/defaultmethods.html +RedirectTemp /14-13 https://docs.python.org/3/library/collections.abc.html +RedirectTemp /14-14 https://github.com/python/cpython/blob/8ece98a7e418c3c68a4c61bc47a2d0931b59a889/Lib/collections/__init__.py#L1084 +RedirectTemp /14-15 https://docs.python.org/3/library/http.server.html +RedirectTemp /14-16 https://github.com/python/cpython/blob/17c23167942498296f0bdfffe52e72d53d66d693/Lib/http/server.py#L144 +RedirectTemp /14-17 https://github.com/python/cpython/blob/699ee016af5736ffc80f68359617611a22b72943/Lib/socketserver.py#L664 +RedirectTemp /14-18 https://docs.python.org/3/library/socketserver.html#socketserver.ForkingMixIn +RedirectTemp /14-19 https://docs.python.org/3/library/os.html#os.fork +RedirectTemp /14-20 https://en.wikipedia.org/wiki/POSIX +RedirectTemp /14-21 http://ccbv.co.uk/ +RedirectTemp /14-22 https://github.com/django/django/tree/main/django/views/generic +RedirectTemp /14-23 https://en.wikipedia.org/wiki/Template_method_pattern +RedirectTemp /14-24 https://docs.python.org/3/library/tkinter.html +RedirectTemp /14-25 https://docs.python.org/3/library/tkinter.ttk.html +RedirectTemp /14-26 https://docs.oracle.com/javase/10/docs/api/java/awt/package-tree.html +RedirectTemp /14-27 https://docs.oracle.com/javase/10/docs/api/javax/swing/package-tree.html +RedirectTemp /14-28 https://squeak.org/ +RedirectTemp /14-29 https://github.com/django/django/blob/b64db05b9cedd96905d637a2d824cbbf428e40e7/django/views/generic/list.py#L194 +RedirectTemp /14-30 https://github.com/python/cpython/blob/8ed183391241f0c73e7ba7f42b1d49fc02985f7b/Lib/tkinter/__init__.py#L2618 +RedirectTemp /14-31 https://docs.python.org/3/library/socketserver.html +RedirectTemp /14-32 https://docs.python.org/3/library/socketserver.html#socketserver.BaseServer +RedirectTemp /14-33 https://github.com/python/cpython/blob/699ee016af5736ffc80f68359617611a22b72943/Lib/socketserver.py#L153 +RedirectTemp /14-34 https://docs.python.org/3/library/typing.html#typing.final +RedirectTemp /14-35 https://docs.python.org/3/library/typing.html#typing.Final +RedirectTemp /14-36 https://docs.python.org/3/library/collections.abc.html +RedirectTemp /14-37 https://hynek.me/articles/python-subclassing-redux/ +RedirectTemp /14-38 https://www.oreilly.com/library/view/python-cookbook-3rd/9781449357337/ch08.html#super +RedirectTemp /14-39 https://rhettinger.wordpress.com/2011/05/26/super-considered-super/ +RedirectTemp /14-40 https://fuhm.net/super-harmful/ +RedirectTemp /14-41 https://stackoverflow.com/questions/30190185/how-to-use-super-with-one-argument/30190341#30190341 +RedirectTemp /14-42 https://www.artima.com/weblogs/viewpost.jsp?thread=246488 +RedirectTemp /14-43 https://www.artima.com/weblogs/viewpost.jsp?thread=281127 +RedirectTemp /14-44 https://www.artima.com/weblogs/viewpost.jsp?thread=246341 +RedirectTemp /14-45 https://www.artima.com/weblogs/viewpost.jsp?thread=246483 +RedirectTemp /14-46 https://www.artima.com/weblogs/viewpost.jsp?thread=236275 +RedirectTemp /14-47 https://www.artima.com/weblogs/viewpost.jsp?thread=236278 +RedirectTemp /14-48 https://www.artima.com/weblogs/viewpost.jsp?thread=237121 +RedirectTemp /14-49 https://python-patterns.guide/gang-of-four/composition-over-inheritance/ +RedirectTemp /14-50 https://python-patterns.guide/ +RedirectTemp /14-51 https://www.youtube.com/watch?v=3MNVP9-hglc +RedirectTemp /14-52 http://worrydream.com/EarlyHistoryOfSmalltalk/ +RedirectTemp /14-53 https://en.wikipedia.org/wiki/Polymorphism_(computer_science) +############################################################ 15 +RedirectTemp /15-1 https://www.youtube.com/watch?v=csL8DLXGNlU&t=92m5s +RedirectTemp /15-2 https://github.com/python/typeshed/blob/a8834fcd46339e17fc8add82b5803a1ce53d3d60/stdlib/2and3/builtins.pyi#L1434 +RedirectTemp /15-3 https://github.com/python/typeshed/blob/a8834fcd46339e17fc8add82b5803a1ce53d3d60/stdlib/2and3/builtins.pyi +RedirectTemp /15-4 https://twitter.com/gwidion/status/1265384692464967680 +RedirectTemp /15-5 https://pypi.org/project/pydantic/ +RedirectTemp /15-6 https://google.github.io/pytype/faq.html +RedirectTemp /15-7 https://google.github.io/pytype/faq.html +RedirectTemp /15-8 https://lxml.de/ +RedirectTemp /15-9 https://docs.python.org/3/library/xml.etree.elementtree.html +RedirectTemp /15-10 https://mypy.readthedocs.io/en/stable/common_issues.html +RedirectTemp /15-11 https://mypy.readthedocs.io/en/stable/common_issues.html#types-of-empty-collections +RedirectTemp /15-12 https://github.com/python/typing/issues/182 +RedirectTemp /15-13 https://pypi.org/project/pydantic/ +RedirectTemp /15-14 https://mypy.readthedocs.io/en/stable/type_narrowing.html#casts +RedirectTemp /15-15 https://github.com/python/cpython/blob/bee66d3cb98e740f9d8057eb7f503122052ca5d8/Lib/typing.py#L1340 +RedirectTemp /15-16 https://www.python.org/dev/peps/pep-0484/#casts +RedirectTemp /15-17 https://github.com/python/typeshed/issues/5535 +RedirectTemp /15-18 https://docs.python.org/3/library/asyncio-stream.html#tcp-echo-server-using-streams +RedirectTemp /15-19 https://github.com/python/cpython/blob/b798ab06937f8bb24b444a49dd42e11fff15e654/Lib/test/test_asyncio/test_server.py#L55 +RedirectTemp /15-20 https://en.wikipedia.org/wiki/Code_smell +RedirectTemp /15-21 https://mail.python.org/archives/list/typing-sig@python.org/message/5LCWMN2UY2UQNLC5Z47GHBZKSPZW4I63/ +RedirectTemp /15-22 https://mypy.readthedocs.io/en/stable/error_codes.html#error-codes +RedirectTemp /15-23 https://github.com/fluentpython/example-code-2e/blob/master/15-more-types/clip_annot.py +RedirectTemp /15-24 https://docs.python.org/3/library/typing.html#introspection-helpers +RedirectTemp /15-25 https://docs.python.org/3.10/library/inspect.html#inspect.get_annotations +RedirectTemp /15-26 https://www.python.org/dev/peps/pep-0563/#abstract +RedirectTemp /15-27 https://mail.python.org/archives/list/python-dev@python.org/message/ZBJ7MD6CSGM6LZAOTET7GXAVBZB7O77O/ +RedirectTemp /15-28 https://docs.python.org/3.10/howto/annotations.html +RedirectTemp /15-29 https://docs.python.org/3/library/typing.html#user-defined-generic-types +RedirectTemp /15-30 https://github.com/python/typeshed/blob/bfc83c365a0b26ab16586beac77ff16729d0e473/stdlib/builtins.pyi#L743 +RedirectTemp /15-31 https://docs.python.org/3.10/library/typing.html#typing.FrozenSet +RedirectTemp /15-32 https://docs.python.org/3.10/library/typing.html#typing.Generator +RedirectTemp /15-33 https://docs.python.org/3.10/library/typing.html#typing.AsyncGenerator +RedirectTemp /15-34 https://github.com/python/cpython/blob/46b16d0bdbb1722daed10389e27226a2370f1635/Lib/typing.py#L1786 +RedirectTemp /15-35 https://github.com/python/typeshed/blob/2a9f081abbf01134e4e04ced6a750107db904d70/stdlib/builtins.pyi#L239 +RedirectTemp /15-36 https://www.oreilly.com/library/view/robust-python/9781098100650/ +RedirectTemp /15-37 https://www.python.org/dev/peps/pep-0484/#covariance-and-contravariance +RedirectTemp /15-38 https://mypy.readthedocs.io/en/stable/generics.html#variance-of-generic-types +RedirectTemp /15-39 https://mypy.readthedocs.io/en/stable/common_issues.html#variance +RedirectTemp /15-40 https://www.artima.com/weblogs/viewpost.jsp?thread=85551 +RedirectTemp /15-41 https://dl.acm.org/action/cookieAbsent +RedirectTemp /15-42 http://bracha.org/pluggableTypesPosition.pdf +RedirectTemp /15-43 https://www.researchgate.net/publication/213886116_Static_Typing_Where_Possible_Dynamic_Typing_When_Needed_The_End_of_the_Cold_War_Between_Programming_Languages +RedirectTemp /15-44 https://www.atomickotlin.com/atomickotlin/ +RedirectTemp /15-45 https://www.informit.com/store/effective-java-9780134685991 +RedirectTemp /15-46 https://www.manning.com/books/programming-with-types +RedirectTemp /15-47 https://www.oreilly.com/library/view/programming-typescript/9781492037644/ +RedirectTemp /15-48 https://www.informit.com/store/dart-programming-language-9780321927705 +RedirectTemp /15-49 https://www.yodaiken.com/2017/09/15/bad-ideas-in-type-theory/ +RedirectTemp /15-50 https://www.yodaiken.com/2017/11/30/types-considered-harmful-ii/ +RedirectTemp /15-51 https://web.archive.org/web/20071010002142/http://weblogs.java.net/blog/arnold/archive/2005/06/generics_consid_1.html +RedirectTemp /15-52 https://github.com/python/cpython/blob/3e7ee02327db13e4337374597cdc4458ecb9e3ad/Lib/asyncio/trsock.py#L5 +RedirectTemp /15-53 https://www.python.org/dev/peps/pep-0484/#covariance-and-contravariance +############################################################ 16 +RedirectTemp /16-1 http://www.gotw.ca/publications/c_family_interview.htm +RedirectTemp /16-2 https://docs.python.org/3/reference/expressions.html#unary-arithmetic-and-bitwise-operations +RedirectTemp /16-3 https://docs.python.org/3/reference/datamodel.html#object.__neg__ +RedirectTemp /16-4 https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#boolean-indexing +RedirectTemp /16-5 https://docs.python.org/3/library/collections.html#collections.Counter +RedirectTemp /16-6 https://docs.python.org/3/reference/datamodel.html#emulating-container-types +RedirectTemp /16-7 https://docs.python.org/3/library/numbers.html#implementing-the-arithmetic-operations +RedirectTemp /16-8 https://www.fluentpython.com/lingo/#fail-fast +RedirectTemp /16-9 https://github.com/python/cpython/blob/0bbf30e2b910bc9c5899134ae9d73a8df968da35/Objects/typeobject.c#L4598 +RedirectTemp /16-10 https://neopythonic.blogspot.com/2019/03/why-operators-are-useful.html +RedirectTemp /16-11 https://treyhunner.com/2019/03/python-deep-comparisons-and-code-readability/ +RedirectTemp /16-12 https://docs.python.org/3/library/numbers.html#implementing-the-arithmetic-operations +RedirectTemp /16-13 https://docs.python.org/3/library/pathlib.html +RedirectTemp /16-14 https://pypi.org/project/scapy/ +RedirectTemp /16-15 https://scapy.readthedocs.io/en/latest/usage.html#stacking-layers +RedirectTemp /16-16 https://docs.python.org/3/library/functools.html#functools.total_ordering +RedirectTemp /16-17 https://wiki.illinois.edu//wiki/download/attachments/273416327/ingalls.pdf +RedirectTemp /16-18 https://wiki.illinois.edu//wiki/download/attachments/273416327/double-dispatch.pdf +RedirectTemp /16-19 http://www.gotw.ca/publications/c_family_interview.htm +RedirectTemp /16-20 http://www.gotw.ca/publications/c_family_interview.htm +RedirectTemp /16-21 https://doc.rust-lang.org/std/ops/index.html +RedirectTemp /16-22 https://www.fluentpython.com/lingo/#lazy +############################################################ 17 +RedirectTemp /17-1 http://www.paulgraham.com/icad.html +RedirectTemp /17-2 https://en.wikipedia.org/wiki/Sentinel_value +RedirectTemp /17-3 https://docs.python.org/3.10/library/functions.html#iter +RedirectTemp /17-4 https://docs.python.org/3.10/library/functions.html#iter +RedirectTemp /17-5 https://github.com/python/cpython/blob/b1930bf75f276cd7ca08c4455298128d89adf7d1/Lib/_collections_abc.py#L271 +RedirectTemp /17-6 https://github.com/python/cpython/blob/main/Lib/types.py#L6 +RedirectTemp /17-7 https://en.wikipedia.org/wiki/CLU_(programming_language) +RedirectTemp /17-8 https://docs.python.org/3/glossary.html +RedirectTemp /17-9 https://docs.python.org/3/glossary.html#term-generator-iterator +RedirectTemp /17-10 https://docs.python.org/3/glossary.html#term-generator-expression +RedirectTemp /17-11 https://marc.info/?l=python-list&m=141826925106951&w=2 +RedirectTemp /17-12 https://docs.python.org/3/library/os.html#os.walk +RedirectTemp /17-13 https://docs.python.org/3/library/itertools.html +RedirectTemp /17-14 https://docs.python.org/3/library/exceptions.html#exception-hierarchy +RedirectTemp /17-15 https://en.wikipedia.org/wiki/Depth-first_search +RedirectTemp /17-16 https://docs.python.org/3.10/library/typing.html#typing.TypeAlias +RedirectTemp /17-17 https://docs.python.org/3/library/typing.html#typing.Generator +RedirectTemp /17-18 http://www.dabeaz.com/coroutines/Coroutines.pdf +RedirectTemp /17-19 http://www.dabeaz.com/coroutines/Coroutines.pdf +RedirectTemp /17-20 https://mail.python.org/pipermail/python-ideas/2009-April/003841.html +RedirectTemp /17-21 https://mail.python.org/pipermail/python-ideas/2009-April/003912.html +RedirectTemp /17-22 https://docs.python.org/3/library/exceptions.html#StopIteration +RedirectTemp /17-23 https://docs.python.org/3/reference/expressions.html#yield-expressions +RedirectTemp /17-24 https://docs.python.org/3/reference/index.html +RedirectTemp /17-25 https://github.com/python/cpython/blob/6f743e7a4da904f61dfa84cc7d7385e4dcc79ac5/Lib/typing.py#L2060 +RedirectTemp /17-26 http://catb.org/~esr/jargon/html/G/grok.html +RedirectTemp /17-27 https://docs.python.org/3/reference/expressions.html#yieldexpr +RedirectTemp /17-28 https://docs.python.org/3/library/itertools.html +RedirectTemp /17-29 https://docs.python.org/3/library/itertools.html#itertools-recipes +RedirectTemp /17-30 https://more-itertools.readthedocs.io/en/stable/index.html +RedirectTemp /17-31 https://rittau.org/2006/11/java-iterators-are-not-iterable/ +RedirectTemp /17-32 https://docs.python.org/3/whatsnew/3.3.html#pep-380-syntax-for-delegating-to-a-subgenerator +RedirectTemp /17-33 http://www.dabeaz.com/generators/ +RedirectTemp /17-34 http://www.dabeaz.com/coroutines/ +RedirectTemp /17-35 https://archive.org/details/pyvideo_213___pycon-2009-a-curious-course-on-coroutines-and-concurrency-part-1-of-3 +RedirectTemp /17-36 https://archive.org/details/pyvideo_215___pycon-2009-a-curious-course-on-coroutines-and-concurrency-part-2-of-3 +RedirectTemp /17-37 https://archive.org/details/pyvideo_214___pycon-2009-a-curious-course-on-coroutines-and-concurrency-part-3-of-3 +RedirectTemp /17-38 http://www.dabeaz.com/finalgenerator/ +RedirectTemp /17-39 https://web.archive.org/web/20200218150637/http://seriously.dontusethiscode.com/2013/05/01/greedy-coroutine.html +RedirectTemp /17-40 https://effectivepython.com/ +RedirectTemp /17-41 https://effectivepython.com/2015/03/10/consider-coroutines-to-run-many-functions-concurrently +RedirectTemp /17-42 https://en.wikipedia.org/wiki/Conway's_Game_of_Life +RedirectTemp /17-43 https://gist.github.com/ramalho/da5590bc38c973408839 +RedirectTemp /17-44 https://gist.github.com/ramalho/da5590bc38c973408839 +RedirectTemp /17-45 https://journal.code4lib.org/articles/4893 +RedirectTemp /17-46 https://github.com/fluentpython/isis2json +RedirectTemp /17-47 https://github.com/fluentpython/isis2json/blob/master/README.rst +############################################################ 18 +RedirectTemp /18-1 https://pyvideo.org/video/1669/keynote-3/ +RedirectTemp /18-2 https://docs.python.org/3/library/sqlite3.html#using-the-connection-as-a-context-manager +RedirectTemp /18-3 https://docs.python.org/3/library/threading.html#using-locks-conditions-and-semaphores-in-the-with-statement +RedirectTemp /18-4 https://docs.python.org/3/library/decimal.html#decimal.localcontext +RedirectTemp /18-5 https://docs.python.org/3/library/unittest.mock.html#patch +RedirectTemp /18-6 https://docs.python.org/3/library/contextlib.html#contextlib.redirect_stdout +RedirectTemp /18-7 https://docs.python.org/3/library/sys.html#sys.exc_info +RedirectTemp /18-8 https://en.wikipedia.org/wiki/LL_parser +RedirectTemp /18-9 https://docs.python.org/3/library/contextlib.html +RedirectTemp /18-10 https://github.com/python/cpython/blob/8afab2ebbc1b343cd88d058914cf622fe687a2be/Lib/contextlib.py#L123 +RedirectTemp /18-11 https://www.zopatista.com/python/2013/11/26/inplace-file-rewriting/ +RedirectTemp /18-12 https://docs.python.org/3/library/fileinput.html#fileinput.input +RedirectTemp /18-13 https://www.zopatista.com/python/2013/11/26/inplace-file-rewriting/ +RedirectTemp /18-14 https://en.wikipedia.org/wiki/Euclidean_algorithm +RedirectTemp /18-15 https://github.com/fluentpython/example-code-2e/tree/master/18-with-match/lispy/py3.10/ +RedirectTemp /18-16 https://github.com/fluentpython/example-code-2e/blob/master/18-with-match/lispy/original/lispy.py +RedirectTemp /18-17 https://github.com/fluentpython/example-code-2e/blob/6527037ae7319ba370a1ee2d9fe79214d0ed9452/18-with-match/lispy/py3.10/lis.py#L35 +RedirectTemp /18-18 https://github.com/fluentpython/example-code-2e/blob/master/18-with-match/lispy/py3.10/examples_test.py +RedirectTemp /18-19 https://github.com/python/typeshed/issues/6042 +RedirectTemp /18-20 https://github.com/fluentpython/lispy/tree/main/mylis +RedirectTemp /18-21 https://github.com/fluentpython/example-code-2e/blob/00e4741926e1b771ee7c753148b1415c0bd12e39/02-array-seq/lispy/py3.10/examples_test.py +RedirectTemp /18-22 https://mitpress.mit.edu/sites/default/files/sicp/index.html +RedirectTemp /18-23 https://github.com/fluentpython/example-code-2e/blob/master/18-with-match/lispy/py3.10/examples_test.py +RedirectTemp /18-24 https://github.com/fluentpython/example-code-2e/blob/master/18-with-match/lispy/py3.10/lis.py +RedirectTemp /18-25 https://www.python.org/dev/peps/pep-0634/#or-patterns +RedirectTemp /18-26 https://en.wikipedia.org/wiki/Lambda#Character_encodings +RedirectTemp /18-27 https://docs.python.org/3/reference/compound_stmts.html +RedirectTemp /18-28 https://docs.python.org/3/glossary.html#term-eafp +RedirectTemp /18-29 https://speakerdeck.com/pyconslides/pycon-keynote-python-is-awesome-by-raymond-hettinger?slide=21 +RedirectTemp /18-30 https://docs.python.org/3/reference/compound_stmts.html +RedirectTemp /18-31 https://stackoverflow.com/questions/16138232/is-it-a-good-practice-to-use-try-except-else-in-python +RedirectTemp /18-32 https://docs.python.org/3/library/stdtypes.html#typecontextmanager +RedirectTemp /18-33 https://docs.python.org/3/reference/datamodel.html#with-statement-context-managers +RedirectTemp /18-34 https://speakerdeck.com/pyconslides/pycon-keynote-python-is-awesome-by-raymond-hettinger?slide=21 +RedirectTemp /18-35 https://speakerdeck.com/pyconslides/transforming-code-into-beautiful-idiomatic-python-by-raymond-hettinger-1?slide=34 +RedirectTemp /18-36 https://preshing.com/20110920/the-python-with-statement-by-example/ +RedirectTemp /18-37 https://www.rath.org/on-the-beauty-of-pythons-exitstack.html +RedirectTemp /18-38 https://norvig.com/lispy.html +RedirectTemp /18-39 https://norvig.com/lispy2.html +RedirectTemp /18-40 https://github.com/norvig/pytudes +RedirectTemp /18-41 https://github.com/fluentpython/lispy +RedirectTemp /18-42 https://racket-lang.org/ +RedirectTemp /18-43 https://pyvideo.org/video/1669/keynote-3/ +RedirectTemp /18-44 https://en.wikipedia.org/wiki/Tail_call +RedirectTemp /18-45 https://2ality.com/2015/06/tail-call-optimization.html +RedirectTemp /18-46 https://github.com/fluentpython/example-code-2e/blob/master/18-with-match/lispy/original/lis.py +RedirectTemp /18-47 https://github.com/fluentpython/example-code-2e/blob/master/18-with-match/lispy/original/lispy.py +RedirectTemp /18-48 http://neopythonic.blogspot.com/2009/04/final-words-on-tail-calls.html +RedirectTemp /18-49 https://webkit.org/blog/6240/ecmascript-6-proper-tail-calls-in-webkit/ +RedirectTemp /18-50 http://kangax.github.io/compat-table/es6/ +RedirectTemp /18-51 https://world.hey.com/mgmarlow/what-happened-to-proper-tail-calls-in-javascript-5494c256 +RedirectTemp /18-52 http://neopythonic.blogspot.com/2009/04/tail-recursion-elimination.html +RedirectTemp /18-53 https://github.com/fluentpython/lispy/blob/main/mylis/mylis_2/lis.py +RedirectTemp /18-54 https://github.com/fluentpython/lispy/tree/main/mylis +############################################################ 19 +RedirectTemp /19-1 https://go.dev/blog/waza-talk +RedirectTemp /19-2 https://en.wikipedia.org/wiki/Graphics_processing_unit +RedirectTemp /19-3 https://docs.python.org/3/library/sys.html#sys.getswitchinterval +RedirectTemp /19-4 https://docs.python.org/3/library/sys.html#sys.setswitchinterval +RedirectTemp /19-5 https://en.wikipedia.org/wiki/System_call +RedirectTemp /19-6 https://mail.python.org/pipermail/python-dev/2009-October/093356.html +RedirectTemp /19-7 http://www.dabeaz.com/finalgenerator/ +RedirectTemp /19-8 https://docs.python.org/3/library/threading.html#thread-objects +RedirectTemp /19-9 https://www.pypy.org/ +RedirectTemp /19-10 https://mail.python.org/pipermail/python-list/2009-February/675659.html +RedirectTemp /19-11 https://en.wikipedia.org/wiki/Braille_Patterns +RedirectTemp /19-12 https://docs.python.org/3/library/multiprocessing.shared_memory.html +RedirectTemp /19-13 https://docs.python.org/3/library/multiprocessing.shared_memory.html#multiprocessing.shared_memory.ShareableList +RedirectTemp /19-14 https://greenlet.readthedocs.io/en/latest/ +RedirectTemp /19-15 https://docs.sqlalchemy.org/en/14/changelog/migration_14.html#asynchronous-io-support-for-core-and-orm +RedirectTemp /19-16 https://docs.sqlalchemy.org/en/14/orm/extensions/asyncio.html +RedirectTemp /19-17 http://www.gevent.org/ +RedirectTemp /19-18 https://github.com/gevent/gevent/wiki/Projects +RedirectTemp /19-19 https://docs.python.org/3/library/concurrent.futures.html#processpoolexecutor-example +RedirectTemp /19-20 https://github.com/python/asyncio/issues/284 +RedirectTemp /19-21 https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.loop.run_in_executor +RedirectTemp /19-22 https://mail.python.org/archives/list/python-dev@python.org/message/JBYXQH3NV3YBF7P2HLHB5CD6V3GVTY55/ +RedirectTemp /19-23 https://docs.python.org/3/library/queue.html#queue.SimpleQueue.get +RedirectTemp /19-24 https://en.wikipedia.org/wiki/Race_condition +RedirectTemp /19-25 https://github.com/fluentpython/example-code-2e/commit/2c1230579db99738a5e5e6802063bda585f6476d +RedirectTemp /19-26 https://github.com/fluentpython/example-code-2e/blob/master/19-concurrency/primes/README.md +RedirectTemp /19-27 https://github.com/fluentpython/example-code-2e/blob/master/19-concurrency/primes/threads.py +RedirectTemp /19-28 https://en.wikipedia.org/wiki/Context_switch +RedirectTemp /19-29 http://www.gotw.ca/publications/concurrency-ddj.htm +RedirectTemp /19-30 https://www.ansible.com/ +RedirectTemp /19-31 https://saltproject.io/ +RedirectTemp /19-32 https://www.fabfile.org/ +RedirectTemp /19-33 https://engineering.fb.com/2016/05/27/production-engineering/python-in-production-engineering/ +RedirectTemp /19-34 https://jupyter.org/ +RedirectTemp /19-35 https://docs.bokeh.org/en/latest/index.html +RedirectTemp /19-36 https://www.tensorflow.org/ +RedirectTemp /19-37 https://pytorch.org/ +RedirectTemp /19-38 https://www.oreilly.com/radar/where-programming-ops-ai-and-the-cloud-are-headed-in-2021/ +RedirectTemp /19-39 https://www.youtube.com/watch?v=ods97a5Pzw0 +RedirectTemp /19-40 https://www.thoughtworks.com/radar/techniques/high-performance-envy-web-scale-envy +RedirectTemp /19-41 https://modwsgi.readthedocs.io/en/master/ +RedirectTemp /19-42 https://uwsgi-docs.readthedocs.io/en/latest/ +RedirectTemp /19-43 https://unit.nginx.org/ +RedirectTemp /19-44 https://www.techatbloomberg.com/blog/configuring-uwsgi-production-deployment/ +RedirectTemp /19-45 https://www.youtube.com/watch?v=p6R1h2Nn468 +RedirectTemp /19-46 https://asgi.readthedocs.io/en/latest/index.html +RedirectTemp /19-47 https://docs.celeryproject.org/en/stable/getting-started/introduction.html +RedirectTemp /19-48 https://python-rq.org/ +RedirectTemp /19-49 https://docs.celeryproject.org/en/stable/faq.html#what-kinds-of-things-should-i-use-celery-for +RedirectTemp /19-50 https://redis.io/ +RedirectTemp /19-51 https://realpython.com/intro-to-python-threading/ +RedirectTemp /19-52 https://pymotw.com/3/concurrency.html +RedirectTemp /19-53 https://www.pearson.com/us/higher-education/program/Hellmann-Python-3-Standard-Library-by-Example-The/PGM328871.html +RedirectTemp /19-54 https://docs.python.org/3/library/multiprocessing.html#programming-guidelines +RedirectTemp /19-55 https://docs.python.org/3/library/multiprocessing.html +RedirectTemp /19-56 https://www.oreilly.com/library/view/high-performance-python/9781492055013/ +RedirectTemp /19-57 https://link.springer.com/book/10.1007/978-1-4842-5793-7?error=cookies_not_supported&code=2ed5d61d-ae9f-4f3d-94ac-0f68cf45ea4f +RedirectTemp /19-58 https://www.packtpub.com/product/parallel-programming-with-python/9781783288397 +RedirectTemp /19-59 https://greenteapress.com/wp/semaphores/ +RedirectTemp /19-60 https://docs.python.org/3/c-api/init.html#thread-state-and-the-global-interpreter-lock +RedirectTemp /19-61 https://docs.python.org/3/faq/library.html#can-t-we-get-rid-of-the-global-interpreter-lock +RedirectTemp /19-62 https://www.artima.com/weblogs/viewpost.jsp?thread=214235 +RedirectTemp /19-63 http://jessenoller.com/blog/2009/02/01/python-threads-and-the-global-interpreter-lock +RedirectTemp /19-64 https://realpython.com/products/cpython-internals-book/ +RedirectTemp /19-65 http://www.dabeaz.com/GIL/ +RedirectTemp /19-66 http://www.dabeaz.com/python/UnderstandingGIL.pdf +RedirectTemp /19-67 https://bugs.python.org/issue7946#msg223110 +RedirectTemp /19-68 https://bugs.python.org/issue7946 +RedirectTemp /19-69 https://www.fullstackpython.com/ +RedirectTemp /19-70 https://www.oreilly.com/library/view/high-performance-python/9781492055013/ +RedirectTemp /19-71 https://www.packtpub.com/product/parallel-programming-with-python/9781783288397 +RedirectTemp /19-72 https://www.packtpub.com/product/distributed-computing-with-python/9781785889691 +RedirectTemp /19-73 https://towardsdatascience.com/python-performance-and-gpus-1be860ffd58d?gi=a7d537cc2fb4 +RedirectTemp /19-74 https://instagram-engineering.com/web-service-efficiency-at-instagram-with-python-4976d078e366?gi=12a441991c88 +RedirectTemp /19-75 https://www.oreilly.com/library/view/architecture-patterns-with/9781492052197/ +RedirectTemp /19-76 https://www.cosmicpython.com/ +RedirectTemp /19-77 https://pypi.org/project/lelo/ +RedirectTemp /19-78 https://github.com/npryce/python-parallelize +RedirectTemp /19-79 https://github.com/ericsnowcurrently/multi-core-python/wiki +RedirectTemp /19-80 https://gist.github.com/markshannon/79cace3656b40e21b7021504daee950c +RedirectTemp /19-81 https://mail.python.org/archives/list/python-dev@python.org/message/YOOQZCFOKEPQ24YHWWLQSJ3RCXFMS7D7/ +RedirectTemp /19-82 https://en.wikipedia.org/wiki/Communicating_sequential_processes +RedirectTemp /19-83 https://github.com/stackless-dev/stackless/wiki +RedirectTemp /19-84 https://www.eveonline.com +RedirectTemp /19-85 https://www.ccpgames.com/ +RedirectTemp /19-86 https://stackless.readthedocs.io/en/3.6-slp/stackless-python.html#history +RedirectTemp /19-87 https://doc.pypy.org/en/latest/stackless.html +RedirectTemp /19-88 https://greenlet.readthedocs.io/en/latest/ +RedirectTemp /19-89 http://www.gevent.org/ +RedirectTemp /19-90 http://thespianpy.com/doc/ +RedirectTemp /19-91 https://pykka.readthedocs.io/en/latest/ +RedirectTemp /19-92 https://www.manning.com/books/rabbitmq-in-action +RedirectTemp /19-93 https://pragprog.com/titles/pb7con/seven-concurrency-models-in-seven-weeks/ +RedirectTemp /19-94 https://en.wikipedia.org/wiki/OpenCL +RedirectTemp /19-95 https://media.pragprog.com/titles/pb7con/Bonus_Chapter.pdf +RedirectTemp /19-96 https://martinfowler.com/ +RedirectTemp /19-97 https://martinfowler.com/articles/patterns-of-distributed-systems/ +RedirectTemp /19-98 https://www.oreilly.com/library/view/designing-data-intensive-applications/9781491903063/ +RedirectTemp /19-99 https://www.oreilly.com/library/view/oscon-2016-video/9781491965153/video247021.html +RedirectTemp /19-100 https://www.oreilly.com/library/view/designing-for-scalability/9781449361556/ +RedirectTemp /19-101 https://www.thoughtworks.com/radar/techniques/high-performance-envy-web-scale-envy +RedirectTemp /19-102 https://en.wikipedia.org/wiki/KISS_principle +RedirectTemp /19-103 https://www.usenix.org/conference/hotos15/workshop-program/presentation/mcsherry +############################################################ 20 +RedirectTemp /20-1 https://www.artima.com/weblogs/viewpost.jsp?thread=299551 +RedirectTemp /20-2 https://docs.python.org/3/library/http.server.html +RedirectTemp /20-3 https://www.youtube.com/watch?v=A9e9Cy1UkME +RedirectTemp /20-4 https://www.cia.gov/the-world-factbook/ +RedirectTemp /20-5 https://docs.python-requests.org/en/latest/ +RedirectTemp /20-6 https://docs.python.org/3.10/library/concurrent.futures.html#concurrent.futures.ThreadPoolExecutor +RedirectTemp /20-7 https://docs.python.org/3/library/concurrent.futures.html#concurrent.futures.as_completed +RedirectTemp /20-8 https://docs.python.org/3/library/concurrent.futures.html +RedirectTemp /20-9 https://docs.python.org/3.10/library/concurrent.futures.html#concurrent.futures.Executor +RedirectTemp /20-10 https://github.com/fluentpython/example-code-2e/blob/master/20-executors/getflags/flags2_common.py +RedirectTemp /20-11 https://github.com/noamraph/tqdm +RedirectTemp /20-12 https://www.youtube.com/watch?v=M8Z65tAl5l4 +RedirectTemp /20-13 https://github.com/noamraph/tqdm/blob/master/README.md +RedirectTemp /20-14 https://docs.python.org/3/library/concurrent.futures.html#concurrent.futures.as_completed +RedirectTemp /20-15 https://docs.python.org/3/library/asyncio-task.html#asyncio.as_completed +RedirectTemp /20-16 https://www.cloudflare.com/ +RedirectTemp /20-17 https://github.com/fluentpython/example-code-2e/tree/master/20-executors/getflags +RedirectTemp /20-18 https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/418 +RedirectTemp /20-19 https://en.wikipedia.org/wiki/Embarrassingly_parallel +RedirectTemp /20-20 https://pyvideo.org/video/480/pyconau-2010--the-future-is-soon/ +RedirectTemp /20-21 http://www.dabeaz.com/coroutines/ +RedirectTemp /20-22 https://en.wikipedia.org/wiki/POSIX_Threads +RedirectTemp /20-23 https://en.wikipedia.org/wiki/C_dynamic_memory_allocation +RedirectTemp /20-24 https://pragprog.com/titles/pb7con/seven-concurrency-models-in-seven-weeks/ +RedirectTemp /20-25 https://hexdocs.pm/ecto/getting-started.html +############################################################ 21 +RedirectTemp /21-1 https://docs.python.org/3/library/asyncio.html +RedirectTemp /21-2 https://bugs.python.org/issue43216 +RedirectTemp /21-3 https://bugs.python.org/issue36921 +RedirectTemp /21-4 https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.loop.getaddrinfo +RedirectTemp /21-5 https://docs.python.org/3/library/socket.html#socket.getaddrinfo +RedirectTemp /21-6 https://docs.python.org/3.10/library/asyncio-eventloop.html#asyncio.get_event_loop +RedirectTemp /21-7 https://www.python.org/dev/peps/pep-0492/#await-expression +RedirectTemp /21-8 https://www.fluentpython.com/extra/classic-coroutines/#yield_from_meaning_sec +RedirectTemp /21-9 https://github.com/fluentpython/example-code-2e/tree/master/20-executors/getflags +RedirectTemp /21-10 https://magicstack.github.io/asyncpg/current/ +RedirectTemp /21-11 https://magicstack.github.io/asyncpg/current/api/index.html#transactions +RedirectTemp /21-12 https://magicstack.github.io/asyncpg/current/api/index.html#transactions +RedirectTemp /21-13 https://github.com/MagicStack/asyncpg/blob/4d39a05268ce4cc01b00458223a767542da048b8/asyncpg/transaction.py#L57 +RedirectTemp /21-14 https://magicstack.github.io/asyncpg/current/usage.html#connection-pools +RedirectTemp /21-15 https://gist.github.com/jboner/2841832 +RedirectTemp /21-16 https://en.wikipedia.org/wiki/Network-attached_storage +RedirectTemp /21-17 https://en.wikipedia.org/wiki/Semaphore_(programming) +RedirectTemp /21-18 https://en.wikipedia.org/wiki/Semaphore_(programming) +RedirectTemp /21-19 https://groups.google.com/forum/#!msg/python-tulip/PdAEtwpaJHs/7fqb-Qj2zJoJ +RedirectTemp /21-20 https://web.archive.org/web/20151209151711/http://tritarget.org/blog/2012/11/28/the-pyramid-of-doom-a-javascript-style-trap +RedirectTemp /21-21 https://stackoverflow.com/questions/53701841/what-is-the-use-case-for-future-add-done-callback/53710563 +RedirectTemp /21-22 https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.loop.run_in_executor +RedirectTemp /21-23 https://motor.readthedocs.io/en/stable/ +RedirectTemp /21-24 https://emptysqua.re/blog/response-to-asynchronous-python-and-databases/ +RedirectTemp /21-25 https://docs.python.org/3/library/asyncio-stream.html#tcp-echo-server-using-streams +RedirectTemp /21-26 https://en.wikipedia.org/wiki/Phaistos_Disc +RedirectTemp /21-27 https://en.wikipedia.org/wiki/Inverted_index +RedirectTemp /21-28 https://fastapi.tiangolo.com/ +RedirectTemp /21-29 https://swagger.io/specification/ +RedirectTemp /21-30 https://asgi.readthedocs.io/en/latest/implementations.html +RedirectTemp /21-31 https://pydantic-docs.helpmanual.io/ +RedirectTemp /21-32 https://doc.traefik.io/traefik/ +RedirectTemp /21-33 https://fastapi.tiangolo.com/project-generation/ +RedirectTemp /21-34 https://fastapi.tiangolo.com/tutorial/response-model/ +RedirectTemp /21-35 https://docs.python.org/3/library/asyncio-stream.html#asyncio.start_server +RedirectTemp /21-36 https://github.com/python/typeshed/issues/5535 +RedirectTemp /21-37 https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.Server.serve_forever +RedirectTemp /21-38 https://docs.python.org/3/library/asyncio-stream.html#asyncio.StreamWriter.close +RedirectTemp /21-39 https://docs.python.org/3/library/asyncio-stream.html#streamwriter +RedirectTemp /21-40 https://docs.python.org/3/library/asyncio-stream.html +RedirectTemp /21-41 https://docs.python.org/3/library/asyncio-protocol.html +RedirectTemp /21-42 https://docs.python.org/3/library/asyncio-protocol.html#tcp-echo-server +RedirectTemp /21-43 https://github.com/aio-libs/aiopg +RedirectTemp /21-44 https://docs.python.org/3/whatsnew/3.8.html#asyncio +RedirectTemp /21-45 https://datatracker.ietf.org/doc/html/rfc6761 +RedirectTemp /21-46 https://docs.python.org/3/library/contextlib.html#contextlib.asynccontextmanager +RedirectTemp /21-47 https://docs.python.org/3/library/contextlib.html#contextlib.asynccontextmanager +RedirectTemp /21-48 https://docs.python.org/3/library/asyncio-task.html#asyncio.gather +RedirectTemp /21-49 https://curio.readthedocs.io/en/latest/index.html +RedirectTemp /21-50 https://curio.readthedocs.io/en/latest/reference.html#task-groups +RedirectTemp /21-51 https://en.wikipedia.org/wiki/Structured_concurrency +RedirectTemp /21-52 https://mail.python.org/archives/list/python-dev@python.org/thread/2ORDAW74LGE3ZI2QETPJRT2ZL7MCCPG2/ +RedirectTemp /21-53 https://www.python.org/dev/peps/pep-0654/#motivation +RedirectTemp /21-54 https://curio.readthedocs.io/en/latest/reference.html#AWAIT +RedirectTemp /21-55 https://www.python-httpx.org/async/#curio +RedirectTemp /21-56 https://github.com/dabeaz/curio/tree/78bca8a6ad677ef51e1568ac7b3e51441ab49c42/examples +RedirectTemp /21-57 https://datatracker.ietf.org/doc/html/rfc8305 +RedirectTemp /21-58 https://trio.readthedocs.io/en/stable/ +RedirectTemp /21-59 https://www.youtube.com/watch?v=M-sc73Y-zQA +RedirectTemp /21-60 https://en.wikipedia.org/wiki/Technical_debt +RedirectTemp /21-61 https://www.youtube.com/watch?v=E-1Y4kSsAFc +RedirectTemp /21-62 https://github.com/dabeaz/curio +RedirectTemp /21-63 https://trio.readthedocs.io/en/stable/ +RedirectTemp /21-64 https://curio.readthedocs.io/en/latest/#curio-university +RedirectTemp /21-65 https://vorpus.org/blog/some-thoughts-on-asynchronous-api-design-in-a-post-asyncawait-world/ +RedirectTemp /21-66 https://vorpus.org/blog/notes-on-structured-concurrency-or-go-statement-considered-harmful/ +RedirectTemp /21-67 https://stackoverflow.com/questions/49482969/what-is-the-core-difference-between-asyncio-and-trio +RedirectTemp /21-68 https://docs.python.org/3/library/asyncio.html +RedirectTemp /21-69 https://bugs.python.org/issue33649 +RedirectTemp /21-70 https://docs.python.org/3/library/asyncio-dev.html +RedirectTemp /21-71 https://www.youtube.com/watch?v=iG6fr81xHKA +RedirectTemp /21-72 https://www.youtube.com/watch?v=F19R_M4Nay4 +RedirectTemp /21-73 https://asherman.io/projects/unsync.html +RedirectTemp /21-74 https://pyladies.com/ +RedirectTemp /21-75 https://www.youtube.com/watch?v=sW76-pRkZk8 +RedirectTemp /21-76 https://www.youtube.com/watch?v=Xbl7XjFYsN4 +RedirectTemp /21-77 https://www.youtube.com/watch?v=02CLD-42VdI +RedirectTemp /21-78 https://micropython.org/ +RedirectTemp /21-79 https://docs.micropython.org/en/latest/library/uasyncio.html +RedirectTemp /21-80 https://www.encode.io/articles/python-async-frameworks-beyond-developer-tribalism +RedirectTemp /21-81 https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/ +RedirectTemp /21-82 https://vorpus.org/blog/notes-on-structured-concurrency-or-go-statement-considered-harmful/ +RedirectTemp /21-83 https://github.com/MagicStack/uvloop +RedirectTemp /21-84 http://magic.io/blog/uvloop-blazing-fast-python-networking/ +RedirectTemp /21-85 https://github.com/MagicStack/httptools +RedirectTemp /21-86 https://docs.aiohttp.org/en/stable/ +RedirectTemp /21-87 https://github.com/wg/wrk +RedirectTemp /21-88 https://twistedmatrix.com/trac/ +############################################################ 22 +RedirectTemp /22-1 https://github.com/fluentpython/example-code-2e/blob/master/22-dyn-attr-prop/oscon/data/osconfeed.json +RedirectTemp /22-2 https://pypi.org/project/attrdict/ +RedirectTemp /22-3 https://pypi.org/project/addict/ +RedirectTemp /22-4 https://github.com/ActiveState/code/tree/master/recipes/Python/52308_simple_but_handy_collector_bunch_named_stuff +RedirectTemp /22-5 https://docs.python.org/3/library/types.html#types.SimpleNamespace +RedirectTemp /22-6 https://docs.python.org/3/library/argparse.html#argparse.Namespace +RedirectTemp /22-7 https://docs.python.org/3/library/multiprocessing.html#multiprocessing.managers.Namespace +RedirectTemp /22-8 https://github.com/fluentpython/example-code-2e/blob/master/22-dyn-attr-prop/oscon/schedule_v2.py +RedirectTemp /22-9 https://docs.python.org/3/library/functools.html#functools.cached_property +RedirectTemp /22-10 https://docs.python.org/3/library/functools.html#functools.cached_property +RedirectTemp /22-11 https://bugs.python.org/issue42781 +RedirectTemp /22-12 https://docs.python.org/3/howto/descriptor.html +RedirectTemp /22-13 https://github.com/python/cpython/blob/e6d0107e13ed957109e79b796984d3d026a8660d/Lib/functools.py#L926 +RedirectTemp /22-14 https://docs.python.org/3/library/threading.html#rlock-objects +RedirectTemp /22-15 https://docs.python.org/3.10/library/functools.html#functools.cached_property +RedirectTemp /22-16 https://www.wsj.com/articles/SB10001424052970203914304576627102996831200 +RedirectTemp /22-17 https://www.youtube.com/watch?v=s35rVw1zskA&feature=youtu.be +RedirectTemp /22-18 https://docs.python.org/3/library/functions.html#dir +RedirectTemp /22-19 https://github.com/python/cpython/blob/19903085c3ad7a17c8047e1556c700f2eb109931/Lib/cmd.py#L214 +RedirectTemp /22-20 https://docs.python.org/3/library/functions.html#hasattr +RedirectTemp /22-21 https://docs.python.org/3.10/reference/datamodel.html#special-method-lookup +RedirectTemp /22-22 https://docs.python.org/3/library/functions.html +RedirectTemp /22-23 https://docs.python.org/3/reference/datamodel.html#customizing-attribute-access +RedirectTemp /22-24 https://docs.python.org/3/reference/datamodel.html#special-method-lookup +RedirectTemp /22-25 https://docs.python.org/3/library/stdtypes.html#special-attributes +RedirectTemp /22-26 http://wiki.c2.com/?WelcomeVisitors +RedirectTemp /22-27 http://wiki.c2.com/?UniformAccessPrinciple +RedirectTemp /22-28 https://docs.oracle.com/javase/tutorial/java/javaOO/accesscontrol.html +RedirectTemp /22-29 http://www.pingo.io/docs/ +RedirectTemp /22-30 https://www.drdobbs.com/javas-new-considered-harmful/184405016 +RedirectTemp /22-31 https://www.python.org/dev/peps/pep-0008/#class-names +############################################################ 23 +RedirectTemp /23-1 http://www.aleax.it/goo_pydp.pdf +RedirectTemp /23-2 https://docs.python.org/3.10/reference/datamodel.html#implementing-descriptors +RedirectTemp /23-3 https://docs.python.org/3/howto/descriptor.html +RedirectTemp /23-4 https://docs.python.org/3/howto/ +RedirectTemp /23-5 http://www.aleax.it/Python/nylug05_om.pdf +RedirectTemp /23-6 https://www.youtube.com/watch?v=VOzvpHoYQoo +RedirectTemp /23-7 https://www.python.org/dev/peps/pep-0487/#trait-descriptors +RedirectTemp /23-8 https://dreamsongs.com/RiseOfWorseIsBetter.html +RedirectTemp /23-9 http://web.archive.org/web/20031002184114/www.amk.ca/python/writing/warts.html +RedirectTemp /23-10 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this +RedirectTemp /23-11 http://python-history.blogspot.com/2009/02/adding-support-for-user-defined-classes.html +############################################################ 24 +RedirectTemp /24-1 https://docs.python.org/3/library/stdtypes.html#special-attributes +RedirectTemp /24-2 https://docs.djangoproject.com/en/3.2/topics/db/models/#meta-options +RedirectTemp /24-3 https://www.python.org/dev/peps/pep-3155/ +RedirectTemp /24-4 https://github.com/python/cpython/blob/3.9/Lib/collections/__init__.py +RedirectTemp /24-5 https://en.wikipedia.org/wiki/Tony_Hoare#Apologies_and_retractions +RedirectTemp /24-6 https://go.dev/tour/basics/12 +RedirectTemp /24-7 https://bugs.python.org/issue42102 +RedirectTemp /24-8 https://docs.python.org/3/reference/datamodel.html#object.__init_subclass__ +RedirectTemp /24-9 https://www.python.org/dev/peps/pep-0557/#abstract +RedirectTemp /24-10 https://github.com/python/cpython/blob/3.9/Lib/dataclasses.py +RedirectTemp /24-11 https://docs.python.org/3/reference/datamodel.html#creating-the-class-object +RedirectTemp /24-12 https://mail.python.org/pipermail/python-list/2002-December/134521.html +RedirectTemp /24-13 https://mail.python.org/pipermail/python-list/2002-July/162558.html +RedirectTemp /24-14 https://github.com/fluentpython/example-code/tree/master/21-class-metaprog/bulkfood +RedirectTemp /24-15 https://en.wikipedia.org/wiki/Principle_of_least_astonishment +RedirectTemp /24-16 https://docs.python.org/3/reference/datamodel.html#object.__class_getitem__ +RedirectTemp /24-17 https://en.wikipedia.org/wiki/Trait_(computer_programming) +RedirectTemp /24-18 https://en.wikipedia.org/wiki/Aspect-oriented_programming +RedirectTemp /24-19 https://dhh.dk/arc/000416.html +RedirectTemp /24-20 https://github.com/cjrh/autoslot +RedirectTemp /24-21 https://docs.python.org/3/reference/datamodel.html#customizing-class-creation +RedirectTemp /24-22 https://docs.python.org/3/library/functions.html#type +RedirectTemp /24-23 https://docs.python.org/3/library/stdtypes.html#special-attributes +RedirectTemp /24-24 https://docs.python.org/3/library/types.html +RedirectTemp /24-25 https://www.python.org/dev/peps/pep-3129/ +RedirectTemp /24-26 https://www.youtube.com/watch?v=cAGliEJV9_o +RedirectTemp /24-27 https://docs.python.org/3/library/functools.html#functools.total_ordering +RedirectTemp /24-28 https://www.python.org/download/releases/2.2.3/descrintro/ +RedirectTemp /24-29 https://github.com/lihaoyi/macropy +RedirectTemp /24-30 https://people.eecs.berkeley.edu/~bh/ss-toc2.html diff --git a/links/short.htaccess b/links/short.htaccess new file mode 100644 index 0000000..fbe7ddf --- /dev/null +++ b/links/short.htaccess @@ -0,0 +1 @@ +# file created and managed by short.py diff --git a/links/short.py b/links/short.py new file mode 100755 index 0000000..a9ae96c --- /dev/null +++ b/links/short.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python3 + +import itertools +from collections.abc import Iterator + + +def load_redirects(): + redirects = {} + targets = {} + for filename in ('custom.htaccess', 'short.htaccess'): + with open(filename) as fp: + for line in fp: + if line.startswith('RedirectTemp'): + _, short, long = line.split() + short = short[1:] # Remove leading slash + assert short not in redirects, f"{filename}: duplicate redirect from {short}" + # custom is live since 2022, we cannot change it remove duplicate targets + if not filename.startswith('custom'): + assert long not in targets, f"{filename}: Duplicate redirect to {long}" + redirects[short] = long + targets[long] = short + return redirects, targets + + +SDIGITS = '23456789abcdefghjkmnpqrstvwxyz' + + +def gen_short() -> Iterator[str]: + """ + Generate every possible sequence of SDIGITS. + """ + length = 1 + while True: + for short in itertools.product(SDIGITS, repeat=length): + yield ''.join(short) + length += 1 + + +def shorten(n: int) -> str: + """ + Get Nth short URL made from SDIGITS, where 0 is the first. + """ + iter_short = gen_short() + for i in range(n+1): + short = next(iter_short) + if i == n: + return short + + +def gen_free_short(redirects: dict) -> Iterator[str]: + """ + Generate next available short URL. + """ + for short in gen_short(): + if short not in redirects: + yield short + + +def new_urls(urls: list[str], redirects: dict, targets: dict) -> None: + iter_short = gen_free_short(redirects) + with open('short.htaccess', 'a') as fp: + for url in urls: + assert 'fpy.li' not in url, f"{url} is a fpy.li URL" + if url in targets: + continue + short = next(iter_short) + redirects[short] = url + targets[url] = short + fp.write(f"RedirectTemp /{short} {url}\n") + + +def main(): + from random import randrange + urls = [f'https://example.com/{randrange(100000)}.html' for n in range(7)] + + redirects, targets = load_redirects() + new_urls(urls, redirects, targets) + + +if __name__ == '__main__': + main() From c5490b1569c9d4d4d52f0834a5f29b81bf657529 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 22 May 2025 04:13:41 +0000 Subject: [PATCH 159/166] build(deps): bump h11 from 0.12.0 to 0.16.0 in /21-async/mojifinder Bumps [h11](https://github.com/python-hyper/h11) from 0.12.0 to 0.16.0. - [Commits](https://github.com/python-hyper/h11/compare/v0.12.0...v0.16.0) --- updated-dependencies: - dependency-name: h11 dependency-version: 0.16.0 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- 21-async/mojifinder/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/21-async/mojifinder/requirements.txt b/21-async/mojifinder/requirements.txt index f7de911..6831fee 100644 --- a/21-async/mojifinder/requirements.txt +++ b/21-async/mojifinder/requirements.txt @@ -1,6 +1,6 @@ click==7.1.2 fastapi==0.65.2 -h11==0.12.0 +h11==0.16.0 pydantic==1.10.13 starlette==0.40.0 typing-extensions==3.7.4.3 From cc4e26c67a4fbe7a84094b7d1c6fffe3fddc1f45 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Thu, 22 May 2025 02:18:34 -0300 Subject: [PATCH 160/166] Remove redundant if from short.py --- links/short.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/links/short.py b/links/short.py index a9ae96c..0e95b55 100755 --- a/links/short.py +++ b/links/short.py @@ -41,10 +41,9 @@ def shorten(n: int) -> str: Get Nth short URL made from SDIGITS, where 0 is the first. """ iter_short = gen_short() - for i in range(n+1): + for _ in range(n+1): short = next(iter_short) - if i == n: - return short + return short def gen_free_short(redirects: dict) -> Iterator[str]: From 648e9f6394c753730f48587ce70e0c5d0cffdf92 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Thu, 22 May 2025 10:05:54 -0300 Subject: [PATCH 161/166] Update short.py to return list of URL substitutions --- links/short.py | 37 +++++++++++++++++-------------------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/links/short.py b/links/short.py index 0e95b55..89464a3 100755 --- a/links/short.py +++ b/links/short.py @@ -36,16 +36,6 @@ def gen_short() -> Iterator[str]: length += 1 -def shorten(n: int) -> str: - """ - Get Nth short URL made from SDIGITS, where 0 is the first. - """ - iter_short = gen_short() - for _ in range(n+1): - short = next(iter_short) - return short - - def gen_free_short(redirects: dict) -> Iterator[str]: """ Generate next available short URL. @@ -55,17 +45,23 @@ def gen_free_short(redirects: dict) -> Iterator[str]: yield short -def new_urls(urls: list[str], redirects: dict, targets: dict) -> None: +def shorten(urls: list[str], redirects: dict, targets: dict) -> list[tuple[str,str]]: + """return (short, long) pairs, updating short.htaccess as needed""' iter_short = gen_free_short(redirects) + pairs = [] with open('short.htaccess', 'a') as fp: - for url in urls: - assert 'fpy.li' not in url, f"{url} is a fpy.li URL" - if url in targets: - continue - short = next(iter_short) - redirects[short] = url - targets[url] = short - fp.write(f"RedirectTemp /{short} {url}\n") + for long in urls: + assert 'fpy.li' not in long, f"{long} is a fpy.li URL" + if long in targets: + short = targets[long] + else: + short = next(iter_short) + redirects[short] = url + targets[url] = short + fp.write(f"RedirectTemp /{short} {url}\n") + pairs.append((short, long)) + + return pairs def main(): @@ -73,7 +69,8 @@ def main(): urls = [f'https://example.com/{randrange(100000)}.html' for n in range(7)] redirects, targets = load_redirects() - new_urls(urls, redirects, targets) + for short, long in shorten(urls, redirects, targets): + print(f'fpy.li/{short}\t{long}') if __name__ == '__main__': From 5b743b5bd7040cd5c29bf2fa4804dc734ba010bd Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Thu, 22 May 2025 13:24:50 -0300 Subject: [PATCH 162/166] short.py now reads files and stdin --- links/sample-urls.txt | 47 ++++++++++++++++++++++++++++++++++++++++ links/short.htaccess | 2 +- links/short.py | 50 +++++++++++++++++++++++++++++-------------- 3 files changed, 82 insertions(+), 17 deletions(-) create mode 100644 links/sample-urls.txt diff --git a/links/sample-urls.txt b/links/sample-urls.txt new file mode 100644 index 0000000..9eac47c --- /dev/null +++ b/links/sample-urls.txt @@ -0,0 +1,47 @@ +https://www.oreilly.com/library/view/fluent-python-2nd/9781492056348/ +https://dask.org/ +http://example.com/1572039572038573208 +http://www.unicode.org/ +https://www.techcrunch.com/2024/startup-funding-trends +https://blog.medium.com/writing-tips-for-beginners +https://github.com/microsoft/typescript +https://stackoverflow.com/questions/javascript-async-await +https://www.reddit.com/r/programming/hot +https://docs.google.com/spreadsheets/create +https://www.youtube.com/watch?v=dQw4w9WgXcQ +https://www.amazon.com/dp/B08N5WRWNW +https://support.apple.com/iphone-setup-guide +https://www.wikipedia.org/wiki/Machine_Learning +https://www.linkedin.com/in/johndoe123 +https://www.instagram.com/p/CxYz123AbC/ +https://twitter.com/elonmusk/status/1234567890 +https://www.facebook.com/events/987654321 +https://drive.google.com/file/d/1AbCdEfGhIjKlMnOp/view +https://www.dropbox.com/s/qwerty123/document.pdf +https://zoom.us/j/1234567890?pwd=abcdef +https://calendly.com/janedoe/30min-meeting +https://www.shopify.com/admin/products/new +https://stripe.com/docs/api/charges/create +https://www.paypal.com/invoice/create +https://mailchimp.com/campaigns/dashboard +https://analytics.google.com/analytics/web/ +https://console.aws.amazon.com/s3/buckets +https://portal.azure.com/dashboard +https://www.figma.com/file/AbCdEf123456/design-system +https://www.notion.so/workspace/project-notes +https://trello.com/b/AbCdEfGh/marketing-board +https://slack.com/app_redirect?channel=general +https://discord.gg/AbCdEfGh123 +https://www.twitch.tv/streamername/videos +https://www.spotify.com/playlist/37i9dQZF1DXcBWIGoYBM5M +https://www.netflix.com/browse/genre/83 +https://www.hulu.com/series/breaking-bad-2008 +https://www.airbnb.com/rooms/12345678 +https://www.booking.com/hotel/us/grand-plaza.html +https://www.expedia.com/flights/search?trip=roundtrip +https://www.uber.com/ride/request +https://www.doordash.com/store/pizza-palace-123 +https://www.grubhub.com/restaurant/tacos-el-rey-456 +https://www.zillow.com/homes/for_sale/San-Francisco-CA +https://www.craigslist.org/about/sites +https://www.python.org/dev/peps/pep-0484/ \ No newline at end of file diff --git a/links/short.htaccess b/links/short.htaccess index fbe7ddf..6b09a50 100644 --- a/links/short.htaccess +++ b/links/short.htaccess @@ -1 +1 @@ -# file created and managed by short.py +# content of short.htaccess file created and managed by short.py diff --git a/links/short.py b/links/short.py index 89464a3..a1b6664 100755 --- a/links/short.py +++ b/links/short.py @@ -1,8 +1,23 @@ #!/usr/bin/env python3 +""" +short.py generates unique short URLs. + +This script reads lines from stdin or files named as arguments, then: + +1. retrieves or creates new short URLs, taking into account existing RedirectTemp + directives in custom.htacess or short.htacess; +2. appends RedirectTemp directives for newly created short URLs to short.htacess; +3. outputs the list of (short, long) URLs retrieved or created. + +""" + +import fileinput import itertools from collections.abc import Iterator +from time import strftime +BASE_DOMAIN = 'fpy.li' def load_redirects(): redirects = {} @@ -25,52 +40,55 @@ def load_redirects(): SDIGITS = '23456789abcdefghjkmnpqrstvwxyz' -def gen_short() -> Iterator[str]: +def gen_short(start_len=1) -> Iterator[str]: """ - Generate every possible sequence of SDIGITS. + Generate every possible sequence of SDIGITS, starting with start_len """ - length = 1 + length = start_len while True: for short in itertools.product(SDIGITS, repeat=length): yield ''.join(short) length += 1 -def gen_free_short(redirects: dict) -> Iterator[str]: +def gen_unused_short(redirects: dict) -> Iterator[str]: """ - Generate next available short URL. + Generate next available short URL of len >= 2. """ - for short in gen_short(): + for short in gen_short(2): if short not in redirects: yield short def shorten(urls: list[str], redirects: dict, targets: dict) -> list[tuple[str,str]]: - """return (short, long) pairs, updating short.htaccess as needed""' - iter_short = gen_free_short(redirects) + """return (short, long) pairs, appending directives to short.htaccess as needed""" + iter_short = gen_unused_short(redirects) pairs = [] + timestamp = strftime('%Y-%m-%d %H:%M:%S') with open('short.htaccess', 'a') as fp: for long in urls: - assert 'fpy.li' not in long, f"{long} is a fpy.li URL" + assert BASE_DOMAIN not in long, f"{long} is a {BASE_DOMAIN} URL" if long in targets: short = targets[long] else: short = next(iter_short) - redirects[short] = url - targets[url] = short - fp.write(f"RedirectTemp /{short} {url}\n") + redirects[short] = long + targets[long] = short + if timestamp: + fp.write(f'\n# appended: {timestamp}\n') + timestamp = None + fp.write(f'RedirectTemp /{short} {long}\n') pairs.append((short, long)) return pairs def main(): - from random import randrange - urls = [f'https://example.com/{randrange(100000)}.html' for n in range(7)] - + """read URLS from filename arguments or stdin""" + urls = [line.strip() for line in fileinput.input(encoding="utf-8")] redirects, targets = load_redirects() for short, long in shorten(urls, redirects, targets): - print(f'fpy.li/{short}\t{long}') + print(f'{BASE_DOMAIN}/{short}\t{long}') if __name__ == '__main__': From ec03da74cac0899c1f6572a8d9ed880b358494af Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Thu, 22 May 2025 13:44:46 -0300 Subject: [PATCH 163/166] short.py appends timestamps to short.htaccesss --- links/short.py | 44 ++++++++++++++++++++++---------------------- ruff.toml | 4 ++++ 2 files changed, 26 insertions(+), 22 deletions(-) create mode 100644 ruff.toml diff --git a/links/short.py b/links/short.py index a1b6664..1ebd0bd 100755 --- a/links/short.py +++ b/links/short.py @@ -6,8 +6,8 @@ This script reads lines from stdin or files named as arguments, then: 1. retrieves or creates new short URLs, taking into account existing RedirectTemp - directives in custom.htacess or short.htacess; -2. appends RedirectTemp directives for newly created short URLs to short.htacess; + directives in custom.htaccess or short.htaccess; +2. appends RedirectTemp directives for newly created short URLs to short.htaccess; 3. outputs the list of (short, long) URLs retrieved or created. """ @@ -17,21 +17,25 @@ from collections.abc import Iterator from time import strftime +HTACCESS_CUSTOM = 'custom.htaccess' +HTACCESS_SHORT = 'short.htaccess' +HTACCESS_FILES = (HTACCESS_CUSTOM, HTACCESS_SHORT) BASE_DOMAIN = 'fpy.li' -def load_redirects(): + +def load_redirects() -> tuple[dict, dict]: redirects = {} targets = {} - for filename in ('custom.htaccess', 'short.htaccess'): + for filename in HTACCESS_FILES: with open(filename) as fp: for line in fp: if line.startswith('RedirectTemp'): _, short, long = line.split() short = short[1:] # Remove leading slash - assert short not in redirects, f"{filename}: duplicate redirect from {short}" - # custom is live since 2022, we cannot change it remove duplicate targets - if not filename.startswith('custom'): - assert long not in targets, f"{filename}: Duplicate redirect to {long}" + assert short not in redirects, f'{filename}: duplicate redirect from {short}' + # htaccess.custom is live since 2022, we can't change it remove duplicate targets + if filename != HTACCESS_CUSTOM: + assert long not in targets, f'{filename}: duplicate redirect to {long}' redirects[short] = long targets[long] = short return redirects, targets @@ -41,9 +45,7 @@ def load_redirects(): def gen_short(start_len=1) -> Iterator[str]: - """ - Generate every possible sequence of SDIGITS, starting with start_len - """ + """Generate every possible sequence of SDIGITS, starting with start_len""" length = start_len while True: for short in itertools.product(SDIGITS, repeat=length): @@ -52,22 +54,20 @@ def gen_short(start_len=1) -> Iterator[str]: def gen_unused_short(redirects: dict) -> Iterator[str]: - """ - Generate next available short URL of len >= 2. - """ + """Generate next available short URL of len >= 2.""" for short in gen_short(2): if short not in redirects: yield short -def shorten(urls: list[str], redirects: dict, targets: dict) -> list[tuple[str,str]]: - """return (short, long) pairs, appending directives to short.htaccess as needed""" +def shorten(urls: list[str], redirects: dict, targets: dict) -> list[tuple[str, str]]: + """Return (short, long) pairs, appending directives to HTACCESS_SHORT as needed.""" iter_short = gen_unused_short(redirects) pairs = [] timestamp = strftime('%Y-%m-%d %H:%M:%S') - with open('short.htaccess', 'a') as fp: + with open(HTACCESS_SHORT, 'a') as fp: for long in urls: - assert BASE_DOMAIN not in long, f"{long} is a {BASE_DOMAIN} URL" + assert BASE_DOMAIN not in long, f'{long} is a {BASE_DOMAIN} URL' if long in targets: short = targets[long] else: @@ -79,16 +79,16 @@ def shorten(urls: list[str], redirects: dict, targets: dict) -> list[tuple[str,s timestamp = None fp.write(f'RedirectTemp /{short} {long}\n') pairs.append((short, long)) - + return pairs -def main(): +def main() -> None: """read URLS from filename arguments or stdin""" - urls = [line.strip() for line in fileinput.input(encoding="utf-8")] + urls = [line.strip() for line in fileinput.input(encoding='utf-8')] redirects, targets = load_redirects() for short, long in shorten(urls, redirects, targets): - print(f'{BASE_DOMAIN}/{short}\t{long}') + print(f'{BASE_DOMAIN}/{short}\t{long}') if __name__ == '__main__': diff --git a/ruff.toml b/ruff.toml new file mode 100644 index 0000000..f7b07e2 --- /dev/null +++ b/ruff.toml @@ -0,0 +1,4 @@ +line-length = 100 +[format] +# Like Python's repr(), use single quotes for strings. +quote-style = "single" \ No newline at end of file From cf996500070e0d712a3b088d29428f74ddc9fa1f Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Thu, 22 May 2025 14:28:42 -0300 Subject: [PATCH 164/166] minor refactoring to make it easier to call shorten() --- links/short.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/links/short.py b/links/short.py index 1ebd0bd..d67c71d 100755 --- a/links/short.py +++ b/links/short.py @@ -60,8 +60,9 @@ def gen_unused_short(redirects: dict) -> Iterator[str]: yield short -def shorten(urls: list[str], redirects: dict, targets: dict) -> list[tuple[str, str]]: +def shorten(urls: list[str]) -> list[tuple[str, str]]: """Return (short, long) pairs, appending directives to HTACCESS_SHORT as needed.""" + redirects, targets = load_redirects() iter_short = gen_unused_short(redirects) pairs = [] timestamp = strftime('%Y-%m-%d %H:%M:%S') @@ -86,8 +87,7 @@ def shorten(urls: list[str], redirects: dict, targets: dict) -> list[tuple[str, def main() -> None: """read URLS from filename arguments or stdin""" urls = [line.strip() for line in fileinput.input(encoding='utf-8')] - redirects, targets = load_redirects() - for short, long in shorten(urls, redirects, targets): + for short, long in shorten(urls): print(f'{BASE_DOMAIN}/{short}\t{long}') From 162dbadbe5cabdfc737c85446a8d788398dfe8b8 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Fri, 23 May 2025 16:35:01 -0300 Subject: [PATCH 165/166] links from vol1 to pythonfluente.com --- links/FPY.LI.htaccess | 1091 ++++++++++++++++++++++++++++++++++++++++- links/short.htaccess | 13 + 2 files changed, 1102 insertions(+), 2 deletions(-) diff --git a/links/FPY.LI.htaccess b/links/FPY.LI.htaccess index 7338157..5607fa4 100644 --- a/links/FPY.LI.htaccess +++ b/links/FPY.LI.htaccess @@ -1,2 +1,1089 @@ -# to update this file: -# cat custom.htaccess short.htaccess >> FPY.LI.htaccess +# to recreate or update this file: +# $ cat custom.htaccess short.htaccess > FPY.LI.htaccess + +ErrorDocument 404 /404.html + +# main resources +RedirectTemp /book https://www.oreilly.com/library/view/fluent-python-2nd/9781492056348/ +RedirectTemp /code https://github.com/fluentpython/example-code-2e +RedirectTemp /home https://www.fluentpython.com/ + +# URLs mentioned at least three times +RedirectTemp /bisect https://www.fluentpython.com/extra/ordered-sequences-with-bisect/ +RedirectTemp /cardxvi https://www.python.org/dev/peps/pep-0484/#the-numeric-tower +RedirectTemp /collec https://docs.python.org/3/library/collections.html +RedirectTemp /dask https://dask.org/ +RedirectTemp /dtmodel https://docs.python.org/3/reference/datamodel.html +RedirectTemp /descr101 https://www.python.org/download/releases/2.2.3/descrintro/ +RedirectTemp /descrhow https://docs.python.org/3/howto/descriptor.html +RedirectTemp /doctest https://docs.python.org/3/library/doctest.html +RedirectTemp /effectpy https://effectivepython.com/ +RedirectTemp /fmtspec https://docs.python.org/3/library/string.html#formatspec +RedirectTemp /gunicorn https://gunicorn.org/ +RedirectTemp /hashint https://www.fluentpython.com/extra/internals-of-sets-and-dicts/ +RedirectTemp /hattingh https://www.oreilly.com/library/view/using-asyncio-in/9781492075325/ +RedirectTemp /httpx https://www.python-httpx.org/ +RedirectTemp /initvar https://docs.python.org/3/library/dataclasses.html#init-only-variables +RedirectTemp /mypy https://mypy.readthedocs.io/en/stable/ +RedirectTemp /norvigdp http://norvig.com/design-patterns/ +RedirectTemp /nsphere https://en.wikipedia.org/wiki/N-sphere +RedirectTemp /oldcoro https://www.fluentpython.com/extra/classic-coroutines/ +RedirectTemp /pandas https://pandas.pydata.org/ +RedirectTemp /pycook3 https://www.oreilly.com/library/view/python-cookbook-3rd/9781449357337/ +RedirectTemp /pynut3 https://www.oreilly.com/library/view/python-in-a/9781491913833/ +RedirectTemp /pypydif https://doc.pypy.org/en/latest/cpython_differences.html#subclasses-of-built-in-types +RedirectTemp /shed4051 https://github.com/python/typeshed/issues/4051 +RedirectTemp /specattr https://docs.python.org/3/library/stdtypes.html#special-attributes +RedirectTemp /typecoro https://docs.python.org/3.10/library/typing.html#typing.Coroutine +RedirectTemp /typing https://docs.python.org/3/library/typing.html +RedirectTemp /weakref https://www.fluentpython.com/extra/weak-references/ + +# URL added during QA of the Second Edition +RedirectTemp /bdfl https://www.artima.com/weblogs/viewpost.jsp?thread=235725 + +# Python Enhancement Proposals +RedirectTemp /pep218 https://www.python.org/dev/peps/pep-0218/ +RedirectTemp /pep227 https://www.python.org/dev/peps/pep-0227/ +RedirectTemp /pep255 https://www.python.org/dev/peps/pep-0255/ +RedirectTemp /pep342 https://www.python.org/dev/peps/pep-0342/ +RedirectTemp /pep343 https://www.python.org/dev/peps/pep-0343/ +RedirectTemp /pep357 https://www.python.org/dev/peps/pep-0357/ +RedirectTemp /pep362 https://www.python.org/dev/peps/pep-0362/ +RedirectTemp /pep371 https://www.python.org/dev/peps/pep-0371/ +RedirectTemp /pep380 https://www.python.org/dev/peps/pep-0380/ +RedirectTemp /pep393 https://www.python.org/dev/peps/pep-0393/ +RedirectTemp /pep412 https://www.python.org/dev/peps/pep-0412/ +RedirectTemp /pep442 https://www.python.org/dev/peps/pep-0442/ +RedirectTemp /pep443 https://www.python.org/dev/peps/pep-0443/ +RedirectTemp /pep448 https://www.python.org/dev/peps/pep-0448/ +RedirectTemp /pep455 https://www.python.org/dev/peps/pep-0455/ +RedirectTemp /pep456 https://www.python.org/dev/peps/pep-0456/ +RedirectTemp /pep461 https://www.python.org/dev/peps/pep-0461/ +RedirectTemp /pep465 https://www.python.org/dev/peps/pep-0465/ +RedirectTemp /pep467 https://www.python.org/dev/peps/pep-0467/ +RedirectTemp /pep482 https://www.python.org/dev/peps/pep-0482/ +RedirectTemp /pep483 https://www.python.org/dev/peps/pep-0483/ +RedirectTemp /pep484 https://www.python.org/dev/peps/pep-0484/ +RedirectTemp /pep487 https://www.python.org/dev/peps/pep-0487/ +RedirectTemp /pep492 https://www.python.org/dev/peps/pep-0492/ +RedirectTemp /pep519 https://www.python.org/dev/peps/pep-0519/ +RedirectTemp /pep525 https://www.python.org/dev/peps/pep-0525/ +RedirectTemp /pep526 https://www.python.org/dev/peps/pep-0526/ +RedirectTemp /pep528 https://www.python.org/dev/peps/pep-0528/ +RedirectTemp /pep529 https://www.python.org/dev/peps/pep-0529/ +RedirectTemp /pep530 https://www.python.org/dev/peps/pep-0530/ +RedirectTemp /pep544 https://www.python.org/dev/peps/pep-0544/ +RedirectTemp /pep554 https://www.python.org/dev/peps/pep-0554/ +RedirectTemp /pep557 https://www.python.org/dev/peps/pep-0557/ +RedirectTemp /pep560 https://www.python.org/dev/peps/pep-0560/ +RedirectTemp /pep561 https://www.python.org/dev/peps/pep-0561/ +RedirectTemp /pep563 https://www.python.org/dev/peps/pep-0563/ +RedirectTemp /pep570 https://www.python.org/dev/peps/pep-0570/ +RedirectTemp /pep572 https://www.python.org/dev/peps/pep-0572/ +RedirectTemp /pep584 https://www.python.org/dev/peps/pep-0584/ +RedirectTemp /pep585 https://www.python.org/dev/peps/pep-0585/ +RedirectTemp /pep586 https://www.python.org/dev/peps/pep-0586/ +RedirectTemp /pep589 https://www.python.org/dev/peps/pep-0589/ +RedirectTemp /pep591 https://www.python.org/dev/peps/pep-0591/ +RedirectTemp /pep593 https://www.python.org/dev/peps/pep-0593/ +RedirectTemp /pep604 https://www.python.org/dev/peps/pep-0604/ +RedirectTemp /pep612 https://www.python.org/dev/peps/pep-0612/ +RedirectTemp /pep613 https://www.python.org/dev/peps/pep-0613/ +RedirectTemp /pep616 https://www.python.org/dev/peps/pep-0616/ +RedirectTemp /pep617 https://www.python.org/dev/peps/pep-0617/ +RedirectTemp /pep618 https://www.python.org/dev/peps/pep-0618/ +RedirectTemp /pep634 https://www.python.org/dev/peps/pep-0634/ +RedirectTemp /pep635 https://www.python.org/dev/peps/pep-0635/ +RedirectTemp /pep636 https://www.python.org/dev/peps/pep-0636/ +RedirectTemp /pep638 https://www.python.org/dev/peps/pep-0638/ +RedirectTemp /pep645 https://www.python.org/dev/peps/pep-0645/ +RedirectTemp /pep646 https://www.python.org/dev/peps/pep-0646/ +RedirectTemp /pep647 https://www.python.org/dev/peps/pep-0647/ +RedirectTemp /pep649 https://www.python.org/dev/peps/pep-0649/ +RedirectTemp /pep654 https://www.python.org/dev/peps/pep-0654/ +RedirectTemp /pep655 https://www.python.org/dev/peps/pep-0655/ +RedirectTemp /pep661 https://www.python.org/dev/peps/pep-0661/ +RedirectTemp /pep3099 https://www.python.org/dev/peps/pep-3099/ +RedirectTemp /pep3102 https://www.python.org/dev/peps/pep-3102/ +RedirectTemp /pep3104 https://www.python.org/dev/peps/pep-3104/ +RedirectTemp /pep3106 https://www.python.org/dev/peps/pep-3106/ +RedirectTemp /pep3107 https://www.python.org/dev/peps/pep-3107/ +RedirectTemp /pep3115 https://www.python.org/dev/peps/pep-3115/ +RedirectTemp /pep3118 https://www.python.org/dev/peps/pep-3118/ +RedirectTemp /pep3119 https://www.python.org/dev/peps/pep-3119/ +RedirectTemp /pep3129 https://www.python.org/dev/peps/pep-3129/ +RedirectTemp /pep3132 https://www.python.org/dev/peps/pep-3132/ +RedirectTemp /pep3141 https://www.python.org/dev/peps/pep-3141/ +RedirectTemp /pep3148 https://www.python.org/dev/peps/pep-3148/ +RedirectTemp /pep3155 https://www.python.org/dev/peps/pep-3155/ +RedirectTemp /pep3333 https://www.python.org/dev/peps/pep-3333/ + +# Remaining URLs by chapter + +############################################################ p +RedirectTemp /p-1 https://mail.python.org/pipermail/python-list/2002-December/134521.html +RedirectTemp /p-2 https://docs.python.org/3.10/tutorial/ +RedirectTemp /p-3 https://docs.python.org/3/tutorial/ +RedirectTemp /p-4 https://www.oreilly.com/library/view/fluent-python-2nd/9781492056348/ +RedirectTemp /p-5 https://www.oreilly.com/online-learning/try-now.html +RedirectTemp /p-6 https://www.oreilly.com/library/view/fluent-python-2nd/9781492056348/ +RedirectTemp /p-7 https://stackoverflow.com/users/95810/alex-martelli +RedirectTemp /p-8 https://pythonpro.com.br +RedirectTemp /p-9 https://groups.google.com/g/python-brasil +RedirectTemp /p-10 https://www.coffeelab.com.br/ +RedirectTemp /p-11 https://garoa.net.br/wiki/P%C3%A1gina_principal +############################################################ a +RedirectTemp /a-1 https://groups.google.com/forum/#!topic/python-tulip/Y4bhLNbKs74 +RedirectTemp /a-2 https://docs.python.org/3/library/asyncio-eventloop.html#executor +RedirectTemp /a-3 https://www.youtube.com/watch?v=x-kB2o8sd5c +RedirectTemp /a-4 https://www.youtube.com/watch?v=OSGv2VnC0go +RedirectTemp /a-5 https://mail.python.org/pipermail/python-ideas/2015-March/032557.html +RedirectTemp /a-6 https://pypi.org/project/pep8/ +RedirectTemp /a-7 https://pypi.org/project/flake8/ +RedirectTemp /a-8 https://pypi.org/project/pyflakes/ +RedirectTemp /a-9 https://pypi.org/project/mccabe/ +RedirectTemp /a-10 https://google.github.io/styleguide/pyguide.html +RedirectTemp /a-11 https://flask.palletsprojects.com/en/1.1.x/styleguide/ +RedirectTemp /a-12 https://docs.python-guide.org/ +RedirectTemp /a-13 https://david.goodger.org/projects/pycon/2007/idiomatic/handout.html +RedirectTemp /a-14 https://docs.mongodb.com/manual/about/#about-the-documentation-process +RedirectTemp /a-15 https://blog.startifact.com/posts/older/what-is-pythonic.html +RedirectTemp /a-16 https://mail.python.org/pipermail/tutor/2003-October/thread.html#25930 +RedirectTemp /a-17 https://mail.python.org/pipermail/python-list/2003-April/192027.html +RedirectTemp /a-18 https://www.python.org/doc/essays/ +############################################################ 01 +RedirectTemp /1-1 http://hugunin.net/story_of_jython.html +RedirectTemp /1-2 https://www.oreilly.com/library/view/jython-essentials/9781449397364/ +RedirectTemp /1-3 https://docs.python.org/3/reference/lexical_analysis.html#reserved-classes-of-identifiers +RedirectTemp /1-4 https://docs.python.org/3.10/library/string.html#format-string-syntax +RedirectTemp /1-5 https://stackoverflow.com/questions/1436703/what-is-the-difference-between-str-and-repr +RedirectTemp /1-6 https://docs.python.org/3/library/stdtypes.html#truth +RedirectTemp /1-7 https://docs.python.org/3/tutorial/controlflow.html#unpacking-argument-lists +RedirectTemp /1-8 https://www.python.org/doc/humor/#the-zen-of-python +RedirectTemp /1-9 https://stackoverflow.com/users/95810/alex-martelli +RedirectTemp /1-10 https://en.wikipedia.org/wiki/Object_model +RedirectTemp /1-11 https://www.dourish.com/goodies/jargon.html +RedirectTemp /1-12 https://zopeinterface.readthedocs.io/en/latest/ +RedirectTemp /1-13 https://plone.org/ +############################################################ 02 +RedirectTemp /2-1 https://github.com/fluentpython/example-code-2e/blob/master/02-array-seq/listcomp_speed.py +RedirectTemp /2-2 https://www.python.org/dev/peps/pep-3132/ +RedirectTemp /2-3 https://stackoverflow.com/questions/68630/are-tuples-more-efficient-than-lists-in-python/22140115#22140115 +RedirectTemp /2-4 https://docs.python.org/3/whatsnew/3.5.html#pep-448-additional-unpacking-generalizations +RedirectTemp /2-5 https://docs.python.org/3/whatsnew/3.5.html#pep-448-additional-unpacking-generalizations +RedirectTemp /2-6 https://docs.python.org/3.10/whatsnew/3.10.html#pep-634-structural-pattern-matching +RedirectTemp /2-7 https://docs.python.org/3.10/whatsnew/3.10.html +RedirectTemp /2-8 https://en.wikipedia.org/wiki/Switch_statement#Fallthrough +RedirectTemp /2-9 https://en.wikipedia.org/wiki/Dangling_else +RedirectTemp /2-10 https://github.com/gvanrossum/patma/blob/3ece6444ef70122876fd9f0099eb9490a2d630df/EXAMPLES.md#case-6-a-very-deep-iterable-and-type-match-with-extraction +RedirectTemp /2-11 https://github.com/fluentpython/lispy/blob/main/original/norvig/lis.py +RedirectTemp /2-12 https://norvig.com/lispy.html +RedirectTemp /2-13 https://numpy.org/doc/stable/user/quickstart.html#indexing-slicing-and-iterating +RedirectTemp /2-14 https://pythontutor.com/ +RedirectTemp /2-15 https://en.wikipedia.org/wiki/Fluent_interface +RedirectTemp /2-16 https://docs.python.org/3/library/bisect.html#bisect.insort +RedirectTemp /2-17 https://stackoverflow.com/questions/4845418/when-should-a-memoryview-be-used/ +RedirectTemp /2-18 https://www.fluentpython.com/extra/parsing-binary-struct/ +RedirectTemp /2-19 http://www.netlib.org +RedirectTemp /2-20 https://pandas.pydata.org/ +RedirectTemp /2-21 https://scikit-learn.org/stable/ +RedirectTemp /2-22 https://docs.python.org/3/howto/sorting.html +RedirectTemp /2-23 https://www.python.org/dev/peps/pep-3132/ +RedirectTemp /2-24 https://bugs.python.org/issue2292 +RedirectTemp /2-25 https://docs.python.org/3.10/whatsnew/3.10.html#pep-634-structural-pattern-matching +RedirectTemp /2-26 https://docs.python.org/3.10/whatsnew/3.10.html +RedirectTemp /2-27 https://www.python.org/dev/peps/pep-0636/#appendix-a-quick-intro +RedirectTemp /2-28 https://eli.thegreenplace.net/2011/11/28/less-copies-in-python-with-the-buffer-protocol-and-memoryviews/ +RedirectTemp /2-29 https://jakevdp.github.io/PythonDataScienceHandbook/ +RedirectTemp /2-30 https://www.oreilly.com/library/view/python-for-data/9781491957653/ +RedirectTemp /2-31 https://www.labri.fr/perso/nrougier/from-python-to-numpy/ +RedirectTemp /2-32 https://www.cs.utexas.edu/users/EWD/transcriptions/EWD08xx/EWD831.html +RedirectTemp /2-33 http://www.fonts101.com/fonts/view/Uncategorized/34398/Dijkstra +RedirectTemp /2-34 https://docs.python.org/3/reference/datamodel.html#objects-values-and-types +RedirectTemp /2-35 https://en.wikipedia.org/wiki/Timsort +RedirectTemp /2-36 http://www.groklaw.net/pdf3/OraGoogle-1202.pdf +RedirectTemp /2-37 https://www.python.org/doc/humor/#id9 +############################################################ 03 +RedirectTemp /3-1 https://www.python.org/dev/peps/pep-0584/#motivation +RedirectTemp /3-2 https://docs.python.org/3.10/c-api/typeobj.html#Py_TPFLAGS_MAPPING +RedirectTemp /3-3 https://docs.python.org/3/glossary.html#term-hashable +RedirectTemp /3-4 https://docs.python.org/3/glossary.html#term-hashable +RedirectTemp /3-5 http://www.aleax.it/Python/accu04_Relearn_Python_alex.pdf +RedirectTemp /3-6 https://github.com/pingo-io/pingo-py +RedirectTemp /3-7 https://github.com/fluentpython/example-code-2e/blob/master/03-dict-set/missing.py +RedirectTemp /3-8 https://docs.python.org/3/library/collections.html#collections.ChainMap +RedirectTemp /3-9 https://docs.python.org/3/library/collections.html#collections.Counter +RedirectTemp /3-10 https://docs.python.org/3/library/shelve.html +RedirectTemp /3-11 https://docs.python.org/3/library/dbm.html +RedirectTemp /3-12 https://docs.python.org/3/library/pickle.html +RedirectTemp /3-13 https://nedbatchelder.com/blog/202006/pickles_nine_flaws.html +RedirectTemp /3-14 https://github.com/python/cpython/blob/0bbf30e2b910bc9c5899134ae9d73a8df968da35/Lib/_collections_abc.py#L813 +RedirectTemp /3-15 https://mail.python.org/pipermail/python-dev/2015-May/140003.html +RedirectTemp /3-16 https://bugs.python.org/issue18986 +RedirectTemp /3-17 https://github.com/fluentpython/example-code-2e/blob/master/03-dict-set/transformdict.py +RedirectTemp /3-18 http://gandenberger.org/2018/03/10/ordered-dicts-vs-ordereddict/ +RedirectTemp /3-19 https://www.pypy.org/ +RedirectTemp /3-20 https://morepypy.blogspot.com/2015/01/faster-more-memory-efficient-and-more.html +RedirectTemp /3-21 https://www.npopov.com/2014/12/22/PHPs-new-hashtable-implementation.html +RedirectTemp /3-22 https://www.youtube.com/watch?v=66P5FMkWoVU +RedirectTemp /3-23 https://pyvideo.org/video/276/the-mighty-dictionary-55/ +RedirectTemp /3-24 https://www.youtube.com/watch?v=p33CVV29OG8 +RedirectTemp /3-25 https://docs.python.org/3/whatsnew/3.6.html#new-dict-implementation +RedirectTemp /3-26 https://github.com/python/cpython/blob/cf7eaa4617295747ee5646c4e2b7e7a16d7c64ab/Objects/dictobject.c +RedirectTemp /3-27 https://github.com/python/cpython/blob/cf7eaa4617295747ee5646c4e2b7e7a16d7c64ab/Objects/dictnotes.txt +RedirectTemp /3-28 https://www.youtube.com/watch?v=tGAngdU_8D8 +RedirectTemp /3-29 https://speakerdeck.com/ramalho/python-set-practice-at-pycon +RedirectTemp /3-30 https://github.com/standupdev/uintset +RedirectTemp /3-31 https://spectrum.ieee.org/hans-peter-luhn-and-the-birth-of-the-hashing-algorithm +RedirectTemp /3-32 http://www.json.org/fatfree.html +RedirectTemp /3-33 https://twitter.com/mitsuhiko/status/1229385843585974272 +############################################################ 04 +RedirectTemp /4-1 https://www.slideshare.net/fischertrav/character-encoding-unicode-how-to-with-dignity-33352863 +RedirectTemp /4-2 https://pyvideo.org/video/2625/character-encoding-and-unicode-in-python/ +RedirectTemp /4-3 https://www.fluentpython.com/extra/parsing-binary-struct/ +RedirectTemp /4-4 https://www.fluentpython.com/extra/multi-character-emojis/ +RedirectTemp /4-5 https://w3techs.com/technologies/overview/character_encoding +RedirectTemp /4-6 https://docs.python.org/3/library/codecs.html#codecs.register_error +RedirectTemp /4-7 https://docs.python.org/3/library/stdtypes.html#str.isascii +RedirectTemp /4-8 https://pypi.org/project/chardet/ +RedirectTemp /4-9 https://docs.python.org/3/library/codecs.html#encodings-and-unicode +RedirectTemp /4-10 https://nedbatchelder.com/text/unipain/unipain.html +RedirectTemp /4-11 https://devblogs.microsoft.com/commandline/windows-command-line-unicode-and-utf-8-output-text-buffer/ +RedirectTemp /4-12 https://docs.python.org/3/using/cmdline.html#envvar-PYTHONIOENCODING +RedirectTemp /4-13 https://docs.python.org/3/using/cmdline.html#envvar-PYTHONLEGACYWINDOWSSTDIO +RedirectTemp /4-14 https://docs.python.org/3/library/locale.html#locale.getpreferredencoding +RedirectTemp /4-15 http://www.w3.org/TR/charmod-norm/ +RedirectTemp /4-16 https://docs.python.org/3/library/locale.html?highlight=strxfrm#locale.strxfrm +RedirectTemp /4-17 https://pypi.org/project/pyuca/ +RedirectTemp /4-18 https://github.com/jtauber/pyuca +RedirectTemp /4-19 http://www.unicode.org/Public/UCA/6.3.0/allkeys.txt +RedirectTemp /4-20 https://pypi.org/project/PyICU/ +RedirectTemp /4-21 https://docs.python.org/3.10/library/stdtypes.html#str.isalpha +RedirectTemp /4-22 https://en.wikipedia.org/wiki/Unicode_character_property#General_Category +RedirectTemp /4-23 https://en.wikipedia.org/wiki/Unicode_character_property +RedirectTemp /4-24 https://github.com/microsoft/terminal +RedirectTemp /4-25 https://docs.python.org/3/library/unicodedata.html +RedirectTemp /4-26 https://docs.python.org/3/reference/lexical_analysis.html#string-literal-concatenation +RedirectTemp /4-27 https://docs.python.org/3/library/re.html +RedirectTemp /4-28 https://nedbatchelder.com/text/unipain.html +RedirectTemp /4-29 https://www.slideshare.net/fischertrav/character-encoding-unicode-how-to-with-dignity-33352863 +RedirectTemp /4-30 https://pyvideo.org/video/2625/character-encoding-and-unicode-in-python/ +RedirectTemp /4-31 https://regebro.wordpress.com/2011/03/23/unconfusing-unicode-what-is-unicode/ +RedirectTemp /4-32 https://docs.python.org/3/howto/unicode.html +RedirectTemp /4-33 https://diveintopython3.net/strings.html +RedirectTemp /4-34 https://diveintopython3.net/ +RedirectTemp /4-35 https://finderiko.com/python-book +RedirectTemp /4-36 https://docs.python.org/3.0/whatsnew/3.0.html#text-vs-data-instead-of-unicode-vs-8-bit +RedirectTemp /4-37 https://lucumr.pocoo.org/2013/7/2/the-updated-guide-to-unicode/ +RedirectTemp /4-38 http://python-notes.curiousefficiency.org/en/latest/python3/binary_protocols.html +RedirectTemp /4-39 http://python-notes.curiousefficiency.org/en/latest/python3/text_file_processing.html +RedirectTemp /4-40 https://docs.python.org/3/library/codecs.html#standard-encodings +RedirectTemp /4-41 https://github.com/python/cpython/blob/0bbf30e2b910bc9c5899134ae9d73a8df968da35/Tools/unicode/listcodecs.py +RedirectTemp /4-42 https://www.oreilly.com/library/view/unicode-explained/059610121X/ +RedirectTemp /4-43 https://www.informit.com/store/unicode-demystified-a-practical-programmers-guide-to-9780201700527 +RedirectTemp /4-44 https://unicodebook.readthedocs.io/index.html +RedirectTemp /4-45 https://www.w3.org/International/wiki/Case_folding +RedirectTemp /4-46 http://www.w3.org/TR/charmod-norm/ +RedirectTemp /4-47 http://unicode.org/reports/tr15/ +RedirectTemp /4-48 http://www.unicode.org/faq/normalization.html +RedirectTemp /4-49 http://www.unicode.org/ +RedirectTemp /4-50 http://www.macchiato.com/unicode/nfc-faq +RedirectTemp /4-51 https://stories.moma.org/the-original-emoji-set-has-been-added-to-the-museum-of-modern-arts-collection-c6060e141f61?gi=c403f5a840a5 +RedirectTemp /4-52 https://emojipedia.org/ +RedirectTemp /4-53 https://blog.emojipedia.org/correcting-the-record-on-the-first-emoji-set/ +RedirectTemp /4-54 http://emojitracker.com/ +RedirectTemp /4-55 http://www.unicode.org/glossary/#plain_text +RedirectTemp /4-56 http://www.methods.co.nz/asciidoc/ +RedirectTemp /4-57 https://atlas.oreilly.com/ +############################################################ 05 +RedirectTemp /5-1 https://docs.python.org/3/library/typing.html#typing.TypedDict +RedirectTemp /5-2 https://docs.python.org/3.10/library/inspect.html#inspect.get_annotations +RedirectTemp /5-3 https://docs.python.org/3/library/typing.html#typing.get_type_hints +RedirectTemp /5-4 https://docs.python.org/3.8/library/collections.html#collections.somenamedtuple._asdict +RedirectTemp /5-5 https://www.jetbrains.com/pycharm/ +RedirectTemp /5-6 https://www.python.org/dev/peps/pep-0484/#acceptable-type-hints +RedirectTemp /5-7 https://docs.python.org/3/library/dataclasses.html#dataclasses.dataclass +RedirectTemp /5-8 https://docs.python.org/3/library/dataclasses.html#dataclasses.dataclass +RedirectTemp /5-9 https://docs.python.org/3/library/dataclasses.html +RedirectTemp /5-10 https://docs.python.org/3/library/dataclasses.html#inheritance +RedirectTemp /5-11 https://www.python.org/dev/peps/pep-0526/#class-and-instance-variable-annotations +RedirectTemp /5-12 https://dublincore.org/specifications/dublin-core/ +RedirectTemp /5-13 https://en.wikipedia.org/wiki/Dublin_Core +RedirectTemp /5-14 https://martinfowler.com/bliki/CodeSmell.html +RedirectTemp /5-15 https://martinfowler.com/books/refactoring.html +RedirectTemp /5-16 https://www.python.org/dev/peps/pep-0634/#class-patterns +RedirectTemp /5-17 https://docs.python.org/3/library/dataclasses.html +RedirectTemp /5-18 https://www.python.org/dev/peps/pep-0557/#id47 +RedirectTemp /5-19 https://www.python.org/dev/peps/pep-0557/#id48 +RedirectTemp /5-20 https://www.python.org/dev/peps/pep-0557/#id33 +RedirectTemp /5-21 https://realpython.com +RedirectTemp /5-22 https://realpython.com/python-data-classes/ +RedirectTemp /5-23 https://www.youtube.com/watch?v=T-TwcmT6Rcw +RedirectTemp /5-24 https://www.attrs.org/en/stable/ +RedirectTemp /5-25 https://glyph.twistedmatrix.com/2016/08/attrs.html +RedirectTemp /5-26 https://www.attrs.org/en/stable/why.html +RedirectTemp /5-27 https://github.com/dabeaz/cluegen +RedirectTemp /5-28 https://refactoring.guru/ +RedirectTemp /5-29 https://refactoring.guru/smells/data-class +RedirectTemp /5-30 https://web.archive.org/web/20190204130328/http://catb.org/esr/jargon/html/G/Guido.html +RedirectTemp /5-31 https://web.archive.org/web/20190211161610/http://catb.org/esr/jargon/html/index.html +RedirectTemp /5-32 https://www.attrs.org/en/stable/ +############################################################ 06 +RedirectTemp /6-1 https://www.olin.edu/faculty/profile/lynn-andrea-stein/ +RedirectTemp /6-2 https://docs.python.org/3/reference/datamodel.html#objects-values-and-types +RedirectTemp /6-3 https://pythontutor.com/ +RedirectTemp /6-4 https://docs.python.org/3/library/copy.html +RedirectTemp /6-5 https://en.wikipedia.org/wiki/Principle_of_least_astonishment +RedirectTemp /6-6 https://docs.python.org/3/reference/datamodel.html#object.%5C_%5C_del__ +RedirectTemp /6-7 https://emptysqua.re/blog/pypy-garbage-collection-and-a-deadlock/ +RedirectTemp /6-8 https://www.youtube.com/watch?v=HHFCFJSPWrI&feature=youtu.be +RedirectTemp /6-9 http://pymotw.com/3/copy/ +RedirectTemp /6-10 http://pymotw.com/3/weakref/ +RedirectTemp /6-11 https://docs.python.org/3/library/gc.html +RedirectTemp /6-12 https://devguide.python.org/garbage_collector/ +RedirectTemp /6-13 https://devguide.python.org/ +RedirectTemp /6-14 https://www.python.org/dev/peps/pep-0442/ +RedirectTemp /6-15 https://en.wikipedia.org/wiki/String_interning +RedirectTemp /6-16 https://en.wikipedia.org/wiki/Haddocks%27_Eyes +RedirectTemp /6-17 https://thp.io/2012/python-gc/python_gc_final_2012-01-22.pdf +############################################################ 07 +RedirectTemp /7-1 http://python-history.blogspot.com/2009/04/origins-of-pythons-functional-features.html +RedirectTemp /7-2 https://www.fluentpython.com/extra/function-introspection/ +RedirectTemp /7-3 https://docs.python.org/3/library/functions.html#map +RedirectTemp /7-4 https://en.wikipedia.org/wiki/Functional_programming +RedirectTemp /7-5 https://docs.python.org/3/howto/functional.html +RedirectTemp /7-6 https://docs.python.org/3/reference/datamodel.html#the-standard-type-hierarchy +RedirectTemp /7-7 https://docs.python.org/3/whatsnew/3.8.html#positional-only-parameters +RedirectTemp /7-8 https://docs.python.org/3/whatsnew/3.8.html#positional-only-parameters +RedirectTemp /7-9 https://github.com/python/cpython/blob/0bbf30e2b910bc9c5899134ae9d73a8df968da35/Lib/functools.py#L341 +RedirectTemp /7-10 https://docs.python.org/3/reference/datamodel.html#the-standard-type-hierarchy +RedirectTemp /7-11 https://docs.python.org/3/howto/functional.html +RedirectTemp /7-12 https://stackoverflow.com/questions/3252228/python-why-is-functools-partial-necessary +RedirectTemp /7-13 https://speakerdeck.com/ramalho/beyond-paradigms-berlin-edition +RedirectTemp /7-14 https://www.youtube.com/watch?v=bF3a2VYXxa0 +RedirectTemp /7-15 http://cs.brown.edu/~sk/Publications/Papers/Published/sk-teach-pl-post-linnaean/ +RedirectTemp /7-16 http://python-history.blogspot.com/2009/04/origins-of-pythons-functional-features.html +RedirectTemp /7-17 https://raw.githubusercontent.com/python/cpython/main/Misc/HISTORY +RedirectTemp /7-18 http://neopythonic.blogspot.com/2009/04/tail-recursion-elimination.html +############################################################ 08 +RedirectTemp /8-1 https://www.python.org/dev/peps/pep-0484/#non-goals +RedirectTemp /8-2 https://github.com/python/typing/issues/182 +RedirectTemp /8-3 https://github.com/python/mypy/issues/731 +RedirectTemp /8-4 https://github.com/google/pytype +RedirectTemp /8-5 https://github.com/Microsoft/pyright +RedirectTemp /8-6 https://pyre-check.org/ +RedirectTemp /8-7 https://mypy.readthedocs.io/en/stable/introduction.html +RedirectTemp /8-8 https://mypy.readthedocs.io/en/stable/config_file.html +RedirectTemp /8-9 https://pypi.org/project/flake8/ +RedirectTemp /8-10 https://pypi.org/project/blue/ +RedirectTemp /8-11 https://pypi.org/project/black/ +RedirectTemp /8-12 https://wefearchange.org/2020/11/steeringcouncil.rst.html +RedirectTemp /8-13 https://docs.python.org/3/library/collections.abc.html#collections-abstract-base-classes +RedirectTemp /8-14 https://en.wikipedia.org/wiki/Barbara_Liskov +RedirectTemp /8-15 https://en.wikipedia.org/wiki/Behavioral_subtyping +RedirectTemp /8-16 https://www.python.org/dev/peps/pep-0585/#implementation +RedirectTemp /8-17 https://docs.python.org/3/library/typing.html#module-contents +RedirectTemp /8-18 https://en.wikipedia.org/wiki/Geohash +RedirectTemp /8-19 https://en.wikipedia.org/wiki/Inverted_index +RedirectTemp /8-20 https://docs.python.org/3/library/typing.html#typing.List +RedirectTemp /8-21 https://docs.python.org/3/library/typing.html#typing.Dict +RedirectTemp /8-22 https://docs.python.org/3/library/typing.html#typing.Set +RedirectTemp /8-23 https://www.python.org/dev/peps/pep-0585/#implementation +RedirectTemp /8-24 https://docs.python.org/3/library/numbers.html +RedirectTemp /8-25 https://docs.python.org/3/library/typing.html#typing.List +RedirectTemp /8-26 https://github.com/python/typeshed +RedirectTemp /8-27 https://github.com/python/typeshed/blob/66cd36268a6a667714efaa27198a41d0d7f89477/stdlib/2and3/math.pyi#L45 +RedirectTemp /8-28 https://docs.python.org/3/library/statistics.html#statistics.mode +RedirectTemp /8-29 https://github.com/python/cpython/blob/822efa5695b5ba6c2316c1400e4e9ec2546f7ea5/Lib/statistics.py#L534 +RedirectTemp /8-30 https://github.com/python/typeshed/blob/e1e99245bb46223928eba68d4fc74962240ba5b4/stdlib/3/statistics.pyi +RedirectTemp /8-31 https://docs.python.org/3/library/statistics.html#statistics.mode +RedirectTemp /8-32 https://github.com/python/typeshed/blob/a8834fcd46339e17fc8add82b5803a1ce53d3d60/stdlib/3/statistics.pyi#L32 +RedirectTemp /8-33 https://mail.python.org/archives/list/python-dev@python.org/thread/CLVXXPQ2T2LQ5MP2Y53VVQFCXYWQJHKZ/ +RedirectTemp /8-34 https://docs.python.org/3/library/typing.html#typing.Callable +RedirectTemp /8-35 https://pypi.org/project/blue/ +RedirectTemp /8-36 https://www.python.org/dev/peps/pep-0484/#id38 +RedirectTemp /8-37 https://docs.google.com/document/d/1aXs1tpwzPjW9MdsG5dI7clNFyYayFBkcXwRDo-qvbIk/preview +RedirectTemp /8-38 https://www.oreilly.com/library/view/the-best-software/9781590595008/ +RedirectTemp /8-39 https://www.youtube.com/watch?v=YFexUDjHO6w +RedirectTemp /8-40 https://www.youtube.com/watch?v=YFexUDjHO6w&t=13m40s +RedirectTemp /8-41 https://bernat.tech/posts/the-state-of-type-hints-in-python/ +RedirectTemp /8-42 https://realpython.com/python-type-checking/ +RedirectTemp /8-43 https://cjolowicz.github.io/posts/hypermodern-python-04-typing/ +RedirectTemp /8-44 https://mypy.readthedocs.io/en/stable/index.html +RedirectTemp /8-45 https://mypy.readthedocs.io/en/stable/cheat_sheet_py3.html +RedirectTemp /8-46 https://mypy.readthedocs.io/en/stable/common_issues.html +RedirectTemp /8-47 https://github.com/typeddjango/awesome-python-typing +RedirectTemp /8-48 https://docs.python.org/3/library/functions.html#max +RedirectTemp /8-49 https://en.wikipedia.org/wiki/Linguistic_relativity +RedirectTemp /8-50 https://pypistats.org/top +RedirectTemp /8-51 https://github.com/psf/requests/issues/3855 +RedirectTemp /8-52 https://lwn.net/Articles/643399/ +RedirectTemp /8-53 https://docs.python-requests.org/en/master/api/#requests.request +RedirectTemp /8-54 https://queue.acm.org/detail.cfm?id=1039523 +############################################################ 09 +RedirectTemp /9-1 https://docs.python.org/3/library/dis.html +RedirectTemp /9-2 https://en.wikipedia.org/wiki/Memoization +RedirectTemp /9-3 https://numpy.org/doc/stable/user/basics.types.html +RedirectTemp /9-4 https://docs.python.org/3/library/functools.html#functools.singledispatch +RedirectTemp /9-5 https://github.com/GrahamDumpleton/wrapt/blob/develop/blog/README.md +RedirectTemp /9-6 https://github.com/GrahamDumpleton/wrapt/blob/develop/blog/01-how-you-implemented-your-python-decorator-is-wrong.md +RedirectTemp /9-7 https://wrapt.readthedocs.io/en/latest/ +RedirectTemp /9-8 https://www.oreilly.com/library/view/python-cookbook-3rd/9781449357337/ch09.html +RedirectTemp /9-9 https://pypi.org/project/decorator/ +RedirectTemp /9-10 https://wiki.python.org/moin/PythonDecoratorLibrary +RedirectTemp /9-11 http://web.archive.org/web/20201109032203/http://effbot.org/zone/closure.htm +RedirectTemp /9-12 https://www.python.org/dev/peps/pep-3104/ +RedirectTemp /9-13 https://www.python.org/dev/peps/pep-0227/ +RedirectTemp /9-14 https://www.python.org/dev/peps/pep-0443/ +RedirectTemp /9-15 https://www.artima.com/weblogs/viewpost.jsp?thread=101605 +RedirectTemp /9-16 https://reg.readthedocs.io/en/latest/ +RedirectTemp /9-17 https://morepath.readthedocs.io/en/latest/ +RedirectTemp /9-18 https://www.gnu.org/software/emacs/manual/html_node/elisp/Dynamic-Binding.html +RedirectTemp /9-19 http://www.paulgraham.com/rootsoflisp.html +RedirectTemp /9-20 http://www-formal.stanford.edu/jmc/recursive/recursive.html +RedirectTemp /9-21 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this +############################################################ 10 +RedirectTemp /10-1 https://en.wikipedia.org/wiki/Software_design_pattern +RedirectTemp /10-2 https://en.wikipedia.org/wiki/Iterator_pattern +RedirectTemp /10-3 https://github.com/python/mypy/issues/9397 +RedirectTemp /10-4 http://www.norvig.com/design-patterns/index.htm +RedirectTemp /10-5 https://pyvideo.org/video/1110/python-design-patterns/ +RedirectTemp /10-6 http://www.aleax.it/gdd_pydp.pdf +RedirectTemp /10-7 https://perl.plover.com/yak/design/ +RedirectTemp /10-8 https://en.wikipedia.org/wiki/Turtles_all_the_way_down +############################################################ 11 +RedirectTemp /11-1 https://blog.startifact.com/posts/older/what-is-pythonic.html +RedirectTemp /11-2 https://julien.danjou.info/guide-python-static-class-abstract-methods/ +RedirectTemp /11-3 https://docs.python.org/3/library/string.html#formatspec +RedirectTemp /11-4 https://docs.python.org/3/reference/lexical_analysis.html#f-strings +RedirectTemp /11-5 https://docs.python.org/3/library/string.html#format-string-syntax +RedirectTemp /11-6 https://docs.python.org/3/library/string.html#formatspec +RedirectTemp /11-7 https://docs.python.org/3/reference/datamodel.html#object.__hash__ +RedirectTemp /11-8 https://web.archive.org/web/20161025185040/http://pythonpaste.org/StyleGuide.html +RedirectTemp /11-9 https://docs.python.org/3/tutorial/modules.html#more-on-modules +RedirectTemp /11-10 https://docs.python.org/3/library/gettext.html#gettext.NullTranslations +RedirectTemp /11-11 https://github.com/fluentpython/example-code-2e/blob/master/11-pythonic-obj/mem_test.py +RedirectTemp /11-12 https://docs.python.org/3/reference/datamodel.html#basic-customization +RedirectTemp /11-13 http://esug.org/data/HistoricalDocuments/TheSmalltalkReport/ST07/04wo.pdf +RedirectTemp /11-14 https://www.artima.com/articles/the-simplest-thing-that-could-possibly-work#part3 +RedirectTemp /11-15 https://docs.oracle.com/javase/tutorial/essential/environment/security.html +RedirectTemp /11-16 https://github.com/fluentpython/example-code-2e/blob/master/11-pythonic-obj/private/Expose.java +RedirectTemp /11-17 https://docs.oracle.com/javase/tutorial/essential/environment/security.html +############################################################ 12 +RedirectTemp /12-1 https://en.wikipedia.org/wiki/Vector_space_model +RedirectTemp /12-2 https://pypi.org/project/gensim/ +RedirectTemp /12-3 https://docs.python.org/3/library/functions.html#enumerate +RedirectTemp /12-4 https://mathworld.wolfram.com/Hypersphere.html +RedirectTemp /12-5 https://en.wikipedia.org/wiki/Fold_(higher-order_function) +RedirectTemp /12-6 https://docs.python.org/2.5/whatsnew/pep-357.html +RedirectTemp /12-7 https://docs.python.org/3/reference/datamodel.html#special-method-names +RedirectTemp /12-8 https://en.wikipedia.org/wiki/KISS_principle +RedirectTemp /12-9 https://mail.python.org/pipermail/python-list/2000-July/046184.html +RedirectTemp /12-10 https://en.wikipedia.org/wiki/Duck_typing +RedirectTemp /12-11 https://mail.python.org/mailman/listinfo/python-list +RedirectTemp /12-12 https://mail.python.org/pipermail/python-list/2003-April/218568.html +############################################################ 13 +RedirectTemp /13-1 https://docs.python.org/3/c-api/index.html +RedirectTemp /13-2 https://docs.python.org/3/c-api/sequence.html +RedirectTemp /13-3 https://github.com/python/cpython/blob/31ceccb2c77854893f3a754aca04bedd74bedb10/Lib/_collections_abc.py#L870 +RedirectTemp /13-4 https://en.wikipedia.org/wiki/Monkey_patch +RedirectTemp /13-5 https://www.gevent.org/api/gevent.monkey.html +RedirectTemp /13-6 https://docs.python.org/3/library/random.html#random.shuffle +RedirectTemp /13-7 https://docs.python.org/3/reference/datamodel.html#emulating-container-types +RedirectTemp /13-8 https://docs.python.org/3/library/collections.html#collections.namedtuple +RedirectTemp /13-9 https://github.com/python/typeshed/blob/24afb531ffd07083d6a74be917342195062f7277/stdlib/collections/__init__.pyi +RedirectTemp /13-10 https://docs.python.org/3/glossary.html#term-abstract-base-class +RedirectTemp /13-11 https://en.wikipedia.org/wiki/Duck_typing#History +RedirectTemp /13-12 http://ptgmedia.pearsoncmg.com/images/020163371x/items/item33.html +RedirectTemp /13-13 https://docs.python.org/3/library/bisect.html#bisect.bisect +RedirectTemp /13-14 https://github.com/python/cpython/blob/main/Lib/_collections_abc.py +RedirectTemp /13-15 https://github.com/python/cpython/blob/main/Lib/abc.py +RedirectTemp /13-16 https://docs.python.org/3/library/collections.abc.html#collections-abstract-base-classes +RedirectTemp /13-17 https://docs.python.org/3/library/collections.abc.html#collections.abc.Iterable +RedirectTemp /13-18 https://docs.python.org/3/library/abc.html +RedirectTemp /13-19 https://docs.python.org/dev/library/abc.html#abc.abstractmethod +RedirectTemp /13-20 https://docs.python.org/dev/library/abc.html +RedirectTemp /13-21 https://docs.python.org/3/library/os.html#os.urandom +RedirectTemp /13-22 https://github.com/python/mypy/issues/2922 +RedirectTemp /13-23 https://docs.python.org/3/library/stdtypes.html#truth +RedirectTemp /13-24 https://github.com/python/cpython/blob/0bbf30e2b910bc9c5899134ae9d73a8df968da35/Lib/_collections_abc.py +RedirectTemp /13-25 https://github.com/python/cpython/blob/0fbddb14dc03f61738af01af88e7d8aa8df07336/Lib/_collections_abc.py#L369 +RedirectTemp /13-26 https://github.com/python/cpython/blob/c0a9afe2ac1820409e6173bd1893ebee2cf50270/Lib/abc.py#L196 +RedirectTemp /13-27 https://bugs.python.org/issue31333 +RedirectTemp /13-28 https://github.com/python/cpython/blob/3635388f52b42e5280229104747962117104c453/Modules/_abc.c#L605 +RedirectTemp /13-29 https://github.com/python/cpython/blob/0fbddb14dc03f61738af01af88e7d8aa8df07336/Lib/_collections_abc.py#L881 +RedirectTemp /13-30 https://docs.python.org/3/library/typing.html#protocols +RedirectTemp /13-31 https://github.com/python/cpython/blob/3635388f52b42e5280229104747962117104c453/Lib/typing.py#L1751 +RedirectTemp /13-32 https://mail.python.org/archives/list/python-dev@python.org/thread/CLVXXPQ2T2LQ5MP2Y53VVQFCXYWQJHKZ/ +RedirectTemp /13-33 https://martinfowler.com/bliki/RoleInterface.html +RedirectTemp /13-34 https://en.wikipedia.org/wiki/Interface_segregation_principle +RedirectTemp /13-35 https://github.com/python/typeshed/blob/master/CONTRIBUTING.md +RedirectTemp /13-36 https://gist.github.com/asukakenji/ac8a05644a2e98f1d5ea8c299541fce9 +RedirectTemp /13-37 https://www.python.org/dev/peps/pep-0544/#runtime-checkable-decorator-and-narrowing-types-by-isinstance +RedirectTemp /13-38 https://www.python.org/dev/peps/pep-0544/#merging-and-extending-protocols +RedirectTemp /13-39 https://numpy.org/devdocs/user/basics.types.html +RedirectTemp /13-40 https://github.com/python/typeshed/blob/master/stdlib/statistics.pyi +RedirectTemp /13-41 https://bugs.python.org/issue41974 +RedirectTemp /13-42 https://glyph.twistedmatrix.com/2020/07/new-duck.html +RedirectTemp /13-43 https://glyph.twistedmatrix.com/2021/03/interfaces-and-protocols.html +RedirectTemp /13-44 https://plone.org/ +RedirectTemp /13-45 https://trypyramid.com/ +RedirectTemp /13-46 https://twistedmatrix.com/trac/ +RedirectTemp /13-47 https://www.artima.com/articles/contracts-in-python +RedirectTemp /13-48 https://martinfowler.com/bliki/DynamicTyping.html +RedirectTemp /13-49 https://martinfowler.com/bliki/RoleInterface.html +RedirectTemp /13-50 https://mypy.readthedocs.io/en/stable/protocols.html +RedirectTemp /13-51 https://pymotw.com/3/abc/index.html +RedirectTemp /13-52 https://www.python.org/dev/peps/pep-3119/ +RedirectTemp /13-53 https://www.python.org/dev/peps/pep-3141/ +RedirectTemp /13-54 https://docs.python.org/3/library/numbers.html +RedirectTemp /13-55 https://github.com/python/mypy/issues/3186 +RedirectTemp /13-56 https://stackoverflow.com/questions/69334475/how-to-hint-at-number-types-i-e-subclasses-of-number-not-numbers-themselv/69383462#69383462 +RedirectTemp /13-57 https://github.com/python/mypy/issues/3186 +RedirectTemp /13-58 https://martinfowler.com/articles/lean-inception/ +RedirectTemp /13-59 https://martinfowler.com +RedirectTemp /13-60 https://www.jetbrains.com/pycharm/ +RedirectTemp /13-61 https://wingware.com/ +RedirectTemp /13-62 https://code.visualstudio.com/ +############################################################ 14 +RedirectTemp /14-1 http://worrydream.com/EarlyHistoryOfSmalltalk/ +RedirectTemp /14-2 https://docs.python.org/3/tutorial/classes.html +RedirectTemp /14-3 https://docs.python.org/3/library/collections.html#ordereddict-examples-and-recipes +RedirectTemp /14-4 https://discuss.python.org/t/is-it-time-to-deprecate-unbound-super-methods/1833 +RedirectTemp /14-5 https://doc.pypy.org/en/latest/cpython_differences.html#subclasses-of-built-in-types +RedirectTemp /14-6 https://docs.python.org/3/library/collections.html +RedirectTemp /14-7 https://github.com/fluentpython/example-code-2e/blob/master/14-inheritance/strkeydict_dictsub.py +RedirectTemp /14-8 https://doc.pypy.org/en/latest/cpython_differences.html#subclasses-of-built-in-types +RedirectTemp /14-9 https://en.wikipedia.org/wiki/Breadth-first_search +RedirectTemp /14-10 https://www.python.org/download/releases/2.3/mro/ +RedirectTemp /14-11 https://github.com/fluentpython/example-code-2e/blob/master/14-inheritance/uppermixin.py +RedirectTemp /14-12 https://docs.oracle.com/javase/tutorial/java/IandI/defaultmethods.html +RedirectTemp /14-13 https://docs.python.org/3/library/collections.abc.html +RedirectTemp /14-14 https://github.com/python/cpython/blob/8ece98a7e418c3c68a4c61bc47a2d0931b59a889/Lib/collections/__init__.py#L1084 +RedirectTemp /14-15 https://docs.python.org/3/library/http.server.html +RedirectTemp /14-16 https://github.com/python/cpython/blob/17c23167942498296f0bdfffe52e72d53d66d693/Lib/http/server.py#L144 +RedirectTemp /14-17 https://github.com/python/cpython/blob/699ee016af5736ffc80f68359617611a22b72943/Lib/socketserver.py#L664 +RedirectTemp /14-18 https://docs.python.org/3/library/socketserver.html#socketserver.ForkingMixIn +RedirectTemp /14-19 https://docs.python.org/3/library/os.html#os.fork +RedirectTemp /14-20 https://en.wikipedia.org/wiki/POSIX +RedirectTemp /14-21 http://ccbv.co.uk/ +RedirectTemp /14-22 https://github.com/django/django/tree/main/django/views/generic +RedirectTemp /14-23 https://en.wikipedia.org/wiki/Template_method_pattern +RedirectTemp /14-24 https://docs.python.org/3/library/tkinter.html +RedirectTemp /14-25 https://docs.python.org/3/library/tkinter.ttk.html +RedirectTemp /14-26 https://docs.oracle.com/javase/10/docs/api/java/awt/package-tree.html +RedirectTemp /14-27 https://docs.oracle.com/javase/10/docs/api/javax/swing/package-tree.html +RedirectTemp /14-28 https://squeak.org/ +RedirectTemp /14-29 https://github.com/django/django/blob/b64db05b9cedd96905d637a2d824cbbf428e40e7/django/views/generic/list.py#L194 +RedirectTemp /14-30 https://github.com/python/cpython/blob/8ed183391241f0c73e7ba7f42b1d49fc02985f7b/Lib/tkinter/__init__.py#L2618 +RedirectTemp /14-31 https://docs.python.org/3/library/socketserver.html +RedirectTemp /14-32 https://docs.python.org/3/library/socketserver.html#socketserver.BaseServer +RedirectTemp /14-33 https://github.com/python/cpython/blob/699ee016af5736ffc80f68359617611a22b72943/Lib/socketserver.py#L153 +RedirectTemp /14-34 https://docs.python.org/3/library/typing.html#typing.final +RedirectTemp /14-35 https://docs.python.org/3/library/typing.html#typing.Final +RedirectTemp /14-36 https://docs.python.org/3/library/collections.abc.html +RedirectTemp /14-37 https://hynek.me/articles/python-subclassing-redux/ +RedirectTemp /14-38 https://www.oreilly.com/library/view/python-cookbook-3rd/9781449357337/ch08.html#super +RedirectTemp /14-39 https://rhettinger.wordpress.com/2011/05/26/super-considered-super/ +RedirectTemp /14-40 https://fuhm.net/super-harmful/ +RedirectTemp /14-41 https://stackoverflow.com/questions/30190185/how-to-use-super-with-one-argument/30190341#30190341 +RedirectTemp /14-42 https://www.artima.com/weblogs/viewpost.jsp?thread=246488 +RedirectTemp /14-43 https://www.artima.com/weblogs/viewpost.jsp?thread=281127 +RedirectTemp /14-44 https://www.artima.com/weblogs/viewpost.jsp?thread=246341 +RedirectTemp /14-45 https://www.artima.com/weblogs/viewpost.jsp?thread=246483 +RedirectTemp /14-46 https://www.artima.com/weblogs/viewpost.jsp?thread=236275 +RedirectTemp /14-47 https://www.artima.com/weblogs/viewpost.jsp?thread=236278 +RedirectTemp /14-48 https://www.artima.com/weblogs/viewpost.jsp?thread=237121 +RedirectTemp /14-49 https://python-patterns.guide/gang-of-four/composition-over-inheritance/ +RedirectTemp /14-50 https://python-patterns.guide/ +RedirectTemp /14-51 https://www.youtube.com/watch?v=3MNVP9-hglc +RedirectTemp /14-52 http://worrydream.com/EarlyHistoryOfSmalltalk/ +RedirectTemp /14-53 https://en.wikipedia.org/wiki/Polymorphism_(computer_science) +############################################################ 15 +RedirectTemp /15-1 https://www.youtube.com/watch?v=csL8DLXGNlU&t=92m5s +RedirectTemp /15-2 https://github.com/python/typeshed/blob/a8834fcd46339e17fc8add82b5803a1ce53d3d60/stdlib/2and3/builtins.pyi#L1434 +RedirectTemp /15-3 https://github.com/python/typeshed/blob/a8834fcd46339e17fc8add82b5803a1ce53d3d60/stdlib/2and3/builtins.pyi +RedirectTemp /15-4 https://twitter.com/gwidion/status/1265384692464967680 +RedirectTemp /15-5 https://pypi.org/project/pydantic/ +RedirectTemp /15-6 https://google.github.io/pytype/faq.html +RedirectTemp /15-7 https://google.github.io/pytype/faq.html +RedirectTemp /15-8 https://lxml.de/ +RedirectTemp /15-9 https://docs.python.org/3/library/xml.etree.elementtree.html +RedirectTemp /15-10 https://mypy.readthedocs.io/en/stable/common_issues.html +RedirectTemp /15-11 https://mypy.readthedocs.io/en/stable/common_issues.html#types-of-empty-collections +RedirectTemp /15-12 https://github.com/python/typing/issues/182 +RedirectTemp /15-13 https://pypi.org/project/pydantic/ +RedirectTemp /15-14 https://mypy.readthedocs.io/en/stable/type_narrowing.html#casts +RedirectTemp /15-15 https://github.com/python/cpython/blob/bee66d3cb98e740f9d8057eb7f503122052ca5d8/Lib/typing.py#L1340 +RedirectTemp /15-16 https://www.python.org/dev/peps/pep-0484/#casts +RedirectTemp /15-17 https://github.com/python/typeshed/issues/5535 +RedirectTemp /15-18 https://docs.python.org/3/library/asyncio-stream.html#tcp-echo-server-using-streams +RedirectTemp /15-19 https://github.com/python/cpython/blob/b798ab06937f8bb24b444a49dd42e11fff15e654/Lib/test/test_asyncio/test_server.py#L55 +RedirectTemp /15-20 https://en.wikipedia.org/wiki/Code_smell +RedirectTemp /15-21 https://mail.python.org/archives/list/typing-sig@python.org/message/5LCWMN2UY2UQNLC5Z47GHBZKSPZW4I63/ +RedirectTemp /15-22 https://mypy.readthedocs.io/en/stable/error_codes.html#error-codes +RedirectTemp /15-23 https://github.com/fluentpython/example-code-2e/blob/master/15-more-types/clip_annot.py +RedirectTemp /15-24 https://docs.python.org/3/library/typing.html#introspection-helpers +RedirectTemp /15-25 https://docs.python.org/3.10/library/inspect.html#inspect.get_annotations +RedirectTemp /15-26 https://www.python.org/dev/peps/pep-0563/#abstract +RedirectTemp /15-27 https://mail.python.org/archives/list/python-dev@python.org/message/ZBJ7MD6CSGM6LZAOTET7GXAVBZB7O77O/ +RedirectTemp /15-28 https://docs.python.org/3.10/howto/annotations.html +RedirectTemp /15-29 https://docs.python.org/3/library/typing.html#user-defined-generic-types +RedirectTemp /15-30 https://github.com/python/typeshed/blob/bfc83c365a0b26ab16586beac77ff16729d0e473/stdlib/builtins.pyi#L743 +RedirectTemp /15-31 https://docs.python.org/3.10/library/typing.html#typing.FrozenSet +RedirectTemp /15-32 https://docs.python.org/3.10/library/typing.html#typing.Generator +RedirectTemp /15-33 https://docs.python.org/3.10/library/typing.html#typing.AsyncGenerator +RedirectTemp /15-34 https://github.com/python/cpython/blob/46b16d0bdbb1722daed10389e27226a2370f1635/Lib/typing.py#L1786 +RedirectTemp /15-35 https://github.com/python/typeshed/blob/2a9f081abbf01134e4e04ced6a750107db904d70/stdlib/builtins.pyi#L239 +RedirectTemp /15-36 https://www.oreilly.com/library/view/robust-python/9781098100650/ +RedirectTemp /15-37 https://www.python.org/dev/peps/pep-0484/#covariance-and-contravariance +RedirectTemp /15-38 https://mypy.readthedocs.io/en/stable/generics.html#variance-of-generic-types +RedirectTemp /15-39 https://mypy.readthedocs.io/en/stable/common_issues.html#variance +RedirectTemp /15-40 https://www.artima.com/weblogs/viewpost.jsp?thread=85551 +RedirectTemp /15-41 https://dl.acm.org/action/cookieAbsent +RedirectTemp /15-42 http://bracha.org/pluggableTypesPosition.pdf +RedirectTemp /15-43 https://www.researchgate.net/publication/213886116_Static_Typing_Where_Possible_Dynamic_Typing_When_Needed_The_End_of_the_Cold_War_Between_Programming_Languages +RedirectTemp /15-44 https://www.atomickotlin.com/atomickotlin/ +RedirectTemp /15-45 https://www.informit.com/store/effective-java-9780134685991 +RedirectTemp /15-46 https://www.manning.com/books/programming-with-types +RedirectTemp /15-47 https://www.oreilly.com/library/view/programming-typescript/9781492037644/ +RedirectTemp /15-48 https://www.informit.com/store/dart-programming-language-9780321927705 +RedirectTemp /15-49 https://www.yodaiken.com/2017/09/15/bad-ideas-in-type-theory/ +RedirectTemp /15-50 https://www.yodaiken.com/2017/11/30/types-considered-harmful-ii/ +RedirectTemp /15-51 https://web.archive.org/web/20071010002142/http://weblogs.java.net/blog/arnold/archive/2005/06/generics_consid_1.html +RedirectTemp /15-52 https://github.com/python/cpython/blob/3e7ee02327db13e4337374597cdc4458ecb9e3ad/Lib/asyncio/trsock.py#L5 +RedirectTemp /15-53 https://www.python.org/dev/peps/pep-0484/#covariance-and-contravariance +############################################################ 16 +RedirectTemp /16-1 http://www.gotw.ca/publications/c_family_interview.htm +RedirectTemp /16-2 https://docs.python.org/3/reference/expressions.html#unary-arithmetic-and-bitwise-operations +RedirectTemp /16-3 https://docs.python.org/3/reference/datamodel.html#object.__neg__ +RedirectTemp /16-4 https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#boolean-indexing +RedirectTemp /16-5 https://docs.python.org/3/library/collections.html#collections.Counter +RedirectTemp /16-6 https://docs.python.org/3/reference/datamodel.html#emulating-container-types +RedirectTemp /16-7 https://docs.python.org/3/library/numbers.html#implementing-the-arithmetic-operations +RedirectTemp /16-8 https://www.fluentpython.com/lingo/#fail-fast +RedirectTemp /16-9 https://github.com/python/cpython/blob/0bbf30e2b910bc9c5899134ae9d73a8df968da35/Objects/typeobject.c#L4598 +RedirectTemp /16-10 https://neopythonic.blogspot.com/2019/03/why-operators-are-useful.html +RedirectTemp /16-11 https://treyhunner.com/2019/03/python-deep-comparisons-and-code-readability/ +RedirectTemp /16-12 https://docs.python.org/3/library/numbers.html#implementing-the-arithmetic-operations +RedirectTemp /16-13 https://docs.python.org/3/library/pathlib.html +RedirectTemp /16-14 https://pypi.org/project/scapy/ +RedirectTemp /16-15 https://scapy.readthedocs.io/en/latest/usage.html#stacking-layers +RedirectTemp /16-16 https://docs.python.org/3/library/functools.html#functools.total_ordering +RedirectTemp /16-17 https://wiki.illinois.edu//wiki/download/attachments/273416327/ingalls.pdf +RedirectTemp /16-18 https://wiki.illinois.edu//wiki/download/attachments/273416327/double-dispatch.pdf +RedirectTemp /16-19 http://www.gotw.ca/publications/c_family_interview.htm +RedirectTemp /16-20 http://www.gotw.ca/publications/c_family_interview.htm +RedirectTemp /16-21 https://doc.rust-lang.org/std/ops/index.html +RedirectTemp /16-22 https://www.fluentpython.com/lingo/#lazy +############################################################ 17 +RedirectTemp /17-1 http://www.paulgraham.com/icad.html +RedirectTemp /17-2 https://en.wikipedia.org/wiki/Sentinel_value +RedirectTemp /17-3 https://docs.python.org/3.10/library/functions.html#iter +RedirectTemp /17-4 https://docs.python.org/3.10/library/functions.html#iter +RedirectTemp /17-5 https://github.com/python/cpython/blob/b1930bf75f276cd7ca08c4455298128d89adf7d1/Lib/_collections_abc.py#L271 +RedirectTemp /17-6 https://github.com/python/cpython/blob/main/Lib/types.py#L6 +RedirectTemp /17-7 https://en.wikipedia.org/wiki/CLU_(programming_language) +RedirectTemp /17-8 https://docs.python.org/3/glossary.html +RedirectTemp /17-9 https://docs.python.org/3/glossary.html#term-generator-iterator +RedirectTemp /17-10 https://docs.python.org/3/glossary.html#term-generator-expression +RedirectTemp /17-11 https://marc.info/?l=python-list&m=141826925106951&w=2 +RedirectTemp /17-12 https://docs.python.org/3/library/os.html#os.walk +RedirectTemp /17-13 https://docs.python.org/3/library/itertools.html +RedirectTemp /17-14 https://docs.python.org/3/library/exceptions.html#exception-hierarchy +RedirectTemp /17-15 https://en.wikipedia.org/wiki/Depth-first_search +RedirectTemp /17-16 https://docs.python.org/3.10/library/typing.html#typing.TypeAlias +RedirectTemp /17-17 https://docs.python.org/3/library/typing.html#typing.Generator +RedirectTemp /17-18 http://www.dabeaz.com/coroutines/Coroutines.pdf +RedirectTemp /17-19 http://www.dabeaz.com/coroutines/Coroutines.pdf +RedirectTemp /17-20 https://mail.python.org/pipermail/python-ideas/2009-April/003841.html +RedirectTemp /17-21 https://mail.python.org/pipermail/python-ideas/2009-April/003912.html +RedirectTemp /17-22 https://docs.python.org/3/library/exceptions.html#StopIteration +RedirectTemp /17-23 https://docs.python.org/3/reference/expressions.html#yield-expressions +RedirectTemp /17-24 https://docs.python.org/3/reference/index.html +RedirectTemp /17-25 https://github.com/python/cpython/blob/6f743e7a4da904f61dfa84cc7d7385e4dcc79ac5/Lib/typing.py#L2060 +RedirectTemp /17-26 http://catb.org/~esr/jargon/html/G/grok.html +RedirectTemp /17-27 https://docs.python.org/3/reference/expressions.html#yieldexpr +RedirectTemp /17-28 https://docs.python.org/3/library/itertools.html +RedirectTemp /17-29 https://docs.python.org/3/library/itertools.html#itertools-recipes +RedirectTemp /17-30 https://more-itertools.readthedocs.io/en/stable/index.html +RedirectTemp /17-31 https://rittau.org/2006/11/java-iterators-are-not-iterable/ +RedirectTemp /17-32 https://docs.python.org/3/whatsnew/3.3.html#pep-380-syntax-for-delegating-to-a-subgenerator +RedirectTemp /17-33 http://www.dabeaz.com/generators/ +RedirectTemp /17-34 http://www.dabeaz.com/coroutines/ +RedirectTemp /17-35 https://archive.org/details/pyvideo_213___pycon-2009-a-curious-course-on-coroutines-and-concurrency-part-1-of-3 +RedirectTemp /17-36 https://archive.org/details/pyvideo_215___pycon-2009-a-curious-course-on-coroutines-and-concurrency-part-2-of-3 +RedirectTemp /17-37 https://archive.org/details/pyvideo_214___pycon-2009-a-curious-course-on-coroutines-and-concurrency-part-3-of-3 +RedirectTemp /17-38 http://www.dabeaz.com/finalgenerator/ +RedirectTemp /17-39 https://web.archive.org/web/20200218150637/http://seriously.dontusethiscode.com/2013/05/01/greedy-coroutine.html +RedirectTemp /17-40 https://effectivepython.com/ +RedirectTemp /17-41 https://effectivepython.com/2015/03/10/consider-coroutines-to-run-many-functions-concurrently +RedirectTemp /17-42 https://en.wikipedia.org/wiki/Conway's_Game_of_Life +RedirectTemp /17-43 https://gist.github.com/ramalho/da5590bc38c973408839 +RedirectTemp /17-44 https://gist.github.com/ramalho/da5590bc38c973408839 +RedirectTemp /17-45 https://journal.code4lib.org/articles/4893 +RedirectTemp /17-46 https://github.com/fluentpython/isis2json +RedirectTemp /17-47 https://github.com/fluentpython/isis2json/blob/master/README.rst +############################################################ 18 +RedirectTemp /18-1 https://pyvideo.org/video/1669/keynote-3/ +RedirectTemp /18-2 https://docs.python.org/3/library/sqlite3.html#using-the-connection-as-a-context-manager +RedirectTemp /18-3 https://docs.python.org/3/library/threading.html#using-locks-conditions-and-semaphores-in-the-with-statement +RedirectTemp /18-4 https://docs.python.org/3/library/decimal.html#decimal.localcontext +RedirectTemp /18-5 https://docs.python.org/3/library/unittest.mock.html#patch +RedirectTemp /18-6 https://docs.python.org/3/library/contextlib.html#contextlib.redirect_stdout +RedirectTemp /18-7 https://docs.python.org/3/library/sys.html#sys.exc_info +RedirectTemp /18-8 https://en.wikipedia.org/wiki/LL_parser +RedirectTemp /18-9 https://docs.python.org/3/library/contextlib.html +RedirectTemp /18-10 https://github.com/python/cpython/blob/8afab2ebbc1b343cd88d058914cf622fe687a2be/Lib/contextlib.py#L123 +RedirectTemp /18-11 https://www.zopatista.com/python/2013/11/26/inplace-file-rewriting/ +RedirectTemp /18-12 https://docs.python.org/3/library/fileinput.html#fileinput.input +RedirectTemp /18-13 https://www.zopatista.com/python/2013/11/26/inplace-file-rewriting/ +RedirectTemp /18-14 https://en.wikipedia.org/wiki/Euclidean_algorithm +RedirectTemp /18-15 https://github.com/fluentpython/example-code-2e/tree/master/18-with-match/lispy/py3.10/ +RedirectTemp /18-16 https://github.com/fluentpython/example-code-2e/blob/master/18-with-match/lispy/original/lispy.py +RedirectTemp /18-17 https://github.com/fluentpython/example-code-2e/blob/6527037ae7319ba370a1ee2d9fe79214d0ed9452/18-with-match/lispy/py3.10/lis.py#L35 +RedirectTemp /18-18 https://github.com/fluentpython/example-code-2e/blob/master/18-with-match/lispy/py3.10/examples_test.py +RedirectTemp /18-19 https://github.com/python/typeshed/issues/6042 +RedirectTemp /18-20 https://github.com/fluentpython/lispy/tree/main/mylis +RedirectTemp /18-21 https://github.com/fluentpython/example-code-2e/blob/00e4741926e1b771ee7c753148b1415c0bd12e39/02-array-seq/lispy/py3.10/examples_test.py +RedirectTemp /18-22 https://mitpress.mit.edu/sites/default/files/sicp/index.html +RedirectTemp /18-23 https://github.com/fluentpython/example-code-2e/blob/master/18-with-match/lispy/py3.10/examples_test.py +RedirectTemp /18-24 https://github.com/fluentpython/example-code-2e/blob/master/18-with-match/lispy/py3.10/lis.py +RedirectTemp /18-25 https://www.python.org/dev/peps/pep-0634/#or-patterns +RedirectTemp /18-26 https://en.wikipedia.org/wiki/Lambda#Character_encodings +RedirectTemp /18-27 https://docs.python.org/3/reference/compound_stmts.html +RedirectTemp /18-28 https://docs.python.org/3/glossary.html#term-eafp +RedirectTemp /18-29 https://speakerdeck.com/pyconslides/pycon-keynote-python-is-awesome-by-raymond-hettinger?slide=21 +RedirectTemp /18-30 https://docs.python.org/3/reference/compound_stmts.html +RedirectTemp /18-31 https://stackoverflow.com/questions/16138232/is-it-a-good-practice-to-use-try-except-else-in-python +RedirectTemp /18-32 https://docs.python.org/3/library/stdtypes.html#typecontextmanager +RedirectTemp /18-33 https://docs.python.org/3/reference/datamodel.html#with-statement-context-managers +RedirectTemp /18-34 https://speakerdeck.com/pyconslides/pycon-keynote-python-is-awesome-by-raymond-hettinger?slide=21 +RedirectTemp /18-35 https://speakerdeck.com/pyconslides/transforming-code-into-beautiful-idiomatic-python-by-raymond-hettinger-1?slide=34 +RedirectTemp /18-36 https://preshing.com/20110920/the-python-with-statement-by-example/ +RedirectTemp /18-37 https://www.rath.org/on-the-beauty-of-pythons-exitstack.html +RedirectTemp /18-38 https://norvig.com/lispy.html +RedirectTemp /18-39 https://norvig.com/lispy2.html +RedirectTemp /18-40 https://github.com/norvig/pytudes +RedirectTemp /18-41 https://github.com/fluentpython/lispy +RedirectTemp /18-42 https://racket-lang.org/ +RedirectTemp /18-43 https://pyvideo.org/video/1669/keynote-3/ +RedirectTemp /18-44 https://en.wikipedia.org/wiki/Tail_call +RedirectTemp /18-45 https://2ality.com/2015/06/tail-call-optimization.html +RedirectTemp /18-46 https://github.com/fluentpython/example-code-2e/blob/master/18-with-match/lispy/original/lis.py +RedirectTemp /18-47 https://github.com/fluentpython/example-code-2e/blob/master/18-with-match/lispy/original/lispy.py +RedirectTemp /18-48 http://neopythonic.blogspot.com/2009/04/final-words-on-tail-calls.html +RedirectTemp /18-49 https://webkit.org/blog/6240/ecmascript-6-proper-tail-calls-in-webkit/ +RedirectTemp /18-50 http://kangax.github.io/compat-table/es6/ +RedirectTemp /18-51 https://world.hey.com/mgmarlow/what-happened-to-proper-tail-calls-in-javascript-5494c256 +RedirectTemp /18-52 http://neopythonic.blogspot.com/2009/04/tail-recursion-elimination.html +RedirectTemp /18-53 https://github.com/fluentpython/lispy/blob/main/mylis/mylis_2/lis.py +RedirectTemp /18-54 https://github.com/fluentpython/lispy/tree/main/mylis +############################################################ 19 +RedirectTemp /19-1 https://go.dev/blog/waza-talk +RedirectTemp /19-2 https://en.wikipedia.org/wiki/Graphics_processing_unit +RedirectTemp /19-3 https://docs.python.org/3/library/sys.html#sys.getswitchinterval +RedirectTemp /19-4 https://docs.python.org/3/library/sys.html#sys.setswitchinterval +RedirectTemp /19-5 https://en.wikipedia.org/wiki/System_call +RedirectTemp /19-6 https://mail.python.org/pipermail/python-dev/2009-October/093356.html +RedirectTemp /19-7 http://www.dabeaz.com/finalgenerator/ +RedirectTemp /19-8 https://docs.python.org/3/library/threading.html#thread-objects +RedirectTemp /19-9 https://www.pypy.org/ +RedirectTemp /19-10 https://mail.python.org/pipermail/python-list/2009-February/675659.html +RedirectTemp /19-11 https://en.wikipedia.org/wiki/Braille_Patterns +RedirectTemp /19-12 https://docs.python.org/3/library/multiprocessing.shared_memory.html +RedirectTemp /19-13 https://docs.python.org/3/library/multiprocessing.shared_memory.html#multiprocessing.shared_memory.ShareableList +RedirectTemp /19-14 https://greenlet.readthedocs.io/en/latest/ +RedirectTemp /19-15 https://docs.sqlalchemy.org/en/14/changelog/migration_14.html#asynchronous-io-support-for-core-and-orm +RedirectTemp /19-16 https://docs.sqlalchemy.org/en/14/orm/extensions/asyncio.html +RedirectTemp /19-17 http://www.gevent.org/ +RedirectTemp /19-18 https://github.com/gevent/gevent/wiki/Projects +RedirectTemp /19-19 https://docs.python.org/3/library/concurrent.futures.html#processpoolexecutor-example +RedirectTemp /19-20 https://github.com/python/asyncio/issues/284 +RedirectTemp /19-21 https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.loop.run_in_executor +RedirectTemp /19-22 https://mail.python.org/archives/list/python-dev@python.org/message/JBYXQH3NV3YBF7P2HLHB5CD6V3GVTY55/ +RedirectTemp /19-23 https://docs.python.org/3/library/queue.html#queue.SimpleQueue.get +RedirectTemp /19-24 https://en.wikipedia.org/wiki/Race_condition +RedirectTemp /19-25 https://github.com/fluentpython/example-code-2e/commit/2c1230579db99738a5e5e6802063bda585f6476d +RedirectTemp /19-26 https://github.com/fluentpython/example-code-2e/blob/master/19-concurrency/primes/README.md +RedirectTemp /19-27 https://github.com/fluentpython/example-code-2e/blob/master/19-concurrency/primes/threads.py +RedirectTemp /19-28 https://en.wikipedia.org/wiki/Context_switch +RedirectTemp /19-29 http://www.gotw.ca/publications/concurrency-ddj.htm +RedirectTemp /19-30 https://www.ansible.com/ +RedirectTemp /19-31 https://saltproject.io/ +RedirectTemp /19-32 https://www.fabfile.org/ +RedirectTemp /19-33 https://engineering.fb.com/2016/05/27/production-engineering/python-in-production-engineering/ +RedirectTemp /19-34 https://jupyter.org/ +RedirectTemp /19-35 https://docs.bokeh.org/en/latest/index.html +RedirectTemp /19-36 https://www.tensorflow.org/ +RedirectTemp /19-37 https://pytorch.org/ +RedirectTemp /19-38 https://www.oreilly.com/radar/where-programming-ops-ai-and-the-cloud-are-headed-in-2021/ +RedirectTemp /19-39 https://www.youtube.com/watch?v=ods97a5Pzw0 +RedirectTemp /19-40 https://www.thoughtworks.com/radar/techniques/high-performance-envy-web-scale-envy +RedirectTemp /19-41 https://modwsgi.readthedocs.io/en/master/ +RedirectTemp /19-42 https://uwsgi-docs.readthedocs.io/en/latest/ +RedirectTemp /19-43 https://unit.nginx.org/ +RedirectTemp /19-44 https://www.techatbloomberg.com/blog/configuring-uwsgi-production-deployment/ +RedirectTemp /19-45 https://www.youtube.com/watch?v=p6R1h2Nn468 +RedirectTemp /19-46 https://asgi.readthedocs.io/en/latest/index.html +RedirectTemp /19-47 https://docs.celeryproject.org/en/stable/getting-started/introduction.html +RedirectTemp /19-48 https://python-rq.org/ +RedirectTemp /19-49 https://docs.celeryproject.org/en/stable/faq.html#what-kinds-of-things-should-i-use-celery-for +RedirectTemp /19-50 https://redis.io/ +RedirectTemp /19-51 https://realpython.com/intro-to-python-threading/ +RedirectTemp /19-52 https://pymotw.com/3/concurrency.html +RedirectTemp /19-53 https://www.pearson.com/us/higher-education/program/Hellmann-Python-3-Standard-Library-by-Example-The/PGM328871.html +RedirectTemp /19-54 https://docs.python.org/3/library/multiprocessing.html#programming-guidelines +RedirectTemp /19-55 https://docs.python.org/3/library/multiprocessing.html +RedirectTemp /19-56 https://www.oreilly.com/library/view/high-performance-python/9781492055013/ +RedirectTemp /19-57 https://link.springer.com/book/10.1007/978-1-4842-5793-7?error=cookies_not_supported&code=2ed5d61d-ae9f-4f3d-94ac-0f68cf45ea4f +RedirectTemp /19-58 https://www.packtpub.com/product/parallel-programming-with-python/9781783288397 +RedirectTemp /19-59 https://greenteapress.com/wp/semaphores/ +RedirectTemp /19-60 https://docs.python.org/3/c-api/init.html#thread-state-and-the-global-interpreter-lock +RedirectTemp /19-61 https://docs.python.org/3/faq/library.html#can-t-we-get-rid-of-the-global-interpreter-lock +RedirectTemp /19-62 https://www.artima.com/weblogs/viewpost.jsp?thread=214235 +RedirectTemp /19-63 http://jessenoller.com/blog/2009/02/01/python-threads-and-the-global-interpreter-lock +RedirectTemp /19-64 https://realpython.com/products/cpython-internals-book/ +RedirectTemp /19-65 http://www.dabeaz.com/GIL/ +RedirectTemp /19-66 http://www.dabeaz.com/python/UnderstandingGIL.pdf +RedirectTemp /19-67 https://bugs.python.org/issue7946#msg223110 +RedirectTemp /19-68 https://bugs.python.org/issue7946 +RedirectTemp /19-69 https://www.fullstackpython.com/ +RedirectTemp /19-70 https://www.oreilly.com/library/view/high-performance-python/9781492055013/ +RedirectTemp /19-71 https://www.packtpub.com/product/parallel-programming-with-python/9781783288397 +RedirectTemp /19-72 https://www.packtpub.com/product/distributed-computing-with-python/9781785889691 +RedirectTemp /19-73 https://towardsdatascience.com/python-performance-and-gpus-1be860ffd58d?gi=a7d537cc2fb4 +RedirectTemp /19-74 https://instagram-engineering.com/web-service-efficiency-at-instagram-with-python-4976d078e366?gi=12a441991c88 +RedirectTemp /19-75 https://www.oreilly.com/library/view/architecture-patterns-with/9781492052197/ +RedirectTemp /19-76 https://www.cosmicpython.com/ +RedirectTemp /19-77 https://pypi.org/project/lelo/ +RedirectTemp /19-78 https://github.com/npryce/python-parallelize +RedirectTemp /19-79 https://github.com/ericsnowcurrently/multi-core-python/wiki +RedirectTemp /19-80 https://gist.github.com/markshannon/79cace3656b40e21b7021504daee950c +RedirectTemp /19-81 https://mail.python.org/archives/list/python-dev@python.org/message/YOOQZCFOKEPQ24YHWWLQSJ3RCXFMS7D7/ +RedirectTemp /19-82 https://en.wikipedia.org/wiki/Communicating_sequential_processes +RedirectTemp /19-83 https://github.com/stackless-dev/stackless/wiki +RedirectTemp /19-84 https://www.eveonline.com +RedirectTemp /19-85 https://www.ccpgames.com/ +RedirectTemp /19-86 https://stackless.readthedocs.io/en/3.6-slp/stackless-python.html#history +RedirectTemp /19-87 https://doc.pypy.org/en/latest/stackless.html +RedirectTemp /19-88 https://greenlet.readthedocs.io/en/latest/ +RedirectTemp /19-89 http://www.gevent.org/ +RedirectTemp /19-90 http://thespianpy.com/doc/ +RedirectTemp /19-91 https://pykka.readthedocs.io/en/latest/ +RedirectTemp /19-92 https://www.manning.com/books/rabbitmq-in-action +RedirectTemp /19-93 https://pragprog.com/titles/pb7con/seven-concurrency-models-in-seven-weeks/ +RedirectTemp /19-94 https://en.wikipedia.org/wiki/OpenCL +RedirectTemp /19-95 https://media.pragprog.com/titles/pb7con/Bonus_Chapter.pdf +RedirectTemp /19-96 https://martinfowler.com/ +RedirectTemp /19-97 https://martinfowler.com/articles/patterns-of-distributed-systems/ +RedirectTemp /19-98 https://www.oreilly.com/library/view/designing-data-intensive-applications/9781491903063/ +RedirectTemp /19-99 https://www.oreilly.com/library/view/oscon-2016-video/9781491965153/video247021.html +RedirectTemp /19-100 https://www.oreilly.com/library/view/designing-for-scalability/9781449361556/ +RedirectTemp /19-101 https://www.thoughtworks.com/radar/techniques/high-performance-envy-web-scale-envy +RedirectTemp /19-102 https://en.wikipedia.org/wiki/KISS_principle +RedirectTemp /19-103 https://www.usenix.org/conference/hotos15/workshop-program/presentation/mcsherry +############################################################ 20 +RedirectTemp /20-1 https://www.artima.com/weblogs/viewpost.jsp?thread=299551 +RedirectTemp /20-2 https://docs.python.org/3/library/http.server.html +RedirectTemp /20-3 https://www.youtube.com/watch?v=A9e9Cy1UkME +RedirectTemp /20-4 https://www.cia.gov/the-world-factbook/ +RedirectTemp /20-5 https://docs.python-requests.org/en/latest/ +RedirectTemp /20-6 https://docs.python.org/3.10/library/concurrent.futures.html#concurrent.futures.ThreadPoolExecutor +RedirectTemp /20-7 https://docs.python.org/3/library/concurrent.futures.html#concurrent.futures.as_completed +RedirectTemp /20-8 https://docs.python.org/3/library/concurrent.futures.html +RedirectTemp /20-9 https://docs.python.org/3.10/library/concurrent.futures.html#concurrent.futures.Executor +RedirectTemp /20-10 https://github.com/fluentpython/example-code-2e/blob/master/20-executors/getflags/flags2_common.py +RedirectTemp /20-11 https://github.com/noamraph/tqdm +RedirectTemp /20-12 https://www.youtube.com/watch?v=M8Z65tAl5l4 +RedirectTemp /20-13 https://github.com/noamraph/tqdm/blob/master/README.md +RedirectTemp /20-14 https://docs.python.org/3/library/concurrent.futures.html#concurrent.futures.as_completed +RedirectTemp /20-15 https://docs.python.org/3/library/asyncio-task.html#asyncio.as_completed +RedirectTemp /20-16 https://www.cloudflare.com/ +RedirectTemp /20-17 https://github.com/fluentpython/example-code-2e/tree/master/20-executors/getflags +RedirectTemp /20-18 https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/418 +RedirectTemp /20-19 https://en.wikipedia.org/wiki/Embarrassingly_parallel +RedirectTemp /20-20 https://pyvideo.org/video/480/pyconau-2010--the-future-is-soon/ +RedirectTemp /20-21 http://www.dabeaz.com/coroutines/ +RedirectTemp /20-22 https://en.wikipedia.org/wiki/POSIX_Threads +RedirectTemp /20-23 https://en.wikipedia.org/wiki/C_dynamic_memory_allocation +RedirectTemp /20-24 https://pragprog.com/titles/pb7con/seven-concurrency-models-in-seven-weeks/ +RedirectTemp /20-25 https://hexdocs.pm/ecto/getting-started.html +############################################################ 21 +RedirectTemp /21-1 https://docs.python.org/3/library/asyncio.html +RedirectTemp /21-2 https://bugs.python.org/issue43216 +RedirectTemp /21-3 https://bugs.python.org/issue36921 +RedirectTemp /21-4 https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.loop.getaddrinfo +RedirectTemp /21-5 https://docs.python.org/3/library/socket.html#socket.getaddrinfo +RedirectTemp /21-6 https://docs.python.org/3.10/library/asyncio-eventloop.html#asyncio.get_event_loop +RedirectTemp /21-7 https://www.python.org/dev/peps/pep-0492/#await-expression +RedirectTemp /21-8 https://www.fluentpython.com/extra/classic-coroutines/#yield_from_meaning_sec +RedirectTemp /21-9 https://github.com/fluentpython/example-code-2e/tree/master/20-executors/getflags +RedirectTemp /21-10 https://magicstack.github.io/asyncpg/current/ +RedirectTemp /21-11 https://magicstack.github.io/asyncpg/current/api/index.html#transactions +RedirectTemp /21-12 https://magicstack.github.io/asyncpg/current/api/index.html#transactions +RedirectTemp /21-13 https://github.com/MagicStack/asyncpg/blob/4d39a05268ce4cc01b00458223a767542da048b8/asyncpg/transaction.py#L57 +RedirectTemp /21-14 https://magicstack.github.io/asyncpg/current/usage.html#connection-pools +RedirectTemp /21-15 https://gist.github.com/jboner/2841832 +RedirectTemp /21-16 https://en.wikipedia.org/wiki/Network-attached_storage +RedirectTemp /21-17 https://en.wikipedia.org/wiki/Semaphore_(programming) +RedirectTemp /21-18 https://en.wikipedia.org/wiki/Semaphore_(programming) +RedirectTemp /21-19 https://groups.google.com/forum/#!msg/python-tulip/PdAEtwpaJHs/7fqb-Qj2zJoJ +RedirectTemp /21-20 https://web.archive.org/web/20151209151711/http://tritarget.org/blog/2012/11/28/the-pyramid-of-doom-a-javascript-style-trap +RedirectTemp /21-21 https://stackoverflow.com/questions/53701841/what-is-the-use-case-for-future-add-done-callback/53710563 +RedirectTemp /21-22 https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.loop.run_in_executor +RedirectTemp /21-23 https://motor.readthedocs.io/en/stable/ +RedirectTemp /21-24 https://emptysqua.re/blog/response-to-asynchronous-python-and-databases/ +RedirectTemp /21-25 https://docs.python.org/3/library/asyncio-stream.html#tcp-echo-server-using-streams +RedirectTemp /21-26 https://en.wikipedia.org/wiki/Phaistos_Disc +RedirectTemp /21-27 https://en.wikipedia.org/wiki/Inverted_index +RedirectTemp /21-28 https://fastapi.tiangolo.com/ +RedirectTemp /21-29 https://swagger.io/specification/ +RedirectTemp /21-30 https://asgi.readthedocs.io/en/latest/implementations.html +RedirectTemp /21-31 https://pydantic-docs.helpmanual.io/ +RedirectTemp /21-32 https://doc.traefik.io/traefik/ +RedirectTemp /21-33 https://fastapi.tiangolo.com/project-generation/ +RedirectTemp /21-34 https://fastapi.tiangolo.com/tutorial/response-model/ +RedirectTemp /21-35 https://docs.python.org/3/library/asyncio-stream.html#asyncio.start_server +RedirectTemp /21-36 https://github.com/python/typeshed/issues/5535 +RedirectTemp /21-37 https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.Server.serve_forever +RedirectTemp /21-38 https://docs.python.org/3/library/asyncio-stream.html#asyncio.StreamWriter.close +RedirectTemp /21-39 https://docs.python.org/3/library/asyncio-stream.html#streamwriter +RedirectTemp /21-40 https://docs.python.org/3/library/asyncio-stream.html +RedirectTemp /21-41 https://docs.python.org/3/library/asyncio-protocol.html +RedirectTemp /21-42 https://docs.python.org/3/library/asyncio-protocol.html#tcp-echo-server +RedirectTemp /21-43 https://github.com/aio-libs/aiopg +RedirectTemp /21-44 https://docs.python.org/3/whatsnew/3.8.html#asyncio +RedirectTemp /21-45 https://datatracker.ietf.org/doc/html/rfc6761 +RedirectTemp /21-46 https://docs.python.org/3/library/contextlib.html#contextlib.asynccontextmanager +RedirectTemp /21-47 https://docs.python.org/3/library/contextlib.html#contextlib.asynccontextmanager +RedirectTemp /21-48 https://docs.python.org/3/library/asyncio-task.html#asyncio.gather +RedirectTemp /21-49 https://curio.readthedocs.io/en/latest/index.html +RedirectTemp /21-50 https://curio.readthedocs.io/en/latest/reference.html#task-groups +RedirectTemp /21-51 https://en.wikipedia.org/wiki/Structured_concurrency +RedirectTemp /21-52 https://mail.python.org/archives/list/python-dev@python.org/thread/2ORDAW74LGE3ZI2QETPJRT2ZL7MCCPG2/ +RedirectTemp /21-53 https://www.python.org/dev/peps/pep-0654/#motivation +RedirectTemp /21-54 https://curio.readthedocs.io/en/latest/reference.html#AWAIT +RedirectTemp /21-55 https://www.python-httpx.org/async/#curio +RedirectTemp /21-56 https://github.com/dabeaz/curio/tree/78bca8a6ad677ef51e1568ac7b3e51441ab49c42/examples +RedirectTemp /21-57 https://datatracker.ietf.org/doc/html/rfc8305 +RedirectTemp /21-58 https://trio.readthedocs.io/en/stable/ +RedirectTemp /21-59 https://www.youtube.com/watch?v=M-sc73Y-zQA +RedirectTemp /21-60 https://en.wikipedia.org/wiki/Technical_debt +RedirectTemp /21-61 https://www.youtube.com/watch?v=E-1Y4kSsAFc +RedirectTemp /21-62 https://github.com/dabeaz/curio +RedirectTemp /21-63 https://trio.readthedocs.io/en/stable/ +RedirectTemp /21-64 https://curio.readthedocs.io/en/latest/#curio-university +RedirectTemp /21-65 https://vorpus.org/blog/some-thoughts-on-asynchronous-api-design-in-a-post-asyncawait-world/ +RedirectTemp /21-66 https://vorpus.org/blog/notes-on-structured-concurrency-or-go-statement-considered-harmful/ +RedirectTemp /21-67 https://stackoverflow.com/questions/49482969/what-is-the-core-difference-between-asyncio-and-trio +RedirectTemp /21-68 https://docs.python.org/3/library/asyncio.html +RedirectTemp /21-69 https://bugs.python.org/issue33649 +RedirectTemp /21-70 https://docs.python.org/3/library/asyncio-dev.html +RedirectTemp /21-71 https://www.youtube.com/watch?v=iG6fr81xHKA +RedirectTemp /21-72 https://www.youtube.com/watch?v=F19R_M4Nay4 +RedirectTemp /21-73 https://asherman.io/projects/unsync.html +RedirectTemp /21-74 https://pyladies.com/ +RedirectTemp /21-75 https://www.youtube.com/watch?v=sW76-pRkZk8 +RedirectTemp /21-76 https://www.youtube.com/watch?v=Xbl7XjFYsN4 +RedirectTemp /21-77 https://www.youtube.com/watch?v=02CLD-42VdI +RedirectTemp /21-78 https://micropython.org/ +RedirectTemp /21-79 https://docs.micropython.org/en/latest/library/uasyncio.html +RedirectTemp /21-80 https://www.encode.io/articles/python-async-frameworks-beyond-developer-tribalism +RedirectTemp /21-81 https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/ +RedirectTemp /21-82 https://vorpus.org/blog/notes-on-structured-concurrency-or-go-statement-considered-harmful/ +RedirectTemp /21-83 https://github.com/MagicStack/uvloop +RedirectTemp /21-84 http://magic.io/blog/uvloop-blazing-fast-python-networking/ +RedirectTemp /21-85 https://github.com/MagicStack/httptools +RedirectTemp /21-86 https://docs.aiohttp.org/en/stable/ +RedirectTemp /21-87 https://github.com/wg/wrk +RedirectTemp /21-88 https://twistedmatrix.com/trac/ +############################################################ 22 +RedirectTemp /22-1 https://github.com/fluentpython/example-code-2e/blob/master/22-dyn-attr-prop/oscon/data/osconfeed.json +RedirectTemp /22-2 https://pypi.org/project/attrdict/ +RedirectTemp /22-3 https://pypi.org/project/addict/ +RedirectTemp /22-4 https://github.com/ActiveState/code/tree/master/recipes/Python/52308_simple_but_handy_collector_bunch_named_stuff +RedirectTemp /22-5 https://docs.python.org/3/library/types.html#types.SimpleNamespace +RedirectTemp /22-6 https://docs.python.org/3/library/argparse.html#argparse.Namespace +RedirectTemp /22-7 https://docs.python.org/3/library/multiprocessing.html#multiprocessing.managers.Namespace +RedirectTemp /22-8 https://github.com/fluentpython/example-code-2e/blob/master/22-dyn-attr-prop/oscon/schedule_v2.py +RedirectTemp /22-9 https://docs.python.org/3/library/functools.html#functools.cached_property +RedirectTemp /22-10 https://docs.python.org/3/library/functools.html#functools.cached_property +RedirectTemp /22-11 https://bugs.python.org/issue42781 +RedirectTemp /22-12 https://docs.python.org/3/howto/descriptor.html +RedirectTemp /22-13 https://github.com/python/cpython/blob/e6d0107e13ed957109e79b796984d3d026a8660d/Lib/functools.py#L926 +RedirectTemp /22-14 https://docs.python.org/3/library/threading.html#rlock-objects +RedirectTemp /22-15 https://docs.python.org/3.10/library/functools.html#functools.cached_property +RedirectTemp /22-16 https://www.wsj.com/articles/SB10001424052970203914304576627102996831200 +RedirectTemp /22-17 https://www.youtube.com/watch?v=s35rVw1zskA&feature=youtu.be +RedirectTemp /22-18 https://docs.python.org/3/library/functions.html#dir +RedirectTemp /22-19 https://github.com/python/cpython/blob/19903085c3ad7a17c8047e1556c700f2eb109931/Lib/cmd.py#L214 +RedirectTemp /22-20 https://docs.python.org/3/library/functions.html#hasattr +RedirectTemp /22-21 https://docs.python.org/3.10/reference/datamodel.html#special-method-lookup +RedirectTemp /22-22 https://docs.python.org/3/library/functions.html +RedirectTemp /22-23 https://docs.python.org/3/reference/datamodel.html#customizing-attribute-access +RedirectTemp /22-24 https://docs.python.org/3/reference/datamodel.html#special-method-lookup +RedirectTemp /22-25 https://docs.python.org/3/library/stdtypes.html#special-attributes +RedirectTemp /22-26 http://wiki.c2.com/?WelcomeVisitors +RedirectTemp /22-27 http://wiki.c2.com/?UniformAccessPrinciple +RedirectTemp /22-28 https://docs.oracle.com/javase/tutorial/java/javaOO/accesscontrol.html +RedirectTemp /22-29 http://www.pingo.io/docs/ +RedirectTemp /22-30 https://www.drdobbs.com/javas-new-considered-harmful/184405016 +RedirectTemp /22-31 https://www.python.org/dev/peps/pep-0008/#class-names +############################################################ 23 +RedirectTemp /23-1 http://www.aleax.it/goo_pydp.pdf +RedirectTemp /23-2 https://docs.python.org/3.10/reference/datamodel.html#implementing-descriptors +RedirectTemp /23-3 https://docs.python.org/3/howto/descriptor.html +RedirectTemp /23-4 https://docs.python.org/3/howto/ +RedirectTemp /23-5 http://www.aleax.it/Python/nylug05_om.pdf +RedirectTemp /23-6 https://www.youtube.com/watch?v=VOzvpHoYQoo +RedirectTemp /23-7 https://www.python.org/dev/peps/pep-0487/#trait-descriptors +RedirectTemp /23-8 https://dreamsongs.com/RiseOfWorseIsBetter.html +RedirectTemp /23-9 http://web.archive.org/web/20031002184114/www.amk.ca/python/writing/warts.html +RedirectTemp /23-10 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this +RedirectTemp /23-11 http://python-history.blogspot.com/2009/02/adding-support-for-user-defined-classes.html +############################################################ 24 +RedirectTemp /24-1 https://docs.python.org/3/library/stdtypes.html#special-attributes +RedirectTemp /24-2 https://docs.djangoproject.com/en/3.2/topics/db/models/#meta-options +RedirectTemp /24-3 https://www.python.org/dev/peps/pep-3155/ +RedirectTemp /24-4 https://github.com/python/cpython/blob/3.9/Lib/collections/__init__.py +RedirectTemp /24-5 https://en.wikipedia.org/wiki/Tony_Hoare#Apologies_and_retractions +RedirectTemp /24-6 https://go.dev/tour/basics/12 +RedirectTemp /24-7 https://bugs.python.org/issue42102 +RedirectTemp /24-8 https://docs.python.org/3/reference/datamodel.html#object.__init_subclass__ +RedirectTemp /24-9 https://www.python.org/dev/peps/pep-0557/#abstract +RedirectTemp /24-10 https://github.com/python/cpython/blob/3.9/Lib/dataclasses.py +RedirectTemp /24-11 https://docs.python.org/3/reference/datamodel.html#creating-the-class-object +RedirectTemp /24-12 https://mail.python.org/pipermail/python-list/2002-December/134521.html +RedirectTemp /24-13 https://mail.python.org/pipermail/python-list/2002-July/162558.html +RedirectTemp /24-14 https://github.com/fluentpython/example-code/tree/master/21-class-metaprog/bulkfood +RedirectTemp /24-15 https://en.wikipedia.org/wiki/Principle_of_least_astonishment +RedirectTemp /24-16 https://docs.python.org/3/reference/datamodel.html#object.__class_getitem__ +RedirectTemp /24-17 https://en.wikipedia.org/wiki/Trait_(computer_programming) +RedirectTemp /24-18 https://en.wikipedia.org/wiki/Aspect-oriented_programming +RedirectTemp /24-19 https://dhh.dk/arc/000416.html +RedirectTemp /24-20 https://github.com/cjrh/autoslot +RedirectTemp /24-21 https://docs.python.org/3/reference/datamodel.html#customizing-class-creation +RedirectTemp /24-22 https://docs.python.org/3/library/functions.html#type +RedirectTemp /24-23 https://docs.python.org/3/library/stdtypes.html#special-attributes +RedirectTemp /24-24 https://docs.python.org/3/library/types.html +RedirectTemp /24-25 https://www.python.org/dev/peps/pep-3129/ +RedirectTemp /24-26 https://www.youtube.com/watch?v=cAGliEJV9_o +RedirectTemp /24-27 https://docs.python.org/3/library/functools.html#functools.total_ordering +RedirectTemp /24-28 https://www.python.org/download/releases/2.2.3/descrintro/ +RedirectTemp /24-29 https://github.com/lihaoyi/macropy +RedirectTemp /24-30 https://people.eecs.berkeley.edu/~bh/ss-toc2.html +# content of short.htaccess file created and managed by short.py + +# appended: 2025-05-23 15:12:13 +RedirectTemp /22 https://pythonfluente.com/2/#pattern_matching_case_study_sec +RedirectTemp /23 https://pythonfluente.com/2/#how_slicing_works +RedirectTemp /24 https://pythonfluente.com/2/#sliceable_sequence +RedirectTemp /25 https://pythonfluente.com/2/#virtual_subclass_sec +RedirectTemp /26 https://pythonfluente.com/2/#environment_class_ex +RedirectTemp /27 https://pythonfluente.com/2/#subclass_builtin_woes +RedirectTemp /28 https://pythonfluente.com/2/#slots_section +RedirectTemp /29 https://pythonfluente.com/2/#typeddict_sec +RedirectTemp /2a https://pythonfluente.com/2/#problems_annot_runtime_sec +RedirectTemp /2b https://pythonfluente.com/2/#legacy_deprecated_typing_box +RedirectTemp /2c https://pythonfluente.com/2/#positional_pattern_implement_sec diff --git a/links/short.htaccess b/links/short.htaccess index 6b09a50..86fe3e2 100644 --- a/links/short.htaccess +++ b/links/short.htaccess @@ -1 +1,14 @@ # content of short.htaccess file created and managed by short.py + +# appended: 2025-05-23 15:12:13 +RedirectTemp /22 https://pythonfluente.com/2/#pattern_matching_case_study_sec +RedirectTemp /23 https://pythonfluente.com/2/#how_slicing_works +RedirectTemp /24 https://pythonfluente.com/2/#sliceable_sequence +RedirectTemp /25 https://pythonfluente.com/2/#virtual_subclass_sec +RedirectTemp /26 https://pythonfluente.com/2/#environment_class_ex +RedirectTemp /27 https://pythonfluente.com/2/#subclass_builtin_woes +RedirectTemp /28 https://pythonfluente.com/2/#slots_section +RedirectTemp /29 https://pythonfluente.com/2/#typeddict_sec +RedirectTemp /2a https://pythonfluente.com/2/#problems_annot_runtime_sec +RedirectTemp /2b https://pythonfluente.com/2/#legacy_deprecated_typing_box +RedirectTemp /2c https://pythonfluente.com/2/#positional_pattern_implement_sec From cf3161ca006b106bfc0ba698e9135e9cdfb51e55 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Wed, 4 Jun 2025 18:54:30 -0300 Subject: [PATCH 166/166] moved URL shortening code to https://github.com/pythonfluente/pythonfluente2e --- links/README.md | 54 +-- links/custom.htaccess | 1072 ----------------------------------------- links/deploy.sh | 2 - links/sample-urls.txt | 47 -- links/short.htaccess | 14 - links/short.py | 95 ---- 6 files changed, 4 insertions(+), 1280 deletions(-) delete mode 100644 links/custom.htaccess delete mode 100755 links/deploy.sh delete mode 100644 links/sample-urls.txt delete mode 100644 links/short.htaccess delete mode 100755 links/short.py diff --git a/links/README.md b/links/README.md index 2a3d80a..65749b2 100644 --- a/links/README.md +++ b/links/README.md @@ -1,52 +1,6 @@ -# Short links for URLs in the book +This file is deployed as `.htaccess` to the FPY.LI domain +to map short URLs in Fluent Python to the original URLs. -## Problem: link rot +To update it, I use tools in this other repo: -_Fluent Python, Second Edition_ has more than 1000 links to external resources. -Inevitably, some of those links will rot as time passes. -But I can't change the URLs in the print book... - -## Solution: indirection - -I replaced almost all URLs in the book with shortened versions that go through the `fpy.li` site which I control. -The site has an `.htaccess` file with *temporary* redirects. - -When I find out a link is stale, I can thange the redirect in `.htaccess` to a new target, -which may be a link to copy in the Internet Archive's -[Wayback Machine](https://archive.org/web/) -o the link in the book is back in service through the updated redirect. - - -## Help wanted - -Please report broken links as bugs in the [`FPY.LI.htaccess`](FPY.LI.htaccess) file. -Also, feel free to send pull requests with fixes to that file. -When I accept a PR, I will redeploy it to `fpy.li/.htaccess`. - - -## Details - -Almost all URLs in the book are replaced with shortened versions like -[`http://fpy.li/1-3`](http://fpy.li/1-3)—for chapter 1, link #3. - -There are also custom short URLs like -[`https://fpy.li/code`](https://fpy.li/code) which redirects to the example code repository. -I used custom short URLs for URLs with 3 or more mentions, or links to PEPs. - -Exceptions: - -- URLs with `oreilly` in them are unchanged; -- `fluentpython.com` URL (with no path) is unchanged; - -The `custom.htaccess` file contains redirects with custom names -plus numbered URLs generated from the links in each chapter in -the Second Edition in English. - -`short.htaccess` has redirects made by `short.py`, starting -with the Second Edition in Brazilian Portuguese. - -```shell -cat custom.htaccess short.htaccess > FPY.LI.htaccess -``` - -`FPY.LI.htaccess` is deployed at the root folder in `http://fpy.li`. +https://github.com/pythonfluente/pythonfluente2e diff --git a/links/custom.htaccess b/links/custom.htaccess deleted file mode 100644 index cc779db..0000000 --- a/links/custom.htaccess +++ /dev/null @@ -1,1072 +0,0 @@ -ErrorDocument 404 /404.html - -# main resources -RedirectTemp /book https://www.oreilly.com/library/view/fluent-python-2nd/9781492056348/ -RedirectTemp /code https://github.com/fluentpython/example-code-2e -RedirectTemp /home https://www.fluentpython.com/ - -# URLs mentioned at least three times -RedirectTemp /bisect https://www.fluentpython.com/extra/ordered-sequences-with-bisect/ -RedirectTemp /cardxvi https://www.python.org/dev/peps/pep-0484/#the-numeric-tower -RedirectTemp /collec https://docs.python.org/3/library/collections.html -RedirectTemp /dask https://dask.org/ -RedirectTemp /dtmodel https://docs.python.org/3/reference/datamodel.html -RedirectTemp /descr101 https://www.python.org/download/releases/2.2.3/descrintro/ -RedirectTemp /descrhow https://docs.python.org/3/howto/descriptor.html -RedirectTemp /doctest https://docs.python.org/3/library/doctest.html -RedirectTemp /effectpy https://effectivepython.com/ -RedirectTemp /fmtspec https://docs.python.org/3/library/string.html#formatspec -RedirectTemp /gunicorn https://gunicorn.org/ -RedirectTemp /hashint https://www.fluentpython.com/extra/internals-of-sets-and-dicts/ -RedirectTemp /hattingh https://www.oreilly.com/library/view/using-asyncio-in/9781492075325/ -RedirectTemp /httpx https://www.python-httpx.org/ -RedirectTemp /initvar https://docs.python.org/3/library/dataclasses.html#init-only-variables -RedirectTemp /mypy https://mypy.readthedocs.io/en/stable/ -RedirectTemp /norvigdp http://norvig.com/design-patterns/ -RedirectTemp /nsphere https://en.wikipedia.org/wiki/N-sphere -RedirectTemp /oldcoro https://www.fluentpython.com/extra/classic-coroutines/ -RedirectTemp /pandas https://pandas.pydata.org/ -RedirectTemp /pycook3 https://www.oreilly.com/library/view/python-cookbook-3rd/9781449357337/ -RedirectTemp /pynut3 https://www.oreilly.com/library/view/python-in-a/9781491913833/ -RedirectTemp /pypydif https://doc.pypy.org/en/latest/cpython_differences.html#subclasses-of-built-in-types -RedirectTemp /shed4051 https://github.com/python/typeshed/issues/4051 -RedirectTemp /specattr https://docs.python.org/3/library/stdtypes.html#special-attributes -RedirectTemp /typecoro https://docs.python.org/3.10/library/typing.html#typing.Coroutine -RedirectTemp /typing https://docs.python.org/3/library/typing.html -RedirectTemp /weakref https://www.fluentpython.com/extra/weak-references/ - -# URL added during QA of the Second Edition -RedirectTemp /bdfl https://www.artima.com/weblogs/viewpost.jsp?thread=235725 - -# Python Enhancement Proposals -RedirectTemp /pep218 https://www.python.org/dev/peps/pep-0218/ -RedirectTemp /pep227 https://www.python.org/dev/peps/pep-0227/ -RedirectTemp /pep255 https://www.python.org/dev/peps/pep-0255/ -RedirectTemp /pep342 https://www.python.org/dev/peps/pep-0342/ -RedirectTemp /pep343 https://www.python.org/dev/peps/pep-0343/ -RedirectTemp /pep357 https://www.python.org/dev/peps/pep-0357/ -RedirectTemp /pep362 https://www.python.org/dev/peps/pep-0362/ -RedirectTemp /pep371 https://www.python.org/dev/peps/pep-0371/ -RedirectTemp /pep380 https://www.python.org/dev/peps/pep-0380/ -RedirectTemp /pep393 https://www.python.org/dev/peps/pep-0393/ -RedirectTemp /pep412 https://www.python.org/dev/peps/pep-0412/ -RedirectTemp /pep442 https://www.python.org/dev/peps/pep-0442/ -RedirectTemp /pep443 https://www.python.org/dev/peps/pep-0443/ -RedirectTemp /pep448 https://www.python.org/dev/peps/pep-0448/ -RedirectTemp /pep455 https://www.python.org/dev/peps/pep-0455/ -RedirectTemp /pep456 https://www.python.org/dev/peps/pep-0456/ -RedirectTemp /pep461 https://www.python.org/dev/peps/pep-0461/ -RedirectTemp /pep465 https://www.python.org/dev/peps/pep-0465/ -RedirectTemp /pep467 https://www.python.org/dev/peps/pep-0467/ -RedirectTemp /pep482 https://www.python.org/dev/peps/pep-0482/ -RedirectTemp /pep483 https://www.python.org/dev/peps/pep-0483/ -RedirectTemp /pep484 https://www.python.org/dev/peps/pep-0484/ -RedirectTemp /pep487 https://www.python.org/dev/peps/pep-0487/ -RedirectTemp /pep492 https://www.python.org/dev/peps/pep-0492/ -RedirectTemp /pep519 https://www.python.org/dev/peps/pep-0519/ -RedirectTemp /pep525 https://www.python.org/dev/peps/pep-0525/ -RedirectTemp /pep526 https://www.python.org/dev/peps/pep-0526/ -RedirectTemp /pep528 https://www.python.org/dev/peps/pep-0528/ -RedirectTemp /pep529 https://www.python.org/dev/peps/pep-0529/ -RedirectTemp /pep530 https://www.python.org/dev/peps/pep-0530/ -RedirectTemp /pep544 https://www.python.org/dev/peps/pep-0544/ -RedirectTemp /pep554 https://www.python.org/dev/peps/pep-0554/ -RedirectTemp /pep557 https://www.python.org/dev/peps/pep-0557/ -RedirectTemp /pep560 https://www.python.org/dev/peps/pep-0560/ -RedirectTemp /pep561 https://www.python.org/dev/peps/pep-0561/ -RedirectTemp /pep563 https://www.python.org/dev/peps/pep-0563/ -RedirectTemp /pep570 https://www.python.org/dev/peps/pep-0570/ -RedirectTemp /pep572 https://www.python.org/dev/peps/pep-0572/ -RedirectTemp /pep584 https://www.python.org/dev/peps/pep-0584/ -RedirectTemp /pep585 https://www.python.org/dev/peps/pep-0585/ -RedirectTemp /pep586 https://www.python.org/dev/peps/pep-0586/ -RedirectTemp /pep589 https://www.python.org/dev/peps/pep-0589/ -RedirectTemp /pep591 https://www.python.org/dev/peps/pep-0591/ -RedirectTemp /pep593 https://www.python.org/dev/peps/pep-0593/ -RedirectTemp /pep604 https://www.python.org/dev/peps/pep-0604/ -RedirectTemp /pep612 https://www.python.org/dev/peps/pep-0612/ -RedirectTemp /pep613 https://www.python.org/dev/peps/pep-0613/ -RedirectTemp /pep616 https://www.python.org/dev/peps/pep-0616/ -RedirectTemp /pep617 https://www.python.org/dev/peps/pep-0617/ -RedirectTemp /pep618 https://www.python.org/dev/peps/pep-0618/ -RedirectTemp /pep634 https://www.python.org/dev/peps/pep-0634/ -RedirectTemp /pep635 https://www.python.org/dev/peps/pep-0635/ -RedirectTemp /pep636 https://www.python.org/dev/peps/pep-0636/ -RedirectTemp /pep638 https://www.python.org/dev/peps/pep-0638/ -RedirectTemp /pep645 https://www.python.org/dev/peps/pep-0645/ -RedirectTemp /pep646 https://www.python.org/dev/peps/pep-0646/ -RedirectTemp /pep647 https://www.python.org/dev/peps/pep-0647/ -RedirectTemp /pep649 https://www.python.org/dev/peps/pep-0649/ -RedirectTemp /pep654 https://www.python.org/dev/peps/pep-0654/ -RedirectTemp /pep655 https://www.python.org/dev/peps/pep-0655/ -RedirectTemp /pep661 https://www.python.org/dev/peps/pep-0661/ -RedirectTemp /pep3099 https://www.python.org/dev/peps/pep-3099/ -RedirectTemp /pep3102 https://www.python.org/dev/peps/pep-3102/ -RedirectTemp /pep3104 https://www.python.org/dev/peps/pep-3104/ -RedirectTemp /pep3106 https://www.python.org/dev/peps/pep-3106/ -RedirectTemp /pep3107 https://www.python.org/dev/peps/pep-3107/ -RedirectTemp /pep3115 https://www.python.org/dev/peps/pep-3115/ -RedirectTemp /pep3118 https://www.python.org/dev/peps/pep-3118/ -RedirectTemp /pep3119 https://www.python.org/dev/peps/pep-3119/ -RedirectTemp /pep3129 https://www.python.org/dev/peps/pep-3129/ -RedirectTemp /pep3132 https://www.python.org/dev/peps/pep-3132/ -RedirectTemp /pep3141 https://www.python.org/dev/peps/pep-3141/ -RedirectTemp /pep3148 https://www.python.org/dev/peps/pep-3148/ -RedirectTemp /pep3155 https://www.python.org/dev/peps/pep-3155/ -RedirectTemp /pep3333 https://www.python.org/dev/peps/pep-3333/ - -# Remaining URLs by chapter - -############################################################ p -RedirectTemp /p-1 https://mail.python.org/pipermail/python-list/2002-December/134521.html -RedirectTemp /p-2 https://docs.python.org/3.10/tutorial/ -RedirectTemp /p-3 https://docs.python.org/3/tutorial/ -RedirectTemp /p-4 https://www.oreilly.com/library/view/fluent-python-2nd/9781492056348/ -RedirectTemp /p-5 https://www.oreilly.com/online-learning/try-now.html -RedirectTemp /p-6 https://www.oreilly.com/library/view/fluent-python-2nd/9781492056348/ -RedirectTemp /p-7 https://stackoverflow.com/users/95810/alex-martelli -RedirectTemp /p-8 https://pythonpro.com.br -RedirectTemp /p-9 https://groups.google.com/g/python-brasil -RedirectTemp /p-10 https://www.coffeelab.com.br/ -RedirectTemp /p-11 https://garoa.net.br/wiki/P%C3%A1gina_principal -############################################################ a -RedirectTemp /a-1 https://groups.google.com/forum/#!topic/python-tulip/Y4bhLNbKs74 -RedirectTemp /a-2 https://docs.python.org/3/library/asyncio-eventloop.html#executor -RedirectTemp /a-3 https://www.youtube.com/watch?v=x-kB2o8sd5c -RedirectTemp /a-4 https://www.youtube.com/watch?v=OSGv2VnC0go -RedirectTemp /a-5 https://mail.python.org/pipermail/python-ideas/2015-March/032557.html -RedirectTemp /a-6 https://pypi.org/project/pep8/ -RedirectTemp /a-7 https://pypi.org/project/flake8/ -RedirectTemp /a-8 https://pypi.org/project/pyflakes/ -RedirectTemp /a-9 https://pypi.org/project/mccabe/ -RedirectTemp /a-10 https://google.github.io/styleguide/pyguide.html -RedirectTemp /a-11 https://flask.palletsprojects.com/en/1.1.x/styleguide/ -RedirectTemp /a-12 https://docs.python-guide.org/ -RedirectTemp /a-13 https://david.goodger.org/projects/pycon/2007/idiomatic/handout.html -RedirectTemp /a-14 https://docs.mongodb.com/manual/about/#about-the-documentation-process -RedirectTemp /a-15 https://blog.startifact.com/posts/older/what-is-pythonic.html -RedirectTemp /a-16 https://mail.python.org/pipermail/tutor/2003-October/thread.html#25930 -RedirectTemp /a-17 https://mail.python.org/pipermail/python-list/2003-April/192027.html -RedirectTemp /a-18 https://www.python.org/doc/essays/ -############################################################ 01 -RedirectTemp /1-1 http://hugunin.net/story_of_jython.html -RedirectTemp /1-2 https://www.oreilly.com/library/view/jython-essentials/9781449397364/ -RedirectTemp /1-3 https://docs.python.org/3/reference/lexical_analysis.html#reserved-classes-of-identifiers -RedirectTemp /1-4 https://docs.python.org/3.10/library/string.html#format-string-syntax -RedirectTemp /1-5 https://stackoverflow.com/questions/1436703/what-is-the-difference-between-str-and-repr -RedirectTemp /1-6 https://docs.python.org/3/library/stdtypes.html#truth -RedirectTemp /1-7 https://docs.python.org/3/tutorial/controlflow.html#unpacking-argument-lists -RedirectTemp /1-8 https://www.python.org/doc/humor/#the-zen-of-python -RedirectTemp /1-9 https://stackoverflow.com/users/95810/alex-martelli -RedirectTemp /1-10 https://en.wikipedia.org/wiki/Object_model -RedirectTemp /1-11 https://www.dourish.com/goodies/jargon.html -RedirectTemp /1-12 https://zopeinterface.readthedocs.io/en/latest/ -RedirectTemp /1-13 https://plone.org/ -############################################################ 02 -RedirectTemp /2-1 https://github.com/fluentpython/example-code-2e/blob/master/02-array-seq/listcomp_speed.py -RedirectTemp /2-2 https://www.python.org/dev/peps/pep-3132/ -RedirectTemp /2-3 https://stackoverflow.com/questions/68630/are-tuples-more-efficient-than-lists-in-python/22140115#22140115 -RedirectTemp /2-4 https://docs.python.org/3/whatsnew/3.5.html#pep-448-additional-unpacking-generalizations -RedirectTemp /2-5 https://docs.python.org/3/whatsnew/3.5.html#pep-448-additional-unpacking-generalizations -RedirectTemp /2-6 https://docs.python.org/3.10/whatsnew/3.10.html#pep-634-structural-pattern-matching -RedirectTemp /2-7 https://docs.python.org/3.10/whatsnew/3.10.html -RedirectTemp /2-8 https://en.wikipedia.org/wiki/Switch_statement#Fallthrough -RedirectTemp /2-9 https://en.wikipedia.org/wiki/Dangling_else -RedirectTemp /2-10 https://github.com/gvanrossum/patma/blob/3ece6444ef70122876fd9f0099eb9490a2d630df/EXAMPLES.md#case-6-a-very-deep-iterable-and-type-match-with-extraction -RedirectTemp /2-11 https://github.com/fluentpython/lispy/blob/main/original/norvig/lis.py -RedirectTemp /2-12 https://norvig.com/lispy.html -RedirectTemp /2-13 https://numpy.org/doc/stable/user/quickstart.html#indexing-slicing-and-iterating -RedirectTemp /2-14 https://pythontutor.com/ -RedirectTemp /2-15 https://en.wikipedia.org/wiki/Fluent_interface -RedirectTemp /2-16 https://docs.python.org/3/library/bisect.html#bisect.insort -RedirectTemp /2-17 https://stackoverflow.com/questions/4845418/when-should-a-memoryview-be-used/ -RedirectTemp /2-18 https://www.fluentpython.com/extra/parsing-binary-struct/ -RedirectTemp /2-19 http://www.netlib.org -RedirectTemp /2-20 https://pandas.pydata.org/ -RedirectTemp /2-21 https://scikit-learn.org/stable/ -RedirectTemp /2-22 https://docs.python.org/3/howto/sorting.html -RedirectTemp /2-23 https://www.python.org/dev/peps/pep-3132/ -RedirectTemp /2-24 https://bugs.python.org/issue2292 -RedirectTemp /2-25 https://docs.python.org/3.10/whatsnew/3.10.html#pep-634-structural-pattern-matching -RedirectTemp /2-26 https://docs.python.org/3.10/whatsnew/3.10.html -RedirectTemp /2-27 https://www.python.org/dev/peps/pep-0636/#appendix-a-quick-intro -RedirectTemp /2-28 https://eli.thegreenplace.net/2011/11/28/less-copies-in-python-with-the-buffer-protocol-and-memoryviews/ -RedirectTemp /2-29 https://jakevdp.github.io/PythonDataScienceHandbook/ -RedirectTemp /2-30 https://www.oreilly.com/library/view/python-for-data/9781491957653/ -RedirectTemp /2-31 https://www.labri.fr/perso/nrougier/from-python-to-numpy/ -RedirectTemp /2-32 https://www.cs.utexas.edu/users/EWD/transcriptions/EWD08xx/EWD831.html -RedirectTemp /2-33 http://www.fonts101.com/fonts/view/Uncategorized/34398/Dijkstra -RedirectTemp /2-34 https://docs.python.org/3/reference/datamodel.html#objects-values-and-types -RedirectTemp /2-35 https://en.wikipedia.org/wiki/Timsort -RedirectTemp /2-36 http://www.groklaw.net/pdf3/OraGoogle-1202.pdf -RedirectTemp /2-37 https://www.python.org/doc/humor/#id9 -############################################################ 03 -RedirectTemp /3-1 https://www.python.org/dev/peps/pep-0584/#motivation -RedirectTemp /3-2 https://docs.python.org/3.10/c-api/typeobj.html#Py_TPFLAGS_MAPPING -RedirectTemp /3-3 https://docs.python.org/3/glossary.html#term-hashable -RedirectTemp /3-4 https://docs.python.org/3/glossary.html#term-hashable -RedirectTemp /3-5 http://www.aleax.it/Python/accu04_Relearn_Python_alex.pdf -RedirectTemp /3-6 https://github.com/pingo-io/pingo-py -RedirectTemp /3-7 https://github.com/fluentpython/example-code-2e/blob/master/03-dict-set/missing.py -RedirectTemp /3-8 https://docs.python.org/3/library/collections.html#collections.ChainMap -RedirectTemp /3-9 https://docs.python.org/3/library/collections.html#collections.Counter -RedirectTemp /3-10 https://docs.python.org/3/library/shelve.html -RedirectTemp /3-11 https://docs.python.org/3/library/dbm.html -RedirectTemp /3-12 https://docs.python.org/3/library/pickle.html -RedirectTemp /3-13 https://nedbatchelder.com/blog/202006/pickles_nine_flaws.html -RedirectTemp /3-14 https://github.com/python/cpython/blob/0bbf30e2b910bc9c5899134ae9d73a8df968da35/Lib/_collections_abc.py#L813 -RedirectTemp /3-15 https://mail.python.org/pipermail/python-dev/2015-May/140003.html -RedirectTemp /3-16 https://bugs.python.org/issue18986 -RedirectTemp /3-17 https://github.com/fluentpython/example-code-2e/blob/master/03-dict-set/transformdict.py -RedirectTemp /3-18 http://gandenberger.org/2018/03/10/ordered-dicts-vs-ordereddict/ -RedirectTemp /3-19 https://www.pypy.org/ -RedirectTemp /3-20 https://morepypy.blogspot.com/2015/01/faster-more-memory-efficient-and-more.html -RedirectTemp /3-21 https://www.npopov.com/2014/12/22/PHPs-new-hashtable-implementation.html -RedirectTemp /3-22 https://www.youtube.com/watch?v=66P5FMkWoVU -RedirectTemp /3-23 https://pyvideo.org/video/276/the-mighty-dictionary-55/ -RedirectTemp /3-24 https://www.youtube.com/watch?v=p33CVV29OG8 -RedirectTemp /3-25 https://docs.python.org/3/whatsnew/3.6.html#new-dict-implementation -RedirectTemp /3-26 https://github.com/python/cpython/blob/cf7eaa4617295747ee5646c4e2b7e7a16d7c64ab/Objects/dictobject.c -RedirectTemp /3-27 https://github.com/python/cpython/blob/cf7eaa4617295747ee5646c4e2b7e7a16d7c64ab/Objects/dictnotes.txt -RedirectTemp /3-28 https://www.youtube.com/watch?v=tGAngdU_8D8 -RedirectTemp /3-29 https://speakerdeck.com/ramalho/python-set-practice-at-pycon -RedirectTemp /3-30 https://github.com/standupdev/uintset -RedirectTemp /3-31 https://spectrum.ieee.org/hans-peter-luhn-and-the-birth-of-the-hashing-algorithm -RedirectTemp /3-32 http://www.json.org/fatfree.html -RedirectTemp /3-33 https://twitter.com/mitsuhiko/status/1229385843585974272 -############################################################ 04 -RedirectTemp /4-1 https://www.slideshare.net/fischertrav/character-encoding-unicode-how-to-with-dignity-33352863 -RedirectTemp /4-2 https://pyvideo.org/video/2625/character-encoding-and-unicode-in-python/ -RedirectTemp /4-3 https://www.fluentpython.com/extra/parsing-binary-struct/ -RedirectTemp /4-4 https://www.fluentpython.com/extra/multi-character-emojis/ -RedirectTemp /4-5 https://w3techs.com/technologies/overview/character_encoding -RedirectTemp /4-6 https://docs.python.org/3/library/codecs.html#codecs.register_error -RedirectTemp /4-7 https://docs.python.org/3/library/stdtypes.html#str.isascii -RedirectTemp /4-8 https://pypi.org/project/chardet/ -RedirectTemp /4-9 https://docs.python.org/3/library/codecs.html#encodings-and-unicode -RedirectTemp /4-10 https://nedbatchelder.com/text/unipain/unipain.html -RedirectTemp /4-11 https://devblogs.microsoft.com/commandline/windows-command-line-unicode-and-utf-8-output-text-buffer/ -RedirectTemp /4-12 https://docs.python.org/3/using/cmdline.html#envvar-PYTHONIOENCODING -RedirectTemp /4-13 https://docs.python.org/3/using/cmdline.html#envvar-PYTHONLEGACYWINDOWSSTDIO -RedirectTemp /4-14 https://docs.python.org/3/library/locale.html#locale.getpreferredencoding -RedirectTemp /4-15 http://www.w3.org/TR/charmod-norm/ -RedirectTemp /4-16 https://docs.python.org/3/library/locale.html?highlight=strxfrm#locale.strxfrm -RedirectTemp /4-17 https://pypi.org/project/pyuca/ -RedirectTemp /4-18 https://github.com/jtauber/pyuca -RedirectTemp /4-19 http://www.unicode.org/Public/UCA/6.3.0/allkeys.txt -RedirectTemp /4-20 https://pypi.org/project/PyICU/ -RedirectTemp /4-21 https://docs.python.org/3.10/library/stdtypes.html#str.isalpha -RedirectTemp /4-22 https://en.wikipedia.org/wiki/Unicode_character_property#General_Category -RedirectTemp /4-23 https://en.wikipedia.org/wiki/Unicode_character_property -RedirectTemp /4-24 https://github.com/microsoft/terminal -RedirectTemp /4-25 https://docs.python.org/3/library/unicodedata.html -RedirectTemp /4-26 https://docs.python.org/3/reference/lexical_analysis.html#string-literal-concatenation -RedirectTemp /4-27 https://docs.python.org/3/library/re.html -RedirectTemp /4-28 https://nedbatchelder.com/text/unipain.html -RedirectTemp /4-29 https://www.slideshare.net/fischertrav/character-encoding-unicode-how-to-with-dignity-33352863 -RedirectTemp /4-30 https://pyvideo.org/video/2625/character-encoding-and-unicode-in-python/ -RedirectTemp /4-31 https://regebro.wordpress.com/2011/03/23/unconfusing-unicode-what-is-unicode/ -RedirectTemp /4-32 https://docs.python.org/3/howto/unicode.html -RedirectTemp /4-33 https://diveintopython3.net/strings.html -RedirectTemp /4-34 https://diveintopython3.net/ -RedirectTemp /4-35 https://finderiko.com/python-book -RedirectTemp /4-36 https://docs.python.org/3.0/whatsnew/3.0.html#text-vs-data-instead-of-unicode-vs-8-bit -RedirectTemp /4-37 https://lucumr.pocoo.org/2013/7/2/the-updated-guide-to-unicode/ -RedirectTemp /4-38 http://python-notes.curiousefficiency.org/en/latest/python3/binary_protocols.html -RedirectTemp /4-39 http://python-notes.curiousefficiency.org/en/latest/python3/text_file_processing.html -RedirectTemp /4-40 https://docs.python.org/3/library/codecs.html#standard-encodings -RedirectTemp /4-41 https://github.com/python/cpython/blob/0bbf30e2b910bc9c5899134ae9d73a8df968da35/Tools/unicode/listcodecs.py -RedirectTemp /4-42 https://www.oreilly.com/library/view/unicode-explained/059610121X/ -RedirectTemp /4-43 https://www.informit.com/store/unicode-demystified-a-practical-programmers-guide-to-9780201700527 -RedirectTemp /4-44 https://unicodebook.readthedocs.io/index.html -RedirectTemp /4-45 https://www.w3.org/International/wiki/Case_folding -RedirectTemp /4-46 http://www.w3.org/TR/charmod-norm/ -RedirectTemp /4-47 http://unicode.org/reports/tr15/ -RedirectTemp /4-48 http://www.unicode.org/faq/normalization.html -RedirectTemp /4-49 http://www.unicode.org/ -RedirectTemp /4-50 http://www.macchiato.com/unicode/nfc-faq -RedirectTemp /4-51 https://stories.moma.org/the-original-emoji-set-has-been-added-to-the-museum-of-modern-arts-collection-c6060e141f61?gi=c403f5a840a5 -RedirectTemp /4-52 https://emojipedia.org/ -RedirectTemp /4-53 https://blog.emojipedia.org/correcting-the-record-on-the-first-emoji-set/ -RedirectTemp /4-54 http://emojitracker.com/ -RedirectTemp /4-55 http://www.unicode.org/glossary/#plain_text -RedirectTemp /4-56 http://www.methods.co.nz/asciidoc/ -RedirectTemp /4-57 https://atlas.oreilly.com/ -############################################################ 05 -RedirectTemp /5-1 https://docs.python.org/3/library/typing.html#typing.TypedDict -RedirectTemp /5-2 https://docs.python.org/3.10/library/inspect.html#inspect.get_annotations -RedirectTemp /5-3 https://docs.python.org/3/library/typing.html#typing.get_type_hints -RedirectTemp /5-4 https://docs.python.org/3.8/library/collections.html#collections.somenamedtuple._asdict -RedirectTemp /5-5 https://www.jetbrains.com/pycharm/ -RedirectTemp /5-6 https://www.python.org/dev/peps/pep-0484/#acceptable-type-hints -RedirectTemp /5-7 https://docs.python.org/3/library/dataclasses.html#dataclasses.dataclass -RedirectTemp /5-8 https://docs.python.org/3/library/dataclasses.html#dataclasses.dataclass -RedirectTemp /5-9 https://docs.python.org/3/library/dataclasses.html -RedirectTemp /5-10 https://docs.python.org/3/library/dataclasses.html#inheritance -RedirectTemp /5-11 https://www.python.org/dev/peps/pep-0526/#class-and-instance-variable-annotations -RedirectTemp /5-12 https://dublincore.org/specifications/dublin-core/ -RedirectTemp /5-13 https://en.wikipedia.org/wiki/Dublin_Core -RedirectTemp /5-14 https://martinfowler.com/bliki/CodeSmell.html -RedirectTemp /5-15 https://martinfowler.com/books/refactoring.html -RedirectTemp /5-16 https://www.python.org/dev/peps/pep-0634/#class-patterns -RedirectTemp /5-17 https://docs.python.org/3/library/dataclasses.html -RedirectTemp /5-18 https://www.python.org/dev/peps/pep-0557/#id47 -RedirectTemp /5-19 https://www.python.org/dev/peps/pep-0557/#id48 -RedirectTemp /5-20 https://www.python.org/dev/peps/pep-0557/#id33 -RedirectTemp /5-21 https://realpython.com -RedirectTemp /5-22 https://realpython.com/python-data-classes/ -RedirectTemp /5-23 https://www.youtube.com/watch?v=T-TwcmT6Rcw -RedirectTemp /5-24 https://www.attrs.org/en/stable/ -RedirectTemp /5-25 https://glyph.twistedmatrix.com/2016/08/attrs.html -RedirectTemp /5-26 https://www.attrs.org/en/stable/why.html -RedirectTemp /5-27 https://github.com/dabeaz/cluegen -RedirectTemp /5-28 https://refactoring.guru/ -RedirectTemp /5-29 https://refactoring.guru/smells/data-class -RedirectTemp /5-30 https://web.archive.org/web/20190204130328/http://catb.org/esr/jargon/html/G/Guido.html -RedirectTemp /5-31 https://web.archive.org/web/20190211161610/http://catb.org/esr/jargon/html/index.html -RedirectTemp /5-32 https://www.attrs.org/en/stable/ -############################################################ 06 -RedirectTemp /6-1 https://www.olin.edu/faculty/profile/lynn-andrea-stein/ -RedirectTemp /6-2 https://docs.python.org/3/reference/datamodel.html#objects-values-and-types -RedirectTemp /6-3 https://pythontutor.com/ -RedirectTemp /6-4 https://docs.python.org/3/library/copy.html -RedirectTemp /6-5 https://en.wikipedia.org/wiki/Principle_of_least_astonishment -RedirectTemp /6-6 https://docs.python.org/3/reference/datamodel.html#object.%5C_%5C_del__ -RedirectTemp /6-7 https://emptysqua.re/blog/pypy-garbage-collection-and-a-deadlock/ -RedirectTemp /6-8 https://www.youtube.com/watch?v=HHFCFJSPWrI&feature=youtu.be -RedirectTemp /6-9 http://pymotw.com/3/copy/ -RedirectTemp /6-10 http://pymotw.com/3/weakref/ -RedirectTemp /6-11 https://docs.python.org/3/library/gc.html -RedirectTemp /6-12 https://devguide.python.org/garbage_collector/ -RedirectTemp /6-13 https://devguide.python.org/ -RedirectTemp /6-14 https://www.python.org/dev/peps/pep-0442/ -RedirectTemp /6-15 https://en.wikipedia.org/wiki/String_interning -RedirectTemp /6-16 https://en.wikipedia.org/wiki/Haddocks%27_Eyes -RedirectTemp /6-17 https://thp.io/2012/python-gc/python_gc_final_2012-01-22.pdf -############################################################ 07 -RedirectTemp /7-1 http://python-history.blogspot.com/2009/04/origins-of-pythons-functional-features.html -RedirectTemp /7-2 https://www.fluentpython.com/extra/function-introspection/ -RedirectTemp /7-3 https://docs.python.org/3/library/functions.html#map -RedirectTemp /7-4 https://en.wikipedia.org/wiki/Functional_programming -RedirectTemp /7-5 https://docs.python.org/3/howto/functional.html -RedirectTemp /7-6 https://docs.python.org/3/reference/datamodel.html#the-standard-type-hierarchy -RedirectTemp /7-7 https://docs.python.org/3/whatsnew/3.8.html#positional-only-parameters -RedirectTemp /7-8 https://docs.python.org/3/whatsnew/3.8.html#positional-only-parameters -RedirectTemp /7-9 https://github.com/python/cpython/blob/0bbf30e2b910bc9c5899134ae9d73a8df968da35/Lib/functools.py#L341 -RedirectTemp /7-10 https://docs.python.org/3/reference/datamodel.html#the-standard-type-hierarchy -RedirectTemp /7-11 https://docs.python.org/3/howto/functional.html -RedirectTemp /7-12 https://stackoverflow.com/questions/3252228/python-why-is-functools-partial-necessary -RedirectTemp /7-13 https://speakerdeck.com/ramalho/beyond-paradigms-berlin-edition -RedirectTemp /7-14 https://www.youtube.com/watch?v=bF3a2VYXxa0 -RedirectTemp /7-15 http://cs.brown.edu/~sk/Publications/Papers/Published/sk-teach-pl-post-linnaean/ -RedirectTemp /7-16 http://python-history.blogspot.com/2009/04/origins-of-pythons-functional-features.html -RedirectTemp /7-17 https://raw.githubusercontent.com/python/cpython/main/Misc/HISTORY -RedirectTemp /7-18 http://neopythonic.blogspot.com/2009/04/tail-recursion-elimination.html -############################################################ 08 -RedirectTemp /8-1 https://www.python.org/dev/peps/pep-0484/#non-goals -RedirectTemp /8-2 https://github.com/python/typing/issues/182 -RedirectTemp /8-3 https://github.com/python/mypy/issues/731 -RedirectTemp /8-4 https://github.com/google/pytype -RedirectTemp /8-5 https://github.com/Microsoft/pyright -RedirectTemp /8-6 https://pyre-check.org/ -RedirectTemp /8-7 https://mypy.readthedocs.io/en/stable/introduction.html -RedirectTemp /8-8 https://mypy.readthedocs.io/en/stable/config_file.html -RedirectTemp /8-9 https://pypi.org/project/flake8/ -RedirectTemp /8-10 https://pypi.org/project/blue/ -RedirectTemp /8-11 https://pypi.org/project/black/ -RedirectTemp /8-12 https://wefearchange.org/2020/11/steeringcouncil.rst.html -RedirectTemp /8-13 https://docs.python.org/3/library/collections.abc.html#collections-abstract-base-classes -RedirectTemp /8-14 https://en.wikipedia.org/wiki/Barbara_Liskov -RedirectTemp /8-15 https://en.wikipedia.org/wiki/Behavioral_subtyping -RedirectTemp /8-16 https://www.python.org/dev/peps/pep-0585/#implementation -RedirectTemp /8-17 https://docs.python.org/3/library/typing.html#module-contents -RedirectTemp /8-18 https://en.wikipedia.org/wiki/Geohash -RedirectTemp /8-19 https://en.wikipedia.org/wiki/Inverted_index -RedirectTemp /8-20 https://docs.python.org/3/library/typing.html#typing.List -RedirectTemp /8-21 https://docs.python.org/3/library/typing.html#typing.Dict -RedirectTemp /8-22 https://docs.python.org/3/library/typing.html#typing.Set -RedirectTemp /8-23 https://www.python.org/dev/peps/pep-0585/#implementation -RedirectTemp /8-24 https://docs.python.org/3/library/numbers.html -RedirectTemp /8-25 https://docs.python.org/3/library/typing.html#typing.List -RedirectTemp /8-26 https://github.com/python/typeshed -RedirectTemp /8-27 https://github.com/python/typeshed/blob/66cd36268a6a667714efaa27198a41d0d7f89477/stdlib/2and3/math.pyi#L45 -RedirectTemp /8-28 https://docs.python.org/3/library/statistics.html#statistics.mode -RedirectTemp /8-29 https://github.com/python/cpython/blob/822efa5695b5ba6c2316c1400e4e9ec2546f7ea5/Lib/statistics.py#L534 -RedirectTemp /8-30 https://github.com/python/typeshed/blob/e1e99245bb46223928eba68d4fc74962240ba5b4/stdlib/3/statistics.pyi -RedirectTemp /8-31 https://docs.python.org/3/library/statistics.html#statistics.mode -RedirectTemp /8-32 https://github.com/python/typeshed/blob/a8834fcd46339e17fc8add82b5803a1ce53d3d60/stdlib/3/statistics.pyi#L32 -RedirectTemp /8-33 https://mail.python.org/archives/list/python-dev@python.org/thread/CLVXXPQ2T2LQ5MP2Y53VVQFCXYWQJHKZ/ -RedirectTemp /8-34 https://docs.python.org/3/library/typing.html#typing.Callable -RedirectTemp /8-35 https://pypi.org/project/blue/ -RedirectTemp /8-36 https://www.python.org/dev/peps/pep-0484/#id38 -RedirectTemp /8-37 https://docs.google.com/document/d/1aXs1tpwzPjW9MdsG5dI7clNFyYayFBkcXwRDo-qvbIk/preview -RedirectTemp /8-38 https://www.oreilly.com/library/view/the-best-software/9781590595008/ -RedirectTemp /8-39 https://www.youtube.com/watch?v=YFexUDjHO6w -RedirectTemp /8-40 https://www.youtube.com/watch?v=YFexUDjHO6w&t=13m40s -RedirectTemp /8-41 https://bernat.tech/posts/the-state-of-type-hints-in-python/ -RedirectTemp /8-42 https://realpython.com/python-type-checking/ -RedirectTemp /8-43 https://cjolowicz.github.io/posts/hypermodern-python-04-typing/ -RedirectTemp /8-44 https://mypy.readthedocs.io/en/stable/index.html -RedirectTemp /8-45 https://mypy.readthedocs.io/en/stable/cheat_sheet_py3.html -RedirectTemp /8-46 https://mypy.readthedocs.io/en/stable/common_issues.html -RedirectTemp /8-47 https://github.com/typeddjango/awesome-python-typing -RedirectTemp /8-48 https://docs.python.org/3/library/functions.html#max -RedirectTemp /8-49 https://en.wikipedia.org/wiki/Linguistic_relativity -RedirectTemp /8-50 https://pypistats.org/top -RedirectTemp /8-51 https://github.com/psf/requests/issues/3855 -RedirectTemp /8-52 https://lwn.net/Articles/643399/ -RedirectTemp /8-53 https://docs.python-requests.org/en/master/api/#requests.request -RedirectTemp /8-54 https://queue.acm.org/detail.cfm?id=1039523 -############################################################ 09 -RedirectTemp /9-1 https://docs.python.org/3/library/dis.html -RedirectTemp /9-2 https://en.wikipedia.org/wiki/Memoization -RedirectTemp /9-3 https://numpy.org/doc/stable/user/basics.types.html -RedirectTemp /9-4 https://docs.python.org/3/library/functools.html#functools.singledispatch -RedirectTemp /9-5 https://github.com/GrahamDumpleton/wrapt/blob/develop/blog/README.md -RedirectTemp /9-6 https://github.com/GrahamDumpleton/wrapt/blob/develop/blog/01-how-you-implemented-your-python-decorator-is-wrong.md -RedirectTemp /9-7 https://wrapt.readthedocs.io/en/latest/ -RedirectTemp /9-8 https://www.oreilly.com/library/view/python-cookbook-3rd/9781449357337/ch09.html -RedirectTemp /9-9 https://pypi.org/project/decorator/ -RedirectTemp /9-10 https://wiki.python.org/moin/PythonDecoratorLibrary -RedirectTemp /9-11 http://web.archive.org/web/20201109032203/http://effbot.org/zone/closure.htm -RedirectTemp /9-12 https://www.python.org/dev/peps/pep-3104/ -RedirectTemp /9-13 https://www.python.org/dev/peps/pep-0227/ -RedirectTemp /9-14 https://www.python.org/dev/peps/pep-0443/ -RedirectTemp /9-15 https://www.artima.com/weblogs/viewpost.jsp?thread=101605 -RedirectTemp /9-16 https://reg.readthedocs.io/en/latest/ -RedirectTemp /9-17 https://morepath.readthedocs.io/en/latest/ -RedirectTemp /9-18 https://www.gnu.org/software/emacs/manual/html_node/elisp/Dynamic-Binding.html -RedirectTemp /9-19 http://www.paulgraham.com/rootsoflisp.html -RedirectTemp /9-20 http://www-formal.stanford.edu/jmc/recursive/recursive.html -RedirectTemp /9-21 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this -############################################################ 10 -RedirectTemp /10-1 https://en.wikipedia.org/wiki/Software_design_pattern -RedirectTemp /10-2 https://en.wikipedia.org/wiki/Iterator_pattern -RedirectTemp /10-3 https://github.com/python/mypy/issues/9397 -RedirectTemp /10-4 http://www.norvig.com/design-patterns/index.htm -RedirectTemp /10-5 https://pyvideo.org/video/1110/python-design-patterns/ -RedirectTemp /10-6 http://www.aleax.it/gdd_pydp.pdf -RedirectTemp /10-7 https://perl.plover.com/yak/design/ -RedirectTemp /10-8 https://en.wikipedia.org/wiki/Turtles_all_the_way_down -############################################################ 11 -RedirectTemp /11-1 https://blog.startifact.com/posts/older/what-is-pythonic.html -RedirectTemp /11-2 https://julien.danjou.info/guide-python-static-class-abstract-methods/ -RedirectTemp /11-3 https://docs.python.org/3/library/string.html#formatspec -RedirectTemp /11-4 https://docs.python.org/3/reference/lexical_analysis.html#f-strings -RedirectTemp /11-5 https://docs.python.org/3/library/string.html#format-string-syntax -RedirectTemp /11-6 https://docs.python.org/3/library/string.html#formatspec -RedirectTemp /11-7 https://docs.python.org/3/reference/datamodel.html#object.__hash__ -RedirectTemp /11-8 https://web.archive.org/web/20161025185040/http://pythonpaste.org/StyleGuide.html -RedirectTemp /11-9 https://docs.python.org/3/tutorial/modules.html#more-on-modules -RedirectTemp /11-10 https://docs.python.org/3/library/gettext.html#gettext.NullTranslations -RedirectTemp /11-11 https://github.com/fluentpython/example-code-2e/blob/master/11-pythonic-obj/mem_test.py -RedirectTemp /11-12 https://docs.python.org/3/reference/datamodel.html#basic-customization -RedirectTemp /11-13 http://esug.org/data/HistoricalDocuments/TheSmalltalkReport/ST07/04wo.pdf -RedirectTemp /11-14 https://www.artima.com/articles/the-simplest-thing-that-could-possibly-work#part3 -RedirectTemp /11-15 https://docs.oracle.com/javase/tutorial/essential/environment/security.html -RedirectTemp /11-16 https://github.com/fluentpython/example-code-2e/blob/master/11-pythonic-obj/private/Expose.java -RedirectTemp /11-17 https://docs.oracle.com/javase/tutorial/essential/environment/security.html -############################################################ 12 -RedirectTemp /12-1 https://en.wikipedia.org/wiki/Vector_space_model -RedirectTemp /12-2 https://pypi.org/project/gensim/ -RedirectTemp /12-3 https://docs.python.org/3/library/functions.html#enumerate -RedirectTemp /12-4 https://mathworld.wolfram.com/Hypersphere.html -RedirectTemp /12-5 https://en.wikipedia.org/wiki/Fold_(higher-order_function) -RedirectTemp /12-6 https://docs.python.org/2.5/whatsnew/pep-357.html -RedirectTemp /12-7 https://docs.python.org/3/reference/datamodel.html#special-method-names -RedirectTemp /12-8 https://en.wikipedia.org/wiki/KISS_principle -RedirectTemp /12-9 https://mail.python.org/pipermail/python-list/2000-July/046184.html -RedirectTemp /12-10 https://en.wikipedia.org/wiki/Duck_typing -RedirectTemp /12-11 https://mail.python.org/mailman/listinfo/python-list -RedirectTemp /12-12 https://mail.python.org/pipermail/python-list/2003-April/218568.html -############################################################ 13 -RedirectTemp /13-1 https://docs.python.org/3/c-api/index.html -RedirectTemp /13-2 https://docs.python.org/3/c-api/sequence.html -RedirectTemp /13-3 https://github.com/python/cpython/blob/31ceccb2c77854893f3a754aca04bedd74bedb10/Lib/_collections_abc.py#L870 -RedirectTemp /13-4 https://en.wikipedia.org/wiki/Monkey_patch -RedirectTemp /13-5 https://www.gevent.org/api/gevent.monkey.html -RedirectTemp /13-6 https://docs.python.org/3/library/random.html#random.shuffle -RedirectTemp /13-7 https://docs.python.org/3/reference/datamodel.html#emulating-container-types -RedirectTemp /13-8 https://docs.python.org/3/library/collections.html#collections.namedtuple -RedirectTemp /13-9 https://github.com/python/typeshed/blob/24afb531ffd07083d6a74be917342195062f7277/stdlib/collections/__init__.pyi -RedirectTemp /13-10 https://docs.python.org/3/glossary.html#term-abstract-base-class -RedirectTemp /13-11 https://en.wikipedia.org/wiki/Duck_typing#History -RedirectTemp /13-12 http://ptgmedia.pearsoncmg.com/images/020163371x/items/item33.html -RedirectTemp /13-13 https://docs.python.org/3/library/bisect.html#bisect.bisect -RedirectTemp /13-14 https://github.com/python/cpython/blob/main/Lib/_collections_abc.py -RedirectTemp /13-15 https://github.com/python/cpython/blob/main/Lib/abc.py -RedirectTemp /13-16 https://docs.python.org/3/library/collections.abc.html#collections-abstract-base-classes -RedirectTemp /13-17 https://docs.python.org/3/library/collections.abc.html#collections.abc.Iterable -RedirectTemp /13-18 https://docs.python.org/3/library/abc.html -RedirectTemp /13-19 https://docs.python.org/dev/library/abc.html#abc.abstractmethod -RedirectTemp /13-20 https://docs.python.org/dev/library/abc.html -RedirectTemp /13-21 https://docs.python.org/3/library/os.html#os.urandom -RedirectTemp /13-22 https://github.com/python/mypy/issues/2922 -RedirectTemp /13-23 https://docs.python.org/3/library/stdtypes.html#truth -RedirectTemp /13-24 https://github.com/python/cpython/blob/0bbf30e2b910bc9c5899134ae9d73a8df968da35/Lib/_collections_abc.py -RedirectTemp /13-25 https://github.com/python/cpython/blob/0fbddb14dc03f61738af01af88e7d8aa8df07336/Lib/_collections_abc.py#L369 -RedirectTemp /13-26 https://github.com/python/cpython/blob/c0a9afe2ac1820409e6173bd1893ebee2cf50270/Lib/abc.py#L196 -RedirectTemp /13-27 https://bugs.python.org/issue31333 -RedirectTemp /13-28 https://github.com/python/cpython/blob/3635388f52b42e5280229104747962117104c453/Modules/_abc.c#L605 -RedirectTemp /13-29 https://github.com/python/cpython/blob/0fbddb14dc03f61738af01af88e7d8aa8df07336/Lib/_collections_abc.py#L881 -RedirectTemp /13-30 https://docs.python.org/3/library/typing.html#protocols -RedirectTemp /13-31 https://github.com/python/cpython/blob/3635388f52b42e5280229104747962117104c453/Lib/typing.py#L1751 -RedirectTemp /13-32 https://mail.python.org/archives/list/python-dev@python.org/thread/CLVXXPQ2T2LQ5MP2Y53VVQFCXYWQJHKZ/ -RedirectTemp /13-33 https://martinfowler.com/bliki/RoleInterface.html -RedirectTemp /13-34 https://en.wikipedia.org/wiki/Interface_segregation_principle -RedirectTemp /13-35 https://github.com/python/typeshed/blob/master/CONTRIBUTING.md -RedirectTemp /13-36 https://gist.github.com/asukakenji/ac8a05644a2e98f1d5ea8c299541fce9 -RedirectTemp /13-37 https://www.python.org/dev/peps/pep-0544/#runtime-checkable-decorator-and-narrowing-types-by-isinstance -RedirectTemp /13-38 https://www.python.org/dev/peps/pep-0544/#merging-and-extending-protocols -RedirectTemp /13-39 https://numpy.org/devdocs/user/basics.types.html -RedirectTemp /13-40 https://github.com/python/typeshed/blob/master/stdlib/statistics.pyi -RedirectTemp /13-41 https://bugs.python.org/issue41974 -RedirectTemp /13-42 https://glyph.twistedmatrix.com/2020/07/new-duck.html -RedirectTemp /13-43 https://glyph.twistedmatrix.com/2021/03/interfaces-and-protocols.html -RedirectTemp /13-44 https://plone.org/ -RedirectTemp /13-45 https://trypyramid.com/ -RedirectTemp /13-46 https://twistedmatrix.com/trac/ -RedirectTemp /13-47 https://www.artima.com/articles/contracts-in-python -RedirectTemp /13-48 https://martinfowler.com/bliki/DynamicTyping.html -RedirectTemp /13-49 https://martinfowler.com/bliki/RoleInterface.html -RedirectTemp /13-50 https://mypy.readthedocs.io/en/stable/protocols.html -RedirectTemp /13-51 https://pymotw.com/3/abc/index.html -RedirectTemp /13-52 https://www.python.org/dev/peps/pep-3119/ -RedirectTemp /13-53 https://www.python.org/dev/peps/pep-3141/ -RedirectTemp /13-54 https://docs.python.org/3/library/numbers.html -RedirectTemp /13-55 https://github.com/python/mypy/issues/3186 -RedirectTemp /13-56 https://stackoverflow.com/questions/69334475/how-to-hint-at-number-types-i-e-subclasses-of-number-not-numbers-themselv/69383462#69383462 -RedirectTemp /13-57 https://github.com/python/mypy/issues/3186 -RedirectTemp /13-58 https://martinfowler.com/articles/lean-inception/ -RedirectTemp /13-59 https://martinfowler.com -RedirectTemp /13-60 https://www.jetbrains.com/pycharm/ -RedirectTemp /13-61 https://wingware.com/ -RedirectTemp /13-62 https://code.visualstudio.com/ -############################################################ 14 -RedirectTemp /14-1 http://worrydream.com/EarlyHistoryOfSmalltalk/ -RedirectTemp /14-2 https://docs.python.org/3/tutorial/classes.html -RedirectTemp /14-3 https://docs.python.org/3/library/collections.html#ordereddict-examples-and-recipes -RedirectTemp /14-4 https://discuss.python.org/t/is-it-time-to-deprecate-unbound-super-methods/1833 -RedirectTemp /14-5 https://doc.pypy.org/en/latest/cpython_differences.html#subclasses-of-built-in-types -RedirectTemp /14-6 https://docs.python.org/3/library/collections.html -RedirectTemp /14-7 https://github.com/fluentpython/example-code-2e/blob/master/14-inheritance/strkeydict_dictsub.py -RedirectTemp /14-8 https://doc.pypy.org/en/latest/cpython_differences.html#subclasses-of-built-in-types -RedirectTemp /14-9 https://en.wikipedia.org/wiki/Breadth-first_search -RedirectTemp /14-10 https://www.python.org/download/releases/2.3/mro/ -RedirectTemp /14-11 https://github.com/fluentpython/example-code-2e/blob/master/14-inheritance/uppermixin.py -RedirectTemp /14-12 https://docs.oracle.com/javase/tutorial/java/IandI/defaultmethods.html -RedirectTemp /14-13 https://docs.python.org/3/library/collections.abc.html -RedirectTemp /14-14 https://github.com/python/cpython/blob/8ece98a7e418c3c68a4c61bc47a2d0931b59a889/Lib/collections/__init__.py#L1084 -RedirectTemp /14-15 https://docs.python.org/3/library/http.server.html -RedirectTemp /14-16 https://github.com/python/cpython/blob/17c23167942498296f0bdfffe52e72d53d66d693/Lib/http/server.py#L144 -RedirectTemp /14-17 https://github.com/python/cpython/blob/699ee016af5736ffc80f68359617611a22b72943/Lib/socketserver.py#L664 -RedirectTemp /14-18 https://docs.python.org/3/library/socketserver.html#socketserver.ForkingMixIn -RedirectTemp /14-19 https://docs.python.org/3/library/os.html#os.fork -RedirectTemp /14-20 https://en.wikipedia.org/wiki/POSIX -RedirectTemp /14-21 http://ccbv.co.uk/ -RedirectTemp /14-22 https://github.com/django/django/tree/main/django/views/generic -RedirectTemp /14-23 https://en.wikipedia.org/wiki/Template_method_pattern -RedirectTemp /14-24 https://docs.python.org/3/library/tkinter.html -RedirectTemp /14-25 https://docs.python.org/3/library/tkinter.ttk.html -RedirectTemp /14-26 https://docs.oracle.com/javase/10/docs/api/java/awt/package-tree.html -RedirectTemp /14-27 https://docs.oracle.com/javase/10/docs/api/javax/swing/package-tree.html -RedirectTemp /14-28 https://squeak.org/ -RedirectTemp /14-29 https://github.com/django/django/blob/b64db05b9cedd96905d637a2d824cbbf428e40e7/django/views/generic/list.py#L194 -RedirectTemp /14-30 https://github.com/python/cpython/blob/8ed183391241f0c73e7ba7f42b1d49fc02985f7b/Lib/tkinter/__init__.py#L2618 -RedirectTemp /14-31 https://docs.python.org/3/library/socketserver.html -RedirectTemp /14-32 https://docs.python.org/3/library/socketserver.html#socketserver.BaseServer -RedirectTemp /14-33 https://github.com/python/cpython/blob/699ee016af5736ffc80f68359617611a22b72943/Lib/socketserver.py#L153 -RedirectTemp /14-34 https://docs.python.org/3/library/typing.html#typing.final -RedirectTemp /14-35 https://docs.python.org/3/library/typing.html#typing.Final -RedirectTemp /14-36 https://docs.python.org/3/library/collections.abc.html -RedirectTemp /14-37 https://hynek.me/articles/python-subclassing-redux/ -RedirectTemp /14-38 https://www.oreilly.com/library/view/python-cookbook-3rd/9781449357337/ch08.html#super -RedirectTemp /14-39 https://rhettinger.wordpress.com/2011/05/26/super-considered-super/ -RedirectTemp /14-40 https://fuhm.net/super-harmful/ -RedirectTemp /14-41 https://stackoverflow.com/questions/30190185/how-to-use-super-with-one-argument/30190341#30190341 -RedirectTemp /14-42 https://www.artima.com/weblogs/viewpost.jsp?thread=246488 -RedirectTemp /14-43 https://www.artima.com/weblogs/viewpost.jsp?thread=281127 -RedirectTemp /14-44 https://www.artima.com/weblogs/viewpost.jsp?thread=246341 -RedirectTemp /14-45 https://www.artima.com/weblogs/viewpost.jsp?thread=246483 -RedirectTemp /14-46 https://www.artima.com/weblogs/viewpost.jsp?thread=236275 -RedirectTemp /14-47 https://www.artima.com/weblogs/viewpost.jsp?thread=236278 -RedirectTemp /14-48 https://www.artima.com/weblogs/viewpost.jsp?thread=237121 -RedirectTemp /14-49 https://python-patterns.guide/gang-of-four/composition-over-inheritance/ -RedirectTemp /14-50 https://python-patterns.guide/ -RedirectTemp /14-51 https://www.youtube.com/watch?v=3MNVP9-hglc -RedirectTemp /14-52 http://worrydream.com/EarlyHistoryOfSmalltalk/ -RedirectTemp /14-53 https://en.wikipedia.org/wiki/Polymorphism_(computer_science) -############################################################ 15 -RedirectTemp /15-1 https://www.youtube.com/watch?v=csL8DLXGNlU&t=92m5s -RedirectTemp /15-2 https://github.com/python/typeshed/blob/a8834fcd46339e17fc8add82b5803a1ce53d3d60/stdlib/2and3/builtins.pyi#L1434 -RedirectTemp /15-3 https://github.com/python/typeshed/blob/a8834fcd46339e17fc8add82b5803a1ce53d3d60/stdlib/2and3/builtins.pyi -RedirectTemp /15-4 https://twitter.com/gwidion/status/1265384692464967680 -RedirectTemp /15-5 https://pypi.org/project/pydantic/ -RedirectTemp /15-6 https://google.github.io/pytype/faq.html -RedirectTemp /15-7 https://google.github.io/pytype/faq.html -RedirectTemp /15-8 https://lxml.de/ -RedirectTemp /15-9 https://docs.python.org/3/library/xml.etree.elementtree.html -RedirectTemp /15-10 https://mypy.readthedocs.io/en/stable/common_issues.html -RedirectTemp /15-11 https://mypy.readthedocs.io/en/stable/common_issues.html#types-of-empty-collections -RedirectTemp /15-12 https://github.com/python/typing/issues/182 -RedirectTemp /15-13 https://pypi.org/project/pydantic/ -RedirectTemp /15-14 https://mypy.readthedocs.io/en/stable/type_narrowing.html#casts -RedirectTemp /15-15 https://github.com/python/cpython/blob/bee66d3cb98e740f9d8057eb7f503122052ca5d8/Lib/typing.py#L1340 -RedirectTemp /15-16 https://www.python.org/dev/peps/pep-0484/#casts -RedirectTemp /15-17 https://github.com/python/typeshed/issues/5535 -RedirectTemp /15-18 https://docs.python.org/3/library/asyncio-stream.html#tcp-echo-server-using-streams -RedirectTemp /15-19 https://github.com/python/cpython/blob/b798ab06937f8bb24b444a49dd42e11fff15e654/Lib/test/test_asyncio/test_server.py#L55 -RedirectTemp /15-20 https://en.wikipedia.org/wiki/Code_smell -RedirectTemp /15-21 https://mail.python.org/archives/list/typing-sig@python.org/message/5LCWMN2UY2UQNLC5Z47GHBZKSPZW4I63/ -RedirectTemp /15-22 https://mypy.readthedocs.io/en/stable/error_codes.html#error-codes -RedirectTemp /15-23 https://github.com/fluentpython/example-code-2e/blob/master/15-more-types/clip_annot.py -RedirectTemp /15-24 https://docs.python.org/3/library/typing.html#introspection-helpers -RedirectTemp /15-25 https://docs.python.org/3.10/library/inspect.html#inspect.get_annotations -RedirectTemp /15-26 https://www.python.org/dev/peps/pep-0563/#abstract -RedirectTemp /15-27 https://mail.python.org/archives/list/python-dev@python.org/message/ZBJ7MD6CSGM6LZAOTET7GXAVBZB7O77O/ -RedirectTemp /15-28 https://docs.python.org/3.10/howto/annotations.html -RedirectTemp /15-29 https://docs.python.org/3/library/typing.html#user-defined-generic-types -RedirectTemp /15-30 https://github.com/python/typeshed/blob/bfc83c365a0b26ab16586beac77ff16729d0e473/stdlib/builtins.pyi#L743 -RedirectTemp /15-31 https://docs.python.org/3.10/library/typing.html#typing.FrozenSet -RedirectTemp /15-32 https://docs.python.org/3.10/library/typing.html#typing.Generator -RedirectTemp /15-33 https://docs.python.org/3.10/library/typing.html#typing.AsyncGenerator -RedirectTemp /15-34 https://github.com/python/cpython/blob/46b16d0bdbb1722daed10389e27226a2370f1635/Lib/typing.py#L1786 -RedirectTemp /15-35 https://github.com/python/typeshed/blob/2a9f081abbf01134e4e04ced6a750107db904d70/stdlib/builtins.pyi#L239 -RedirectTemp /15-36 https://www.oreilly.com/library/view/robust-python/9781098100650/ -RedirectTemp /15-37 https://www.python.org/dev/peps/pep-0484/#covariance-and-contravariance -RedirectTemp /15-38 https://mypy.readthedocs.io/en/stable/generics.html#variance-of-generic-types -RedirectTemp /15-39 https://mypy.readthedocs.io/en/stable/common_issues.html#variance -RedirectTemp /15-40 https://www.artima.com/weblogs/viewpost.jsp?thread=85551 -RedirectTemp /15-41 https://dl.acm.org/action/cookieAbsent -RedirectTemp /15-42 http://bracha.org/pluggableTypesPosition.pdf -RedirectTemp /15-43 https://www.researchgate.net/publication/213886116_Static_Typing_Where_Possible_Dynamic_Typing_When_Needed_The_End_of_the_Cold_War_Between_Programming_Languages -RedirectTemp /15-44 https://www.atomickotlin.com/atomickotlin/ -RedirectTemp /15-45 https://www.informit.com/store/effective-java-9780134685991 -RedirectTemp /15-46 https://www.manning.com/books/programming-with-types -RedirectTemp /15-47 https://www.oreilly.com/library/view/programming-typescript/9781492037644/ -RedirectTemp /15-48 https://www.informit.com/store/dart-programming-language-9780321927705 -RedirectTemp /15-49 https://www.yodaiken.com/2017/09/15/bad-ideas-in-type-theory/ -RedirectTemp /15-50 https://www.yodaiken.com/2017/11/30/types-considered-harmful-ii/ -RedirectTemp /15-51 https://web.archive.org/web/20071010002142/http://weblogs.java.net/blog/arnold/archive/2005/06/generics_consid_1.html -RedirectTemp /15-52 https://github.com/python/cpython/blob/3e7ee02327db13e4337374597cdc4458ecb9e3ad/Lib/asyncio/trsock.py#L5 -RedirectTemp /15-53 https://www.python.org/dev/peps/pep-0484/#covariance-and-contravariance -############################################################ 16 -RedirectTemp /16-1 http://www.gotw.ca/publications/c_family_interview.htm -RedirectTemp /16-2 https://docs.python.org/3/reference/expressions.html#unary-arithmetic-and-bitwise-operations -RedirectTemp /16-3 https://docs.python.org/3/reference/datamodel.html#object.__neg__ -RedirectTemp /16-4 https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#boolean-indexing -RedirectTemp /16-5 https://docs.python.org/3/library/collections.html#collections.Counter -RedirectTemp /16-6 https://docs.python.org/3/reference/datamodel.html#emulating-container-types -RedirectTemp /16-7 https://docs.python.org/3/library/numbers.html#implementing-the-arithmetic-operations -RedirectTemp /16-8 https://www.fluentpython.com/lingo/#fail-fast -RedirectTemp /16-9 https://github.com/python/cpython/blob/0bbf30e2b910bc9c5899134ae9d73a8df968da35/Objects/typeobject.c#L4598 -RedirectTemp /16-10 https://neopythonic.blogspot.com/2019/03/why-operators-are-useful.html -RedirectTemp /16-11 https://treyhunner.com/2019/03/python-deep-comparisons-and-code-readability/ -RedirectTemp /16-12 https://docs.python.org/3/library/numbers.html#implementing-the-arithmetic-operations -RedirectTemp /16-13 https://docs.python.org/3/library/pathlib.html -RedirectTemp /16-14 https://pypi.org/project/scapy/ -RedirectTemp /16-15 https://scapy.readthedocs.io/en/latest/usage.html#stacking-layers -RedirectTemp /16-16 https://docs.python.org/3/library/functools.html#functools.total_ordering -RedirectTemp /16-17 https://wiki.illinois.edu//wiki/download/attachments/273416327/ingalls.pdf -RedirectTemp /16-18 https://wiki.illinois.edu//wiki/download/attachments/273416327/double-dispatch.pdf -RedirectTemp /16-19 http://www.gotw.ca/publications/c_family_interview.htm -RedirectTemp /16-20 http://www.gotw.ca/publications/c_family_interview.htm -RedirectTemp /16-21 https://doc.rust-lang.org/std/ops/index.html -RedirectTemp /16-22 https://www.fluentpython.com/lingo/#lazy -############################################################ 17 -RedirectTemp /17-1 http://www.paulgraham.com/icad.html -RedirectTemp /17-2 https://en.wikipedia.org/wiki/Sentinel_value -RedirectTemp /17-3 https://docs.python.org/3.10/library/functions.html#iter -RedirectTemp /17-4 https://docs.python.org/3.10/library/functions.html#iter -RedirectTemp /17-5 https://github.com/python/cpython/blob/b1930bf75f276cd7ca08c4455298128d89adf7d1/Lib/_collections_abc.py#L271 -RedirectTemp /17-6 https://github.com/python/cpython/blob/main/Lib/types.py#L6 -RedirectTemp /17-7 https://en.wikipedia.org/wiki/CLU_(programming_language) -RedirectTemp /17-8 https://docs.python.org/3/glossary.html -RedirectTemp /17-9 https://docs.python.org/3/glossary.html#term-generator-iterator -RedirectTemp /17-10 https://docs.python.org/3/glossary.html#term-generator-expression -RedirectTemp /17-11 https://marc.info/?l=python-list&m=141826925106951&w=2 -RedirectTemp /17-12 https://docs.python.org/3/library/os.html#os.walk -RedirectTemp /17-13 https://docs.python.org/3/library/itertools.html -RedirectTemp /17-14 https://docs.python.org/3/library/exceptions.html#exception-hierarchy -RedirectTemp /17-15 https://en.wikipedia.org/wiki/Depth-first_search -RedirectTemp /17-16 https://docs.python.org/3.10/library/typing.html#typing.TypeAlias -RedirectTemp /17-17 https://docs.python.org/3/library/typing.html#typing.Generator -RedirectTemp /17-18 http://www.dabeaz.com/coroutines/Coroutines.pdf -RedirectTemp /17-19 http://www.dabeaz.com/coroutines/Coroutines.pdf -RedirectTemp /17-20 https://mail.python.org/pipermail/python-ideas/2009-April/003841.html -RedirectTemp /17-21 https://mail.python.org/pipermail/python-ideas/2009-April/003912.html -RedirectTemp /17-22 https://docs.python.org/3/library/exceptions.html#StopIteration -RedirectTemp /17-23 https://docs.python.org/3/reference/expressions.html#yield-expressions -RedirectTemp /17-24 https://docs.python.org/3/reference/index.html -RedirectTemp /17-25 https://github.com/python/cpython/blob/6f743e7a4da904f61dfa84cc7d7385e4dcc79ac5/Lib/typing.py#L2060 -RedirectTemp /17-26 http://catb.org/~esr/jargon/html/G/grok.html -RedirectTemp /17-27 https://docs.python.org/3/reference/expressions.html#yieldexpr -RedirectTemp /17-28 https://docs.python.org/3/library/itertools.html -RedirectTemp /17-29 https://docs.python.org/3/library/itertools.html#itertools-recipes -RedirectTemp /17-30 https://more-itertools.readthedocs.io/en/stable/index.html -RedirectTemp /17-31 https://rittau.org/2006/11/java-iterators-are-not-iterable/ -RedirectTemp /17-32 https://docs.python.org/3/whatsnew/3.3.html#pep-380-syntax-for-delegating-to-a-subgenerator -RedirectTemp /17-33 http://www.dabeaz.com/generators/ -RedirectTemp /17-34 http://www.dabeaz.com/coroutines/ -RedirectTemp /17-35 https://archive.org/details/pyvideo_213___pycon-2009-a-curious-course-on-coroutines-and-concurrency-part-1-of-3 -RedirectTemp /17-36 https://archive.org/details/pyvideo_215___pycon-2009-a-curious-course-on-coroutines-and-concurrency-part-2-of-3 -RedirectTemp /17-37 https://archive.org/details/pyvideo_214___pycon-2009-a-curious-course-on-coroutines-and-concurrency-part-3-of-3 -RedirectTemp /17-38 http://www.dabeaz.com/finalgenerator/ -RedirectTemp /17-39 https://web.archive.org/web/20200218150637/http://seriously.dontusethiscode.com/2013/05/01/greedy-coroutine.html -RedirectTemp /17-40 https://effectivepython.com/ -RedirectTemp /17-41 https://effectivepython.com/2015/03/10/consider-coroutines-to-run-many-functions-concurrently -RedirectTemp /17-42 https://en.wikipedia.org/wiki/Conway's_Game_of_Life -RedirectTemp /17-43 https://gist.github.com/ramalho/da5590bc38c973408839 -RedirectTemp /17-44 https://gist.github.com/ramalho/da5590bc38c973408839 -RedirectTemp /17-45 https://journal.code4lib.org/articles/4893 -RedirectTemp /17-46 https://github.com/fluentpython/isis2json -RedirectTemp /17-47 https://github.com/fluentpython/isis2json/blob/master/README.rst -############################################################ 18 -RedirectTemp /18-1 https://pyvideo.org/video/1669/keynote-3/ -RedirectTemp /18-2 https://docs.python.org/3/library/sqlite3.html#using-the-connection-as-a-context-manager -RedirectTemp /18-3 https://docs.python.org/3/library/threading.html#using-locks-conditions-and-semaphores-in-the-with-statement -RedirectTemp /18-4 https://docs.python.org/3/library/decimal.html#decimal.localcontext -RedirectTemp /18-5 https://docs.python.org/3/library/unittest.mock.html#patch -RedirectTemp /18-6 https://docs.python.org/3/library/contextlib.html#contextlib.redirect_stdout -RedirectTemp /18-7 https://docs.python.org/3/library/sys.html#sys.exc_info -RedirectTemp /18-8 https://en.wikipedia.org/wiki/LL_parser -RedirectTemp /18-9 https://docs.python.org/3/library/contextlib.html -RedirectTemp /18-10 https://github.com/python/cpython/blob/8afab2ebbc1b343cd88d058914cf622fe687a2be/Lib/contextlib.py#L123 -RedirectTemp /18-11 https://www.zopatista.com/python/2013/11/26/inplace-file-rewriting/ -RedirectTemp /18-12 https://docs.python.org/3/library/fileinput.html#fileinput.input -RedirectTemp /18-13 https://www.zopatista.com/python/2013/11/26/inplace-file-rewriting/ -RedirectTemp /18-14 https://en.wikipedia.org/wiki/Euclidean_algorithm -RedirectTemp /18-15 https://github.com/fluentpython/example-code-2e/tree/master/18-with-match/lispy/py3.10/ -RedirectTemp /18-16 https://github.com/fluentpython/example-code-2e/blob/master/18-with-match/lispy/original/lispy.py -RedirectTemp /18-17 https://github.com/fluentpython/example-code-2e/blob/6527037ae7319ba370a1ee2d9fe79214d0ed9452/18-with-match/lispy/py3.10/lis.py#L35 -RedirectTemp /18-18 https://github.com/fluentpython/example-code-2e/blob/master/18-with-match/lispy/py3.10/examples_test.py -RedirectTemp /18-19 https://github.com/python/typeshed/issues/6042 -RedirectTemp /18-20 https://github.com/fluentpython/lispy/tree/main/mylis -RedirectTemp /18-21 https://github.com/fluentpython/example-code-2e/blob/00e4741926e1b771ee7c753148b1415c0bd12e39/02-array-seq/lispy/py3.10/examples_test.py -RedirectTemp /18-22 https://mitpress.mit.edu/sites/default/files/sicp/index.html -RedirectTemp /18-23 https://github.com/fluentpython/example-code-2e/blob/master/18-with-match/lispy/py3.10/examples_test.py -RedirectTemp /18-24 https://github.com/fluentpython/example-code-2e/blob/master/18-with-match/lispy/py3.10/lis.py -RedirectTemp /18-25 https://www.python.org/dev/peps/pep-0634/#or-patterns -RedirectTemp /18-26 https://en.wikipedia.org/wiki/Lambda#Character_encodings -RedirectTemp /18-27 https://docs.python.org/3/reference/compound_stmts.html -RedirectTemp /18-28 https://docs.python.org/3/glossary.html#term-eafp -RedirectTemp /18-29 https://speakerdeck.com/pyconslides/pycon-keynote-python-is-awesome-by-raymond-hettinger?slide=21 -RedirectTemp /18-30 https://docs.python.org/3/reference/compound_stmts.html -RedirectTemp /18-31 https://stackoverflow.com/questions/16138232/is-it-a-good-practice-to-use-try-except-else-in-python -RedirectTemp /18-32 https://docs.python.org/3/library/stdtypes.html#typecontextmanager -RedirectTemp /18-33 https://docs.python.org/3/reference/datamodel.html#with-statement-context-managers -RedirectTemp /18-34 https://speakerdeck.com/pyconslides/pycon-keynote-python-is-awesome-by-raymond-hettinger?slide=21 -RedirectTemp /18-35 https://speakerdeck.com/pyconslides/transforming-code-into-beautiful-idiomatic-python-by-raymond-hettinger-1?slide=34 -RedirectTemp /18-36 https://preshing.com/20110920/the-python-with-statement-by-example/ -RedirectTemp /18-37 https://www.rath.org/on-the-beauty-of-pythons-exitstack.html -RedirectTemp /18-38 https://norvig.com/lispy.html -RedirectTemp /18-39 https://norvig.com/lispy2.html -RedirectTemp /18-40 https://github.com/norvig/pytudes -RedirectTemp /18-41 https://github.com/fluentpython/lispy -RedirectTemp /18-42 https://racket-lang.org/ -RedirectTemp /18-43 https://pyvideo.org/video/1669/keynote-3/ -RedirectTemp /18-44 https://en.wikipedia.org/wiki/Tail_call -RedirectTemp /18-45 https://2ality.com/2015/06/tail-call-optimization.html -RedirectTemp /18-46 https://github.com/fluentpython/example-code-2e/blob/master/18-with-match/lispy/original/lis.py -RedirectTemp /18-47 https://github.com/fluentpython/example-code-2e/blob/master/18-with-match/lispy/original/lispy.py -RedirectTemp /18-48 http://neopythonic.blogspot.com/2009/04/final-words-on-tail-calls.html -RedirectTemp /18-49 https://webkit.org/blog/6240/ecmascript-6-proper-tail-calls-in-webkit/ -RedirectTemp /18-50 http://kangax.github.io/compat-table/es6/ -RedirectTemp /18-51 https://world.hey.com/mgmarlow/what-happened-to-proper-tail-calls-in-javascript-5494c256 -RedirectTemp /18-52 http://neopythonic.blogspot.com/2009/04/tail-recursion-elimination.html -RedirectTemp /18-53 https://github.com/fluentpython/lispy/blob/main/mylis/mylis_2/lis.py -RedirectTemp /18-54 https://github.com/fluentpython/lispy/tree/main/mylis -############################################################ 19 -RedirectTemp /19-1 https://go.dev/blog/waza-talk -RedirectTemp /19-2 https://en.wikipedia.org/wiki/Graphics_processing_unit -RedirectTemp /19-3 https://docs.python.org/3/library/sys.html#sys.getswitchinterval -RedirectTemp /19-4 https://docs.python.org/3/library/sys.html#sys.setswitchinterval -RedirectTemp /19-5 https://en.wikipedia.org/wiki/System_call -RedirectTemp /19-6 https://mail.python.org/pipermail/python-dev/2009-October/093356.html -RedirectTemp /19-7 http://www.dabeaz.com/finalgenerator/ -RedirectTemp /19-8 https://docs.python.org/3/library/threading.html#thread-objects -RedirectTemp /19-9 https://www.pypy.org/ -RedirectTemp /19-10 https://mail.python.org/pipermail/python-list/2009-February/675659.html -RedirectTemp /19-11 https://en.wikipedia.org/wiki/Braille_Patterns -RedirectTemp /19-12 https://docs.python.org/3/library/multiprocessing.shared_memory.html -RedirectTemp /19-13 https://docs.python.org/3/library/multiprocessing.shared_memory.html#multiprocessing.shared_memory.ShareableList -RedirectTemp /19-14 https://greenlet.readthedocs.io/en/latest/ -RedirectTemp /19-15 https://docs.sqlalchemy.org/en/14/changelog/migration_14.html#asynchronous-io-support-for-core-and-orm -RedirectTemp /19-16 https://docs.sqlalchemy.org/en/14/orm/extensions/asyncio.html -RedirectTemp /19-17 http://www.gevent.org/ -RedirectTemp /19-18 https://github.com/gevent/gevent/wiki/Projects -RedirectTemp /19-19 https://docs.python.org/3/library/concurrent.futures.html#processpoolexecutor-example -RedirectTemp /19-20 https://github.com/python/asyncio/issues/284 -RedirectTemp /19-21 https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.loop.run_in_executor -RedirectTemp /19-22 https://mail.python.org/archives/list/python-dev@python.org/message/JBYXQH3NV3YBF7P2HLHB5CD6V3GVTY55/ -RedirectTemp /19-23 https://docs.python.org/3/library/queue.html#queue.SimpleQueue.get -RedirectTemp /19-24 https://en.wikipedia.org/wiki/Race_condition -RedirectTemp /19-25 https://github.com/fluentpython/example-code-2e/commit/2c1230579db99738a5e5e6802063bda585f6476d -RedirectTemp /19-26 https://github.com/fluentpython/example-code-2e/blob/master/19-concurrency/primes/README.md -RedirectTemp /19-27 https://github.com/fluentpython/example-code-2e/blob/master/19-concurrency/primes/threads.py -RedirectTemp /19-28 https://en.wikipedia.org/wiki/Context_switch -RedirectTemp /19-29 http://www.gotw.ca/publications/concurrency-ddj.htm -RedirectTemp /19-30 https://www.ansible.com/ -RedirectTemp /19-31 https://saltproject.io/ -RedirectTemp /19-32 https://www.fabfile.org/ -RedirectTemp /19-33 https://engineering.fb.com/2016/05/27/production-engineering/python-in-production-engineering/ -RedirectTemp /19-34 https://jupyter.org/ -RedirectTemp /19-35 https://docs.bokeh.org/en/latest/index.html -RedirectTemp /19-36 https://www.tensorflow.org/ -RedirectTemp /19-37 https://pytorch.org/ -RedirectTemp /19-38 https://www.oreilly.com/radar/where-programming-ops-ai-and-the-cloud-are-headed-in-2021/ -RedirectTemp /19-39 https://www.youtube.com/watch?v=ods97a5Pzw0 -RedirectTemp /19-40 https://www.thoughtworks.com/radar/techniques/high-performance-envy-web-scale-envy -RedirectTemp /19-41 https://modwsgi.readthedocs.io/en/master/ -RedirectTemp /19-42 https://uwsgi-docs.readthedocs.io/en/latest/ -RedirectTemp /19-43 https://unit.nginx.org/ -RedirectTemp /19-44 https://www.techatbloomberg.com/blog/configuring-uwsgi-production-deployment/ -RedirectTemp /19-45 https://www.youtube.com/watch?v=p6R1h2Nn468 -RedirectTemp /19-46 https://asgi.readthedocs.io/en/latest/index.html -RedirectTemp /19-47 https://docs.celeryproject.org/en/stable/getting-started/introduction.html -RedirectTemp /19-48 https://python-rq.org/ -RedirectTemp /19-49 https://docs.celeryproject.org/en/stable/faq.html#what-kinds-of-things-should-i-use-celery-for -RedirectTemp /19-50 https://redis.io/ -RedirectTemp /19-51 https://realpython.com/intro-to-python-threading/ -RedirectTemp /19-52 https://pymotw.com/3/concurrency.html -RedirectTemp /19-53 https://www.pearson.com/us/higher-education/program/Hellmann-Python-3-Standard-Library-by-Example-The/PGM328871.html -RedirectTemp /19-54 https://docs.python.org/3/library/multiprocessing.html#programming-guidelines -RedirectTemp /19-55 https://docs.python.org/3/library/multiprocessing.html -RedirectTemp /19-56 https://www.oreilly.com/library/view/high-performance-python/9781492055013/ -RedirectTemp /19-57 https://link.springer.com/book/10.1007/978-1-4842-5793-7?error=cookies_not_supported&code=2ed5d61d-ae9f-4f3d-94ac-0f68cf45ea4f -RedirectTemp /19-58 https://www.packtpub.com/product/parallel-programming-with-python/9781783288397 -RedirectTemp /19-59 https://greenteapress.com/wp/semaphores/ -RedirectTemp /19-60 https://docs.python.org/3/c-api/init.html#thread-state-and-the-global-interpreter-lock -RedirectTemp /19-61 https://docs.python.org/3/faq/library.html#can-t-we-get-rid-of-the-global-interpreter-lock -RedirectTemp /19-62 https://www.artima.com/weblogs/viewpost.jsp?thread=214235 -RedirectTemp /19-63 http://jessenoller.com/blog/2009/02/01/python-threads-and-the-global-interpreter-lock -RedirectTemp /19-64 https://realpython.com/products/cpython-internals-book/ -RedirectTemp /19-65 http://www.dabeaz.com/GIL/ -RedirectTemp /19-66 http://www.dabeaz.com/python/UnderstandingGIL.pdf -RedirectTemp /19-67 https://bugs.python.org/issue7946#msg223110 -RedirectTemp /19-68 https://bugs.python.org/issue7946 -RedirectTemp /19-69 https://www.fullstackpython.com/ -RedirectTemp /19-70 https://www.oreilly.com/library/view/high-performance-python/9781492055013/ -RedirectTemp /19-71 https://www.packtpub.com/product/parallel-programming-with-python/9781783288397 -RedirectTemp /19-72 https://www.packtpub.com/product/distributed-computing-with-python/9781785889691 -RedirectTemp /19-73 https://towardsdatascience.com/python-performance-and-gpus-1be860ffd58d?gi=a7d537cc2fb4 -RedirectTemp /19-74 https://instagram-engineering.com/web-service-efficiency-at-instagram-with-python-4976d078e366?gi=12a441991c88 -RedirectTemp /19-75 https://www.oreilly.com/library/view/architecture-patterns-with/9781492052197/ -RedirectTemp /19-76 https://www.cosmicpython.com/ -RedirectTemp /19-77 https://pypi.org/project/lelo/ -RedirectTemp /19-78 https://github.com/npryce/python-parallelize -RedirectTemp /19-79 https://github.com/ericsnowcurrently/multi-core-python/wiki -RedirectTemp /19-80 https://gist.github.com/markshannon/79cace3656b40e21b7021504daee950c -RedirectTemp /19-81 https://mail.python.org/archives/list/python-dev@python.org/message/YOOQZCFOKEPQ24YHWWLQSJ3RCXFMS7D7/ -RedirectTemp /19-82 https://en.wikipedia.org/wiki/Communicating_sequential_processes -RedirectTemp /19-83 https://github.com/stackless-dev/stackless/wiki -RedirectTemp /19-84 https://www.eveonline.com -RedirectTemp /19-85 https://www.ccpgames.com/ -RedirectTemp /19-86 https://stackless.readthedocs.io/en/3.6-slp/stackless-python.html#history -RedirectTemp /19-87 https://doc.pypy.org/en/latest/stackless.html -RedirectTemp /19-88 https://greenlet.readthedocs.io/en/latest/ -RedirectTemp /19-89 http://www.gevent.org/ -RedirectTemp /19-90 http://thespianpy.com/doc/ -RedirectTemp /19-91 https://pykka.readthedocs.io/en/latest/ -RedirectTemp /19-92 https://www.manning.com/books/rabbitmq-in-action -RedirectTemp /19-93 https://pragprog.com/titles/pb7con/seven-concurrency-models-in-seven-weeks/ -RedirectTemp /19-94 https://en.wikipedia.org/wiki/OpenCL -RedirectTemp /19-95 https://media.pragprog.com/titles/pb7con/Bonus_Chapter.pdf -RedirectTemp /19-96 https://martinfowler.com/ -RedirectTemp /19-97 https://martinfowler.com/articles/patterns-of-distributed-systems/ -RedirectTemp /19-98 https://www.oreilly.com/library/view/designing-data-intensive-applications/9781491903063/ -RedirectTemp /19-99 https://www.oreilly.com/library/view/oscon-2016-video/9781491965153/video247021.html -RedirectTemp /19-100 https://www.oreilly.com/library/view/designing-for-scalability/9781449361556/ -RedirectTemp /19-101 https://www.thoughtworks.com/radar/techniques/high-performance-envy-web-scale-envy -RedirectTemp /19-102 https://en.wikipedia.org/wiki/KISS_principle -RedirectTemp /19-103 https://www.usenix.org/conference/hotos15/workshop-program/presentation/mcsherry -############################################################ 20 -RedirectTemp /20-1 https://www.artima.com/weblogs/viewpost.jsp?thread=299551 -RedirectTemp /20-2 https://docs.python.org/3/library/http.server.html -RedirectTemp /20-3 https://www.youtube.com/watch?v=A9e9Cy1UkME -RedirectTemp /20-4 https://www.cia.gov/the-world-factbook/ -RedirectTemp /20-5 https://docs.python-requests.org/en/latest/ -RedirectTemp /20-6 https://docs.python.org/3.10/library/concurrent.futures.html#concurrent.futures.ThreadPoolExecutor -RedirectTemp /20-7 https://docs.python.org/3/library/concurrent.futures.html#concurrent.futures.as_completed -RedirectTemp /20-8 https://docs.python.org/3/library/concurrent.futures.html -RedirectTemp /20-9 https://docs.python.org/3.10/library/concurrent.futures.html#concurrent.futures.Executor -RedirectTemp /20-10 https://github.com/fluentpython/example-code-2e/blob/master/20-executors/getflags/flags2_common.py -RedirectTemp /20-11 https://github.com/noamraph/tqdm -RedirectTemp /20-12 https://www.youtube.com/watch?v=M8Z65tAl5l4 -RedirectTemp /20-13 https://github.com/noamraph/tqdm/blob/master/README.md -RedirectTemp /20-14 https://docs.python.org/3/library/concurrent.futures.html#concurrent.futures.as_completed -RedirectTemp /20-15 https://docs.python.org/3/library/asyncio-task.html#asyncio.as_completed -RedirectTemp /20-16 https://www.cloudflare.com/ -RedirectTemp /20-17 https://github.com/fluentpython/example-code-2e/tree/master/20-executors/getflags -RedirectTemp /20-18 https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/418 -RedirectTemp /20-19 https://en.wikipedia.org/wiki/Embarrassingly_parallel -RedirectTemp /20-20 https://pyvideo.org/video/480/pyconau-2010--the-future-is-soon/ -RedirectTemp /20-21 http://www.dabeaz.com/coroutines/ -RedirectTemp /20-22 https://en.wikipedia.org/wiki/POSIX_Threads -RedirectTemp /20-23 https://en.wikipedia.org/wiki/C_dynamic_memory_allocation -RedirectTemp /20-24 https://pragprog.com/titles/pb7con/seven-concurrency-models-in-seven-weeks/ -RedirectTemp /20-25 https://hexdocs.pm/ecto/getting-started.html -############################################################ 21 -RedirectTemp /21-1 https://docs.python.org/3/library/asyncio.html -RedirectTemp /21-2 https://bugs.python.org/issue43216 -RedirectTemp /21-3 https://bugs.python.org/issue36921 -RedirectTemp /21-4 https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.loop.getaddrinfo -RedirectTemp /21-5 https://docs.python.org/3/library/socket.html#socket.getaddrinfo -RedirectTemp /21-6 https://docs.python.org/3.10/library/asyncio-eventloop.html#asyncio.get_event_loop -RedirectTemp /21-7 https://www.python.org/dev/peps/pep-0492/#await-expression -RedirectTemp /21-8 https://www.fluentpython.com/extra/classic-coroutines/#yield_from_meaning_sec -RedirectTemp /21-9 https://github.com/fluentpython/example-code-2e/tree/master/20-executors/getflags -RedirectTemp /21-10 https://magicstack.github.io/asyncpg/current/ -RedirectTemp /21-11 https://magicstack.github.io/asyncpg/current/api/index.html#transactions -RedirectTemp /21-12 https://magicstack.github.io/asyncpg/current/api/index.html#transactions -RedirectTemp /21-13 https://github.com/MagicStack/asyncpg/blob/4d39a05268ce4cc01b00458223a767542da048b8/asyncpg/transaction.py#L57 -RedirectTemp /21-14 https://magicstack.github.io/asyncpg/current/usage.html#connection-pools -RedirectTemp /21-15 https://gist.github.com/jboner/2841832 -RedirectTemp /21-16 https://en.wikipedia.org/wiki/Network-attached_storage -RedirectTemp /21-17 https://en.wikipedia.org/wiki/Semaphore_(programming) -RedirectTemp /21-18 https://en.wikipedia.org/wiki/Semaphore_(programming) -RedirectTemp /21-19 https://groups.google.com/forum/#!msg/python-tulip/PdAEtwpaJHs/7fqb-Qj2zJoJ -RedirectTemp /21-20 https://web.archive.org/web/20151209151711/http://tritarget.org/blog/2012/11/28/the-pyramid-of-doom-a-javascript-style-trap -RedirectTemp /21-21 https://stackoverflow.com/questions/53701841/what-is-the-use-case-for-future-add-done-callback/53710563 -RedirectTemp /21-22 https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.loop.run_in_executor -RedirectTemp /21-23 https://motor.readthedocs.io/en/stable/ -RedirectTemp /21-24 https://emptysqua.re/blog/response-to-asynchronous-python-and-databases/ -RedirectTemp /21-25 https://docs.python.org/3/library/asyncio-stream.html#tcp-echo-server-using-streams -RedirectTemp /21-26 https://en.wikipedia.org/wiki/Phaistos_Disc -RedirectTemp /21-27 https://en.wikipedia.org/wiki/Inverted_index -RedirectTemp /21-28 https://fastapi.tiangolo.com/ -RedirectTemp /21-29 https://swagger.io/specification/ -RedirectTemp /21-30 https://asgi.readthedocs.io/en/latest/implementations.html -RedirectTemp /21-31 https://pydantic-docs.helpmanual.io/ -RedirectTemp /21-32 https://doc.traefik.io/traefik/ -RedirectTemp /21-33 https://fastapi.tiangolo.com/project-generation/ -RedirectTemp /21-34 https://fastapi.tiangolo.com/tutorial/response-model/ -RedirectTemp /21-35 https://docs.python.org/3/library/asyncio-stream.html#asyncio.start_server -RedirectTemp /21-36 https://github.com/python/typeshed/issues/5535 -RedirectTemp /21-37 https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.Server.serve_forever -RedirectTemp /21-38 https://docs.python.org/3/library/asyncio-stream.html#asyncio.StreamWriter.close -RedirectTemp /21-39 https://docs.python.org/3/library/asyncio-stream.html#streamwriter -RedirectTemp /21-40 https://docs.python.org/3/library/asyncio-stream.html -RedirectTemp /21-41 https://docs.python.org/3/library/asyncio-protocol.html -RedirectTemp /21-42 https://docs.python.org/3/library/asyncio-protocol.html#tcp-echo-server -RedirectTemp /21-43 https://github.com/aio-libs/aiopg -RedirectTemp /21-44 https://docs.python.org/3/whatsnew/3.8.html#asyncio -RedirectTemp /21-45 https://datatracker.ietf.org/doc/html/rfc6761 -RedirectTemp /21-46 https://docs.python.org/3/library/contextlib.html#contextlib.asynccontextmanager -RedirectTemp /21-47 https://docs.python.org/3/library/contextlib.html#contextlib.asynccontextmanager -RedirectTemp /21-48 https://docs.python.org/3/library/asyncio-task.html#asyncio.gather -RedirectTemp /21-49 https://curio.readthedocs.io/en/latest/index.html -RedirectTemp /21-50 https://curio.readthedocs.io/en/latest/reference.html#task-groups -RedirectTemp /21-51 https://en.wikipedia.org/wiki/Structured_concurrency -RedirectTemp /21-52 https://mail.python.org/archives/list/python-dev@python.org/thread/2ORDAW74LGE3ZI2QETPJRT2ZL7MCCPG2/ -RedirectTemp /21-53 https://www.python.org/dev/peps/pep-0654/#motivation -RedirectTemp /21-54 https://curio.readthedocs.io/en/latest/reference.html#AWAIT -RedirectTemp /21-55 https://www.python-httpx.org/async/#curio -RedirectTemp /21-56 https://github.com/dabeaz/curio/tree/78bca8a6ad677ef51e1568ac7b3e51441ab49c42/examples -RedirectTemp /21-57 https://datatracker.ietf.org/doc/html/rfc8305 -RedirectTemp /21-58 https://trio.readthedocs.io/en/stable/ -RedirectTemp /21-59 https://www.youtube.com/watch?v=M-sc73Y-zQA -RedirectTemp /21-60 https://en.wikipedia.org/wiki/Technical_debt -RedirectTemp /21-61 https://www.youtube.com/watch?v=E-1Y4kSsAFc -RedirectTemp /21-62 https://github.com/dabeaz/curio -RedirectTemp /21-63 https://trio.readthedocs.io/en/stable/ -RedirectTemp /21-64 https://curio.readthedocs.io/en/latest/#curio-university -RedirectTemp /21-65 https://vorpus.org/blog/some-thoughts-on-asynchronous-api-design-in-a-post-asyncawait-world/ -RedirectTemp /21-66 https://vorpus.org/blog/notes-on-structured-concurrency-or-go-statement-considered-harmful/ -RedirectTemp /21-67 https://stackoverflow.com/questions/49482969/what-is-the-core-difference-between-asyncio-and-trio -RedirectTemp /21-68 https://docs.python.org/3/library/asyncio.html -RedirectTemp /21-69 https://bugs.python.org/issue33649 -RedirectTemp /21-70 https://docs.python.org/3/library/asyncio-dev.html -RedirectTemp /21-71 https://www.youtube.com/watch?v=iG6fr81xHKA -RedirectTemp /21-72 https://www.youtube.com/watch?v=F19R_M4Nay4 -RedirectTemp /21-73 https://asherman.io/projects/unsync.html -RedirectTemp /21-74 https://pyladies.com/ -RedirectTemp /21-75 https://www.youtube.com/watch?v=sW76-pRkZk8 -RedirectTemp /21-76 https://www.youtube.com/watch?v=Xbl7XjFYsN4 -RedirectTemp /21-77 https://www.youtube.com/watch?v=02CLD-42VdI -RedirectTemp /21-78 https://micropython.org/ -RedirectTemp /21-79 https://docs.micropython.org/en/latest/library/uasyncio.html -RedirectTemp /21-80 https://www.encode.io/articles/python-async-frameworks-beyond-developer-tribalism -RedirectTemp /21-81 https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/ -RedirectTemp /21-82 https://vorpus.org/blog/notes-on-structured-concurrency-or-go-statement-considered-harmful/ -RedirectTemp /21-83 https://github.com/MagicStack/uvloop -RedirectTemp /21-84 http://magic.io/blog/uvloop-blazing-fast-python-networking/ -RedirectTemp /21-85 https://github.com/MagicStack/httptools -RedirectTemp /21-86 https://docs.aiohttp.org/en/stable/ -RedirectTemp /21-87 https://github.com/wg/wrk -RedirectTemp /21-88 https://twistedmatrix.com/trac/ -############################################################ 22 -RedirectTemp /22-1 https://github.com/fluentpython/example-code-2e/blob/master/22-dyn-attr-prop/oscon/data/osconfeed.json -RedirectTemp /22-2 https://pypi.org/project/attrdict/ -RedirectTemp /22-3 https://pypi.org/project/addict/ -RedirectTemp /22-4 https://github.com/ActiveState/code/tree/master/recipes/Python/52308_simple_but_handy_collector_bunch_named_stuff -RedirectTemp /22-5 https://docs.python.org/3/library/types.html#types.SimpleNamespace -RedirectTemp /22-6 https://docs.python.org/3/library/argparse.html#argparse.Namespace -RedirectTemp /22-7 https://docs.python.org/3/library/multiprocessing.html#multiprocessing.managers.Namespace -RedirectTemp /22-8 https://github.com/fluentpython/example-code-2e/blob/master/22-dyn-attr-prop/oscon/schedule_v2.py -RedirectTemp /22-9 https://docs.python.org/3/library/functools.html#functools.cached_property -RedirectTemp /22-10 https://docs.python.org/3/library/functools.html#functools.cached_property -RedirectTemp /22-11 https://bugs.python.org/issue42781 -RedirectTemp /22-12 https://docs.python.org/3/howto/descriptor.html -RedirectTemp /22-13 https://github.com/python/cpython/blob/e6d0107e13ed957109e79b796984d3d026a8660d/Lib/functools.py#L926 -RedirectTemp /22-14 https://docs.python.org/3/library/threading.html#rlock-objects -RedirectTemp /22-15 https://docs.python.org/3.10/library/functools.html#functools.cached_property -RedirectTemp /22-16 https://www.wsj.com/articles/SB10001424052970203914304576627102996831200 -RedirectTemp /22-17 https://www.youtube.com/watch?v=s35rVw1zskA&feature=youtu.be -RedirectTemp /22-18 https://docs.python.org/3/library/functions.html#dir -RedirectTemp /22-19 https://github.com/python/cpython/blob/19903085c3ad7a17c8047e1556c700f2eb109931/Lib/cmd.py#L214 -RedirectTemp /22-20 https://docs.python.org/3/library/functions.html#hasattr -RedirectTemp /22-21 https://docs.python.org/3.10/reference/datamodel.html#special-method-lookup -RedirectTemp /22-22 https://docs.python.org/3/library/functions.html -RedirectTemp /22-23 https://docs.python.org/3/reference/datamodel.html#customizing-attribute-access -RedirectTemp /22-24 https://docs.python.org/3/reference/datamodel.html#special-method-lookup -RedirectTemp /22-25 https://docs.python.org/3/library/stdtypes.html#special-attributes -RedirectTemp /22-26 http://wiki.c2.com/?WelcomeVisitors -RedirectTemp /22-27 http://wiki.c2.com/?UniformAccessPrinciple -RedirectTemp /22-28 https://docs.oracle.com/javase/tutorial/java/javaOO/accesscontrol.html -RedirectTemp /22-29 http://www.pingo.io/docs/ -RedirectTemp /22-30 https://www.drdobbs.com/javas-new-considered-harmful/184405016 -RedirectTemp /22-31 https://www.python.org/dev/peps/pep-0008/#class-names -############################################################ 23 -RedirectTemp /23-1 http://www.aleax.it/goo_pydp.pdf -RedirectTemp /23-2 https://docs.python.org/3.10/reference/datamodel.html#implementing-descriptors -RedirectTemp /23-3 https://docs.python.org/3/howto/descriptor.html -RedirectTemp /23-4 https://docs.python.org/3/howto/ -RedirectTemp /23-5 http://www.aleax.it/Python/nylug05_om.pdf -RedirectTemp /23-6 https://www.youtube.com/watch?v=VOzvpHoYQoo -RedirectTemp /23-7 https://www.python.org/dev/peps/pep-0487/#trait-descriptors -RedirectTemp /23-8 https://dreamsongs.com/RiseOfWorseIsBetter.html -RedirectTemp /23-9 http://web.archive.org/web/20031002184114/www.amk.ca/python/writing/warts.html -RedirectTemp /23-10 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this -RedirectTemp /23-11 http://python-history.blogspot.com/2009/02/adding-support-for-user-defined-classes.html -############################################################ 24 -RedirectTemp /24-1 https://docs.python.org/3/library/stdtypes.html#special-attributes -RedirectTemp /24-2 https://docs.djangoproject.com/en/3.2/topics/db/models/#meta-options -RedirectTemp /24-3 https://www.python.org/dev/peps/pep-3155/ -RedirectTemp /24-4 https://github.com/python/cpython/blob/3.9/Lib/collections/__init__.py -RedirectTemp /24-5 https://en.wikipedia.org/wiki/Tony_Hoare#Apologies_and_retractions -RedirectTemp /24-6 https://go.dev/tour/basics/12 -RedirectTemp /24-7 https://bugs.python.org/issue42102 -RedirectTemp /24-8 https://docs.python.org/3/reference/datamodel.html#object.__init_subclass__ -RedirectTemp /24-9 https://www.python.org/dev/peps/pep-0557/#abstract -RedirectTemp /24-10 https://github.com/python/cpython/blob/3.9/Lib/dataclasses.py -RedirectTemp /24-11 https://docs.python.org/3/reference/datamodel.html#creating-the-class-object -RedirectTemp /24-12 https://mail.python.org/pipermail/python-list/2002-December/134521.html -RedirectTemp /24-13 https://mail.python.org/pipermail/python-list/2002-July/162558.html -RedirectTemp /24-14 https://github.com/fluentpython/example-code/tree/master/21-class-metaprog/bulkfood -RedirectTemp /24-15 https://en.wikipedia.org/wiki/Principle_of_least_astonishment -RedirectTemp /24-16 https://docs.python.org/3/reference/datamodel.html#object.__class_getitem__ -RedirectTemp /24-17 https://en.wikipedia.org/wiki/Trait_(computer_programming) -RedirectTemp /24-18 https://en.wikipedia.org/wiki/Aspect-oriented_programming -RedirectTemp /24-19 https://dhh.dk/arc/000416.html -RedirectTemp /24-20 https://github.com/cjrh/autoslot -RedirectTemp /24-21 https://docs.python.org/3/reference/datamodel.html#customizing-class-creation -RedirectTemp /24-22 https://docs.python.org/3/library/functions.html#type -RedirectTemp /24-23 https://docs.python.org/3/library/stdtypes.html#special-attributes -RedirectTemp /24-24 https://docs.python.org/3/library/types.html -RedirectTemp /24-25 https://www.python.org/dev/peps/pep-3129/ -RedirectTemp /24-26 https://www.youtube.com/watch?v=cAGliEJV9_o -RedirectTemp /24-27 https://docs.python.org/3/library/functools.html#functools.total_ordering -RedirectTemp /24-28 https://www.python.org/download/releases/2.2.3/descrintro/ -RedirectTemp /24-29 https://github.com/lihaoyi/macropy -RedirectTemp /24-30 https://people.eecs.berkeley.edu/~bh/ss-toc2.html diff --git a/links/deploy.sh b/links/deploy.sh deleted file mode 100755 index ced9bd9..0000000 --- a/links/deploy.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/bash -scp FPY.LI.htaccess dh_i4p2ka@fpy.li:~/fpy.li/.htaccess diff --git a/links/sample-urls.txt b/links/sample-urls.txt deleted file mode 100644 index 9eac47c..0000000 --- a/links/sample-urls.txt +++ /dev/null @@ -1,47 +0,0 @@ -https://www.oreilly.com/library/view/fluent-python-2nd/9781492056348/ -https://dask.org/ -http://example.com/1572039572038573208 -http://www.unicode.org/ -https://www.techcrunch.com/2024/startup-funding-trends -https://blog.medium.com/writing-tips-for-beginners -https://github.com/microsoft/typescript -https://stackoverflow.com/questions/javascript-async-await -https://www.reddit.com/r/programming/hot -https://docs.google.com/spreadsheets/create -https://www.youtube.com/watch?v=dQw4w9WgXcQ -https://www.amazon.com/dp/B08N5WRWNW -https://support.apple.com/iphone-setup-guide -https://www.wikipedia.org/wiki/Machine_Learning -https://www.linkedin.com/in/johndoe123 -https://www.instagram.com/p/CxYz123AbC/ -https://twitter.com/elonmusk/status/1234567890 -https://www.facebook.com/events/987654321 -https://drive.google.com/file/d/1AbCdEfGhIjKlMnOp/view -https://www.dropbox.com/s/qwerty123/document.pdf -https://zoom.us/j/1234567890?pwd=abcdef -https://calendly.com/janedoe/30min-meeting -https://www.shopify.com/admin/products/new -https://stripe.com/docs/api/charges/create -https://www.paypal.com/invoice/create -https://mailchimp.com/campaigns/dashboard -https://analytics.google.com/analytics/web/ -https://console.aws.amazon.com/s3/buckets -https://portal.azure.com/dashboard -https://www.figma.com/file/AbCdEf123456/design-system -https://www.notion.so/workspace/project-notes -https://trello.com/b/AbCdEfGh/marketing-board -https://slack.com/app_redirect?channel=general -https://discord.gg/AbCdEfGh123 -https://www.twitch.tv/streamername/videos -https://www.spotify.com/playlist/37i9dQZF1DXcBWIGoYBM5M -https://www.netflix.com/browse/genre/83 -https://www.hulu.com/series/breaking-bad-2008 -https://www.airbnb.com/rooms/12345678 -https://www.booking.com/hotel/us/grand-plaza.html -https://www.expedia.com/flights/search?trip=roundtrip -https://www.uber.com/ride/request -https://www.doordash.com/store/pizza-palace-123 -https://www.grubhub.com/restaurant/tacos-el-rey-456 -https://www.zillow.com/homes/for_sale/San-Francisco-CA -https://www.craigslist.org/about/sites -https://www.python.org/dev/peps/pep-0484/ \ No newline at end of file diff --git a/links/short.htaccess b/links/short.htaccess deleted file mode 100644 index 86fe3e2..0000000 --- a/links/short.htaccess +++ /dev/null @@ -1,14 +0,0 @@ -# content of short.htaccess file created and managed by short.py - -# appended: 2025-05-23 15:12:13 -RedirectTemp /22 https://pythonfluente.com/2/#pattern_matching_case_study_sec -RedirectTemp /23 https://pythonfluente.com/2/#how_slicing_works -RedirectTemp /24 https://pythonfluente.com/2/#sliceable_sequence -RedirectTemp /25 https://pythonfluente.com/2/#virtual_subclass_sec -RedirectTemp /26 https://pythonfluente.com/2/#environment_class_ex -RedirectTemp /27 https://pythonfluente.com/2/#subclass_builtin_woes -RedirectTemp /28 https://pythonfluente.com/2/#slots_section -RedirectTemp /29 https://pythonfluente.com/2/#typeddict_sec -RedirectTemp /2a https://pythonfluente.com/2/#problems_annot_runtime_sec -RedirectTemp /2b https://pythonfluente.com/2/#legacy_deprecated_typing_box -RedirectTemp /2c https://pythonfluente.com/2/#positional_pattern_implement_sec diff --git a/links/short.py b/links/short.py deleted file mode 100755 index d67c71d..0000000 --- a/links/short.py +++ /dev/null @@ -1,95 +0,0 @@ -#!/usr/bin/env python3 - -""" -short.py generates unique short URLs. - -This script reads lines from stdin or files named as arguments, then: - -1. retrieves or creates new short URLs, taking into account existing RedirectTemp - directives in custom.htaccess or short.htaccess; -2. appends RedirectTemp directives for newly created short URLs to short.htaccess; -3. outputs the list of (short, long) URLs retrieved or created. - -""" - -import fileinput -import itertools -from collections.abc import Iterator -from time import strftime - -HTACCESS_CUSTOM = 'custom.htaccess' -HTACCESS_SHORT = 'short.htaccess' -HTACCESS_FILES = (HTACCESS_CUSTOM, HTACCESS_SHORT) -BASE_DOMAIN = 'fpy.li' - - -def load_redirects() -> tuple[dict, dict]: - redirects = {} - targets = {} - for filename in HTACCESS_FILES: - with open(filename) as fp: - for line in fp: - if line.startswith('RedirectTemp'): - _, short, long = line.split() - short = short[1:] # Remove leading slash - assert short not in redirects, f'{filename}: duplicate redirect from {short}' - # htaccess.custom is live since 2022, we can't change it remove duplicate targets - if filename != HTACCESS_CUSTOM: - assert long not in targets, f'{filename}: duplicate redirect to {long}' - redirects[short] = long - targets[long] = short - return redirects, targets - - -SDIGITS = '23456789abcdefghjkmnpqrstvwxyz' - - -def gen_short(start_len=1) -> Iterator[str]: - """Generate every possible sequence of SDIGITS, starting with start_len""" - length = start_len - while True: - for short in itertools.product(SDIGITS, repeat=length): - yield ''.join(short) - length += 1 - - -def gen_unused_short(redirects: dict) -> Iterator[str]: - """Generate next available short URL of len >= 2.""" - for short in gen_short(2): - if short not in redirects: - yield short - - -def shorten(urls: list[str]) -> list[tuple[str, str]]: - """Return (short, long) pairs, appending directives to HTACCESS_SHORT as needed.""" - redirects, targets = load_redirects() - iter_short = gen_unused_short(redirects) - pairs = [] - timestamp = strftime('%Y-%m-%d %H:%M:%S') - with open(HTACCESS_SHORT, 'a') as fp: - for long in urls: - assert BASE_DOMAIN not in long, f'{long} is a {BASE_DOMAIN} URL' - if long in targets: - short = targets[long] - else: - short = next(iter_short) - redirects[short] = long - targets[long] = short - if timestamp: - fp.write(f'\n# appended: {timestamp}\n') - timestamp = None - fp.write(f'RedirectTemp /{short} {long}\n') - pairs.append((short, long)) - - return pairs - - -def main() -> None: - """read URLS from filename arguments or stdin""" - urls = [line.strip() for line in fileinput.input(encoding='utf-8')] - for short, long in shorten(urls): - print(f'{BASE_DOMAIN}/{short}\t{long}') - - -if __name__ == '__main__': - main()

8C3IP(HKX9mR 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�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