From e662f8e9ceb1a7f37dbbdb2deb432b7b21fd09ce Mon Sep 17 00:00:00 2001 From: Benjamin Swineford Date: Mon, 14 Aug 2023 11:46:54 -0500 Subject: [PATCH 01/54] add .idea folder --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 68bc17f9..2dc53ca3 100644 --- a/.gitignore +++ b/.gitignore @@ -157,4 +157,4 @@ cython_debug/ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ +.idea/ From 5c33063de47a5e77d441cbdc4f9b2b044bc09592 Mon Sep 17 00:00:00 2001 From: Benjamin Swineford Date: Mon, 14 Aug 2023 11:47:09 -0500 Subject: [PATCH 02/54] done --- Practice/art.py | 16 ++++++++++++++++ Practice/helloworld.py | 1 + 2 files changed, 17 insertions(+) create mode 100644 Practice/art.py create mode 100644 Practice/helloworld.py diff --git a/Practice/art.py b/Practice/art.py new file mode 100644 index 00000000..f0585030 --- /dev/null +++ b/Practice/art.py @@ -0,0 +1,16 @@ +#! +import sys +import random + +chars = "||🫠||🥰||" + + +def draw(rows, columns): + for r in range(rows): + print("".join(random.choice(chars) for _ in range(columns))) + + +if __name__ == "__main__": + if len(sys.argv) != 3: + raise SystemExit("Usage: art.py rows columns") + draw(int(sys.argv[1]), int(sys.argv[2])) diff --git a/Practice/helloworld.py b/Practice/helloworld.py new file mode 100644 index 00000000..ce47b771 --- /dev/null +++ b/Practice/helloworld.py @@ -0,0 +1 @@ +print("hello") \ No newline at end of file From 857d7fb0ce3f248b7c32e3acbc54290142f81912 Mon Sep 17 00:00:00 2001 From: Benjamin Swineford Date: Mon, 14 Aug 2023 11:58:39 -0500 Subject: [PATCH 03/54] ex 1-3 --- Practice/art.py => art.py | 0 Practice/helloworld.py => helloworld.py | 0 pcost.py | 10 ++++++++++ 3 files changed, 10 insertions(+) rename Practice/art.py => art.py (100%) rename Practice/helloworld.py => helloworld.py (100%) create mode 100644 pcost.py diff --git a/Practice/art.py b/art.py similarity index 100% rename from Practice/art.py rename to art.py diff --git a/Practice/helloworld.py b/helloworld.py similarity index 100% rename from Practice/helloworld.py rename to helloworld.py diff --git a/pcost.py b/pcost.py new file mode 100644 index 00000000..c0bf2cfb --- /dev/null +++ b/pcost.py @@ -0,0 +1,10 @@ +total = 0 + +for line in open("Data/portfolio.dat"): + ticker, shares, price = line.split() + shares = int(shares) + price = float(price) + print(f"ticker:{ticker}, shares:{shares}, price:{price}, cost: {shares * price}") + total += shares * price + +print(total) From 5b89af462840f81867483b35e733f9a0c58b0607 Mon Sep 17 00:00:00 2001 From: Benjamin Swineford Date: Mon, 14 Aug 2023 12:02:56 -0500 Subject: [PATCH 04/54] lol why not --- pcost.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pcost.py b/pcost.py index c0bf2cfb..f71f0b9c 100644 --- a/pcost.py +++ b/pcost.py @@ -1,4 +1,4 @@ -total = 0 +total = 0.0 for line in open("Data/portfolio.dat"): ticker, shares, price = line.split() From 9af48bc30e857836adfa2e49ef663293830813f7 Mon Sep 17 00:00:00 2001 From: Benjamin Swineford Date: Mon, 14 Aug 2023 13:34:38 -0500 Subject: [PATCH 05/54] ex 1-4 --- pcost.py | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/pcost.py b/pcost.py index f71f0b9c..c298c3cc 100644 --- a/pcost.py +++ b/pcost.py @@ -1,10 +1,21 @@ -total = 0.0 +def portfolio_cost(filename): + total_cost = 0.0 -for line in open("Data/portfolio.dat"): - ticker, shares, price = line.split() - shares = int(shares) - price = float(price) - print(f"ticker:{ticker}, shares:{shares}, price:{price}, cost: {shares * price}") - total += shares * price + with open(filename) as f: + for line in f: + fields = line.split() -print(total) + try: + nshares = int(fields[1]) + price = float(fields[2]) + total_cost = total_cost + nshares * price + + # This catches errors in int() and float() conversions above + except ValueError as e: + print("Couldn't parse:", repr(line)) + print("Reason:", e) + + return total_cost + + +print(portfolio_cost("../../Data/portfolio3.dat")) From 444cf906ea8228b345df829a48b7f96eeb035187 Mon Sep 17 00:00:00 2001 From: Benjamin Swineford Date: Mon, 14 Aug 2023 13:37:13 -0500 Subject: [PATCH 06/54] ex 1-5 --- stock.py | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 stock.py diff --git a/stock.py b/stock.py new file mode 100644 index 00000000..7b4e71ee --- /dev/null +++ b/stock.py @@ -0,0 +1,8 @@ +class Stock: + def __init__(self, name, shares, price): + self.name = name + self.shares = shares + self.price = price + + def cost(self): + return self.shares * self.price From 59f83bcda2340d5a2abc47f4b16b277f7e6e1d6d Mon Sep 17 00:00:00 2001 From: Benjamin Swineford Date: Mon, 14 Aug 2023 13:53:44 -0500 Subject: [PATCH 07/54] ex 1-6 --- pcost.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pcost.py b/pcost.py index c298c3cc..4477b613 100644 --- a/pcost.py +++ b/pcost.py @@ -18,4 +18,5 @@ def portfolio_cost(filename): return total_cost -print(portfolio_cost("../../Data/portfolio3.dat")) +if __name__ == "__main__": + print("portfolio3 total:", portfolio_cost("Data/portfolio3.dat")) From 04af6ca73c6fbc73978867e3a0f8c21e7e86482e Mon Sep 17 00:00:00 2001 From: Benjamin Swineford Date: Mon, 14 Aug 2023 15:27:53 -0500 Subject: [PATCH 08/54] ex 1-6 --- readrides.py | 180 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 180 insertions(+) create mode 100644 readrides.py diff --git a/readrides.py b/readrides.py new file mode 100644 index 00000000..899ac44c --- /dev/null +++ b/readrides.py @@ -0,0 +1,180 @@ +import csv +import tracemalloc +from collections import namedtuple + + +class Row: + def __init__(self, route, date, daytype, rides): + self.route = route + self.date = date + self.daytype = daytype + self.rides = rides + + +class RowWithSlots: + # Uncomment to see effect of slots + __slots__ = ("route", "date", "daytype", "rides") + + def __init__(self, route, date, daytype, rides): + self.route = route + self.date = date + self.daytype = daytype + self.rides = rides + + +# Uncomment to use a namedtuple instead +# from collections import namedtuple +# Row = namedtuple('Row',('route','date','daytype','rides')) + + +def read_rides_as_dicts(filename): + """ + Read the bus ride data as a list of dicts + """ + records = [] + with open(filename) as f: + rows = csv.reader(f) + headings = next(rows) # Skip headers + + for row in rows: + route = row[0] + date = row[1] + daytype = row[2] + rides = int(row[3]) + + record = {"route": route, "date": date, "daytype": daytype, "rides": rides} + records.append(record) + + return records + + +def read_rides_as_instances(filename): + """ + Read the bus ride data as a list of Class instances + """ + records = [] + with open(filename) as f: + rows = csv.reader(f) + headings = next(rows) # Skip headers + + for row in rows: + route = row[0] + date = row[1] + daytype = row[2] + rides = int(row[3]) + record = Row(route, date, daytype, rides) + records.append(record) + + return records + + +def read_rides_as_instances_with_slots(filename): + """ + Read the bus ride data as a list of Class instances + """ + records = [] + with open(filename) as f: + rows = csv.reader(f) + headings = next(rows) # Skip headers + + for row in rows: + route = row[0] + date = row[1] + daytype = row[2] + rides = int(row[3]) + record = RowWithSlots(route, date, daytype, rides) + records.append(record) + + return records + + +def read_rides_as_tuples(filename): + """ + Read the bus ride data as a list of tuples + """ + records = [] + with open(filename) as f: + rows = csv.reader(f) + headings = next(rows) # Skip headers + + for row in rows: + route = row[0] + date = row[1] + daytype = row[2] + rides = int(row[3]) + record = (route, date, daytype, rides) + records.append(record) + + return records + + +RowTuple = namedtuple("Row", ("route", "date", "daytype", "rides")) + + +def read_rides_as_named_tuples(filename): + """ + Read the bus ride data as a list of tuples + """ + + records = [] + with open(filename) as f: + rows = csv.reader(f) + headings = next(rows) # Skip headers + + for row in rows: + route = row[0] + date = row[1] + daytype = row[2] + rides = int(row[3]) + record = RowTuple(route, date, daytype, rides) + + records.append(record) + + return records + + +def read_file_as_type(filename, type_name): + + try: + if type_name == "tuple": + return read_rides_as_tuples(filename) + + if type_name == "named_tuple": + return read_rides_as_tuples(filename) + + if type_name == "dict": + return read_rides_as_dicts(filename) + + if type_name == "instance": + return read_rides_as_instances(filename) + + if type_name == "slots": + return read_rides_as_instances_with_slots(filename) + + except Exception as e: + print(e) + + return read_rides_as_tuples(filename) + + +if __name__ == "__main__": + tracemalloc.start() + + # for type_name in ["tuple", "named_tuple", "dict", "instance", "slots"]: + for function_to_call in [ + read_rides_as_tuples, + read_rides_as_named_tuples, + read_rides_as_instances, + read_rides_as_instances_with_slots, + read_rides_as_dicts, + ]: + tracemalloc.clear_traces() + + # read_file_as_type("Data/ctabus.csv", type_name) + function_to_call("Data/ctabus.csv") + + current, peak = tracemalloc.get_traced_memory() + + print( + f"Memory Use for {function_to_call.__name__}: Current {current}, Peak {format(peak,'08,.1f')}" + ) From 79beb9765bf550efdb7e5b4f9cb8f1f593f4b2d2 Mon Sep 17 00:00:00 2001 From: Benjamin Swineford Date: Tue, 15 Aug 2023 10:07:07 -0500 Subject: [PATCH 09/54] partial fix; learn some things --- cta_data_analysis.py | 65 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 cta_data_analysis.py diff --git a/cta_data_analysis.py b/cta_data_analysis.py new file mode 100644 index 00000000..dd310880 --- /dev/null +++ b/cta_data_analysis.py @@ -0,0 +1,65 @@ +from readrides import read_file_as_type +from pprint import pprint +from collections import Counter + +rows = read_file_as_type( + "Data/ctabus.csv", + "dict", +) + + +# How many bus routes exist in Chicago? +routes = sorted({s["route"] for s in rows}) + +print(f"Number of routes: {len(routes)}") + + +# How many people rode the number 22 bus on February 2, 2011? What about any route on any date of your choosing? + + +def get_riders_by_date(date, data=rows): + + # count = Counter(row["rides"] for row in routes if date == row["date"]) + # pprint(count) + # for row in data: + # if date == row["date"]: + # pprint(row) + + try: + riders = [row for row in data if date == row["date"]] + count = sum([r["rides"] for r in riders]) + + print( + f'Count: {count}, Revamp: {filter(lambda ride: ride["date"] == date , data)}' + ) + + return {riders: riders, count: count} + + except Exception as e: + print(f"Oops: {e}") + + +def get_riders_by_bus_number(route_to_check, data=rows): + + filtered_rows = [row for row in data if route_to_check == row["route"]] + count = sum([r["rides"] for r in filtered_rows]) + results = {filtered_rows: filtered_rows, count: count} + return results + + +print( + f"Number of riders on February 2, 2011: {get_riders_by_date('02/02/2011')['count']}" +) +print(f"Number of riders on Bus #22: {get_riders_by_bus_number('22')['count']}") + +bus22 = get_riders_by_bus_number("22") + + +print( + f"Riders on February 2, 2011 on Bus #22: {sum([r['rides'] for r in get_riders_by_date('02/02/2011', get_riders_by_bus_number('22')['filtered_rows'])])}" +) +# What is the total number of rides taken on each bus route? + + +# +# What five bus routes had the greatest ten-year increase in ridership from 2001 to 2011? From b9010e5d60e2df941cc4b1d20cd1b3dc78f4fde0 Mon Sep 17 00:00:00 2001 From: Benjamin Swineford Date: Tue, 15 Aug 2023 15:03:04 -0500 Subject: [PATCH 10/54] get it all working & add solution check --- cta_data_analysis.py | 135 ++++++++++++++++++++++++++++++------------- 1 file changed, 96 insertions(+), 39 deletions(-) diff --git a/cta_data_analysis.py b/cta_data_analysis.py index dd310880..cc71c850 100644 --- a/cta_data_analysis.py +++ b/cta_data_analysis.py @@ -1,65 +1,122 @@ -from readrides import read_file_as_type +from readrides import read_file_as_type, read_rides_as_dicts +import csv from pprint import pprint -from collections import Counter +import tracemalloc +from collections import Counter, defaultdict -rows = read_file_as_type( - "Data/ctabus.csv", - "dict", -) +def solutions(): + from collections import defaultdict, Counter -# How many bus routes exist in Chicago? -routes = sorted({s["route"] for s in rows}) + rows = read_file_as_type() -print(f"Number of routes: {len(routes)}") + # -------------------------------------------------- + # Question 1: How many bus routes are in Chicago? + # Solution: Use a set to get unique values. + routes = set() + for row in rows: + routes.add(row["route"]) -# How many people rode the number 22 bus on February 2, 2011? What about any route on any date of your choosing? + answer1 = len(routes) + + # -------------------------------------------------- + # Question 2: How many people rode route 22 on February 2, 2011? + # Solution: Make dictionary with composite keys + + by_route_date = {} + for row in rows: + by_route_date[row["route"], row["date"]] = row["rides"] + + answer2 = by_route_date["22", "02/02/2011"] + + # -------------------------------------------------- + # Question 3: Total number of rides per route + # Solution: Use a counter to tabulate things + rides_per_route = Counter() + for row in rows: + rides_per_route[row["route"]] += row["rides"] + + answer3 = list() + + # Make a table showing the routes and a count ranked by popularity + for route, count in rides_per_route.most_common(): + answer3.append((route, count)) + + # -------------------------------------------------- + # Question 4: Routes with greatest increase in ridership 2001 - 2011 + # Solution: Counters embedded inside a defaultdict + + answer4 = list() + rides_by_year = defaultdict(Counter) + for row in rows: + year = row["date"].split("/")[2] + rides_by_year[year][row["route"]] += row["rides"] -def get_riders_by_date(date, data=rows): + diffs = rides_by_year["2011"] - rides_by_year["2001"] - # count = Counter(row["rides"] for row in routes if date == row["date"]) - # pprint(count) - # for row in data: - # if date == row["date"]: - # pprint(row) + for route, diff in diffs.most_common(5): + answer4.append((route, diff)) - try: - riders = [row for row in data if date == row["date"]] - count = sum([r["rides"] for r in riders]) + return { + "answer1": answer1, + "answer2": answer2, + "answer3": answer3, + "answer4": answer4, + } - print( - f'Count: {count}, Revamp: {filter(lambda ride: ride["date"] == date , data)}' - ) - return {riders: riders, count: count} +rides_by_route = Counter() +rides_by_year = defaultdict(Counter) +unique_routes = set() +by_route_date = {} - except Exception as e: - print(f"Oops: {e}") +rows = read_file_as_type("Data/ctabus.csv") +for row in rows: + route, date, daytype, rides = row.values() + year = date.split("/")[2] -def get_riders_by_bus_number(route_to_check, data=rows): + unique_routes.add(route) - filtered_rows = [row for row in data if route_to_check == row["route"]] - count = sum([r["rides"] for r in filtered_rows]) - results = {filtered_rows: filtered_rows, count: count} - return results + by_route_date[route, date] = rides + rides_by_year[year][route] += rides -print( - f"Number of riders on February 2, 2011: {get_riders_by_date('02/02/2011')['count']}" -) -print(f"Number of riders on Bus #22: {get_riders_by_bus_number('22')['count']}") + rides_by_route[route] += rides -bus22 = get_riders_by_bus_number("22") +unique_routes = sorted(unique_routes) + +answers = solutions() + + +def check_my_answer(answer, solution): + if answer == solution: + print(f"{answer} is correct!") + else: + print(f"{answer} is not correct! The correct answer is {solution}") + + +# How many bus routes exist in Chicago? +myAnswer1 = len(unique_routes) +check_my_answer(myAnswer1, answers["answer1"]) + + +# How many people rode the number 22 bus on February 2, 2011? What about any route on any date of your choosing? +myAnswer2 = by_route_date["22", "02/02/2011"] +check_my_answer(myAnswer2, answers["answer2"]) -print( - f"Riders on February 2, 2011 on Bus #22: {sum([r['rides'] for r in get_riders_by_date('02/02/2011', get_riders_by_bus_number('22')['filtered_rows'])])}" -) # What is the total number of rides taken on each bus route? +for index, (route, count) in enumerate(rides_by_route.most_common(10)): + myAnswer3 = (route, count) + check_my_answer(myAnswer3, answers["answer3"][index]) -# # What five bus routes had the greatest ten-year increase in ridership from 2001 to 2011? +diffs = rides_by_year["2011"] - rides_by_year["2001"] + +for index, (route, diff) in enumerate(diffs.most_common(5)): + myAnswer4 = (route, diff) + check_my_answer(myAnswer4, answers["answer4"][index]) From 5803a24d79c8b38ace9e687854ad1e15639b2d0e Mon Sep 17 00:00:00 2001 From: Benjamin Swineford Date: Tue, 15 Aug 2023 15:03:24 -0500 Subject: [PATCH 11/54] add RideData class --- readrides.py | 112 +++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 91 insertions(+), 21 deletions(-) diff --git a/readrides.py b/readrides.py index 899ac44c..cc22f55a 100644 --- a/readrides.py +++ b/readrides.py @@ -1,8 +1,35 @@ import csv import tracemalloc +from collections.abc import Sequence from collections import namedtuple +class RideData(Sequence): + def __init__(self): + self.routes = [] # Columns + self.dates = [] + self.daytypes = [] + self.numrides = [] + + def __len__(self): + # All lists assumed to have the same length + return len(self.routes) + + def __getitem__(self, index): + return { + "route": self.routes[index], + "date": self.dates[index], + "daytype": self.daytypes[index], + "rides": self.numrides[index], + } + + def append(self, d): + self.routes.append(d["route"]) + self.dates.append(d["date"]) + self.daytypes.append(d["daytype"]) + self.numrides.append((d["rides"])) + + class Row: def __init__(self, route, date, daytype, rides): self.route = route @@ -31,7 +58,7 @@ def read_rides_as_dicts(filename): """ Read the bus ride data as a list of dicts """ - records = [] + records = RideData() with open(filename) as f: rows = csv.reader(f) headings = next(rows) # Skip headers @@ -133,32 +160,83 @@ def read_rides_as_named_tuples(filename): return records -def read_file_as_type(filename, type_name): +def read_rides_with_generator(filename): + f = open(filename) + f_csv = csv.reader(f) + headers = next(f_csv) + + # rows = list() + # + # for (route, date, daytype, rides) in f_csv: + # item = dict(zip(headers, (route, date, daytype, rides))) + # if route == "22" and date == "02/02/2011": + # pprint(item) + # + # rows.append(item) + + return (dict(zip(headers, row)) for row in f_csv) + + +def read_rides_as_columns(filename): + """ + Read the bus ride data into 4 lists, representing columns + """ + routes = [] + dates = [] + daytypes = [] + numrides = [] + with open(filename) as f: + rows = csv.reader(f) + headings = next(rows) # Skip headers + for row in rows: + routes.append(row[0]) + dates.append(row[1]) + daytypes.append(row[2]) + numrides.append(int(row[3])) + return dict(routes=routes, dates=dates, daytypes=daytypes, numrides=numrides) + + +def read_file_as_type(filename="Data/ctabus.csv", type_name="dict"): + tracemalloc.start() + tracemalloc.clear_traces() + + results = None try: - if type_name == "tuple": - return read_rides_as_tuples(filename) + if type_name == "columns": + results = read_rides_with_generator(filename) + + if type_name == "generator": + results = read_rides_as_columns(filename) if type_name == "named_tuple": - return read_rides_as_tuples(filename) + results = read_rides_as_tuples(filename) + + elif type_name == "dict": + results = read_rides_as_dicts(filename) - if type_name == "dict": - return read_rides_as_dicts(filename) + elif type_name == "instance": + results = read_rides_as_instances(filename) - if type_name == "instance": - return read_rides_as_instances(filename) + elif type_name == "slots": + results = read_rides_as_instances_with_slots(filename) - if type_name == "slots": - return read_rides_as_instances_with_slots(filename) + else: + results = read_rides_as_tuples(filename) + + current, peak = tracemalloc.get_traced_memory() + + print( + f"Memory Use for {type_name}: Current {current}, Peak {format(peak, '08,.1f')}" + ) except Exception as e: print(e) - return read_rides_as_tuples(filename) + return results if __name__ == "__main__": - tracemalloc.start() # for type_name in ["tuple", "named_tuple", "dict", "instance", "slots"]: for function_to_call in [ @@ -168,13 +246,5 @@ def read_file_as_type(filename, type_name): read_rides_as_instances_with_slots, read_rides_as_dicts, ]: - tracemalloc.clear_traces() - # read_file_as_type("Data/ctabus.csv", type_name) function_to_call("Data/ctabus.csv") - - current, peak = tracemalloc.get_traced_memory() - - print( - f"Memory Use for {function_to_call.__name__}: Current {current}, Peak {format(peak,'08,.1f')}" - ) From 7156839e1c0e9cbaaf1b48c9f48d7e588ab927b4 Mon Sep 17 00:00:00 2001 From: Benjamin Swineford Date: Tue, 15 Aug 2023 15:03:56 -0500 Subject: [PATCH 12/54] ex 2_4 --- mutint.py | 62 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 mutint.py diff --git a/mutint.py b/mutint.py new file mode 100644 index 00000000..32a9f9b9 --- /dev/null +++ b/mutint.py @@ -0,0 +1,62 @@ +from functools import total_ordering + + +@total_ordering +class MutInt: + __slots__ = ["value"] + + def __init__(self, value): + self.value = value + + def __str__(self): + return str(self.value) + + def __repr__(self): + return f"MutInt({self.value!r})" + + def __format__(self, fmt): + return format(self.value, fmt) + + def __add__(self, other): + if isinstance(other, MutInt): + return MutInt(self.value + other.value) + elif isinstance(other, int): + return MutInt(self.value + other) + else: + return NotImplemented + + __radd__ = __add__ # Reversed operands + + def __iadd__(self, other): + if isinstance(other, MutInt): + self.value += other.value + return self + elif isinstance(other, int): + self.value += other + return self + else: + return NotImplemented + + def __eq__(self, other): + if isinstance(other, MutInt): + return self.value == other.value + elif isinstance(other, int): + return self.value == other + else: + return NotImplemented + + def __lt__(self, other): + if isinstance(other, MutInt): + return self.value < other.value + elif isinstance(other, int): + return self.value < other + else: + return NotImplemented + + def __int__(self): + return self.value + + __index__ = __int__ # Make indexing work + + def __float__(self): + return float(self.value) From 9f5b75d61cba563678b9054d02d8cd5b30e0cd2a Mon Sep 17 00:00:00 2001 From: Benjamin Swineford Date: Tue, 15 Aug 2023 15:04:10 -0500 Subject: [PATCH 13/54] ex 2_1? --- readport.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 readport.py diff --git a/readport.py b/readport.py new file mode 100644 index 00000000..5db720ff --- /dev/null +++ b/readport.py @@ -0,0 +1,14 @@ +import csv + + +def read_portfolio(filename): + portfolio = [] + with open(filename) as f: + rows = csv.reader(f) + headers = next(rows) + + for row in rows: + record = {"name": row[0], "shares": int(row[1]), "price": float(row[2])} + portfolio.append(record) + + return portfolio From df9102f88be444c864e89c990a318b533caef43a Mon Sep 17 00:00:00 2001 From: Benjamin Swineford Date: Tue, 15 Aug 2023 16:44:51 -0500 Subject: [PATCH 14/54] get DataCollection working --- cta_data_analysis.py | 28 +++++++++++--- reader.py | 16 ++++++++ readrides.py | 91 +++++++++++++++++++++++--------------------- 3 files changed, 86 insertions(+), 49 deletions(-) create mode 100644 reader.py diff --git a/cta_data_analysis.py b/cta_data_analysis.py index cc71c850..50ff20dc 100644 --- a/cta_data_analysis.py +++ b/cta_data_analysis.py @@ -4,18 +4,23 @@ import tracemalloc from collections import Counter, defaultdict +from sys import intern +from reader import read_csv_as_dicts + def solutions(): from collections import defaultdict, Counter - rows = read_file_as_type() + solution_rows = read_file_as_type( + type_name="data_collection", converters=[intern, intern, intern, int] + ) # -------------------------------------------------- # Question 1: How many bus routes are in Chicago? # Solution: Use a set to get unique values. routes = set() - for row in rows: + for row in solution_rows: routes.add(row["route"]) answer1 = len(routes) @@ -25,7 +30,7 @@ def solutions(): # Solution: Make dictionary with composite keys by_route_date = {} - for row in rows: + for row in solution_rows: by_route_date[row["route"], row["date"]] = row["rides"] answer2 = by_route_date["22", "02/02/2011"] @@ -34,7 +39,7 @@ def solutions(): # Question 3: Total number of rides per route # Solution: Use a counter to tabulate things rides_per_route = Counter() - for row in rows: + for row in solution_rows: rides_per_route[row["route"]] += row["rides"] answer3 = list() @@ -50,7 +55,7 @@ def solutions(): answer4 = list() rides_by_year = defaultdict(Counter) - for row in rows: + for row in solution_rows: year = row["date"].split("/")[2] rides_by_year[year][row["route"]] += row["rides"] @@ -72,10 +77,21 @@ def solutions(): unique_routes = set() by_route_date = {} -rows = read_file_as_type("Data/ctabus.csv") +rows = read_file_as_type( + filename="Data/ctabus.csv", + type_name="data_collection", + converters=[intern, intern, intern, int], +) + +status = 0 for row in rows: + route, date, daytype, rides = row.values() + if status < 10: + pprint({"route": route, "date": date, "daytype": daytype, "rides": rides}) + status += 1 + year = date.split("/")[2] unique_routes.add(route) diff --git a/reader.py b/reader.py new file mode 100644 index 00000000..4920d0f5 --- /dev/null +++ b/reader.py @@ -0,0 +1,16 @@ +from pprint import pprint +import csv + + +def read_csv_as_dicts(filename, converter_funcs): + + with open(filename) as f: + rows = csv.reader(f) + headers = next(rows) + + converted_values = [ + {name: func(val) for name, func, val in zip(headers, converter_funcs, row)} + for row in rows + ] + + return converted_values diff --git a/readrides.py b/readrides.py index cc22f55a..271778aa 100644 --- a/readrides.py +++ b/readrides.py @@ -1,33 +1,30 @@ import csv import tracemalloc from collections.abc import Sequence -from collections import namedtuple +from collections import namedtuple, defaultdict -class RideData(Sequence): - def __init__(self): - self.routes = [] # Columns - self.dates = [] - self.daytypes = [] - self.numrides = [] +class DataCollection(Sequence): + def __init__(self, columns): + self.column_names = list(columns) + self.column_data = list(columns.values()) def __len__(self): - # All lists assumed to have the same length - return len(self.routes) - - def __getitem__(self, index): - return { - "route": self.routes[index], - "date": self.dates[index], - "daytype": self.daytypes[index], - "rides": self.numrides[index], - } + return len(self.column_data.values()[0]) + + def __getitem__(self, key): + if isinstance(key, slice): + slicer = DataCollection() + slicer.column_data = { + header: data_list[key] for header, data_list in self.column_data.items() + } + return slicer + elif isinstance(key, int): + return dict(zip(self.column_names, (col[key] for col in self.column_data))) def append(self, d): - self.routes.append(d["route"]) - self.dates.append(d["date"]) - self.daytypes.append(d["daytype"]) - self.numrides.append((d["rides"])) + for key, val in d.items(): + self.column_data[key].append(val) class Row: @@ -54,11 +51,37 @@ def __init__(self, route, date, daytype, rides): # Row = namedtuple('Row',('route','date','daytype','rides')) +def read_csv_as_columns(filename, types): + columns = collections.defaultdict(list) + with open(filename) as f: + rows = csv.reader(f) + headers = next(rows) + for row in rows: + for name, func, val in zip(headers, types, row): + columns[name].append(func(val)) + + return DataCollection(columns) + + +def read_rides_as_data_collection(filename, converters): + + columns = defaultdict(list) + + with open(filename) as f: + rows = csv.reader(f) + headers = next(rows) + for row in rows: + for name, func, val in zip(headers, converters, row): + columns[name].append(func(val)) + + return DataCollection(columns) + + def read_rides_as_dicts(filename): """ Read the bus ride data as a list of dicts """ - records = RideData() + records = DataCollection() with open(filename) as f: rows = csv.reader(f) headings = next(rows) # Skip headers @@ -160,7 +183,7 @@ def read_rides_as_named_tuples(filename): return records -def read_rides_with_generator(filename): +def read_rides_as_generator(filename): f = open(filename) f_csv = csv.reader(f) headers = next(f_csv) @@ -196,33 +219,15 @@ def read_rides_as_columns(filename): return dict(routes=routes, dates=dates, daytypes=daytypes, numrides=numrides) -def read_file_as_type(filename="Data/ctabus.csv", type_name="dict"): +def read_file_as_type(filename="Data/ctabus.csv", type_name="dict", converters=[]): tracemalloc.start() tracemalloc.clear_traces() results = None try: - if type_name == "columns": - results = read_rides_with_generator(filename) - - if type_name == "generator": - results = read_rides_as_columns(filename) - - if type_name == "named_tuple": - results = read_rides_as_tuples(filename) - - elif type_name == "dict": - results = read_rides_as_dicts(filename) - - elif type_name == "instance": - results = read_rides_as_instances(filename) - - elif type_name == "slots": - results = read_rides_as_instances_with_slots(filename) - else: - results = read_rides_as_tuples(filename) + results = globals()[f"read_rides_as_{type_name}"](filename, converters) current, peak = tracemalloc.get_traced_memory() From eff5ed00b67be80ab0d678f16b422ab1df56fa79 Mon Sep 17 00:00:00 2001 From: Benjamin Swineford Date: Wed, 16 Aug 2023 10:37:12 -0500 Subject: [PATCH 15/54] fix collection --- readrides.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readrides.py b/readrides.py index 271778aa..aebfad0d 100644 --- a/readrides.py +++ b/readrides.py @@ -52,7 +52,7 @@ def __init__(self, route, date, daytype, rides): def read_csv_as_columns(filename, types): - columns = collections.defaultdict(list) + columns = defaultdict(list) with open(filename) as f: rows = csv.reader(f) headers = next(rows) From aa577db3a07dc103792024a0764dff71728b34e1 Mon Sep 17 00:00:00 2001 From: Benjamin Swineford Date: Wed, 16 Aug 2023 10:37:31 -0500 Subject: [PATCH 16/54] make reader functions more reusable --- reader.py | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 65 insertions(+), 1 deletion(-) diff --git a/reader.py b/reader.py index 4920d0f5..4d12cb1d 100644 --- a/reader.py +++ b/reader.py @@ -1,5 +1,32 @@ -from pprint import pprint import csv +import tracemalloc +from collections import defaultdict +from collections.abc import Sequence + +from pprint import pprint + + +class DataCollection(Sequence): + def __init__(self, columns): + self.column_names = list(columns) + self.column_data = list(columns.values()) + + def __len__(self): + return len(self.column_data.values()[0]) + + def __getitem__(self, key): + if isinstance(key, slice): + slicer = DataCollection() + slicer.column_data = { + header: data_list[key] for header, data_list in self.column_data.items() + } + return slicer + elif isinstance(key, int): + return dict(zip(self.column_names, (col[key] for col in self.column_data))) + + def append(self, d): + for key, val in d.items(): + self.column_data[key].append(val) def read_csv_as_dicts(filename, converter_funcs): @@ -14,3 +41,40 @@ def read_csv_as_dicts(filename, converter_funcs): ] return converted_values + + +def read_file_as_data_collection(filename, converters): + columns = defaultdict(list) + + with open(filename) as f: + rows = csv.reader(f) + headers = next(rows) + for row in rows: + for name, func, val in zip(headers, converters, row): + columns[name].append(func(val)) + + return DataCollection(columns) + + +def read_file_as_type(filename="Data/ctabus.csv", type_name="dict", converters=[]): + tracemalloc.start() + tracemalloc.clear_traces() + + results = None + + try: + + results = globals()[f"read_file_as_{type_name}"](filename, converters) + + current, peak = tracemalloc.get_traced_memory() + + print( + f"Memory Use for {type_name}: Current {current}, Peak {format(peak, '08,.1f')}" + ) + + except Exception as e: + print(e) + + pprint(results) + + return results From 2fb50d45e3f79b7b32eeef280dfdb7506596f0df Mon Sep 17 00:00:00 2001 From: Benjamin Swineford Date: Wed, 16 Aug 2023 10:37:44 -0500 Subject: [PATCH 17/54] ex 3_1 --- stock.py | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/stock.py b/stock.py index 7b4e71ee..f9399974 100644 --- a/stock.py +++ b/stock.py @@ -1,8 +1,51 @@ +from pprint import pprint +from sys import intern + +import reader + + class Stock: def __init__(self, name, shares, price): + self.name = name self.shares = shares self.price = price def cost(self): return self.shares * self.price + + def sell(self, nshares): + self.shares -= nshares + + +def read_portfolio(filename="Data/portfolio.csv"): + + rows = reader.read_file_as_type( + filename=filename, + type_name="data_collection", + converters=[intern, int, float], + ) + + stocks = list() + + for row in rows: + + stocks.append(Stock(row["name"], row["shares"], row["price"])) + + final = [Stock(name, shares, price) for (name, shares, price) in rows] + + return stocks + + +def print_portfolio(p): + + print("| name | shares | price |") + print("---------- ---------- ---------- ") + for line in p: + print(f" {line.name} | {line.price} | {line.shares}") + + +s = Stock("GOOG", 100, 490.10) +portfolio = read_portfolio() + +print_portfolio(portfolio) From f7987a3eeddc5941f3efc2825f92acb57be3a88f Mon Sep 17 00:00:00 2001 From: Benjamin Swineford Date: Wed, 16 Aug 2023 10:54:20 -0500 Subject: [PATCH 18/54] remove pprint --- reader.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/reader.py b/reader.py index 4d12cb1d..d53acf1a 100644 --- a/reader.py +++ b/reader.py @@ -75,6 +75,4 @@ def read_file_as_type(filename="Data/ctabus.csv", type_name="dict", converters=[ except Exception as e: print(e) - pprint(results) - return results From 090d1f5cdf582b4899248faa7280ed37d763b12e Mon Sep 17 00:00:00 2001 From: Benjamin Swineford Date: Wed, 16 Aug 2023 12:03:05 -0500 Subject: [PATCH 19/54] add helpers --- tableformat.py | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 tableformat.py diff --git a/tableformat.py b/tableformat.py new file mode 100644 index 00000000..d79a2079 --- /dev/null +++ b/tableformat.py @@ -0,0 +1,37 @@ +def print_line(line): + print("|" + line + "|") + + +def print_divider(width, columns, separator="-"): + print("|" + (separator * width + "|") * columns) + + +def print_table(table, headers): + list_to_print = list() + + width = max((len(h) + 9) // 10 * 10 for h in headers) + + for row in table: + attributes = [getattr(row, header) for header in headers] + + for attribute in attributes: + try: + width = max(width, len(attribute)) + + except TypeError as e: + continue + + list_to_print.append( + f"|{'|'.join(f'{getattr(row, header):^{width}}' for header in headers)}|" + ) + + print(width) + + print_divider(width=width, columns=len(headers)) + print("|" + "x".join(f"{header:^{width}s}" for header in headers) + "|") + print_divider(width=width, columns=len(headers)) + + for line in list_to_print: + print(line) + + print_divider(width=width, columns=len(headers)) From a356a155f867982a6b80c8558c65dc256ff2d31b Mon Sep 17 00:00:00 2001 From: Benjamin Swineford Date: Wed, 16 Aug 2023 12:03:19 -0500 Subject: [PATCH 20/54] ex 3_3 --- stock.py | 47 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 36 insertions(+), 11 deletions(-) diff --git a/stock.py b/stock.py index f9399974..4a05d561 100644 --- a/stock.py +++ b/stock.py @@ -1,16 +1,29 @@ from pprint import pprint from sys import intern +from decimal import Decimal import reader +import tableformat class Stock: - def __init__(self, name, shares, price): + types = (str, int, float) + def __init__(self, name, shares, price): self.name = name self.shares = shares self.price = price + @classmethod + def from_row(cls, row): + try: + values = [func(val) for func, val in zip(cls.types, row.values())] + + return cls(*values) + + except Exception as e: + print(e) + def cost(self): return self.shares * self.price @@ -18,7 +31,11 @@ def sell(self, nshares): self.shares -= nshares -def read_portfolio(filename="Data/portfolio.csv"): +class DStock(Stock): + types = (str, int, Decimal) + + +def read_portfolio(filename="Data/portfolio.csv", cls=Stock): rows = reader.read_file_as_type( filename=filename, @@ -29,23 +46,31 @@ def read_portfolio(filename="Data/portfolio.csv"): stocks = list() for row in rows: + result = cls.from_row(row) + stocks.append(result) - stocks.append(Stock(row["name"], row["shares"], row["price"])) - - final = [Stock(name, shares, price) for (name, shares, price) in rows] + # final = [Stock(name, shares, price) for (name, shares, price) in rows] return stocks def print_portfolio(p): - - print("| name | shares | price |") - print("---------- ---------- ---------- ") - for line in p: - print(f" {line.name} | {line.price} | {line.shares}") + tableformat.print_table(p, ["name", "shares", "price"]) + # print("%10s %10s %10s" % ("name", "shares", "price")) + # print(("-" * 10 + " ") * 3) + # for s in p: + # print("%10s %10d %10.2f" % (s.name, s.shares, s.price)) -s = Stock("GOOG", 100, 490.10) portfolio = read_portfolio() +Dportfolio = read_portfolio(cls=DStock) print_portfolio(portfolio) + +print() +print() +print() +print() +print() + +print_portfolio(Dportfolio) From 4d18ed549de868bd729709b4c6524219e21a3c02 Mon Sep 17 00:00:00 2001 From: Benjamin Swineford Date: Wed, 16 Aug 2023 13:12:32 -0500 Subject: [PATCH 21/54] ex 3_4 plus premature optimizing for fun --- stock.py | 80 ++++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 61 insertions(+), 19 deletions(-) diff --git a/stock.py b/stock.py index 4a05d561..86cc7aa2 100644 --- a/stock.py +++ b/stock.py @@ -7,7 +7,8 @@ class Stock: - types = (str, int, float) + __slots__ = ('name', '_shares', '_price') + _types = {"name": str, "shares": int, "price": float} def __init__(self, name, shares, price): self.name = name @@ -16,27 +17,69 @@ def __init__(self, name, shares, price): @classmethod def from_row(cls, row): - try: - values = [func(val) for func, val in zip(cls.types, row.values())] + values = [func(val) for func, val in zip(cls._types, row)] + return cls(*values) - return cls(*values) + @classmethod + def check_type(cls, value, type_key): + type_to_check = cls._types.get(type_key) + if not isinstance(value, type_to_check): + try: + converted_value = type_to_check(value) + + print( + f'Value for {type_key} ({value}) converted from {type(value).__name__} to {type_to_check.__name__} ({converted_value})') + + return converted_value + + except ValueError: + raise TypeError( + f"Expected {type_to_check.__name__}, received --{value}-- with type {type(value).__name__}") + + return value + + @property + def shares(self): + return self._shares + + @shares.setter + def shares(self, value): + converted_value = self.check_type(value, 'shares') - except Exception as e: - print(e) + if converted_value < 0: + raise ValueError('shares must be >= 0') + self._shares = converted_value + @property + def price(self): + return self._price + + @price.setter + def price(self, value): + converted_value = self.check_type(value, 'price') + if converted_value <= 0: + raise ValueError('price must be > 0') + self._price = converted_value + + @property def cost(self): - return self.shares * self.price + return self._shares * self._price def sell(self, nshares): - self.shares -= nshares + self._shares -= nshares class DStock(Stock): - types = (str, int, Decimal) + __slots__ = ('name', '_shares', '_price') + _types = {"name": str, "shares": int, "price": Decimal} + + @Stock.cost.getter + def cost(self): + print(type(self._price)) + return Decimal(self._shares) * self._price def read_portfolio(filename="Data/portfolio.csv", cls=Stock): - rows = reader.read_file_as_type( filename=filename, type_name="data_collection", @@ -62,15 +105,14 @@ def print_portfolio(p): # print("%10s %10d %10.2f" % (s.name, s.shares, s.price)) -portfolio = read_portfolio() -Dportfolio = read_portfolio(cls=DStock) +s = Stock("GOOG", 100, 490.1) + +s.price = 42 +s.price = '42' +s.price = 42.0 +s.price = Decimal(42.0) -print_portfolio(portfolio) -print() -print() -print() -print() -print() +# s.price = Decimal(50.000000) -print_portfolio(Dportfolio) +# s.price = "lol" From 66cae9ba0b15cd649f29eed79b48400026d1a4ea Mon Sep 17 00:00:00 2001 From: Benjamin Swineford Date: Thu, 17 Aug 2023 10:38:11 -0500 Subject: [PATCH 22/54] ex 3_5 --- reader.py | 12 +++++ stock.py | 52 +++++++++------------ tableformat.py | 122 +++++++++++++++++++++++++++++++++++++++---------- 3 files changed, 133 insertions(+), 53 deletions(-) diff --git a/reader.py b/reader.py index d53acf1a..def8c159 100644 --- a/reader.py +++ b/reader.py @@ -42,6 +42,18 @@ def read_csv_as_dicts(filename, converter_funcs): return converted_values +def read_csv_as_instances(filename, cls): + ''' + Read a CSV file into a list of instances + ''' + records = [] + with open(filename) as f: + rows = csv.reader(f) + headers = next(rows) + for row in rows: + records.append(cls.from_row(row)) + return records + def read_file_as_data_collection(filename, converters): columns = defaultdict(list) diff --git a/stock.py b/stock.py index 86cc7aa2..1c2518af 100644 --- a/stock.py +++ b/stock.py @@ -3,11 +3,11 @@ from decimal import Decimal import reader -import tableformat +from tableformat import create_formatter, print_table class Stock: - __slots__ = ('name', '_shares', '_price') + __slots__ = ("name", "_shares", "_price") _types = {"name": str, "shares": int, "price": float} def __init__(self, name, shares, price): @@ -17,7 +17,7 @@ def __init__(self, name, shares, price): @classmethod def from_row(cls, row): - values = [func(val) for func, val in zip(cls._types, row)] + values = [func(val) for func, val in zip(cls._types.values(), row)] return cls(*values) @classmethod @@ -28,13 +28,15 @@ def check_type(cls, value, type_key): converted_value = type_to_check(value) print( - f'Value for {type_key} ({value}) converted from {type(value).__name__} to {type_to_check.__name__} ({converted_value})') + f"Value for {type_key} ({value}) converted from {type(value).__name__} to {type_to_check.__name__} ({converted_value})" + ) return converted_value except ValueError: raise TypeError( - f"Expected {type_to_check.__name__}, received --{value}-- with type {type(value).__name__}") + f"Expected {type_to_check.__name__}, received --{value}-- with type {type(value).__name__}" + ) return value @@ -44,10 +46,10 @@ def shares(self): @shares.setter def shares(self, value): - converted_value = self.check_type(value, 'shares') + converted_value = self.check_type(value, "shares") if converted_value < 0: - raise ValueError('shares must be >= 0') + raise ValueError("shares must be >= 0") self._shares = converted_value @property @@ -56,9 +58,9 @@ def price(self): @price.setter def price(self, value): - converted_value = self.check_type(value, 'price') + converted_value = self.check_type(value, "price") if converted_value <= 0: - raise ValueError('price must be > 0') + raise ValueError("price must be > 0") self._price = converted_value @property @@ -68,14 +70,16 @@ def cost(self): def sell(self, nshares): self._shares -= nshares + def __repr__(self): + return + class DStock(Stock): - __slots__ = ('name', '_shares', '_price') + __slots__ = ("name", "_shares", "_price") _types = {"name": str, "shares": int, "price": Decimal} @Stock.cost.getter def cost(self): - print(type(self._price)) return Decimal(self._shares) * self._price @@ -97,22 +101,10 @@ def read_portfolio(filename="Data/portfolio.csv", cls=Stock): return stocks -def print_portfolio(p): - tableformat.print_table(p, ["name", "shares", "price"]) - # print("%10s %10s %10s" % ("name", "shares", "price")) - # print(("-" * 10 + " ") * 3) - # for s in p: - # print("%10s %10d %10.2f" % (s.name, s.shares, s.price)) - - -s = Stock("GOOG", 100, 490.1) - -s.price = 42 -s.price = '42' -s.price = 42.0 -s.price = Decimal(42.0) - - -# s.price = Decimal(50.000000) - -# s.price = "lol" +# portfolio = reader.read_csv_as_instances("Data/portfolio.csv", Stock) +# formatter = create_formatter("text") +# print_table(portfolio, ["name", "shares", "price"], formatter) +# formatter = create_formatter("csv") +# print_table(portfolio, ["name", "shares", "price"], formatter) +# formatter = create_formatter("html") +# print_table(portfolio, ["name", "shares", "price"], formatter) diff --git a/tableformat.py b/tableformat.py index d79a2079..2543d3c5 100644 --- a/tableformat.py +++ b/tableformat.py @@ -1,37 +1,113 @@ -def print_line(line): - print("|" + line + "|") +from pprint import pprint -def print_divider(width, columns, separator="-"): - print("|" + (separator * width + "|") * columns) +class TableFormatter: + def __init__(self): + self._width = 0 + self._columns = 0 + self._list_to_print = list() + @property + def width(self): + return self._width -def print_table(table, headers): - list_to_print = list() + def headings(self, headers): + raise NotImplementedError() - width = max((len(h) + 9) // 10 * 10 for h in headers) + def row(self, rowdata): + raise NotImplementedError() - for row in table: - attributes = [getattr(row, header) for header in headers] + @staticmethod + def print_line(line=""): + print(line) - for attribute in attributes: - try: - width = max(width, len(attribute)) + def print(self): + for line in self._list_to_print: + self.print_line(line) - except TypeError as e: - continue + def print_divider(self, separator="-"): + print(f"|{f'{separator * self._width}|' * self._columns}") - list_to_print.append( - f"|{'|'.join(f'{getattr(row, header):^{width}}' for header in headers)}|" + def add_divider(self, separator="-"): + self._list_to_print.append( + "|".join(f"{separator * self._width}" for _ in range(self._columns)) ) - print(width) - print_divider(width=width, columns=len(headers)) - print("|" + "x".join(f"{header:^{width}s}" for header in headers) + "|") - print_divider(width=width, columns=len(headers)) +def print_table(records, fields, formatter): + formatter.headings(fields) + for r in records: + rowdata = [getattr(r, fieldname) for fieldname in fields] + formatter.row(rowdata) + + formatter.print() + + +class TextTableFormatter(TableFormatter): + def headings(self, headers): + self._columns = len(headers) + self._width = max(self._width, *[(len(h) + 9) // 10 * 10 for h in headers]) + + self.add_divider() + + self._list_to_print.append( + "x".join(f"{header:^{self._width}s}" for header in headers) + ) + self.add_divider() + + def row(self, rowdata): + self._list_to_print.append( + "|".join(f"{cell:^{self._width}}" for cell in rowdata) + ) + + @staticmethod + def print_line(line=""): + print(f"|{line}|") + + def print(self): + for line in self._list_to_print: + self.print_line(line) + + self.print_divider() + + +class CSVTableFormatter(TableFormatter): + def headings(self, headers): + width = max(self._width, *[(len(h) + 9) // 10 * 10 for h in headers]) + self._width = width + + self._list_to_print.append(",".join(header.upper() for header in headers)) + self._list_to_print.append("") + + def row(self, rowdata): + self._list_to_print.append(f"{','.join(f'{cell}' for cell in rowdata)}") + + +class HTMLTableFormatter(TableFormatter): + def tr(self, content): + self._list_to_print.append(f"\t\t {content} ") + + def headings(self, headers): + self._list_to_print.append("\n\t") + self.tr(" ".join(f"" for header in headers)) + self._list_to_print.append("\t\n\t") + + def row(self, rowdata): + self.tr("".join(f"" for row in rowdata)) + + def print(self): + self._list_to_print.append(f"\t\n
{header}
{row}
") + + super().print() - for line in list_to_print: - print(line) - print_divider(width=width, columns=len(headers)) +def create_formatter(name): + if name == "text": + formatter_cls = TextTableFormatter + elif name == "csv": + formatter_cls = CSVTableFormatter + elif name == "html": + formatter_cls = HTMLTableFormatter + else: + raise RuntimeError("Unknown format %s" % name) + return formatter_cls() From 00e2870c566a466c698bdb3c7d6ffebf52c087ff Mon Sep 17 00:00:00 2001 From: Benjamin Swineford Date: Thu, 17 Aug 2023 13:15:06 -0500 Subject: [PATCH 23/54] more formmatting --- stock.py | 55 ++++++++++++++++++++++++++++++-------- tableformat.py | 72 ++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 96 insertions(+), 31 deletions(-) diff --git a/stock.py b/stock.py index 1c2518af..3cd98392 100644 --- a/stock.py +++ b/stock.py @@ -1,10 +1,28 @@ from pprint import pprint -from sys import intern +import sys +from sys import intern, stdout from decimal import Decimal +from colored import Fore, Back, Style +import os import reader from tableformat import create_formatter, print_table +os.system("") + + +class redirect_stdout: + def __init__(self, out_file): + self.out_file = out_file + + def __enter__(self): + self.stdout = sys.stdout + sys.stdout = self.out_file + return self.out_file + + def __exit__(self, ty, val, tb): + sys.stdout = self.stdout + class Stock: __slots__ = ("name", "_shares", "_price") @@ -15,6 +33,16 @@ def __init__(self, name, shares, price): self.shares = shares self.price = price + def __repr__(self): + # Note: The !r format code produces the repr() string + return f"{type(self).__name__}({f'{Fore.blue + Style.bold}{self.name!r}{Style.reset}'}, {Fore.cyan}{self.shares!r}{Style.reset}, {Fore.green}{self.price!r}{Style.reset})" + + def __eq__(self, other): + return isinstance(other, Stock) and ( + (self.name, self.shares, self.price) + == (other.name, other.shares, other.price) + ) + @classmethod def from_row(cls, row): values = [func(val) for func, val in zip(cls._types.values(), row)] @@ -70,9 +98,6 @@ def cost(self): def sell(self, nshares): self._shares -= nshares - def __repr__(self): - return - class DStock(Stock): __slots__ = ("name", "_shares", "_price") @@ -101,10 +126,18 @@ def read_portfolio(filename="Data/portfolio.csv", cls=Stock): return stocks -# portfolio = reader.read_csv_as_instances("Data/portfolio.csv", Stock) -# formatter = create_formatter("text") -# print_table(portfolio, ["name", "shares", "price"], formatter) -# formatter = create_formatter("csv") -# print_table(portfolio, ["name", "shares", "price"], formatter) -# formatter = create_formatter("html") -# print_table(portfolio, ["name", "shares", "price"], formatter) +portfolio = reader.read_csv_as_instances("Data/portfolio.csv", Stock) + + +formatter = create_formatter("text") + +# with redirect_stdout(open("out.txt", "w")) as file: +# print_table(portfolio, ["name", "shares", "price"], formatter) +# file.close() + +for format_to_use in ["text", "csv", "html"]: + print() + formatter = create_formatter(format_to_use) + print_table(portfolio, ["name", "shares", "price"], formatter) + + print() diff --git a/tableformat.py b/tableformat.py index 2543d3c5..536df01a 100644 --- a/tableformat.py +++ b/tableformat.py @@ -1,4 +1,6 @@ from pprint import pprint +from colored import Fore, Back, Style +from html5print import HTMLBeautifier class TableFormatter: @@ -25,14 +27,17 @@ def print(self): for line in self._list_to_print: self.print_line(line) - def print_divider(self, separator="-"): - print(f"|{f'{separator * self._width}|' * self._columns}") - - def add_divider(self, separator="-"): - self._list_to_print.append( - "|".join(f"{separator * self._width}" for _ in range(self._columns)) + def divider(self, separator="-"): + return ( + Fore.yellow + + Style.bold + + "|".join(f"{separator * self._width}" for _ in range(self._columns)) + + Style.reset ) + def add_divider(self): + self._list_to_print.append(self.divider()) + def print_table(records, fields, formatter): formatter.headings(fields) @@ -51,7 +56,10 @@ def headings(self, headers): self.add_divider() self._list_to_print.append( - "x".join(f"{header:^{self._width}s}" for header in headers) + Fore.white + + Back.deep_sky_blue_3a + + "x".join(f"{header:^{self._width}s}" for header in headers) + + Style.reset ) self.add_divider() @@ -62,14 +70,15 @@ def row(self, rowdata): @staticmethod def print_line(line=""): - print(f"|{line}|") + print( + f"{Fore.yellow + Style.bold}|{Style.reset}{line}{Fore.yellow + Style.bold}|{Style.reset}" + ) def print(self): + self.add_divider() for line in self._list_to_print: self.print_line(line) - self.print_divider() - class CSVTableFormatter(TableFormatter): def headings(self, headers): @@ -83,22 +92,45 @@ def row(self, rowdata): self._list_to_print.append(f"{','.join(f'{cell}' for cell in rowdata)}") -class HTMLTableFormatter(TableFormatter): - def tr(self, content): - self._list_to_print.append(f"\t\t {content} ") +def html_tag(tag, content, color="yellow", indent=0): + tag_style = getattr(Fore, color) + Style.bold + return f"{tag_style}<{tag}>{Style.reset}{content}{tag_style}{Style.reset}" + +def tr(content): + return html_tag(tag="tr", content=content, indent=2) + + +class HTMLTableFormatter(TableFormatter): def headings(self, headers): - self._list_to_print.append("\n\t") - self.tr(" ".join(f"" for header in headers)) - self._list_to_print.append("\t\n\t") + self._list_to_print.append( + html_tag( + tag="thead", + content=html_tag( + tag="tr", + content="".join( + html_tag( + tag="th", + content=f"{Style.bold} {header.upper()} {Style.reset}", + color="green", + ) + for header in headers + ), + ), + ) + ) def row(self, rowdata): - self.tr("".join(f"" for row in rowdata)) + tr("".join(html_tag(tag="td", content=cell, indent=3) for cell in rowdata)) def print(self): - self._list_to_print.append(f"\t\n
{header}
{row}
") - - super().print() + table_header = html_tag(tag="thead", content=self._list_to_print[0], indent=1) + table_body = html_tag( + tag="tbody", + indent=1, + content=[self.row(r) for r in self._list_to_print], + ) + print(html_tag(tag="table", content=f"{table_header}{table_body}", indent=0)) def create_formatter(name): From 255d0e8ff574183e731cb933b019ada616a447b6 Mon Sep 17 00:00:00 2001 From: Benjamin Swineford Date: Thu, 17 Aug 2023 13:57:35 -0500 Subject: [PATCH 24/54] set up poetry env --- poetry.lock | 813 +++++++++++++++++++++++++++++++++++++++++++++++++ pyproject.toml | 17 ++ 2 files changed, 830 insertions(+) create mode 100644 poetry.lock create mode 100644 pyproject.toml diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 00000000..06b3384d --- /dev/null +++ b/poetry.lock @@ -0,0 +1,813 @@ +# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. + +[[package]] +name = "aiohttp" +version = "3.8.5" +description = "Async http client/server framework (asyncio)" +optional = false +python-versions = ">=3.6" +files = [ + {file = "aiohttp-3.8.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a94159871304770da4dd371f4291b20cac04e8c94f11bdea1c3478e557fbe0d8"}, + {file = "aiohttp-3.8.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:13bf85afc99ce6f9ee3567b04501f18f9f8dbbb2ea11ed1a2e079670403a7c84"}, + {file = "aiohttp-3.8.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2ce2ac5708501afc4847221a521f7e4b245abf5178cf5ddae9d5b3856ddb2f3a"}, + {file = "aiohttp-3.8.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:96943e5dcc37a6529d18766597c491798b7eb7a61d48878611298afc1fca946c"}, + {file = "aiohttp-3.8.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2ad5c3c4590bb3cc28b4382f031f3783f25ec223557124c68754a2231d989e2b"}, + {file = "aiohttp-3.8.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0c413c633d0512df4dc7fd2373ec06cc6a815b7b6d6c2f208ada7e9e93a5061d"}, + {file = "aiohttp-3.8.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df72ac063b97837a80d80dec8d54c241af059cc9bb42c4de68bd5b61ceb37caa"}, + {file = "aiohttp-3.8.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c48c5c0271149cfe467c0ff8eb941279fd6e3f65c9a388c984e0e6cf57538e14"}, + {file = "aiohttp-3.8.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:368a42363c4d70ab52c2c6420a57f190ed3dfaca6a1b19afda8165ee16416a82"}, + {file = "aiohttp-3.8.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7607ec3ce4993464368505888af5beb446845a014bc676d349efec0e05085905"}, + {file = "aiohttp-3.8.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:0d21c684808288a98914e5aaf2a7c6a3179d4df11d249799c32d1808e79503b5"}, + {file = "aiohttp-3.8.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:312fcfbacc7880a8da0ae8b6abc6cc7d752e9caa0051a53d217a650b25e9a691"}, + {file = "aiohttp-3.8.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ad093e823df03bb3fd37e7dec9d4670c34f9e24aeace76808fc20a507cace825"}, + {file = "aiohttp-3.8.5-cp310-cp310-win32.whl", hash = "sha256:33279701c04351a2914e1100b62b2a7fdb9a25995c4a104259f9a5ead7ed4802"}, + {file = "aiohttp-3.8.5-cp310-cp310-win_amd64.whl", hash = "sha256:6e4a280e4b975a2e7745573e3fc9c9ba0d1194a3738ce1cbaa80626cc9b4f4df"}, + {file = "aiohttp-3.8.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ae871a964e1987a943d83d6709d20ec6103ca1eaf52f7e0d36ee1b5bebb8b9b9"}, + {file = "aiohttp-3.8.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:461908b2578955045efde733719d62f2b649c404189a09a632d245b445c9c975"}, + {file = "aiohttp-3.8.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:72a860c215e26192379f57cae5ab12b168b75db8271f111019509a1196dfc780"}, + {file = "aiohttp-3.8.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc14be025665dba6202b6a71cfcdb53210cc498e50068bc088076624471f8bb9"}, + {file = "aiohttp-3.8.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8af740fc2711ad85f1a5c034a435782fbd5b5f8314c9a3ef071424a8158d7f6b"}, + {file = "aiohttp-3.8.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:841cd8233cbd2111a0ef0a522ce016357c5e3aff8a8ce92bcfa14cef890d698f"}, + {file = "aiohttp-3.8.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ed1c46fb119f1b59304b5ec89f834f07124cd23ae5b74288e364477641060ff"}, + {file = "aiohttp-3.8.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84f8ae3e09a34f35c18fa57f015cc394bd1389bce02503fb30c394d04ee6b938"}, + {file = "aiohttp-3.8.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:62360cb771707cb70a6fd114b9871d20d7dd2163a0feafe43fd115cfe4fe845e"}, + {file = "aiohttp-3.8.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:23fb25a9f0a1ca1f24c0a371523546366bb642397c94ab45ad3aedf2941cec6a"}, + {file = "aiohttp-3.8.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:b0ba0d15164eae3d878260d4c4df859bbdc6466e9e6689c344a13334f988bb53"}, + {file = "aiohttp-3.8.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:5d20003b635fc6ae3f96d7260281dfaf1894fc3aa24d1888a9b2628e97c241e5"}, + {file = "aiohttp-3.8.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0175d745d9e85c40dcc51c8f88c74bfbaef9e7afeeeb9d03c37977270303064c"}, + {file = "aiohttp-3.8.5-cp311-cp311-win32.whl", hash = "sha256:2e1b1e51b0774408f091d268648e3d57f7260c1682e7d3a63cb00d22d71bb945"}, + {file = "aiohttp-3.8.5-cp311-cp311-win_amd64.whl", hash = "sha256:043d2299f6dfdc92f0ac5e995dfc56668e1587cea7f9aa9d8a78a1b6554e5755"}, + {file = "aiohttp-3.8.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:cae533195e8122584ec87531d6df000ad07737eaa3c81209e85c928854d2195c"}, + {file = "aiohttp-3.8.5-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f21e83f355643c345177a5d1d8079f9f28b5133bcd154193b799d380331d5d3"}, + {file = "aiohttp-3.8.5-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a7a75ef35f2df54ad55dbf4b73fe1da96f370e51b10c91f08b19603c64004acc"}, + {file = "aiohttp-3.8.5-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2e2e9839e14dd5308ee773c97115f1e0a1cb1d75cbeeee9f33824fa5144c7634"}, + {file = "aiohttp-3.8.5-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c44e65da1de4403d0576473e2344828ef9c4c6244d65cf4b75549bb46d40b8dd"}, + {file = "aiohttp-3.8.5-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:78d847e4cde6ecc19125ccbc9bfac4a7ab37c234dd88fbb3c5c524e8e14da543"}, + {file = "aiohttp-3.8.5-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:c7a815258e5895d8900aec4454f38dca9aed71085f227537208057853f9d13f2"}, + {file = "aiohttp-3.8.5-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:8b929b9bd7cd7c3939f8bcfffa92fae7480bd1aa425279d51a89327d600c704d"}, + {file = "aiohttp-3.8.5-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:5db3a5b833764280ed7618393832e0853e40f3d3e9aa128ac0ba0f8278d08649"}, + {file = "aiohttp-3.8.5-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:a0215ce6041d501f3155dc219712bc41252d0ab76474615b9700d63d4d9292af"}, + {file = "aiohttp-3.8.5-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:fd1ed388ea7fbed22c4968dd64bab0198de60750a25fe8c0c9d4bef5abe13824"}, + {file = "aiohttp-3.8.5-cp36-cp36m-win32.whl", hash = "sha256:6e6783bcc45f397fdebc118d772103d751b54cddf5b60fbcc958382d7dd64f3e"}, + {file = "aiohttp-3.8.5-cp36-cp36m-win_amd64.whl", hash = "sha256:b5411d82cddd212644cf9360879eb5080f0d5f7d809d03262c50dad02f01421a"}, + {file = "aiohttp-3.8.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:01d4c0c874aa4ddfb8098e85d10b5e875a70adc63db91f1ae65a4b04d3344cda"}, + {file = "aiohttp-3.8.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5980a746d547a6ba173fd5ee85ce9077e72d118758db05d229044b469d9029a"}, + {file = "aiohttp-3.8.5-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2a482e6da906d5e6e653be079b29bc173a48e381600161c9932d89dfae5942ef"}, + {file = "aiohttp-3.8.5-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80bd372b8d0715c66c974cf57fe363621a02f359f1ec81cba97366948c7fc873"}, + {file = "aiohttp-3.8.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1161b345c0a444ebcf46bf0a740ba5dcf50612fd3d0528883fdc0eff578006a"}, + {file = "aiohttp-3.8.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cd56db019015b6acfaaf92e1ac40eb8434847d9bf88b4be4efe5bfd260aee692"}, + {file = "aiohttp-3.8.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:153c2549f6c004d2754cc60603d4668899c9895b8a89397444a9c4efa282aaf4"}, + {file = "aiohttp-3.8.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4a01951fabc4ce26ab791da5f3f24dca6d9a6f24121746eb19756416ff2d881b"}, + {file = "aiohttp-3.8.5-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bfb9162dcf01f615462b995a516ba03e769de0789de1cadc0f916265c257e5d8"}, + {file = "aiohttp-3.8.5-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:7dde0009408969a43b04c16cbbe252c4f5ef4574ac226bc8815cd7342d2028b6"}, + {file = "aiohttp-3.8.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:4149d34c32f9638f38f544b3977a4c24052042affa895352d3636fa8bffd030a"}, + {file = "aiohttp-3.8.5-cp37-cp37m-win32.whl", hash = "sha256:68c5a82c8779bdfc6367c967a4a1b2aa52cd3595388bf5961a62158ee8a59e22"}, + {file = "aiohttp-3.8.5-cp37-cp37m-win_amd64.whl", hash = "sha256:2cf57fb50be5f52bda004b8893e63b48530ed9f0d6c96c84620dc92fe3cd9b9d"}, + {file = "aiohttp-3.8.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:eca4bf3734c541dc4f374ad6010a68ff6c6748f00451707f39857f429ca36ced"}, + {file = "aiohttp-3.8.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1274477e4c71ce8cfe6c1ec2f806d57c015ebf84d83373676036e256bc55d690"}, + {file = "aiohttp-3.8.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:28c543e54710d6158fc6f439296c7865b29e0b616629767e685a7185fab4a6b9"}, + {file = "aiohttp-3.8.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:910bec0c49637d213f5d9877105d26e0c4a4de2f8b1b29405ff37e9fc0ad52b8"}, + {file = "aiohttp-3.8.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5443910d662db951b2e58eb70b0fbe6b6e2ae613477129a5805d0b66c54b6cb7"}, + {file = "aiohttp-3.8.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2e460be6978fc24e3df83193dc0cc4de46c9909ed92dd47d349a452ef49325b7"}, + {file = "aiohttp-3.8.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb1558def481d84f03b45888473fc5a1f35747b5f334ef4e7a571bc0dfcb11f8"}, + {file = "aiohttp-3.8.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:34dd0c107799dcbbf7d48b53be761a013c0adf5571bf50c4ecad5643fe9cfcd0"}, + {file = "aiohttp-3.8.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:aa1990247f02a54185dc0dff92a6904521172a22664c863a03ff64c42f9b5410"}, + {file = "aiohttp-3.8.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:0e584a10f204a617d71d359fe383406305a4b595b333721fa50b867b4a0a1548"}, + {file = "aiohttp-3.8.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:a3cf433f127efa43fee6b90ea4c6edf6c4a17109d1d037d1a52abec84d8f2e42"}, + {file = "aiohttp-3.8.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:c11f5b099adafb18e65c2c997d57108b5bbeaa9eeee64a84302c0978b1ec948b"}, + {file = "aiohttp-3.8.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:84de26ddf621d7ac4c975dbea4c945860e08cccde492269db4e1538a6a6f3c35"}, + {file = "aiohttp-3.8.5-cp38-cp38-win32.whl", hash = "sha256:ab88bafedc57dd0aab55fa728ea10c1911f7e4d8b43e1d838a1739f33712921c"}, + {file = "aiohttp-3.8.5-cp38-cp38-win_amd64.whl", hash = "sha256:5798a9aad1879f626589f3df0f8b79b3608a92e9beab10e5fda02c8a2c60db2e"}, + {file = "aiohttp-3.8.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a6ce61195c6a19c785df04e71a4537e29eaa2c50fe745b732aa937c0c77169f3"}, + {file = "aiohttp-3.8.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:773dd01706d4db536335fcfae6ea2440a70ceb03dd3e7378f3e815b03c97ab51"}, + {file = "aiohttp-3.8.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f83a552443a526ea38d064588613aca983d0ee0038801bc93c0c916428310c28"}, + {file = "aiohttp-3.8.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f7372f7341fcc16f57b2caded43e81ddd18df53320b6f9f042acad41f8e049a"}, + {file = "aiohttp-3.8.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ea353162f249c8097ea63c2169dd1aa55de1e8fecbe63412a9bc50816e87b761"}, + {file = "aiohttp-3.8.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5d47ae48db0b2dcf70bc8a3bc72b3de86e2a590fc299fdbbb15af320d2659de"}, + {file = "aiohttp-3.8.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d827176898a2b0b09694fbd1088c7a31836d1a505c243811c87ae53a3f6273c1"}, + {file = "aiohttp-3.8.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3562b06567c06439d8b447037bb655ef69786c590b1de86c7ab81efe1c9c15d8"}, + {file = "aiohttp-3.8.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4e874cbf8caf8959d2adf572a78bba17cb0e9d7e51bb83d86a3697b686a0ab4d"}, + {file = "aiohttp-3.8.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6809a00deaf3810e38c628e9a33271892f815b853605a936e2e9e5129762356c"}, + {file = "aiohttp-3.8.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:33776e945d89b29251b33a7e7d006ce86447b2cfd66db5e5ded4e5cd0340585c"}, + {file = "aiohttp-3.8.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:eaeed7abfb5d64c539e2db173f63631455f1196c37d9d8d873fc316470dfbacd"}, + {file = "aiohttp-3.8.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e91d635961bec2d8f19dfeb41a539eb94bd073f075ca6dae6c8dc0ee89ad6f91"}, + {file = "aiohttp-3.8.5-cp39-cp39-win32.whl", hash = "sha256:00ad4b6f185ec67f3e6562e8a1d2b69660be43070bd0ef6fcec5211154c7df67"}, + {file = "aiohttp-3.8.5-cp39-cp39-win_amd64.whl", hash = "sha256:c0a9034379a37ae42dea7ac1e048352d96286626251862e448933c0f59cbd79c"}, + {file = "aiohttp-3.8.5.tar.gz", hash = "sha256:b9552ec52cc147dbf1944ac7ac98af7602e51ea2dcd076ed194ca3c0d1c7d0bc"}, +] + +[package.dependencies] +aiosignal = ">=1.1.2" +async-timeout = ">=4.0.0a3,<5.0" +attrs = ">=17.3.0" +charset-normalizer = ">=2.0,<4.0" +frozenlist = ">=1.1.1" +multidict = ">=4.5,<7.0" +yarl = ">=1.0,<2.0" + +[package.extras] +speedups = ["Brotli", "aiodns", "cchardet"] + +[[package]] +name = "aiosignal" +version = "1.3.1" +description = "aiosignal: a list of registered asynchronous callbacks" +optional = false +python-versions = ">=3.7" +files = [ + {file = "aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17"}, + {file = "aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc"}, +] + +[package.dependencies] +frozenlist = ">=1.1.0" + +[[package]] +name = "async-timeout" +version = "4.0.3" +description = "Timeout context manager for asyncio programs" +optional = false +python-versions = ">=3.7" +files = [ + {file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"}, + {file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"}, +] + +[[package]] +name = "attrs" +version = "23.1.0" +description = "Classes Without Boilerplate" +optional = false +python-versions = ">=3.7" +files = [ + {file = "attrs-23.1.0-py3-none-any.whl", hash = "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04"}, + {file = "attrs-23.1.0.tar.gz", hash = "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015"}, +] + +[package.extras] +cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] +dev = ["attrs[docs,tests]", "pre-commit"] +docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] +tests = ["attrs[tests-no-zope]", "zope-interface"] +tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] + +[[package]] +name = "beautifulsoup4" +version = "4.12.2" +description = "Screen-scraping library" +optional = false +python-versions = ">=3.6.0" +files = [ + {file = "beautifulsoup4-4.12.2-py3-none-any.whl", hash = "sha256:bd2520ca0d9d7d12694a53d44ac482d181b4ec1888909b035a3dbf40d0f57d4a"}, + {file = "beautifulsoup4-4.12.2.tar.gz", hash = "sha256:492bbc69dca35d12daac71c4db1bfff0c876c00ef4a2ffacce226d4638eb72da"}, +] + +[package.dependencies] +soupsieve = ">1.2" + +[package.extras] +html5lib = ["html5lib"] +lxml = ["lxml"] + +[[package]] +name = "black" +version = "23.7.0" +description = "The uncompromising code formatter." +optional = false +python-versions = ">=3.8" +files = [ + {file = "black-23.7.0-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:5c4bc552ab52f6c1c506ccae05681fab58c3f72d59ae6e6639e8885e94fe2587"}, + {file = "black-23.7.0-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:552513d5cd5694590d7ef6f46e1767a4df9af168d449ff767b13b084c020e63f"}, + {file = "black-23.7.0-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:86cee259349b4448adb4ef9b204bb4467aae74a386bce85d56ba4f5dc0da27be"}, + {file = "black-23.7.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:501387a9edcb75d7ae8a4412bb8749900386eaef258f1aefab18adddea1936bc"}, + {file = "black-23.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:fb074d8b213749fa1d077d630db0d5f8cc3b2ae63587ad4116e8a436e9bbe995"}, + {file = "black-23.7.0-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:b5b0ee6d96b345a8b420100b7d71ebfdd19fab5e8301aff48ec270042cd40ac2"}, + {file = "black-23.7.0-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:893695a76b140881531062d48476ebe4a48f5d1e9388177e175d76234ca247cd"}, + {file = "black-23.7.0-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:c333286dc3ddca6fdff74670b911cccedacb4ef0a60b34e491b8a67c833b343a"}, + {file = "black-23.7.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:831d8f54c3a8c8cf55f64d0422ee875eecac26f5f649fb6c1df65316b67c8926"}, + {file = "black-23.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:7f3bf2dec7d541b4619b8ce526bda74a6b0bffc480a163fed32eb8b3c9aed8ad"}, + {file = "black-23.7.0-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:f9062af71c59c004cd519e2fb8f5d25d39e46d3af011b41ab43b9c74e27e236f"}, + {file = "black-23.7.0-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:01ede61aac8c154b55f35301fac3e730baf0c9cf8120f65a9cd61a81cfb4a0c3"}, + {file = "black-23.7.0-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:327a8c2550ddc573b51e2c352adb88143464bb9d92c10416feb86b0f5aee5ff6"}, + {file = "black-23.7.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d1c6022b86f83b632d06f2b02774134def5d4d4f1dac8bef16d90cda18ba28a"}, + {file = "black-23.7.0-cp38-cp38-win_amd64.whl", hash = "sha256:27eb7a0c71604d5de083757fbdb245b1a4fae60e9596514c6ec497eb63f95320"}, + {file = "black-23.7.0-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:8417dbd2f57b5701492cd46edcecc4f9208dc75529bcf76c514864e48da867d9"}, + {file = "black-23.7.0-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:47e56d83aad53ca140da0af87678fb38e44fd6bc0af71eebab2d1f59b1acf1d3"}, + {file = "black-23.7.0-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:25cc308838fe71f7065df53aedd20327969d05671bac95b38fdf37ebe70ac087"}, + {file = "black-23.7.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:642496b675095d423f9b8448243336f8ec71c9d4d57ec17bf795b67f08132a91"}, + {file = "black-23.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:ad0014efc7acf0bd745792bd0d8857413652979200ab924fbf239062adc12491"}, + {file = "black-23.7.0-py3-none-any.whl", hash = "sha256:9fd59d418c60c0348505f2ddf9609c1e1de8e7493eab96198fc89d9f865e7a96"}, + {file = "black-23.7.0.tar.gz", hash = "sha256:022a582720b0d9480ed82576c920a8c1dde97cc38ff11d8d8859b3bd6ca9eedb"}, +] + +[package.dependencies] +aiohttp = {version = ">=3.7.4", optional = true, markers = "extra == \"d\""} +click = ">=8.0.0" +mypy-extensions = ">=0.4.3" +packaging = ">=22.0" +pathspec = ">=0.9.0" +platformdirs = ">=2" + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.7.4)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] + +[[package]] +name = "certifi" +version = "2023.7.22" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.6" +files = [ + {file = "certifi-2023.7.22-py3-none-any.whl", hash = "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"}, + {file = "certifi-2023.7.22.tar.gz", hash = "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082"}, +] + +[[package]] +name = "chardet" +version = "5.2.0" +description = "Universal encoding detector for Python 3" +optional = false +python-versions = ">=3.7" +files = [ + {file = "chardet-5.2.0-py3-none-any.whl", hash = "sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970"}, + {file = "chardet-5.2.0.tar.gz", hash = "sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7"}, +] + +[[package]] +name = "charset-normalizer" +version = "3.2.0" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "charset-normalizer-3.2.0.tar.gz", hash = "sha256:3bb3d25a8e6c0aedd251753a79ae98a093c7e7b471faa3aa9a93a81431987ace"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b87549028f680ca955556e3bd57013ab47474c3124dc069faa0b6545b6c9710"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7c70087bfee18a42b4040bb9ec1ca15a08242cf5867c58726530bdf3945672ed"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a103b3a7069b62f5d4890ae1b8f0597618f628b286b03d4bc9195230b154bfa9"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94aea8eff76ee6d1cdacb07dd2123a68283cb5569e0250feab1240058f53b623"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:db901e2ac34c931d73054d9797383d0f8009991e723dab15109740a63e7f902a"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b0dac0ff919ba34d4df1b6131f59ce95b08b9065233446be7e459f95554c0dc8"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:193cbc708ea3aca45e7221ae58f0fd63f933753a9bfb498a3b474878f12caaad"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09393e1b2a9461950b1c9a45d5fd251dc7c6f228acab64da1c9c0165d9c7765c"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:baacc6aee0b2ef6f3d308e197b5d7a81c0e70b06beae1f1fcacffdbd124fe0e3"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:bf420121d4c8dce6b889f0e8e4ec0ca34b7f40186203f06a946fa0276ba54029"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:c04a46716adde8d927adb9457bbe39cf473e1e2c2f5d0a16ceb837e5d841ad4f"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:aaf63899c94de41fe3cf934601b0f7ccb6b428c6e4eeb80da72c58eab077b19a"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d62e51710986674142526ab9f78663ca2b0726066ae26b78b22e0f5e571238dd"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-win32.whl", hash = "sha256:04e57ab9fbf9607b77f7d057974694b4f6b142da9ed4a199859d9d4d5c63fe96"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:48021783bdf96e3d6de03a6e39a1171ed5bd7e8bb93fc84cc649d11490f87cea"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4957669ef390f0e6719db3613ab3a7631e68424604a7b448f079bee145da6e09"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:46fb8c61d794b78ec7134a715a3e564aafc8f6b5e338417cb19fe9f57a5a9bf2"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f779d3ad205f108d14e99bb3859aa7dd8e9c68874617c72354d7ecaec2a054ac"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f25c229a6ba38a35ae6e25ca1264621cc25d4d38dca2942a7fce0b67a4efe918"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2efb1bd13885392adfda4614c33d3b68dee4921fd0ac1d3988f8cbb7d589e72a"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f30b48dd7fa1474554b0b0f3fdfdd4c13b5c737a3c6284d3cdc424ec0ffff3a"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:246de67b99b6851627d945db38147d1b209a899311b1305dd84916f2b88526c6"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bd9b3b31adcb054116447ea22caa61a285d92e94d710aa5ec97992ff5eb7cf3"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8c2f5e83493748286002f9369f3e6607c565a6a90425a3a1fef5ae32a36d749d"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:3170c9399da12c9dc66366e9d14da8bf7147e1e9d9ea566067bbce7bb74bd9c2"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7a4826ad2bd6b07ca615c74ab91f32f6c96d08f6fcc3902ceeedaec8cdc3bcd6"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:3b1613dd5aee995ec6d4c69f00378bbd07614702a315a2cf6c1d21461fe17c23"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9e608aafdb55eb9f255034709e20d5a83b6d60c054df0802fa9c9883d0a937aa"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-win32.whl", hash = "sha256:f2a1d0fd4242bd8643ce6f98927cf9c04540af6efa92323e9d3124f57727bfc1"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:681eb3d7e02e3c3655d1b16059fbfb605ac464c834a0c629048a30fad2b27489"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c57921cda3a80d0f2b8aec7e25c8aa14479ea92b5b51b6876d975d925a2ea346"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41b25eaa7d15909cf3ac4c96088c1f266a9a93ec44f87f1d13d4a0e86c81b982"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f058f6963fd82eb143c692cecdc89e075fa0828db2e5b291070485390b2f1c9c"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7647ebdfb9682b7bb97e2a5e7cb6ae735b1c25008a70b906aecca294ee96cf4"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eef9df1eefada2c09a5e7a40991b9fc6ac6ef20b1372abd48d2794a316dc0449"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e03b8895a6990c9ab2cdcd0f2fe44088ca1c65ae592b8f795c3294af00a461c3"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:ee4006268ed33370957f55bf2e6f4d263eaf4dc3cfc473d1d90baff6ed36ce4a"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c4983bf937209c57240cff65906b18bb35e64ae872da6a0db937d7b4af845dd7"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:3bb7fda7260735efe66d5107fb7e6af6a7c04c7fce9b2514e04b7a74b06bf5dd"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:72814c01533f51d68702802d74f77ea026b5ec52793c791e2da806a3844a46c3"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:70c610f6cbe4b9fce272c407dd9d07e33e6bf7b4aa1b7ffb6f6ded8e634e3592"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-win32.whl", hash = "sha256:a401b4598e5d3f4a9a811f3daf42ee2291790c7f9d74b18d75d6e21dda98a1a1"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:c0b21078a4b56965e2b12f247467b234734491897e99c1d51cee628da9786959"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:95eb302ff792e12aba9a8b8f8474ab229a83c103d74a750ec0bd1c1eea32e669"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1a100c6d595a7f316f1b6f01d20815d916e75ff98c27a01ae817439ea7726329"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6339d047dab2780cc6220f46306628e04d9750f02f983ddb37439ca47ced7149"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4b749b9cc6ee664a3300bb3a273c1ca8068c46be705b6c31cf5d276f8628a94"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a38856a971c602f98472050165cea2cdc97709240373041b69030be15047691f"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f87f746ee241d30d6ed93969de31e5ffd09a2961a051e60ae6bddde9ec3583aa"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89f1b185a01fe560bc8ae5f619e924407efca2191b56ce749ec84982fc59a32a"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1c8a2f4c69e08e89632defbfabec2feb8a8d99edc9f89ce33c4b9e36ab63037"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2f4ac36d8e2b4cc1aa71df3dd84ff8efbe3bfb97ac41242fbcfc053c67434f46"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a386ebe437176aab38c041de1260cd3ea459c6ce5263594399880bbc398225b2"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:ccd16eb18a849fd8dcb23e23380e2f0a354e8daa0c984b8a732d9cfaba3a776d"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:e6a5bf2cba5ae1bb80b154ed68a3cfa2fa00fde979a7f50d6598d3e17d9ac20c"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:45de3f87179c1823e6d9e32156fb14c1927fcc9aba21433f088fdfb555b77c10"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-win32.whl", hash = "sha256:1000fba1057b92a65daec275aec30586c3de2401ccdcd41f8a5c1e2c87078706"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:8b2c760cfc7042b27ebdb4a43a4453bd829a5742503599144d54a032c5dc7e9e"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:855eafa5d5a2034b4621c74925d89c5efef61418570e5ef9b37717d9c796419c"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:203f0c8871d5a7987be20c72442488a0b8cfd0f43b7973771640fc593f56321f"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e857a2232ba53ae940d3456f7533ce6ca98b81917d47adc3c7fd55dad8fab858"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e86d77b090dbddbe78867a0275cb4df08ea195e660f1f7f13435a4649e954e5"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4fb39a81950ec280984b3a44f5bd12819953dc5fa3a7e6fa7a80db5ee853952"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2dee8e57f052ef5353cf608e0b4c871aee320dd1b87d351c28764fc0ca55f9f4"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8700f06d0ce6f128de3ccdbc1acaea1ee264d2caa9ca05daaf492fde7c2a7200"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1920d4ff15ce893210c1f0c0e9d19bfbecb7983c76b33f046c13a8ffbd570252"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c1c76a1743432b4b60ab3358c937a3fe1341c828ae6194108a94c69028247f22"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f7560358a6811e52e9c4d142d497f1a6e10103d3a6881f18d04dbce3729c0e2c"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:c8063cf17b19661471ecbdb3df1c84f24ad2e389e326ccaf89e3fb2484d8dd7e"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:cd6dbe0238f7743d0efe563ab46294f54f9bc8f4b9bcf57c3c666cc5bc9d1299"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1249cbbf3d3b04902ff081ffbb33ce3377fa6e4c7356f759f3cd076cc138d020"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-win32.whl", hash = "sha256:6c409c0deba34f147f77efaa67b8e4bb83d2f11c8806405f76397ae5b8c0d1c9"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:7095f6fbfaa55defb6b733cfeb14efaae7a29f0b59d8cf213be4e7ca0b857b80"}, + {file = "charset_normalizer-3.2.0-py3-none-any.whl", hash = "sha256:8e098148dd37b4ce3baca71fb394c81dc5d9c7728c95df695d2dca218edf40e6"}, +] + +[[package]] +name = "click" +version = "8.1.7" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "colored" +version = "2.2.3" +description = "Simple python library for color and formatting to terminal" +optional = false +python-versions = ">=3.9" +files = [ + {file = "colored-2.2.3-py3-none-any.whl", hash = "sha256:1318b2fb8e0313d39724b8ab0707af79d1e2c0e60710b608a00e70fe0f84ff5d"}, + {file = "colored-2.2.3.tar.gz", hash = "sha256:1905ae45fa2b7fd63a8b4776586e63aeaba4df8db225b72b78fd167408558983"}, +] + +[[package]] +name = "frozenlist" +version = "1.4.0" +description = "A list-like structure which implements collections.abc.MutableSequence" +optional = false +python-versions = ">=3.8" +files = [ + {file = "frozenlist-1.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:764226ceef3125e53ea2cb275000e309c0aa5464d43bd72abd661e27fffc26ab"}, + {file = "frozenlist-1.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d6484756b12f40003c6128bfcc3fa9f0d49a687e171186c2d85ec82e3758c559"}, + {file = "frozenlist-1.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9ac08e601308e41eb533f232dbf6b7e4cea762f9f84f6357136eed926c15d12c"}, + {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d081f13b095d74b67d550de04df1c756831f3b83dc9881c38985834387487f1b"}, + {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:71932b597f9895f011f47f17d6428252fc728ba2ae6024e13c3398a087c2cdea"}, + {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:981b9ab5a0a3178ff413bca62526bb784249421c24ad7381e39d67981be2c326"}, + {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e41f3de4df3e80de75845d3e743b3f1c4c8613c3997a912dbf0229fc61a8b963"}, + {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6918d49b1f90821e93069682c06ffde41829c346c66b721e65a5c62b4bab0300"}, + {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0e5c8764c7829343d919cc2dfc587a8db01c4f70a4ebbc49abde5d4b158b007b"}, + {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8d0edd6b1c7fb94922bf569c9b092ee187a83f03fb1a63076e7774b60f9481a8"}, + {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e29cda763f752553fa14c68fb2195150bfab22b352572cb36c43c47bedba70eb"}, + {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:0c7c1b47859ee2cac3846fde1c1dc0f15da6cec5a0e5c72d101e0f83dcb67ff9"}, + {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:901289d524fdd571be1c7be054f48b1f88ce8dddcbdf1ec698b27d4b8b9e5d62"}, + {file = "frozenlist-1.4.0-cp310-cp310-win32.whl", hash = "sha256:1a0848b52815006ea6596c395f87449f693dc419061cc21e970f139d466dc0a0"}, + {file = "frozenlist-1.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:b206646d176a007466358aa21d85cd8600a415c67c9bd15403336c331a10d956"}, + {file = "frozenlist-1.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:de343e75f40e972bae1ef6090267f8260c1446a1695e77096db6cfa25e759a95"}, + {file = "frozenlist-1.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ad2a9eb6d9839ae241701d0918f54c51365a51407fd80f6b8289e2dfca977cc3"}, + {file = "frozenlist-1.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bd7bd3b3830247580de99c99ea2a01416dfc3c34471ca1298bccabf86d0ff4dc"}, + {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bdf1847068c362f16b353163391210269e4f0569a3c166bc6a9f74ccbfc7e839"}, + {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:38461d02d66de17455072c9ba981d35f1d2a73024bee7790ac2f9e361ef1cd0c"}, + {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5a32087d720c608f42caed0ef36d2b3ea61a9d09ee59a5142d6070da9041b8f"}, + {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dd65632acaf0d47608190a71bfe46b209719bf2beb59507db08ccdbe712f969b"}, + {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:261b9f5d17cac914531331ff1b1d452125bf5daa05faf73b71d935485b0c510b"}, + {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b89ac9768b82205936771f8d2eb3ce88503b1556324c9f903e7156669f521472"}, + {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:008eb8b31b3ea6896da16c38c1b136cb9fec9e249e77f6211d479db79a4eaf01"}, + {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:e74b0506fa5aa5598ac6a975a12aa8928cbb58e1f5ac8360792ef15de1aa848f"}, + {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:490132667476f6781b4c9458298b0c1cddf237488abd228b0b3650e5ecba7467"}, + {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:76d4711f6f6d08551a7e9ef28c722f4a50dd0fc204c56b4bcd95c6cc05ce6fbb"}, + {file = "frozenlist-1.4.0-cp311-cp311-win32.whl", hash = "sha256:a02eb8ab2b8f200179b5f62b59757685ae9987996ae549ccf30f983f40602431"}, + {file = "frozenlist-1.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:515e1abc578dd3b275d6a5114030b1330ba044ffba03f94091842852f806f1c1"}, + {file = "frozenlist-1.4.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:f0ed05f5079c708fe74bf9027e95125334b6978bf07fd5ab923e9e55e5fbb9d3"}, + {file = "frozenlist-1.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ca265542ca427bf97aed183c1676e2a9c66942e822b14dc6e5f42e038f92a503"}, + {file = "frozenlist-1.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:491e014f5c43656da08958808588cc6c016847b4360e327a62cb308c791bd2d9"}, + {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:17ae5cd0f333f94f2e03aaf140bb762c64783935cc764ff9c82dff626089bebf"}, + {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1e78fb68cf9c1a6aa4a9a12e960a5c9dfbdb89b3695197aa7064705662515de2"}, + {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5655a942f5f5d2c9ed93d72148226d75369b4f6952680211972a33e59b1dfdc"}, + {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c11b0746f5d946fecf750428a95f3e9ebe792c1ee3b1e96eeba145dc631a9672"}, + {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e66d2a64d44d50d2543405fb183a21f76b3b5fd16f130f5c99187c3fb4e64919"}, + {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:88f7bc0fcca81f985f78dd0fa68d2c75abf8272b1f5c323ea4a01a4d7a614efc"}, + {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5833593c25ac59ede40ed4de6d67eb42928cca97f26feea219f21d0ed0959b79"}, + {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:fec520865f42e5c7f050c2a79038897b1c7d1595e907a9e08e3353293ffc948e"}, + {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:b826d97e4276750beca7c8f0f1a4938892697a6bcd8ec8217b3312dad6982781"}, + {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ceb6ec0a10c65540421e20ebd29083c50e6d1143278746a4ef6bcf6153171eb8"}, + {file = "frozenlist-1.4.0-cp38-cp38-win32.whl", hash = "sha256:2b8bcf994563466db019fab287ff390fffbfdb4f905fc77bc1c1d604b1c689cc"}, + {file = "frozenlist-1.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:a6c8097e01886188e5be3e6b14e94ab365f384736aa1fca6a0b9e35bd4a30bc7"}, + {file = "frozenlist-1.4.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:6c38721585f285203e4b4132a352eb3daa19121a035f3182e08e437cface44bf"}, + {file = "frozenlist-1.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a0c6da9aee33ff0b1a451e867da0c1f47408112b3391dd43133838339e410963"}, + {file = "frozenlist-1.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:93ea75c050c5bb3d98016b4ba2497851eadf0ac154d88a67d7a6816206f6fa7f"}, + {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f61e2dc5ad442c52b4887f1fdc112f97caeff4d9e6ebe78879364ac59f1663e1"}, + {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aa384489fefeb62321b238e64c07ef48398fe80f9e1e6afeff22e140e0850eef"}, + {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:10ff5faaa22786315ef57097a279b833ecab1a0bfb07d604c9cbb1c4cdc2ed87"}, + {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:007df07a6e3eb3e33e9a1fe6a9db7af152bbd8a185f9aaa6ece10a3529e3e1c6"}, + {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f4f399d28478d1f604c2ff9119907af9726aed73680e5ed1ca634d377abb087"}, + {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c5374b80521d3d3f2ec5572e05adc94601985cc526fb276d0c8574a6d749f1b3"}, + {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ce31ae3e19f3c902de379cf1323d90c649425b86de7bbdf82871b8a2a0615f3d"}, + {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7211ef110a9194b6042449431e08c4d80c0481e5891e58d429df5899690511c2"}, + {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:556de4430ce324c836789fa4560ca62d1591d2538b8ceb0b4f68fb7b2384a27a"}, + {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7645a8e814a3ee34a89c4a372011dcd817964ce8cb273c8ed6119d706e9613e3"}, + {file = "frozenlist-1.4.0-cp39-cp39-win32.whl", hash = "sha256:19488c57c12d4e8095a922f328df3f179c820c212940a498623ed39160bc3c2f"}, + {file = "frozenlist-1.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:6221d84d463fb110bdd7619b69cb43878a11d51cbb9394ae3105d082d5199167"}, + {file = "frozenlist-1.4.0.tar.gz", hash = "sha256:09163bdf0b2907454042edb19f887c6d33806adc71fbd54afc14908bfdc22251"}, +] + +[[package]] +name = "html5lib" +version = "1.1" +description = "HTML parser based on the WHATWG HTML specification" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "html5lib-1.1-py2.py3-none-any.whl", hash = "sha256:0d78f8fde1c230e99fe37986a60526d7049ed4bf8a9fadbad5f00e22e58e041d"}, + {file = "html5lib-1.1.tar.gz", hash = "sha256:b2e5b40261e20f354d198eae92afc10d750afb487ed5e50f9c4eaf07c184146f"}, +] + +[package.dependencies] +six = ">=1.9" +webencodings = "*" + +[package.extras] +all = ["chardet (>=2.2)", "genshi", "lxml"] +chardet = ["chardet (>=2.2)"] +genshi = ["genshi"] +lxml = ["lxml"] + +[[package]] +name = "html5print" +version = "0.1.2" +description = "HTML5, CSS, Javascript Pretty Print" +optional = false +python-versions = "*" +files = [ + {file = "html5print-0.1.2.tar.gz", hash = "sha256:1ff645698a7192a61f8a5963f23dc894d16fa3346804a2d1ac462683f6e44d4b"}, +] + +[package.dependencies] +beautifulsoup4 = ">=4.3.2" +chardet = ">=2.2.1" +html5lib = ">=0.999" +ply = "3.4" +requests = ">=2.3.5" +slimit = ">=0.8.1" +tinycss2 = ">=0.4" + +[[package]] +name = "idna" +version = "3.4" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.5" +files = [ + {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, + {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, +] + +[[package]] +name = "multidict" +version = "6.0.4" +description = "multidict implementation" +optional = false +python-versions = ">=3.7" +files = [ + {file = "multidict-6.0.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b1a97283e0c85772d613878028fec909f003993e1007eafa715b24b377cb9b8"}, + {file = "multidict-6.0.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eeb6dcc05e911516ae3d1f207d4b0520d07f54484c49dfc294d6e7d63b734171"}, + {file = "multidict-6.0.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d6d635d5209b82a3492508cf5b365f3446afb65ae7ebd755e70e18f287b0adf7"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c048099e4c9e9d615545e2001d3d8a4380bd403e1a0578734e0d31703d1b0c0b"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ea20853c6dbbb53ed34cb4d080382169b6f4554d394015f1bef35e881bf83547"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:16d232d4e5396c2efbbf4f6d4df89bfa905eb0d4dc5b3549d872ab898451f569"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36c63aaa167f6c6b04ef2c85704e93af16c11d20de1d133e39de6a0e84582a93"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:64bdf1086b6043bf519869678f5f2757f473dee970d7abf6da91ec00acb9cb98"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:43644e38f42e3af682690876cff722d301ac585c5b9e1eacc013b7a3f7b696a0"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7582a1d1030e15422262de9f58711774e02fa80df0d1578995c76214f6954988"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:ddff9c4e225a63a5afab9dd15590432c22e8057e1a9a13d28ed128ecf047bbdc"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:ee2a1ece51b9b9e7752e742cfb661d2a29e7bcdba2d27e66e28a99f1890e4fa0"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a2e4369eb3d47d2034032a26c7a80fcb21a2cb22e1173d761a162f11e562caa5"}, + {file = "multidict-6.0.4-cp310-cp310-win32.whl", hash = "sha256:574b7eae1ab267e5f8285f0fe881f17efe4b98c39a40858247720935b893bba8"}, + {file = "multidict-6.0.4-cp310-cp310-win_amd64.whl", hash = "sha256:4dcbb0906e38440fa3e325df2359ac6cb043df8e58c965bb45f4e406ecb162cc"}, + {file = "multidict-6.0.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0dfad7a5a1e39c53ed00d2dd0c2e36aed4650936dc18fd9a1826a5ae1cad6f03"}, + {file = "multidict-6.0.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:64da238a09d6039e3bd39bb3aee9c21a5e34f28bfa5aa22518581f910ff94af3"}, + {file = "multidict-6.0.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ff959bee35038c4624250473988b24f846cbeb2c6639de3602c073f10410ceba"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:01a3a55bd90018c9c080fbb0b9f4891db37d148a0a18722b42f94694f8b6d4c9"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c5cb09abb18c1ea940fb99360ea0396f34d46566f157122c92dfa069d3e0e982"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:666daae833559deb2d609afa4490b85830ab0dfca811a98b70a205621a6109fe"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11bdf3f5e1518b24530b8241529d2050014c884cf18b6fc69c0c2b30ca248710"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d18748f2d30f94f498e852c67d61261c643b349b9d2a581131725595c45ec6c"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:458f37be2d9e4c95e2d8866a851663cbc76e865b78395090786f6cd9b3bbf4f4"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b1a2eeedcead3a41694130495593a559a668f382eee0727352b9a41e1c45759a"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7d6ae9d593ef8641544d6263c7fa6408cc90370c8cb2bbb65f8d43e5b0351d9c"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:5979b5632c3e3534e42ca6ff856bb24b2e3071b37861c2c727ce220d80eee9ed"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dcfe792765fab89c365123c81046ad4103fcabbc4f56d1c1997e6715e8015461"}, + {file = "multidict-6.0.4-cp311-cp311-win32.whl", hash = "sha256:3601a3cece3819534b11d4efc1eb76047488fddd0c85a3948099d5da4d504636"}, + {file = "multidict-6.0.4-cp311-cp311-win_amd64.whl", hash = "sha256:81a4f0b34bd92df3da93315c6a59034df95866014ac08535fc819f043bfd51f0"}, + {file = "multidict-6.0.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:67040058f37a2a51ed8ea8f6b0e6ee5bd78ca67f169ce6122f3e2ec80dfe9b78"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:853888594621e6604c978ce2a0444a1e6e70c8d253ab65ba11657659dcc9100f"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:39ff62e7d0f26c248b15e364517a72932a611a9b75f35b45be078d81bdb86603"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:af048912e045a2dc732847d33821a9d84ba553f5c5f028adbd364dd4765092ac"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1e8b901e607795ec06c9e42530788c45ac21ef3aaa11dbd0c69de543bfb79a9"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62501642008a8b9871ddfccbf83e4222cf8ac0d5aeedf73da36153ef2ec222d2"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:99b76c052e9f1bc0721f7541e5e8c05db3941eb9ebe7b8553c625ef88d6eefde"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:509eac6cf09c794aa27bcacfd4d62c885cce62bef7b2c3e8b2e49d365b5003fe"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:21a12c4eb6ddc9952c415f24eef97e3e55ba3af61f67c7bc388dcdec1404a067"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:5cad9430ab3e2e4fa4a2ef4450f548768400a2ac635841bc2a56a2052cdbeb87"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ab55edc2e84460694295f401215f4a58597f8f7c9466faec545093045476327d"}, + {file = "multidict-6.0.4-cp37-cp37m-win32.whl", hash = "sha256:5a4dcf02b908c3b8b17a45fb0f15b695bf117a67b76b7ad18b73cf8e92608775"}, + {file = "multidict-6.0.4-cp37-cp37m-win_amd64.whl", hash = "sha256:6ed5f161328b7df384d71b07317f4d8656434e34591f20552c7bcef27b0ab88e"}, + {file = "multidict-6.0.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5fc1b16f586f049820c5c5b17bb4ee7583092fa0d1c4e28b5239181ff9532e0c"}, + {file = "multidict-6.0.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1502e24330eb681bdaa3eb70d6358e818e8e8f908a22a1851dfd4e15bc2f8161"}, + {file = "multidict-6.0.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b692f419760c0e65d060959df05f2a531945af31fda0c8a3b3195d4efd06de11"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45e1ecb0379bfaab5eef059f50115b54571acfbe422a14f668fc8c27ba410e7e"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ddd3915998d93fbcd2566ddf9cf62cdb35c9e093075f862935573d265cf8f65d"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:59d43b61c59d82f2effb39a93c48b845efe23a3852d201ed2d24ba830d0b4cf2"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc8e1d0c705233c5dd0c5e6460fbad7827d5d36f310a0fadfd45cc3029762258"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6aa0418fcc838522256761b3415822626f866758ee0bc6632c9486b179d0b52"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6748717bb10339c4760c1e63da040f5f29f5ed6e59d76daee30305894069a660"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4d1a3d7ef5e96b1c9e92f973e43aa5e5b96c659c9bc3124acbbd81b0b9c8a951"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4372381634485bec7e46718edc71528024fcdc6f835baefe517b34a33c731d60"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:fc35cb4676846ef752816d5be2193a1e8367b4c1397b74a565a9d0389c433a1d"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4b9d9e4e2b37daddb5c23ea33a3417901fa7c7b3dee2d855f63ee67a0b21e5b1"}, + {file = "multidict-6.0.4-cp38-cp38-win32.whl", hash = "sha256:e41b7e2b59679edfa309e8db64fdf22399eec4b0b24694e1b2104fb789207779"}, + {file = "multidict-6.0.4-cp38-cp38-win_amd64.whl", hash = "sha256:d6c254ba6e45d8e72739281ebc46ea5eb5f101234f3ce171f0e9f5cc86991480"}, + {file = "multidict-6.0.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:16ab77bbeb596e14212e7bab8429f24c1579234a3a462105cda4a66904998664"}, + {file = "multidict-6.0.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bc779e9e6f7fda81b3f9aa58e3a6091d49ad528b11ed19f6621408806204ad35"}, + {file = "multidict-6.0.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ceef517eca3e03c1cceb22030a3e39cb399ac86bff4e426d4fc6ae49052cc60"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:281af09f488903fde97923c7744bb001a9b23b039a909460d0f14edc7bf59706"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:52f2dffc8acaba9a2f27174c41c9e57f60b907bb9f096b36b1a1f3be71c6284d"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b41156839806aecb3641f3208c0dafd3ac7775b9c4c422d82ee2a45c34ba81ca"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5e3fc56f88cc98ef8139255cf8cd63eb2c586531e43310ff859d6bb3a6b51f1"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8316a77808c501004802f9beebde51c9f857054a0c871bd6da8280e718444449"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f70b98cd94886b49d91170ef23ec5c0e8ebb6f242d734ed7ed677b24d50c82cf"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bf6774e60d67a9efe02b3616fee22441d86fab4c6d335f9d2051d19d90a40063"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:e69924bfcdda39b722ef4d9aa762b2dd38e4632b3641b1d9a57ca9cd18f2f83a"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:6b181d8c23da913d4ff585afd1155a0e1194c0b50c54fcfe286f70cdaf2b7176"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:52509b5be062d9eafc8170e53026fbc54cf3b32759a23d07fd935fb04fc22d95"}, + {file = "multidict-6.0.4-cp39-cp39-win32.whl", hash = "sha256:27c523fbfbdfd19c6867af7346332b62b586eed663887392cff78d614f9ec313"}, + {file = "multidict-6.0.4-cp39-cp39-win_amd64.whl", hash = "sha256:33029f5734336aa0d4c0384525da0387ef89148dc7191aae00ca5fb23d7aafc2"}, + {file = "multidict-6.0.4.tar.gz", hash = "sha256:3666906492efb76453c0e7b97f2cf459b0682e7402c0489a95484965dbc1da49"}, +] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.5" +files = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, +] + +[[package]] +name = "packaging" +version = "23.1" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.7" +files = [ + {file = "packaging-23.1-py3-none-any.whl", hash = "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61"}, + {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"}, +] + +[[package]] +name = "pathspec" +version = "0.11.2" +description = "Utility library for gitignore style pattern matching of file paths." +optional = false +python-versions = ">=3.7" +files = [ + {file = "pathspec-0.11.2-py3-none-any.whl", hash = "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20"}, + {file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"}, +] + +[[package]] +name = "platformdirs" +version = "3.10.0" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +optional = false +python-versions = ">=3.7" +files = [ + {file = "platformdirs-3.10.0-py3-none-any.whl", hash = "sha256:d7c24979f292f916dc9cbf8648319032f551ea8c49a4c9bf2fb556a02070ec1d"}, + {file = "platformdirs-3.10.0.tar.gz", hash = "sha256:b45696dab2d7cc691a3226759c0d3b00c47c8b6e293d96f6436f733303f77f6d"}, +] + +[package.extras] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"] + +[[package]] +name = "ply" +version = "3.4" +description = "Python Lex & Yacc" +optional = false +python-versions = "*" +files = [ + {file = "ply-3.4.tar.gz", hash = "sha256:af435f11b7bdd69da5ffbc3fecb8d70a7073ec952e101764c88720cdefb2546b"}, +] + +[[package]] +name = "requests" +version = "2.31.0" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.7" +files = [ + {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, + {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] + +[[package]] +name = "slimit" +version = "0.8.1" +description = "SlimIt - JavaScript minifier" +optional = false +python-versions = "*" +files = [ + {file = "slimit-0.8.1.zip", hash = "sha256:f433dcef899f166b207b67d91d3f7344659cb33b8259818f084167244e17720b"}, +] + +[package.dependencies] +ply = ">=3.4" + +[[package]] +name = "soupsieve" +version = "2.4.1" +description = "A modern CSS selector implementation for Beautiful Soup." +optional = false +python-versions = ">=3.7" +files = [ + {file = "soupsieve-2.4.1-py3-none-any.whl", hash = "sha256:1c1bfee6819544a3447586c889157365a27e10d88cde3ad3da0cf0ddf646feb8"}, + {file = "soupsieve-2.4.1.tar.gz", hash = "sha256:89d12b2d5dfcd2c9e8c22326da9d9aa9cb3dfab0a83a024f05704076ee8d35ea"}, +] + +[[package]] +name = "tinycss2" +version = "1.2.1" +description = "A tiny CSS parser" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tinycss2-1.2.1-py3-none-any.whl", hash = "sha256:2b80a96d41e7c3914b8cda8bc7f705a4d9c49275616e886103dd839dfc847847"}, + {file = "tinycss2-1.2.1.tar.gz", hash = "sha256:8cff3a8f066c2ec677c06dbc7b45619804a6938478d9d73c284b29d14ecb0627"}, +] + +[package.dependencies] +webencodings = ">=0.4" + +[package.extras] +doc = ["sphinx", "sphinx_rtd_theme"] +test = ["flake8", "isort", "pytest"] + +[[package]] +name = "urllib3" +version = "2.0.4" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=3.7" +files = [ + {file = "urllib3-2.0.4-py3-none-any.whl", hash = "sha256:de7df1803967d2c2a98e4b11bb7d6bd9210474c46e8a0401514e3a42a75ebde4"}, + {file = "urllib3-2.0.4.tar.gz", hash = "sha256:8d22f86aae8ef5e410d4f539fde9ce6b2113a001bb4d189e0aed70642d602b11"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +secure = ["certifi", "cryptography (>=1.9)", "idna (>=2.0.0)", "pyopenssl (>=17.1.0)", "urllib3-secure-extra"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + +[[package]] +name = "webencodings" +version = "0.5.1" +description = "Character encoding aliases for legacy web content" +optional = false +python-versions = "*" +files = [ + {file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"}, + {file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"}, +] + +[[package]] +name = "yarl" +version = "1.9.2" +description = "Yet another URL library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "yarl-1.9.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8c2ad583743d16ddbdf6bb14b5cd76bf43b0d0006e918809d5d4ddf7bde8dd82"}, + {file = "yarl-1.9.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:82aa6264b36c50acfb2424ad5ca537a2060ab6de158a5bd2a72a032cc75b9eb8"}, + {file = "yarl-1.9.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c0c77533b5ed4bcc38e943178ccae29b9bcf48ffd1063f5821192f23a1bd27b9"}, + {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee4afac41415d52d53a9833ebae7e32b344be72835bbb589018c9e938045a560"}, + {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9bf345c3a4f5ba7f766430f97f9cc1320786f19584acc7086491f45524a551ac"}, + {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2a96c19c52ff442a808c105901d0bdfd2e28575b3d5f82e2f5fd67e20dc5f4ea"}, + {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:891c0e3ec5ec881541f6c5113d8df0315ce5440e244a716b95f2525b7b9f3608"}, + {file = "yarl-1.9.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c3a53ba34a636a256d767c086ceb111358876e1fb6b50dfc4d3f4951d40133d5"}, + {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:566185e8ebc0898b11f8026447eacd02e46226716229cea8db37496c8cdd26e0"}, + {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:2b0738fb871812722a0ac2154be1f049c6223b9f6f22eec352996b69775b36d4"}, + {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:32f1d071b3f362c80f1a7d322bfd7b2d11e33d2adf395cc1dd4df36c9c243095"}, + {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:e9fdc7ac0d42bc3ea78818557fab03af6181e076a2944f43c38684b4b6bed8e3"}, + {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:56ff08ab5df8429901ebdc5d15941b59f6253393cb5da07b4170beefcf1b2528"}, + {file = "yarl-1.9.2-cp310-cp310-win32.whl", hash = "sha256:8ea48e0a2f931064469bdabca50c2f578b565fc446f302a79ba6cc0ee7f384d3"}, + {file = "yarl-1.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:50f33040f3836e912ed16d212f6cc1efb3231a8a60526a407aeb66c1c1956dde"}, + {file = "yarl-1.9.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:646d663eb2232d7909e6601f1a9107e66f9791f290a1b3dc7057818fe44fc2b6"}, + {file = "yarl-1.9.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aff634b15beff8902d1f918012fc2a42e0dbae6f469fce134c8a0dc51ca423bb"}, + {file = "yarl-1.9.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a83503934c6273806aed765035716216cc9ab4e0364f7f066227e1aaea90b8d0"}, + {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b25322201585c69abc7b0e89e72790469f7dad90d26754717f3310bfe30331c2"}, + {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:22a94666751778629f1ec4280b08eb11815783c63f52092a5953faf73be24191"}, + {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ec53a0ea2a80c5cd1ab397925f94bff59222aa3cf9c6da938ce05c9ec20428d"}, + {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:159d81f22d7a43e6eabc36d7194cb53f2f15f498dbbfa8edc8a3239350f59fe7"}, + {file = "yarl-1.9.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:832b7e711027c114d79dffb92576acd1bd2decc467dec60e1cac96912602d0e6"}, + {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:95d2ecefbcf4e744ea952d073c6922e72ee650ffc79028eb1e320e732898d7e8"}, + {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:d4e2c6d555e77b37288eaf45b8f60f0737c9efa3452c6c44626a5455aeb250b9"}, + {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:783185c75c12a017cc345015ea359cc801c3b29a2966c2655cd12b233bf5a2be"}, + {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:b8cc1863402472f16c600e3e93d542b7e7542a540f95c30afd472e8e549fc3f7"}, + {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:822b30a0f22e588b32d3120f6d41e4ed021806418b4c9f0bc3048b8c8cb3f92a"}, + {file = "yarl-1.9.2-cp311-cp311-win32.whl", hash = "sha256:a60347f234c2212a9f0361955007fcf4033a75bf600a33c88a0a8e91af77c0e8"}, + {file = "yarl-1.9.2-cp311-cp311-win_amd64.whl", hash = "sha256:be6b3fdec5c62f2a67cb3f8c6dbf56bbf3f61c0f046f84645cd1ca73532ea051"}, + {file = "yarl-1.9.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:38a3928ae37558bc1b559f67410df446d1fbfa87318b124bf5032c31e3447b74"}, + {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac9bb4c5ce3975aeac288cfcb5061ce60e0d14d92209e780c93954076c7c4367"}, + {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3da8a678ca8b96c8606bbb8bfacd99a12ad5dd288bc6f7979baddd62f71c63ef"}, + {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:13414591ff516e04fcdee8dc051c13fd3db13b673c7a4cb1350e6b2ad9639ad3"}, + {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf74d08542c3a9ea97bb8f343d4fcbd4d8f91bba5ec9d5d7f792dbe727f88938"}, + {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e7221580dc1db478464cfeef9b03b95c5852cc22894e418562997df0d074ccc"}, + {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:494053246b119b041960ddcd20fd76224149cfea8ed8777b687358727911dd33"}, + {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:52a25809fcbecfc63ac9ba0c0fb586f90837f5425edfd1ec9f3372b119585e45"}, + {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:e65610c5792870d45d7b68c677681376fcf9cc1c289f23e8e8b39c1485384185"}, + {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:1b1bba902cba32cdec51fca038fd53f8beee88b77efc373968d1ed021024cc04"}, + {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:662e6016409828ee910f5d9602a2729a8a57d74b163c89a837de3fea050c7582"}, + {file = "yarl-1.9.2-cp37-cp37m-win32.whl", hash = "sha256:f364d3480bffd3aa566e886587eaca7c8c04d74f6e8933f3f2c996b7f09bee1b"}, + {file = "yarl-1.9.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6a5883464143ab3ae9ba68daae8e7c5c95b969462bbe42e2464d60e7e2698368"}, + {file = "yarl-1.9.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5610f80cf43b6202e2c33ba3ec2ee0a2884f8f423c8f4f62906731d876ef4fac"}, + {file = "yarl-1.9.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b9a4e67ad7b646cd6f0938c7ebfd60e481b7410f574c560e455e938d2da8e0f4"}, + {file = "yarl-1.9.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:83fcc480d7549ccebe9415d96d9263e2d4226798c37ebd18c930fce43dfb9574"}, + {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5fcd436ea16fee7d4207c045b1e340020e58a2597301cfbcfdbe5abd2356c2fb"}, + {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84e0b1599334b1e1478db01b756e55937d4614f8654311eb26012091be109d59"}, + {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3458a24e4ea3fd8930e934c129b676c27452e4ebda80fbe47b56d8c6c7a63a9e"}, + {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:838162460b3a08987546e881a2bfa573960bb559dfa739e7800ceeec92e64417"}, + {file = "yarl-1.9.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f4e2d08f07a3d7d3e12549052eb5ad3eab1c349c53ac51c209a0e5991bbada78"}, + {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:de119f56f3c5f0e2fb4dee508531a32b069a5f2c6e827b272d1e0ff5ac040333"}, + {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:149ddea5abf329752ea5051b61bd6c1d979e13fbf122d3a1f9f0c8be6cb6f63c"}, + {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:674ca19cbee4a82c9f54e0d1eee28116e63bc6fd1e96c43031d11cbab8b2afd5"}, + {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:9b3152f2f5677b997ae6c804b73da05a39daa6a9e85a512e0e6823d81cdad7cc"}, + {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5415d5a4b080dc9612b1b63cba008db84e908b95848369aa1da3686ae27b6d2b"}, + {file = "yarl-1.9.2-cp38-cp38-win32.whl", hash = "sha256:f7a3d8146575e08c29ed1cd287068e6d02f1c7bdff8970db96683b9591b86ee7"}, + {file = "yarl-1.9.2-cp38-cp38-win_amd64.whl", hash = "sha256:63c48f6cef34e6319a74c727376e95626f84ea091f92c0250a98e53e62c77c72"}, + {file = "yarl-1.9.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:75df5ef94c3fdc393c6b19d80e6ef1ecc9ae2f4263c09cacb178d871c02a5ba9"}, + {file = "yarl-1.9.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c027a6e96ef77d401d8d5a5c8d6bc478e8042f1e448272e8d9752cb0aff8b5c8"}, + {file = "yarl-1.9.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3b078dbe227f79be488ffcfc7a9edb3409d018e0952cf13f15fd6512847f3f7"}, + {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59723a029760079b7d991a401386390c4be5bfec1e7dd83e25a6a0881859e716"}, + {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b03917871bf859a81ccb180c9a2e6c1e04d2f6a51d953e6a5cdd70c93d4e5a2a"}, + {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c1012fa63eb6c032f3ce5d2171c267992ae0c00b9e164efe4d73db818465fac3"}, + {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a74dcbfe780e62f4b5a062714576f16c2f3493a0394e555ab141bf0d746bb955"}, + {file = "yarl-1.9.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8c56986609b057b4839968ba901944af91b8e92f1725d1a2d77cbac6972b9ed1"}, + {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2c315df3293cd521033533d242d15eab26583360b58f7ee5d9565f15fee1bef4"}, + {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:b7232f8dfbd225d57340e441d8caf8652a6acd06b389ea2d3222b8bc89cbfca6"}, + {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:53338749febd28935d55b41bf0bcc79d634881195a39f6b2f767870b72514caf"}, + {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:066c163aec9d3d073dc9ffe5dd3ad05069bcb03fcaab8d221290ba99f9f69ee3"}, + {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8288d7cd28f8119b07dd49b7230d6b4562f9b61ee9a4ab02221060d21136be80"}, + {file = "yarl-1.9.2-cp39-cp39-win32.whl", hash = "sha256:b124e2a6d223b65ba8768d5706d103280914d61f5cae3afbc50fc3dfcc016623"}, + {file = "yarl-1.9.2-cp39-cp39-win_amd64.whl", hash = "sha256:61016e7d582bc46a5378ffdd02cd0314fb8ba52f40f9cf4d9a5e7dbef88dee18"}, + {file = "yarl-1.9.2.tar.gz", hash = "sha256:04ab9d4b9f587c06d801c2abfe9317b77cdf996c65a90d5e84ecc45010823571"}, +] + +[package.dependencies] +idna = ">=2.0" +multidict = ">=4.0" + +[metadata] +lock-version = "2.0" +python-versions = "^3.11" +content-hash = "1fe8b904d77f9ad06bc5ef03c9cf5f8c363ec3176f2a9117aba4121032508c97" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..61c69eba --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,17 @@ +[tool.poetry] +name = "python-mastery" +version = "0.1.0" +description = "" +authors = ["Benjamin Swineford "] +readme = "README.md" + +[tool.poetry.dependencies] +python = "^3.11" +black = {extras = ["d"], version = "^23.7.0"} +colored = "^2.2.3" + + + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" From 7f7893581a7441b8c8b319da3737d6ebf568f9f5 Mon Sep 17 00:00:00 2001 From: Benjamin Swineford Date: Thu, 17 Aug 2023 13:57:46 -0500 Subject: [PATCH 25/54] fix html formatting --- tableformat.py | 53 +++++++++++++++++++++++++++++++------------------- 1 file changed, 33 insertions(+), 20 deletions(-) diff --git a/tableformat.py b/tableformat.py index 536df01a..0730412c 100644 --- a/tableformat.py +++ b/tableformat.py @@ -1,6 +1,5 @@ from pprint import pprint from colored import Fore, Back, Style -from html5print import HTMLBeautifier class TableFormatter: @@ -92,43 +91,57 @@ def row(self, rowdata): self._list_to_print.append(f"{','.join(f'{cell}' for cell in rowdata)}") -def html_tag(tag, content, color="yellow", indent=0): +def html_tag(tag, content, color="yellow", indent=0, new_line=True): tag_style = getattr(Fore, color) + Style.bold - return f"{tag_style}<{tag}>{Style.reset}{content}{tag_style}{Style.reset}" + + whitespace = "\n" if new_line else "" + + indent_string = "\t" * indent if new_line else "" + + tag_open = f"{whitespace}{indent_string}{tag_style}<{tag}>{Style.reset}" + tag_close = f"{tag_style}{whitespace}{indent_string}{Style.reset}" + return f"{tag_open}{content}{tag_close}" def tr(content): - return html_tag(tag="tr", content=content, indent=2) + return html_tag(tag="tr", content=content, indent=1) class HTMLTableFormatter(TableFormatter): def headings(self, headers): self._list_to_print.append( - html_tag( - tag="thead", - content=html_tag( - tag="tr", - content="".join( - html_tag( - tag="th", - content=f"{Style.bold} {header.upper()} {Style.reset}", - color="green", - ) - for header in headers - ), + tr( + content="\n\t\t" + + "".join( + html_tag( + tag="th", + content=f"{Style.bold} {header.upper()} {Style.reset}", + color="green", + new_line=False, + ) + for header in headers ), ) ) def row(self, rowdata): - tr("".join(html_tag(tag="td", content=cell, indent=3) for cell in rowdata)) + self._list_to_print.append( + tr( + "\n\t\t" + + "".join( + html_tag(tag="td", content=cell, new_line=False) for cell in rowdata + ) + ) + ) def print(self): - table_header = html_tag(tag="thead", content=self._list_to_print[0], indent=1) + table_header = html_tag(tag="thead", content=self._list_to_print[0], indent=0) + print(self._list_to_print[1:]) + table_body = html_tag( tag="tbody", - indent=1, - content=[self.row(r) for r in self._list_to_print], + indent=0, + content=f'{"".join(self._list_to_print[1:])}', ) print(html_tag(tag="table", content=f"{table_header}{table_body}", indent=0)) From b4821ae376455af51b1d27088f6feecf3c483387 Mon Sep 17 00:00:00 2001 From: Benjamin Swineford Date: Thu, 17 Aug 2023 14:05:57 -0500 Subject: [PATCH 26/54] add type check for formatter --- tableformat.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tableformat.py b/tableformat.py index 0730412c..e523f672 100644 --- a/tableformat.py +++ b/tableformat.py @@ -39,6 +39,10 @@ def add_divider(self): def print_table(records, fields, formatter): + if not isinstance(formatter, TableFormatter): + raise TypeError( + f"Expected a TableFormatter, received ~{type(formatter).__name__})~" + ) formatter.headings(fields) for r in records: rowdata = [getattr(r, fieldname) for fieldname in fields] @@ -136,7 +140,6 @@ def row(self, rowdata): def print(self): table_header = html_tag(tag="thead", content=self._list_to_print[0], indent=0) - print(self._list_to_print[1:]) table_body = html_tag( tag="tbody", From fada5ef2fdbbf3ff79c5eb8af8dfabe3850ac665 Mon Sep 17 00:00:00 2001 From: Benjamin Swineford Date: Thu, 17 Aug 2023 14:26:24 -0500 Subject: [PATCH 27/54] convert TableFormatter to ABC --- stock.py | 17 ++++++----------- tableformat.py | 15 ++++++++++++++- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/stock.py b/stock.py index 3cd98392..b33a776d 100644 --- a/stock.py +++ b/stock.py @@ -6,7 +6,7 @@ import os import reader -from tableformat import create_formatter, print_table +from tableformat import create_formatter, print_table, TableFormatter os.system("") @@ -129,15 +129,10 @@ def read_portfolio(filename="Data/portfolio.csv", cls=Stock): portfolio = reader.read_csv_as_instances("Data/portfolio.csv", Stock) -formatter = create_formatter("text") +def check_formatters(): + for format_to_use in ["text", "csv", "html"]: + formatter = create_formatter(format_to_use) + print_table(portfolio, ["name", "shares", "price"], formatter) -# with redirect_stdout(open("out.txt", "w")) as file: -# print_table(portfolio, ["name", "shares", "price"], formatter) -# file.close() -for format_to_use in ["text", "csv", "html"]: - print() - formatter = create_formatter(format_to_use) - print_table(portfolio, ["name", "shares", "price"], formatter) - - print() +# check_formatters() diff --git a/tableformat.py b/tableformat.py index e523f672..7362d2bc 100644 --- a/tableformat.py +++ b/tableformat.py @@ -1,8 +1,9 @@ from pprint import pprint from colored import Fore, Back, Style +from abc import ABC, abstractmethod -class TableFormatter: +class TableFormatter(ABC): def __init__(self): self._width = 0 self._columns = 0 @@ -12,9 +13,11 @@ def __init__(self): def width(self): return self._width + @abstractmethod def headings(self, headers): raise NotImplementedError() + @abstractmethod def row(self, rowdata): raise NotImplementedError() @@ -22,6 +25,7 @@ def row(self, rowdata): def print_line(line=""): print(line) + @staticmethod def print(self): for line in self._list_to_print: self.print_line(line) @@ -43,6 +47,15 @@ def print_table(records, fields, formatter): raise TypeError( f"Expected a TableFormatter, received ~{type(formatter).__name__})~" ) + + print( + Fore.white + + Style.bold + + Back.dark_sea_green_4b + + " " + + " ".join(type(formatter).__name__.upper().split("TABLE")) + ) + print(Style.reset + "\n") formatter.headings(fields) for r in records: rowdata = [getattr(r, fieldname) for fieldname in fields] From 29ba2c1ade4adc1373f826da4f686610e1b95399 Mon Sep 17 00:00:00 2001 From: Benjamin Swineford Date: Thu, 17 Aug 2023 14:38:50 -0500 Subject: [PATCH 28/54] ex 3_7 --- reader.py | 58 +++++++++++++++++++++++++++++++++----------------- stock.py | 7 ++---- tableformat.py | 1 - 3 files changed, 40 insertions(+), 26 deletions(-) diff --git a/reader.py b/reader.py index def8c159..4f6041f3 100644 --- a/reader.py +++ b/reader.py @@ -1,4 +1,5 @@ import csv +from abc import ABC, abstractmethod import tracemalloc from collections import defaultdict from collections.abc import Sequence @@ -29,30 +30,48 @@ def append(self, d): self.column_data[key].append(val) -def read_csv_as_dicts(filename, converter_funcs): +class CSVParser(ABC): + def parse(self, filename): + records = [] + with open(filename) as f: + rows = csv.reader(f) + headers = next(rows) + for row in rows: + record = self.make_record(headers, row) + records.append(record) + return records + + @abstractmethod + def make_record(self, headers, row): + pass - with open(filename) as f: - rows = csv.reader(f) - headers = next(rows) - converted_values = [ - {name: func(val) for name, func, val in zip(headers, converter_funcs, row)} - for row in rows - ] +class DictCSVParser(CSVParser): + def __init__(self, types): + self.types = types + + def make_record(self, headers, row): + return {name: func(val) for name, func, val in zip(headers, self.types, row)} + + +class InstanceCSVParser(CSVParser): + def __init__(self, cls): + self.cls = cls + + def make_record(self, headers, row): + return self.cls.from_row(row) + + +def read_csv_as_dicts(filename, converter_funcs): + parser = DictCSVParser(converter_funcs) + + return parser.parse(filename) - return converted_values def read_csv_as_instances(filename, cls): - ''' - Read a CSV file into a list of instances - ''' - records = [] - with open(filename) as f: - rows = csv.reader(f) - headers = next(rows) - for row in rows: - records.append(cls.from_row(row)) - return records + parser = InstanceCSVParser(cls) + + return parser.parse(filename) def read_file_as_data_collection(filename, converters): @@ -75,7 +94,6 @@ def read_file_as_type(filename="Data/ctabus.csv", type_name="dict", converters=[ results = None try: - results = globals()[f"read_file_as_{type_name}"](filename, converters) current, peak = tracemalloc.get_traced_memory() diff --git a/stock.py b/stock.py index b33a776d..aee41703 100644 --- a/stock.py +++ b/stock.py @@ -3,13 +3,10 @@ from sys import intern, stdout from decimal import Decimal from colored import Fore, Back, Style -import os import reader from tableformat import create_formatter, print_table, TableFormatter -os.system("") - class redirect_stdout: def __init__(self, out_file): @@ -126,7 +123,7 @@ def read_portfolio(filename="Data/portfolio.csv", cls=Stock): return stocks -portfolio = reader.read_csv_as_instances("Data/portfolio.csv", Stock) +portfolio = reader.read_csv_as_instances(filename="Data/portfolio.csv", cls=Stock) def check_formatters(): @@ -135,4 +132,4 @@ def check_formatters(): print_table(portfolio, ["name", "shares", "price"], formatter) -# check_formatters() +check_formatters() diff --git a/tableformat.py b/tableformat.py index 7362d2bc..03c14e58 100644 --- a/tableformat.py +++ b/tableformat.py @@ -25,7 +25,6 @@ def row(self, rowdata): def print_line(line=""): print(line) - @staticmethod def print(self): for line in self._list_to_print: self.print_line(line) From cfe06787c4aceae7dc52ce69324c3cef9e05697b Mon Sep 17 00:00:00 2001 From: Benjamin Swineford Date: Thu, 17 Aug 2023 15:07:54 -0500 Subject: [PATCH 29/54] ex 3_8 --- stock.py | 27 ++++++++++++++++++++++++--- tableformat.py | 49 ++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 62 insertions(+), 14 deletions(-) diff --git a/stock.py b/stock.py index aee41703..d68dae5a 100644 --- a/stock.py +++ b/stock.py @@ -5,7 +5,12 @@ from colored import Fore, Back, Style import reader -from tableformat import create_formatter, print_table, TableFormatter +from tableformat import ( + create_formatter, + print_table, + TextTableFormatter, + UpperHeadersMixin, +) class redirect_stdout: @@ -125,11 +130,27 @@ def read_portfolio(filename="Data/portfolio.csv", cls=Stock): portfolio = reader.read_csv_as_instances(filename="Data/portfolio.csv", cls=Stock) +format_list = [ + {"name": "text", "column_formats": ['"%s"', "%d", "%0.2f"]}, + {"name": "text", "upper_headers": True}, + {"name": "csv", "upper_headers": False}, + {"name": "csv", "upper_headers": True, "column_formats": ['"%s"', "%d", "%0.2f"]}, + {"name": "html", "upper_headers": False}, + {"name": "html", "upper_headers": True, "column_formats": ['"%s"', "%d", "%0.2f"]}, +] + def check_formatters(): - for format_to_use in ["text", "csv", "html"]: - formatter = create_formatter(format_to_use) + for format_to_use in format_list: + formatter = create_formatter(**format_to_use) print_table(portfolio, ["name", "shares", "price"], formatter) check_formatters() + + +# class PortfolioFormatter(UpperHeadersMixin, TextTableFormatter): +# formats = ["%s", "%d", "%0.2f"] +# +# +# print_table(portfolio, ["name", "shares", "price"], PortfolioFormatter()) diff --git a/tableformat.py b/tableformat.py index 03c14e58..a61781de 100644 --- a/tableformat.py +++ b/tableformat.py @@ -3,6 +3,19 @@ from abc import ABC, abstractmethod +class ColumnFormatMixin: + formats = [] + + def row(self, rowdata): + rowdata = [(fmt % d) for fmt, d in zip(self.formats, rowdata)] + super().row(rowdata) + + +class UpperHeadersMixin: + def headings(self, headers): + super().headings([h.upper() for h in headers]) + + class TableFormatter(ABC): def __init__(self): self._width = 0 @@ -47,14 +60,6 @@ def print_table(records, fields, formatter): f"Expected a TableFormatter, received ~{type(formatter).__name__})~" ) - print( - Fore.white - + Style.bold - + Back.dark_sea_green_4b - + " " - + " ".join(type(formatter).__name__.upper().split("TABLE")) - ) - print(Style.reset + "\n") formatter.headings(fields) for r in records: rowdata = [getattr(r, fieldname) for fieldname in fields] @@ -100,7 +105,7 @@ def headings(self, headers): width = max(self._width, *[(len(h) + 9) // 10 * 10 for h in headers]) self._width = width - self._list_to_print.append(",".join(header.upper() for header in headers)) + self._list_to_print.append(",".join(header for header in headers)) self._list_to_print.append("") def row(self, rowdata): @@ -131,7 +136,7 @@ def headings(self, headers): + "".join( html_tag( tag="th", - content=f"{Style.bold} {header.upper()} {Style.reset}", + content=f"{Style.bold} {header} {Style.reset}", color="green", new_line=False, ) @@ -161,7 +166,18 @@ def print(self): print(html_tag(tag="table", content=f"{table_header}{table_body}", indent=0)) -def create_formatter(name): +def create_formatter(name, column_formats=None, upper_headers=False): + print( + "\n" + + Fore.white + + Style.bold + + Back.dark_sea_green_4b + + " " + + name.upper() + + "\t\t" + + Style.reset + ) + if name == "text": formatter_cls = TextTableFormatter elif name == "csv": @@ -170,4 +186,15 @@ def create_formatter(name): formatter_cls = HTMLTableFormatter else: raise RuntimeError("Unknown format %s" % name) + + if column_formats: + + class formatter_cls(ColumnFormatMixin, formatter_cls): + formats = column_formats + + if upper_headers: + + class formatter_cls(UpperHeadersMixin, formatter_cls): + pass + return formatter_cls() From 7cb6ae28dc156ed73e2f96106755a736ec742b4a Mon Sep 17 00:00:00 2001 From: Benjamin Swineford Date: Thu, 17 Aug 2023 15:25:24 -0500 Subject: [PATCH 30/54] add validators --- validate.py | 54 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 validate.py diff --git a/validate.py b/validate.py new file mode 100644 index 00000000..e1be1a3f --- /dev/null +++ b/validate.py @@ -0,0 +1,54 @@ +class Validator: + @classmethod + def check(cls, value): + return value + + +class Typed(Validator): + expected_type = object + + @classmethod + def check(cls, value): + if not isinstance(value, cls.expected_type): + raise TypeError(f"Expected {cls.expected_type}") + return super().check(value) + + +class Integer(Typed): + expected_type = int + + +class Float(Typed): + expected_type = float + + +class String(Typed): + expected_type = str + + +class Positive(Validator): + @classmethod + def check(cls, value): + if value < 0: + raise ValueError("Expected >= 0") + return super().check(value) + + +class NonEmpty(Validator): + @classmethod + def check(cls, value): + if len(value) == 0: + raise ValueError("Must be non-empty") + return super().check(value) + + +class PositiveInteger(Integer, Positive): + pass + + +class PositiveFloat(Float, Positive): + pass + + +class NonEmptyString(String, NonEmpty): + pass From 844e1803f5be17bda3050218ce4d41b15223c17f Mon Sep 17 00:00:00 2001 From: Benjamin Swineford Date: Thu, 17 Aug 2023 15:25:33 -0500 Subject: [PATCH 31/54] ex 4_2 --- stock.py | 25 +++++-------------------- 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/stock.py b/stock.py index d68dae5a..3935aa83 100644 --- a/stock.py +++ b/stock.py @@ -4,6 +4,8 @@ from decimal import Decimal from colored import Fore, Back, Style +from validate import PositiveFloat, PositiveInteger, Positive + import reader from tableformat import ( create_formatter, @@ -76,11 +78,7 @@ def shares(self): @shares.setter def shares(self, value): - converted_value = self.check_type(value, "shares") - - if converted_value < 0: - raise ValueError("shares must be >= 0") - self._shares = converted_value + self._shares = PositiveInteger.check(value) @property def price(self): @@ -88,17 +86,14 @@ def price(self): @price.setter def price(self, value): - converted_value = self.check_type(value, "price") - if converted_value <= 0: - raise ValueError("price must be > 0") - self._price = converted_value + self._price = PositiveFloat.check(value) @property def cost(self): return self._shares * self._price def sell(self, nshares): - self._shares -= nshares + self._shares -= Positive.check(nshares) class DStock(Stock): @@ -144,13 +139,3 @@ def check_formatters(): for format_to_use in format_list: formatter = create_formatter(**format_to_use) print_table(portfolio, ["name", "shares", "price"], formatter) - - -check_formatters() - - -# class PortfolioFormatter(UpperHeadersMixin, TextTableFormatter): -# formats = ["%s", "%d", "%0.2f"] -# -# -# print_table(portfolio, ["name", "shares", "price"], PortfolioFormatter()) From 9a67321a11628f9da81b318eaf8c0255df667080 Mon Sep 17 00:00:00 2001 From: Benjamin Swineford Date: Thu, 17 Aug 2023 15:45:47 -0500 Subject: [PATCH 32/54] ex 4_3 --- descrip.py | 29 ++++++++++++++++++++++++++ stock.py | 59 ++++++++++------------------------------------------- validate.py | 9 ++++++++ 3 files changed, 49 insertions(+), 48 deletions(-) create mode 100644 descrip.py diff --git a/descrip.py b/descrip.py new file mode 100644 index 00000000..b4c9a00c --- /dev/null +++ b/descrip.py @@ -0,0 +1,29 @@ +class Descriptor: + def __init__(self, name): + self.name = name + + def __get__(self, instance, cls): + print("%s:__get__" % self.name) + + def __set__(self, instance, value): + print("%s:__set__ %s" % (self.name, value)) + + def __delete__(self, instance): + print("%s:__delete__" % self.name) + + +class Foo: + a = Descriptor("a") + b = Descriptor("b") + c = Descriptor("c") + + +f = Foo() +print(f) +print(f.a) +print(f.b) +f.a = 23 + +print(f.a) +del f.a +print(f.a) diff --git a/stock.py b/stock.py index 3935aa83..e149ef9a 100644 --- a/stock.py +++ b/stock.py @@ -4,7 +4,7 @@ from decimal import Decimal from colored import Fore, Back, Style -from validate import PositiveFloat, PositiveInteger, Positive +from validate import PositiveFloat, PositiveInteger, Positive, String, NonEmptyString import reader from tableformat import ( @@ -29,71 +29,31 @@ def __exit__(self, ty, val, tb): class Stock: - __slots__ = ("name", "_shares", "_price") _types = {"name": str, "shares": int, "price": float} + name = NonEmptyString() + shares = PositiveInteger() + price = PositiveFloat() + def __init__(self, name, shares, price): self.name = name self.shares = shares self.price = price def __repr__(self): - # Note: The !r format code produces the repr() string - return f"{type(self).__name__}({f'{Fore.blue + Style.bold}{self.name!r}{Style.reset}'}, {Fore.cyan}{self.shares!r}{Style.reset}, {Fore.green}{self.price!r}{Style.reset})" - - def __eq__(self, other): - return isinstance(other, Stock) and ( - (self.name, self.shares, self.price) - == (other.name, other.shares, other.price) - ) + return f"Stock({self.name!r}, {self.shares!r}, {self.price!r})" @classmethod def from_row(cls, row): values = [func(val) for func, val in zip(cls._types.values(), row)] return cls(*values) - @classmethod - def check_type(cls, value, type_key): - type_to_check = cls._types.get(type_key) - if not isinstance(value, type_to_check): - try: - converted_value = type_to_check(value) - - print( - f"Value for {type_key} ({value}) converted from {type(value).__name__} to {type_to_check.__name__} ({converted_value})" - ) - - return converted_value - - except ValueError: - raise TypeError( - f"Expected {type_to_check.__name__}, received --{value}-- with type {type(value).__name__}" - ) - - return value - - @property - def shares(self): - return self._shares - - @shares.setter - def shares(self, value): - self._shares = PositiveInteger.check(value) - - @property - def price(self): - return self._price - - @price.setter - def price(self, value): - self._price = PositiveFloat.check(value) - @property def cost(self): - return self._shares * self._price + return self.shares * self.price def sell(self, nshares): - self._shares -= Positive.check(nshares) + self.shares -= nshares class DStock(Stock): @@ -139,3 +99,6 @@ def check_formatters(): for format_to_use in format_list: formatter = create_formatter(**format_to_use) print_table(portfolio, ["name", "shares", "price"], formatter) + + +check_formatters() diff --git a/validate.py b/validate.py index e1be1a3f..0af10bda 100644 --- a/validate.py +++ b/validate.py @@ -1,8 +1,17 @@ class Validator: + def __init__(self, name=None): + self.name = name + + def __set_name__(self, cls, name): + self.name = name + @classmethod def check(cls, value): return value + def __set__(self, instance, value): + instance.__dict__[self.name] = self.check(value) + class Typed(Validator): expected_type = object From 769dc4c94dac1d028a0fbf31d1115977ea9702f4 Mon Sep 17 00:00:00 2001 From: Benjamin Swineford Date: Thu, 17 Aug 2023 16:04:48 -0500 Subject: [PATCH 33/54] ex 5_1 --- reader.py | 145 +++++++++++++++--------------------------------------- stock.py | 14 ++++-- 2 files changed, 52 insertions(+), 107 deletions(-) diff --git a/reader.py b/reader.py index 4f6041f3..b93235b1 100644 --- a/reader.py +++ b/reader.py @@ -1,108 +1,45 @@ import csv -from abc import ABC, abstractmethod -import tracemalloc -from collections import defaultdict -from collections.abc import Sequence -from pprint import pprint - -class DataCollection(Sequence): - def __init__(self, columns): - self.column_names = list(columns) - self.column_data = list(columns.values()) - - def __len__(self): - return len(self.column_data.values()[0]) - - def __getitem__(self, key): - if isinstance(key, slice): - slicer = DataCollection() - slicer.column_data = { - header: data_list[key] for header, data_list in self.column_data.items() - } - return slicer - elif isinstance(key, int): - return dict(zip(self.column_names, (col[key] for col in self.column_data))) - - def append(self, d): - for key, val in d.items(): - self.column_data[key].append(val) - - -class CSVParser(ABC): - def parse(self, filename): - records = [] - with open(filename) as f: - rows = csv.reader(f) - headers = next(rows) - for row in rows: - record = self.make_record(headers, row) - records.append(record) - return records - - @abstractmethod - def make_record(self, headers, row): - pass - - -class DictCSVParser(CSVParser): - def __init__(self, types): - self.types = types - - def make_record(self, headers, row): - return {name: func(val) for name, func, val in zip(headers, self.types, row)} - - -class InstanceCSVParser(CSVParser): - def __init__(self, cls): - self.cls = cls - - def make_record(self, headers, row): - return self.cls.from_row(row) - - -def read_csv_as_dicts(filename, converter_funcs): - parser = DictCSVParser(converter_funcs) - - return parser.parse(filename) - - -def read_csv_as_instances(filename, cls): - parser = InstanceCSVParser(cls) - - return parser.parse(filename) - - -def read_file_as_data_collection(filename, converters): - columns = defaultdict(list) - - with open(filename) as f: - rows = csv.reader(f) +def csv_as_dicts(lines, types, *, headers=None): + """ + Convert lines of CSV data into a list of dictionaries + """ + records = [] + rows = csv.reader(lines) + if headers is None: headers = next(rows) - for row in rows: - for name, func, val in zip(headers, converters, row): - columns[name].append(func(val)) - - return DataCollection(columns) - - -def read_file_as_type(filename="Data/ctabus.csv", type_name="dict", converters=[]): - tracemalloc.start() - tracemalloc.clear_traces() - - results = None - - try: - results = globals()[f"read_file_as_{type_name}"](filename, converters) - - current, peak = tracemalloc.get_traced_memory() - - print( - f"Memory Use for {type_name}: Current {current}, Peak {format(peak, '08,.1f')}" - ) - - except Exception as e: - print(e) - - return results + for row in rows: + record = {name: func(val) for name, func, val in zip(headers, types, row)} + records.append(record) + return records + + +def csv_as_instances(lines, cls, *, headers=None): + """ + Convert lines of CSV data into a list of instances + """ + records = [] + rows = csv.reader(lines) + if headers is None: + headers = next(rows) + for row in rows: + record = cls.from_row(row) + records.append(record) + return records + + +def read_csv_as_dicts(filename, types, *, headers=None): + """ + Read CSV data into a list of dictionaries with optional type conversion + """ + with open(filename) as file: + return csv_as_dicts(file, types, headers=headers) + + +def read_csv_as_instances(filename, cls, *, headers=None): + """ + Read CSV data into a list of instances + """ + with open(filename) as file: + return csv_as_instances(file, cls, headers=headers) diff --git a/stock.py b/stock.py index e149ef9a..5139b445 100644 --- a/stock.py +++ b/stock.py @@ -40,6 +40,11 @@ def __init__(self, name, shares, price): self.shares = shares self.price = price + def __setattr__(self, name, value): + if name not in {"name", "shares", "price"}: + raise AttributeError("No attribute %s" % name) + super().__setattr__(name, value) + def __repr__(self): return f"Stock({self.name!r}, {self.shares!r}, {self.price!r})" @@ -83,7 +88,7 @@ def read_portfolio(filename="Data/portfolio.csv", cls=Stock): return stocks -portfolio = reader.read_csv_as_instances(filename="Data/portfolio.csv", cls=Stock) +# portfolio = reader.read_csv_as_instances(filename="Data/portfolio.csv", cls=Stock) format_list = [ {"name": "text", "column_formats": ['"%s"', "%d", "%0.2f"]}, @@ -98,7 +103,10 @@ def read_portfolio(filename="Data/portfolio.csv", cls=Stock): def check_formatters(): for format_to_use in format_list: formatter = create_formatter(**format_to_use) - print_table(portfolio, ["name", "shares", "price"], formatter) + # print_table(portfolio, ["name", "shares", "price"], formatter) + +# check_formatters() +file = open("Data/portfolio.csv") -check_formatters() +print(reader.csv_as_dicts(file, [str, int, float])) From 92a7674f300bd24488171f831d8150a4fdd50c34 Mon Sep 17 00:00:00 2001 From: Benjamin Swineford Date: Thu, 17 Aug 2023 16:32:05 -0500 Subject: [PATCH 34/54] extract csv conversion to new function --- reader.py | 38 ++++++++++++++++++-------------------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/reader.py b/reader.py index b93235b1..16732d37 100644 --- a/reader.py +++ b/reader.py @@ -1,32 +1,30 @@ import csv -def csv_as_dicts(lines, types, *, headers=None): - """ - Convert lines of CSV data into a list of dictionaries - """ - records = [] +def convert_csv(lines, converter, *, headers=None): rows = csv.reader(lines) if headers is None: headers = next(rows) - for row in rows: - record = {name: func(val) for name, func, val in zip(headers, types, row)} - records.append(record) - return records + + return list(map(lambda row: converter(headers, row), rows)) + + +def csv_as_dicts(lines, types, *, headers=None): + return convert_csv( + lines, + lambda headers, row: { + name: func(val) for name, func, val in zip(headers, types, row) + }, + headers=headers, + ) def csv_as_instances(lines, cls, *, headers=None): - """ - Convert lines of CSV data into a list of instances - """ - records = [] - rows = csv.reader(lines) - if headers is None: - headers = next(rows) - for row in rows: - record = cls.from_row(row) - records.append(record) - return records + return convert_csv( + lines, + lambda headers, row: cls.from_row(row), + headers=headers, + ) def read_csv_as_dicts(filename, types, *, headers=None): From 8c25dee80446c48160f7f43e8827894499967817 Mon Sep 17 00:00:00 2001 From: Benjamin Swineford Date: Thu, 17 Aug 2023 16:32:31 -0500 Subject: [PATCH 35/54] add option to look up from dict --- tableformat.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tableformat.py b/tableformat.py index a61781de..a8cf0122 100644 --- a/tableformat.py +++ b/tableformat.py @@ -61,8 +61,12 @@ def print_table(records, fields, formatter): ) formatter.headings(fields) + for r in records: - rowdata = [getattr(r, fieldname) for fieldname in fields] + rowdata = [ + r[fieldname] if type(r) == dict else getattr(r, fieldname) + for fieldname in fields + ] formatter.row(rowdata) formatter.print() From 017b25044d5893678ad0d2de9e16cffb031056f3 Mon Sep 17 00:00:00 2001 From: Benjamin Swineford Date: Thu, 17 Aug 2023 16:32:46 -0500 Subject: [PATCH 36/54] get name from class instance --- stock.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/stock.py b/stock.py index 5139b445..af85c691 100644 --- a/stock.py +++ b/stock.py @@ -46,7 +46,7 @@ def __setattr__(self, name, value): super().__setattr__(name, value) def __repr__(self): - return f"Stock({self.name!r}, {self.shares!r}, {self.price!r})" + return f"{type(self).__name__}({self.name!r}, {self.shares!r}, {self.price!r})" @classmethod def from_row(cls, row): @@ -90,6 +90,11 @@ def read_portfolio(filename="Data/portfolio.csv", cls=Stock): # portfolio = reader.read_csv_as_instances(filename="Data/portfolio.csv", cls=Stock) +file = open("Data/portfolio.csv") + +portfolio = reader.csv_as_instances(lines=file, cls=Stock) +# portfolio = reader.csv_as_dicts(lines=file, types=[str, int, float]) + format_list = [ {"name": "text", "column_formats": ['"%s"', "%d", "%0.2f"]}, {"name": "text", "upper_headers": True}, @@ -103,10 +108,12 @@ def read_portfolio(filename="Data/portfolio.csv", cls=Stock): def check_formatters(): for format_to_use in format_list: formatter = create_formatter(**format_to_use) - # print_table(portfolio, ["name", "shares", "price"], formatter) + print_table( + records=portfolio, + fields=["name", "shares", "price"], + formatter=formatter, + ) -# check_formatters() -file = open("Data/portfolio.csv") -print(reader.csv_as_dicts(file, [str, int, float])) +check_formatters() From 278f07df5b9d03757cdad08579c43a6556c9c6ef Mon Sep 17 00:00:00 2001 From: Benjamin Swineford Date: Fri, 18 Aug 2023 12:14:38 -0500 Subject: [PATCH 37/54] ex 5_6 --- PythonMastery.pdf | Bin 4180332 -> 4265045 bytes reader.py | 14 ++++++++- stock.py | 12 ++++++-- teststock.py | 53 +++++++++++++++++++++++++++++++++ typedproperty.py | 74 ++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 150 insertions(+), 3 deletions(-) create mode 100644 teststock.py create mode 100644 typedproperty.py diff --git a/PythonMastery.pdf b/PythonMastery.pdf index e8f0c06892ecfd8744d6ad8b1596a72482bfcc78..c3113bc09eedf409fdc658bbe97e011637a918ea 100644 GIT binary patch delta 79806 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 C<%zZc delta 137 zcmV~$>0QAA007XRzbm;axk5yRzk>7e5;_P8ghfQf z#N9}^m6Vc}k#* expected_type: + # pprint(self) + + return getattr(self, private_name) + + @value.setter + def value(self, val): + if not isinstance(val, expected_type): + raise TypeError(f"Expected {expected_type}") + + pprint(self) + # self.public_name = name + # self.private_name = "_" + name + + setattr(self, private_name, val) + + return value + + +String = my_decorator(lambda: typedproperty(expected_type=str)) + + +# @my_decorator +# def String(): +# pprint(locals()) +# return typedproperty(expected_type=str) + + +Integer: Callable[[Any], property] = my_decorator(lambda: typedproperty(int)) +Float: Callable[[Any], property] = my_decorator(lambda: typedproperty(float)) + + +if __name__ == "__main__": + + class Stock: + name = String() + shares = Integer() + price = Float() + + def __init__(self, name, shares, price): + self.name = name + self.shares = shares + self.price = price From cde12f562cd6c2f30bebaf18965771b9962ea6f4 Mon Sep 17 00:00:00 2001 From: Benjamin Swineford Date: Fri, 18 Aug 2023 13:40:30 -0500 Subject: [PATCH 38/54] ex 5_6 --- teststock.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/teststock.py b/teststock.py index f77a672d..7546b7b2 100644 --- a/teststock.py +++ b/teststock.py @@ -48,6 +48,37 @@ def test_eq(self): b = Stock("GOOG", 100, 490.1) self.assertTrue(a == b) + def test_bad_shares(self): + s = Stock("GOOG", 100, 490.1) + with self.assertRaises(TypeError): + s.shares = "50" + + # Tests for failure conditions + def test_shares_badtype(self): + s = Stock("GOOG", 100, 490.1) + with self.assertRaises(TypeError): + s.shares = "50" + + def test_shares_badvalue(self): + s = Stock("GOOG", 100, 490.1) + with self.assertRaises(ValueError): + s.shares = -50 + + def test_price_badtype(self): + s = Stock("GOOG", 100, 490.1) + with self.assertRaises(TypeError): + s.price = "45.23" + + def test_price_badvalue(self): + s = Stock("GOOG", 100, 490.1) + with self.assertRaises(ValueError): + s.price = -45.23 + + def test_bad_attribute(self): + s = Stock("GOOG", 100, 490.1) + with self.assertRaises(AttributeError): + s.share = 100 + if __name__ == "__main__": unittest.main() From 1e56394bfda1381708c71f09002e650885f8e1cd Mon Sep 17 00:00:00 2001 From: Benjamin Swineford Date: Fri, 18 Aug 2023 13:41:35 -0500 Subject: [PATCH 39/54] copy orriginal stock.py --- orig_stock.py | 127 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 orig_stock.py diff --git a/orig_stock.py b/orig_stock.py new file mode 100644 index 00000000..5f4db369 --- /dev/null +++ b/orig_stock.py @@ -0,0 +1,127 @@ +from pprint import pprint +import sys +from sys import intern, stdout +from decimal import Decimal +from colored import Fore, Back, Style + +# from typedproperty import String, Integer, Float + +from validate import PositiveFloat, PositiveInteger, Positive, String, NonEmptyString + +import reader +from tableformat import ( + create_formatter, + print_table, + TextTableFormatter, + UpperHeadersMixin, +) + + +class redirect_stdout: + def __init__(self, out_file): + self.out_file = out_file + + def __enter__(self): + self.stdout = sys.stdout + sys.stdout = self.out_file + return self.out_file + + def __exit__(self, ty, val, tb): + sys.stdout = self.stdout + + +class Stock: + _types = {"name": str, "shares": int, "price": float} + + name = String() + shares = PositiveInteger() + price = PositiveFloat() + + def __init__(self, name, shares, price): + self.name = name + self.shares = shares + self.price = price + + def __setattr__(self, name, value): + if name not in {"name", "shares", "price"}: + raise AttributeError("No attribute %s" % name) + super().__setattr__(name, value) + + def __repr__(self): + return f"{type(self).__name__}({self.name!r}, {self.shares!r}, {self.price!r})" + + def __eq__(self, other): + return isinstance(other, Stock) and ( + (self.name, self.shares, self.price) + == (other.name, other.shares, other.price) + ) + + @classmethod + def from_row(cls, row): + values = [func(val) for func, val in zip(cls._types.values(), row)] + return cls(*values) + + @property + def cost(self): + return self.shares * self.price + + def sell(self, nshares): + self.shares -= nshares + + +class DStock(Stock): + __slots__ = ("name", "_shares", "_price") + _types = {"name": str, "shares": int, "price": Decimal} + + @Stock.cost.getter + def cost(self): + return Decimal(self._shares) * self._price + + +def read_portfolio(filename="Data/portfolio.csv", cls=Stock): + rows = reader.read_file_as_type( + filename=filename, + type_name="data_collection", + converters=[intern, int, float], + ) + + stocks = list() + + for row in rows: + result = cls.from_row(row) + stocks.append(result) + + # final = [Stock(name, shares, price) for (name, shares, price) in rows] + + return stocks + + +# portfolio = reader.read_csv_as_instances(filename="Data/portfolio.csv", cls=Stock) + +file = open("Data/portfolio.csv") + +portfolio = reader.csv_as_instances(lines=file, cls=Stock) +# portfolio = reader.csv_as_dicts(lines=file, types=[str, int, float]) + +format_list = [ + {"name": "text", "column_formats": ['"%s"', "%d", "%0.2f"]}, + {"name": "text", "upper_headers": True}, + {"name": "csv", "upper_headers": False}, + {"name": "csv", "upper_headers": True, "column_formats": ['"%s"', "%d", "%0.2f"]}, + {"name": "html", "upper_headers": False}, + {"name": "html", "upper_headers": True, "column_formats": ['"%s"', "%d", "%0.2f"]}, +] + + +def check_formatters(): + for format_to_use in format_list: + formatter = create_formatter(**format_to_use) + + print_table( + records=portfolio, + fields=["name", "shares", "price"], + formatter=formatter, + ) + + +# check_formatters() From 6f820569a1c1dfcf62e33ec8efb93d5dce2c3570 Mon Sep 17 00:00:00 2001 From: Benjamin Swineford Date: Fri, 18 Aug 2023 14:16:26 -0500 Subject: [PATCH 40/54] test --- orig_stock.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/orig_stock.py b/orig_stock.py index 5f4db369..f988fe01 100644 --- a/orig_stock.py +++ b/orig_stock.py @@ -4,9 +4,9 @@ from decimal import Decimal from colored import Fore, Back, Style -# from typedproperty import String, Integer, Float +from typedproperty import String, Integer, Float -from validate import PositiveFloat, PositiveInteger, Positive, String, NonEmptyString +# from validate import PositiveFloat, PositiveInteger, Positive, String, NonEmptyString import reader from tableformat import ( @@ -34,8 +34,10 @@ class Stock: _types = {"name": str, "shares": int, "price": float} name = String() - shares = PositiveInteger() - price = PositiveFloat() + # shares = PositiveInteger() + shares = Integer() + # price = PositiveFloat() + price = Float() def __init__(self, name, shares, price): self.name = name From a90cd1a3d81b2ca46096ae46a1ba31ab55da65ce Mon Sep 17 00:00:00 2001 From: Benjamin Swineford Date: Fri, 18 Aug 2023 14:28:13 -0500 Subject: [PATCH 41/54] ex 6_3 --- stock.py | 116 ++----------------------------------------------------- 1 file changed, 4 insertions(+), 112 deletions(-) diff --git a/stock.py b/stock.py index 5f4db369..c2074e8b 100644 --- a/stock.py +++ b/stock.py @@ -1,65 +1,10 @@ -from pprint import pprint -import sys -from sys import intern, stdout -from decimal import Decimal -from colored import Fore, Back, Style +from structure import Structure -# from typedproperty import String, Integer, Float -from validate import PositiveFloat, PositiveInteger, Positive, String, NonEmptyString - -import reader -from tableformat import ( - create_formatter, - print_table, - TextTableFormatter, - UpperHeadersMixin, -) - - -class redirect_stdout: - def __init__(self, out_file): - self.out_file = out_file - - def __enter__(self): - self.stdout = sys.stdout - sys.stdout = self.out_file - return self.out_file - - def __exit__(self, ty, val, tb): - sys.stdout = self.stdout - - -class Stock: - _types = {"name": str, "shares": int, "price": float} - - name = String() - shares = PositiveInteger() - price = PositiveFloat() +class Stock(Structure): def __init__(self, name, shares, price): - self.name = name - self.shares = shares - self.price = price - - def __setattr__(self, name, value): - if name not in {"name", "shares", "price"}: - raise AttributeError("No attribute %s" % name) - super().__setattr__(name, value) - - def __repr__(self): - return f"{type(self).__name__}({self.name!r}, {self.shares!r}, {self.price!r})" - - def __eq__(self, other): - return isinstance(other, Stock) and ( - (self.name, self.shares, self.price) - == (other.name, other.shares, other.price) - ) - - @classmethod - def from_row(cls, row): - values = [func(val) for func, val in zip(cls._types.values(), row)] - return cls(*values) + self._init() @property def cost(self): @@ -69,59 +14,6 @@ def sell(self, nshares): self.shares -= nshares -class DStock(Stock): - __slots__ = ("name", "_shares", "_price") - _types = {"name": str, "shares": int, "price": Decimal} - - @Stock.cost.getter - def cost(self): - return Decimal(self._shares) * self._price - - -def read_portfolio(filename="Data/portfolio.csv", cls=Stock): - rows = reader.read_file_as_type( - filename=filename, - type_name="data_collection", - converters=[intern, int, float], - ) - - stocks = list() - - for row in rows: - result = cls.from_row(row) - stocks.append(result) - - # final = [Stock(name, shares, price) for (name, shares, price) in rows] - - return stocks - - -# portfolio = reader.read_csv_as_instances(filename="Data/portfolio.csv", cls=Stock) - -file = open("Data/portfolio.csv") - -portfolio = reader.csv_as_instances(lines=file, cls=Stock) -# portfolio = reader.csv_as_dicts(lines=file, types=[str, int, float]) - -format_list = [ - {"name": "text", "column_formats": ['"%s"', "%d", "%0.2f"]}, - {"name": "text", "upper_headers": True}, - {"name": "csv", "upper_headers": False}, - {"name": "csv", "upper_headers": True, "column_formats": ['"%s"', "%d", "%0.2f"]}, - {"name": "html", "upper_headers": False}, - {"name": "html", "upper_headers": True, "column_formats": ['"%s"', "%d", "%0.2f"]}, -] - - -def check_formatters(): - for format_to_use in format_list: - formatter = create_formatter(**format_to_use) - - print_table( - records=portfolio, - fields=["name", "shares", "price"], - formatter=formatter, - ) +Stock.set_fields() -# check_formatters() From 70f0a98e081a8a3bacc4933b3d3c5606f62d9b9d Mon Sep 17 00:00:00 2001 From: Benjamin Swineford Date: Fri, 18 Aug 2023 14:28:21 -0500 Subject: [PATCH 42/54] ex 6_3 --- structure.py | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 structure.py diff --git a/structure.py b/structure.py new file mode 100644 index 00000000..afe42637 --- /dev/null +++ b/structure.py @@ -0,0 +1,44 @@ +import sys +import inspect + + +class Structure: + _fields = () + + @staticmethod + def _init(): + locs = sys._getframe(1).f_locals + self = locs.pop("self") + for name, val in locs.items(): + setattr(self, name, val) + + def __eq__(self, other): + return isinstance(other, self.__class__) and ( + all([getattr(self, field) == getattr(other, field) for field in self._fields]) + ) + + def __init__(self, *args): + if len(args) != len(self._fields): + raise TypeError("Expected %d arguments" % len(self._fields)) + for name, arg in zip(self._fields, args): + setattr(self, name, arg) + + def __repr__(self): + return "%s(%s)" % ( + type(self).__name__, + ", ".join(repr(getattr(self, name)) for name in self._fields), + ) + + def __setattr__(self, name, value): + if name.startswith("_") or name in self._fields: + super().__setattr__(name, value) + else: + raise AttributeError("No attribute %s" % name) + + + + @classmethod + def set_fields(cls): + sig = inspect.signature(cls) + cls._fields = tuple(sig.parameters) + pass From 39755e5e15349ce873be226bc31846c812582436 Mon Sep 17 00:00:00 2001 From: Benjamin Swineford Date: Fri, 18 Aug 2023 14:44:36 -0500 Subject: [PATCH 43/54] ex 6_4 --- stock.py | 9 ++++----- structure.py | 23 +++++++++-------------- 2 files changed, 13 insertions(+), 19 deletions(-) diff --git a/stock.py b/stock.py index c2074e8b..d1b97184 100644 --- a/stock.py +++ b/stock.py @@ -1,10 +1,9 @@ from structure import Structure - +import inspect +from collections import namedtuple class Stock(Structure): - - def __init__(self, name, shares, price): - self._init() + _fields = ('name', 'shares', 'price') @property def cost(self): @@ -14,6 +13,6 @@ def sell(self, nshares): self.shares -= nshares -Stock.set_fields() +Stock.create_init() diff --git a/structure.py b/structure.py index afe42637..9cd6f733 100644 --- a/structure.py +++ b/structure.py @@ -5,12 +5,15 @@ class Structure: _fields = () - @staticmethod - def _init(): - locs = sys._getframe(1).f_locals - self = locs.pop("self") - for name, val in locs.items(): - setattr(self, name, val) + @classmethod + def create_init(cls): + args = ','.join(cls._fields) + code = 'def __init__(self, {0}):\n'.format(args) + statements = [' self.{0} = {0}'.format(name) for name in cls._fields] + code += '\n'.join(statements) + locs = {} + exec(code, locs) + cls.__init__ = locs['__init__'] def __eq__(self, other): return isinstance(other, self.__class__) and ( @@ -34,11 +37,3 @@ def __setattr__(self, name, value): super().__setattr__(name, value) else: raise AttributeError("No attribute %s" % name) - - - - @classmethod - def set_fields(cls): - sig = inspect.signature(cls) - cls._fields = tuple(sig.parameters) - pass From 709829810a3b683bf3f965551775690c067ebb7a Mon Sep 17 00:00:00 2001 From: Benjamin Swineford Date: Fri, 18 Aug 2023 15:50:14 -0500 Subject: [PATCH 44/54] ex 7_1 --- logcall.py | 9 ++++++++ sample.py | 11 +++++++++ stock.py | 9 ++++++-- validate.py | 64 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 91 insertions(+), 2 deletions(-) create mode 100644 logcall.py create mode 100644 sample.py diff --git a/logcall.py b/logcall.py new file mode 100644 index 00000000..89f200ce --- /dev/null +++ b/logcall.py @@ -0,0 +1,9 @@ + +def logged(func): + print('Adding logging to', func.__name__) + + def wrapper(*args, **kwargs): + print('Calling', func.__name__) + return func(*args, **kwargs) + + return wrapper diff --git a/sample.py b/sample.py new file mode 100644 index 00000000..e495df99 --- /dev/null +++ b/sample.py @@ -0,0 +1,11 @@ + +from logcall import logged + +@logged +def add(x,y): + return x+y + +@logged +def sub(x,y): + return x-y + diff --git a/stock.py b/stock.py index d1b97184..98a56b31 100644 --- a/stock.py +++ b/stock.py @@ -2,6 +2,9 @@ import inspect from collections import namedtuple +from validate import ValidatedFunction, Integer + + class Stock(Structure): _fields = ('name', 'shares', 'price') @@ -9,10 +12,12 @@ class Stock(Structure): def cost(self): return self.shares * self.price - def sell(self, nshares): + @ValidatedFunction + def sell(self, nshares: Integer): self.shares -= nshares Stock.create_init() - +s = Stock('GOOG', 100, 490.1) +s.sell(10) diff --git a/validate.py b/validate.py index 0af10bda..16c7368f 100644 --- a/validate.py +++ b/validate.py @@ -1,3 +1,6 @@ +from inspect import signature +from functools import wraps + class Validator: def __init__(self, name=None): self.name = name @@ -61,3 +64,64 @@ class PositiveFloat(Float, Positive): class NonEmptyString(String, NonEmpty): pass + + +def validated(func): + sig = signature(func) + + # Gather the function annotations + annotations = dict(func.__annotations__) + + # Get the return annotation (if any) + retcheck = annotations.pop('return', None) + + @wraps(func) + def wrapper(*args, **kwargs): + bound = sig.bind(*args, *kwargs) + errors = [] + + # Enforce argument checks + for name, validator in annotations.items(): + try: + validator.check(bound.arguments[name]) + except Exception as e: + errors.append(f' {name}: {e}') + + if errors: + raise TypeError('Bad Arguments\n' + '\n'.join(errors)) + + for name, val in annotations.items(): + val.check(bound.arguments[name]) + + result = func(*args, **kwargs) + + # Enforce return check (if any) + if retcheck: + try: + retcheck.check(result) + except Exception as e: + raise TypeError(f'Bad return: {e}') from None + return result + + return wrapper + + +class Stock: + def __init__(self, name, shares, price): + self.name = name + self.shares = shares + self.price = price + + @property + def cost(self): + return self.shares * self.price + + @validated + def sell(self, nshares: Integer): + self.shares -= nshares + return self.shares + + +s = Stock('GOOG', 100, 490.1) +print(s.cost) +s.sell(10) From 11d164a917aeeac18ed4e7a065859c683960eaa1 Mon Sep 17 00:00:00 2001 From: Benjamin Swineford Date: Mon, 21 Aug 2023 09:43:31 -0500 Subject: [PATCH 45/54] idk finish out friday lol --- logcall.py | 87 +++++++++++++++++++++++++++++++++++++++++++++++++---- sample.py | 45 ++++++++++++++++++++++++--- validate.py | 55 ++++++++++++++++++++++++--------- 3 files changed, 161 insertions(+), 26 deletions(-) diff --git a/logcall.py b/logcall.py index 89f200ce..1286329f 100644 --- a/logcall.py +++ b/logcall.py @@ -1,9 +1,84 @@ +from functools import wraps, update_wrapper -def logged(func): - print('Adding logging to', func.__name__) +WRAPPER_ASSIGNMENTS = ('__module__', + '__name__', '__qualname__', '__doc__', + '__annotations__') +WRAPPER_UPDATES = ('__dict__',) - def wrapper(*args, **kwargs): - print('Calling', func.__name__) - return func(*args, **kwargs) - return wrapper +class object_proxy(object): + + def __init__(self, wrapped): + self.wrapped = wrapped + try: + self.__name__ = wrapped.__name__ + except AttributeError: + pass + + @property + def __class__(self): + return self.wrapped.__class__ + + def __getattr__(self, name): + return getattr(self.wrapped, name) + + +def update_wrapper(wrapper, wrapped, + assigned=WRAPPER_ASSIGNMENTS, + updated=WRAPPER_UPDATES): + wrapper.__wrapped__ = wrapped + for attr in assigned: + try: + value = getattr(wrapped, attr) + except AttributeError: + pass + else: + setattr(wrapper, attr, value) + for attr in updated: + getattr(wrapper, attr).update( + getattr(wrapped, attr, {})) + + +class bound_function_wrapper(object_proxy): + + def __init__(self, wrapped): + super(bound_function_wrapper, self).__init__(wrapped) + + def __call__(self, *args, **kwargs): + return self.wrapped(*args, **kwargs) + + +class function_wrapper(object_proxy): + + def __init__(self, wrapped): + super(function_wrapper, self).__init__(wrapped) + + def __get__(self, instance, owner): + wrapped = self.wrapped.__get__(instance, owner) + return bound_function_wrapper(wrapped) + + def __call__(self, *args, **kwargs): + return self.wrapped(*args, **kwargs) + + +def logformat(fmt): + def logged(func): + final_func= func + if hasattr(func, '__func__'): + final_func = func.__func__ + print(func.__func__.__str__()) + + name = func.fget.__name__ if type(func) == property else func.__name__ + # print('Adding logging to', name) + + @wraps(func) + def wrapper(*args, **kwargs): + print(fmt.format(func=func)) + return final_func(*args, **kwargs) + + return wrapper + + return logged + + +logged = logformat('Calling {func.__name__}') diff --git a/sample.py b/sample.py index e495df99..e74158c3 100644 --- a/sample.py +++ b/sample.py @@ -1,11 +1,46 @@ +from logcall import logged, logformat -from logcall import logged @logged -def add(x,y): - return x+y +def add(x, y): + 'Adds two things' + return x + y + @logged -def sub(x,y): - return x-y +def sub(x, y): + return x - y + + +@logformat('{func.__code__.co_filename}:{func.__name__}') +def mul(x, y): + return x * y + + +class Spam: + @logged + def instance_method(self): + pass + + @logged + @classmethod + def class_method(cls): + pass + + @logged + @staticmethod + def static_method(): + pass + + @logged + @property + def property_method(self): + return 'property method return' + +s = Spam() +print(s) +print(s.instance_method()) +print(Spam.class_method()) +print(Spam.static_method()) +print(s.property_method) \ No newline at end of file diff --git a/validate.py b/validate.py index 16c7368f..3b99157e 100644 --- a/validate.py +++ b/validate.py @@ -1,6 +1,7 @@ from inspect import signature from functools import wraps + class Validator: def __init__(self, name=None): self.name = name @@ -106,22 +107,46 @@ def wrapper(*args, **kwargs): return wrapper -class Stock: - def __init__(self, name, shares, price): - self.name = name - self.shares = shares - self.price = price +def enforce(**annotations): + retcheck = annotations.pop('return_', None) + + def decorate(func): + sig = signature(func) + + @wraps(func) + def wrapper(*args, **kwargs): + bound = sig.bind(*args, **kwargs) + errors = [] + + # Enforce argument checks + for name, validator in annotations.items(): + try: + validator.check(bound.arguments[name]) + except Exception as e: + errors.append(f' {name}: {e}') + + if errors: + raise TypeError('Bad Arguments\n' + '\n'.join(errors)) + + result = func(*args, **kwargs) + + if retcheck: + try: + retcheck.check(result) + except Exception as e: + raise TypeError(f'Bad return: {e}') from None + return result + + return wrapper + + return decorate - @property - def cost(self): - return self.shares * self.price - @validated - def sell(self, nshares: Integer): - self.shares -= nshares - return self.shares +@enforce(x=Integer, y=Integer, return_=Integer) +def add(x, y): + return x + y -s = Stock('GOOG', 100, 490.1) -print(s.cost) -s.sell(10) +print(add(2, 3)) +# print(add(2,3.0)) +# print(add('two',3.0)) From 0632926d0e68e1469d2ee9d8eeb9aa68e483148f Mon Sep 17 00:00:00 2001 From: Benjamin Swineford Date: Mon, 21 Aug 2023 10:33:45 -0500 Subject: [PATCH 46/54] ex 7_3 --- stock.py | 19 +++++++++-------- structure.py | 58 ++++++++++++++++++++++++++++++++++++++++++++++------ validate.py | 17 ++++++++------- 3 files changed, 70 insertions(+), 24 deletions(-) diff --git a/stock.py b/stock.py index 98a56b31..5565b14f 100644 --- a/stock.py +++ b/stock.py @@ -1,23 +1,24 @@ from structure import Structure -import inspect -from collections import namedtuple +from validate import String, PositiveInteger, PositiveFloat -from validate import ValidatedFunction, Integer +import reader class Stock(Structure): - _fields = ('name', 'shares', 'price') + name = String() + shares = PositiveInteger() + price = PositiveFloat() @property def cost(self): return self.shares * self.price - @ValidatedFunction - def sell(self, nshares: Integer): + def sell(self, nshares): self.shares -= nshares -Stock.create_init() +s = Stock.from_row(["GOOG", "100", "490.1"]) +print(s) -s = Stock('GOOG', 100, 490.1) -s.sell(10) + +port = reader.read_csv_as_instances("Data/portfolio.csv", Stock) diff --git a/structure.py b/structure.py index 9cd6f733..0cd7c579 100644 --- a/structure.py +++ b/structure.py @@ -1,23 +1,69 @@ import sys import inspect +from validate import Validator, validated + + +def validate_attributes(cls): + """ + Class decorator that scans a class definition for Validators + and builds a _fields variable that captures their definition order. + """ + validators = [] + for name, val in vars(cls).items(): + if isinstance(val, Validator): + validators.append(val) + # Apply validated decorator to any callable with annotations + elif callable(val) and val.__annotations__: + setattr(cls, name, validated(val)) + + # Collect all the field names + cls._fields = tuple([v.name for v in validators]) + + # Collect type conversions. The lambda x:x is an identity + # function that's used in case no expected_type is found. + cls._types = tuple([getattr(v, "expected_type", lambda x: x) for v in validators]) + + # Create the __init__ method + if cls._fields: + cls.create_init() + + return cls class Structure: _fields = () + _types = () + + @classmethod + def __init_subclass__(cls): + validate_attributes(cls) @classmethod def create_init(cls): - args = ','.join(cls._fields) - code = 'def __init__(self, {0}):\n'.format(args) - statements = [' self.{0} = {0}'.format(name) for name in cls._fields] - code += '\n'.join(statements) + """ + Create an __init__ method from _fields + """ + args = ",".join(cls._fields) + code = "def __init__(self, {0}):\n".format(args) + statements = [" self.{0} = {0}".format(name) for name in cls._fields] + code += "\n".join(statements) locs = {} exec(code, locs) - cls.__init__ = locs['__init__'] + cls.__init__ = locs["__init__"] + + @classmethod + def from_row(cls, row): + rowdata = [func(val) for func, val in zip(cls._types, row)] + return cls(*rowdata) def __eq__(self, other): return isinstance(other, self.__class__) and ( - all([getattr(self, field) == getattr(other, field) for field in self._fields]) + all( + [ + getattr(self, field) == getattr(other, field) + for field in self._fields + ] + ) ) def __init__(self, *args): diff --git a/validate.py b/validate.py index 3b99157e..2c855859 100644 --- a/validate.py +++ b/validate.py @@ -74,7 +74,7 @@ def validated(func): annotations = dict(func.__annotations__) # Get the return annotation (if any) - retcheck = annotations.pop('return', None) + retcheck = annotations.pop("return", None) @wraps(func) def wrapper(*args, **kwargs): @@ -86,10 +86,10 @@ def wrapper(*args, **kwargs): try: validator.check(bound.arguments[name]) except Exception as e: - errors.append(f' {name}: {e}') + errors.append(f" {name}: {e}") if errors: - raise TypeError('Bad Arguments\n' + '\n'.join(errors)) + raise TypeError("Bad Arguments\n" + "\n".join(errors)) for name, val in annotations.items(): val.check(bound.arguments[name]) @@ -101,14 +101,14 @@ def wrapper(*args, **kwargs): try: retcheck.check(result) except Exception as e: - raise TypeError(f'Bad return: {e}') from None + raise TypeError(f"Bad return: {e}") from None return result return wrapper def enforce(**annotations): - retcheck = annotations.pop('return_', None) + retcheck = annotations.pop("return_", None) def decorate(func): sig = signature(func) @@ -123,10 +123,10 @@ def wrapper(*args, **kwargs): try: validator.check(bound.arguments[name]) except Exception as e: - errors.append(f' {name}: {e}') + errors.append(f" {name}: {e}") if errors: - raise TypeError('Bad Arguments\n' + '\n'.join(errors)) + raise TypeError("Bad Arguments\n" + "\n".join(errors)) result = func(*args, **kwargs) @@ -134,7 +134,7 @@ def wrapper(*args, **kwargs): try: retcheck.check(result) except Exception as e: - raise TypeError(f'Bad return: {e}') from None + raise TypeError(f"Bad return: {e}") from None return result return wrapper @@ -147,6 +147,5 @@ def add(x, y): return x + y -print(add(2, 3)) # print(add(2,3.0)) # print(add('two',3.0)) From b9e1510f456a1506a1de3e0659ffcc33e630c7d1 Mon Sep 17 00:00:00 2001 From: Benjamin Swineford Date: Mon, 21 Aug 2023 11:21:08 -0500 Subject: [PATCH 47/54] ex 7_5 --- mymeta.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 mymeta.py diff --git a/mymeta.py b/mymeta.py new file mode 100644 index 00000000..e7b1af12 --- /dev/null +++ b/mymeta.py @@ -0,0 +1,31 @@ +class mytype(type): + @staticmethod + def __new__(meta, name, bases, __dict__): + print("Creating class :", name) + print("Base classes :", bases) + print("Attributes :", list(__dict__)) + return super().__new__(meta, name, bases, __dict__) + + +class myobject(metaclass=mytype): + pass + + +class Stock(myobject): + def __init__(self, name, shares, price): + self.name = name + self.shares = shares + self.price = price + + def cost(self): + return self.shares * self.price + + def sell(self, nshares): + self.shares -= nshares + + +class MyStock(Stock): + pass + + +s = MyStock("GOOG", 10, 420.1) From 49b1d5956f6e2ac8968e0ee2d106e56aa8125391 Mon Sep 17 00:00:00 2001 From: Benjamin Swineford Date: Mon, 21 Aug 2023 11:27:26 -0500 Subject: [PATCH 48/54] ex 7_6 --- stock.py | 1 - structure.py | 69 ++++++++++++++++++++++++++++++++-------------------- validate.py | 7 ++++++ 3 files changed, 49 insertions(+), 28 deletions(-) diff --git a/stock.py b/stock.py index 5565b14f..24d2e4fa 100644 --- a/stock.py +++ b/stock.py @@ -1,5 +1,4 @@ from structure import Structure -from validate import String, PositiveInteger, PositiveFloat import reader diff --git a/structure.py b/structure.py index 0cd7c579..015885ff 100644 --- a/structure.py +++ b/structure.py @@ -1,36 +1,19 @@ -import sys -import inspect from validate import Validator, validated +from collections import ChainMap -def validate_attributes(cls): - """ - Class decorator that scans a class definition for Validators - and builds a _fields variable that captures their definition order. - """ - validators = [] - for name, val in vars(cls).items(): - if isinstance(val, Validator): - validators.append(val) - # Apply validated decorator to any callable with annotations - elif callable(val) and val.__annotations__: - setattr(cls, name, validated(val)) - - # Collect all the field names - cls._fields = tuple([v.name for v in validators]) - - # Collect type conversions. The lambda x:x is an identity - # function that's used in case no expected_type is found. - cls._types = tuple([getattr(v, "expected_type", lambda x: x) for v in validators]) - - # Create the __init__ method - if cls._fields: - cls.create_init() +class StructureMeta(type): + @classmethod + def __prepare__(meta, clsname, bases): + return ChainMap({}, Validator.validators) - return cls + @staticmethod + def __new__(meta, name, bases, methods): + methods = methods.maps[0] + return super().__new__(meta, name, bases, methods) -class Structure: +class Structure(metaclass=StructureMeta): _fields = () _types = () @@ -83,3 +66,35 @@ def __setattr__(self, name, value): super().__setattr__(name, value) else: raise AttributeError("No attribute %s" % name) + + +def validate_attributes(cls): + """ + Class decorator that scans a class definition for Validators + and builds a _fields variable that captures their definition order. + """ + validators = [] + for name, val in vars(cls).items(): + if isinstance(val, Validator): + validators.append(val) + # Apply validated decorator to any callable with annotations + elif callable(val) and val.__annotations__: + setattr(cls, name, validated(val)) + + # Collect all the field names + cls._fields = tuple([v.name for v in validators]) + + # Collect type conversions. The lambda x:x is an identity + # function that's used in case no expected_type is found. + cls._types = tuple([getattr(v, "expected_type", lambda x: x) for v in validators]) + + # Create the __init__ method + if cls._fields: + cls.create_init() + + return cls + + +def typed_structure(clsname, **validators): + cls = type(clsname, (Structure,), validators) + return cls diff --git a/validate.py b/validate.py index 2c855859..bbc276b7 100644 --- a/validate.py +++ b/validate.py @@ -3,6 +3,9 @@ class Validator: + # Collect all derived classes into a dict + validators = {} + def __init__(self, name=None): self.name = name @@ -13,6 +16,10 @@ def __set_name__(self, cls, name): def check(cls, value): return value + @classmethod + def __init_subclass__(cls): + cls.validators[cls.__name__] = cls + def __set__(self, instance, value): instance.__dict__[self.name] = self.check(value) From b91bade901fae2cdbf649f507e735edcf3d98c85 Mon Sep 17 00:00:00 2001 From: Benjamin Swineford Date: Tue, 22 Aug 2023 11:17:46 -0500 Subject: [PATCH 49/54] ex 8_1 --- follow.py | 28 +++++++++++++++++++++++++++ stock.py | 8 +++++--- structure.py | 53 +++++++++++++++++++++++++--------------------------- 3 files changed, 58 insertions(+), 31 deletions(-) create mode 100644 follow.py diff --git a/follow.py b/follow.py new file mode 100644 index 00000000..01428ca9 --- /dev/null +++ b/follow.py @@ -0,0 +1,28 @@ +import os +import time + + +def follow(filename): + f = open(filename) + f.seek(0, os.SEEK_END) # Move file pointer 0 bytes from end of file + + while True: + line = f.readline() + if line == "": + time.sleep(0.1) # Sleep briefly and retry + continue + fields = line.split(",") + name = fields[0].strip('"') + price = float(fields[1]) + change = float(fields[4]) + if change < 0: + print("%10s %10.2f %10.2f" % (name, price, change)) + + +for line in follow("Data/stocklog.csv"): + fields = line.split(",") + name = fields[0].strip('"') + price = float(fields[1]) + change = float(fields[4]) + if change < 0: + print("%10s %10.2f %10.2f" % (name, price, change)) diff --git a/stock.py b/stock.py index 24d2e4fa..b85b3025 100644 --- a/stock.py +++ b/stock.py @@ -16,8 +16,10 @@ def sell(self, nshares): self.shares -= nshares -s = Stock.from_row(["GOOG", "100", "490.1"]) -print(s) +s = Stock("GOOG", 100, 490.1) +s2 = Stock("GOOG", 100, 490.1) -port = reader.read_csv_as_instances("Data/portfolio.csv", Stock) +print(s == s2) +print(list(s)) +print(tuple(s)) diff --git a/structure.py b/structure.py index 015885ff..78da16bf 100644 --- a/structure.py +++ b/structure.py @@ -17,6 +17,31 @@ class Structure(metaclass=StructureMeta): _fields = () _types = () + def __iter__(self): + for name in self._fields: + yield getattr(self, name) + + def __eq__(self, other): + return isinstance(other, type(self)) and tuple(self) == tuple(other) + + def __init__(self, *args): + if len(args) != len(self._fields): + raise TypeError("Expected %d arguments" % len(self._fields)) + for name, arg in zip(self._fields, args): + setattr(self, name, arg) + + def __repr__(self): + return "%s(%s)" % ( + type(self).__name__, + ", ".join(repr(getattr(self, name)) for name in self._fields), + ) + + def __setattr__(self, name, value): + if name.startswith("_") or name in self._fields: + super().__setattr__(name, value) + else: + raise AttributeError("No attribute %s" % name) + @classmethod def __init_subclass__(cls): validate_attributes(cls) @@ -39,34 +64,6 @@ def from_row(cls, row): rowdata = [func(val) for func, val in zip(cls._types, row)] return cls(*rowdata) - def __eq__(self, other): - return isinstance(other, self.__class__) and ( - all( - [ - getattr(self, field) == getattr(other, field) - for field in self._fields - ] - ) - ) - - def __init__(self, *args): - if len(args) != len(self._fields): - raise TypeError("Expected %d arguments" % len(self._fields)) - for name, arg in zip(self._fields, args): - setattr(self, name, arg) - - def __repr__(self): - return "%s(%s)" % ( - type(self).__name__, - ", ".join(repr(getattr(self, name)) for name in self._fields), - ) - - def __setattr__(self, name, value): - if name.startswith("_") or name in self._fields: - super().__setattr__(name, value) - else: - raise AttributeError("No attribute %s" % name) - def validate_attributes(cls): """ From 34c4377fe2a0f67860b3b06d618eec3812515027 Mon Sep 17 00:00:00 2001 From: Benjamin Swineford Date: Tue, 22 Aug 2023 11:59:05 -0500 Subject: [PATCH 50/54] ex 8_2 --- follow.py | 39 +++++++++++++++++++-------------------- tableformat.py | 30 +++++++++++++++--------------- ticker.py | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 66 insertions(+), 35 deletions(-) create mode 100644 ticker.py diff --git a/follow.py b/follow.py index 01428ca9..1c250d15 100644 --- a/follow.py +++ b/follow.py @@ -3,26 +3,25 @@ def follow(filename): - f = open(filename) - f.seek(0, os.SEEK_END) # Move file pointer 0 bytes from end of file + """ + 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) # Move file pointer 0 bytes from end of file + while True: + line = f.readline() + if line == "": + time.sleep(0.1) # Sleep briefly to avoid busy wait + continue + yield line - while True: - line = f.readline() - if line == "": - time.sleep(0.1) # Sleep briefly and retry - continue - fields = line.split(",") - name = fields[0].strip('"') - price = float(fields[1]) - change = float(fields[4]) + +# Example use +if __name__ == "__main__": + for line in follow("Data/stocklog.csv"): + row = line.split(",") + name = row[0].strip('"') + price = float(row[1]) + change = float(row[4]) if change < 0: print("%10s %10.2f %10.2f" % (name, price, change)) - - -for line in follow("Data/stocklog.csv"): - fields = line.split(",") - name = fields[0].strip('"') - price = float(fields[1]) - change = float(fields[4]) - if change < 0: - print("%10s %10.2f %10.2f" % (name, price, change)) diff --git a/tableformat.py b/tableformat.py index a8cf0122..14b339ce 100644 --- a/tableformat.py +++ b/tableformat.py @@ -42,9 +42,9 @@ def print(self): for line in self._list_to_print: self.print_line(line) - def divider(self, separator="-"): + def divider(self, separator="-", color="yellow"): return ( - Fore.yellow + getattr(Fore, color) + Style.bold + "|".join(f"{separator * self._width}" for _ in range(self._columns)) + Style.reset @@ -87,9 +87,14 @@ def headings(self, headers): ) self.add_divider() + for line in self._list_to_print: + self.print_line(line) + def row(self, rowdata): - self._list_to_print.append( - "|".join(f"{cell:^{self._width}}" for cell in rowdata) + self.print_line( + f"{Fore.yellow}|{Style.reset}".join( + f"{cell:^{self._width}}" for cell in rowdata + ) ) @staticmethod @@ -100,6 +105,7 @@ def print_line(line=""): def print(self): self.add_divider() + pprint(self._list_to_print) for line in self._list_to_print: self.print_line(line) @@ -171,17 +177,6 @@ def print(self): def create_formatter(name, column_formats=None, upper_headers=False): - print( - "\n" - + Fore.white - + Style.bold - + Back.dark_sea_green_4b - + " " - + name.upper() - + "\t\t" - + Style.reset - ) - if name == "text": formatter_cls = TextTableFormatter elif name == "csv": @@ -201,4 +196,9 @@ class formatter_cls(ColumnFormatMixin, formatter_cls): class formatter_cls(UpperHeadersMixin, formatter_cls): pass + print( + f"\n{Fore.white}{Style.bold}{Back.dark_sea_green_4b}" + + f"\t\t{formatter_cls.__name__}\t\t" + + Style.reset + ) return formatter_cls() diff --git a/ticker.py b/ticker.py new file mode 100644 index 00000000..c264f4d3 --- /dev/null +++ b/ticker.py @@ -0,0 +1,32 @@ +from pprint import pprint + +from structure import Structure + + +class Ticker(Structure): + name = String() + price = Float() + date = String() + time = String() + change = Float() + open = Float() + high = Float() + low = Float() + volume = Integer() + + +if __name__ == "__main__": + from follow import follow + import csv + + from tableformat import create_formatter, print_table + + formatter = create_formatter("text") + + lines = follow("Data/stocklog.csv") + rows = csv.reader(lines) + + records = (Ticker.from_row(row) for row in rows) + negative = (rec for rec in records if rec.change < 0) + + print_table(negative, ["name", "price", "change"], formatter) From b3799098b2becb9818f0446f276ebb7e178e102b Mon Sep 17 00:00:00 2001 From: Benjamin Swineford Date: Tue, 22 Aug 2023 12:04:20 -0500 Subject: [PATCH 51/54] ex 8_3 --- cofollow.py | 40 ++++++++++++++++++++++++++++++++ coticker.py | 66 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+) create mode 100644 cofollow.py create mode 100644 coticker.py diff --git a/cofollow.py b/cofollow.py new file mode 100644 index 00000000..47cf590f --- /dev/null +++ b/cofollow.py @@ -0,0 +1,40 @@ +import os +import time + +# Decorator for coroutine functions +from functools import wraps + + +# Data source +def follow(filename, target): + with open(filename, "r") as f: + f.seek(0, os.SEEK_END) + while True: + line = f.readline() + if line != "": + target.send(line) + else: + time.sleep(0.1) + + +def consumer(func): + @wraps(func) + def start(*args, **kwargs): + f = func(*args, **kwargs) + f.send(None) + return f + + return start + + +# Sample coroutine +@consumer +def printer(): + while True: + item = yield # Receive an item sent to me + print(item) + + +# Example use +if __name__ == "__main__": + follow("Data/stocklog.csv", printer()) diff --git a/coticker.py b/coticker.py new file mode 100644 index 00000000..cbe58ae4 --- /dev/null +++ b/coticker.py @@ -0,0 +1,66 @@ +from structure import Structure + + +from cofollow import consumer, follow +from tableformat import create_formatter +import csv + + +class Ticker(Structure): + name = String() + price = Float() + date = String() + time = String() + change = Float() + open = Float() + high = Float() + low = Float() + volume = Integer() + + +# Coroutine that splits rows using the CSV module. This is rather +# mind-bending due to the fact that the csv module only understands +# iteration with the for-loop. To make it work, we wrap it around +# a generator that simply produces the last item received. +@consumer +def to_csv(target): + def producer(): + while True: + yield line + + reader = csv.reader(producer()) + while True: + line = yield + target.send(next(reader)) + + +@consumer +def create_ticker(target): + while True: + row = yield + target.send(Ticker.from_row(row)) + + +@consumer +def negchange(target): + while True: + record = yield + if record.change < 0: + target.send(record) + + +@consumer +def ticker(fmt, fields): + formatter = create_formatter(fmt) + formatter.headings(fields) + while True: + rec = yield + row = [getattr(rec, name) for name in fields] + formatter.row(row) + + +if __name__ == "__main__": + follow( + "Data/stocklog.csv", + to_csv(create_ticker(negchange(ticker("text", ["name", "price", "change"])))), + ) From b716e7827ee5b17b2a1036065993a973a65a3537 Mon Sep 17 00:00:00 2001 From: Benjamin Swineford Date: Tue, 22 Aug 2023 12:11:20 -0500 Subject: [PATCH 52/54] ex 8_4 --- cofollow.py | 19 +++++++++++++++---- follow.py | 42 +++++++++++++++++++++++++++--------------- 2 files changed, 42 insertions(+), 19 deletions(-) diff --git a/cofollow.py b/cofollow.py index 47cf590f..8bf7ee94 100644 --- a/cofollow.py +++ b/cofollow.py @@ -27,14 +27,25 @@ def start(*args, **kwargs): return start -# Sample coroutine @consumer def printer(): while True: - item = yield # Receive an item sent to me - print(item) + try: + item = yield + print(item) + except Exception as e: + print("ERROR: %r" % e) # Example use if __name__ == "__main__": - follow("Data/stocklog.csv", printer()) + p = printer() + p.send("hello") + + p.send(42) + + p.throw(ValueError("It failed")) + try: + int("n/a") + except ValueError as e: + p.throw(e) diff --git a/follow.py b/follow.py index 1c250d15..ad51a5ba 100644 --- a/follow.py +++ b/follow.py @@ -6,22 +6,34 @@ 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) # Move file pointer 0 bytes from end of file - while True: - line = f.readline() - if line == "": - time.sleep(0.1) # Sleep briefly to avoid busy wait - continue - yield line + try: + with open(filename, "r") as f: + f.seek(0, os.SEEK_END) + while True: + line = f.readline() + if line == "": + time.sleep(0.1) # Sleep briefly to avoid busy wait + continue + yield line + except GeneratorExit: + print("Following Done") # Example use if __name__ == "__main__": - for line in follow("Data/stocklog.csv"): - row = line.split(",") - name = row[0].strip('"') - price = float(row[1]) - change = float(row[4]) - if change < 0: - print("%10s %10.2f %10.2f" % (name, price, change)) + # Experiment: Garbage collection of a running generator + f = follow("Data/stocklog.csv") + + print("next(f)") + next(f) + print() + + print("del f") + print() + del f + # Experiment: Closing a generator + f = follow("Data/stocklog.csv") + for line in f: + print(line, end="") + if "IBM" in line: + f.close() From c20082a691e3792f43031ebac24d98f866e07616 Mon Sep 17 00:00:00 2001 From: Benjamin Swineford Date: Tue, 22 Aug 2023 12:11:44 -0500 Subject: [PATCH 53/54] add generated stocklog csv to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 2dc53ca3..863a6acb 100644 --- a/.gitignore +++ b/.gitignore @@ -158,3 +158,4 @@ cython_debug/ # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. .idea/ +/Data/stocklog.csv From 668b02e22064ea8e579648fbed00dbcc99cd0953 Mon Sep 17 00:00:00 2001 From: Benjamin Swineford Date: Tue, 22 Aug 2023 12:26:57 -0500 Subject: [PATCH 54/54] ex 8_5 --- multitask.py | 35 ++++++++++++++++++++++++++++++++ server.py | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+) create mode 100644 multitask.py create mode 100644 server.py diff --git a/multitask.py b/multitask.py new file mode 100644 index 00000000..251864b9 --- /dev/null +++ b/multitask.py @@ -0,0 +1,35 @@ +from collections import deque + +tasks = deque() + + +def run(): + while tasks: + task = tasks.popleft() + try: + task.send(None) + tasks.append(task) + except StopIteration: + print("Task done") + + +def countdown(n): + while n > 0: + print("T-minus", n) + yield + n -= 1 + + +def countup(n): + x = 0 + while x < n: + print("Up we go", x) + yield + x += 1 + + +if __name__ == "__main__": + tasks.append(countdown(10)) + tasks.append(countdown(5)) + tasks.append(countup(20)) + run() diff --git a/server.py b/server.py new file mode 100644 index 00000000..d604efee --- /dev/null +++ b/server.py @@ -0,0 +1,56 @@ +from socket import * +from select import select +from collections import deque + +tasks = deque() +recv_wait = {} # sock -> task +send_wait = {} # sock -> task + + +def run(): + while any([tasks, recv_wait, send_wait]): + while not tasks: + can_recv, can_send, _ = select(recv_wait, send_wait, []) + for s in can_recv: + tasks.append(recv_wait.pop(s)) + for s in can_send: + tasks.append(send_wait.pop(s)) + task = tasks.popleft() + try: + reason, resource = task.send(None) + if reason == "recv": + recv_wait[resource] = task + elif reason == "send": + send_wait[resource] = task + else: + raise RuntimeError("Unknown reason %r" % reason) + except StopIteration: + print("Task done") + + +def tcp_server(address, handler): + sock = socket(AF_INET, SOCK_STREAM) + sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) + sock.bind(address) + sock.listen(5) + while True: + yield "recv", sock + client, addr = sock.accept() + tasks.append(handler(client, addr)) + + +def echo_handler(client, address): + print("Connection from", address) + while True: + yield "recv", client + data = client.recv(1000) + if not data: + break + yield "send", client + client.send(b"GOT:" + data) + print("Connection closed") + + +if __name__ == "__main__": + tasks.append(tcp_server(("", 25000), echo_handler)) + run()