Exemplo n.º 1
0
def driver():
    utils.log_debug('driver')
    global _driver
    install.setup_chromedriver()
    try:
        if is_alive(_driver):
            return _driver
        close_orphan_drivers()
        options = Options()
        assert os.path.exists(settings.chrome_data_dir)
        options.add_argument('user-data-dir={}'.format(
            settings.chrome_data_dir))
        options.add_argument('--disable-extensions')
        assert os.path.exists(settings.chromedriver_path)
        _driver = webdriver.Chrome(options=options,
                                   executable_path=settings.chromedriver_path)
    except BaseException:
        quit()
        if 'data directory is already in use' in traceback.format_exc():
            utils.log_exception_debug()
            utils.log_error('Must close Selenium-controlled Chrome.')
            if input('Try again? [Y/n]\n').lower() != 'n':
                utils.log_info('Trying again')
                return driver()
            sys.exit()
        else:
            utils.log_exception()
    assert _driver
    return _driver
Exemplo n.º 2
0
def do_delete():
    utils.log_debug('do_delete')
    for a in account_delete_queue.values():
        queue_delete_transactions(afy.Assistant.transactions.by_name(a.name))
    do_delete_transactions()
    do_delete_accounts()
    gui.quit()
Exemplo n.º 3
0
def adjust_subtransaction_rows(t):
    utils.log_debug('add_subtransactions_rows', len(t.subtransactions))
    # Remove existing subtransactions
    memo = gui.get_by_text('user-entered-text', t.id, count=1)  # TODO: why was this partial?
    gui.click(memo, 2)
    removes = gui.get('ynab-grid-sub-remove', require=False, wait=1)
    while removes:
        gui.click(removes)
        removes = gui.get('ynab-grid-sub-remove', require=False, wait=.5)

    # Add rows for the new ones
    n = len(t.subtransactions)
    if n == 0:
        return
    gui.get_by_placeholder('accounts-text-field', 'category').clear()  # needed?
    category_dropdown = gui.get_by_placeholder('dropdown-text-field', 'category')
    category_dropdown.send_keys(' ')
    split = gui.get('modal-account-categories-split-transaction')
    gui.click(split)
    for i in range(n - 2):
        # gui.clicking split means we already have two
        gui.click(gui.get('ynab-grid-split-add-sub-transaction'))
    if n == 1:
        # which is weird but in principle OK
        gui.click(gui.get('ynab-grid-sub-remove'))
    assert len(gui.get('ynab-grid-sub-remove')) == n
Exemplo n.º 4
0
 def __init__(self, d):
     self._parent_dict = d
     utils.log_debug(d)
     self.order_date = parse_date(d['Order Date'])
     self.id = d['Order ID']
     self.shipment_date = parse_date(d['Shipment Date'])
     self.total_charged = parse_money(d['Total Charged'])
Exemplo n.º 5
0
def enter_transaction(t):
    utils.log_debug('enter_transaction', t)
    accounts_sidebar = gui.get_by_text('user-entered-text', t.account_name)
    gui.click(accounts_sidebar)  # handles that it contains two elements
    locate_transaction(t)
    assert t.account_name not in ('Annotated', 'Test Data'
                                  )  # don't overwrite test data
    adjust_subtransaction_rows(t)
    date, payees, categories, memos = map(
        lambda p: gui.get_by_placeholder('accounts-text-field', p),
        ('date', 'payee', 'category', 'memo'))
    date.send_keys(gui_format_date(t.date))
    outflows, inflows = map(
        lambda p: gui.get_by_placeholder('ember-text-field', p),
        ('outflow', 'inflow'))
    if len(t.subtransactions) == 0:
        enter(t, payees, categories, memos, outflows, inflows)
        ' TODO: do not approve, only save? '
        ' Maybe it is only approving things that are already approved? '
        approve = gui.get_by_text('button-primary', ['Approve', 'Save'])
        utils.log_debug('approve/save?', approve.text)
        gui.click(approve)
    else:
        for i, s in enumerate(t.subtransactions):
            enter(s, payees[i + 1], categories[i + 1], memos[i + 1],
                  outflows[i + 1], inflows[i + 1])
        outflows[-1].send_keys(gui.Keys.ENTER)
Exemplo n.º 6
0
def load(data_type):
    utils.log_debug('load', data_type)
    assert data_type in data_parsers
    target_path = csv_paths[data_type]
    try:
        if not os.path.exists(target_path) or stale(target_path):
            d = gui.driver()
            url = 'https://smile.amazon.com/gp/b2b/reports'
            if url not in d.current_url:
                d.get(url)

            d.find_element_by_id('report-use-today').click()
            enter_start_date()
            d.find_element_by_id('report-type').click()
            d.find_element_by_id('report-type').send_keys(data_type)
            d.find_element_by_id('report-confirm').click()
            path = wait_for_download()
            utils.log_debug(path, target_path)
            os.rename(path, target_path)

    except BaseException:
        utils.log_exception_debug()
        if 'AttributeError' in traceback.format_exc():  # TODO: only retry on missing csv
            utils.log_info('real error', traceback.format_exc())
            sys.exit()
        if input('One more try? [Y/n]').lower() != 'n':
            load(data_type)

    list_of_dicts = read(target_path)
    utils.log_info('Found %s %s' % (len(list_of_dicts), data_type))
    return data_parsers[data_type](list_of_dicts)
Exemplo n.º 7
0
def load_path(path):
    utils.log_debug('loading', path)
    if not os.path.exists(path):
        return []
    with open(path, 'r') as f:
        raw = f.read()
        decoded = jsonpickle.decode(raw)
        return decoded
Exemplo n.º 8
0
def load(t, n=None, predicates=()):
    utils.log_debug('load', t, n)
    if n is None:
        return sum(map(lambda n: load(t, n), range(versions[t] + 1)), [])
    if n == -1:  # ie, most recent
        n = versions[t]
    loaded = load_path(get_backup_path(t, n))
    return utils.multi_filter(predicates, loaded)
Exemplo n.º 9
0
 def make_get_by_name(self):
     grouped = utils.group_by(self.xs, self.name)
     self.names = utils.by(map(lambda v: v.pop(), grouped.values()),
                           self.name)
     for name, g in grouped.items():
         if g:
             utils.log_debug('duplicate name, using first', name,
                             self.names[name], *g)
Exemplo n.º 10
0
def get_eligible_transactions(transactions):
    utils.log_debug('get_eligible_transactions')
    predicates = newer_than, has_blank_or_WIP_memo, matches_account
    eligible = utils.multi_filter(predicates, transactions)
    utils.log_info(
        'Found %s transactions to attempt to match with Amazon orders' %
        len(eligible))
    return utils.by(eligible, lambda t: t.id)
Exemplo n.º 11
0
def click(element, n=1, pause=.5):
    utils.log_debug('click')
    if type(element) in (tuple, list):
        element = element[0]
    for i in range(n):
        driver().execute_script('arguments[0].click();', element)
        if i != n - 1:
            time.sleep(pause)
Exemplo n.º 12
0
def queue_copy_to_account(ts, account):
    utils.log_debug('queue_copy_to_account', ts, account)
    assert afy.Assistant.accounts.get(account.id)
    to_copy = utils.copy(ts)
    for t in to_copy:
        modify_transaction_for_moving(t, account)
    utils.log_debug('to_copy', to_copy)
    queue_create(to_copy)
Exemplo n.º 13
0
def trim_memo_length(t):
    utils.log_debug('trim_memo_length', t)
    type_assert_st(t)
    if t.memo is not None:
        t.memo = t.memo[:200]
        if isinstance(t, ynab_api.TransactionDetail):
            [trim_memo_length(st) for st in t.subtransactions]
    return t
Exemplo n.º 14
0
def check_category(st, categories):
    utils.log_debug('check_category', st)
    type_assert_st(st)
    assert not st.category_id or st.category_id in categories
    if st.category_id and st.__dict__.get('category_name'):
        assert categories[st.category_id].name == st.category_name
    if isinstance(st, ynab_api.TransactionDetail):
        [check_category(s, categories) for s in st.subtransactions]
Exemplo n.º 15
0
def get_transactions():
    utils.log_debug('get_transactions')
    initialized or init()
    response = transactions_api.get_transactions(settings.budget_id)
    ts = response.data.transactions
    assert all(isinstance(t, ynab_api.TransactionDetail) for t in ts)
    ts.sort(key=lambda t: t.date, reverse=True)
    return ts
Exemplo n.º 16
0
def get_accounts():
    utils.log_debug('get_accounts')
    initialized or init()
    response = accounts_api.get_accounts(settings.budget_id)
    acs = response.data.accounts
    assert all(isinstance(ac, ynab_api.Account) for ac in acs)
    acs.sort(key=lambda ac: ac.name, reverse=True)
    return acs
Exemplo n.º 17
0
def get_payees():
    utils.log_debug('get_payees')
    initialized or init()
    response = payees_api.get_payees(settings.budget_id)
    ps = response.data.payees
    assert all(isinstance(p, ynab_api.Payee) for p in ps)
    ps.sort(key=lambda p: p.name)
    return ps
Exemplo n.º 18
0
def install():
    utils.log_info('Installing')
    setup_chromedriver()
    make_dirs()
    setup_ynab_auth()
    setup_ynab_budget_id()
    settings.init()
    utils.log_debug('Settings:', settings._s.settings)
    utils.log_info('Installed!')
Exemplo n.º 19
0
def combine_orders(orders):
    utils.log_debug('combine_orders')
    combined = {}
    for order in orders:
        if order.id in combined:
            combined[order.id] += order  # overloaded operator
        else:
            combined[order.id] = order
    return combined
Exemplo n.º 20
0
def is_alive(d):
    utils.log_debug('is_alive')
    try:
        d.execute(Command.STATUS)
        d.current_url
        return True
    except BaseException:
        utils.log_exception_debug()
        return False
Exemplo n.º 21
0
def update_transactions(transactions):
    utils.log_debug('update_transactions')
    initialized or init()
    assert all(isinstance(t, ynab_api.TransactionDetail) for t in transactions)
    ut = utils.convert(transactions, ynab_api.UpdateTransaction)
    utw = ynab_api.UpdateTransactionsWrapper(transactions=ut)
    ts = transactions_api.update_transactions(settings.budget_id,
                                              utw).data.transactions
    assert all(isinstance(t, ynab_api.TransactionDetail) for t in ts)
    return ts
Exemplo n.º 22
0
def create_transactions(transactions):
    utils.log_debug('create_transactions')
    initialized or init()
    assert all(isinstance(t, ynab_api.TransactionDetail) for t in transactions)
    st = utils.convert(transactions, ynab_api.SaveTransaction)
    stw = ynab_api.SaveTransactionsWrapper(transactions=st)
    ts = transactions_api.create_transaction(settings.budget_id,
                                             stw).data.transactions
    assert all(isinstance(t, ynab_api.TransactionDetail) for t in ts)
    return ts
Exemplo n.º 23
0
def enter(st, payee_element, category_element, memo_element, outflow_element, inflow_element):
    utils.log_debug('enter', st)
    type_assert_st(st)
    category = get_category(st)
    payee = get_payee(st)
    amount = get_amount(st)
    outflow = 0 if amount > 0 else abs(amount)
    inflow = 0 if amount < 0 else abs(amount)
    enter_fields((payee_element, category_element, memo_element, outflow_element),
                 (payee, category, st.memo, outflow, inflow))
Exemplo n.º 24
0
def check_payee(st, payees):
    utils.log_debug('check_payee', st)
    type_assert_st(st)
    assert not st.payee_id or st.payee_id in payees
    # Need get because this is a field that isn't on the api model
    # I just add it for gui_client convenience in amazon.amazon
    if st.payee_id and st.__dict__.get('payee_name'):
        assert payees[st.payee_id].name == st.payee_name
    if isinstance(st, ynab_api.TransactionDetail):
        [check_payee(s, payees) for s in st.subtransactions]
Exemplo n.º 25
0
def get_category_groups():
    utils.log_debug('get_category_groups')
    initialized or init()
    response = categories_api.get_categories(settings.budget_id)
    groups = response.data.category_groups
    assert all(
        isinstance(g, ynab_api.CategoryGroupWithCategories) for g in groups)
    categories = [c for g in groups for c in g.categories]
    assert all(isinstance(c, ynab_api.Category) for c in categories)
    return groups
Exemplo n.º 26
0
def match_all(transactions, orders):
    utils.log_debug('match_all', len(transactions), len(orders))
    orders_by_transaction_id = {}
    for t_id, t in transactions.items():
        order = get_order(t, orders)
        if not order:
            continue
        orders_by_transaction_id[t_id] = order
    utils.log_info('Found %s matches' % len(orders_by_transaction_id))
    return orders_by_transaction_id
Exemplo n.º 27
0
def queue(ts, mode, payees, categories):
    utils.log_debug('queue', ts, mode)
    assert mode in rest_modes
    assert all(isinstance(t, ynab_api.TransactionDetail) for t in ts)
    for t in ts:
        if payees is not None:  # TODO: check them all at once. Or even like actually use this..
            check_payee(t, payees)
        if categories is not None:
            check_category(t, categories)
        enqueue(t, (gui_queue if t.subtransactions else rest_queue)[mode])
Exemplo n.º 28
0
def add_unlinked_account(account_name, balance=0, account_type='credit'):
    utils.log_debug('add_unlinked_account', account_name, balance, account_type)
    gui_client.add_unlinked_account(account_name, balance, account_type)
    gui.quit()
    afy.Assistant.download_ynab(accounts=True)
    start = time.time()
    while time.time() - start < 30:  # TODO: generic retrier
        new_account = afy.Assistant.accounts.by_name(account_name)
        if new_account:
            return new_account
    assert False
Exemplo n.º 29
0
def do_delete_transactions():
    utils.log_debug('do_delete_transactions', transaction_delete_queue)
    if not transaction_delete_queue:
        return
    utils.log_info('Set deletion memo on %s transactions via YNAB REST API' % len(transaction_delete_queue))
    for t in transaction_delete_queue.values():
        t.memo = delete_key
    api_client.update_transactions(transaction_delete_queue)
    utils.log_info('delete %s transactions via YNAB webapp' % len(transaction_delete_queue))
    gui_client.delete_transactions()
    transaction_delete_queue.clear()
Exemplo n.º 30
0
def close_orphan_drivers():
    return
    # TODO: this doesn't work from WSL
    utils.log_debug('close_orphan_drivers')
    p = subprocess.Popen(['ps', '-A'], stdout=subprocess.PIPE)
    out, err = p.communicate()
    for line in out.splitlines():
        if settings.chromedriver_filename in str(line.lower()):
            pid = int(line.split(None, 1)[0])
            utils.log_debug('killing', line, pid)
            os.kill(pid, signal.SIGKILL)