diff --git a/.github/workflows/documentation_check.yml b/.github/workflows/documentation_check.yml index e5fb46fc..c64eee83 100644 --- a/.github/workflows/documentation_check.yml +++ b/.github/workflows/documentation_check.yml @@ -1,11 +1,22 @@ name: Documentation Check -on: [push, pull_request] +on: + push: + branches: [main] + pull_request: jobs: markdown-link-check: name: Check markdown links runs-on: ubuntu-latest steps: - - uses: actions/checkout@main - - uses: gaurav-nelson/github-action-markdown-link-check@v1 + - name: Checkout Code + uses: actions/checkout@v4 + + - name: Link Checker + uses: lycheeverse/lychee-action@v2 + with: + # Check all markdown files recursively + args: --config lychee.toml --verbose --no-progress './**/*.md' + # Fail the job if broken links are found + fail: true diff --git a/.github/workflows/static_analysis.yml b/.github/workflows/static_analysis.yml index 3d20bd0b..57e06164 100644 --- a/.github/workflows/static_analysis.yml +++ b/.github/workflows/static_analysis.yml @@ -8,7 +8,7 @@ jobs: strategy: matrix: os: [ubuntu-latest] - python-version: [3.9] + python-version: ["3.10"] env: PYTHONPATH: ./src:./tests MYPYPATH: ./src:./tests:./src/stubs diff --git a/.github/workflows/unix_unit_tests.yml b/.github/workflows/unix_unit_tests.yml index bb144585..d6dc154f 100644 --- a/.github/workflows/unix_unit_tests.yml +++ b/.github/workflows/unix_unit_tests.yml @@ -8,7 +8,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest] - python-version: [3.8, 3.9, "3.10", 3.11, 3.12] + python-version: ["3.10", 3.11, 3.12, 3.13, 3.14] env: PYTHONPATH: ./src:./test steps: diff --git a/.github/workflows/windows_unit_tests.yml b/.github/workflows/windows_unit_tests.yml index 245e7960..5e0b789c 100644 --- a/.github/workflows/windows_unit_tests.yml +++ b/.github/workflows/windows_unit_tests.yml @@ -8,7 +8,7 @@ jobs: strategy: matrix: os: [windows-latest] - python-version: [3.8, 3.9, "3.10", 3.11, 3.12] + python-version: ["3.10", 3.11, 3.12, 3.13, 3.14] env: PYTHONPATH: .\src;.\test steps: diff --git a/README.dev.md b/README.dev.md index 1031ad1e..0abde554 100644 --- a/README.dev.md +++ b/README.dev.md @@ -51,7 +51,7 @@ RP2 is released under the terms of Apache License Version 2.0. For more informat The latest RP2 source can be downloaded at: ## Setup -RP2 has been tested on Ubuntu Linux, macOS and Windows 10 but it should work on all systems that have Python version 3.8.0 or greater. Virtualenv is recommended for RP2 development. +RP2 has been tested on Ubuntu Linux, macOS and Windows 10 but it should work on all systems that have Python version 3.10.0 or greater. Virtualenv is recommended for RP2 development. ### Setup on Ubuntu Linux First make sure Python, pip and virtualenv are installed. If not, open a terminal window and enter the following commands: @@ -82,7 +82,7 @@ virtualenv -p python3 .venv .venv/bin/pip3 install -e '.[dev]' ``` ### Setup on Windows 10 -First make sure [Python](https://python.org) 3.8 or greater is installed (in the Python installer window be sure to click on "Add Python to PATH"), then open a PowerShell window and enter the following commands: +First make sure [Python](https://python.org) 3.10 or greater is installed (in the Python installer window be sure to click on "Add Python to PATH"), then open a PowerShell window and enter the following commands: ``` python -m pip install virtualenv ``` @@ -97,7 +97,7 @@ python -m pip install -e ".[dev]" If `activate.ps1` cannot be loaded because running scripts is disabled on the system, run `activate.bat` instead or change the PowerShell execution policy `Set-ExecutionPolicy RemoteSigned -Scope CurrentUser`. ### Setup on Other Unix-like Systems -* install python 3.8 or greater +* install python 3.10 or greater * install pip3 * install virtualenv diff --git a/README.md b/README.md index 77f3b3bc..fadb1e36 100644 --- a/README.md +++ b/README.md @@ -108,7 +108,7 @@ RP2 is released under the terms of Apache License Version 2.0. For more informat The latest version of RP2 can be downloaded at: ## Installation -RP2 has been tested on Ubuntu Linux, macOS and Windows 10 but it should work on all systems that have Python version 3.8.0 or greater. +RP2 has been tested on Ubuntu Linux, macOS and Windows 10 but it should work on all systems that have Python version 3.10.0 or greater. ### Installation on Ubuntu Linux Open a terminal window and enter the following commands: @@ -133,13 +133,13 @@ Then install RP2: pip install rp2 ``` ### Installation on Windows 10 -First make sure [Python](https://python.org) 3.8 or greater is installed (in the Python installer window be sure to click on "Add Python to PATH"), then open a PowerShell window and enter the following: +First make sure [Python](https://python.org) 3.10 or greater is installed (in the Python installer window be sure to click on "Add Python to PATH"), then open a PowerShell window and enter the following: ``` pip install rp2 ``` ### Installation on Other Unix-like Systems -* install python 3.8 or greater +* install python 3.10 or greater * install pip3 Then install RP2: diff --git a/lychee.toml b/lychee.toml new file mode 100644 index 00000000..b6cd449a --- /dev/null +++ b/lychee.toml @@ -0,0 +1,7 @@ +# Ignore these domains because they block CI/CD bots +exclude = [ + "https://www.forbes.com/.*", + "https://www.reddit.com/.*", + "https://finance.yahoo.com/.*", + "https://www.cointracker.io/.*" +] \ No newline at end of file diff --git a/mypy.ini b/mypy.ini index 5b86b04a..c45ffcf9 100644 --- a/mypy.ini +++ b/mypy.ini @@ -30,7 +30,6 @@ pretty = True show_column_numbers = True show_error_codes = True show_error_context = True -show_none_errors = True strict_equality = True strict_optional = True warn_no_return = True diff --git a/setup.cfg b/setup.cfg index 74e6bd7e..d882ecb7 100644 --- a/setup.cfg +++ b/setup.cfg @@ -14,11 +14,11 @@ classifiers = Intended Audience :: End Users/Desktop License :: OSI Approved :: Apache Software License Operating System :: OS Independent - Programming Language :: Python :: 3.8 - Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 Programming Language :: Python :: 3.11 Programming Language :: Python :: 3.12 + Programming Language :: Python :: 3.13 + Programming Language :: Python :: 3.14 Topic :: Office/Business :: Financial :: Accounting Topic :: Utilities Typing :: Typed @@ -56,13 +56,14 @@ dev = pytest-mock rope types-jsonschema + types-pycountry types-python-dateutil [options.packages.find] where = src include_package_data = True zip_safe = False -python_requires = >=3.8 +python_requires = >=3.10 [options.package_data] rp2 = py.typed, locales/*/*/*.mo diff --git a/src/rp2/abstract_country.py b/src/rp2/abstract_country.py index 32a8be24..439e175f 100644 --- a/src/rp2/abstract_country.py +++ b/src/rp2/abstract_country.py @@ -16,6 +16,7 @@ from typing import List, Set from pycountry import countries, currencies + from rp2.rp2_error import RP2TypeError, RP2ValueError diff --git a/src/rp2/plugin/country/generic.py b/src/rp2/plugin/country/generic.py index 0aa14910..847c51eb 100644 --- a/src/rp2/plugin/country/generic.py +++ b/src/rp2/plugin/country/generic.py @@ -13,7 +13,6 @@ # limitations under the License. import os - from typing import Set from rp2.abstract_country import AbstractCountry diff --git a/src/rp2/plugin/country/ie.py b/src/rp2/plugin/country/ie.py index fe279fa2..0b4e5d78 100644 --- a/src/rp2/plugin/country/ie.py +++ b/src/rp2/plugin/country/ie.py @@ -13,11 +13,11 @@ # limitations under the License. +import sys from typing import Set from rp2.abstract_country import AbstractCountry from rp2.rp2_main import rp2_main -import sys # Ireland-specific class diff --git a/src/rp2/rp2_decimal.py b/src/rp2/rp2_decimal.py index 3bd07c7f..da17c273 100644 --- a/src/rp2/rp2_decimal.py +++ b/src/rp2/rp2_decimal.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +# pylint: disable=signature-differs + from decimal import Decimal, FloatOperation, getcontext from rp2.rp2_error import RP2TypeError diff --git a/tests/test_accounting_method.py b/tests/test_accounting_method.py index ea36a97e..1a09743a 100644 --- a/tests/test_accounting_method.py +++ b/tests/test_accounting_method.py @@ -13,20 +13,19 @@ # limitations under the License. import unittest - from dataclasses import dataclass from datetime import datetime, timedelta from typing import List from rp2.abstract_accounting_method import AbstractAccountingMethod from rp2.configuration import Configuration +from rp2.in_transaction import InTransaction from rp2.plugin.accounting_method.fifo import AccountingMethod as AccountingMethodFIFO -from rp2.plugin.accounting_method.lifo import AccountingMethod as AccountingMethodLIFO from rp2.plugin.accounting_method.hifo import AccountingMethod as AccountingMethodHIFO +from rp2.plugin.accounting_method.lifo import AccountingMethod as AccountingMethodLIFO from rp2.plugin.accounting_method.lofo import AccountingMethod as AccountingMethodLOFO from rp2.plugin.country.us import US from rp2.rp2_decimal import RP2Decimal -from rp2.in_transaction import InTransaction @dataclass(frozen=True, eq=True) @@ -40,6 +39,7 @@ class InTransactionDescriptor: spot_price: int amount: int + @dataclass(frozen=True, eq=True) class _Test: description: str @@ -189,14 +189,12 @@ def test_with_fixed_lot_candidates(self) -> None: in_transactions=[InTransactionDescriptor(12, 10), InTransactionDescriptor(10, 20), InTransactionDescriptor(11, 30)], amounts_to_match=[15, 5, 35, 5], want=[SeekLotResult(20, 2), SeekLotResult(5, 2), SeekLotResult(30, 3), SeekLotResult(10, 1), SeekLotResult(5, 1)], - ) - + ), ] for test in tests: with self.subTest(name=f"{test.description}"): self._run_test_fixed_lot_candidates(lot_selection_method=test.lot_selection_method, test=test) - def test_with_dynamic_lot_candidates(self) -> None: # Go-style, table-based tests. The want field contains the expected results. tests: List[_Test] = [ @@ -227,7 +225,7 @@ def test_with_dynamic_lot_candidates(self) -> None: in_transactions=[InTransactionDescriptor(12, 10), InTransactionDescriptor(10, 20), InTransactionDescriptor(11, 30)], amounts_to_match=[4, 16, 40], want=[SeekLotResult(10, 1), SeekLotResult(20, 2), SeekLotResult(4, 2), SeekLotResult(30, 3), SeekLotResult(6, 1)], - ) + ), ] for test in tests: with self.subTest(name=f"{test.description}"): diff --git a/tests/test_input_parser.py b/tests/test_input_parser.py index adc08378..7e0ba742 100644 --- a/tests/test_input_parser.py +++ b/tests/test_input_parser.py @@ -13,7 +13,7 @@ # limitations under the License. import unittest -from typing import Dict, List, NamedTuple, Optional, Type +from typing import Dict, List, NamedTuple, Optional, Type, cast from dateutil.parser import parse @@ -26,7 +26,7 @@ from rp2.out_transaction import OutTransaction from rp2.plugin.country.us import US from rp2.rp2_decimal import RP2Decimal -from rp2.rp2_error import RP2Error, RP2RuntimeError, RP2TypeError, RP2ValueError +from rp2.rp2_error import RP2Error, RP2TypeError, RP2ValueError from rp2.transaction_set import TransactionSet @@ -128,9 +128,8 @@ def _verify_non_empty_in_table(self, in_transaction_set: TransactionSet, asset: fiat_balance_changes, is_taxable_values, ): + transaction = cast(InTransaction, transaction) row = int(internal_id) - if not in_transaction_set or not transaction: # Unwrap the Optional types to keep mypy happy - raise RP2RuntimeError("Internal error: in_transaction_set or transaction are None") self.assertEqual(in_transaction_set.get_parent(transaction), previous_transaction) self.assertEqual(transaction.row, row) self.assertEqual(transaction.internal_id, internal_id) @@ -203,9 +202,8 @@ def _verify_non_empty_out_table(self, out_transaction_set: TransactionSet, asset fiat_balance_changes, is_taxable_values, ): + transaction = cast(OutTransaction, transaction) row = int(internal_id) - if not out_transaction_set or not transaction: # Unwrap the Optional types to keep mypy happy - raise RP2RuntimeError("Internal error: in_transaction_set or transaction are None") self.assertEqual(out_transaction_set.get_parent(transaction), previous_transaction) self.assertEqual(transaction.row, row) self.assertEqual(transaction.internal_id, internal_id) @@ -275,9 +273,8 @@ def _verify_non_empty_intra_table(self, intra_transaction_set: TransactionSet, a fiat_balance_changes, is_taxable_values, ): + transaction = cast(IntraTransaction, transaction) row = int(internal_id) - if not intra_transaction_set or not transaction: # Unwrap the Optional types to keep mypy happy - raise RP2RuntimeError("Internal error: intra_transaction_set or transaction are None") self.assertEqual(intra_transaction_set.get_parent(transaction), previous_transaction) self.assertEqual(transaction.row, row) self.assertEqual(transaction.internal_id, internal_id)