示例#1
0
    def close_connection(self, del_cursors: bool = False) -> None:
        """ Method to close connection to this database.

        Parameters:
            del_cursors (bool): if True, first delete dependent cursors,
                                if False, refuse to close connection.
        Returns:
        """
        if del_cursors:
            # TODO need to test with multiple DBClients.
            for caller, cursor in self.callers.items():
                self.delete_cursor(caller)
                del caller
        else:
            print('Dependent DBClients exist, will not close connection.')

        if self.db_type in c.FILE_DATABASES:
            z = '\n{} from database at "{}".'
            z = z.format('{}', self.db_path)
        else:
            z = '\n{} from instance "{}" on host "{}".'
            z = z.format('{}', self.instance, self.hostname)
        try:
            self.connection.close()
            self.connection = None
            print(z.format('Successfully disconnected'))
        except self.db_lib_obj.Error:
            print_stacktrace()
            print(z.format('Failed to disconnect'))
            # Nothing to clean up.
            exit(1)
        return
    def __init__(self,
                 out_file_name: str = '',
                 align_col: bool = True,
                 col_sep: str = ',') -> None:
        """ Constructor method for this class.

        Parameters:
            out_file_name (str): relative or absolute path to output file, or '' for standard output.
            align_col (bool): if True, pad columns with spaces in the output so they always have the same width.
            col_sep (str): character(s) to separate columns in output.  Common choices:
                "" (no characters)
                " " (single space, aka chr(32))
                chr(9) (aka the horizontal tab character)
                "|"
                ","
        Returns:
        """
        out_file = None
        if out_file_name == '':
            out_file = sys.stdout
        else:
            try:
                out_file = open(out_file_name, 'w')
            except OSError:
                print_stacktrace()
                # Can envision situations where exiting might be excessive.
                exit(1)

        self.out_file_name: str = out_file_name
        self.out_file = out_file
        self.align_col: bool = align_col
        self.col_sep: str = col_sep
        return
    def get_out_file_name(self) -> None:
        """ Prompt for relative or absolute path to output file.

        Parameters:
        Returns:
        """
        prompt = ('\nEnter the name and relative or absolute'
                  '\nlocation of the file to write output to.'
                  '\nOr hit Return to print to the standard output:\n')
        # Keep looping until able to open output file, or "" entered.
        while True:
            out_file_name = input(prompt).strip()
            if out_file_name == '':
                out_file = sys.stdout
                break
            else:
                dir_name = dirname(out_file_name)
                if isdir(dir_name):
                    try:
                        out_file = open(out_file_name, 'w')
                        break
                    except OSError:
                        print_stacktrace()
                else:
                    print('Invalid directory specified: ' + dir_name)

        self.out_file = out_file
        self.out_file_name = out_file_name
        if self.out_file_name == '':
            print('You chose to write to the standard output.')
        else:
            print('Your output file is "{}".'.format(self.out_file_name))
        return
示例#4
0
    def __init__(self,
                 os: str,
                 db_type: str,
                 db_path: str,
                 username: str,
                 password: str,
                 hostname: str,
                 port_num: int,
                 instance: str) -> None:
        """ Constructor method for this class.

        Parameters:
            os (str): the OS on which this program is executing.
            db_type (str): the type of database (Oracle, SQL Server, etc).
            db_path (str): the path of the database file, for SQLite and Access.
            username (str): the username for the connection to this database.
            password (str): the password for "username".
            hostname (str): the hostname of this database.
            port_num (int): the port this database listens on.
            instance (str): the name of this database instance.
        Returns:
        """
        # Save arguments of __init__.
        self.os: str = os
        self.db_type: str = db_type
        self.db_path: str = db_path
        self.username: str = username
        self.password: str = password
        self.hostname: str = hostname
        self.port_num: int = port_num
        self.instance: str = instance

        # Check if db_type valid.
        if self.db_type not in c.DB_TYPES:
            print('Invalid database type "{}".'.format(self.db_type))
            # Nothing to clean up.
            exit(1)

        # Import appropriate database library.
        self.db_lib_name = c.LIB_NAME_FOR_DB[self.db_type]
        self.db_lib_obj = __import__(self.db_lib_name)

        # Appropriate database client executable.
        self.db_client_exe = c.DB_CLIENT_EXES[self.db_type]

        # Get database library version.
        if self.db_lib_name in {c.PSYCOPG2, c.PYMYSQL}:
            self.db_lib_version = self.db_lib_obj.__version__
        else:
            self.db_lib_version = self.db_lib_obj.version
        z = 'Using {} library version {}.'
        print(z.format(self.db_lib_name, self.db_lib_version))

        # Get the library's primary parameter style.
        print('Parameter style "{}".'.format(self.db_lib_obj.paramstyle))
        # paramstyle = 'named': cx_Oracle.  Option for sqlite3 & psycopg2.
        # paramstyle = 'qmark': sqlite3 and pyodbc.
        # paramstyle = 'pyformat': pymysql and psycopg2.

        # Get the parameter style we're using.
        self.paramstyle = c.PARAMSTYLE_FOR_LIB[self.db_lib_name]
        if self.db_type == ACCESS:
            self.paramstyle = c.NOBINDVARS

        # Initialize bind_vars.
        if self.paramstyle in {c.NAMED, c.PYFORMAT}:
            self.bind_vars = dict()
        elif self.paramstyle == c.QMARK:
            self.bind_vars = tuple()
        else:
            self.bind_vars = None

        # Connect to database instance.
        self.connection = None
        try:
            if db_type in c.USES_CONNECTION_STRING:
                z = self.get_db_connection_string()
                self.connection = self.db_lib_obj.connect(z)
            else:
                args = (self.hostname, self.username, self.password,
                        self.instance, self.port_num)
                self.connection = self.db_lib_obj.connect(*args)
            print('Successfully connected to database.')
        except self.db_lib_obj.Error:
            print_stacktrace()
            print('Failed to connect to database.')
            # Nothing to clean up.
            exit(1)

        # Get database software version.
        self.db_software_version = self.get_db_software_version()

        # Prepare to save cursors for this connection.
        self.callers = dict()

        return
示例#5
0
def main():
    """ Function main.  Watch for new online jobs on Wyzant.com.

    Parameters:
    Returns:
    """
    do_beep = f.get_boolean("Beep when new job found? ")
    do_email = f.get_boolean("Email when new job found? ")
    do_log = f.get_boolean("Save activity to log? ")
    print_jobs = f.get_boolean("Print jobs? ")
    while True:
        num_pages = input("Number of job pages to read (1 or 2)? ")
        if num_pages in {"1", "2"}:
            num_pages = int(num_pages)
            break

    # On Exception, come back to here and re-initialize everything.
    while True:
        try:
            # Jobs dicts, stores info about job listings.
            jobs = Jobs()
            jobs_prev = Jobs()

            # Check the version of chromedriver.exe, and update when needed.
            # Check_Chromedriver.driver_mother_path = r"C:\Program Files\WebDrivers"
            # Check_Chromedriver.main()

            # stdout.write("Done checking the version of chromedriver.exe.\n\n")

            # Open output file for appending.
            if do_log:
                outfile = open('log.txt', 'a')
            else:
                outfile = None

            stdout.write("Initializing Selenium.\n")
            my_selenium = MySelenium()
            stdout.write("Done initializing Selenium.\n")
            stdout.write("Logging into Wyzant.\n")
            my_selenium.website_login(c.USERNAME, c.PASSWORD, c.LOGIN_PAGE_URL,
                                      c.PRE_LOGIN_PAGE_TITLE,
                                      c.POST_LOGIN_PAGE_TITLE,
                                      c.USERNAME_FIELD_XPATH,
                                      c.PASSWORD_FIELD_XPATH,
                                      c.LOGIN_BUTTON_XPATH)
            stdout.write("Done logging into Wyzant.\n")
            stdout.write("Going to the Wyzant job listings page.\n")
            my_selenium.go_to_web_page(c.JOBS_PAGE_URL, By.CLASS_NAME,
                                       c.UI_PAGE_LINK)
            stdout.write("At Wyzant job listings page.\n")

            xpath = "//label[@for='lesson_type_online']"
            my_selenium.click_sleep_wait(xpath, c.SLEEP_TIME, By.CLASS_NAME,
                                         c.UI_PAGE_LINK)
            stdout.write("Fetched Wyzant jobs list.\n")

            # Loop forever.
            while True:
                my_selenium.force_refresh(By.CLASS_NAME, c.UI_PAGE_LINK)

                # Save jobs into jobs_prev.  Skip if job_ids empty due to faulty page load.
                if jobs.count_jobs() > 0:
                    jobs_prev = deepcopy(jobs)
                    jobs.reset()

                # Print and write to log file the current datetime.
                date_time = datetime.now().strftime("%Y/%m/%d %H:%M:%S")
                stdout.write(date_time + "  ")
                if do_log:
                    outfile.write(date_time + "\n")
                    outfile.flush()

                for page_num in range(1, num_pages + 1):
                    if num_pages == 2:
                        # Click back and forth between pages 1 and page 2.
                        xpath = f'//div[@role="navigation"]/a[text()="{page_num}"]'
                        pages = my_selenium.find_elements_by_xpath(xpath)
                        # print("selecting: " + pages[0].text)
                        pages[0].click()
                    # Each instance of class "academy-card" contains 1 job, 10 visible per page.
                    academy_cards = my_selenium.get_all_related_by_class(
                        "academy-card")

                    for card_num, card_obj in enumerate(academy_cards):
                        # Get Job listing URL.
                        job_url = card_obj.find_element_by_xpath(
                            './h3/a').get_attribute('href')
                        card_num_display = 10 * (page_num - 1) + card_num

                        # Save job properties.
                        params = dict()
                        params[c.JOB_ID] = int(job_url.split("/")[-1].strip())
                        params[c.CARD_NUMBER] = card_num_display
                        job_age_info = card_obj.find_element_by_xpath(
                            './div[1]/span[1]').text.strip()
                        if job_age_info == "No applications yet":
                            params[c.APPLICATIONS] = "N"
                            job_age_info = card_obj.find_element_by_xpath(
                                './div[1]/span[2]').text.strip()
                        else:
                            params[c.APPLICATIONS] = "Y"
                        params[c.JOB_AGE] = f.age_to_minutes(job_age_info)
                        params[
                            c.STUDENT_NAME] = card_obj.find_element_by_xpath(
                                './p[1]').text.strip()
                        params[c.JOB_TOPIC] = card_obj.find_element_by_xpath(
                            './h3/a').text.strip()
                        pay_rate = card_obj.find_element_by_xpath(
                            './div[3]/span/div/div[1]/span').text.strip()
                        params[c.PAY_RATE] = pay_rate.replace(
                            "Recommended rate: ", "")
                        params[
                            c.
                            JOB_DESCRIPTION] = card_obj.find_element_by_xpath(
                                './p[2]').text.strip()

                        # Does "Show Details" control exist?
                        show_details = card_obj.find_elements_by_xpath(
                            './div[4]/div/div/p')
                        if len(show_details) == 1:
                            # If "Show Details" exists, click it.
                            show_details[0].click()

                            # Each instance of class "spc_zero" contains one job attribute.
                            spc_zeros = card_obj.find_elements_by_class_name(
                                "spc-zero")

                            # Iterate over all job attributes in class "spc_zero".
                            for spc_zero in spc_zeros:
                                # There are 1-2 children of class "spc_zero".
                                children = spc_zero.find_elements_by_xpath(
                                    './child::*')
                                if len(children) == 2:
                                    # Job attribute in 2nd child of class "spc_zero".
                                    value = spc_zero.find_element_by_xpath(
                                        './span[2]').text.strip()
                                else:
                                    # Sometimes the job availability attribute isn't the 2nd child of class "spc_zero".
                                    xpath = './../p[@class="text-semibold spc-tiny"]'
                                    items = spc_zero.find_elements_by_xpath(
                                        xpath)
                                    value = "; ".join(
                                        [item.text for item in items]).strip()

                                # Job attribute in 1st child of class "spc_zero".
                                my_key = spc_zero.find_element_by_xpath(
                                    './span[1]').text
                                my_key = my_key.replace(":", "").strip()
                                params[my_key] = value
                            # Done iterating over all job attributes in class "spc_zero".

                        # Save job properties in new instance of class Jobs.
                        jobs.add_job(**params)

                        # Print progress, on just one line.
                        if card_num_display == 0:
                            stdout.write(
                                f"Done fetching job {card_num_display}")
                        else:
                            stdout.write(f", {card_num_display}")
                    # Done iterating over academy_cards.
                # Done iterating over pages.

                # After stdout.write, need to add newline.
                stdout.write("\n")

                # Look for new jobs.
                # Get job IDs in job_ids and not in job_ids_prev.
                current_num = jobs.count_jobs()
                previous_num = jobs_prev.count_jobs()
                # Skip if job_ids or job_ids_prev has too few entries (1st loop or faulty page load).
                if current_num <= 10 * (num_pages - 1):
                    stdout.write(f"Current  # Job IDs: {current_num}.\n")
                elif previous_num <= 10 * (num_pages - 1):
                    stdout.write(f"Previous # Job IDs: {previous_num}.\n")
                else:
                    job_ids_previous = jobs_prev.get_job_ids()
                    new_job_ids = jobs.get_new_job_ids(job_ids_previous)

                    # Iterate over all new job listings.
                    for job_id in new_job_ids:
                        # Collect job data.
                        email_subject = f"New job at www.wyzant.com/tutor/jobs/{job_id}"
                        job_summary, job_data, age = jobs.get_job_data(
                            email_subject + "\n", job_id)

                        if age <= 120:
                            # Make audible tone.
                            if do_beep:
                                Beep(6000, 1000)

                            # Send email.
                            if do_email:
                                f.send_email(c.SMTP_SERVER,
                                             c.SMTP_PORT,
                                             c.SMTP_PASSWORD,
                                             c.EMAIL_SENDER,
                                             c.EMAIL_RECIPIENT,
                                             subject=email_subject,
                                             body=job_data)

                            # Print the job data, write job summary to log file.
                            stdout.write(job_data)
                            if do_log:
                                outfile.write(job_summary + "\n")
                                outfile.flush()
                    # Done iterating over new_job_ids.

                if print_jobs and jobs is not None:
                    stdout.write("BEGIN PRINTING JOBS.\n")
                    stdout.write(str(jobs) + '\n')
                    stdout.write("DONE PRINTING JOBS.\n")

                # Wait some more, so that jobs page polled about every 30 seconds.
                sleep(20)
            # End of inner while loop.
        except Exception:
            # Print exception.
            f.print_stacktrace()
            # Close log file.
            if do_log:
                outfile.flush()
                outfile.close()
            # Make audible tone.
            if do_beep:
                Beep(1000, 1000)
            # Wait, in case of a web glitch.
            sleep(c.SLEEP_TIME)
    def run_sql(self) -> (list, list, int):
        """ Run the SQL, perhaps return rows and column names.

        Parameters:
        Returns:
            For SQL SELECT:
                col_names: list of the names of the columns being fetched.
                all_rows: list of tuples, each tuple is one row being fetched.
                row_count: number of rows fetched.
            For other types of SQL:
                list()
                list()
                row_count: number of rows affected.
        """
        col_names = list()
        all_rows = list()
        row_count = 0
        if not self.sql:
            print('NO SQL TO EXECUTE.')
            self.clean_up()
            exit(1)
        else:
            try:
                # Execute SQL.
                if len(self.bind_vars) > 0:
                    if self.db_type == ACCESS:
                        print('NO BIND VARIABLES ALLOWED IN MICROSOFT ACCESS.')
                        self.clean_up()
                        exit(1)
                    self.cursor.execute(self.sql, self.bind_vars)
                else:
                    self.cursor.execute(self.sql)

                # Check for something really yucky.
                if self.cursor is None:
                    print('\nCursor is None.')
                    self.clean_up()
                    exit(1)

                # Classify SQL.
                sql_type: str = self.sql.split()[0].upper()

                row_count = self.cursor.rowcount

                # Handle SQL results.
                if sql_type in {'INSERT', 'UPDATE', 'DELETE'}:
                    self.cursor.commit()
                elif sql_type != 'SELECT':
                    print('Not a CRUD statement!')
                elif sql_type == 'SELECT':
                    # Fetch rows.  Fetchall for large number of rows a problem.
                    all_rows = self.cursor.fetchall()

                    # Get column names
                    col_names = [item[0] for item in self.cursor.description]

                    # Column data types.  Too specific and inconsistent to use.
                    # col_types = [item[1] for item in self.cursor.description]

                    # Could combine all_rows & col_names into dict, with keys
                    # from col_names & values from all_rows, but performance
                    # would suffer.

                    # In Oracle, cursor.rowcount = 0, so get row count directly.
                    row_count = len(all_rows)

            except self.db_library.Error:
                print_stacktrace()
            finally:
                return col_names, all_rows, row_count