object patch
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
# stock.py
|
# stock.py
|
||||||
|
|
||||||
class Stock(object):
|
class Stock:
|
||||||
'''
|
'''
|
||||||
An instance of a stock holding consisting of name, shares, and price.
|
An instance of a stock holding consisting of name, shares, and price.
|
||||||
'''
|
'''
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# tableformat.py
|
# tableformat.py
|
||||||
|
|
||||||
class TableFormatter(object):
|
class TableFormatter:
|
||||||
def headings(self, headers):
|
def headings(self, headers):
|
||||||
'''
|
'''
|
||||||
Emit the table headers
|
Emit the table headers
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# stock.py
|
# stock.py
|
||||||
|
|
||||||
class Stock(object):
|
class Stock:
|
||||||
'''
|
'''
|
||||||
An instance of a stock holding consisting of name, shares, and price.
|
An instance of a stock holding consisting of name, shares, and price.
|
||||||
'''
|
'''
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# stock.py
|
# stock.py
|
||||||
|
|
||||||
class Stock(object):
|
class Stock:
|
||||||
'''
|
'''
|
||||||
An instance of a stock holding consisting of name, shares, and price.
|
An instance of a stock holding consisting of name, shares, and price.
|
||||||
'''
|
'''
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# tableformat.py
|
# tableformat.py
|
||||||
|
|
||||||
class TableFormatter(object):
|
class TableFormatter:
|
||||||
def headings(self, headers):
|
def headings(self, headers):
|
||||||
'''
|
'''
|
||||||
Emit the table headers
|
Emit the table headers
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# portfolio.py
|
# portfolio.py
|
||||||
|
|
||||||
class Portfolio(object):
|
class Portfolio:
|
||||||
def __init__(self, holdings):
|
def __init__(self, holdings):
|
||||||
self._holdings = holdings
|
self._holdings = holdings
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# stock.py
|
# stock.py
|
||||||
|
|
||||||
class Stock(object):
|
class Stock:
|
||||||
'''
|
'''
|
||||||
An instance of a stock holding consisting of name, shares, and price.
|
An instance of a stock holding consisting of name, shares, and price.
|
||||||
'''
|
'''
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# tableformat.py
|
# tableformat.py
|
||||||
|
|
||||||
class TableFormatter(object):
|
class TableFormatter:
|
||||||
def headings(self, headers):
|
def headings(self, headers):
|
||||||
'''
|
'''
|
||||||
Emit the table headers
|
Emit the table headers
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# portfolio.py
|
# portfolio.py
|
||||||
|
|
||||||
class Portfolio(object):
|
class Portfolio:
|
||||||
def __init__(self, holdings):
|
def __init__(self, holdings):
|
||||||
self._holdings = holdings
|
self._holdings = holdings
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# stock.py
|
# stock.py
|
||||||
|
|
||||||
class Stock(object):
|
class Stock:
|
||||||
'''
|
'''
|
||||||
An instance of a stock holding consisting of name, shares, and price.
|
An instance of a stock holding consisting of name, shares, and price.
|
||||||
'''
|
'''
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# tableformat.py
|
# tableformat.py
|
||||||
|
|
||||||
class TableFormatter(object):
|
class TableFormatter:
|
||||||
def headings(self, headers):
|
def headings(self, headers):
|
||||||
'''
|
'''
|
||||||
Emit the table headers
|
Emit the table headers
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# portfolio.py
|
# portfolio.py
|
||||||
|
|
||||||
class Portfolio(object):
|
class Portfolio:
|
||||||
def __init__(self, holdings):
|
def __init__(self, holdings):
|
||||||
self._holdings = holdings
|
self._holdings = holdings
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# stock.py
|
# stock.py
|
||||||
|
|
||||||
class Stock(object):
|
class Stock:
|
||||||
'''
|
'''
|
||||||
An instance of a stock holding consisting of name, shares, and price.
|
An instance of a stock holding consisting of name, shares, and price.
|
||||||
'''
|
'''
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# tableformat.py
|
# tableformat.py
|
||||||
|
|
||||||
class TableFormatter(object):
|
class TableFormatter:
|
||||||
def headings(self, headers):
|
def headings(self, headers):
|
||||||
'''
|
'''
|
||||||
Emit the table headers
|
Emit the table headers
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# portfolio.py
|
# portfolio.py
|
||||||
|
|
||||||
class Portfolio(object):
|
class Portfolio:
|
||||||
def __init__(self, holdings):
|
def __init__(self, holdings):
|
||||||
self._holdings = holdings
|
self._holdings = holdings
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# stock.py
|
# stock.py
|
||||||
|
|
||||||
class Stock(object):
|
class Stock:
|
||||||
'''
|
'''
|
||||||
An instance of a stock holding consisting of name, shares, and price.
|
An instance of a stock holding consisting of name, shares, and price.
|
||||||
'''
|
'''
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# tableformat.py
|
# tableformat.py
|
||||||
|
|
||||||
class TableFormatter(object):
|
class TableFormatter:
|
||||||
def headings(self, headers):
|
def headings(self, headers):
|
||||||
'''
|
'''
|
||||||
Emit the table headers
|
Emit the table headers
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import fileparse
|
import fileparse
|
||||||
import stock
|
import stock
|
||||||
|
|
||||||
class Portfolio(object):
|
class Portfolio:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._holdings = []
|
self._holdings = []
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
from typedproperty import String, Integer, Float
|
from typedproperty import String, Integer, Float
|
||||||
|
|
||||||
class Stock(object):
|
class Stock:
|
||||||
'''
|
'''
|
||||||
An instance of a stock holding consisting of name, shares, and price.
|
An instance of a stock holding consisting of name, shares, and price.
|
||||||
'''
|
'''
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# tableformat.py
|
# tableformat.py
|
||||||
|
|
||||||
class TableFormatter(object):
|
class TableFormatter:
|
||||||
def headings(self, headers):
|
def headings(self, headers):
|
||||||
'''
|
'''
|
||||||
Emit the table headers
|
Emit the table headers
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ Float = lambda name: typedproperty(name, float)
|
|||||||
|
|
||||||
# Example
|
# Example
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
class Stock(object):
|
class Stock:
|
||||||
name = typedproperty('name', str)
|
name = typedproperty('name', str)
|
||||||
shares = typedproperty('shares', int)
|
shares = typedproperty('shares', int)
|
||||||
price = typedproperty('price', float)
|
price = typedproperty('price', float)
|
||||||
@@ -31,4 +31,15 @@ if __name__ == '__main__':
|
|||||||
self.price = price
|
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
|
# portfolio.py
|
||||||
|
|
||||||
class Portfolio(object):
|
class Portfolio:
|
||||||
def __init__(self, holdings):
|
def __init__(self, holdings):
|
||||||
self._holdings = holdings
|
self._holdings = holdings
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# stock.py
|
# stock.py
|
||||||
|
|
||||||
class Stock(object):
|
class Stock:
|
||||||
'''
|
'''
|
||||||
An instance of a stock holding consisting of name, shares, and price.
|
An instance of a stock holding consisting of name, shares, and price.
|
||||||
'''
|
'''
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# tableformat.py
|
# tableformat.py
|
||||||
|
|
||||||
class TableFormatter(object):
|
class TableFormatter:
|
||||||
def headings(self, headers):
|
def headings(self, headers):
|
||||||
'''
|
'''
|
||||||
Emit the table headers
|
Emit the table headers
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# portfolio.py
|
# portfolio.py
|
||||||
|
|
||||||
class Portfolio(object):
|
class Portfolio:
|
||||||
def __init__(self, holdings):
|
def __init__(self, holdings):
|
||||||
self._holdings = holdings
|
self._holdings = holdings
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
from typedproperty import String, Integer, Float
|
from typedproperty import String, Integer, Float
|
||||||
|
|
||||||
class Stock(object):
|
class Stock:
|
||||||
'''
|
'''
|
||||||
An instance of a stock holding consisting of name, shares, and price.
|
An instance of a stock holding consisting of name, shares, and price.
|
||||||
'''
|
'''
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# tableformat.py
|
# tableformat.py
|
||||||
|
|
||||||
class TableFormatter(object):
|
class TableFormatter:
|
||||||
def headings(self, headers):
|
def headings(self, headers):
|
||||||
'''
|
'''
|
||||||
Emit the table headers
|
Emit the table headers
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ Float = lambda name: typedproperty(name, float)
|
|||||||
|
|
||||||
# Example
|
# Example
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
class Stock(object):
|
class Stock:
|
||||||
name = typedproperty('name', str)
|
name = typedproperty('name', str)
|
||||||
shares = typedproperty('shares', int)
|
shares = typedproperty('shares', int)
|
||||||
price = typedproperty('price', float)
|
price = typedproperty('price', float)
|
||||||
@@ -30,5 +30,15 @@ if __name__ == '__main__':
|
|||||||
self.shares = shares
|
self.shares = shares
|
||||||
self.price = price
|
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 fileparse
|
||||||
import stock
|
import stock
|
||||||
|
|
||||||
class Portfolio(object):
|
class Portfolio:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._holdings = []
|
self._holdings = []
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
from typedproperty import String, Integer, Float
|
from typedproperty import String, Integer, Float
|
||||||
|
|
||||||
class Stock(object):
|
class Stock:
|
||||||
'''
|
'''
|
||||||
An instance of a stock holding consisting of name, shares, and price.
|
An instance of a stock holding consisting of name, shares, and price.
|
||||||
'''
|
'''
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# tableformat.py
|
# tableformat.py
|
||||||
|
|
||||||
class TableFormatter(object):
|
class TableFormatter:
|
||||||
def headings(self, headers):
|
def headings(self, headers):
|
||||||
'''
|
'''
|
||||||
Emit the table headers
|
Emit the table headers
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ Float = lambda name: typedproperty(name, float)
|
|||||||
|
|
||||||
# Example
|
# Example
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
class Stock(object):
|
class Stock:
|
||||||
name = typedproperty('name', str)
|
name = typedproperty('name', str)
|
||||||
shares = typedproperty('shares', int)
|
shares = typedproperty('shares', int)
|
||||||
price = typedproperty('price', float)
|
price = typedproperty('price', float)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import fileparse
|
import fileparse
|
||||||
import stock
|
import stock
|
||||||
|
|
||||||
class Portfolio(object):
|
class Portfolio:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._holdings = []
|
self._holdings = []
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
from typedproperty import String, Integer, Float
|
from typedproperty import String, Integer, Float
|
||||||
|
|
||||||
class Stock(object):
|
class Stock:
|
||||||
'''
|
'''
|
||||||
An instance of a stock holding consisting of name, shares, and price.
|
An instance of a stock holding consisting of name, shares, and price.
|
||||||
'''
|
'''
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# tableformat.py
|
# tableformat.py
|
||||||
|
|
||||||
class TableFormatter(object):
|
class TableFormatter:
|
||||||
def headings(self, headers):
|
def headings(self, headers):
|
||||||
'''
|
'''
|
||||||
Emit the table headers
|
Emit the table headers
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ Float = lambda name: typedproperty(name, float)
|
|||||||
|
|
||||||
# Example
|
# Example
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
class Stock(object):
|
class Stock:
|
||||||
name = typedproperty('name', str)
|
name = typedproperty('name', str)
|
||||||
shares = typedproperty('shares', int)
|
shares = typedproperty('shares', int)
|
||||||
price = typedproperty('price', float)
|
price = typedproperty('price', float)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
from . import fileparse
|
from . import fileparse
|
||||||
from . import stock
|
from . import stock
|
||||||
|
|
||||||
class Portfolio(object):
|
class Portfolio:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._holdings = []
|
self._holdings = []
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
from .typedproperty import String, Integer, Float
|
from .typedproperty import String, Integer, Float
|
||||||
|
|
||||||
class Stock(object):
|
class Stock:
|
||||||
'''
|
'''
|
||||||
An instance of a stock holding consisting of name, shares, and price.
|
An instance of a stock holding consisting of name, shares, and price.
|
||||||
'''
|
'''
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# tableformat.py
|
# tableformat.py
|
||||||
|
|
||||||
class TableFormatter(object):
|
class TableFormatter:
|
||||||
def headings(self, headers):
|
def headings(self, headers):
|
||||||
'''
|
'''
|
||||||
Emit the table headers
|
Emit the table headers
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ Float = lambda name: typedproperty(name, float)
|
|||||||
|
|
||||||
# Example
|
# Example
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
class Stock(object):
|
class Stock:
|
||||||
name = typedproperty('name', str)
|
name = typedproperty('name', str)
|
||||||
shares = typedproperty('shares', int)
|
shares = typedproperty('shares', int)
|
||||||
price = typedproperty('price', float)
|
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