monopyly 1.5.0__py3-none-any.whl → 1.5.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- monopyly/CHANGELOG.md +23 -0
- monopyly/README.md +2 -2
- monopyly/__init__.py +22 -27
- monopyly/_version.py +14 -2
- monopyly/auth/actions.py +12 -0
- monopyly/auth/routes.py +31 -22
- monopyly/auth/tools.py +1 -2
- monopyly/banking/accounts.py +3 -3
- monopyly/banking/banks.py +1 -1
- monopyly/banking/routes.py +1 -1
- monopyly/banking/transactions.py +14 -5
- monopyly/common/forms/utils.py +1 -2
- monopyly/common/transactions.py +17 -7
- monopyly/core/actions.py +0 -7
- monopyly/core/routes.py +0 -6
- monopyly/credit/accounts.py +1 -1
- monopyly/credit/cards.py +7 -3
- monopyly/credit/forms.py +1 -1
- monopyly/credit/routes.py +46 -25
- monopyly/credit/statements.py +1 -1
- monopyly/credit/transactions/_transactions.py +18 -5
- monopyly/credit/transactions/activity/parser.py +14 -6
- monopyly/credit/transactions/activity/reconciliation.py +20 -1
- monopyly/database/__init__.py +1 -56
- monopyly/database/models.py +181 -273
- monopyly/static/css/style.css +191 -11
- monopyly/static/js/create-balance-chart.js +1 -1
- monopyly/templates/auth/change_password.html +21 -0
- monopyly/templates/auth/login.html +3 -1
- monopyly/templates/auth/register.html +17 -7
- monopyly/templates/core/profile.html +2 -2
- monopyly/templates/credit/statement_reconciliation/statement_reconciliation_inquiry.html +1 -1
- monopyly/templates/credit/transaction_submission_page.html +64 -71
- monopyly/templates/credit/transactions_table/condensed_row_content.html +0 -1
- monopyly/templates/layout.html +2 -2
- {monopyly-1.5.0.dist-info → monopyly-1.5.2.dist-info}/METADATA +12 -13
- {monopyly-1.5.0.dist-info → monopyly-1.5.2.dist-info}/RECORD +41 -45
- {monopyly-1.5.0.dist-info → monopyly-1.5.2.dist-info}/WHEEL +1 -1
- monopyly-1.5.2.dist-info/entry_points.txt +2 -0
- monopyly/cli/apps.py +0 -108
- monopyly/cli/launch.py +0 -135
- monopyly/config/__init__.py +0 -1
- monopyly/config/default_settings.py +0 -56
- monopyly/config/settings.py +0 -59
- monopyly-1.5.0.dist-info/entry_points.txt +0 -2
- {monopyly-1.5.0.dist-info → monopyly-1.5.2.dist-info}/licenses/COPYING +0 -0
- {monopyly-1.5.0.dist-info → monopyly-1.5.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Tools for interacting with the credit transactions in the database.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
from
|
|
5
|
+
from dry_foundation.database.handler import DatabaseHandler, DatabaseViewHandler
|
|
6
6
|
|
|
7
7
|
from ...common.forms.utils import execute_on_form_validation
|
|
8
8
|
from ...common.transactions import TransactionHandler, TransactionTagHandler
|
|
@@ -38,7 +38,13 @@ class CreditTransactionHandler(
|
|
|
38
38
|
@classmethod
|
|
39
39
|
@DatabaseViewHandler.view_query
|
|
40
40
|
def get_transactions(
|
|
41
|
-
cls,
|
|
41
|
+
cls,
|
|
42
|
+
statement_ids=None,
|
|
43
|
+
card_ids=None,
|
|
44
|
+
active=None,
|
|
45
|
+
sort_order="DESC",
|
|
46
|
+
offset=None,
|
|
47
|
+
limit=None,
|
|
42
48
|
):
|
|
43
49
|
"""
|
|
44
50
|
Get credit card transactions from the database.
|
|
@@ -66,6 +72,13 @@ class CreditTransactionHandler(
|
|
|
66
72
|
An indicator of whether the transactions should be ordered
|
|
67
73
|
in ascending (oldest at top) or descending (newest at top)
|
|
68
74
|
order.
|
|
75
|
+
offset : int, optional
|
|
76
|
+
The number of transactions by which to offset the results
|
|
77
|
+
returned by this query. The default is `None`, in which case
|
|
78
|
+
no offset will be added.
|
|
79
|
+
limit : int, optional
|
|
80
|
+
A limit on the number of transactions retrieved from the
|
|
81
|
+
database.
|
|
69
82
|
|
|
70
83
|
Returns
|
|
71
84
|
-------
|
|
@@ -77,7 +90,7 @@ class CreditTransactionHandler(
|
|
|
77
90
|
criteria.add_match_filter(CreditCard, "id", card_ids)
|
|
78
91
|
criteria.add_match_filter(CreditCard, "active", active)
|
|
79
92
|
transactions = super()._get_transactions(
|
|
80
|
-
criteria=criteria, sort_order=sort_order
|
|
93
|
+
criteria=criteria, sort_order=sort_order, offset=offset, limit=limit
|
|
81
94
|
)
|
|
82
95
|
return transactions
|
|
83
96
|
|
|
@@ -196,7 +209,7 @@ class CreditTagHandler(TransactionTagHandler, model=TransactionTagHandler.model)
|
|
|
196
209
|
return tags
|
|
197
210
|
|
|
198
211
|
@classmethod
|
|
199
|
-
def _filter_entries(cls, query, criteria):
|
|
212
|
+
def _filter_entries(cls, query, criteria, offset, limit):
|
|
200
213
|
# Add a join to enable filtering by transaction ID or subtransaction ID
|
|
201
214
|
join_transaction = CreditTransactionView in criteria.discriminators
|
|
202
215
|
join_subtransaction = (
|
|
@@ -206,7 +219,7 @@ class CreditTagHandler(TransactionTagHandler, model=TransactionTagHandler.model)
|
|
|
206
219
|
query = query.join(credit_tag_link_table).join(CreditSubtransaction)
|
|
207
220
|
if join_transaction:
|
|
208
221
|
query = query.join(CreditTransactionView)
|
|
209
|
-
return super()._filter_entries(query, criteria)
|
|
222
|
+
return super()._filter_entries(query, criteria, offset, limit)
|
|
210
223
|
|
|
211
224
|
|
|
212
225
|
@execute_on_form_validation
|
|
@@ -4,12 +4,14 @@ import csv
|
|
|
4
4
|
from abc import ABC, abstractmethod
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
|
|
7
|
-
from flask import current_app
|
|
7
|
+
from flask import abort, current_app
|
|
8
8
|
from werkzeug.utils import secure_filename
|
|
9
9
|
|
|
10
10
|
from ....common.utils import parse_date
|
|
11
11
|
from .data import ActivityLoadingError, TransactionActivities, TransactionActivityLoader
|
|
12
12
|
|
|
13
|
+
SUPPORTED_BANKS = ("Bank of America", "Chase", "Discover")
|
|
14
|
+
|
|
13
15
|
|
|
14
16
|
def parse_transaction_activity_file(transaction_file):
|
|
15
17
|
"""
|
|
@@ -91,7 +93,7 @@ class _TransactionDateColumnIdentifier(_ColumnIdentifier):
|
|
|
91
93
|
elif "date" in standardized_title.split():
|
|
92
94
|
if "trans." in standardized_title:
|
|
93
95
|
return True
|
|
94
|
-
elif standardized_title == "date":
|
|
96
|
+
elif standardized_title == "date" or standardized_title == "posted date":
|
|
95
97
|
return None
|
|
96
98
|
return False
|
|
97
99
|
|
|
@@ -104,8 +106,8 @@ class _TransactionTotalColumnIdentifier(_ColumnIdentifier):
|
|
|
104
106
|
|
|
105
107
|
class _TransactionDescriptionColumnIdentifier(_ColumnIdentifier):
|
|
106
108
|
def check(self, standardized_title):
|
|
107
|
-
"""Check if the title indicates this column contains a description."""
|
|
108
|
-
return standardized_title in ("description", "desc.")
|
|
109
|
+
"""Check if the title indicates this column contains a description (payee)."""
|
|
110
|
+
return standardized_title in ("description", "desc.", "payee")
|
|
109
111
|
|
|
110
112
|
|
|
111
113
|
class _TransactionCategoryColumnIdentifier(_ColumnIdentifier):
|
|
@@ -184,9 +186,15 @@ class _TransactionActivityParser:
|
|
|
184
186
|
}
|
|
185
187
|
for column_type in self.column_types:
|
|
186
188
|
if raw_column_indices[column_type] is None:
|
|
187
|
-
|
|
188
|
-
f"The '{column_type}' column could not be identified in the data."
|
|
189
|
+
current_app.logger.debug(
|
|
190
|
+
f"The '{column_type}' column could not be identified in the data. "
|
|
191
|
+
)
|
|
192
|
+
msg = (
|
|
193
|
+
"The data was unable to be parsed, most likely because it did not "
|
|
194
|
+
"match a recognized format. Supported data formats include those "
|
|
195
|
+
f"from the following banks: {', '.join(SUPPORTED_BANKS)}."
|
|
189
196
|
)
|
|
197
|
+
abort(400, msg)
|
|
190
198
|
return raw_column_indices
|
|
191
199
|
|
|
192
200
|
def _determine_expenditure_sign(self, raw_data):
|
|
@@ -222,7 +222,26 @@ class _Matchmaker(ABC):
|
|
|
222
222
|
field : str
|
|
223
223
|
The string of text to be tokenized.
|
|
224
224
|
"""
|
|
225
|
-
|
|
225
|
+
replacements = [
|
|
226
|
+
# Remove disruptive punctuation
|
|
227
|
+
("1-800", "1800"),
|
|
228
|
+
("-", " "),
|
|
229
|
+
(".", " "),
|
|
230
|
+
(",", " "),
|
|
231
|
+
("(", " "),
|
|
232
|
+
(")", " "),
|
|
233
|
+
("'", ""),
|
|
234
|
+
# Standardize characters
|
|
235
|
+
("&", "and"),
|
|
236
|
+
("é", "e"),
|
|
237
|
+
]
|
|
238
|
+
for original, replacement in replacements:
|
|
239
|
+
field = field.replace(original, replacement)
|
|
240
|
+
tokens = set(wordpunct_tokenize(field.replace("'", "").casefold()))
|
|
241
|
+
removals = ["and", "the", "of"]
|
|
242
|
+
for word in removals:
|
|
243
|
+
tokens.discard(word)
|
|
244
|
+
return tokens
|
|
226
245
|
|
|
227
246
|
def _compute_transaction_activity_similarity_score(
|
|
228
247
|
self, merchant_tokens, notes_tokens, activity_tokens
|
monopyly/database/__init__.py
CHANGED
|
@@ -2,17 +2,10 @@
|
|
|
2
2
|
Expose commonly used database functionality to the rest of the package.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
import sqlite3
|
|
6
5
|
from pathlib import Path
|
|
7
6
|
|
|
8
|
-
import
|
|
7
|
+
from dry_foundation.database import SQLAlchemy as _SQLAlchemy
|
|
9
8
|
from flask import current_app
|
|
10
|
-
from flask.cli import with_appcontext
|
|
11
|
-
from fuisce.database import SQLAlchemy as _SQLAlchemy
|
|
12
|
-
|
|
13
|
-
from ..core.actions import get_timestamp
|
|
14
|
-
|
|
15
|
-
BASE_DB_NAME = f"monopyly.sqlite"
|
|
16
9
|
|
|
17
10
|
|
|
18
11
|
class SQLAlchemy(_SQLAlchemy):
|
|
@@ -51,51 +44,3 @@ class SQLAlchemy(_SQLAlchemy):
|
|
|
51
44
|
|
|
52
45
|
|
|
53
46
|
SQLAlchemy.create_default_interface(echo_engine=False)
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
@click.command("init-db")
|
|
57
|
-
@with_appcontext
|
|
58
|
-
def init_db_command():
|
|
59
|
-
"""Initialize the database from the command line (if it does not exist)."""
|
|
60
|
-
db_path = current_app.config["DATABASE"]
|
|
61
|
-
if not db_path.is_file():
|
|
62
|
-
current_app.db.initialize(current_app)
|
|
63
|
-
click.echo(f"Initialized the database ('{db_path}')")
|
|
64
|
-
preload_path = current_app.config.get("PRELOAD_DATA_PATH")
|
|
65
|
-
if preload_path:
|
|
66
|
-
click.echo(f"Prepopulated the database using '{preload_path}'")
|
|
67
|
-
else:
|
|
68
|
-
click.echo(f"Database exists, using '{db_path}'")
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
@click.command("back-up-db")
|
|
72
|
-
@with_appcontext
|
|
73
|
-
def back_up_db_command():
|
|
74
|
-
"""Back up the database from the command line."""
|
|
75
|
-
timestamp = get_timestamp()
|
|
76
|
-
# Connect to the database
|
|
77
|
-
db = sqlite3.connect(current_app.config["DATABASE"])
|
|
78
|
-
# Create and connect to the backup database
|
|
79
|
-
backup_db_dir_path = Path(current_app.instance_path) / "db_backups"
|
|
80
|
-
backup_db_dir_path.mkdir(exist_ok=True, parents=True)
|
|
81
|
-
backup_db_path = backup_db_dir_path / f"backup_{timestamp}.sqlite"
|
|
82
|
-
backup_db = sqlite3.connect(backup_db_path)
|
|
83
|
-
# Back up the database and print status
|
|
84
|
-
back_up_db(db, backup_db)
|
|
85
|
-
click.echo(f"Backup complete ({timestamp})")
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
def back_up_db(db, backup_db):
|
|
89
|
-
"""Create a backup of the database."""
|
|
90
|
-
# Backup the database
|
|
91
|
-
with backup_db:
|
|
92
|
-
db.backup(backup_db)
|
|
93
|
-
# Close the connections
|
|
94
|
-
backup_db.close()
|
|
95
|
-
db.close()
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
def register_db_cli_commands(app):
|
|
99
|
-
"""Register database CLI commands with the app."""
|
|
100
|
-
app.cli.add_command(init_db_command)
|
|
101
|
-
app.cli.add_command(back_up_db_command)
|