diff --git a/Examples/README.rst b/Examples/README.rst deleted file mode 100644 index f5a5ccb3..00000000 --- a/Examples/README.rst +++ /dev/null @@ -1,4 +0,0 @@ -Example code, etc. - -Random stuff here. - diff --git a/Examples/Session01/schedule.py b/Examples/Session01/schedule.py deleted file mode 100644 index 7dcb1300..00000000 --- a/Examples/Session01/schedule.py +++ /dev/null @@ -1,42 +0,0 @@ -""" -Schedule students for lightning talks, fall 2015 -""" -import random - -students = open('students.txt').readlines() - -# remove the header line -del students[0] - -# remove the languages, colon, etc. -students = [line.split(":")[0] for line in students] - -# reverse the first, last names - -# separate them: -students = [line.split(",") for line in students] - -# put them back together -students = ["{} {}".format(first, last) for last, first in students] - -# put them in random order -random.shuffle(students) - -# make a list from 1 to 10 -weeks = list(range(2, 11)) - -# make three of them... -weeks = weeks * 3 - -# put the students together with the weeks -schedule = zip(weeks, students) - -# sort it for output -schedule = sorted(schedule) - -# write it to a file (and print to screen) -with open('schedule.txt', 'w') as outfile: - for week, student in schedule: - line = 'week {}: {}\n'.format(week, student) - print(line) - outfile.write(line) diff --git a/Examples/Session01/schedule.txt b/Examples/Session01/schedule.txt deleted file mode 100644 index 33d73c7e..00000000 --- a/Examples/Session01/schedule.txt +++ /dev/null @@ -1,25 +0,0 @@ -week 2: Bruce Bauman -week 2: Eric Starr Vegors -week 2: Masako Tebbetts -week 3: Marty Pitts -week 3: Maxwell MacCamy -week 3: Michelle Yu -week 4: Joseph Cardenas -week 4: Robert Stevens Alford -week 4: Tiffany Ku -week 5: Andrey Gusev -week 5: Kathleen Devlin -week 5: Pei Lin -week 6: Brendan Fogarty -week 6: Michael Waddle -week 6: Robert Ryan Leslie -week 7: Austin Scara -week 7: Cheryl Ohashi -week 7: Eric Rosko -week 8: Marc Teale -week 8: Michael Cimino -week 8: Ryan Morin -week 9: Gabriel Meringolo -week 9: Ian Cote -week 10: Erica Winberry -week 10: Robert Jenkins diff --git a/Examples/Session01/students.txt b/Examples/Session01/students.txt deleted file mode 100644 index c44b2f27..00000000 --- a/Examples/Session01/students.txt +++ /dev/null @@ -1,26 +0,0 @@ -name: languages -Alford, Robert Stevens: -Bauman, Bruce: -Cardenas, Joseph: -Cimino, Michael: -Cote, Ian: -Devlin, Kathleen: -Fogarty, Brendan: -Gusev, Andrey: -Jenkins, Robert: -Ku, Tiffany: -Leslie, Robert Ryan: -Lin, Pei: -MacCamy, Maxwell: -Meringolo, Gabriel: -Morin, Ryan: -Ohashi, Cheryl: -Pitts, Marty: -Rosko, Eric: -Scara, Austin: -Teale, Marc: -Tebbetts, Masako: -Vegors, Eric Starr: -Waddle, Michael: -Winberry, Erica: -Yu, Michelle: diff --git a/Examples/Session01/test.py b/Examples/Session01/test.py deleted file mode 100644 index 2304d6c1..00000000 --- a/Examples/Session01/test.py +++ /dev/null @@ -1,8 +0,0 @@ -x = 5 -y = 56 - -print x, y - -def f(): - x = 5 - diff --git a/Examples/Session02/codingbat.rst b/Examples/Session02/codingbat.rst deleted file mode 100644 index b1dddb52..00000000 --- a/Examples/Session02/codingbat.rst +++ /dev/null @@ -1,51 +0,0 @@ - -Coding Bat examples -#################### - -Warmup-1 > monkey_trouble -============================ - -We have two monkeys, a and b, and the parameters a_smile and b_smile indicate if each is smiling. We are in trouble if they are both smiling or if neither of them is smiling. Return True if we are in trouble:: - - monkey_trouble(True, True) → True - monkey_trouble(False, False) → True - monkey_trouble(True, False) → False - - -Warmup-1 > sleep_in -======================= - -The parameter weekday is True if it is a weekday, and the parameter vacation is True if we are on vacation. We sleep in if it is not a weekday or we're on vacation. Return True if we sleep in. - -sleep_in(False, False) → True -sleep_in(True, False) → False -sleep_in(False, True) → True - - -Warmup-1 > diff21 -================= - -Given an int n, return the absolute difference between n and 21, except return double the absolute difference if n is over 21. - -diff21(19) → 2 -diff21(10) → 11 -diff21(21) → 0 - -Warmup-1 > makes10 -=================== - -Given 2 ints, a and b, return True if one if them is 10 or if their sum is 10. - -makes10(9, 10) → True -makes10(9, 9) → False -makes10(1, 9) → True - -Logic-1 > cigar_party -====================== - -When squirrels get together for a party, they like to have cigars. A squirrel party is successful when the number of cigars is between 40 and 60, inclusive. Unless it is the weekend, in which case there is no upper bound on the number of cigars. Return True if the party with the given values is successful, or False otherwise. - -cigar_party(30, False) → False -cigar_party(50, False) → True -cigar_party(70, True) → True - diff --git a/Examples/Session03/module_reload.py b/Examples/Session03/module_reload.py deleted file mode 100644 index 446f70e2..00000000 --- a/Examples/Session03/module_reload.py +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/python - -""" -a really simple module to use to test reloading -""" - -this = "this2" -that = "that" - -def print_something(): - print "I'm printing something else" - diff --git a/Examples/Session03/test_script.py b/Examples/Session03/test_script.py deleted file mode 100644 index 5d602d4b..00000000 --- a/Examples/Session03/test_script.py +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env python - -print "yes, it ran" - diff --git a/Examples/Session04/format_example.py b/Examples/Session04/format_example.py deleted file mode 100644 index efc1af73..00000000 --- a/Examples/Session04/format_example.py +++ /dev/null @@ -1,7 +0,0 @@ -def print_me( nums ): - formatter = "the first %d numbers are: " + ", ".join( ["%i"] * len(nums) ) - print "formatter: ", formatter - print formatter%(( len(nums), ) + nums) - -print_me( (2,3,4,5) ) - diff --git a/Examples/Session04/format_test.py b/Examples/Session04/format_test.py deleted file mode 100644 index f3380d46..00000000 --- a/Examples/Session04/format_test.py +++ /dev/null @@ -1,3 +0,0 @@ -def print_msg(t): - print ("the first %i numbers are: " + ", ".join(["%i"] * len(t)) ) % ((len(t),) + t) - diff --git a/Examples/Session04/junk2.txt b/Examples/Session04/junk2.txt deleted file mode 100644 index e69de29b..00000000 diff --git a/Examples/Session04/junkfile.txt b/Examples/Session04/junkfile.txt deleted file mode 100644 index c28c057c..00000000 --- a/Examples/Session04/junkfile.txt +++ /dev/null @@ -1 +0,0 @@ -some textsome more text \ No newline at end of file diff --git a/Examples/Session04/simple_text_file.txt b/Examples/Session04/simple_text_file.txt deleted file mode 100644 index acec0797..00000000 --- a/Examples/Session04/simple_text_file.txt +++ /dev/null @@ -1,2 +0,0 @@ -This is a text file with very little in it -but it at least has more than one line diff --git a/Examples/Session04/test_file2.txt b/Examples/Session04/test_file2.txt deleted file mode 100644 index 1708fac3..00000000 --- a/Examples/Session04/test_file2.txt +++ /dev/null @@ -1,2 +0,0 @@ -antoher simple text file -still with jsut a couple lines in it diff --git a/Examples/Session05/arg_test.py b/Examples/Session05/arg_test.py deleted file mode 100644 index c84b5bdf..00000000 --- a/Examples/Session05/arg_test.py +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env python - -import sys - -print sys.argv - diff --git a/Examples/Session05/codingbat.py b/Examples/Session05/codingbat.py deleted file mode 100644 index 1dcf82eb..00000000 --- a/Examples/Session05/codingbat.py +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/env python - -""" -Examples from: http://codingbat.com - -Put here so we can write unit tests for them ourselves -""" - -# Python > Warmup-1 > sleep_in - - -def sleep_in(weekday, vacation): - return not (weekday == True and vacation == False) - - diff --git a/Examples/Session05/test_codingbat.py b/Examples/Session05/test_codingbat.py deleted file mode 100755 index 6e845b0e..00000000 --- a/Examples/Session05/test_codingbat.py +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env python - -""" -test file for codingbat module - -This version can be run with nose or py.test -""" - -from codingbat import sleep_in - - -def test_false_false(): - assert sleep_in(False, False) - - -def test_true_false(): - assert not ( sleep_in(True, False) ) - - -def test_false_true(): - assert sleep_in(False, True) - - -def test_true_true(): - assert sleep_in(True, True) diff --git a/Examples/Session05/test_pytest_parameter.py b/Examples/Session05/test_pytest_parameter.py deleted file mode 100644 index 52449af3..00000000 --- a/Examples/Session05/test_pytest_parameter.py +++ /dev/null @@ -1,30 +0,0 @@ -#!/usr/bin/env python - -""" -pytest example of a parameterized test - -NOTE: there is a failure in here! can you fix it? - -""" -import pytest - -# a (really simple) function to test -def add(a, b): - """ - returns the sum of a and b - """ - return a + b - -# now some test data: - -test_data = [ ( ( 2, 3), 5), - ( (-3, 2), -1), - ( ( 2, 0.5), 2.5), - ( ( "this", "that"), "this that"), - ( ( [1,2,3], [6,7,8]), [1,2,3,6,7,8]), - ] - -@pytest.mark.parametrize(("input", "result"), test_data) -def test_add(input, result): - assert add(*input) == result - diff --git a/Examples/Session05/test_random_pytest.py b/Examples/Session05/test_random_pytest.py deleted file mode 100644 index e8b80f8c..00000000 --- a/Examples/Session05/test_random_pytest.py +++ /dev/null @@ -1,39 +0,0 @@ -#!/usr/bin/env python - -""" -port of the random unit tests from the python docs to py.test -""" - -import random -import pytest - - -seq = range(10) - - -def test_shuffle(): - # make sure the shuffled sequence does not lose any elements - random.shuffle(seq) - seq.sort() - print "seq:", seq - ## expect this to fail -- so we can see the output. - assert seq == range(10) - - -def test_shuffle_immutable(): - pytest.raises(TypeError, random.shuffle, (1,2,3) ) - - -def test_choice(): - element = random.choice(seq) - assert (element in seq) - - -def test_sample(): - for element in random.sample(seq, 5): - assert element in seq - - -def test_sample_too_large(): - with pytest.raises(ValueError): - random.sample(seq, 20) diff --git a/Examples/Session05/test_random_unitest.py b/Examples/Session05/test_random_unitest.py deleted file mode 100644 index 6458e6ce..00000000 --- a/Examples/Session05/test_random_unitest.py +++ /dev/null @@ -1,29 +0,0 @@ -import random -import unittest - -class TestSequenceFunctions(unittest.TestCase): - - def setUp(self): - self.seq = range(10) - - def test_shuffle(self): - # make sure the shuffled sequence does not lose any elements - random.shuffle(self.seq) - self.seq.sort() - self.assertEqual(self.seq, range(10)) - - # should raise an exception for an immutable sequence - self.assertRaises(TypeError, random.shuffle, (1,2,3) ) - - def test_choice(self): - element = random.choice(self.seq) - self.assertTrue(element in self.seq) - - def test_sample(self): - with self.assertRaises(ValueError): - random.sample(self.seq, 20) - for element in random.sample(self.seq, 5): - self.assertTrue(element in self.seq) - -if __name__ == '__main__': - unittest.main() \ No newline at end of file diff --git a/Examples/Session06/cigar_party.py b/Examples/Session06/cigar_party.py deleted file mode 100644 index 18878463..00000000 --- a/Examples/Session06/cigar_party.py +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env python - -""" -When squirrels get together for a party, they like to have cigars. -A squirrel party is successful when the number of cigars is between -40 and 60, inclusive. Unless it is the weekend, in which case there -is no upper bound on the number of cigars. - -Return True if the party with the given values is successful, -or False otherwise. -""" - - -def cigar_party(cigars, is_weekend): - """ - basic solution - """ - if ( 40 <= cigars <= 60 ) or ( cigars >= 40 and is_weekend): - return True - else: - return False - - -def cigar_party3(cigars, is_weekend): - """ - conditional expression - """ - return (cigars >= 40) if is_weekend else (cigars >= 40 and cigars <= 60) diff --git a/Examples/Session06/class.py b/Examples/Session06/class.py deleted file mode 100644 index 1c131142..00000000 --- a/Examples/Session06/class.py +++ /dev/null @@ -1,8 +0,0 @@ -class C(object): - x = 5 - def __init__(self, y): - self.y = y - def meth(self, z): - return self.x + self.y + z - - \ No newline at end of file diff --git a/Examples/Session06/class_demo.py b/Examples/Session06/class_demo.py deleted file mode 100644 index 33841a88..00000000 --- a/Examples/Session06/class_demo.py +++ /dev/null @@ -1,10 +0,0 @@ - -class C(object): - x = 5 - - def __init__(self, y): - self.y = y - - def meth(self, z): - C.x = z - return self.x + self.y + z diff --git a/Examples/Session06/html_render/.DS_Store b/Examples/Session06/html_render/.DS_Store deleted file mode 100644 index 5008ddfc..00000000 Binary files a/Examples/Session06/html_render/.DS_Store and /dev/null differ diff --git a/Examples/Session06/html_render/html_render.py b/Examples/Session06/html_render/html_render.py deleted file mode 100755 index f46760fd..00000000 --- a/Examples/Session06/html_render/html_render.py +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env python - -""" -Python class example. -""" - - -# The start of it all: -# Fill it all in here. -class Element(object): - - def __init__(self, content=None): - pass - def append(self, new_content): - pass - def render(self, file_out, ind=""): - file_out.write("just something as a place holder...") diff --git a/Examples/Session06/simple_classes.py b/Examples/Session06/simple_classes.py deleted file mode 100644 index b170209d..00000000 --- a/Examples/Session06/simple_classes.py +++ /dev/null @@ -1,95 +0,0 @@ -#!/usr/bin/env python -""" -simple_classes.py - -demonstrating the basics of a class -""" - -import math - - -## create a point class -class Point(object): - def __init__(self, x, y): - self.x = x - self.y = y - -## create an instance of that class -p = Point(3,4) - -## access the attributes -print "p.x is:", p.x -print "p.y is:", p.y - - -class Point2(object): - size = 4 - color= "red" - def __init__(self, x, y): - self.x = x - self.y = y - -p2 = Point2(4,5) -print p2.size -print p2.color - - -class Point3(object): - size = 4 - color= "red" - def __init__(self, x, y): - self.x = x - self.y = y - def get_color(self): - return self.color - - -p3 = Point3(4,5) -print p3.size -print p3.get_color() - - -class Circle(object): - color = "red" - styles = ['dashed'] - def __init__(self, diameter): - self.diameter = diameter - - def grow(self, factor=2): - """ - grows the circle's diameter - - :param factor=2: factor by which to grow the circle - """ - self.diameter = self.diameter * factor - - def add_style(self, style): - self.styles.append(style) - - def get_area(self): - return math.pi * self.diameter / 2.0 - - -class NewCircle(Circle): - color = "blue" - - def grow(self, factor=2): - """grows the area by factor...""" - self.diameter = self.diameter * math.sqrt(2) - -nc = NewCircle -print nc.color - - -class CircleR(Circle): - def __init__(self, radius): - diameter = radius*2 - Circle.__init__(self, diameter) - - -class CircleR2(Circle): - def __init__(self, radius): - self.radius = radius - - def get_area(self): - return Circle.get_area(self, self.radius*2) diff --git a/Examples/Session06/test_cigar_party.py b/Examples/Session06/test_cigar_party.py deleted file mode 100644 index a03ca3c5..00000000 --- a/Examples/Session06/test_cigar_party.py +++ /dev/null @@ -1,54 +0,0 @@ -#!/usr/bin/env python - -import cigar_party - -#cigar_party = cigar_party.cigar_party -#cigar_party = cigar_party.cigar_party2 -cigar_party = cigar_party.cigar_party3 - - -def test_1(): - assert cigar_party(30, False) is False - - -def test_2(): - - assert cigar_party(50, False) is True - - -def test_3(): - - assert cigar_party(70, True) is True - - -def test_4(): - assert cigar_party(30, True) is False - - -def test_5(): - assert cigar_party(50, True) is True - - -def test_6(): - assert cigar_party(60, False) is True - - -def test_7(): - assert cigar_party(61, False) is False - - -def test_8(): - assert cigar_party(40, False) is True - - -def test_9(): - assert cigar_party(39, False) is False - - -def test_10(): - assert cigar_party(40, True) is True - - -def test_11(): - assert cigar_party(39, True) is False - diff --git a/Examples/Session07/circle.py b/Examples/Session07/circle.py deleted file mode 100644 index a6545632..00000000 --- a/Examples/Session07/circle.py +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env python -"""circle class -- - -fill this in so it will pass all the tests. -""" -import math - - -class Circle(object): - pass diff --git a/Examples/Session07/class_method.py b/Examples/Session07/class_method.py deleted file mode 100644 index 1d893b94..00000000 --- a/Examples/Session07/class_method.py +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env python - -""" -example of a class method -""" - - -class C(object): - def __init__(self, x, y): - self.x = x - self.y = y - - @classmethod - def a_class_method(cls, y): - print "in a_class_method", cls - return cls(y, y**2) - - -class C2(C): - pass - - -if __name__ == "__main__": - - c = C(3, 4) - print type(c), c.x, c.y - - c2 = C.a_class_method(3) - print type(c2), c2.x, c2.y - - c3 = c2.a_class_method(2) - print type(c3), c3.x, c3.y diff --git a/Examples/Session07/properties_example.py b/Examples/Session07/properties_example.py deleted file mode 100644 index fa9c732e..00000000 --- a/Examples/Session07/properties_example.py +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env python - -""" -Example code for properties - -NOTE: if your getters and setters are this simple: don't do this! - -""" - - -class C(object): - def __init__(self): - self._x = None - @property - def x(self): - return self._x - @x.setter - def x(self, value): - self._x = value - @x.deleter - def x(self): - del self._x - -if __name__ == "__main__": - c = C() - c.x = 5 - print c.x - diff --git a/Examples/Session07/static_method.py b/Examples/Session07/static_method.py deleted file mode 100644 index 6865408c..00000000 --- a/Examples/Session07/static_method.py +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env python - -""" -examples of a static methods -""" - - -class C(object): - - @staticmethod - def a_static_method(a, b): - print "in a_static_method" - return a+b - - def test(self): - return self.a_static_method(2,3) - -# if __name__ == "__main__": - -# print C.a_static_method(3,4) - -# c = C() - -# print c.a_static_method(4,5) - -# print c.test() - - diff --git a/Examples/Session07/vector.py b/Examples/Session07/vector.py deleted file mode 100644 index 56ee2404..00000000 --- a/Examples/Session07/vector.py +++ /dev/null @@ -1,47 +0,0 @@ -""" -Vector type with +, * redefined as vector addition and dot product -From Jon Jacky's Intro to Python course: - http://staff.washington.edu/jon/python-course/ -""" - - -class vector(list): - def __repr__(self): - """ - String representation, uses list (superclass) representation - """ - return 'vector(%s)' % super(vector, self).__repr__() - - def __add__(self, v): - """ - redefine + as element-wise vector sum - """ - assert len(self) == len(v) - return vector([x1 + x2 for x1, x2 in zip(self, v)]) - - def __mul__(self, v): - """ - redefine * as vector dot product - """ - assert len(self) == len(v) - return sum([x1 * x2 for x1, x2 in zip(self, v)]) - -l1 = [1, 2, 3] -l2 = [4, 5, 6] -v1 = vector(l1) -v2 = vector(l2) - -if __name__ == '__main__': - print 'l1' - print l1 - print 'l1 + l2' - print l1 + l2 - # print l1 * l2 # TypeError - print 'zip(l1, l2)' - print zip(l1, l2) - print 'v1' - print v1 - print 'v1 + v2' - print v1 + v2 - print 'v1 * v2' - print v1 * v2 diff --git a/Examples/Session08/iterator_1.py b/Examples/Session08/iterator_1.py deleted file mode 100644 index f2402385..00000000 --- a/Examples/Session08/iterator_1.py +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env python - -""" -Simple iterator examples -""" - - -class IterateMe_1(object): - """ - About as simple an iterator as you can get: - - returns the sequence of numbers from zero to 4 - ( like xrange(4) ) - """ - def __init__(self, stop=5): - self.current = -1 - self.stop = stop - def __iter__(self): - return self - def next(self): - self.current += 1 - if self.current < self.stop: - return self.current - else: - raise StopIteration - -if __name__ == "__main__": - - print "Testing the iterator" - for i in IterateMe_1(): - print i - diff --git a/Examples/Session08/my_for.py b/Examples/Session08/my_for.py deleted file mode 100644 index fd43ac6d..00000000 --- a/Examples/Session08/my_for.py +++ /dev/null @@ -1,50 +0,0 @@ -#!/usr/bin/env python - -""" -hand writing 'for' - -demonstrates how for interacts with an iterable -""" - - -l = [1,2,3,4,5,] - - -def my_for(an_iterable, func): - """ - Emulation of a for loop. - - func() will be called with each item in an_iterable - - :param an_iterable: anything that satisfies the interation protocol - - :param func: a callable -- it will be called, passing in each item - in an_iterable. - - """ - # equiv of "for i in l:" - iterator = iter(an_iterable) - while True: - try: - i = iterator.next() - except StopIteration: - break - func(i) - - -if __name__ == "__main__": - - def print_func(x): - print x - - l = [1,2,3,4,5,] - my_for(l, print_func) - - t = ('a','b','c','d') - - my_for(t, print_func) - - - - - diff --git a/Examples/Session08/test_generator.py b/Examples/Session08/test_generator.py deleted file mode 100644 index cf02fae5..00000000 --- a/Examples/Session08/test_generator.py +++ /dev/null @@ -1,78 +0,0 @@ -""" -test_generator.py - -tests the solution to the generator lab - -can be run with py.test or nosetests -""" - -import generator_solution as gen - - -def test_intsum(): - - g = gen.intsum() - - assert g.next() == 0 - assert g.next() == 1 - assert g.next() == 3 - assert g.next() == 6 - assert g.next() == 10 - assert g.next() == 15 - - -def test_intsum2(): - - g = gen.intsum2() - - assert g.next() == 0 - assert g.next() == 1 - assert g.next() == 3 - assert g.next() == 6 - assert g.next() == 10 - assert g.next() == 15 - - -def test_doubler(): - - g = gen.doubler() - - assert g.next() == 1 - assert g.next() == 2 - assert g.next() == 4 - assert g.next() == 8 - assert g.next() == 16 - assert g.next() == 32 - - for i in range(10): - j = g.next() - - assert j == 2**15 - - -def test_fib(): - g = gen.fib() - - assert g.next() == 1 - assert g.next() == 1 - assert g.next() == 2 - assert g.next() == 3 - assert g.next() == 5 - assert g.next() == 8 - assert g.next() == 13 - assert g.next() == 21 - - -def test_prime(): - g = gen.prime() - - assert g.next() == 2 - assert g.next() == 3 - assert g.next() == 5 - assert g.next() == 7 - assert g.next() == 11 - assert g.next() == 13 - assert g.next() == 17 - assert g.next() == 19 - assert g.next() == 23 - diff --git a/Examples/Session08/yield_example.py b/Examples/Session08/yield_example.py deleted file mode 100644 index bbccb79f..00000000 --- a/Examples/Session08/yield_example.py +++ /dev/null @@ -1,22 +0,0 @@ -def counter(): - print 'counter: starting counter' - i = -3 - while i < 3: - i = i + 1 - print 'counter: yield', i - yield i - return None - - -# if __name__ == '__main__': -# print "the generator function:" -# print repr(counter) -# print "call generator function" - -# c = counter() -# print "the generator:" -# print repr(c) - -# print 'iterate' -# for item in c: -# print 'received:', item diff --git a/Examples/Session09/capitalize/capitalize/__init__.py b/Examples/Session09/capitalize/capitalize/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/Examples/Session09/capitalize/capitalize/capital_mod.py b/Examples/Session09/capitalize/capitalize/capital_mod.py deleted file mode 100644 index 352f0874..00000000 --- a/Examples/Session09/capitalize/capitalize/capital_mod.py +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env python - -""" -A really simple module, just to demonstrate disutils -""" - -def capitalize(infilename, outfilename): - """ - reads the contents of infilename, and writes it to outfilename, but with - every word capitalized - - note: very primitive -- it will mess some files up! - - this is called by the capitalize script - """ - infile = open(infilename, 'U') - outfile = open(outfilename, 'w') - - for line in infile: - outfile.write( " ".join( [word.capitalize() for word in line.split() ] ) ) - outfile.write("\n") - - return None \ No newline at end of file diff --git a/Examples/Session09/capitalize/capitalize/test/__init__.py b/Examples/Session09/capitalize/capitalize/test/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/Examples/Session09/capitalize/capitalize/test/test_text_file.txt b/Examples/Session09/capitalize/capitalize/test/test_text_file.txt deleted file mode 100644 index a64b50f7..00000000 --- a/Examples/Session09/capitalize/capitalize/test/test_text_file.txt +++ /dev/null @@ -1,7 +0,0 @@ -This is a really simple Text file. -It is here so that I can test the capitalize script. - -And that's only there to try out distutils. - -So there. - \ No newline at end of file diff --git a/Examples/Session09/capitalize/scripts/cap_script.py b/Examples/Session09/capitalize/scripts/cap_script.py deleted file mode 100755 index 08f999e3..00000000 --- a/Examples/Session09/capitalize/scripts/cap_script.py +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env python - -""" -A really simple script just to demonstrate disutils -""" - -import sys, os -from capitalize import capital_mod - - -if __name__ == "__main__": - try: - infilename = sys.argv[1] - except IndexError: - print "you need to pass in a file to process" - - root, ext = os.path.splitext(infilename) - outfilename = root + "_cap" + ext - - # do the real work: - print "Capitalizing: %s and storing it in %s"%(infilename, outfilename) - capital_mod.capitalize(infilename, outfilename) - - print "I'm done" - \ No newline at end of file diff --git a/Examples/Session09/capitalize/setup.py b/Examples/Session09/capitalize/setup.py deleted file mode 100755 index d7acd8eb..00000000 --- a/Examples/Session09/capitalize/setup.py +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env python - -""" -This is about as simple a setup.py as you can have - -It installs the capitalize module and script - -""" - -# classic distutils -#from distutils.core import setup - -## uncomment to support "develop" mode -from setuptools import setup - -setup( - name='Capitalize', - version='0.1.0', - author='Chris Barker', - py_modules=['capitalize/capital_mod',], - scripts=['scripts/cap_script.py',], - description='Not very useful capitalizing module and script', -) - diff --git a/Examples/Session09/context_managers.py b/Examples/Session09/context_managers.py deleted file mode 100644 index 326b66d3..00000000 --- a/Examples/Session09/context_managers.py +++ /dev/null @@ -1,49 +0,0 @@ -# -*- coding: utf-8 -*- -import sys -from StringIO import StringIO -from contextlib import contextmanager - - -class Context(object): - """from Doug Hellmann, PyMOTW - http://pymotw.com/2/contextlib/#module-contextlib - """ - def __init__(self, handle_error): - print '__init__(%s)' % handle_error - self.handle_error = handle_error - - def __enter__(self): - print '__enter__()' - return self - - def __exit__(self, exc_type, exc_val, exc_tb): - print '__exit__(%s, %s, %s)' % (exc_type, exc_val, exc_tb) - return self.handle_error - - -@contextmanager -def context(boolean): - print "__init__ code here" - try: - print "__enter__ code goes here" - yield object() - except Exception as e: - print "errors handled here" - if not boolean: - raise - finally: - print "__exit__ cleanup goes here" - - -@contextmanager -def print_encoded(encoding): - old_stdout = sys.stdout - sys.stdout = buff = StringIO() - try: - yield None - finally: - sys.stdout = old_stdout - buff.seek(0) - raw = buff.read() - encoded = raw.encode(encoding) - print encoded diff --git a/Examples/Session09/decorators.py b/Examples/Session09/decorators.py deleted file mode 100644 index fc6bd80b..00000000 --- a/Examples/Session09/decorators.py +++ /dev/null @@ -1,77 +0,0 @@ -# -*- coding: utf-8 -*- - - -def substitute(a_function): - """return a different function than the one supplied""" - def new_function(*args, **kwargs): - return "I'm not that other function" - return new_function - - -def add(a, b): -# print "Function 'add' called with args: %r" % locals() - result = a + b -# print "\tResult --> %r" % result - return result - - -def logged_func(func): - def logged(*args, **kwargs): - print "Function %r called" % func.__name__ - if args: - print "\twith args: %r" % (args, ) - if kwargs: - print "\twith kwargs: %r" % kwargs - result = func(*args, **kwargs) - print "\t Result --> %r" % result - return result - return logged - - -def simple_add(a, b): - return a + b - - -class Memoize(object): - """ - memoize decorator from avinash.vora - http://avinashv.net/2008/04/python-decorators-syntactic-sugar/ - """ - def __init__(self, function): # runs when memoize class is called - self.function = function - self.memoized = {} - - def __call__(self, *args): # runs when memoize instance is called - try: - return self.memoized[args] - except KeyError: - self.memoized[args] = self.function(*args) - return self.memoized[args] - - -#@Memoize -def sum2x(n): - return sum(2 * i for i in xrange(n)) -sum2x = Memoize(sum2x) - - - - -import time - - -def timed_func(func): - def timed(*args, **kwargs): - start = time.time() - result = func(*args, **kwargs) - elapsed = time.time() - start - print "time expired: %s" % elapsed - return result - return timed - -@timed_func -@Memoize -def sum2x(n): - return sum(2 * i for i in xrange(n)) - - diff --git a/Examples/Session09/ipythonlog.txt b/Examples/Session09/ipythonlog.txt deleted file mode 100644 index 52e36c40..00000000 --- a/Examples/Session09/ipythonlog.txt +++ /dev/null @@ -1,180 +0,0 @@ -# IPython log file - - -get_ipython().magic(u'run decorators.py') -add -add(2,3) -get_ipython().magic(u'run decorators.py') -add(2,3) -new_add = substitute(add) -new_add(2,3) -@substitute -def fun(1,2,3) -@substitute -def fun(1,2,3): - print "in fun" - -@substitute -def fun(a,b): - print "in fun" - -fun() -@logged_func -def fun(a,b): - print "in fun" - -fun(2,3) -fun(2,3,fred=4) -get_ipython().magic(u'run decorators.py') -get_ipython().magic(u'timeit sum2x(10)') -get_ipython().magic(u'run decorators.py') -get_ipython().magic(u'timeit sum2x(10)') -sum2x(100000000) -sum2x(100000000) -sum2x(10000000) -sum2x(10000000) -type(sum2x) -sum2x.function -sum2x.function(10) -sum2x.memoized -@Memoize -def other_fun(): -def other_fun(x): - -@Memoize -def fun2(x): - return x**100 - -fun2(4) -fun2(4) -fun2.memoized -@Memoize -def fun2asdf(x): - return x**100 - -get_ipython().magic(u'run decorators.py') -sum2x(1e6) -sum2x(1000000) -get_ipython().magic(u'run decorators.py') -sum2x(1000000) -sum2x(1000000) -sum2x(10000000) -sum2x(10000000) -get_ipython().magic(u'paste') -@Memoize -@timed_func -def sum2x(n): - return sum(2 * i for i in xrange(n)) -sum2x(1000000) -sum2x(1000000) -get_ipython().magic(u'run context_managers.py') -with Context(False): - print something - -with Context(False): - print "something" - -get_ipython().magic(u'paste') -with Context(True) as foo: - print 'This is in the context' - raise RuntimeError('this is the error message') -with Context(False): - raise RuntimeError('this is the error message') - -with context(False): - raise RuntimeError('this is the error message') - -with context(True): - raise RuntimeError('this is the error message') - -get_ipython().magic(u'run timer_context.py') -with Timer as t: - [x^2 for x in range(100000000)] - -get_ipython().magic(u'run timer_context.py') -with Timer as t: - [x^2 for x in range(100000000)] - -with Timer as t: - [x^2 for x in range(100000000)] - -get_ipython().magic(u'run timer_context.py') -with Timer as t: - [x^2 for x in range(100000000)] - -with Timer() as t: - [x^2 for x in range(100000000)] - -with Timer() as t: - [x^2 for x in range(1000000)] - -get_ipython().magic(u'run timer_context.py') -with Timer() as t: - [x^2 for x in range(1000000)] - -get_ipython().magic(u'run timer_context.py') -with Timer() as t: - [x^2 for x in range(1000000)] - -get_ipython().magic(u'run timer_context.py') -with Timer() as t: - [x^2 for x in range(1000000)] - -with Timer() as t: - [x^2 for x in range(10000000)] - -t -t.elapsed -with Timer() as t: - [x^2 for x in range(10000000)] - raise RuntimeError("this is an error") - -with Timer() as t: - [x^2 for x in range(10000000)] - raise RuntimeError("this is an error") - -with Timer() as t: - [x^2 for x in range(10000000)] - raise RuntimeError("this is an error") - -get_ipython().magic(u'run timer_context.py') -with Timer() as t: - [x^2 for x in range(10000000)] - raise RuntimeError("this is an error") - -get_ipython().magic(u'run timer_context.py') -with Timer() as t: - [x^2 for x in range(10000000)] - raise RuntimeError("this is an error") - -get_ipython().magic(u'run timer_context.py') -with Timer() as t: - [x^2 for x in range(10000000)] - raise RuntimeError("this is an error") - -get_ipython().magic(u'pinfo isinstance') -get_ipython().magic(u'run timer_context.py') -with Timer() as t: - [x^2 for x in range(10000000)] - raise RuntimeError("this is an error") - -get_ipython().magic(u'run timer_context.py') -with Timer() as t: - [x^2 for x in range(10000000)] - raise RuntimeError("this is an error") - -with Timer() as t: - [x^2 for x in range(10000000)] - raise RuntimeError("this is an error") -print "after Exception" - -with Timer() as t: - [x^2 for x in range(10000000)] - raise RuntimeError("this is an error") - print "after Exception" - -with Timer() as t: - [x^2 for x in range(10000000)] - raise ValueError("this is an error") - print "after Exception" - diff --git a/Examples/Session09/memoize.py b/Examples/Session09/memoize.py deleted file mode 100644 index 7c9650f1..00000000 --- a/Examples/Session09/memoize.py +++ /dev/null @@ -1,37 +0,0 @@ -class Memoize: - """ - memoize decorator from avinash.vora - http://avinashv.net/2008/04/python-decorators-syntactic-sugar/ - """ - def __init__(self, function): # runs when memoize class is called - self.function = function - self.memoized = {} - - def __call__(self, *args): # runs when memoize instance is called - try: - return self.memoized[args] - except KeyError: - self.memoized[args] = self.function(*args) - return self.memoized[args] - - -@Memoize -def sum2x(n): - return sum(2 * i for i in xrange(n)) - -import time - - -def timed_func(func): - def timed(*args, **kwargs): - start = time.time() - result = func(*args, **kwargs) - elapsed = time.time() - start - print "time expired: %s" % elapsed - return result - return timed - -@timed_func -@Memoize -def sum2x(n): - return sum(2 * i for i in xrange(n)) diff --git a/Examples/Session09/property_ugly.py b/Examples/Session09/property_ugly.py deleted file mode 100644 index d20c1dcc..00000000 --- a/Examples/Session09/property_ugly.py +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env python - -class C(object): - """ - Property defined in about the most ugly way possible - """ - def __init__(self): - self._x = None - def x(self): - return self._x - x = property(x) - def _set_x(self, value): - self._x = value - x = x.setter(_set_x) - def _del_x(self): - del self._x - x = x.deleter(_del_x) - - diff --git a/Examples/Session09/test_p_wrapper.py b/Examples/Session09/test_p_wrapper.py deleted file mode 100644 index c73b6ef6..00000000 --- a/Examples/Session09/test_p_wrapper.py +++ /dev/null @@ -1,31 +0,0 @@ -#!/usr/bin/env python - -""" -test code for the p_wrapper assignment -""" - -from p_wrapper import p_wrapper, tag_wrapper - - -def test_p_wrapper(): - @p_wrapper - def return_a_string(string): - return string - - assert return_a_string('this is a string') == '

this is a string

' - -#Extra credit: - -def test_tag_wrapper(): - @tag_wrapper('html') - def return_a_string(string): - return string - - assert return_a_string("this is a string") == " this is a string " - -def test_tag_wrapper2(): - @tag_wrapper('div') - def return_a_string(string): - return string - - assert return_a_string("this is a string") == "
this is a string
" diff --git a/Examples/Session10/ICanEatGlass.utf16.txt b/Examples/Session10/ICanEatGlass.utf16.txt deleted file mode 100644 index 24a0858d..00000000 Binary files a/Examples/Session10/ICanEatGlass.utf16.txt and /dev/null differ diff --git a/Examples/Session10/ICanEatGlass.utf8.txt b/Examples/Session10/ICanEatGlass.utf8.txt deleted file mode 100644 index 9ecba2b9..00000000 --- a/Examples/Session10/ICanEatGlass.utf8.txt +++ /dev/null @@ -1,23 +0,0 @@ -I Can Eat Glass: - -And from the sublime to the ridiculous, here is a certain phrase in an assortment of languages: - -Sanskrit: काचं शक्नोम्यत्तुम् । नोपहिनस्ति माम् ॥ - -Sanskrit (standard transcription): kācaṃ śaknomyattum; nopahinasti mām. - -Classical Greek: ὕαλον ϕαγεῖν δύναμαι· τοῦτο οὔ με βλάπτει. - -Greek (monotonic): Μπορώ να φάω σπασμένα γυαλιά χωρίς να πάθω τίποτα. - -Greek (polytonic): Μπορῶ νὰ φάω σπασμένα γυαλιὰ χωρὶς νὰ πάθω τίποτα. - -Latin: Vitrum edere possum; mihi non nocet. - -Old French: Je puis mangier del voirre. Ne me nuit. - -French: Je peux manger du verre, ça ne me fait pas mal. - -Provençal / Occitan: Pòdi manjar de veire, me nafrariá pas. - -Québécois: J'peux manger d'la vitre, ça m'fa pas mal. \ No newline at end of file diff --git a/Examples/Session10/add_book_data.py b/Examples/Session10/add_book_data.py deleted file mode 100644 index 6fa4b5d1..00000000 --- a/Examples/Session10/add_book_data.py +++ /dev/null @@ -1,49 +0,0 @@ -#!/usr/bin/env python - -""" -sample data for persistence/serializatiion examples - -This version is nested, with more stucture - - can be saved with pickle, JSON, xml... -""" - -AddressBook = [ {'first_name': "Chris", - 'last_name': "Barker", - 'address' : {'line_1':"835 NE 33rd St", - 'line_2' : "", - 'city' : "Seattle", - 'state': "WA", - 'zip': "96543"}, - 'email' : "PythonCHB@gmail.com", - 'home_phone' : "206-555-1234", - 'office_phone' : "123-456-7890", - 'cell_phone' : "234-567-8901", - }, - - {'first_name': "Fred", - 'last_name': "Jones", - 'address' : {'line_1':"123 SE 13th St", - 'line_2' : "Apt. 43", - 'city' : "Tacoma", - 'state': "WA", - 'zip': "93465"}, - 'email' : "FredJones@some_company.com", - 'home_phone' : "510-555-1234", - 'office_phone' : "564-466-7990", - 'cell_phone' : "403-561-8911", - }, - - {'first_name': "Nancy", - 'last_name': "Wilson", - 'address' : {'line_1':"8654 Walnut St", - 'line_2' : "Suite 567", - 'city' : "Pasadena", - 'state': "CA", - 'zip': "12345"}, - 'email' : "Wilson.Nancy@gmail.com", - 'home_phone' : "423-321-9876", - 'office_phone' : "123-765-9877", - 'cell_phone' : "432-567-8466", - }, - ] - diff --git a/Examples/Session10/add_book_data_flat.py b/Examples/Session10/add_book_data_flat.py deleted file mode 100644 index 97a0869d..00000000 --- a/Examples/Session10/add_book_data_flat.py +++ /dev/null @@ -1,47 +0,0 @@ -#!/usr/bin/env python - -""" -sample data for persistence/serialization examples -this version is flat for saving in CSV, ini, etc. -""" - -AddressBook = [ {'first_name': "Chris", - 'last_name': "Barker", - 'address_line_1':"835 NE 33rd St", - 'address_line_2' : "", - 'address_city' : "Seattle", - 'address_state': "WA", - 'address_zip': "96543", - 'email' : "PythonCHB@gmail.com", - 'home_phone' : "206-555-1234", - 'office_phone' : "123-456-7890", - 'cell_phone' : "234-567-8901", - }, - - {'first_name': "Fred", - 'last_name': "Jones", - 'address_line_1':"123 SE 13th St", - 'address_line_2' : "Apt. 43", - 'address_city' : "Tacoma", - 'address_state': "WA", - 'address_zip': "93465", - 'email' : "FredJones@some_company.com", - 'home_phone' : "510-555-1234", - 'office_phone' : "564-466-7990", - 'cell_phone' : "403-561-8911", - }, - - {'first_name': "Nancy", - 'last_name': "Wilson", - 'address_line_1':"8654 Walnut St", - 'address_line_2' : "Suite 567", - 'address_city' : "Pasadena", - 'address_state': "CA", - 'address_zip': "12345", - 'email' : "Wilson.Nancy@gmail.com", - 'home_phone' : "423-321-9876", - 'office_phone' : "123-765-9877", - 'cell_phone' : "432-567-8466", - }, - ] - diff --git a/Examples/Session10/example.cfg b/Examples/Session10/example.cfg deleted file mode 100644 index c27f2939..00000000 --- a/Examples/Session10/example.cfg +++ /dev/null @@ -1,9 +0,0 @@ -[Section1] -int = 15 -bool = true -float = 3.1415 - -[Section2] -int = 32 -bool = False -float = 1.4235 \ No newline at end of file diff --git a/Examples/Session10/hello_unicode.py b/Examples/Session10/hello_unicode.py deleted file mode 100644 index 6bbad1de..00000000 --- a/Examples/Session10/hello_unicode.py +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -hello = 'Hello ' -world = u'世界' - -print hello + world - -print u"It was nice weather today: it reached 80\u00B0" - -print u"Maybe it will reach 90\N{degree sign}" - -print u"It is extremely rare for it ever to reach 100° in Seattle" diff --git a/Examples/Session10/latin1_test.py b/Examples/Session10/latin1_test.py deleted file mode 100644 index 3990078f..00000000 --- a/Examples/Session10/latin1_test.py +++ /dev/null @@ -1,66 +0,0 @@ -#!/usr/bin/env python - -""" -An example of using latin-1 as a universal encoding - -latin-1 is a superset of ASCII that is suitable for western european languages. - -Is the most common, and a good default, if you need a one-byte per char encoding -for European text. - -It also has a nice property: - : every byte value from 0 to 255 is avalid charactor - -Thus you will never get an UnicodeDecodeError if -you try to decode arbitrary bytes with latin-1. - -And it can "round-trip" trhough a unicode object. - -This can be useful is you don't know the encoding -- at least it won't break. -It's also useful if you need to work with cobined text+binary data. - - - -""" - -# all the byte values in a bytes (str) object: -all_bytes = ''.join( [chr(i) for i in range(255)] ) - -print type(all_bytes) -print len(all_bytes) - -print "Example value: 20" -print ord(all_bytes[20]) == 20 -print "Example high value: 245" -print ord(all_bytes[245]) == 245 - -# now decode it to a unicode object: -try: - uni = all_bytes.decode() -except UnicodeDecodeError: - print "OOPS: can't decode with default encoding" - -# latin-1 works: -try: - all_uni = all_bytes.decode('latin-1') - print "Yup -- that worked" - print all_uni - print "note that the ASCII subset is the same..." -except UnicodeDecodeError: - print "OOPS: This should have worked!!" - raise - -## now show that it round-trips: -all_bytes2 = all_uni.encode('latin-1') - -if all_bytes2 == all_bytes: - print "yup -- that worked...the values are preserved on the round trip." -else: - print "Hey, that should have worked" - - - - - - - diff --git a/Examples/Session10/text.utf16 b/Examples/Session10/text.utf16 deleted file mode 100644 index b80b2efc..00000000 Binary files a/Examples/Session10/text.utf16 and /dev/null differ diff --git a/Examples/Session10/text.utf32 b/Examples/Session10/text.utf32 deleted file mode 100644 index c5295310..00000000 Binary files a/Examples/Session10/text.utf32 and /dev/null differ diff --git a/Examples/Session10/text.utf8 b/Examples/Session10/text.utf8 deleted file mode 100644 index 9de18890..00000000 --- a/Examples/Session10/text.utf8 +++ /dev/null @@ -1,17 +0,0 @@ -Origin (in native language) Name (in native language) -Հայաստան Արամ Խաչատրյան - Australia Nicole Kidman - Österreich Johann Strauß - Azərbaycan Vaqif Səmədoğlu - Азәрбајҹан Вагиф Сәмәдоғлу - Azərbaycan Heydər Əliyev - Азәрбајҹан Һејдәр Әлијев - België René Magritte - Belgique René Magritte - Belgien René Magritte - বাংলা সুকুমার রায় - འབྲུག་ཡུལ། མགོན་པོ་རྡོ་རྗེ། - ប្រទេស​​​កម្ពុជា ព្រះ​ពុទ្ឋឃោសាចារ‌្យ​ជួន​ណាត -Canada Céline Dion - ᓄᓇᕗᒻᒥᐅᑦ ᓱᓴᓐ ᐊᒡᓗᒃᑲᖅ - \ No newline at end of file diff --git a/Examples/Session10/unicode_exception_test.py b/Examples/Session10/unicode_exception_test.py deleted file mode 100755 index 24666dc2..00000000 --- a/Examples/Session10/unicode_exception_test.py +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/python - -""" -example for what happens when you pass non-ascii unicode to a Exception -""" - -#msg = u'This is an ASCII-compatible unicode message' - -msg = u'This is an non ASCII\N{EM DASH}compatible unicode message' - -print "\nDo you see this message in the Exception report?\n" -print msg -print - -raise ValueError(msg) - diff --git a/Examples/Session10/unicodify.py b/Examples/Session10/unicodify.py deleted file mode 100644 index 15683ee6..00000000 --- a/Examples/Session10/unicodify.py +++ /dev/null @@ -1,54 +0,0 @@ -#!/usr/bin/env python - -''' -Decorators to convert all arguments passed to a function or method to -unicode or str, including default arguments - -From: http://axialcorps.com/2014/03/20/unicode-str/ - -''' - - -import sys -import functools -import inspect - -def _convert_arg(arg, from_, conv, enc): - '''Safely convert unicode to string or string to unicode''' - return getattr(arg, conv)(encoding=enc) if isinstance(arg, from_) else arg - -def _wrap_convert(from_type, fn, encoding=None): - '''Decorate a function converting all str arguments to unicode or - vice-versa''' - conv = 'decode' if from_type is str else 'encode' - encoding = encoding or sys.getdefaultencoding() - - # override string defaults using partial - aspec, dflts = inspect.getargspec(fn), {} - if aspec.defaults: - for k,v in zip(aspec.args[-len(aspec.defaults):],aspec.defaults): - dflts[k] = _convert_arg(v, from_type, conv, encoding) - fn = functools.partial(fn, **dflts) - - @functools.wraps(fn.func if isinstance(fn, functools.partial) else fn) - def converted(*args, **kwargs): - args = [_convert_arg(a, from_type, conv, encoding) for a in args] - for k,v in kwargs.iteritems(): - kwargs[k] = _convert_arg(v, from_type, conv, encoding) - return fn(*args, **kwargs) - - return converted - -def unicodify(fn=None, encoding=None): - '''Convert all str arguments to unicode''' - if fn is None: - return functools.partial(unicodify, encoding=encoding) - return _wrap_convert(str, fn, encoding=encoding) - -def stringify(fn=None, encoding=None): - '''Convert all unicode arguments to str''' - if fn is None: - return functools.partial(stringify, encoding=encoding) - return _wrap_convert(unicode, fn, encoding=encoding) - -__all__ = ['unicodify', 'stringify'] \ No newline at end of file diff --git a/README.rst b/README.rst deleted file mode 100644 index 835744c3..00000000 --- a/README.rst +++ /dev/null @@ -1,15 +0,0 @@ -IntroToPython -============== - -Introduction to Python: First in the Python Certification series. - -This repository contains the source materials for the first class in the the University of Washington Professional and Continuing Education Program Python Certification Program: - -.. _Certificate in Python Programming : http://www.pce.uw.edu/certificates/python-programming.html - -See the Syllabus for more detail. - -Class lecture materials are available in a rendered version from: - -http://UWPCE-PythonCert.github.io/IntroToPython - diff --git a/Solutions/README.rst b/Solutions/README.rst index 77cf496c..4608bc0d 100644 --- a/Solutions/README.rst +++ b/Solutions/README.rst @@ -1,4 +1,4 @@ -This is place for example solutions to the assignments for the CodeFellows Foundations II Python class: sept 2014 +This is place for example solutions to the assignments for UWPCE Intro To Python class. Solutions will be added to this directory as the course progresses. diff --git a/Solutions/Session01/break_me.py b/Solutions/Session01/break_me.py new file mode 100644 index 00000000..d0dcf95e --- /dev/null +++ b/Solutions/Session01/break_me.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python + +""" +A simple set of examples that raise various Exceptions +""" + + +def name_error(): + """This function raises a NameError""" + # very easy to do -- simply try to use a name you haven't defined + x = something + + +def type_error(): + """This function raises a TypeError""" + # Try to use an object in a way that doesn't make sense + "543" / 3 + +def attribute_error(): + """This function raises an AttributeError""" + x = 5 + y = x.strip() + +# have to comment this out, because the SyntaxError keeps the code from +# running at all +# def syntax_error(): +# """This function raises a SyntaxError""" +# del = 32 # this one is tricky -- it's an error because "del" is a keyword + +# now run the functions: +# Note: I have all but one commented out, becaue the code stops running +# when the first Error is hit. + +# name_error() +# type_error() +attribute_error() diff --git a/Solutions/Session01/codingbat/Logic-1/cigar_party.py b/Solutions/Session01/codingbat/Logic-1/cigar_party.py index 7c16089f..1ee273fc 100755 --- a/Solutions/Session01/codingbat/Logic-1/cigar_party.py +++ b/Solutions/Session01/codingbat/Logic-1/cigar_party.py @@ -43,4 +43,4 @@ def cigar_party3(cigars, is_weekend): assert cigar_party(40, True) is True assert cigar_party(39, True) is False - print "All tests passed" + print("All tests passed") diff --git a/Solutions/Session01/codingbat/README.rst b/Solutions/Session01/codingbat/README.rst index b42e4bdc..97e63f80 100644 --- a/Solutions/Session01/codingbat/README.rst +++ b/Solutions/Session01/codingbat/README.rst @@ -1,7 +1,7 @@ CodingBat Solutions ==================== -A few selected solutions form teh codingbat site: +A few selected solutions from the codingbat site: http://codingbat.com/python diff --git a/Solutions/Session01/codingbat/Warmup-1/diff21.py b/Solutions/Session01/codingbat/Warmup-1/diff21.py index 915002c6..a1783537 100755 --- a/Solutions/Session01/codingbat/Warmup-1/diff21.py +++ b/Solutions/Session01/codingbat/Warmup-1/diff21.py @@ -15,5 +15,9 @@ def diff21b(n): """ direct return of conditional expression """ - return 2 * (n - 21) if n > 21 else 21-n + return 2 * (n - 21) if n > 21 else 21 - n +if __name__ == "__main__": + # needs + print(diff21(3)) + print(diff21b(3)) diff --git a/Solutions/Session01/codingbat/Warmup-1/monkey_trouble.py b/Solutions/Session01/codingbat/Warmup-1/monkey_trouble.py index 693ab669..d94c2737 100755 --- a/Solutions/Session01/codingbat/Warmup-1/monkey_trouble.py +++ b/Solutions/Session01/codingbat/Warmup-1/monkey_trouble.py @@ -1,7 +1,7 @@ #!/usr/bin/env python -############################## -## Warmup-1 > monkey_trouble +############################# +# Warmup-1 > monkey_trouble def monkey_trouble(a_smile, b_smile): @@ -43,7 +43,7 @@ def monkey_trouble4(a_smile, b_smile): if __name__ == "__main__": # a few tests - ## neat trick to test all versions: + # neat trick to test all versions: for test_fun in (monkey_trouble, monkey_trouble2, monkey_trouble3, @@ -53,4 +53,4 @@ def monkey_trouble4(a_smile, b_smile): assert test_fun(True, False) is False assert test_fun(False, True) is False - print "All tests passed" + print("All tests passed") diff --git a/Solutions/Session02/ack.py b/Solutions/Session02/ack.py index 44e1345c..920a7cc2 100755 --- a/Solutions/Session02/ack.py +++ b/Solutions/Session02/ack.py @@ -29,23 +29,22 @@ def ack(m, n): # you don't really need to test them all # they will get called as part of the recusive calls anyway... - assert ack(0,0) == 1 - assert ack(0,4) == 5 + assert ack(0, 0) == 1 + assert ack(0, 4) == 5 - assert ack(1,2) == 4 - assert ack(1,4) == 6 + assert ack(1, 2) == 4 + assert ack(1, 4) == 6 - assert ack(2,2) == 7 - assert ack(2,4) == 11 + assert ack(2, 2) == 7 + assert ack(2, 4) == 11 - assert ack(3,2) == 29 - assert ack(3,4) == 125 + assert ack(3, 2) == 29 + assert ack(3, 4) == 125 - ## ack(4,*) exceeds the recursion limit. + # ack(4,*) exceeds the recursion limit. - # check for neagative inputs -- should return None. + # check for negative inputs -- should return None. assert ack(-1, 0) is None assert ack(2, -1) is None - - print "tests passed" + print("tests passed") diff --git a/Solutions/Session02/fizz_buzz.py b/Solutions/Session02/fizz_buzz.py index a9589608..4560b65b 100755 --- a/Solutions/Session02/fizz_buzz.py +++ b/Solutions/Session02/fizz_buzz.py @@ -7,71 +7,77 @@ # basic approach: def fizzbuzz1(n): - for i in range(1, n+1): - if i%3 == 0 and i%5 == 0: - print "FizzBuzz" - elif i%3 == 0: - print "Fizz" - elif i%5 == 0: - print "Buzz" + for i in range(1, n + 1): + if i % 3 == 0 and i % 5 == 0: + print("FizzBuzz") + elif i % 3 == 0: + print ("Fizz") + elif i % 5 == 0: + print ("Buzz") else: - print i + print(i) def fizzbuzz2(n): """ Why evaluate i%3 and i%5 twice? """ - for i in range(1, n+1): + for i in range(1, n + 1): msg = '' - if i%3 == 0: + if i % 3 == 0: msg += "Fizz" - if i%5 == 0: + if i % 5 == 0: msg += "Buzz" if msg: - print msg + print(msg) else: - print i + print(i) def fizzbuzz3(n): """ - use conditional expressions: + Or print on one line... """ - for i in range(1, n+1): - msg = "Fizz" if i%3 == 0 else '' - msg += "Buzz" if i%5 == 0 else '' - print msg or i + for i in range(1, n + 1): + num = True + if i % 3 == 0: + print("\nFizz", end='') + num = False + if i % 5 == 0: + if num: + print() + print("Buzz", end='') + num = False + else: + if num: + print("\n", i, end='') def fizzbuzz4(n): """ - the one liner + use conditional expressions: """ - for i in range(1,n+1): print ( "Fizz" * (not (i%3)) + "Buzz" * (not (i%5)) ) or i + for i in range(1, n + 1): + msg = "Fizz" if i % 3 == 0 else '' + msg += "Buzz" if i % 5 == 0 else '' + print(msg or i) -def fizzbuzz_ruby(n): +def fizzbuzz5(n): """ - This is a one-liner version inspired by the Ruby one-liner - found here: - - http://www.commandercoriander.net/blog/2013/02/03/fizzbuzz-in-one-line - - This uses list comprehensions, and slicing, and is, well, pretty darn ugly! - + a one liner """ - for word in [ ("".join(["Fizz",][0:1-i%3]+["Buzz",][0:1-i%5]) or `i`) for i in range(1, n+1)]: print word + for i in range(1, n + 1): print (("Fizz" * (not (i % 3)) + "Buzz" * (not (i % 5))) or i) + if __name__ == "__main__": fizzbuzz1(16) - print + print() fizzbuzz2(16) - print + print() fizzbuzz3(16) - print + print() fizzbuzz4(16) - print - fizzbuzz_ruby(16) - - + print() + fizzbuzz4(16) + print() diff --git a/Solutions/Session02/fizz_buzz_one_liner.py b/Solutions/Session02/fizz_buzz_one_liner.py new file mode 100644 index 00000000..5b962d0a --- /dev/null +++ b/Solutions/Session02/fizz_buzz_one_liner.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 + +# This One Liner solution to the Fizz Buzz problem +# was found by a student on the internet + +for i in range(1,101): print([i,'Fizz','Buzz','FizzBuzz'][(i%3==0)+2*(i%5==0)]) + +# this is a good example of why the most compact code is not always the +# best -- readability counts! +# And this is pretty impenatrable. +# butit's also pretty nifty logic, so below, +# It's unpacked to make it easeir to understand. + +# first , add some white space to make it pep8 compatible, and more readable. + +for i in range(1, 101): print([i, 'Fizz', 'Buzz', 'FizzBuzz'][(i % 3 == 0) + 2 * (i % 5 == 0)]) + +# second, take the for loop off one line -- that really makes no difference: + +for i in range(1, 101): + print([i, 'Fizz', 'Buzz', 'FizzBuzz'][(i % 3 == 0) + 2 * (i % 5 == 0)]) + +# so we are looping through the numbers, and the contents of the print() +# is decidding what to print for i + +# unpack that line: + +for i in range(1, 101): + options_to_print = [i, 'Fizz', 'Buzz', 'FizzBuzz'] + index = 0 # default index is zero + index += (i % 3 == 0) # add one to index if it's a multiple of 3 + index += (2 * (i % 5 == 0)) # add two to the index if a multiple of 5 + print(options_to_print[index]) # print the selection. + +# there are 4 possible options that might get printed on each line: +# 1) the number, i +# 2) Fizz +# 3) Buzz +# 4) FizzBuzz + +# we now need an index to pick which of these to print +# +# remember that True and False are also the integers 1 and 0: +# so (i % 3 == 0) will return 1 (True) if i is a multiple of 3 +# and 0 (False) if not +# and (i % 5 == 0) will do the same for 5 +# so (2 * (i % 5 == 0)) will return either 0 or 2 +# +# so if the number is a multiple of neither, index will be zero, +# and we'll get the zeroth element of the list: i +# +# if i is a multiple of three (i % 3 == 0), then the index will be 1 +# +# and if i is a multiple of 5 the index will then be 3 (1+2) +# if it wasn't a multiple of three, then it will be 2 (0+2) +# +# so using the index will get the right element from the options list. +# +# pretty slick! diff --git a/Solutions/Session01/print_grid.py b/Solutions/Session02/print_grid.py similarity index 53% rename from Solutions/Session01/print_grid.py rename to Solutions/Session02/print_grid.py index b341f32b..a270eaf7 100755 --- a/Solutions/Session01/print_grid.py +++ b/Solutions/Session02/print_grid.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Chris' solution to the week 1 homework problem. +Chris' solution to the grid printing Exercise. Note that we only did the basics of loops, and you can do all this without any loops at all, so that's what I did. @@ -11,6 +11,25 @@ """ +def print_grid_trivial(): + """ + Did anyone come up with the most trivial possible solution? + """ + print(""" ++ - - - - + - - - - + +| | | +| | | +| | | +| | | ++ - - - - + - - - - + +| | | +| | | +| | | +| | | ++ - - - - + - - - - + +""") + + def print_grid(size): """ print a 2x2 grid with a total size of size @@ -19,17 +38,17 @@ def print_grid(size): a multiple of 2 """ number = 2 - box_size = int((size-1) // 2) # size of one grid box - print "box_size:", box_size + box_size = int((size - 1) // 2) # size of one grid box: integer division + print("box_size:", box_size) # top row top = ('+ ' + '- ' * box_size) * number + '+' + '\n' middle = ('| ' + ' ' * 2 * box_size) * number + '|' + '\n' - row = top + middle*box_size + row = top + middle * box_size - grid = row*number + top + grid = row * number + top - print grid + print(grid) def print_grid2(number, size): @@ -41,14 +60,14 @@ def print_grid2(number, size): :param size: size of each grid box """ # top row - top = ('+ ' + '- '*size)*number + '+' + '\n' - middle = ('| ' + ' '*2*size)*number + '|' + '\n' + top = ('+ ' + '- ' * size) * number + '+' + '\n' + middle = ('| ' + ' ' * 2 * size) * number + '|' + '\n' - row = top + middle*size + row = top + middle * size - grid = row*number + top + grid = row * number + top - print grid + print(grid) def print_grid3(size): @@ -56,10 +75,12 @@ def print_grid3(size): same as print_grid, but calling print_grid2 to do the work """ number = 2 - box_size = (size-1) / 2 # size of one grid box + box_size = (size - 1) // 2 # size of one grid box: note integer divsion! print_grid2(number, box_size) +print_grid_trivial() + print_grid(11) print_grid(7) diff --git a/Solutions/Session02/series.py b/Solutions/Session02/series.py index 8ba6ede7..ccdd4636 100755 --- a/Solutions/Session02/series.py +++ b/Solutions/Session02/series.py @@ -78,8 +78,8 @@ def sum_series(n, n0=0, n1=1): (2, 3), (3, 4), (4, 7), - (5,11), - (6,18), + (5, 11), + (6, 18), (7, 29), ] for input, output in tests: @@ -93,4 +93,4 @@ def sum_series(n, n0=0, n1=1): for n in range(0, 10): assert sum_series(n, 2, 1) == lucas(n) - print "tests passed" + print("tests passed") diff --git a/Solutions/Session02/series_template.py b/Solutions/Session02/series_template.py new file mode 100644 index 00000000..8af982d0 --- /dev/null +++ b/Solutions/Session02/series_template.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 + +""" +a template for the series assignment +""" + + +def fibonacci(n): + """ compute the nth Fibonacci number """ + pass + + +def lucas(n): + """ compute the nth Lucas number """ + pass + + +def sum_series(n, n0=0, n1=1): + """ + compute the nth value of a summation series. + + :param n0=0: value of zeroth element in the series + :param n1=1: value of first element in the series + + if n0 == 0 and n1 == 1, the result is the Fibbonacci series + + if n0 == 2 and n1 == 1, the result is the Lucas series + """ + pass + +if __name__ == "__main__": + # run some tests + assert fibonacci(0) == 0 + assert fibonacci(1) == 1 + assert fibonacci(2) == 1 + assert fibonacci(3) == 2 + assert fibonacci(4) == 3 + assert fibonacci(5) == 5 + assert fibonacci(6) == 8 + assert fibonacci(7) == 13 + + assert lucas(0) == 2 + assert lucas(1) == 1 + + assert lucas(4) == 7 + + assert sum_series(5) == fibonacci(5) + + # test if sum_series matched lucas + assert sum_series(5, 2, 1) == lucas(5) + + print("tests passed") diff --git a/Solutions/Session03/list_lab.py b/Solutions/Session03/list_lab.py index 5601e493..c081fa34 100644 --- a/Solutions/Session03/list_lab.py +++ b/Solutions/Session03/list_lab.py @@ -31,65 +31,69 @@ # Task 1 fruits = ["Apples", "Pears", "Oranges", "Peaches"] -print fruits +print(fruits) -new = raw_input("type a fruit to add> ") +new = input("type a fruit to add> ") fruits.append(new) -print fruits +print(fruits) -ind = int(raw_input("give me an index> ")) +ind = int(input("give me an index> ")) -print "you selected fruit number: %i, which is %s"%(ind, fruits[ind-1]) - -print fruits +print("you selected fruit number: %i, which is %s" % (ind, fruits[ind-1])) +print() +print("All the fruit:\n", fruits) +print() fruits.insert(0, 'Kiwi') -print fruits +print("Added a kiwi:\n", fruits) -print "All the P fruits" +print("All the P fruits") for fruit in fruits: if fruit[0].lower() == 'p': - print fruit + print(fruit) +print() # make a new list with duplicated entries - to test removal d_fruits = fruits * 2 -print " All the fruits are:", fruits -ans = raw_input("Which fruit would you like to delete? ") +print("All the fruits are:", fruits) +ans = input("Which fruit would you like to delete? ") while ans in d_fruits: d_fruits.remove(ans) -print d_fruits +print("with fruit deleted:\n", d_fruits) -## another way to do that: -## This one only requires looping through list once +# another way to do that: +# This one only requires looping through list once d_fruits = [] for fruit in fruits * 2: if fruit != ans: d_fruits.append(fruit) -print d_fruits +print("With fruit deleted another way:\n", d_fruits) +print() # keep a copy around for next step orig_fruits = fruits[:] -for fruit in fruits: - ans = raw_input("Do you like: %s? "%fruit) - if ans[0].lower() == 'n': # so they could answer y or Y or yes or Yes... +for fruit in fruits[:]: # loop through a copy! + ans = input("Do you like: %s? " % fruit) + if ans[0].lower() == 'n': # so they could answer y or Y or yes or Yes... while fruit in fruits: # just in case there are duplicates fruits.remove(fruit) elif ans[0].lower() == 'y': pass else: - print "please answer yes or no next time!" + print("please answer yes or no next time!") # Note: I could be smarter about re-asking here, # but that's not the point of this excercise... -print "here they are with only the ones you like" -print fruits +print("here they are with only the ones you like") +print(fruits) +print() fruits = orig_fruits[:] # makes a copy @@ -98,12 +102,12 @@ for fruit in fruits: r_fruits.append(fruit[::-1]) -print "here they are reversed" -print r_fruits +print("here they are reversed") +print(r_fruits) # option 2: make a copy, then modify in place: r_fruits = fruits[:] for i, fruit in enumerate(r_fruits): r_fruits[i] = fruit[::-1] -print r_fruits +print(r_fruits) diff --git a/Solutions/Session03/mailroom.py b/Solutions/Session03/mailroom.py index 871a1dd4..9fd134a0 100755 --- a/Solutions/Session03/mailroom.py +++ b/Solutions/Session03/mailroom.py @@ -1,22 +1,29 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 -from textwrap import dedent +""" +Mailroom Exercise -- as of Session 3 -- no dictionaries or Exceptions +""" + +from textwrap import dedent # nifty utility! import math # In memory representation of the donor database # using a tuple for each donor # -- kind of like a record in a database table -donor_db = [] -donor_db.append( ("William Gates, III", [653772.32, 12.17]) ) -donor_db.append( ("Jeff Bezos", [877.33]) ) -donor_db.append( ("Paul Allen", [663.23, 43.87, 1.32]) ) -donor_db.append( ("Mark Zuckerberg", [1663.23, 4300.87, 10432.0]) ) +# the donations are in a list -- so you can add to them +# Note the mutable inside an immutable +donor_db = [("William Gates, III", [653772.32, 12.17]), + ("Jeff Bezos", [877.33]), + ("Paul Allen", [663.23, 43.87, 1.32]), + ("Mark Zuckerberg", [1663.23, 4300.87, 10432.0]), + ] +#loop through the donor list and print the 0th element of the list def print_donors(): - print "Donor list:\n" + print("Donor list:\n") for donor in donor_db: - print donor[0] + print(donor[0]) def find_donor(name): @@ -28,7 +35,7 @@ def find_donor(name): :returns: The donor data structure -- None if not in the donor_db """ for donor in donor_db: - # do an non-capitalized compare + # do a case-insenstive compare if name.strip().lower() == donor[0].lower(): return donor return None @@ -38,15 +45,15 @@ def main_menu_selection(): """ Print out the main application menu and then read the user input. """ - input = raw_input(dedent(''' + action = input(dedent(''' Choose an action: - 1 - Send a Thank You - 2 - Create a Report - 3 - Quit + 't' - Send a Thank You + 'r' - Create a Report + 'q' - Quit > ''')) - return input.strip() + return action.strip() def gen_letter(donor): @@ -58,14 +65,14 @@ def gen_letter(donor): :returns: string with letter """ return dedent(''' - Dear %s + Dear {} - Thank you for your very kind donation of %.2f. + Thank you for your very kind donation of ${:.2f}. It will be put to very good use. Sincerely, -The Team - ''' % (donor[0], donor[1][-1]) ) + '''.format(donor[0], donor[1][-1])) def send_thank_you(): @@ -75,7 +82,8 @@ def send_thank_you(): # Read a valid donor to send a thank you from, handling special commands to # let the user navigate as defined. while True: - name = raw_input("Enter a donor's name (or list to see all donors or 'menu' to exit)> ").strip() + name = input("Enter a donor's name " + "(or 'list' to see all donors or 'menu' to exit)> ").strip() if name == "list": print_donors() elif name == "menu": @@ -83,17 +91,19 @@ def send_thank_you(): else: break - # Now prompt the user for a donation amount to apply. Since this is also an exit - # point to the main menu, we want to make sure this is done before mutating the db - # list object. + # Now prompt the user for a donation amount to apply. Since this is + # also an exit point to the main menu, we want to make sure this is + # done before mutating the donors list object. while True: - amount_str = raw_input("Enter a donation amount (or 'menu' to exit)> ").strip() + amount_str = input("Enter a donation amount (or 'menu' to exit) > ").strip() if amount_str == "menu": return # Make sure amount is a valid amount before leaving the input loop amount = float(amount_str) + # NOTE: this is getting a bit carried away... if math.isnan(amount) or math.isinf(amount) or round(amount, 2) == 0.00: - print "error: donation amount is invalid\n" + print("error: donation amount is invalid\n") + continue # not really needed, but makes it more clear else: break @@ -101,11 +111,13 @@ def send_thank_you(): donor = find_donor(name) if donor is None: donor = (name, []) - donor_db.append( donor ) + donor_db.append(donor) # Record the donation + # Note how the donor object can be manipulated while it is in the donors list. donor[1].append(amount) - print gen_letter(donor) + + print(gen_letter(donor)) def sort_key(item): @@ -122,24 +134,26 @@ def print_donor_report(): total_gifts = sum(gifts) num_gifts = len(gifts) avg_gift = total_gifts / num_gifts - report_rows.append( (name, total_gifts, num_gifts, avg_gift) ) + report_rows.append((name, total_gifts, num_gifts, avg_gift)) - #sort the report data + # sort the report data report_rows.sort(key=sort_key) - print "%25s | %11s | %9s | %12s"%("Donor Name","Total Given","Num Gifts","Average Gift") - print "-"*66 + # print it out in with a nice format. + print("{:25s} | {:11s} | {:9s} | {:12s}".format( + "Donor Name", "Total Given", "Num Gifts", "Average Gift")) + print("-" * 66) for row in report_rows: - print "%25s %11.2f %9i %12.2f"%row + print("{:25s} {:11.2f} {:9d} {:12.2f}".format(*row)) if __name__ == "__main__": running = True while running: selection = main_menu_selection() - if selection is "1": + if selection == "t": send_thank_you() - elif selection is "2": + elif selection == "r": print_donor_report() - elif selection is "3": + elif selection == "q": running = False else: - print "error: menu selection is invalid!" + print("error: menu selection is invalid!") diff --git a/Solutions/Session03/rot13.py b/Solutions/Session03/rot13.py index 68ddf3cc..1ca0a63a 100644 --- a/Solutions/Session03/rot13.py +++ b/Solutions/Session03/rot13.py @@ -1,7 +1,4 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- -## the above line is so I can use Unicode in the source -## only there for the µ charactor in the timings report... """ A simple function to compute rot13 encoding @@ -13,11 +10,12 @@ the alphabet, wrapping back to the beginning if necessary """ -## note: the string translate() method would be the high-performance solution +# note: the string translate() method would be the high-performance solution +# the string module has some handy constants. import string -# a few handy constanst: +# a few handy constants: a = ord('a') z = ord('z') A = ord('A') @@ -26,9 +24,9 @@ def rot13a(text): """ - My first solution + My first solution: brute force """ - # loop through the letters + # loop through the letters in the input string new_text = [] for c in text: # do upper and lower case separately @@ -42,7 +40,7 @@ def rot13a(text): o = A-1 + o-Z else: o = ord(c) - new_text.append( chr(o) ) + new_text.append(chr(o)) return "".join(new_text) @@ -53,104 +51,131 @@ def rot13b(text): And do a check on the ord value, rather than looking in string.ascii_lowercase """ - # loop through the letters + # loop through the letters in teh input string new_text = [] for c in text: o = ord(c) # do upper and lower case separately if a <= o <= z: - o = a + ( (o - a + 13)%26 ) + o = a + ((o - a + 13) % 26) elif A <= o <= Z: - o = A + ( (o - A + 13)%26 ) - new_text.append( chr(o) ) + o = A + ((o - A + 13) % 26) + new_text.append(chr(o)) return "".join(new_text) # Translation table for 1 byte string objects: -## Faster if you build a translation table and use that -## a translation table needs to be 256 characters long -## -- all ord vales from 0 to 255 +# Faster if you build a translation table and use that + # build a translation table: str_table = [] # loop through all possible ascii values -for c in range(256): +for c in range(z+1): # only need up to z # do upper and lower case separately if a <= c <= z: - c = a + (c - a + 13)%26 + c = a + (c - a + 13) % 26 elif A <= c <= Z: - c = A + (c - A + 13)%26 - str_table.append( chr(c) ) + c = A + (c - A + 13) % 26 + str_table.append(chr(c)) str_table = "".join(str_table) -## Translation table for unicode objects. -## Unicode has a LOT of code points, so you only specifiy the ones -## that need changing in a Unicode translation table -- in a dict -## NOTE: I'm not expecting anyone to do Unicode at this pint, but for -## completelness sake, it's here. -uni_table = {} +# Unicode has a LOT of code points, so better to use a dict +# and only specifiy the ones that need changing -- in a dict +# NOTE: we haven't covered dicts yet, but for completelness' sake, +# it's here. + +dict_table = {} # the lower-case letters for c in range(a, z+1): - uni_table[c] = a + (c - a + 13)%26 + dict_table[c] = a + (c - a + 13) % 26 # the lower-case letters for c in range(A, Z+1): - uni_table[c] = A + (c - A + 13)%26 + dict_table[c] = A + (c - A + 13) % 26 + +# OR use the maketrans() method, and hard code the translation +orig = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" +rotated = "nopqrstuvwxyzabcdefghijklmNOPQRSTUVWXYZABCDEFGHIJKLM" +table = str.maketrans(orig, rotated) def rot13c(text): """ This one uses str.translate() or unicode.translate() """ - if type(text) == str: - return text.translate(str_table) - elif type(text) == unicode: - return text.translate(uni_table) + return text.translate(str_table) def rot13d(text): """ - This one "cheats" by using the built-in 'rot13' encoding + this one uses a dict translation table -- so better suite to unicode """ - return text.encode('rot13') + return text.translate(dict_table) -if __name__ == "__main__": - print rot13a("Zntargvp sebz bhgfvqr arne pbeare") - print rot13b("Zntargvp sebz bhgfvqr arne pbeare") - print rot13c("Zntargvp sebz bhgfvqr arne pbeare") - print rot13d("Zntargvp sebz bhgfvqr arne pbeare") +def rot13e(text): + """ + this one uses a dict translation table -- so better suite to unicode + """ + return text.translate(dict_table) - ## rot13 should be reversible: - assert ( rot13a("Zntargvp sebz bhgfvqr arne pbeare") == - "Magnetic from outside near corner" ) +import codecs +def rot13f(text): + """ + This one "cheats" by using the built-in 'rot13' encoding + """ + return codecs.encode(text, encoding='rot13') - text = "Some random text to try!" - assert rot13a( rot13a(text) ) == text - assert rot13a(text) == rot13b(text) == rot13c(text) +if __name__ == "__main__": + print (rot13a("Zntargvp sebz bhgfvqr arne pbeare")) + print (rot13b("Zntargvp sebz bhgfvqr arne pbeare")) + print (rot13c("Zntargvp sebz bhgfvqr arne pbeare")) + print (rot13d("Zntargvp sebz bhgfvqr arne pbeare")) + print (rot13e("Zntargvp sebz bhgfvqr arne pbeare")) + print (rot13f("Zntargvp sebz bhgfvqr arne pbeare")) + - ## And a Unicode test: + assert (rot13a("Zntargvp sebz bhgfvqr arne pbeare") == + "Magnetic from outside near corner") - text = u"Some random text to try!" - print rot13a(text) + # rot13 should be reversible: + text = "Some random text to try!" + assert rot13a(rot13a(text)) == text - assert rot13a( rot13a(text) ) == text + # they all should do the same thing + assert (rot13a(text) == + rot13b(text) == + rot13c(text) == + rot13d(text) == + rot13e(text) == + rot13f(text) + ) - assert rot13a(text) == rot13b(text) == rot13c(text) == rot13d(text) + print ("All assertions pass") - print "All assertions pass" +# # Some timings: +# # Note that the translate tabel versions are MUCH faster +# In [2]: timeit rot13a('This is a pretty short string, but maybe long enough to test') +# 10000 loops, best of 3: 31.5 µs per loop -# In [34]: timeit rot13a('This is a pretty short string, but maybe long enough to test') -# 10000 loops, best of 3: 29.4 µs per loop +# In [3]: timeit rot13b('This is a pretty short string, but maybe long enough to test') +# 10000 loops, best of 3: 32.5 µs per loop -# In [35]: timeit rot13b('This is a pretty short string, but maybe long enough to test') -# 10000 loops, best of 3: 29 µs per loop +# In [4]: timeit rot13c('This is a pretty short string, but maybe long enough to test') +# The slowest run took 9.36 times longer than the fastest. This could mean that an intermediate result is being cached +# 1000000 loops, best of 3: 1.03 µs per loop -# In [36]: timeit rot13c('This is a pretty short string, but maybe long enough to test') -# 1000000 loops, best of 3: 419 ns per loop +# In [5]: timeit rot13c('This is a pretty short string, but maybe long enough to test') +# The slowest run took 10.23 times longer than the fastest. This could mean that an intermediate result is being cached +# 1000000 loops, best of 3: 1.02 µs per loop -# In [37]: timeit rot13d('This is a pretty short string, but maybe long enough to test') -# 100000 loops, best of 3: 2.78 µs per loop +# In [6]: timeit rot13d('This is a pretty short string, but maybe long enough to test') +# The slowest run took 6.75 times longer than the fastest. This could mean that an intermediate result is being cached +# 1000000 loops, best of 3: 1.1 µs per loop +# In [7]: timeit rot13e('This is a pretty short string, but maybe long enough to test') +# The slowest run took 8.59 times longer than the fastest. This could mean that an intermediate result is being cached +# 100000 loops, best of 3: 2.31 µs per loop diff --git a/Solutions/Session03/slicing_lab.py b/Solutions/Session03/slicing_lab.py new file mode 100644 index 00000000..9c81c346 --- /dev/null +++ b/Solutions/Session03/slicing_lab.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 + +""" +The version done in class +""" + + +def swap(seq): + return seq[-1:]+seq[1:-1]+seq[:1] + + +assert swap('something') == 'gomethins' +assert swap(tuple(range(10))) == (9, 1, 2, 3, 4, 5, 6, 7, 8, 0) + + +def rem(seq): + return seq[::2] + +assert rem('a word') == 'awr' + + +def rem4(seq): + return seq[4:-4:2] + +print(rem4((1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11), )) + + +def reverse(seq): + return seq[::-1] + + +print(reverse('a string')) + + +def thirds(seq): + i = len(seq)//3 + return seq[i:-i] + seq[-i:] + seq[:i] + +print (thirds(tuple(range(12)))) diff --git a/Solutions/Session03/sort_example.py b/Solutions/Session03/sort_example.py new file mode 100644 index 00000000..d731780e --- /dev/null +++ b/Solutions/Session03/sort_example.py @@ -0,0 +1,14 @@ +def sort_key(item): + name = item[0] + last_name = name.split()[1] + return last_name.upper() + +donor_db = [("William Gates, III", [653772.32, 12.17]), + ("Jeff Bezos", [877.33]), + ("Paul Allen", [663.23, 43.87, 1.32]), + ("Mark Zuckerberg", [1663.23, 4300.87, 10432.0]), + ] + +print(sorted(donor_db, key=sort_key)) +print() +print(sorted(donor_db, key=sort_key, reverse=True)) diff --git a/Solutions/Session03/string_formatting.py b/Solutions/Session03/string_formatting.py index 1b35e50b..b66e1298 100644 --- a/Solutions/Session03/string_formatting.py +++ b/Solutions/Session03/string_formatting.py @@ -3,48 +3,48 @@ """ String formatting lab: -This version using "old style" formatting +This version using the format() method """ -# Rewrite: "the first 3 numbers are: %i, %i, %i"%(1,2,3) + +##### +# Write a format string that will take: +# ( 2, 123.4567, 10000) +# and produce: +# 'file_002 : 123.46, 1.00e+04' +##### + +print("file_{:03d} : {:10.2f}, {:.2g}".format(2, 123.4567, 10000)) + +# could use '{:.2e}' for the last one, too -- I like '%g' -- +# it does significant figures... + +####################### +# Rewrite: "the 3 numbers are: %i, %i, %i"%(1,2,3) # for an arbitrary number of numbers... # solution 1 # the goal was to demonstrate dynamic building of format strings: -# create the numbers -numbers = [32, 56, 34, 12, 48, 18] - -# build the format string for the numbers: -formatter = " %i," * len(numbers) -formatter = formatter[:-1] # take the extra comma off the end +def formatter(t): + fstring = "the {:d} numbers are: ".format(len(t)) + fstring += ", ".join(['{:d}'] * len(t)) + # * unpacks a sequence into the arguments of a function -- we'll get to that! + return fstring.format(*t) -# put it together with the rest of the string -formatter = "the first %i numbers are: %s"%(len(numbers), formatter) +# call it with a couple different tuples of numbers: +formatter((2, 3, 5)) -# use it: -# the format operator needs a tuple -# tuple(seq) will make a tuple out of any sequence -print formatter%tuple(numbers) +formatter((2, 3, 5, 7, 9)) # solution 2 -# in class, a couple people realized that str() would make a nice string from +# some of you realized that str() would make a nice string from # a list or tuple -numbers_str = str(numbers)[1:-1] # make a string, remove the brackets -# put it together with the rest of the string -print "the first %i numbers are: %s"%(len(numbers), numbers_str) - -##### -# Write a format string that will take: -# ( 2, 123.4567, 10000) -# and produce: -# 'file_002 : 123.46, 1e+04' -##### - -t = (2, 123.4567, 10000) -print "file_%03i : %10.2f, %.3g"%t +numbers = (34, 12, 3, 56) -# could use '%e' for the last one, too -- I like '%g' -- it does significant figures... +numbers_str = str(numbers)[1:-1] # make a string, remove the brackets +# put it together with the rest of the string +print("the first {:d} numbers are: {}".format(len(numbers), numbers_str)) diff --git a/Solutions/Session03/string_formatting_new.py b/Solutions/Session03/string_formatting_new.py deleted file mode 100644 index 2d1cdade..00000000 --- a/Solutions/Session03/string_formatting_new.py +++ /dev/null @@ -1,46 +0,0 @@ -#!/usr/bin/env python - -""" -String formatting lab: - -This version using "new style" formatting - -""" -# Rewrite: "the first 3 numbers are: %i, %i, %i"%(1,2,3) -# for an arbitrary number of numbers... - -# solution 1 -# the goal was to demonstrate dynamic building of format strings: - -# create the numbers -numbers = [32, 56, 34, 12, 48, 18] - -# build the format string for the numbers: -formatter = ", ".join(["{:d}"] * len(numbers)) - -# put it together with the rest of the string -formatter = "the first {0:d} numbers are: {1}".format(len(numbers), formatter) - -# use it: -# * unpacks a sequence inot the arguments of a function -- we'll get to that! -print formatter.format(*numbers) - -# solution 2 -# in class, a couple people realized that str() would make a nice string from -# a list or tuple - -numbers_str = str(numbers)[1:-1] # make a string, remove the brackets -# put it together with the rest of the string -print "the first {:d} numbers are: {}".format(len(numbers), numbers_str) - -##### -# Write a format string that will take: -# ( 2, 123.4567, 10000) -# and produce: -# 'file_002 : 123.46, 1e+04' -##### - -print "file_{:03d} : {:10.2f}, {:.3g}".format(2, 123.4567, 10000) - -# could use '{:.3e}' for the last one, too -- I like '%g' -- it does significant figures... - diff --git a/slides_sources/old_versions/week-03/code/string_formatting_solution.py b/Solutions/Session03/string_formatting_old.py similarity index 55% rename from slides_sources/old_versions/week-03/code/string_formatting_solution.py rename to Solutions/Session03/string_formatting_old.py index 3a8951ba..1e3d58d2 100644 --- a/slides_sources/old_versions/week-03/code/string_formatting_solution.py +++ b/Solutions/Session03/string_formatting_old.py @@ -3,6 +3,9 @@ """ String formatting lab: +This version using "old style" formatting + +Still pretty hand, and while less flexible, also a bit simpler """ # Rewrite: "the first 3 numbers are: %i, %i, %i"%(1,2,3) # for an arbitrary number of numbers... @@ -14,34 +17,36 @@ numbers = [32, 56, 34, 12, 48, 18] # build the format string for the numbers: -formatter = " %i," * len(numbers) +formatter = ("%i, " * len(numbers))[:-2] # take the extra comma and space off the end -formatter = formatter[:-1] # take the extra comma off the end +# or use join(): +#formatter = ", ".join(["%i"] * len(numbers)) # put it together with the rest of the string -formatter = "the first %i numbers are: %s"%(len(numbers), formatter) +formatter = "the first %i numbers are: %s" % (len(numbers), formatter) # use it: # the format operator needs a tuple # tuple(seq) will make a tuple out of any sequence -print formatter%tuple(numbers) +print(formatter % tuple(numbers)) # solution 2 # in class, a couple people realized that str() would make a nice string from # a list or tuple -numbers_str = str(numbers)[1:-1] # make a string, remove the brackets +numbers_str = str(numbers)[1:-1] # make a string, remove the brackets # put it together with the rest of the string -print "the first %i numbers are: %s"%(len(numbers), numbers_str) +print("the first %i numbers are: %s" % (len(numbers), numbers_str)) ##### # Write a format string that will take: # ( 2, 123.4567, 10000) -# and produce: +# and produce: # 'file_002 : 123.46, 1e+04' ##### t = (2, 123.4567, 10000) -print "file_%03i, %10.2f, %.3g"%t +print("file_%03i : %10.2f, %.3g" % t) -# could use '%e' for the last one, too -- I like '%g' -- it does significant figures... +# could use '%e' for the last one, too -- I like '%g' -- +# it does significant figures... diff --git a/Solutions/Session04/arbitrary_key.py b/Solutions/Session04/arbitrary_key.py new file mode 100644 index 00000000..eb7c80a1 --- /dev/null +++ b/Solutions/Session04/arbitrary_key.py @@ -0,0 +1,118 @@ +#!/usr/bin/env python3 + +# getting an arbitrary item from a dictionary + +""" +in class, we spend a bi tof time figureing out how to get an arbitrary +item from a dictionary without removing it: dict.popitem() gets you an +arbitrary item, but it removes it also. In this case, we didn't want to +remove it + +In this case, we only needed and arbitrary key, but the principle is +the same if you want the whole item. + +""" + +import random + +# make a tiny little dict to test with: +tiny = {'that': 2, 'this': 3, 'the other': 56} + +# One simple solution is to take advantage of popitem() and then put it back: +item = tiny.popitem() +print("An arbitrary key") +print(item[0]) +# put it back: +tiny.update((item,)) +# it's still the same +print(tiny) + +# but this is pretty ugly: +# - we only want a key but we got the entire item, so we need to pull the key out of that +# - even worse, we have to save the entire item and put it back +# So this is pretty unsatisfactory + + +# solution we used in class: +print("An arbitrary key") +print(list(tiny.keys())[0]) + +# This works fine -- the list of the keys is in arbitrary order, so +# grabbing the first one gets you an arbitrary key +# +# This also has the advantage that you could easily adapt it to give you a ransom key instead: +print("a random key") +print(random.choice(list(tiny.keys()))) + +# However -- the downside of this is that you are making an entire list of all the keys, +# just to toss it away afterward. Computers are fast and have lots of memory, so probably +# not a big deal, but it is good to think it terms of efficient algorithms, if they don't +# complicate your code. This is why dict.keys() does not return a list (in py3): +print("type of keys():", type(tiny.keys())) + +# the dict_Keys type is an "iterable" -- something you can iterate through with a for loop: +for key in tiny.keys(): + print(key) + +# The dict_keys object "gives" the for loop each key, one at a time, without ever making +# a full copy of them all. + +# So we could take advantage of this to get an arbitrary key without making an entire list +# first: + +for key in tiny.keys(): + print("An arbitrary key") + print(key) + break +# (notice that it's the SAME arbitrary key as the first item of the list method -- +# it IS the first item off the list. + +# So this leads us to the solution we ALMOST got to in class: +# +# for loops use the "iterator protocol". You can use that directly if you want to control how +# things are iterated over. +# +# We'll get into this in detail later in the class, but the (very) short version is that the +# next() function returns the next item from an iterator. So if you call it only once, you get +# the first item: + +print("An arbitrary key") +print(next(iter(tiny.keys()))) + +# Wait! what is that `iter()` call? the `iter()` function takes a "iterable" and returns and +# "iterator" from it. An "iterable" is something you can iterate over -- an "iterator" is the +# actual object that saves state and returns the actual items. +# You can't call next() on an iterable: + +next(tiny.keys()) + +# you get a type error: +# TypeError: 'dict_keys' object is not an iterator + + +# So what should one do in this case? Isn't part of Python's Philosophy: +# There is only one way to do it? +# +# This is why, in class, I thought there should be a quick and easy way to do this. +# +# However, on further thought, this is pretty unusual thing to need to do: +# .popitems() makes sense -- you sometimes need to pull out a item from a dict one by one, and +# do something with it, and want it removed from the dict. you are likeley to do +# this over an over again until the dict is empty. +# but not caring which item you get, but wanting the dict to remain unaltered is a pretty rare use case. +# +# so that's why there isn't one obvious way to do it + +# I think that: +# +# next(iter(tiny.keys())) +# +# is probably the most "pythonic" way to do it -- compact and efficient +# However, it does require fairly advanced understanding of iterables and iterators. +# So: +# +# list(tiny.keys())[0] +# +# is a fine option. And leaves the door open for using random.choice, too. + + diff --git a/Solutions/Session04/copy_file.py b/Solutions/Session04/copy_file.py old mode 100644 new mode 100755 index 7655c4ed..371902b6 --- a/Solutions/Session04/copy_file.py +++ b/Solutions/Session04/copy_file.py @@ -1,21 +1,31 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 """ Very simple script to copy a file + +Use like so: + +python copy_file original_file_name, new_file_name """ import sys -source, dest = sys.argv[1:3] +print(sys.argv) -infile = open(source, 'rb') # read binary mode -outfile = open(dest, 'wb') # write binary mode +source, dest = sys.argv[1:3] -outfile.write(infile.read()) -infile.close() -outfile.close() +# # binary mode important here. +# with open(source, 'rb') as infile, open(dest, 'wb') as outfile: +# outfile.write(infile.read()) -## or as a one-liner: -# open(dest, 'wb').write(open(source, 'rb').read()) +# what if the file it too big to easily fit in memory? +# do it in chunks: +CHUNKSIZE = 128 # Really should be bigger -- 1MB? but for testing... +with open(source, 'rb') as infile, open(dest, 'wb') as outfile: + while True: + data = infile.read(CHUNKSIZE) + if not data: # End of file + break + outfile.write(data) diff --git a/Solutions/Session04/dict_set_solution.py b/Solutions/Session04/dict_set_solution.py index f0b6d32f..4d914c74 100755 --- a/Solutions/Session04/dict_set_solution.py +++ b/Solutions/Session04/dict_set_solution.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python + #!/usr/bin/env python """ dict/set lab solutions: Chris' version. @@ -13,16 +13,16 @@ "cake": "Chocolate"} # Display the dictionary. -print d +print(d) -#or something fancier, like: -print "{name} is from {city}, and likes {cake} cake.".format(name=d['name'], - city=d['city'], - cake=d['cake']) +# or something fancier, like: +print ("{name} is from {city}, and likes {cake} cake.".format(name=d['name'], + city=d['city'], + cake=d['cake'])) # But if that seems like unnecceasry typing -- it is: # we'll learn the **d form is session05: -print "{name} is from {city}, and likes {cake} cake.".format(**d) +print("{name} is from {city}, and likes {cake} cake.".format(**d)) # Delete the entry for "cake". @@ -30,29 +30,29 @@ del d["cake"] -print d +print(d) # Add an entry for "fruit" with "Mango" and display the dictionary. d['fruit'] = 'Mango' -print d +print(d) # Display the dictionary keys. -print d.keys() +print(d.keys()) # Display the dictionary values. -print d.values() +print(d.values()) # Display whether or not "cake" is a key in the dictionary (i.e. False) (now). -print 'cake' in d +print('cake' in d) # Display whether or not "Mango" is a value in the dictionary. -print 'Mango' in d.values() +print('Mango' in d.values()) # Using the dict constructor and zip, build a dictionary of numbers # from zero to fifteen and the hexadecimal equivalent (string is fine). @@ -64,7 +64,7 @@ hex_dict = dict(zip(nums, hexes)) -print hex_dict +print(hex_dict) # Using the dictionary from item 1: Make a dictionary using the same keys @@ -73,26 +73,18 @@ a_dict = {} for key, val in d.items(): a_dict[key] = val.count('t') -print a_dict +print(a_dict) # replacing the values in the original dict: -<<<<<<< HEAD - for key, val in d.items(): d[key] = val.count('t') -print d +print(d) -======= +# Or the direct way -- update() is very handy! d.update(a_dict) -print d - -# could also do it this way. -#for key, val in d.items(): -# d[key] = val.count('t') -#print d +print(d) ->>>>>>> 46ce6ea660d7458ad07d0a092349a82db8fecb7c # Create sets s2, s3 and s4 that contain numbers from zero through # twenty, divisible 2, 3 and 4. @@ -102,32 +94,30 @@ s3 = set() s4 = set() for i in range(21): - if not i%2: + if not i % 2: s2.add(i) - if not i%3: + if not i % 3: s3.add(i) - if not i%4: + if not i % 4: s4.add(i) -print s2 -print s3 -print s4 +print(s2) +print(s3) +print(s4) # Display if s3 is a subset of s2 (False) - -print s3.issubset(s2) +print(s3.issubset(s2)) # and if s4 is a subset of s2 (True). - -print s4.issubset(s2) +print(s4.issubset(s2)) # Create a set with the letters in 'Python' and add 'i' to the set. s = set('Python') s.add('i') -print s +print(s) # maybe: s = set('Python'.lower()) # that wasn't specified... @@ -139,14 +129,10 @@ # display the union and intersection of the two sets. -print "union:", s.union(fs) -print "intersection:", s.intersection(fs) - -## not that order doesn't matter for these: - -print "union:", fs.union(s) -print "intersection:", fs.intersection(s) - - +print("union:", s.union(fs)) +print("intersection:", s.intersection(fs)) +# note that order doesn't matter for these: +print("union:", fs.union(s)) +print("intersection:", fs.intersection(s)) diff --git a/Solutions/Session04/file_lister.py b/Solutions/Session04/file_lister.py old mode 100644 new mode 100755 index 766a6e2f..015c43de --- a/Solutions/Session04/file_lister.py +++ b/Solutions/Session04/file_lister.py @@ -8,22 +8,27 @@ """ import os +import pathlib -## Two ways to do this: +# Two ways to do this: -## One: get file names, and convert to absolute path: +# One: get file names, and convert to absolute path: -print "listing using method one" +print("listing using method one") for name in os.listdir(os.curdir): - print os.path.abspath(name) + print(os.path.abspath(name)) -# Note: os.curdir is "." on all sytems I knwo of... +# Note: os.curdir is "." on all sytems I know of... # so you could just do os.curdir(".") -# Back in the days of the old MacOS -- it was different there. +# Back in the days of the old MacOS -- it WAS different there. -## Two: get the current dir path, then join that with each of the filenames +# Two: get the current dir path, then join that with each of the filenames curdir = os.getcwd() -print "listing using method two" +print("listing using method two") for name in os.listdir(curdir): - print os.path.join(curdir, name) + print(os.path.join(curdir, name)) +# Three: using pathlib: +print("listing using pathlib") +for name in pathlib.Path().glob('*'): + print(name.absolute()) diff --git a/Solutions/Session04/get_langs.py b/Solutions/Session04/get_langs.py old mode 100644 new mode 100755 index cefb9b20..1cd05698 --- a/Solutions/Session04/get_langs.py +++ b/Solutions/Session04/get_langs.py @@ -1,23 +1,54 @@ #!/usr/bin/env python """ -script to determine what programming languages students came to this clas iwth +script to determine what programming languages students came to this class with """ -file_path = '../../Examples/Session01/students.txt' +infilename = "students.txt" -all_langs = set() # use a set to ensure unique values +# all_langs = set() # use a set to ensure unique values -f = open(file_path) # default read text mode +# with open(infilename) as f: # default read text mode -f.readline() # read and toss the header +# f.readline() # read and toss the header -for line in f: - langs = line.split(':')[1] - langs = langs.split(',') - for lang in langs: - lang = lang.strip().lower() - if lang: # don't want empty strings - all_langs.add(lang) -for lang in all_langs: - print lang +# # loop through the rest of the lines in the file +# for line in f: +# langs = line.split(':')[1] # toss the names +# # a bit of cleanup: +# langs = langs.replace(',', ' ') +# langs = langs.replace('and', ' ') +# langs = langs.split() # separate the languages +# for lang in langs: +# lang = lang.strip().capitalize() # clean them up -- and make case the same +# if lang: # don't want empty strings +# all_langs.add(lang) + +# # done reading the file -- no on to the rest +# for lang in all_langs: +# print(lang) + +# extra credit version: using collections.Counter: +# https://docs.python.org/3/library/collections.html#collections.Counter + +from collections import Counter +all_langs = Counter() + +# all_langs = {} + +with open(infilename) as f: + f.readline() # read and toss the header + + for line in f: + langs = line.partition(':')[2] # get just what's after the colon + # a bit of cleanup: + langs = langs.replace(',', ' ').replace('and', ' ') + langs = langs.split() # separate the languages + for lang in langs: + lang = lang.strip().capitalize() # clean them up -- and make case the same + if lang: # don't want empty strings + all_langs[lang] += 1 + +print("And now the counted version") +for lang, count in all_langs.items(): + print(lang, ":", count) diff --git a/Solutions/Session04/mailroom.py b/Solutions/Session04/mailroom2.py similarity index 67% rename from Solutions/Session04/mailroom.py rename to Solutions/Session04/mailroom2.py index 9d3a4a0f..81d3a97e 100755 --- a/Solutions/Session04/mailroom.py +++ b/Solutions/Session04/mailroom2.py @@ -1,5 +1,4 @@ #!/usr/bin/env python - """ mailroom assignment @@ -18,23 +17,27 @@ # using a tuple for each donor # -- kind of like a record in a database table # using a dict with a lower case version of the donor's name as the key -donor_db = {} -donor_db['william gates, iii'] = ("William Gates, III", [653772.32, 12.17]) -donor_db['jeff bezos'] = ("Jeff Bezos", [877.33]) -donor_db['paul allen'] = ("Paul Allen", [663.23, 43.87, 1.32]) -donor_db['mark zuckerberg'] = ("Mark Zuckerberg", [1663.23, 4300.87, 10432.0]) +# This makes it easier to have a 'normalized' key. +# you could get a bit fancier by having each "record" be a dict, with +# "name" and "donations" as keys. +def get_donor_db(): + return {'william gates iii': ("William Gates III", [653772.32, 12.17]), + 'jeff bezos': ("Jeff Bezos", [877.33]), + 'paul allen': ("Paul Allen", [663.23, 43.87, 1.32]), + 'mark zuckerberg': ("Mark Zuckerberg", [1663.23, 4300.87, 10432.0]), + } def list_donors(): """ - creates alist of the donors as a string, so tehy can be printed + creates a list of the donors as a string, so they can be printed - not calling print from here makes it more flexible and easier to + Not calling print from here makes it more flexible and easier to test """ listing = ["Donor list:"] for donor in donor_db.values(): - listing.append( donor[0] ) + listing.append(donor[0]) return "\n".join(listing) @@ -52,7 +55,7 @@ def find_donor(name): def add_donor(name): """ - add a new donor to the donr db + Add a new donor to the donor db :param: the name of the donor @@ -68,7 +71,7 @@ def main_menu_selection(): """ Print out the main application menu and then read the user input. """ - input = raw_input(dedent(''' + action = input(dedent(''' Choose an action: 1 - Send a Thank You @@ -77,7 +80,7 @@ def main_menu_selection(): 4 - Quit > ''')) - return input.strip() + return action.strip() def gen_letter(donor): @@ -87,15 +90,18 @@ def gen_letter(donor): :param: donor tuple :returns: string with letter + + note: This doesn't actually write to a file -- that's a separate + function. This makes it more flexible and easier to test. """ - return dedent('''Dear {0:s} + return dedent('''Dear {0:s}, Thank you for your very kind donation of ${1:.2f}. It will be put to very good use. Sincerely, -The Team - '''.format(donor[0], donor[1][-1]) ) + '''.format(donor[0], donor[1][-1])) def send_thank_you(): @@ -105,9 +111,9 @@ def send_thank_you(): # Read a valid donor to send a thank you from, handling special commands to # let the user navigate as defined. while True: - name = raw_input("Enter a donor's name (or list to see all donors or 'menu' to exit)> ").strip() + name = input("Enter a donor's name (or list to see all donors or 'menu' to exit)> ").strip() if name == "list": - print list_donors() + print(list_donors()) elif name == "menu": return else: @@ -115,9 +121,9 @@ def send_thank_you(): # Now prompt the user for a donation amount to apply. Since this is # also an exit point to the main menu, we want to make sure this is - # done before mutating the db . + # done before mutating the db. while True: - amount_str = raw_input("Enter a donation amount (or 'menu' to exit)> ").strip() + amount_str = input("Enter a donation amount (or 'menu' to exit)> ").strip() if amount_str == "menu": return # Make sure amount is a valid amount before leaving the input loop @@ -130,7 +136,7 @@ def send_thank_you(): raise ValueError # in this case, the ValueError could be raised by the float() call, or by the NaN-check except ValueError: - print "error: donation amount is invalid\n" + print("error: donation amount is invalid\n") else: break @@ -142,11 +148,11 @@ def send_thank_you(): # Record the donation donor[1].append(amount) - print gen_letter(donor) + print(gen_letter(donor)) def sort_key(item): - ## used to sort on name in donor_db + # used to sort on name in donor_db return item[1] @@ -162,15 +168,18 @@ def generate_donor_report(): total_gifts = sum(gifts) num_gifts = len(gifts) avg_gift = total_gifts / num_gifts - report_rows.append( (name, total_gifts, num_gifts, avg_gift) ) + report_rows.append((name, total_gifts, num_gifts, avg_gift)) - #sort the report data + # sort the report data report_rows.sort(key=sort_key) report = [] - report.append("%25s | %11s | %9s | %12s"%("Donor Name","Total Given","Num Gifts","Average Gift") ) - report.append("-"*66) + report.append("{:25s} | {:11s} | {:9s} | {:12s}".format("Donor Name", + "Total Given", + "Num Gifts", + "Average Gift")) + report.append("-" * 66) for row in report_rows: - report.append("%25s %11.2f %9i %12.2f"%row) + report.append("{:25s} ${:10.2f} {:9d} ${:11.2f}".format(*row)) return "\n".join(report) @@ -180,18 +189,22 @@ def save_letters_to_disk(): """ for donor in donor_db.values(): letter = gen_letter(donor) - filename = donor[0].replace(" ","_") + ".txt" # I don't like spaces in filenames... + # I don't like spaces in filenames... + filename = donor[0].replace(" ", "_") + ".txt" open(filename, 'w').write(letter) def print_donor_report(): - print generate_donor_report() + print(generate_donor_report()) def quit(): sys.exit(0) if __name__ == "__main__": + + donor_db = get_donor_db() + running = True selection_dict = {"1": send_thank_you, @@ -204,7 +217,4 @@ def quit(): try: selection_dict[selection]() except KeyError: - print "error: menu selection is invalid!" - - - + print("error: menu selection is invalid!") diff --git a/Solutions/Session04/print_paths.py b/Solutions/Session04/print_paths.py deleted file mode 100644 index 507fb987..00000000 --- a/Solutions/Session04/print_paths.py +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/env python - -""" -very simple script to print the full paths in the current dir -""" - -import os - -files = os.listdir('.') -for name in files: - print os.path.abspath(name) - diff --git a/Solutions/Session04/sherlock_small.txt b/Solutions/Session04/sherlock_small.txt new file mode 100644 index 00000000..5c551ebf --- /dev/null +++ b/Solutions/Session04/sherlock_small.txt @@ -0,0 +1,200 @@ +Project Gutenberg's The Adventures of Sherlock Holmes, by Arthur Conan Doyle + +This eBook is for the use of anyone anywhere at no cost and with +almost no restrictions whatsoever. You may copy it, give it away or +re-use it under the terms of the Project Gutenberg License included +with this eBook or online at www.gutenberg.net + + +Title: The Adventures of Sherlock Holmes + +Author: Arthur Conan Doyle + +Posting Date: April 18, 2011 [EBook #1661] +First Posted: November 29, 2002 + +Language: English + + +*** START OF THIS PROJECT GUTENBERG EBOOK THE ADVENTURES OF SHERLOCK HOLMES *** + + + + +Produced by an anonymous Project Gutenberg volunteer and Jose Menendez + + + + + + + + + +THE ADVENTURES OF SHERLOCK HOLMES + +by + +SIR ARTHUR CONAN DOYLE + + + + I. A Scandal in Bohemia + II. The Red-headed League + III. A Case of Identity + IV. The Boscombe Valley Mystery + V. The Five Orange Pips + VI. The Man with the Twisted Lip + VII. The Adventure of the Blue Carbuncle +VIII. The Adventure of the Speckled Band + IX. The Adventure of the Engineer's Thumb + X. The Adventure of the Noble Bachelor + XI. The Adventure of the Beryl Coronet + XII. The Adventure of the Copper Beeches + + + + +ADVENTURE I. A SCANDAL IN BOHEMIA + +I. + +To Sherlock Holmes she is always THE woman. I have seldom heard +him mention her under any other name. In his eyes she eclipses +and predominates the whole of her sex. It was not that he felt +any emotion akin to love for Irene Adler. All emotions, and that +one particularly, were abhorrent to his cold, precise but +admirably balanced mind. He was, I take it, the most perfect +reasoning and observing machine that the world has seen, but as a +lover he would have placed himself in a false position. He never +spoke of the softer passions, save with a gibe and a sneer. They +were admirable things for the observer--excellent for drawing the +veil from men's motives and actions. But for the trained reasoner +to admit such intrusions into his own delicate and finely +adjusted temperament was to introduce a distracting factor which +might throw a doubt upon all his mental results. Grit in a +sensitive instrument, or a crack in one of his own high-power +lenses, would not be more disturbing than a strong emotion in a +nature such as his. And yet there was but one woman to him, and +that woman was the late Irene Adler, of dubious and questionable +memory. + +I had seen little of Holmes lately. My marriage had drifted us +away from each other. My own complete happiness, and the +home-centred interests which rise up around the man who first +finds himself master of his own establishment, were sufficient to +absorb all my attention, while Holmes, who loathed every form of +society with his whole Bohemian soul, remained in our lodgings in +Baker Street, buried among his old books, and alternating from +week to week between cocaine and ambition, the drowsiness of the +drug, and the fierce energy of his own keen nature. He was still, +as ever, deeply attracted by the study of crime, and occupied his +immense faculties and extraordinary powers of observation in +following out those clues, and clearing up those mysteries which +had been abandoned as hopeless by the official police. From time +to time I heard some vague account of his doings: of his summons +to Odessa in the case of the Trepoff murder, of his clearing up +of the singular tragedy of the Atkinson brothers at Trincomalee, +and finally of the mission which he had accomplished so +delicately and successfully for the reigning family of Holland. +Beyond these signs of his activity, however, which I merely +shared with all the readers of the daily press, I knew little of +my former friend and companion. + +One night--it was on the twentieth of March, 1888--I was +returning from a journey to a patient (for I had now returned to +civil practice), when my way led me through Baker Street. As I +passed the well-remembered door, which must always be associated +in my mind with my wooing, and with the dark incidents of the +Study in Scarlet, I was seized with a keen desire to see Holmes +again, and to know how he was employing his extraordinary powers. +His rooms were brilliantly lit, and, even as I looked up, I saw +his tall, spare figure pass twice in a dark silhouette against +the blind. He was pacing the room swiftly, eagerly, with his head +sunk upon his chest and his hands clasped behind him. To me, who +knew his every mood and habit, his attitude and manner told their +own story. He was at work again. He had risen out of his +drug-created dreams and was hot upon the scent of some new +problem. I rang the bell and was shown up to the chamber which +had formerly been in part my own. + +His manner was not effusive. It seldom was; but he was glad, I +think, to see me. With hardly a word spoken, but with a kindly +eye, he waved me to an armchair, threw across his case of cigars, +and indicated a spirit case and a gasogene in the corner. Then he +stood before the fire and looked me over in his singular +introspective fashion. + +"Wedlock suits you," he remarked. "I think, Watson, that you have +put on seven and a half pounds since I saw you." + +"Seven!" I answered. + +"Indeed, I should have thought a little more. Just a trifle more, +I fancy, Watson. And in practice again, I observe. You did not +tell me that you intended to go into harness." + +"Then, how do you know?" + +"I see it, I deduce it. How do I know that you have been getting +yourself very wet lately, and that you have a most clumsy and +careless servant girl?" + +"My dear Holmes," said I, "this is too much. You would certainly +have been burned, had you lived a few centuries ago. It is true +that I had a country walk on Thursday and came home in a dreadful +mess, but as I have changed my clothes I can't imagine how you +deduce it. As to Mary Jane, she is incorrigible, and my wife has +given her notice, but there, again, I fail to see how you work it +out." + +He chuckled to himself and rubbed his long, nervous hands +together. + +"It is simplicity itself," said he; "my eyes tell me that on the +inside of your left shoe, just where the firelight strikes it, +the leather is scored by six almost parallel cuts. Obviously they +have been caused by someone who has very carelessly scraped round +the edges of the sole in order to remove crusted mud from it. +Hence, you see, my double deduction that you had been out in vile +weather, and that you had a particularly malignant boot-slitting +specimen of the London slavey. As to your practice, if a +gentleman walks into my rooms smelling of iodoform, with a black +mark of nitrate of silver upon his right forefinger, and a bulge +on the right side of his top-hat to show where he has secreted +his stethoscope, I must be dull, indeed, if I do not pronounce +him to be an active member of the medical profession." + +I could not help laughing at the ease with which he explained his +process of deduction. "When I hear you give your reasons," I +remarked, "the thing always appears to me to be so ridiculously +simple that I could easily do it myself, though at each +successive instance of your reasoning I am baffled until you +explain your process. And yet I believe that my eyes are as good +as yours." + +"Quite so," he answered, lighting a cigarette, and throwing +himself down into an armchair. "You see, but you do not observe. +The distinction is clear. For example, you have frequently seen +the steps which lead up from the hall to this room." + +"Frequently." + +"How often?" + +"Well, some hundreds of times." + +"Then how many are there?" + +"How many? I don't know." + +"Quite so! You have not observed. And yet you have seen. That is +just my point. Now, I know that there are seventeen steps, +because I have both seen and observed. By-the-way, since you are +interested in these little problems, and since you are good +enough to chronicle one or two of my trifling experiences, you +may be interested in this." He threw over a sheet of thick, +pink-tinted note-paper which had been lying open upon the table. +"It came by the last post," said he. "Read it aloud." + +The note was undated, and without either signature or address. diff --git a/Solutions/Session04/students.txt b/Solutions/Session04/students.txt new file mode 100644 index 00000000..1c42594b --- /dev/null +++ b/Solutions/Session04/students.txt @@ -0,0 +1,32 @@ +name: languages +Acharya, Madhumita: SQL, shell +Anderson, Paul G: basic, python, C++ +Baumel, Bradley I: java, sql, python, html +Bearer, Jerry: bash, sql, asp, html +Briant, Paul S: python, html, php, sql, java +Casey, Paul A: C++, python, +Change, Simbarashe P: SQL +Chavis, Brandon: ruby, python, bash +Cowhey, Isaac: python, javascript, ruby, scala +Galande, Nachiket: R, java, php +He, Beatrice: SQL, R, SaS, C, shell +Hefner, Jack M: python, perl, C++ +Hicks, Josh: C#, bash , perl +Hollis, Adam: python, basic, html, actionscript, php +Latif, Shu A: python, bash, html +Mandava, Sasi: C, C++ +McGhin, Spencer G: python, sql, R, html +Muralidharan, Sharmila: cobol, sql, +Naik, Ninad: C++, python +Pena, Sheree: swift +Raina, Jay N: SQL, R, python +Robison, Charles E: SQL, python, +Silva, Enrique R: python, sql +Tobey, David E: bash, qbasic, sql +Truong, Alexander C: python , html, xml +Vosper, Paul: C#, macscript, python +Weidner, Matthew T: German, python, html +Williams, Marcus D: python +Wong, Darryl: perl, php +Yang, Minghao: +Hagi, Abdishu: python, bash diff --git a/Solutions/Session04/test_mailroom.py b/Solutions/Session04/test_mailroom.py deleted file mode 100644 index 95cecf74..00000000 --- a/Solutions/Session04/test_mailroom.py +++ /dev/null @@ -1,77 +0,0 @@ -#!/usr/bin/env python - -""" -unit tests for the mailroom program -""" -import os - -import mailroom - - -#DB = mailroom.donor_db - -def test_list_donors(): - listing = mailroom.list_donors() - - # hard to test this throughly -- better not to hard code the entire - # thing. But check for a few aspects -- this will catchthe likley - # errors - assert listing.startswith("Donor list:\n") - assert "Jeff Bezos" in listing - assert "William Gates, III" in listing - assert len(listing.split('\n')) == 5 - - -def test_find_donor(): - """ checks a donor that isthere, but with odd case and spaces""" - donor = mailroom.find_donor("jefF beZos ") - - assert donor[0] == "Jeff Bezos" - - -def test_find_donor_not(): - "test one that's not there" - donor = mailroom.find_donor("Jeff Bzos") - - assert donor is None - - -def test_gen_letter(): - """ test the donor letter """ - - # create a sample donor - donor = ( "Fred Flintstone", [432.45, 65.45, 230.0] ) - letter = mailroom.gen_letter(donor) - # what to test? tricky! - assert letter.startswith("Dear Fred Flintstone") - assert letter.endswith("-The Team\n") - assert "donation of $230.00" in letter - - -def test_add_donor(): - name = "Fred Flintstone " - - donor = mailroom.add_donor(name) - donor[1].append(300) - assert donor[0] == "Fred Flintstone" - assert donor[1] == [300] - assert mailroom.find_donor(name) == donor - - -def test_generate_donor_report(): - - report = mailroom.generate_donor_report() - - print report - assert report.startswith(" Donor Name | Total Given | Num Gifts | Average Gift") - assert " Jeff Bezos 877.33 1 877.33" in report - - -def test_save_letters_to_disk(): - """This only tests that the files get created, but that's a start""" - - mailroom.save_letters_to_disk() - - assert os.path.isfile('Jeff_Bezos.txt') - assert os.path.isfile('William_Gates,_III.txt') - diff --git a/Solutions/Session04/trigram_solution.py b/Solutions/Session04/trigram.py old mode 100644 new mode 100755 similarity index 55% rename from Solutions/Session04/trigram_solution.py rename to Solutions/Session04/trigram.py index 06830749..584af8be --- a/Solutions/Session04/trigram_solution.py +++ b/Solutions/Session04/trigram.py @@ -7,78 +7,74 @@ http://codekata.com/kata/kata14-tom-swift-under-the-milkwood/ -Chris Barker's Solution +Chris Barker's Solution -- with improvements by Maria Mckinley This one is pretty straight forward -- really a quickie script There is lots of room to make it fancier of you want """ -# infilename = "sherlock_small.txt" -infilename = "sherlock.txt" - import sys -import string import random def make_words(text): - """ make a list of words from a large bunch of text - strips all the punctuation and other stuff from a string + Strips all the punctuation and other stuff from a + large string, and returns a list of words """ - - # build a translation table for string.translate: - # there are other ways to do this: - # a_word.strip() works well, too. - - punctuation = string.punctuation - punctuation = punctuation.replace("'", "") # keep apostropies - punctuation = punctuation.replace("-", "") # keep hyphenated words - ## this will replace punctiation with spaces - ## -- then split() will remove the extra spaces - table = string.maketrans(punctuation, " "*len(punctuation)) - + replace_punc = [('-', ' '), + (',', ''), + (',', ''), + ('.', ''), + (')', ''), + ('(', ''), + ('"', '')] + + # make a translation table for str.translate + table = {} + for orig, replace in replace_punc: + table[ord(orig)] = replace + text = text.translate(table) # lower-case everything to remove that complication: text = text.lower() - # remove punctuation - text = text.translate(table) - - # remove "--" -- can't do multiple characters with translate - text = text.replace("--", " ") - # split into words words = text.split() - # remove the bare single quotes - # " ' " is both a quote and an apostrophe + # remove the bare single quotes: "'" is both a quote and an apostrophe + # and capitalize "i" words2 = [] for word in words: - if word != "'": # remove quote by itself + if word != "'": # remove quote by itself # "i" by itself should be capitalized words2.append("I" if word == 'i' else word) - ## could be done with list comp too -- next week! + # could be done with list comprehension too -- next week! # words2 = [("I" if word == 'i' else word) for word in words if word != "'"] - return words2 + print(words) + return words def read_in_data(infilename): + """ + read the contents of a project Gutenberg book - infile = open(infilename, 'r') # text mode is default - # strip out the header, table of contents, etc. - for i in range(61): - infile.readline() - - full_text = [] - # read the rest of the file line by line - for line in infile: - if line.startswith("End of the Project Gutenberg EBook"): - break - full_text.append(line) + returns it as one big string + """ + with open(infilename, 'r') as infile: # text mode is default + # strip out the header, table of contents, etc. + for i in range(61): + infile.readline() + + full_text = [] + # read the rest of the file line by line -- stopping at the footer + for line in infile: + if line.startswith("End of the Project Gutenberg EBook"): + break + full_text.append(line) # put all the lines together into one big string: return " ".join(full_text) @@ -92,13 +88,12 @@ def build_trigram(words): # The values will be a list of the words that follow each pair word_pairs = {} - # loop through the words # (rare case where using the index to loop is easiest) - for i in range(len(words) - 2): # minus 2, 'cause you need a pair' - pair = tuple( words[i:i+2] ) # a tuple so it can be a key in the dict - follower = words[i+2] - word_pairs.setdefault(pair,[]).append(follower) + for i in range(len(words) - 2): # minus 2, 'cause you need a pair + pair = tuple(words[i:i + 2]) # a tuple so it can be a key in the dict + follower = words[i + 2] + word_pairs.setdefault(pair, []).append(follower) # setdefault() returns the value if pair is already in the dict # if it's not, it adds it, setting the value to a an empty list @@ -120,19 +115,19 @@ def build_text(word_pairs): """ new_text = [] - for i in range(30): # do thirty sentences + for i in range(10): # do ten sentences # pick a word pair to start the sentence - sentence = list(random.choice( word_pairs.keys() ) ) - + # need to make dict.keys() a list to randomly select from it + sentence = list(random.choice(list(word_pairs.keys()))) # now add a random number of additional words to the sentence - for j in range(random.randint(2,10)): - pair = tuple(sentence[-2:]) - sentence.append( random.choice(word_pairs[pair]) ) + for j in range(random.randint(2, 10)): + pair = tuple(sentence[-2:]) # the next word pair is the last two words + sentence.append(random.choice(word_pairs[pair])) - #capitalize the first word: + # capitalize the first word: sentence[0] = sentence[0].capitalize() - #Add the period + # Add the period sentence[-1] += "." new_text.extend(sentence) @@ -142,12 +137,11 @@ def build_text(word_pairs): if __name__ == "__main__": - # get the filename from the command line try: filename = sys.argv[1] except IndexError: - print "You must pass in a filename" + print("You must pass in a filename") sys.exit(1) in_data = read_in_data(filename) @@ -155,4 +149,4 @@ def build_text(word_pairs): word_pairs = build_trigram(words) new_text = build_text(word_pairs) - print new_text + print(new_text) diff --git a/Solutions/Session05/count_evens.py b/Solutions/Session05/count_evens.py deleted file mode 100644 index f5c2a26e..00000000 --- a/Solutions/Session05/count_evens.py +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/env python - -""" -This is from CodingBat "count_evens" (http://codingbat.com/prob/p189616) - -Using a list comprehension, return the number of even ints in the given array. -""" - - -def count_evens(arr): - return len( [i for i in arr if not i%2] ) - diff --git a/Solutions/Session05/dict_set_solution.py b/Solutions/Session05/dict_set_solution.py index 320cd37e..cc98e574 100644 --- a/Solutions/Session05/dict_set_solution.py +++ b/Solutions/Session05/dict_set_solution.py @@ -11,60 +11,61 @@ "salad": "greek", "pasta": "lasagna"} - # 1. Print the dict by passing it to a string format method, so that you # get something like: -print ( "{name} is from Seattle, and he likes {cake} cake, {fruit} fruit," - "{salad} salad, and {pasta} pasta".format(**food_prefs) ) +print("{name} is from Seattle, and he likes {cake} cake, {fruit} fruit," + "{salad} salad, and {pasta} pasta".format(**food_prefs)) # 2. Using a list comprehension, build a dictionary of numbers from zero # to fifteen and the hexadecimal equivalent (string is fine). -print dict( [ (i, hex(i)) for i in range(16) ] ) +print(dict([(i, hex(i)) for i in range(16)])) # 3. Do the previous entirely with a dict comprehension -- should be a one-liner -print { i: hex(i) for i in range(16) } +print({i: hex(i) for i in range(16)}) # 4. Using the dictionary from item 1: Make a dictionary using the same # keys but with the number of 'a's in each value. You can do this either # by editing the dict in place, or making a new one. If you edit in place, # make a copy first! -print { key:val.count('a') for key, val in food_prefs.items()} +print({key: val.count('a') for key, val in food_prefs.items()}) # 5. Create sets s2, s3 and s4 that contain numbers from zero through twenty, # divisible 2, 3 and 4. # a. Do this with one set comprehension for each set. -s2 = { i for i in range(21) if not i%2} -s3 = { i for i in range(21) if not i%3} -s4 = { i for i in range(21) if not i%4} +s2 = {i for i in range(21) if not i % 2} +s3 = {i for i in range(21) if not i % 3} +s4 = {i for i in range(21) if not i % 4} -print s2 -print s3 -print s4 +print("\nHere are the three sets:") +print(s2) +print(s3) +print(s4) # b. What if you had a lot more than 3? -- Don't Repeat Yourself (DRY) # - create a sequence that holds all three sets # - loop through that sequence to build the sets up -- so no repeated code. n = 5 -divisors = range(2, n+1) -sets = [ set() for i in divisors ] +divisors = range(2, n + 1) +# create a list of empty sets +sets = [set() for i in divisors] -for i, st in zip( divisors, sets): - [ st.add(j) for j in range(21) if not j%i ] +# fill up the sets +for i, st in zip(divisors, sets): + [st.add(j) for j in range(21) if not j % i] -print sets +print("\nHere are all the sets:\n", sets) # c. Extra credit: do it all as a one-liner by nesting a set comprehension # inside a list comprehension. (OK, that may be getting carried away!) -sets = [ { i for i in range(21) if not i%j } for j in range(2,n+1) ] - -print sets +sets = [{i for i in range(21) if not i % j} for j in range(2, n + 1)] +print("\nHere are all the sets from the one liner:\n", sets) diff --git a/Solutions/Session05/fizz_buz_comprehension.py b/Solutions/Session05/fizz_buz_comprehension.py new file mode 100644 index 00000000..434d7e6f --- /dev/null +++ b/Solutions/Session05/fizz_buz_comprehension.py @@ -0,0 +1,3 @@ +for i in xrange(1,101): print [i,'Fizz','Buzz','FizzBuzz'][(i%3==0)+2*(i%5==0)] + +for i in range(1,101): diff --git a/Solutions/Session05/fizz_buz_one_liner.py b/Solutions/Session05/fizz_buz_one_liner.py new file mode 100644 index 00000000..84846cf0 --- /dev/null +++ b/Solutions/Session05/fizz_buz_one_liner.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 + +# This One Liner solution to the Fizz Buzz problem +# was found by a student on the internet + +for i in range(1,101): print([i,'Fizz','Buzz','FizzBuzz'][(i%3==0)+2*(i%5==0)]) + +# this is a good example of why the most compact code is not always the +# best -- readability counts! +# And this is pretty impenatrable. +# butit's also pretty nifty logic, so below, +# It's unpacked to make it easeir to understand. + +# first , add some white space to make it pep8 compatible, and more readable. + +for i in range(1, 101): print([i, 'Fizz', 'Buzz', 'FizzBuzz'][(i % 3 == 0) + 2 * (i % 5 == 0)]) + + +# first, take the for loop off one line -- that really makes no difference: + + diff --git a/Solutions/Session05/get_langs.py b/Solutions/Session05/get_langs.py index 9f58a634..1534f529 100644 --- a/Solutions/Session05/get_langs.py +++ b/Solutions/Session05/get_langs.py @@ -9,23 +9,23 @@ class with """ -import collections # lots of neat stuff in there +import collections # lots of neat stuff in there file_path = '../../Examples/Session01/students.txt' # use a counter to ensure unique values and keep track of count all_langs = collections.Counter() -f = open(file_path) # default read text mode +f = open(file_path) # default read text mode -f.readline() # read and toss the header +f.readline() # read and toss the header for line in f: - langs = line.split(':')[1] - langs = langs.split(',') - for lang in langs: - lang = lang.strip().lower() - if lang: # don't want empty strings + # partition is more robust tha split() for mal-formed data. + langs = line.partition(':')[2] + for lang in [lang.strip().capitalize() for lang in langs.split(',')]: + if lang: # don't want any empty strings all_langs[lang] += 1 -for lang, count in all_langs.items(): - print "%10s: %i students"%(lang, count) + +for lang, count in sorted(all_langs.items()): + print("{:25}: {:d} students".format(lang, count)) diff --git a/Solutions/Session04/safe_input.py b/Solutions/Session05/safe_input.py similarity index 68% rename from Solutions/Session04/safe_input.py rename to Solutions/Session05/safe_input.py index 2536e1df..e01d346f 100644 --- a/Solutions/Session04/safe_input.py +++ b/Solutions/Session05/safe_input.py @@ -14,7 +14,7 @@ KeyboardInterrupt gets caught somewhere deeper in the process, and this function doesn't work. -Don't worry about that -- I do'nt really understnd what's going on in +Don't worry about that -- I don't really understnd what's going on in the REPL (Read, Evaluate, Print Loop) either -- and the point of this assigment is simple Exception handling. """ @@ -22,20 +22,20 @@ def safe_input(prompt_string=""): """ - print user for input -- returning a raw string. + print user for input -- returning a string. - This is just like the built-in raw_input(), but it will return None - if the user hits ctrl+c, ratehr than raise an Exception. + This is just like the built-in input(), but it will return None + if the user hits ctrl+c, (or ctrl+D) rather than raise an Exception. """ try: - response = raw_input(prompt_string) + response = input(prompt_string) return response except (EOFError, KeyboardInterrupt): return None if __name__ == "__main__": response = safe_input("Say Something: ") - print "You said:", response - - - + if response is None: + print("Hey! you cancled out!!!") + else: + print("You said:", response) diff --git a/Solutions/Session05/test_count_evens.py b/Solutions/Session05/test_count_evens.py deleted file mode 100644 index 4fb393eb..00000000 --- a/Solutions/Session05/test_count_evens.py +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env python - -""" -test code for count_evens.py - -can be run with py.test or nose -""" - -from count_evens import count_evens - - -def test_1(): - assert count_evens([2, 1, 2, 3, 4]) == 3 - - -def test_2(): - assert count_evens([2, 2, 0]) == 3 - - -def test_3(): - count_evens([1, 3, 5, -9, -3]) == 0 - -def test_4(): - count_evens([1, 3, 5, -9, -3, -4, -2]) == 2 diff --git a/Solutions/Session06/html_render.py b/Solutions/Session06/html_render.py deleted file mode 100644 index 3d2b6fb2..00000000 --- a/Solutions/Session06/html_render.py +++ /dev/null @@ -1,106 +0,0 @@ -#!/usr/bin/env python - -""" -html render code -""" - - -class Element(object): - - tag = 'html' - indent = ' ' - - def __init__(self, content=None, **attributes): - - self.content = [] - self.attributes = attributes - - if content is not None: - self.content.append(content) - - def append(self, content): - self.content.append(content) - - def render_tag(self, current_ind): - attrs = "".join([' {}="{}"'.format(key,val) for key, val in self.attributes.items()]) - tag_str = "{}<{}{}>".format(current_ind, self.tag, attrs) - return tag_str - - def render(self, file_out, current_ind=""): - file_out.write(self.render_tag(current_ind)) - file_out.write('\n') - #print "in render:" - #print self.content - for con in self.content: - #print "trying to render:", con - try: - file_out.write( current_ind + self.indent + con+"\n") - except TypeError: - con.render(file_out, current_ind+self.indent) - file_out.write("{}\n".format(current_ind,self.tag)) - - -class OneLineTag(Element): - def render(self, file_out, current_ind=""): - file_out.write(self.render_tag(current_ind)) - for con in self.content: - file_out.write(con) - file_out.write("\n".format(self.tag)) - - -class Title(OneLineTag): - tag = 'title' - - -class SelfClosingTag(Element): - def render(self, file_out, current_ind=""): - file_out.write(self.render_tag(current_ind)[:-1]) - file_out.write(" />\n") - - -class Hr(SelfClosingTag): - tag = 'hr' - - -class A(OneLineTag): - tag = 'a' - def __init__(self, link, content=None, **attributes): - OneLineTag.__init__(self, content, **attributes) - self.attributes["href"] = link - - -class H(OneLineTag): - def __init__(self, level, content=None, **attributes): - OneLineTag.__init__(self, content, **attributes) - self.tag = "h%i"%(level) - -# H(2, "The text of the header") - - -class Ul(Element): - tag = 'ul' - - -class Li(Element): - tag = 'li' - - -class Html(Element): - tag = 'html' - def render(self, file_out, current_ind=""): - file_out.write("\n") - Element.render(self, file_out, current_ind=current_ind) - - -class Body(Element): - tag = 'body' - - -class P(Element): - tag = 'p' - - -class Head(Element): - tag = 'head' - - diff --git a/Solutions/Session06/kwargs_lab.py b/Solutions/Session06/kwargs_lab.py new file mode 100644 index 00000000..d1c27c27 --- /dev/null +++ b/Solutions/Session06/kwargs_lab.py @@ -0,0 +1,123 @@ +#!/usr/bin/env python + +""" +args-kwargs lab: + +Experimenting with various ways to call and define functions + +The functions defined below is called in various ways by the test module: + +test_kwargs_lab.py + +colors() is a function with a bunch of keyword arguments + the test code calls it in various ways and confirms the results are as expected. + +call_colors() is a functiont hat takes totaly generic arguments + the tet code calls it in various ways so you can confirm that it works as expected. + +colors_manual() takes generic arguments, but processes it as if it had keyworkd parameters. + - you'd never write code like this, but it shows what's going on under the hood. + - the test code confirms it works the same way. + +""" + +from collections import OrderedDict + + +def colors(fore_color='red', + back_color='blue', + link_color='green', + visited_color='cyan', + ): + output = ("The colors are:\n" + " foreground: {fore_color}\n" + " background: {back_color}\n" + " link: {link_color}\n" + " visited: {visited_color}") + output = output.format(fore_color=fore_color, + back_color=back_color, + link_color=link_color, + visited_color=visited_color) + + return output + + +def call_colors(*args, **kwargs): + """ + this function has the most generic signature possible: + + you can pass ANYTHING in, and it will accept that. + + But the goal here is to explore the other side of the question: + making sure you know what happens when this function is called + in various ways. + + So this function will simpily return args and kwargs, and the test + code will check and see if it works the way it is expected. + + As a rule, you don't want to do that -- you are throwing away Python's + ability to check your arguments for you. + + *args and **kwargs should be saved for times when you NEED generic + function processing. There are two common use cases for this: + + 1) When you need to pass arguments through to antoher nested function -- + we'll see examples of this in OO programming with subclassing and + some exampels of caling functions uknown at code writing time. + 2) When the arguments aren't known when you define the function: + - either the number of arguments -- *args -- for example: + os.path.join() function. + - or any arbitrary keyword arguments, for example: + str.format() + + """ + return args, kwargs + + +def colors_manual(*args, **kwargs): + """ + This example to show you how much work you need to do to do this by hand! + + This passes all the same tests as the colors function above: + with a LOT more code! + """ + # putting this in a tuple, as order is important + # could also use an ordereddict + default_colors = OrderedDict((('fore_color', 'red'), + ('back_color', 'blue'), + ('link_color', 'green'), + ('visited_color', 'cyan'), + )) + for key in kwargs: + if key not in default_colors: + msg = "colors_manual() got an unexpected keyword argument: {}".format(key) + raise TypeError + + all_args = {} + # unpack the args tuple: + for i, key in enumerate(default_colors.keys()): + try: + all_args[key] = args[i] + except IndexError: # This will get raised when the tuple is exausted + break + + for key, color in default_colors.items(): + if key in all_args: # it's already been set + if key in kwargs: # it's a duplicate + msg = "colors_manual() got multiple values for argument '{}'".format(key) + raise TypeError(msg) + elif key in kwargs: + # set it from the kwargs dict + all_args[key] = kwargs[key] + else: + # set it to the default + all_args[key] = color + + output = ("The colors are:\n" + " foreground: {fore_color}\n" + " background: {back_color}\n" + " link: {link_color}\n" + " visited: {visited_color}") + output = output.format(**all_args) + + return output diff --git a/Solutions/Session06/mailroom2.py b/Solutions/Session06/mailroom2.py new file mode 100755 index 00000000..49dfd81d --- /dev/null +++ b/Solutions/Session06/mailroom2.py @@ -0,0 +1,220 @@ +#!/usr/bin/env python +""" +mailroom assignment + +This version uses a dict for the main db, and exception handling to +check input, and has been factored to be amenable to testing. +""" + +import sys +import math + +# handy utility to make pretty printing easier +from textwrap import dedent + + +# In memory representation of the donor database +# using a tuple for each donor +# -- kind of like a record in a database table +# using a dict with a lower case version of the donor's name as the key +# This makes it easier to have a 'normalized' key. +# you could get a bit fancier by having each "record" be a dict, with +# "name" and "donations" as keys. +def get_donor_db(): + return {'william gates iii': ("William Gates III", [653772.32, 12.17]), + 'jeff bezos': ("Jeff Bezos", [877.33]), + 'paul allen': ("Paul Allen", [663.23, 43.87, 1.32]), + 'mark zuckerberg': ("Mark Zuckerberg", [1663.23, 4300.87, 10432.0]), + } + + +def list_donors(): + """ + creates a list of the donors as a string, so they can be printed + + Not calling print from here makes it more flexible and easier to + test + """ + listing = ["Donor list:"] + for donor in donor_db.values(): + listing.append(donor[0]) + return "\n".join(listing) + + +def find_donor(name): + """ + find a donor in the donor db + + :param: the name of the donor + + :returns: The donor data structure -- None if not in the donor_db + """ + key = name.strip().lower() + return donor_db.get(key) + + +def add_donor(name): + """ + Add a new donor to the donor db + + :param: the name of the donor + + :returns: the new Donor data structure + """ + name = name.strip() + donor = (name, []) + donor_db[name.lower()] = donor + return donor + + +def main_menu_selection(): + """ + Print out the main application menu and then read the user input. + """ + action = input(dedent(''' + Choose an action: + + 1 - Send a Thank You + 2 - Create a Report + 3 - Send letters to everyone + 4 - Quit + + > ''')) + return action.strip() + + +def gen_letter(donor): + """ + Generate a thank you letter for the donor + + :param: donor tuple + + :returns: string with letter + + note: This doesn't actually write to a file -- that's a separate + function. This makes it more flexible and easier to test. + """ + return dedent('''Dear {0:s}, + + Thank you for your very kind donation of ${1:.2f}. + It will be put to very good use. + + Sincerely, + -The Team + '''.format(donor[0], donor[1][-1])) + + +def send_thank_you(): + """ + Execute the logic to record a donation and generate a thank you message. + """ + # Read a valid donor to send a thank you from, handling special commands to + # let the user navigate as defined. + while True: + name = input("Enter a donor's name (or list to see all donors or 'menu' to exit)> ").strip() + if name == "list": + print(list_donors()) + elif name == "menu": + return + else: + break + + # Now prompt the user for a donation amount to apply. Since this is + # also an exit point to the main menu, we want to make sure this is + # done before mutating the db. + while True: + amount_str = input("Enter a donation amount (or 'menu' to exit)> ").strip() + if amount_str == "menu": + return + # Make sure amount is a valid amount before leaving the input loop + try: + amount = float(amount_str) + # extra check here -- unlikely that someone will type "NaN", but + # it IS possible, and it is a valid floating point number: + # http://en.wikipedia.org/wiki/NaN + if math.isnan(amount) or math.isinf(amount) or round(amount, 2) == 0.00: + raise ValueError + # in this case, the ValueError could be raised by the float() call, or by the NaN-check + except ValueError: + print("error: donation amount is invalid\n") + else: + break + + # If this is a new user, ensure that the database has the necessary + # data structure. + donor = find_donor(name) + if donor is None: + donor = add_donor(name) + + # Record the donation + donor[1].append(amount) + print(gen_letter(donor)) + + +def sort_key(item): + # used to sort on name in donor_db + return item[1] + + +def generate_donor_report(): + """ + Generate the report of the donors and amounts donated. + + :returns: the donor report as a string. + """ + # First, reduce the raw data into a summary list view + report_rows = [] + for (name, gifts) in donor_db.values(): + total_gifts = sum(gifts) + num_gifts = len(gifts) + avg_gift = total_gifts / num_gifts + report_rows.append((name, total_gifts, num_gifts, avg_gift)) + + # sort the report data + report_rows.sort(key=sort_key) + report = [] + report.append("{:25s} | {:11s} | {:9s} | {:12s}".format("Donor Name", + "Total Given", + "Num Gifts", + "Average Gift")) + report.append("-" * 66) + for row in report_rows: + report.append("{:25s} ${:10.2f} {:9d} ${:11.2f}".format(*row)) + return "\n".join(report) + + +def save_letters_to_disk(): + """ + make a letter for each donor, and save it to disk. + """ + for donor in donor_db.values(): + letter = gen_letter(donor) + # I don't like spaces in filenames... + filename = donor[0].replace(" ", "_") + ".txt" + open(filename, 'w').write(letter) + + +def print_donor_report(): + print(generate_donor_report()) + + +def quit(): + sys.exit(0) + +if __name__ == "__main__": + + donor_db = get_donor_db() + + running = True + + selection_dict = {"1": send_thank_you, + "2": print_donor_report, + "3": save_letters_to_disk, + "4": quit} + + while True: + selection = main_menu_selection() + try: + selection_dict[selection]() + except KeyError: + print("error: menu selection is invalid!") diff --git a/Solutions/Session06/run_html_render.py b/Solutions/Session06/run_html_render.py deleted file mode 100755 index 132acbf3..00000000 --- a/Solutions/Session06/run_html_render.py +++ /dev/null @@ -1,227 +0,0 @@ -#!/usr/bin/env python - -""" -a simple script can run and test your html rendering classes. - -Uncomment the steps as you add to your rendering. - -""" - -from cStringIO import StringIO - - -# importing the html_rendering code with a short name for easy typing. -import html_render_eb as hr -reload(hr) # reloading in case you are running this in iPython - # -- we want to make sure the latest version is used - - -## writing the file out: -def render_page(page, filename): - """ - render the tree of elements - - This uses cSstringIO to render to memory, then dump to console and - write to file -- very handy! - """ - - f = StringIO() - page.render(f, " ") - - f.reset() - - print f.read() - - f.reset() - open(filename, 'w').write( f.read() ) - - -## Step 1 -########## - -# page = hr.Element() - -# page.append("Here is a paragraph of text -- there could be more of them, but this is enough to show that we can do some text") - -# page.append("And here is another piece of text -- you should be able to add any number") - -# render_page(page, "test_html_output1.html") - -# ## Step 2 -# ########## - -# page = hr.Html() - -# body = hr.Body() - -# body.append(hr.P("Here is a paragraph of text -- there could be more of them, but this is enough to show that we can do some text")) - -# body.append(hr.P("And here is another piece of text -- you should be able to add any number")) - -# page.append(body) - -# render_page(page, "test_html_output2.html") - -# # Step 3 -# ########## - -# page = hr.Html() - -# head = hr.Head() -# head.append(hr.Title("PythonClass = Revision 1087:")) - -# page.append(head) - -# body = hr.Body() - -# body.append(hr.P("Here is a paragraph of text -- there could be more of them, but this is enough to show that we can do some text")) -# body.append(hr.P("And here is another piece of text -- you should be able to add any number")) - -# page.append(body) - -# render_page(page, "test_html_output3.html") - -# # Step 4 -# ########## - -# page = hr.Html() - -# head = hr.Head() -# head.append(hr.Title("PythonClass = Revision 1087:")) - -# page.append(head) - -# body = hr.Body() - -# body.append(hr.P("Here is a paragraph of text -- there could be more of them, but this is enough to show that we can do some text", -# style="text-align: center; font-style: oblique;")) - -# page.append(body) - -# render_page(page, "test_html_output4.html") - -# # Step 5 -# ######### - -# page = hr.Html() - -# head = hr.Head() -# head.append(hr.Title("PythonClass = Revision 1087:")) - -# page.append(head) - -# body = hr.Body() - -# body.append(hr.P("Here is a paragraph of text -- there could be more of them, but this is enough to show that we can do some text", -# style="text-align: center; font-style: oblique;")) - -# body.append(hr.Hr()) - -# page.append(body) - -# render_page(page, "test_html_output5.html") - -# # Step 6 -# ######### - -# page = hr.Html() - -# head = hr.Head() -# head.append(hr.Title("PythonClass = Revision 1087:")) - -# page.append(head) - -# body = hr.Body() - -# body.append(hr.P("Here is a paragraph of text -- there could be more of them, but this is enough to show that we can do some text", -# style="text-align: center; font-style: oblique;")) - -# body.append(hr.Hr()) - -# body.append("And this is a ") -# body.append( hr.A("http://google.com", "link") ) -# body.append("to google") - -# page.append(body) - -# render_page(page, "test_html_output6.html") - -# # Step 7 -# ######### - -# page = hr.Html() - -# head = hr.Head() -# head.append(hr.Title("PythonClass = Revision 1087:")) - -# page.append(head) - -# body = hr.Body() - -# body.append( hr.H(2, "PythonClass - Class 6 example") ) - -# body.append(hr.P("Here is a paragraph of text -- there could be more of them, but this is enough to show that we can do some text", -# style="text-align: center; font-style: oblique;")) - -# body.append(hr.Hr()) - -# list = hr.Ul(id="TheList", style="line-height:200%") - -# list.append( hr.Li("The first item in a list") ) -# list.append( hr.Li("This is the second item", style="color: red") ) - -# item = hr.Li() -# item.append("And this is a ") -# item.append( hr.A("http://google.com", "link") ) -# item.append("to google") - -# list.append(item) - -# body.append(list) - -# page.append(body) - -# render_page(page, "test_html_output7.html") - -# # Step 8 -# ######## - -page = hr.Html() - - -head = hr.Head() -head.append( hr.Meta(charset="UTF-8") ) -head.append(hr.Title("PythonClass = Revision 1087:")) - -page.append(head) - -body = hr.Body() - -body.append( hr.H(2, "PythonClass - Class 6 example") ) - -body.append(hr.P("Here is a paragraph of text -- there could be more of them, but this is enough to show that we can do some text", - style="text-align: center; font-style: oblique;")) - -body.append(hr.Hr()) - -list = hr.Ul(id="TheList", style="line-height:200%") - -list.append( hr.Li("The first item in a list") ) -list.append( hr.Li("This is the second item", style="color: red") ) - -item = hr.Li() -item.append("And this is a ") -item.append( hr.A("http://google.com", "link") ) -item.append("to google") - -list.append(item) - -body.append(list) - -page.append(body) - -render_page(page, "test_html_output8.html") - - - - diff --git a/Solutions/Session06/test_html_render.py b/Solutions/Session06/test_html_render.py deleted file mode 100644 index 25c3c0f4..00000000 --- a/Solutions/Session06/test_html_render.py +++ /dev/null @@ -1,234 +0,0 @@ -#!/usr/bin/env python - -""" -unit tests for html rendering code -""" - -from cStringIO import StringIO - -import html_render as hr - - -## utility function for tests: -def render_element(element, ind=""): - """ - call the render method of an element and return the results as a string - """ - f = StringIO() - element.render(f, ind) - f.reset() - output = f.read() - return output # we don't care about leading/trailing whitespace - -## these should all pass with my framework code. - - -def test_init(): - " can I even initialize an Element" - hr.Element() - assert True - - -def test_init_content(): - " can I even initialize an Element with content" - - hr.Element("this is some text") - - assert True - - -def test_element_content(): - """ should be able to initilize with a string""" - hr.Element("some content") - - -## these should pass after part 1 - - -def test_render_content1(): - """ does the render method work """ - e = hr.Element("this is some content") - - output = render_element(e) - - assert output.startswith('') - assert output.endswith('\n') - assert "this is some content" in output - - -def test_render_content2(): - """ does the render method work after appending a string """ - e = hr.Element("this is some content") - e.append("and this is some more") - - output = render_element(e) - - assert output.startswith('') - assert output.endswith('\n') - assert "this is some content" in output - assert "and this is some more" in output - print output - - -def test_render_content_indent(): - """ does the render method work after appending a string """ - e = hr.Element("this is some content") - e.append("and this is some more") - - output = render_element(e) - lines = output.split('\n') - print lines - assert lines[1].startswith(" ") - - -## this one no longer currect with step 8 added -# def test_render_html(): -# """ the html element """ - -# e = hr.Html("this is some content") -# e.append("and this is some more") - -# output = render_element(e) - -# print output -# assert output.startswith('') -# assert output.endswith('\n') -# assert "this is some content" in output -# assert "and this is some more" in output - - -def test_render_body(): - """ the html element """ - - e = hr.Body("this is some content") - e.append("and this is some more") - - output = render_element(e) - - print output - assert output.startswith('') - assert output.endswith('\n') - assert "this is some content" in output - assert "and this is some more" in output - - -def test_render_p_indent(): - p = hr.P("a simple paragraph") - output = render_element(p, " ") - - print output - lines = output.split('\n') - - assert lines[0].startswith(" ") - assert lines[1].startswith(" ") - assert lines[2].startswith(" ") - - -def test_render_sub_elements(): - e = hr.Html("some simple text") - p = hr.P("a simple paragraph") - p.append(hr.P("a nested paragraph")) - - e.append(p) - - output = render_element(e) - - print output - lines = output.split('\n') - lines[0].startswith('') - lines[1].startswith(' ') - lines[3].startswith(' ') - lines[5].startswith(' ') - lines[-1].startswith('') - - -def test_one_line_tag(): - e = hr.OneLineTag('something') - output = render_element(e, ind=" ") - - print output - - assert output == " something\n" - - -def test_attributes(): - e = hr.P("some text", id="TheList", style="line-height:200%") - - output = render_element(e, ind=" ") - - print output - - lines = output.split('\n') - assert 'id="TheList"' in lines[0] - assert 'style="line-height:200%"' in lines[0] - - -def test_attributes_one_line(): - e = hr.Title("some text", id="TheList", style="line-height:200%") - - output = render_element(e, ind=" ") - - print output - - lines = output.split('\n') - assert 'id="TheList"' in lines[0] - assert 'style="line-height:200%"' in lines[0] - - -def test_self_closing(): - e = hr.Hr() - output = render_element(e, ind=" ") - - print output - assert output == "
\n" - - -def test_self_closing_attr(): - e = hr.Hr(id='fred', style='box') - output = render_element(e, ind=" ") - - print output - assert output == '
\n' - - -def test_anchor(): - e = hr.A("http://google.com", "link to google") - - output = render_element(e, ind=" ") - - print output - assert output == ' link to google\n' - - -def test_header(): - e = hr.H(2, "The text of the header") - - output = render_element(e, ind=" ") - - print output - assert output == '

The text of the header

\n' - - -def test_header3(): - e = hr.H(3, "The text of the header") - - output = render_element(e, ind=" ") - - print output - assert output == '

The text of the header

\n' - - -def test_doctype(): - """ - html element should render teh doctype header, too - """ - - e = hr.Html("Just a tiny bit of content") - - output = render_element( e ) - - print output - assert output.startswith("") - assert "" in output - assert output.endswith("\n") - diff --git a/Solutions/Session06/test_kwargs_lab.py b/Solutions/Session06/test_kwargs_lab.py new file mode 100644 index 00000000..bad89fe1 --- /dev/null +++ b/Solutions/Session06/test_kwargs_lab.py @@ -0,0 +1,412 @@ +#!/usr/bin/env python3 + +""" +Test code for the args-kwargs lab + +This kind of experimental code isn't all the suited to testing, but it's +not a bad way to run code as you develop it anyway... + +And we want to encourage test code -- so we'll use it everywhere possible + +Note: I decided that instead of having my funciton print somthing it would +return a string -- that way I could test that the returned string was correct. + +""" + +import pytest # used for testing exceptions + +from kwargs_lab import colors, call_colors, colors_manual + + +# Calling "colors" in various ways. +def test_all_positional(): + result = colors('red', 'blue', 'yellow', 'chartreuse') + + # these aren't exhaustive by any means + # but mostly I want to make the code runs without error + print(result) + assert 'red' in result + assert 'blue' in result + assert 'chartreuse' in result + # you can force a test failure if you want to see the output + # assert False + + +def test_one_keyword(): + result = colors(link_color='purple') + # these aren't exhaustive by any means + # but mostly I want to make the code runs without error + print(result) + assert 'link: purple' in result + # you can force a test failure if you want to see the output + # assert False + + +def test_pos_and_keyword(): + result = colors('yellow', 'gray', link_color='purple') + # these aren't exhaustive by any means + + print(result) + assert 'foreground: yellow' in result + assert 'background: gray' in result + assert 'link: purple' in result + # you can force a test failure if you want to see the output + # assert False + + +def test_duplicate(): + """ + It's an error to have a keyword argument that duplicates a + positional one + """ + # this is the nifty pytest way to check for Exceptions + with pytest.raises(TypeError): + result = colors('yellow', fore_color='purple') + print(result) + + +def test_duplicate2(): + """ + It's an error to have a keyword argument that duplicates a + positional one + """ + # this is a way to do it by hand: + try: + result = colors('yellow', fore_color='purple') + print(result) + assert False + except TypeError as err: + # you can also check if the error message is what you expect + assert "multiple values for argument" in err.args[0] + + +def test_call_tuple(): + t = ('red', 'blue', 'yellow', 'chartreuse') + result = colors(*t) + + # these aren't exhaustive by any means + # but mostly I want to make the code runs without error + print(result) + assert 'red' in result + assert 'blue' in result + assert 'chartreuse' in result + # you can force a test failure if you want to see the output + # assert False + + +def test_call_dict(): + d = {'fore_color': 'red', + 'visited_color': 'cyan', + 'link_color': 'green', + 'back_color': 'blue', + } + result = colors(**d) + + print(result) + assert 'foreground: red' in result + assert 'background: blue' in result + assert 'visited: cyan' in result + assert 'link: green' in result + # you can force a test failure if you want to see the output + # assert False + + +def test_call_tuple_dict(): + t = ('red', 'blue') + + d = {'visited_color': 'cyan', + 'link_color': 'green', + } + result = colors(*t, **d) + + print(result) + assert 'foreground: red' in result + assert 'background: blue' in result + assert 'visited: cyan' in result + assert 'link: green' in result + # you can force a test failure if you want to see the output + # assert False + + +def test_call_everything(): + """ + this one uses: + - a positional argument + - *tuple + - a keyword argument + - **dict + """ + t = ('red',) # note the extra comma to amke it a tuple! + + d = {'visited_color': 'cyan'} + + result = colors('blue', *t, link_color='orange', **d) + + print(result) + assert 'foreground: blue' in result + assert 'background: red' in result + assert 'visited: cyan' in result + assert 'link: orange' in result + # you can force a test failure if you want to see the output + # assert False + + +def test_call_undefined_kwarg(): + # should get an error passing in non-existand keyword + with pytest.raises(TypeError): + result = colors(weird_color='grey') + + +# ########################### +# now lets try calling call_colors in all the above ways, and see if we get what we expect. +# +# Note that these tests are really testing the Python machinery +# -- which you don't need to do! Python is already well tested. +# +# But writing theese tests tests your undestanding of how things work +# if a test fails, it's because you ( I ;- ) didn't understand the +# calling convertions. +# ############################ + + +def test_call_all_positional(): + args, kwargs = call_colors('red', 'blue', 'yellow', 'chartreuse') + + # no kwrags, they should all be in the args tuple: + assert not kwargs # kwargs is empty + assert args == ('red', 'blue', 'yellow', 'chartreuse') + + +def test_call_one_keyword(): + args, kwargs = call_colors(link_color='purple') + + assert not args # args should be an empty tuple + assert kwargs['link_color'] == 'purple' + assert len(kwargs) == 1 # only one entry in the kwargs dict + + +def test_call_pos_and_keyword(): + args, kwargs = call_colors('yellow', 'gray', link_color='purple') + + assert args == ('yellow', 'gray') + assert kwargs == {'link_color': 'purple'} + + +def test_call_duplicate(): + """ + This was an error above -- but is not here -- no keyword arguments + to duplicate! + """ + + args, kwargs = call_colors('yellow', fore_color='purple') + + assert args == ('yellow',) + assert kwargs == {'fore_color': 'purple'} + + +def test_call_call_tuple(): + t = ('red', 'blue', 'yellow', 'chartreuse') + args, kwargs = call_colors(*t) + + # This is straighforward -- the args tuple should be the one passed in! + assert args == t + + # But it is NOT the SAME tuple! + assert args is not t + # and an empty kwargs dict + assert kwargs == {} # multiple ways to test for an empty dict. + + +def test_call_call_dict(): + d = {'fore_color': 'red', + 'visited_color': 'cyan', + 'link_color': 'green', + 'back_color': 'blue', + } + args, kwargs = call_colors(**d) + + # also easy -- should be the dict passed in + assert kwargs == d + assert args == tuple() + + +def test_call_call_tuple_dict(): + t = ('red', 'blue') + + d = {'visited_color': 'cyan', + 'link_color': 'green', + } + args, kwargs = call_colors(*t, **d) + + # this should be just what's passed in. + assert args == t + assert kwargs == d + + +def test_call_call_everything(): + """ + this one uses: + - a positional argument + - *tuple + - a keyword argument + - **dict + """ + t = ('red',) # note the extra comma to amke it a tuple! + + d = {'visited_color': 'cyan'} + + args, kwargs = call_colors('blue', *t, link_color='orange', **d) + + # This one is more interesting: + assert args == ('blue',) + t + assert args == ('blue', 'red') # a different way to spell the same thing + + assert kwargs == {'visited_color': 'cyan', 'link_color': 'orange'} + # or: + d['link_color'] = 'orange' + assert kwargs == d + + +# ################## +# Now to test the manual upacking of args, kwargs +# All the same tests as above should pass +# ################## + + +# Calling "colors" in various ways. +def test_manual_all_positional(): + result = colors('red', 'blue', 'yellow', 'chartreuse') + + # these aren't exhaustive by any means + # but mostly I want to make the code runs without error + print(result) + assert 'red' in result + assert 'blue' in result + assert 'chartreuse' in result + # you can force a test failure if you want to see the output + # assert False + + +def test_manual_one_keyword(): + result = colors_manual(link_color='purple') + # these aren't exhaustive by any means + # but mostly I want to make the code runs without error + print(result) + assert 'link: purple' in result + # you can force a test failure if you want to see the output + # assert False + + +def test_manual_pos_and_keyword(): + result = colors_manual('yellow', 'gray', link_color='purple') + # these aren't exhaustive by any means + + print(result) + assert 'foreground: yellow' in result + assert 'background: gray' in result + assert 'link: purple' in result + # you can force a test failure if you want to see the output + # assert False + + +def test_manual_duplicate(): + """ + It's an error to have a keyword argument that duplicates a + positional one + """ + # this is the nifty pytest way to check for Exceptions + with pytest.raises(TypeError): + result = colors_manual('yellow', fore_color='purple') + print(result) + + +def test_manual_duplicate2(): + """ + It's an error to have a keyword argument that duplicates a + positional one + """ + # this is a way to do it by hand: + try: + result = colors_manual('yellow', fore_color='purple') + print(result) + assert False + except TypeError as err: + # you can also check if the error message is what you expect + assert "multiple values for argument" in err.args[0] + + +def test_manual_call_tuple(): + t = ('red', 'blue', 'yellow', 'chartreuse') + result = colors_manual(*t) + + # these aren't exhaustive by any means + # but mostly I want to make the code runs without error + print(result) + assert 'red' in result + assert 'blue' in result + assert 'chartreuse' in result + # you can force a test failure if you want to see the output + # assert False + + +def test_manual_call_dict(): + d = {'fore_color': 'red', + 'visited_color': 'cyan', + 'link_color': 'green', + 'back_color': 'blue', + } + result = colors_manual(**d) + + print(result) + assert 'foreground: red' in result + assert 'background: blue' in result + assert 'visited: cyan' in result + assert 'link: green' in result + # you can force a test failure if you want to see the output + # assert False + + +def test_manual_call_tuple_dict(): + t = ('red', 'blue') + + d = {'visited_color': 'cyan', + 'link_color': 'green', + } + result = colors_manual(*t, **d) + + print(result) + assert 'foreground: red' in result + assert 'background: blue' in result + assert 'visited: cyan' in result + assert 'link: green' in result + # you can force a test failure if you want to see the output + # assert False + + +def test_manual_call_everything(): + """ + this one uses: + - a positional argument + - *tuple + - a keyword argument + - **dict + """ + t = ('red',) # note the extra comma to amke it a tuple! + + d = {'visited_color': 'cyan'} + + result = colors_manual('blue', *t, link_color='orange', **d) + + print(result) + assert 'foreground: blue' in result + assert 'background: red' in result + assert 'visited: cyan' in result + assert 'link: orange' in result + # you can force a test failure if you want to see the output + # assert False + + +def test_manual_call_undefined_kwarg(): + # should get an error passing in non-existand keyword + with pytest.raises(TypeError): + result = colors_manual(weird_color='grey') diff --git a/Solutions/Session06/test_mailroom2.py b/Solutions/Session06/test_mailroom2.py new file mode 100644 index 00000000..e633e575 --- /dev/null +++ b/Solutions/Session06/test_mailroom2.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python + +""" +unit tests for the mailroom program +""" +import os + +import mailroom2 as mailroom + +# so that it's there for the tests +mailroom.donor_db = mailroom.get_donor_db() + + +def test_list_donors(): + listing = mailroom.list_donors() + + # hard to test this throughly -- better not to hard code the entire + # thing. But check for a few aspects -- this will catch the likely + # errors + assert listing.startswith("Donor list:\n") + assert "Jeff Bezos" in listing + assert "William Gates III" in listing + assert len(listing.split('\n')) == 5 + + +def test_find_donor(): + """ checks a donor that is there, but with odd case and spaces""" + donor = mailroom.find_donor("jefF beZos ") + + assert donor[0] == "Jeff Bezos" + + +def test_find_donor_not(): + "test one that's not there" + donor = mailroom.find_donor("Jeff Bzos") + + assert donor is None + + +def test_gen_letter(): + """ test the donor letter """ + + # create a sample donor + donor = ("Fred Flintstone", [432.45, 65.45, 230.0]) + letter = mailroom.gen_letter(donor) + # what to test? tricky! + assert letter.startswith("Dear Fred Flintstone") + assert letter.endswith("-The Team\n") + assert "donation of $230.00" in letter + + +def test_add_donor(): + name = "Fred Flintstone " + + donor = mailroom.add_donor(name) + donor[1].append(300) + assert donor[0] == "Fred Flintstone" + assert donor[1] == [300] + assert mailroom.find_donor(name) == donor + + +def test_generate_donor_report(): + + report = mailroom.generate_donor_report() + + print(report) # printing so you can see it if it fails + # this is pretty tough to test + # these are not great, because they will fail if unimportant parts of the + # report are changed. + # but at least you know that codes working now. + assert report.startswith("Donor Name | Total Given | Num Gifts | Average Gift") + + assert "Jeff Bezos $ 877.33 1 $ 877.33" in report + + +def test_save_letters_to_disk(): + """ + This only tests that the files get created, but that's a start + + Note that the contents of the letter was already + tested with test_gen_letter + """ + + mailroom.save_letters_to_disk() + + assert os.path.isfile('Jeff_Bezos.txt') + assert os.path.isfile('William_Gates_III.txt') + # check that it'snot empty: + with open('William_Gates_III.txt') as f: + size = len(f.read()) + assert size > 0 + + +if __name__ == "__main__": + # this is best run with a test runner, like pytest + # But if not, at least this will run them all. + test_list_donors() + test_find_donor() + test_find_donor_not() + test_gen_letter() + test_add_donor() + test_generate_donor_report() + test_save_letters_to_disk() + print("All tests Passed") diff --git a/Solutions/Session07/circle.py b/Solutions/Session07/circle.py deleted file mode 100644 index 2ed8d59b..00000000 --- a/Solutions/Session07/circle.py +++ /dev/null @@ -1,92 +0,0 @@ -#!/usr/bin/env python - -""" -nifty Circle class -""" - -from math import pi -import functools - - -@functools.total_ordering -class Circle(object): - - def __init__(self, radius): - self.radius = float(radius) - - @classmethod - def from_diameter(cls, diameter): - return cls(diameter/2.0) - - @property - def diameter(self): - return self.radius * 2.0 - @diameter.setter - def diameter(self, value): - self.radius = value / 2.0 - - @property - def area(self): - return self.radius**2 * pi - - def __repr__(self): - return "Circle(%s)"%self.radius - - def __str__(self): - return "Circle with radius: %.4f"%self.radius - - def __add__(self, other): - return Circle(self.radius + other.radius) - - def __iadd__(self, other): - """ - for "augmented assignment" -- can be used for in-place addition - - generally used that way for mutable types. This approach returns - self, so that the object is changed in place. - """ - self.radius += other.radius - return self - - def __mul__(self, factor): - return Circle(self.radius * factor) - - def __imul__(self, factor): - self.radius *= factor - return self - - def __rmul__(self, factor): - return Circle(self.radius * factor) - - # def __cmp__(self, other): - # """ - # This is the easy way to support comparing all in one shot - # """ - # return cmp(self.radius, other.radius) - - ## Or you can define them all - ## So can support odd situations - # def __eq__(self, other): - # return self.radius == other.radius - # def __ne__(self, other): - # return self.radius != other.radius - # def __gt__(self, other): - # return self.radius > other.radius - # def __ge__(self, other): - # return self.radius >= other.radius - # def __lt__(self, other): - # return self.radius < other.radius - # def __le__(self, other): - # return self.radius <= other.radius - - ## or you can put the @total_ordering decorator on the class definiton and do this: - def __eq__(self, other): - return self.radius == other.radius - def __gt__(self, other): - return self.radius > other.radius - - - -class SubCircle(Circle): - pass - diff --git a/Solutions/Session07/class_html_render/.cache/v/cache/lastfailed b/Solutions/Session07/class_html_render/.cache/v/cache/lastfailed new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/Solutions/Session07/class_html_render/.cache/v/cache/lastfailed @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/Solutions/Session07/class_html_render/html_render.py b/Solutions/Session07/class_html_render/html_render.py new file mode 100644 index 00000000..89f121c9 --- /dev/null +++ b/Solutions/Session07/class_html_render/html_render.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python3 + + +class Element: + + tag = 'html' + + def __init__(self, content=None): + self.content = [] + if content: + self.content.append(content) + + def append(self, content): + self.content.append(content) + + def render(self, out_file, ind=""): + out_file.write("<{}>\n".format(self.tag)) + for stuff in self.content: + out_file.write(stuff + "\n") + out_file.write("\n".format(self.tag)) diff --git a/Solutions/Session07/class_html_render/html_render_wrap.py b/Solutions/Session07/class_html_render/html_render_wrap.py new file mode 100644 index 00000000..7fb1ca9f --- /dev/null +++ b/Solutions/Session07/class_html_render/html_render_wrap.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python + +""" +html render code -- this shows how to wrap plain text in a simple class +for rendering. +""" + + +class TextWrapper: + """ + A simple wrapper that creates a class with a render method + for simple text + """ + def __init__(self, text): + self.text = text + + def render(self, file_out, current_ind=""): + file_out.write(current_ind + self.text) + + +# here is how you might use it: +class Element: + + tag = 'html' # shouldn't really be usable without properly subclassing + indent = ' ' + + def __init__(self, content=None, **attributes): + + self.content = [] + self.attributes = attributes + + if content is not None: + # call this class's append -- so any magic done in there is used. + self.append(content) + + def append(self, content): + if hasattr(content, 'render'): + self.content.append(content) + else: + self.content.append(TextWrapper(str(content))) diff --git a/Examples/Session06/html_render/run_html_render.py b/Solutions/Session07/class_html_render/run_html_render.py similarity index 77% rename from Examples/Session06/html_render/run_html_render.py rename to Solutions/Session07/class_html_render/run_html_render.py index 74e5c378..d282fdc9 100644 --- a/Examples/Session06/html_render/run_html_render.py +++ b/Solutions/Session07/class_html_render/run_html_render.py @@ -7,46 +7,51 @@ """ -from cStringIO import StringIO - +from io import StringIO # importing the html_rendering code with a short name for easy typing. import html_render as hr -reload(hr) # reloading in case you are running this in iPython - # -- we want to make sure the latest version is used +# reloading in case you are running this in iPython +# -- we want to make sure the latest version is used +import importlib +importlib.reload(hr) -## writing the file out: +# writing the file out: def render_page(page, filename): """ render the tree of elements - This uses cSstringIO to render to memory, then dump to console and + This uses StringIO to render to memory, then dump to console and write to file -- very handy! """ f = StringIO() page.render(f, " ") - f.reset() + f.seek(0) - print f.read() + print(f.read()) - f.reset() - open(filename, 'w').write( f.read() ) + f.seek(0) + open(filename, 'w').write(f.read()) -## Step 1 -########## +# Step 1 +######### page = hr.Element() -page.append("Here is a paragraph of text -- there could be more of them, but this is enough to show that we can do some text") +page.append("Here is a paragraph of text -- there could be more of them, " + "but this is enough to show that we can do some text") page.append("And here is another piece of text -- you should be able to add any number") render_page(page, "test_html_output1.html") +# The rest of the steps have been commented out. +# Uncomment them a you move along with the assignment. + # ## Step 2 # ########## @@ -54,7 +59,8 @@ def render_page(page, filename): # body = hr.Body() -# body.append(hr.P("Here is a paragraph of text -- there could be more of them, but this is enough to show that we can do some text")) +# body.append(hr.P("Here is a paragraph of text -- there could be more of them, " +# "but this is enough to show that we can do some text")) # body.append(hr.P("And here is another piece of text -- you should be able to add any number")) @@ -74,7 +80,8 @@ def render_page(page, filename): # body = hr.Body() -# body.append(hr.P("Here is a paragraph of text -- there could be more of them, but this is enough to show that we can do some text")) +# body.append(hr.P("Here is a paragraph of text -- there could be more of them, " +# "but this is enough to show that we can do some text")) # body.append(hr.P("And here is another piece of text -- you should be able to add any number")) # page.append(body) @@ -93,7 +100,8 @@ def render_page(page, filename): # body = hr.Body() -# body.append(hr.P("Here is a paragraph of text -- there could be more of them, but this is enough to show that we can do some text", +# body.append(hr.P("Here is a paragraph of text -- there could be more of them, " +# "but this is enough to show that we can do some text", # style="text-align: center; font-style: oblique;")) # page.append(body) @@ -112,7 +120,8 @@ def render_page(page, filename): # body = hr.Body() -# body.append(hr.P("Here is a paragraph of text -- there could be more of them, but this is enough to show that we can do some text", +# body.append(hr.P("Here is a paragraph of text -- there could be more of them, " +# "but this is enough to show that we can do some text", # style="text-align: center; font-style: oblique;")) # body.append(hr.Hr()) @@ -133,7 +142,8 @@ def render_page(page, filename): # body = hr.Body() -# body.append(hr.P("Here is a paragraph of text -- there could be more of them, but this is enough to show that we can do some text", +# body.append(hr.P("Here is a paragraph of text -- there could be more of them, " +# "but this is enough to show that we can do some text", # style="text-align: center; font-style: oblique;")) # body.append(hr.Hr()) @@ -160,7 +170,8 @@ def render_page(page, filename): # body.append( hr.H(2, "PythonClass - Class 6 example") ) -# body.append(hr.P("Here is a paragraph of text -- there could be more of them, but this is enough to show that we can do some text", +# body.append(hr.P("Here is a paragraph of text -- there could be more of them, " +# "but this is enough to show that we can do some text", # style="text-align: center; font-style: oblique;")) # body.append(hr.Hr()) @@ -199,7 +210,8 @@ def render_page(page, filename): # body.append( hr.H(2, "PythonClass - Class 6 example") ) -# body.append(hr.P("Here is a paragraph of text -- there could be more of them, but this is enough to show that we can do some text", +# body.append(hr.P("Here is a paragraph of text -- there could be more of them, " +# "but this is enough to show that we can do some text", # style="text-align: center; font-style: oblique;")) # body.append(hr.Hr()) @@ -221,7 +233,3 @@ def render_page(page, filename): # page.append(body) # render_page(page, "test_html_output8.html") - - - - diff --git a/Examples/Session06/html_render/sample_html.html b/Solutions/Session07/class_html_render/sample_html.html similarity index 100% rename from Examples/Session06/html_render/sample_html.html rename to Solutions/Session07/class_html_render/sample_html.html diff --git a/Solutions/Session07/class_html_render/test1.html b/Solutions/Session07/class_html_render/test1.html new file mode 100644 index 00000000..aea2657b --- /dev/null +++ b/Solutions/Session07/class_html_render/test1.html @@ -0,0 +1,2 @@ +this is some text +and this is some more text diff --git a/Solutions/Session07/class_html_render/test_html_output1.html b/Solutions/Session07/class_html_render/test_html_output1.html new file mode 100644 index 00000000..69c61524 --- /dev/null +++ b/Solutions/Session07/class_html_render/test_html_output1.html @@ -0,0 +1,4 @@ + +Here is a paragraph of text -- there could be more of them, but this is enough to show that we can do some text +And here is another piece of text -- you should be able to add any number + diff --git a/Solutions/Session07/class_html_render/test_html_render.py b/Solutions/Session07/class_html_render/test_html_render.py new file mode 100644 index 00000000..92f3e1ca --- /dev/null +++ b/Solutions/Session07/class_html_render/test_html_render.py @@ -0,0 +1,64 @@ +""" +test code for html_render.py +""" +import io + +from html_render import Element + +# utility function for testing render methods +# needs to be used in multiple tests, so write it once here. + + +def render_result(element, ind=""): + """ + calls element's render method, and returns what got rendered as a string + """ + outfile = io.StringIO() + element.render(outfile, ind) + return outfile.getvalue() + + +def test_init(): + """ + this only tests that it can be initialized -- but it's a start + """ + e = Element() + + e = Element("this is some text") + + +def test_content(): + # fixme: this tests internals!!!! + e = Element("this is some text") + + assert "this is some text" in e.content + + +def test_append(): + e = Element("this is some text") + + e.append("some more text") + + assert "some more text" in e.content + + +def test_two_instances(): + e = Element("this is some text") + e2 = Element("this is some text") + + e.append("some more text") + + assert "some more text" not in e2.content + + +def test_render(): + e = Element("this is some text") + e.append("and this is some more text") + + file_contents = render_result(e) + + assert("this is some text") in file_contents + assert("and this is some more text") in file_contents + + assert file_contents.startswith("") + assert file_contents.strip().endswith("") diff --git a/Solutions/Session07/step_1/html_render.py b/Solutions/Session07/step_1/html_render.py new file mode 100644 index 00000000..d5e6819a --- /dev/null +++ b/Solutions/Session07/step_1/html_render.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python3 + +""" +step 1 of Chris's solution +""" + + +class Element: + + tag = 'html' + + def __init__(self, content=None): + self.content = [] + if content: + self.content.append(content) + + def append(self, content): + self.content.append(content) + + def render(self, out_file, ind=""): + out_file.write("<{}>\n".format(self.tag)) + for stuff in self.content: + out_file.write(stuff + "\n") + out_file.write("\n".format(self.tag)) diff --git a/Solutions/Session07/step_1/test_html_render.py b/Solutions/Session07/step_1/test_html_render.py new file mode 100644 index 00000000..45e51019 --- /dev/null +++ b/Solutions/Session07/step_1/test_html_render.py @@ -0,0 +1,67 @@ +""" +test code for html_render.py + +only step 1 +""" + +import io + +from html_render import Element + +# utility function for testing render methods +# needs to be used in multiple tests, so write it once here. + + +def render_result(element, ind=""): + """ + calls element's render method, and returns what got rendered as a string + """ + outfile = io.StringIO() + element.render(outfile, ind) + return outfile.getvalue() + + +def test_init(): + """ + this only tests that it can be initialized -- but it's a start + """ + e = Element() + + e = Element("this is some text") + + +def test_content(): + # fixme: this tests internals!!!! + e = Element("this is some text") + + assert "this is some text" in e.content + + +def test_append(): + e = Element("this is some text") + + e.append("some more text") + + assert "some more text" in e.content + + +def test_two_instances(): + e = Element("this is some text") + e2 = Element("this is some text") + + e.append("some more text") + + assert "some more text" not in e2.content + + +def test_render(): + e = Element("this is some text") + e.append("and this is some more text") + + file_contents = render_result(e) + + assert("this is some text") in file_contents + assert("and this is some more text") in file_contents + + assert file_contents.startswith("") + assert file_contents.strip().endswith("") diff --git a/Solutions/Session07/step_2_complete/.cache/v/cache/lastfailed b/Solutions/Session07/step_2_complete/.cache/v/cache/lastfailed new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/Solutions/Session07/step_2_complete/.cache/v/cache/lastfailed @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/Solutions/Session07/step_2_complete/html_render.py b/Solutions/Session07/step_2_complete/html_render.py new file mode 100644 index 00000000..75d8d842 --- /dev/null +++ b/Solutions/Session07/step_2_complete/html_render.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python3 + +""" +Chris's solution through step 2 with full indentation +""" + + +class TextWrapper: + """ + A simple wrapper that creates a class with a render method + for just text + + This allows the Element classes to render either Element objects or + plain text + + """ + def __init__(self, text): + self.text = text + + def render(self, file_out, current_ind=""): + file_out.write(current_ind + self.text) + + +class Element: + + tag = "html" + indent = " " + + def __init__(self, content=None): + self.content = [] + if content: + # call the classes append method + # so that it can do anything special it needs to do + self.append(content) + + def append(self, content): + """ + add a new piece of content or another element to this element + """ + # note: this changed the internal represntation of content + # it no longer holds strings -- so a test will fail + # but that test was testing internal API -- + # it's probably better remove it + if hasattr(content, 'render'): + self.content.append(content) + else: + self.content.append(TextWrapper(str(content))) + + def render(self, out_file, ind=""): + out_file.write("{}<{}>\n".format(ind, self.tag)) + for stuff in self.content: + stuff.render(out_file, ind + self.indent) + out_file.write("\n") + out_file.write("{}".format(ind, self.tag)) + + +class Html(Element): + tag = 'html' + + +class Body(Element): + tag = "body" + + +class P(Element): + tag = "p" + diff --git a/Solutions/Session07/step_2_complete/test_html_render.py b/Solutions/Session07/step_2_complete/test_html_render.py new file mode 100644 index 00000000..c9346389 --- /dev/null +++ b/Solutions/Session07/step_2_complete/test_html_render.py @@ -0,0 +1,201 @@ +""" +test code for html_render.py + +includes through step 2 +""" +import io + +from html_render import Element, Html, Body, P, TextWrapper + + +# utility function for testing render methods +# needs to be used in multiple tests, so write it once here. + + +def render_result(element, ind=""): + """ + calls element's render method, and returns what got rendered as a string + """ + outfile = io.StringIO() + element.render(outfile, ind) + return outfile.getvalue() + + +def test_init(): + """ + this only tests that it can be initialized -- but it's a start + """ + e = Element() + + e = Element("this is some text") + + +# These two tests were testing internals +# so they failed when I added the TextWrapper +# but I"m removing them because tests really should be testing +# the external API. +# def test_content(): +# # fixme: this tests internals!!!! +# e = Element("this is some text") + +# assert "this is some text" in e.content + +# def test_append(): +# e = Element("this is some text") + +# e.append("some more text") + +# assert "some more text" in e.content + + +def test_two_instances(): + e = Element("this is some text") + e2 = Element("this is some text") + + e.append("some more text") + + assert "some more text" not in e2.content + + +def test_render(): + e = Element("this is some text") + e.append("and this is some more text") + + file_contents = render_result(e) + + assert("this is some text") in file_contents + assert("and this is some more text") in file_contents + + assert file_contents.startswith("") + assert file_contents.strip().endswith("") + + +def test_html(): + e = Html("this is some text") + e.append("and this is some more text") + + file_contents = render_result(e) + + assert("this is some text") in file_contents + assert("and this is some more text") in file_contents + + assert file_contents.startswith("") + assert file_contents.strip().endswith("") + + +def test_body(): + e = Body("this is some text") + e.append("and this is some more text") + + file_contents = render_result(e) + + assert("this is some text") in file_contents + assert("and this is some more text") in file_contents + + assert file_contents.startswith("") + assert file_contents.strip().endswith("") + + +def test_p(): + e = P("this is some text") + e.append("and this is some more text") + + file_contents = render_result(e) + + assert("this is some text") in file_contents + assert("and this is some more text") in file_contents + + assert file_contents.startswith("

") + assert file_contents.strip().endswith("

") + + +def test_text_wrapper(): + tw = TextWrapper("A basic piece of text") + + file_contents = render_result(tw) + assert file_contents == "A basic piece of text" + + +def test_sub_element(): + """ + tests that you can add another element and still render properly + """ + page = Html() + page.append("some plain text.") + page.append(P("A simple paragraph of text")) + page.append("Some more plain text.") + + file_contents = render_result(page) + + # note: the above tests should make sure that the tags are getting rendered. + assert "some plain text" in file_contents + assert "A simple paragraph of text" in file_contents + assert "Some more plain text." in file_contents + assert "some plain text" in file_contents + + +def test_step_2_noindent(): + """ + This is more if an integration test -- a number of things together + + this test does not yet include indentation + """ + page = Html() + body = Body() + page.append(body) + body.append(P("a small paragraph of text")) + body.append(P("another small paragraph of text")) + body.append(P("and here is a bit more")) + + file_contents = render_result(page).strip() + + print(file_contents) + assert file_contents.startswith("") + assert file_contents.endswith("") + assert "a small paragraph of text" in file_contents + assert "" in file_contents + # you could do more here, but it should all be covered above. + + +def test_indent(): + """ + Tests that the indentation gets passed through to the renderer + """ + html = Html("some content") + file_contents = render_result(html, ind=" ") + + print(file_contents) + lines = file_contents.split("\n") + assert lines[0].startswith(" <") + assert lines[-1].startswith(" <") + + +def test_indent_contents(): + """ + The contents in a element should be indented more than the tag + by the amount in the indent class attribute + """ + html = Html("some content") + file_contents = render_result(html, ind="") + + print(file_contents) + lines = file_contents.split("\n") + assert lines[1].startswith(Element.indent) + + +def test_multiple_indent(): + """ + make sure multiple levels get indented fully + """ + body = Body() + body.append(P("some text")) + html = Html(body) + + file_contents = render_result(html) + + print(file_contents) + lines = file_contents.split("\n") + for i in range(3): + assert lines[i].startswith(i * Element.indent + "<") + + assert lines[3].startswith(3 * Element.indent + "some") diff --git a/Solutions/Session07/step_2_noindent/.cache/v/cache/lastfailed b/Solutions/Session07/step_2_noindent/.cache/v/cache/lastfailed new file mode 100644 index 00000000..a2066bad --- /dev/null +++ b/Solutions/Session07/step_2_noindent/.cache/v/cache/lastfailed @@ -0,0 +1,3 @@ +{ + "test_html_render.py::test_step_2_noindent": true +} \ No newline at end of file diff --git a/Solutions/Session07/step_2_noindent/html_render.py b/Solutions/Session07/step_2_noindent/html_render.py new file mode 100644 index 00000000..9dbd2110 --- /dev/null +++ b/Solutions/Session07/step_2_noindent/html_render.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 + +""" +Chris's solution through step 2 without indentation +""" + + +class TextWrapper: + """ + A simple wrapper that creates a class with a render method + for just text + + This allows the Element classes to render either Element objects or + plain text + + """ + def __init__(self, text): + self.text = text + + def render(self, file_out, current_ind=""): + file_out.write(current_ind + self.text) + + +class Element: + + tag = 'html' + + def __init__(self, content=None): + self.content = [] + if content: + # call the classes append method + # so that it can do anything special it needs to do + self.append(content) + + def append(self, content): + """ + add a new piece of content or another element to this element + """ + # note: this changed the internal represntation of content + # it no longer holds strings -- so a test will fail + # but that test was testing internal API -- + # it's probably better remove it + if hasattr(content, 'render'): + self.content.append(content) + else: + self.content.append(TextWrapper(str(content))) + + def render(self, out_file, ind=""): + out_file.write("<{}>\n".format(self.tag)) + for stuff in self.content: + stuff.render(out_file) + out_file.write("\n") + out_file.write("".format(self.tag)) + + +class Html(Element): + tag = 'html' + + +class Body(Element): + tag = "body" + + +class P(Element): + tag = "p" + diff --git a/Solutions/Session07/step_2_noindent/test_html_render.py b/Solutions/Session07/step_2_noindent/test_html_render.py new file mode 100644 index 00000000..7fed7dc3 --- /dev/null +++ b/Solutions/Session07/step_2_noindent/test_html_render.py @@ -0,0 +1,159 @@ +""" +test code for html_render.py + +includes through step 2 without indentation +""" +import io + +from html_render import Element, Html, Body, P, TextWrapper + + +# utility function for testing render methods +# needs to be used in multiple tests, so write it once here. + + +def render_result(element, ind=""): + """ + calls element's render method, and returns what got rendered as a string + """ + outfile = io.StringIO() + element.render(outfile, ind) + return outfile.getvalue() + + +def test_init(): + """ + this only tests that it can be initialized -- but it's a start + """ + e = Element() + + e = Element("this is some text") + + +# These two tests were testing internals +# so they failed when I added the TextWrapper +# but I"m removing them because tests really should be testing +# the external API. +# def test_content(): +# # fixme: this tests internals!!!! +# e = Element("this is some text") + +# assert "this is some text" in e.content + +# def test_append(): +# e = Element("this is some text") + +# e.append("some more text") + +# assert "some more text" in e.content + + +def test_two_instances(): + e = Element("this is some text") + e2 = Element("this is some text") + + e.append("some more text") + + assert "some more text" not in e2.content + + +def test_render(): + e = Element("this is some text") + e.append("and this is some more text") + + file_contents = render_result(e) + + assert("this is some text") in file_contents + assert("and this is some more text") in file_contents + + assert file_contents.startswith("") + assert file_contents.strip().endswith("") + + +def test_html(): + e = Html("this is some text") + e.append("and this is some more text") + + file_contents = render_result(e) + + assert("this is some text") in file_contents + assert("and this is some more text") in file_contents + + assert file_contents.startswith("") + assert file_contents.strip().endswith("") + + +def test_body(): + e = Body("this is some text") + e.append("and this is some more text") + + file_contents = render_result(e) + + assert("this is some text") in file_contents + assert("and this is some more text") in file_contents + + assert file_contents.startswith("") + assert file_contents.strip().endswith("") + + +def test_p(): + e = P("this is some text") + e.append("and this is some more text") + + file_contents = render_result(e) + + assert("this is some text") in file_contents + assert("and this is some more text") in file_contents + + assert file_contents.startswith("

") + assert file_contents.strip().endswith("

") + + +def test_text_wrapper(): + tw = TextWrapper("A basic piece of text") + + file_contents = render_result(tw) + assert file_contents == "A basic piece of text" + + +def test_sub_element(): + """ + tests that you can add another element and still render properly + """ + page = Html() + page.append("some plain text.") + page.append(P("A simple paragraph of text")) + page.append("Some more plain text.") + + file_contents = render_result(page) + + # note: the above tests should make sure that the tags are getting rendered. + assert "some plain text" in file_contents + assert "A simple paragraph of text" in file_contents + assert "Some more plain text." in file_contents + assert "some plain text" in file_contents + + +def test_step_2_noindent(): + """ + This is more if an integration test -- a number of things together + + this test does not yet include indentation + """ + page = Html() + body = Body() + page.append(body) + body.append(P("a small paragraph of text")) + body.append(P("another small paragraph of text")) + body.append(P("and here is a bit more")) + + file_contents = render_result(page).strip() + + print(file_contents) + assert file_contents.startswith("") + assert file_contents.endswith("") + assert "a small paragraph of text" in file_contents + assert "" in file_contents + # you could do more here, but it should all be covered above. + assert False + diff --git a/Solutions/Session07/step_3/.cache/v/cache/lastfailed b/Solutions/Session07/step_3/.cache/v/cache/lastfailed new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/Solutions/Session07/step_3/.cache/v/cache/lastfailed @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/Solutions/Session07/step_3/html_render.py b/Solutions/Session07/step_3/html_render.py new file mode 100644 index 00000000..8cfdce28 --- /dev/null +++ b/Solutions/Session07/step_3/html_render.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python3 + +""" +Chris's solution through step 3 +""" + + +class TextWrapper: + """ + A simple wrapper that creates a class with a render method + for just text + + This allows the Element classes to render either Element objects or + plain text + + """ + def __init__(self, text): + self.text = text + + def render(self, file_out, current_ind=""): + file_out.write(current_ind + self.text) + + +class Element: + + tag = "html" + indent = " " + + def __init__(self, content=None): + self.content = [] + if content: + # call the classes append method + # so that it can do anything special it needs to do + self.append(content) + + def append(self, content): + """ + add a new piece of content or another element to this element + """ + # note: this changed the internal represntation of content + # it no longer holds strings -- so a test will fail + # but that test was testing internal API -- + # it's probably better remove it + if hasattr(content, 'render'): + self.content.append(content) + else: + self.content.append(TextWrapper(str(content))) + + def render(self, out_file, ind=""): + out_file.write("{}<{}>\n".format(ind, self.tag)) + for stuff in self.content: + stuff.render(out_file, ind + self.indent) + out_file.write("\n") + out_file.write("{}".format(ind, self.tag)) + + +class OneLineTag(Element): + def render(self, out_file, ind=""): + # there is some repition here -- maybe factor that out? + out_file.write("{}<{}>".format(ind, self.tag)) + for stuff in self.content: + stuff.render(out_file) + out_file.write("".format(self.tag)) + + +class Html(Element): + tag = 'html' + + +class Body(Element): + tag = "body" + + +class P(Element): + tag = "p" + + +class Head(Element): + tag = "head" + + +class Title(OneLineTag): + tag = "title" + + diff --git a/Solutions/Session07/step_3/test_html_render.py b/Solutions/Session07/step_3/test_html_render.py new file mode 100644 index 00000000..f13069ff --- /dev/null +++ b/Solutions/Session07/step_3/test_html_render.py @@ -0,0 +1,278 @@ +""" +test code for html_render.py + +includes through step 3 +""" + +import io + +from html_render import (Element, + Html, + Body, + P, + TextWrapper, + Head, + Title, + ) + +# utility function for testing render methods +# needs to be used in multiple tests, so write it once here. + + +def render_result(element, ind=""): + """ + calls element's render method, and returns what got rendered as a string + """ + outfile = io.StringIO() + element.render(outfile, ind) + return outfile.getvalue() + + +def test_init(): + """ + this only tests that it can be initialized -- but it's a start + """ + e = Element() + + e = Element("this is some text") + + +# These two tests were testing internals +# so they failed when I added the TextWrapper +# but I"m removing them because tests really should be testing +# the external API. +# def test_content(): +# # fixme: this tests internals!!!! +# e = Element("this is some text") + +# assert "this is some text" in e.content + +# def test_append(): +# e = Element("this is some text") + +# e.append("some more text") + +# assert "some more text" in e.content + + +def test_two_instances(): + e = Element("this is some text") + e2 = Element("this is some text") + + e.append("some more text") + + assert "some more text" not in e2.content + + +def test_render(): + e = Element("this is some text") + e.append("and this is some more text") + + file_contents = render_result(e) + + assert("this is some text") in file_contents + assert("and this is some more text") in file_contents + + assert file_contents.startswith("") + assert file_contents.strip().endswith("") + + +def test_html(): + e = Html("this is some text") + e.append("and this is some more text") + + file_contents = render_result(e) + + assert("this is some text") in file_contents + assert("and this is some more text") in file_contents + + assert file_contents.startswith("") + assert file_contents.strip().endswith("") + + +def test_body(): + e = Body("this is some text") + e.append("and this is some more text") + + file_contents = render_result(e) + + assert("this is some text") in file_contents + assert("and this is some more text") in file_contents + + assert file_contents.startswith("") + assert file_contents.strip().endswith("") + + +def test_p(): + e = P("this is some text") + e.append("and this is some more text") + + file_contents = render_result(e) + + assert("this is some text") in file_contents + assert("and this is some more text") in file_contents + + assert file_contents.startswith("

") + assert file_contents.strip().endswith("

") + + +def test_text_wrapper(): + tw = TextWrapper("A basic piece of text") + + file_contents = render_result(tw) + assert file_contents == "A basic piece of text" + + +def test_non_str(): + """ you should be able to pass anything in, and it will get + "stringified" + """ + e = P(34) # a number + e.append((3, 4, 5)) # even a tuple + + file_contents = render_result(e) + + print(file_contents) + assert("34") in file_contents + assert("(3, 4, 5)") in file_contents + + +def test_sub_element(): + """ + tests that you can add another element and still render properly + """ + page = Html() + page.append("some plain text.") + page.append(P("A simple paragraph of text")) + page.append("Some more plain text.") + + file_contents = render_result(page) + + # note: the above tests should make sure that the tags are getting rendered. + assert "some plain text" in file_contents + assert "A simple paragraph of text" in file_contents + assert "Some more plain text." in file_contents + assert "some plain text" in file_contents + + +def test_step_2_noindent(): + """ + This is more if an integration test -- a number of things together + + this test does not yet include indentation + """ + page = Html() + body = Body() + page.append(body) + body.append(P("a small paragraph of text")) + body.append(P("another small paragraph of text")) + body.append(P("and here is a bit more")) + + file_contents = render_result(page).strip() + + print(file_contents) + assert file_contents.startswith("") + assert file_contents.endswith("") + assert "a small paragraph of text" in file_contents + assert "" in file_contents + # you could do more here, but it should all be covered above. + # assert False + + +def test_indent(): + """ + Tests that the indentation gets passed through to the renderer + """ + html = Html("some content") + file_contents = render_result(html, ind=" ") + + print(file_contents) + lines = file_contents.split("\n") + assert lines[0].startswith(" <") + assert lines[-1].startswith(" <") + + +def test_indent_contents(): + """ + The contents in a element should be indented more than the tag + by the amount in the indent class attribute + """ + html = Html("some content") + file_contents = render_result(html, ind="") + + print(file_contents) + lines = file_contents.split("\n") + assert lines[1].startswith(Element.indent) + + +def test_multiple_indent(): + """ + make sure multiple levels get indented fully + """ + body = Body() + body.append(P("some text")) + html = Html(body) + + file_contents = render_result(html) + + print(file_contents) + lines = file_contents.split("\n") + for i in range(3): + assert lines[i].startswith(i * Element.indent + "<") + + assert lines[3].startswith(3 * Element.indent + "some") + + +def test_title(): + """ + This will implicitly test the OneLineTag element + """ + t = Title("Isn't this a nice title?") + + # making sure indentation still works + file_contents = render_result(t, ind=" ") + + print(file_contents) + # no "strip()" -- making sure there are no extra newlines + assert "\n" not in file_contents + assert file_contents.startswith(" ") + assert file_contents.endswith("") + # the only newline should be at the end + assert "\n" not in file_contents + + +def test_head(): + """ + testing Head with a title in it -- it should never be blank + """ + h = Head() + h.append(Title("A nifty title for the page")) + + +def test_full_page_with_title(): + """ + not much to actually test here, but good to see it put together. + + everything should have already been tested. + """ + page = Html() + + head = Head() + head.append(Title("PythonClass Example")) + + page.append(head) + + body = Body() + + body.append(P("Here is a paragraph of text -- there could be more of them, " + "but this is enough to show that we can do some text")) + body.append(P("And here is another piece of text -- you should be able to add any number")) + + page.append(body) + + file_contents = render_result(page) + + print(file_contents) + + # uncomment this to see results + # assert False diff --git a/Solutions/Session07/step_4/.cache/v/cache/lastfailed b/Solutions/Session07/step_4/.cache/v/cache/lastfailed new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/Solutions/Session07/step_4/.cache/v/cache/lastfailed @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/Solutions/Session07/step_4/html_render.py b/Solutions/Session07/step_4/html_render.py new file mode 100644 index 00000000..510bbafc --- /dev/null +++ b/Solutions/Session07/step_4/html_render.py @@ -0,0 +1,103 @@ +#!/usr/bin/env python3 + +""" +Chris's solution through step 4 +""" + + +class TextWrapper: + """ + A simple wrapper that creates a class with a render method + for just text + + This allows the Element classes to render either Element objects or + plain text + + """ + def __init__(self, text): + self.text = text + + def render(self, file_out, current_ind=""): + file_out.write(current_ind + self.text) + + +class Element: + + tag = "html" + indent = " " + + def __init__(self, content=None, **kwargs): + self.attributes = kwargs + self.content = [] + if content: + # call the classes append method + # so that it can do anything special it needs to do + self.append(content) + + def append(self, content): + """ + add a new piece of content or another element to this element + """ + # note: this changed the internal representation of content + # it no longer holds strings -- so a test will fail + # but that test was testing internal API -- + # it's probably better remove it + if hasattr(content, 'render'): + self.content.append(content) + else: + self.content.append(TextWrapper(str(content))) + + def make_tags(self): + """ + create the tags + -- in a separate method so different subclass's render methods can use it + """ + attrs = " ".join(['{}="{}"'.format(key, val) for key, val in self.attributes.items()]) + if attrs.strip(): + open_tag = "<{} {}>".format(self.tag, attrs.strip()) + else: + open_tag = "<{}>".format(self.tag) + close_tag = "".format(self.tag) + + return open_tag, close_tag + + def render(self, out_file, ind=""): + open_tag, close_tag = self.make_tags() + out_file.write(ind + open_tag + "\n") + for stuff in self.content: + stuff.render(out_file, ind + self.indent) + out_file.write("\n") + out_file.write(ind + close_tag) + + +class OneLineTag(Element): + # note: by re-writting the render + def render(self, out_file, ind=""): + # there is some repition here -- maybe factor that out? + open_tag, close_tag = self.make_tags() + out_file.write(ind + open_tag) + for stuff in self.content: + stuff.render(out_file) + out_file.write(close_tag) + + +class Html(Element): + tag = 'html' + + +class Body(Element): + tag = "body" + + +class P(Element): + tag = "p" + + +class Head(Element): + tag = "head" + + +class Title(OneLineTag): + tag = "title" + + diff --git a/Solutions/Session07/step_4/html_render_1.py b/Solutions/Session07/step_4/html_render_1.py new file mode 100644 index 00000000..d0da047b --- /dev/null +++ b/Solutions/Session07/step_4/html_render_1.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python3 + +""" +Chris's solution through step 4 + +This doesn't do attributes in OneLineTags! +""" + + +class TextWrapper: + """ + A simple wrapper that creates a class with a render method + for just text + + This allows the Element classes to render either Element objects or + plain text + + """ + def __init__(self, text): + self.text = text + + def render(self, file_out, current_ind=""): + file_out.write(current_ind + self.text) + + +class Element: + + tag = "html" + indent = " " + + def __init__(self, content=None, **kwargs): + self.attributes = kwargs + self.content = [] + if content: + # call the classes append method + # so that it can do anything special it needs to do + self.append(content) + + def append(self, content): + """ + add a new piece of content or another element to this element + """ + # note: this changed the internal representation of content + # it no longer holds strings -- so a test will fail + # but that test was testing internal API -- + # it's probably better remove it + if hasattr(content, 'render'): + self.content.append(content) + else: + self.content.append(TextWrapper(str(content))) + + def render(self, out_file, ind=""): + attrs = " ".join(['{}="{}"'.format(key, val) for key, val in self.attributes.items()]) + out_file.write("{}<{} {}>\n".format(ind, self.tag, attrs)) + for stuff in self.content: + stuff.render(out_file, ind + self.indent) + out_file.write("\n") + out_file.write("{}".format(ind, self.tag)) + + +class OneLineTag(Element): + def render(self, out_file, ind=""): + # there is some repition here -- maybe factor that out? + out_file.write("{}<{}>".format(ind, self.tag)) + for stuff in self.content: + stuff.render(out_file) + out_file.write("".format(self.tag)) + + +class Html(Element): + tag = 'html' + + +class Body(Element): + tag = "body" + + +class P(Element): + tag = "p" + + +class Head(Element): + tag = "head" + + +class Title(OneLineTag): + tag = "title" + + diff --git a/Solutions/Session07/step_4/test_html_render.py b/Solutions/Session07/step_4/test_html_render.py new file mode 100644 index 00000000..b1308def --- /dev/null +++ b/Solutions/Session07/step_4/test_html_render.py @@ -0,0 +1,304 @@ +""" +test code for html_render.py + +includes step 4 +""" +import io + +from html_render import (Element, + Html, + Body, + P, + TextWrapper, + Head, + Title, + ) + +# utility function for testing render methods +# needs to be used in multiple tests, so write it once here. + + +def render_result(element, ind=""): + """ + calls element's render method, and returns what got rendered as a string + """ + outfile = io.StringIO() + element.render(outfile, ind) + return outfile.getvalue() + + +def test_init(): + """ + this only tests that it can be initialized -- but it's a start + """ + e = Element() + + e = Element("this is some text") + + +# These two tests were testing internals +# so they failed when I added the TextWrapper +# but I"m removing them because tests really should be testing +# the external API. +# def test_content(): +# # fixme: this tests internals!!!! +# e = Element("this is some text") + +# assert "this is some text" in e.content + +# def test_append(): +# e = Element("this is some text") + +# e.append("some more text") + +# assert "some more text" in e.content + + +def test_two_instances(): + e = Element("this is some text") + e2 = Element("this is some text") + + e.append("some more text") + + assert "some more text" not in e2.content + + +def test_render(): + e = Element("this is some text") + e.append("and this is some more text") + + file_contents = render_result(e) + + assert("this is some text") in file_contents + assert("and this is some more text") in file_contents + + assert file_contents.startswith("") + assert file_contents.strip().endswith("") + + +def test_html(): + e = Html("this is some text") + e.append("and this is some more text") + + file_contents = render_result(e) + + assert("this is some text") in file_contents + assert("and this is some more text") in file_contents + + assert file_contents.startswith("") + assert file_contents.strip().endswith("") + + +def test_body(): + e = Body("this is some text") + e.append("and this is some more text") + + file_contents = render_result(e) + + assert("this is some text") in file_contents + assert("and this is some more text") in file_contents + + assert file_contents.startswith("") + assert file_contents.strip().endswith("") + + +def test_p(): + e = P("this is some text") + e.append("and this is some more text") + + file_contents = render_result(e) + + assert("this is some text") in file_contents + assert("and this is some more text") in file_contents + + assert file_contents.startswith("

") + assert file_contents.strip().endswith("

") + + +def test_text_wrapper(): + tw = TextWrapper("A basic piece of text") + + file_contents = render_result(tw) + assert file_contents == "A basic piece of text" + + +def test_non_str(): + """ you should be able to pass anything in, and it will get + "stringified" + """ + e = P(34) # a number + e.append((3, 4, 5)) # even a tuple + + file_contents = render_result(e) + + print(file_contents) + assert("34") in file_contents + assert("(3, 4, 5)") in file_contents + + +def test_sub_element(): + """ + tests that you can add another element and still render properly + """ + page = Html() + page.append("some plain text.") + page.append(P("A simple paragraph of text")) + page.append("Some more plain text.") + + file_contents = render_result(page) + + # note: the above tests should make sure that the tags are getting rendered. + assert "some plain text" in file_contents + assert "A simple paragraph of text" in file_contents + assert "Some more plain text." in file_contents + assert "some plain text" in file_contents + + +def test_step_2_noindent(): + """ + This is more if an integration test -- a number of things together + + this test does not yet include indentation + """ + page = Html() + body = Body() + page.append(body) + body.append(P("a small paragraph of text")) + body.append(P("another small paragraph of text")) + body.append(P("and here is a bit more")) + + file_contents = render_result(page).strip() + + print(file_contents) + assert file_contents.startswith("") + assert file_contents.endswith("") + assert "a small paragraph of text" in file_contents + assert "" in file_contents + # you could do more here, but it should all be covered above. + # assert False + + +def test_indent(): + """ + Tests that the indentation gets passed through to the renderer + """ + html = Html("some content") + file_contents = render_result(html, ind=" ") + + print(file_contents) + lines = file_contents.split("\n") + assert lines[0].startswith(" <") + assert lines[-1].startswith(" <") + + +def test_indent_contents(): + """ + The contents in a element should be indented more than the tag + by the amount in the indent class attribute + """ + html = Html("some content") + file_contents = render_result(html, ind="") + + print(file_contents) + lines = file_contents.split("\n") + assert lines[1].startswith(Element.indent) + + +def test_multiple_indent(): + """ + make sure multiple levels get indented fully + """ + body = Body() + body.append(P("some text")) + html = Html(body) + + file_contents = render_result(html) + + print(file_contents) + lines = file_contents.split("\n") + for i in range(3): + assert lines[i].startswith(i * Element.indent + "<") + + assert lines[3].startswith(3 * Element.indent + "some") + + +def test_title(): + """ + This will implicitly test the OneLineTag element + """ + t = Title("Isn't this a nice title?") + + # making sure indentation still works + file_contents = render_result(t, ind=" ") + + print(file_contents) + # no "strip()" -- making sure there are no extra newlines + assert "\n" not in file_contents + assert "> " not in file_contents + assert file_contents.startswith(" ") + assert file_contents.endswith("") + # the only newline should be at the end + assert "\n" not in file_contents + + +def test_head(): + """ + testing Head with a title in it -- it should never be blank + """ + h = Head() + h.append(Title("A nifty title for the page")) + + +def test_full_page_with_title(): + """ + not much to actually test here, but good to see it put together. + + everything should have already been tested. + """ + page = Html() + + head = Head() + head.append(Title("PythonClass Example")) + + page.append(head) + + body = Body() + + body.append(P("Here is a paragraph of text -- there could be more of them, " + "but this is enough to show that we can do some text")) + body.append(P("And here is another piece of text -- you should be able to add any number")) + + page.append(body) + + file_contents = render_result(page) + + print(file_contents) + + # uncomment this to see results + # assert False + + +def test_attributes(): + """ + tests that you can pass attributes in to the tag + """ + e = Element("some text", id="this", color="red") # could be any attributes + file_contents = render_result(e) + print(file_contents) + assert 'id="this"' in file_contents + assert 'color="red"' in file_contents + # note -- dicts aren't ordered, so you can't enforce order! + # assert '' in file_contents + + +def test_attributes_one_line_tag(): + """ + tests that you can pass attributes in to the tag + """ + e = Title("some text", id="this", color="red") # could be any attributes + file_contents = render_result(e) + print(file_contents) + assert 'id="this"' in file_contents + assert 'color="red"' in file_contents + + diff --git a/Solutions/Session07/step_5/.cache/v/cache/lastfailed b/Solutions/Session07/step_5/.cache/v/cache/lastfailed new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/Solutions/Session07/step_5/.cache/v/cache/lastfailed @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/Solutions/Session07/step_5/html_render.py b/Solutions/Session07/step_5/html_render.py new file mode 100644 index 00000000..c316c5bc --- /dev/null +++ b/Solutions/Session07/step_5/html_render.py @@ -0,0 +1,128 @@ +#!/usr/bin/env python3 + +""" +Chris's solution through step 5 +""" + + +class TextWrapper: + """ + A simple wrapper that creates a class with a render method + for just text + + This allows the Element classes to render either Element objects or + plain text + + """ + def __init__(self, text): + self.text = text + + def render(self, file_out, current_ind=""): + file_out.write(current_ind + self.text) + + +class Element: + + tag = "html" + indent = " " + + def __init__(self, content=None, **kwargs): + self.attributes = kwargs + self.content = [] + if content: + # call the classes append method + # so that it can do anything special it needs to do + self.append(content) + + def append(self, content): + """ + add a new piece of content or another element to this element + """ + # note: this changed the internal representation of content + # it no longer holds strings -- so a test will fail + # but that test was testing internal API -- + # it's probably better remove it + if hasattr(content, 'render'): + self.content.append(content) + else: + self.content.append(TextWrapper(str(content))) + + def make_tags(self): + """ + create the tags + -- in a separate method so different subclass's render methods can use it + """ + attrs = " ".join(['{}="{}"'.format(key, val) for key, val in self.attributes.items()]) + if attrs.strip(): + open_tag = "<{} {}>".format(self.tag, attrs.strip()) + else: + open_tag = "<{}>".format(self.tag) + close_tag = "".format(self.tag) + + return open_tag, close_tag + + def render(self, out_file, ind=""): + open_tag, close_tag = self.make_tags() + out_file.write(ind + open_tag + "\n") + for stuff in self.content: + stuff.render(out_file, ind + self.indent) + out_file.write("\n") + out_file.write(ind + close_tag) + + +class OneLineTag(Element): + # note: by re-writting the render + def render(self, out_file, ind=""): + # there is some repition here -- maybe factor that out? + open_tag, close_tag = self.make_tags() + out_file.write(ind + open_tag) + for stuff in self.content: + stuff.render(out_file) + out_file.write(close_tag) + + +class Html(Element): + tag = 'html' + + +class Body(Element): + tag = "body" + + +class P(Element): + tag = "p" + + +class Head(Element): + tag = "head" + + +class Title(OneLineTag): + tag = "title" + + +class SelfClosingTag(Element): + """ + base class for tags that have no content + """ + def render(self, out_file, ind=""): + # there is some repition here -- maybe factor that out? + open_tag, _ = self.make_tags() + # make it a self cloding tag by adding the / + out_file.write(ind + open_tag.replace(">", " />")) + + +class Hr(SelfClosingTag): + """ + Horizontal Rule + """ + tag = "hr" + + +class Br(SelfClosingTag): + """ + Line break + """ + tag = "br" + + diff --git a/Solutions/Session07/step_5/test_html_render.py b/Solutions/Session07/step_5/test_html_render.py new file mode 100644 index 00000000..8966bfe6 --- /dev/null +++ b/Solutions/Session07/step_5/test_html_render.py @@ -0,0 +1,328 @@ +""" +test code for html_render.py + +includes up to step 5 +""" + +import io + +from html_render import (Element, + Html, + Body, + P, + TextWrapper, + Head, + Title, + Hr, + Br + ) + +# utility function for testing render methods +# needs to be used in multiple tests, so write it once here. + + +def render_result(element, ind=""): + """ + calls element's render method, and returns what got rendered as a string + """ + outfile = io.StringIO() + element.render(outfile, ind) + return outfile.getvalue() + + +def test_init(): + """ + this only tests that it can be initialized -- but it's a start + """ + e = Element() + + e = Element("this is some text") + + +# These two tests were testing internals +# so they failed when I added the TextWrapper +# but I"m removing them because tests really should be testing +# the external API. +# def test_content(): +# # fixme: this tests internals!!!! +# e = Element("this is some text") + +# assert "this is some text" in e.content + +# def test_append(): +# e = Element("this is some text") + +# e.append("some more text") + +# assert "some more text" in e.content + + +def test_two_instances(): + e = Element("this is some text") + e2 = Element("this is some text") + + e.append("some more text") + + assert "some more text" not in e2.content + + +def test_render(): + e = Element("this is some text") + e.append("and this is some more text") + + file_contents = render_result(e) + + assert("this is some text") in file_contents + assert("and this is some more text") in file_contents + + assert file_contents.startswith("") + assert file_contents.strip().endswith("") + + +def test_html(): + e = Html("this is some text") + e.append("and this is some more text") + + file_contents = render_result(e) + + assert("this is some text") in file_contents + assert("and this is some more text") in file_contents + + assert file_contents.startswith("") + assert file_contents.strip().endswith("") + + +def test_body(): + e = Body("this is some text") + e.append("and this is some more text") + + file_contents = render_result(e) + + assert("this is some text") in file_contents + assert("and this is some more text") in file_contents + + assert file_contents.startswith("") + assert file_contents.strip().endswith("") + + +def test_p(): + e = P("this is some text") + e.append("and this is some more text") + + file_contents = render_result(e) + + assert("this is some text") in file_contents + assert("and this is some more text") in file_contents + + assert file_contents.startswith("

") + assert file_contents.strip().endswith("

") + + +def test_text_wrapper(): + tw = TextWrapper("A basic piece of text") + + file_contents = render_result(tw) + assert file_contents == "A basic piece of text" + + +def test_non_str(): + """ you should be able to pass anything in, and it will get + "stringified" + """ + e = P(34) # a number + e.append((3, 4, 5)) # even a tuple + + file_contents = render_result(e) + + print(file_contents) + assert("34") in file_contents + assert("(3, 4, 5)") in file_contents + + +def test_sub_element(): + """ + tests that you can add another element and still render properly + """ + page = Html() + page.append("some plain text.") + page.append(P("A simple paragraph of text")) + page.append("Some more plain text.") + + file_contents = render_result(page) + + # note: the above tests should make sure that the tags are getting rendered. + assert "some plain text" in file_contents + assert "A simple paragraph of text" in file_contents + assert "Some more plain text." in file_contents + assert "some plain text" in file_contents + + +def test_step_2_noindent(): + """ + This is more if an integration test -- a number of things together + + this test does not yet include indentation + """ + page = Html() + body = Body() + page.append(body) + body.append(P("a small paragraph of text")) + body.append(P("another small paragraph of text")) + body.append(P("and here is a bit more")) + + file_contents = render_result(page).strip() + + print(file_contents) + assert file_contents.startswith("") + assert file_contents.endswith("") + assert "a small paragraph of text" in file_contents + assert "" in file_contents + # you could do more here, but it should all be covered above. + # assert False + + +def test_indent(): + """ + Tests that the indentation gets passed through to the renderer + """ + html = Html("some content") + file_contents = render_result(html, ind=" ") + + print(file_contents) + lines = file_contents.split("\n") + assert lines[0].startswith(" <") + assert lines[-1].startswith(" <") + + +def test_indent_contents(): + """ + The contents in a element should be indented more than the tag + by the amount in the indent class attribute + """ + html = Html("some content") + file_contents = render_result(html, ind="") + + print(file_contents) + lines = file_contents.split("\n") + assert lines[1].startswith(Element.indent) + + +def test_multiple_indent(): + """ + make sure multiple levels get indented fully + """ + body = Body() + body.append(P("some text")) + html = Html(body) + + file_contents = render_result(html) + + print(file_contents) + lines = file_contents.split("\n") + for i in range(3): + assert lines[i].startswith(i * Element.indent + "<") + + assert lines[3].startswith(3 * Element.indent + "some") + + +def test_title(): + """ + This will implicitly test the OneLineTag element + """ + t = Title("Isn't this a nice title?") + + # making sure indentation still works + file_contents = render_result(t, ind=" ") + + print(file_contents) + # no "strip()" -- making sure there are no extra newlines + assert "\n" not in file_contents + assert "> " not in file_contents + assert file_contents.startswith(" ") + assert file_contents.endswith("") + # the only newline should be at the end + assert "\n" not in file_contents + + +def test_head(): + """ + testing Head with a title in it -- it should never be blank + """ + h = Head() + h.append(Title("A nifty title for the page")) + + +def test_full_page_with_title(): + """ + not much to actually test here, but good to see it put together. + + everything should have already been tested. + """ + page = Html() + + head = Head() + head.append(Title("PythonClass Example")) + + page.append(head) + + body = Body() + + body.append(P("Here is a paragraph of text -- there could be more of them, " + "but this is enough to show that we can do some text")) + body.append(P("And here is another piece of text -- you should be able to add any number")) + + page.append(body) + + file_contents = render_result(page) + + print(file_contents) + + # uncomment this to see results + # assert False + + +def test_attributes(): + """ + tests that you can pass attributes in to the tag + """ + e = Element("some text", id="this", color="red") # could be any attributes + file_contents = render_result(e) + print(file_contents) + assert 'id="this"' in file_contents + assert 'color="red"' in file_contents + # note -- dicts aren't ordered, so you can't enforce order! + # assert '' in file_contents + + +def test_attributes_one_line_tag(): + """ + tests that you can pass attributes in to the tag + """ + e = Title("some text", id="this", color="red") # could be any attributes + file_contents = render_result(e) + print(file_contents) + assert 'id="this"' in file_contents + assert 'color="red"' in file_contents + + +def test_br(): + br = Br("") + file_contents = render_result(br) + print(file_contents) + assert file_contents == "
" + + +def test_br_in_p(): + p = P("here is a small paragraph of text") + p.append(Br()) + p.append("And here is some more text after a line break") + + file_contents = render_result(p).split('\n') + print(file_contents) + assert file_contents[2].strip() == "
" + +def test_hr(): + hr = Hr(width=400) + file_contents = render_result(hr) + print(file_contents) + assert file_contents == '
' diff --git a/Solutions/Session07/step_6/.cache/v/cache/lastfailed b/Solutions/Session07/step_6/.cache/v/cache/lastfailed new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/Solutions/Session07/step_6/.cache/v/cache/lastfailed @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/Solutions/Session07/step_6/html_render.py b/Solutions/Session07/step_6/html_render.py new file mode 100644 index 00000000..125551e7 --- /dev/null +++ b/Solutions/Session07/step_6/html_render.py @@ -0,0 +1,140 @@ +#!/usr/bin/env python3 + +""" +Chris's solution through step 6 +""" + + +class TextWrapper: + """ + A simple wrapper that creates a class with a render method + for just text + + This allows the Element classes to render either Element objects or + plain text + + """ + def __init__(self, text): + self.text = text + + def render(self, file_out, current_ind=""): + file_out.write(current_ind + self.text) + + +class Element: + + tag = "html" + indent = " " + + def __init__(self, content=None, **kwargs): + self.attributes = kwargs + self.content = [] + if content: + # call the classes append method + # so that it can do anything special it needs to do + self.append(content) + + def append(self, content): + """ + add a new piece of content or another element to this element + """ + # note: this changed the internal representation of content + # it no longer holds strings -- so a test will fail + # but that test was testing internal API -- + # it's probably better remove it + if hasattr(content, 'render'): + self.content.append(content) + else: + self.content.append(TextWrapper(str(content))) + + def make_tags(self): + """ + create the tags + -- in a separate method so different subclass's render methods can use it + """ + attrs = " ".join(['{}="{}"'.format(key, val) for key, val in self.attributes.items()]) + if attrs.strip(): + open_tag = "<{} {}>".format(self.tag, attrs.strip()) + else: + open_tag = "<{}>".format(self.tag) + close_tag = "".format(self.tag) + + return open_tag, close_tag + + def render(self, out_file, ind=""): + open_tag, close_tag = self.make_tags() + out_file.write(ind + open_tag + "\n") + for stuff in self.content: + stuff.render(out_file, ind + self.indent) + out_file.write("\n") + out_file.write(ind + close_tag) + + +class OneLineTag(Element): + # note: by re-writting the render + def render(self, out_file, ind=""): + # there is some repition here -- maybe factor that out? + open_tag, close_tag = self.make_tags() + out_file.write(ind + open_tag) + for stuff in self.content: + stuff.render(out_file) + out_file.write(close_tag) + + +class Html(Element): + tag = 'html' + + +class Body(Element): + tag = "body" + + +class P(Element): + tag = "p" + + +class Head(Element): + tag = "head" + + +class Title(OneLineTag): + tag = "title" + + +class SelfClosingTag(Element): + """ + base class for tags that have no content + """ + def render(self, out_file, ind=""): + # there is some repition here -- maybe factor that out? + open_tag, _ = self.make_tags() + # make it a self cloding tag by adding the / + out_file.write(ind + open_tag.replace(">", " />")) + + +class Hr(SelfClosingTag): + """ + Horizontal Rule + """ + tag = "hr" + + +class Br(SelfClosingTag): + """ + Line break + """ + tag = "br" + + +class A(OneLineTag): + """ + anchor element + """ + tag = "a" + + def __init__(self, link, content, **kwargs): + kwargs['href'] = link + super().__init__(content, **kwargs) + # this could also be direct: + # Element.__init__(self, content, **kwargs) + diff --git a/Solutions/Session07/step_6/test_html_render.py b/Solutions/Session07/step_6/test_html_render.py new file mode 100644 index 00000000..b2aee897 --- /dev/null +++ b/Solutions/Session07/step_6/test_html_render.py @@ -0,0 +1,329 @@ +""" +test code for html_render.py + +includes up to step 6 +""" +import io + +from html_render import (Element, + Html, + Body, + P, + TextWrapper, + Head, + Title, + Hr, + Br, + A, + ) + +# utility function for testing render methods +# needs to be used in multiple tests, so write it once here. + + +def render_result(element, ind=""): + """ + calls element's render method, and returns what got rendered as a string + """ + outfile = io.StringIO() + element.render(outfile, ind) + return outfile.getvalue() + + +def test_init(): + """ + this only tests that it can be initialized -- but it's a start + """ + e = Element() + + e = Element("this is some text") + + +# These two tests were testing internals +# so they failed when I added the TextWrapper +# but I"m removing them because tests really should be testing +# the external API. +# def test_content(): +# # fixme: this tests internals!!!! +# e = Element("this is some text") + +# assert "this is some text" in e.content + +# def test_append(): +# e = Element("this is some text") + +# e.append("some more text") + +# assert "some more text" in e.content + + +def test_two_instances(): + e = Element("this is some text") + e2 = Element("this is some text") + + e.append("some more text") + + assert "some more text" not in e2.content + + +def test_render(): + e = Element("this is some text") + e.append("and this is some more text") + + file_contents = render_result(e) + + assert("this is some text") in file_contents + assert("and this is some more text") in file_contents + + assert file_contents.startswith("") + assert file_contents.strip().endswith("") + + +def test_html(): + e = Html("this is some text") + e.append("and this is some more text") + + file_contents = render_result(e) + + assert("this is some text") in file_contents + assert("and this is some more text") in file_contents + + assert file_contents.startswith("") + assert file_contents.strip().endswith("") + + +def test_body(): + e = Body("this is some text") + e.append("and this is some more text") + + file_contents = render_result(e) + + assert("this is some text") in file_contents + assert("and this is some more text") in file_contents + + assert file_contents.startswith("") + assert file_contents.strip().endswith("") + + +def test_p(): + e = P("this is some text") + e.append("and this is some more text") + + file_contents = render_result(e) + + assert("this is some text") in file_contents + assert("and this is some more text") in file_contents + + assert file_contents.startswith("

") + assert file_contents.strip().endswith("

") + + +def test_text_wrapper(): + tw = TextWrapper("A basic piece of text") + + file_contents = render_result(tw) + assert file_contents == "A basic piece of text" + + +def test_non_str(): + """ you should be able to pass anything in, and it will get + "stringified" + """ + e = P(34) # a number + e.append((3, 4, 5)) # even a tuple + + file_contents = render_result(e) + + print(file_contents) + assert("34") in file_contents + assert("(3, 4, 5)") in file_contents + + +def test_sub_element(): + """ + tests that you can add another element and still render properly + """ + page = Html() + page.append("some plain text.") + page.append(P("A simple paragraph of text")) + page.append("Some more plain text.") + + file_contents = render_result(page) + + # note: the above tests should make sure that the tags are getting rendered. + assert "some plain text" in file_contents + assert "A simple paragraph of text" in file_contents + assert "Some more plain text." in file_contents + assert "some plain text" in file_contents + + +def test_step_2_noindent(): + """ + This is more if an integration test -- a number of things together + + this test does not yet include indentation + """ + page = Html() + body = Body() + page.append(body) + body.append(P("a small paragraph of text")) + body.append(P("another small paragraph of text")) + body.append(P("and here is a bit more")) + + file_contents = render_result(page).strip() + + print(file_contents) + assert file_contents.startswith("") + assert file_contents.endswith("") + assert "a small paragraph of text" in file_contents + assert "" in file_contents + # you could do more here, but it should all be covered above. + # assert False + + +def test_indent(): + """ + Tests that the indentation gets passed through to the renderer + """ + html = Html("some content") + file_contents = render_result(html, ind=" ") + + print(file_contents) + lines = file_contents.split("\n") + assert lines[0].startswith(" <") + assert lines[-1].startswith(" <") + + +def test_indent_contents(): + """ + The contents in a element should be indented more than the tag + by the amount in the indent class attribute + """ + html = Html("some content") + file_contents = render_result(html, ind="") + + print(file_contents) + lines = file_contents.split("\n") + assert lines[1].startswith(Element.indent) + + +def test_multiple_indent(): + """ + make sure multiple levels get indented fully + """ + body = Body() + body.append(P("some text")) + html = Html(body) + + file_contents = render_result(html) + + print(file_contents) + lines = file_contents.split("\n") + for i in range(3): + assert lines[i].startswith(i * Element.indent + "<") + + assert lines[3].startswith(3 * Element.indent + "some") + + +def test_title(): + """ + This will implicitly test the OneLineTag element + """ + t = Title("Isn't this a nice title?") + + # making sure indentation still works + file_contents = render_result(t, ind=" ") + + print(file_contents) + # no "strip()" -- making sure there are no extra newlines + assert "\n" not in file_contents + assert "> " not in file_contents + assert file_contents.startswith(" ") + assert file_contents.endswith("") + # the only newline should be at the end + assert "\n" not in file_contents + + +def test_head(): + """ + testing Head with a title in it -- it should never be blank + """ + h = Head() + h.append(Title("A nifty title for the page")) + + +def test_full_page_with_title(): + """ + not much to actually test here, but good to see it put together. + + everything should have already been tested. + """ + page = Html() + + head = Head() + head.append(Title("PythonClass Example")) + + page.append(head) + + body = Body() + + body.append(P("Here is a paragraph of text -- there could be more of them, " + "but this is enough to show that we can do some text")) + body.append(P("And here is another piece of text -- you should be able to add any number")) + + page.append(body) + + file_contents = render_result(page) + + print(file_contents) + + # uncomment this to see results + # assert False + + +def test_attributes(): + """ + tests that you can pass attributes in to the tag + """ + e = Element("some text", id="this", color="red") # could be any attributes + file_contents = render_result(e) + print(file_contents) + assert 'id="this"' in file_contents + assert 'color="red"' in file_contents + # note -- dicts aren't ordered, so you can't enforce order! + # assert '' in file_contents + + +def test_attributes_one_line_tag(): + """ + tests that you can pass attributes in to the tag + """ + e = Title("some text", id="this", color="red") # could be any attributes + file_contents = render_result(e) + print(file_contents) + assert 'id="this"' in file_contents + assert 'color="red"' in file_contents + + +def test_br(): + br = Br("") + file_contents = render_result(br) + print(file_contents) + assert file_contents == "
" + + +def test_hr(): + hr = Hr(width=400) + file_contents = render_result(hr) + print(file_contents) + assert file_contents == '
' + + +def test_anchor(): + a = A("http://google.com", "link to google") + file_contents = render_result(a) + print(file_contents) + assert file_contents.startswith('') + assert 'href="http://google.com"' in file_contents + assert 'link to google' in file_contents diff --git a/Solutions/Session07/step_7/.cache/v/cache/lastfailed b/Solutions/Session07/step_7/.cache/v/cache/lastfailed new file mode 100644 index 00000000..34db0489 --- /dev/null +++ b/Solutions/Session07/step_7/.cache/v/cache/lastfailed @@ -0,0 +1,3 @@ +{ + "test_html_render.py::test_header": true +} \ No newline at end of file diff --git a/Solutions/Session07/step_7/html_render.py b/Solutions/Session07/step_7/html_render.py new file mode 100644 index 00000000..5efbeffd --- /dev/null +++ b/Solutions/Session07/step_7/html_render.py @@ -0,0 +1,163 @@ +#!/usr/bin/env python3 + +""" +Chris's solution through step 7 +""" + + +class TextWrapper: + """ + A simple wrapper that creates a class with a render method + for just text + + This allows the Element classes to render either Element objects or + plain text + + """ + def __init__(self, text): + self.text = text + + def render(self, file_out, current_ind=""): + file_out.write(current_ind + self.text) + + +class Element: + + tag = "html" + indent = " " + + def __init__(self, content=None, **kwargs): + self.attributes = kwargs + self.content = [] + if content: + # call the classes append method + # so that it can do anything special it needs to do + self.append(content) + + def append(self, content): + """ + add a new piece of content or another element to this element + """ + # note: this changed the internal representation of content + # it no longer holds strings -- so a test will fail + # but that test was testing internal API -- + # it's probably better remove it + if hasattr(content, 'render'): + self.content.append(content) + else: + self.content.append(TextWrapper(str(content))) + + def make_tags(self): + """ + create the tags + -- in a separate method so different subclass's render methods can use it + """ + attrs = " ".join(['{}="{}"'.format(key, val) for key, val in self.attributes.items()]) + if attrs.strip(): + open_tag = "<{} {}>".format(self.tag, attrs.strip()) + else: + open_tag = "<{}>".format(self.tag) + close_tag = "".format(self.tag) + + return open_tag, close_tag + + def render(self, out_file, ind=""): + open_tag, close_tag = self.make_tags() + out_file.write(ind + open_tag + "\n") + for stuff in self.content: + stuff.render(out_file, ind + self.indent) + out_file.write("\n") + out_file.write(ind + close_tag) + + +class OneLineTag(Element): + def render(self, out_file, ind=""): + # there is some repition here -- maybe factor that out? + open_tag, close_tag = self.make_tags() + out_file.write(ind + open_tag) + for stuff in self.content: + stuff.render(out_file) + out_file.write(close_tag) + + +class Html(Element): + tag = 'html' + + +class Body(Element): + tag = "body" + + +class P(Element): + tag = "p" + + +class Head(Element): + tag = "head" + + +class Title(OneLineTag): + tag = "title" + + +class SelfClosingTag(Element): + """ + base class for tags that have no content + """ + def render(self, out_file, ind=""): + # there is some repition here -- maybe factor that out? + open_tag, _ = self.make_tags() + # make it a self cloding tag by adding the / + out_file.write(ind + open_tag.replace(">", " />")) + + +class Hr(SelfClosingTag): + """ + Horizontal Rule + """ + tag = "hr" + + +class Br(SelfClosingTag): + """ + Line break + """ + tag = "br" + + +class A(OneLineTag): + """ + anchor element + """ + tag = "a" + + def __init__(self, link, content, **kwargs): + kwargs['href'] = link + super().__init__(content, **kwargs) + # this could also be direct: + # Element.__init__(self, content, **kwargs) + + +class Ul(Element): + """ + unordered list + """ + tag = "ul" + + +class Li(Element): + """ + list element + """ + tag = "li" + + +class H(OneLineTag): + """ + section head + """ + tag = "H" + + def __init__(self, level, content=None, **kwargs): + self.tag = "h" + str(int(level)) + super().__init__(content, **kwargs) diff --git a/Solutions/Session07/step_7/test_html_render.py b/Solutions/Session07/step_7/test_html_render.py new file mode 100644 index 00000000..ee99caa0 --- /dev/null +++ b/Solutions/Session07/step_7/test_html_render.py @@ -0,0 +1,366 @@ +""" +test code for html_render.py + +includes step 7 +""" +import io + +from html_render import (Element, + Html, + Body, + P, + TextWrapper, + Head, + Title, + Hr, + Br, + A, + Ul, + Li, + H, + ) + +# utility function for testing render methods +# needs to be used in multiple tests, so write it once here. + + +def render_result(element, ind=""): + """ + calls element's render method, and returns what got rendered as a string + """ + outfile = io.StringIO() + element.render(outfile, ind) + return outfile.getvalue() + + +def test_init(): + """ + this only tests that it can be initialized -- but it's a start + """ + e = Element() + + e = Element("this is some text") + + +# These two tests were testing internals +# so they failed when I added the TextWrapper +# but I"m removing them because tests really should be testing +# the external API. +# def test_content(): +# # fixme: this tests internals!!!! +# e = Element("this is some text") + +# assert "this is some text" in e.content + +# def test_append(): +# e = Element("this is some text") + +# e.append("some more text") + +# assert "some more text" in e.content + + +def test_two_instances(): + e = Element("this is some text") + e2 = Element("this is some text") + + e.append("some more text") + + assert "some more text" not in e2.content + + +def test_render(): + e = Element("this is some text") + e.append("and this is some more text") + + file_contents = render_result(e) + + assert("this is some text") in file_contents + assert("and this is some more text") in file_contents + + assert file_contents.startswith("") + assert file_contents.strip().endswith("") + + +def test_html(): + e = Html("this is some text") + e.append("and this is some more text") + + file_contents = render_result(e) + + assert("this is some text") in file_contents + assert("and this is some more text") in file_contents + + assert file_contents.startswith("") + assert file_contents.strip().endswith("") + + +def test_body(): + e = Body("this is some text") + e.append("and this is some more text") + + file_contents = render_result(e) + + assert("this is some text") in file_contents + assert("and this is some more text") in file_contents + + assert file_contents.startswith("") + assert file_contents.strip().endswith("") + + +def test_p(): + e = P("this is some text") + e.append("and this is some more text") + + file_contents = render_result(e) + + assert("this is some text") in file_contents + assert("and this is some more text") in file_contents + + assert file_contents.startswith("

") + assert file_contents.strip().endswith("

") + + +def test_text_wrapper(): + tw = TextWrapper("A basic piece of text") + + file_contents = render_result(tw) + assert file_contents == "A basic piece of text" + + +def test_non_str(): + """ you should be able to pass anything in, and it will get + "stringified" + """ + e = P(34) # a number + e.append((3, 4, 5)) # even a tuple + + file_contents = render_result(e) + + print(file_contents) + assert("34") in file_contents + assert("(3, 4, 5)") in file_contents + + +def test_sub_element(): + """ + tests that you can add another element and still render properly + """ + page = Html() + page.append("some plain text.") + page.append(P("A simple paragraph of text")) + page.append("Some more plain text.") + + file_contents = render_result(page) + + # note: the above tests should make sure that the tags are getting rendered. + assert "some plain text" in file_contents + assert "A simple paragraph of text" in file_contents + assert "Some more plain text." in file_contents + assert "some plain text" in file_contents + + +def test_step_2_noindent(): + """ + This is more if an integration test -- a number of things together + + this test does not yet include indentation + """ + page = Html() + body = Body() + page.append(body) + body.append(P("a small paragraph of text")) + body.append(P("another small paragraph of text")) + body.append(P("and here is a bit more")) + + file_contents = render_result(page).strip() + + print(file_contents) + assert file_contents.startswith("") + assert file_contents.endswith("") + assert "a small paragraph of text" in file_contents + assert "" in file_contents + # you could do more here, but it should all be covered above. + # assert False + + +def test_indent(): + """ + Tests that the indentation gets passed through to the renderer + """ + html = Html("some content") + file_contents = render_result(html, ind=" ") + + print(file_contents) + lines = file_contents.split("\n") + assert lines[0].startswith(" <") + assert lines[-1].startswith(" <") + + +def test_indent_contents(): + """ + The contents in a element should be indented more than the tag + by the amount in the indent class attribute + """ + html = Html("some content") + file_contents = render_result(html, ind="") + + print(file_contents) + lines = file_contents.split("\n") + assert lines[1].startswith(Element.indent) + + +def test_multiple_indent(): + """ + make sure multiple levels get indented fully + """ + body = Body() + body.append(P("some text")) + html = Html(body) + + file_contents = render_result(html) + + print(file_contents) + lines = file_contents.split("\n") + for i in range(3): + assert lines[i].startswith(i * Element.indent + "<") + + assert lines[3].startswith(3 * Element.indent + "some") + + +def test_title(): + """ + This will implicitly test the OneLineTag element + """ + t = Title("Isn't this a nice title?") + + # making sure indentation still works + file_contents = render_result(t, ind=" ") + + print(file_contents) + # no "strip()" -- making sure there are no extra newlines + assert "\n" not in file_contents + assert "> " not in file_contents + assert file_contents.startswith(" ") + assert file_contents.endswith("") + # the only newline should be at the end + assert "\n" not in file_contents + + +def test_head(): + """ + testing Head with a title in it -- it should never be blank + """ + h = Head() + h.append(Title("A nifty title for the page")) + + +def test_full_page_with_title(): + """ + not much to actually test here, but good to see it put together. + + everything should have already been tested. + """ + page = Html() + + head = Head() + head.append(Title("PythonClass Example")) + + page.append(head) + + body = Body() + + body.append(P("Here is a paragraph of text -- there could be more of them, " + "but this is enough to show that we can do some text")) + body.append(P("And here is another piece of text -- you should be able to add any number")) + + page.append(body) + + file_contents = render_result(page) + + print(file_contents) + + # uncomment this to see results + # assert False + + +def test_attributes(): + """ + tests that you can pass attributes in to the tag + """ + e = Element("some text", id="this", color="red") # could be any attributes + file_contents = render_result(e) + print(file_contents) + assert 'id="this"' in file_contents + assert 'color="red"' in file_contents + # note -- dicts aren't ordered, so you can't enforce order! + # assert '' in file_contents + + +def test_attributes_one_line_tag(): + """ + tests that you can pass attributes in to the tag + """ + e = Title("some text", id="this", color="red") # could be any attributes + file_contents = render_result(e) + print(file_contents) + assert 'id="this"' in file_contents + assert 'color="red"' in file_contents + + +def test_br(): + br = Br("") + file_contents = render_result(br) + print(file_contents) + assert file_contents == "
" + + +def test_hr(): + hr = Hr(width=400) + file_contents = render_result(hr) + print(file_contents) + assert file_contents == '
' + + +def test_anchor(): + a = A("http://google.com", "link to google") + file_contents = render_result(a) + print(file_contents) + assert file_contents.startswith('
') + assert 'href="http://google.com"' in file_contents + assert 'link to google' in file_contents + + +def test_ul(): + ul = Ul() + ul.append(Li("item one in a list")) + ul.append(Li("item two in a list")) + file_contents = render_result(ul) + print(file_contents) + assert file_contents.startswith('') + assert "item one in a list" in file_contents + assert "item two in a list" in file_contents + assert file_contents.count("
  • ") == 2 + assert file_contents.count("
  • ") == 2 + + +def test_header(): + h = H(3, "A nice header line") + file_contents = render_result(h) + print(file_contents) + assert file_contents.startswith('

    ') + assert file_contents.endswith('

    ') + assert "A nice header line" in file_contents + + +def test_header(): + h = H(3, "A nice header line", align="center") + file_contents = render_result(h) + print(file_contents) + assert file_contents.startswith('') + assert "A nice header line" in file_contents + assert ' align="center"' in file_contents + diff --git a/Solutions/Session07/step_8/.cache/v/cache/lastfailed b/Solutions/Session07/step_8/.cache/v/cache/lastfailed new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/Solutions/Session07/step_8/.cache/v/cache/lastfailed @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/Solutions/Session07/step_8/html_render.py b/Solutions/Session07/step_8/html_render.py new file mode 100644 index 00000000..63303df9 --- /dev/null +++ b/Solutions/Session07/step_8/html_render.py @@ -0,0 +1,180 @@ +#!/usr/bin/env python3 + +""" +Chris's solution through step 8 +""" + + +class TextWrapper: + """ + A simple wrapper that creates a class with a render method + for just text + + This allows the Element classes to render either Element objects or + plain text + + """ + def __init__(self, text): + self.text = text + + def render(self, file_out, current_ind=""): + file_out.write(current_ind + self.text) + + +class Element: + + tag = "html" + indent = " " + + def __init__(self, content=None, **kwargs): + self.attributes = kwargs + self.content = [] + if content: + # call the classes append method + # so that it can do anything special it needs to do + self.append(content) + + def append(self, content): + """ + add a new piece of content or another element to this element + """ + # note: this changed the internal representation of content + # it no longer holds strings -- so a test will fail + # but that test was testing internal API -- + # it's probably better remove it + # if isinstance(content, Element): + if hasattr(content, 'render'): + self.content.append(content) + else: + self.content.append(TextWrapper(str(content))) + # self.content.append(content) + + + def make_tags(self): + """ + create the tags + -- in a separate method so different subclass's render methods can use it + """ + attrs = " ".join(['{}="{}"'.format(key, val) for key, val in self.attributes.items()]) + if attrs.strip(): + open_tag = "<{} {}>".format(self.tag, attrs.strip()) + else: + open_tag = "<{}>".format(self.tag) + close_tag = "".format(self.tag) + + return open_tag, close_tag + + def render(self, out_file, cur_ind=""): + print("in render, type of self", type(self)) + open_tag, close_tag = self.make_tags() + out_file.write(cur_ind + open_tag + "\n") + for stuff in self.content: + stuff.render(out_file, cur_ind + self.indent) + out_file.write("\n") + out_file.write(cur_ind + close_tag) + + +class OneLineTag(Element): + def render(self, out_file, cur_ind=""): + # there is some repition here -- maybe factor that out? + open_tag, close_tag = self.make_tags() + out_file.write(cur_ind + open_tag) + for stuff in self.content: + try: + stuff.render(out_file) + except AttributeError: + out_file.write(stuff) + out_file.write(close_tag) + + +class Html(Element): + tag = 'html' + + def render(self, file_out, cur_ind=""): + file_out.write(cur_ind + "\n") + super().render(file_out, cur_ind=cur_ind) + + +class Body(Element): + tag = "body" + + +class P(Element): + tag = "p" + +class Head(Element): + tag = "head" + + +class Title(OneLineTag): + tag = "title" + + +class SelfClosingTag(Element): + """ + base class for tags that have no content + """ + def render(self, out_file, ind=""): + # there is some repition here -- maybe factor that out? + open_tag, _ = self.make_tags() + # make it a self cloding tag by adding the / + out_file.write(ind + open_tag.replace(">", " />")) + + +class Hr(SelfClosingTag): + """ + Horizontal Rule + """ + tag = "hr" + + +class Br(SelfClosingTag): + """ + Line break + """ + tag = "br" + + +class A(OneLineTag): + """ + anchor element + """ + tag = "a" + + def __init__(self, link, content, **kwargs): + kwargs['href'] = link + super().__init__(content, **kwargs) + # this could also be direct: + # Element.__init__(self, content, **kwargs) + + +class Ul(Element): + """ + unordered list + """ + tag = "ul" + + +class Li(Element): + """ + list element + """ + tag = "li" + + +class H(OneLineTag): + """ + section head + """ + tag = "H" + + def __init__(self, level, content=None, **kwargs): + self.tag = "h" + str(int(level)) + super().__init__(content, **kwargs) + + +class Meta(SelfClosingTag): + """ + metadata tag + """ + tag = "meta" diff --git a/slides_sources/source/exercises/sample_html.html b/Solutions/Session07/step_8/sample_output.html similarity index 79% rename from slides_sources/source/exercises/sample_html.html rename to Solutions/Session07/step_8/sample_output.html index f2687e95..def5d37f 100644 --- a/slides_sources/source/exercises/sample_html.html +++ b/Solutions/Session07/step_8/sample_output.html @@ -2,12 +2,12 @@ - PythonClass = Revision 1087: + Python Class Sample page -

    PythonClass - Class 6 example

    +

    Python Class - Html rendering example

    - Here is a paragraph of text -- there could be more of them, but this is enough to show that we can do some text + Here is a paragraph of text -- there could be more of them, but this is enough to show that we can do some text