├── 9781484200292.jpg ├── Chapter02 ├── listing2-1.py ├── listing2-2.py ├── listing2-3.py └── listing2-4.py ├── Chapter03 └── listing3-1.py ├── Chapter04 ├── listing4-1.py └── listing4-2.py ├── Chapter10 ├── listing10-1.py ├── listing10-10.py ├── listing10-11.py ├── listing10-12.txt ├── listing10-13.txt ├── listing10-14.txt ├── listing10-2.py ├── listing10-3.py ├── listing10-4.py ├── listing10-5.py ├── listing10-6.py └── listing10-8.py ├── Chapter11 ├── listing11-1.py ├── listing11-10.py ├── listing11-11.py ├── listing11-12.py ├── listing11-13.py ├── listing11-2.txt ├── listing11-3.txt ├── listing11-4.txt ├── listing11-5.txt ├── listing11-6.py ├── listing11-7.py ├── listing11-8.py └── listing11-9.py ├── Chapter12 └── listing12-1.py ├── Chapter13 ├── listing13-1.py └── listing13-2.py ├── Chapter14 ├── listing14-1.py ├── listing14-2.py ├── listing14-3.py ├── listing14-4.py ├── listing14-5.py ├── listing14-6.py ├── listing14-7.py ├── listing14-8.py └── listing14-9.py ├── Chapter15 ├── listing15-1.py ├── listing15-2.py ├── listing15-3.py ├── listing15-4.py ├── listing15-5.py ├── listing15-6.py └── listing15-7.py ├── Chapter16 ├── listing16-1.py ├── listing16-2.py └── listing16-3.py ├── Chapter17 ├── listing17-1.java ├── listing17-2.cs ├── listing17-3.c ├── listing17-4.py ├── listing17-5.i └── listing17-6.c ├── Chapter18 └── listing18-1.py ├── Chapter19 ├── listing19-1.cfg ├── listing19-2.py └── listing19-3.py ├── Chapter20 ├── listing20-1.txt ├── listing20-2.py ├── listing20-3.py ├── listing20-4.py ├── listing20-5.py └── listing20-6.py ├── Chapter21 ├── listing21-1.py ├── listing21-2.py └── listing21-3.py ├── Chapter22 ├── listing22-1.xml ├── listing22-2.py └── listing22-3.py ├── Chapter23 ├── listing23-1.py └── listing23-2.py ├── Chapter24 ├── listing24-1.py ├── listing24-2.py ├── listing24-3.py ├── listing24-4.py ├── listing24-5.py └── listing24-6.py ├── Chapter25 ├── listing25-1.py ├── listing25-2.py └── listing25-3.py ├── Chapter26 ├── listing26-1.sql ├── listing26-2.sql ├── listing26-3.sql ├── listing26-4.py ├── listing26-5.py ├── listing26-6.py ├── listing26-7.py └── listing26-8.py ├── Chapter27 ├── listing27-1.py ├── listing27-2.py └── listing27-3.py ├── Chapter28 ├── listing28-1.py └── listing28-2.py ├── Chapter29 ├── listing29-1.py ├── listing29-2.py ├── listing29-3.py └── listing29-4.py ├── LICENSE.txt ├── README.md ├── README.txt ├── contributing.md └── errata.md /9781484200292.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/beginning-python-3ed/bcff4b8f3ad2345eff8494cf955f656112e3c4cc/9781484200292.jpg -------------------------------------------------------------------------------- /Chapter02/listing2-1.py: -------------------------------------------------------------------------------- 1 | # Print out a date, given year, month, and day as numbers 2 | 3 | months = [ 4 | 'January', 5 | 'February', 6 | 'March', 7 | 'April', 8 | 'May', 9 | 'June', 10 | 'July', 11 | 'August', 12 | 'September', 13 | 'October', 14 | 'November', 15 | 'December' 16 | ] 17 | 18 | # A list with one ending for each number from 1 to 31 19 | endings = ['st', 'nd', 'rd'] + 17 * ['th'] \ 20 | + ['st', 'nd', 'rd'] + 7 * ['th'] \ 21 | + ['st'] 22 | 23 | year = input('Year: ') 24 | month = input('Month (1-12): ') 25 | day = input('Day (1-31): ') 26 | 27 | month_number = int(month) 28 | day_number = int(day) 29 | 30 | # Remember to subtract 1 from month and day to get a correct index 31 | month_name = months[month_number-1] 32 | ordinal = day + endings[day_number-1] 33 | 34 | print(month_name + ' ' + ordinal + ', ' + year) -------------------------------------------------------------------------------- /Chapter02/listing2-2.py: -------------------------------------------------------------------------------- 1 | # Split up a URL of the form http://www.something.com 2 | 3 | url = input('Please enter the URL:') 4 | domain = url[11:-4] 5 | 6 | print("Domain name: " + domain) -------------------------------------------------------------------------------- /Chapter02/listing2-3.py: -------------------------------------------------------------------------------- 1 | # Prints a sentence in a centered "box" of correct width 2 | 3 | sentence = input("Sentence: ") 4 | 5 | screen_width = 80 6 | text_width = len(sentence) 7 | box_width = text_width + 6 8 | left_margin = (screen_width - box_width) // 2 9 | 10 | print() 11 | print(' ' * left_margin + '+' + '-' * (box_width-2) + '+') 12 | print(' ' * left_margin + '| ' + ' ' * text_width + ' |') 13 | print(' ' * left_margin + '| ' + sentence + ' |') 14 | print(' ' * left_margin + '| ' + ' ' * text_width + ' |') 15 | print(' ' * left_margin + '+' + '-' * (box_width-2) + '+') 16 | print() -------------------------------------------------------------------------------- /Chapter02/listing2-4.py: -------------------------------------------------------------------------------- 1 | # Check a user name and PIN code 2 | 3 | database = [ 4 | ['albert', '1234'], 5 | ['dilbert', '4242'], 6 | ['smith', '7524'], 7 | ['jones', '9843'] 8 | ] 9 | 10 | username = input('User name: ') 11 | pin = input('PIN code: ') 12 | 13 | if [username, pin] in database: print('Access granted') -------------------------------------------------------------------------------- /Chapter03/listing3-1.py: -------------------------------------------------------------------------------- 1 | # Print a formatted price list with a given width 2 | 3 | width = int(input('Please enter width: ')) 4 | 5 | price_width = 10 6 | item_width = width - price_width 7 | 8 | header_fmt = '{{:{}}}{{:>{}}}'.format(item_width, price_width) 9 | fmt = '{{:{}}}{{:>{}.2f}}'.format(item_width, price_width) 10 | 11 | print('=' * width) 12 | 13 | print(header_fmt.format('Item', 'Price')) 14 | 15 | print('-' * width) 16 | 17 | print(fmt.format('Apples', 0.4)) 18 | print(fmt.format('Pears', 0.5)) 19 | print(fmt.format('Cantaloupes', 1.92)) 20 | print(fmt.format('Dried Apricots (16 oz.)', 8)) 21 | print(fmt.format('Prunes (4 lbs.)', 12)) 22 | 23 | print('=' * width) -------------------------------------------------------------------------------- /Chapter04/listing4-1.py: -------------------------------------------------------------------------------- 1 | # A simple database 2 | 3 | # A dictionary with person names as keys. Each person is represented as 4 | # another dictionary with the keys 'phone' and 'addr' referring to their phone 5 | # number and address, respectively. 6 | people = { 7 | 8 | 'Alice': { 9 | 'phone': '2341', 10 | 'addr': 'Foo drive 23' 11 | }, 12 | 13 | 'Beth': { 14 | 'phone': '9102', 15 | 'addr': 'Bar street 42' 16 | }, 17 | 18 | 'Cecil': { 19 | 'phone': '3158', 20 | 'addr': 'Baz avenue 90' 21 | } 22 | 23 | } 24 | 25 | # Descriptive labels for the phone number and address. These will be used 26 | # when printing the output. 27 | labels = { 28 | 'phone': 'phone number', 29 | 'addr': 'address' 30 | } 31 | 32 | name = input('Name: ') 33 | 34 | # Are we looking for a phone number or an address? 35 | request = input('Phone number (p) or address (a)? ') 36 | 37 | # Use the correct key: 38 | if request == 'p': key = 'phone' 39 | if request == 'a': key = 'addr' 40 | 41 | # Only try to print information if the name is a valid key in 42 | # our dictionary: 43 | if name in people: print("{}'s {} is {}.".format(name, labels[key], people[name][key])) -------------------------------------------------------------------------------- /Chapter04/listing4-2.py: -------------------------------------------------------------------------------- 1 | # A simple database using get() 2 | 3 | # Insert database (people) from Listing 4-1 here. 4 | 5 | labels = { 6 | 'phone': 'phone number', 7 | 'addr': 'address' 8 | } 9 | 10 | name = input('Name: ') 11 | 12 | # Are we looking for a phone number or an address? 13 | request = input('Phone number (p) or address (a)? ') 14 | 15 | # Use the correct key: 16 | key = request # In case the request is neither 'p' nor 'a' 17 | if request == 'p': key = 'phone' 18 | if request == 'a': key = 'addr' 19 | 20 | # Use get to provide default values: 21 | person = people.get(name, {}) 22 | label = labels.get(key, key) 23 | result = person.get(key, 'not available') 24 | 25 | print("{}'s {} is {}.".format(name, label, result)) -------------------------------------------------------------------------------- /Chapter10/listing10-1.py: -------------------------------------------------------------------------------- 1 | # hello.py 2 | print("Hello, world!") -------------------------------------------------------------------------------- /Chapter10/listing10-10.py: -------------------------------------------------------------------------------- 1 | # find_sender.py 2 | import fileinput, re 3 | pat = re.compile('From: (.*) <.*?>$') 4 | for line in fileinput.input(): 5 | m = pat.match(line) 6 | if m: print(m.group(1)) -------------------------------------------------------------------------------- /Chapter10/listing10-11.py: -------------------------------------------------------------------------------- 1 | # templates.py 2 | 3 | import fileinput, re 4 | 5 | # Matches fields enclosed in square brackets: 6 | field_pat = re.compile(r'\[(.+?)\]') 7 | 8 | # We'll collect variables in this: 9 | scope = {} 10 | 11 | # This is used in re.sub: 12 | def replacement(match): 13 | code = match.group(1) 14 | try: 15 | # If the field can be evaluated, return it: 16 | return str(eval(code, scope)) 17 | except SyntaxError: 18 | # Otherwise, execute the assignment in the same scope ... 19 | exec code in scope 20 | # ... and return an empty string: 21 | return '' 22 | 23 | # Get all the text as a single string: 24 | 25 | # (There are other ways of doing this; see Chapter 11) 26 | lines = [] 27 | for line in fileinput.input(): 28 | lines.append(line) 29 | text = ''.join(lines) 30 | 31 | # Substitute all the occurrences of the field pattern: 32 | print(field_pat.sub(replacement, text)) -------------------------------------------------------------------------------- /Chapter10/listing10-12.txt: -------------------------------------------------------------------------------- 1 | [x = 2] 2 | [y = 3] 3 | The sum of [x] and [y] is [x + y]. -------------------------------------------------------------------------------- /Chapter10/listing10-13.txt: -------------------------------------------------------------------------------- 1 | [name = 'Magnus Lie Hetland' ] 2 | [email = 'magnus@foo.bar' ] 3 | [language = 'python' ] -------------------------------------------------------------------------------- /Chapter10/listing10-14.txt: -------------------------------------------------------------------------------- 1 | [import time] 2 | Dear [name], 3 | 4 | I would like to learn how to program. I hear you use 5 | the [language] language a lot -- is it something I 6 | should consider? 7 | 8 | And, by the way, is [email] your correct email address? 9 | 10 | 11 | Fooville, [time.asctime()] 12 | 13 | Oscar Frozzbozz -------------------------------------------------------------------------------- /Chapter10/listing10-2.py: -------------------------------------------------------------------------------- 1 | # hello2.py 2 | def hello(): 3 | print("Hello, world!") -------------------------------------------------------------------------------- /Chapter10/listing10-3.py: -------------------------------------------------------------------------------- 1 | # hello3.py 2 | 3 | def hello(): 4 | print("Hello, world!") 5 | 6 | # A test: 7 | hello() -------------------------------------------------------------------------------- /Chapter10/listing10-4.py: -------------------------------------------------------------------------------- 1 | # hello4.py 2 | def hello(): 3 | print("Hello, world!") 4 | 5 | def test(): 6 | hello() 7 | 8 | if __name__ == '__main__': test() -------------------------------------------------------------------------------- /Chapter10/listing10-5.py: -------------------------------------------------------------------------------- 1 | # reverseargs.py 2 | import sys 3 | args = sys.argv[1:] 4 | args.reverse() 5 | print(' '.join(args)) -------------------------------------------------------------------------------- /Chapter10/listing10-6.py: -------------------------------------------------------------------------------- 1 | # numberlines.py 2 | 3 | import fileinput 4 | 5 | for line in fileinput.input(inplace=True): 6 | line = line.rstrip() 7 | num = fileinput.lineno() 8 | print('{:<50} # {:2d}'.format(line, num)) -------------------------------------------------------------------------------- /Chapter10/listing10-8.py: -------------------------------------------------------------------------------- 1 | # database.py 2 | import sys, shelve 3 | 4 | def store_person(db): 5 | """ 6 | Query user for data and store it in the shelf object 7 | """ 8 | pid = input('Enter unique ID number: ') 9 | person = {} 10 | person['name'] = input('Enter name: ') 11 | person['age'] = input('Enter age: ') 12 | person['phone'] = input('Enter phone number: ') 13 | db[pid] = person 14 | 15 | def lookup_person(db): 16 | """ 17 | Query user for ID and desired field, and fetch the corresponding data from 18 | the shelf object 19 | """ 20 | pid = input('Enter ID number: ') 21 | field = input('What would you like to know? (name, age, phone) ') 22 | field = field.strip().lower() 23 | 24 | print(field.capitalize() + ':', db[pid][field]) 25 | 26 | def print_help(): 27 | print('The available commands are:') 28 | print('store : Stores information about a person') 29 | print('lookup : Looks up a person from ID number') 30 | print('quit : Save changes and exit') 31 | print('? : Prints this message') 32 | 33 | def enter_command(): 34 | cmd = input('Enter command (? for help): ') 35 | cmd = cmd.strip().lower() 36 | return cmd 37 | 38 | def main(): 39 | database = shelve.open('C:\\database.dat') # You may want to change this name 40 | try: 41 | while True: 42 | cmd = enter_command() 43 | if cmd == 'store': 44 | store_person(database) 45 | elif cmd == 'lookup': 46 | lookup_person(database) 47 | elif cmd == '?': 48 | print_help() 49 | elif cmd == 'quit': 50 | return 51 | finally: 52 | database.close() 53 | 54 | if name == '__main__': main() -------------------------------------------------------------------------------- /Chapter11/listing11-1.py: -------------------------------------------------------------------------------- 1 | # somescript.py 2 | import sys 3 | text = sys.stdin.read() 4 | words = text.split() 5 | wordcount = len(words) 6 | print('Wordcount:', wordcount) -------------------------------------------------------------------------------- /Chapter11/listing11-10.py: -------------------------------------------------------------------------------- 1 | with open(filename) as f: 2 | for line in f.readlines(): 3 | process(line) -------------------------------------------------------------------------------- /Chapter11/listing11-11.py: -------------------------------------------------------------------------------- 1 | import fileinput 2 | for line in fileinput.input(filename): 3 | process(line) -------------------------------------------------------------------------------- /Chapter11/listing11-12.py: -------------------------------------------------------------------------------- 1 | with open(filename) as f: 2 | for line in f: 3 | process(line) -------------------------------------------------------------------------------- /Chapter11/listing11-13.py: -------------------------------------------------------------------------------- 1 | for line in open(filename): 2 | process(line) -------------------------------------------------------------------------------- /Chapter11/listing11-2.txt: -------------------------------------------------------------------------------- 1 | Your mother was a hamster and your 2 | father smelled of elderberries. -------------------------------------------------------------------------------- /Chapter11/listing11-3.txt: -------------------------------------------------------------------------------- 1 | Welcome to this file 2 | There is nothing here except 3 | This stupid haiku -------------------------------------------------------------------------------- /Chapter11/listing11-4.txt: -------------------------------------------------------------------------------- 1 | this 2 | is no 3 | haiku -------------------------------------------------------------------------------- /Chapter11/listing11-5.txt: -------------------------------------------------------------------------------- 1 | this 2 | isn't a 3 | haiku -------------------------------------------------------------------------------- /Chapter11/listing11-6.py: -------------------------------------------------------------------------------- 1 | with open(filename) as f: 2 | char = f.read(1) 3 | while char: 4 | process(char) 5 | char = f.read(1) -------------------------------------------------------------------------------- /Chapter11/listing11-7.py: -------------------------------------------------------------------------------- 1 | with open(filename) as f: 2 | while True: 3 | char = f.read(1) 4 | if not char: break 5 | process(char) -------------------------------------------------------------------------------- /Chapter11/listing11-8.py: -------------------------------------------------------------------------------- 1 | with open(filename) as f: 2 | while True: 3 | line = f.readline() 4 | if not line: break 5 | process(line) -------------------------------------------------------------------------------- /Chapter11/listing11-9.py: -------------------------------------------------------------------------------- 1 | with open(filename) as f: 2 | for char in f.read(): 3 | process(char) -------------------------------------------------------------------------------- /Chapter12/listing12-1.py: -------------------------------------------------------------------------------- 1 | from tkinter import * 2 | from tkinter.scrolledtext import ScrolledText 3 | 4 | def load(): 5 | with open(filename.get()) as file: 6 | contents.delete('1.0', END) 7 | contents.insert(INSERT, file.read()) 8 | 9 | def save(): 10 | with open(filename.get(), 'w') as file: 11 | file.write(contents.get('1.0', END)) 12 | 13 | top = Tk() 14 | top.title("Simple Editor") 15 | 16 | contents = ScrolledText() 17 | contents.pack(side=BOTTOM, expand=True, fill=BOTH) 18 | 19 | filename = Entry() 20 | filename.pack(side=LEFT, expand=True, fill=X) 21 | 22 | Button(text='Open', command=load).pack(side=LEFT) 23 | Button(text='Save', command=save).pack(side=LEFT) 24 | 25 | mainloop() -------------------------------------------------------------------------------- /Chapter13/listing13-1.py: -------------------------------------------------------------------------------- 1 | import sqlite3 2 | 3 | def convert(value): 4 | if value.startswith('~'): 5 | return value.strip('~') 6 | if not value: 7 | value = '0' 8 | return float(value) 9 | 10 | conn = sqlite3.connect('food.db') 11 | curs = conn.cursor() 12 | 13 | curs.execute(''' 14 | CREATE TABLE food ( 15 | id TEXT PRIMARY KEY, 16 | desc TEXT, 17 | water FLOAT, 18 | kcal FLOAT, 19 | protein FLOAT, 20 | fat FLOAT, 21 | ash FLOAT, 22 | carbs FLOAT, 23 | fiber FLOAT, 24 | sugar FLOAT 25 | ) 26 | ''') 27 | 28 | query = 'INSERT INTO food VALUES (?,?,?,?,?,?,?,?,?,?)' 29 | field_count = 10 30 | 31 | for line in open('ABBREV.txt'): 32 | fields = line.split('^') 33 | vals = [convert(f) for f in fields[:field_count]] 34 | curs.execute(query, vals) 35 | 36 | conn.commit() 37 | conn.close() -------------------------------------------------------------------------------- /Chapter13/listing13-2.py: -------------------------------------------------------------------------------- 1 | import sqlite3, sys 2 | 3 | conn = sqlite3.connect('food.db') 4 | curs = conn.cursor() 5 | 6 | query = 'SELECT * FROM food WHERE ' + sys.argv[1] 7 | print(query) 8 | curs.execute(query) 9 | names = [f[0] for f in curs.description] 10 | for row in curs.fetchall(): 11 | for pair in zip(names, row): 12 | print('{}: {}'.format(*pair)) 13 | print() -------------------------------------------------------------------------------- /Chapter14/listing14-1.py: -------------------------------------------------------------------------------- 1 | import socket 2 | 3 | s = socket.socket() 4 | 5 | host = socket.gethostname() 6 | port = 1234 7 | s.bind((host, port)) 8 | 9 | s.listen(5) 10 | while True: 11 | c, addr = s.accept() 12 | print('Got connection from', addr 13 | c.send('Thank you for connecting') 14 | c.close() -------------------------------------------------------------------------------- /Chapter14/listing14-2.py: -------------------------------------------------------------------------------- 1 | import socket 2 | 3 | s = socket.socket() 4 | 5 | host = socket.gethostname() 6 | port = 1234 7 | 8 | s.connect((host, port)) 9 | print(s.recv(1024)) -------------------------------------------------------------------------------- /Chapter14/listing14-3.py: -------------------------------------------------------------------------------- 1 | from socketserver import TCPServer, StreamRequestHandler 2 | 3 | class Handler(StreamRequestHandler): 4 | 5 | def handle(self): 6 | addr = self.request.getpeername() 7 | print('Got connection from', addr) 8 | self.wfile.write('Thank you for connecting') 9 | 10 | server = TCPServer(('', 1234), Handler) 11 | server.serve_forever() -------------------------------------------------------------------------------- /Chapter14/listing14-4.py: -------------------------------------------------------------------------------- 1 | from socketserver import TCPServer, ForkingMixIn, StreamRequestHandler 2 | 3 | class Server(ForkingMixIn, TCPServer): pass 4 | 5 | class Handler(StreamRequestHandler): 6 | 7 | def handle(self): 8 | addr = self.request.getpeername() 9 | print('Got connection from', addr) 10 | self.wfile.write('Thank you for connecting') 11 | 12 | server = Server(('', 1234), Handler) 13 | server.serve_forever() -------------------------------------------------------------------------------- /Chapter14/listing14-5.py: -------------------------------------------------------------------------------- 1 | from socketserver import TCPServer, ThreadingMixIn, StreamRequestHandler 2 | 3 | class Server(ThreadingMixIn, TCPServer): pass 4 | 5 | class Handler(StreamRequestHandler): 6 | 7 | def handle(self): 8 | addr = self.request.getpeername() 9 | print('Got connection from', addr) 10 | self.wfile.write('Thank you for connecting') 11 | 12 | server = Server(('', 1234), Handler) 13 | server.serve_forever() -------------------------------------------------------------------------------- /Chapter14/listing14-6.py: -------------------------------------------------------------------------------- 1 | import socket, select 2 | 3 | s = socket.socket() 4 | 5 | host = socket.gethostname() 6 | port = 1234 7 | s.bind((host, port)) 8 | 9 | s.listen(5) 10 | inputs = [s] 11 | while True: 12 | rs, ws, es = select.select(inputs, [], []) 13 | for r in rs: 14 | if r is s: 15 | c, addr = s.accept() 16 | print('Got connection from', addr) 17 | inputs.append(c) 18 | else: 19 | try: 20 | data = r.recv(1024) 21 | disconnected = not data 22 | except socket.error: 23 | disconnected = True 24 | 25 | if disconnected: 26 | print r.getpeername(), 'disconnected' 27 | inputs.remove(r) 28 | else: 29 | print data -------------------------------------------------------------------------------- /Chapter14/listing14-7.py: -------------------------------------------------------------------------------- 1 | import socket, select 2 | 3 | s = socket.socket() 4 | 5 | host = socket.gethostname() 6 | port = 1234 7 | s.bind((host, port)) 8 | 9 | fdmap = {s.fileno(): s} 10 | 11 | 12 | s.listen(5) 13 | p = select.poll() 14 | p.register(s) 15 | while True: 16 | events = p.poll() 17 | for fd, event in events: 18 | if fd in fdmap: 19 | c, addr = s.accept() 20 | print 'Got connection from', addr 21 | p.register(c) 22 | fdmap[c.fileno()] = c 23 | elif event & select.POLLIN: 24 | data = fdmap[fd].recv(1024) 25 | if not data: # No data -- connection closed 26 | print fdmap[fd].getpeername(), 'disconnected' 27 | p.unregister(fd) 28 | del fdmap[fd] 29 | else: 30 | print data -------------------------------------------------------------------------------- /Chapter14/listing14-8.py: -------------------------------------------------------------------------------- 1 | from twisted.internet import reactor 2 | from twisted.internet.protocol import Protocol, Factory 3 | 4 | class SimpleLogger(Protocol): 5 | 6 | def connectionMade(self): 7 | print 'Got connection from', self.transport.client 8 | 9 | def connectionLost(self, reason): 10 | print self.transport.client, 'disconnected' 11 | 12 | def dataReceived(self, data): 13 | print data 14 | 15 | factory = Factory() 16 | factory.protocol = SimpleLogger 17 | 18 | reactor.listenTCP(1234, factory) 19 | reactor.run() -------------------------------------------------------------------------------- /Chapter14/listing14-9.py: -------------------------------------------------------------------------------- 1 | from twisted.internet import reactor 2 | from twisted.internet.protocol import Factory 3 | from twisted.protocols.basic import LineReceiver 4 | 5 | 6 | class SimpleLogger(LineReceiver): 7 | 8 | def connectionMade(self): 9 | print 'Got connection from', self.transport.client 10 | 11 | def connectionLost(self, reason): 12 | print self.transport.client, 'disconnected' 13 | 14 | def lineReceived(self, line): 15 | print line 16 | 17 | factory = Factory() 18 | factory.protocol = SimpleLogger 19 | 20 | reactor.listenTCP(1234, factory) 21 | reactor.run() -------------------------------------------------------------------------------- /Chapter15/listing15-1.py: -------------------------------------------------------------------------------- 1 | from urllib.request import urlopen 2 | import re 3 | p = re.compile('(.*?)') 4 | text = urlopen('http://python.org/jobs').read().decode() 5 | for url, name in p.findall(text): 6 | print('{} ({})'.format(name, url)) -------------------------------------------------------------------------------- /Chapter15/listing15-2.py: -------------------------------------------------------------------------------- 1 | from urllib.request import urlopen 2 | from html.parser import HTMLParser 3 | 4 | def isjob(url): 5 | try: 6 | a, b, c, d = url.split('/') 7 | except ValueError: 8 | return False 9 | return a == d == '' and b == 'jobs' and c.isdigit() 10 | 11 | class Scraper(HTMLParser): 12 | 13 | in_link = False 14 | 15 | def handle_starttag(self, tag, attrs): 16 | attrs = dict(attrs) 17 | url = attrs.get('href', '') 18 | if tag == 'a' and isjob(url): 19 | self.url = url 20 | self.in_link = True 21 | self.chunks = [] 22 | 23 | def handle_data(self, data): 24 | if self.in_link: 25 | self.chunks.append(data) 26 | 27 | def handle_endtag(self, tag): 28 | if tag == 'a' and self.in_link: 29 | print('{} ({})'.format(''.join(self.chunks), self.url)) 30 | self.in_link = False 31 | 32 | text = urlopen('http://python.org/jobs').read().decode() 33 | parser = Scraper() 34 | parser.feed(text) 35 | parser.close() -------------------------------------------------------------------------------- /Chapter15/listing15-3.py: -------------------------------------------------------------------------------- 1 | from urllib.request import urlopen 2 | from bs4 import BeautifulSoup 3 | 4 | text = urlopen('http://python.org/jobs').read() 5 | soup = BeautifulSoup(text, 'html.parser') 6 | 7 | jobs = set() 8 | for job in soup.body.section('h2'): 9 | jobs.add('{} ({})'.format(job.a.string, job.a['href'])) 10 | 11 | print('\n'.join(sorted(jobs, key=str.lower))) -------------------------------------------------------------------------------- /Chapter15/listing15-4.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | print('Content-type: text/plain') 4 | print() # Prints an empty line, to end the headers 5 | 6 | print('Hello, world!') -------------------------------------------------------------------------------- /Chapter15/listing15-5.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import cgitb; cgitb.enable() 4 | 5 | print('Content-type: text/html\n') 6 | 7 | print(1/0) 8 | 9 | print('Hello, world!') -------------------------------------------------------------------------------- /Chapter15/listing15-6.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import cgi 4 | form = cgi.FieldStorage() 5 | 6 | name = form.getvalue('name', 'world') 7 | 8 | print('Content-type: text/plain\n') 9 | 10 | print('Hello, {}!'.format(name)) -------------------------------------------------------------------------------- /Chapter15/listing15-7.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import cgi 4 | form = cgi.FieldStorage() 5 | 6 | name = form.getvalue('name', 'world') 7 | 8 | print("""Content-type: text/html 9 | 10 | 11 | 12 | Greeting Page 13 | 14 | 15 |

Hello, %s!

16 | 17 | 18 |
19 | Change name 20 | 21 |
22 | 23 | 24 | """.format(name)) -------------------------------------------------------------------------------- /Chapter16/listing16-1.py: -------------------------------------------------------------------------------- 1 | from area import rect_area 2 | height = 3 3 | width = 4 4 | correct_answer = 12 5 | answer = rect_area(height, width) 6 | if answer == correct_answer: 7 | print('Test passed ') 8 | else: 9 | print('Test failed ') -------------------------------------------------------------------------------- /Chapter16/listing16-2.py: -------------------------------------------------------------------------------- 1 | import unittest, my_math 2 | 3 | class ProductTestCase(unittest.TestCase): 4 | 5 | def test_integers(self): 6 | for x in range(-10, 10): 7 | for y in range(-10, 10): 8 | p = my_math.product(x, y) 9 | self.assertEqual(p, x * y, 'Integer multiplication failed') 10 | 11 | def test_floats(self): 12 | for x in range(-10, 10): 13 | for y in range(-10, 10): 14 | x = x / 10 15 | y = y / 10 16 | p = my_math.product(x, y) 17 | self.assertEqual(p, x * y, 'Float multiplication failed') 18 | 19 | if __name__ == '__main__': unittest.main() -------------------------------------------------------------------------------- /Chapter16/listing16-3.py: -------------------------------------------------------------------------------- 1 | import unittest, my_math 2 | from subprocess import Popen, PIPE 3 | 4 | class ProductTestCase(unittest.TestCase): 5 | 6 | # Insert previous tests here 7 | 8 | def test_with_PyChecker(self): 9 | cmd = 'pychecker', '-Q', my_math.__file__.rstrip('c') 10 | pychecker = Popen(cmd, stdout=PIPE, stderr=PIPE) 11 | self.assertEqual(pychecker.stdout.read(), '') 12 | 13 | def test_with_PyLint(self): 14 | cmd = 'pylint', '-rn', 'my_math' 15 | pylint = Popen(cmd, stdout=PIPE, stderr=PIPE) 16 | self.assertEqual(pylint.stdout.read(), '') 17 | 18 | if __name__ == '__main__': unittest.main() -------------------------------------------------------------------------------- /Chapter17/listing17-1.java: -------------------------------------------------------------------------------- 1 | public class JythonTest { 2 | 3 | public void greeting() { 4 | System.out.println("Hello, world!"); 5 | } 6 | 7 | } -------------------------------------------------------------------------------- /Chapter17/listing17-2.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | namespace FePyTest { 3 | public class IronPythonTest { 4 | 5 | public void greeting() { 6 | Console.WriteLine("Hello, world!"); 7 | } 8 | 9 | } 10 | } -------------------------------------------------------------------------------- /Chapter17/listing17-3.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int is_palindrome(char *text) { 4 | int i, n = strlen(text); 5 | for (i = 0; i <= n/2; ++i) { 6 | if (text[i] != text[n-i-1]) return 0; 7 | } 8 | return 1; 9 | } -------------------------------------------------------------------------------- /Chapter17/listing17-4.py: -------------------------------------------------------------------------------- 1 | def is_palindrome(text): 2 | n = len(text) 3 | for i in range(len(text)//2): 4 | if text[i] != text[n-i-1]: 5 | return False 6 | return True -------------------------------------------------------------------------------- /Chapter17/listing17-5.i: -------------------------------------------------------------------------------- 1 | %module palindrome 2 | 3 | %{ 4 | #include 5 | %} 6 | 7 | extern int is_palindrome(char *text); -------------------------------------------------------------------------------- /Chapter17/listing17-6.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | static PyObject *is_palindrome(PyObject *self, PyObject *args) { 4 | int i, n; 5 | const char *text; 6 | int result; 7 | /* "s" means a single string: */ 8 | if (!PyArg_ParseTuple(args, "s", &text)) { 9 | return NULL; 10 | } 11 | /* The old code, more or less: */ 12 | n=strlen(text); 13 | result = 1; 14 | for (i = 0; i <= n/2; ++i) { 15 | if (text[i] != text[n-i-1]) { 16 | result = 0; 17 | break; 18 | } 19 | } 20 | /* "i" means a single integer: */ 21 | return Py_BuildValue("i", result); 22 | } 23 | 24 | /* A listing of our methods/functions: */ 25 | static PyMethodDef PalindromeMethods[] = { 26 | 27 | /* name, function, argument type, docstring */ 28 | {"is_palindrome", is_palindrome, METH_VARARGS, "Detect palindromes"}, 29 | /* An end-of-listing sentinel: */ 30 | {NULL, NULL, 0, NULL} 31 | 32 | }; 33 | 34 | static struct PyModuleDef palindrome = 35 | { 36 | PyModuleDef_HEAD_INIT, 37 | "palindrome", /* module name */ 38 | "", /* docstring */ 39 | -1, /* signals state kept in global variables */ 40 | PalindromeMethods 41 | }; 42 | 43 | 44 | /* An initialization function for the module: */ 45 | PyMODINIT_FUNC PyInit_palindrome(void) 46 | { 47 | return PyModule_Create(&palindrome); 48 | } -------------------------------------------------------------------------------- /Chapter18/listing18-1.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup(name='Hello', 4 | version='1.0', 5 | description='A simple example', 6 | author='Magnus Lie Hetland', 7 | py_modules=['hello']) -------------------------------------------------------------------------------- /Chapter19/listing19-1.cfg: -------------------------------------------------------------------------------- 1 | [numbers] 2 | 3 | pi: 3.1415926535897931 4 | 5 | [messages] 6 | 7 | greeting: Welcome to the area calculation program! 8 | question: Please enter the radius: 9 | result_message: The area is -------------------------------------------------------------------------------- /Chapter19/listing19-2.py: -------------------------------------------------------------------------------- 1 | from configparser import ConfigParser 2 | 3 | CONFIGFILE = "area.ini" 4 | 5 | config = ConfigParser() 6 | # Read the configuration file: 7 | config.read(CONFIGFILE) 8 | 9 | # Print out an initial greeting; 10 | # 'messages' is the section to look in: 11 | print(config['messages'].get('greeting')) 12 | 13 | # Read in the radius, using a question from the config file: 14 | radius = float(input(config['messages'].get('question') + ' ')) 15 | 16 | # Print a result message from the config file; 17 | # end with a space to stay on same line: 18 | print(config['messages'].get('result_message'), end=' ') 19 | 20 | # getfloat() converts the config value to a float: 21 | print(config['numbers'].getfloat('pi') * radius**2) -------------------------------------------------------------------------------- /Chapter19/listing19-3.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | logging.basicConfig(level=logging.INFO, filename='mylog.log') 4 | 5 | logging.info('Starting program') 6 | 7 | logging.info('Trying to divide 1 by 0') 8 | 9 | print(1 / 0) 10 | 11 | logging.info('The division succeeded') 12 | 13 | logging.info('Ending program') -------------------------------------------------------------------------------- /Chapter20/listing20-1.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Welcome to World Wide Spam, Inc. 5 | 6 | 7 | These are the corporate web pages of *World Wide Spam*, Inc. We hope 8 | you find your stay enjoyable, and that you will sample many of our 9 | products. 10 | 11 | A short history of the company 12 | 13 | World Wide Spam was started in the summer of 2000. The business 14 | concept was to ride the dot-com wave and to make money both through 15 | bulk email and by selling canned meat online. 16 | 17 | After receiving several complaints from customers who weren't 18 | satisfied by their bulk email, World Wide Spam altered their profile, 19 | and focused 100% on canned goods. Today, they rank as the world's 20 | 13,892nd online supplier of SPAM. 21 | 22 | Destinations 23 | 24 | From this page you may visit several of our interesting web pages: 25 | 26 | - What is SPAM? (http://wwspam.fu/whatisspam) 27 | 28 | - How do they make it? (http://wwspam.fu/howtomakeit) 29 | 30 | - Why should I eat it? (http://wwspam.fu/whyeatit) 31 | 32 | How to get in touch with us 33 | 34 | You can get in touch with us in *many* ways: By phone (555-1234), by 35 | email (wwspam@wwspam.fu) or by visiting our customer feedback page 36 | (http://wwspam.fu/feedback). -------------------------------------------------------------------------------- /Chapter20/listing20-2.py: -------------------------------------------------------------------------------- 1 | def lines(file): 2 | for line in file: yield line 3 | yield '\n' 4 | 5 | def blocks(file): 6 | block = [] 7 | for line in lines(file): 8 | if line.strip(): 9 | block.append(line) 10 | elif block: 11 | yield ''.join(block).strip() 12 | block = [] -------------------------------------------------------------------------------- /Chapter20/listing20-3.py: -------------------------------------------------------------------------------- 1 | import sys, re 2 | from util import * 3 | 4 | print('...') 5 | 6 | title = True 7 | for block in blocks(sys.stdin): 8 | block = re.sub(r'\*(.+?)\*', r'\1', block) 9 | if title: 10 | print('

') 11 | print(block) 12 | print('

') 13 | title = False 14 | else: 15 | print('

') 16 | print(block) 17 | print('

') 18 | 19 | print('') -------------------------------------------------------------------------------- /Chapter20/listing20-4.py: -------------------------------------------------------------------------------- 1 | class Handler: 2 | """ 3 | An object that handles method calls from the Parser. 4 | 5 | The Parser will call the start() and end() methods at the 6 | beginning of each block, with the proper block name as a 7 | parameter. The sub() method will be used in regular expression 8 | substitution. When called with a name such as 'emphasis', it will 9 | return a proper substitution function. 10 | """ 11 | def callback(self, prefix, name, *args): 12 | method = getattr(self, prefix + name, None) 13 | if callable(method): return method(*args) 14 | def start(self, name): 15 | self.callback('start_', name) 16 | def end(self, name): 17 | self.callback('end_', name) 18 | def sub(self, name): 19 | def substitution(match): 20 | result = self.callback('sub_', name, match) 21 | if result is None: match.group(0) 22 | return result 23 | return substitution 24 | 25 | class HTMLRenderer(Handler): 26 | """ 27 | A specific handler used for rendering HTML. 28 | 29 | The methods in HTMLRenderer are accessed from the superclass 30 | Handler's start(), end(), and sub() methods. They implement basic 31 | markup as used in HTML documents. 32 | """ 33 | def start_document(self): 34 | print('...') 35 | def end_document(self): 36 | print('') 37 | def start_paragraph(self): 38 | print('

') 39 | def end_paragraph(self): 40 | print('

') 41 | def start_heading(self): 42 | print('

') 43 | def end_heading(self): 44 | print('

') 45 | def start_list(self): 46 | print('
    ') 47 | def end_list(self): 48 | print('
') 49 | def start_listitem(self): 50 | print('
  • ') 51 | def end_listitem(self): 52 | print('
  • ') 53 | def start_title(self): 54 | print('

    ') 55 | def end_title(self): 56 | print('

    ') 57 | def sub_emphasis(self, match): 58 | return '{}'.format(match.group(1)) 59 | def sub_url(self, match): 60 | return '{}'.format(match.group(1), match.group(1)) 61 | def sub_mail(self, match): 62 | return '{}'.format(match.group(1), match.group(1)) 63 | def feed(self, data): 64 | print(data) -------------------------------------------------------------------------------- /Chapter20/listing20-5.py: -------------------------------------------------------------------------------- 1 | class Rule: 2 | """ 3 | Base class for all rules. 4 | """ 5 | 6 | def action(self, block, handler): 7 | handler.start(self.type) 8 | handler.feed(block) 9 | handler.end(self.type) 10 | return True 11 | 12 | class HeadingRule(Rule): 13 | """ 14 | A heading is a single line that is at most 70 characters and 15 | that doesn't end with a colon. 16 | """ 17 | type = 'heading' 18 | def condition(self, block): 19 | return not '\n' in block and len(block) <= 70 and not block[-1] == ':' 20 | 21 | class TitleRule(HeadingRule): 22 | """ 23 | The title is the first block in the document, provided that 24 | it is a heading. 25 | """ 26 | type = 'title' 27 | first = True 28 | 29 | def condition(self, block): 30 | if not self.first: return False 31 | self.first = False 32 | return HeadingRule.condition(self, block) 33 | 34 | class ListItemRule(Rule): 35 | """ 36 | A list item is a paragraph that begins with a hyphen. As part of the 37 | formatting, the hyphen is removed. 38 | """ 39 | type = 'listitem' 40 | def condition(self, block): 41 | return block[0] == '-' 42 | def action(self, block, handler): 43 | handler.start(self.type) 44 | handler.feed(block[1:].strip()) 45 | handler.end(self.type) 46 | return True 47 | 48 | class ListRule(ListItemRule): 49 | """ 50 | A list begins between a block that is not a list item and a 51 | subsequent list item. It ends after the last consecutive list item. 52 | """ 53 | type = 'list' 54 | inside = False 55 | def condition(self, block): 56 | return True 57 | def action(self, block, handler): 58 | if not self.inside and ListItemRule.condition(self, block): 59 | handler.start(self.type) 60 | self.inside = True 61 | elif self.inside and not ListItemRule.condition(self, block): 62 | handler.end(self.type) 63 | self.inside = False 64 | return False 65 | 66 | class ParagraphRule(Rule): 67 | """ 68 | A paragraph is simply a block that isn't covered by any of the other rules. 69 | """ 70 | type = 'paragraph' 71 | def condition(self, block): 72 | return True -------------------------------------------------------------------------------- /Chapter20/listing20-6.py: -------------------------------------------------------------------------------- 1 | import sys, re 2 | from handlers import * 3 | from util import * 4 | from rules import * 5 | 6 | class Parser: 7 | """ 8 | A Parser reads a text file, applying rules and controlling a handler. 9 | """ 10 | def __init__(self, handler): 11 | self.handler = handler 12 | self.rules = [] 13 | self.filters = [] 14 | def addRule(self, rule): 15 | self.rules.append(rule) 16 | def addFilter(self, pattern, name): 17 | def filter(block, handler): 18 | return re.sub(pattern, handler.sub(name), block) 19 | self.filters.append(filter) 20 | 21 | def parse(self, file): 22 | self.handler.start('document') 23 | for block in blocks(file): 24 | for filter in self.filters: 25 | block = filter(block, self.handler) 26 | for rule in self.rules: 27 | if rule.condition(block): 28 | last = rule.action(block, 29 | self.handler) 30 | if last: break 31 | self.handler.end('document') 32 | 33 | class BasicTextParser(Parser): 34 | """ 35 | A specific Parser that adds rules and filters in its constructor. 36 | """ 37 | def __init__(self, handler): 38 | Parser.__init__(self, handler) 39 | self.addRule(ListRule()) 40 | self.addRule(ListItemRule()) 41 | self.addRule(TitleRule()) 42 | self.addRule(HeadingRule()) 43 | self.addRule(ParagraphRule()) 44 | 45 | self.addFilter(r'\*(.+?)\*', 'emphasis') 46 | self.addFilter(r'(http://[\.a-zA-Z/]+)', 'url') 47 | self.addFilter(r'([\.a-zA-Z]+@[\.a-zA-Z]+[a-zA-Z]+)', 'mail') 48 | 49 | handler = HTMLRenderer() 50 | parser = BasicTextParser(handler) 51 | 52 | parser.parse(sys.stdin) -------------------------------------------------------------------------------- /Chapter21/listing21-1.py: -------------------------------------------------------------------------------- 1 | from reportlab.graphics.shapes import Drawing, String 2 | from reportlab.graphics import renderPDF 3 | 4 | d = Drawing(100, 100) 5 | s = String(50, 50, 'Hello, world!', textAnchor='middle') 6 | 7 | d.add(s) 8 | 9 | renderPDF.drawToFile(d, 'hello.pdf', 'A simple PDF file') -------------------------------------------------------------------------------- /Chapter21/listing21-2.py: -------------------------------------------------------------------------------- 1 | from reportlab.lib import colors 2 | from reportlab.graphics.shapes import * 3 | from reportlab.graphics import renderPDF 4 | 5 | data = [ 6 | # Year Month Predicted High Low 7 | (2007, 8, 113.2, 114.2, 112.2), 8 | (2007, 9, 112.8, 115.8, 109.8), 9 | (2007, 10, 111.0, 116.0, 106.0), 10 | (2007, 11, 109.8, 116.8, 102.8), 11 | (2007, 12, 107.3, 115.3, 99.3), 12 | (2008, 1, 105.2, 114.2, 96.2), 13 | (2008, 2, 104.1, 114.1, 94.1), 14 | (2008, 3, 99.9, 110.9, 88.9), 15 | (2008, 4, 94.8, 106.8, 82.8), 16 | (2008, 5, 91.2, 104.2, 78.2), 17 | ] 18 | 19 | drawing = Drawing(200, 150) 20 | 21 | pred = [row[2]-40 for row in data] 22 | high = [row[3]-40 for row in data] 23 | low = [row[4]-40 for row in data] 24 | times = [200*((row[0] + row[1]/12.0) - 2007)-110 for row in data] 25 | 26 | drawing.add(PolyLine(list(zip(times, pred)), strokeColor=colors.blue)) 27 | drawing.add(PolyLine(list(zip(times, high)), strokeColor=colors.red)) 28 | drawing.add(PolyLine(list(zip(times, low)), strokeColor=colors.green)) 29 | 30 | drawing.add(String(65, 115, 'Sunspots', fontSize=18, fillColor=colors.red)) 31 | renderPDF.drawToFile(drawing, 'report1.pdf', 'Sunspots') -------------------------------------------------------------------------------- /Chapter21/listing21-3.py: -------------------------------------------------------------------------------- 1 | from urllib.request import urlopen 2 | from reportlab.graphics.shapes import * 3 | from reportlab.graphics.charts.lineplots import LinePlot 4 | from reportlab.graphics.charts.textlabels import Label 5 | from reportlab.graphics import renderPDF 6 | 7 | URL = 'ftp://ftp.swpc.noaa.gov/pub/weekly/Predict.txt' 8 | COMMENT_CHARS = '#:' 9 | 10 | 11 | drawing = Drawing(400, 200) 12 | data = [] 13 | for line in urlopen(URL).readlines(): 14 | line = line.decode() 15 | if not line.isspace() and line[0] not in COMMENT_CHARS: 16 | data.append([float(n) for n in line.split()]) 17 | 18 | pred = [row[2] for row in data] 19 | high = [row[3] for row in data] 20 | low = [row[4] for row in data] 21 | times = [row[0] + row[1]/12.0 for row in data] 22 | 23 | lp = LinePlot() 24 | lp.x = 50 25 | lp.y = 50 26 | lp.height = 125 27 | lp.width = 300 28 | lp.data = [list(zip(times, pred)), 29 | list(zip(times, high)), 30 | list(zip(times, low))] 31 | lp.lines[0].strokeColor = colors.blue 32 | lp.lines[1].strokeColor = colors.red 33 | lp.lines[2].strokeColor = colors.green 34 | 35 | drawing.add(lp) 36 | 37 | drawing.add(String(250, 150, 'Sunspots', 38 | fontSize=14, fillColor=colors.red)) 39 | 40 | 41 | renderPDF.drawToFile(drawing, 'report2.pdf', 'Sunspots') -------------------------------------------------------------------------------- /Chapter22/listing22-1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 |

    Welcome to My Home Page

    4 | 5 |

    Hi, there. My name is Mr. Gumby, and this is my home page. Here 6 | are some of my interests:

    7 | 8 | 13 |
    14 | 15 | 16 |

    Mr. Gumby's Shouting Page

    17 | 18 |

    ...

    19 |
    20 | 21 |

    Mr. Gumby's Sleeping Page

    22 | 23 |

    ...

    24 |
    25 | 26 |

    Mr. Gumby's Eating Page

    27 | 28 |

    ...

    29 |
    30 |
    31 |
    -------------------------------------------------------------------------------- /Chapter22/listing22-2.py: -------------------------------------------------------------------------------- 1 | from xml.sax.handler import ContentHandler 2 | from xml.sax import parse 3 | 4 | class PageMaker(ContentHandler): 5 | 6 | passthrough = False 7 | 8 | def startElement(self, name, attrs): 9 | if name == 'page': 10 | self.passthrough = True 11 | self.out = open(attrs['name'] + '.html', 'w') 12 | self.out.write('\n') 13 | self.out.write('{}\n'.format(attrs['title'])) 14 | self.out.write('\n') 15 | elif self.passthrough: 16 | self.out.write('<' + name) 17 | for key, val in attrs.items(): 18 | self.out.write(' {}="{}"'.format(key, val)) 19 | self.out.write('>') 20 | 21 | def endElement(self, name): 22 | if name == 'page': 23 | self.passthrough = False 24 | self.out.write('\n\n') 25 | self.out.close() 26 | elif self.passthrough: 27 | self.out.write(''.format(name)) 28 | 29 | def characters(self, chars): 30 | if self.passthrough: self.out.write(chars) 31 | 32 | parse('website.xml', PageMaker()) -------------------------------------------------------------------------------- /Chapter22/listing22-3.py: -------------------------------------------------------------------------------- 1 | from xml.sax.handler import ContentHandler 2 | from xml.sax import parse 3 | import os 4 | 5 | class Dispatcher: 6 | 7 | def dispatch(self, prefix, name, attrs=None): 8 | mname = prefix + name.capitalize() 9 | dname = 'default' + prefix.capitalize() 10 | method = getattr(self, mname, None) 11 | if callable(method): args = () 12 | else: 13 | method = getattr(self, dname, None) 14 | args = name, 15 | if prefix == 'start': args += attrs, 16 | if callable(method): method(*args) 17 | 18 | def startElement(self, name, attrs): 19 | self.dispatch('start', name, attrs) 20 | 21 | def endElement(self, name): 22 | self.dispatch('end', name) 23 | 24 | class WebsiteConstructor(Dispatcher, ContentHandler): 25 | 26 | passthrough = False 27 | 28 | def __init__(self, directory): 29 | self.directory = [directory] 30 | self.ensureDirectory() 31 | 32 | def ensureDirectory(self): 33 | path = os.path.join(*self.directory) 34 | os.makedirs(path, exist_ok=True) 35 | 36 | def characters(self, chars): 37 | if self.passthrough: self.out.write(chars) 38 | 39 | def defaultStart(self, name, attrs): 40 | if self.passthrough: 41 | self.out.write('<' + name) 42 | for key, val in attrs.items(): 43 | self.out.write(' {}="{}"'.format(key, val)) 44 | self.out.write('>') 45 | 46 | def defaultEnd(self, name): 47 | if self.passthrough: 48 | self.out.write(''.format(name)) 49 | 50 | def startDirectory(self, attrs): 51 | self.directory.append(attrs['name']) 52 | self.ensureDirectory() 53 | 54 | def endDirectory(self): 55 | self.directory.pop() 56 | 57 | def startPage(self, attrs): 58 | filename = os.path.join(*self.directory + [attrs['name'] + '.html']) 59 | self.out = open(filename, 'w') 60 | self.writeHeader(attrs['title']) 61 | self.passthrough = True 62 | 63 | def endPage(self): 64 | self.passthrough = False 65 | self.writeFooter() 66 | self.out.close() 67 | 68 | def writeHeader(self, title): 69 | self.out.write('\n \n ') 70 | self.out.write(title) 71 | self.out.write('\n \n \n') 72 | 73 | def writeFooter(self): 74 | self.out.write('\n \n\n') 75 | 76 | parse('website.xml', WebsiteConstructor('public_html')) -------------------------------------------------------------------------------- /Chapter23/listing23-1.py: -------------------------------------------------------------------------------- 1 | from nntplib import NNTP 2 | 3 | servername = 'news.foo.bar' 4 | group = 'comp.lang.python.announce' 5 | server = NNTP(servername) 6 | howmany = 10 7 | 8 | resp, count, first, last, name = server.group(group) 9 | 10 | start = last - howmany + 1 11 | 12 | resp, overviews = server.over((start, last)) 13 | 14 | for id, over in overviews: 15 | subject = over['subject'] 16 | resp, info = server.body(id) 17 | print(subject) 18 | print('-' * len(subject)) 19 | for line in info.lines: 20 | print(line.decode('latin1')) 21 | print() 22 | 23 | server.quit() -------------------------------------------------------------------------------- /Chapter23/listing23-2.py: -------------------------------------------------------------------------------- 1 | from nntplib import NNTP, decode_header 2 | from urllib.request import urlopen 3 | import textwrap 4 | import re 5 | 6 | class NewsAgent: 7 | """ 8 | An object that can distribute news items from news sources to news 9 | destinations. 10 | """ 11 | 12 | def __init__(self): 13 | self.sources = [] 14 | self.destinations = [] 15 | 16 | def add_source(self, source): 17 | self.sources.append(source) 18 | 19 | def addDestination(self, dest): 20 | self.destinations.append(dest) 21 | 22 | def distribute(self): 23 | """ 24 | Retrieve all news items from all sources, and Distribute them to all 25 | destinations. 26 | """ 27 | items = [] 28 | for source in self.sources: 29 | items.extend(source.get_items()) 30 | for dest in self.destinations: 31 | dest.receive_items(items) 32 | 33 | class NewsItem: 34 | """ 35 | A simple news item consisting of a title and body text. 36 | """ 37 | def __init__(self, title, body): 38 | self.title = title 39 | self.body = body 40 | 41 | class NNTPSource: 42 | """ 43 | A news source that retrieves news items from an NNTP group. 44 | """ 45 | def __init__(self, servername, group, howmany): 46 | self.servername = servername 47 | self.group = group 48 | self.howmany = howmany 49 | 50 | def get_items(self): 51 | server = NNTP(self.servername) 52 | resp, count, first, last, name = server.group(self.group) 53 | start = last - self.howmany + 1 54 | resp, overviews = server.over((start, last)) 55 | for id, over in overviews: 56 | title = decode_header(over['subject']) 57 | resp, info = server.body(id) 58 | body = '\n'.join(line.decode('latin') 59 | for line in info.lines) + '\n\n' 60 | yield NewsItem(title, body) 61 | server.quit() 62 | 63 | class SimpleWebSource: 64 | """ 65 | A news source that extracts news items from a web page using regular 66 | expressions. 67 | """ 68 | def __init__(self, url, title_pattern, body_pattern, encoding='utf8'): 69 | self.url = url 70 | self.title_pattern = re.compile(title_pattern) 71 | self.body_pattern = re.compile(body_pattern) 72 | self.encoding = encoding 73 | 74 | def get_items(self): 75 | text = urlopen(self.url).read().decode(self.encoding) 76 | titles = self.title_pattern.findall(text) 77 | bodies = self.body_pattern.findall(text) 78 | for title, body in zip(titles, bodies): 79 | yield NewsItem(title, textwrap.fill(body) + '\n') 80 | 81 | class PlainDestination: 82 | """ 83 | A news destination that formats all its news items as plain text. 84 | """ 85 | def receive_items(self, items): 86 | for item in items: 87 | print(item.title) 88 | print('-' * len(item.title)) 89 | print(item.body) 90 | 91 | class HTMLDestination: 92 | """ 93 | A news destination that formats all its news items as HTML. 94 | """ 95 | def __init__(self, filename): 96 | self.filename = filename 97 | 98 | def receive_items(self, items): 99 | 100 | out = open(self.filename, 'w') 101 | print(""" 102 | 103 | 104 | Today's News 105 | 106 | 107 |

    Today's News

    108 | """, file=out) 109 | 110 | print('
      ', file=out) 111 | id = 0 112 | for item in items: 113 | id += 1 114 | print('
    • {}
    • ' 115 | .format(id, item.title), file=out) 116 | print('
    ', file=out) 117 | 118 | id = 0 119 | for item in items: 120 | id += 1 121 | print('

    {}

    ' 122 | .format(id, item.title), file=out) 123 | print('
    {}
    '.format(item.body), file=out) 124 | 125 | print(""" 126 | 127 | 128 | """, file=out) 129 | 130 | def runDefaultSetup(): 131 | """ 132 | A default setup of sources and destination. Modify to taste. 133 | """ 134 | agent = NewsAgent() 135 | 136 | # A SimpleWebSource that retrieves news from Reuters: 137 | reuters_url = 'http://www.reuters.com/news/world' 138 | reuters_title = r'

    (.*?)' 139 | reuters_body = r'

    (.*?)

    ' 140 | reuters = SimpleWebSource(reuters_url, reuters_title, reuters_body) 141 | 142 | agent.add_source(reuters) 143 | 144 | # An NNTPSource that retrieves news from comp.lang.python.announce: 145 | clpa_server = 'news.foo.bar' # Insert real server name 146 | clpa_server = 'news.ntnu.no' 147 | clpa_group = 'comp.lang.python.announce' 148 | clpa_howmany = 10 149 | clpa = NNTPSource(clpa_server, clpa_group, clpa_howmany) 150 | 151 | agent.add_source(clpa) 152 | 153 | # Add plain-text destination and an HTML destination: 154 | agent.addDestination(PlainDestination()) 155 | agent.addDestination(HTMLDestination('news.html')) 156 | 157 | # Distribute the news items: 158 | agent.distribute() 159 | 160 | if __name__ == '__main__': runDefaultSetup() -------------------------------------------------------------------------------- /Chapter24/listing24-1.py: -------------------------------------------------------------------------------- 1 | from asyncore import dispatcher 2 | import asyncore 3 | 4 | class ChatServer(dispatcher): pass 5 | 6 | s = ChatServer() 7 | asyncore.loop() -------------------------------------------------------------------------------- /Chapter24/listing24-2.py: -------------------------------------------------------------------------------- 1 | from asyncore import dispatcher 2 | import socket, asyncore 3 | 4 | class ChatServer(dispatcher): 5 | 6 | def handle_accept(self): 7 | conn, addr = self.accept() 8 | print('Connection attempt from', addr[0]) 9 | 10 | s = ChatServer() 11 | s.create_socket(socket.AF_INET, socket.SOCK_STREAM) 12 | s.bind(('', 5005)) 13 | s.listen(5) 14 | asyncore.loop() -------------------------------------------------------------------------------- /Chapter24/listing24-3.py: -------------------------------------------------------------------------------- 1 | from asyncore import dispatcher 2 | import socket, asyncore 3 | 4 | PORT = 5005 5 | 6 | class ChatServer(dispatcher): 7 | 8 | def __init__(self, port): 9 | dispatcher.__init__(self) 10 | self.create_socket(socket.AF_INET, socket.SOCK_STREAM) 11 | self.set_reuse_addr() 12 | self.bind(('', port)) 13 | self.listen(5) 14 | 15 | def handle_accept(self): 16 | conn, addr = self.accept() 17 | print('Connection attempt from', addr[0]) 18 | 19 | if __name__ == '__main__': 20 | s = ChatServer(PORT) 21 | try: asyncore.loop() 22 | except KeyboardInterrupt: pass -------------------------------------------------------------------------------- /Chapter24/listing24-4.py: -------------------------------------------------------------------------------- 1 | from asyncore import dispatcher 2 | from asynchat import async_chat 3 | import socket, asyncore 4 | 5 | PORT = 5005 6 | 7 | class ChatSession(async_chat): 8 | 9 | def __init__(self, sock): 10 | async_chat. init (self, sock) 11 | self.set_terminator("\r\n") 12 | self.data = [] 13 | 14 | def collect_incoming_data(self, data): 15 | self.data.append(data) 16 | 17 | def found_terminator(self): 18 | line = ''.join(self.data) 19 | self.data = [] 20 | # Do something with the line... 21 | print(line) 22 | 23 | class ChatServer(dispatcher): 24 | 25 | def __init__(self, port): dispatcher. init (self) 26 | self.create_socket(socket.AF_INET, socket.SOCK_STREAM) 27 | self.set_reuse_addr() 28 | self.bind(('', port)) 29 | self.listen(5) 30 | self.sessions = [] 31 | 32 | def handle_accept(self): 33 | conn, addr = self.accept() 34 | self.sessions.append(ChatSession(conn)) 35 | 36 | if __name__ == '__main__': 37 | s = ChatServer(PORT) 38 | try: asyncore.loop() 39 | except KeyboardInterrupt: print() -------------------------------------------------------------------------------- /Chapter24/listing24-5.py: -------------------------------------------------------------------------------- 1 | from asyncore import dispatcher 2 | from asynchat import async_chat 3 | import socket, asyncore 4 | 5 | PORT = 5005 6 | NAME = 'TestChat' 7 | 8 | class ChatSession(async_chat): 9 | """ 10 | A class that takes care of a connection between the server and a single user. 11 | """ 12 | def __init__(self, server, sock): 13 | # Standard setup tasks: 14 | async_chat. init (self, sock) 15 | self.server = server 16 | self.set_terminator("\r\n") 17 | self.data = [] 18 | # Greet the user: 19 | self.push('Welcome to %s\r\n' % self.server.name) 20 | 21 | def collect_incoming_data(self, data): 22 | self.data.append(data) 23 | 24 | def found_terminator(self): 25 | """ 26 | If a terminator is found, that means that a full 27 | line has been read. Broadcast it to everyone. 28 | """ 29 | line = ''.join(self.data) 30 | self.data = [] 31 | self.server.broadcast(line) 32 | 33 | def handle_close(self): 34 | async_chat.handle_close(self) 35 | self.server.disconnect(self) 36 | 37 | class ChatServer(dispatcher): 38 | """ 39 | A class that receives connections and spawns individual 40 | sessions. It also handles broadcasts to these sessions. 41 | """ 42 | def __init__(self, port, name): 43 | # Standard setup tasks dispatcher. init (self) 44 | self.create_socket(socket.AF_INET, socket.SOCK_STREAM) 45 | self.set_reuse_addr() 46 | self.bind(('', port)) 47 | self.listen(5) 48 | self.name = name 49 | self.sessions = [] 50 | 51 | def disconnect(self, session): 52 | self.sessions.remove(session) 53 | 54 | def broadcast(self, line): 55 | for session in self.sessions: 56 | session.push(line + '\r\n') 57 | 58 | def handle_accept(self): 59 | conn, addr = self.accept() 60 | self.sessions.append(ChatSession(self, conn)) 61 | 62 | if __name__ == '__main__': 63 | s = ChatServer(PORT, NAME) 64 | try: asyncore.loop() 65 | except KeyboardInterrupt: print() -------------------------------------------------------------------------------- /Chapter24/listing24-6.py: -------------------------------------------------------------------------------- 1 | from asyncore import dispatcher 2 | from asynchat import async_chat 3 | import socket, asyncore 4 | 5 | PORT = 5005 6 | NAME = 'TestChat' 7 | 8 | class EndSession(Exception): pass 9 | 10 | class CommandHandler: 11 | """ 12 | Simple command handler similar to cmd.Cmd from the standard library. 13 | """ 14 | 15 | def unknown(self, session, cmd): 16 | 'Respond to an unknown command' 17 | session.push('Unknown command: {}s\r\n'.format(cmd)) 18 | 19 | def handle(self, session, line): 20 | 'Handle a received line from a given session' 21 | if not line.strip(): return 22 | # Split off the command: 23 | parts = line.split(' ', 1) 24 | cmd = parts[0] 25 | try: line = parts[1].strip() 26 | except IndexError: line = '' 27 | # Try to find a handler: 28 | meth = getattr(self, 'do_' + cmd, None) 29 | try: 30 | # Assume it's callable: 31 | meth(session, line) 32 | except TypeError: 33 | # If it isn't, respond to the unknown command: 34 | self.unknown(session, cmd) 35 | 36 | class Room(CommandHandler): 37 | """ 38 | A generic environment that may contain one or more users (sessions). 39 | It takes care of basic command handling and broadcasting. 40 | """ 41 | 42 | def __init__(self, server): 43 | self.server = server 44 | self.sessions = [] 45 | 46 | def add(self, session): 47 | 'A session (user) has entered the room' 48 | self.sessions.append(session) 49 | 50 | def remove(self, session): 51 | 'A session (user) has left the room' 52 | self.sessions.remove(session) 53 | 54 | def broadcast(self, line): 55 | 'Send a line to all sessions in the room' 56 | for session in self.sessions: 57 | session.push(line) 58 | 59 | def do_logout(self, session, line): 60 | 'Respond to the logout command' 61 | raise EndSession 62 | 63 | class LoginRoom(Room): 64 | """ 65 | A room meant for a single person who has just connected. 66 | """ 67 | 68 | def add(self, session): 69 | Room.add(self, session) 70 | # When a user enters, greet him/her: 71 | self.broadcast('Welcome to {}\r\n'.format(self.server.name)) 72 | 73 | def unknown(self, session, cmd): 74 | # All unknown commands (anything except login or logout) 75 | # results in a prodding: 76 | session.push('Please log in\nUse "login "\r\n') 77 | 78 | def do_login(self, session, line): 79 | name = line.strip() 80 | # Make sure the user has entered a name: 81 | if not name: 82 | session.push('Please enter a name\r\n') 83 | # Make sure that the name isn't in use: 84 | elif name in self.server.users: 85 | session.push('The name "{}" is taken.\r\n'.format(name)) 86 | session.push('Please try again.\r\n') 87 | else: 88 | # The name is OK, so it is stored in the session, and 89 | # the user is moved into the main room. session.name = name 90 | session.enter(self.server.main_room) 91 | 92 | class ChatRoom(Room): 93 | """ 94 | A room meant for multiple users who can chat with the others in the room. 95 | """ 96 | 97 | def add(self, session): 98 | # Notify everyone that a new user has entered: 99 | self.broadcast(session.name + ' has entered the room.\r\n') 100 | self.server.users[session.name] = session 101 | super().add(session) 102 | 103 | def remove(self, session): 104 | Room.remove(self, session) 105 | # Notify everyone that a user has left: 106 | self.broadcast(session.name + ' has left the room.\r\n') 107 | 108 | def do_say(self, session, line): 109 | self.broadcast(session.name + ': ' + line + '\r\n') 110 | 111 | def do_look(self, session, line): 112 | 'Handles the look command, used to see who is in a room' 113 | session.push('The following are in this room:\r\n') 114 | for other in self.sessions: 115 | session.push(other.name + '\r\n') 116 | 117 | def do_who(self, session, line): 118 | 'Handles the who command, used to see who is logged in' 119 | session.push('The following are logged in:\r\n') 120 | for name in self.server.users: 121 | session.push(name + '\r\n') 122 | 123 | class LogoutRoom(Room): 124 | """ 125 | A simple room for a single user. Its sole purpose is to remove the 126 | user's name from the server. 127 | """ 128 | 129 | def add(self, session): 130 | # When a session (user) enters the LogoutRoom it is deleted 131 | try: del self.server.users[session.name] 132 | except KeyError: pass 133 | 134 | class ChatSession(async_chat): 135 | """ 136 | A single session, which takes care of the communication with a single user. 137 | """ 138 | 139 | def __init__(self, server, sock): 140 | super().__init__(sock) 141 | self.server = server 142 | self.set_terminator("\r\n") 143 | self.data = [] 144 | self.name = None 145 | # All sessions begin in a separate LoginRoom: 146 | self.enter(LoginRoom(server)) 147 | 148 | def enter(self, room): 149 | # Remove self from current room and add self to 150 | # next room... 151 | try: cur = self.room 152 | except AttributeError: pass 153 | else: cur.remove(self) 154 | self.room = room 155 | room.add(self) 156 | 157 | def collect_incoming_data(self, data): 158 | self.data.append(data) 159 | 160 | def found_terminator(self): 161 | line = ''.join(self.data) 162 | self.data = [] 163 | try: self.room.handle(self, line) 164 | except EndSession: self.handle_close() 165 | 166 | def handle_close(self): 167 | async_chat.handle_close(self) 168 | self.enter(LogoutRoom(self.server)) 169 | 170 | class ChatServer(dispatcher): 171 | """ 172 | A chat server with a single room. 173 | """ 174 | 175 | def __init__(self, port, name): 176 | super().__init__() 177 | self.create_socket(socket.AF_INET, socket.SOCK_STREAM) 178 | self.set_reuse_addr() 179 | self.bind(('', port)) 180 | self.listen(5) 181 | self.name = name 182 | self.users = {} 183 | self.main_room = ChatRoom(self) 184 | 185 | def handle_accept(self): 186 | conn, addr = self.accept() 187 | ChatSession(self, conn) 188 | 189 | if __name__ == '__main__': 190 | s = ChatServer(PORT, NAME) 191 | try: asyncore.loop() 192 | except KeyboardInterrupt: print() -------------------------------------------------------------------------------- /Chapter25/listing25-1.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import cgi 4 | form = cgi.FieldStorage() 5 | 6 | text = form.getvalue('text', open('simple_edit.dat').read()) 7 | f = open('simple_edit.dat', 'w') 8 | f.write(text) 9 | f.close() 10 | 11 | 12 | print("""Content-type: text/html 13 | 14 | 15 | 16 | 17 | A Simple Editor 18 | 19 | 20 |
    21 |
    22 | 23 |
    24 | 25 | 26 | """.format(text)) -------------------------------------------------------------------------------- /Chapter25/listing25-2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | print('Content-type: text/html\n') 4 | 5 | from os.path import join, abspath 6 | import cgi, sys 7 | 8 | BASE_DIR = abspath('data') 9 | 10 | form = cgi.FieldStorage() 11 | filename = form.getvalue('filename') 12 | if not filename: 13 | print('Please enter a file name') 14 | sys.exit() 15 | text = open(join(BASE_DIR, filename)).read() 16 | 17 | print(""" 18 | 19 | 20 | Editing... 21 | 22 | 23 |
    24 | File: {}
    25 | 26 | Password:
    27 |
    28 | Text:
    29 |
    30 | 31 |
    32 | 33 | 34 | """.format(filename, filename, text)) -------------------------------------------------------------------------------- /Chapter25/listing25-3.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | print('Content-type: text/html\n') 4 | 5 | from os.path import join, abspath 6 | from hashlib import sha1 7 | import cgi, sys 8 | 9 | BASE_DIR = abspath('data') 10 | 11 | form = cgi.FieldStorage() 12 | 13 | text = form.getvalue('text') 14 | filename = form.getvalue('filename') 15 | password = form.getvalue('password') 16 | 17 | if not (filename and text and password): 18 | print('Invalid parameters.') 19 | sys.exit() 20 | 21 | if sha1(password.encode()).hexdigest() != '8843d7f92416211de9ebb963ff4ce28125932878': 22 | print('Invalid password') 23 | sys.exit() 24 | 25 | f = open(join(BASE_DIR,filename), 'w') 26 | f.write(text) 27 | f.close() 28 | 29 | print('The file has been saved.') -------------------------------------------------------------------------------- /Chapter26/listing26-1.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE messages ( 2 | id SERIAL PRIMARY KEY, 3 | subject TEXT NOT NULL, 4 | sender TEXT NOT NULL, 5 | reply_to INTEGER REFERENCES messages, 6 | text TEXT NOT NULL 7 | ); -------------------------------------------------------------------------------- /Chapter26/listing26-2.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE messages ( 2 | id INT NOT NULL AUTO_INCREMENT, 3 | subject VARCHAR(100) NOT NULL, 4 | sender VARCHAR(15) NOT NULL, 5 | reply_to INT, 6 | text MEDIUMTEXT NOT NULL, 7 | PRIMARY KEY(id) 8 | ); -------------------------------------------------------------------------------- /Chapter26/listing26-3.sql: -------------------------------------------------------------------------------- 1 | create table messages ( 2 | id integer primary key autoincrement, 3 | subject text not null, 4 | sender text not null, 5 | reply_to int, 6 | text text not null 7 | ); -------------------------------------------------------------------------------- /Chapter26/listing26-4.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | print('Content-type: text/html\n') 4 | 5 | import cgitb; cgitb.enable() 6 | 7 | import psycopg2 8 | conn = psycopg2.connect('user=foo password=bar dbname=baz') 9 | curs = conn.cursor() 10 | 11 | print(""" 12 | 13 | 14 | The FooBar Bulletin Board 15 | 16 | 17 |

    The FooBar Bulletin Board

    18 | """) 19 | 20 | curs.execute('SELECT * FROM messages') 21 | rows = curs.dictfetchall() 22 | 23 | toplevel = [] 24 | children = {} 25 | 26 | for row in rows: 27 | parent_id = row['reply_to'] 28 | if parent_id is None: 29 | toplevel.append(row) 30 | else: 31 | children.setdefault(parent_id, []).append(row) 32 | def format(row): 33 | print(row['subject']) 34 | try: kids = children[row['id']] 35 | except KeyError: pass 36 | else: 37 | print('
    ') 38 | for kid in kids: 39 | format(kid) 40 | print('
    ') 41 | 42 | print('

    ') 43 | 44 | for row in toplevel: 45 | format(row) 46 | 47 | print(""" 48 |

    49 | 50 | 51 | """) -------------------------------------------------------------------------------- /Chapter26/listing26-5.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | print('Content-type: text/html\n') 4 | 5 | import cgitb; cgitb.enable() 6 | 7 | import psycopg2 8 | conn = psycopg2.connect('user=foo password=bar dbname=baz') 9 | curs = conn.cursor() 10 | 11 | print(""" 12 | 13 | 14 | The FooBar Bulletin Board 15 | 16 | 17 |

    The FooBar Bulletin Board

    18 | """) 19 | 20 | curs.execute('SELECT * FROM messages') 21 | rows = curs.dictfetchall() 22 | 23 | toplevel = [] 24 | children = {} 25 | 26 | for row in rows: 27 | parent_id = row['reply_to'] 28 | if parent_id is None: 29 | toplevel.append(row) 30 | else: 31 | children.setdefault(parent_id, []).append(row) 32 | 33 | def format(row): 34 | print('

    {subject}

    '.format(row)) 35 | try: kids = children[row['id']] 36 | except KeyError: pass 37 | else: 38 | print('
    ') 39 | for kid in kids: 40 | format(kid) 41 | print('
    ') 42 | print('

    ') 43 | 44 | for row in toplevel: 45 | format(row) 46 | 47 | print(""" 48 |

    49 |
    50 |

    Post message

    51 | 52 | 53 | """) -------------------------------------------------------------------------------- /Chapter26/listing26-6.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | print('Content-type: text/html\n') 4 | 5 | import cgitb; cgitb.enable() 6 | 7 | import psycopg2 8 | conn = psycopg2.connect('user=foo password=bar dbname=baz') 9 | curs = conn.cursor() 10 | 11 | import cgi, sys 12 | form = cgi.FieldStorage() 13 | id = form.getvalue('id') 14 | 15 | print(""" 16 | 17 | 18 | View Message 19 | 20 | 21 |

    View Message

    22 | """) 23 | 24 | try: id = int(id) 25 | except: 26 | print('Invalid message ID') 27 | sys.exit() 28 | 29 | curs.execute('SELECT * FROM messages WHERE id = %s', (format(id),)) 30 | rows = curs.dictfetchall() 31 | 32 | if not rows: 33 | print('Unknown message ID') 34 | sys.exit() 35 | 36 | row = rows[0] 37 | 38 | print(""" 39 |

    Subject: {subject}
    40 | Sender: {sender}
    41 |

    {text}
    42 |

    43 |
    44 | Back to the main page 45 | | Reply 46 | 47 | 48 | """.format(row)) -------------------------------------------------------------------------------- /Chapter26/listing26-7.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | print('Content-type: text/html\n') 4 | 5 | import cgitb; cgitb.enable() 6 | 7 | import psycopg2 8 | conn = psycopg2.connect('user=foo password=bar dbname=baz') 9 | curs = conn.cursor() 10 | 11 | import cgi, sys 12 | form = cgi.FieldStorage() 13 | reply_to = form.getvalue('reply_to') 14 | 15 | print(""" 16 | 17 | 18 | Compose Message 19 | 20 | 21 |

    Compose Message

    22 | 23 |
    24 | """) 25 | 26 | subject = '' 27 | if reply_to is not None: 28 | print(''.format(reply_to)) 29 | curs.execute('SELECT subject FROM messages WHERE id = %s', (format(reply_to),)) 30 | subject = curs.fetchone()[0] 31 | if not subject.startswith('Re: '): 32 | subject = 'Re: ' + subject 33 | 34 | print(""" 35 | Subject:
    36 |
    37 | Sender:
    38 |
    39 | Message:
    40 |
    41 | 42 |
    43 |
    44 | Back to the main page' 45 | 46 | 47 | """.format(subject)) -------------------------------------------------------------------------------- /Chapter26/listing26-8.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | print('Content-type: text/html\n') 4 | 5 | import cgitb; cgitb.enable() 6 | 7 | import psycopg2 8 | conn = psycopg2.connect('user=foo password=bar dbname=baz') 9 | curs = conn.cursor() 10 | 11 | import cgi, sys 12 | form = cgi.FieldStorage() 13 | 14 | sender = form.getvalue('sender') 15 | subject = form.getvalue('subject') 16 | text = form.getvalue('text') 17 | reply_to = form.getvalue('reply_to') 18 | 19 | if not (sender and subject and text): 20 | print('Please supply sender, subject, and text') 21 | sys.exit() 22 | 23 | if reply_to is not None: 24 | query = (""" 25 | INSERT INTO messages(reply_to, sender, subject, text) 26 | VALUES(%s, '%s', '%s', '%s')""", (int(reply_to), sender, subject, text)) 27 | else: 28 | query = (""" 29 | INSERT INTO messages(sender, subject, text) 30 | VALUES('%s', '%s', '%s')""", (sender, subject, text)) 31 | 32 | curs.execute(*query) 33 | conn.commit() 34 | 35 | print(""" 36 | 37 | 38 | 39 | Message Saved 40 | 41 | 42 |

    Message Saved

    43 |
    44 | Back to the main page 45 | 46 | s 47 | """) -------------------------------------------------------------------------------- /Chapter27/listing27-1.py: -------------------------------------------------------------------------------- 1 | from xmlrpc.client import ServerProxy 2 | from os.path import join, isfile 3 | from xmlrpc.server import SimpleXMLRPCServer 4 | from urllib.parse import urlparse 5 | import sys 6 | 7 | MAX_HISTORY_LENGTH = 6 8 | 9 | OK = 1 10 | FAIL = 2 11 | EMPTY = '' 12 | 13 | def get_port(url): 14 | 'Extracts the port from a URL' 15 | name = urlparse(url)[1] 16 | parts = name.split(':') 17 | return int(parts[-1]) 18 | 19 | 20 | class Node: 21 | """ 22 | A node in a peer-to-peer network. 23 | """ 24 | def __init__(self, url, dirname, secret): 25 | self.url = url 26 | self.dirname = dirname 27 | self.secret = secret 28 | self.known = set() 29 | 30 | def query(self, query, history=[]): 31 | """ 32 | Performs a query for a file, possibly asking other known Nodes for 33 | help. Returns the file as a string. 34 | """ 35 | code, data = self._handle(query) 36 | if code == OK: 37 | return code, data 38 | else: 39 | history = history + [self.url] 40 | if len(history) >= MAX_HISTORY_LENGTH: 41 | return FAIL, EMPTY 42 | return self._broadcast(query, history) 43 | 44 | def hello(self, other): 45 | """ 46 | Used to introduce the Node to other Nodes. 47 | """ 48 | self.known.add(other) 49 | return OK 50 | 51 | def fetch(self, query, secret): 52 | """ 53 | Used to make the Node find a file and download it. 54 | """ 55 | if secret != self.secret: return FAIL 56 | code, data = self.query(query) 57 | if code == OK: 58 | f = open(join(self.dirname, query), 'w') 59 | f.write(data) 60 | f.close() 61 | return OK 62 | else: 63 | return FAIL 64 | 65 | def _start(self): 66 | """ 67 | Used internally to start the XML-RPC server. 68 | """ 69 | s = SimpleXMLRPCServer(("", get_port(self.url)), logRequests=False) 70 | s.register_instance(self) 71 | s.serve_forever() 72 | 73 | def _handle(self, query): 74 | """ 75 | Used internally to handle queries. 76 | """ 77 | dir = self.dirname 78 | name = join(dir, query) 79 | if not isfile(name): return FAIL, EMPTY 80 | return OK, open(name).read() 81 | 82 | def _broadcast(self, query, history): 83 | """ 84 | Used internally to broadcast a query to all known Nodes. 85 | """ 86 | for other in self.known.copy(): 87 | if other in history: continue 88 | try: 89 | s = ServerProxy(other) 90 | code, data = s.query(query, history) 91 | if code == OK: 92 | return code, data 93 | except: 94 | self.known.remove(other) 95 | return FAIL, EMPTY 96 | 97 | def main(): 98 | url, directory, secret = sys.argv[1:] 99 | n = Node(url, directory, secret) 100 | n._start() 101 | 102 | if __name__ == '__main__': main() -------------------------------------------------------------------------------- /Chapter27/listing27-2.py: -------------------------------------------------------------------------------- 1 | from xmlrpc.client import ServerProxy, Fault 2 | from os.path import join, abspath, isfile 3 | from xmlrpc.server import SimpleXMLRPCServer 4 | from urllib.parse import urlparse 5 | import sys 6 | 7 | SimpleXMLRPCServer.allow_reuse_address = 1 8 | 9 | MAX_HISTORY_LENGTH = 6 10 | 11 | UNHANDLED = 100 12 | ACCESS_DENIED = 200 13 | 14 | class UnhandledQuery(Fault): 15 | """ 16 | An exception that represents an unhandled query. 17 | """ 18 | def __init__(self, message="Couldn't handle the query"): 19 | super().__init__(UNHANDLED, message) 20 | 21 | class AccessDenied(Fault): 22 | """ 23 | An exception that is raised if a user tries to access a 24 | resource for which he or she is not authorized. 25 | """ 26 | def __init__(self, message="Access denied"): 27 | super().__init__(ACCESS_DENIED, message) 28 | 29 | def inside(dir, name): 30 | """ 31 | Checks whether a given file name lies within a given directory. 32 | """ 33 | dir = abspath(dir) 34 | name = abspath(name) 35 | return name.startswith(join(dir, '')) 36 | 37 | def get_port(url): 38 | """ 39 | Extracts the port number from a URL. 40 | """ 41 | name = urlparse(url)[1] 42 | parts = name.split(':') 43 | return int(parts[-1]) 44 | 45 | class Node: 46 | """ 47 | A node in a peer-to-peer network. 48 | """ 49 | def __init__(self, url, dirname, secret): 50 | self.url = url 51 | self.dirname = dirname 52 | self.secret = secret 53 | self.known = set() 54 | 55 | def query(self, query, history=[]): 56 | """ 57 | Performs a query for a file, possibly asking other known Nodes for 58 | help. Returns the file as a string. 59 | """ 60 | try: 61 | return self._handle(query) 62 | except UnhandledQuery: 63 | history = history + [self.url] 64 | if len(history) >= MAX_HISTORY_LENGTH: raise 65 | return self._broadcast(query, history) 66 | 67 | def hello(self, other): 68 | """ 69 | Used to introduce the Node to other Nodes. 70 | """ 71 | self.known.add(other) 72 | return 0 73 | 74 | def fetch(self, query, secret): 75 | """ 76 | Used to make the Node find a file and download it. 77 | """ 78 | if secret != self.secret: raise AccessDenied 79 | result = self.query(query) 80 | f = open(join(self.dirname, query), 'w') 81 | f.write(result) 82 | f.close() 83 | return 0 84 | 85 | def _start(self): 86 | """ 87 | Used internally to start the XML-RPC server. 88 | """ 89 | s = SimpleXMLRPCServer(("", get_port(self.url)), logRequests=False) 90 | s.register_instance(self) 91 | s.serve_forever() 92 | 93 | def _handle(self, query): 94 | """ 95 | Used internally to handle queries. 96 | """ 97 | dir = self.dirname 98 | name = join(dir, query) 99 | if not isfile(name): raise UnhandledQuery 100 | if not inside(dir, name): raise AccessDenied 101 | return open(name).read() 102 | 103 | def _broadcast(self, query, history): 104 | """ 105 | Used internally to broadcast a query to all known Nodes. 106 | """ 107 | for other in self.known.copy(): 108 | if other in history: continue 109 | try: 110 | s = ServerProxy(other) 111 | return s.query(query, history) 112 | except Fault as f: 113 | if f.faultCode == UNHANDLED: pass 114 | else: self.known.remove(other) 115 | except: 116 | self.known.remove(other) 117 | raise UnhandledQuery 118 | 119 | def main(): 120 | url, directory, secret = sys.argv[1:] 121 | n = Node(url, directory, secret) 122 | n._start() 123 | 124 | if __name__ == '__main__': main() -------------------------------------------------------------------------------- /Chapter27/listing27-3.py: -------------------------------------------------------------------------------- 1 | from xmlrpc.client import ServerProxy, Fault 2 | from cmd import Cmd 3 | from random import choice 4 | from string import ascii_lowercase 5 | from server import Node, UNHANDLED 6 | from threading import Thread 7 | from time import sleep 8 | import sys 9 | 10 | HEAD_START = 0.1 # Seconds 11 | SECRET_LENGTH = 100 12 | 13 | def random_string(length): 14 | """ 15 | Returns a random string of letters with the given length. 16 | """ 17 | chars = [] 18 | letters = ascii_lowercase[:26] 19 | 20 | while length > 0: 21 | length -= 1 22 | chars.append(choice(letters)) 23 | return ''.join(chars) 24 | 25 | class Client(Cmd): 26 | """ 27 | A simple text-based interface to the Node class. 28 | """ 29 | 30 | prompt = '> ' 31 | 32 | def __init__(self, url, dirname, urlfile): 33 | """ 34 | Sets the url, dirname, and urlfile, and starts the Node 35 | Server in a separate thread. 36 | """ 37 | Cmd.__init__(self) 38 | self.secret = random_string(SECRET_LENGTH) 39 | n = Node(url, dirname, self.secret) 40 | t = Thread(target=n._start) 41 | t.setDaemon(1) 42 | t.start() 43 | # Give the server a head start: 44 | sleep(HEAD_START) 45 | self.server = ServerProxy(url) 46 | for line in open(urlfile): 47 | line = line.strip() 48 | self.server.hello(line) 49 | 50 | def do_fetch(self, arg): 51 | "Call the fetch method of the Server." 52 | try: 53 | self.server.fetch(arg, self.secret) 54 | except Fault as f: 55 | if f.faultCode != UNHANDLED: raise 56 | print("Couldn't find the file", arg) 57 | 58 | def do_exit(self, arg): 59 | "Exit the program." 60 | print() 61 | sys.exit() 62 | 63 | do_EOF = do_exit # End-Of-File is synonymous with 'exit' 64 | 65 | def main(): 66 | urlfile, directory, url = sys.argv[1:] 67 | client = Client(url, directory, urlfile) 68 | client.cmdloop() 69 | 70 | if __name__ == '__main__': main() -------------------------------------------------------------------------------- /Chapter28/listing28-1.py: -------------------------------------------------------------------------------- 1 | from xmlrpc.client import ServerProxy, Fault 2 | from server import Node, UNHANDLED 3 | from client import random_string 4 | from threading import Thread 5 | from time import sleep 6 | from os import listdir 7 | import sys 8 | import tkinter as tk 9 | 10 | HEAD_START = 0.1 # Seconds 11 | SECRET_LENGTH = 100 12 | 13 | class Client(tk.Frame): 14 | 15 | def __init__(self, master, url, dirname, urlfile): 16 | super().__init__(master) 17 | self.node_setup(url, dirname, urlfile) 18 | self.pack() 19 | self.create_widgets() 20 | 21 | def node_setup(self, url, dirname, urlfile): 22 | self.secret = random_string(SECRET_LENGTH) 23 | n = Node(url, dirname, self.secret) 24 | t = Thread(target=n._start) 25 | t.setDaemon(1) 26 | t.start() 27 | # Give the server a head start: 28 | sleep(HEAD_START) 29 | self.server = ServerProxy(url) 30 | for line in open(urlfile): 31 | line = line.strip() 32 | self.server.hello(line) 33 | 34 | def create_widgets(self): 35 | self.input = input = tk.Entry(self) 36 | input.pack(side='left') 37 | 38 | self.submit = submit = tk.Button(self) 39 | submit['text'] = "Fetch" 40 | submit['command'] = self.fetch_handler 41 | submit.pack() 42 | 43 | def fetch_handler(self): 44 | query = self.input.get() 45 | try: 46 | self.server.fetch(query, self.secret) 47 | except Fault as f: 48 | if f.faultCode != UNHANDLED: raise 49 | print("Couldn't find the file", query) 50 | 51 | def main(): 52 | urlfile, directory, url = sys.argv[1:] 53 | 54 | root = tk.Tk() 55 | root.title("File Sharing Client") 56 | 57 | client = Client(root, url, directory, urlfile) 58 | client.mainloop() 59 | 60 | if __name__ == "__main__": main() -------------------------------------------------------------------------------- /Chapter28/listing28-2.py: -------------------------------------------------------------------------------- 1 | from xmlrpc.client import ServerProxy, Fault 2 | from server import Node, UNHANDLED 3 | from client import random_string 4 | from threading import Thread 5 | from time import sleep 6 | from os import listdir 7 | import sys 8 | import tkinter as tk 9 | 10 | HEAD_START = 0.1 # Seconds 11 | SECRET_LENGTH = 100 12 | 13 | class ListableNode(Node): 14 | 15 | def list(self): 16 | return listdir(self.dirname) 17 | 18 | class Client(tk.Frame): 19 | 20 | def __init__(self, master, url, dirname, urlfile): 21 | super().__init__(master) 22 | self.node_setup(url, dirname, urlfile) 23 | self.pack() 24 | self.create_widgets() 25 | 26 | def node_setup(self, url, dirname, urlfile): 27 | self.secret = random_string(SECRET_LENGTH) 28 | n = ListableNode(url, dirname, self.secret) 29 | t = Thread(target=n._start) 30 | t.setDaemon(1) 31 | t.start() 32 | # Give the server a head start: 33 | sleep(HEAD_START) 34 | self.server = ServerProxy(url) 35 | for line in open(urlfile): 36 | line = line.strip() 37 | self.server.hello(line) 38 | 39 | def create_widgets(self): 40 | self.input = input = tk.Entry(self) 41 | input.pack(side='left') 42 | 43 | self.submit = submit = tk.Button(self) 44 | submit['text'] = "Fetch" 45 | submit['command'] = self.fetch_handler 46 | submit.pack() 47 | 48 | self.files = files = tk.Listbox() 49 | files.pack(side='bottom', expand=True, fill=tk.BOTH) 50 | self.update_list() 51 | 52 | def fetch_handler(self): 53 | query = self.input.get() 54 | try: 55 | self.server.fetch(query, self.secret) 56 | self.update_list() 57 | except Fault as f: 58 | if f.faultCode != UNHANDLED: raise 59 | print("Couldn't find the file", query) 60 | 61 | def update_list(self): 62 | self.files.delete(0, tk.END) 63 | self.files.insert(tk.END, self.server.list()) 64 | 65 | def main(): 66 | urlfile, directory, url = sys.argv[1:] 67 | 68 | root = tk.Tk() 69 | root.title("File Sharing Client") 70 | 71 | client = Client(root, url, directory, urlfile) 72 | client.mainloop() 73 | 74 | if __name__ == '__main__': main() -------------------------------------------------------------------------------- /Chapter29/listing29-1.py: -------------------------------------------------------------------------------- 1 | import sys, pygame 2 | from pygame.locals import * 3 | from random import randrange 4 | 5 | class Weight(pygame.sprite.Sprite): 6 | 7 | def __init__(self, speed): 8 | pygame.sprite.Sprite.__init__(self) 9 | self.speed = speed 10 | # image and rect used when drawing sprite: 11 | self.image = weight_image 12 | self.rect = self.image.get_rect() 13 | self.reset() 14 | 15 | def reset(self): 16 | """ 17 | Move the weight to a random position at the top of the screen. 18 | """ 19 | self.rect.top = -self.rect.height 20 | self.rect.centerx = randrange(screen_size[0]) 21 | 22 | def update(self): 23 | """ 24 | Update the weight for display in the next frame. 25 | """ 26 | self.rect.top += self.speed 27 | 28 | if self.rect.top > screen_size[1]: 29 | self.reset() 30 | 31 | # Initialize things 32 | pygame.init() 33 | screen_size = 800, 600 34 | pygame.display.set_mode(screen_size, FULLSCREEN) 35 | pygame.mouse.set_visible(0) 36 | 37 | # Load the weight image 38 | weight_image = pygame.image.load('weight.png') 39 | weight_image = weight_image.convert() # ... to match the display 40 | 41 | # You might want a different speed, of courase 42 | speed = 5 43 | 44 | # Create a sprite group and add a Weight 45 | sprites = pygame.sprite.RenderUpdates() 46 | sprites.add(Weight(speed)) 47 | 48 | # Get the screen surface and fill it 49 | screen = pygame.display.get_surface() 50 | bg = (255, 255, 255) # White 51 | screen.fill(bg) 52 | pygame.display.flip() 53 | 54 | # Used to erase the sprites: 55 | def clear_callback(surf, rect): 56 | surf.fill(bg, rect) 57 | 58 | while True: 59 | # Check for quit events: 60 | for event in pygame.event.get(): 61 | if event.type == QUIT: 62 | sys.exit() 63 | if event.type == KEYDOWN and event.key == K_ESCAPE: 64 | sys.exit() 65 | # Erase previous positions: 66 | sprites.clear(screen, clear_callback) 67 | # Update all sprites: 68 | sprites.update() 69 | # Draw all sprites: 70 | updates = sprites.draw(screen) 71 | # Update the necessary parts of the display: 72 | pygame.display.update(updates) -------------------------------------------------------------------------------- /Chapter29/listing29-2.py: -------------------------------------------------------------------------------- 1 | # Configuration file for Squish 2 | # ----------------------------- 3 | 4 | # Feel free to modify the configuration variables below to taste. 5 | # If the game is too fast or too slow, try to modify the speed 6 | # variables. 7 | 8 | # Change these to use other images in the game: 9 | banana_image = 'banana.png' 10 | weight_image = 'weight.png' 11 | splash_image = 'weight.png' 12 | 13 | # Change these to affect the general appearance: 14 | screen_size = 800, 600 15 | background_color = 255, 255, 255 16 | margin = 30 17 | full_screen = 1 18 | font_size = 48 19 | 20 | # These affect the behavior of the game: 21 | drop_speed = 1 22 | banana_speed = 10 23 | speed_increase = 1 24 | weights_per_level = 10 25 | banana_pad_top = 40 26 | banana_pad_side = 20 -------------------------------------------------------------------------------- /Chapter29/listing29-3.py: -------------------------------------------------------------------------------- 1 | import pygame, config, os 2 | from random import randrange 3 | 4 | "This module contains the game objects of the Squish game." 5 | 6 | class SquishSprite(pygame.sprite.Sprite): 7 | 8 | """ 9 | Generic superclass for all sprites in Squish. The constructor 10 | takes care of loading an image, setting up the sprite rect, and 11 | the area within which it is allowed to move. That area is governed 12 | by the screen size and the margin. 13 | """ 14 | 15 | def __init__(self, image): 16 | super().__init__() 17 | self.image = pygame.image.load(image).convert() 18 | self.rect = self.image.get_rect() 19 | screen = pygame.display.get_surface() 20 | shrink = -config.margin * 2 21 | self.area = screen.get_rect().inflate(shrink, shrink) 22 | 23 | class Weight(SquishSprite): 24 | 25 | """ 26 | A falling weight. It uses the SquishSprite constructor to set up 27 | its weight image, and will fall with a speed given as a parameter 28 | to its constructor. 29 | """ 30 | 31 | def __init__(self, speed): 32 | super().__init__(config.weight_image) 33 | self.speed = speed 34 | self.reset() 35 | 36 | def reset(self): 37 | """ 38 | Move the weight to the top of the screen (just out of sight) 39 | and place it at a random horizontal position. 40 | """ 41 | x = randrange(self.area.left, self.area.right) 42 | self.rect.midbottom = x, 0 43 | 44 | def update(self): 45 | """ 46 | Move the weight vertically (downwards) a distance 47 | corresponding to its speed. Also set the landed attribute 48 | according to whether it has reached the bottom of the screen. 49 | """ 50 | self.rect.top += self.speed 51 | self.landed = self.rect.top >= self.area.bottom 52 | 53 | class Banana(SquishSprite): 54 | 55 | """ 56 | A desperate banana. It uses the SquishSprite constructor to set up 57 | its banana image, and will stay near the bottom of the screen, 58 | with its horizontal position governed by the current mouse 59 | position (within certain limits). 60 | """ 61 | 62 | def __init__(self): 63 | super().__init__(config.banana_image) 64 | self.rect.bottom = self.area.bottom 65 | # These paddings represent parts of the image where there is 66 | # no banana. If a weight moves into these areas, it doesn't 67 | # constitute a hit (or, rather, a squish): 68 | self.pad_top = config.banana_pad_top 69 | self.pad_side = config.banana_pad_side 70 | 71 | def update(self): 72 | """ 73 | Set the Banana's center x-coordinate to the current mouse 74 | x-coordinate, and then use the rect method clamp to ensure 75 | that the Banana stays within its allowed range of motion. 76 | """ 77 | self.rect.centerx = pygame.mouse.get_pos()[0] 78 | self.rect = self.rect.clamp(self.area) 79 | 80 | def touches(self, other): 81 | """ 82 | Determines whether the banana touches another sprite (e.g., a 83 | Weight). Instead of just using the rect method colliderect, a 84 | new rectangle is first calculated (using the rect method 85 | inflate with the side and top paddings) that does not include 86 | the 'empty' areas on the top and sides of the banana. 87 | """ 88 | # Deflate the bounds with the proper padding: 89 | bounds = self.rect.inflate(-self.pad_side, -self.pad_top) 90 | # Move the bounds so they are placed at the bottom of the Banana: 91 | bounds.bottom = self.rect.bottom 92 | # Check whether the bounds intersect with the other object's rect: 93 | return bounds.colliderect(other.rect) -------------------------------------------------------------------------------- /Chapter29/listing29-4.py: -------------------------------------------------------------------------------- 1 | import os, sys, pygame 2 | from pygame.locals import * 3 | import objects, config 4 | 5 | "This module contains the main game logic of the Squish game." 6 | 7 | class State: 8 | 9 | """ 10 | A generic game state class that can handle events and display 11 | itself on a given surface. 12 | """ 13 | 14 | def handle(self, event): 15 | """ 16 | Default event handling only deals with quitting. 17 | """ 18 | if event.type == QUIT: 19 | sys.exit() 20 | if event.type == KEYDOWN and event.key == K_ESCAPE: 21 | sys.exit() 22 | 23 | def first_display(self, screen): 24 | """ 25 | Used to display the State for the first time. Fills the screen 26 | with the background color. 27 | """ 28 | screen.fill(config.background_color) 29 | # Remember to call flip, to make the changes visible: 30 | pygame.display.flip() 31 | 32 | def display(self, screen): 33 | """ 34 | Used to display the State after it has already been displayed 35 | once. The default behavior is to do nothing. 36 | """ 37 | pass 38 | 39 | 40 | class Level(State): 41 | """ 42 | A game level. Takes care of counting how many weights have been 43 | dropped, moving the sprites around, and other tasks relating to 44 | game logic. 45 | """ 46 | 47 | def __init__(self, number=1): 48 | self.number = number 49 | # How many weights remain to dodge in this level? 50 | self.remaining = config.weights_per_level 51 | 52 | speed = config.drop_speed 53 | # One speed_increase added for each level above 1: 54 | speed += (self.number-1) * config.speed_increase 55 | # Create the weight and banana: 56 | self.weight = objects.Weight(speed) 57 | self.banana = objects.Banana() 58 | both = self.weight, self.banana # This could contain more sprites... 59 | self.sprites = pygame.sprite.RenderUpdates(both) 60 | 61 | def update(self, game): 62 | "Updates the game state from the previous frame." 63 | # Update all sprites: 64 | self.sprites.update() 65 | # If the banana touches the weight, tell the game to switch to 66 | # a GameOver state: 67 | if self.banana.touches(self.weight): 68 | game.next_state = GameOver() 69 | # Otherwise, if the weight has landed, reset it. If all the 70 | # weights of this level have been dodged, tell the game to 71 | # switch to a LevelCleared state: 72 | elif self.weight.landed: 73 | self.weight.reset() 74 | self.remaining -= 1 75 | if self.remaining == 0: 76 | game.next_state = LevelCleared(self.number) 77 | 78 | def display(self, screen): 79 | """ 80 | Displays the state after the first display (which simply wipes 81 | the screen). As opposed to firstDisplay, this method uses 82 | pygame.display.update with a list of rectangles that need to 83 | be updated, supplied from self.sprites.draw. 84 | """ 85 | screen.fill(config.background_color) 86 | updates = self.sprites.draw(screen) 87 | pygame.display.update(updates) 88 | 89 | 90 | class Paused(State): 91 | """ 92 | A simple, paused game state, which may be broken out of by pressing 93 | either a keyboard key or the mouse button. 94 | """ 95 | 96 | finished = 0 # Has the user ended the pause? 97 | image = None # Set this to a file name if you want an image 98 | text = '' # Set this to some informative text 99 | 100 | def handle(self, event): 101 | """ 102 | Handles events by delegating to State (which handles quitting 103 | in general) and by reacting to key presses and mouse 104 | clicks. If a key is pressed or the mouse is clicked, 105 | self.finished is set to true. 106 | """ 107 | State.handle(self, event) 108 | if event.type in [MOUSEBUTTONDOWN, KEYDOWN]: 109 | self.finished = 1 110 | 111 | def update(self, game): 112 | """ 113 | Update the level. If a key has been pressed or the mouse has 114 | been clicked (i.e., self.finished is true), tell the game to 115 | move to the state represented by self.next_state() (should be 116 | implemented by subclasses). 117 | """ 118 | if self.finished: 119 | game.next_state = self.next_state() 120 | 121 | def first_display(self, screen): 122 | """ 123 | The first time the Paused state is displayed, draw the image 124 | (if any) and render the text. 125 | """ 126 | # First, clear the screen by filling it with the background color: 127 | screen.fill(config.background_color) 128 | 129 | # Create a Font object with the default appearance, and specified size: 130 | font = pygame.font.Font(None, config.font_size) 131 | 132 | # Get the lines of text in self.text, ignoring empty lines at 133 | # the top or bottom: 134 | lines = self.text.strip().splitlines() 135 | 136 | # Calculate the height of the text (using font.get_linesize() 137 | # to get the height of each line of text): 138 | height = len(lines) * font.get_linesize() 139 | 140 | # Calculate the placement of the text (centered on the screen): 141 | center, top = screen.get_rect().center 142 | top -= height // 2 143 | 144 | # If there is an image to display... 145 | if self.image: 146 | # load it: 147 | image = pygame.image.load(self.image).convert() 148 | # get its rect: 149 | r = image.get_rect() 150 | # move the text down by half the image height: 151 | top += r.height // 2 152 | # place the image 20 pixels above the text: 153 | r.midbottom = center, top - 20 154 | # blit the image to the screen: 155 | screen.blit(image, r) 156 | 157 | antialias = 1 # Smooth the text 158 | black = 0, 0, 0 # Render it as black 159 | 160 | # Render all the lines, starting at the calculated top, and 161 | # move down font.get_linesize() pixels for each line: 162 | for line in lines: 163 | text = font.render(line.strip(), antialias, black) 164 | r = text.get_rect() 165 | r.midtop = center, top 166 | screen.blit(text, r) 167 | top += font.get_linesize() 168 | 169 | # Display all the changes: 170 | pygame.display.flip() 171 | 172 | 173 | class Info(Paused): 174 | 175 | """ 176 | A simple paused state that displays some information about the 177 | game. It is followed by a Level state (the first level). 178 | """ 179 | 180 | next_state = Level 181 | text = ''' 182 | In this game you are a banana, 183 | trying to survive a course in 184 | self-defense against fruit, where the 185 | participants will "defend" themselves 186 | against you with a 16 ton weight.''' 187 | 188 | class StartUp(Paused): 189 | 190 | """ 191 | A paused state that displays a splash image and a welcome 192 | message. It is followed by an Info state. 193 | """ 194 | 195 | next_state = Info 196 | image = config.splash_image 197 | text = ''' 198 | Welcome to Squish, 199 | the game of Fruit Self-Defense''' 200 | 201 | 202 | class LevelCleared(Paused): 203 | """ 204 | A paused state that informs the user that he or she has cleared a 205 | given level. It is followed by the next level state. 206 | """ 207 | 208 | def __init__(self, number): 209 | self.number = number 210 | self.text = '''Level {} cleared 211 | Click to start next level'''.format(self.number) 212 | 213 | def next_state(self): 214 | return Level(self.number + 1) 215 | 216 | class GameOver(Paused): 217 | 218 | """ 219 | A state that informs the user that he or she has lost the 220 | game. It is followed by the first level. 221 | """ 222 | 223 | next_state = Level 224 | text = ''' 225 | Game Over 226 | Click to Restart, Esc to Quit''' 227 | 228 | class Game: 229 | 230 | """ 231 | A game object that takes care of the main event loop, including 232 | changing between the different game states. 233 | """ 234 | 235 | def __init__(self, *args): 236 | # Get the directory where the game and the images are located: 237 | path = os.path.abspath(args[0]) 238 | dir = os.path.split(path)[0] 239 | # Move to that directory (so that the image files may be 240 | # opened later on): 241 | os.chdir(dir) 242 | # Start with no state: 243 | self.state = None 244 | # Move to StartUp in the first event loop iteration: 245 | self.next_state = StartUp() 246 | 247 | def run(self): 248 | """ 249 | This method sets things in motion. It performs some vital 250 | initialization tasks, and enters the main event loop. 251 | """ 252 | pygame.init() # This is needed to initialize all the pygame modules 253 | 254 | # Decide whether to display the game in a window or to use the 255 | # full screen: 256 | flag = 0 # Default (window) mode 257 | 258 | if config.full_screen: 259 | flag = FULLSCREEN # Full screen mode 260 | screen_size = config.screen_size 261 | screen = pygame.display.set_mode(screen_size, flag) 262 | 263 | pygame.display.set_caption('Fruit Self Defense') 264 | pygame.mouse.set_visible(False) 265 | 266 | # The main loop: 267 | while True: 268 | # (1) If nextState has been changed, move to the new state, and 269 | # display it (for the first time): 270 | if self.state != self.next_state: 271 | self.state = self.next_state 272 | self.state.first_display(screen) 273 | # (2) Delegate the event handling to the current state: 274 | for event in pygame.event.get(): 275 | self.state.handle(event) 276 | # (3) Update the current state: 277 | self.state.update(self) 278 | # (4) Display the current state: 279 | self.state.display(screen) 280 | 281 | if __name__ == '__main__': 282 | game = Game(*sys.argv) 283 | game.run() 284 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/beginning-python-3ed/bcff4b8f3ad2345eff8494cf955f656112e3c4cc/LICENSE.txt -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #Apress Source Code 2 | 3 | This repository accompanies [*Beginning Python*](http://www.apress.com/9781484200292) by Magnus Lie Hetland (Apress, 2017). 4 | 5 | ![Cover image](9781484200292.jpg) 6 | 7 | Download the files as a zip using the green button, or clone the repository to your machine using Git. 8 | 9 | ##Releases 10 | 11 | Release v1.0 corresponds to the code in the published book, without corrections or updates. 12 | 13 | ##Contributions 14 | 15 | See the file Contributing.md for more information on how you can contribute to this repository. 16 | -------------------------------------------------------------------------------- /README.txt: -------------------------------------------------------------------------------- 1 | This is the source code for Beginning Python : From Novice to Professional, 2 | third edition. Each code listing in the book can be found in a correspondingly 3 | numbered directory/file. In order to work properly together, some of these may 4 | need to be renamed (as indicated by the book). 5 | 6 | Chapter02/listing2-1.py: Indexing Example 7 | Chapter02/listing2-2.py: Slicing Example 8 | Chapter02/listing2-3.py: Sequence (String) Multiplication Example 9 | Chapter02/listing2-4.py: Sequence Membership Example 10 | Chapter03/listing3-1.py: String Formatting Example 11 | Chapter04/listing4-1.py: Dictionary Example 12 | Chapter04/listing4-2.py: Dictionary Method Example 13 | Chapter10/listing10-1.py: A Simple Module 14 | Chapter10/listing10-2.py: A Simple Module Containing a Function 15 | Chapter10/listing10-3.py: A Simple Module with Some Problematic Test Code 16 | Chapter10/listing10-4.py: A Module with Conditional Test Code 17 | Chapter10/listing10-5.py: Reversing and Printing Command-Line Arguments 18 | Chapter10/listing10-6.py: Adding Line Numbers to a Python Script 19 | Chapter10/listing10-8.py: A Simple Database Application 20 | Chapter10/listing10-10.py: A Program for Finding the Sender of an E-mail 21 | Chapter10/listing10-11.py: A Template System 22 | Chapter10/listing10-12.txt: A Simple Template Example 23 | Chapter10/listing10-13.txt: Some Template Definitions 24 | Chapter10/listing10-14.txt: A Template 25 | Chapter11/listing11-1.py: Simple Script That Counts the Words in sys.stdin 26 | Chapter11/listing11-2.txt: A File Containing Some Nonsensical Text 27 | Chapter11/listing11-3.txt: A Simple Text File 28 | Chapter11/listing11-4.txt: The Modified Text File 29 | Chapter11/listing11-5.txt: The Text File, Modified Again 30 | Chapter11/listing11-6.py: Looping over Characters with read 31 | Chapter11/listing11-7.py: Writing the Loop Differently 32 | Chapter11/listing11-8.py: Using readline in a while Loop 33 | Chapter11/listing11-9.py: Iterating over Characters with read 34 | Chapter11/listing11-10.py: Iterating over Lines with readlines 35 | Chapter11/listing11-11.py: Iterating over Lines with fileinput 36 | Chapter11/listing11-12.py: Iterating over a File 37 | Chapter11/listing11-13.py: Iterating over a File Without Storing the File Object in a Variable 38 | Chapter12/listing12-1.py: Simple GUI Text Editor 39 | Chapter13/listing13-1.py: Importing Data into the Database (importdata.py) 40 | Chapter13/listing13-2.py: Food Database Query Program (food_query.py) 41 | Chapter14/listing14-1.py: A Minimal Server 42 | Chapter14/listing14-2.py: A Minimal Client 43 | Chapter14/listing14-3.py: A SocketServer-Based Minimal Server 44 | Chapter14/listing14-4.py: A Forking Server 45 | Chapter14/listing14-5.py: A Threading Server 46 | Chapter14/listing14-6.py: A Simple Server Using select 47 | Chapter14/listing14-7.py: A Simple Server Using poll 48 | Chapter14/listing14-8.py: A Simple Server Using Twisted 49 | Chapter14/listing14-9.py: An Improved Logging Server, Using the LineReceiver Protocol 50 | Chapter15/listing15-1.py: A Simple Screen Scraping Program 51 | Chapter15/listing15-2.py: A Screen Scraping Program Using the html.parser Module 52 | Chapter15/listing15-3.py: A Screen Scraping Program Using Beautiful Soup 53 | Chapter15/listing15-4.py: A Simple CGI Script 54 | Chapter15/listing15-5.py: A CGI Script That Invokes a Traceback (faulty.cgi) 55 | Chapter15/listing15-6.py: A CGI Script That Retrieves a Single Value from a FieldStorage (simple2.cgi) 56 | Chapter15/listing15-7.py: A Greeting Script with an HTML Form (simple3.cgi) 57 | Chapter16/listing16-1.py: A Simple Test Program 58 | Chapter16/listing16-2.py: A Simple Test Using the unittest Framework 59 | Chapter16/listing16-3.py: Calling External Checkers Using the subprocess Module 60 | Chapter17/listing17-1.java: A Simple Java Class (JythonTest.java) 61 | Chapter17/listing17-2.cs: A Simple C# Class (IronPythonTest.cs) 62 | Chapter17/listing17-3.c: A Simple C Function for Detecting a Palindrome (palindrome.c) 63 | Chapter17/listing17-4.py: Detecting Palindromes in Python 64 | Chapter17/listing17-5.i: Interface to the Palindrome Library (palindrome.i) 65 | Chapter17/listing17-6.c: Palindrome Checking Again (palindrome2.c) 66 | Chapter18/listing18-1.py: Simple Distutils Setup Script (setup.py) 67 | Chapter19/listing19-1.cfg: A Simple Configuration File 68 | Chapter19/listing19-2.py: A Program Using ConfigParser 69 | Chapter19/listing19-3.py: A Program Using the logging Module 70 | Chapter20/listing20-1.txt: A Sample Plain-Text Document (test_input.txt) 71 | Chapter20/listing20-2.py: A Text Block Generator (util.py) 72 | Chapter20/listing20-3.py: A Simple Markup Program (simple_markup.py) 73 | Chapter20/listing20-4.py: The Handlers (handlers.py) 74 | Chapter20/listing20-5.py: The Rules (rules.py) 75 | Chapter20/listing20-6.py: The Main Program (markup.py) 76 | Chapter21/listing21-1.py: A Simple ReportLab Program (hello_report.py) 77 | Chapter21/listing21-2.py: The First Prototype for the Sunspot Graph Program (sunspots_proto.py) 78 | Chapter21/listing21-3.py: The Final Sunspot Program (sunspots.py) 79 | Chapter22/listing22-1.xml: A Simple Web Site Represented As an XML File (website.xml) 80 | Chapter22/listing22-2.py: A Simple Page Maker Script (pagemaker.py) 81 | Chapter22/listing22-3.py: The Web Site Constructor (website.py) 82 | Chapter23/listing23-1.py: A Simple News-Gathering Agent (newsagent1.py) 83 | Chapter23/listing23-2.py: A More Flexible News-Gathering Agent (newsagent2.py) 84 | Chapter24/listing24-1.py: A Minimal Server Program 85 | Chapter24/listing24-2.py: A Server That Accepts Connections 86 | Chapter24/listing24-3.py: The Basic Server with Some Cleanups 87 | Chapter24/listing24-4.py: Server Program with ChatSession Class 88 | Chapter24/listing24-5.py: A Simple Chat Server (simple_chat.py) 89 | Chapter24/listing24-6.py: A Slightly More Complicated Chat Server (chatserver.py) 90 | Chapter25/listing25-1.py: A Simple Web Editor (simple_edit.cgi) 91 | Chapter25/listing25-2.py: The Editor Script (edit.cgi) 92 | Chapter25/listing25-3.py: The Saving Script (save.cgi) 93 | Chapter26/listing26-1.sql: Creating the Database in PostgreSQL 94 | Chapter26/listing26-2.sql: Creating the Database in MySQL 95 | Chapter26/listing26-3.sql: Creating the Database in SQLite 96 | Chapter26/listing26-4.py: The Main Bulletin Board (simple_main.cgi) 97 | Chapter26/listing26-5.py: The Main Bulletin Board (main.cgi) 98 | Chapter26/listing26-6.py: The Message Viewer (view.cgi) 99 | Chapter26/listing26-7.py: The Message Editor (edit.cgi) 100 | Chapter26/listing26-8.py: The Save Script (save.cgi) 101 | Chapter27/listing27-1.py: A Simple Node Implementation (simple_node.py) 102 | Chapter27/listing27-2.py: A New Node Implementation (server.py) 103 | Chapter27/listing27-3.py: A Node Controller Interface (client.py) 104 | Chapter28/listing28-1.py: A Simple GUI Client (simple_guiclient.py) 105 | Chapter28/listing28-2.py: The Finished GUI Client (guiclient.py) 106 | Chapter29/listing29-1.py: A Simple “Falling Weights” Animation (weights.py) 107 | Chapter29/listing29-2.py: The Squish Configuration File (config.py) 108 | Chapter29/listing29-3.py: The Squish Game Objects (objects.py) 109 | Chapter29/listing29-4.py: The Main Game Module (squish.py) 110 | -------------------------------------------------------------------------------- /contributing.md: -------------------------------------------------------------------------------- 1 | # Contributing to Apress Source Code 2 | 3 | Copyright for Apress source code belongs to the author(s). However, under fair use you are encouraged to fork and contribute minor corrections and updates for the benefit of the author(s) and other readers. 4 | 5 | ## How to Contribute 6 | 7 | 1. Make sure you have a GitHub account. 8 | 2. Fork the repository for the relevant book. 9 | 3. Create a new branch on which to make your change, e.g. 10 | `git checkout -b my_code_contribution` 11 | 4. Commit your change. Include a commit message describing the correction. Please note that if your commit message is not clear, the correction will not be accepted. 12 | 5. Submit a pull request. 13 | 14 | Thank you for your contribution! -------------------------------------------------------------------------------- /errata.md: -------------------------------------------------------------------------------- 1 | # Errata for *Beginning Python, 3rd Edition* 2 | 3 | On **page 369**: 4 | 5 | 6 | The error is in the definition of function parse. 7 | 8 | The original source code: 9 | def parse(self, file): 10 | self.handler.start('document') 11 | for block in blocks(file): 12 | for filter in self.filters: 13 | block = filter(block, self.handler) 14 | for rule in self.rules: 15 | if rule.condition(block): 16 | last = rule.action(block, 17 | self.handler) 18 | if last: break 19 | self.handler.end('document') 20 | 'rule' has been looped 3 times in the 'for filter in self.filters:' statement, this is incorrect. 21 | I think it should be: 22 | def parse(self, file): 23 | self.handler.start('document') 24 | for block in blocks(file): 25 | for filter in self.filters: 26 | block = filter(block, self.handler) 27 | for rule in self.rules: 28 | if rule.condition(block): 29 | last = rule.action(block, self.handler) 30 | if last: break 31 | self.handler.end('document') 32 | ] 33 | --------------------------------------------------------------------------------