From a31a9ee0f4f37acfcc4ee61c6064720322921136 Mon Sep 17 00:00:00 2001 From: David Beazley Date: Thu, 28 May 2020 17:52:19 -0500 Subject: [PATCH] object patch --- Solutions/4_10/stock.py | 2 +- Solutions/4_10/tableformat.py | 2 +- Solutions/4_4/stock.py | 2 +- Solutions/5_8/stock.py | 2 +- Solutions/5_8/tableformat.py | 2 +- Solutions/6_12/portfolio.py | 2 +- Solutions/6_12/stock.py | 2 +- Solutions/6_12/tableformat.py | 2 +- Solutions/6_15/portfolio.py | 2 +- Solutions/6_15/stock.py | 2 +- Solutions/6_15/tableformat.py | 2 +- Solutions/6_3/portfolio.py | 2 +- Solutions/6_3/stock.py | 2 +- Solutions/6_3/tableformat.py | 2 +- Solutions/6_7/portfolio.py | 2 +- Solutions/6_7/stock.py | 2 +- Solutions/6_7/tableformat.py | 2 +- Solutions/7_11/portfolio.py | 2 +- Solutions/7_11/stock.py | 2 +- Solutions/7_11/tableformat.py | 2 +- Solutions/7_11/typedproperty.py | 13 ++- Solutions/7_4/portfolio.py | 2 +- Solutions/7_4/stock.py | 2 +- Solutions/7_4/tableformat.py | 2 +- Solutions/7_9/portfolio.py | 2 +- Solutions/7_9/stock.py | 2 +- Solutions/7_9/tableformat.py | 2 +- Solutions/7_9/typedproperty.py | 12 ++- Solutions/8_1/portfolio.py | 2 +- Solutions/8_1/stock.py | 2 +- Solutions/8_1/tableformat.py | 2 +- Solutions/8_1/typedproperty.py | 2 +- Solutions/8_2/portfolio.py | 2 +- Solutions/8_2/stock.py | 2 +- Solutions/8_2/tableformat.py | 2 +- Solutions/8_2/typedproperty.py | 2 +- Solutions/9_3/porty-app/porty/portfolio.py | 2 +- Solutions/9_3/porty-app/porty/stock.py | 2 +- Solutions/9_3/porty-app/porty/tableformat.py | 2 +- .../9_3/porty-app/porty/typedproperty.py | 2 +- Solutions/9_5/porty-app/MANIFEST.in | 1 + Solutions/9_5/porty-app/README.txt | 17 ++++ Solutions/9_5/porty-app/portfolio.csv | 8 ++ Solutions/9_5/porty-app/porty/__init__.py | 0 Solutions/9_5/porty-app/porty/fileparse.py | 47 +++++++++++ Solutions/9_5/porty-app/porty/follow.py | 17 ++++ Solutions/9_5/porty-app/porty/pcost.py | 20 +++++ Solutions/9_5/porty-app/porty/portfolio.py | 51 ++++++++++++ Solutions/9_5/porty-app/porty/report.py | 67 +++++++++++++++ Solutions/9_5/porty-app/porty/stock.py | 33 ++++++++ Solutions/9_5/porty-app/porty/tableformat.py | 81 +++++++++++++++++++ Solutions/9_5/porty-app/porty/test_stock.py | 28 +++++++ Solutions/9_5/porty-app/porty/ticker.py | 43 ++++++++++ .../9_5/porty-app/porty/typedproperty.py | 34 ++++++++ Solutions/9_5/porty-app/prices.csv | 31 +++++++ Solutions/9_5/porty-app/print-report.py | 6 ++ Solutions/9_5/porty-app/setup.py | 13 +++ 57 files changed, 558 insertions(+), 40 deletions(-) create mode 100644 Solutions/9_5/porty-app/MANIFEST.in create mode 100644 Solutions/9_5/porty-app/README.txt create mode 100755 Solutions/9_5/porty-app/portfolio.csv create mode 100644 Solutions/9_5/porty-app/porty/__init__.py create mode 100644 Solutions/9_5/porty-app/porty/fileparse.py create mode 100644 Solutions/9_5/porty-app/porty/follow.py create mode 100644 Solutions/9_5/porty-app/porty/pcost.py create mode 100644 Solutions/9_5/porty-app/porty/portfolio.py create mode 100644 Solutions/9_5/porty-app/porty/report.py create mode 100644 Solutions/9_5/porty-app/porty/stock.py create mode 100644 Solutions/9_5/porty-app/porty/tableformat.py create mode 100644 Solutions/9_5/porty-app/porty/test_stock.py create mode 100644 Solutions/9_5/porty-app/porty/ticker.py create mode 100644 Solutions/9_5/porty-app/porty/typedproperty.py create mode 100644 Solutions/9_5/porty-app/prices.csv create mode 100644 Solutions/9_5/porty-app/print-report.py create mode 100644 Solutions/9_5/porty-app/setup.py diff --git a/Solutions/4_10/stock.py b/Solutions/4_10/stock.py index 8206801..1530b72 100644 --- a/Solutions/4_10/stock.py +++ b/Solutions/4_10/stock.py @@ -1,6 +1,6 @@ # stock.py -class Stock(object): +class Stock: ''' An instance of a stock holding consisting of name, shares, and price. ''' diff --git a/Solutions/4_10/tableformat.py b/Solutions/4_10/tableformat.py index df3d102..cb5350a 100644 --- a/Solutions/4_10/tableformat.py +++ b/Solutions/4_10/tableformat.py @@ -1,6 +1,6 @@ # tableformat.py -class TableFormatter(object): +class TableFormatter: def headings(self, headers): ''' Emit the table headers diff --git a/Solutions/4_4/stock.py b/Solutions/4_4/stock.py index 1f3ad99..6524ab4 100644 --- a/Solutions/4_4/stock.py +++ b/Solutions/4_4/stock.py @@ -1,6 +1,6 @@ # stock.py -class Stock(object): +class Stock: ''' An instance of a stock holding consisting of name, shares, and price. ''' diff --git a/Solutions/5_8/stock.py b/Solutions/5_8/stock.py index 99ca129..3c1614e 100644 --- a/Solutions/5_8/stock.py +++ b/Solutions/5_8/stock.py @@ -1,6 +1,6 @@ # stock.py -class Stock(object): +class Stock: ''' An instance of a stock holding consisting of name, shares, and price. ''' diff --git a/Solutions/5_8/tableformat.py b/Solutions/5_8/tableformat.py index df3d102..cb5350a 100644 --- a/Solutions/5_8/tableformat.py +++ b/Solutions/5_8/tableformat.py @@ -1,6 +1,6 @@ # tableformat.py -class TableFormatter(object): +class TableFormatter: def headings(self, headers): ''' Emit the table headers diff --git a/Solutions/6_12/portfolio.py b/Solutions/6_12/portfolio.py index 77a1b1f..9a79361 100644 --- a/Solutions/6_12/portfolio.py +++ b/Solutions/6_12/portfolio.py @@ -1,6 +1,6 @@ # portfolio.py -class Portfolio(object): +class Portfolio: def __init__(self, holdings): self._holdings = holdings diff --git a/Solutions/6_12/stock.py b/Solutions/6_12/stock.py index 99ca129..3c1614e 100644 --- a/Solutions/6_12/stock.py +++ b/Solutions/6_12/stock.py @@ -1,6 +1,6 @@ # stock.py -class Stock(object): +class Stock: ''' An instance of a stock holding consisting of name, shares, and price. ''' diff --git a/Solutions/6_12/tableformat.py b/Solutions/6_12/tableformat.py index df3d102..cb5350a 100644 --- a/Solutions/6_12/tableformat.py +++ b/Solutions/6_12/tableformat.py @@ -1,6 +1,6 @@ # tableformat.py -class TableFormatter(object): +class TableFormatter: def headings(self, headers): ''' Emit the table headers diff --git a/Solutions/6_15/portfolio.py b/Solutions/6_15/portfolio.py index b738d26..9b5322f 100644 --- a/Solutions/6_15/portfolio.py +++ b/Solutions/6_15/portfolio.py @@ -1,6 +1,6 @@ # portfolio.py -class Portfolio(object): +class Portfolio: def __init__(self, holdings): self._holdings = holdings diff --git a/Solutions/6_15/stock.py b/Solutions/6_15/stock.py index 99ca129..3c1614e 100644 --- a/Solutions/6_15/stock.py +++ b/Solutions/6_15/stock.py @@ -1,6 +1,6 @@ # stock.py -class Stock(object): +class Stock: ''' An instance of a stock holding consisting of name, shares, and price. ''' diff --git a/Solutions/6_15/tableformat.py b/Solutions/6_15/tableformat.py index df3d102..cb5350a 100644 --- a/Solutions/6_15/tableformat.py +++ b/Solutions/6_15/tableformat.py @@ -1,6 +1,6 @@ # tableformat.py -class TableFormatter(object): +class TableFormatter: def headings(self, headers): ''' Emit the table headers diff --git a/Solutions/6_3/portfolio.py b/Solutions/6_3/portfolio.py index 77a1b1f..9a79361 100644 --- a/Solutions/6_3/portfolio.py +++ b/Solutions/6_3/portfolio.py @@ -1,6 +1,6 @@ # portfolio.py -class Portfolio(object): +class Portfolio: def __init__(self, holdings): self._holdings = holdings diff --git a/Solutions/6_3/stock.py b/Solutions/6_3/stock.py index 99ca129..3c1614e 100644 --- a/Solutions/6_3/stock.py +++ b/Solutions/6_3/stock.py @@ -1,6 +1,6 @@ # stock.py -class Stock(object): +class Stock: ''' An instance of a stock holding consisting of name, shares, and price. ''' diff --git a/Solutions/6_3/tableformat.py b/Solutions/6_3/tableformat.py index df3d102..cb5350a 100644 --- a/Solutions/6_3/tableformat.py +++ b/Solutions/6_3/tableformat.py @@ -1,6 +1,6 @@ # tableformat.py -class TableFormatter(object): +class TableFormatter: def headings(self, headers): ''' Emit the table headers diff --git a/Solutions/6_7/portfolio.py b/Solutions/6_7/portfolio.py index 77a1b1f..9a79361 100644 --- a/Solutions/6_7/portfolio.py +++ b/Solutions/6_7/portfolio.py @@ -1,6 +1,6 @@ # portfolio.py -class Portfolio(object): +class Portfolio: def __init__(self, holdings): self._holdings = holdings diff --git a/Solutions/6_7/stock.py b/Solutions/6_7/stock.py index 99ca129..3c1614e 100644 --- a/Solutions/6_7/stock.py +++ b/Solutions/6_7/stock.py @@ -1,6 +1,6 @@ # stock.py -class Stock(object): +class Stock: ''' An instance of a stock holding consisting of name, shares, and price. ''' diff --git a/Solutions/6_7/tableformat.py b/Solutions/6_7/tableformat.py index df3d102..cb5350a 100644 --- a/Solutions/6_7/tableformat.py +++ b/Solutions/6_7/tableformat.py @@ -1,6 +1,6 @@ # tableformat.py -class TableFormatter(object): +class TableFormatter: def headings(self, headers): ''' Emit the table headers diff --git a/Solutions/7_11/portfolio.py b/Solutions/7_11/portfolio.py index e616a0a..b839cfe 100644 --- a/Solutions/7_11/portfolio.py +++ b/Solutions/7_11/portfolio.py @@ -3,7 +3,7 @@ import fileparse import stock -class Portfolio(object): +class Portfolio: def __init__(self): self._holdings = [] diff --git a/Solutions/7_11/stock.py b/Solutions/7_11/stock.py index f94e97c..faddc19 100644 --- a/Solutions/7_11/stock.py +++ b/Solutions/7_11/stock.py @@ -2,7 +2,7 @@ from typedproperty import String, Integer, Float -class Stock(object): +class Stock: ''' An instance of a stock holding consisting of name, shares, and price. ''' diff --git a/Solutions/7_11/tableformat.py b/Solutions/7_11/tableformat.py index df3d102..cb5350a 100644 --- a/Solutions/7_11/tableformat.py +++ b/Solutions/7_11/tableformat.py @@ -1,6 +1,6 @@ # tableformat.py -class TableFormatter(object): +class TableFormatter: def headings(self, headers): ''' Emit the table headers diff --git a/Solutions/7_11/typedproperty.py b/Solutions/7_11/typedproperty.py index cda0872..e411930 100644 --- a/Solutions/7_11/typedproperty.py +++ b/Solutions/7_11/typedproperty.py @@ -20,7 +20,7 @@ Float = lambda name: typedproperty(name, float) # Example if __name__ == '__main__': - class Stock(object): + class Stock: name = typedproperty('name', str) shares = typedproperty('shares', int) price = typedproperty('price', float) @@ -30,5 +30,16 @@ if __name__ == '__main__': self.shares = shares self.price = price + + class Stock2: + name = String('name') + shares = Integer('shares') + price = Float('price') + + def __init__(self, name, shares, price): + self.name = name + self.shares = shares + self.price = price + diff --git a/Solutions/7_4/portfolio.py b/Solutions/7_4/portfolio.py index b738d26..9b5322f 100644 --- a/Solutions/7_4/portfolio.py +++ b/Solutions/7_4/portfolio.py @@ -1,6 +1,6 @@ # portfolio.py -class Portfolio(object): +class Portfolio: def __init__(self, holdings): self._holdings = holdings diff --git a/Solutions/7_4/stock.py b/Solutions/7_4/stock.py index 99ca129..3c1614e 100644 --- a/Solutions/7_4/stock.py +++ b/Solutions/7_4/stock.py @@ -1,6 +1,6 @@ # stock.py -class Stock(object): +class Stock: ''' An instance of a stock holding consisting of name, shares, and price. ''' diff --git a/Solutions/7_4/tableformat.py b/Solutions/7_4/tableformat.py index df3d102..cb5350a 100644 --- a/Solutions/7_4/tableformat.py +++ b/Solutions/7_4/tableformat.py @@ -1,6 +1,6 @@ # tableformat.py -class TableFormatter(object): +class TableFormatter: def headings(self, headers): ''' Emit the table headers diff --git a/Solutions/7_9/portfolio.py b/Solutions/7_9/portfolio.py index b738d26..9b5322f 100644 --- a/Solutions/7_9/portfolio.py +++ b/Solutions/7_9/portfolio.py @@ -1,6 +1,6 @@ # portfolio.py -class Portfolio(object): +class Portfolio: def __init__(self, holdings): self._holdings = holdings diff --git a/Solutions/7_9/stock.py b/Solutions/7_9/stock.py index f94e97c..faddc19 100644 --- a/Solutions/7_9/stock.py +++ b/Solutions/7_9/stock.py @@ -2,7 +2,7 @@ from typedproperty import String, Integer, Float -class Stock(object): +class Stock: ''' An instance of a stock holding consisting of name, shares, and price. ''' diff --git a/Solutions/7_9/tableformat.py b/Solutions/7_9/tableformat.py index df3d102..cb5350a 100644 --- a/Solutions/7_9/tableformat.py +++ b/Solutions/7_9/tableformat.py @@ -1,6 +1,6 @@ # tableformat.py -class TableFormatter(object): +class TableFormatter: def headings(self, headers): ''' Emit the table headers diff --git a/Solutions/7_9/typedproperty.py b/Solutions/7_9/typedproperty.py index cda0872..a6b4a59 100644 --- a/Solutions/7_9/typedproperty.py +++ b/Solutions/7_9/typedproperty.py @@ -20,7 +20,7 @@ Float = lambda name: typedproperty(name, float) # Example if __name__ == '__main__': - class Stock(object): + class Stock: name = typedproperty('name', str) shares = typedproperty('shares', int) price = typedproperty('price', float) @@ -30,5 +30,15 @@ if __name__ == '__main__': self.shares = shares self.price = price + class Stock2: + name = String('name') + shares = Integer('shares') + price = Float('price') + + def __init__(self, name, shares, price): + self.name = name + self.shares = shares + self.price = price + diff --git a/Solutions/8_1/portfolio.py b/Solutions/8_1/portfolio.py index e616a0a..b839cfe 100644 --- a/Solutions/8_1/portfolio.py +++ b/Solutions/8_1/portfolio.py @@ -3,7 +3,7 @@ import fileparse import stock -class Portfolio(object): +class Portfolio: def __init__(self): self._holdings = [] diff --git a/Solutions/8_1/stock.py b/Solutions/8_1/stock.py index f94e97c..faddc19 100644 --- a/Solutions/8_1/stock.py +++ b/Solutions/8_1/stock.py @@ -2,7 +2,7 @@ from typedproperty import String, Integer, Float -class Stock(object): +class Stock: ''' An instance of a stock holding consisting of name, shares, and price. ''' diff --git a/Solutions/8_1/tableformat.py b/Solutions/8_1/tableformat.py index df3d102..cb5350a 100644 --- a/Solutions/8_1/tableformat.py +++ b/Solutions/8_1/tableformat.py @@ -1,6 +1,6 @@ # tableformat.py -class TableFormatter(object): +class TableFormatter: def headings(self, headers): ''' Emit the table headers diff --git a/Solutions/8_1/typedproperty.py b/Solutions/8_1/typedproperty.py index cda0872..a2e6883 100644 --- a/Solutions/8_1/typedproperty.py +++ b/Solutions/8_1/typedproperty.py @@ -20,7 +20,7 @@ Float = lambda name: typedproperty(name, float) # Example if __name__ == '__main__': - class Stock(object): + class Stock: name = typedproperty('name', str) shares = typedproperty('shares', int) price = typedproperty('price', float) diff --git a/Solutions/8_2/portfolio.py b/Solutions/8_2/portfolio.py index e616a0a..b839cfe 100644 --- a/Solutions/8_2/portfolio.py +++ b/Solutions/8_2/portfolio.py @@ -3,7 +3,7 @@ import fileparse import stock -class Portfolio(object): +class Portfolio: def __init__(self): self._holdings = [] diff --git a/Solutions/8_2/stock.py b/Solutions/8_2/stock.py index f94e97c..faddc19 100644 --- a/Solutions/8_2/stock.py +++ b/Solutions/8_2/stock.py @@ -2,7 +2,7 @@ from typedproperty import String, Integer, Float -class Stock(object): +class Stock: ''' An instance of a stock holding consisting of name, shares, and price. ''' diff --git a/Solutions/8_2/tableformat.py b/Solutions/8_2/tableformat.py index df3d102..cb5350a 100644 --- a/Solutions/8_2/tableformat.py +++ b/Solutions/8_2/tableformat.py @@ -1,6 +1,6 @@ # tableformat.py -class TableFormatter(object): +class TableFormatter: def headings(self, headers): ''' Emit the table headers diff --git a/Solutions/8_2/typedproperty.py b/Solutions/8_2/typedproperty.py index cda0872..a2e6883 100644 --- a/Solutions/8_2/typedproperty.py +++ b/Solutions/8_2/typedproperty.py @@ -20,7 +20,7 @@ Float = lambda name: typedproperty(name, float) # Example if __name__ == '__main__': - class Stock(object): + class Stock: name = typedproperty('name', str) shares = typedproperty('shares', int) price = typedproperty('price', float) diff --git a/Solutions/9_3/porty-app/porty/portfolio.py b/Solutions/9_3/porty-app/porty/portfolio.py index 0c9fbb1..0c93994 100644 --- a/Solutions/9_3/porty-app/porty/portfolio.py +++ b/Solutions/9_3/porty-app/porty/portfolio.py @@ -3,7 +3,7 @@ from . import fileparse from . import stock -class Portfolio(object): +class Portfolio: def __init__(self): self._holdings = [] diff --git a/Solutions/9_3/porty-app/porty/stock.py b/Solutions/9_3/porty-app/porty/stock.py index 976d5d7..b805210 100644 --- a/Solutions/9_3/porty-app/porty/stock.py +++ b/Solutions/9_3/porty-app/porty/stock.py @@ -2,7 +2,7 @@ from .typedproperty import String, Integer, Float -class Stock(object): +class Stock: ''' An instance of a stock holding consisting of name, shares, and price. ''' diff --git a/Solutions/9_3/porty-app/porty/tableformat.py b/Solutions/9_3/porty-app/porty/tableformat.py index df3d102..cb5350a 100644 --- a/Solutions/9_3/porty-app/porty/tableformat.py +++ b/Solutions/9_3/porty-app/porty/tableformat.py @@ -1,6 +1,6 @@ # tableformat.py -class TableFormatter(object): +class TableFormatter: def headings(self, headers): ''' Emit the table headers diff --git a/Solutions/9_3/porty-app/porty/typedproperty.py b/Solutions/9_3/porty-app/porty/typedproperty.py index cda0872..a2e6883 100644 --- a/Solutions/9_3/porty-app/porty/typedproperty.py +++ b/Solutions/9_3/porty-app/porty/typedproperty.py @@ -20,7 +20,7 @@ Float = lambda name: typedproperty(name, float) # Example if __name__ == '__main__': - class Stock(object): + class Stock: name = typedproperty('name', str) shares = typedproperty('shares', int) price = typedproperty('price', float) diff --git a/Solutions/9_5/porty-app/MANIFEST.in b/Solutions/9_5/porty-app/MANIFEST.in new file mode 100644 index 0000000..d68a495 --- /dev/null +++ b/Solutions/9_5/porty-app/MANIFEST.in @@ -0,0 +1 @@ +include *.csv diff --git a/Solutions/9_5/porty-app/README.txt b/Solutions/9_5/porty-app/README.txt new file mode 100644 index 0000000..4d96adc --- /dev/null +++ b/Solutions/9_5/porty-app/README.txt @@ -0,0 +1,17 @@ +Code from Practical Python. + +The "porty" directory is a Python package of code that's loaded via +import. The "print-report.py" program is a top-level script that +produces a report. Try it: + +shell % python3 print-report.py portfolio.csv prices.csv txt + Name Shares Price Change +---------- ---------- ---------- ---------- + AA 100 9.22 -22.98 + IBM 50 106.28 15.18 + CAT 150 35.46 -47.98 + MSFT 200 20.89 -30.34 + GE 95 13.48 -26.89 + MSFT 50 20.89 -44.21 + IBM 100 106.28 35.84 +shell % diff --git a/Solutions/9_5/porty-app/portfolio.csv b/Solutions/9_5/porty-app/portfolio.csv new file mode 100755 index 0000000..6c16f65 --- /dev/null +++ b/Solutions/9_5/porty-app/portfolio.csv @@ -0,0 +1,8 @@ +name,shares,price +"AA",100,32.20 +"IBM",50,91.10 +"CAT",150,83.44 +"MSFT",200,51.23 +"GE",95,40.37 +"MSFT",50,65.10 +"IBM",100,70.44 diff --git a/Solutions/9_5/porty-app/porty/__init__.py b/Solutions/9_5/porty-app/porty/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Solutions/9_5/porty-app/porty/fileparse.py b/Solutions/9_5/porty-app/porty/fileparse.py new file mode 100644 index 0000000..8c6d5c1 --- /dev/null +++ b/Solutions/9_5/porty-app/porty/fileparse.py @@ -0,0 +1,47 @@ +# fileparse.py +import csv +import logging +log = logging.getLogger(__name__) + +def parse_csv(lines, select=None, types=None, has_headers=True, delimiter=',', silence_errors=False): + ''' + Parse a CSV file into a list of records with type conversion. + ''' + assert not (select and not has_headers), 'select requires column headers' + rows = csv.reader(lines, delimiter=delimiter) + + # Read the file headers (if any) + headers = next(rows) if has_headers else [] + + # If specific columns have been selected, make indices for filtering and set output columns + if select: + indices = [ headers.index(colname) for colname in select ] + headers = select + + records = [] + for rowno, row in enumerate(rows, 1): + if not row: # Skip rows with no data + continue + + # If specific column indices are selected, pick them out + if select: + row = [ row[index] for index in indices] + + # Apply type conversion to the row + if types: + try: + row = [func(val) for func, val in zip(types, row)] + except ValueError as e: + if not silence_errors: + log.warning("Row %d: Couldn't convert %s", rowno, row) + log.debug("Row %d: Reason %s", rowno, e) + continue + + # Make a dictionary or a tuple + if headers: + record = dict(zip(headers, row)) + else: + record = tuple(row) + records.append(record) + + return records diff --git a/Solutions/9_5/porty-app/porty/follow.py b/Solutions/9_5/porty-app/porty/follow.py new file mode 100644 index 0000000..1dec7bc --- /dev/null +++ b/Solutions/9_5/porty-app/porty/follow.py @@ -0,0 +1,17 @@ +# follow.py + +import time +import os + +def follow(filename): + ''' + Generator that produces a sequence of lines being written at the end of a file. + ''' + with open(filename,'r') as f: + f.seek(0,os.SEEK_END) + while True: + line = f.readline() + if line != '': + yield line + else: + time.sleep(0.1) # Sleep briefly to avoid busy wait diff --git a/Solutions/9_5/porty-app/porty/pcost.py b/Solutions/9_5/porty-app/porty/pcost.py new file mode 100644 index 0000000..8f43a3e --- /dev/null +++ b/Solutions/9_5/porty-app/porty/pcost.py @@ -0,0 +1,20 @@ +# pcost.py + +from . import report + +def portfolio_cost(filename): + ''' + Computes the total cost (shares*price) of a portfolio file + ''' + portfolio = report.read_portfolio(filename) + return portfolio.total_cost + +def main(args): + if len(args) != 2: + raise SystemExit('Usage: %s portfoliofile' % args[0]) + filename = args[1] + print('Total cost:', portfolio_cost(filename)) + +if __name__ == '__main__': + import sys + main(sys.argv) diff --git a/Solutions/9_5/porty-app/porty/portfolio.py b/Solutions/9_5/porty-app/porty/portfolio.py new file mode 100644 index 0000000..0c93994 --- /dev/null +++ b/Solutions/9_5/porty-app/porty/portfolio.py @@ -0,0 +1,51 @@ +# portfolio.py + +from . import fileparse +from . import stock + +class Portfolio: + def __init__(self): + self._holdings = [] + + @classmethod + def from_csv(cls, lines, **opts): + self = cls() + portdicts = fileparse.parse_csv(lines, + select=['name','shares','price'], + types=[str,int,float], + **opts) + + for d in portdicts: + self.append(stock.Stock(**d)) + + return self + + def append(self, holding): + self._holdings.append(holding) + + def __iter__(self): + return self._holdings.__iter__() + + def __len__(self): + return len(self._holdings) + + def __getitem__(self, index): + return self._holdings[index] + + def __contains__(self, name): + return any(s.name == name for s in self._holdings) + + @property + def total_cost(self): + return sum(s.shares * s.price for s in self._holdings) + + def tabulate_shares(self): + from collections import Counter + total_shares = Counter() + for s in self._holdings: + total_shares[s.name] += s.shares + return total_shares + + + + diff --git a/Solutions/9_5/porty-app/porty/report.py b/Solutions/9_5/porty-app/porty/report.py new file mode 100644 index 0000000..0131aad --- /dev/null +++ b/Solutions/9_5/porty-app/porty/report.py @@ -0,0 +1,67 @@ +# report.py + +from . import fileparse +from .stock import Stock +from .portfolio import Portfolio +from . import tableformat + +def read_portfolio(filename, **opts): + ''' + Read a stock portfolio file into a list of dictionaries with keys + name, shares, and price. + ''' + with open(filename) as lines: + return Portfolio.from_csv(lines, **opts) + +def read_prices(filename, **opts): + ''' + Read a CSV file of price data into a dict mapping names to prices. + ''' + with open(filename) as lines: + return dict(fileparse.parse_csv(lines, types=[str,float], has_headers=False, **opts)) + +def make_report(portfolio, prices): + ''' + Make a list of (name, shares, price, change) tuples given a portfolio list + and prices dictionary. + ''' + rows = [] + for s in portfolio: + current_price = prices[s.name] + change = current_price - s.price + summary = (s.name, s.shares, current_price, change) + rows.append(summary) + return rows + +def print_report(reportdata, formatter): + ''' + Print a nicely formated table from a list of (name, shares, price, change) tuples. + ''' + formatter.headings(['Name','Shares','Price','Change']) + for name, shares, price, change in reportdata: + rowdata = [ name, str(shares), f'{price:0.2f}', f'{change:0.2f}' ] + formatter.row(rowdata) + +def portfolio_report(portfoliofile, pricefile, fmt='txt'): + ''' + Make a stock report given portfolio and price data files. + ''' + # Read data files + portfolio = read_portfolio(portfoliofile) + prices = read_prices(pricefile) + + # Create the report data + report = make_report(portfolio, prices) + + # Print it out + formatter = tableformat.create_formatter(fmt) + print_report(report, formatter) + +def main(args): + if len(args) != 4: + raise SystemExit('Usage: %s portfile pricefile format' % args[0]) + portfolio_report(args[1], args[2], args[3]) + +if __name__ == '__main__': + import sys + main(sys.argv) diff --git a/Solutions/9_5/porty-app/porty/stock.py b/Solutions/9_5/porty-app/porty/stock.py new file mode 100644 index 0000000..b805210 --- /dev/null +++ b/Solutions/9_5/porty-app/porty/stock.py @@ -0,0 +1,33 @@ +# stock.py + +from .typedproperty import String, Integer, Float + +class Stock: + ''' + An instance of a stock holding consisting of name, shares, and price. + ''' + if __debug__: + name = String('name') + shares = Integer('shares') + price = Float('price') + + def __init__(self,name, shares, price): + self.name = name + self.shares = shares + self.price = price + + def __repr__(self): + return f'Stock({self.name!r}, {self.shares!r}, {self.price!r})' + + @property + def cost(self): + ''' + Return the cost as shares*price + ''' + return self.shares * self.price + + def sell(self, nshares): + ''' + Sell a number of shares and return the remaining number. + ''' + self.shares -= nshares diff --git a/Solutions/9_5/porty-app/porty/tableformat.py b/Solutions/9_5/porty-app/porty/tableformat.py new file mode 100644 index 0000000..cb5350a --- /dev/null +++ b/Solutions/9_5/porty-app/porty/tableformat.py @@ -0,0 +1,81 @@ +# tableformat.py + +class TableFormatter: + def headings(self, headers): + ''' + Emit the table headers + ''' + raise NotImplementedError() + + def row(self, rowdata): + ''' + Emit a single row of table data + ''' + raise NotImplementedError() + +class TextTableFormatter(TableFormatter): + ''' + Output data in plain-text format. + ''' + def headings(self, headers): + for h in headers: + print(f'{h:>10s}', end=' ') + print() + print(('-'*10 + ' ')*len(headers)) + + def row(self, rowdata): + for d in rowdata: + print(f'{d:>10s}', end=' ') + print() + +class CSVTableFormatter(TableFormatter): + ''' + Output data in CSV format. + ''' + def headings(self, headers): + print(','.join(headers)) + + def row(self, rowdata): + print(','.join(rowdata)) + +class HTMLTableFormatter(TableFormatter): + ''' + Output data in HTML format. + ''' + def headings(self, headers): + print('', end='') + for h in headers: + print(f'{h}', end='') + print('') + + def row(self, rowdata): + print('', end='') + for d in rowdata: + print(f'{d}', end='') + print('') + +class FormatError(Exception): + pass + +def create_formatter(name): + ''' + Create an appropriate formatter given an output format name + ''' + if name == 'txt': + return TextTableFormatter() + elif name == 'csv': + return CSVTableFormatter() + elif name == 'html': + return HTMLTableFormatter() + else: + raise FormatError(f'Unknown table format {name}') + +def print_table(objects, columns, formatter): + ''' + Make a nicely formatted table from a list of objects and attribute names. + ''' + formatter.headings(columns) + for obj in objects: + rowdata = [ str(getattr(obj, name)) for name in columns ] + formatter.row(rowdata) + diff --git a/Solutions/9_5/porty-app/porty/test_stock.py b/Solutions/9_5/porty-app/porty/test_stock.py new file mode 100644 index 0000000..b7bf691 --- /dev/null +++ b/Solutions/9_5/porty-app/porty/test_stock.py @@ -0,0 +1,28 @@ +# test_stock.py + +import unittest +from . import stock + +class TestStock(unittest.TestCase): + def test_create(self): + s = stock.Stock('GOOG', 100, 490.1) + self.assertEqual(s.name, 'GOOG') + self.assertEqual(s.shares, 100) + self.assertEqual(s.price, 490.1) + + def test_cost(self): + s = stock.Stock('GOOG', 100, 490.1) + self.assertEqual(s.cost, 49010.0) + + def test_sell(self): + s = stock.Stock('GOOG', 100, 490.1) + s.sell(25) + self.assertEqual(s.shares, 75) + + def test_shares_check(self): + s = stock.Stock('GOOG', 100, 490.1) + with self.assertRaises(TypeError): + s.shares = '100' + +if __name__ == '__main__': + unittest.main() diff --git a/Solutions/9_5/porty-app/porty/ticker.py b/Solutions/9_5/porty-app/porty/ticker.py new file mode 100644 index 0000000..4ac26bc --- /dev/null +++ b/Solutions/9_5/porty-app/porty/ticker.py @@ -0,0 +1,43 @@ +# ticker.py + +import csv +from . import report +from . import tableformat +from .follow import follow + +def select_columns(rows, indices): + for row in rows: + yield [row[index] for index in indices] + +def convert_types(rows, types): + for row in rows: + yield [func(val) for func, val in zip(types, row)] + +def make_dicts(rows, headers): + return (dict(zip(headers,row)) for row in rows) + +def parse_stock_data(lines): + rows = csv.reader(lines) + rows = select_columns(rows, [0, 1, 4]) + rows = convert_types(rows, [str,float,float]) + rows = make_dicts(rows, ['name','price','change']) + return rows + +def ticker(portfile, logfile, fmt): + portfolio = report.read_portfolio(portfile) + lines = follow(logfile) + rows = parse_stock_data(lines) + rows = (row for row in rows if row['name'] in portfolio) + formatter = tableformat.create_formatter(fmt) + formatter.headings(['Name','Price','Change']) + for row in rows: + formatter.row([ row['name'], f"{row['price']:0.2f}", f"{row['change']:0.2f}"] ) + +def main(args): + if len(args) != 4: + raise SystemExit('Usage: %s portfoliofile logfile fmt' % args[0]) + ticker(args[1], args[2], args[3]) + +if __name__ == '__main__': + import sys + main(sys.argv) diff --git a/Solutions/9_5/porty-app/porty/typedproperty.py b/Solutions/9_5/porty-app/porty/typedproperty.py new file mode 100644 index 0000000..a2e6883 --- /dev/null +++ b/Solutions/9_5/porty-app/porty/typedproperty.py @@ -0,0 +1,34 @@ +# typedproperty.py + +def typedproperty(name, expected_type): + private_name = '_' + name + @property + def prop(self): + return getattr(self, private_name) + + @prop.setter + def prop(self, value): + if not isinstance(value, expected_type): + raise TypeError(f'Expected {expected_type}') + setattr(self, private_name, value) + + return prop + +String = lambda name: typedproperty(name, str) +Integer = lambda name: typedproperty(name, int) +Float = lambda name: typedproperty(name, float) + +# Example +if __name__ == '__main__': + class Stock: + name = typedproperty('name', str) + shares = typedproperty('shares', int) + price = typedproperty('price', float) + + def __init__(self, name, shares, price): + self.name = name + self.shares = shares + self.price = price + + + diff --git a/Solutions/9_5/porty-app/prices.csv b/Solutions/9_5/porty-app/prices.csv new file mode 100644 index 0000000..6bbcb20 --- /dev/null +++ b/Solutions/9_5/porty-app/prices.csv @@ -0,0 +1,31 @@ +"AA",9.22 +"AXP",24.85 +"BA",44.85 +"BAC",11.27 +"C",3.72 +"CAT",35.46 +"CVX",66.67 +"DD",28.47 +"DIS",24.22 +"GE",13.48 +"GM",0.75 +"HD",23.16 +"HPQ",34.35 +"IBM",106.28 +"INTC",15.72 +"JNJ",55.16 +"JPM",36.90 +"KFT",26.11 +"KO",49.16 +"MCD",58.99 +"MMM",57.10 +"MRK",27.58 +"MSFT",20.89 +"PFE",15.19 +"PG",51.94 +"T",24.79 +"UTX",52.61 +"VZ",29.26 +"WMT",49.74 +"XOM",69.35 + diff --git a/Solutions/9_5/porty-app/print-report.py b/Solutions/9_5/porty-app/print-report.py new file mode 100644 index 0000000..afe330a --- /dev/null +++ b/Solutions/9_5/porty-app/print-report.py @@ -0,0 +1,6 @@ +#!/usr/bin/env python3 +# print-report.py + +import sys +from porty.report import main +main(sys.argv) diff --git a/Solutions/9_5/porty-app/setup.py b/Solutions/9_5/porty-app/setup.py new file mode 100644 index 0000000..5d153a6 --- /dev/null +++ b/Solutions/9_5/porty-app/setup.py @@ -0,0 +1,13 @@ +# setup.py + +import setuptools + +setuptools.setup( + name="porty", + version="0.0.1", + author="Your Name", + author_email="you@example.com", + description="Practical Python Code", + packages=setuptools.find_packages(), + scripts=['print-report.py'], +)