├── .github └── workflows │ └── fava-review.yml ├── .history └── .github │ └── workflows │ ├── fava-review_20230201140208.yml │ └── fava-review_20230201172509.yml ├── LICENSE ├── README.md ├── example.beancount ├── fava_review ├── __init__.py ├── pivot_review.py └── templates │ ├── FavaReview.html │ └── __init__.py ├── poetry.lock ├── pyproject.toml ├── screenshot-date-and-account-filter.png ├── screenshot-sorting.png ├── screenshot.png └── test ├── __init__.py ├── conftest.py ├── example.beancount ├── gbp-first-operating-currency-and-usd-more-than-gbp.beancount ├── no-operating-currency-and-usd-more-than-gbp.beancount ├── test_fava_review_template.py └── test_pivot_review.py /.github/workflows/fava-review.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a variety of Python versions 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions 3 | 4 | name: Fava Review 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | strategy: 17 | fail-fast: false 18 | matrix: 19 | python-version: ["3.8", "3.9", "3.10", "3.11"] 20 | 21 | steps: 22 | - uses: actions/checkout@v2 23 | - name: Set up Python ${{ matrix.python-version }} 24 | uses: actions/setup-python@v2 25 | with: 26 | python-version: ${{ matrix.python-version }} 27 | - name: Set up Poetry 28 | uses: abatilo/actions-poetry@v2.1.4 29 | with: 30 | poetry-version: 1.3.2 31 | - name: Install dependencies 32 | run: | 33 | poetry install 34 | - name: Test 35 | run: | 36 | poetry run pytest test 37 | - name: Lint with flake8 38 | run: | 39 | # stop the build if there are Python syntax errors or undefined names 40 | poetry run flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics 41 | # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide 42 | poetry run flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics -------------------------------------------------------------------------------- /.history/.github/workflows/fava-review_20230201140208.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a variety of Python versions 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions 3 | 4 | name: Fava Review 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | strategy: 17 | fail-fast: false 18 | matrix: 19 | python-version: ["3.7", "3.8", "3.9", "3.10"] 20 | 21 | steps: 22 | - uses: actions/checkout@v2 23 | - name: Set up Python ${{ matrix.python-version }} 24 | uses: actions/setup-python@v2 25 | with: 26 | python-version: ${{ matrix.python-version }} 27 | - name: Set up Poetry 28 | uses: abatilo/actions-poetry@v2.1.4 29 | with: 30 | poetry-version: 1.1.8 31 | - name: Install dependencies 32 | run: | 33 | poetry install 34 | - name: Test 35 | run: | 36 | poetry run pytest test 37 | - name: Lint with flake8 38 | run: | 39 | # stop the build if there are Python syntax errors or undefined names 40 | poetry run flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics 41 | # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide 42 | poetry run flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics -------------------------------------------------------------------------------- /.history/.github/workflows/fava-review_20230201172509.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a variety of Python versions 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions 3 | 4 | name: Fava Review 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | strategy: 17 | fail-fast: false 18 | matrix: 19 | python-version: ["3.7", "3.8", "3.9", "3.10"] 20 | 21 | steps: 22 | - uses: actions/checkout@v2 23 | - name: Set up Python ${{ matrix.python-version }} 24 | uses: actions/setup-python@v2 25 | with: 26 | python-version: ${{ matrix.python-version }} 27 | - name: Set up Poetry 28 | uses: abatilo/actions-poetry@v2.1.4 29 | with: 30 | poetry-version: 1.3.2 31 | - name: Install dependencies 32 | run: | 33 | poetry install 34 | - name: Test 35 | run: | 36 | poetry run pytest test 37 | - name: Lint with flake8 38 | run: | 39 | # stop the build if there are Python syntax errors or undefined names 40 | poetry run flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics 41 | # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide 42 | poetry run flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Jakub Jamro 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![](https://img.shields.io/pypi/l/fava-review.svg)](https://pypi.python.org/pypi/fava-review) 2 | [![](https://img.shields.io/pypi/v/fava-review.svg)](https://pypi.python.org/pypi/fava-review) 3 | 4 | # The problem 5 | [Fava](https://beancount.github.io/fava/index.html) is a great UI for [Beancount](https://beancount.github.io/) however 6 | it only presents you with a snapshot of your finances for any given time period. Often it is useful to see ones' finances over a series of time periods to: 7 | - Identify trends across accounts (e.g. spending more on food or earning less on interest?) 8 | - Identify which accounts are the greatest contributors to our spending (e.g. the highest value accounts to try and reduce to save money) 9 | - And to spot ingest mistakes (e.g. spikes in bills when it should be flat?) 10 | 11 | # A solution 12 | 13 | Fava Review is an [extension](https://beancount.github.io/fava/api/fava.ext.html) for 14 | [fava](https://beancount.github.io/fava/index.html) which takes your finances and presents them over time. 15 | 16 | ![](screenshot.png) 17 | 18 | Fava review currently supports the following views: 19 | - Income Statement 20 | - Balance Sheet 21 | 22 | And the views allow filtering your accounts by the usual date, account and payee/tag filters. 23 | [Screenshot](screenshot-date-and-account-filter.png). 24 | 25 | And it's also possible to sort your data. 26 | [Screenshot](screenshot-sorting.png). 27 | 28 | # How it works 29 | fava-review is very simple in its implementation. It uses bean-query through 30 | [fava](https://beancount.github.io/fava/index.html)'s FavaLedger class and retrieves the monthly data for each account. 31 | This information is then feed into [petl](https://petl.readthedocs.io/en/stable/) and pivoted into the appropriate view. 32 | 33 | # How to install 34 | `pip install fava-review` 35 | 36 | ## Requirements 37 | - python >= 3.8 38 | - fava >= 1.25.1 39 | - beancount <3, >=2.3.0 40 | 41 | # Planned Features 42 | - Support all currencies within beancount file. 43 | - Percentage changes instead of absolute values. 44 | - Setting projections and tracking actuals against estimates. 45 | - [More](https://github.com/kubauk/fava-review/issues) 46 | -------------------------------------------------------------------------------- /fava_review/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import Optional, Any 4 | 5 | from fava.application import g 6 | from fava.core import FavaLedger 7 | from fava.ext import FavaExtensionBase 8 | 9 | from fava_review.pivot_review import PivotReview 10 | 11 | 12 | class FavaReview(FavaExtensionBase): 13 | report_title = "Fava Review" 14 | 15 | def __init__(self, ledger: FavaLedger, config: Optional[str] = None) -> None: 16 | super().__init__(ledger, config) 17 | self.review = PivotReview(self.ledger) 18 | 19 | def get_income_statement_report(self) -> list[dict[str, Any]]: 20 | try: 21 | return self.review.income_statement_by(g.interval) 22 | except Exception: 23 | raise 24 | 25 | def get_balance_sheet_report(self) -> list[dict[str, Any]]: 26 | try: 27 | return self.review.balance_sheet_by(g.interval) 28 | except Exception: 29 | raise 30 | 31 | def current_operating_currency(self) -> str: 32 | return self.review.current_operating_currency() 33 | -------------------------------------------------------------------------------- /fava_review/pivot_review.py: -------------------------------------------------------------------------------- 1 | from decimal import Decimal 2 | from typing import Optional, List 3 | 4 | import petl 5 | from fava.context import g 6 | from fava.core import FavaLedger 7 | from fava.util.date import Interval 8 | from petl import Table 9 | from petl.transform.joins import JoinView 10 | 11 | 12 | def bean_query_to_petl(rows, interval: Interval) -> petl.Table: 13 | record_date = { 14 | Interval.MONTH: lambda rec: '{}-{:02d}'.format(rec['year'], rec['month']), 15 | Interval.YEAR: lambda rec: '{}'.format(rec['year']), 16 | Interval.QUARTER: lambda rec: "".join(rec['quarter_date'].split('-')) 17 | } 18 | 19 | t = petl.fromdicts([nt._asdict() for nt in rows]) 20 | t = petl.fieldmap(t, {'date': record_date[interval], 21 | 'account': lambda rec: AccountName(rec['account'].strip()), 22 | 'total': lambda rec: rec['total'], 23 | 'currency': lambda rec: rec['currency']}) 24 | return t 25 | 26 | 27 | class AccountName(str): 28 | pass 29 | 30 | 31 | class PivotReview(object): 32 | CURRENCY_QUERY = 'SELECT currency, count(currency) GROUP BY currency ORDER BY count_currency DESC' 33 | 34 | def __init__(self, ledger: FavaLedger) -> None: 35 | self._ledger: FavaLedger = ledger 36 | self._current_currency: Optional[str] = None 37 | super().__init__() 38 | 39 | def income_statement_by(self, interval: Interval): 40 | return self.report_for(interval, ['Income', 'Expenses']) 41 | 42 | def balance_sheet_by(self, interval: Interval): 43 | return self.report_for(interval, ['Assets', 'Liabilities', 'Equity']) 44 | 45 | def report_for(self, interval, accounts): 46 | _, _, rows = self._ledger.query_shell.execute_query(query=self.review_query_for(interval, accounts), 47 | entries=g.filtered.entries) 48 | t = bean_query_to_petl(rows, interval) 49 | t = petl.pivot(t, 'account', 'date', 'total', sum, Decimal(0)) 50 | t = self.add_total_column_and_row(t) 51 | return list(petl.dicts(t)) 52 | 53 | def review_query_for(self, interval: Interval, accounts: List[str]): 54 | date_query = { 55 | Interval.MONTH: 'year, month', 56 | Interval.YEAR: 'year', 57 | Interval.QUARTER: 'quarter(date)' 58 | } 59 | 60 | quoted_accounts = " OR account ~ ".join([f'"{a}"' for a in accounts]) 61 | 62 | return f'SELECT {date_query[interval]}, root(account, 4) as account, ' \ 63 | f'sum(number) as total, currency ' \ 64 | f'WHERE (account ~ {quoted_accounts}) and currency = "{self.current_operating_currency()}" ' \ 65 | f'GROUP BY {date_query[interval]}, account, currency ' \ 66 | f'ORDER BY {date_query[interval]}, currency, account ' \ 67 | f'FLATTEN' 68 | 69 | @staticmethod 70 | def add_total_column_and_row(view: Table) -> Table: 71 | # Add total column 72 | view = PivotReview.add_totals_column(view) 73 | # Add total row 74 | view = petl.transpose(PivotReview.add_totals_column(petl.transpose(view))) 75 | return view 76 | 77 | @staticmethod 78 | def add_totals_column(table: Table) -> JoinView: 79 | def sum_row(key, rows): 80 | records = list(rows) 81 | total = Decimal(0) 82 | for record in records: 83 | for i in range(1, len(record)): 84 | total += record[i] 85 | return key, total 86 | 87 | return petl.join(table, petl.rowreduce(table, 'account', sum_row, ['account', 'total']), 'account') 88 | 89 | def current_operating_currency(self) -> str: 90 | if self._current_currency is None: 91 | self._current_currency = self.best_starting_currency() 92 | 93 | return self._current_currency 94 | 95 | def best_starting_currency(self) -> str: 96 | if len(self._ledger.options['operating_currency']) > 0: 97 | return self._ledger.options['operating_currency'][0] 98 | _, _, rows = self._ledger.query_shell.execute_query(query=self.CURRENCY_QUERY, 99 | entries=self._ledger.get_filtered().entries) 100 | return rows[0][0] 101 | -------------------------------------------------------------------------------- /fava_review/templates/FavaReview.html: -------------------------------------------------------------------------------- 1 | {% import 'macros/_account_macros.html' as account_macros %} 2 | {% macro report_table(review_data) %} 3 | 4 | 5 | 6 | {% for name in review_data[0].keys() %} 7 | {% set data = review_data[0][name] %} 8 | 9 | {% endfor %} 10 | 11 | 12 | 13 | {% for row in review_data[0:review_data|length-1] %} 14 | 15 | {% for data in row.values() %} 16 | 23 | {% endfor %} 24 | 25 | {% endfor %} 26 | 27 | 28 | 29 | {% for data in review_data[review_data|length-1].values() %} 30 | 31 | {% endfor %} 32 | 33 | 34 |
{{ name }}
17 | {% if 'AccountName' in data.__class__.__name__ %} 18 | {{ account_macros.account_name(extension.ledger, data) }} 19 | {% else %} 20 | {{ data }} 21 | {% endif %} 22 |
{{ data }}
35 | {% endmacro %} 36 | {% set view = request.args.get('view', 'income_statement') %} 37 | {% set views = { 38 | 'income_statement': ('Income Statement', extension.get_income_statement_report), 39 | 'balance_sheet': ('Balance Sheet', extension.get_balance_sheet_report) } %} 40 |
41 | {% for key, value in views.items() %} 42 |

43 | {% if not (view == key) %} 44 | {{ value[0] }} 45 | {% else %} 46 | {{ value[0] }} 47 | {% endif %} 48 |

49 | {% endfor %} 50 |
51 | 52 |

{{ views[view][0] }} Review - {{ extension.current_operating_currency() }}

53 | {{ report_table(views[view][1]()) }} 54 | -------------------------------------------------------------------------------- /fava_review/templates/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubauk/fava-review/dbc9089011a0072e8def81d3f78a90e2dfb2996e/fava_review/templates/__init__.py -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Poetry 1.4.1 and should not be changed by hand. 2 | 3 | [[package]] 4 | name = "atomicwrites" 5 | version = "1.4.1" 6 | description = "Atomic file writes." 7 | category = "main" 8 | optional = false 9 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 10 | files = [ 11 | {file = "atomicwrites-1.4.1.tar.gz", hash = "sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11"}, 12 | ] 13 | 14 | [[package]] 15 | name = "attrs" 16 | version = "22.2.0" 17 | description = "Classes Without Boilerplate" 18 | category = "main" 19 | optional = false 20 | python-versions = ">=3.6" 21 | files = [ 22 | {file = "attrs-22.2.0-py3-none-any.whl", hash = "sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836"}, 23 | {file = "attrs-22.2.0.tar.gz", hash = "sha256:c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99"}, 24 | ] 25 | 26 | [package.extras] 27 | cov = ["attrs[tests]", "coverage-enable-subprocess", "coverage[toml] (>=5.3)"] 28 | dev = ["attrs[docs,tests]"] 29 | docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope.interface"] 30 | tests = ["attrs[tests-no-zope]", "zope.interface"] 31 | tests-no-zope = ["cloudpickle", "cloudpickle", "hypothesis", "hypothesis", "mypy (>=0.971,<0.990)", "mypy (>=0.971,<0.990)", "pympler", "pympler", "pytest (>=4.3.0)", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-mypy-plugins", "pytest-xdist[psutil]", "pytest-xdist[psutil]"] 32 | 33 | [[package]] 34 | name = "babel" 35 | version = "2.12.1" 36 | description = "Internationalization utilities" 37 | category = "main" 38 | optional = false 39 | python-versions = ">=3.7" 40 | files = [ 41 | {file = "Babel-2.12.1-py3-none-any.whl", hash = "sha256:b4246fb7677d3b98f501a39d43396d3cafdc8eadb045f4a31be01863f655c610"}, 42 | {file = "Babel-2.12.1.tar.gz", hash = "sha256:cc2d99999cd01d44420ae725a21c9e3711b3aadc7976d6147f622d8581963455"}, 43 | ] 44 | 45 | [package.dependencies] 46 | pytz = {version = ">=2015.7", markers = "python_version < \"3.9\""} 47 | 48 | [[package]] 49 | name = "beancount" 50 | version = "2.3.5" 51 | description = "Command-line Double-Entry Accounting" 52 | category = "main" 53 | optional = false 54 | python-versions = ">=3.6" 55 | files = [ 56 | {file = "beancount-2.3.5-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:c633fb96dedfb7b0473f1a79d49c43c90501163bee5263acc8d6b26d43161a1b"}, 57 | {file = "beancount-2.3.5-cp36-cp36m-win_amd64.whl", hash = "sha256:4f7138db409a142583a481f23855029680284bef57fad3f2ed65c737014de834"}, 58 | {file = "beancount-2.3.5-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:09b735db1a6bf56094243c4aada6e102f0fac0a29adf3222f67a2e995ec24adb"}, 59 | {file = "beancount-2.3.5-cp37-cp37m-win_amd64.whl", hash = "sha256:ce967f1b32b48e31eac1d669072ef788813ef7edcd62651c6247f71b4bbf891f"}, 60 | {file = "beancount-2.3.5-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:3fb45bffd2ec846dba78476dde670979ff26de725d5b3fb3ea9222583aa0763b"}, 61 | {file = "beancount-2.3.5-cp38-cp38-win_amd64.whl", hash = "sha256:14631f3599778bca51af496223d44c82983eef40e90cd60a6046bd1d01b9a66f"}, 62 | {file = "beancount-2.3.5-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:66949af8cbe0035e6e08e1a66715ac210b36a7a2e7f24e59b91c163315962899"}, 63 | {file = "beancount-2.3.5-cp39-cp39-win_amd64.whl", hash = "sha256:bf41641339ea68af5edf434bda74bd694947cb8dfd4436d7f28da0e4de6683f2"}, 64 | {file = "beancount-2.3.5.tar.gz", hash = "sha256:14e35625a2e9cbd43cae6178da08cb3f1224f6261e541ca6726df35d98e9c36a"}, 65 | ] 66 | 67 | [package.dependencies] 68 | beautifulsoup4 = "*" 69 | bottle = "*" 70 | chardet = "*" 71 | google-api-python-client = "*" 72 | lxml = "*" 73 | ply = "*" 74 | pytest = "*" 75 | python-dateutil = "*" 76 | python-magic = {version = ">=0.4.12", markers = "sys_platform != \"win32\""} 77 | requests = "*" 78 | 79 | [[package]] 80 | name = "beautifulsoup4" 81 | version = "4.12.0" 82 | description = "Screen-scraping library" 83 | category = "main" 84 | optional = false 85 | python-versions = ">=3.6.0" 86 | files = [ 87 | {file = "beautifulsoup4-4.12.0-py3-none-any.whl", hash = "sha256:2130a5ad7f513200fae61a17abb5e338ca980fa28c439c0571014bc0217e9591"}, 88 | {file = "beautifulsoup4-4.12.0.tar.gz", hash = "sha256:c5fceeaec29d09c84970e47c65f2f0efe57872f7cff494c9691a26ec0ff13234"}, 89 | ] 90 | 91 | [package.dependencies] 92 | soupsieve = ">1.2" 93 | 94 | [package.extras] 95 | html5lib = ["html5lib"] 96 | lxml = ["lxml"] 97 | 98 | [[package]] 99 | name = "bottle" 100 | version = "0.12.25" 101 | description = "Fast and simple WSGI-framework for small web-applications." 102 | category = "main" 103 | optional = false 104 | python-versions = "*" 105 | files = [ 106 | {file = "bottle-0.12.25-py3-none-any.whl", hash = "sha256:d6f15f9d422670b7c073d63bd8d287b135388da187a0f3e3c19293626ce034ea"}, 107 | {file = "bottle-0.12.25.tar.gz", hash = "sha256:e1a9c94970ae6d710b3fb4526294dfeb86f2cb4a81eff3a4b98dc40fb0e5e021"}, 108 | ] 109 | 110 | [[package]] 111 | name = "cachetools" 112 | version = "5.3.0" 113 | description = "Extensible memoizing collections and decorators" 114 | category = "main" 115 | optional = false 116 | python-versions = "~=3.7" 117 | files = [ 118 | {file = "cachetools-5.3.0-py3-none-any.whl", hash = "sha256:429e1a1e845c008ea6c85aa35d4b98b65d6a9763eeef3e37e92728a12d1de9d4"}, 119 | {file = "cachetools-5.3.0.tar.gz", hash = "sha256:13dfddc7b8df938c21a940dfa6557ce6e94a2f1cdfa58eb90c805721d58f2c14"}, 120 | ] 121 | 122 | [[package]] 123 | name = "certifi" 124 | version = "2022.12.7" 125 | description = "Python package for providing Mozilla's CA Bundle." 126 | category = "main" 127 | optional = false 128 | python-versions = ">=3.6" 129 | files = [ 130 | {file = "certifi-2022.12.7-py3-none-any.whl", hash = "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"}, 131 | {file = "certifi-2022.12.7.tar.gz", hash = "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3"}, 132 | ] 133 | 134 | [[package]] 135 | name = "chardet" 136 | version = "5.1.0" 137 | description = "Universal encoding detector for Python 3" 138 | category = "main" 139 | optional = false 140 | python-versions = ">=3.7" 141 | files = [ 142 | {file = "chardet-5.1.0-py3-none-any.whl", hash = "sha256:362777fb014af596ad31334fde1e8c327dfdb076e1960d1694662d46a6917ab9"}, 143 | {file = "chardet-5.1.0.tar.gz", hash = "sha256:0d62712b956bc154f85fb0a266e2a3c5913c2967e00348701b32411d6def31e5"}, 144 | ] 145 | 146 | [[package]] 147 | name = "charset-normalizer" 148 | version = "3.1.0" 149 | description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." 150 | category = "main" 151 | optional = false 152 | python-versions = ">=3.7.0" 153 | files = [ 154 | {file = "charset-normalizer-3.1.0.tar.gz", hash = "sha256:34e0a2f9c370eb95597aae63bf85eb5e96826d81e3dcf88b8886012906f509b5"}, 155 | {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e0ac8959c929593fee38da1c2b64ee9778733cdf03c482c9ff1d508b6b593b2b"}, 156 | {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d7fc3fca01da18fbabe4625d64bb612b533533ed10045a2ac3dd194bfa656b60"}, 157 | {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:04eefcee095f58eaabe6dc3cc2262f3bcd776d2c67005880894f447b3f2cb9c1"}, 158 | {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20064ead0717cf9a73a6d1e779b23d149b53daf971169289ed2ed43a71e8d3b0"}, 159 | {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1435ae15108b1cb6fffbcea2af3d468683b7afed0169ad718451f8db5d1aff6f"}, 160 | {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c84132a54c750fda57729d1e2599bb598f5fa0344085dbde5003ba429a4798c0"}, 161 | {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75f2568b4189dda1c567339b48cba4ac7384accb9c2a7ed655cd86b04055c795"}, 162 | {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11d3bcb7be35e7b1bba2c23beedac81ee893ac9871d0ba79effc7fc01167db6c"}, 163 | {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:891cf9b48776b5c61c700b55a598621fdb7b1e301a550365571e9624f270c203"}, 164 | {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:5f008525e02908b20e04707a4f704cd286d94718f48bb33edddc7d7b584dddc1"}, 165 | {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:b06f0d3bf045158d2fb8837c5785fe9ff9b8c93358be64461a1089f5da983137"}, 166 | {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:49919f8400b5e49e961f320c735388ee686a62327e773fa5b3ce6721f7e785ce"}, 167 | {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:22908891a380d50738e1f978667536f6c6b526a2064156203d418f4856d6e86a"}, 168 | {file = "charset_normalizer-3.1.0-cp310-cp310-win32.whl", hash = "sha256:12d1a39aa6b8c6f6248bb54550efcc1c38ce0d8096a146638fd4738e42284448"}, 169 | {file = "charset_normalizer-3.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:65ed923f84a6844de5fd29726b888e58c62820e0769b76565480e1fdc3d062f8"}, 170 | {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9a3267620866c9d17b959a84dd0bd2d45719b817245e49371ead79ed4f710d19"}, 171 | {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6734e606355834f13445b6adc38b53c0fd45f1a56a9ba06c2058f86893ae8017"}, 172 | {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f8303414c7b03f794347ad062c0516cee0e15f7a612abd0ce1e25caf6ceb47df"}, 173 | {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aaf53a6cebad0eae578f062c7d462155eada9c172bd8c4d250b8c1d8eb7f916a"}, 174 | {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3dc5b6a8ecfdc5748a7e429782598e4f17ef378e3e272eeb1340ea57c9109f41"}, 175 | {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e1b25e3ad6c909f398df8921780d6a3d120d8c09466720226fc621605b6f92b1"}, 176 | {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ca564606d2caafb0abe6d1b5311c2649e8071eb241b2d64e75a0d0065107e62"}, 177 | {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b82fab78e0b1329e183a65260581de4375f619167478dddab510c6c6fb04d9b6"}, 178 | {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bd7163182133c0c7701b25e604cf1611c0d87712e56e88e7ee5d72deab3e76b5"}, 179 | {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:11d117e6c63e8f495412d37e7dc2e2fff09c34b2d09dbe2bee3c6229577818be"}, 180 | {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:cf6511efa4801b9b38dc5546d7547d5b5c6ef4b081c60b23e4d941d0eba9cbeb"}, 181 | {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:abc1185d79f47c0a7aaf7e2412a0eb2c03b724581139193d2d82b3ad8cbb00ac"}, 182 | {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cb7b2ab0188829593b9de646545175547a70d9a6e2b63bf2cd87a0a391599324"}, 183 | {file = "charset_normalizer-3.1.0-cp311-cp311-win32.whl", hash = "sha256:c36bcbc0d5174a80d6cccf43a0ecaca44e81d25be4b7f90f0ed7bcfbb5a00909"}, 184 | {file = "charset_normalizer-3.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:cca4def576f47a09a943666b8f829606bcb17e2bc2d5911a46c8f8da45f56755"}, 185 | {file = "charset_normalizer-3.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0c95f12b74681e9ae127728f7e5409cbbef9cd914d5896ef238cc779b8152373"}, 186 | {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fca62a8301b605b954ad2e9c3666f9d97f63872aa4efcae5492baca2056b74ab"}, 187 | {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac0aa6cd53ab9a31d397f8303f92c42f534693528fafbdb997c82bae6e477ad9"}, 188 | {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c3af8e0f07399d3176b179f2e2634c3ce9c1301379a6b8c9c9aeecd481da494f"}, 189 | {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a5fc78f9e3f501a1614a98f7c54d3969f3ad9bba8ba3d9b438c3bc5d047dd28"}, 190 | {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:628c985afb2c7d27a4800bfb609e03985aaecb42f955049957814e0491d4006d"}, 191 | {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:74db0052d985cf37fa111828d0dd230776ac99c740e1a758ad99094be4f1803d"}, 192 | {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:1e8fcdd8f672a1c4fc8d0bd3a2b576b152d2a349782d1eb0f6b8e52e9954731d"}, 193 | {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:04afa6387e2b282cf78ff3dbce20f0cc071c12dc8f685bd40960cc68644cfea6"}, 194 | {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:dd5653e67b149503c68c4018bf07e42eeed6b4e956b24c00ccdf93ac79cdff84"}, 195 | {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d2686f91611f9e17f4548dbf050e75b079bbc2a82be565832bc8ea9047b61c8c"}, 196 | {file = "charset_normalizer-3.1.0-cp37-cp37m-win32.whl", hash = "sha256:4155b51ae05ed47199dc5b2a4e62abccb274cee6b01da5b895099b61b1982974"}, 197 | {file = "charset_normalizer-3.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:322102cdf1ab682ecc7d9b1c5eed4ec59657a65e1c146a0da342b78f4112db23"}, 198 | {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e633940f28c1e913615fd624fcdd72fdba807bf53ea6925d6a588e84e1151531"}, 199 | {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3a06f32c9634a8705f4ca9946d667609f52cf130d5548881401f1eb2c39b1e2c"}, 200 | {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7381c66e0561c5757ffe616af869b916c8b4e42b367ab29fedc98481d1e74e14"}, 201 | {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3573d376454d956553c356df45bb824262c397c6e26ce43e8203c4c540ee0acb"}, 202 | {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e89df2958e5159b811af9ff0f92614dabf4ff617c03a4c1c6ff53bf1c399e0e1"}, 203 | {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:78cacd03e79d009d95635e7d6ff12c21eb89b894c354bd2b2ed0b4763373693b"}, 204 | {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de5695a6f1d8340b12a5d6d4484290ee74d61e467c39ff03b39e30df62cf83a0"}, 205 | {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c60b9c202d00052183c9be85e5eaf18a4ada0a47d188a83c8f5c5b23252f649"}, 206 | {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f645caaf0008bacf349875a974220f1f1da349c5dbe7c4ec93048cdc785a3326"}, 207 | {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ea9f9c6034ea2d93d9147818f17c2a0860d41b71c38b9ce4d55f21b6f9165a11"}, 208 | {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:80d1543d58bd3d6c271b66abf454d437a438dff01c3e62fdbcd68f2a11310d4b"}, 209 | {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:73dc03a6a7e30b7edc5b01b601e53e7fc924b04e1835e8e407c12c037e81adbd"}, 210 | {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6f5c2e7bc8a4bf7c426599765b1bd33217ec84023033672c1e9a8b35eaeaaaf8"}, 211 | {file = "charset_normalizer-3.1.0-cp38-cp38-win32.whl", hash = "sha256:12a2b561af122e3d94cdb97fe6fb2bb2b82cef0cdca131646fdb940a1eda04f0"}, 212 | {file = "charset_normalizer-3.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:3160a0fd9754aab7d47f95a6b63ab355388d890163eb03b2d2b87ab0a30cfa59"}, 213 | {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:38e812a197bf8e71a59fe55b757a84c1f946d0ac114acafaafaf21667a7e169e"}, 214 | {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6baf0baf0d5d265fa7944feb9f7451cc316bfe30e8df1a61b1bb08577c554f31"}, 215 | {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8f25e17ab3039b05f762b0a55ae0b3632b2e073d9c8fc88e89aca31a6198e88f"}, 216 | {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3747443b6a904001473370d7810aa19c3a180ccd52a7157aacc264a5ac79265e"}, 217 | {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b116502087ce8a6b7a5f1814568ccbd0e9f6cfd99948aa59b0e241dc57cf739f"}, 218 | {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d16fd5252f883eb074ca55cb622bc0bee49b979ae4e8639fff6ca3ff44f9f854"}, 219 | {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21fa558996782fc226b529fdd2ed7866c2c6ec91cee82735c98a197fae39f706"}, 220 | {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f6c7a8a57e9405cad7485f4c9d3172ae486cfef1344b5ddd8e5239582d7355e"}, 221 | {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ac3775e3311661d4adace3697a52ac0bab17edd166087d493b52d4f4f553f9f0"}, 222 | {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:10c93628d7497c81686e8e5e557aafa78f230cd9e77dd0c40032ef90c18f2230"}, 223 | {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:6f4f4668e1831850ebcc2fd0b1cd11721947b6dc7c00bf1c6bd3c929ae14f2c7"}, 224 | {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:0be65ccf618c1e7ac9b849c315cc2e8a8751d9cfdaa43027d4f6624bd587ab7e"}, 225 | {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:53d0a3fa5f8af98a1e261de6a3943ca631c526635eb5817a87a59d9a57ebf48f"}, 226 | {file = "charset_normalizer-3.1.0-cp39-cp39-win32.whl", hash = "sha256:a04f86f41a8916fe45ac5024ec477f41f886b3c435da2d4e3d2709b22ab02af1"}, 227 | {file = "charset_normalizer-3.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:830d2948a5ec37c386d3170c483063798d7879037492540f10a475e3fd6f244b"}, 228 | {file = "charset_normalizer-3.1.0-py3-none-any.whl", hash = "sha256:3d9098b479e78c85080c98e1e35ff40b4a31d8953102bb0fd7d1b6f8a2111a3d"}, 229 | ] 230 | 231 | [[package]] 232 | name = "cheroot" 233 | version = "9.0.0" 234 | description = "Highly-optimized, pure-python HTTP server" 235 | category = "main" 236 | optional = false 237 | python-versions = ">=3.6" 238 | files = [ 239 | {file = "cheroot-9.0.0-py2.py3-none-any.whl", hash = "sha256:fa4231fdb42d7720df118ff62a79371e406369f49e6f867c63ad649cfb4e466c"}, 240 | {file = "cheroot-9.0.0.tar.gz", hash = "sha256:3d47ad9ee19ecbec144b4758399036692fdbf67a40b96eef1fb1454367b3d338"}, 241 | ] 242 | 243 | [package.dependencies] 244 | "jaraco.functools" = "*" 245 | more-itertools = {version = ">=2.6", markers = "python_version >= \"3.6\""} 246 | six = ">=1.11.0" 247 | 248 | [package.extras] 249 | docs = ["furo", "jaraco.packaging (>=3.2)", "python-dateutil", "sphinx (>=1.8.2)", "sphinx-tabs (>=1.1.0)", "sphinxcontrib-apidoc (>=0.3.0)"] 250 | 251 | [[package]] 252 | name = "click" 253 | version = "8.1.3" 254 | description = "Composable command line interface toolkit" 255 | category = "main" 256 | optional = false 257 | python-versions = ">=3.7" 258 | files = [ 259 | {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, 260 | {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, 261 | ] 262 | 263 | [package.dependencies] 264 | colorama = {version = "*", markers = "platform_system == \"Windows\""} 265 | 266 | [[package]] 267 | name = "colorama" 268 | version = "0.4.6" 269 | description = "Cross-platform colored terminal text." 270 | category = "main" 271 | optional = false 272 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" 273 | files = [ 274 | {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, 275 | {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, 276 | ] 277 | 278 | [[package]] 279 | name = "fava" 280 | version = "1.25.1" 281 | description = "Web interface for the accounting tool Beancount." 282 | category = "main" 283 | optional = false 284 | python-versions = ">=3.8" 285 | files = [ 286 | {file = "fava-1.25.1-py3-none-any.whl", hash = "sha256:2afb4b16a2d5062c59abd554840835c18b790135ee33d46fee2ede4382059ea2"}, 287 | {file = "fava-1.25.1.tar.gz", hash = "sha256:4496cfaa3ea15ea7fc0f91bcceb83404061fc524437d4811346c17f8b6653cf6"}, 288 | ] 289 | 290 | [package.dependencies] 291 | Babel = ">=2.7,<3" 292 | beancount = ">=2.3.5,<3" 293 | cheroot = ">=8,<10" 294 | click = ">=7,<9" 295 | Flask = ">=2.2,<3" 296 | Flask-Babel = ">=1,<4" 297 | Jinja2 = ">=3,<4" 298 | markdown2 = ">=2.3.0,<3" 299 | ply = "*" 300 | simplejson = ">=3.16.0,<4" 301 | Werkzeug = ">=2.2,<3" 302 | 303 | [package.extras] 304 | dev = ["build", "flake8", "flake8-pyi", "mypy", "pre-commit", "pyinstaller", "pylint", "pytest", "pytest-cov", "sphinx", "tox", "twine", "types-babel", "types-pkg-resources", "types-simplejson"] 305 | excel = ["pyexcel (>=0.2.2)", "pyexcel-ods3 (>=0.1.1)", "pyexcel-xls (>=0.1.0)", "pyexcel-xlsx (>=0.1.0)"] 306 | 307 | [[package]] 308 | name = "flake8" 309 | version = "4.0.1" 310 | description = "the modular source code checker: pep8 pyflakes and co" 311 | category = "dev" 312 | optional = false 313 | python-versions = ">=3.6" 314 | files = [ 315 | {file = "flake8-4.0.1-py2.py3-none-any.whl", hash = "sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d"}, 316 | {file = "flake8-4.0.1.tar.gz", hash = "sha256:806e034dda44114815e23c16ef92f95c91e4c71100ff52813adf7132a6ad870d"}, 317 | ] 318 | 319 | [package.dependencies] 320 | mccabe = ">=0.6.0,<0.7.0" 321 | pycodestyle = ">=2.8.0,<2.9.0" 322 | pyflakes = ">=2.4.0,<2.5.0" 323 | 324 | [[package]] 325 | name = "flask" 326 | version = "2.2.3" 327 | description = "A simple framework for building complex web applications." 328 | category = "main" 329 | optional = false 330 | python-versions = ">=3.7" 331 | files = [ 332 | {file = "Flask-2.2.3-py3-none-any.whl", hash = "sha256:c0bec9477df1cb867e5a67c9e1ab758de9cb4a3e52dd70681f59fa40a62b3f2d"}, 333 | {file = "Flask-2.2.3.tar.gz", hash = "sha256:7eb373984bf1c770023fce9db164ed0c3353cd0b53f130f4693da0ca756a2e6d"}, 334 | ] 335 | 336 | [package.dependencies] 337 | click = ">=8.0" 338 | importlib-metadata = {version = ">=3.6.0", markers = "python_version < \"3.10\""} 339 | itsdangerous = ">=2.0" 340 | Jinja2 = ">=3.0" 341 | Werkzeug = ">=2.2.2" 342 | 343 | [package.extras] 344 | async = ["asgiref (>=3.2)"] 345 | dotenv = ["python-dotenv"] 346 | 347 | [[package]] 348 | name = "flask-babel" 349 | version = "3.0.1" 350 | description = "Adds i18n/l10n support fo Flask applications." 351 | category = "main" 352 | optional = false 353 | python-versions = ">=3.7,<4.0" 354 | files = [ 355 | {file = "flask_babel-3.0.1-py3-none-any.whl", hash = "sha256:ceb8c82039954a6b29da33ec5deb84878b78069d1ea628b21cac3f8233e9189c"}, 356 | {file = "flask_babel-3.0.1.tar.gz", hash = "sha256:d408cace25514bea8b92e898fd7e55877fbac79b71bc230e266ff515408eba38"}, 357 | ] 358 | 359 | [package.dependencies] 360 | Babel = ">=2.11.0,<3.0.0" 361 | Flask = ">=2.0.0,<3.0.0" 362 | Jinja2 = ">=3.1.2,<4.0.0" 363 | pytz = ">=2022.7,<2023.0" 364 | 365 | [[package]] 366 | name = "google-api-core" 367 | version = "2.11.0" 368 | description = "Google API client core library" 369 | category = "main" 370 | optional = false 371 | python-versions = ">=3.7" 372 | files = [ 373 | {file = "google-api-core-2.11.0.tar.gz", hash = "sha256:4b9bb5d5a380a0befa0573b302651b8a9a89262c1730e37bf423cec511804c22"}, 374 | {file = "google_api_core-2.11.0-py3-none-any.whl", hash = "sha256:ce222e27b0de0d7bc63eb043b956996d6dccab14cc3b690aaea91c9cc99dc16e"}, 375 | ] 376 | 377 | [package.dependencies] 378 | google-auth = ">=2.14.1,<3.0dev" 379 | googleapis-common-protos = ">=1.56.2,<2.0dev" 380 | protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0dev" 381 | requests = ">=2.18.0,<3.0.0dev" 382 | 383 | [package.extras] 384 | grpc = ["grpcio (>=1.33.2,<2.0dev)", "grpcio (>=1.49.1,<2.0dev)", "grpcio-status (>=1.33.2,<2.0dev)", "grpcio-status (>=1.49.1,<2.0dev)"] 385 | grpcgcp = ["grpcio-gcp (>=0.2.2,<1.0dev)"] 386 | grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0dev)"] 387 | 388 | [[package]] 389 | name = "google-api-python-client" 390 | version = "2.83.0" 391 | description = "Google API Client Library for Python" 392 | category = "main" 393 | optional = false 394 | python-versions = ">=3.7" 395 | files = [ 396 | {file = "google-api-python-client-2.83.0.tar.gz", hash = "sha256:d07509f1b2d2b2427363b454db996f7a15e1751a48cfcaf28427050560dd51cf"}, 397 | {file = "google_api_python_client-2.83.0-py2.py3-none-any.whl", hash = "sha256:afa7fe2a5d77e8f136cdb8f40a120dd6660c2292f791c1b22734dfe786bd1dac"}, 398 | ] 399 | 400 | [package.dependencies] 401 | google-api-core = ">=1.31.5,<2.0.0 || >2.3.0,<3.0.0dev" 402 | google-auth = ">=1.19.0,<3.0.0dev" 403 | google-auth-httplib2 = ">=0.1.0" 404 | httplib2 = ">=0.15.0,<1dev" 405 | uritemplate = ">=3.0.1,<5" 406 | 407 | [[package]] 408 | name = "google-auth" 409 | version = "2.17.0" 410 | description = "Google Authentication Library" 411 | category = "main" 412 | optional = false 413 | python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*" 414 | files = [ 415 | {file = "google-auth-2.17.0.tar.gz", hash = "sha256:f51d26ebb3e5d723b9a7dbd310b6c88654ef1ad1fc35750d1fdba48ca4d82f52"}, 416 | {file = "google_auth-2.17.0-py2.py3-none-any.whl", hash = "sha256:45ba9b4b3e49406de3c5451697820694b2f6ce8a6b75bb187852fdae231dab94"}, 417 | ] 418 | 419 | [package.dependencies] 420 | cachetools = ">=2.0.0,<6.0" 421 | pyasn1-modules = ">=0.2.1" 422 | rsa = {version = ">=3.1.4,<5", markers = "python_version >= \"3.6\""} 423 | six = ">=1.9.0" 424 | 425 | [package.extras] 426 | aiohttp = ["aiohttp (>=3.6.2,<4.0.0dev)", "requests (>=2.20.0,<3.0.0dev)"] 427 | enterprise-cert = ["cryptography (==36.0.2)", "pyopenssl (==22.0.0)"] 428 | pyopenssl = ["cryptography (>=38.0.3)", "pyopenssl (>=20.0.0)"] 429 | reauth = ["pyu2f (>=0.1.5)"] 430 | requests = ["requests (>=2.20.0,<3.0.0dev)"] 431 | 432 | [[package]] 433 | name = "google-auth-httplib2" 434 | version = "0.1.0" 435 | description = "Google Authentication Library: httplib2 transport" 436 | category = "main" 437 | optional = false 438 | python-versions = "*" 439 | files = [ 440 | {file = "google-auth-httplib2-0.1.0.tar.gz", hash = "sha256:a07c39fd632becacd3f07718dfd6021bf396978f03ad3ce4321d060015cc30ac"}, 441 | {file = "google_auth_httplib2-0.1.0-py2.py3-none-any.whl", hash = "sha256:31e49c36c6b5643b57e82617cb3e021e3e1d2df9da63af67252c02fa9c1f4a10"}, 442 | ] 443 | 444 | [package.dependencies] 445 | google-auth = "*" 446 | httplib2 = ">=0.15.0" 447 | six = "*" 448 | 449 | [[package]] 450 | name = "googleapis-common-protos" 451 | version = "1.59.0" 452 | description = "Common protobufs used in Google APIs" 453 | category = "main" 454 | optional = false 455 | python-versions = ">=3.7" 456 | files = [ 457 | {file = "googleapis-common-protos-1.59.0.tar.gz", hash = "sha256:4168fcb568a826a52f23510412da405abd93f4d23ba544bb68d943b14ba3cb44"}, 458 | {file = "googleapis_common_protos-1.59.0-py2.py3-none-any.whl", hash = "sha256:b287dc48449d1d41af0c69f4ea26242b5ae4c3d7249a38b0984c86a4caffff1f"}, 459 | ] 460 | 461 | [package.dependencies] 462 | protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0dev" 463 | 464 | [package.extras] 465 | grpc = ["grpcio (>=1.44.0,<2.0.0dev)"] 466 | 467 | [[package]] 468 | name = "httplib2" 469 | version = "0.22.0" 470 | description = "A comprehensive HTTP client library." 471 | category = "main" 472 | optional = false 473 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 474 | files = [ 475 | {file = "httplib2-0.22.0-py3-none-any.whl", hash = "sha256:14ae0a53c1ba8f3d37e9e27cf37eabb0fb9980f435ba405d546948b009dd64dc"}, 476 | {file = "httplib2-0.22.0.tar.gz", hash = "sha256:d7a10bc5ef5ab08322488bde8c726eeee5c8618723fdb399597ec58f3d82df81"}, 477 | ] 478 | 479 | [package.dependencies] 480 | pyparsing = {version = ">=2.4.2,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.0.2 || >3.0.2,<3.0.3 || >3.0.3,<4", markers = "python_version > \"3.0\""} 481 | 482 | [[package]] 483 | name = "idna" 484 | version = "3.4" 485 | description = "Internationalized Domain Names in Applications (IDNA)" 486 | category = "main" 487 | optional = false 488 | python-versions = ">=3.5" 489 | files = [ 490 | {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, 491 | {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, 492 | ] 493 | 494 | [[package]] 495 | name = "importlib-metadata" 496 | version = "4.2.0" 497 | description = "Read metadata from Python packages" 498 | category = "main" 499 | optional = false 500 | python-versions = ">=3.6" 501 | files = [ 502 | {file = "importlib_metadata-4.2.0-py3-none-any.whl", hash = "sha256:057e92c15bc8d9e8109738a48db0ccb31b4d9d5cfbee5a8670879a30be66304b"}, 503 | {file = "importlib_metadata-4.2.0.tar.gz", hash = "sha256:b7e52a1f8dec14a75ea73e0891f3060099ca1d8e6a462a4dff11c3e119ea1b31"}, 504 | ] 505 | 506 | [package.dependencies] 507 | zipp = ">=0.5" 508 | 509 | [package.extras] 510 | docs = ["jaraco.packaging (>=8.2)", "rst.linker (>=1.9)", "sphinx"] 511 | testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pep517", "pyfakefs", "pytest (>=4.6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-flake8", "pytest-mypy"] 512 | 513 | [[package]] 514 | name = "iniconfig" 515 | version = "2.0.0" 516 | description = "brain-dead simple config-ini parsing" 517 | category = "main" 518 | optional = false 519 | python-versions = ">=3.7" 520 | files = [ 521 | {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, 522 | {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, 523 | ] 524 | 525 | [[package]] 526 | name = "itsdangerous" 527 | version = "2.1.2" 528 | description = "Safely pass data to untrusted environments and back." 529 | category = "main" 530 | optional = false 531 | python-versions = ">=3.7" 532 | files = [ 533 | {file = "itsdangerous-2.1.2-py3-none-any.whl", hash = "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44"}, 534 | {file = "itsdangerous-2.1.2.tar.gz", hash = "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a"}, 535 | ] 536 | 537 | [[package]] 538 | name = "jaraco-functools" 539 | version = "3.6.0" 540 | description = "Functools like those found in stdlib" 541 | category = "main" 542 | optional = false 543 | python-versions = ">=3.7" 544 | files = [ 545 | {file = "jaraco.functools-3.6.0-py3-none-any.whl", hash = "sha256:f43fdb95a9b96e85eb2a5126481cb7c96cf342dc9ff4d4d45d322186d33720b8"}, 546 | {file = "jaraco.functools-3.6.0.tar.gz", hash = "sha256:2e1a3be11abaecee5f5ab8dd589638be8304cc4cb91361fe5e683f4b6d9fb7a3"}, 547 | ] 548 | 549 | [package.dependencies] 550 | more-itertools = "*" 551 | 552 | [package.extras] 553 | docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] 554 | testing = ["flake8 (<5)", "jaraco.classes", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] 555 | 556 | [[package]] 557 | name = "jinja2" 558 | version = "3.1.2" 559 | description = "A very fast and expressive template engine." 560 | category = "main" 561 | optional = false 562 | python-versions = ">=3.7" 563 | files = [ 564 | {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, 565 | {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, 566 | ] 567 | 568 | [package.dependencies] 569 | MarkupSafe = ">=2.0" 570 | 571 | [package.extras] 572 | i18n = ["Babel (>=2.7)"] 573 | 574 | [[package]] 575 | name = "lxml" 576 | version = "4.9.2" 577 | description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." 578 | category = "main" 579 | optional = false 580 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*" 581 | files = [ 582 | {file = "lxml-4.9.2-cp27-cp27m-macosx_10_15_x86_64.whl", hash = "sha256:76cf573e5a365e790396a5cc2b909812633409306c6531a6877c59061e42c4f2"}, 583 | {file = "lxml-4.9.2-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b1f42b6921d0e81b1bcb5e395bc091a70f41c4d4e55ba99c6da2b31626c44892"}, 584 | {file = "lxml-4.9.2-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:9f102706d0ca011de571de32c3247c6476b55bb6bc65a20f682f000b07a4852a"}, 585 | {file = "lxml-4.9.2-cp27-cp27m-win32.whl", hash = "sha256:8d0b4612b66ff5d62d03bcaa043bb018f74dfea51184e53f067e6fdcba4bd8de"}, 586 | {file = "lxml-4.9.2-cp27-cp27m-win_amd64.whl", hash = "sha256:4c8f293f14abc8fd3e8e01c5bd86e6ed0b6ef71936ded5bf10fe7a5efefbaca3"}, 587 | {file = "lxml-4.9.2-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2899456259589aa38bfb018c364d6ae7b53c5c22d8e27d0ec7609c2a1ff78b50"}, 588 | {file = "lxml-4.9.2-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6749649eecd6a9871cae297bffa4ee76f90b4504a2a2ab528d9ebe912b101975"}, 589 | {file = "lxml-4.9.2-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:a08cff61517ee26cb56f1e949cca38caabe9ea9fbb4b1e10a805dc39844b7d5c"}, 590 | {file = "lxml-4.9.2-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:85cabf64adec449132e55616e7ca3e1000ab449d1d0f9d7f83146ed5bdcb6d8a"}, 591 | {file = "lxml-4.9.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:8340225bd5e7a701c0fa98284c849c9b9fc9238abf53a0ebd90900f25d39a4e4"}, 592 | {file = "lxml-4.9.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:1ab8f1f932e8f82355e75dda5413a57612c6ea448069d4fb2e217e9a4bed13d4"}, 593 | {file = "lxml-4.9.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:699a9af7dffaf67deeae27b2112aa06b41c370d5e7633e0ee0aea2e0b6c211f7"}, 594 | {file = "lxml-4.9.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b9cc34af337a97d470040f99ba4282f6e6bac88407d021688a5d585e44a23184"}, 595 | {file = "lxml-4.9.2-cp310-cp310-win32.whl", hash = "sha256:d02a5399126a53492415d4906ab0ad0375a5456cc05c3fc0fc4ca11771745cda"}, 596 | {file = "lxml-4.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:a38486985ca49cfa574a507e7a2215c0c780fd1778bb6290c21193b7211702ab"}, 597 | {file = "lxml-4.9.2-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:c83203addf554215463b59f6399835201999b5e48019dc17f182ed5ad87205c9"}, 598 | {file = "lxml-4.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:2a87fa548561d2f4643c99cd13131acb607ddabb70682dcf1dff5f71f781a4bf"}, 599 | {file = "lxml-4.9.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:d6b430a9938a5a5d85fc107d852262ddcd48602c120e3dbb02137c83d212b380"}, 600 | {file = "lxml-4.9.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:3efea981d956a6f7173b4659849f55081867cf897e719f57383698af6f618a92"}, 601 | {file = "lxml-4.9.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:df0623dcf9668ad0445e0558a21211d4e9a149ea8f5666917c8eeec515f0a6d1"}, 602 | {file = "lxml-4.9.2-cp311-cp311-win32.whl", hash = "sha256:da248f93f0418a9e9d94b0080d7ebc407a9a5e6d0b57bb30db9b5cc28de1ad33"}, 603 | {file = "lxml-4.9.2-cp311-cp311-win_amd64.whl", hash = "sha256:3818b8e2c4b5148567e1b09ce739006acfaa44ce3156f8cbbc11062994b8e8dd"}, 604 | {file = "lxml-4.9.2-cp35-cp35m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ca989b91cf3a3ba28930a9fc1e9aeafc2a395448641df1f387a2d394638943b0"}, 605 | {file = "lxml-4.9.2-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:822068f85e12a6e292803e112ab876bc03ed1f03dddb80154c395f891ca6b31e"}, 606 | {file = "lxml-4.9.2-cp35-cp35m-win32.whl", hash = "sha256:be7292c55101e22f2a3d4d8913944cbea71eea90792bf914add27454a13905df"}, 607 | {file = "lxml-4.9.2-cp35-cp35m-win_amd64.whl", hash = "sha256:998c7c41910666d2976928c38ea96a70d1aa43be6fe502f21a651e17483a43c5"}, 608 | {file = "lxml-4.9.2-cp36-cp36m-macosx_10_15_x86_64.whl", hash = "sha256:b26a29f0b7fc6f0897f043ca366142d2b609dc60756ee6e4e90b5f762c6adc53"}, 609 | {file = "lxml-4.9.2-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:ab323679b8b3030000f2be63e22cdeea5b47ee0abd2d6a1dc0c8103ddaa56cd7"}, 610 | {file = "lxml-4.9.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:689bb688a1db722485e4610a503e3e9210dcc20c520b45ac8f7533c837be76fe"}, 611 | {file = "lxml-4.9.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:f49e52d174375a7def9915c9f06ec4e569d235ad428f70751765f48d5926678c"}, 612 | {file = "lxml-4.9.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:36c3c175d34652a35475a73762b545f4527aec044910a651d2bf50de9c3352b1"}, 613 | {file = "lxml-4.9.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a35f8b7fa99f90dd2f5dc5a9fa12332642f087a7641289ca6c40d6e1a2637d8e"}, 614 | {file = "lxml-4.9.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:58bfa3aa19ca4c0f28c5dde0ff56c520fbac6f0daf4fac66ed4c8d2fb7f22e74"}, 615 | {file = "lxml-4.9.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:bc718cd47b765e790eecb74d044cc8d37d58562f6c314ee9484df26276d36a38"}, 616 | {file = "lxml-4.9.2-cp36-cp36m-win32.whl", hash = "sha256:d5bf6545cd27aaa8a13033ce56354ed9e25ab0e4ac3b5392b763d8d04b08e0c5"}, 617 | {file = "lxml-4.9.2-cp36-cp36m-win_amd64.whl", hash = "sha256:3ab9fa9d6dc2a7f29d7affdf3edebf6ece6fb28a6d80b14c3b2fb9d39b9322c3"}, 618 | {file = "lxml-4.9.2-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:05ca3f6abf5cf78fe053da9b1166e062ade3fa5d4f92b4ed688127ea7d7b1d03"}, 619 | {file = "lxml-4.9.2-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:a5da296eb617d18e497bcf0a5c528f5d3b18dadb3619fbdadf4ed2356ef8d941"}, 620 | {file = "lxml-4.9.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:04876580c050a8c5341d706dd464ff04fd597095cc8c023252566a8826505726"}, 621 | {file = "lxml-4.9.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:c9ec3eaf616d67db0764b3bb983962b4f385a1f08304fd30c7283954e6a7869b"}, 622 | {file = "lxml-4.9.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2a29ba94d065945944016b6b74e538bdb1751a1db6ffb80c9d3c2e40d6fa9894"}, 623 | {file = "lxml-4.9.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a82d05da00a58b8e4c0008edbc8a4b6ec5a4bc1e2ee0fb6ed157cf634ed7fa45"}, 624 | {file = "lxml-4.9.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:223f4232855ade399bd409331e6ca70fb5578efef22cf4069a6090acc0f53c0e"}, 625 | {file = "lxml-4.9.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d17bc7c2ccf49c478c5bdd447594e82692c74222698cfc9b5daae7ae7e90743b"}, 626 | {file = "lxml-4.9.2-cp37-cp37m-win32.whl", hash = "sha256:b64d891da92e232c36976c80ed7ebb383e3f148489796d8d31a5b6a677825efe"}, 627 | {file = "lxml-4.9.2-cp37-cp37m-win_amd64.whl", hash = "sha256:a0a336d6d3e8b234a3aae3c674873d8f0e720b76bc1d9416866c41cd9500ffb9"}, 628 | {file = "lxml-4.9.2-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:da4dd7c9c50c059aba52b3524f84d7de956f7fef88f0bafcf4ad7dde94a064e8"}, 629 | {file = "lxml-4.9.2-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:821b7f59b99551c69c85a6039c65b75f5683bdc63270fec660f75da67469ca24"}, 630 | {file = "lxml-4.9.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:e5168986b90a8d1f2f9dc1b841467c74221bd752537b99761a93d2d981e04889"}, 631 | {file = "lxml-4.9.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:8e20cb5a47247e383cf4ff523205060991021233ebd6f924bca927fcf25cf86f"}, 632 | {file = "lxml-4.9.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:13598ecfbd2e86ea7ae45ec28a2a54fb87ee9b9fdb0f6d343297d8e548392c03"}, 633 | {file = "lxml-4.9.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:880bbbcbe2fca64e2f4d8e04db47bcdf504936fa2b33933efd945e1b429bea8c"}, 634 | {file = "lxml-4.9.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:7d2278d59425777cfcb19735018d897ca8303abe67cc735f9f97177ceff8027f"}, 635 | {file = "lxml-4.9.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5344a43228767f53a9df6e5b253f8cdca7dfc7b7aeae52551958192f56d98457"}, 636 | {file = "lxml-4.9.2-cp38-cp38-win32.whl", hash = "sha256:925073b2fe14ab9b87e73f9a5fde6ce6392da430f3004d8b72cc86f746f5163b"}, 637 | {file = "lxml-4.9.2-cp38-cp38-win_amd64.whl", hash = "sha256:9b22c5c66f67ae00c0199f6055705bc3eb3fcb08d03d2ec4059a2b1b25ed48d7"}, 638 | {file = "lxml-4.9.2-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:5f50a1c177e2fa3ee0667a5ab79fdc6b23086bc8b589d90b93b4bd17eb0e64d1"}, 639 | {file = "lxml-4.9.2-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:090c6543d3696cbe15b4ac6e175e576bcc3f1ccfbba970061b7300b0c15a2140"}, 640 | {file = "lxml-4.9.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:63da2ccc0857c311d764e7d3d90f429c252e83b52d1f8f1d1fe55be26827d1f4"}, 641 | {file = "lxml-4.9.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:5b4545b8a40478183ac06c073e81a5ce4cf01bf1734962577cf2bb569a5b3bbf"}, 642 | {file = "lxml-4.9.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2e430cd2824f05f2d4f687701144556646bae8f249fd60aa1e4c768ba7018947"}, 643 | {file = "lxml-4.9.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6804daeb7ef69e7b36f76caddb85cccd63d0c56dedb47555d2fc969e2af6a1a5"}, 644 | {file = "lxml-4.9.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a6e441a86553c310258aca15d1c05903aaf4965b23f3bc2d55f200804e005ee5"}, 645 | {file = "lxml-4.9.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ca34efc80a29351897e18888c71c6aca4a359247c87e0b1c7ada14f0ab0c0fb2"}, 646 | {file = "lxml-4.9.2-cp39-cp39-win32.whl", hash = "sha256:6b418afe5df18233fc6b6093deb82a32895b6bb0b1155c2cdb05203f583053f1"}, 647 | {file = "lxml-4.9.2-cp39-cp39-win_amd64.whl", hash = "sha256:f1496ea22ca2c830cbcbd473de8f114a320da308438ae65abad6bab7867fe38f"}, 648 | {file = "lxml-4.9.2-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:b264171e3143d842ded311b7dccd46ff9ef34247129ff5bf5066123c55c2431c"}, 649 | {file = "lxml-4.9.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:0dc313ef231edf866912e9d8f5a042ddab56c752619e92dfd3a2c277e6a7299a"}, 650 | {file = "lxml-4.9.2-pp38-pypy38_pp73-macosx_10_15_x86_64.whl", hash = "sha256:16efd54337136e8cd72fb9485c368d91d77a47ee2d42b057564aae201257d419"}, 651 | {file = "lxml-4.9.2-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:0f2b1e0d79180f344ff9f321327b005ca043a50ece8713de61d1cb383fb8ac05"}, 652 | {file = "lxml-4.9.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:7b770ed79542ed52c519119473898198761d78beb24b107acf3ad65deae61f1f"}, 653 | {file = "lxml-4.9.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:efa29c2fe6b4fdd32e8ef81c1528506895eca86e1d8c4657fda04c9b3786ddf9"}, 654 | {file = "lxml-4.9.2-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7e91ee82f4199af8c43d8158024cbdff3d931df350252288f0d4ce656df7f3b5"}, 655 | {file = "lxml-4.9.2-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:b23e19989c355ca854276178a0463951a653309fb8e57ce674497f2d9f208746"}, 656 | {file = "lxml-4.9.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:01d36c05f4afb8f7c20fd9ed5badca32a2029b93b1750f571ccc0b142531caf7"}, 657 | {file = "lxml-4.9.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7b515674acfdcadb0eb5d00d8a709868173acece5cb0be3dd165950cbfdf5409"}, 658 | {file = "lxml-4.9.2.tar.gz", hash = "sha256:2455cfaeb7ac70338b3257f41e21f0724f4b5b0c0e7702da67ee6c3640835b67"}, 659 | ] 660 | 661 | [package.extras] 662 | cssselect = ["cssselect (>=0.7)"] 663 | html5 = ["html5lib"] 664 | htmlsoup = ["BeautifulSoup4"] 665 | source = ["Cython (>=0.29.7)"] 666 | 667 | [[package]] 668 | name = "markdown2" 669 | version = "2.4.8" 670 | description = "A fast and complete Python implementation of Markdown" 671 | category = "main" 672 | optional = false 673 | python-versions = ">=3.5, <4" 674 | files = [ 675 | {file = "markdown2-2.4.8-py2.py3-none-any.whl", hash = "sha256:7d49ca871d3e0e412c65d7d21fcbc13ae897f7876f3e5f14dd4db3b7fbf27f10"}, 676 | {file = "markdown2-2.4.8.tar.gz", hash = "sha256:90475aca3d9c8e7df6d70c51de5bbbe9edf7fcf6a380bd1044d321500f5445da"}, 677 | ] 678 | 679 | [package.extras] 680 | all = ["pygments (>=2.7.3)", "wavedrom"] 681 | code-syntax-highlighting = ["pygments (>=2.7.3)"] 682 | wavedrom = ["wavedrom"] 683 | 684 | [[package]] 685 | name = "markupsafe" 686 | version = "2.1.2" 687 | description = "Safely add untrusted strings to HTML/XML markup." 688 | category = "main" 689 | optional = false 690 | python-versions = ">=3.7" 691 | files = [ 692 | {file = "MarkupSafe-2.1.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:665a36ae6f8f20a4676b53224e33d456a6f5a72657d9c83c2aa00765072f31f7"}, 693 | {file = "MarkupSafe-2.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:340bea174e9761308703ae988e982005aedf427de816d1afe98147668cc03036"}, 694 | {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22152d00bf4a9c7c83960521fc558f55a1adbc0631fbb00a9471e097b19d72e1"}, 695 | {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28057e985dace2f478e042eaa15606c7efccb700797660629da387eb289b9323"}, 696 | {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca244fa73f50a800cf8c3ebf7fd93149ec37f5cb9596aa8873ae2c1d23498601"}, 697 | {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d9d971ec1e79906046aa3ca266de79eac42f1dbf3612a05dc9368125952bd1a1"}, 698 | {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7e007132af78ea9df29495dbf7b5824cb71648d7133cf7848a2a5dd00d36f9ff"}, 699 | {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7313ce6a199651c4ed9d7e4cfb4aa56fe923b1adf9af3b420ee14e6d9a73df65"}, 700 | {file = "MarkupSafe-2.1.2-cp310-cp310-win32.whl", hash = "sha256:c4a549890a45f57f1ebf99c067a4ad0cb423a05544accaf2b065246827ed9603"}, 701 | {file = "MarkupSafe-2.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:835fb5e38fd89328e9c81067fd642b3593c33e1e17e2fdbf77f5676abb14a156"}, 702 | {file = "MarkupSafe-2.1.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2ec4f2d48ae59bbb9d1f9d7efb9236ab81429a764dedca114f5fdabbc3788013"}, 703 | {file = "MarkupSafe-2.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:608e7073dfa9e38a85d38474c082d4281f4ce276ac0010224eaba11e929dd53a"}, 704 | {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:65608c35bfb8a76763f37036547f7adfd09270fbdbf96608be2bead319728fcd"}, 705 | {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2bfb563d0211ce16b63c7cb9395d2c682a23187f54c3d79bfec33e6705473c6"}, 706 | {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:da25303d91526aac3672ee6d49a2f3db2d9502a4a60b55519feb1a4c7714e07d"}, 707 | {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9cad97ab29dfc3f0249b483412c85c8ef4766d96cdf9dcf5a1e3caa3f3661cf1"}, 708 | {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:085fd3201e7b12809f9e6e9bc1e5c96a368c8523fad5afb02afe3c051ae4afcc"}, 709 | {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1bea30e9bf331f3fef67e0a3877b2288593c98a21ccb2cf29b74c581a4eb3af0"}, 710 | {file = "MarkupSafe-2.1.2-cp311-cp311-win32.whl", hash = "sha256:7df70907e00c970c60b9ef2938d894a9381f38e6b9db73c5be35e59d92e06625"}, 711 | {file = "MarkupSafe-2.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:e55e40ff0cc8cc5c07996915ad367fa47da6b3fc091fdadca7f5403239c5fec3"}, 712 | {file = "MarkupSafe-2.1.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a6e40afa7f45939ca356f348c8e23048e02cb109ced1eb8420961b2f40fb373a"}, 713 | {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf877ab4ed6e302ec1d04952ca358b381a882fbd9d1b07cccbfd61783561f98a"}, 714 | {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63ba06c9941e46fa389d389644e2d8225e0e3e5ebcc4ff1ea8506dce646f8c8a"}, 715 | {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f1cd098434e83e656abf198f103a8207a8187c0fc110306691a2e94a78d0abb2"}, 716 | {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:55f44b440d491028addb3b88f72207d71eeebfb7b5dbf0643f7c023ae1fba619"}, 717 | {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:a6f2fcca746e8d5910e18782f976489939d54a91f9411c32051b4aab2bd7c513"}, 718 | {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0b462104ba25f1ac006fdab8b6a01ebbfbce9ed37fd37fd4acd70c67c973e460"}, 719 | {file = "MarkupSafe-2.1.2-cp37-cp37m-win32.whl", hash = "sha256:7668b52e102d0ed87cb082380a7e2e1e78737ddecdde129acadb0eccc5423859"}, 720 | {file = "MarkupSafe-2.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6d6607f98fcf17e534162f0709aaad3ab7a96032723d8ac8750ffe17ae5a0666"}, 721 | {file = "MarkupSafe-2.1.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a806db027852538d2ad7555b203300173dd1b77ba116de92da9afbc3a3be3eed"}, 722 | {file = "MarkupSafe-2.1.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a4abaec6ca3ad8660690236d11bfe28dfd707778e2442b45addd2f086d6ef094"}, 723 | {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f03a532d7dee1bed20bc4884194a16160a2de9ffc6354b3878ec9682bb623c54"}, 724 | {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4cf06cdc1dda95223e9d2d3c58d3b178aa5dacb35ee7e3bbac10e4e1faacb419"}, 725 | {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22731d79ed2eb25059ae3df1dfc9cb1546691cc41f4e3130fe6bfbc3ecbbecfa"}, 726 | {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f8ffb705ffcf5ddd0e80b65ddf7bed7ee4f5a441ea7d3419e861a12eaf41af58"}, 727 | {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8db032bf0ce9022a8e41a22598eefc802314e81b879ae093f36ce9ddf39ab1ba"}, 728 | {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2298c859cfc5463f1b64bd55cb3e602528db6fa0f3cfd568d3605c50678f8f03"}, 729 | {file = "MarkupSafe-2.1.2-cp38-cp38-win32.whl", hash = "sha256:50c42830a633fa0cf9e7d27664637532791bfc31c731a87b202d2d8ac40c3ea2"}, 730 | {file = "MarkupSafe-2.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:bb06feb762bade6bf3c8b844462274db0c76acc95c52abe8dbed28ae3d44a147"}, 731 | {file = "MarkupSafe-2.1.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:99625a92da8229df6d44335e6fcc558a5037dd0a760e11d84be2260e6f37002f"}, 732 | {file = "MarkupSafe-2.1.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8bca7e26c1dd751236cfb0c6c72d4ad61d986e9a41bbf76cb445f69488b2a2bd"}, 733 | {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f"}, 734 | {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40dfd3fefbef579ee058f139733ac336312663c6706d1163b82b3003fb1925c4"}, 735 | {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:090376d812fb6ac5f171e5938e82e7f2d7adc2b629101cec0db8b267815c85e2"}, 736 | {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2e7821bffe00aa6bd07a23913b7f4e01328c3d5cc0b40b36c0bd81d362faeb65"}, 737 | {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c0a33bc9f02c2b17c3ea382f91b4db0e6cde90b63b296422a939886a7a80de1c"}, 738 | {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b8526c6d437855442cdd3d87eede9c425c4445ea011ca38d937db299382e6fa3"}, 739 | {file = "MarkupSafe-2.1.2-cp39-cp39-win32.whl", hash = "sha256:137678c63c977754abe9086a3ec011e8fd985ab90631145dfb9294ad09c102a7"}, 740 | {file = "MarkupSafe-2.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:0576fe974b40a400449768941d5d0858cc624e3249dfd1e0c33674e5c7ca7aed"}, 741 | {file = "MarkupSafe-2.1.2.tar.gz", hash = "sha256:abcabc8c2b26036d62d4c746381a6f7cf60aafcc653198ad678306986b09450d"}, 742 | ] 743 | 744 | [[package]] 745 | name = "mccabe" 746 | version = "0.6.1" 747 | description = "McCabe checker, plugin for flake8" 748 | category = "dev" 749 | optional = false 750 | python-versions = "*" 751 | files = [ 752 | {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, 753 | {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, 754 | ] 755 | 756 | [[package]] 757 | name = "more-itertools" 758 | version = "9.1.0" 759 | description = "More routines for operating on iterables, beyond itertools" 760 | category = "main" 761 | optional = false 762 | python-versions = ">=3.7" 763 | files = [ 764 | {file = "more-itertools-9.1.0.tar.gz", hash = "sha256:cabaa341ad0389ea83c17a94566a53ae4c9d07349861ecb14dc6d0345cf9ac5d"}, 765 | {file = "more_itertools-9.1.0-py3-none-any.whl", hash = "sha256:d2bc7f02446e86a68911e58ded76d6561eea00cddfb2a91e7019bbb586c799f3"}, 766 | ] 767 | 768 | [[package]] 769 | name = "packaging" 770 | version = "23.0" 771 | description = "Core utilities for Python packages" 772 | category = "main" 773 | optional = false 774 | python-versions = ">=3.7" 775 | files = [ 776 | {file = "packaging-23.0-py3-none-any.whl", hash = "sha256:714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2"}, 777 | {file = "packaging-23.0.tar.gz", hash = "sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97"}, 778 | ] 779 | 780 | [[package]] 781 | name = "petl" 782 | version = "1.7.12" 783 | description = "A Python package for extracting, transforming and loading tables of data." 784 | category = "main" 785 | optional = false 786 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 787 | files = [ 788 | {file = "petl-1.7.12.tar.gz", hash = "sha256:d4a94e0d6f1274062fe92353b99a007ee12c7c84b4c31c0cb9713ed5dbda22e7"}, 789 | ] 790 | 791 | [package.extras] 792 | avro = ["fastavro (>=0.24.0)"] 793 | bcolz = ["bcolz (>=1.2.1)"] 794 | db = ["SQLAlchemy (>=1.3.6)"] 795 | hdf5 = ["cython (>=0.29.13)", "numexpr (>=2.6.9)", "numpy (>=1.16.4)", "tables (>=3.5.2)"] 796 | http = ["aiohttp (>=3.6.2)", "requests"] 797 | interval = ["intervaltree (>=3.0.2)"] 798 | numpy = ["numpy (>=1.16.4)"] 799 | pandas = ["pandas (>=0.24.2)"] 800 | remote = ["fsspec (>=0.7.4)"] 801 | smb = ["smbprotocol (>=1.0.1)"] 802 | whoosh = ["whoosh"] 803 | xls = ["xlrd (>=2.0.1)", "xlwt (>=1.3.0)"] 804 | xlsx = ["openpyxl (>=2.6.2)"] 805 | xpath = ["lxml (>=4.4.0)"] 806 | 807 | [[package]] 808 | name = "pluggy" 809 | version = "1.0.0" 810 | description = "plugin and hook calling mechanisms for python" 811 | category = "main" 812 | optional = false 813 | python-versions = ">=3.6" 814 | files = [ 815 | {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, 816 | {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, 817 | ] 818 | 819 | [package.extras] 820 | dev = ["pre-commit", "tox"] 821 | testing = ["pytest", "pytest-benchmark"] 822 | 823 | [[package]] 824 | name = "ply" 825 | version = "3.11" 826 | description = "Python Lex & Yacc" 827 | category = "main" 828 | optional = false 829 | python-versions = "*" 830 | files = [ 831 | {file = "ply-3.11-py2.py3-none-any.whl", hash = "sha256:096f9b8350b65ebd2fd1346b12452efe5b9607f7482813ffca50c22722a807ce"}, 832 | {file = "ply-3.11.tar.gz", hash = "sha256:00c7c1aaa88358b9c765b6d3000c6eec0ba42abca5351b095321aef446081da3"}, 833 | ] 834 | 835 | [[package]] 836 | name = "protobuf" 837 | version = "4.22.1" 838 | description = "" 839 | category = "main" 840 | optional = false 841 | python-versions = ">=3.7" 842 | files = [ 843 | {file = "protobuf-4.22.1-cp310-abi3-win32.whl", hash = "sha256:85aa9acc5a777adc0c21b449dafbc40d9a0b6413ff3a4f77ef9df194be7f975b"}, 844 | {file = "protobuf-4.22.1-cp310-abi3-win_amd64.whl", hash = "sha256:8bc971d76c03f1dd49f18115b002254f2ddb2d4b143c583bb860b796bb0d399e"}, 845 | {file = "protobuf-4.22.1-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:5917412347e1da08ce2939eb5cd60650dfb1a9ab4606a415b9278a1041fb4d19"}, 846 | {file = "protobuf-4.22.1-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:9e12e2810e7d297dbce3c129ae5e912ffd94240b050d33f9ecf023f35563b14f"}, 847 | {file = "protobuf-4.22.1-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:953fc7904ef46900262a26374b28c2864610b60cdc8b272f864e22143f8373c4"}, 848 | {file = "protobuf-4.22.1-cp37-cp37m-win32.whl", hash = "sha256:6e100f7bc787cd0a0ae58dbf0ab8bbf1ee7953f862b89148b6cf5436d5e9eaa1"}, 849 | {file = "protobuf-4.22.1-cp37-cp37m-win_amd64.whl", hash = "sha256:87a6393fa634f294bf24d1cfe9fdd6bb605cbc247af81b9b10c4c0f12dfce4b3"}, 850 | {file = "protobuf-4.22.1-cp38-cp38-win32.whl", hash = "sha256:e3fb58076bdb550e75db06ace2a8b3879d4c4f7ec9dd86e4254656118f4a78d7"}, 851 | {file = "protobuf-4.22.1-cp38-cp38-win_amd64.whl", hash = "sha256:651113695bc2e5678b799ee5d906b5d3613f4ccfa61b12252cfceb6404558af0"}, 852 | {file = "protobuf-4.22.1-cp39-cp39-win32.whl", hash = "sha256:67b7d19da0fda2733702c2299fd1ef6cb4b3d99f09263eacaf1aa151d9d05f02"}, 853 | {file = "protobuf-4.22.1-cp39-cp39-win_amd64.whl", hash = "sha256:b8700792f88e59ccecfa246fa48f689d6eee6900eddd486cdae908ff706c482b"}, 854 | {file = "protobuf-4.22.1-py3-none-any.whl", hash = "sha256:3e19dcf4adbf608924d3486ece469dd4f4f2cf7d2649900f0efcd1a84e8fd3ba"}, 855 | {file = "protobuf-4.22.1.tar.gz", hash = "sha256:dce7a55d501c31ecf688adb2f6c3f763cf11bc0be815d1946a84d74772ab07a7"}, 856 | ] 857 | 858 | [[package]] 859 | name = "py" 860 | version = "1.11.0" 861 | description = "library with cross-python path, ini-parsing, io, code, log facilities" 862 | category = "main" 863 | optional = false 864 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 865 | files = [ 866 | {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, 867 | {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, 868 | ] 869 | 870 | [[package]] 871 | name = "pyasn1" 872 | version = "0.4.8" 873 | description = "ASN.1 types and codecs" 874 | category = "main" 875 | optional = false 876 | python-versions = "*" 877 | files = [ 878 | {file = "pyasn1-0.4.8-py2.py3-none-any.whl", hash = "sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d"}, 879 | {file = "pyasn1-0.4.8.tar.gz", hash = "sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba"}, 880 | ] 881 | 882 | [[package]] 883 | name = "pyasn1-modules" 884 | version = "0.2.8" 885 | description = "A collection of ASN.1-based protocols modules." 886 | category = "main" 887 | optional = false 888 | python-versions = "*" 889 | files = [ 890 | {file = "pyasn1-modules-0.2.8.tar.gz", hash = "sha256:905f84c712230b2c592c19470d3ca8d552de726050d1d1716282a1f6146be65e"}, 891 | {file = "pyasn1_modules-0.2.8-py2.py3-none-any.whl", hash = "sha256:a50b808ffeb97cb3601dd25981f6b016cbb3d31fbf57a8b8a87428e6158d0c74"}, 892 | ] 893 | 894 | [package.dependencies] 895 | pyasn1 = ">=0.4.6,<0.5.0" 896 | 897 | [[package]] 898 | name = "pycodestyle" 899 | version = "2.8.0" 900 | description = "Python style guide checker" 901 | category = "dev" 902 | optional = false 903 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 904 | files = [ 905 | {file = "pycodestyle-2.8.0-py2.py3-none-any.whl", hash = "sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20"}, 906 | {file = "pycodestyle-2.8.0.tar.gz", hash = "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f"}, 907 | ] 908 | 909 | [[package]] 910 | name = "pyflakes" 911 | version = "2.4.0" 912 | description = "passive checker of Python programs" 913 | category = "dev" 914 | optional = false 915 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 916 | files = [ 917 | {file = "pyflakes-2.4.0-py2.py3-none-any.whl", hash = "sha256:3bb3a3f256f4b7968c9c788781e4ff07dce46bdf12339dcda61053375426ee2e"}, 918 | {file = "pyflakes-2.4.0.tar.gz", hash = "sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c"}, 919 | ] 920 | 921 | [[package]] 922 | name = "pyhamcrest" 923 | version = "2.0.4" 924 | description = "Hamcrest framework for matcher objects" 925 | category = "dev" 926 | optional = false 927 | python-versions = ">=3.6" 928 | files = [ 929 | {file = "pyhamcrest-2.0.4-py3-none-any.whl", hash = "sha256:60a41d4783b9d56c9ec8586635d2301db5072b3ea8a51c32dd03c408ae2b0f79"}, 930 | {file = "pyhamcrest-2.0.4.tar.gz", hash = "sha256:b5d9ce6b977696286cf232ce2adf8969b4d0b045975b0936ac9005e84e67e9c1"}, 931 | ] 932 | 933 | [package.extras] 934 | dev = ["black", "flake8", "pyhamcrest[docs,tests]", "pytest-mypy", "towncrier", "tox", "tox-asdf", "twine"] 935 | docs = ["alabaster (>=0.7,<1.0)", "sphinx (>=4.0,<5.0)"] 936 | tests = ["coverage[toml]", "dataclasses", "mypy (!=0.940)", "pytest (>=5.0)", "pytest-mypy-plugins", "pytest-sugar", "pytest-xdist", "types-dataclasses", "types-mock"] 937 | tests-numpy = ["numpy", "pyhamcrest[tests]"] 938 | 939 | [[package]] 940 | name = "pyparsing" 941 | version = "3.0.9" 942 | description = "pyparsing module - Classes and methods to define and execute parsing grammars" 943 | category = "main" 944 | optional = false 945 | python-versions = ">=3.6.8" 946 | files = [ 947 | {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, 948 | {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, 949 | ] 950 | 951 | [package.extras] 952 | diagrams = ["jinja2", "railroad-diagrams"] 953 | 954 | [[package]] 955 | name = "pytest" 956 | version = "6.2.5" 957 | description = "pytest: simple powerful testing with Python" 958 | category = "main" 959 | optional = false 960 | python-versions = ">=3.6" 961 | files = [ 962 | {file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"}, 963 | {file = "pytest-6.2.5.tar.gz", hash = "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89"}, 964 | ] 965 | 966 | [package.dependencies] 967 | atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} 968 | attrs = ">=19.2.0" 969 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 970 | iniconfig = "*" 971 | packaging = "*" 972 | pluggy = ">=0.12,<2.0" 973 | py = ">=1.8.2" 974 | toml = "*" 975 | 976 | [package.extras] 977 | testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] 978 | 979 | [[package]] 980 | name = "python-dateutil" 981 | version = "2.8.2" 982 | description = "Extensions to the standard Python datetime module" 983 | category = "main" 984 | optional = false 985 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" 986 | files = [ 987 | {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, 988 | {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, 989 | ] 990 | 991 | [package.dependencies] 992 | six = ">=1.5" 993 | 994 | [[package]] 995 | name = "python-magic" 996 | version = "0.4.27" 997 | description = "File type identification using libmagic" 998 | category = "main" 999 | optional = false 1000 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 1001 | files = [ 1002 | {file = "python-magic-0.4.27.tar.gz", hash = "sha256:c1ba14b08e4a5f5c31a302b7721239695b2f0f058d125bd5ce1ee36b9d9d3c3b"}, 1003 | {file = "python_magic-0.4.27-py2.py3-none-any.whl", hash = "sha256:c212960ad306f700aa0d01e5d7a325d20548ff97eb9920dcd29513174f0294d3"}, 1004 | ] 1005 | 1006 | [[package]] 1007 | name = "pytz" 1008 | version = "2022.7.1" 1009 | description = "World timezone definitions, modern and historical" 1010 | category = "main" 1011 | optional = false 1012 | python-versions = "*" 1013 | files = [ 1014 | {file = "pytz-2022.7.1-py2.py3-none-any.whl", hash = "sha256:78f4f37d8198e0627c5f1143240bb0206b8691d8d7ac6d78fee88b78733f8c4a"}, 1015 | {file = "pytz-2022.7.1.tar.gz", hash = "sha256:01a0681c4b9684a28304615eba55d1ab31ae00bf68ec157ec3708a8182dbbcd0"}, 1016 | ] 1017 | 1018 | [[package]] 1019 | name = "requests" 1020 | version = "2.28.2" 1021 | description = "Python HTTP for Humans." 1022 | category = "main" 1023 | optional = false 1024 | python-versions = ">=3.7, <4" 1025 | files = [ 1026 | {file = "requests-2.28.2-py3-none-any.whl", hash = "sha256:64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa"}, 1027 | {file = "requests-2.28.2.tar.gz", hash = "sha256:98b1b2782e3c6c4904938b84c0eb932721069dfdb9134313beff7c83c2df24bf"}, 1028 | ] 1029 | 1030 | [package.dependencies] 1031 | certifi = ">=2017.4.17" 1032 | charset-normalizer = ">=2,<4" 1033 | idna = ">=2.5,<4" 1034 | urllib3 = ">=1.21.1,<1.27" 1035 | 1036 | [package.extras] 1037 | socks = ["PySocks (>=1.5.6,!=1.5.7)"] 1038 | use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] 1039 | 1040 | [[package]] 1041 | name = "rsa" 1042 | version = "4.9" 1043 | description = "Pure-Python RSA implementation" 1044 | category = "main" 1045 | optional = false 1046 | python-versions = ">=3.6,<4" 1047 | files = [ 1048 | {file = "rsa-4.9-py3-none-any.whl", hash = "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7"}, 1049 | {file = "rsa-4.9.tar.gz", hash = "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21"}, 1050 | ] 1051 | 1052 | [package.dependencies] 1053 | pyasn1 = ">=0.1.3" 1054 | 1055 | [[package]] 1056 | name = "simplejson" 1057 | version = "3.18.4" 1058 | description = "Simple, fast, extensible JSON encoder/decoder for Python" 1059 | category = "main" 1060 | optional = false 1061 | python-versions = ">=2.5, !=3.0.*, !=3.1.*, !=3.2.*" 1062 | files = [ 1063 | {file = "simplejson-3.18.4-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:8f381747c2edebe3c750a571e55103bfcc33b2707a9b91ae033ab9ba718d976a"}, 1064 | {file = "simplejson-3.18.4-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:094275b1b8f003afce1167c8a674cd1ee2fd48c566632dac5d149901d5012ff8"}, 1065 | {file = "simplejson-3.18.4-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:676e8c182f8079851f12ae1cee2fcebe04def2da2a5703a9d747ab125af47732"}, 1066 | {file = "simplejson-3.18.4-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:4b5df4ee48403885046c6f4fd8adc84c4ac0adec69482f22a17bd4ba52876341"}, 1067 | {file = "simplejson-3.18.4-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:edb334cab35dcd90eb563fdacb085f10e5dd0b1acb57fa43f8933308b42a8f88"}, 1068 | {file = "simplejson-3.18.4-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:b6c6cfc492710d8f0303705fa1ff7bb3d6a145f523384e45a6f3b13ada37021f"}, 1069 | {file = "simplejson-3.18.4-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ced906b172bfad62736a27cfafcb6e24bc9938533b0529ff8150f7926fe35b54"}, 1070 | {file = "simplejson-3.18.4-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:7701a289d45fdfeb37f1d15cf638801cea439df667a613379443772a86e82936"}, 1071 | {file = "simplejson-3.18.4-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:e2f87a483c4ab0bb2a9adc9ca09173e7f7cf3696e4fa67bd45a6b33181e57921"}, 1072 | {file = "simplejson-3.18.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c0444423129df448788edc66a129bc7560ad7d6a661d74f0900959c0b44349a1"}, 1073 | {file = "simplejson-3.18.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:29a86bc9c8a913a4e0ffab85c563a7505cdf4bd13fba05342f8314facc0b7586"}, 1074 | {file = "simplejson-3.18.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e2fa1ee5ca34ab2ecfbe3f7a7e952a1ecaebb5b4818f002b5b146324912ac3d5"}, 1075 | {file = "simplejson-3.18.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b17026f3f349a6e87818cd3531e3bbb5cc78a6f4b2b6718f574a8e0512d71e08"}, 1076 | {file = "simplejson-3.18.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a255d30cda6334ba780eb40a56e8134efd3453948b995d3966e45212e34bf018"}, 1077 | {file = "simplejson-3.18.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d9f0dfde448611f4f818da05f9b544a78f29355dc39151b0dad8e7c65c513e4f"}, 1078 | {file = "simplejson-3.18.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f1085cadec0f7e76377951d7a87744628c90ac6cc634fc97eecce0c4d41ec563"}, 1079 | {file = "simplejson-3.18.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f15f56b3119fb71fa57eb4613bcd87eb7df6c2f3547de7d341853d3e50cef97e"}, 1080 | {file = "simplejson-3.18.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:695da62e494e4689ab78fae173a78390a175b6a5ccc4292277ce0f8dba3945d5"}, 1081 | {file = "simplejson-3.18.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:097e48686e49026836ef384c7c10ca670acc023cb16a976a689c2eb6c1852df4"}, 1082 | {file = "simplejson-3.18.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a56005332d70b8d02d476d4a85818b27b01e51dac1a21d5c1a1d8a5df2efb4a6"}, 1083 | {file = "simplejson-3.18.4-cp310-cp310-win32.whl", hash = "sha256:3d549efc7e8f9a180c59462b124991b690ff25c235d5cf495c3246c66a7679cd"}, 1084 | {file = "simplejson-3.18.4-cp310-cp310-win_amd64.whl", hash = "sha256:bd694c465cc61fa8e599355e535b6eb561279834d9883aeef08d0e86c44c300c"}, 1085 | {file = "simplejson-3.18.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad37f25fd8dfbed80815c3281b82a165be2a74e663856b9a50099d18789987bc"}, 1086 | {file = "simplejson-3.18.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2362c66d2c633925d90f2f177f05e0570d320d986130d34dff9ad6edbf7be8ac"}, 1087 | {file = "simplejson-3.18.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30e381471158290ccb79bd31e7bbda4c8f2cf7e1a5f6b557c1b97d6036ccd05b"}, 1088 | {file = "simplejson-3.18.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d45ed9452a42064805143480397b586ea2ea322f4b8b69034c51181e7f38342"}, 1089 | {file = "simplejson-3.18.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0dcc54e7cfbd9674ec4ca181e26eaa5b038446601faeaa6c83d146ddef2f2652"}, 1090 | {file = "simplejson-3.18.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:05a668d4a93816fb8a644e90e7987aa3beeb9d2112ca50a474d41e6acb5bb88a"}, 1091 | {file = "simplejson-3.18.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da6dc0cb00ef1e1a8daf285074ca8b2bb89591170c42ceab0c37bcdb9adc802c"}, 1092 | {file = "simplejson-3.18.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f31e126204ec38f92dee119af87cf881044ef7dea6f7477ef774ed3d84199c24"}, 1093 | {file = "simplejson-3.18.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:fb0f8b35c11fd8e4b924f974d331b20fa54555282451db7f2a3b24bd2d33cc11"}, 1094 | {file = "simplejson-3.18.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:2d1b47f768e1f4c1c8a9457effabed735939401e85c0ddcdf68444c88a9242e6"}, 1095 | {file = "simplejson-3.18.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6d65ea4582b47d77e9094c22eb0aeded0ebd96c1df86e988870b40c6514c6e21"}, 1096 | {file = "simplejson-3.18.4-cp311-cp311-win32.whl", hash = "sha256:32de1672f91a789cc9e1c36c406b2d75457a242d64e9e73a70b9b814ef00095e"}, 1097 | {file = "simplejson-3.18.4-cp311-cp311-win_amd64.whl", hash = "sha256:c37b092d29741096c4723f48924a80b1d3de62ca1de254ce88178fa083dd520c"}, 1098 | {file = "simplejson-3.18.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:706a7fc81ceeb321a1040d008b134056012188f95a5c31ad94fb03153b35cc84"}, 1099 | {file = "simplejson-3.18.4-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ab64f087c5863ac621b42e227e5a43bd9b28de581afe7be12ad96562b9be8203"}, 1100 | {file = "simplejson-3.18.4-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7f27a079cb009ba569983061a50a9270b7e1d35f81e4eeaf0e26f8924027e550"}, 1101 | {file = "simplejson-3.18.4-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:93ba80fbf959b5852554f23201a5f4b30885930c303546ffa883859a435ea3cf"}, 1102 | {file = "simplejson-3.18.4-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0cdb5069870f7d26a34e5adc30672d0a7b26e652720530a023bb3a8d8a42e37f"}, 1103 | {file = "simplejson-3.18.4-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:340b7d085b4a5063aacb8664b1250e4a7426c16e1cc80705c548a229153af147"}, 1104 | {file = "simplejson-3.18.4-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:b9893852c559998f667e6434d2c2474518d4cdfd1b9cec8e57b3c9d577ba55c1"}, 1105 | {file = "simplejson-3.18.4-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:efae49d0148ec68b6e012f1b9e19bd530f4dced378ba919e3e906ae2b829cc31"}, 1106 | {file = "simplejson-3.18.4-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:a89d7fe994b115f0a792e6673f387af3db812a1760d594abad51e0ea11d3e470"}, 1107 | {file = "simplejson-3.18.4-cp36-cp36m-win32.whl", hash = "sha256:44058bea97429cfa0d6fb1d8eb0736a77022f34a326d5bc64fd6fed8d9304571"}, 1108 | {file = "simplejson-3.18.4-cp36-cp36m-win_amd64.whl", hash = "sha256:f85d87986ca375b8305b5c4f166783b8db383a6469e8b99b8dba22878388f234"}, 1109 | {file = "simplejson-3.18.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a3bba99178f1b25878752a8bc6da2f93fbae754ebd4914d2ac4b869b9fb24102"}, 1110 | {file = "simplejson-3.18.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5f67bffa6fc68e391b2250e1feb43d534ded64a7b918eb89cf7e3e679759d94"}, 1111 | {file = "simplejson-3.18.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a8ac155e3fd3b54a63040df024e57e62c130b15a2fc66eff3c2a946f42beed52"}, 1112 | {file = "simplejson-3.18.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:682b202f56d9d9e1bb22eaca3e37321002223fd5ddef7189b9233e3c14079917"}, 1113 | {file = "simplejson-3.18.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3dbfaa79b1c0efdb768392a19110f1aff793f3e8d43f57e292f46734b8affb45"}, 1114 | {file = "simplejson-3.18.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7339bd6203351555c1e728acd601ba95ebce0f6041ebdb386e025f00af3f1769"}, 1115 | {file = "simplejson-3.18.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:544e5607142d66a469ecf78a3154ec0f915834dc3b8cfdb2677a78ca58319ad6"}, 1116 | {file = "simplejson-3.18.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:56d36f47bc7c7684504f0f18feb161a0b1162546b3622e45aa6155f8285180ac"}, 1117 | {file = "simplejson-3.18.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:b482d1fdd8f860e743c7de8cd6dfe54fb9fe8cd6ccba29e2966912ac89e17b2f"}, 1118 | {file = "simplejson-3.18.4-cp37-cp37m-win32.whl", hash = "sha256:313dfd911723dc3022fed7050a7b315d5d0681cd56eee08e44e2cbd39fd9ad81"}, 1119 | {file = "simplejson-3.18.4-cp37-cp37m-win_amd64.whl", hash = "sha256:f5e0a03e533313eee9437ccc6c4eab47369f17bc919b57df4a20ccd8bc85d8fd"}, 1120 | {file = "simplejson-3.18.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5c4f59dd358c3a99efa46d62dc1583be3a1c37171f5240c4cbdc2d5838870902"}, 1121 | {file = "simplejson-3.18.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:041dd69026284d10f035cefb4a75026d2cfcef31f31e62585eeb2b7776e7e047"}, 1122 | {file = "simplejson-3.18.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:47509775a5c41ec2a6cd17c9c00fc14965cad8e6670059663872ba5e39332f57"}, 1123 | {file = "simplejson-3.18.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1b425a857ce52e651739314e4118fc68bd702ef983148b8fd5cb6f68bb6a020"}, 1124 | {file = "simplejson-3.18.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:deb71e6166e4f1264174d78b5b88abd52b14c6649e6eabaf9cf93cb1c7362850"}, 1125 | {file = "simplejson-3.18.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:827ddc3b3603f7d0421b054388da6face7871d800c4b3bbedeedc8778e4085ea"}, 1126 | {file = "simplejson-3.18.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc74a9ef4d61e18ee6f1886b6ef1fe285b1f432885288afacfb7402f7d469448"}, 1127 | {file = "simplejson-3.18.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:16fbebfc38ad4285c256d2430797fd669b0437d090e985c6d443521d4303b133"}, 1128 | {file = "simplejson-3.18.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:e7d3f7cd57ce0c6a5bb8133f8ed5c3d1be0473a88b7d91a300626298f12d0999"}, 1129 | {file = "simplejson-3.18.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:b43d3c2e204d709af955bdb904ae127fe137363ace87fbf7dc8fe6017f7f8449"}, 1130 | {file = "simplejson-3.18.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ab5941e1fd509fc151258477ef4b663fe14c94f8faf3581827bf4b02080fd4ba"}, 1131 | {file = "simplejson-3.18.4-cp38-cp38-win32.whl", hash = "sha256:a1163bfe5d043c20adeb5c4c8e89dd1dd39b375c8ca6f1c1e35ec537ad7a12e7"}, 1132 | {file = "simplejson-3.18.4-cp38-cp38-win_amd64.whl", hash = "sha256:8ccc982197982cdda19e3e5ba4ef7f6ad6bed3eb39bb423bfbf7fa2cd29488ab"}, 1133 | {file = "simplejson-3.18.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:01f426ee9e3a2d205aa4c22c3da996b51f2de75c4199ef703258a28b304dea8c"}, 1134 | {file = "simplejson-3.18.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:46b8cc86204b51eddcf157cbaf3c44a20f24393030442af0909eeb961186cb67"}, 1135 | {file = "simplejson-3.18.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:65de5876e34780b43f92d9d2539de16ecc56d16f56e56e59b34adfa1cebe064f"}, 1136 | {file = "simplejson-3.18.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fa6fe8fa94a831886ee164ac03514f361e1387a62a1b9da32fde5c0c1f27fa8d"}, 1137 | {file = "simplejson-3.18.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a50a9da1cf93e35f26c4ddee162abf3184a340339ec2d4001c34607b87e71b4"}, 1138 | {file = "simplejson-3.18.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a2285609b4edbf9957440642493788ebef6583042b3fb96217c2e71f29bc6d80"}, 1139 | {file = "simplejson-3.18.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5b217201efc007166e24e9a282007cc208a2d059350a7c5bd0b0303460ad3019"}, 1140 | {file = "simplejson-3.18.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0cc9a47bf8cde85c99db5f4a919bb756e62427ade0f2e875a6ec89ae8492d486"}, 1141 | {file = "simplejson-3.18.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e042ae053e05fe193514d51d6b0f0243729961901e9a75f8b596bfaf69522c52"}, 1142 | {file = "simplejson-3.18.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:d0d3b9f7cee233368d92c89746dde74313abafaa3ec1f0c06a3f4f164dc27bcc"}, 1143 | {file = "simplejson-3.18.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1844d7782652f859d9648531778582d4842d80cfff8d334eb23bb8da0d22a1b0"}, 1144 | {file = "simplejson-3.18.4-cp39-cp39-win32.whl", hash = "sha256:2a6e5c0e0817fb20dbb880c83caebbd4ef39f1901f6f8e53b73a3c74de4e5172"}, 1145 | {file = "simplejson-3.18.4-cp39-cp39-win_amd64.whl", hash = "sha256:34d95ad8e27754f0d91917600d6ea273e05c82a71021f168c45be48637d9502f"}, 1146 | {file = "simplejson-3.18.4-py3-none-any.whl", hash = "sha256:03de1ec4ad734f28ca49b0a758b997d752be0d089ed30360157c4e8811999c8f"}, 1147 | {file = "simplejson-3.18.4.tar.gz", hash = "sha256:6197cfebe659ac802a686b5408494115a7062b45cdf37679c4d6a9d4f39649b7"}, 1148 | ] 1149 | 1150 | [[package]] 1151 | name = "six" 1152 | version = "1.16.0" 1153 | description = "Python 2 and 3 compatibility utilities" 1154 | category = "main" 1155 | optional = false 1156 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" 1157 | files = [ 1158 | {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, 1159 | {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, 1160 | ] 1161 | 1162 | [[package]] 1163 | name = "soupsieve" 1164 | version = "2.4" 1165 | description = "A modern CSS selector implementation for Beautiful Soup." 1166 | category = "main" 1167 | optional = false 1168 | python-versions = ">=3.7" 1169 | files = [ 1170 | {file = "soupsieve-2.4-py3-none-any.whl", hash = "sha256:49e5368c2cda80ee7e84da9dbe3e110b70a4575f196efb74e51b94549d921955"}, 1171 | {file = "soupsieve-2.4.tar.gz", hash = "sha256:e28dba9ca6c7c00173e34e4ba57448f0688bb681b7c5e8bf4971daafc093d69a"}, 1172 | ] 1173 | 1174 | [[package]] 1175 | name = "toml" 1176 | version = "0.10.2" 1177 | description = "Python Library for Tom's Obvious, Minimal Language" 1178 | category = "main" 1179 | optional = false 1180 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 1181 | files = [ 1182 | {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, 1183 | {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, 1184 | ] 1185 | 1186 | [[package]] 1187 | name = "uritemplate" 1188 | version = "4.1.1" 1189 | description = "Implementation of RFC 6570 URI Templates" 1190 | category = "main" 1191 | optional = false 1192 | python-versions = ">=3.6" 1193 | files = [ 1194 | {file = "uritemplate-4.1.1-py2.py3-none-any.whl", hash = "sha256:830c08b8d99bdd312ea4ead05994a38e8936266f84b9a7878232db50b044e02e"}, 1195 | {file = "uritemplate-4.1.1.tar.gz", hash = "sha256:4346edfc5c3b79f694bccd6d6099a322bbeb628dbf2cd86eea55a456ce5124f0"}, 1196 | ] 1197 | 1198 | [[package]] 1199 | name = "urllib3" 1200 | version = "1.26.15" 1201 | description = "HTTP library with thread-safe connection pooling, file post, and more." 1202 | category = "main" 1203 | optional = false 1204 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" 1205 | files = [ 1206 | {file = "urllib3-1.26.15-py2.py3-none-any.whl", hash = "sha256:aa751d169e23c7479ce47a0cb0da579e3ede798f994f5816a74e4f4500dcea42"}, 1207 | {file = "urllib3-1.26.15.tar.gz", hash = "sha256:8a388717b9476f934a21484e8c8e61875ab60644d29b9b39e11e4b9dc1c6b305"}, 1208 | ] 1209 | 1210 | [package.extras] 1211 | brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] 1212 | secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] 1213 | socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] 1214 | 1215 | [[package]] 1216 | name = "werkzeug" 1217 | version = "2.2.3" 1218 | description = "The comprehensive WSGI web application library." 1219 | category = "main" 1220 | optional = false 1221 | python-versions = ">=3.7" 1222 | files = [ 1223 | {file = "Werkzeug-2.2.3-py3-none-any.whl", hash = "sha256:56433961bc1f12533306c624f3be5e744389ac61d722175d543e1751285da612"}, 1224 | {file = "Werkzeug-2.2.3.tar.gz", hash = "sha256:2e1ccc9417d4da358b9de6f174e3ac094391ea1d4fbef2d667865d819dfd0afe"}, 1225 | ] 1226 | 1227 | [package.dependencies] 1228 | MarkupSafe = ">=2.1.1" 1229 | 1230 | [package.extras] 1231 | watchdog = ["watchdog"] 1232 | 1233 | [[package]] 1234 | name = "zipp" 1235 | version = "3.15.0" 1236 | description = "Backport of pathlib-compatible object wrapper for zip files" 1237 | category = "main" 1238 | optional = false 1239 | python-versions = ">=3.7" 1240 | files = [ 1241 | {file = "zipp-3.15.0-py3-none-any.whl", hash = "sha256:48904fc76a60e542af151aded95726c1a5c34ed43ab4134b597665c86d7ad556"}, 1242 | {file = "zipp-3.15.0.tar.gz", hash = "sha256:112929ad649da941c23de50f356a2b5570c954b65150642bccdd66bf194d224b"}, 1243 | ] 1244 | 1245 | [package.extras] 1246 | docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] 1247 | testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] 1248 | 1249 | [metadata] 1250 | lock-version = "2.0" 1251 | python-versions = "^3.8" 1252 | content-hash = "8c01f96be70bdd2dfe587f87416a5a23207704993a5c76b4b1be7214a37557cb" 1253 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "fava-review" 3 | version = "1.0.1" 4 | description = "A Fava extension to help review transactions over a series of periods." 5 | authors = ["Jakub Jamro "] 6 | license = "MIT License" 7 | 8 | [tool.poetry.dependencies] 9 | python = "^3.8" 10 | fava = "1.25.1" 11 | petl = "^1.7.4" 12 | beautifulsoup4 = "^4.10.0" 13 | 14 | [tool.poetry.dev-dependencies] 15 | PyHamcrest = "^2.0.3" 16 | pytest = "^6.2.5" 17 | flake8 = "^4.0.1" 18 | 19 | [build-system] 20 | requires = ["poetry-core>=1.0.0"] 21 | build-backend = "poetry.core.masonry.api" -------------------------------------------------------------------------------- /screenshot-date-and-account-filter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubauk/fava-review/dbc9089011a0072e8def81d3f78a90e2dfb2996e/screenshot-date-and-account-filter.png -------------------------------------------------------------------------------- /screenshot-sorting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubauk/fava-review/dbc9089011a0072e8def81d3f78a90e2dfb2996e/screenshot-sorting.png -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubauk/fava-review/dbc9089011a0072e8def81d3f78a90e2dfb2996e/screenshot.png -------------------------------------------------------------------------------- /test/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubauk/fava-review/dbc9089011a0072e8def81d3f78a90e2dfb2996e/test/__init__.py -------------------------------------------------------------------------------- /test/conftest.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | from pathlib import Path 4 | from typing import Callable 5 | 6 | import bs4 7 | import jinja2 8 | import pytest 9 | from fava.application import create_app 10 | from fava.core import FavaLedger 11 | 12 | TEST_BEANCOUNT_FILES = [os.path.join(os.path.dirname(__file__), 'example.beancount')] 13 | 14 | 15 | @pytest.fixture 16 | def fava_app(): 17 | return create_app(TEST_BEANCOUNT_FILES, load=True) 18 | 19 | 20 | @pytest.fixture 21 | def test_request(request, fava_app): 22 | path = request.param if hasattr(request, 'param') else 'http://localhost/beancount/extension/FavaReview/' 23 | with fava_app.test_request_context(path=path) as ctx: 24 | ctx.push() 25 | ctx.match_request() 26 | fava_app.preprocess_request() 27 | yield fava_app 28 | ctx.pop() 29 | 30 | 31 | @pytest.fixture 32 | def load_ledger() -> Callable: 33 | def ledger_loader(filename: str): 34 | ledger = FavaLedger(os.path.join(os.path.dirname(__file__), filename)) 35 | ledger.load_file() 36 | return ledger 37 | 38 | return ledger_loader 39 | 40 | 41 | @pytest.fixture 42 | def example_ledger(test_request, load_ledger: Callable) -> FavaLedger: 43 | return load_ledger('example.beancount') 44 | 45 | 46 | @pytest.fixture 47 | def extension_template(fava_app) -> Callable: 48 | extension_directory = Path(os.path.dirname(__file__)).parent.absolute() 49 | fava_directory = os.path.dirname(sys.modules['fava'].__file__) 50 | 51 | def template_loader(template_file): 52 | fava_app.jinja_env.loader = jinja2.FileSystemLoader( 53 | searchpath=[os.path.join(extension_directory, 'fava_review', 'templates'), 54 | (os.path.join(fava_directory, 'templates'))]) 55 | return fava_app.jinja_env.get_template(template_file) 56 | 57 | return template_loader 58 | 59 | 60 | @pytest.fixture 61 | def extension_template_soup(extension_template) -> Callable: 62 | def soup_of(template_file, extension): 63 | template = extension_template(template_file) 64 | return bs4.BeautifulSoup(template.render(extension=extension)) 65 | 66 | return soup_of 67 | -------------------------------------------------------------------------------- /test/example.beancount: -------------------------------------------------------------------------------- 1 | 2 | option "operating_currency" "GBP" 3 | 4 | 2020-10-10 custom "fava-extension" "fava_review" "" 5 | 6 | 2020-10-10 open Income:Salary:ABC 7 | 2020-10-11 open Income:Salary:Hoogle 8 | 2020-10-11 open Expenses:Groceries 9 | 2020-10-12 open Liabilities:CreditCard 10 | 2020-10-10 open Assets:Current:JointAccount 11 | 12 | 2020-10-10 * "Salary From ABC Corp." 13 | Income:Salary:ABC -1000.00 GBP 14 | Assets:Current:JointAccount 15 | 16 | 2020-10-11 * "Salary From Hoogle" 17 | Income:Salary:Hoogle -200.00 USD 18 | Assets:Current:JointAccount 19 | 20 | 2020-10-11 * "Groceries" 21 | Assets:Current:JointAccount 22 | Expenses:Groceries 10.00 GBP 23 | 24 | 2020-10-12 * "Pay Credit Card" 25 | Assets:Current:JointAccount 26 | Liabilities:CreditCard 100.00 GBP 27 | 28 | 2020-11-10 * "Salary From ABC Corp." 29 | Income:Salary:ABC -1000.00 GBP 30 | Assets:Current:JointAccount 31 | 32 | 2020-11-11 * "Groceries" 33 | Assets:Current:JointAccount 34 | Expenses:Groceries 20.00 GBP 35 | 36 | 2020-11-12 * "Pay Credit Card" 37 | Assets:Current:JointAccount 38 | Liabilities:CreditCard 90.00 GBP 39 | 40 | 2020-12-10 * "Salary From ABC Corp." 41 | Income:Salary:ABC -1000.00 GBP 42 | Assets:Current:JointAccount 43 | 44 | 2020-12-11 * "Groceries" 45 | Assets:Current:JointAccount 46 | Expenses:Groceries 30.00 GBP 47 | 48 | 2020-12-12 * "Pay Credit Card" 49 | Assets:Current:JointAccount 50 | Liabilities:CreditCard 80.00 GBP 51 | 52 | 2021-01-10 * "Salary From ABC Corp." 53 | Income:Salary:ABC -1000.00 GBP 54 | Assets:Current:JointAccount 55 | 56 | 2021-01-12 * "Pay Credit Card" 57 | Assets:Current:JointAccount 58 | Liabilities:CreditCard 200.00 GBP 59 | 60 | 2021-02-10 * "Salary From ABC Corp." 61 | Income:Salary:ABC -1000.00 GBP 62 | Assets:Current:JointAccount 63 | 64 | 2021-03-10 * "Salary From ABC Corp." 65 | Income:Salary:ABC -1000.00 GBP 66 | Assets:Current:JointAccount 67 | 68 | 2021-03-11 * "Groceries" 69 | Assets:Current:JointAccount 70 | Expenses:Groceries 60.00 GBP 71 | 72 | 2021-04-10 * "Salary From ABC Corp." 73 | Income:Salary:ABC -1000.00 GBP 74 | Assets:Current:JointAccount 75 | 76 | 2021-04-11 * "Groceries" 77 | Assets:Current:JointAccount 78 | Expenses:Groceries 70.00 GBP 79 | 80 | 2021-04-12 * "Pay Credit Card" 81 | Assets:Current:JointAccount 82 | Liabilities:CreditCard 25.00 GBP 83 | -------------------------------------------------------------------------------- /test/gbp-first-operating-currency-and-usd-more-than-gbp.beancount: -------------------------------------------------------------------------------- 1 | option "operating_currency" "GBP" 2 | option "operating_currency" "USD" 3 | 4 | 2020-10-10 open Income:Salary:ABC 5 | 2020-10-10 open Assets:Current:JointAccount 6 | 7 | 2020-10-10 * "Salary From Hoogle" 8 | Income:Salary:ABC -1000.00 USD 9 | Assets:Current:JointAccount 10 | 11 | 2020-10-11 * "Salary From ABC Corp." 12 | Income:Salary:Hoogle -200.00 GBP 13 | Assets:Current:JointAccount 14 | 15 | 2020-10-11 * "Groceries" 16 | Assets:Current:JointAccount 17 | Expenses:Groceries 10.00 USD 18 | 19 | 2020-11-10 * "Salary From Hoogle" 20 | Income:Salary:ABC -1000.00 USD 21 | Assets:Current:JointAccount 22 | 23 | 2020-11-11 * "Groceries" 24 | Assets:Current:JointAccount 25 | Expenses:Groceries 20.00 USD 26 | 27 | 2020-12-10 * "Salary From Hoogle" 28 | Income:Salary:ABC -1000.00 USD 29 | Assets:Current:JointAccount 30 | 31 | 2020-12-11 * "Groceries" 32 | Assets:Current:JointAccount 33 | Expenses:Groceries 30.00 USD 34 | 35 | 2021-01-10 * "Salary From Hoogle" 36 | Income:Salary:ABC -1000.00 USD 37 | Assets:Current:JointAccount 38 | 39 | 2021-02-10 * "Salary From Hoogle" 40 | Income:Salary:ABC -1000.00 USD 41 | Assets:Current:JointAccount 42 | 43 | 2021-03-10 * "Salary From Hoogle" 44 | Income:Salary:ABC -1000.00 USD 45 | Assets:Current:JointAccount 46 | 47 | 2021-03-11 * "Groceries" 48 | Assets:Current:JointAccount 49 | Expenses:Groceries 60.00 USD 50 | 51 | 2021-04-10 * "Salary From Hoogle" 52 | Income:Salary:ABC -1000.00 USD 53 | Assets:Current:JointAccount 54 | 55 | 2021-04-11 * "Groceries" 56 | Assets:Current:JointAccount 57 | Expenses:Groceries 70.00 USD 58 | -------------------------------------------------------------------------------- /test/no-operating-currency-and-usd-more-than-gbp.beancount: -------------------------------------------------------------------------------- 1 | 2 | 2020-10-10 open Income:Salary:ABC 3 | 2020-10-10 open Assets:Current:JointAccount 4 | 5 | 2020-10-10 * "Salary From Hoogle" 6 | Income:Salary:ABC -1000.00 USD 7 | Assets:Current:JointAccount 8 | 9 | 2020-10-11 * "Salary From ABC Corp." 10 | Income:Salary:Hoogle -200.00 GBP 11 | Assets:Current:JointAccount 12 | 13 | 2020-10-11 * "Groceries" 14 | Assets:Current:JointAccount 15 | Expenses:Groceries 10.00 USD 16 | 17 | 2020-11-10 * "Salary From Hoogle" 18 | Income:Salary:ABC -1000.00 USD 19 | Assets:Current:JointAccount 20 | 21 | 2020-11-11 * "Groceries" 22 | Assets:Current:JointAccount 23 | Expenses:Groceries 20.00 USD 24 | 25 | 2020-12-10 * "Salary From Hoogle" 26 | Income:Salary:ABC -1000.00 USD 27 | Assets:Current:JointAccount 28 | 29 | 2020-12-11 * "Groceries" 30 | Assets:Current:JointAccount 31 | Expenses:Groceries 30.00 USD 32 | 33 | 2021-01-10 * "Salary From Hoogle" 34 | Income:Salary:ABC -1000.00 USD 35 | Assets:Current:JointAccount 36 | 37 | 2021-02-10 * "Salary From Hoogle" 38 | Income:Salary:ABC -1000.00 USD 39 | Assets:Current:JointAccount 40 | 41 | 2021-03-10 * "Salary From Hoogle" 42 | Income:Salary:ABC -1000.00 USD 43 | Assets:Current:JointAccount 44 | 45 | 2021-03-11 * "Groceries" 46 | Assets:Current:JointAccount 47 | Expenses:Groceries 60.00 USD 48 | 49 | 2021-04-10 * "Salary From Hoogle" 50 | Income:Salary:ABC -1000.00 USD 51 | Assets:Current:JointAccount 52 | 53 | 2021-04-11 * "Groceries" 54 | Assets:Current:JointAccount 55 | Expenses:Groceries 70.00 USD 56 | -------------------------------------------------------------------------------- /test/test_fava_review_template.py: -------------------------------------------------------------------------------- 1 | from typing import Sequence, Union 2 | 3 | import pytest 4 | from fava.core import FavaLedger 5 | from hamcrest import assert_that, contains_exactly 6 | from hamcrest.core.matcher import Matcher 7 | 8 | from fava_review import FavaReview 9 | 10 | 11 | def test_table_header_contains_all_fields(example_ledger: FavaLedger, extension_template_soup: callable) -> None: 12 | template_soup = extension_template_soup("FavaReview.html", 13 | FavaReview(example_ledger)) 14 | tags: Sequence[str] = [t.get_text().strip() for t in template_soup.select('thead th')] 15 | matchers: Matcher[Sequence[str]] = contains_exactly( 16 | 'account', '2020-10', '2020-11', '2020-12', '2021-01', '2021-02', '2021-03', '2021-04', 'total') 17 | assert_that(tags, matchers) 18 | 19 | 20 | def test_table_body_contains_all_fields(example_ledger: FavaLedger, extension_template_soup: callable) -> None: 21 | template_soup = extension_template_soup("FavaReview.html", FavaReview(example_ledger)) 22 | 23 | tags: Sequence[Sequence[str]] = [[c.get_text().strip() for c in row.find_all('td')] 24 | for row in template_soup.select('tbody tr')] 25 | matchers: Matcher[Sequence[Sequence[str]]] = contains_exactly( 26 | ['Expenses:Groceries', '10.00', '20.00', '30.00', '0', '0', '60.00', '70.00', '190.00'], 27 | ['Income:Salary:ABC', '-1000.00', '-1000.00', '-1000.00', 28 | '-1000.00', '-1000.00', '-1000.00', '-1000.00', '-7000.00']) 29 | assert_that(tags, matchers) 30 | 31 | 32 | def test_table_footer_contains_all_fields(example_ledger: FavaLedger, extension_template_soup: callable) -> None: 33 | template_soup = extension_template_soup("FavaReview.html", FavaReview(example_ledger)) 34 | tags: Sequence[str] = [t.get_text() for t in template_soup.select('tfoot td')] 35 | matchers: Matcher[Sequence[str]] = contains_exactly( 36 | 'total', '-990.00', '-980.00', '-970.00', '-1000.00', '-1000.00', '-940.00', '-930.00', '-6810.00') 37 | assert_that(tags, matchers) 38 | 39 | 40 | def test_table_body_account_names_are_links(example_ledger: FavaLedger, extension_template_soup: callable) -> None: 41 | template_soup = extension_template_soup("FavaReview.html", FavaReview(example_ledger)) 42 | tags: Sequence[str] = [link.get_text().strip() for link in template_soup.select('tbody td a')] 43 | matchers: Matcher[Sequence[str]] = contains_exactly('Expenses:Groceries', 'Income:Salary:ABC') 44 | assert_that(tags, matchers) 45 | 46 | 47 | def test_table_head_has_sort_attributes(example_ledger: FavaLedger, extension_template_soup: callable) -> None: 48 | template_soup = extension_template_soup("FavaReview.html", FavaReview(example_ledger)) 49 | tags: Sequence[tuple[str, Union[str, str]]] = \ 50 | [(tag.get_text().strip(), 'no data-sort' if not tag.has_attr('data-sort') else tag['data-sort']) 51 | for tag in template_soup.select('thead th')] 52 | matchers: Matcher[Sequence[tuple[str, Union[str, str]]]] = \ 53 | contains_exactly(('account', 'string'), ('2020-10', 'num'), ('2020-11', 'num'), ('2020-12', 'num'), 54 | ('2021-01', 'num'), ('2021-02', 'num'), ('2021-03', 'num'), ('2021-04', 'num'), 55 | ('total', 'num')) 56 | assert_that(tags, matchers) 57 | 58 | 59 | def test_header_has_all_view_options(example_ledger: FavaLedger, extension_template_soup: callable) -> None: 60 | template_soup = extension_template_soup("FavaReview.html", FavaReview(example_ledger)) 61 | tags: Sequence[str] = [tag.get_text().strip() for tag in template_soup.select('div.headerline b')] 62 | assert_that(tags, contains_exactly('Income Statement', 'Balance Sheet')) 63 | 64 | 65 | def test_header_only_has_links_for_unselected_view_options(example_ledger: FavaLedger, 66 | extension_template_soup: callable) -> None: 67 | template_soup = extension_template_soup("FavaReview.html", FavaReview(example_ledger)) 68 | tags_with_links: Sequence[str] = [tag.get_text().strip() for tag in template_soup.select('div.headerline a')] 69 | tags_no_links: Sequence[str] = [tag.get_text().strip() for tag in template_soup.select('div.headerline b') 70 | if tag.find('a') is None] 71 | assert_that(tags_with_links, contains_exactly('Balance Sheet')) 72 | assert_that(tags_no_links, contains_exactly('Income Statement')) 73 | 74 | 75 | @pytest.mark.parametrize('test_request', ['http://localhost/beancount/extension/FavaReview/?view=balance_sheet'], 76 | indirect=True) 77 | def test_header_link_selection_changes_with_each_view(example_ledger: FavaLedger, 78 | extension_template_soup: callable) -> None: 79 | template_soup = extension_template_soup("FavaReview.html", FavaReview(example_ledger)) 80 | tags_with_links: Sequence[str] = [tag.get_text().strip() for tag in template_soup.select('div.headerline a')] 81 | tags_no_links: Sequence[str] = [tag.get_text().strip() for tag in template_soup.select('div.headerline b') 82 | if tag.find('a') is None] 83 | assert_that(tags_with_links, contains_exactly('Income Statement')) 84 | assert_that(tags_no_links, contains_exactly('Balance Sheet')) 85 | pass 86 | -------------------------------------------------------------------------------- /test/test_pivot_review.py: -------------------------------------------------------------------------------- 1 | from decimal import Decimal 2 | from typing import NamedTuple, Callable 3 | 4 | import petl 5 | from fava.core import FavaLedger 6 | from fava.util.date import Interval 7 | from hamcrest import assert_that, is_ 8 | from hamcrest.core.base_matcher import BaseMatcher, T 9 | from hamcrest.core.description import Description 10 | from hamcrest.core.matcher import Matcher 11 | from hamcrest.core.string_description import StringDescription 12 | from petl import Table, MemorySource 13 | 14 | from fava_review.pivot_review import PivotReview 15 | from fava_review.pivot_review import bean_query_to_petl 16 | 17 | 18 | def test_monthly_income_statement_query(example_ledger: FavaLedger): 19 | pivot_review = PivotReview(example_ledger) 20 | rows = pivot_review.income_statement_by(Interval.MONTH) 21 | 22 | assert_that(rows, is_([{'account': 'Expenses:Groceries', '2020-10': Decimal('10.00'), 23 | '2020-11': Decimal('20.00'), '2020-12': Decimal('30.00'), 24 | '2021-01': Decimal('0.00'), '2021-02': Decimal('0.00'), 25 | '2021-03': Decimal('60.00'), '2021-04': Decimal('70.00'), 26 | 'total': Decimal('190.00')}, 27 | {'account': 'Income:Salary:ABC', '2020-10': Decimal('-1000.00'), 28 | '2020-11': Decimal('-1000.00'), '2020-12': Decimal('-1000.00'), 29 | '2021-01': Decimal('-1000.00'), '2021-02': Decimal('-1000.00'), 30 | '2021-03': Decimal('-1000.00'), '2021-04': Decimal('-1000.00'), 31 | 'total': Decimal('-7000.00')}, 32 | {'account': 'total', '2020-10': Decimal('-990.00'), 33 | '2020-11': Decimal('-980.00'), '2020-12': Decimal('-970.00'), 34 | '2021-01': Decimal('-1000.00'), '2021-02': Decimal('-1000.00'), 35 | '2021-03': Decimal('-940.00'), '2021-04': Decimal('-930.00'), 36 | 'total': Decimal('-6810.00')}])) 37 | 38 | 39 | def test_yearly_income_statement_query(example_ledger: FavaLedger): 40 | pivot_review = PivotReview(example_ledger) 41 | rows = pivot_review.income_statement_by(Interval.YEAR) 42 | 43 | assert_that(rows, is_([{'account': 'Expenses:Groceries', 44 | '2020': Decimal('60.00'), '2021': Decimal('130.00'), 'total': Decimal('190.00')}, 45 | {'account': 'Income:Salary:ABC', 46 | '2020': Decimal('-3000.00'), '2021': Decimal('-4000.00'), 'total': Decimal('-7000.00')}, 47 | {'account': 'total', 48 | '2020': Decimal('-2940.00'), '2021': Decimal('-3870.00'), 'total': Decimal('-6810.00')}])) 49 | 50 | 51 | def test_quarterly_income_statement_query(example_ledger: FavaLedger): 52 | pivot_review = PivotReview(example_ledger) 53 | rows = pivot_review.income_statement_by(Interval.QUARTER) 54 | 55 | assert_that(rows, is_([{'account': 'Expenses:Groceries', 56 | '2020Q4': Decimal('60.00'), '2021Q1': Decimal('60.00'), '2021Q2': Decimal('70.00'), 57 | 'total': Decimal('190.00')}, 58 | {'account': 'Income:Salary:ABC', 59 | '2020Q4': Decimal('-3000.00'), '2021Q1': Decimal('-3000.00'), '2021Q2': Decimal('-1000.00'), 60 | 'total': Decimal('-7000.00')}, 61 | {'account': 'total', 62 | '2020Q4': Decimal('-2940.00'), '2021Q1': Decimal('-2940.00'), '2021Q2': Decimal('-930.00'), 63 | 'total': Decimal('-6810.00')}])) 64 | 65 | 66 | def test_monthly_balance_sheet_query(example_ledger: FavaLedger): 67 | pivot_review = PivotReview(example_ledger) 68 | rows = pivot_review.balance_sheet_by(Interval.MONTH) 69 | 70 | assert_that(rows, is_([{'account': 'Assets:Current:JointAccount', '2020-10': Decimal('890.00'), 71 | '2020-11': Decimal('890.00'), '2020-12': Decimal('890.00'), 72 | '2021-01': Decimal('800.00'), '2021-02': Decimal('1000.00'), 73 | '2021-03': Decimal('940.00'), '2021-04': Decimal('905.00'), 74 | 'total': Decimal('6315.00')}, 75 | {'account': 'Liabilities:CreditCard', '2020-10': Decimal('100.00'), 76 | '2020-11': Decimal('90.00'), '2020-12': Decimal('80.00'), 77 | '2021-01': Decimal('200.00'), '2021-02': Decimal('0.00'), 78 | '2021-03': Decimal('0.00'), '2021-04': Decimal('25.00'), 79 | 'total': Decimal('495.00')}, 80 | {'account': 'total', '2020-10': Decimal('990.00'), 81 | '2020-11': Decimal('980.00'), '2020-12': Decimal('970.00'), 82 | '2021-01': Decimal('1000.00'), '2021-02': Decimal('1000.00'), 83 | '2021-03': Decimal('940.00'), '2021-04': Decimal('930.00'), 84 | 'total': Decimal('6810.00')}])) 85 | 86 | 87 | def test_yearly_balance_sheet_query(example_ledger: FavaLedger): 88 | pivot_review = PivotReview(example_ledger) 89 | rows = pivot_review.balance_sheet_by(Interval.YEAR) 90 | 91 | assert_that(rows, is_([{'account': 'Assets:Current:JointAccount', 92 | '2020': Decimal('2670.00'), '2021': Decimal('3645.00'), 'total': Decimal('6315.00')}, 93 | {'account': 'Liabilities:CreditCard', 94 | '2020': Decimal('270.00'), '2021': Decimal('225.00'), 'total': Decimal('495.00')}, 95 | {'account': 'total', 96 | '2020': Decimal('2940.00'), '2021': Decimal('3870.00'), 'total': Decimal('6810.00')}])) 97 | 98 | 99 | def test_quarterly_balance_sheet_query(example_ledger: FavaLedger): 100 | pivot_review = PivotReview(example_ledger) 101 | rows = pivot_review.balance_sheet_by(Interval.QUARTER) 102 | 103 | assert_that(rows, is_([{'account': 'Assets:Current:JointAccount', 104 | '2020Q4': Decimal('2670.00'), '2021Q1': Decimal('2740.00'), '2021Q2': Decimal('905.00'), 105 | 'total': Decimal('6315.00')}, 106 | {'account': 'Liabilities:CreditCard', 107 | '2020Q4': Decimal('270.00'), '2021Q1': Decimal('200.00'), '2021Q2': Decimal('25.00'), 108 | 'total': Decimal('495.00')}, 109 | {'account': 'total', 110 | '2020Q4': Decimal('2940.00'), '2021Q1': Decimal('2940.00'), '2021Q2': Decimal('930.00'), 111 | 'total': Decimal('6810.00')}])) 112 | 113 | 114 | def petl_matching_csv(param) -> Matcher[Table]: 115 | class PetlMatcher(BaseMatcher[Table]): 116 | @staticmethod 117 | def _check_and_describe(item: T, mismatch_description: Description): 118 | if not isinstance(item, Table): 119 | mismatch_description.append_text("item was not PETL Table, but was ").append_text(item) 120 | return False 121 | 122 | mem_source = MemorySource('') 123 | petl.tocsv(item, mem_source, lineterminator='\n') 124 | 125 | return True 126 | 127 | def _matches(self, item: T) -> bool: 128 | return self._check_and_describe(item, StringDescription()) 129 | 130 | def describe_to(self, description: Description) -> None: 131 | description.append_text("A PETL Table which produces csv:\n").append_text(param) 132 | 133 | def describe_mismatch(self, item: T, mismatch_description: Description) -> None: 134 | self._check_and_describe(item, mismatch_description) 135 | 136 | return PetlMatcher() 137 | 138 | 139 | def test_bean_query_to_petl(): 140 | # noinspection PyPep8Naming 141 | TestTuple = NamedTuple('TestTuple', 142 | [('year', int), ('total', Decimal), 143 | ('account', str), ('month', int), 144 | ('currency', str)]) 145 | t = bean_query_to_petl( 146 | [TestTuple(account='Expenses:EatingOut', month=2, year=2022, total=Decimal(2), currency='GBP')], Interval.MONTH) 147 | assert_that(list(petl.dicts(t)), 148 | is_([{'date': '2022-02', 'account': 'Expenses:EatingOut', 'total': Decimal('2'), 'currency': 'GBP'}])) 149 | 150 | 151 | def test_starting_currency_matches_currency_with_more_positions(load_ledger: Callable): 152 | ledger = load_ledger('no-operating-currency-and-usd-more-than-gbp.beancount') 153 | review = PivotReview(ledger) 154 | assert_that(review.best_starting_currency(), is_('USD')) 155 | 156 | 157 | def test_starting_currency_matches_first_operating_currency(load_ledger: Callable): 158 | ledger = load_ledger('gbp-first-operating-currency-and-usd-more-than-gbp.beancount') 159 | review = PivotReview(ledger) 160 | assert_that(review.best_starting_currency(), is_('GBP')) 161 | --------------------------------------------------------------------------------