From 065ef0841e762dd09fa6392e587374fbba3502ff Mon Sep 17 00:00:00 2001 From: Siddiquee Date: Sun, 17 Jun 2018 14:26:10 -0500 Subject: [PATCH 01/10] lesson 01 commit --- .../osiddiquee/lesson01/generator_solution.py | 59 +++++++++++++++++++ Student/osiddiquee/lesson01/iterator_2.py | 36 +++++++++++ Student/osiddiquee/lesson01/music.py | 35 +++++++++++ 3 files changed, 130 insertions(+) create mode 100644 Student/osiddiquee/lesson01/generator_solution.py create mode 100644 Student/osiddiquee/lesson01/iterator_2.py create mode 100644 Student/osiddiquee/lesson01/music.py diff --git a/Student/osiddiquee/lesson01/generator_solution.py b/Student/osiddiquee/lesson01/generator_solution.py new file mode 100644 index 0000000..1b8078a --- /dev/null +++ b/Student/osiddiquee/lesson01/generator_solution.py @@ -0,0 +1,59 @@ +def intsum(): + previous, last = 0, 0 + while last >= 0: + if last == 0: + yield previous + last += 1 + else: + sum = previous + last + previous = sum + last += 1 + yield sum + + +def doubler(): + doubled = 1 + while doubled > 0: + if doubled == 1: + yield 1 + doubled *= 2 + elif doubled == 2: + yield doubled + doubled *= 2 + else: + yield doubled + doubled *= 2 + + +def fib(): + first, second = 0, 1 + while first >= 0: + if first == 0: + yield second + first = second + second *= 2 + elif first == 1: + yield first + fib2 = second + second = first + fib2 + first = fib2 + else: + yield first + fib2 = second + second = first + fib2 + first = fib2 + + +def prime(): + integer = 2 + while integer > 1: + if integer == 2: + yield integer + else: + isprime = True + for i in range(2, integer): + if integer % i == 0: + isprime = False + if isprime: + yield integer + integer += 1 diff --git a/Student/osiddiquee/lesson01/iterator_2.py b/Student/osiddiquee/lesson01/iterator_2.py new file mode 100644 index 0000000..5d845cf --- /dev/null +++ b/Student/osiddiquee/lesson01/iterator_2.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python + +""" +Simple iterator examples +""" + + +class IterateMe_2: + """ + About as simple an iterator as you can get: + + returns the sequence of numbers from zero to 4 + ( like range(4) ) + """ + + def __init__(self, start = 0, stop = 5, step = 1): + self.current = start + self.stop = stop + self.step = step + + def __iter__(self): + return self + + def __next__(self): + self.current += self.step + if self.current < self.stop: + return self.current + else: + raise StopIteration + + +if __name__ == "__main__": + + print("Testing the iterator") + for i in IterateMe_2(): + print(i) diff --git a/Student/osiddiquee/lesson01/music.py b/Student/osiddiquee/lesson01/music.py new file mode 100644 index 0000000..b04f530 --- /dev/null +++ b/Student/osiddiquee/lesson01/music.py @@ -0,0 +1,35 @@ +import pandas as pd + +music = pd.read_csv('featuresdf.csv') + +artists = [artists for artists in + music[(music['danceability'] > 0.8) & + (music['loudness'] < -5.0)].artists] + +songs = [name for name in + music[(music['danceability'] > 0.8) & + (music['loudness'] < -5.0)].name] + +danceability = [danceability for danceability in + music[(music['danceability'] > 0.8) & + (music['loudness'] < -5.0)].danceability] + +spotify_data = list(zip(artists, songs, danceability)) + +sorted_spotify = sorted(spotify_data, key = lambda x: x[2], reverse = True) + +print('Simple solution output: ') +for artist, song, dance in sorted_spotify[:5]: + print(song) + +print('\nA much more elegant solution output: ') +''' A much more elegant solution is below ''' +spotify_query = [(artist, song, dance, loud) + for (artist, song, dance, loud) in + zip(music.artists, music.name, music.danceability, music.loudness) + if (dance > 0.8) & (loud < -5.0)] + +for artist, song, dance, loud in sorted(spotify_query, + key = lambda x: x[2], + reverse = True)[:5]: + print(song) From da543d0c1859c7a5ce332fe18ffa366e92caaa0d Mon Sep 17 00:00:00 2001 From: Siddiquee Date: Sun, 17 Jun 2018 14:27:21 -0500 Subject: [PATCH 02/10] lesson02 commit --- Student/osiddiquee/lesson02/Untitled.ipynb | 309 ++++++++++++++++++ .../osiddiquee/lesson02/spotify_generator.py | 27 ++ 2 files changed, 336 insertions(+) create mode 100644 Student/osiddiquee/lesson02/Untitled.ipynb create mode 100644 Student/osiddiquee/lesson02/spotify_generator.py diff --git a/Student/osiddiquee/lesson02/Untitled.ipynb b/Student/osiddiquee/lesson02/Untitled.ipynb new file mode 100644 index 0000000..82af52a --- /dev/null +++ b/Student/osiddiquee/lesson02/Untitled.ipynb @@ -0,0 +1,309 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def check_prime(number): \n", + " for divisor in range(2, int(number ** 0.5) + 1): \n", + " if number % divisor == 0: \n", + " return False \n", + " return True" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "class Primes:\n", + " def __init__(self, max):\n", + " self.max = max\n", + "\n", + " self.number = 1\n", + "\n", + " def __iter__(self):\n", + "\n", + " return self\n", + "\n", + " def __next__(self):\n", + "\n", + " self.number += 1\n", + "\n", + " if self.number >= self.max:\n", + "\n", + " raise StopIteration\n", + "\n", + " elif check_prime(self.number):\n", + "\n", + " return self.number\n", + "\n", + " else:\n", + "\n", + " return self.__next__()" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "<__main__.Primes object at 0x000001D133542B70>\n", + "5\n", + "7\n", + "9\n", + "11\n", + "13\n", + "15\n", + "17\n", + "19\n", + "21\n", + "23\n", + "25\n", + "27\n", + "29\n", + "31\n", + "33\n", + "35\n", + "37\n", + "39\n", + "41\n", + "43\n", + "45\n", + "47\n", + "49\n", + "51\n", + "53\n", + "55\n", + "57\n", + "59\n", + "61\n", + "63\n", + "65\n", + "67\n", + "69\n", + "71\n", + "73\n", + "75\n", + "77\n", + "79\n", + "81\n", + "83\n", + "85\n", + "87\n", + "89\n", + "91\n", + "93\n", + "95\n", + "97\n", + "99\n" + ] + } + ], + "source": [ + "primes = Primes(100)\n", + "print(primes)\n", + "for x in primes: \n", + " print(x) " + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Hey!\n" + ] + } + ], + "source": [ + " def outerFunction(text):\n", + " text = text\n", + " \n", + " def innerFunction():\n", + " print(text)\n", + " \n", + " return innerFunction # Note we are returning function WITHOUT parenthesis\n", + " \n", + "if __name__ == '__main__':\n", + " myFunction = outerFunction('Hey!')\n", + " myFunction() " + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'outside': 1, 'inside': 2}\n", + "{'outside': 1, 'inside': 2}\n" + ] + } + ], + "source": [ + " def outside():\n", + " d = {\"outside\": 1}\n", + " def inside():\n", + " d[\"inside\"] = 2\n", + " print(d)\n", + " inside()\n", + " print(d)\n", + " \n", + "outside()" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "[1, 2, 4, 6]" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import heapq\n", + "\n", + "heap =[]\n", + "heapq.heappush(heap,1)\n", + "heapq.heappush(heap,2)\n", + "heapq.heappush(heap,4)\n", + "heapq.heappush(heap,6)\n", + "heapq.heappush(heap,7)\n", + "heapq.heappush(heap,9)\n", + "heapq.heappush(heap,10)\n", + "heapq.heappush(heap,12) \n", + "heapq.nsmallest(4, heap)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### With partial code\n" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "[1, 3, 4, 5, 6, 8]" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import functools\n", + "import heapq\n", + "heap =[]\n", + "push = functools.partial(heapq.heappush, heap)\n", + "smallest = functools.partial(heapq.nsmallest, iterable=heap)\n", + "push(1)\n", + "push(3)\n", + "push(5)\n", + "push(6)\n", + "push(8)\n", + "push(11)\n", + "push(4)\n", + "push(16)\n", + "push(17)\n", + "smallest(6)" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "from itertools import *\n", + "\n", + "string = islice('ABCDEF', 2, None)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.0" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/Student/osiddiquee/lesson02/spotify_generator.py b/Student/osiddiquee/lesson02/spotify_generator.py new file mode 100644 index 0000000..9d03647 --- /dev/null +++ b/Student/osiddiquee/lesson02/spotify_generator.py @@ -0,0 +1,27 @@ +import pandas + +spotify = pandas.read_csv('featuresdf.csv') + +#Generators +data = ((artist, song) for (artist, song) in zip(spotify.artists, spotify.name) + if artist == 'Kendrick Lamar') +print('Generator ouput - Kendrick Lamar tracks: ') +for artist, song in data: + print(song) + +#Closures +def ingest_data(filename): + spotify = pandas.read_csv(filename) + data = ((artist, song, energy) for (artist, song, energy) + in zip(spotify.artists, spotify.name, spotify.energy) + if energy >= 0.8) + def print_high_energy(): + print('Artist' + ' ' * 14, 'Song' + ' ' * 46, 'Energy') + for artist, song, energy in data: + print(artist + ' ' * (20 - len(artist)), + song + ' ' * (50 - len(song)), + energy) + return print_high_energy() + +print('Generator ouput - Kendrick Lamar tracks: ') +ingest_data('featuresdf.csv') From 98005fa6ddd9ef752c8f337fac3f03952c386d71 Mon Sep 17 00:00:00 2001 From: Siddiquee Date: Sun, 17 Jun 2018 14:28:58 -0500 Subject: [PATCH 03/10] lesson03 commit --- Student/osiddiquee/lesson03/factorial.py | 5 +++++ Student/osiddiquee/lesson03/locke.py | 27 ++++++++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 Student/osiddiquee/lesson03/factorial.py create mode 100644 Student/osiddiquee/lesson03/locke.py diff --git a/Student/osiddiquee/lesson03/factorial.py b/Student/osiddiquee/lesson03/factorial.py new file mode 100644 index 0000000..366c7d6 --- /dev/null +++ b/Student/osiddiquee/lesson03/factorial.py @@ -0,0 +1,5 @@ +def factorial(number): + if number == 1: + return 1 + else: + return number * factorial(number - 1) diff --git a/Student/osiddiquee/lesson03/locke.py b/Student/osiddiquee/lesson03/locke.py new file mode 100644 index 0000000..9c9e255 --- /dev/null +++ b/Student/osiddiquee/lesson03/locke.py @@ -0,0 +1,27 @@ + +class Locke: + + def __init__(self, locke_capacity = 10, handle_error = None): + self.locke_capacity = locke_capacity + self.handle_error = handle_error + + def __enter__(self): + if not self.handle_error: + print('Stopping the pumps.\n', + 'Opening the doors.\n') + + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + if not self.handle_error: + print('Closing the doors.\n', + 'Restarting the pumps.\n') + + return self.handle_error + + def move_boats_through(self, number_boats): + if number_boats <= self.locke_capacity: + print('Boats have moved through') + else: + raise RuntimeError('The number of boats is too damn high') + return self From 7c6584d8c02efe613c19cfacd4cc687d81841d67 Mon Sep 17 00:00:00 2001 From: Siddiquee Date: Sun, 17 Jun 2018 15:36:35 -0500 Subject: [PATCH 04/10] lesson04 commits --- Student/osiddiquee/lesson04/mailroom_json.py | 292 +++++++++++++++++++ 1 file changed, 292 insertions(+) create mode 100644 Student/osiddiquee/lesson04/mailroom_json.py diff --git a/Student/osiddiquee/lesson04/mailroom_json.py b/Student/osiddiquee/lesson04/mailroom_json.py new file mode 100644 index 0000000..8f4bfc7 --- /dev/null +++ b/Student/osiddiquee/lesson04/mailroom_json.py @@ -0,0 +1,292 @@ +#!/usr/bin/env python +""" +mailroom assignment + +This version uses a dict for the main db, and exception handling to +check input, and has been factored to be amenable to testing. +""" + +import sys +import math + +from functools import reduce + +# handy utility to make pretty printing easier +from textwrap import dedent + + +# In memory representation of the donor database +# using a tuple for each donor +# -- kind of like a record in a database table +# using a dict with a lower case version of the donor's name as the key +# This makes it easier to have a 'normalized' key. +# you could get a bit fancier by having each "record" be a dict, with +# "name" and "donations" as keys. +def get_donor_db(): + return {'william gates iii': ("William Gates III", [653772.32, 12.17]), + 'jeff bezos': ("Jeff Bezos", [877.33]), + 'paul allen': ("Paul Allen", [663.23, 43.87, 1.32]), + 'mark zuckerberg': ("Mark Zuckerberg", [1663.23, 4300.87, 10432.0]), + } + +def get_donor_db_oo(): + db = DonorDB() + raw_data = get_donor_db() + + for k, v in raw_data.items(): + donor = Donor(k) + for donation in v[1]: + donor.add_donation(donation) + db.add_donor(donor) + + return db + + +class Donor: + def __init__(self, name, donations=None): + self._name = name + self._donations = [] if donations is None else donations + + @property + def name(self): + return self._name + + @property + def donations(self): + return self._donations + + def add_donation(self, donation): + self._donations.append(donation) + + @property + def total_donations(self): + return sum(self._donations) + + #return reduce(lambda a,x: a+x, self._donations, 0) + + # s = 0 + # for d in self._donations: + # s += d + # return s + + def __repr__(self): + return "{}: {}".format(self._name, self._donations) + + +class DonorDB: + def __init__(self): + self._donors = {} + + def add_donor(self, donor): + self._donors[donor.name.lower()] = donor + + def get_total_from_donor(self, donor_name): + return self._donors[donor_name.lower()].total_donations + + def get_donor(self, donor_name): + return self._donors[donor_name.lower()] + + @property + def num_donors(self): + return len(self._donors) + + def __repr__(self): + return str(self._donors) + + +def list_donors(): + """ + creates a list of the donors as a string, so they can be printed + + Not calling print from here makes it more flexible and easier to + test + """ + listing = ["Donor list:"] + for donor in donor_db.values(): + listing.append(donor[0]) + return "\n".join(listing) + + +def find_donor(name): + """ + find a donor in the donor db + + :param: the name of the donor + + :returns: The donor data structure -- None if not in the donor_db + """ + key = name.strip().lower() + return donor_db.get(key) + + +def add_donor(name): + """ + Add a new donor to the donor db + + :param: the name of the donor + + :returns: the new Donor data structure + """ + name = name.strip() + donor = (name, []) + donor_db[name.lower()] = donor + return donor + + +def main_menu_selection(): + """ + Print out the main application menu and then read the user input. + """ + action = input(dedent(''' + Choose an action: + + 1 - Send a Thank You + 2 - Create a Report + 3 - Send letters to everyone + 4 - Quit + + > ''')) + return action.strip() + + +def gen_letter(donor): + """ + Generate a thank you letter for the donor + + :param: donor tuple + + :returns: string with letter + + note: This doesn't actually write to a file -- that's a separate + function. This makes it more flexible and easier to test. + """ + return dedent('''Dear {0:s}, + + Thank you for your very kind donation of ${1:.2f}. + It will be put to very good use. + + Sincerely, + -The Team + '''.format(donor[0], donor[1][-1])) + + +def send_thank_you(): + """ + Execute the logic to record a donation and generate a thank you message. + """ + # Read a valid donor to send a thank you from, handling special commands to + # let the user navigate as defined. + while True: + name = input("Enter a donor's name (or list to see all donors or 'menu' to exit)> ").strip() + if name == "list": + print(list_donors()) + elif name == "menu": + return + else: + break + + # Now prompt the user for a donation amount to apply. Since this is + # also an exit point to the main menu, we want to make sure this is + # done before mutating the db. + while True: + amount_str = input("Enter a donation amount (or 'menu' to exit)> ").strip() + if amount_str == "menu": + return + # Make sure amount is a valid amount before leaving the input loop + try: + amount = float(amount_str) + # extra check here -- unlikely that someone will type "NaN", but + # it IS possible, and it is a valid floating point number: + # http://en.wikipedia.org/wiki/NaN + if math.isnan(amount) or math.isinf(amount) or round(amount, 2) == 0.00: + raise ValueError + # in this case, the ValueError could be raised by the float() call, or by the NaN-check + except ValueError: + print("error: donation amount is invalid\n") + else: + break + + # If this is a new user, ensure that the database has the necessary + # data structure. + donor = find_donor(name) + if donor is None: + donor = add_donor(name) + + # Record the donation + donor[1].append(amount) + print(gen_letter(donor)) + + +def sort_key(item): + # used to sort on name in donor_db + return item[1] + + +def generate_donor_report(): + """ + Generate the report of the donors and amounts donated. + + :returns: the donor report as a string. + """ + # First, reduce the raw data into a summary list view + report_rows = [] + for (name, gifts) in donor_db.values(): + total_gifts = sum(gifts) + num_gifts = len(gifts) + avg_gift = total_gifts / num_gifts + report_rows.append((name, total_gifts, num_gifts, avg_gift)) + + # sort the report data + report_rows.sort(key=sort_key) + report = [] + report.append("{:25s} | {:11s} | {:9s} | {:12s}".format("Donor Name", + "Total Given", + "Num Gifts", + "Average Gift")) + report.append("-" * 66) + for row in report_rows: + report.append("{:25s} ${:10.2f} {:9d} ${:11.2f}".format(*row)) + return "\n".join(report) + + +def save_letters_to_disk(): + """ + make a letter for each donor, and save it to disk. + """ + for donor in donor_db.values(): + letter = gen_letter(donor) + # I don't like spaces in filenames... + filename = donor[0].replace(" ", "_") + ".txt" + open(filename, 'w').write(letter) + + +def print_donor_report(): + print(generate_donor_report()) + + +def quit(): + sys.exit(0) + +def main(): + donor_db = get_donor_db_oo() + donor_name = input('Whose donation record would you like to see?') + donor = donor_db.get_donor(donor_name) + print("{}, you donated total of {}! Thank you!".format(donor.name, donor.total_donations)) + +if __name__ == "__main__": + main() + # donor_db = get_donor_db() + + # running = True + + # selection_dict = {"1": send_thank_you, + # "2": print_donor_report, + # "3": save_letters_to_disk, + # "4": quit} + + # while True: + # selection = main_menu_selection() + # try: + # selection_dict[selection]() + # except KeyError: + # print("error: menu selection is invalid!") From b5a42fb76642e86e0d15b78a42f406869c3aeac9 Mon Sep 17 00:00:00 2001 From: Siddiquee Date: Wed, 20 Jun 2018 18:09:02 -0700 Subject: [PATCH 05/10] lesson05 commits --- .../lesson05/SiddiqueeDebugging.txt | 9 +++++ Student/osiddiquee/lesson05/logging.py | 40 +++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 Student/osiddiquee/lesson05/SiddiqueeDebugging.txt create mode 100644 Student/osiddiquee/lesson05/logging.py diff --git a/Student/osiddiquee/lesson05/SiddiqueeDebugging.txt b/Student/osiddiquee/lesson05/SiddiqueeDebugging.txt new file mode 100644 index 0000000..0278e65 --- /dev/null +++ b/Student/osiddiquee/lesson05/SiddiqueeDebugging.txt @@ -0,0 +1,9 @@ +If you put an odd number for n, when you half n it will never become 2. +It will keep halving forever. As the n function, gets deeper into recursion. +The call stack gets much deeper and runs slower. + + +I also could not find the file for the exercise. + +But i would theoretically, utilize n and s to dig into the function. I would +use b because I wouldn't select a large n. diff --git a/Student/osiddiquee/lesson05/logging.py b/Student/osiddiquee/lesson05/logging.py new file mode 100644 index 0000000..ac20271 --- /dev/null +++ b/Student/osiddiquee/lesson05/logging.py @@ -0,0 +1,40 @@ +import logging, logging.handlers, datetime + +date = datetime.datetime.now() + +format_console = "%(asctime)s %(filename)s:%(lineno)-3d %(levelname)s %(message)s" +format_syslog = '%(filename)s:%(lineno)-3d %(levelname)s %(message)s"' + +formatter_console = logging.Formatter(format_console) +formatter_syslog = logging.Formatter(format_syslog) + +file_handler = logging.FileHandler(f'{date.day}-{date.month}-{date.year}.log') +file_handler.setLevel(logging.WARNING) +file_handler.setFormatter(formatter_console) + +console_handler = logging.StreamHandler() +console_handler.setLevel(logging.DEBUG) +console_handler.setFormatter(formatter_console) + +syslog_handler = logging.handlers.DatagramHandler('127.0.0.1', 514) +syslog_handler.setLevel(logging.ERROR) +syslog_handler.setFormatter(formatter_syslog) + +logger = logging.getLogger() +logger.setLevel(logging.DEBUG) +logger.addHandler(file_handler) +logger.addHandler(console_handler) +logger.addHandler(syslog_handler) + +def my_fun(n): + for i in range(0, n): + logging.debug(i) + if i == 50: + logging.warning("The value of i is 50.") + try: + i / (50 - i) + except ZeroDivisionError: + logging.error("Tried to divide by zero. Var i was {}. Recovered gracefully.".format(i)) + +if __name__ == "__main__": + my_fun(100) From d4fedf036776eb89603a4310623b31f1137e8b83 Mon Sep 17 00:00:00 2001 From: Siddiquee Date: Sun, 24 Jun 2018 18:35:57 -0700 Subject: [PATCH 06/10] lesson 06 commit for assignment --- Student/osiddiquee/lesson06/test.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 Student/osiddiquee/lesson06/test.py diff --git a/Student/osiddiquee/lesson06/test.py b/Student/osiddiquee/lesson06/test.py new file mode 100644 index 0000000..9c207f2 --- /dev/null +++ b/Student/osiddiquee/lesson06/test.py @@ -0,0 +1,27 @@ +import unittest + +from roman_to_int import RomanToInt + + +class TestRomanToInteger(unittest.TestCase): + + def test_single_x(self): + self.assertEqual(RomanToInt.convert('X'), 10) + + def test_single_i(self): + self.assertEqual(RomanToInt.convert('I'), 1) + + def test_single_v(self): + self.assertEqual(RomanToInt.convert('V'), 5) + + def test_single_l(self): + self.assertEqual(RomanToInt.convert('L'), 50) + + def test_single_c(self): + self.assertEqual(RomanToInt.convert('C'), 100) + + def test_single_d(self): + self.assertEqual(RomanToInt.convert('D'), 500) + + def test_single_m(self): + self.assertEqual(RomanToInt.convert('M'), 1000) From a0842e19cdd0fbcbeff7489c844cdd23f8e62fc9 Mon Sep 17 00:00:00 2001 From: Siddiquee Date: Wed, 27 Jun 2018 21:35:30 -0700 Subject: [PATCH 07/10] lesson07 activity --- Student/osiddiquee/lesson07/DB.png | Bin 0 -> 25323 bytes Student/osiddiquee/lesson07/activity.py | 59 +++++++++++++++++++ Student/osiddiquee/lesson07/activity2.py | 70 +++++++++++++++++++++++ 3 files changed, 129 insertions(+) create mode 100644 Student/osiddiquee/lesson07/DB.png create mode 100644 Student/osiddiquee/lesson07/activity.py create mode 100644 Student/osiddiquee/lesson07/activity2.py diff --git a/Student/osiddiquee/lesson07/DB.png b/Student/osiddiquee/lesson07/DB.png new file mode 100644 index 0000000000000000000000000000000000000000..4ebb2a55fd229ea78f6aae1f776e63b6b2762685 GIT binary patch literal 25323 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 M1vUAsYxf@iKO*8?pa1{> literal 0 HcmV?d00001 diff --git a/Student/osiddiquee/lesson07/activity.py b/Student/osiddiquee/lesson07/activity.py new file mode 100644 index 0000000..cc4025e --- /dev/null +++ b/Student/osiddiquee/lesson07/activity.py @@ -0,0 +1,59 @@ +""" + Simple database examle with Peewee ORM, sqlite and Python + Here we define the schema + +""" + +from peewee import * + +database = SqliteDatabase('personjob.db') +database.connect() +database.execute_sql('PRAGMA foreign_keys = ON;') + +class BaseModel(Model): + class Meta: + database = database + + +class Person(BaseModel): + """ + This class defines Person, which maintains details of someone + for whom we want to research career to date. + """ + person_name = CharField(primary_key = True, max_length = 30) + lives_in_town = CharField(max_length = 40) + nickname = CharField(max_length = 20, null = True) + +class Job(BaseModel): + """ + This class defines Job, which maintains details of past Jobs + held by a Person. + """ + job_name = CharField(primary_key = True, max_length = 30) + start_date = DateField(formats = 'YYYY-MM-DD') + end_date = DateField(formats = 'YYYY-MM-DD') + duration = IntegerField() + salary = DecimalField(max_digits = 7, decimal_places = 2) + person_employed = ForeignKeyField(Person, related_name ='was_filled_by', null = False) + department_number = ForeignKeyField(Department, related_name = 'department_id', null = False) + + +class Department(BaseModel): + ''' + This class defines Department. Multiple jobs in dept + ''' + department_number = CharField(primary_key = True, max_length = 4) + department_name = CharField(max_lenth = 30) + department_manager = CharField(max_lenth = 30) + + +class PersonNumKey(BaseModel): + """ + This class defines Person, which maintains details of someone + for whom we want to research career to date. + + *** I am implemented with a numeric PK that is generated by the system *** + """ + person_name = CharField(max_length = 30) + lives_in_town = CharField(max_length = 40) + nickname = CharField(max_length = 20, null = True) diff --git a/Student/osiddiquee/lesson07/activity2.py b/Student/osiddiquee/lesson07/activity2.py new file mode 100644 index 0000000..02542ca --- /dev/null +++ b/Student/osiddiquee/lesson07/activity2.py @@ -0,0 +1,70 @@ +""" + Learning persistence with Peewee and sqlite + delete the database to start over + (but running this program does not require it) +""" + +from personjob_model import * + +import logging + +def populate_db(): + """ + this is a proof of concept because i cant download peewee with only add department data to database + """ + + logging.basicConfig(level=logging.INFO) + logger = logging.getLogger(__name__) + + database = SqliteDatabase('personjob.db') + + logger.info('Working with Job class') + logger.info('Creating Job records: just like Person. We use the foreign key') + + DEPARTMENT_NUMBER = 0 + DEPARTMENT_NAME = 1 + DEPARTMENT_MANAGER = 2 + + departments = [ + ('PM01', 'Product Management', 'Mario Mario'), + ('MK01', 'Marketing', 'Luigi Mario'), + ('FN01', 'Finance', 'King Koopa') + ] + + try: + database.connect() + database.execute_sql('PRAGMA foreign_keys = ON;') + for department in departments: + with database.transaction(): + new_department = Department.create( + department_number = departments[DEPARTMENT_NUMBER], + department_name = departments[DEPARTMENT_NAME], + department_manager = departments[DEPARTMENT_MANAGER] + new_department.save() + + logger.info('Reading and print all Department rows (note the value of person)...') + for department in departments: + logger.info(f'{department.department_number} : {department.department_name}') + + except Exception as e: + logger.info(f'Error creating = {department[department_number]}') + logger.info(e) + + finally: + logger.info('database closes') + database.close() + +print_db(): +''' +in theory this function should work, but it would be 50 times easier in sql +''' +departments = (Person + .select(Person, Job) + .join(Job)) +for tweet in tweets: + print(department.person_name, department.job.job_name, department.job.department_id) + + +if __name__ == '__main__': + populate_db() + print_db() From 00a5a9091a34d04e8d2b04add69b2569ee3c34dc Mon Sep 17 00:00:00 2001 From: Siddiquee Date: Fri, 29 Jun 2018 14:24:21 -0700 Subject: [PATCH 08/10] lesson07 assignmetnt: untested because unable to install peewee, also zoom recording is missing from canvas --- Student/osiddiquee/lesson07/mailroom_db.py | 82 ++++ Student/osiddiquee/lesson07/mailroom_model.py | 37 ++ .../lesson07/mailroom_relational.py | 461 ++++++++++++++++++ 3 files changed, 580 insertions(+) create mode 100644 Student/osiddiquee/lesson07/mailroom_db.py create mode 100644 Student/osiddiquee/lesson07/mailroom_model.py create mode 100644 Student/osiddiquee/lesson07/mailroom_relational.py diff --git a/Student/osiddiquee/lesson07/mailroom_db.py b/Student/osiddiquee/lesson07/mailroom_db.py new file mode 100644 index 0000000..7d20096 --- /dev/null +++ b/Student/osiddiquee/lesson07/mailroom_db.py @@ -0,0 +1,82 @@ +from personjob_model import * + +import logging + +def populate_db(): + + logging.basicConfig(level=logging.INFO) + logger = logging.getLogger(__name__) + + database = SqliteDatabase('mailroom.db') + + ''' + creating donor stuff + ''' + + logger.info('Creating donor records') + + DONOR_ID = 0 + DONOR_NAME = 1 + + donors = [ + (1, 'Walter White'), + (2, 'Jesse Pinkman'), + (3, 'Gustavo Fring') + ] + + try: + database.connect() + database.execute_sql('PRAGMA foreign_keys = ON;') + for donor in donors: + with database.transaction(): + new_donor = Donor.create( + donor_id = donors[DONOR_ID], + donor_name = donors[DONOR_NAME]) + new_donor.save() + + except Exception as e: + logger.info(f'Error creating = {donors[DONOR_NAME]}') + logger.info(e) + + finally: + logger.info('database closes') + database.close() + + ''' + creating donation stuff + ''' + + logger.info('Creating donation records') + + DONATION_ID = 0 + DONATION_AMOUNT = 1 + DONOR_ID = 2 + + donations = [ + (1, 5000.00, 1), + (2, 6000.00, 1), + (3, 4000.00, 2), + (4, 4500.00, 3) + ] + + try: + database.connect() + database.execute_sql('PRAGMA foreign_keys = ON;') + for donation in donations: + with database.transaction(): + new_donation = Donation.create( + donation_id = donations[DONATION_ID], + donation_amount = donations[DONATION_AMOUNT], + donor_id = donations[DONOR_ID]) + new_donation.save() + + except Exception as e: + logger.info(f'Error creating = {donations[DONATION_ID]}') + logger.info(e) + + finally: + logger.info('database closes') + database.close() + +if __name__ == '__main__': + populate_db() diff --git a/Student/osiddiquee/lesson07/mailroom_model.py b/Student/osiddiquee/lesson07/mailroom_model.py new file mode 100644 index 0000000..79fd6db --- /dev/null +++ b/Student/osiddiquee/lesson07/mailroom_model.py @@ -0,0 +1,37 @@ +""" + Donor database + Here we define the schema + +""" + +from peewee import * + +database = SqliteDatabase('mailroom.db') +database.connect() +database.execute_sql('PRAGMA foreign_keys = ON;') + + + +class BaseModel(Model): + class Meta: + database = database + + +class Donor(BaseModel): + """ + This class defines Person, which maintains details of someone + for whom we want to research career to date. + """ + donor_id = IntegerField(primary_key = True) + donor_name = CharField(max_length = 30) + +class Donation(BaseModel): + """ + This class defines Job, which maintains details of past Jobs + held by a Person. + """ + donation_id = IntegerField(primary_key = True) + donation_amount = DecimalField(max_digits = 9, decimal_places = 2) + donor_id = ForeignKeyField(Donor, related_name = 'donor_id', null = False) + +database.create_tables([Donor, Donation], safe = True) diff --git a/Student/osiddiquee/lesson07/mailroom_relational.py b/Student/osiddiquee/lesson07/mailroom_relational.py new file mode 100644 index 0000000..5631d91 --- /dev/null +++ b/Student/osiddiquee/lesson07/mailroom_relational.py @@ -0,0 +1,461 @@ +#!/usr/bin/env python +""" +This is an object oriented version +""" + +import sys +import math +from textwrap import dedent +from mailroom_model import * + + +# Utility so we have data to test with, etc. +# def get_sample_data(): +# """ +# returns a list of donor objects to use as sample data +# +# +# """ +# +# +# return [Donor("William Gates III", [653772.32, 12.17]), +# Donor("Jeff Bezos", [877.33]), +# Donor("Paul Allen", [663.23, 43.87, 1.32]), +# Donor("Mark Zuckerberg", [1663.23, 4300.87, 10432.0]), +# ] + + + + + +class Donor(): + """ + class to hold the information about a single donor + """ + + def __init__(self, name, donations=None): + """ + create a new Donor object + + :param name: the full name of the donor + + :param donations=None: iterable of past donations + """ + + self.norm_name = self.normalize_name(name) + self.name = name.strip() + if donations is None: + self.donations = [] + else: + self.donations = list(donations) + + @staticmethod + def normalize_name(name): + """ + return a normalized version of a name to use as a comparison key + + simple enough to not be in a method now, but maybe you'd want to make it fancier later. + """ + return name.lower().strip().replace(" ", "") + + @property + def last_donation(self): + """ + The most recent donation made + """ + try: + return self.donations[-1] + except IndexError: + return None + + @property + def total_donations(self): + return sum(self.donations) + + @property + def average_donation(self): + return self.total_donations / len(self.donations) + + def add_donation(self, amount): + """ + add a new donation + """ + amount = float(amount) + if amount <= 0.0: + raise ValueError("Donation must be greater than zero") + self.donations.append(amount) + + +class DonorDB(): + """ + encapsulation of the entire database of donors and data associated with them. + """ + + def __init__(self, donors=None): + """ + Initialize a new donor database + + :param donors=None: iterable of Donor objects + """ + if donors is None: + self.donor_data = {} + else: + self.donor_data = {d.norm_name: d for d in donors} + + # def save_to_file(self, filename): + # with open(filename, 'w') as outfile: + # self.to_json(outfile) + + # @classmethod + # def load_from_file(cls, filename): + # with open(filename, 'r') as infile: + # obj = js.from_json(infile) + # return obj + + @property + def donors(self): + """ + an iterable of all the donors + """ + return self.donor_data.values() + + def list_donors(self): + """ + creates a list of the donors as a string, so they can be printed + + Not calling print from here makes it more flexible and easier to + test + """ + listing = ["Donor list:"] + for donor in self.donors: + listing.append(donor.name) + return "\n".join(listing) + + def find_donor(self, name): + """ + find a donor in the donor db + + :param: the name of the donor + + :returns: The donor data structure -- None if not in the self.donor_data + """ + return self.donor_data.get(Donor.normalize_name(name)) + + def add_donor(self, name): + """ + Add a new donor to the donor db + + :param: the name of the donor + + :returns: the new Donor data structure + """ + donor = Donor(name) + self.donor_data[donor.norm_name] = donor + return donor + + def gen_letter(self, donor): + """ + Generate a thank you letter for the donor + + :param: donor tuple + + :returns: string with letter + + note: This doesn't actually write to a file -- that's a separate + function. This makes it more flexible and easier to test. + """ + return dedent('''Dear {0:s}, + + Thank you for your very kind donation of ${1:.2f}. + It will be put to very good use. + + Sincerely, + -The Team + '''.format(donor.name, donor.last_donation) + ) + + @staticmethod + def sort_key(item): + # used to sort on name in self.donor_data + return item[1] + + def generate_donor_report(self): + """ + Generate the report of the donors and amounts donated. + + :returns: the donor report as a string. + """ + # First, reduce the raw data into a summary list view + report_rows = [] + for donor in self.donor_data.values(): + name = donor.name + gifts = donor.donations + total_gifts = donor.total_donations + num_gifts = len(gifts) + avg_gift = donor.average_donation + report_rows.append((name, total_gifts, num_gifts, avg_gift)) + + # sort the report data + report_rows.sort(key=self.sort_key) + report = [] + report.append("{:25s} | {:11s} | {:9s} | {:12s}".format("Donor Name", + "Total Given", + "Num Gifts", + "Average Gift")) + report.append("-" * 66) + for row in report_rows: + report.append("{:25s} ${:10.2f} {:9d} ${:11.2f}".format(*row)) + return "\n".join(report) + + def save_letters_to_disk(self): + """ + make a letter for each donor, and save it to disk. + """ + for donor in self.donor_data.values(): + print("Writing a letter to:", donor.name) + letter = self.gen_letter(donor) + # I don't like spaces in filenames... + filename = donor.name.replace(" ", "_") + ".txt" + open(filename, 'w').write(letter) + + +# User-interaction code +# Above this is all the logic code +# The stuff you'd need if you had a totally different UI.different +# below is code only for the command line interface. + + +# import sys +# import math + +# # handy utility to make pretty printing easier +# from textwrap import dedent + +# from mailroom import model + +# create a DB with the sample data + +def main_menu_selection(): + """ + Print out the main application menu and then read the user input. + """ + action = input(dedent(''' + Choose an action: + + 1 - Send a Thank You + 2 - Create a Report + 3 - Send letters to everyone + 4 - Quit + + > ''')) + return action.strip() + + +def send_thank_you(): + """ + Record a donation and generate a thank you message. + """ + # Read a valid donor to send a thank you from, handling special commands to + # let the user navigate as defined. + while True: + name = input("Enter a donor's name" + "(or 'list' to see all donors or 'menu' to exit)> ").strip() + if name == "list": + print(db.list_donors()) + elif name == "menu": + return + else: + break + + # Now prompt the user for a donation amount to apply. Since this is + # also an exit point to the main menu, we want to make sure this is + # done before mutating the db. + while True: + amount_str = input("Enter a donation amount (or 'menu' to exit)> ").strip() + if amount_str == "menu": + return + # Make sure amount is a valid amount before leaving the input loop + try: + amount = float(amount_str) + # extra check here -- unlikely that someone will type "NaN", but + # it IS possible, and it is a valid floating point number: + # http://en.wikipedia.org/wiki/NaN + if math.isnan(amount) or math.isinf(amount) or round(amount, 2) == 0.00: + raise ValueError + # in this case, the ValueError could be raised by the float() call, or by the NaN-check + except ValueError: + print("error: donation amount is invalid\n") + else: + break + + # If this is a new user, ensure that the database has the necessary + # data structure. + donor = db.find_donor(name) + if donor is None: + donor = db.add_donor(name) + + # Record the donation + donor.add_donation(amount) + print(db.gen_letter(donor)) + + +def print_donor_report(): + print(db.generate_donor_report()) + +def quit(): + sys.exit(0) + + +''' +I realize that this next piece of code is impractical. +This is because im basically storing the db in memory which is inefficent +However, it's not like peewee doesn't have it's own reasons for being impractical +Finally, given that this code will be refactored 4 times and given that +I 100% know this database won't ever be large, I have taken an approach to +simplify it's refactoring hurdles +''' + +#transforms the db to a db_to_dict +def db_to_dict(): + database = SqliteDatabase('mailroom.db') + + database.connect() + database.execute_sql('PRAGMA foreign_keys = ON;') + + query = (Donor + .select(Donor.donor_name, Donation.donation_amount) + .join(Donation)) + + donor_dict = {} + for donor, donation in query: + if donor in donor_dict: + donor_dict[donor].append(donation) + else: + donor_dict[donor] = [donation] + + data = [] + for donor, donations in donor_dict: + if not data: + data = [Donor(donor, donations)] + else: + data.append(Donor(donor, donations)) + + return data + + +def dict_to_db(donors_database): + database = SqliteDatabase('mailroom.db') + + database.connect() + database.execute_sql('PRAGMA foreign_keys = ON;') + + ''' + creating donor stuff + ''' + + logger.info('Creating donor records') + + DONOR_ID = 0 + DONOR_NAME = 1 + + donors = [] + donor_id_lookup = {} + for id, donor in enumerate(donors_database): + if not donors: + [(id, donor.name)] + else: + donor.append((id, donor.name)) + donor_id_lookup[donor.name] = id + + + # donors = [ + # (1, 'Walter White'), + # (2, 'Jesse Pinkman'), + # (3, 'Gustavo Fring') + # ] + + try: + database.connect() + database.execute_sql('PRAGMA foreign_keys = ON;') + for donor in donors: + with database.transaction(): + new_donor = Donor.create( + donor_id = donors[DONOR_ID], + donor_name = donors[DONOR_NAME]) + new_donor.save() + + except Exception as e: + logger.info(f'Error creating = {donors[DONOR_NAME]}') + logger.info(e) + + finally: + logger.info('database closes') + database.close() + + ''' + creating donation stuff + ''' + + logger.info('Creating donation records') + + DONATION_ID = 0 + DONATION_AMOUNT = 1 + DONOR_ID = 2 + + donations = [] + for id, donor in enumerate(donors_database) + for name, donation in donor.name, donor.donations + if not donations: + donations = [(id, donation, donor_id_lookup[name])] + else: + donations.append((id, donation, donor_id_lookup[name])) + + # donations = [ + # (1, 5000.00, 1), + # (2, 6000.00, 1), + # (3, 4000.00, 2), + # (4, 4500.00, 3) + # ] + + try: + database.connect() + database.execute_sql('PRAGMA foreign_keys = ON;') + for donation in donations: + with database.transaction(): + new_donation = Donation.create( + donation_id = donations[DONATION_ID], + donation_amount = donations[DONATION_AMOUNT], + donor_id = donations[DONOR_ID]) + new_donation.save() + + except Exception as e: + logger.info(f'Error creating = {donations[DONATION_ID]}') + logger.info(e) + + finally: + logger.info('database closes') + database.close() + + + +def main(): + #this is the same as the db but provides a starting point + db = db_to_dict() + + selection_dict = {"1": send_thank_you, + "2": print_donor_report, + "3": db.save_letters_to_disk, + "4": quit} + + while True: + selection = main_menu_selection() + try: + selection_dict[selection]() + except KeyError: + print("error: menu selection is invalid!") + + dict_to_db(db) + +if __name__ == "__main__": + + main() From 291b749c441f1f8889050c49707b214689197691 Mon Sep 17 00:00:00 2001 From: Siddiquee Date: Sun, 1 Jul 2018 13:13:39 -0700 Subject: [PATCH 09/10] lesson10 activity --- Student/osiddiquee/lesson09/profiler.py | 100 ++++++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 Student/osiddiquee/lesson09/profiler.py diff --git a/Student/osiddiquee/lesson09/profiler.py b/Student/osiddiquee/lesson09/profiler.py new file mode 100644 index 0000000..8273522 --- /dev/null +++ b/Student/osiddiquee/lesson09/profiler.py @@ -0,0 +1,100 @@ + +import time + +################################################### +# # +# Decorators for profiling are located below # +# # +################################################### + +# timer decorator to test sequency functions +def timer(timed_sequence): + def timed(index): + start = time.time() + result = timed_sequence(index) + end = time.time() + print(end - start) + return result + return timed + +class Cache_sequence(): + def __init__(self, sequence_function): + self.sequence_function = sequence_function + self.cache = {} + + def __call__(self, index): + if not index in self.cache: + self.cache[index] = self.sequence_function(index) + return self.cache[index] + +############################################################### +# # +# This is the definition of an arbitrary number function # +# In this case, it is fibonacii # +# # +############################################################### + + +#uncached version for the closure +#@timer +def F(n): + if n == 0: return 0 + elif n == 1: return 1 + else: return F(n-1)+F(n-2) + + +#@timer +@Cache_sequence +def Fib(n): + if n == 0: return 0 + elif n == 1: return 1 + else: return F(n-1)+F(n-2) + +############################## +# # +# Executing functions # +# # +############################## + +@timer +def run_functions(function): + function(25) + + +print('Cached sequence function...') +run_functions(Fib) + +print('Uncached sequence function...') +run_functions(F) + +s = time.time() +Fib(50) +e = time.time() +print(e - s) + +s = time.time() +F(50) +e = time.time() +print(e - s) + + + + + + + + +''' + Failed attempt to make a closure to cached +''' +# making a cache with a closure +# @timer +# def number_sequence_cache(sequence_function): +# cached = {} +# def cache(index): +# if index not in cache: +# cached[index] = sequence_function(index) +# return cache[index] +# else: +# return cache[index] +# return cache From 6c6d38c927849035b37823308e01f19ee0faefca Mon Sep 17 00:00:00 2001 From: Siddiquee Date: Sun, 1 Jul 2018 16:16:34 -0700 Subject: [PATCH 10/10] lesson09 commits --- Student/osiddiquee/lesson09/Acitivity09.py | 103 +++++++++++++++++ Student/osiddiquee/lesson09/newapi.py | 126 +++++++++++++++++++++ 2 files changed, 229 insertions(+) create mode 100644 Student/osiddiquee/lesson09/Acitivity09.py create mode 100644 Student/osiddiquee/lesson09/newapi.py diff --git a/Student/osiddiquee/lesson09/Acitivity09.py b/Student/osiddiquee/lesson09/Acitivity09.py new file mode 100644 index 0000000..2a105c2 --- /dev/null +++ b/Student/osiddiquee/lesson09/Acitivity09.py @@ -0,0 +1,103 @@ +''' + Notes for concurrency and async +''' + + +import sys +import threading +import time +from Queue import Queue + +''' + This is an intengration function +def f(x): +''' + return x**2 + +def integrate(f, a, b, N): + s = 0 + dx = (b-a)/N + for i in xrange(N): + s += f(a+i*dx) + return s * dx + +''' + This starts threading +''' +def func(): + for i in xrange(5): + print("hello from thread %s" % threading.current_thread().name) + time.sleep(1) + +threads = [] +for i in xrange(3): + thread = threading.Thread(target=func, args=()) + thread.start() + threads.append(thread) + +''' + This is an example of subclassing +''' +class MyThread(threading.Thread): + + def run(self): + print("hello from %s" % threading.current_thread().name) + +thread = MyThread() +thread.start() + +''' + Mutex locks (threading.Lock) + + Probably most common + Only one thread can modify shared data at any given time + Thread determines when unlocked + Must put lock/unlock around critical code in ALL threads + Difficult to manage + + Easiest with context manager: +''' +x = 0 +x_lock = threading.Lock() + +# Example critical section +with x_lock: + # statements using x + + +''' + Locking threads +''' +lock = threading.Lock() + +def f(): + lock.acquire() + print("%s got lock" % threading.current_thread().name) + time.sleep(1) + lock.release() + +threading.Thread(target=f).start() +threading.Thread(target=f).start() +threading.Thread(target=f).start() + +''' + This is a nonblocking locks +''' +lock = threading.Lock() +lock.acquire() +if not lock.acquire(False): + print("couldn't get lock") +lock.release() +if lock.acquire(False): + print("got lock") + +''' + Queueing +''' + +#from Queue import Queue +q = Queue(maxsize=10) +q.put(37337) +block = True +timeout = 2 +print(q.get(block, timeout)) diff --git a/Student/osiddiquee/lesson09/newapi.py b/Student/osiddiquee/lesson09/newapi.py new file mode 100644 index 0000000..f20df65 --- /dev/null +++ b/Student/osiddiquee/lesson09/newapi.py @@ -0,0 +1,126 @@ +import requests +import threading +from math import ceil +import time + +############################# +## Assignment ## +############################# +# If you are struggling with the assignment from +# lesson 9, then modify this script to run. +# I have purposefully disabled the threading code +# to not work (silently... no error will occur). + + +################################### +## Other Suggested modifications ## +################################### +# - Use recursive function for API pagination +# - Persist / store data collected from API +# - Use generator for API pagination +# - Trying multiprocessing and queue packages +# - Rewrite the whole script + + +########################### +## Persist or store data ## +########################### +# import sqlite3 +# import json +# import pymongo + + +################################# +## Get initial result metadata ## +################################# +api_key = "d841cf2b36404cc98115f9328bfdfaec" +keywords= ",".join(["Nintendo","Sony", "Microsoft"]) +page = 1 +url = (f'https://newsapi.org/v2/everything?' + 'q={keywords}&' + 'from=2018-06-21&' + 'sortBy=popularity&' + 'apiKey={api_key}&' + 'page={page}') + +url = url.format(keywords=keywords, page=page, api_key=api_key) +print("API URL:", url) + +response = requests.get(url) +totalResults = response.json()["totalResults"] +resultsPerPage = len(response.json()["articles"]) # defaults to 20 +numPages = ceil(totalResults/resultsPerPage) + + +################################ +## Define `get_page` function ## +## w/ `page` parameter ## +################################ +def get_page(page): + url = (f'https://newsapi.org/v2/everything?' + 'q={keywords}&' + 'from=2018-06-21&' + 'sortBy=popularity&' + 'apiKey={api_key}&' + 'page={page}') + + url = url.format(keywords=keywords, page=page, api_key=api_key) + + r = requests.get(url) + if r.status_code == 200: + raw = r.json() + articles = raw["articles"] + ############################################## + ## `articles` is essentially your data.... ## + ## do something with it here ## + ############################################## + with open('search_data.csv', 'a') as search_csv: + for article in articles: + #article_row = {} + # article_row['title'] = article['title'] + # article_row['author'] = article['author'] + # article_row['url'] = article['url'] + + title = article['title'] + author = article['author'] + link = article['url'] + + # i want a perfectly clean dataset + # i doubt i would lose much data because of multithreading... + # allowing me to get a lot efficiently + if not (title or author or link): + search_csv.write(f'''{article['title'].encode('utf8')}, + {article['author'].encode('utf8')}, + {article['url'].encode('utf8')}''') + else: + pass + + else: + print("API Error - Status Code", r.status_code) + + +############################################## +## This here is the single threaded version ## +############################################## +start = time.time() +for i in range(numPages): + get_page(i+1) +end = time.time() +print("single threaded time: %.3f seconds", end - start) + + + +############################################# +## This here is the multi-threaded version ## +############################################# +start = time.time() +threads = [] +for i in range(numPages): + thread = threading.Thread(target = get_page, args = ((i+1,))) + thread.start() + threads.append(thread) + +for thread in threads: + thread.join() +end = time.time() +print("multi-threaded time: %.3f seconds", end - start)