object patch
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# stock.py
|
||||
|
||||
class Stock(object):
|
||||
class Stock:
|
||||
'''
|
||||
An instance of a stock holding consisting of name, shares, and price.
|
||||
'''
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# tableformat.py
|
||||
|
||||
class TableFormatter(object):
|
||||
class TableFormatter:
|
||||
def headings(self, headers):
|
||||
'''
|
||||
Emit the table headers
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# stock.py
|
||||
|
||||
class Stock(object):
|
||||
class Stock:
|
||||
'''
|
||||
An instance of a stock holding consisting of name, shares, and price.
|
||||
'''
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# stock.py
|
||||
|
||||
class Stock(object):
|
||||
class Stock:
|
||||
'''
|
||||
An instance of a stock holding consisting of name, shares, and price.
|
||||
'''
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# tableformat.py
|
||||
|
||||
class TableFormatter(object):
|
||||
class TableFormatter:
|
||||
def headings(self, headers):
|
||||
'''
|
||||
Emit the table headers
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# portfolio.py
|
||||
|
||||
class Portfolio(object):
|
||||
class Portfolio:
|
||||
def __init__(self, holdings):
|
||||
self._holdings = holdings
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# stock.py
|
||||
|
||||
class Stock(object):
|
||||
class Stock:
|
||||
'''
|
||||
An instance of a stock holding consisting of name, shares, and price.
|
||||
'''
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# tableformat.py
|
||||
|
||||
class TableFormatter(object):
|
||||
class TableFormatter:
|
||||
def headings(self, headers):
|
||||
'''
|
||||
Emit the table headers
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# portfolio.py
|
||||
|
||||
class Portfolio(object):
|
||||
class Portfolio:
|
||||
def __init__(self, holdings):
|
||||
self._holdings = holdings
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# stock.py
|
||||
|
||||
class Stock(object):
|
||||
class Stock:
|
||||
'''
|
||||
An instance of a stock holding consisting of name, shares, and price.
|
||||
'''
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# tableformat.py
|
||||
|
||||
class TableFormatter(object):
|
||||
class TableFormatter:
|
||||
def headings(self, headers):
|
||||
'''
|
||||
Emit the table headers
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# portfolio.py
|
||||
|
||||
class Portfolio(object):
|
||||
class Portfolio:
|
||||
def __init__(self, holdings):
|
||||
self._holdings = holdings
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# stock.py
|
||||
|
||||
class Stock(object):
|
||||
class Stock:
|
||||
'''
|
||||
An instance of a stock holding consisting of name, shares, and price.
|
||||
'''
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# tableformat.py
|
||||
|
||||
class TableFormatter(object):
|
||||
class TableFormatter:
|
||||
def headings(self, headers):
|
||||
'''
|
||||
Emit the table headers
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# portfolio.py
|
||||
|
||||
class Portfolio(object):
|
||||
class Portfolio:
|
||||
def __init__(self, holdings):
|
||||
self._holdings = holdings
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# stock.py
|
||||
|
||||
class Stock(object):
|
||||
class Stock:
|
||||
'''
|
||||
An instance of a stock holding consisting of name, shares, and price.
|
||||
'''
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# tableformat.py
|
||||
|
||||
class TableFormatter(object):
|
||||
class TableFormatter:
|
||||
def headings(self, headers):
|
||||
'''
|
||||
Emit the table headers
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import fileparse
|
||||
import stock
|
||||
|
||||
class Portfolio(object):
|
||||
class Portfolio:
|
||||
def __init__(self):
|
||||
self._holdings = []
|
||||
|
||||
|
||||
@@ -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.
|
||||
'''
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# tableformat.py
|
||||
|
||||
class TableFormatter(object):
|
||||
class TableFormatter:
|
||||
def headings(self, headers):
|
||||
'''
|
||||
Emit the table headers
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# portfolio.py
|
||||
|
||||
class Portfolio(object):
|
||||
class Portfolio:
|
||||
def __init__(self, holdings):
|
||||
self._holdings = holdings
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# stock.py
|
||||
|
||||
class Stock(object):
|
||||
class Stock:
|
||||
'''
|
||||
An instance of a stock holding consisting of name, shares, and price.
|
||||
'''
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# tableformat.py
|
||||
|
||||
class TableFormatter(object):
|
||||
class TableFormatter:
|
||||
def headings(self, headers):
|
||||
'''
|
||||
Emit the table headers
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# portfolio.py
|
||||
|
||||
class Portfolio(object):
|
||||
class Portfolio:
|
||||
def __init__(self, holdings):
|
||||
self._holdings = holdings
|
||||
|
||||
|
||||
@@ -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.
|
||||
'''
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# tableformat.py
|
||||
|
||||
class TableFormatter(object):
|
||||
class TableFormatter:
|
||||
def headings(self, headers):
|
||||
'''
|
||||
Emit the table headers
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import fileparse
|
||||
import stock
|
||||
|
||||
class Portfolio(object):
|
||||
class Portfolio:
|
||||
def __init__(self):
|
||||
self._holdings = []
|
||||
|
||||
|
||||
@@ -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.
|
||||
'''
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# tableformat.py
|
||||
|
||||
class TableFormatter(object):
|
||||
class TableFormatter:
|
||||
def headings(self, headers):
|
||||
'''
|
||||
Emit the table headers
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import fileparse
|
||||
import stock
|
||||
|
||||
class Portfolio(object):
|
||||
class Portfolio:
|
||||
def __init__(self):
|
||||
self._holdings = []
|
||||
|
||||
|
||||
@@ -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.
|
||||
'''
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# tableformat.py
|
||||
|
||||
class TableFormatter(object):
|
||||
class TableFormatter:
|
||||
def headings(self, headers):
|
||||
'''
|
||||
Emit the table headers
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
from . import fileparse
|
||||
from . import stock
|
||||
|
||||
class Portfolio(object):
|
||||
class Portfolio:
|
||||
def __init__(self):
|
||||
self._holdings = []
|
||||
|
||||
|
||||
@@ -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.
|
||||
'''
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# tableformat.py
|
||||
|
||||
class TableFormatter(object):
|
||||
class TableFormatter:
|
||||
def headings(self, headers):
|
||||
'''
|
||||
Emit the table headers
|
||||
|
||||
@@ -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)
|
||||
|
||||
1
Solutions/9_5/porty-app/MANIFEST.in
Normal file
1
Solutions/9_5/porty-app/MANIFEST.in
Normal file
@@ -0,0 +1 @@
|
||||
include *.csv
|
||||
17
Solutions/9_5/porty-app/README.txt
Normal file
17
Solutions/9_5/porty-app/README.txt
Normal file
@@ -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 %
|
||||
8
Solutions/9_5/porty-app/portfolio.csv
Executable file
8
Solutions/9_5/porty-app/portfolio.csv
Executable file
@@ -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
|
||||
|
0
Solutions/9_5/porty-app/porty/__init__.py
Normal file
0
Solutions/9_5/porty-app/porty/__init__.py
Normal file
47
Solutions/9_5/porty-app/porty/fileparse.py
Normal file
47
Solutions/9_5/porty-app/porty/fileparse.py
Normal file
@@ -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
|
||||
17
Solutions/9_5/porty-app/porty/follow.py
Normal file
17
Solutions/9_5/porty-app/porty/follow.py
Normal file
@@ -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
|
||||
20
Solutions/9_5/porty-app/porty/pcost.py
Normal file
20
Solutions/9_5/porty-app/porty/pcost.py
Normal file
@@ -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)
|
||||
51
Solutions/9_5/porty-app/porty/portfolio.py
Normal file
51
Solutions/9_5/porty-app/porty/portfolio.py
Normal file
@@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
67
Solutions/9_5/porty-app/porty/report.py
Normal file
67
Solutions/9_5/porty-app/porty/report.py
Normal file
@@ -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)
|
||||
33
Solutions/9_5/porty-app/porty/stock.py
Normal file
33
Solutions/9_5/porty-app/porty/stock.py
Normal file
@@ -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
|
||||
81
Solutions/9_5/porty-app/porty/tableformat.py
Normal file
81
Solutions/9_5/porty-app/porty/tableformat.py
Normal file
@@ -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('<tr>', end='')
|
||||
for h in headers:
|
||||
print(f'<th>{h}</th>', end='')
|
||||
print('</tr>')
|
||||
|
||||
def row(self, rowdata):
|
||||
print('<tr>', end='')
|
||||
for d in rowdata:
|
||||
print(f'<td>{d}</td>', end='')
|
||||
print('</tr>')
|
||||
|
||||
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)
|
||||
|
||||
28
Solutions/9_5/porty-app/porty/test_stock.py
Normal file
28
Solutions/9_5/porty-app/porty/test_stock.py
Normal file
@@ -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()
|
||||
43
Solutions/9_5/porty-app/porty/ticker.py
Normal file
43
Solutions/9_5/porty-app/porty/ticker.py
Normal file
@@ -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)
|
||||
34
Solutions/9_5/porty-app/porty/typedproperty.py
Normal file
34
Solutions/9_5/porty-app/porty/typedproperty.py
Normal file
@@ -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
|
||||
|
||||
|
||||
|
||||
31
Solutions/9_5/porty-app/prices.csv
Normal file
31
Solutions/9_5/porty-app/prices.csv
Normal file
@@ -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
|
||||
|
||||
|
6
Solutions/9_5/porty-app/print-report.py
Normal file
6
Solutions/9_5/porty-app/print-report.py
Normal file
@@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
# print-report.py
|
||||
|
||||
import sys
|
||||
from porty.report import main
|
||||
main(sys.argv)
|
||||
13
Solutions/9_5/porty-app/setup.py
Normal file
13
Solutions/9_5/porty-app/setup.py
Normal file
@@ -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'],
|
||||
)
|
||||
Reference in New Issue
Block a user