Compare commits
6 Commits
2ad5f8171d
...
a3d2d8215e
| Author | SHA1 | Date | |
|---|---|---|---|
| a3d2d8215e | |||
| eb5c1781f0 | |||
| 184628dcfc | |||
| 26cf921a6f | |||
| d7bdafa62a | |||
| 88a29bdf83 |
@ -83,7 +83,7 @@ if __name__ == "__main__":
|
|||||||
assert len(args["bank"]) > 0, "argparser ill defined"
|
assert len(args["bank"]) > 0, "argparser ill defined"
|
||||||
params = args["bank"]
|
params = args["bank"]
|
||||||
|
|
||||||
case Operation.NordigenAdd:
|
case Operation.PSD2Add:
|
||||||
keys = {"bank", "bank_id", "requisition_id", "invert"}
|
keys = {"bank", "bank_id", "requisition_id", "invert"}
|
||||||
assert args.keys() >= keys, f"missing {args.keys() - keys}"
|
assert args.keys() >= keys, f"missing {args.keys() - keys}"
|
||||||
|
|
||||||
@ -96,7 +96,7 @@ if __name__ == "__main__":
|
|||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
case Operation.NordigenMod:
|
case Operation.PSD2Mod:
|
||||||
keys = {"bank", "bank_id", "requisition_id", "invert", "remove"}
|
keys = {"bank", "bank_id", "requisition_id", "invert", "remove"}
|
||||||
assert args.keys() >= keys, f"missing {args.keys() - keys}"
|
assert args.keys() >= keys, f"missing {args.keys() - keys}"
|
||||||
|
|
||||||
@ -110,11 +110,11 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
params = [param]
|
params = [param]
|
||||||
|
|
||||||
case Operation.NordigenDel:
|
case Operation.PSD2Del:
|
||||||
assert len(args["bank"]) > 0, "argparser ill defined"
|
assert len(args["bank"]) > 0, "argparser ill defined"
|
||||||
params = args["bank"]
|
params = args["bank"]
|
||||||
|
|
||||||
case Operation.NordigenCountryBanks:
|
case Operation.PSD2CountryBanks:
|
||||||
keys = {"country"}
|
keys = {"country"}
|
||||||
assert args.keys() >= keys, f"missing {args.keys() - keys}"
|
assert args.keys() >= keys, f"missing {args.keys() - keys}"
|
||||||
|
|
||||||
@ -149,7 +149,16 @@ if __name__ == "__main__":
|
|||||||
]
|
]
|
||||||
|
|
||||||
case Operation.RuleAdd:
|
case Operation.RuleAdd:
|
||||||
keys = {"category", "start", "end", "description", "regex", "bank", "min", "max"}
|
keys = {
|
||||||
|
"category",
|
||||||
|
"start",
|
||||||
|
"end",
|
||||||
|
"description",
|
||||||
|
"regex",
|
||||||
|
"bank",
|
||||||
|
"min",
|
||||||
|
"max",
|
||||||
|
}
|
||||||
assert args.keys() >= keys, f"missing {args.keys() - keys}"
|
assert args.keys() >= keys, f"missing {args.keys() - keys}"
|
||||||
|
|
||||||
params = [
|
params = [
|
||||||
|
|||||||
@ -126,16 +126,16 @@ def argparser() -> argparse.ArgumentParser:
|
|||||||
# Banks
|
# Banks
|
||||||
bank(subparsers.add_parser("bank"))
|
bank(subparsers.add_parser("bank"))
|
||||||
|
|
||||||
# Nordigen access token
|
# PSD2 access token
|
||||||
subparsers.add_parser("token").set_defaults(op=Operation.Token)
|
subparsers.add_parser("token").set_defaults(op=Operation.Token)
|
||||||
|
|
||||||
# Nordigen requisition id
|
# PSD2 requisition id
|
||||||
requisition = subparsers.add_parser("eua")
|
requisition = subparsers.add_parser("eua")
|
||||||
requisition.set_defaults(op=Operation.RequisitionId)
|
requisition.set_defaults(op=Operation.RequisitionId)
|
||||||
requisition.add_argument("id", nargs=1, type=str)
|
requisition.add_argument("id", nargs=1, type=str)
|
||||||
requisition.add_argument("country", nargs=1, type=str)
|
requisition.add_argument("country", nargs=1, type=str)
|
||||||
|
|
||||||
# Download through the Nordigen API
|
# Download through the PSD2 API
|
||||||
download = subparsers.add_parser("download", parents=[period])
|
download = subparsers.add_parser("download", parents=[period])
|
||||||
download.set_defaults(op=Operation.Download)
|
download.set_defaults(op=Operation.Download)
|
||||||
download_banks = download.add_mutually_exclusive_group()
|
download_banks = download.add_mutually_exclusive_group()
|
||||||
@ -145,7 +145,7 @@ def argparser() -> argparse.ArgumentParser:
|
|||||||
|
|
||||||
# List available banks in country C
|
# List available banks in country C
|
||||||
banks = subparsers.add_parser("banks")
|
banks = subparsers.add_parser("banks")
|
||||||
banks.set_defaults(op=Operation.NordigenCountryBanks)
|
banks.set_defaults(op=Operation.PSD2CountryBanks)
|
||||||
banks.add_argument("country", nargs=1, type=str)
|
banks.add_argument("country", nargs=1, type=str)
|
||||||
|
|
||||||
# Categories
|
# Categories
|
||||||
@ -214,7 +214,7 @@ def bank(parser: argparse.ArgumentParser):
|
|||||||
mod.add_argument("--type", nargs=1, type=str, choices=[e.name for e in AccountType])
|
mod.add_argument("--type", nargs=1, type=str, choices=[e.name for e in AccountType])
|
||||||
mod.add_argument("--remove", nargs="*", default=[], type=str)
|
mod.add_argument("--remove", nargs="*", default=[], type=str)
|
||||||
|
|
||||||
nordigen(commands.add_parser("nordigen"))
|
psd2(commands.add_parser("psd2"))
|
||||||
|
|
||||||
export = commands.add_parser("export")
|
export = commands.add_parser("export")
|
||||||
export.set_defaults(op=Operation.ExportBanks)
|
export.set_defaults(op=Operation.ExportBanks)
|
||||||
@ -225,22 +225,22 @@ def bank(parser: argparse.ArgumentParser):
|
|||||||
file_options(pimport)
|
file_options(pimport)
|
||||||
|
|
||||||
|
|
||||||
def nordigen(parser: argparse.ArgumentParser):
|
def psd2(parser: argparse.ArgumentParser):
|
||||||
commands = parser.add_subparsers(required=True)
|
commands = parser.add_subparsers(required=True)
|
||||||
|
|
||||||
add = commands.add_parser("add")
|
add = commands.add_parser("add")
|
||||||
add.set_defaults(op=Operation.NordigenAdd)
|
add.set_defaults(op=Operation.PSD2Add)
|
||||||
add.add_argument("bank", nargs=1, type=str)
|
add.add_argument("bank", nargs=1, type=str)
|
||||||
add.add_argument("--bank_id", nargs=1, type=str)
|
add.add_argument("--bank_id", nargs=1, type=str)
|
||||||
add.add_argument("--requisition_id", nargs=1, type=str)
|
add.add_argument("--requisition_id", nargs=1, type=str)
|
||||||
add.add_argument("--invert", action="store_true")
|
add.add_argument("--invert", action="store_true")
|
||||||
|
|
||||||
rem = commands.add_parser("del")
|
rem = commands.add_parser("del")
|
||||||
rem.set_defaults(op=Operation.NordigenDel)
|
rem.set_defaults(op=Operation.PSD2Del)
|
||||||
rem.add_argument("bank", nargs="+", type=str)
|
rem.add_argument("bank", nargs="+", type=str)
|
||||||
|
|
||||||
mod = commands.add_parser("mod")
|
mod = commands.add_parser("mod")
|
||||||
mod.set_defaults(op=Operation.NordigenMod)
|
mod.set_defaults(op=Operation.PSD2Mod)
|
||||||
mod.add_argument("bank", nargs=1, type=str)
|
mod.add_argument("bank", nargs=1, type=str)
|
||||||
mod.add_argument("--bank_id", nargs=1, type=str)
|
mod.add_argument("--bank_id", nargs=1, type=str)
|
||||||
mod.add_argument("--requisition_id", nargs=1, type=str)
|
mod.add_argument("--requisition_id", nargs=1, type=str)
|
||||||
|
|||||||
@ -65,7 +65,8 @@ class Interactive:
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
if other.startswith("note:"):
|
if other.startswith("note:"):
|
||||||
# TODO adding notes to a splitted transaction won't allow categorization
|
# TODO adding notes to a splitted transaction won't allow
|
||||||
|
# categorization
|
||||||
next.note = Note(other[len("note:") :].strip())
|
next.note = Note(other[len("note:") :].strip())
|
||||||
else:
|
else:
|
||||||
ct = other.split(":")
|
ct = other.split(":")
|
||||||
|
|||||||
@ -33,10 +33,10 @@ class Operation(Enum):
|
|||||||
BankAdd = auto()
|
BankAdd = auto()
|
||||||
BankMod = auto()
|
BankMod = auto()
|
||||||
BankDel = auto()
|
BankDel = auto()
|
||||||
NordigenAdd = auto()
|
PSD2Add = auto()
|
||||||
NordigenMod = auto()
|
PSD2Mod = auto()
|
||||||
NordigenDel = auto()
|
PSD2Del = auto()
|
||||||
NordigenCountryBanks = auto()
|
PSD2CountryBanks = auto()
|
||||||
Export = auto()
|
Export = auto()
|
||||||
Import = auto()
|
Import = auto()
|
||||||
ExportBanks = auto()
|
ExportBanks = auto()
|
||||||
|
|||||||
@ -54,7 +54,6 @@ groups = {
|
|||||||
|
|
||||||
|
|
||||||
def categorize_data(db: DatabaseClient):
|
def categorize_data(db: DatabaseClient):
|
||||||
|
|
||||||
# 1st) Classifying null transactions, i.e. transfers between banks.
|
# 1st) Classifying null transactions, i.e. transfers between banks.
|
||||||
# Will not overwrite previous categories
|
# Will not overwrite previous categories
|
||||||
nulls(db)
|
nulls(db)
|
||||||
@ -77,7 +76,8 @@ def categorize_data(db: DatabaseClient):
|
|||||||
# 4th) Manually update categories from the uncategorized transactions
|
# 4th) Manually update categories from the uncategorized transactions
|
||||||
if transactions := db.get_uncategorized_transactions():
|
if transactions := db.get_uncategorized_transactions():
|
||||||
print(
|
print(
|
||||||
f"Still {len(transactions)} uncategorized transactions left. Type quit/exit to exit the program."
|
f"Still {len(transactions)} uncategorized transactions left. Type quit/exit"
|
||||||
|
"to exit the program."
|
||||||
)
|
)
|
||||||
for transaction in transactions:
|
for transaction in transactions:
|
||||||
while True:
|
while True:
|
||||||
@ -88,7 +88,8 @@ def categorize_data(db: DatabaseClient):
|
|||||||
break
|
break
|
||||||
if category not in categories:
|
if category not in categories:
|
||||||
print(
|
print(
|
||||||
f"Category {category} doesn't exist. Please use one of {categories.keys()}"
|
f"Category {category} doesn't exist. Please use one of"
|
||||||
|
f"{categories.keys()}"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
transaction.category = category
|
transaction.category = category
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
import csv
|
import csv
|
||||||
|
import dotenv
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import pickle
|
import pickle
|
||||||
|
import os
|
||||||
import webbrowser
|
import webbrowser
|
||||||
|
|
||||||
from pfbudget.common.types import Operation
|
from pfbudget.common.types import Operation
|
||||||
@ -25,8 +27,12 @@ from pfbudget.db.model import (
|
|||||||
Transaction,
|
Transaction,
|
||||||
TransactionCategory,
|
TransactionCategory,
|
||||||
)
|
)
|
||||||
from pfbudget.input.nordigen import NordigenInput
|
from pfbudget.extract.credentials import Credentials
|
||||||
from pfbudget.input.parsers import parse_data
|
from pfbudget.extract.extract import Extract
|
||||||
|
from pfbudget.extract.psd2 import PSD2Client
|
||||||
|
from pfbudget.extract.parsers import parse_data
|
||||||
|
|
||||||
|
dotenv.load_dotenv()
|
||||||
|
|
||||||
|
|
||||||
class Manager:
|
class Manager:
|
||||||
@ -72,16 +78,16 @@ class Manager:
|
|||||||
session.add(sorted(transactions))
|
session.add(sorted(transactions))
|
||||||
|
|
||||||
case Operation.Download:
|
case Operation.Download:
|
||||||
client = NordigenInput()
|
client = Manager.nordigen_client()
|
||||||
with self.db.session() as session:
|
with self.db.session() as session:
|
||||||
if len(params[3]) == 0:
|
if len(params[3]) == 0:
|
||||||
client.banks = session.get(Bank, Bank.nordigen)
|
banks = session.get(Bank, Bank.nordigen)
|
||||||
else:
|
else:
|
||||||
client.banks = session.get(Bank, Bank.name, params[3])
|
banks = session.get(Bank, Bank.name, params[3])
|
||||||
session.expunge_all()
|
session.expunge_all()
|
||||||
client.start = params[0]
|
client.start = params[0]
|
||||||
client.end = params[1]
|
client.end = params[1]
|
||||||
transactions = client.parse()
|
transactions = client.extract(banks)
|
||||||
|
|
||||||
# dry-run
|
# dry-run
|
||||||
if not params[2]:
|
if not params[2]:
|
||||||
@ -103,7 +109,7 @@ class Manager:
|
|||||||
with self.db.session() as session:
|
with self.db.session() as session:
|
||||||
session.update(Bank, params)
|
session.update(Bank, params)
|
||||||
|
|
||||||
case Operation.NordigenMod:
|
case Operation.PSD2Mod:
|
||||||
with self.db.session() as session:
|
with self.db.session() as session:
|
||||||
session.update(Nordigen, params)
|
session.update(Nordigen, params)
|
||||||
|
|
||||||
@ -111,26 +117,26 @@ class Manager:
|
|||||||
with self.db.session() as session:
|
with self.db.session() as session:
|
||||||
session.remove_by_name(Bank, params)
|
session.remove_by_name(Bank, params)
|
||||||
|
|
||||||
case Operation.NordigenDel:
|
case Operation.PSD2Del:
|
||||||
with self.db.session() as session:
|
with self.db.session() as session:
|
||||||
session.remove_by_name(Nordigen, params)
|
session.remove_by_name(Nordigen, params)
|
||||||
|
|
||||||
case Operation.Token:
|
case Operation.Token:
|
||||||
NordigenInput().token()
|
Manager.nordigen_client().generate_token()
|
||||||
|
|
||||||
case Operation.RequisitionId:
|
case Operation.RequisitionId:
|
||||||
link, _ = NordigenInput().requisition(params[0], params[1])
|
link, _ = Manager.nordigen_client().requisition(params[0], params[1])
|
||||||
print(f"Opening {link} to request access to {params[0]}")
|
print(f"Opening {link} to request access to {params[0]}")
|
||||||
webbrowser.open(link)
|
webbrowser.open(link)
|
||||||
|
|
||||||
case Operation.NordigenCountryBanks:
|
case Operation.PSD2CountryBanks:
|
||||||
banks = NordigenInput().country_banks(params[0])
|
banks = Manager.nordigen_client().country_banks(params[0])
|
||||||
print(banks)
|
print(banks)
|
||||||
|
|
||||||
case (
|
case (
|
||||||
Operation.BankAdd
|
Operation.BankAdd
|
||||||
| Operation.CategoryAdd
|
| Operation.CategoryAdd
|
||||||
| Operation.NordigenAdd
|
| Operation.PSD2Add
|
||||||
| Operation.RuleAdd
|
| Operation.RuleAdd
|
||||||
| Operation.TagAdd
|
| Operation.TagAdd
|
||||||
| Operation.TagRuleAdd
|
| Operation.TagRuleAdd
|
||||||
@ -270,7 +276,8 @@ class Manager:
|
|||||||
row["date"], row["description"], row["amount"]
|
row["date"], row["description"], row["amount"]
|
||||||
)
|
)
|
||||||
|
|
||||||
# TODO case "split" how to match to original transaction?? also save ids?
|
# TODO case "split" how to match to original transaction?? also
|
||||||
|
# save ids?
|
||||||
case _:
|
case _:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@ -410,3 +417,12 @@ class Manager:
|
|||||||
@db.setter
|
@db.setter
|
||||||
def db(self, url: str):
|
def db(self, url: str):
|
||||||
self._db = url
|
self._db = url
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def nordigen_client() -> Extract:
|
||||||
|
credentials = Credentials(
|
||||||
|
os.environ.get("SECRET_ID"),
|
||||||
|
os.environ.get("SECRET_KEY"),
|
||||||
|
os.environ.get("TOKEN"),
|
||||||
|
)
|
||||||
|
return PSD2Client(credentials)
|
||||||
|
|||||||
@ -141,7 +141,8 @@ ORDER BY date ASC
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
ADD_BANK = """
|
ADD_BANK = """
|
||||||
INSERT INTO banks (name, bic, nordigen_id, nordigen_name, requisition_id, invert) values (?,?,?,?,?,?)
|
INSERT INTO banks (name, bic, nordigen_id, nordigen_name, requisition_id, invert)
|
||||||
|
values (?,?,?,?,?,?)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
DELETE_BANK = """
|
DELETE_BANK = """
|
||||||
|
|||||||
11
pfbudget/extract/credentials.py
Normal file
11
pfbudget/extract/credentials.py
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Credentials:
|
||||||
|
id: str
|
||||||
|
key: str
|
||||||
|
token: str = ""
|
||||||
|
|
||||||
|
def valid(self) -> bool:
|
||||||
|
return self.id and self.key
|
||||||
10
pfbudget/extract/exceptions.py
Normal file
10
pfbudget/extract/exceptions.py
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
class ExtractError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class BankError(ExtractError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class CredentialsError(ExtractError):
|
||||||
|
pass
|
||||||
@ -3,7 +3,7 @@ from abc import ABC, abstractmethod
|
|||||||
from pfbudget.db.model import Transaction
|
from pfbudget.db.model import Transaction
|
||||||
|
|
||||||
|
|
||||||
class Input(ABC):
|
class Extract(ABC):
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def parse(self) -> list[Transaction]:
|
def extract(self) -> list[Transaction]:
|
||||||
return NotImplementedError
|
return NotImplementedError
|
||||||
@ -77,7 +77,7 @@ def parse_data(filename: Path, args: dict) -> list[Transaction]:
|
|||||||
options["category"] = args["category"][0]
|
options["category"] = args["category"][0]
|
||||||
|
|
||||||
if options.get("additional_parser"):
|
if options.get("additional_parser"):
|
||||||
parser = getattr(import_module("pfbudget.input.parsers"), bank)
|
parser = getattr(import_module("pfbudget.extract.parsers"), bank)
|
||||||
transactions = parser(filename, bank, options).parse()
|
transactions = parser(filename, bank, options).parse()
|
||||||
else:
|
else:
|
||||||
transactions = Parser(filename, bank, options).parse()
|
transactions = Parser(filename, bank, options).parse()
|
||||||
142
pfbudget/extract/psd2.py
Normal file
142
pfbudget/extract/psd2.py
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
import datetime as dt
|
||||||
|
import json
|
||||||
|
import nordigen
|
||||||
|
import requests
|
||||||
|
import time
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
from typing import Sequence
|
||||||
|
|
||||||
|
import pfbudget.db.model as t
|
||||||
|
from pfbudget.utils.converters import convert
|
||||||
|
|
||||||
|
from .credentials import Credentials
|
||||||
|
from .exceptions import BankError, CredentialsError, ExtractError
|
||||||
|
from .extract import Extract
|
||||||
|
|
||||||
|
|
||||||
|
class PSD2Client(Extract):
|
||||||
|
redirect_url = "https://murta.dev"
|
||||||
|
|
||||||
|
def __init__(self, credentials: Credentials):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
if not credentials.valid():
|
||||||
|
raise CredentialsError
|
||||||
|
|
||||||
|
self._client = nordigen.NordigenClient(
|
||||||
|
secret_key=credentials.key, secret_id=credentials.id, timeout=5
|
||||||
|
)
|
||||||
|
|
||||||
|
if credentials.token:
|
||||||
|
self._client.token = credentials.token
|
||||||
|
|
||||||
|
self._start = dt.date.min
|
||||||
|
self._end = dt.date.max
|
||||||
|
|
||||||
|
def extract(self, banks: Sequence[t.Bank]) -> list[t.BankTransaction]:
|
||||||
|
transactions = []
|
||||||
|
if not banks or any(not b.nordigen for b in banks):
|
||||||
|
raise BankError
|
||||||
|
|
||||||
|
for bank in banks:
|
||||||
|
downloaded = None
|
||||||
|
try:
|
||||||
|
print(f"Downloading from {bank}...")
|
||||||
|
downloaded = self.download(bank.nordigen.requisition_id)
|
||||||
|
except requests.HTTPError as e:
|
||||||
|
print(f"There was an issue downloading from {bank.name} -> {e}")
|
||||||
|
raise ExtractError(e)
|
||||||
|
|
||||||
|
if downloaded:
|
||||||
|
self.dump(bank, downloaded)
|
||||||
|
|
||||||
|
converted = [
|
||||||
|
convert(t, bank) for t in downloaded["transactions"]["booked"]
|
||||||
|
]
|
||||||
|
|
||||||
|
transactions.extend(
|
||||||
|
[t for t in converted if self._start <= t.date <= self._end]
|
||||||
|
)
|
||||||
|
|
||||||
|
return sorted(transactions)
|
||||||
|
|
||||||
|
def download(self, requisition_id):
|
||||||
|
requisition = self._client.requisition.get_requisition_by_id(requisition_id)
|
||||||
|
print(requisition)
|
||||||
|
|
||||||
|
transactions = {}
|
||||||
|
for acc in requisition["accounts"]:
|
||||||
|
account = self._client.account_api(acc)
|
||||||
|
|
||||||
|
retries = 0
|
||||||
|
while retries < 3:
|
||||||
|
try:
|
||||||
|
downloaded = account.get_transactions()
|
||||||
|
break
|
||||||
|
except requests.ReadTimeout:
|
||||||
|
retries += 1
|
||||||
|
print(f"Request #{retries} timed-out, retrying in 1s")
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
if not downloaded:
|
||||||
|
print(f"Couldn't download transactions for {account}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
transactions.update(downloaded)
|
||||||
|
|
||||||
|
return transactions
|
||||||
|
|
||||||
|
def dump(self, bank, downloaded):
|
||||||
|
with open("json/" + bank.name + ".json", "w") as f:
|
||||||
|
json.dump(downloaded, f)
|
||||||
|
|
||||||
|
def generate_token(self):
|
||||||
|
self.token = self._client.generate_token()
|
||||||
|
print(f"New access token: {self.token}")
|
||||||
|
return self.token
|
||||||
|
|
||||||
|
def requisition(self, id: str, country: str = "PT"):
|
||||||
|
requisition = self._client.initialize_session(
|
||||||
|
redirect_uri=self.redirect_url,
|
||||||
|
institution_id=id,
|
||||||
|
reference_id=str(uuid.uuid4()),
|
||||||
|
)
|
||||||
|
return requisition.link, requisition.requisition_id
|
||||||
|
|
||||||
|
def country_banks(self, country: str):
|
||||||
|
return self._client.institution.get_institutions(country)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def start(self):
|
||||||
|
return self._start
|
||||||
|
|
||||||
|
@start.setter
|
||||||
|
def start(self, value):
|
||||||
|
self._start = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def end(self):
|
||||||
|
return self._end
|
||||||
|
|
||||||
|
@end.setter
|
||||||
|
def end(self, value):
|
||||||
|
self._end = value
|
||||||
|
|
||||||
|
# def __token(self):
|
||||||
|
# if token := os.environ.get("TOKEN"):
|
||||||
|
# return token
|
||||||
|
# else:
|
||||||
|
# token = self._client.generate_token()
|
||||||
|
# print(f"New access token: {token}")
|
||||||
|
# return token["access"]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def token(self):
|
||||||
|
return self._token
|
||||||
|
|
||||||
|
@token.setter
|
||||||
|
def token(self, value):
|
||||||
|
if self._token:
|
||||||
|
print("Replacing existing token with {value}")
|
||||||
|
self._token = value
|
||||||
@ -1,134 +0,0 @@
|
|||||||
import datetime as dt
|
|
||||||
import dotenv
|
|
||||||
import json
|
|
||||||
import nordigen
|
|
||||||
import os
|
|
||||||
import requests
|
|
||||||
import time
|
|
||||||
import uuid
|
|
||||||
|
|
||||||
import pfbudget.db.model as t
|
|
||||||
from pfbudget.utils.converters import convert
|
|
||||||
|
|
||||||
from .input import Input
|
|
||||||
|
|
||||||
dotenv.load_dotenv()
|
|
||||||
|
|
||||||
|
|
||||||
class NordigenInput(Input):
|
|
||||||
redirect_url = "https://murta.dev"
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__()
|
|
||||||
|
|
||||||
if not (key := os.environ.get("SECRET_KEY")) or not (
|
|
||||||
id := os.environ.get("SECRET_ID")
|
|
||||||
):
|
|
||||||
raise
|
|
||||||
|
|
||||||
self._client = nordigen.NordigenClient(
|
|
||||||
secret_key=key,
|
|
||||||
secret_id=id,
|
|
||||||
)
|
|
||||||
|
|
||||||
self._client.token = self.__token()
|
|
||||||
self._start = dt.date.min
|
|
||||||
self._end = dt.date.max
|
|
||||||
|
|
||||||
def parse(self) -> list[t.BankTransaction]:
|
|
||||||
transactions = []
|
|
||||||
assert len(self._banks) > 0
|
|
||||||
|
|
||||||
for bank in self._banks:
|
|
||||||
print(f"Downloading from {bank}...")
|
|
||||||
requisition = self.client.requisition.get_requisition_by_id(
|
|
||||||
bank.nordigen.requisition_id
|
|
||||||
)
|
|
||||||
|
|
||||||
print(requisition)
|
|
||||||
for acc in requisition["accounts"]:
|
|
||||||
account = self._client.account_api(acc)
|
|
||||||
|
|
||||||
retries = 0
|
|
||||||
downloaded = {}
|
|
||||||
while retries < 3:
|
|
||||||
try:
|
|
||||||
downloaded = account.get_transactions()
|
|
||||||
break
|
|
||||||
except requests.ReadTimeout:
|
|
||||||
retries += 1
|
|
||||||
print(f"Request #{retries} timed-out, retrying in 1s")
|
|
||||||
time.sleep(1)
|
|
||||||
except requests.HTTPError as e:
|
|
||||||
retries += 1
|
|
||||||
print(f"Request #{retries} failed with {e}, retrying in 1s")
|
|
||||||
time.sleep(1)
|
|
||||||
|
|
||||||
if not downloaded:
|
|
||||||
print(f"Couldn't download transactions for {account}")
|
|
||||||
continue
|
|
||||||
|
|
||||||
with open("json/" + bank.name + ".json", "w") as f:
|
|
||||||
json.dump(downloaded, f)
|
|
||||||
|
|
||||||
converted = [
|
|
||||||
convert(t, bank) for t in downloaded["transactions"]["booked"]
|
|
||||||
]
|
|
||||||
|
|
||||||
transactions.extend(
|
|
||||||
[t for t in converted if self._start <= t.date <= self._end]
|
|
||||||
)
|
|
||||||
|
|
||||||
return sorted(transactions)
|
|
||||||
|
|
||||||
def token(self):
|
|
||||||
token = self._client.generate_token()
|
|
||||||
print(f"New access token: {token}")
|
|
||||||
return token
|
|
||||||
|
|
||||||
def requisition(self, id: str, country: str = "PT"):
|
|
||||||
requisition = self._client.initialize_session(
|
|
||||||
redirect_uri=self.redirect_url,
|
|
||||||
institution_id=id,
|
|
||||||
reference_id=str(uuid.uuid4()),
|
|
||||||
)
|
|
||||||
return requisition.link, requisition.requisition_id
|
|
||||||
|
|
||||||
def country_banks(self, country: str):
|
|
||||||
return self._client.institution.get_institutions(country)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def client(self):
|
|
||||||
return self._client
|
|
||||||
|
|
||||||
@property
|
|
||||||
def banks(self):
|
|
||||||
return self._banks
|
|
||||||
|
|
||||||
@banks.setter
|
|
||||||
def banks(self, value):
|
|
||||||
self._banks = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def start(self):
|
|
||||||
return self._start
|
|
||||||
|
|
||||||
@start.setter
|
|
||||||
def start(self, value):
|
|
||||||
self._start = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def end(self):
|
|
||||||
return self._end
|
|
||||||
|
|
||||||
@end.setter
|
|
||||||
def end(self, value):
|
|
||||||
self._end = value
|
|
||||||
|
|
||||||
def __token(self):
|
|
||||||
if token := os.environ.get("TOKEN"):
|
|
||||||
return token
|
|
||||||
else:
|
|
||||||
token = self._client.generate_token()
|
|
||||||
print(f"New access token: {token}")
|
|
||||||
return token["access"]
|
|
||||||
436
poetry.lock
generated
436
poetry.lock
generated
@ -264,6 +264,73 @@ mypy = ["contourpy[bokeh]", "docutils-stubs", "mypy (==0.991)", "types-Pillow"]
|
|||||||
test = ["Pillow", "matplotlib", "pytest"]
|
test = ["Pillow", "matplotlib", "pytest"]
|
||||||
test-no-images = ["pytest"]
|
test-no-images = ["pytest"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "coverage"
|
||||||
|
version = "7.2.3"
|
||||||
|
description = "Code coverage measurement for Python"
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "coverage-7.2.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e58c0d41d336569d63d1b113bd573db8363bc4146f39444125b7f8060e4e04f5"},
|
||||||
|
{file = "coverage-7.2.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:344e714bd0fe921fc72d97404ebbdbf9127bac0ca1ff66d7b79efc143cf7c0c4"},
|
||||||
|
{file = "coverage-7.2.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:974bc90d6f6c1e59ceb1516ab00cf1cdfbb2e555795d49fa9571d611f449bcb2"},
|
||||||
|
{file = "coverage-7.2.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0743b0035d4b0e32bc1df5de70fba3059662ace5b9a2a86a9f894cfe66569013"},
|
||||||
|
{file = "coverage-7.2.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d0391fb4cfc171ce40437f67eb050a340fdbd0f9f49d6353a387f1b7f9dd4fa"},
|
||||||
|
{file = "coverage-7.2.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4a42e1eff0ca9a7cb7dc9ecda41dfc7cbc17cb1d02117214be0561bd1134772b"},
|
||||||
|
{file = "coverage-7.2.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:be19931a8dcbe6ab464f3339966856996b12a00f9fe53f346ab3be872d03e257"},
|
||||||
|
{file = "coverage-7.2.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:72fcae5bcac3333a4cf3b8f34eec99cea1187acd55af723bcbd559adfdcb5535"},
|
||||||
|
{file = "coverage-7.2.3-cp310-cp310-win32.whl", hash = "sha256:aeae2aa38395b18106e552833f2a50c27ea0000122bde421c31d11ed7e6f9c91"},
|
||||||
|
{file = "coverage-7.2.3-cp310-cp310-win_amd64.whl", hash = "sha256:83957d349838a636e768251c7e9979e899a569794b44c3728eaebd11d848e58e"},
|
||||||
|
{file = "coverage-7.2.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:dfd393094cd82ceb9b40df4c77976015a314b267d498268a076e940fe7be6b79"},
|
||||||
|
{file = "coverage-7.2.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:182eb9ac3f2b4874a1f41b78b87db20b66da6b9cdc32737fbbf4fea0c35b23fc"},
|
||||||
|
{file = "coverage-7.2.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bb1e77a9a311346294621be905ea8a2c30d3ad371fc15bb72e98bfcfae532df"},
|
||||||
|
{file = "coverage-7.2.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca0f34363e2634deffd390a0fef1aa99168ae9ed2af01af4a1f5865e362f8623"},
|
||||||
|
{file = "coverage-7.2.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:55416d7385774285b6e2a5feca0af9652f7f444a4fa3d29d8ab052fafef9d00d"},
|
||||||
|
{file = "coverage-7.2.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:06ddd9c0249a0546997fdda5a30fbcb40f23926df0a874a60a8a185bc3a87d93"},
|
||||||
|
{file = "coverage-7.2.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:fff5aaa6becf2c6a1699ae6a39e2e6fb0672c2d42eca8eb0cafa91cf2e9bd312"},
|
||||||
|
{file = "coverage-7.2.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ea53151d87c52e98133eb8ac78f1206498c015849662ca8dc246255265d9c3c4"},
|
||||||
|
{file = "coverage-7.2.3-cp311-cp311-win32.whl", hash = "sha256:8f6c930fd70d91ddee53194e93029e3ef2aabe26725aa3c2753df057e296b925"},
|
||||||
|
{file = "coverage-7.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:fa546d66639d69aa967bf08156eb8c9d0cd6f6de84be9e8c9819f52ad499c910"},
|
||||||
|
{file = "coverage-7.2.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b2317d5ed777bf5a033e83d4f1389fd4ef045763141d8f10eb09a7035cee774c"},
|
||||||
|
{file = "coverage-7.2.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be9824c1c874b73b96288c6d3de793bf7f3a597770205068c6163ea1f326e8b9"},
|
||||||
|
{file = "coverage-7.2.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2c3b2803e730dc2797a017335827e9da6da0e84c745ce0f552e66400abdfb9a1"},
|
||||||
|
{file = "coverage-7.2.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f69770f5ca1994cb32c38965e95f57504d3aea96b6c024624fdd5bb1aa494a1"},
|
||||||
|
{file = "coverage-7.2.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1127b16220f7bfb3f1049ed4a62d26d81970a723544e8252db0efde853268e21"},
|
||||||
|
{file = "coverage-7.2.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:aa784405f0c640940595fa0f14064d8e84aff0b0f762fa18393e2760a2cf5841"},
|
||||||
|
{file = "coverage-7.2.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:3146b8e16fa60427e03884301bf8209221f5761ac754ee6b267642a2fd354c48"},
|
||||||
|
{file = "coverage-7.2.3-cp37-cp37m-win32.whl", hash = "sha256:1fd78b911aea9cec3b7e1e2622c8018d51c0d2bbcf8faaf53c2497eb114911c1"},
|
||||||
|
{file = "coverage-7.2.3-cp37-cp37m-win_amd64.whl", hash = "sha256:0f3736a5d34e091b0a611964c6262fd68ca4363df56185902528f0b75dbb9c1f"},
|
||||||
|
{file = "coverage-7.2.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:981b4df72c93e3bc04478153df516d385317628bd9c10be699c93c26ddcca8ab"},
|
||||||
|
{file = "coverage-7.2.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c0045f8f23a5fb30b2eb3b8a83664d8dc4fb58faddf8155d7109166adb9f2040"},
|
||||||
|
{file = "coverage-7.2.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f760073fcf8f3d6933178d67754f4f2d4e924e321f4bb0dcef0424ca0215eba1"},
|
||||||
|
{file = "coverage-7.2.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c86bd45d1659b1ae3d0ba1909326b03598affbc9ed71520e0ff8c31a993ad911"},
|
||||||
|
{file = "coverage-7.2.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:172db976ae6327ed4728e2507daf8a4de73c7cc89796483e0a9198fd2e47b462"},
|
||||||
|
{file = "coverage-7.2.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d2a3a6146fe9319926e1d477842ca2a63fe99af5ae690b1f5c11e6af074a6b5c"},
|
||||||
|
{file = "coverage-7.2.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:f649dd53833b495c3ebd04d6eec58479454a1784987af8afb77540d6c1767abd"},
|
||||||
|
{file = "coverage-7.2.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7c4ed4e9f3b123aa403ab424430b426a1992e6f4c8fd3cb56ea520446e04d152"},
|
||||||
|
{file = "coverage-7.2.3-cp38-cp38-win32.whl", hash = "sha256:eb0edc3ce9760d2f21637766c3aa04822030e7451981ce569a1b3456b7053f22"},
|
||||||
|
{file = "coverage-7.2.3-cp38-cp38-win_amd64.whl", hash = "sha256:63cdeaac4ae85a179a8d6bc09b77b564c096250d759eed343a89d91bce8b6367"},
|
||||||
|
{file = "coverage-7.2.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:20d1a2a76bb4eb00e4d36b9699f9b7aba93271c9c29220ad4c6a9581a0320235"},
|
||||||
|
{file = "coverage-7.2.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ea748802cc0de4de92ef8244dd84ffd793bd2e7be784cd8394d557a3c751e21"},
|
||||||
|
{file = "coverage-7.2.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21b154aba06df42e4b96fc915512ab39595105f6c483991287021ed95776d934"},
|
||||||
|
{file = "coverage-7.2.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd214917cabdd6f673a29d708574e9fbdb892cb77eb426d0eae3490d95ca7859"},
|
||||||
|
{file = "coverage-7.2.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c2e58e45fe53fab81f85474e5d4d226eeab0f27b45aa062856c89389da2f0d9"},
|
||||||
|
{file = "coverage-7.2.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:87ecc7c9a1a9f912e306997ffee020297ccb5ea388421fe62a2a02747e4d5539"},
|
||||||
|
{file = "coverage-7.2.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:387065e420aed3c71b61af7e82c7b6bc1c592f7e3c7a66e9f78dd178699da4fe"},
|
||||||
|
{file = "coverage-7.2.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ea3f5bc91d7d457da7d48c7a732beaf79d0c8131df3ab278e6bba6297e23c6c4"},
|
||||||
|
{file = "coverage-7.2.3-cp39-cp39-win32.whl", hash = "sha256:ae7863a1d8db6a014b6f2ff9c1582ab1aad55a6d25bac19710a8df68921b6e30"},
|
||||||
|
{file = "coverage-7.2.3-cp39-cp39-win_amd64.whl", hash = "sha256:3f04becd4fcda03c0160d0da9c8f0c246bc78f2f7af0feea1ec0930e7c93fa4a"},
|
||||||
|
{file = "coverage-7.2.3-pp37.pp38.pp39-none-any.whl", hash = "sha256:965ee3e782c7892befc25575fa171b521d33798132692df428a09efacaffe8d0"},
|
||||||
|
{file = "coverage-7.2.3.tar.gz", hash = "sha256:d298c2815fa4891edd9abe5ad6e6cb4207104c7dd9fd13aea3fdebf6f9b91259"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""}
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
toml = ["tomli"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cycler"
|
name = "cycler"
|
||||||
version = "0.11.0"
|
version = "0.11.0"
|
||||||
@ -276,6 +343,38 @@ files = [
|
|||||||
{file = "cycler-0.11.0.tar.gz", hash = "sha256:9c87405839a19696e837b3b818fed3f5f69f16f1eec1a1ad77e043dcea9c772f"},
|
{file = "cycler-0.11.0.tar.gz", hash = "sha256:9c87405839a19696e837b3b818fed3f5f69f16f1eec1a1ad77e043dcea9c772f"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "exceptiongroup"
|
||||||
|
version = "1.1.1"
|
||||||
|
description = "Backport of PEP 654 (exception groups)"
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "exceptiongroup-1.1.1-py3-none-any.whl", hash = "sha256:232c37c63e4f682982c8b6459f33a8981039e5fb8756b2074364e5055c498c9e"},
|
||||||
|
{file = "exceptiongroup-1.1.1.tar.gz", hash = "sha256:d484c3090ba2889ae2928419117447a14daf3c1231d5e30d0aae34f354f01785"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
test = ["pytest (>=6)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "flake8"
|
||||||
|
version = "6.0.0"
|
||||||
|
description = "the modular source code checker: pep8 pyflakes and co"
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8.1"
|
||||||
|
files = [
|
||||||
|
{file = "flake8-6.0.0-py2.py3-none-any.whl", hash = "sha256:3833794e27ff64ea4e9cf5d410082a8b97ff1a06c16aa3d2027339cd0f1195c7"},
|
||||||
|
{file = "flake8-6.0.0.tar.gz", hash = "sha256:c61007e76655af75e6785a931f452915b371dc48f56efd765247c8fe68f2b181"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
mccabe = ">=0.7.0,<0.8.0"
|
||||||
|
pycodestyle = ">=2.10.0,<2.11.0"
|
||||||
|
pyflakes = ">=3.0.0,<3.1.0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fonttools"
|
name = "fonttools"
|
||||||
version = "4.39.3"
|
version = "4.39.3"
|
||||||
@ -388,6 +487,18 @@ files = [
|
|||||||
{file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"},
|
{file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "iniconfig"
|
||||||
|
version = "2.0.0"
|
||||||
|
description = "brain-dead simple config-ini parsing"
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"},
|
||||||
|
{file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"},
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "kiwisolver"
|
name = "kiwisolver"
|
||||||
version = "1.4.4"
|
version = "1.4.4"
|
||||||
@ -468,53 +579,53 @@ files = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "matplotlib"
|
name = "matplotlib"
|
||||||
version = "3.6.1"
|
version = "3.7.1"
|
||||||
description = "Python plotting package"
|
description = "Python plotting package"
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
files = [
|
files = [
|
||||||
{file = "matplotlib-3.6.1-cp310-cp310-macosx_10_12_universal2.whl", hash = "sha256:7730e60e751cfcfe7fcb223cf03c0b979e9a064c239783ad37929d340a364cef"},
|
{file = "matplotlib-3.7.1-cp310-cp310-macosx_10_12_universal2.whl", hash = "sha256:95cbc13c1fc6844ab8812a525bbc237fa1470863ff3dace7352e910519e194b1"},
|
||||||
{file = "matplotlib-3.6.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:9dd40505ccc526acaf9a5db1b3029e237c64b58f1249983b28a291c2d6a1d0fa"},
|
{file = "matplotlib-3.7.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:08308bae9e91aca1ec6fd6dda66237eef9f6294ddb17f0d0b3c863169bf82353"},
|
||||||
{file = "matplotlib-3.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:85948b303534b69fd771126764cf883fde2af9b003eb5778cb60f3b46f93d3f6"},
|
{file = "matplotlib-3.7.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:544764ba51900da4639c0f983b323d288f94f65f4024dc40ecb1542d74dc0500"},
|
||||||
{file = "matplotlib-3.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71eced071825005011cdc64efbae2e2c76b8209c18aa487dedf69796fe4b1e40"},
|
{file = "matplotlib-3.7.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56d94989191de3fcc4e002f93f7f1be5da476385dde410ddafbb70686acf00ea"},
|
||||||
{file = "matplotlib-3.6.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:220314c2d6b9ca11570d7cd4b841c9f3137546f188336003b9fb8def4dcb804d"},
|
{file = "matplotlib-3.7.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e99bc9e65901bb9a7ce5e7bb24af03675cbd7c70b30ac670aa263240635999a4"},
|
||||||
{file = "matplotlib-3.6.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2cc5d726d4d42865f909c5208a7841109d76584950dd0587b01a77cc279d4ab7"},
|
{file = "matplotlib-3.7.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb7d248c34a341cd4c31a06fd34d64306624c8cd8d0def7abb08792a5abfd556"},
|
||||||
{file = "matplotlib-3.6.1-cp310-cp310-win32.whl", hash = "sha256:183bf3ac6a6023ee590aa4b677f391ceed65ec0d6b930901a8483c267bd12995"},
|
{file = "matplotlib-3.7.1-cp310-cp310-win32.whl", hash = "sha256:ce463ce590f3825b52e9fe5c19a3c6a69fd7675a39d589e8b5fbe772272b3a24"},
|
||||||
{file = "matplotlib-3.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:a68b91ac7e6bb26100a540a033f54c95fe06d9c0aa51312c2a52d07d1bde78f4"},
|
{file = "matplotlib-3.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:3d7bc90727351fb841e4d8ae620d2d86d8ed92b50473cd2b42ce9186104ecbba"},
|
||||||
{file = "matplotlib-3.6.1-cp311-cp311-macosx_10_12_universal2.whl", hash = "sha256:4648f0d79a87bf50ee740058305c91091ee5e1fbb71a7d2f5fe6707bfe328d1c"},
|
{file = "matplotlib-3.7.1-cp311-cp311-macosx_10_12_universal2.whl", hash = "sha256:770a205966d641627fd5cf9d3cb4b6280a716522cd36b8b284a8eb1581310f61"},
|
||||||
{file = "matplotlib-3.6.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:9403764017d20ff570f7ce973a8b9637f08a6109118f4e0ce6c7493d8849a0d3"},
|
{file = "matplotlib-3.7.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:f67bfdb83a8232cb7a92b869f9355d677bce24485c460b19d01970b64b2ed476"},
|
||||||
{file = "matplotlib-3.6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e4c8b5a243dd29d50289d694e931bd6cb6ae0b5bd654d12c647543d63862540c"},
|
{file = "matplotlib-3.7.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2bf092f9210e105f414a043b92af583c98f50050559616930d884387d0772aba"},
|
||||||
{file = "matplotlib-3.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1effccef0cea2d4da9feeed22079adf6786f92c800a7d0d2ef2104318a1c66c"},
|
{file = "matplotlib-3.7.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89768d84187f31717349c6bfadc0e0d8c321e8eb34522acec8a67b1236a66332"},
|
||||||
{file = "matplotlib-3.6.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8dc25473319afabe49150267e54648ac559c33b0fc2a80c8caecfbbc2948a820"},
|
{file = "matplotlib-3.7.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83111e6388dec67822e2534e13b243cc644c7494a4bb60584edbff91585a83c6"},
|
||||||
{file = "matplotlib-3.6.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47cb088bbce82ae9fc2edf3c25e56a5c6142ce2553fea2b781679f960a70c207"},
|
{file = "matplotlib-3.7.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a867bf73a7eb808ef2afbca03bcdb785dae09595fbe550e1bab0cd023eba3de0"},
|
||||||
{file = "matplotlib-3.6.1-cp311-cp311-win32.whl", hash = "sha256:4d3b0e0a4611bd22065bbf47e9b2f689ac9e575bcb850a9f0ae2bbed75cab956"},
|
{file = "matplotlib-3.7.1-cp311-cp311-win32.whl", hash = "sha256:fbdeeb58c0cf0595efe89c05c224e0a502d1aa6a8696e68a73c3efc6bc354304"},
|
||||||
{file = "matplotlib-3.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:e3c116e779fbbf421a9e4d3060db259a9bb486d98f4e3c5a0877c599bd173582"},
|
{file = "matplotlib-3.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:c0bd19c72ae53e6ab979f0ac6a3fafceb02d2ecafa023c5cca47acd934d10be7"},
|
||||||
{file = "matplotlib-3.6.1-cp38-cp38-macosx_10_12_universal2.whl", hash = "sha256:565f514dec81a41cbed10eb6011501879695087fc2787fb89423a466508abbbd"},
|
{file = "matplotlib-3.7.1-cp38-cp38-macosx_10_12_universal2.whl", hash = "sha256:6eb88d87cb2c49af00d3bbc33a003f89fd9f78d318848da029383bfc08ecfbfb"},
|
||||||
{file = "matplotlib-3.6.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:05e86446562063d6186ff6d700118c0dbd5dccc403a6187351ee526c48878f10"},
|
{file = "matplotlib-3.7.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:cf0e4f727534b7b1457898c4f4ae838af1ef87c359b76dcd5330fa31893a3ac7"},
|
||||||
{file = "matplotlib-3.6.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8245e85fd793f58edf29b8a9e3be47e8ecf76ea1a1e8240545f2746181ca5787"},
|
{file = "matplotlib-3.7.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:46a561d23b91f30bccfd25429c3c706afe7d73a5cc64ef2dfaf2b2ac47c1a5dc"},
|
||||||
{file = "matplotlib-3.6.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1e2c75d5d1ff6b7ef9870360bfa23bea076b8dc0945a60d19453d7619ed9ea8f"},
|
{file = "matplotlib-3.7.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:8704726d33e9aa8a6d5215044b8d00804561971163563e6e6591f9dcf64340cc"},
|
||||||
{file = "matplotlib-3.6.1-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c9756a8e69f6e1f76d47eb42132175b6814da1fbeae0545304c6d0fc2aae252a"},
|
{file = "matplotlib-3.7.1-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4cf327e98ecf08fcbb82685acaf1939d3338548620ab8dfa02828706402c34de"},
|
||||||
{file = "matplotlib-3.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f5788168da2661b42f7468063b725cc73fdbeeb80f2704cb2d8c415e9a57c50"},
|
{file = "matplotlib-3.7.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:617f14ae9d53292ece33f45cba8503494ee199a75b44de7717964f70637a36aa"},
|
||||||
{file = "matplotlib-3.6.1-cp38-cp38-win32.whl", hash = "sha256:0bab7564aafd5902128d54b68dca04f5755413fb6b502100bb0235a545882c48"},
|
{file = "matplotlib-3.7.1-cp38-cp38-win32.whl", hash = "sha256:7c9a4b2da6fac77bcc41b1ea95fadb314e92508bf5493ceff058e727e7ecf5b0"},
|
||||||
{file = "matplotlib-3.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:3c53486278a0629fd892783271dc994b962fba8dfe207445d039e14f1928ea46"},
|
{file = "matplotlib-3.7.1-cp38-cp38-win_amd64.whl", hash = "sha256:14645aad967684e92fc349493fa10c08a6da514b3d03a5931a1bac26e6792bd1"},
|
||||||
{file = "matplotlib-3.6.1-cp39-cp39-macosx_10_12_universal2.whl", hash = "sha256:27337bcb38d5db7430c14f350924542d75416ec1546d5d9d9f39b362b71db3fb"},
|
{file = "matplotlib-3.7.1-cp39-cp39-macosx_10_12_universal2.whl", hash = "sha256:81a6b377ea444336538638d31fdb39af6be1a043ca5e343fe18d0f17e098770b"},
|
||||||
{file = "matplotlib-3.6.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:fad858519bd6d52dbfeebdbe04d00dd8e932ed436f1c535e61bcc970a96c11e4"},
|
{file = "matplotlib-3.7.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:28506a03bd7f3fe59cd3cd4ceb2a8d8a2b1db41afede01f66c42561b9be7b4b7"},
|
||||||
{file = "matplotlib-3.6.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4a3d903588b519b38ed085d0ae762a1dcd4b70164617292175cfd91b90d6c415"},
|
{file = "matplotlib-3.7.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8c587963b85ce41e0a8af53b9b2de8dddbf5ece4c34553f7bd9d066148dc719c"},
|
||||||
{file = "matplotlib-3.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87bdbd37d0a41e025879863fe9b17bab15c0421313bc33e77e5e1aa54215c9c5"},
|
{file = "matplotlib-3.7.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8bf26ade3ff0f27668989d98c8435ce9327d24cffb7f07d24ef609e33d582439"},
|
||||||
{file = "matplotlib-3.6.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e632f66218811d4cf8b7a2a649e25ec15406c3c498f72d19e2bcf8377f38445d"},
|
{file = "matplotlib-3.7.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:def58098f96a05f90af7e92fd127d21a287068202aa43b2a93476170ebd99e87"},
|
||||||
{file = "matplotlib-3.6.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ddd58324dc9a77e2e56d7b7aea7dbd0575b6f7cd1333c3ca9d388ac70978344"},
|
{file = "matplotlib-3.7.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f883a22a56a84dba3b588696a2b8a1ab0d2c3d41be53264115c71b0a942d8fdb"},
|
||||||
{file = "matplotlib-3.6.1-cp39-cp39-win32.whl", hash = "sha256:12ab21d0cad122f5b23688d453a0280676e7c42f634f0dbd093d15d42d142b1f"},
|
{file = "matplotlib-3.7.1-cp39-cp39-win32.whl", hash = "sha256:4f99e1b234c30c1e9714610eb0c6d2f11809c9c78c984a613ae539ea2ad2eb4b"},
|
||||||
{file = "matplotlib-3.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:563896ba269324872ace436a57775dcc8322678a9496b28a8c25cdafa5ec2b92"},
|
{file = "matplotlib-3.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:3ba2af245e36990facf67fde840a760128ddd71210b2ab6406e640188d69d136"},
|
||||||
{file = "matplotlib-3.6.1-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:52935b7d4ccbf0dbc9cf454dbb10ca99c11cbe8da9467596b96e5e21fd4dfc5c"},
|
{file = "matplotlib-3.7.1-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3032884084f541163f295db8a6536e0abb0db464008fadca6c98aaf84ccf4717"},
|
||||||
{file = "matplotlib-3.6.1-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:87027ff7b2edeb14476900261ef04d4beae949e1dfa0a3eb3ad6a6efbf9d0e1d"},
|
{file = "matplotlib-3.7.1-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3a2cb34336110e0ed8bb4f650e817eed61fa064acbefeb3591f1b33e3a84fd96"},
|
||||||
{file = "matplotlib-3.6.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4de03085afb3b80fab341afaf8e60dfe06ce439b6dfed55d657cf34a7bc3c40"},
|
{file = "matplotlib-3.7.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b867e2f952ed592237a1828f027d332d8ee219ad722345b79a001f49df0936eb"},
|
||||||
{file = "matplotlib-3.6.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:b53387d4e59432ff221540a4ffb5ee9669c69417805e4faf0148a00d701c61f9"},
|
{file = "matplotlib-3.7.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:57bfb8c8ea253be947ccb2bc2d1bb3862c2bccc662ad1b4626e1f5e004557042"},
|
||||||
{file = "matplotlib-3.6.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:02561141c434154f7bae8e5449909d152367cb40aa57bfb2a27f2748b9c5f95f"},
|
{file = "matplotlib-3.7.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:438196cdf5dc8d39b50a45cb6e3f6274edbcf2254f85fa9b895bf85851c3a613"},
|
||||||
{file = "matplotlib-3.6.1-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d0161ebf87518ecfe0980c942d5f0d5df0e080c1746ebaab2027a969967014b7"},
|
{file = "matplotlib-3.7.1-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:21e9cff1a58d42e74d01153360de92b326708fb205250150018a52c70f43c290"},
|
||||||
{file = "matplotlib-3.6.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2469f57e4c5cc0e85eddc7b30995ea9c404a78c0b1856da75d1a5887156ca350"},
|
{file = "matplotlib-3.7.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75d4725d70b7c03e082bbb8a34639ede17f333d7247f56caceb3801cb6ff703d"},
|
||||||
{file = "matplotlib-3.6.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:5f97141e05baf160c3ec125f06ceb2a44c9bb62f42fcb8ee1c05313c73e99432"},
|
{file = "matplotlib-3.7.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:97cc368a7268141afb5690760921765ed34867ffb9655dd325ed207af85c7529"},
|
||||||
{file = "matplotlib-3.6.1.tar.gz", hash = "sha256:e2d1b7225666f7e1bcc94c0bc9c587a82e3e8691da4757e357e5c2515222ee37"},
|
{file = "matplotlib-3.7.1.tar.gz", hash = "sha256:7b73305f25eab4541bd7ee0b96d87e53ae9c9f1823be5659b806cd85786fe882"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
@ -522,12 +633,24 @@ contourpy = ">=1.0.1"
|
|||||||
cycler = ">=0.10"
|
cycler = ">=0.10"
|
||||||
fonttools = ">=4.22.0"
|
fonttools = ">=4.22.0"
|
||||||
kiwisolver = ">=1.0.1"
|
kiwisolver = ">=1.0.1"
|
||||||
numpy = ">=1.19"
|
numpy = ">=1.20"
|
||||||
packaging = ">=20.0"
|
packaging = ">=20.0"
|
||||||
pillow = ">=6.2.0"
|
pillow = ">=6.2.0"
|
||||||
pyparsing = ">=2.2.1"
|
pyparsing = ">=2.3.1"
|
||||||
python-dateutil = ">=2.7"
|
python-dateutil = ">=2.7"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mccabe"
|
||||||
|
version = "0.7.0"
|
||||||
|
description = "McCabe checker, plugin for flake8"
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.6"
|
||||||
|
files = [
|
||||||
|
{file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"},
|
||||||
|
{file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"},
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mypy-extensions"
|
name = "mypy-extensions"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
@ -542,14 +665,14 @@ files = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nordigen"
|
name = "nordigen"
|
||||||
version = "1.3.0"
|
version = "1.3.1"
|
||||||
description = "Python client for Nordigen API"
|
description = "Python client for Nordigen API"
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8,<4.0"
|
python-versions = ">=3.8,<4.0"
|
||||||
files = [
|
files = [
|
||||||
{file = "nordigen-1.3.0-py3-none-any.whl", hash = "sha256:a2c7f864d8cb06f2f3604d8dc10489b0e2084eec3b9627f668f6cc97066c4285"},
|
{file = "nordigen-1.3.1-py3-none-any.whl", hash = "sha256:ef3b7da95bfae22a9070f75f67a3680ca2392cd1fd4fec0b1748928ca573cb75"},
|
||||||
{file = "nordigen-1.3.0.tar.gz", hash = "sha256:892835744e648ddb66bedc5da06c0981402c57be255dfc6c97734efe721a61cf"},
|
{file = "nordigen-1.3.1.tar.gz", hash = "sha256:89a6128ccc997448adaa84f720925094b01dcf1725eb04e3e9c54981b0a761a2"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
@ -713,6 +836,69 @@ files = [
|
|||||||
docs = ["furo (>=2022.12.7)", "proselint (>=0.13)", "sphinx (>=6.1.3)", "sphinx-autodoc-typehints (>=1.22,!=1.23.4)"]
|
docs = ["furo (>=2022.12.7)", "proselint (>=0.13)", "sphinx (>=6.1.3)", "sphinx-autodoc-typehints (>=1.22,!=1.23.4)"]
|
||||||
test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.2.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"]
|
test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.2.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pluggy"
|
||||||
|
version = "1.0.0"
|
||||||
|
description = "plugin and hook calling mechanisms for python"
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.6"
|
||||||
|
files = [
|
||||||
|
{file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"},
|
||||||
|
{file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
dev = ["pre-commit", "tox"]
|
||||||
|
testing = ["pytest", "pytest-benchmark"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "psycopg2"
|
||||||
|
version = "2.9.6"
|
||||||
|
description = "psycopg2 - Python-PostgreSQL Database Adapter"
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.6"
|
||||||
|
files = [
|
||||||
|
{file = "psycopg2-2.9.6-cp310-cp310-win32.whl", hash = "sha256:f7a7a5ee78ba7dc74265ba69e010ae89dae635eea0e97b055fb641a01a31d2b1"},
|
||||||
|
{file = "psycopg2-2.9.6-cp310-cp310-win_amd64.whl", hash = "sha256:f75001a1cbbe523e00b0ef896a5a1ada2da93ccd752b7636db5a99bc57c44494"},
|
||||||
|
{file = "psycopg2-2.9.6-cp311-cp311-win32.whl", hash = "sha256:53f4ad0a3988f983e9b49a5d9765d663bbe84f508ed655affdb810af9d0972ad"},
|
||||||
|
{file = "psycopg2-2.9.6-cp311-cp311-win_amd64.whl", hash = "sha256:b81fcb9ecfc584f661b71c889edeae70bae30d3ef74fa0ca388ecda50b1222b7"},
|
||||||
|
{file = "psycopg2-2.9.6-cp36-cp36m-win32.whl", hash = "sha256:11aca705ec888e4f4cea97289a0bf0f22a067a32614f6ef64fcf7b8bfbc53744"},
|
||||||
|
{file = "psycopg2-2.9.6-cp36-cp36m-win_amd64.whl", hash = "sha256:36c941a767341d11549c0fbdbb2bf5be2eda4caf87f65dfcd7d146828bd27f39"},
|
||||||
|
{file = "psycopg2-2.9.6-cp37-cp37m-win32.whl", hash = "sha256:869776630c04f335d4124f120b7fb377fe44b0a7645ab3c34b4ba42516951889"},
|
||||||
|
{file = "psycopg2-2.9.6-cp37-cp37m-win_amd64.whl", hash = "sha256:a8ad4a47f42aa6aec8d061fdae21eaed8d864d4bb0f0cade5ad32ca16fcd6258"},
|
||||||
|
{file = "psycopg2-2.9.6-cp38-cp38-win32.whl", hash = "sha256:2362ee4d07ac85ff0ad93e22c693d0f37ff63e28f0615a16b6635a645f4b9214"},
|
||||||
|
{file = "psycopg2-2.9.6-cp38-cp38-win_amd64.whl", hash = "sha256:d24ead3716a7d093b90b27b3d73459fe8cd90fd7065cf43b3c40966221d8c394"},
|
||||||
|
{file = "psycopg2-2.9.6-cp39-cp39-win32.whl", hash = "sha256:1861a53a6a0fd248e42ea37c957d36950da00266378746588eab4f4b5649e95f"},
|
||||||
|
{file = "psycopg2-2.9.6-cp39-cp39-win_amd64.whl", hash = "sha256:ded2faa2e6dfb430af7713d87ab4abbfc764d8d7fb73eafe96a24155f906ebf5"},
|
||||||
|
{file = "psycopg2-2.9.6.tar.gz", hash = "sha256:f15158418fd826831b28585e2ab48ed8df2d0d98f502a2b4fe619e7d5ca29011"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pycodestyle"
|
||||||
|
version = "2.10.0"
|
||||||
|
description = "Python style guide checker"
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.6"
|
||||||
|
files = [
|
||||||
|
{file = "pycodestyle-2.10.0-py2.py3-none-any.whl", hash = "sha256:8a4eaf0d0495c7395bdab3589ac2db602797d76207242c17d470186815706610"},
|
||||||
|
{file = "pycodestyle-2.10.0.tar.gz", hash = "sha256:347187bdb476329d98f695c213d7295a846d1152ff4fe9bacb8a9590b8ee7053"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pyflakes"
|
||||||
|
version = "3.0.1"
|
||||||
|
description = "passive checker of Python programs"
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.6"
|
||||||
|
files = [
|
||||||
|
{file = "pyflakes-3.0.1-py2.py3-none-any.whl", hash = "sha256:ec55bf7fe21fff7f1ad2f7da62363d749e2a470500eab1b555334b67aa1ef8cf"},
|
||||||
|
{file = "pyflakes-3.0.1.tar.gz", hash = "sha256:ec8b276a6b60bd80defed25add7e439881c19e64850afd9b346283d4165fd0fd"},
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pyparsing"
|
name = "pyparsing"
|
||||||
version = "3.0.9"
|
version = "3.0.9"
|
||||||
@ -728,6 +914,66 @@ files = [
|
|||||||
[package.extras]
|
[package.extras]
|
||||||
diagrams = ["jinja2", "railroad-diagrams"]
|
diagrams = ["jinja2", "railroad-diagrams"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pytest"
|
||||||
|
version = "7.3.0"
|
||||||
|
description = "pytest: simple powerful testing with Python"
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "pytest-7.3.0-py3-none-any.whl", hash = "sha256:933051fa1bfbd38a21e73c3960cebdad4cf59483ddba7696c48509727e17f201"},
|
||||||
|
{file = "pytest-7.3.0.tar.gz", hash = "sha256:58ecc27ebf0ea643ebfdf7fb1249335da761a00c9f955bcd922349bcb68ee57d"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
colorama = {version = "*", markers = "sys_platform == \"win32\""}
|
||||||
|
exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""}
|
||||||
|
iniconfig = "*"
|
||||||
|
packaging = "*"
|
||||||
|
pluggy = ">=0.12,<2.0"
|
||||||
|
tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""}
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pytest-cov"
|
||||||
|
version = "4.0.0"
|
||||||
|
description = "Pytest plugin for measuring coverage."
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.6"
|
||||||
|
files = [
|
||||||
|
{file = "pytest-cov-4.0.0.tar.gz", hash = "sha256:996b79efde6433cdbd0088872dbc5fb3ed7fe1578b68cdbba634f14bb8dd0470"},
|
||||||
|
{file = "pytest_cov-4.0.0-py3-none-any.whl", hash = "sha256:2feb1b751d66a8bd934e5edfa2e961d11309dc37b73b0eabe73b5945fee20f6b"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
coverage = {version = ">=5.2.1", extras = ["toml"]}
|
||||||
|
pytest = ">=4.6"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pytest-mock"
|
||||||
|
version = "3.10.0"
|
||||||
|
description = "Thin-wrapper around the mock package for easier use with pytest"
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "pytest-mock-3.10.0.tar.gz", hash = "sha256:fbbdb085ef7c252a326fd8cdcac0aa3b1333d8811f131bdcc701002e1be7ed4f"},
|
||||||
|
{file = "pytest_mock-3.10.0-py3-none-any.whl", hash = "sha256:f4c973eeae0282963eb293eb173ce91b091a79c1334455acfac9ddee8a1c784b"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
pytest = ">=5.0"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
dev = ["pre-commit", "pytest-asyncio", "tox"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "python-dateutil"
|
name = "python-dateutil"
|
||||||
version = "2.8.2"
|
version = "2.8.2"
|
||||||
@ -745,14 +991,14 @@ six = ">=1.5"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "python-dotenv"
|
name = "python-dotenv"
|
||||||
version = "0.21.0"
|
version = "1.0.0"
|
||||||
description = "Read key-value pairs from a .env file and set them as environment variables"
|
description = "Read key-value pairs from a .env file and set them as environment variables"
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.8"
|
||||||
files = [
|
files = [
|
||||||
{file = "python-dotenv-0.21.0.tar.gz", hash = "sha256:b77d08274639e3d34145dfa6c7008e66df0f04b7be7a75fd0d5292c191d79045"},
|
{file = "python-dotenv-1.0.0.tar.gz", hash = "sha256:a8df96034aae6d2d50a4ebe8216326c61c3eb64836776504fcca410e5937a3ba"},
|
||||||
{file = "python_dotenv-0.21.0-py3-none-any.whl", hash = "sha256:1684eb44636dd462b66c3ee016599815514527ad99965de77f43e0944634a7e5"},
|
{file = "python_dotenv-1.0.0-py3-none-any.whl", hash = "sha256:f5971a9226b701070a4bf2c38c89e5a3f0d64de8debda981d1db98583009122a"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
@ -844,53 +1090,53 @@ files = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sqlalchemy"
|
name = "sqlalchemy"
|
||||||
version = "2.0.0rc2"
|
version = "2.0.9"
|
||||||
description = "Database Abstraction Library"
|
description = "Database Abstraction Library"
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.7"
|
||||||
files = [
|
files = [
|
||||||
{file = "SQLAlchemy-2.0.0rc2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:19486279fe24297bf0743c1563735e7cab1f439f36acf165bd8e1be699fb3fcb"},
|
{file = "SQLAlchemy-2.0.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:734805708632e3965c2c40081f9a59263c29ffa27cba9b02d4d92dfd57ba869f"},
|
||||||
{file = "SQLAlchemy-2.0.0rc2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fdb8aeea859346dc38881ef74843e3bd7bbe743357746190feaeef17d0307586"},
|
{file = "SQLAlchemy-2.0.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8d3ece5960b3e821e43a4927cc851b6e84a431976d3ffe02aadb96519044807e"},
|
||||||
{file = "SQLAlchemy-2.0.0rc2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:102d7a5526ede7b727db59cbaaabc5abc7033e9951b179372163e7d75d7bb7b2"},
|
{file = "SQLAlchemy-2.0.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d118e233f416d713aac715e2c1101e17f91e696ff315fc9efbc75b70d11e740"},
|
||||||
{file = "SQLAlchemy-2.0.0rc2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ead16eda7747479f83965b684bfd2f104ddcae177599b1c2646a46afed29ec98"},
|
{file = "SQLAlchemy-2.0.9-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f005245e1cb9b8ca53df73ee85e029ac43155e062405015e49ec6187a2e3fb44"},
|
||||||
{file = "SQLAlchemy-2.0.0rc2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f708025fdfe134a46ee314dbaabeb65eaf5a3844b911d84d29346105f5f117ac"},
|
{file = "SQLAlchemy-2.0.9-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:34eb96c1de91d8f31e988302243357bef3f7785e1b728c7d4b98bd0c117dafeb"},
|
||||||
{file = "SQLAlchemy-2.0.0rc2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9799f2e74fc2757fea779d049debb52463ac39201e5f601a51ca8369d8684737"},
|
{file = "SQLAlchemy-2.0.9-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7e472e9627882f2d75b87ff91c5a2bc45b31a226efc7cc0a054a94fffef85862"},
|
||||||
{file = "SQLAlchemy-2.0.0rc2-cp310-cp310-win32.whl", hash = "sha256:fb400e9f5b5a4ed10a120b22d13f25f7ae102cb7d2c950b61e219c8770bf7320"},
|
{file = "SQLAlchemy-2.0.9-cp310-cp310-win32.whl", hash = "sha256:0a865b5ec4ba24f57c33b633b728e43fde77b968911a6046443f581b25d29dd9"},
|
||||||
{file = "SQLAlchemy-2.0.0rc2-cp310-cp310-win_amd64.whl", hash = "sha256:4879b419b0f6377ba94db32547fc061534a50f52bcb8c984165bf01a0cb9e2aa"},
|
{file = "SQLAlchemy-2.0.9-cp310-cp310-win_amd64.whl", hash = "sha256:6e84ab63d25d8564d7a8c05dc080659931a459ee27f6ed1cf4c91f292d184038"},
|
||||||
{file = "SQLAlchemy-2.0.0rc2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b7a077307953d34af3c29aad26cecfcd7e1d95f5220f10e37fe7c3af8dc67f7c"},
|
{file = "SQLAlchemy-2.0.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:db4bd1c4792da753f914ff0b688086b9a8fd78bb9bc5ae8b6d2e65f176b81eb9"},
|
||||||
{file = "SQLAlchemy-2.0.0rc2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:166b043798500994ddfffabd88b6f1a0bbb4401f3d15c24be0c5373b11eef03d"},
|
{file = "SQLAlchemy-2.0.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ad5363a1c65fde7b7466769d4261126d07d872fc2e816487ae6cec93da604b6b"},
|
||||||
{file = "SQLAlchemy-2.0.0rc2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fe17ecaaeb93cf7033b0cbb82bd58abdb2539af0863dedf4fea8a0049b7e37e"},
|
{file = "SQLAlchemy-2.0.9-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebc4eeb1737a5a9bdb0c24f4c982319fa6edd23cdee27180978c29cbb026f2bd"},
|
||||||
{file = "SQLAlchemy-2.0.0rc2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97db2b8aebd518e6ccaf2e64984f4ca9119e379e09f3b3109eda8e7b9b96039a"},
|
{file = "SQLAlchemy-2.0.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbda1da8d541904ba262825a833c9f619e93cb3fd1156be0a5e43cd54d588dcd"},
|
||||||
{file = "SQLAlchemy-2.0.0rc2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1efcd6ea9bc2d3721f2033eb22712895d360b7f1f5ad002fdcf7cb56282a5cea"},
|
{file = "SQLAlchemy-2.0.9-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d5327f54a9c39e7871fc532639616f3777304364a0bb9b89d6033ad34ef6c5f8"},
|
||||||
{file = "SQLAlchemy-2.0.0rc2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:39a8024f4a54d20144ce3fb3d71d3b1435e99473fdbc558935ae3d86ec46e5d9"},
|
{file = "SQLAlchemy-2.0.9-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ac6a0311fb21a99855953f84c43fcff4bdca27a2ffcc4f4d806b26b54b5cddc9"},
|
||||||
{file = "SQLAlchemy-2.0.0rc2-cp311-cp311-win32.whl", hash = "sha256:b6a8f837480f5dc5554a1251a24551e48a7bef6fffd8a713d90b4a324e557f3e"},
|
{file = "SQLAlchemy-2.0.9-cp311-cp311-win32.whl", hash = "sha256:d209594e68bec103ad5243ecac1b40bf5770c9ebf482df7abf175748a34f4853"},
|
||||||
{file = "SQLAlchemy-2.0.0rc2-cp311-cp311-win_amd64.whl", hash = "sha256:3737570ae5ac54ea44befaf8fbd74379c3568c7d61248323afe686a02b265566"},
|
{file = "SQLAlchemy-2.0.9-cp311-cp311-win_amd64.whl", hash = "sha256:865392a50a721445156809c1a6d6ab6437be70c1c2599f591a8849ed95d3c693"},
|
||||||
{file = "SQLAlchemy-2.0.0rc2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:50407532b9a14d86df90caf53c1c13602dabcf00ea1793572747da539ff6844b"},
|
{file = "SQLAlchemy-2.0.9-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0b49f1f71d7a44329a43d3edd38cc5ee4c058dfef4487498393d16172007954b"},
|
||||||
{file = "SQLAlchemy-2.0.0rc2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7d70f2599945f231cc512c5b9ee03dd10050ba0a505d0b07a3266f46e17acf7"},
|
{file = "SQLAlchemy-2.0.9-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4a019f723b6c1e6b3781be00fb9e0844bc6156f9951c836ff60787cc3938d76"},
|
||||||
{file = "SQLAlchemy-2.0.0rc2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e569957ab5e14a1ef1f3e61930001653822cb2be1233c25d7446aa4767fb1cd"},
|
{file = "SQLAlchemy-2.0.9-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9838bd247ee42eb74193d865e48dd62eb50e45e3fdceb0fdef3351133ee53dcf"},
|
||||||
{file = "SQLAlchemy-2.0.0rc2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:9de34e0433dff963e28e2be3049dad2957d62e1001c872f067ca20c14de07730"},
|
{file = "SQLAlchemy-2.0.9-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:78612edf4ba50d407d0eb3a64e9ec76e6efc2b5d9a5c63415d53e540266a230a"},
|
||||||
{file = "SQLAlchemy-2.0.0rc2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:3fa701fad6a5a05ab95d9470b1f08e5b90e2f8c900d05a9a5dcd47df4d675c89"},
|
{file = "SQLAlchemy-2.0.9-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:f61ab84956dc628c8dfe9d105b6aec38afb96adae3e5e7da6085b583ff6ea789"},
|
||||||
{file = "SQLAlchemy-2.0.0rc2-cp37-cp37m-win32.whl", hash = "sha256:df0d99a2a9ad28684f9c79567347b4b1477936793cc5e584edc576c7e56f92ed"},
|
{file = "SQLAlchemy-2.0.9-cp37-cp37m-win32.whl", hash = "sha256:07950fc82f844a2de67ddb4e535f29b65652b4d95e8b847823ce66a6d540a41d"},
|
||||||
{file = "SQLAlchemy-2.0.0rc2-cp37-cp37m-win_amd64.whl", hash = "sha256:74de517b7d267c473c3630a60ff841003d6244df9062cf1e8e3a6b3b8ebd1eac"},
|
{file = "SQLAlchemy-2.0.9-cp37-cp37m-win_amd64.whl", hash = "sha256:e62c4e762d6fd2901692a093f208a6a6575b930e9458ad58c2a7f080dd6132da"},
|
||||||
{file = "SQLAlchemy-2.0.0rc2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:98a573acae9eecc50c176f2dc4a8c22d325f3cf3869cc7197e16c9ff234ff8d8"},
|
{file = "SQLAlchemy-2.0.9-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b3e5864eba71a3718236a120547e52c8da2ccb57cc96cecd0480106a0c799c92"},
|
||||||
{file = "SQLAlchemy-2.0.0rc2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0201faafc243c63566c6122425f0aff451152a05aefef18ff7a3bad418324064"},
|
{file = "SQLAlchemy-2.0.9-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1d06e119cf79a3d80ab069f064a07152eb9ba541d084bdaee728d8a6f03fd03d"},
|
||||||
{file = "SQLAlchemy-2.0.0rc2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c7eaf3f238c65b026083d5ee6b1f3de7e542ebd73268042cae6126b5482c86c"},
|
{file = "SQLAlchemy-2.0.9-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee2946042cc7851842d7a086a92b9b7b494cbe8c3e7e4627e27bc912d3a7655e"},
|
||||||
{file = "SQLAlchemy-2.0.0rc2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2c7963c2e2946e54543b3d1176b715f84d7fa4ef59dce5e8e59f545ae1717dd"},
|
{file = "SQLAlchemy-2.0.9-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13f984a190d249769a050634b248aef8991acc035e849d02b634ea006c028fa8"},
|
||||||
{file = "SQLAlchemy-2.0.0rc2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:66b01bfa3e6fce88cb9b3ff839a4cc0477bc81361e14e8ae91213737aa3a2637"},
|
{file = "SQLAlchemy-2.0.9-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e4780be0f19e5894c17f75fc8de2fe1ae233ab37827125239ceb593c6f6bd1e2"},
|
||||||
{file = "SQLAlchemy-2.0.0rc2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d414757c6537fdb0d6a03969286471c7ce4f1705de64af1eb5d469db2f30079b"},
|
{file = "SQLAlchemy-2.0.9-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:68ed381bc340b4a3d373dbfec1a8b971f6350139590c4ca3cb722fdb50035777"},
|
||||||
{file = "SQLAlchemy-2.0.0rc2-cp38-cp38-win32.whl", hash = "sha256:1ec2599c5836d516dc25984539921b9a1abfe05c30d8f6098e0515b0d629c17a"},
|
{file = "SQLAlchemy-2.0.9-cp38-cp38-win32.whl", hash = "sha256:aa5c270ece17c0c0e0a38f2530c16b20ea05d8b794e46c79171a86b93b758891"},
|
||||||
{file = "SQLAlchemy-2.0.0rc2-cp38-cp38-win_amd64.whl", hash = "sha256:a78bcfdf0869233d76c7653110fc13c7b9131e6bbc40f59f889e4c89250db8a6"},
|
{file = "SQLAlchemy-2.0.9-cp38-cp38-win_amd64.whl", hash = "sha256:1b69666e25cc03c602d9d3d460e1281810109e6546739187044fc256c67941ef"},
|
||||||
{file = "SQLAlchemy-2.0.0rc2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f2bfaf345cc9175e4c9bee89bc186c01aa76f2212268dc0ebcdd43ab894bb4ed"},
|
{file = "SQLAlchemy-2.0.9-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c6e27189ff9aebfb2c02fd252c629ea58657e7a5ff1a321b7fc9c2bf6dc0b5f3"},
|
||||||
{file = "SQLAlchemy-2.0.0rc2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:827db258aca75bc2e7a962daf061ebe0fd4694ac5724b7f1f5f34d2e45ddeb62"},
|
{file = "SQLAlchemy-2.0.9-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8239ce63a90007bce479adf5460d48c1adae4b933d8e39a4eafecfc084e503c"},
|
||||||
{file = "SQLAlchemy-2.0.0rc2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bf4aee946fd3d9d5464b2a48373725c81285e61bddce8242186ad333811c881"},
|
{file = "SQLAlchemy-2.0.9-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f759eccb66e6d495fb622eb7f4ac146ae674d829942ec18b7f5a35ddf029597"},
|
||||||
{file = "SQLAlchemy-2.0.0rc2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4305edc685fdb9b761dc7bfe5f7d02e0a9a3c7e5bdc5f507e10058e6228421cf"},
|
{file = "SQLAlchemy-2.0.9-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:246712af9fc761d6c13f4f065470982e175d902e77aa4218c9cb9fc9ff565a0c"},
|
||||||
{file = "SQLAlchemy-2.0.0rc2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:770d9c8f98f5d8e36fa43f6bb59450aff300e23df2044441e9800c085706271c"},
|
{file = "SQLAlchemy-2.0.9-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6b72dccc5864ea95c93e0a9c4e397708917fb450f96737b4a8395d009f90b868"},
|
||||||
{file = "SQLAlchemy-2.0.0rc2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d9ce8c7dfbfc89e05c9e126069a0026a5098c93a613ca757a051e0e465067843"},
|
{file = "SQLAlchemy-2.0.9-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:93c78d42c14aa9a9e0866eacd5b48df40a50d0e2790ee377af7910d224afddcf"},
|
||||||
{file = "SQLAlchemy-2.0.0rc2-cp39-cp39-win32.whl", hash = "sha256:76dd1a635d05f40785be8d3b7688de93105fd83881f11604298175c8d3f490eb"},
|
{file = "SQLAlchemy-2.0.9-cp39-cp39-win32.whl", hash = "sha256:f49c5d3c070a72ecb96df703966c9678dda0d4cb2e2736f88d15f5e1203b4159"},
|
||||||
{file = "SQLAlchemy-2.0.0rc2-cp39-cp39-win_amd64.whl", hash = "sha256:655a1e23681f5339309a9caa1ff8cf83db26864537d62c833d8f523a4e6a0a01"},
|
{file = "SQLAlchemy-2.0.9-cp39-cp39-win_amd64.whl", hash = "sha256:4c3020afb144572c7bfcba9d7cce57ad42bff6e6115dffcfe2d4ae6d444a214f"},
|
||||||
{file = "SQLAlchemy-2.0.0rc2-py3-none-any.whl", hash = "sha256:39c7aaa77d4a70c2115f9ef7e7fe3ab79649e5e1370b29f21f17702c1512d43a"},
|
{file = "SQLAlchemy-2.0.9-py3-none-any.whl", hash = "sha256:e730603cae5747bc6d6dece98b45a57d647ed553c8d5ecef602697b1c1501cf2"},
|
||||||
{file = "SQLAlchemy-2.0.0rc2.tar.gz", hash = "sha256:b48e3eb80334c9b444ef1b857260942739e2174c55458790be518c2033355a4b"},
|
{file = "SQLAlchemy-2.0.9.tar.gz", hash = "sha256:95719215e3ec7337b9f57c3c2eda0e6a7619be194a5166c07c1e599f6afc20fa"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
@ -901,7 +1147,7 @@ typing-extensions = ">=4.2.0"
|
|||||||
aiomysql = ["aiomysql", "greenlet (!=0.4.17)"]
|
aiomysql = ["aiomysql", "greenlet (!=0.4.17)"]
|
||||||
aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing-extensions (!=3.10.0.1)"]
|
aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing-extensions (!=3.10.0.1)"]
|
||||||
asyncio = ["greenlet (!=0.4.17)"]
|
asyncio = ["greenlet (!=0.4.17)"]
|
||||||
asyncmy = ["asyncmy (>=0.2.3,!=0.2.4)", "greenlet (!=0.4.17)"]
|
asyncmy = ["asyncmy (>=0.2.3,!=0.2.4,!=0.2.6)", "greenlet (!=0.4.17)"]
|
||||||
mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2,!=1.1.5)"]
|
mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2,!=1.1.5)"]
|
||||||
mssql = ["pyodbc"]
|
mssql = ["pyodbc"]
|
||||||
mssql-pymssql = ["pymssql"]
|
mssql-pymssql = ["pymssql"]
|
||||||
@ -964,4 +1210,4 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
|
|||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.0"
|
lock-version = "2.0"
|
||||||
python-versions = "^3.10"
|
python-versions = "^3.10"
|
||||||
content-hash = "2a9319dc0d1ad4449b3d4f72d2f00b7c3af3cdc6ba14beffbf2e6e1ad8d1d643"
|
content-hash = "9d3d37a64fddc6654db1f2606001b81a2432e8f4f667f7146921942f59472614"
|
||||||
|
|||||||
@ -9,18 +9,26 @@ packages = [{include = "pfbudget"}]
|
|||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
python = "^3.10"
|
python = "^3.10"
|
||||||
codetiming = "1.4.0"
|
codetiming = "^1.4.0"
|
||||||
matplotlib = "3.6.1"
|
matplotlib = "^3.7.1"
|
||||||
nordigen = "1.3.0"
|
nordigen = "^1.3.1"
|
||||||
python-dateutil = "2.8.2"
|
psycopg2 = "^2.9.6"
|
||||||
python-dotenv = "0.21.0"
|
python-dateutil = "^2.8.2"
|
||||||
pyyaml = "6.0"
|
python-dotenv = "^1.0.0"
|
||||||
sqlalchemy = "2.0.0rc2"
|
pyyaml = "^6.0"
|
||||||
|
sqlalchemy = "^2.0.9"
|
||||||
|
|
||||||
|
|
||||||
[tool.poetry.group.dev.dependencies]
|
[tool.poetry.group.dev.dependencies]
|
||||||
black = "^23.3.0"
|
black = "^23.3.0"
|
||||||
|
flake8 = "^6.0.0"
|
||||||
|
pytest = "^7.3.0"
|
||||||
|
pytest-cov = "^4.0.0"
|
||||||
|
pytest-mock = "^3.10.0"
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["poetry-core"]
|
requires = ["poetry-core"]
|
||||||
build-backend = "poetry.core.masonry.api"
|
build-backend = "poetry.core.masonry.api"
|
||||||
|
|
||||||
|
[pytest]
|
||||||
|
mock_use_standalone_module = true
|
||||||
|
|||||||
82
tests/mocks/nordigen.py
Normal file
82
tests/mocks/nordigen.py
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
id = "3fa85f64-5717-4562-b3fc-2c963f66afa6"
|
||||||
|
|
||||||
|
accounts_id = {
|
||||||
|
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
|
||||||
|
"created": "2023-04-13T21:45:59.957Z",
|
||||||
|
"last_accessed": "2023-04-13T21:45:59.957Z",
|
||||||
|
"iban": "string",
|
||||||
|
"institution_id": "string",
|
||||||
|
"status": "DISCOVERED",
|
||||||
|
"owner_name": "string",
|
||||||
|
}
|
||||||
|
|
||||||
|
accounts_id_transactions = {
|
||||||
|
"transactions": {
|
||||||
|
"booked": [
|
||||||
|
{
|
||||||
|
"transactionId": "string",
|
||||||
|
"debtorName": "string",
|
||||||
|
"debtorAccount": {"iban": "string"},
|
||||||
|
"transactionAmount": {"currency": "string", "amount": "328.18"},
|
||||||
|
"bankTransactionCode": "string",
|
||||||
|
"bookingDate": "2023-01-14",
|
||||||
|
"valueDate": "2023-01-15",
|
||||||
|
"remittanceInformationUnstructured": "string",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"transactionId": "string",
|
||||||
|
"transactionAmount": {"currency": "string", "amount": "947.26"},
|
||||||
|
"bankTransactionCode": "string",
|
||||||
|
"bookingDate": "2023-02-14",
|
||||||
|
"valueDate": "2023-02-15",
|
||||||
|
"remittanceInformationUnstructured": "string",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"pending": [
|
||||||
|
{
|
||||||
|
"transactionAmount": {"currency": "string", "amount": "float"},
|
||||||
|
"valueDate": "2023-04-14",
|
||||||
|
"remittanceInformationUnstructured": "string",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
requisitions = {
|
||||||
|
"count": 123,
|
||||||
|
"next": "https://ob.nordigen.com/api/v2/requisitions/?limit=100&offset=0",
|
||||||
|
"previous": "https://ob.nordigen.com/api/v2/requisitions/?limit=100&offset=0",
|
||||||
|
"results": [
|
||||||
|
{
|
||||||
|
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
|
||||||
|
"created": "2023-04-13T21:43:45.027Z",
|
||||||
|
"redirect": "string",
|
||||||
|
"status": "CR",
|
||||||
|
"institution_id": "string",
|
||||||
|
"agreement": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
|
||||||
|
"reference": "string",
|
||||||
|
"accounts": ["3fa85f64-5717-4562-b3fc-2c963f66afa6"],
|
||||||
|
"user_language": "strin",
|
||||||
|
"link": "https://ob.nordigen.com/psd2/start/3fa85f64-5717-4562-b3fc-2c963f66afa6/{$INSTITUTION_ID}",
|
||||||
|
"ssn": "string",
|
||||||
|
"account_selection": False,
|
||||||
|
"redirect_immediate": False,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
requisitions_id = {
|
||||||
|
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
|
||||||
|
"created": "2023-04-13T21:45:12.336Z",
|
||||||
|
"redirect": "string",
|
||||||
|
"status": "CR",
|
||||||
|
"institution_id": "string",
|
||||||
|
"agreement": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
|
||||||
|
"reference": "string",
|
||||||
|
"accounts": ["3fa85f64-5717-4562-b3fc-2c963f66afa6"],
|
||||||
|
"user_language": "strin",
|
||||||
|
"link": "https://ob.nordigen.com/psd2/start/3fa85f64-5717-4562-b3fc-2c963f66afa6/{$INSTITUTION_ID}",
|
||||||
|
"ssn": "string",
|
||||||
|
"account_selection": False,
|
||||||
|
"redirect_immediate": False,
|
||||||
|
}
|
||||||
99
tests/test_psd2.py
Normal file
99
tests/test_psd2.py
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
import datetime as dt
|
||||||
|
from decimal import Decimal
|
||||||
|
import pytest
|
||||||
|
import requests
|
||||||
|
|
||||||
|
import mocks.nordigen as mock
|
||||||
|
|
||||||
|
from pfbudget.db.model import Bank, BankTransaction, Nordigen
|
||||||
|
from pfbudget.extract.credentials import Credentials
|
||||||
|
from pfbudget.extract.exceptions import BankError, CredentialsError
|
||||||
|
from pfbudget.extract.psd2 import PSD2Client
|
||||||
|
|
||||||
|
|
||||||
|
class MockGet:
|
||||||
|
def __init__(self, mock_exception=None):
|
||||||
|
self._status_code = 200
|
||||||
|
self._mock_exception = mock_exception
|
||||||
|
|
||||||
|
def __call__(self, *args, **kwargs):
|
||||||
|
if self._mock_exception:
|
||||||
|
raise self._mock_exception
|
||||||
|
|
||||||
|
self._headers = kwargs["headers"]
|
||||||
|
if "Authorization" not in self._headers or not self._headers["Authorization"]:
|
||||||
|
self._status_code = 401
|
||||||
|
|
||||||
|
self.url = kwargs["url"]
|
||||||
|
return self
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ok(self):
|
||||||
|
return True if self._status_code < 400 else False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def status_code(self):
|
||||||
|
return self._status_code
|
||||||
|
|
||||||
|
def json(self):
|
||||||
|
if self.url.endswith("accounts/" + mock.id + "/"):
|
||||||
|
return mock.accounts_id
|
||||||
|
elif self.url.endswith("accounts/" + mock.id + "/transactions/"):
|
||||||
|
return mock.accounts_id_transactions
|
||||||
|
elif self.url.endswith("requisitions/"):
|
||||||
|
return mock.requisitions
|
||||||
|
elif self.url.endswith("requisitions/" + mock.id + "/"):
|
||||||
|
return mock.requisitions_id
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def mock_requests(monkeypatch):
|
||||||
|
monkeypatch.setattr("requests.get", MockGet())
|
||||||
|
monkeypatch.delattr("requests.post")
|
||||||
|
monkeypatch.delattr("requests.put")
|
||||||
|
monkeypatch.delattr("requests.delete")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def client() -> PSD2Client:
|
||||||
|
credentials = Credentials("ID", "KEY", "TOKEN")
|
||||||
|
return PSD2Client(credentials)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def banks() -> list[Bank]:
|
||||||
|
bank = Bank("Bank#1", "", "")
|
||||||
|
bank.nordigen = Nordigen("", "", mock.id, False)
|
||||||
|
return [bank]
|
||||||
|
|
||||||
|
|
||||||
|
class TestExtractPSD2:
|
||||||
|
def test_empty_credentials(self):
|
||||||
|
cred = Credentials("", "")
|
||||||
|
with pytest.raises(CredentialsError):
|
||||||
|
PSD2Client(cred)
|
||||||
|
|
||||||
|
def test_empty_banks(self, client):
|
||||||
|
with pytest.raises(BankError):
|
||||||
|
client.extract([])
|
||||||
|
|
||||||
|
def test_no_psd2_bank(self, client):
|
||||||
|
with pytest.raises(BankError):
|
||||||
|
client.extract([Bank("", "", "")])
|
||||||
|
|
||||||
|
def test_timeout(self, monkeypatch, client, banks):
|
||||||
|
monkeypatch.setattr(
|
||||||
|
"requests.get", MockGet(mock_exception=requests.ReadTimeout)
|
||||||
|
)
|
||||||
|
with pytest.raises(requests.Timeout):
|
||||||
|
client.extract(banks)
|
||||||
|
|
||||||
|
def test_extract(self, client, banks):
|
||||||
|
assert client.extract(banks) == [
|
||||||
|
BankTransaction(
|
||||||
|
dt.date(2023, 1, 14), "string", Decimal("328.18"), "Bank#1"
|
||||||
|
),
|
||||||
|
BankTransaction(
|
||||||
|
dt.date(2023, 2, 14), "string", Decimal("947.26"), "Bank#1"
|
||||||
|
),
|
||||||
|
]
|
||||||
Loading…
x
Reference in New Issue
Block a user