From a895af6bcb31b447fd5809a8107991ab9c2b15cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Mas=C5=82owski?= Date: Tue, 5 Aug 2025 13:32:55 +0200 Subject: [PATCH 01/35] Remove unused aliases from mapping in schemas.py --- clive/__private/models/schemas.py | 63 +------------------------------ 1 file changed, 2 insertions(+), 61 deletions(-) diff --git a/clive/__private/models/schemas.py b/clive/__private/models/schemas.py index 70b6ca6b4e..0b0c90568f 100644 --- a/clive/__private/models/schemas.py +++ b/clive/__private/models/schemas.py @@ -16,9 +16,7 @@ has unnecessary "Fundament" suffix, and is not specialized with HF26 assets. from __future__ import annotations -from schemas._operation_objects import Hf26ApiOperationObject, Hf26ApiVirtualOperationObject -from schemas.apis.account_history_api import EnumVirtualOps, GetAccountHistory, GetOpsInBlock -from schemas.apis.account_history_api.response_schemas import GetTransaction +from schemas.apis.account_history_api import GetAccountHistory from schemas.apis.database_api import ( FindAccounts, FindProposals, @@ -46,7 +44,6 @@ from schemas.apis.database_api.fundaments_of_reponses import ( FindRecurrentTransfersFundament, ListChangeRecoveryAccountRequestsFundament, ListDeclineVotingRightsRequestsFundament, - OwnerHistoriesFundament, SavingsWithdrawalsFundament, VestingDelegationExpirationsFundament, VestingDelegationsFundament, @@ -54,13 +51,9 @@ from schemas.apis.database_api.fundaments_of_reponses import ( WitnessesFundament, ) from schemas.apis.rc_api import FindRcAccounts as SchemasFindRcAccounts -from schemas.apis.rc_api import GetResourceParams, GetResourcePool, ListRcDirectDelegations -from schemas.apis.rc_api import ListRcAccounts as SchemasListRcAccounts from schemas.apis.rc_api.fundaments_of_responses import RcAccount as SchemasRcAccount -from schemas.apis.reputation_api import GetAccountReputations from schemas.apis.transaction_status_api import FindTransaction from schemas.fields.assets import AssetHbdHF26, AssetHiveHF26, AssetVestsHF26 -from schemas.fields.assets._base import AssetBase from schemas.fields.basic import AccountName, PublicKey from schemas.fields.compound import Authority, Manabar, Price from schemas.fields.compound import HbdExchangeRate as SchemasHbdExchangeRate @@ -69,10 +62,6 @@ from schemas.fields.hex import Sha256, Signature, TransactionId from schemas.fields.hive_datetime import HiveDateTime from schemas.fields.hive_int import HiveInt from schemas.fields.serializable import Serializable -from schemas.jsonrpc import ExpectResultT as JSONRPCExpectResultT -from schemas.jsonrpc import JSONRPCRequest as SchemasJSONRPCRequest -from schemas.jsonrpc import JSONRPCResult -from schemas.jsonrpc import get_response_model as schemas_get_response_model from schemas.operation import Operation from schemas.operations import ( AccountCreateOperation, @@ -131,24 +120,15 @@ from schemas.operations.recurrent_transfer_operation import RecurrentTransferOpe from schemas.operations.representation_types import Hf26OperationRepresentationType from schemas.operations.representations import convert_to_representation from schemas.operations.representations.hf26_representation import HF26Representation -from schemas.operations.virtual import AnyVirtualOperation -from schemas.operations.virtual.representation_types import Hf26VirtualOperationRepresentationType from schemas.policies import ExtraFields, MissingFieldsInGetConfig, Policy, set_policies from schemas.transaction import Transaction -from schemas.virtual_operation import VirtualOperation __all__ = [ # noqa: RUF022 # operation BASIC aliases - "ApiOperationObject", "OperationBase", "OperationRepresentationBase", "OperationRepresentationUnion", "OperationUnion", - # virtual operation BASIC aliases - "ApiVirtualOperationObject", - "VirtualOperationBase", - "VirtualOperationRepresentationUnion", - "VirtualOperationUnion", # list API responses (have nested list property which stores actual model) "ListChangeRecoveryAccountRequests", "ListDeclineVotingRightsRequests", @@ -156,7 +136,6 @@ __all__ = [ # noqa: RUF022 "ListProposalVotes", "ListWitnesses", "ListWitnessVotes", - "ListRcDirectDelegations", "ListWithdrawVestingRoutes", # find API response aliases (have nested list property which stores actual model) "FindAccounts", @@ -169,15 +148,11 @@ __all__ = [ # noqa: RUF022 "FindWitnesses", # get API responses (have unnecessary nested property which stores actual model) "GetAccountHistory", - "GetAccountReputations", - "GetOperationsInBlock", - "GetResourcePool", # get API responses (have no unnecessary nested properties, just the model itself) "Config", "DynamicGlobalProperties", "FeedHistory", "HardforkProperties", - "ResourceParams", "Version", "WitnessSchedule", # operations @@ -235,7 +210,6 @@ __all__ = [ # noqa: RUF022 "RecurrentTransferPairIdExtension", "RecurrentTransferPairIdRepresentation", # assets - "AssetBase", "AssetHbdHF26", "AssetHiveHF26", "AssetVestsHF26", @@ -252,32 +226,24 @@ __all__ = [ # noqa: RUF022 "Authority", "ChangeRecoveryAccountRequest", "DeclineVotingRightsRequest", - "EnumeratedVirtualOperations", "HbdExchangeRate", "Manabar", - "OwnerHistory", "PriceFeed", "Proposal", "RcAccount", "RecurrentTransfer", "SavingsWithdrawal", "Transaction", - "TransactionInBlockchain", "TransactionStatus", "VestingDelegation", "VestingDelegationExpiration", "WithdrawRoute", + "Witness", # policies "ExtraFieldsPolicy", - "JSONRPCExpectResultT", - "JSONRPCResult", "MissingFieldsInGetConfigPolicy", "Policy", "set_policies", - # jsonrpc - "get_response_model", - "JSONRPCRequest", - "Witness", # other "convert_to_representation", "RepresentationBase", @@ -286,38 +252,21 @@ __all__ = [ # noqa: RUF022 # operation BASIC aliases -ApiOperationObject = Hf26ApiOperationObject OperationBase = Operation OperationRepresentationBase = HF26Representation[OperationBase] OperationRepresentationUnion = Hf26OperationRepresentationType OperationUnion = AnyOperation -# virtual operation BASIC aliases - -ApiVirtualOperationObject = Hf26ApiVirtualOperationObject -VirtualOperationBase = VirtualOperation -VirtualOperationRepresentationUnion = Hf26VirtualOperationRepresentationType -VirtualOperationUnion = AnyVirtualOperation - -# list API responses (have nested list property which stores actual model) - -ListRcAccounts = SchemasListRcAccounts[AssetVestsHF26] - # find API response aliases (have nested list property which stores actual model) FindRcAccounts = SchemasFindRcAccounts[AssetVestsHF26] -# get API responses (have unnecessary nested property which stores actual model) - -GetOperationsInBlock = GetOpsInBlock - # get API responses (have no unnecessary nested properties, just the model itself) Config = GetConfig DynamicGlobalProperties = GetDynamicGlobalProperties FeedHistory = GetFeedHistory HardforkProperties = GetHardforkProperties -ResourceParams = GetResourceParams Version = GetVersion WitnessSchedule = GetWitnessSchedule @@ -335,15 +284,12 @@ ChainId = Sha256 Account = AccountItemFundament[AssetHiveHF26, AssetHbdHF26, AssetVestsHF26] ChangeRecoveryAccountRequest = ListChangeRecoveryAccountRequestsFundament DeclineVotingRightsRequest = ListDeclineVotingRightsRequestsFundament -EnumeratedVirtualOperations = EnumVirtualOps HbdExchangeRate = SchemasHbdExchangeRate[AssetHiveHF26, AssetHbdHF26] -OwnerHistory = OwnerHistoriesFundament PriceFeed = Price[AssetHiveHF26, AssetHbdHF26, AssetVestsHF26] Proposal = SchemasProposal[AssetHbdHF26] RcAccount = SchemasRcAccount[AssetVestsHF26] RecurrentTransfer = FindRecurrentTransfersFundament[AssetHiveHF26, AssetHbdHF26] SavingsWithdrawal = SavingsWithdrawalsFundament[AssetHiveHF26, AssetHbdHF26] -TransactionInBlockchain = GetTransaction TransactionStatus = FindTransaction VestingDelegation = VestingDelegationsFundament VestingDelegationExpiration = VestingDelegationExpirationsFundament[AssetVestsHF26] @@ -355,11 +301,6 @@ Witness = WitnessesFundament[AssetHiveHF26, AssetHbdHF26] ExtraFieldsPolicy = ExtraFields MissingFieldsInGetConfigPolicy = MissingFieldsInGetConfig -# jsonrpc - -get_response_model = schemas_get_response_model -JSONRPCRequest = SchemasJSONRPCRequest - # other RepresentationBase = HF26Representation -- GitLab From 67de1a567b98ac8542c0f339192ce8ec00fb6bab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Radek=20Mas=C5=82owski?= Date: Tue, 1 Jul 2025 18:47:52 +0000 Subject: [PATCH 02/35] Bump hive --- .gitlab-ci.yml | 2 +- hive | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 9e52a6cede..4c61774237 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -52,7 +52,7 @@ variables: include: - project: 'hive/hive' - ref: 9009edf6270db5a0f3bc1d77308bd5cbfb46c1f6 + ref: 846b3d93e5287004961ca3b3b00a6b24c3078067 file: '/scripts/ci-helpers/prepare_data_image_job.yml' # Do not include common-ci-configuration here, it is already referenced by scripts/ci-helpers/prepare_data_image_job.yml included from Hive diff --git a/hive b/hive index 9009edf627..846b3d93e5 160000 --- a/hive +++ b/hive @@ -1 +1 @@ -Subproject commit 9009edf6270db5a0f3bc1d77308bd5cbfb46c1f6 +Subproject commit 846b3d93e5287004961ca3b3b00a6b24c3078067 -- GitLab From 28e6934089810f0e7c8e1748df697dc6acf702f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Radek=20Mas=C5=82owski?= Date: Tue, 5 Aug 2025 09:46:30 +0000 Subject: [PATCH 03/35] Add hive api sources --- .gitlab-ci.yml | 2 ++ docker/Dockerfile | 2 ++ pyproject.toml | 1 + 3 files changed, 5 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 4c61774237..591e9b314f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -18,10 +18,12 @@ variables: GIT_SUBMODULE_STRATEGY: recursive GIT_SUBMODULE_DEPTH: 1 # first party libraries gitlab indexes: + # - 198 -> hive (generated APIs) # - 362 -> schemas # - 419 -> wax # - 434 -> beekeepy FIRST_PARTY_EXTRA_INDEX_ARGS: | + --extra-index https://gitlab.syncad.com/api/v4/projects/198/packages/pypi/simple --extra-index https://gitlab.syncad.com/api/v4/projects/362/packages/pypi/simple --extra-index https://gitlab.syncad.com/api/v4/projects/419/packages/pypi/simple --extra-index https://gitlab.syncad.com/api/v4/projects/434/packages/pypi/simple diff --git a/docker/Dockerfile b/docker/Dockerfile index e10d4d69a7..7322f77057 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -72,6 +72,7 @@ WORKDIR /clive SHELL ["/bin/bash", "-c"] # Project IDS: +# - 198 -> hive (generated APIs) # - 362 -> schemas # - 392 -> clive # - 419 -> wax @@ -79,6 +80,7 @@ SHELL ["/bin/bash", "-c"] RUN --mount=type=cache,mode=0777,uid=${CLIVE_UID},target=${PIP_CACHE_DIR} \ source /python_venv/bin/activate && \ pip install clive=="${CLIVE_VERSION}" \ + --extra-index-url https://gitlab.syncad.com/api/v4/projects/198/packages/pypi/simple \ --extra-index-url https://gitlab.syncad.com/api/v4/projects/362/packages/pypi/simple \ --extra-index-url https://gitlab.syncad.com/api/v4/projects/393/packages/pypi/simple \ --extra-index-url https://gitlab.syncad.com/api/v4/projects/419/packages/pypi/simple \ diff --git a/pyproject.toml b/pyproject.toml index 93e4a0df78..e6bd098e6e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,6 +41,7 @@ packages = [ { include = "clive" }, ] source = [ + { name = "gitlab-hive", url = "https://gitlab.syncad.com/api/v4/projects/198/packages/pypi/simple", priority = "supplemental" }, { name = "gitlab-schemas", url = "https://gitlab.syncad.com/api/v4/projects/362/packages/pypi/simple", priority = "supplemental" }, { name = "gitlab-wax", url = "https://gitlab.syncad.com/api/v4/projects/419/packages/pypi/simple", priority = "supplemental" }, { name = "gitlab-beekeepy", url = "https://gitlab.syncad.com/api/v4/projects/434/packages/pypi/simple", priority = "supplemental" }, -- GitLab From 70520299e2615bc639dc1a6fae18dbf60042b4e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Mas=C5=82owski?= Date: Tue, 5 Aug 2025 13:10:59 +0200 Subject: [PATCH 04/35] Update wax, schemas, beekeepy dependencies --- poetry.lock | 215 +++++++++++++++++++++++++------------------------ pyproject.toml | 6 +- 2 files changed, 113 insertions(+), 108 deletions(-) diff --git a/poetry.lock b/poetry.lock index 316566ae7f..732ac45257 100644 --- a/poetry.lock +++ b/poetry.lock @@ -221,13 +221,13 @@ extras = ["regex"] [[package]] name = "beekeepy" -version = "0.0.1.dev388+171802c" +version = "0.0.1.dev413+0bc0d4b" description = "All in one package for beekeeper interaction via Python interface." optional = false python-versions = ">=3.12,<4.0" groups = ["main", "dev", "embeddedtestnet"] files = [ - {file = "beekeepy-0.0.1.dev388+171802c-py3-none-any.whl", hash = "sha256:e3cdfd5ffa750538a6f4077db3687796165bd8de79858fe2ae5451ac9758ca51"}, + {file = "beekeepy-0.0.1.dev413+0bc0d4b-py3-none-any.whl", hash = "sha256:f9a17976648a43accc91b345fb46989feb93b376616f59dc9808198001d62e47"}, ] [package.dependencies] @@ -237,63 +237,13 @@ loguru = "0.7.2" psutil = "7.0.0" python-dateutil = "2.8.2" requests = "2.32.3" -schemas = "0.0.1.dev345+0b349a2" -setuptools = "77.0.3" -types-setuptools = "76.0.0.20250313" +schemas = "0.0.1.dev424+eeb5c94" [package.source] type = "legacy" url = "https://gitlab.syncad.com/api/v4/projects/434/packages/pypi/simple" reference = "gitlab-beekeepy" -[[package]] -name = "black" -version = "23.3.0" -description = "The uncompromising code formatter." -optional = false -python-versions = ">=3.7" -groups = ["main", "dev", "embeddedtestnet"] -files = [ - {file = "black-23.3.0-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:0945e13506be58bf7db93ee5853243eb368ace1c08a24c65ce108986eac65915"}, - {file = "black-23.3.0-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:67de8d0c209eb5b330cce2469503de11bca4085880d62f1628bd9972cc3366b9"}, - {file = "black-23.3.0-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:7c3eb7cea23904399866c55826b31c1f55bbcd3890ce22ff70466b907b6775c2"}, - {file = "black-23.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32daa9783106c28815d05b724238e30718f34155653d4d6e125dc7daec8e260c"}, - {file = "black-23.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:35d1381d7a22cc5b2be2f72c7dfdae4072a3336060635718cc7e1ede24221d6c"}, - {file = "black-23.3.0-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:a8a968125d0a6a404842fa1bf0b349a568634f856aa08ffaff40ae0dfa52e7c6"}, - {file = "black-23.3.0-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:c7ab5790333c448903c4b721b59c0d80b11fe5e9803d8703e84dcb8da56fec1b"}, - {file = "black-23.3.0-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:a6f6886c9869d4daae2d1715ce34a19bbc4b95006d20ed785ca00fa03cba312d"}, - {file = "black-23.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f3c333ea1dd6771b2d3777482429864f8e258899f6ff05826c3a4fcc5ce3f70"}, - {file = "black-23.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:11c410f71b876f961d1de77b9699ad19f939094c3a677323f43d7a29855fe326"}, - {file = "black-23.3.0-cp37-cp37m-macosx_10_16_x86_64.whl", hash = "sha256:1d06691f1eb8de91cd1b322f21e3bfc9efe0c7ca1f0e1eb1db44ea367dff656b"}, - {file = "black-23.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50cb33cac881766a5cd9913e10ff75b1e8eb71babf4c7104f2e9c52da1fb7de2"}, - {file = "black-23.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:e114420bf26b90d4b9daa597351337762b63039752bdf72bf361364c1aa05925"}, - {file = "black-23.3.0-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:48f9d345675bb7fbc3dd85821b12487e1b9a75242028adad0333ce36ed2a6d27"}, - {file = "black-23.3.0-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:714290490c18fb0126baa0fca0a54ee795f7502b44177e1ce7624ba1c00f2331"}, - {file = "black-23.3.0-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:064101748afa12ad2291c2b91c960be28b817c0c7eaa35bec09cc63aa56493c5"}, - {file = "black-23.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:562bd3a70495facf56814293149e51aa1be9931567474993c7942ff7d3533961"}, - {file = "black-23.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:e198cf27888ad6f4ff331ca1c48ffc038848ea9f031a3b40ba36aced7e22f2c8"}, - {file = "black-23.3.0-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:3238f2aacf827d18d26db07524e44741233ae09a584273aa059066d644ca7b30"}, - {file = "black-23.3.0-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:f0bd2f4a58d6666500542b26354978218a9babcdc972722f4bf90779524515f3"}, - {file = "black-23.3.0-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:92c543f6854c28a3c7f39f4d9b7694f9a6eb9d3c5e2ece488c327b6e7ea9b266"}, - {file = "black-23.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a150542a204124ed00683f0db1f5cf1c2aaaa9cc3495b7a3b5976fb136090ab"}, - {file = "black-23.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:6b39abdfb402002b8a7d030ccc85cf5afff64ee90fa4c5aebc531e3ad0175ddb"}, - {file = "black-23.3.0-py3-none-any.whl", hash = "sha256:ec751418022185b0c1bb7d7736e6933d40bbb14c14a0abcf9123d1b159f98dd4"}, - {file = "black-23.3.0.tar.gz", hash = "sha256:1c7b8d606e728a41ea1ccbd7264677e494e87cf630e399262ced92d4a8dac940"}, -] - -[package.dependencies] -click = ">=8.0.0" -mypy-extensions = ">=0.4.3" -packaging = ">=22.0" -pathspec = ">=0.9.0" -platformdirs = ">=2" - -[package.extras] -colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.7.4)"] -jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] -uvloop = ["uvloop (>=0.15.2)"] - [[package]] name = "certifi" version = "2025.1.31" @@ -426,7 +376,7 @@ version = "8.1.8" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" -groups = ["main", "dev", "docs", "embeddedtestnet"] +groups = ["main", "dev", "docs"] files = [ {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, @@ -460,7 +410,26 @@ files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] -markers = {main = "sys_platform == \"win32\" or platform_system == \"Windows\"", dev = "sys_platform == \"win32\" or platform_system == \"Windows\"", embeddedtestnet = "sys_platform == \"win32\" or platform_system == \"Windows\""} +markers = {main = "sys_platform == \"win32\" or platform_system == \"Windows\"", dev = "sys_platform == \"win32\" or platform_system == \"Windows\"", embeddedtestnet = "sys_platform == \"win32\""} + +[[package]] +name = "database-api" +version = "1.27.11rc6.dev353+108f17fef" +description = "" +optional = false +python-versions = ">=3.12,<4.0" +groups = ["main", "dev", "embeddedtestnet"] +files = [ + {file = "database_api-1.27.11rc6.dev353+108f17fef-py3-none-any.whl", hash = "sha256:0946e7532a45667a29744866ff1f8c6c539949b07bbb2cafa49bfba2142369af"}, +] + +[package.dependencies] +beekeepy = "0.0.1.dev413+0bc0d4b" + +[package.source] +type = "legacy" +url = "https://gitlab.syncad.com/api/v4/projects/198/packages/pypi/simple" +reference = "gitlab-hive" [[package]] name = "distlib" @@ -1294,6 +1263,59 @@ files = [ {file = "msgpack-1.1.0.tar.gz", hash = "sha256:dd432ccc2c72b914e4cb77afce64aab761c1137cc698be3984eee260bcb2896e"}, ] +[[package]] +name = "msgspec" +version = "0.18.6" +description = "A fast serialization and validation library, with builtin support for JSON, MessagePack, YAML, and TOML." +optional = false +python-versions = ">=3.8" +groups = ["main", "dev", "embeddedtestnet"] +files = [ + {file = "msgspec-0.18.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:77f30b0234eceeff0f651119b9821ce80949b4d667ad38f3bfed0d0ebf9d6d8f"}, + {file = "msgspec-0.18.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1a76b60e501b3932782a9da039bd1cd552b7d8dec54ce38332b87136c64852dd"}, + {file = "msgspec-0.18.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06acbd6edf175bee0e36295d6b0302c6de3aaf61246b46f9549ca0041a9d7177"}, + {file = "msgspec-0.18.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40a4df891676d9c28a67c2cc39947c33de516335680d1316a89e8f7218660410"}, + {file = "msgspec-0.18.6-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:a6896f4cd5b4b7d688018805520769a8446df911eb93b421c6c68155cdf9dd5a"}, + {file = "msgspec-0.18.6-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3ac4dd63fd5309dd42a8c8c36c1563531069152be7819518be0a9d03be9788e4"}, + {file = "msgspec-0.18.6-cp310-cp310-win_amd64.whl", hash = "sha256:fda4c357145cf0b760000c4ad597e19b53adf01382b711f281720a10a0fe72b7"}, + {file = "msgspec-0.18.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e77e56ffe2701e83a96e35770c6adb655ffc074d530018d1b584a8e635b4f36f"}, + {file = "msgspec-0.18.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d5351afb216b743df4b6b147691523697ff3a2fc5f3d54f771e91219f5c23aaa"}, + {file = "msgspec-0.18.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3232fabacef86fe8323cecbe99abbc5c02f7698e3f5f2e248e3480b66a3596b"}, + {file = "msgspec-0.18.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3b524df6ea9998bbc99ea6ee4d0276a101bcc1aa8d14887bb823914d9f60d07"}, + {file = "msgspec-0.18.6-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:37f67c1d81272131895bb20d388dd8d341390acd0e192a55ab02d4d6468b434c"}, + {file = "msgspec-0.18.6-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d0feb7a03d971c1c0353de1a8fe30bb6579c2dc5ccf29b5f7c7ab01172010492"}, + {file = "msgspec-0.18.6-cp311-cp311-win_amd64.whl", hash = "sha256:41cf758d3f40428c235c0f27bc6f322d43063bc32da7b9643e3f805c21ed57b4"}, + {file = "msgspec-0.18.6-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d86f5071fe33e19500920333c11e2267a31942d18fed4d9de5bc2fbab267d28c"}, + {file = "msgspec-0.18.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce13981bfa06f5eb126a3a5a38b1976bddb49a36e4f46d8e6edecf33ccf11df1"}, + {file = "msgspec-0.18.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e97dec6932ad5e3ee1e3c14718638ba333befc45e0661caa57033cd4cc489466"}, + {file = "msgspec-0.18.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad237100393f637b297926cae1868b0d500f764ccd2f0623a380e2bcfb2809ca"}, + {file = "msgspec-0.18.6-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db1d8626748fa5d29bbd15da58b2d73af25b10aa98abf85aab8028119188ed57"}, + {file = "msgspec-0.18.6-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:d70cb3d00d9f4de14d0b31d38dfe60c88ae16f3182988246a9861259c6722af6"}, + {file = "msgspec-0.18.6-cp312-cp312-win_amd64.whl", hash = "sha256:1003c20bfe9c6114cc16ea5db9c5466e49fae3d7f5e2e59cb70693190ad34da0"}, + {file = "msgspec-0.18.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f7d9faed6dfff654a9ca7d9b0068456517f63dbc3aa704a527f493b9200b210a"}, + {file = "msgspec-0.18.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9da21f804c1a1471f26d32b5d9bc0480450ea77fbb8d9db431463ab64aaac2cf"}, + {file = "msgspec-0.18.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46eb2f6b22b0e61c137e65795b97dc515860bf6ec761d8fb65fdb62aa094ba61"}, + {file = "msgspec-0.18.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8355b55c80ac3e04885d72db515817d9fbb0def3bab936bba104e99ad22cf46"}, + {file = "msgspec-0.18.6-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9080eb12b8f59e177bd1eb5c21e24dd2ba2fa88a1dbc9a98e05ad7779b54c681"}, + {file = "msgspec-0.18.6-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:cc001cf39becf8d2dcd3f413a4797c55009b3a3cdbf78a8bf5a7ca8fdb76032c"}, + {file = "msgspec-0.18.6-cp38-cp38-win_amd64.whl", hash = "sha256:fac5834e14ac4da1fca373753e0c4ec9c8069d1fe5f534fa5208453b6065d5be"}, + {file = "msgspec-0.18.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:974d3520fcc6b824a6dedbdf2b411df31a73e6e7414301abac62e6b8d03791b4"}, + {file = "msgspec-0.18.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fd62e5818731a66aaa8e9b0a1e5543dc979a46278da01e85c3c9a1a4f047ef7e"}, + {file = "msgspec-0.18.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7481355a1adcf1f08dedd9311193c674ffb8bf7b79314b4314752b89a2cf7f1c"}, + {file = "msgspec-0.18.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6aa85198f8f154cf35d6f979998f6dadd3dc46a8a8c714632f53f5d65b315c07"}, + {file = "msgspec-0.18.6-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0e24539b25c85c8f0597274f11061c102ad6b0c56af053373ba4629772b407be"}, + {file = "msgspec-0.18.6-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c61ee4d3be03ea9cd089f7c8e36158786cd06e51fbb62529276452bbf2d52ece"}, + {file = "msgspec-0.18.6-cp39-cp39-win_amd64.whl", hash = "sha256:b5c390b0b0b7da879520d4ae26044d74aeee5144f83087eb7842ba59c02bc090"}, + {file = "msgspec-0.18.6.tar.gz", hash = "sha256:a59fc3b4fcdb972d09138cb516dbde600c99d07c38fd9372a6ef500d2d031b4e"}, +] + +[package.extras] +dev = ["attrs", "coverage", "furo", "gcovr", "ipython", "msgpack", "mypy", "pre-commit", "pyright", "pytest", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "tomli ; python_version < \"3.11\"", "tomli-w"] +doc = ["furo", "ipython", "sphinx", "sphinx-copybutton", "sphinx-design"] +test = ["attrs", "msgpack", "mypy", "pyright", "pytest", "pyyaml", "tomli ; python_version < \"3.11\"", "tomli-w"] +toml = ["tomli ; python_version < \"3.11\"", "tomli-w"] +yaml = ["pyyaml"] + [[package]] name = "multidict" version = "6.4.2" @@ -1467,12 +1489,31 @@ version = "1.0.0" description = "Type system extensions for programs checked with the mypy type checker." optional = false python-versions = ">=3.5" -groups = ["main", "dev", "embeddedtestnet"] +groups = ["dev"] files = [ {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, ] +[[package]] +name = "network-broadcast-api" +version = "1.27.11rc6.dev353+108f17fef" +description = "" +optional = false +python-versions = ">=3.12,<4.0" +groups = ["main", "dev", "embeddedtestnet"] +files = [ + {file = "network_broadcast_api-1.27.11rc6.dev353+108f17fef-py3-none-any.whl", hash = "sha256:34b4a4925415fd4f0474bc523a8b37caa3faaf716188f9bd47e10792cac0192d"}, +] + +[package.dependencies] +beekeepy = "0.0.1.dev413+0bc0d4b" + +[package.source] +type = "legacy" +url = "https://gitlab.syncad.com/api/v4/projects/198/packages/pypi/simple" +reference = "gitlab-hive" + [[package]] name = "nodeenv" version = "1.9.1" @@ -1491,7 +1532,7 @@ version = "24.2" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" -groups = ["main", "dev", "docs", "embeddedtestnet"] +groups = ["dev", "docs", "embeddedtestnet"] files = [ {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, @@ -1519,7 +1560,7 @@ version = "0.12.1" description = "Utility library for gitignore style pattern matching of file paths." optional = false python-versions = ">=3.8" -groups = ["main", "dev", "docs", "embeddedtestnet"] +groups = ["docs"] files = [ {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, @@ -1531,7 +1572,7 @@ version = "4.3.7" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.9" -groups = ["main", "dev", "docs", "embeddedtestnet"] +groups = ["main", "dev", "docs"] files = [ {file = "platformdirs-4.3.7-py3-none-any.whl", hash = "sha256:a03875334331946f13c549dbd8f4bac7a13a50a895a0eb1e8c6a8ace80d40a94"}, {file = "platformdirs-4.3.7.tar.gz", hash = "sha256:eb437d586b6a0986388f0d6f74aa0cde27b48d0e3d66843640bfb6bdcdb6e351"}, @@ -1738,7 +1779,7 @@ version = "1.10.18" description = "Data validation and settings management using python type hints" optional = false python-versions = ">=3.7" -groups = ["main", "dev", "embeddedtestnet"] +groups = ["main"] files = [ {file = "pydantic-1.10.18-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e405ffcc1254d76bb0e760db101ee8916b620893e6edfbfee563b3c6f7a67c02"}, {file = "pydantic-1.10.18-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e306e280ebebc65040034bff1a0a81fd86b2f4f05daac0131f29541cafd80b80"}, @@ -2103,45 +2144,23 @@ files = [ [[package]] name = "schemas" -version = "0.0.1.dev345+0b349a2" +version = "0.0.1.dev424+eeb5c94" description = "Tools for checking if message fits expected format" optional = false python-versions = ">=3.12,<4.0" groups = ["main", "dev", "embeddedtestnet"] files = [ - {file = "schemas-0.0.1.dev345+0b349a2-py3-none-any.whl", hash = "sha256:35487f5bd7ca70556f350f74495bc8e8ef15af829b8c86aee783ab2908fab86a"}, + {file = "schemas-0.0.1.dev424+eeb5c94-py3-none-any.whl", hash = "sha256:eaa87991fbec3878d1f3fbba1cf9562995364b80530787d1eb7598ad62bdea72"}, ] [package.dependencies] -black = "23.3.0" -pydantic = "1.10.18" +msgspec = "0.18.6" [package.source] type = "legacy" url = "https://gitlab.syncad.com/api/v4/projects/362/packages/pypi/simple" reference = "gitlab-schemas" -[[package]] -name = "setuptools" -version = "77.0.3" -description = "Easily download, build, install, upgrade, and uninstall Python packages" -optional = false -python-versions = ">=3.9" -groups = ["main", "dev", "embeddedtestnet"] -files = [ - {file = "setuptools-77.0.3-py3-none-any.whl", hash = "sha256:67122e78221da5cf550ddd04cf8742c8fe12094483749a792d56cd669d6cf58c"}, - {file = "setuptools-77.0.3.tar.gz", hash = "sha256:583b361c8da8de57403743e756609670de6fb2345920e36dc5c2d914c319c945"}, -] - -[package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\"", "ruff (>=0.8.0) ; sys_platform != \"cygwin\""] -core = ["importlib_metadata (>=6) ; python_version < \"3.10\"", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1) ; python_version < \"3.11\"", "wheel (>=0.43.0)"] -cover = ["pytest-cov"] -doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] -enabler = ["pytest-enabler (>=2.2)"] -test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21) ; python_version >= \"3.9\" and sys_platform != \"cygwin\"", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf ; sys_platform != \"cygwin\"", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] -type = ["importlib_metadata (>=7.0.2) ; python_version < \"3.10\"", "jaraco.develop (>=7.21) ; sys_platform != \"cygwin\"", "mypy (==1.14.*)", "pytest-mypy"] - [[package]] name = "shellingham" version = "1.5.4" @@ -2192,7 +2211,7 @@ develop = false abstractcp = "0.9.9" loguru = "0.7.2" python-dateutil = "2.8.2" -wax = "0.3.10.dev712+7e6218a0" +wax = "0.3.10.dev841+5ac4412f" [package.source] type = "directory" @@ -2268,21 +2287,6 @@ rich = ">=10.11.0" shellingham = ">=1.3.0" typing-extensions = ">=3.7.4.3" -[[package]] -name = "types-setuptools" -version = "76.0.0.20250313" -description = "Typing stubs for setuptools" -optional = false -python-versions = ">=3.9" -groups = ["main", "dev", "embeddedtestnet"] -files = [ - {file = "types_setuptools-76.0.0.20250313-py3-none-any.whl", hash = "sha256:bf454b2a49b8cfd7ebcf5844d4dd5fe4c8666782df1e3663c5866fd51a47460e"}, - {file = "types_setuptools-76.0.0.20250313.tar.gz", hash = "sha256:b2be66f550f95f3cad2a7d46177b273c7e9c80df7d257fa57addbbcfc8126a9e"}, -] - -[package.dependencies] -setuptools = "*" - [[package]] name = "types-toml" version = "0.10.8.20240310" @@ -2406,19 +2410,20 @@ watchmedo = ["PyYAML (>=3.10)"] [[package]] name = "wax" -version = "0.3.10.dev712+7e6218a0" +version = "0.3.10.dev841+5ac4412f" description = "" optional = false python-versions = ">=3.12,<4.0" groups = ["main", "dev", "embeddedtestnet"] files = [ - {file = "wax-0.3.10.dev712+7e6218a0-cp312-cp312-manylinux_2_39_x86_64.whl", hash = "sha256:d54a51b34036a37363bad13e0e90d501e7b4dbbe5c7ee3c58df16e30553f9811"}, + {file = "wax-0.3.10.dev841+5ac4412f-cp312-cp312-manylinux_2_39_x86_64.whl", hash = "sha256:7b472a060529bcd9fd4e7e4ddde1ab32c0453563b2260bb756da8215c15bd3e3"}, ] [package.dependencies] -beekeepy = "0.0.1.dev388+171802c" +database-api = "1.27.11rc6.dev353+108f17fef" httpx = {version = "0.23.3", extras = ["http2"]} loguru = "0.7.2" +network-broadcast-api = "1.27.11rc6.dev353+108f17fef" protobuf = "4.24.4" python-dateutil = "2.8.2" requests = "2.32.3" @@ -2549,4 +2554,4 @@ propcache = ">=0.2.1" [metadata] lock-version = "2.1" python-versions = ">=3.12,<3.13" -content-hash = "8813b4d16dc3fb1506dd0cb759389c63738c363d440d897f61416120ad4b8991" +content-hash = "b69e729178b8894cfaab8e897f32a9d2a34e83d3016934d3e615ab010673274e" diff --git a/pyproject.toml b/pyproject.toml index e6bd098e6e..805bd0e51d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,9 +22,9 @@ dependencies = [ 'humanize (==4.12.2)', 'pydantic (==1.10.18)', 'toml (==0.10.2)', - 'schemas (==0.0.1.dev345+0b349a2)', - 'beekeepy (==0.0.1.dev388+171802c)', - 'wax (==0.3.10.dev712+7e6218a0)', + 'schemas (==0.0.1.dev424+eeb5c94)', + 'beekeepy (==0.0.1.dev413+0bc0d4b)', + 'wax (==0.3.10.dev841+5ac4412f)', ] [project.urls] -- GitLab From 7728a5cad0decdda06871a5e5e3911a7f72147de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Radek=20Mas=C5=82owski?= Date: Wed, 19 Mar 2025 14:13:35 +0100 Subject: [PATCH 05/35] Use schemas functions instead of clive implementation --- clive/__private/core/accounts/accounts.py | 11 ++--- .../__private/core/get_default_from_model.py | 41 ------------------- clive/__private/core/profile.py | 5 +-- clive/__private/core/validate_schema_field.py | 35 ---------------- clive/__private/models/schemas.py | 3 ++ clive/__private/settings/_safe_settings.py | 5 +-- .../validators/account_name_validator.py | 5 +-- pydoclint-errors-baseline.txt | 10 ----- .../clive_local_tools/helpers.py | 5 +-- 9 files changed, 14 insertions(+), 106 deletions(-) delete mode 100644 clive/__private/core/get_default_from_model.py delete mode 100644 clive/__private/core/validate_schema_field.py diff --git a/clive/__private/core/accounts/accounts.py b/clive/__private/core/accounts/accounts.py index eedecc6dd8..b3930dbbcb 100644 --- a/clive/__private/core/accounts/accounts.py +++ b/clive/__private/core/accounts/accounts.py @@ -3,11 +3,8 @@ from __future__ import annotations from dataclasses import dataclass, field from typing import TYPE_CHECKING -from pydantic import ValidationError - from clive.__private.core.alarms.alarms_storage import AlarmsStorage -from clive.__private.core.validate_schema_field import validate_schema_field -from clive.__private.models.schemas import AccountName +from clive.__private.models.schemas import AccountName, is_matching_model from clive.exceptions import CliveError if TYPE_CHECKING: @@ -78,10 +75,8 @@ class Account: Raises: InvalidAccountNameError: if the given account name is invalid. """ - try: - validate_schema_field(AccountName, name) - except ValidationError as error: - raise InvalidAccountNameError(name) from error + if not is_matching_model(name, AccountName): + raise InvalidAccountNameError(name) @classmethod def is_valid(cls, name: str) -> bool: diff --git a/clive/__private/core/get_default_from_model.py b/clive/__private/core/get_default_from_model.py deleted file mode 100644 index 4fb11627dd..0000000000 --- a/clive/__private/core/get_default_from_model.py +++ /dev/null @@ -1,41 +0,0 @@ -from __future__ import annotations - -from typing import TYPE_CHECKING, Any, overload - -from clive.exceptions import CliveError - -if TYPE_CHECKING: - from pydantic import BaseModel - - -class WrongTypeError(CliveError): - """Raised when the type of the value is not the expected one.""" - - -class NoMatchesError(CliveError): - """Raised when no matches are found.""" - - -@overload -def get_default_from_model(model: type[BaseModel] | BaseModel, field_name: str) -> Any: ... # noqa: ANN401 - - -@overload -def get_default_from_model[T](model: type[BaseModel] | BaseModel, field_name: str, expect_type: type[T]) -> T: ... - - -def get_default_from_model[T]( - model: type[BaseModel] | BaseModel, field_name: str, expect_type: type[T] | None = None -) -> T | Any: - """Get default value from pydantic model.""" - field = model.__fields__.get(field_name, None) - - if field is None: - raise NoMatchesError(f"No matches for {field_name} in {model}") - - default_value = field.default - - if expect_type is not None and not isinstance(default_value, expect_type): - raise WrongTypeError(f"{model}.{field_name} is wrong type; expected {expect_type}, got {type(default_value)}") - - return default_value diff --git a/clive/__private/core/profile.py b/clive/__private/core/profile.py index 0ba405e091..9577713f9d 100644 --- a/clive/__private/core/profile.py +++ b/clive/__private/core/profile.py @@ -9,10 +9,9 @@ from clive.__private.core.accounts.account_manager import AccountManager from clive.__private.core.constants.tui.themes import DEFAULT_THEME from clive.__private.core.formatters.humanize import humanize_validation_result from clive.__private.core.keys import KeyManager, PublicKeyAliased -from clive.__private.core.validate_schema_field import is_schema_field_valid from clive.__private.logger import logger from clive.__private.models import Transaction -from clive.__private.models.schemas import ChainId, OperationRepresentationUnion, OperationUnion +from clive.__private.models.schemas import ChainId, OperationRepresentationUnion, OperationUnion, is_matching_model from clive.__private.settings import safe_settings from clive.__private.storage.runtime_to_storage_converter import RuntimeToStorageConverter from clive.__private.storage.service import PersistentStorageService @@ -156,7 +155,7 @@ class Profile: Raises: InvalidChainIdError: If the format of the provided chain ID is wrong. """ - if not is_schema_field_valid(ChainId, value): + if not is_matching_model(value, ChainId): raise InvalidChainIdError self._chain_id = value diff --git a/clive/__private/core/validate_schema_field.py b/clive/__private/core/validate_schema_field.py deleted file mode 100644 index 2a2790e543..0000000000 --- a/clive/__private/core/validate_schema_field.py +++ /dev/null @@ -1,35 +0,0 @@ -from __future__ import annotations - -from typing import Any - -from pydantic import BaseModel, ValidationError - - -def validate_schema_field(schema_field: type[Any], value: Any) -> None: # noqa: ANN401 - """ - Validate the given value against the given schema field e.g. one that inherits from pydantic.ConstrainedStr. - - For validating models use `pydantic.validate_model` instead. - - Args: - schema_field: The schema field type to validate against. - value: The value to validate. - - Raises: - pydantic.ValidationError: if the given value is invalid. - """ - - class Model(BaseModel): - value: schema_field # type: ignore[valid-type] - - Model.update_forward_refs(**locals()) - Model(value=value) - - -def is_schema_field_valid(schema_field: type[Any], value: Any) -> bool: # noqa: ANN401 - try: - validate_schema_field(schema_field, value) - except ValidationError: - return False - else: - return True diff --git a/clive/__private/models/schemas.py b/clive/__private/models/schemas.py index 0b0c90568f..9da29b9dc3 100644 --- a/clive/__private/models/schemas.py +++ b/clive/__private/models/schemas.py @@ -53,6 +53,7 @@ from schemas.apis.database_api.fundaments_of_reponses import ( from schemas.apis.rc_api import FindRcAccounts as SchemasFindRcAccounts from schemas.apis.rc_api.fundaments_of_responses import RcAccount as SchemasRcAccount from schemas.apis.transaction_status_api import FindTransaction +from schemas.decoders import is_matching_model, validate_schema_field from schemas.fields.assets import AssetHbdHF26, AssetHiveHF26, AssetVestsHF26 from schemas.fields.basic import AccountName, PublicKey from schemas.fields.compound import Authority, Manabar, Price @@ -246,6 +247,8 @@ __all__ = [ # noqa: RUF022 "set_policies", # other "convert_to_representation", + "is_matching_model", + "validate_schema_field", "RepresentationBase", "Serializable", ] diff --git a/clive/__private/settings/_safe_settings.py b/clive/__private/settings/_safe_settings.py index 38e4eca063..0b2290908e 100644 --- a/clive/__private/settings/_safe_settings.py +++ b/clive/__private/settings/_safe_settings.py @@ -324,8 +324,7 @@ class SafeSettings: return remote_handle_settings def _get_node_chain_id(self) -> str | None: - from clive.__private.core.validate_schema_field import is_schema_field_valid - from clive.__private.models.schemas import ChainId + from clive.__private.models.schemas import ChainId, is_matching_model setting_name = NODE_CHAIN_ID value = self._parent._get_value_from_settings(setting_name, "") @@ -335,7 +334,7 @@ class SafeSettings: self._parent._assert_is_string(setting_name, value=value) value_ = cast("str", value) - if not is_schema_field_valid(ChainId, value_): + if not is_matching_model(value_, ChainId): details = f"Chain ID should be {ChainId.max_length} characters long." raise SettingsValueError(setting_name=setting_name, value=value_, details=details) return value_ diff --git a/clive/__private/validators/account_name_validator.py b/clive/__private/validators/account_name_validator.py index 071b3226b1..954fd4efc7 100644 --- a/clive/__private/validators/account_name_validator.py +++ b/clive/__private/validators/account_name_validator.py @@ -4,8 +4,7 @@ from typing import TYPE_CHECKING, Final from textual.validation import Validator -from clive.__private.core.validate_schema_field import is_schema_field_valid -from clive.__private.models.schemas import AccountName +from clive.__private.models.schemas import AccountName, is_matching_model if TYPE_CHECKING: from textual.validation import ValidationResult @@ -18,7 +17,7 @@ class AccountNameValidator(Validator): super().__init__() def validate(self, value: str) -> ValidationResult: - if is_schema_field_valid(AccountName, value): + if is_matching_model(value, AccountName): return self.success() return self.failure(self.INVALID_ACCOUNT_NAME_FAILURE_DESCRIPTION, value) diff --git a/pydoclint-errors-baseline.txt b/pydoclint-errors-baseline.txt index 2725476a56..2631b937e8 100644 --- a/pydoclint-errors-baseline.txt +++ b/pydoclint-errors-baseline.txt @@ -295,13 +295,6 @@ clive/__private/core/error_handlers/abc/error_notificator.py DOC501: Method `ErrorNotificator.__aexit__` has raise statements, but the docstring does not have a "Raises" section DOC503: Method `ErrorNotificator.__aexit__` exceptions in the "Raises" section in the docstring do not match those in the function body. Raised exceptions in the docstring: []. Raised exceptions in the body: ['CannotNotifyError']. -------------------- -clive/__private/core/get_default_from_model.py - DOC101: Function `get_default_from_model`: Docstring contains fewer arguments than in function signature. - DOC103: Function `get_default_from_model`: Docstring arguments are different from function arguments. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Arguments in the function signature but not in the docstring: [expect_type: type[T] | None, field_name: str, model: type[BaseModel] | BaseModel]. - DOC201: Function `get_default_from_model` does not have a return section in docstring - DOC501: Function `get_default_from_model` has raise statements, but the docstring does not have a "Raises" section - DOC503: Function `get_default_from_model` exceptions in the "Raises" section in the docstring do not match those in the function body. Raised exceptions in the docstring: []. Raised exceptions in the body: ['NoMatchesError', 'WrongTypeError']. --------------------- clive/__private/core/known_exchanges.py DOC502: Method `KnownExchanges.get_entity_by_account_name` has a "Raises" section in the docstring, but there are not "raise" statements in the body -------------------- @@ -322,9 +315,6 @@ clive/__private/core/profile.py DOC101: Method `Profile._set_node_address`: Docstring contains fewer arguments than in function signature. DOC103: Method `Profile._set_node_address`: Docstring arguments are different from function arguments. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Arguments in the function signature but not in the docstring: [value: HttpUrl]. -------------------- -clive/__private/core/validate_schema_field.py - DOC502: Function `validate_schema_field` has a "Raises" section in the docstring, but there are not "raise" statements in the body --------------------- clive/__private/models/asset.py DOC502: Method `Asset.hive` has a "Raises" section in the docstring, but there are not "raise" statements in the body DOC502: Method `Asset.hbd` has a "Raises" section in the docstring, but there are not "raise" statements in the body diff --git a/tests/clive-local-tools/clive_local_tools/helpers.py b/tests/clive-local-tools/clive_local_tools/helpers.py index a445be0782..3134ff0e7d 100644 --- a/tests/clive-local-tools/clive_local_tools/helpers.py +++ b/tests/clive-local-tools/clive_local_tools/helpers.py @@ -8,8 +8,7 @@ from typer import rich_utils from clive.__private.core.constants.terminal import TERMINAL_HEIGHT, TERMINAL_WIDTH from clive.__private.core.ensure_transaction import TransactionConvertibleType, ensure_transaction -from clive.__private.core.validate_schema_field import validate_schema_field -from clive.__private.models.schemas import TransactionId +from clive.__private.models.schemas import TransactionId, validate_schema_field if TYPE_CHECKING: from pathlib import Path @@ -22,7 +21,7 @@ def get_transaction_id_from_output(output: str) -> str: transaction_id = line.partition('"transaction_id":')[2] if transaction_id: transaction_id_field = transaction_id.strip(' "') - validate_schema_field(TransactionId, transaction_id_field) + validate_schema_field(transaction_id_field, TransactionId) return transaction_id_field pytest.fail(f"Could not find transaction id in output {output}") -- GitLab From 3a71065bf66964e11301e1ef9a550e9fdb73ac9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Mas=C5=82owski?= Date: Tue, 5 Aug 2025 15:15:57 +0200 Subject: [PATCH 06/35] Use PreconfiguredBaseModel instead CliveBaseModel --- .../__private/core/alarms/alarm_identifier.py | 5 +- clive/__private/models/base.py | 73 ------------------- clive/__private/models/schemas.py | 4 +- clive/__private/storage/migrations/base.py | 4 +- clive/__private/storage/migrations/v0.py | 16 ++-- clive/__private/storage/migrations/v1.py | 4 +- 6 files changed, 16 insertions(+), 90 deletions(-) delete mode 100644 clive/__private/models/base.py diff --git a/clive/__private/core/alarms/alarm_identifier.py b/clive/__private/core/alarms/alarm_identifier.py index 5ef1cd4559..aeb100c0c0 100644 --- a/clive/__private/core/alarms/alarm_identifier.py +++ b/clive/__private/core/alarms/alarm_identifier.py @@ -2,11 +2,10 @@ from __future__ import annotations from abc import ABC -from clive.__private.models.base import CliveBaseModel -from clive.__private.models.schemas import HiveDateTime # noqa: TC001 +from clive.__private.models.schemas import HiveDateTime, PreconfiguredBaseModel -class AlarmIdentifier(CliveBaseModel, ABC): +class AlarmIdentifier(PreconfiguredBaseModel, ABC): """Base class for alarm identifiers.""" diff --git a/clive/__private/models/base.py b/clive/__private/models/base.py deleted file mode 100644 index e3f78c8847..0000000000 --- a/clive/__private/models/base.py +++ /dev/null @@ -1,73 +0,0 @@ -from __future__ import annotations - -from datetime import datetime -from typing import TYPE_CHECKING, Any - -from pydantic import BaseModel - -from clive.__private.core.constants.date import TIME_FORMAT_WITH_SECONDS -from clive.__private.models.schemas import Serializable - -if TYPE_CHECKING: - from collections.abc import Callable - - from pydantic.typing import AbstractSetIntStr, DictStrAny, MappingIntStrAny - - -class CliveBaseModel(BaseModel): - class Config: - allow_population_by_field_name = True - json_encoders = { # noqa: RUF012 # pydantic convention - datetime: lambda d: d.strftime(TIME_FORMAT_WITH_SECONDS), - Serializable: lambda x: x.serialize(), - } - - def json( # noqa: PLR0913 - self, - *, - include: AbstractSetIntStr | MappingIntStrAny | None = None, - exclude: AbstractSetIntStr | MappingIntStrAny | None = None, - by_alias: bool = True, # modified, most of the time we want to dump by alias - skip_defaults: bool | None = None, - exclude_unset: bool = False, - exclude_defaults: bool = False, - exclude_none: bool = False, - encoder: Callable[[Any], Any] | None = None, - models_as_dict: bool = True, - ensure_ascii: bool = False, # modified, so unicode characters are not escaped, will properly dump e.g. polish characters # noqa: E501 - **dumps_kwargs: Any, - ) -> str: - return super().json( - include=include, - exclude=exclude, - by_alias=by_alias, - skip_defaults=skip_defaults, - exclude_unset=exclude_unset, - exclude_defaults=exclude_defaults, - exclude_none=exclude_none, - encoder=encoder, - models_as_dict=models_as_dict, - ensure_ascii=ensure_ascii, - **dumps_kwargs, - ) - - def dict( # noqa: PLR0913 - self, - *, - include: AbstractSetIntStr | MappingIntStrAny | None = None, - exclude: AbstractSetIntStr | MappingIntStrAny | None = None, - by_alias: bool = True, # modified, most of the time we want to dump by alias - skip_defaults: bool | None = None, - exclude_unset: bool = False, - exclude_defaults: bool = False, - exclude_none: bool = False, - ) -> DictStrAny: - return super().dict( - include=include, - exclude=exclude, - by_alias=by_alias, - skip_defaults=skip_defaults, - exclude_unset=exclude_unset, - exclude_defaults=exclude_defaults, - exclude_none=exclude_none, - ) diff --git a/clive/__private/models/schemas.py b/clive/__private/models/schemas.py index 9da29b9dc3..525fd51420 100644 --- a/clive/__private/models/schemas.py +++ b/clive/__private/models/schemas.py @@ -16,6 +16,7 @@ has unnecessary "Fundament" suffix, and is not specialized with HF26 assets. from __future__ import annotations +from schemas._preconfigured_base_model import PreconfiguredBaseModel from schemas.apis.account_history_api import GetAccountHistory from schemas.apis.database_api import ( FindAccounts, @@ -62,7 +63,6 @@ from schemas.fields.compound import Proposal as SchemasProposal from schemas.fields.hex import Sha256, Signature, TransactionId from schemas.fields.hive_datetime import HiveDateTime from schemas.fields.hive_int import HiveInt -from schemas.fields.serializable import Serializable from schemas.operation import Operation from schemas.operations import ( AccountCreateOperation, @@ -246,11 +246,11 @@ __all__ = [ # noqa: RUF022 "Policy", "set_policies", # other + "PreconfiguredBaseModel", "convert_to_representation", "is_matching_model", "validate_schema_field", "RepresentationBase", - "Serializable", ] # operation BASIC aliases diff --git a/clive/__private/storage/migrations/base.py b/clive/__private/storage/migrations/base.py index e0c586cefd..c689000254 100644 --- a/clive/__private/storage/migrations/base.py +++ b/clive/__private/storage/migrations/base.py @@ -4,7 +4,7 @@ from abc import ABC from hashlib import sha256 from typing import Any, ClassVar, Self, get_type_hints -from clive.__private.models.base import CliveBaseModel +from clive.__private.models.schemas import PreconfiguredBaseModel from clive.exceptions import CliveError type Revision = str @@ -23,7 +23,7 @@ class StorageVersionNotFoundError(CliveError): super().__init__(self.message) -class ProfileStorageBase(CliveBaseModel, ABC): +class ProfileStorageBase(PreconfiguredBaseModel, ABC): _REVISIONS: ClassVar[list[Revision]] = [] _REVISION_TO_MODEL_TYPE_MAP: ClassVar[dict[Revision, type[ProfileStorageBase]]] = {} diff --git a/clive/__private/storage/migrations/v0.py b/clive/__private/storage/migrations/v0.py index 3dc1dfd440..6c2aff8b89 100644 --- a/clive/__private/storage/migrations/v0.py +++ b/clive/__private/storage/migrations/v0.py @@ -5,21 +5,21 @@ from pathlib import Path # noqa: TC003 from typing import Any, Self, TypeAlias from clive.__private.core.date_utils import utc_epoch -from clive.__private.models.base import CliveBaseModel from clive.__private.models.schemas import ( HiveDateTime, HiveInt, OperationRepresentationUnion, + PreconfiguredBaseModel, Signature, ) from clive.__private.storage.migrations.base import ProfileStorageBase -class DateTimeAlarmIdentifierStorageModel(CliveBaseModel): +class DateTimeAlarmIdentifierStorageModel(PreconfiguredBaseModel): value: HiveDateTime -class RecoveryAccountWarningListedAlarmIdentifierStorageModel(CliveBaseModel): +class RecoveryAccountWarningListedAlarmIdentifierStorageModel(PreconfiguredBaseModel): recovery_account: str @@ -28,24 +28,24 @@ AllAlarmIdentifiersStorageModel = ( ) -class AlarmStorageModel(CliveBaseModel): +class AlarmStorageModel(PreconfiguredBaseModel): name: str is_harmless: bool = False identifier: AllAlarmIdentifiersStorageModel """Identifies the occurrence of specific alarm among other possible alarms of same type. E.g. end date.""" -class TrackedAccountStorageModel(CliveBaseModel): +class TrackedAccountStorageModel(PreconfiguredBaseModel): name: str alarms: Sequence[AlarmStorageModel] = [] -class KeyAliasStorageModel(CliveBaseModel): +class KeyAliasStorageModel(PreconfiguredBaseModel): alias: str public_key: str -class TransactionCoreStorageModel(CliveBaseModel): +class TransactionCoreStorageModel(PreconfiguredBaseModel): operations: list[OperationRepresentationUnion] = [] # noqa: RUF012 ref_block_num: HiveInt = HiveInt(-1) ref_block_prefix: HiveInt = HiveInt(-1) @@ -58,7 +58,7 @@ class TransactionCoreStorageModel(CliveBaseModel): field_schema.update({"type": "object", "description": "This should not be included in revision calculation"}) -class TransactionStorageModel(CliveBaseModel): +class TransactionStorageModel(PreconfiguredBaseModel): transaction_core: TransactionCoreStorageModel transaction_file_path: Path | None = None diff --git a/clive/__private/storage/migrations/v1.py b/clive/__private/storage/migrations/v1.py index 5c802c6d57..ebeff3ea3c 100644 --- a/clive/__private/storage/migrations/v1.py +++ b/clive/__private/storage/migrations/v1.py @@ -3,7 +3,7 @@ from __future__ import annotations from pathlib import Path # noqa: TC003 from typing import Self, TypeAlias -from clive.__private.models.base import CliveBaseModel +from clive.__private.models.schemas import PreconfiguredBaseModel from clive.__private.models.schemas import Transaction as SchemasTransaction from clive.__private.storage.migrations import v0 @@ -12,7 +12,7 @@ class Transaction(SchemasTransaction): __modify_schema__ = v0.TransactionCoreStorageModel.__modify_schema__ -class TransactionStorageModel(CliveBaseModel): +class TransactionStorageModel(PreconfiguredBaseModel): transaction_core: Transaction transaction_file_path: Path | None = None -- GitLab From 4c4deb0440480207b71b19595deecaa75efe1fd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Radek=20Mas=C5=82owski?= Date: Tue, 5 Aug 2025 07:41:16 +0000 Subject: [PATCH 07/35] Remove indent during model.json() from places where it is not required --- clive/__private/storage/migrations/base.py | 4 ++-- clive/__private/storage/service.py | 2 +- .../storage_migration/regenerate_prepared_profiles.py | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/clive/__private/storage/migrations/base.py b/clive/__private/storage/migrations/base.py index c689000254..62eb599874 100644 --- a/clive/__private/storage/migrations/base.py +++ b/clive/__private/storage/migrations/base.py @@ -38,7 +38,7 @@ class ProfileStorageBase(PreconfiguredBaseModel, ABC): cls._validate_upgrade_definition() def __hash__(self) -> int: - return hash(self.json(indent=4)) + return hash(self.json()) @classmethod def upgrade(cls, old: ProfileStorageBase) -> Self: @@ -93,7 +93,7 @@ class ProfileStorageBase(PreconfiguredBaseModel, ABC): @classmethod def _get_revision_seed(cls) -> str: - return cls.schema_json(indent=4) + str(cls._REVISION_NONCE) + return cls.schema_json() + str(cls._REVISION_NONCE) @classmethod def _validate_upgrade_definition(cls) -> None: diff --git a/clive/__private/storage/service.py b/clive/__private/storage/service.py index 441128c9dc..84fd954679 100644 --- a/clive/__private/storage/service.py +++ b/clive/__private/storage/service.py @@ -335,7 +335,7 @@ class PersistentStorageService: profile_directory.mkdir(parents=True, exist_ok=True) try: - encrypted_profile = await self._encryption_service.encrypt(profile_model.json(indent=4)) + encrypted_profile = await self._encryption_service.encrypt(profile_model.json()) except (CommandEncryptError, CommandRequiresUnlockedEncryptionWalletError) as error: raise ProfileEncryptionError from error diff --git a/tests/clive-local-tools/clive_local_tools/storage_migration/regenerate_prepared_profiles.py b/tests/clive-local-tools/clive_local_tools/storage_migration/regenerate_prepared_profiles.py index f22cb71611..ef6fffae64 100644 --- a/tests/clive-local-tools/clive_local_tools/storage_migration/regenerate_prepared_profiles.py +++ b/tests/clive-local-tools/clive_local_tools/storage_migration/regenerate_prepared_profiles.py @@ -120,7 +120,7 @@ async def _main() -> None: with copy_profile_files_from_tmp_dir("without_alarms_and_operations"): async with prepare_encryption_service() as encryption_service: profile_model = create_model_from_scratch() - encrypted = await encryption_service.encrypt(profile_model.json(indent=4)) + encrypted = await encryption_service.encrypt(profile_model.json()) save_encrypted_profile(encrypted) with copy_profile_files_from_tmp_dir("with_alarms"): @@ -135,7 +135,7 @@ async def _main() -> None: ), ) ] - encrypted = await encryption_service.encrypt(profile_model.json(indent=4)) + encrypted = await encryption_service.encrypt(profile_model.json()) save_encrypted_profile(encrypted) with copy_profile_files_from_tmp_dir("with_operations"): @@ -147,7 +147,7 @@ async def _main() -> None: ), transaction_file_path=Path("example/path"), ) - encrypted = await encryption_service.encrypt(profile_model.json(indent=4)) + encrypted = await encryption_service.encrypt(profile_model.json()) save_encrypted_profile(encrypted) -- GitLab From 7d0b721c3521381e79a2af85d7af69e956c73277 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Mas=C5=82owski?= Date: Tue, 5 Aug 2025 14:14:48 +0200 Subject: [PATCH 08/35] Use order="deterministic" during hash calculation --- clive/__private/storage/migrations/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clive/__private/storage/migrations/base.py b/clive/__private/storage/migrations/base.py index 62eb599874..dc746cad10 100644 --- a/clive/__private/storage/migrations/base.py +++ b/clive/__private/storage/migrations/base.py @@ -38,7 +38,7 @@ class ProfileStorageBase(PreconfiguredBaseModel, ABC): cls._validate_upgrade_definition() def __hash__(self) -> int: - return hash(self.json()) + return hash(self.json(order="deterministic")) @classmethod def upgrade(cls, old: ProfileStorageBase) -> Self: -- GitLab From d0712f12cbd6abf9d432967c458466dc82abb39d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Mas=C5=82owski?= Date: Tue, 5 Aug 2025 14:08:25 +0200 Subject: [PATCH 09/35] Remove by_alias=True since it is the default now --- .../cli/commands/abc/perform_actions_on_transaction_command.py | 2 +- clive/__private/cli/commands/beekeeper.py | 2 +- .../__private/cli/commands/process/process_transfer_schedule.py | 2 +- clive/__private/core/commands/save_transaction.py | 2 +- clive/__private/core/formatters/humanize.py | 2 +- clive/__private/core/iwax.py | 2 +- clive/__private/models/transaction.py | 2 +- tests/clive-local-tools/clive_local_tools/helpers.py | 2 +- tests/functional/commands/test_load_transaction.py | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/clive/__private/cli/commands/abc/perform_actions_on_transaction_command.py b/clive/__private/cli/commands/abc/perform_actions_on_transaction_command.py index 160950e0be..1b2f03e2ad 100644 --- a/clive/__private/cli/commands/abc/perform_actions_on_transaction_command.py +++ b/clive/__private/cli/commands/abc/perform_actions_on_transaction_command.py @@ -137,7 +137,7 @@ class PerformActionsOnTransactionCommand(WorldBasedCommand, ForceableCLICommand, return "created" def __print_transaction(self, transaction: Transaction) -> None: - transaction_json = transaction.json(by_alias=True) + transaction_json = transaction.json() message = self._get_transaction_created_message().capitalize() typer.echo(f"{message} transaction:") rich.print_json(transaction_json) diff --git a/clive/__private/cli/commands/beekeeper.py b/clive/__private/cli/commands/beekeeper.py index 30e4b9c52e..7103f85586 100644 --- a/clive/__private/cli/commands/beekeeper.py +++ b/clive/__private/cli/commands/beekeeper.py @@ -26,7 +26,7 @@ class BeekeeperInfo(WorldBasedCommand): async def _run(self) -> None: session = await self.world.beekeeper_manager.beekeeper.session - info = (await session.get_info()).json(by_alias=True) + info = (await session.get_info()).json() typer.echo(info) diff --git a/clive/__private/cli/commands/process/process_transfer_schedule.py b/clive/__private/cli/commands/process/process_transfer_schedule.py index 8d5466ac67..656ff1415f 100644 --- a/clive/__private/cli/commands/process/process_transfer_schedule.py +++ b/clive/__private/cli/commands/process/process_transfer_schedule.py @@ -62,7 +62,7 @@ class _ProcessTransferScheduleCommon(OperationCommand, ABC): extension = RecurrentTransferPairIdRepresentation( type=recurrent_transfer_extension.get_name(), value=recurrent_transfer_extension ) - return [extension.dict(by_alias=True)] + return [extension.dict()] def _identity_check(self, scheduled_transfer: ScheduledTransfer) -> bool: """Determine if a scheduled transfer matches destination and the specified pair ID.""" diff --git a/clive/__private/core/commands/save_transaction.py b/clive/__private/core/commands/save_transaction.py index cd7eca9fc7..0aaab0f3b4 100644 --- a/clive/__private/core/commands/save_transaction.py +++ b/clive/__private/core/commands/save_transaction.py @@ -28,7 +28,7 @@ class SaveTransaction(Command): self.__save_as_binary() if self.__should_save_as_binary() else self.__save_as_json() def __save_as_json(self) -> None: - serialized = self.transaction.json(by_alias=True) + serialized = self.transaction.json() self.file_path.write_text(serialized) def __save_as_binary(self) -> None: diff --git a/clive/__private/core/formatters/humanize.py b/clive/__private/core/formatters/humanize.py index 807a6a0cef..70b99573d7 100644 --- a/clive/__private/core/formatters/humanize.py +++ b/clive/__private/core/formatters/humanize.py @@ -285,7 +285,7 @@ def humanize_operation_details(operation: OperationBase) -> str: """ out = "" - operation_dict = dict(operation._iter(by_alias=True)) + operation_dict = dict(operation._iter()) for key, value in operation_dict.items(): value_ = value diff --git a/clive/__private/core/iwax.py b/clive/__private/core/iwax.py index 734edd6a45..00c92aeb08 100644 --- a/clive/__private/core/iwax.py +++ b/clive/__private/core/iwax.py @@ -86,7 +86,7 @@ def __as_binary_json(item: OperationUnion | Transaction) -> bytes: if not isinstance(item, Transaction): item = convert_to_representation(item) - return item.json(by_alias=True).encode() + return item.json().encode() def validate_transaction(transaction: Transaction) -> None: diff --git a/clive/__private/models/transaction.py b/clive/__private/models/transaction.py index ef9c7d33a1..900fce96fd 100644 --- a/clive/__private/models/transaction.py +++ b/clive/__private/models/transaction.py @@ -96,7 +96,7 @@ class Transaction(SchemasTransaction): self.signatures.clear() def with_hash(self) -> TransactionWithHash: - return TransactionWithHash(**self.dict(by_alias=True), transaction_id=self.calculate_transaction_id()) + return TransactionWithHash(**self.dict(), transaction_id=self.calculate_transaction_id()) def accept(self, visitor: OperationVisitor) -> None: """ diff --git a/tests/clive-local-tools/clive_local_tools/helpers.py b/tests/clive-local-tools/clive_local_tools/helpers.py index 3134ff0e7d..bbeee8a2cf 100644 --- a/tests/clive-local-tools/clive_local_tools/helpers.py +++ b/tests/clive-local-tools/clive_local_tools/helpers.py @@ -47,6 +47,6 @@ def get_formatted_error_message(error: ClickException) -> str: def create_transaction_file(path: Path, content: TransactionConvertibleType) -> Path: transaction_path = path / "trx.json" transaction = ensure_transaction(content) - transaction_serialized = transaction.json(by_alias=True) + transaction_serialized = transaction.json() transaction_path.write_text(transaction_serialized) return transaction_path diff --git a/tests/functional/commands/test_load_transaction.py b/tests/functional/commands/test_load_transaction.py index 79d6764072..79fe2e443b 100644 --- a/tests/functional/commands/test_load_transaction.py +++ b/tests/functional/commands/test_load_transaction.py @@ -30,7 +30,7 @@ async def test_loading_valid_transaction_file(tmp_path: Path, mode: Literal["jso file_path = tmp_path / file_name if mode == "json": - file_path.write_text(expected_transaction.json(by_alias=True)) + file_path.write_text(expected_transaction.json()) else: file_path.write_bytes(iwax.serialize_transaction(expected_transaction)) -- GitLab From 261574d88aacfb866b5dc907acc52eced2d0d01b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20=C5=BBebrak?= Date: Fri, 1 Aug 2025 09:07:59 +0200 Subject: [PATCH 10/35] Add order="sorted" to json calls where output is visible for the user --- .../cli/commands/abc/perform_actions_on_transaction_command.py | 2 +- clive/__private/cli/commands/beekeeper.py | 2 +- clive/__private/core/commands/save_transaction.py | 2 +- clive/__private/ui/dialogs/raw_json_dialog.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/clive/__private/cli/commands/abc/perform_actions_on_transaction_command.py b/clive/__private/cli/commands/abc/perform_actions_on_transaction_command.py index 1b2f03e2ad..84e1ff5676 100644 --- a/clive/__private/cli/commands/abc/perform_actions_on_transaction_command.py +++ b/clive/__private/cli/commands/abc/perform_actions_on_transaction_command.py @@ -137,7 +137,7 @@ class PerformActionsOnTransactionCommand(WorldBasedCommand, ForceableCLICommand, return "created" def __print_transaction(self, transaction: Transaction) -> None: - transaction_json = transaction.json() + transaction_json = transaction.json(order="sorted") message = self._get_transaction_created_message().capitalize() typer.echo(f"{message} transaction:") rich.print_json(transaction_json) diff --git a/clive/__private/cli/commands/beekeeper.py b/clive/__private/cli/commands/beekeeper.py index 7103f85586..2f8c27eb3a 100644 --- a/clive/__private/cli/commands/beekeeper.py +++ b/clive/__private/cli/commands/beekeeper.py @@ -26,7 +26,7 @@ class BeekeeperInfo(WorldBasedCommand): async def _run(self) -> None: session = await self.world.beekeeper_manager.beekeeper.session - info = (await session.get_info()).json() + info = (await session.get_info()).json(order="sorted") typer.echo(info) diff --git a/clive/__private/core/commands/save_transaction.py b/clive/__private/core/commands/save_transaction.py index 0aaab0f3b4..79e9060615 100644 --- a/clive/__private/core/commands/save_transaction.py +++ b/clive/__private/core/commands/save_transaction.py @@ -28,7 +28,7 @@ class SaveTransaction(Command): self.__save_as_binary() if self.__should_save_as_binary() else self.__save_as_json() def __save_as_json(self) -> None: - serialized = self.transaction.json() + serialized = self.transaction.json(order="sorted") self.file_path.write_text(serialized) def __save_as_binary(self) -> None: diff --git a/clive/__private/ui/dialogs/raw_json_dialog.py b/clive/__private/ui/dialogs/raw_json_dialog.py index e9527fc7e8..d7e1f248ab 100644 --- a/clive/__private/ui/dialogs/raw_json_dialog.py +++ b/clive/__private/ui/dialogs/raw_json_dialog.py @@ -28,4 +28,4 @@ class RawJsonDialog(CliveInfoDialog): @staticmethod def _get_operation_representation_json(operation: OperationBase) -> str: representation: OperationRepresentationBase = convert_to_representation(operation=operation) - return representation.json() + return representation.json(order="sorted") -- GitLab From 9980c54cb4cc1af9a54c7679a3d498e1fe966d62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20=C5=BBebrak?= Date: Tue, 5 Aug 2025 14:02:55 +0200 Subject: [PATCH 11/35] Add indent=4 when saving transaction to json file --- clive/__private/core/commands/save_transaction.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clive/__private/core/commands/save_transaction.py b/clive/__private/core/commands/save_transaction.py index 79e9060615..da8769fc42 100644 --- a/clive/__private/core/commands/save_transaction.py +++ b/clive/__private/core/commands/save_transaction.py @@ -28,7 +28,7 @@ class SaveTransaction(Command): self.__save_as_binary() if self.__should_save_as_binary() else self.__save_as_json() def __save_as_json(self) -> None: - serialized = self.transaction.json(order="sorted") + serialized = self.transaction.json(order="sorted", indent=4) self.file_path.write_text(serialized) def __save_as_binary(self) -> None: -- GitLab From 28be850fbfaeb14b426f86183231f1208125e004 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Radek=20Mas=C5=82owski?= Date: Wed, 16 Jul 2025 07:21:56 +0000 Subject: [PATCH 12/35] Remove ABC, you shouldn't inherit from ABC and msgspec.Struct --- clive/__private/core/alarms/alarm_identifier.py | 4 +--- clive/__private/storage/migrations/base.py | 3 +-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/clive/__private/core/alarms/alarm_identifier.py b/clive/__private/core/alarms/alarm_identifier.py index aeb100c0c0..4eccfb55e6 100644 --- a/clive/__private/core/alarms/alarm_identifier.py +++ b/clive/__private/core/alarms/alarm_identifier.py @@ -1,11 +1,9 @@ from __future__ import annotations -from abc import ABC - from clive.__private.models.schemas import HiveDateTime, PreconfiguredBaseModel -class AlarmIdentifier(PreconfiguredBaseModel, ABC): +class AlarmIdentifier(PreconfiguredBaseModel): """Base class for alarm identifiers.""" diff --git a/clive/__private/storage/migrations/base.py b/clive/__private/storage/migrations/base.py index dc746cad10..9503aca906 100644 --- a/clive/__private/storage/migrations/base.py +++ b/clive/__private/storage/migrations/base.py @@ -1,6 +1,5 @@ from __future__ import annotations -from abc import ABC from hashlib import sha256 from typing import Any, ClassVar, Self, get_type_hints @@ -23,7 +22,7 @@ class StorageVersionNotFoundError(CliveError): super().__init__(self.message) -class ProfileStorageBase(PreconfiguredBaseModel, ABC): +class ProfileStorageBase(PreconfiguredBaseModel): _REVISIONS: ClassVar[list[Revision]] = [] _REVISION_TO_MODEL_TYPE_MAP: ClassVar[dict[Revision, type[ProfileStorageBase]]] = {} -- GitLab From 9475fb6e91920217554543dd4a4b7ba356f6b469 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Radek=20Mas=C5=82owski?= Date: Wed, 16 Jul 2025 07:26:28 +0000 Subject: [PATCH 13/35] Add kw_only, because in schemas are missing defaults --- clive/__private/models/transaction.py | 2 +- clive/__private/storage/migrations/v0.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/clive/__private/models/transaction.py b/clive/__private/models/transaction.py index 900fce96fd..d788177fed 100644 --- a/clive/__private/models/transaction.py +++ b/clive/__private/models/transaction.py @@ -141,5 +141,5 @@ class Transaction(SchemasTransaction): return visitor.get_unknown_accounts(already_known_accounts) -class TransactionWithHash(Transaction): +class TransactionWithHash(Transaction, kw_only=True): transaction_id: TransactionId diff --git a/clive/__private/storage/migrations/v0.py b/clive/__private/storage/migrations/v0.py index 6c2aff8b89..4a0714b9d7 100644 --- a/clive/__private/storage/migrations/v0.py +++ b/clive/__private/storage/migrations/v0.py @@ -28,7 +28,7 @@ AllAlarmIdentifiersStorageModel = ( ) -class AlarmStorageModel(PreconfiguredBaseModel): +class AlarmStorageModel(PreconfiguredBaseModel, kw_only=True): name: str is_harmless: bool = False identifier: AllAlarmIdentifiersStorageModel @@ -63,7 +63,7 @@ class TransactionStorageModel(PreconfiguredBaseModel): transaction_file_path: Path | None = None -class ProfileStorageModel(ProfileStorageBase): +class ProfileStorageModel(ProfileStorageBase, kw_only=True): name: str working_account: str | None = None tracked_accounts: Sequence[TrackedAccountStorageModel] = [] -- GitLab From cf8029d8ca2214cbd76f345863465007e96d73d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Radek=20Mas=C5=82owski?= Date: Wed, 16 Jul 2025 12:50:44 +0000 Subject: [PATCH 14/35] Use strict int conversion in iwax function --- clive/__private/core/iwax.py | 46 +++++++++++++++++++++++++++++------- 1 file changed, 37 insertions(+), 9 deletions(-) diff --git a/clive/__private/core/iwax.py b/clive/__private/core/iwax.py index 00c92aeb08..4ef949100e 100644 --- a/clive/__private/core/iwax.py +++ b/clive/__private/core/iwax.py @@ -1,13 +1,18 @@ from __future__ import annotations import datetime -from typing import TYPE_CHECKING, Protocol, cast +from collections.abc import Callable +from functools import wraps +from typing import TYPE_CHECKING, Any, Protocol, TypeVar, cast import wax from clive.__private.core.constants.precision import HIVE_PERCENT_PRECISION_DOT_PLACES from clive.__private.core.decimal_conventer import DecimalConverter from clive.__private.core.percent_conversions import hive_percent_to_percent -from clive.__private.models.schemas import convert_to_representation +from clive.__private.models.schemas import ( + HiveInt, + convert_to_representation, +) from clive.exceptions import CliveError if TYPE_CHECKING: @@ -17,6 +22,22 @@ if TYPE_CHECKING: from clive.__private.models import Asset, Transaction from clive.__private.models.schemas import OperationUnion, PriceFeed +F = TypeVar("F", bound=Callable[..., Any]) + + +def cast_hiveint_args(func: F) -> F: + @wraps(func) + def wrapper(*args: Any, **kwargs: Any) -> Any: # noqa: ANN401 + def hiveint_to_int(value: Any) -> Any: # noqa: ANN401 + return int(value) if isinstance(value, HiveInt) else value + + new_args = tuple(hiveint_to_int(arg) for arg in args) + new_kwargs = {k: hiveint_to_int(v) for k, v in kwargs.items()} + + return func(*new_args, **new_kwargs) + + return cast("F", wrapper) + class HpAPRProtocol(Protocol): """ @@ -72,7 +93,7 @@ def to_python_json_asset(asset: Asset.AnyT) -> wax.python_json_asset: case "VESTS": return wax.vests(amount=int(asset.amount)) case _: - raise UnknownAssetTypeError(asset.nai) + raise UnknownAssetTypeError(asset.nai()) def __validate_wax_response(response: wax.python_result) -> None: @@ -83,10 +104,8 @@ def __validate_wax_response(response: wax.python_result) -> None: def __as_binary_json(item: OperationUnion | Transaction) -> bytes: from clive.__private.models import Transaction - if not isinstance(item, Transaction): - item = convert_to_representation(item) - - return item.json().encode() + to_serialize = item if isinstance(item, Transaction) else convert_to_representation(item) + return to_serialize.json().encode() def validate_transaction(transaction: Transaction) -> None: @@ -139,6 +158,7 @@ def generate_private_key() -> PrivateKey: return PrivateKey(value=result.result.decode()) +@cast_hiveint_args def calculate_manabar_full_regeneration_time( now: int, max_mana: int, current_mana: int, last_update_time: int ) -> datetime.datetime: @@ -149,6 +169,7 @@ def calculate_manabar_full_regeneration_time( return datetime.datetime.fromtimestamp(int(result.result.decode()), tz=datetime.UTC) +@cast_hiveint_args def calculate_current_manabar_value(now: int, max_mana: int, current_mana: int, last_update_time: int) -> int: result = wax.calculate_current_manabar_value( now=now, max_mana=max_mana, current_mana=current_mana, last_update_time=last_update_time @@ -157,6 +178,7 @@ def calculate_current_manabar_value(now: int, max_mana: int, current_mana: int, return int(result.result.decode()) +@cast_hiveint_args def general_asset(asset_num: int, amount: int) -> Asset.AnyT: return from_python_json_asset(wax.general_asset(asset_num=asset_num, amount=amount)) @@ -165,22 +187,25 @@ def get_tapos_data(block_id: str) -> wax.python_ref_block_data: return wax.get_tapos_data(block_id.encode()) +@cast_hiveint_args def hive(amount: int) -> Asset.Hive: return cast("Asset.Hive", from_python_json_asset(wax.hive(amount))) +@cast_hiveint_args def hbd(amount: int) -> Asset.Hbd: return cast("Asset.Hbd", from_python_json_asset(wax.hbd(amount))) +@cast_hiveint_args def vests(amount: int) -> Asset.Vests: return cast("Asset.Vests", from_python_json_asset(wax.vests(amount))) def calculate_hp_apr(data: HpAPRProtocol) -> Decimal: result = wax.calculate_hp_apr( - head_block_num=data.head_block_number, - vesting_reward_percent=data.vesting_reward_percent, + head_block_num=int(data.head_block_number), + vesting_reward_percent=int(data.vesting_reward_percent), virtual_supply=to_python_json_asset(data.virtual_supply), total_vesting_fund_hive=to_python_json_asset(data.total_vesting_fund_hive), ) @@ -197,6 +222,7 @@ def calculate_hbd_to_hive(_hbd: Asset.Hbd, current_price_feed: PriceFeed) -> Ass return cast("Asset.Hive", from_python_json_asset(result)) +@cast_hiveint_args def calculate_vests_to_hp(_vests: int | Asset.Vests, data: TotalVestingProtocol) -> Asset.Hive: vests_json_asset = wax.vests(_vests) if isinstance(_vests, int) else to_python_json_asset(_vests) result = wax.calculate_vests_to_hp( @@ -216,12 +242,14 @@ def calculate_hp_to_vests(_hive: Asset.Hive, data: TotalVestingProtocol) -> Asse return cast("Asset.Vests", from_python_json_asset(result)) +@cast_hiveint_args def calculate_current_inflation_rate(head_block_num: int) -> Decimal: result = wax.calculate_inflation_rate_for_block(head_block_num) __validate_wax_response(result) return hive_percent_to_percent(result.result.decode()) +@cast_hiveint_args def calculate_witness_votes_hp(votes: int, data: TotalVestingProtocol) -> Asset.Hive: result = wax.calculate_witness_votes_hp( votes=votes, -- GitLab From 665c276b9710ac57dc2b80c7c4bfbe1e08aa28cd Mon Sep 17 00:00:00 2001 From: Marcin Sobczyk Date: Wed, 16 Jul 2025 10:16:17 +0000 Subject: [PATCH 15/35] Define type aliases for profile model components as ClassVars of ProfileStorageModel --- clive/__private/storage/migrations/v0.py | 18 +++++++++--------- clive/__private/storage/migrations/v1.py | 4 ++-- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/clive/__private/storage/migrations/v0.py b/clive/__private/storage/migrations/v0.py index 4a0714b9d7..350e3e91a3 100644 --- a/clive/__private/storage/migrations/v0.py +++ b/clive/__private/storage/migrations/v0.py @@ -2,7 +2,7 @@ from __future__ import annotations from collections.abc import Sequence # noqa: TC003 from pathlib import Path # noqa: TC003 -from typing import Any, Self, TypeAlias +from typing import Any, ClassVar, Self, TypeAlias from clive.__private.core.date_utils import utc_epoch from clive.__private.models.schemas import ( @@ -74,16 +74,16 @@ class ProfileStorageModel(ProfileStorageBase, kw_only=True): node_address: str should_enable_known_accounts: bool = True - _AlarmStorageModel: TypeAlias = AlarmStorageModel # noqa: UP040 - _TrackedAccountStorageModel: TypeAlias = TrackedAccountStorageModel # noqa: UP040 - _KeyAliasStorageModel: TypeAlias = KeyAliasStorageModel # noqa: UP040 - _TransactionCoreStorageModel: TypeAlias = TransactionCoreStorageModel # noqa: UP040 - _TransactionStorageModel: TypeAlias = TransactionStorageModel # noqa: UP040 - _DateTimeAlarmIdentifierStorageModel: TypeAlias = DateTimeAlarmIdentifierStorageModel # noqa: UP040 - _RecoveryAccountWarningListedAlarmIdentifierStorageModel: TypeAlias = ( # noqa: UP040 + _AlarmStorageModel: ClassVar[TypeAlias] = AlarmStorageModel + _TrackedAccountStorageModel: ClassVar[TypeAlias] = TrackedAccountStorageModel + _KeyAliasStorageModel: ClassVar[TypeAlias] = KeyAliasStorageModel + _TransactionCoreStorageModel: ClassVar[TypeAlias] = TransactionCoreStorageModel + _TransactionStorageModel: ClassVar[TypeAlias] = TransactionStorageModel + _DateTimeAlarmIdentifierStorageModel: ClassVar[TypeAlias] = DateTimeAlarmIdentifierStorageModel + _RecoveryAccountWarningListedAlarmIdentifierStorageModel: ClassVar[TypeAlias] = ( RecoveryAccountWarningListedAlarmIdentifierStorageModel ) - _AllAlarmIdentifiersStorageModel: TypeAlias = AllAlarmIdentifiersStorageModel # noqa: UP040 + _AllAlarmIdentifiersStorageModel: ClassVar[TypeAlias] = AllAlarmIdentifiersStorageModel @classmethod def upgrade(cls, old: ProfileStorageBase) -> Self: diff --git a/clive/__private/storage/migrations/v1.py b/clive/__private/storage/migrations/v1.py index ebeff3ea3c..7ce8508e30 100644 --- a/clive/__private/storage/migrations/v1.py +++ b/clive/__private/storage/migrations/v1.py @@ -1,7 +1,7 @@ from __future__ import annotations from pathlib import Path # noqa: TC003 -from typing import Self, TypeAlias +from typing import ClassVar, Self, TypeAlias from clive.__private.models.schemas import PreconfiguredBaseModel from clive.__private.models.schemas import Transaction as SchemasTransaction @@ -22,7 +22,7 @@ class ProfileStorageModel(v0.ProfileStorageModel): transaction: TransactionStorageModel | None = None # type: ignore[assignment] # changed storage model - _TransactionStorageModel: TypeAlias = TransactionStorageModel # noqa: UP040 + _TransactionStorageModel: ClassVar[TypeAlias] = TransactionStorageModel @classmethod def upgrade(cls, old: v0.ProfileStorageModel) -> Self: # type: ignore[override] # should always take previous model -- GitLab From 61de0ae752414c7c2f747c4d52b523aa78864ce3 Mon Sep 17 00:00:00 2001 From: Marcin Sobczyk Date: Fri, 18 Jul 2025 11:23:54 +0000 Subject: [PATCH 16/35] Remove union storing alarm identifiers AllAlarmIdentifiersStorageModel --- clive/__private/storage/migrations/v0.py | 64 +++++++++++++++++-- .../storage/runtime_to_storage_converter.py | 16 +++-- .../storage/storage_to_runtime_converter.py | 4 +- .../regenerate_prepared_profiles.py | 4 +- 4 files changed, 73 insertions(+), 15 deletions(-) diff --git a/clive/__private/storage/migrations/v0.py b/clive/__private/storage/migrations/v0.py index 350e3e91a3..b5e3776822 100644 --- a/clive/__private/storage/migrations/v0.py +++ b/clive/__private/storage/migrations/v0.py @@ -4,6 +4,13 @@ from collections.abc import Sequence # noqa: TC003 from pathlib import Path # noqa: TC003 from typing import Any, ClassVar, Self, TypeAlias +from clive.__private.core.alarms.specific_alarms import ( + ChangingRecoveryAccountInProgress, + DecliningVotingRightsInProgress, + GovernanceNoActiveVotes, + GovernanceVotingExpiration, + RecoveryAccountWarningListed, +) from clive.__private.core.date_utils import utc_epoch from clive.__private.models.schemas import ( HiveDateTime, @@ -28,16 +35,56 @@ AllAlarmIdentifiersStorageModel = ( ) -class AlarmStorageModel(PreconfiguredBaseModel, kw_only=True): - name: str +class AlarmStorageModelBase(PreconfiguredBaseModel, tag_field="name", kw_only=True): + @classmethod + def get_name(cls) -> str: + assert isinstance(cls.__struct_config__.tag, str), "Alarm storage models must have a string tag." + return cls.__struct_config__.tag + is_harmless: bool = False - identifier: AllAlarmIdentifiersStorageModel """Identifies the occurrence of specific alarm among other possible alarms of same type. E.g. end date.""" +class RecoveryAccountWarningListedStorageModel( + AlarmStorageModelBase, tag=RecoveryAccountWarningListed.get_name(), kw_only=True +): + identifier: RecoveryAccountWarningListedAlarmIdentifierStorageModel + + +class GovernanceVotingExpirationStorageModel( + AlarmStorageModelBase, tag=GovernanceVotingExpiration.get_name(), kw_only=True +): + identifier: DateTimeAlarmIdentifierStorageModel + + +class GovernanceNoActiveVotesStorageModel(AlarmStorageModelBase, tag=GovernanceNoActiveVotes.get_name(), kw_only=True): + identifier: DateTimeAlarmIdentifierStorageModel + + +class DecliningVotingRightsInProgressStorageModel( + AlarmStorageModelBase, tag=DecliningVotingRightsInProgress.get_name(), kw_only=True +): + identifier: DateTimeAlarmIdentifierStorageModel + + +class ChangingRecoveryAccountInProgressStorageModel( + AlarmStorageModelBase, tag=ChangingRecoveryAccountInProgress.get_name(), kw_only=True +): + identifier: DateTimeAlarmIdentifierStorageModel + + +AllAlarmStorageModel = ( + RecoveryAccountWarningListedStorageModel + | GovernanceVotingExpirationStorageModel + | GovernanceNoActiveVotesStorageModel + | DecliningVotingRightsInProgressStorageModel + | ChangingRecoveryAccountInProgressStorageModel +) + + class TrackedAccountStorageModel(PreconfiguredBaseModel): name: str - alarms: Sequence[AlarmStorageModel] = [] + alarms: Sequence[AllAlarmStorageModel] = [] class KeyAliasStorageModel(PreconfiguredBaseModel): @@ -74,7 +121,8 @@ class ProfileStorageModel(ProfileStorageBase, kw_only=True): node_address: str should_enable_known_accounts: bool = True - _AlarmStorageModel: ClassVar[TypeAlias] = AlarmStorageModel + _AllAlarmIdentifiersStorageModel: ClassVar[TypeAlias] = AllAlarmIdentifiersStorageModel + _AllAlarmStorageModel: ClassVar[TypeAlias] = AllAlarmStorageModel _TrackedAccountStorageModel: ClassVar[TypeAlias] = TrackedAccountStorageModel _KeyAliasStorageModel: ClassVar[TypeAlias] = KeyAliasStorageModel _TransactionCoreStorageModel: ClassVar[TypeAlias] = TransactionCoreStorageModel @@ -83,7 +131,11 @@ class ProfileStorageModel(ProfileStorageBase, kw_only=True): _RecoveryAccountWarningListedAlarmIdentifierStorageModel: ClassVar[TypeAlias] = ( RecoveryAccountWarningListedAlarmIdentifierStorageModel ) - _AllAlarmIdentifiersStorageModel: ClassVar[TypeAlias] = AllAlarmIdentifiersStorageModel + _RecoveryAccountWarningListedStorageModel: ClassVar[TypeAlias] = RecoveryAccountWarningListedStorageModel + _GovernanceVotingExpirationStorageModel: ClassVar[TypeAlias] = GovernanceVotingExpirationStorageModel + _GovernanceNoActiveVotesStorageModel: ClassVar[TypeAlias] = GovernanceNoActiveVotesStorageModel + _DecliningVotingRightsInProgressStorageModel: ClassVar[TypeAlias] = DecliningVotingRightsInProgressStorageModel + _ChangingRecoveryAccountInProgressStorageModel: ClassVar[TypeAlias] = ChangingRecoveryAccountInProgressStorageModel @classmethod def upgrade(cls, old: ProfileStorageBase) -> Self: diff --git a/clive/__private/storage/runtime_to_storage_converter.py b/clive/__private/storage/runtime_to_storage_converter.py index fdb184780e..899a172122 100644 --- a/clive/__private/storage/runtime_to_storage_converter.py +++ b/clive/__private/storage/runtime_to_storage_converter.py @@ -1,7 +1,7 @@ from __future__ import annotations from copy import deepcopy -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, get_args from clive.__private.core.alarms.alarm_identifier import DateTimeAlarmIdentifier from clive.__private.core.alarms.specific_alarms.recovery_account_warning_listed import ( @@ -71,9 +71,9 @@ class RuntimeToStorageConverter: alarms = [self._alarm_to_model(alarm) for alarm in account._alarms.all_alarms if alarm.has_identifier] return ProfileStorageModel._TrackedAccountStorageModel(name=account.name, alarms=alarms) - def _alarm_to_model(self, alarm: AnyAlarm) -> ProfileStorageModel._AlarmStorageModel: - return ProfileStorageModel._AlarmStorageModel( - name=alarm.get_name(), + def _alarm_to_model(self, alarm: AnyAlarm) -> ProfileStorageModel._AllAlarmStorageModel: + alarm_cls = self._get_alarm_storage_model_cls_by_name(alarm.get_name()) + return alarm_cls( is_harmless=alarm.is_harmless, identifier=self._alarm_identifier_to_model(alarm.identifier_ensure), ) @@ -91,3 +91,11 @@ class RuntimeToStorageConverter: def _key_alias_to_model(self, key: PublicKeyAliased) -> ProfileStorageModel._KeyAliasStorageModel: return ProfileStorageModel._KeyAliasStorageModel(alias=key.alias, public_key=key.value) + + def _get_alarm_storage_model_cls_by_name(self, name: str) -> type[ProfileStorageModel._AllAlarmStorageModel]: + all_alarm_storage_model_classes = get_args(ProfileStorageModel._AllAlarmStorageModel) + name_to_cls: dict[str, type[ProfileStorageModel._AllAlarmStorageModel]] = { + cls.get_name(): cls for cls in all_alarm_storage_model_classes + } + assert name in name_to_cls, f"Alarm class not found for name: {name}" + return name_to_cls[name] diff --git a/clive/__private/storage/storage_to_runtime_converter.py b/clive/__private/storage/storage_to_runtime_converter.py index afe858a552..28f2c70cd4 100644 --- a/clive/__private/storage/storage_to_runtime_converter.py +++ b/clive/__private/storage/storage_to_runtime_converter.py @@ -111,8 +111,8 @@ class StorageToRuntimeConverter: def _known_account_from_model_representation(self, name: str) -> KnownAccount: return KnownAccount(name) - def _alarm_from_model(self, model: ProfileStorageModel._AlarmStorageModel) -> AnyAlarm: - alarm_cls = Alarm.get_alarm_class_by_name(model.name) + def _alarm_from_model(self, model: ProfileStorageModel._AllAlarmStorageModel) -> AnyAlarm: + alarm_cls = Alarm.get_alarm_class_by_name(model.get_name()) identifier = self._alarm_identifier_from_model(model.identifier) return alarm_cls(identifier=identifier, is_harmless=model.is_harmless) diff --git a/tests/clive-local-tools/clive_local_tools/storage_migration/regenerate_prepared_profiles.py b/tests/clive-local-tools/clive_local_tools/storage_migration/regenerate_prepared_profiles.py index ef6fffae64..a0e3572a51 100644 --- a/tests/clive-local-tools/clive_local_tools/storage_migration/regenerate_prepared_profiles.py +++ b/tests/clive-local-tools/clive_local_tools/storage_migration/regenerate_prepared_profiles.py @@ -25,7 +25,6 @@ from clive.__private.core.commands.create_encryption_wallet import CreateEncrypt from clive.__private.core.commands.create_user_wallet import CreateUserWallet from clive.__private.core.constants.setting_identifiers import DATA_PATH from clive.__private.core.encryption import EncryptionService -from clive.__private.core.formatters.case import underscore from clive.__private.core.wallet_container import WalletContainer from clive.__private.models.schemas import TransferOperation, convert_to_representation from clive.__private.settings import safe_settings, settings @@ -127,8 +126,7 @@ async def _main() -> None: async with prepare_encryption_service() as encryption_service: profile_model = create_model_from_scratch() profile_model.tracked_accounts[0].alarms = [ - ProfileStorageModel._AlarmStorageModel( - name=underscore("RecoveryAccountWarningListed"), + ProfileStorageModel._RecoveryAccountWarningListedStorageModel( is_harmless=False, identifier=RecoveryAccountWarningListedAlarmIdentifier( recovery_account=ALT_WORKING_ACCOUNT2_DATA.account.name -- GitLab From c9c36f56bee9f8a0140bd517164cb55ec5e1e914 Mon Sep 17 00:00:00 2001 From: Marcin Sobczyk Date: Fri, 18 Jul 2025 15:09:59 +0000 Subject: [PATCH 17/35] Delay schema_json call during migrations model discovery due to msgspec incompatibility with msgspec schema_json cannot be called during __init_subclasses__ --- clive/__private/storage/current_model.py | 1 + clive/__private/storage/migrations/base.py | 16 +++++++++++----- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/clive/__private/storage/current_model.py b/clive/__private/storage/current_model.py index ba1d424f04..99f8a2c7a7 100644 --- a/clive/__private/storage/current_model.py +++ b/clive/__private/storage/current_model.py @@ -14,4 +14,5 @@ def _validate_current_model_alias() -> None: ) +ProfileStorageBase.gather() _validate_current_model_alias() diff --git a/clive/__private/storage/migrations/base.py b/clive/__private/storage/migrations/base.py index 9503aca906..b6513bb473 100644 --- a/clive/__private/storage/migrations/base.py +++ b/clive/__private/storage/migrations/base.py @@ -25,16 +25,22 @@ class StorageVersionNotFoundError(CliveError): class ProfileStorageBase(PreconfiguredBaseModel): _REVISIONS: ClassVar[list[Revision]] = [] _REVISION_TO_MODEL_TYPE_MAP: ClassVar[dict[Revision, type[ProfileStorageBase]]] = {} + _REGISTERED_MODELS: ClassVar[list[type[ProfileStorageBase]]] = [] _REVISION_NONCE: ClassVar[int] = 0 def __init_subclass__(cls, *args: Any, **kwargs: Any) -> None: super().__init_subclass__(*args, **kwargs) - revision = cls.get_this_revision() - assert revision not in cls._get_revisions(), f"Revision: {revision} already exists." - cls._REVISIONS.append(revision) - cls._REVISION_TO_MODEL_TYPE_MAP[revision] = cls - cls._validate_upgrade_definition() + cls._REGISTERED_MODELS.append(cls) + + @classmethod + def gather(cls) -> None: + for class_ in cls._REGISTERED_MODELS: + revision = class_.get_this_revision() + assert revision not in cls._get_revisions(), f"Revision: {revision} already exists." + class_._REVISIONS.append(revision) + class_._REVISION_TO_MODEL_TYPE_MAP[revision] = class_ + class_._validate_upgrade_definition() def __hash__(self) -> int: return hash(self.json(order="deterministic")) -- GitLab From 4bc4d63a53e8df0d00568bd05ddb256326e801d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Mas=C5=82owski?= Date: Tue, 5 Aug 2025 15:29:46 +0200 Subject: [PATCH 18/35] Reimplement AssetFactoryHolder as generic using dataclasses --- clive/__private/models/asset.py | 22 ++++++++----------- clive/__private/models/schemas.py | 8 +++---- .../currency_selector_base.py | 3 +-- 3 files changed, 14 insertions(+), 19 deletions(-) diff --git a/clive/__private/models/asset.py b/clive/__private/models/asset.py index b09fa6f8d0..f68a757a3a 100644 --- a/clive/__private/models/asset.py +++ b/clive/__private/models/asset.py @@ -2,24 +2,22 @@ from __future__ import annotations import re from collections.abc import Callable +from dataclasses import dataclass from typing import TYPE_CHECKING, Generic, TypeAlias, TypeVar -from pydantic.generics import GenericModel - from clive.__private.core.decimal_conventer import ( DecimalConversionNotANumberError, DecimalConverter, DecimalConvertible, ) -from clive.__private.models.base import CliveBaseModel -from clive.__private.models.schemas import AssetHbdHF26, AssetHiveHF26, AssetVestsHF26 +from clive.__private.models.schemas import AssetHbd, AssetHive, AssetVests from clive.exceptions import CliveError if TYPE_CHECKING: from decimal import Decimal -AssetT = TypeVar("AssetT", bound=AssetHiveHF26 | AssetHbdHF26 | AssetVestsHF26) -AssetExplicitT = TypeVar("AssetExplicitT", AssetHiveHF26, AssetHbdHF26, AssetVestsHF26) +AssetT = TypeVar("AssetT", bound=AssetHive | AssetHbd | AssetVests) +AssetExplicitT = TypeVar("AssetExplicitT", AssetHive, AssetHbd, AssetVests) AssetAmount = DecimalConvertible AssetFactory = Callable[[AssetAmount], AssetT] @@ -58,7 +56,8 @@ class UnknownAssetNaiError(AssetError): super().__init__(message) -class AssetFactoryHolder(CliveBaseModel, GenericModel, Generic[AssetT]): +@dataclass(frozen=True) +class AssetFactoryHolder(Generic[AssetT]): """ Holds factory for asset. @@ -67,17 +66,14 @@ class AssetFactoryHolder(CliveBaseModel, GenericModel, Generic[AssetT]): asset_factory: Factory function to create an instance of the asset. """ - class Config: - frozen = True - asset_cls: type[AssetT] asset_factory: AssetFactory[AssetT] class Asset: - Hive: TypeAlias = AssetHiveHF26 # noqa: UP040 # used in isinstance check - Hbd: TypeAlias = AssetHbdHF26 # noqa: UP040 # used in isinstance check - Vests: TypeAlias = AssetVestsHF26 # noqa: UP040 # used in isinstance check + Hive: TypeAlias = AssetHive # noqa: UP040 # used in isinstance check + Hbd: TypeAlias = AssetHbd # noqa: UP040 # used in isinstance check + Vests: TypeAlias = AssetVests # noqa: UP040 # used in isinstance check LiquidT: TypeAlias = Hive | Hbd # noqa: UP040 # used in isinstance check VotingT: TypeAlias = Hive | Vests # noqa: UP040 # used in isinstance check AnyT: TypeAlias = Hive | Hbd | Vests # noqa: UP040 # used in isinstance check diff --git a/clive/__private/models/schemas.py b/clive/__private/models/schemas.py index 525fd51420..1ef85997aa 100644 --- a/clive/__private/models/schemas.py +++ b/clive/__private/models/schemas.py @@ -55,7 +55,7 @@ from schemas.apis.rc_api import FindRcAccounts as SchemasFindRcAccounts from schemas.apis.rc_api.fundaments_of_responses import RcAccount as SchemasRcAccount from schemas.apis.transaction_status_api import FindTransaction from schemas.decoders import is_matching_model, validate_schema_field -from schemas.fields.assets import AssetHbdHF26, AssetHiveHF26, AssetVestsHF26 +from schemas.fields.assets import AssetHbd, AssetHive, AssetVests from schemas.fields.basic import AccountName, PublicKey from schemas.fields.compound import Authority, Manabar, Price from schemas.fields.compound import HbdExchangeRate as SchemasHbdExchangeRate @@ -211,9 +211,9 @@ __all__ = [ # noqa: RUF022 "RecurrentTransferPairIdExtension", "RecurrentTransferPairIdRepresentation", # assets - "AssetHbdHF26", - "AssetHiveHF26", - "AssetVestsHF26", + "AssetHbd", + "AssetHive", + "AssetVests", # basic fields "AccountName", "ChainId", diff --git a/clive/__private/ui/widgets/currency_selector/currency_selector_base.py b/clive/__private/ui/widgets/currency_selector/currency_selector_base.py index 3336340d44..e176460b28 100644 --- a/clive/__private/ui/widgets/currency_selector/currency_selector_base.py +++ b/clive/__private/ui/widgets/currency_selector/currency_selector_base.py @@ -1,7 +1,6 @@ from __future__ import annotations from abc import abstractmethod -from typing import Generic from clive.__private.abstract_class import AbstractClassMessagePump from clive.__private.models.asset import ( @@ -13,7 +12,7 @@ from clive.__private.models.asset import ( from clive.__private.ui.widgets.clive_basic.clive_select import CliveSelect -class CurrencySelectorBase(CliveSelect[AssetFactoryHolder[AssetT]], Generic[AssetT], AbstractClassMessagePump): +class CurrencySelectorBase(CliveSelect[AssetFactoryHolder[AssetT]], AbstractClassMessagePump): """Base Currency Selector for operations, which require to choose type of Assets.""" def __init__(self) -> None: -- GitLab From e660a7b2ce3a0086fdcddb8d5d8643a42fd3ae34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Radek=20Mas=C5=82owski?= Date: Tue, 5 Aug 2025 05:38:33 +0000 Subject: [PATCH 19/35] Remove unused __modify_schema__ from TransactionStorageModel --- clive/__private/storage/migrations/v0.py | 4 ---- clive/__private/storage/migrations/v1.py | 7 +------ 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/clive/__private/storage/migrations/v0.py b/clive/__private/storage/migrations/v0.py index b5e3776822..992b03e25d 100644 --- a/clive/__private/storage/migrations/v0.py +++ b/clive/__private/storage/migrations/v0.py @@ -100,10 +100,6 @@ class TransactionCoreStorageModel(PreconfiguredBaseModel): extensions: list[Any] = [] # noqa: RUF012 signatures: list[Signature] = [] # noqa: RUF012 - @classmethod - def __modify_schema__(cls, field_schema: dict[str, Any]) -> None: - field_schema.update({"type": "object", "description": "This should not be included in revision calculation"}) - class TransactionStorageModel(PreconfiguredBaseModel): transaction_core: TransactionCoreStorageModel diff --git a/clive/__private/storage/migrations/v1.py b/clive/__private/storage/migrations/v1.py index 7ce8508e30..2c41471831 100644 --- a/clive/__private/storage/migrations/v1.py +++ b/clive/__private/storage/migrations/v1.py @@ -3,15 +3,10 @@ from __future__ import annotations from pathlib import Path # noqa: TC003 from typing import ClassVar, Self, TypeAlias -from clive.__private.models.schemas import PreconfiguredBaseModel -from clive.__private.models.schemas import Transaction as SchemasTransaction +from clive.__private.models.schemas import PreconfiguredBaseModel, Transaction from clive.__private.storage.migrations import v0 -class Transaction(SchemasTransaction): - __modify_schema__ = v0.TransactionCoreStorageModel.__modify_schema__ - - class TransactionStorageModel(PreconfiguredBaseModel): transaction_core: Transaction transaction_file_path: Path | None = None -- GitLab From 5e09abc401a9ec6e924a8a71dc0a8c34466dcc46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Mas=C5=82owski?= Date: Tue, 5 Aug 2025 14:28:23 +0200 Subject: [PATCH 20/35] Use convert_to_representation in required places --- clive/__private/core/ensure_transaction.py | 6 +++--- clive/__private/models/schemas.py | 7 ------- clive/__private/ui/dialogs/raw_json_dialog.py | 6 +++--- .../clive_local_tools/checkers/blockchain_checkers.py | 6 +++--- tests/functional/commands/test_load_transaction.py | 6 ++++-- tests/unit/test_chain_id_in_profile.py | 6 ++++-- 6 files changed, 17 insertions(+), 20 deletions(-) diff --git a/clive/__private/core/ensure_transaction.py b/clive/__private/core/ensure_transaction.py index c45956a1d0..7cf74ea3fe 100644 --- a/clive/__private/core/ensure_transaction.py +++ b/clive/__private/core/ensure_transaction.py @@ -4,7 +4,7 @@ from collections.abc import Iterable from typing import Any from clive.__private.models import Transaction -from clive.__private.models.schemas import OperationBase, OperationUnion +from clive.__private.models.schemas import OperationBase, OperationUnion, convert_to_representation type TransactionConvertibleType = OperationBase | Iterable[OperationBase] | Transaction @@ -34,9 +34,9 @@ def ensure_transaction(content: TransactionConvertibleType) -> Transaction: return content if isinstance(content, OperationBase): - operations = [content] + operations = [convert_to_representation(content)] elif isinstance(content, Iterable): - operations = [__ensure_operation(x) for x in content] + operations = [convert_to_representation(__ensure_operation(x)) for x in content] else: raise TypeError(f"Expected a transaction, operation or iterable of operations, got {type(content)}") diff --git a/clive/__private/models/schemas.py b/clive/__private/models/schemas.py index 1ef85997aa..bad50f06ce 100644 --- a/clive/__private/models/schemas.py +++ b/clive/__private/models/schemas.py @@ -127,7 +127,6 @@ from schemas.transaction import Transaction __all__ = [ # noqa: RUF022 # operation BASIC aliases "OperationBase", - "OperationRepresentationBase", "OperationRepresentationUnion", "OperationUnion", # list API responses (have nested list property which stores actual model) @@ -250,13 +249,11 @@ __all__ = [ # noqa: RUF022 "convert_to_representation", "is_matching_model", "validate_schema_field", - "RepresentationBase", ] # operation BASIC aliases OperationBase = Operation -OperationRepresentationBase = HF26Representation[OperationBase] OperationRepresentationUnion = Hf26OperationRepresentationType OperationUnion = AnyOperation @@ -303,7 +300,3 @@ Witness = WitnessesFundament[AssetHiveHF26, AssetHbdHF26] ExtraFieldsPolicy = ExtraFields MissingFieldsInGetConfigPolicy = MissingFieldsInGetConfig - -# other - -RepresentationBase = HF26Representation diff --git a/clive/__private/ui/dialogs/raw_json_dialog.py b/clive/__private/ui/dialogs/raw_json_dialog.py index d7e1f248ab..213b6d5039 100644 --- a/clive/__private/ui/dialogs/raw_json_dialog.py +++ b/clive/__private/ui/dialogs/raw_json_dialog.py @@ -6,14 +6,14 @@ from typing import TYPE_CHECKING from textual.containers import Center from textual.widgets import Pretty +from clive.__private.models.schemas import convert_to_representation from clive.__private.ui.dialogs.clive_base_dialogs import CliveInfoDialog from clive.__private.ui.widgets.section import Section -from schemas.operations.representations import convert_to_representation if TYPE_CHECKING: from textual.app import ComposeResult - from clive.__private.models.schemas import OperationBase, OperationRepresentationBase + from clive.__private.models.schemas import OperationBase class RawJsonDialog(CliveInfoDialog): @@ -27,5 +27,5 @@ class RawJsonDialog(CliveInfoDialog): @staticmethod def _get_operation_representation_json(operation: OperationBase) -> str: - representation: OperationRepresentationBase = convert_to_representation(operation=operation) + representation = convert_to_representation(operation) return representation.json(order="sorted") diff --git a/tests/clive-local-tools/clive_local_tools/checkers/blockchain_checkers.py b/tests/clive-local-tools/clive_local_tools/checkers/blockchain_checkers.py index fc85e01507..d9802db57b 100644 --- a/tests/clive-local-tools/clive_local_tools/checkers/blockchain_checkers.py +++ b/tests/clive-local-tools/clive_local_tools/checkers/blockchain_checkers.py @@ -7,7 +7,7 @@ import pytest if TYPE_CHECKING: import test_tools as tt - from clive.__private.models.schemas import OperationUnion, RepresentationBase + from clive.__private.models.schemas import OperationUnion from beekeepy.exceptions import ErrorInResponseError from click.testing import Result @@ -47,9 +47,9 @@ def assert_operations_placed_in_blockchain( include_reversible=True, # type: ignore[call-arg] # TODO: id -> id_ after helpy bug fixed ) operations_to_check = list(expected_operations) + for operation_representation in transaction.operations: - _operation_representation: RepresentationBase[OperationUnion] = operation_representation - operation = _operation_representation.value + operation = operation_representation.value if operation in operations_to_check: operations_to_check.remove(operation) diff --git a/tests/functional/commands/test_load_transaction.py b/tests/functional/commands/test_load_transaction.py index 79fe2e443b..de9359c1c1 100644 --- a/tests/functional/commands/test_load_transaction.py +++ b/tests/functional/commands/test_load_transaction.py @@ -7,13 +7,15 @@ import pytest from clive.__private.core import iwax from clive.__private.core.commands.load_transaction import LoadTransaction from clive.__private.models import Asset, Transaction -from clive.__private.models.schemas import TransferOperation +from clive.__private.models.schemas import TransferOperation, convert_to_representation if TYPE_CHECKING: from pathlib import Path VALID_TRANSACTION: Final[Transaction] = Transaction( - operations=[TransferOperation(from_="alice", to="bob", amount=Asset.hbd(1), memo="test")], + operations=[ + convert_to_representation(TransferOperation(from_="alice", to="bob", amount=Asset.hbd(1), memo="test")) + ], ref_block_num=1, ref_block_prefix=2, expiration="2021-01-01T00:00:00", diff --git a/tests/unit/test_chain_id_in_profile.py b/tests/unit/test_chain_id_in_profile.py index 30c9342124..30178f9a97 100644 --- a/tests/unit/test_chain_id_in_profile.py +++ b/tests/unit/test_chain_id_in_profile.py @@ -7,7 +7,7 @@ import pytest from clive.__private.core.constants.setting_identifiers import NODE_CHAIN_ID from clive.__private.core.profile import InvalidChainIdError, Profile from clive.__private.models import Asset, Transaction -from clive.__private.models.schemas import TransferOperation +from clive.__private.models.schemas import TransferOperation, convert_to_representation from clive.__private.settings import safe_settings, settings from clive_local_tools.data.constants import TESTNET_CHAIN_ID from clive_local_tools.data.generates import generate_wallet_name, generate_wallet_password @@ -91,7 +91,9 @@ async def test_chain_id_is_retrieved_from_api_if_not_set( await world.commands.import_key(key_to_import=wallet.private_key) transaction = Transaction( operations=[ - TransferOperation(from_="doesnt-matter", to="null", amount=Asset.hive(1), memo=""), + convert_to_representation( + TransferOperation(from_="doesnt-matter", to="null", amount=Asset.hive(1), memo="") + ), ] ) -- GitLab From a7bb1aa3cd9cf555daa6ce697943d57bf1367355 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Mas=C5=82owski?= Date: Tue, 5 Aug 2025 14:43:32 +0200 Subject: [PATCH 21/35] Fix existing clive imports --- .../ui/widgets/currency_selector/currency_selector.py | 6 ++++-- .../widgets/currency_selector/currency_selector_hive.py | 6 ++++-- .../currency_selector/currency_selector_hp_vests.py | 3 +-- .../currency_selector/currency_selector_liquid.py | 3 +-- .../clive_local_tools/checkers/blockchain_checkers.py | 9 ++++----- 5 files changed, 14 insertions(+), 13 deletions(-) diff --git a/clive/__private/ui/widgets/currency_selector/currency_selector.py b/clive/__private/ui/widgets/currency_selector/currency_selector.py index beca7f63eb..709920b065 100644 --- a/clive/__private/ui/widgets/currency_selector/currency_selector.py +++ b/clive/__private/ui/widgets/currency_selector/currency_selector.py @@ -1,7 +1,9 @@ from __future__ import annotations -from clive.__private.models import Asset -from clive.__private.models.asset import AssetFactoryHolder +from clive.__private.models.asset import ( + Asset, + AssetFactoryHolder, +) from clive.__private.ui.widgets.currency_selector.currency_selector_base import ( CurrencySelectorBase, ) diff --git a/clive/__private/ui/widgets/currency_selector/currency_selector_hive.py b/clive/__private/ui/widgets/currency_selector/currency_selector_hive.py index a98c6e4b9c..e2c362b3f5 100644 --- a/clive/__private/ui/widgets/currency_selector/currency_selector_hive.py +++ b/clive/__private/ui/widgets/currency_selector/currency_selector_hive.py @@ -1,7 +1,9 @@ from __future__ import annotations -from clive.__private.models import Asset -from clive.__private.models.asset import AssetFactoryHolder +from clive.__private.models.asset import ( + Asset, + AssetFactoryHolder, +) from clive.__private.ui.widgets.currency_selector.currency_selector_base import ( CurrencySelectorBase, ) diff --git a/clive/__private/ui/widgets/currency_selector/currency_selector_hp_vests.py b/clive/__private/ui/widgets/currency_selector/currency_selector_hp_vests.py index bbd49f271b..3a8531f951 100644 --- a/clive/__private/ui/widgets/currency_selector/currency_selector_hp_vests.py +++ b/clive/__private/ui/widgets/currency_selector/currency_selector_hp_vests.py @@ -1,7 +1,6 @@ from __future__ import annotations -from clive.__private.models import Asset -from clive.__private.models.asset import AssetFactoryHolder +from clive.__private.models.asset import Asset, AssetFactoryHolder from clive.__private.ui.widgets.currency_selector.currency_selector_base import CurrencySelectorBase diff --git a/clive/__private/ui/widgets/currency_selector/currency_selector_liquid.py b/clive/__private/ui/widgets/currency_selector/currency_selector_liquid.py index b8fa389575..59de3a0f3e 100644 --- a/clive/__private/ui/widgets/currency_selector/currency_selector_liquid.py +++ b/clive/__private/ui/widgets/currency_selector/currency_selector_liquid.py @@ -1,7 +1,6 @@ from __future__ import annotations -from clive.__private.models import Asset -from clive.__private.models.asset import AssetFactoryHolder +from clive.__private.models.asset import Asset, AssetFactoryHolder from clive.__private.ui.widgets.currency_selector.currency_selector_base import ( CurrencySelectorBase, ) diff --git a/tests/clive-local-tools/clive_local_tools/checkers/blockchain_checkers.py b/tests/clive-local-tools/clive_local_tools/checkers/blockchain_checkers.py index d9802db57b..17c46608e0 100644 --- a/tests/clive-local-tools/clive_local_tools/checkers/blockchain_checkers.py +++ b/tests/clive-local-tools/clive_local_tools/checkers/blockchain_checkers.py @@ -3,17 +3,16 @@ from __future__ import annotations from typing import TYPE_CHECKING import pytest +from beekeepy.exceptions import ErrorInResponseError +from click.testing import Result + +from clive_local_tools.helpers import get_transaction_id_from_output if TYPE_CHECKING: import test_tools as tt from clive.__private.models.schemas import OperationUnion -from beekeepy.exceptions import ErrorInResponseError -from click.testing import Result - -from clive_local_tools.helpers import get_transaction_id_from_output - def _ensure_transaction_id(trx_id_or_result: Result | str) -> str: if isinstance(trx_id_or_result, Result): -- GitLab From 0b6fe176bcb923fe3cd61896641444470b8a6646 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Mas=C5=82owski?= Date: Tue, 5 Aug 2025 14:49:23 +0200 Subject: [PATCH 22/35] Explicitly use types from schemas where type error is reported --- clive/__private/cli/commands/process/process_custom_json.py | 4 ++-- .../core/alarms/specific_alarms/govenance_no_active_votes.py | 3 ++- .../alarms/specific_alarms/governance_voting_expiration.py | 3 ++- clive/__private/models/schemas.py | 2 ++ tests/functional/cli/process/test_process_transaction.py | 4 ++-- ...test_perform_working_account_load_in_regular_operations.py | 4 ++-- tests/functional/commands/test_load_transaction.py | 4 ++-- 7 files changed, 14 insertions(+), 10 deletions(-) diff --git a/clive/__private/cli/commands/process/process_custom_json.py b/clive/__private/cli/commands/process/process_custom_json.py index 13fa5d438f..e6655b77c7 100644 --- a/clive/__private/cli/commands/process/process_custom_json.py +++ b/clive/__private/cli/commands/process/process_custom_json.py @@ -7,7 +7,7 @@ from pathlib import Path from clive.__private.cli.commands.abc.operation_command import OperationCommand from clive.__private.cli.exceptions import CLIPrettyError from clive.__private.core.constants.cli import PERFORM_WORKING_ACCOUNT_LOAD -from clive.__private.models.schemas import CustomJsonOperation +from clive.__private.models.schemas import CustomJsonOperation, JsonString from clive.__private.validators.json_validator import JsonValidator @@ -22,7 +22,7 @@ class ProcessCustomJson(OperationCommand): json_ = self.ensure_json_from_json_string_or_path(self.json_or_path) return CustomJsonOperation( id_=self.id_, - json_=json_, + json_=JsonString(json_), required_auths=self.authorize_by_active, required_posting_auths=self.authorize, ) diff --git a/clive/__private/core/alarms/specific_alarms/govenance_no_active_votes.py b/clive/__private/core/alarms/specific_alarms/govenance_no_active_votes.py index 5ecf3a8ae0..550749124e 100644 --- a/clive/__private/core/alarms/specific_alarms/govenance_no_active_votes.py +++ b/clive/__private/core/alarms/specific_alarms/govenance_no_active_votes.py @@ -9,6 +9,7 @@ from clive.__private.core.alarms.alarm_data import AlarmDataNeverExpiresWithoutA from clive.__private.core.alarms.alarm_identifier import DateTimeAlarmIdentifier from clive.__private.core.constants.alarm_descriptions import GOVERNANCE_COMMON_ALARM_DESCRIPTION from clive.__private.core.date_utils import is_null_date +from clive.__private.models.schemas import HiveDateTime if TYPE_CHECKING: from clive.__private.core.commands.data_retrieval.update_alarms_data import AccountAlarmsData @@ -26,7 +27,7 @@ class GovernanceNoActiveVotes(Alarm[DateTimeAlarmIdentifier, GovernanceNoActiveV def update_alarm_status(self, data: AccountAlarmsData) -> None: expiration = data.governance_vote_expiration_ts if is_null_date(expiration): - new_identifier = DateTimeAlarmIdentifier(value=expiration) + new_identifier = DateTimeAlarmIdentifier(value=HiveDateTime(expiration)) self.enable_alarm(new_identifier, GovernanceNoActiveVotesAlarmData(expiration_date=expiration)) return diff --git a/clive/__private/core/alarms/specific_alarms/governance_voting_expiration.py b/clive/__private/core/alarms/specific_alarms/governance_voting_expiration.py index c10d75f195..90b049cc98 100644 --- a/clive/__private/core/alarms/specific_alarms/governance_voting_expiration.py +++ b/clive/__private/core/alarms/specific_alarms/governance_voting_expiration.py @@ -11,6 +11,7 @@ from clive.__private.core.constants.alarm_descriptions import ( GOVERNANCE_VOTING_EXPIRATION_ALARM_DESCRIPTION, ) from clive.__private.core.date_utils import is_null_date +from clive.__private.models.schemas import HiveDateTime if TYPE_CHECKING: from clive.__private.core.commands.data_retrieval.update_alarms_data import AccountAlarmsData @@ -38,7 +39,7 @@ class GovernanceVotingExpiration(Alarm[DateTimeAlarmIdentifier, GovernanceVoting self.disable_alarm() return - new_identifier = DateTimeAlarmIdentifier(value=expiration) + new_identifier = DateTimeAlarmIdentifier(value=HiveDateTime(expiration)) self.enable_alarm(new_identifier, alarm_data) return diff --git a/clive/__private/models/schemas.py b/clive/__private/models/schemas.py index bad50f06ce..1d5d79495b 100644 --- a/clive/__private/models/schemas.py +++ b/clive/__private/models/schemas.py @@ -63,6 +63,7 @@ from schemas.fields.compound import Proposal as SchemasProposal from schemas.fields.hex import Sha256, Signature, TransactionId from schemas.fields.hive_datetime import HiveDateTime from schemas.fields.hive_int import HiveInt +from schemas.fields.resolvables import JsonString from schemas.operation import Operation from schemas.operations import ( AccountCreateOperation, @@ -218,6 +219,7 @@ __all__ = [ # noqa: RUF022 "ChainId", "HiveDateTime", "HiveInt", + "JsonString", "PublicKey", "Signature", "TransactionId", diff --git a/tests/functional/cli/process/test_process_transaction.py b/tests/functional/cli/process/test_process_transaction.py index e90730c2db..3d3f913264 100644 --- a/tests/functional/cli/process/test_process_transaction.py +++ b/tests/functional/cli/process/test_process_transaction.py @@ -6,7 +6,7 @@ import pytest import test_tools as tt from clive.__private.cli.exceptions import CLINoProfileUnlockedError -from clive.__private.models.schemas import CustomJsonOperation +from clive.__private.models.schemas import CustomJsonOperation, JsonString from clive_local_tools.checkers.blockchain_checkers import ( assert_operations_placed_in_blockchain, assert_transaction_in_blockchain, @@ -41,7 +41,7 @@ async def test_load_custom_json_from_file(node: tt.RawNode, cli_tester: CLITeste required_auths=[], required_posting_auths=[WORKING_ACCOUNT_DATA.account.name], id_=ID, - json_=json_, + json_=JsonString(json_), ) # ACT diff --git a/tests/functional/cli/test_perform_working_account_load_in_regular_operations.py b/tests/functional/cli/test_perform_working_account_load_in_regular_operations.py index fbd287dcea..989524e51d 100644 --- a/tests/functional/cli/test_perform_working_account_load_in_regular_operations.py +++ b/tests/functional/cli/test_perform_working_account_load_in_regular_operations.py @@ -6,7 +6,7 @@ import pytest import test_tools as tt from clive.__private.core.keys.keys import PrivateKeyAliased -from clive.__private.models.schemas import CustomJsonOperation, TransferOperation +from clive.__private.models.schemas import CustomJsonOperation, JsonString, TransferOperation from clive_local_tools.checkers.blockchain_checkers import assert_operations_placed_in_blockchain from clive_local_tools.data.constants import ( ALT_WORKING_ACCOUNT2_KEY_ALIAS, @@ -82,7 +82,7 @@ async def test_custom_authority_in_custom_json_operation( required_auths=[], required_posting_auths=[ALT_WORKING_ACCOUNT2_NAME], id_=custom_id, - json_=custom_json, + json_=JsonString(custom_json), ) # ACT diff --git a/tests/functional/commands/test_load_transaction.py b/tests/functional/commands/test_load_transaction.py index de9359c1c1..e7fb68c4a2 100644 --- a/tests/functional/commands/test_load_transaction.py +++ b/tests/functional/commands/test_load_transaction.py @@ -7,7 +7,7 @@ import pytest from clive.__private.core import iwax from clive.__private.core.commands.load_transaction import LoadTransaction from clive.__private.models import Asset, Transaction -from clive.__private.models.schemas import TransferOperation, convert_to_representation +from clive.__private.models.schemas import HiveDateTime, TransferOperation, convert_to_representation if TYPE_CHECKING: from pathlib import Path @@ -18,7 +18,7 @@ VALID_TRANSACTION: Final[Transaction] = Transaction( ], ref_block_num=1, ref_block_prefix=2, - expiration="2021-01-01T00:00:00", + expiration=HiveDateTime("2021-01-01T00:00:00"), extensions=[], signatures=[], ) -- GitLab From bf10db869898f2ce0ec57effd8e35dc461faafa9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Mas=C5=82owski?= Date: Tue, 5 Aug 2025 14:50:53 +0200 Subject: [PATCH 23/35] Prefer imports from models.schemas instead of direct schemas import --- .../common_governance/governance_actions.py | 2 +- tests/functional/cli/process/test_process_proxy.py | 2 +- tests/functional/cli/process/test_process_transfer.py | 2 +- tests/functional/cli/process/test_process_transfer_schedule.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/clive/__private/ui/screens/operations/governance_operations/common_governance/governance_actions.py b/clive/__private/ui/screens/operations/governance_operations/common_governance/governance_actions.py index d1fb4ebf03..5534e27bdb 100644 --- a/clive/__private/ui/screens/operations/governance_operations/common_governance/governance_actions.py +++ b/clive/__private/ui/screens/operations/governance_operations/common_governance/governance_actions.py @@ -17,7 +17,7 @@ if TYPE_CHECKING: from textual.app import ComposeResult from typing_extensions import TypeIs - from schemas.operations import AccountWitnessVoteOperation, UpdateProposalVotesOperation + from clive.__private.models.schemas import AccountWitnessVoteOperation, UpdateProposalVotesOperation class GovernanceActionRow(Horizontal, AbstractClassMessagePump): diff --git a/tests/functional/cli/process/test_process_proxy.py b/tests/functional/cli/process/test_process_proxy.py index 76b546e013..736648c443 100644 --- a/tests/functional/cli/process/test_process_proxy.py +++ b/tests/functional/cli/process/test_process_proxy.py @@ -5,6 +5,7 @@ from typing import TYPE_CHECKING, Final import pytest from clive.__private.core.constants.node import CANCEL_PROXY_VALUE +from clive.__private.models.schemas import AccountWitnessProxyOperation if TYPE_CHECKING: import test_tools as tt @@ -15,7 +16,6 @@ from clive_local_tools.checkers.blockchain_checkers import assert_operations_pla from clive_local_tools.cli.exceptions import CLITestCommandError from clive_local_tools.data.constants import WORKING_ACCOUNT_KEY_ALIAS from clive_local_tools.testnet_block_log.constants import WATCHED_ACCOUNTS_NAMES, WORKING_ACCOUNT_NAME -from schemas.operations import AccountWitnessProxyOperation ACCOUNT_NAME: Final[str] = WORKING_ACCOUNT_NAME PROXY_ACCOUNT_NAME: Final[str] = WATCHED_ACCOUNTS_NAMES[0] diff --git a/tests/functional/cli/process/test_process_transfer.py b/tests/functional/cli/process/test_process_transfer.py index 9c8f9b91bf..c4546d2b1e 100644 --- a/tests/functional/cli/process/test_process_transfer.py +++ b/tests/functional/cli/process/test_process_transfer.py @@ -9,6 +9,7 @@ from clive.__private.cli.exceptions import ( CLINoProfileUnlockedError, CLITransactionNotSignedError, ) +from clive.__private.models.schemas import TransferOperation from clive_local_tools.checkers.blockchain_checkers import ( assert_operations_placed_in_blockchain, assert_transaction_in_blockchain, @@ -16,7 +17,6 @@ from clive_local_tools.checkers.blockchain_checkers import ( from clive_local_tools.cli.exceptions import CLITestCommandError from clive_local_tools.data.constants import WORKING_ACCOUNT_KEY_ALIAS from clive_local_tools.testnet_block_log.constants import WATCHED_ACCOUNTS_DATA, WORKING_ACCOUNT_NAME -from schemas.operations import TransferOperation if TYPE_CHECKING: from clive_local_tools.cli.cli_tester import CLITester diff --git a/tests/functional/cli/process/test_process_transfer_schedule.py b/tests/functional/cli/process/test_process_transfer_schedule.py index a2ffca215a..93f324e263 100644 --- a/tests/functional/cli/process/test_process_transfer_schedule.py +++ b/tests/functional/cli/process/test_process_transfer_schedule.py @@ -10,10 +10,10 @@ import test_tools as tt from clive.__private.cli.common.parsers import scheduled_transfer_frequency_parser from clive.__private.core.constants.node_special_assets import SCHEDULED_TRANSFER_REMOVE_ASSETS from clive.__private.core.date_utils import timedelta_to_int_hours +from clive.__private.models.schemas import RecurrentTransferOperation from clive_local_tools.checkers.blockchain_checkers import assert_operations_placed_in_blockchain from clive_local_tools.data.constants import WORKING_ACCOUNT_KEY_ALIAS from clive_local_tools.testnet_block_log.constants import WATCHED_ACCOUNTS_NAMES, WORKING_ACCOUNT_NAME -from schemas.operations import RecurrentTransferOperation ACCOUNT_NAME: Final[str] = WORKING_ACCOUNT_NAME RECEIVER: Final[str] = WATCHED_ACCOUNTS_NAMES[0] -- GitLab From 31ef918289dc5ea84f0b58656dff1271007b38ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Mas=C5=82owski?= Date: Tue, 5 Aug 2025 14:52:20 +0200 Subject: [PATCH 24/35] Do not use testnet assets in tests --- tests/conftest.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/conftest.py b/tests/conftest.py index 13219d6e9d..354d85c78d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -24,6 +24,10 @@ from clive.__private.core.commands.import_key import ImportKey from clive.__private.core.constants.setting_identifiers import DATA_PATH, LOG_LEVEL_1ST_PARTY, LOG_LEVELS, LOG_PATH from clive.__private.core.world import World from clive.__private.logger import logger +from clive.__private.models.schemas import ( + TestnetAssetsPolicy, + set_policies, +) from clive.__private.settings import safe_settings, settings from clive_local_tools.data.constants import ( BEEKEEPER_REMOTE_ADDRESS_ENV_NAME, @@ -45,6 +49,11 @@ if TYPE_CHECKING: from clive_local_tools.types import EnvContextFactory, GenericEnvContextFactory, SetupWalletsFactory, Wallets +@pytest.fixture(autouse=True) +def _use_testnet_assets() -> None: + set_policies(TestnetAssetsPolicy(use_testnet_assets=False)) + + @pytest.fixture(autouse=True, scope="session") def manage_thread_pool() -> Iterator[None]: with thread_pool: -- GitLab From c53989c699205d3f27cafefe80e3ff907a4b7d20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Mas=C5=82owski?= Date: Tue, 5 Aug 2025 14:57:18 +0200 Subject: [PATCH 25/35] Remove type ignores --- clive/__private/models/transaction.py | 6 +++--- .../operations/governance_operations/proposals/proposals.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/clive/__private/models/transaction.py b/clive/__private/models/transaction.py index d788177fed..5798caaa7c 100644 --- a/clive/__private/models/transaction.py +++ b/clive/__private/models/transaction.py @@ -41,7 +41,7 @@ class Transaction(SchemasTransaction): return operation in self.operations_models return operation in self.operations - def __iter__(self) -> Iterator[OperationUnion]: # type: ignore[override] + def __iter__(self) -> Iterator[OperationUnion]: return iter(self.operations_models) def __len__(self) -> int: @@ -58,7 +58,7 @@ class Transaction(SchemasTransaction): @property def operations_models(self) -> list[OperationUnion]: """Get only the operation models from already stored operations representations.""" - return [op.value for op in self.operations] # type: ignore[attr-defined] + return [op.value for op in self.operations] @validator("operations", pre=True) @classmethod @@ -72,7 +72,7 @@ class Transaction(SchemasTransaction): def remove_operation(self, *operations: OperationUnion) -> None: for op in self.operations: - if op.value in operations: # type: ignore[attr-defined] + if op.value in operations: self.operations.remove(op) return diff --git a/clive/__private/ui/screens/operations/governance_operations/proposals/proposals.py b/clive/__private/ui/screens/operations/governance_operations/proposals/proposals.py index 19d2295a1a..a2fc7ee89f 100644 --- a/clive/__private/ui/screens/operations/governance_operations/proposals/proposals.py +++ b/clive/__private/ui/screens/operations/governance_operations/proposals/proposals.py @@ -270,7 +270,7 @@ class Proposals(GovernanceTabPane): """ def append_operation(operation: UpdateProposalVotesOperation) -> None: - operation.proposal_ids.append(proposal_id) # type: ignore[arg-type] + operation.proposal_ids.append(proposal_id) operation.proposal_ids.sort() # proposal id's must be sorted proposal_id = int(identifier) @@ -302,7 +302,7 @@ class Proposals(GovernanceTabPane): self.app.trigger_profile_watchers() return - operation.proposal_ids.remove(proposal_id) # type: ignore[arg-type] + operation.proposal_ids.remove(proposal_id) self.app.trigger_profile_watchers() def _find_proposal_operation_with_such_id( -- GitLab From 004b4da0c9f4299b66a33ad8eaa154b89d95de76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Mas=C5=82owski?= Date: Tue, 5 Aug 2025 15:07:49 +0200 Subject: [PATCH 26/35] Add assertions when creating RecurrentTransferOperation --- .../cli/commands/process/process_transfer_schedule.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/clive/__private/cli/commands/process/process_transfer_schedule.py b/clive/__private/cli/commands/process/process_transfer_schedule.py index 656ff1415f..c5ce743fc8 100644 --- a/clive/__private/cli/commands/process/process_transfer_schedule.py +++ b/clive/__private/cli/commands/process/process_transfer_schedule.py @@ -124,6 +124,9 @@ class _ProcessTransferScheduleCreateModifyCommon(_ProcessTransferScheduleCommon) raise ProcessTransferScheduleTooLongLifetimeError(requested_lifetime=scheduled_transfer_lifetime) async def _create_operation(self) -> RecurrentTransferOperation: + assert self.repeat is not None, "Value of repeat is None." + assert self.memo is not None, "Value of memo is None." + assert self.amount is not None, "Value of amount is None." return RecurrentTransferOperation( from_=self.from_account, to=self.to, -- GitLab From bbc69ddf25ff7ea927b1b3ee1c590c11c5c5c5ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Mas=C5=82owski?= Date: Tue, 5 Aug 2025 15:03:54 +0200 Subject: [PATCH 27/35] Adjust to schemas interface --- clive/__private/before_launch.py | 4 +- .../process/process_account_update.py | 6 +- .../process/process_transfer_schedule.py | 4 +- .../cli/commands/show/show_witness.py | 2 +- .../data_retrieval/hive_power_data.py | 6 +- .../core/commands/load_transaction.py | 9 ++- clive/__private/core/ensure_transaction.py | 6 +- clive/__private/core/formatters/humanize.py | 3 +- clive/__private/models/asset.py | 2 +- clive/__private/models/schemas.py | 56 +++++++++++-------- clive/__private/models/transaction.py | 18 +++--- clive/__private/settings/_safe_settings.py | 2 +- clive/__private/storage/migrations/v0.py | 7 ++- .../remove_delegation_dialog.py | 2 +- .../remove_withdraw_vesting_route_dialog.py | 2 +- .../bindings/operation_action_bindings.py | 4 +- .../delegate_hive_power.py | 7 +-- .../savings_operations/savings_operations.py | 4 +- .../inputs/account_name_pattern_input.py | 2 +- 19 files changed, 73 insertions(+), 73 deletions(-) diff --git a/clive/__private/before_launch.py b/clive/__private/before_launch.py index ff25d84964..2b24c51007 100644 --- a/clive/__private/before_launch.py +++ b/clive/__private/before_launch.py @@ -3,8 +3,6 @@ from __future__ import annotations import shutil from pathlib import Path -from pydantic import Extra - from clive.__private.core.constants.env import ROOT_DIRECTORY from clive.__private.logger import logger from clive.__private.models.schemas import ExtraFieldsPolicy, MissingFieldsInGetConfigPolicy, set_policies @@ -13,7 +11,7 @@ from clive.dev import is_in_dev_mode def _disable_schemas_extra_fields_check() -> None: - set_policies(ExtraFieldsPolicy(policy=Extra.allow), MissingFieldsInGetConfigPolicy(allow=True)) + set_policies(ExtraFieldsPolicy(allow=True), MissingFieldsInGetConfigPolicy(allow=True)) def _create_clive_data_directory() -> None: diff --git a/clive/__private/cli/commands/process/process_account_update.py b/clive/__private/cli/commands/process/process_account_update.py index bffb7d4eeb..6e5887977c 100644 --- a/clive/__private/cli/commands/process/process_account_update.py +++ b/clive/__private/cli/commands/process/process_account_update.py @@ -118,7 +118,7 @@ def is_on_auths_list[T: (AccountName, PublicKey)](authority_entry: T, authoritie def add_account(auth: Authority, account: str, weight: int) -> Authority: - if is_on_auths_list(AccountName(account), auth.account_auths): + if is_on_auths_list(account, auth.account_auths): raise CLIPrettyError(f"Account {account} is current account authority") account_weight_tuple = (AccountName(account), HiveInt(weight)) auth.account_auths.append(account_weight_tuple) @@ -126,7 +126,7 @@ def add_account(auth: Authority, account: str, weight: int) -> Authority: def add_key(auth: Authority, key: str, weight: int) -> Authority: - if is_on_auths_list(PublicKey(key), auth.key_auths): + if is_on_auths_list(key, auth.key_auths): raise CLIPrettyError(f"Key {key} is current key authority") key_weight_tuple = (PublicKey(key), HiveInt(weight)) auth.key_auths.append(key_weight_tuple) @@ -134,7 +134,7 @@ def add_key(auth: Authority, key: str, weight: int) -> Authority: def remove_account(auth: Authority, account: str) -> Authority: - if not is_on_auths_list(AccountName(account), auth.account_auths): + if not is_on_auths_list(account, auth.account_auths): raise CLIPrettyError(f"Account {account} is not current account authority") auth.account_auths = [ account_weight_tuple for account_weight_tuple in auth.account_auths if account_weight_tuple[0] != account diff --git a/clive/__private/cli/commands/process/process_transfer_schedule.py b/clive/__private/cli/commands/process/process_transfer_schedule.py index c5ce743fc8..af82126378 100644 --- a/clive/__private/cli/commands/process/process_transfer_schedule.py +++ b/clive/__private/cli/commands/process/process_transfer_schedule.py @@ -59,9 +59,7 @@ class _ProcessTransferScheduleCommon(OperationCommand, ABC): return [] recurrent_transfer_extension = RecurrentTransferPairIdExtension(pair_id=self.pair_id) - extension = RecurrentTransferPairIdRepresentation( - type=recurrent_transfer_extension.get_name(), value=recurrent_transfer_extension - ) + extension = RecurrentTransferPairIdRepresentation(value=recurrent_transfer_extension) return [extension.dict()] def _identity_check(self, scheduled_transfer: ScheduledTransfer) -> bool: diff --git a/clive/__private/cli/commands/show/show_witness.py b/clive/__private/cli/commands/show/show_witness.py index 7021f02c69..e57abb4a17 100644 --- a/clive/__private/cli/commands/show/show_witness.py +++ b/clive/__private/cli/commands/show/show_witness.py @@ -33,7 +33,7 @@ class ShowWitness(WorldBasedCommand): hbd_savings_apr: str | None = None if witness.props.hbd_interest_rate: hbd_savings_apr = humanize_hbd_savings_apr(hive_percent_to_percent(witness.props.hbd_interest_rate)) - props_as_legacy = witness.props.copy(exclude={"account_creation_fee", "hbd_interest_rate"}, deep=True) + props_as_legacy = witness.props.copy(exclude={"account_creation_fee", "hbd_interest_rate"}) table = Table(title=f"Details of `{self.name}` witness", show_header=False) diff --git a/clive/__private/core/commands/data_retrieval/hive_power_data.py b/clive/__private/core/commands/data_retrieval/hive_power_data.py index f08a881fee..09014c44b3 100644 --- a/clive/__private/core/commands/data_retrieval/hive_power_data.py +++ b/clive/__private/core/commands/data_retrieval/hive_power_data.py @@ -41,7 +41,7 @@ class SanitizedData: gdpo: DynamicGlobalProperties core_account: Account withdraw_routes: list[WithdrawRoute] - delegations: list[VestingDelegation[Asset.Vests]] + delegations: list[VestingDelegation] @dataclass @@ -52,7 +52,7 @@ class HivePowerData: delegated_balance: HpVestsBalance next_vesting_withdrawal: datetime withdraw_routes: list[WithdrawRoute] - delegations: list[VestingDelegation[Asset.Vests]] + delegations: list[VestingDelegation] to_withdraw: HpVestsBalance withdrawn: HpVestsBalance remaining: HpVestsBalance @@ -143,6 +143,6 @@ class HivePowerDataRetrieval(CommandDataRetrieval[HarvestedDataRaw, SanitizedDat assert data is not None, "ListWithdrawVestingRoutes data is missing" return data.routes - def _assert_delegations(self, data: FindVestingDelegations | None) -> list[VestingDelegation[Asset.Vests]]: + def _assert_delegations(self, data: FindVestingDelegations | None) -> list[VestingDelegation]: assert data is not None, "FindVestingDelegations data is missing" return data.delegations diff --git a/clive/__private/core/commands/load_transaction.py b/clive/__private/core/commands/load_transaction.py index 3f549cc060..44d1940ca1 100644 --- a/clive/__private/core/commands/load_transaction.py +++ b/clive/__private/core/commands/load_transaction.py @@ -5,13 +5,12 @@ from dataclasses import dataclass from json import JSONDecodeError from typing import TYPE_CHECKING -from pydantic import ValidationError - from clive.__private.core import iwax from clive.__private.core.commands.abc.command import CommandError from clive.__private.core.commands.abc.command_with_result import CommandWithResult from clive.__private.core.iwax import WaxOperationFailedError from clive.__private.models import Transaction +from clive.__private.models.schemas import DecodeError if TYPE_CHECKING: from pathlib import Path @@ -26,11 +25,11 @@ class LoadTransaction(CommandWithResult[Transaction]): file_path: Path async def _execute(self) -> None: - with contextlib.suppress(JSONDecodeError, ValidationError): - self._result = Transaction.parse_file(self.file_path) + with contextlib.suppress(JSONDecodeError, DecodeError): + self._result = Transaction.parse_file(path=self.file_path) return - with contextlib.suppress(WaxOperationFailedError, JSONDecodeError, ValidationError): + with contextlib.suppress(WaxOperationFailedError, JSONDecodeError, DecodeError): self._result = iwax.deserialize_transaction(self.file_path.read_bytes()) return diff --git a/clive/__private/core/ensure_transaction.py b/clive/__private/core/ensure_transaction.py index 7cf74ea3fe..fa675b36d3 100644 --- a/clive/__private/core/ensure_transaction.py +++ b/clive/__private/core/ensure_transaction.py @@ -4,7 +4,7 @@ from collections.abc import Iterable from typing import Any from clive.__private.models import Transaction -from clive.__private.models.schemas import OperationBase, OperationUnion, convert_to_representation +from clive.__private.models.schemas import OperationBase, convert_to_representation type TransactionConvertibleType = OperationBase | Iterable[OperationBase] | Transaction @@ -26,9 +26,9 @@ def ensure_transaction(content: TransactionConvertibleType) -> Transaction: The transaction. """ - def __ensure_operation(item: Any) -> OperationUnion: # noqa: ANN401 + def __ensure_operation(item: Any) -> OperationBase: # noqa: ANN401 assert isinstance(item, OperationBase) - return item # type: ignore[return-value] + return item if isinstance(content, Transaction): return content diff --git a/clive/__private/core/formatters/humanize.py b/clive/__private/core/formatters/humanize.py index 70b99573d7..61941f62c2 100644 --- a/clive/__private/core/formatters/humanize.py +++ b/clive/__private/core/formatters/humanize.py @@ -285,7 +285,8 @@ def humanize_operation_details(operation: OperationBase) -> str: """ out = "" - operation_dict = dict(operation._iter()) + operation_dict = operation.dict() + for key, value in operation_dict.items(): value_ = value diff --git a/clive/__private/models/asset.py b/clive/__private/models/asset.py index f68a757a3a..56ec6326c1 100644 --- a/clive/__private/models/asset.py +++ b/clive/__private/models/asset.py @@ -196,7 +196,7 @@ class Asset: @classmethod def pretty_amount(cls, asset: Asset.AnyT) -> str: - return f"{int(asset.amount) / 10**asset.precision:.{asset.precision}f}" + return f"{int(asset.amount) / 10 ** asset.precision():.{asset.precision()}f}" @classmethod def as_decimal(cls, asset: Asset.AnyT) -> Decimal: diff --git a/clive/__private/models/schemas.py b/clive/__private/models/schemas.py index 1d5d79495b..b33f119082 100644 --- a/clive/__private/models/schemas.py +++ b/clive/__private/models/schemas.py @@ -54,7 +54,9 @@ from schemas.apis.database_api.fundaments_of_reponses import ( from schemas.apis.rc_api import FindRcAccounts as SchemasFindRcAccounts from schemas.apis.rc_api.fundaments_of_responses import RcAccount as SchemasRcAccount from schemas.apis.transaction_status_api import FindTransaction +from schemas.base import field from schemas.decoders import is_matching_model, validate_schema_field +from schemas.errors import DecodeError, ValidationError from schemas.fields.assets import AssetHbd, AssetHive, AssetVests from schemas.fields.basic import AccountName, PublicKey from schemas.fields.compound import Authority, Manabar, Price @@ -72,7 +74,6 @@ from schemas.operations import ( AccountUpdateOperation, AccountWitnessProxyOperation, AccountWitnessVoteOperation, - AnyOperation, CancelTransferFromSavingsOperation, ChangeRecoveryAccountOperation, ClaimAccountOperation, @@ -94,6 +95,8 @@ from schemas.operations import ( EscrowReleaseOperation, EscrowTransferOperation, FeedPublishOperation, + Hf26OperationRepresentation, + Hf26Operations, LimitOrderCancelOperation, LimitOrderCreate2Operation, LimitOrderCreateOperation, @@ -116,13 +119,19 @@ from schemas.operations import ( WitnessBlockApproveOperation, WitnessSetPropertiesOperation, WitnessUpdateOperation, + convert_to_representation, ) from schemas.operations.extensions.recurrent_transfer_extensions import RecurrentTransferPairId +from schemas.operations.extensions.representation_types import ( + HF26RepresentationRecurrentTransferPairIdOperationExtension, +) from schemas.operations.recurrent_transfer_operation import RecurrentTransferOperation -from schemas.operations.representation_types import Hf26OperationRepresentationType -from schemas.operations.representations import convert_to_representation -from schemas.operations.representations.hf26_representation import HF26Representation -from schemas.policies import ExtraFields, MissingFieldsInGetConfig, Policy, set_policies +from schemas.policies import ( + ExtraFieldsPolicy, + MissingFieldsInGetConfigPolicy, + TestnetAssetsPolicy, + set_policies, +) from schemas.transaction import Transaction __all__ = [ # noqa: RUF022 @@ -244,24 +253,28 @@ __all__ = [ # noqa: RUF022 # policies "ExtraFieldsPolicy", "MissingFieldsInGetConfigPolicy", - "Policy", + "TestnetAssetsPolicy", "set_policies", + # exceptions + "DecodeError", + "ValidationError", # other "PreconfiguredBaseModel", "convert_to_representation", "is_matching_model", "validate_schema_field", + "field", ] # operation BASIC aliases OperationBase = Operation -OperationRepresentationUnion = Hf26OperationRepresentationType -OperationUnion = AnyOperation +OperationRepresentationUnion = Hf26OperationRepresentation +OperationUnion = Hf26Operations # find API response aliases (have nested list property which stores actual model) -FindRcAccounts = SchemasFindRcAccounts[AssetVestsHF26] +FindRcAccounts = SchemasFindRcAccounts # get API responses (have no unnecessary nested properties, just the model itself) @@ -275,7 +288,7 @@ WitnessSchedule = GetWitnessSchedule # extensions RecurrentTransferPairIdExtension = RecurrentTransferPairId -RecurrentTransferPairIdRepresentation = HF26Representation[RecurrentTransferPairIdExtension] +RecurrentTransferPairIdRepresentation = HF26RepresentationRecurrentTransferPairIdOperationExtension # basic fields @@ -283,22 +296,17 @@ ChainId = Sha256 # compound models -Account = AccountItemFundament[AssetHiveHF26, AssetHbdHF26, AssetVestsHF26] +Account = AccountItemFundament ChangeRecoveryAccountRequest = ListChangeRecoveryAccountRequestsFundament DeclineVotingRightsRequest = ListDeclineVotingRightsRequestsFundament -HbdExchangeRate = SchemasHbdExchangeRate[AssetHiveHF26, AssetHbdHF26] -PriceFeed = Price[AssetHiveHF26, AssetHbdHF26, AssetVestsHF26] -Proposal = SchemasProposal[AssetHbdHF26] -RcAccount = SchemasRcAccount[AssetVestsHF26] -RecurrentTransfer = FindRecurrentTransfersFundament[AssetHiveHF26, AssetHbdHF26] -SavingsWithdrawal = SavingsWithdrawalsFundament[AssetHiveHF26, AssetHbdHF26] +HbdExchangeRate = SchemasHbdExchangeRate +PriceFeed = Price +Proposal = SchemasProposal +RcAccount = SchemasRcAccount +RecurrentTransfer = FindRecurrentTransfersFundament +SavingsWithdrawal = SavingsWithdrawalsFundament TransactionStatus = FindTransaction VestingDelegation = VestingDelegationsFundament -VestingDelegationExpiration = VestingDelegationExpirationsFundament[AssetVestsHF26] +VestingDelegationExpiration = VestingDelegationExpirationsFundament WithdrawRoute = WithdrawVestingRoutesFundament -Witness = WitnessesFundament[AssetHiveHF26, AssetHbdHF26] - -# policies - -ExtraFieldsPolicy = ExtraFields -MissingFieldsInGetConfigPolicy = MissingFieldsInGetConfig +Witness = WitnessesFundament diff --git a/clive/__private/models/transaction.py b/clive/__private/models/transaction.py index 5798caaa7c..9744da3f42 100644 --- a/clive/__private/models/transaction.py +++ b/clive/__private/models/transaction.py @@ -4,8 +4,6 @@ from collections.abc import Iterable from datetime import timedelta from typing import TYPE_CHECKING, Any -from pydantic import Field, validator - from clive.__private.models.schemas import ( HiveDateTime, HiveInt, @@ -14,6 +12,7 @@ from clive.__private.models.schemas import ( Signature, TransactionId, convert_to_representation, + field, ) from clive.__private.models.schemas import Transaction as SchemasTransaction @@ -25,18 +24,18 @@ if TYPE_CHECKING: class Transaction(SchemasTransaction): - operations: list[OperationRepresentationUnion] = Field(default_factory=list) - ref_block_num: HiveInt = Field(default_factory=lambda: HiveInt(-1)) - ref_block_prefix: HiveInt = Field(default_factory=lambda: HiveInt(-1)) - expiration: HiveDateTime = Field(default_factory=lambda: HiveDateTime.now() + timedelta(minutes=30)) - extensions: list[Any] = Field(default_factory=list) - signatures: list[Signature] = Field(default_factory=list) + operations: list[OperationRepresentationUnion] = [] # noqa: RUF012 + ref_block_num: HiveInt = -1 + ref_block_prefix: HiveInt = -1 + expiration: HiveDateTime = field(default_factory=lambda: HiveDateTime.now() + timedelta(minutes=30)) + extensions: list[Any] = [] # noqa: RUF012 + signatures: list[Signature] = [] # noqa: RUF012 def __bool__(self) -> bool: """Return True when there are any operations.""" return bool(self.operations) - def __contains__(self, operation: OperationRepresentationUnion | OperationUnion) -> bool: + def __contains__(self, operation: OperationRepresentationUnion | OperationUnion) -> bool: # type: ignore[override] if isinstance(operation, OperationUnion): return operation in self.operations_models return operation in self.operations @@ -60,7 +59,6 @@ class Transaction(SchemasTransaction): """Get only the operation models from already stored operations representations.""" return [op.value for op in self.operations] - @validator("operations", pre=True) @classmethod def convert_operations(cls, value: Any) -> list[OperationRepresentationUnion]: # noqa: ANN401 assert isinstance(value, Iterable) diff --git a/clive/__private/settings/_safe_settings.py b/clive/__private/settings/_safe_settings.py index 0b2290908e..7612bed4ce 100644 --- a/clive/__private/settings/_safe_settings.py +++ b/clive/__private/settings/_safe_settings.py @@ -335,7 +335,7 @@ class SafeSettings: value_ = cast("str", value) if not is_matching_model(value_, ChainId): - details = f"Chain ID should be {ChainId.max_length} characters long." + details = f"Chain ID should be {ChainId.meta().max_length} characters long." # type: ignore[attr-defined] raise SettingsValueError(setting_name=setting_name, value=value_, details=details) return value_ diff --git a/clive/__private/storage/migrations/v0.py b/clive/__private/storage/migrations/v0.py index 992b03e25d..870435cbab 100644 --- a/clive/__private/storage/migrations/v0.py +++ b/clive/__private/storage/migrations/v0.py @@ -18,6 +18,7 @@ from clive.__private.models.schemas import ( OperationRepresentationUnion, PreconfiguredBaseModel, Signature, + field, ) from clive.__private.storage.migrations.base import ProfileStorageBase @@ -94,9 +95,9 @@ class KeyAliasStorageModel(PreconfiguredBaseModel): class TransactionCoreStorageModel(PreconfiguredBaseModel): operations: list[OperationRepresentationUnion] = [] # noqa: RUF012 - ref_block_num: HiveInt = HiveInt(-1) - ref_block_prefix: HiveInt = HiveInt(-1) - expiration: HiveDateTime = utc_epoch() # type: ignore[assignment] + ref_block_num: HiveInt = -1 + ref_block_prefix: HiveInt = -1 + expiration: HiveDateTime = field(default_factory=lambda: HiveDateTime(utc_epoch())) extensions: list[Any] = [] # noqa: RUF012 signatures: list[Signature] = [] # noqa: RUF012 diff --git a/clive/__private/ui/dialogs/operation_summary/remove_delegation_dialog.py b/clive/__private/ui/dialogs/operation_summary/remove_delegation_dialog.py index c34700eec1..f1e7fb8d4d 100644 --- a/clive/__private/ui/dialogs/operation_summary/remove_delegation_dialog.py +++ b/clive/__private/ui/dialogs/operation_summary/remove_delegation_dialog.py @@ -23,7 +23,7 @@ class RemoveDelegationDialog(OperationSummaryBaseDialog): pretty_hp_amount: The formatted amount of HP being removed. """ - def __init__(self, delegation: VestingDelegation[Asset.Vests], pretty_hp_amount: str) -> None: + def __init__(self, delegation: VestingDelegation, pretty_hp_amount: str) -> None: super().__init__("Remove delegation") self._delegation = delegation self._pretty_hp_amount = pretty_hp_amount diff --git a/clive/__private/ui/dialogs/operation_summary/remove_withdraw_vesting_route_dialog.py b/clive/__private/ui/dialogs/operation_summary/remove_withdraw_vesting_route_dialog.py index f94797829c..9ee4a20fbf 100644 --- a/clive/__private/ui/dialogs/operation_summary/remove_withdraw_vesting_route_dialog.py +++ b/clive/__private/ui/dialogs/operation_summary/remove_withdraw_vesting_route_dialog.py @@ -42,5 +42,5 @@ class RemoveWithdrawVestingRouteDialog(OperationSummaryBaseDialog): from_account=self.working_account_name, to_account=self._withdraw_route.to_account, auto_vest=self._withdraw_route.auto_vest, - percent=PERCENT_TO_REMOVE_WITHDRAW_ROUTE, + percent=int(PERCENT_TO_REMOVE_WITHDRAW_ROUTE), ) diff --git a/clive/__private/ui/screens/operations/bindings/operation_action_bindings.py b/clive/__private/ui/screens/operations/bindings/operation_action_bindings.py index af1b77771a..9c15b2496b 100644 --- a/clive/__private/ui/screens/operations/bindings/operation_action_bindings.py +++ b/clive/__private/ui/screens/operations/bindings/operation_action_bindings.py @@ -3,12 +3,12 @@ from __future__ import annotations import contextlib from typing import TYPE_CHECKING, Any, ClassVar, Final -from pydantic import ValidationError from textual import on from textual.css.query import NoMatches from clive.__private.abstract_class import AbstractClassMessagePump from clive.__private.core import iwax +from clive.__private.models.schemas import ValidationError from clive.__private.ui.bindings import CLIVE_PREDEFINED_BINDINGS from clive.__private.ui.clive_widget import CliveWidget from clive.__private.ui.dialogs.confirm_action_dialog_with_known_exchange import ConfirmActionDialogWithKnownExchange @@ -65,7 +65,7 @@ class OperationActionBindings(CliveWidget, AbstractClassMessagePump): """ Validate operations from callback result. If any of them is invalid, notifies the user and returns None. - First it checks for any unhandled ValidationError (which may lead to app crash) from pydantic + First it checks for any unhandled ValidationError (which may lead to app crash) from schemas and then performs a wax validation. Args: diff --git a/clive/__private/ui/screens/operations/hive_power_management/delegate_hive_power/delegate_hive_power.py b/clive/__private/ui/screens/operations/hive_power_management/delegate_hive_power/delegate_hive_power.py index 1448e91a54..de4629d571 100644 --- a/clive/__private/ui/screens/operations/hive_power_management/delegate_hive_power/delegate_hive_power.py +++ b/clive/__private/ui/screens/operations/hive_power_management/delegate_hive_power/delegate_hive_power.py @@ -32,7 +32,6 @@ if TYPE_CHECKING: from textual.app import ComposeResult from clive.__private.core.commands.data_retrieval.hive_power_data import HivePowerData - from clive.__private.models import Asset from clive.__private.models.schemas import VestingDelegation @@ -54,9 +53,7 @@ class Delegation(CliveCheckerboardTableRow): aligned_vests_amount: aligned amount of vests to dots. """ - def __init__( - self, delegation: VestingDelegation[Asset.Vests], aligned_hp_amount: str, aligned_vests_amount: str - ) -> None: + def __init__(self, delegation: VestingDelegation, aligned_hp_amount: str, aligned_vests_amount: str) -> None: self._aligned_hp_amount = aligned_hp_amount super().__init__( @@ -80,7 +77,7 @@ class DelegationsTable(CliveCheckerboardTable): def __init__(self) -> None: super().__init__(header=DelegationsTableHeader(), title="Current delegations", init_dynamic=False) - self._previous_delegations: list[VestingDelegation[Asset.Vests]] | NotUpdatedYet = NotUpdatedYet() + self._previous_delegations: list[VestingDelegation] | NotUpdatedYet = NotUpdatedYet() def create_dynamic_rows(self, content: HivePowerData) -> list[Delegation]: aligned_hp, aligned_vests = content.get_delegations_aligned_amounts() diff --git a/clive/__private/ui/screens/operations/savings_operations/savings_operations.py b/clive/__private/ui/screens/operations/savings_operations/savings_operations.py index 27407d028e..f29af92106 100644 --- a/clive/__private/ui/screens/operations/savings_operations/savings_operations.py +++ b/clive/__private/ui/screens/operations/savings_operations/savings_operations.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Final, Literal +from typing import TYPE_CHECKING, Any, Final, Literal from textual import on from textual.containers import Grid, Horizontal @@ -256,7 +256,7 @@ class SavingsTransfers(TabPane, OperationActionBindings): if not CliveValidatedInput.validate_many(self._to_account_input, self._amount_input, self._memo_input): return None - data = { + data: dict[str, Any] = { "from_": self.default_receiver, "to": self._to_account_input.value_or_error, "amount": self._amount_input.value_or_error, diff --git a/clive/__private/ui/widgets/inputs/account_name_pattern_input.py b/clive/__private/ui/widgets/inputs/account_name_pattern_input.py index ac1b9ca64e..2028b017b2 100644 --- a/clive/__private/ui/widgets/inputs/account_name_pattern_input.py +++ b/clive/__private/ui/widgets/inputs/account_name_pattern_input.py @@ -45,7 +45,7 @@ class AccountNamePatternInput(TextInput): required=required, suggester=suggester, validators=[ - Length(minimum=1, maximum=AccountName.max_length), + Length(minimum=1, maximum=AccountName.meta().max_length), # type: ignore[attr-defined] ], validate_on=validate_on, valid_empty=valid_empty, -- GitLab From d53dacb8d3f2e6cd8ba4739aa3c6ec13cd534391 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Radek=20Mas=C5=82owski?= Date: Thu, 7 Aug 2025 06:49:55 +0000 Subject: [PATCH 28/35] Use Unit16t and Uint32t instead HiveInt in ref_block_num and ref_block_prefix --- clive/__private/models/schemas.py | 3 +++ clive/__private/models/transaction.py | 11 ++++++----- clive/__private/storage/migrations/v0.py | 7 ++++--- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/clive/__private/models/schemas.py b/clive/__private/models/schemas.py index b33f119082..221b4c6d32 100644 --- a/clive/__private/models/schemas.py +++ b/clive/__private/models/schemas.py @@ -65,6 +65,7 @@ from schemas.fields.compound import Proposal as SchemasProposal from schemas.fields.hex import Sha256, Signature, TransactionId from schemas.fields.hive_datetime import HiveDateTime from schemas.fields.hive_int import HiveInt +from schemas.fields.integers import Uint16t, Uint32t from schemas.fields.resolvables import JsonString from schemas.operation import Operation from schemas.operations import ( @@ -232,6 +233,8 @@ __all__ = [ # noqa: RUF022 "PublicKey", "Signature", "TransactionId", + "Uint16t", + "Uint32t", # compound models "Account", "Authority", diff --git a/clive/__private/models/transaction.py b/clive/__private/models/transaction.py index 9744da3f42..d252575c32 100644 --- a/clive/__private/models/transaction.py +++ b/clive/__private/models/transaction.py @@ -6,11 +6,12 @@ from typing import TYPE_CHECKING, Any from clive.__private.models.schemas import ( HiveDateTime, - HiveInt, OperationRepresentationUnion, OperationUnion, Signature, TransactionId, + Uint16t, + Uint32t, convert_to_representation, field, ) @@ -25,8 +26,8 @@ if TYPE_CHECKING: class Transaction(SchemasTransaction): operations: list[OperationRepresentationUnion] = [] # noqa: RUF012 - ref_block_num: HiveInt = -1 - ref_block_prefix: HiveInt = -1 + ref_block_num: Uint16t = -1 + ref_block_prefix: Uint32t = -1 expiration: HiveDateTime = field(default_factory=lambda: HiveDateTime.now() + timedelta(minutes=30)) extensions: list[Any] = [] # noqa: RUF012 signatures: list[Signature] = [] # noqa: RUF012 @@ -81,8 +82,8 @@ class Transaction(SchemasTransaction): def reset(self) -> None: self.operations = [] - self.ref_block_num = HiveInt(-1) - self.ref_block_prefix = HiveInt(-1) + self.ref_block_num = Uint16t(-1) + self.ref_block_prefix = Uint32t(-1) self.expiration = HiveDateTime.now() + timedelta(minutes=30) self.extensions = [] self.signatures = [] diff --git a/clive/__private/storage/migrations/v0.py b/clive/__private/storage/migrations/v0.py index 870435cbab..738d966e34 100644 --- a/clive/__private/storage/migrations/v0.py +++ b/clive/__private/storage/migrations/v0.py @@ -14,10 +14,11 @@ from clive.__private.core.alarms.specific_alarms import ( from clive.__private.core.date_utils import utc_epoch from clive.__private.models.schemas import ( HiveDateTime, - HiveInt, OperationRepresentationUnion, PreconfiguredBaseModel, Signature, + Uint16t, + Uint32t, field, ) from clive.__private.storage.migrations.base import ProfileStorageBase @@ -95,8 +96,8 @@ class KeyAliasStorageModel(PreconfiguredBaseModel): class TransactionCoreStorageModel(PreconfiguredBaseModel): operations: list[OperationRepresentationUnion] = [] # noqa: RUF012 - ref_block_num: HiveInt = -1 - ref_block_prefix: HiveInt = -1 + ref_block_num: Uint16t = -1 + ref_block_prefix: Uint32t = -1 expiration: HiveDateTime = field(default_factory=lambda: HiveDateTime(utc_epoch())) extensions: list[Any] = [] # noqa: RUF012 signatures: list[Signature] = [] # noqa: RUF012 -- GitLab From 33fffa9730d94c3aa5a0ca16d27dbd5ae54abbc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20=C5=BBebrak?= Date: Wed, 6 Aug 2025 13:31:35 +0200 Subject: [PATCH 29/35] Use 0 instead of -1 for default values of Transaction tapos metadata --- clive/__private/models/transaction.py | 8 ++++---- clive/__private/storage/migrations/v0.py | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/clive/__private/models/transaction.py b/clive/__private/models/transaction.py index d252575c32..596f28e30d 100644 --- a/clive/__private/models/transaction.py +++ b/clive/__private/models/transaction.py @@ -26,8 +26,8 @@ if TYPE_CHECKING: class Transaction(SchemasTransaction): operations: list[OperationRepresentationUnion] = [] # noqa: RUF012 - ref_block_num: Uint16t = -1 - ref_block_prefix: Uint32t = -1 + ref_block_num: Uint16t = 0 + ref_block_prefix: Uint32t = 0 expiration: HiveDateTime = field(default_factory=lambda: HiveDateTime.now() + timedelta(minutes=30)) extensions: list[Any] = [] # noqa: RUF012 signatures: list[Signature] = [] # noqa: RUF012 @@ -82,8 +82,8 @@ class Transaction(SchemasTransaction): def reset(self) -> None: self.operations = [] - self.ref_block_num = Uint16t(-1) - self.ref_block_prefix = Uint32t(-1) + self.ref_block_num = Uint16t(0) + self.ref_block_prefix = Uint32t(0) self.expiration = HiveDateTime.now() + timedelta(minutes=30) self.extensions = [] self.signatures = [] diff --git a/clive/__private/storage/migrations/v0.py b/clive/__private/storage/migrations/v0.py index 738d966e34..4b2bbfb74f 100644 --- a/clive/__private/storage/migrations/v0.py +++ b/clive/__private/storage/migrations/v0.py @@ -96,8 +96,8 @@ class KeyAliasStorageModel(PreconfiguredBaseModel): class TransactionCoreStorageModel(PreconfiguredBaseModel): operations: list[OperationRepresentationUnion] = [] # noqa: RUF012 - ref_block_num: Uint16t = -1 - ref_block_prefix: Uint32t = -1 + ref_block_num: Uint16t = 0 + ref_block_prefix: Uint32t = 0 expiration: HiveDateTime = field(default_factory=lambda: HiveDateTime(utc_epoch())) extensions: list[Any] = [] # noqa: RUF012 signatures: list[Signature] = [] # noqa: RUF012 -- GitLab From cad62fe7fc92415c068360d31d72dc85122c67e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Radek=20Mas=C5=82owski?= Date: Thu, 7 Aug 2025 07:32:54 +0000 Subject: [PATCH 30/35] Regenerate profiles after ref_block_num and ref_block_prefix modifications --- .../storage_migration/with_alarms/data/mary/v0.profile | 2 +- .../storage_migration/with_operations/data/mary/v0.profile | 2 +- .../without_alarms_and_operations/data/mary/v0.profile | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/clive-local-tools/clive_local_tools/storage_migration/with_alarms/data/mary/v0.profile b/tests/clive-local-tools/clive_local_tools/storage_migration/with_alarms/data/mary/v0.profile index 0018bd6bfe..8060c7270d 100644 --- a/tests/clive-local-tools/clive_local_tools/storage_migration/with_alarms/data/mary/v0.profile +++ b/tests/clive-local-tools/clive_local_tools/storage_migration/with_alarms/data/mary/v0.profile @@ -1 +1 @@ -111111113WgftMA1rjZc7TSisRcCGYRGfJH1fscuDCJ9oh9jGswAPYSaqPJCCwhnaLbom1HBq6Ut9BGNsJhUZgKwRYDPUcvRLVrhPHDk1fFHJbH7pyTsxsDvfUD3oF7RRHSFZJXhEkvFHbyySafkmcRY7u6yngoS9fxQgx8cfaipLcHGkt8s3zMstieUgg7LiCzpgknpj4fmDS7hCKLapxzVpnFtnuJnD9ebAJpLRPHw3FdyemZEcT769k1dngfjNvQB45HhZBGPethyNc8336yuDshv3ubSu13G1WvPayRoiW33zeLQFsbx8VtPvotqYp9CjwH8YNyKR98K6cuckYzfZsen4hnKGxuonWj5AVkBc2VR6fZ3iDnUDFdnsUWCqb1h8iTuMruiH6p6YiWt9tgCCoeojbVJJeAiNAQfcW1o58QZ4jKrihh5sTMHfHuFnEwZPX5LDztRwuwqGMLn3Md41mqjs1saTYj7e3azs8tGdRyAHosUeeioJJPUfXgo2EHuHHCEWi41GBmYNzvze2cqrk638ejtPbUpyTQsDDst1GK2xSFzM1si7UdkBByMnkHwq36BMC2sVtGnSRjrop9VC892HFbQyGCkom5yJ7GZRCPPWLZ7k8BKRiJzmw9xfPpjejDeGmFuJAwZgnP4s69hhZ9PTK9yLVazDnBCmBeVYW9yZ7tYomX9LPvETApsD2rmfmFpYXtNiZtN1VJuJpmy765hsGRavr2KUGNWYqfgERwAo6YVaZWctBhQcULBg6CJ4AYj9WTTaBMRhosGtwLYKuiULSyxT8vxXU8WiTg5yfCibJGPrtHo8m3RaCPXD8LcVaMqjVTcamYSvzPJwResXZFjfpr3rrFJ7Lq1yMdEhJorephuNmD5fRwHfZTW7i9Z8EVU2sUhJUYQvMbwVo6rPQgfsJ7eu8SSy6g57rvGP3BKGRKHYVcwoK6z6byCVUSRfLuq9oWhMZie61A8JMqHCbg3s1FymRp8WK85WingGt69Js8v3RdgocNMAj9HNGFfJ4R6ZVuaFQgiWcLWyk3rxStmdWnBfKzcygjpAeajaY1rG12tFbyYhVor2aWruGJoG1ye5nMFXGhkumLr3 \ No newline at end of file +1111111196gsM1k4Nccn3dBJRcj5heBjhZ7zMxi2NayeRiCA2cKpfahX1V1eNojcmSgCKLUeW3aTvbRohjhKqSHSfmgnJwHrXnUCFivmh7YynSxJGWyhHideoWi1grgF2bjRUXzdqcBbHs9KfKDyguQRuaTwJX3pAHuMd8TsnxME1nXhSt1mpk1nXEzoBg7VETeQ5iM2WXH7BeMtK5w2kJGZDFiUgnQVZFy8v9tGywL9KbbZJFAWBQJqcmaLmFQsACy7ZYwecA8mR65YfkHuaoRGUf3yTGrNLnxoYT4hHHWFvZoNnymBoCwhg3Upn7JTAXR24RXrihxi8N69EVAnTnnTZwbki5C54JSQD8RRSx2jVjN9z1Jot4UhC4CJUwtmoi5YSuQNRChjHJSWEc9VppRd4dyygFTSpgfeRXuTZcr2YXcT8jhU5m4YFjupN5THUFCnXrZuHmoLAKCjzgMD2bZwNeiCLEUPuFdJ8U9mEChgHmU8jxzqW22HeZkFwidaQZAZS9Bf3dQHaa2gKUAhHHFCKMxrTmybTjmR8v2dyRJ8eeoRSRD3FNxesRc2jcxfo5uiAum85BVkA231ysrK9w2jGs4oUYr5VbDMVr3jN8vjkbkQZNdm \ No newline at end of file diff --git a/tests/clive-local-tools/clive_local_tools/storage_migration/with_operations/data/mary/v0.profile b/tests/clive-local-tools/clive_local_tools/storage_migration/with_operations/data/mary/v0.profile index d3e32d6071..dc7f7558a5 100644 --- a/tests/clive-local-tools/clive_local_tools/storage_migration/with_operations/data/mary/v0.profile +++ b/tests/clive-local-tools/clive_local_tools/storage_migration/with_operations/data/mary/v0.profile @@ -1 +1 @@ -11111111C8BcgVexmwvtfpyp8qWkKfC4n3zdn4YjCFxfnNuxi4XUtzrcfoZ59LyQyrYVNebZnmMV7kCCHh95AwwpmNZULsGZzyB1cd1Gt4CiDhpjLS8wU2ZyDaTKeWDsXJSaPdbu4657SJj3NMHJkMDc5sugSVH8NXmE3XLo4YZ2HfM9LzwPbSNm9Dvqm5pUexgm4DmQePYVzLJRm9Cx6B6UBPaK4PgfcBGnTZ1jnygB2VHHW2Y8PqjwZxY8KUeSvYWSHrBM1QZN83HTwtEWCoJojJUJeMpU5UyuyboJMuGvVbs9eXmaARKKxkXNVGyotY9KtbHqRQZy5WXTtmvPhJKMDTpVMp6dJvqHEGU4Xs6pE3YdJXf5j5G41EZ5PjafjQ2ixpWmBde76xmGfxhdS5Qkhg1DxnrqYTvbr4NKjJRthJigSzfZTEFEuv3wtrShhZpiCCFW8ebvwYcKKgDBjbtjxjgYxtCqzUuk9yiCd6qYrmxAKjHTBSNQF7LBy8HXdS48qqmN9PzQQyBmXNvhHJptp2AYJJw9CF9SUXGuxjbV2jhUuqqwR27Uy7Njt1TxPvBEm2rQAvU4VwJ5a5Ltoks5nVAw58CBBS5KX5nU2PModhALUtCNJAe8m4nxsYWYVKnAPSnZnqiU8QbHcD6U5pChkdmv5twfbgjWkuyWTjkkVm32THTvbJaJkii6MaQSZpx9rBGc4xyXwDG3bavaNVWySEcf8o5x46hjo5UAXwUnDTstvo3dVTiLsx8uMzv46HuEKhwYK9M6t98rjTq2vsYS6E8zgQFAN8hQHezngEGpuM5deVGMGrm6FHewK7TbueEAs2PAV35tx7KiEeJjQ9yK45xz2TLnf18vMobuUTFNXKoVaCmRezzLg3FBpXna5whfEwmoxJpWEMQtdfAXPCgfuoqQyS7GK83HoBzBqP2SMmvvWXpeAUC2ueN8kQc1iqPaesmZuK7N9ULQidfr7x62rUcwjnd7VpMu1HmF2mnAituFEGKP9XgydVXW9vXMqxVE2we1QqPtzXb1C5ioLKgjxGeLuxrYSJCnGQ2WiNUN9TxUYUY2cfrzPqz9m3QfXf55PFy2RPWEgs13Ay6iLPgEjNmLtsLpnZc21T51C81f8Bhg2fd1sJzBVYDjdsZaF9MeFixLq5AE4Gu3iz8iLwxWVP2X2g16xwTp2BRfTpSoVCFHc2CxjWWQpuPRMYbbHN1zsq6ghYmLdAAiLrE1u6oGfo2uCjmFnPwY4tQtwB32eCQuffJtWcTQPBCisHdvQ1FcqT9a7bkTFxWv6J1FMpCdBfzx9CUmWpUZuFguPXySqBCGUQaWHYYki9oqtFd8Pnc7AZjaWY5CqM2kgypRgH3L5v2FXCfxhtGVgmHZKiSmfgMhBZTThcELEHhqM17HPRtMy5GSh4Hi7WfqcgXoJqrL8FNqDR7XpVWrMxbVymbm9ePcTSs6u3MnjhV2FRtik24mxAanEttjrkndStcmedHYbY1Zyd6pyXKndFwNjjdXnabtimd5aEcoWuriAa1XCraeYtjMxfyBVx8dH6ojsW9vBUUcchxzjAQW3qLwRALSZxgpr9xzhTfhKdLnULWcJvNzsxDcE1Dg9FvMmNii5y3cC9yFRGpwyQCyWCTXsEEPec6jF55n5kE99e6swWGqxrBcsBMtf8qYF5xG5bFn5usksdfgsyh9WjUMuKwG2p7uH7AqoR8DeKsCJ2ArYUvbjaJPnuANUYKcwD56WreJBmyQxSwvGTaATufg7v3pBcXsqG7Rmq1Qz7XHqo \ No newline at end of file +11111111BCo8MUbxzLSryUebhQX2nvqBNZVWREzBzQDzuQsjxEGC3FzSk36S4uF2YD3aDbFERuHh2DNc4yCSFRrByH9AW637PofxbMUohuunJpPb7shxsGa8rv9y6XqE5Rpic5tZrG9MRYBLT47xvZF7jdtENLDye3sMgyS8XeRgFWi4hLACbcKkLyEuQdwTAyhQg13m6kiCtqY9Fujp4VwCxyX26q7C2giacUGU4M433B54UkWvDfrniPjhbku7Ar6QKKvcKNEbDGyYEonHYrTVbQspDSvTcs8nmP5S3urtmGGuVgWZkt4QibbrSj7kS8S3yqhUQn42zHzspRZA77xR1qFU9D7qWRr5XqZbs7iEeL23KqXhihah54yQJhA5qHCveaDpKPqN1eWSuDnYaPfwGvHcvk7HJ9dVkvd7wzDvvDKWnzPeMCu9tZujs1wVxefyFCccVHwJ6y7EmMw3K4FY1YwkCETghFKaaf99w52cH7MhN48nxSPxjfKwa9qKPMPaG4epCE3hwEoeLtBjrbKLfEnAGRstKDyUQcLJJMEMSaoAUwgDHLHQ5whKr78XBtJFCs9Wg1iT7fFgi3M2P7bBw2PwXmpLDV2xqpUQSzDapT4UTGPPfNVYzpxMhD6rVBcvMJEtWmJSr4wAUNA4KzNmZDeph1c8hKu6XC8BhoF1XMoucfYevq59UAQGyYgTiS2TQdCb9XxQz1P9B4uAzUdF5WJGqxJxpLkqBRGaHb2yg1vArZn1zB5jKg6f8zW4ESTUUpjKXe5aLCBniBmVgEd3JnA2vqRySLS4ZY4WeKBg9a6kAFofQdMGoTXcV7qB8bwCxvRi7Wo48LyKgHRJX12jZs3Aidqm5JkmYi9Eg3pUgR6oGj8jufc4DySuDYC8sM3YUUnA8VAoTb22 \ No newline at end of file diff --git a/tests/clive-local-tools/clive_local_tools/storage_migration/without_alarms_and_operations/data/mary/v0.profile b/tests/clive-local-tools/clive_local_tools/storage_migration/without_alarms_and_operations/data/mary/v0.profile index c64e3b4ea8..0c5aa972fe 100644 --- a/tests/clive-local-tools/clive_local_tools/storage_migration/without_alarms_and_operations/data/mary/v0.profile +++ b/tests/clive-local-tools/clive_local_tools/storage_migration/without_alarms_and_operations/data/mary/v0.profile @@ -1 +1 @@ -11111111PbFcb3vAu3RSWCsUu1iwNS4hyweh6kMqM52HqDXQb4hzKZ4jFLTc1bgtLdunDfKJA7ufr2nWjH82dv4KNJh2YAPVyceTMgmBoj9HUBFXEetq6aiaiKw6aQvtn9MmhVcTFPp25HWhWriobLEBhv7Ru8R2TpSJUQpnLWXFQMSczUaphBd49AhiF1YUpsxXTnELatZUDgYdsumJpc6APgec9cZ9yzGJp5phNWXeBNqJ1pz6q2wVVdD7vgwXRgzHKaL2imaR4TRzLqoi8wBE9N1pZZVUcwjLCmSVqVis3aNh9ZsXMB5yt6NZyt4txSw8ZY79hgjopAGb2M8o5X4tHHpA9HuUdhxJcu6rEyJuRVN6x36CRPmNHNBbUqY4qy8UKSDKNJTDhoGqhTVwpjvFVDDBN9wVTHCta94hcWfSRs4Ua4oLVcwWTFrUjFbSXh33PrgopfBcaRAtBNtCecouTbmrUNPRV6LJDFn5aGcuZdhptjMb29wTqvnb8mBvYPhx7GA6sHJy9r3DFMuNpqdecDJg7cStTH38bFg1yBLygHL2GxzaY89reo8jaCZUuG5eVpeARQJJYGm289eMZRX93hxsWAWGyEj1wHLvEcUeJXzK9Y6oMsxG9SNKctYt4mfFf5SX7Gc51aiDwTLrHJdnEhcksYJ4rXvArt5UAGzjWTcMkSMUhzUKXWtwFiU8yCabxKD1W3oKkDwmajCizFA9mYgMT3wUR \ No newline at end of file +11111111JvuHeRa1EZYQbjN4sKJAyDr7vPizCiPrz3d9JiRmg668L1h4FVgGmTZfUCycmLxg99mPG76hnnDiVqV1VUH36ZYnBerMm4oEWgrwr9i4nCg2uxJgBTQjyTa6MH6aEzoRwN9mben5C4UovE7FAnCHrHcMFBaFgei7qznwx19v7xqusNQXt3Wk7VUMbm4t8NC33TSBwU9DVFD94kJGac9rk3MsApD4GzJkoTwEeXdxhKq5iSP5bV7bjTvJSzFC3y1qsPaRoMtY8Rr5y8DQHpEez9xi9xUB1wzRUCCm3uYRvZbQvJacUy2qxnBnXP1DcZSwo2tdznztWaCL4tMdbJVio3waK52V51SSzsU24t5tgWAH9LMtfJJewYtRUx9tRmQoSHB3CsQKSCQFxANWBYL43a4KH54suyZk92oEPNfnK7egEPCcRNJFw1JzWzTPd \ No newline at end of file -- GitLab From 961e81b44c16fbf27906846b5e41c99189197473 Mon Sep 17 00:00:00 2001 From: Marcin Sobczyk Date: Thu, 7 Aug 2025 10:26:13 +0000 Subject: [PATCH 31/35] Replace type aliases inside ProfileSTorageModel with class definitions Because mypy doesnt recognize types of aliases defined as ClassVar[TypeAlias] --- clive/__private/storage/migrations/base.py | 10 ++ clive/__private/storage/migrations/v0.py | 163 +++++++----------- clive/__private/storage/migrations/v1.py | 13 +- .../storage/runtime_to_storage_converter.py | 32 ++-- .../storage/storage_to_runtime_converter.py | 16 +- .../regenerate_prepared_profiles.py | 15 +- 6 files changed, 108 insertions(+), 141 deletions(-) diff --git a/clive/__private/storage/migrations/base.py b/clive/__private/storage/migrations/base.py index b6513bb473..0b918dc03c 100644 --- a/clive/__private/storage/migrations/base.py +++ b/clive/__private/storage/migrations/base.py @@ -119,3 +119,13 @@ class ProfileStorageBase(PreconfiguredBaseModel): assert return_param is Self, ( f"Upgrade function of {cls} should return {Self}, but it returns {return_param} instead." ) + + +class AlarmStorageModelBase(PreconfiguredBaseModel, tag_field="name", kw_only=True): + @classmethod + def get_name(cls) -> str: + assert isinstance(cls.__struct_config__.tag, str), "Alarm storage models must have a string tag." + return cls.__struct_config__.tag + + is_harmless: bool = False + """Identifies the occurrence of specific alarm among other possible alarms of same type. E.g. end date.""" diff --git a/clive/__private/storage/migrations/v0.py b/clive/__private/storage/migrations/v0.py index 4b2bbfb74f..cca875836d 100644 --- a/clive/__private/storage/migrations/v0.py +++ b/clive/__private/storage/migrations/v0.py @@ -2,7 +2,7 @@ from __future__ import annotations from collections.abc import Sequence # noqa: TC003 from pathlib import Path # noqa: TC003 -from typing import Any, ClassVar, Self, TypeAlias +from typing import Any, Self from clive.__private.core.alarms.specific_alarms import ( ChangingRecoveryAccountInProgress, @@ -21,91 +21,7 @@ from clive.__private.models.schemas import ( Uint32t, field, ) -from clive.__private.storage.migrations.base import ProfileStorageBase - - -class DateTimeAlarmIdentifierStorageModel(PreconfiguredBaseModel): - value: HiveDateTime - - -class RecoveryAccountWarningListedAlarmIdentifierStorageModel(PreconfiguredBaseModel): - recovery_account: str - - -AllAlarmIdentifiersStorageModel = ( - DateTimeAlarmIdentifierStorageModel | RecoveryAccountWarningListedAlarmIdentifierStorageModel -) - - -class AlarmStorageModelBase(PreconfiguredBaseModel, tag_field="name", kw_only=True): - @classmethod - def get_name(cls) -> str: - assert isinstance(cls.__struct_config__.tag, str), "Alarm storage models must have a string tag." - return cls.__struct_config__.tag - - is_harmless: bool = False - """Identifies the occurrence of specific alarm among other possible alarms of same type. E.g. end date.""" - - -class RecoveryAccountWarningListedStorageModel( - AlarmStorageModelBase, tag=RecoveryAccountWarningListed.get_name(), kw_only=True -): - identifier: RecoveryAccountWarningListedAlarmIdentifierStorageModel - - -class GovernanceVotingExpirationStorageModel( - AlarmStorageModelBase, tag=GovernanceVotingExpiration.get_name(), kw_only=True -): - identifier: DateTimeAlarmIdentifierStorageModel - - -class GovernanceNoActiveVotesStorageModel(AlarmStorageModelBase, tag=GovernanceNoActiveVotes.get_name(), kw_only=True): - identifier: DateTimeAlarmIdentifierStorageModel - - -class DecliningVotingRightsInProgressStorageModel( - AlarmStorageModelBase, tag=DecliningVotingRightsInProgress.get_name(), kw_only=True -): - identifier: DateTimeAlarmIdentifierStorageModel - - -class ChangingRecoveryAccountInProgressStorageModel( - AlarmStorageModelBase, tag=ChangingRecoveryAccountInProgress.get_name(), kw_only=True -): - identifier: DateTimeAlarmIdentifierStorageModel - - -AllAlarmStorageModel = ( - RecoveryAccountWarningListedStorageModel - | GovernanceVotingExpirationStorageModel - | GovernanceNoActiveVotesStorageModel - | DecliningVotingRightsInProgressStorageModel - | ChangingRecoveryAccountInProgressStorageModel -) - - -class TrackedAccountStorageModel(PreconfiguredBaseModel): - name: str - alarms: Sequence[AllAlarmStorageModel] = [] - - -class KeyAliasStorageModel(PreconfiguredBaseModel): - alias: str - public_key: str - - -class TransactionCoreStorageModel(PreconfiguredBaseModel): - operations: list[OperationRepresentationUnion] = [] # noqa: RUF012 - ref_block_num: Uint16t = 0 - ref_block_prefix: Uint32t = 0 - expiration: HiveDateTime = field(default_factory=lambda: HiveDateTime(utc_epoch())) - extensions: list[Any] = [] # noqa: RUF012 - signatures: list[Signature] = [] # noqa: RUF012 - - -class TransactionStorageModel(PreconfiguredBaseModel): - transaction_core: TransactionCoreStorageModel - transaction_file_path: Path | None = None +from clive.__private.storage.migrations.base import AlarmStorageModelBase, ProfileStorageBase class ProfileStorageModel(ProfileStorageBase, kw_only=True): @@ -119,21 +35,68 @@ class ProfileStorageModel(ProfileStorageBase, kw_only=True): node_address: str should_enable_known_accounts: bool = True - _AllAlarmIdentifiersStorageModel: ClassVar[TypeAlias] = AllAlarmIdentifiersStorageModel - _AllAlarmStorageModel: ClassVar[TypeAlias] = AllAlarmStorageModel - _TrackedAccountStorageModel: ClassVar[TypeAlias] = TrackedAccountStorageModel - _KeyAliasStorageModel: ClassVar[TypeAlias] = KeyAliasStorageModel - _TransactionCoreStorageModel: ClassVar[TypeAlias] = TransactionCoreStorageModel - _TransactionStorageModel: ClassVar[TypeAlias] = TransactionStorageModel - _DateTimeAlarmIdentifierStorageModel: ClassVar[TypeAlias] = DateTimeAlarmIdentifierStorageModel - _RecoveryAccountWarningListedAlarmIdentifierStorageModel: ClassVar[TypeAlias] = ( - RecoveryAccountWarningListedAlarmIdentifierStorageModel + class DateTimeAlarmIdentifierStorageModel(PreconfiguredBaseModel): + value: HiveDateTime + + class RecoveryAccountWarningListedAlarmIdentifierStorageModel(PreconfiguredBaseModel): + recovery_account: str + + AllAlarmIdentifiersStorageModel = ( + DateTimeAlarmIdentifierStorageModel | RecoveryAccountWarningListedAlarmIdentifierStorageModel + ) + + class RecoveryAccountWarningListedStorageModel( + AlarmStorageModelBase, tag=RecoveryAccountWarningListed.get_name(), kw_only=True + ): + identifier: ProfileStorageModel.RecoveryAccountWarningListedAlarmIdentifierStorageModel + + class GovernanceVotingExpirationStorageModel( + AlarmStorageModelBase, tag=GovernanceVotingExpiration.get_name(), kw_only=True + ): + identifier: ProfileStorageModel.DateTimeAlarmIdentifierStorageModel + + class GovernanceNoActiveVotesStorageModel( + AlarmStorageModelBase, tag=GovernanceNoActiveVotes.get_name(), kw_only=True + ): + identifier: ProfileStorageModel.DateTimeAlarmIdentifierStorageModel + + class DecliningVotingRightsInProgressStorageModel( + AlarmStorageModelBase, tag=DecliningVotingRightsInProgress.get_name(), kw_only=True + ): + identifier: ProfileStorageModel.DateTimeAlarmIdentifierStorageModel + + class ChangingRecoveryAccountInProgressStorageModel( + AlarmStorageModelBase, tag=ChangingRecoveryAccountInProgress.get_name(), kw_only=True + ): + identifier: ProfileStorageModel.DateTimeAlarmIdentifierStorageModel + + AllAlarmStorageModel = ( + RecoveryAccountWarningListedStorageModel + | GovernanceVotingExpirationStorageModel + | GovernanceNoActiveVotesStorageModel + | DecliningVotingRightsInProgressStorageModel + | ChangingRecoveryAccountInProgressStorageModel ) - _RecoveryAccountWarningListedStorageModel: ClassVar[TypeAlias] = RecoveryAccountWarningListedStorageModel - _GovernanceVotingExpirationStorageModel: ClassVar[TypeAlias] = GovernanceVotingExpirationStorageModel - _GovernanceNoActiveVotesStorageModel: ClassVar[TypeAlias] = GovernanceNoActiveVotesStorageModel - _DecliningVotingRightsInProgressStorageModel: ClassVar[TypeAlias] = DecliningVotingRightsInProgressStorageModel - _ChangingRecoveryAccountInProgressStorageModel: ClassVar[TypeAlias] = ChangingRecoveryAccountInProgressStorageModel + + class TrackedAccountStorageModel(PreconfiguredBaseModel): + name: str + alarms: Sequence[ProfileStorageModel.AllAlarmStorageModel] = [] + + class KeyAliasStorageModel(PreconfiguredBaseModel): + alias: str + public_key: str + + class TransactionCoreStorageModel(PreconfiguredBaseModel): + operations: list[OperationRepresentationUnion] = [] # noqa: RUF012 + ref_block_num: Uint16t = 0 + ref_block_prefix: Uint32t = 0 + expiration: HiveDateTime = field(default_factory=lambda: HiveDateTime(utc_epoch())) + extensions: list[Any] = [] # noqa: RUF012 + signatures: list[Signature] = [] # noqa: RUF012 + + class TransactionStorageModel(PreconfiguredBaseModel): + transaction_core: ProfileStorageModel.TransactionCoreStorageModel + transaction_file_path: Path | None = None @classmethod def upgrade(cls, old: ProfileStorageBase) -> Self: diff --git a/clive/__private/storage/migrations/v1.py b/clive/__private/storage/migrations/v1.py index 2c41471831..c5cd69b246 100644 --- a/clive/__private/storage/migrations/v1.py +++ b/clive/__private/storage/migrations/v1.py @@ -1,29 +1,26 @@ from __future__ import annotations from pathlib import Path # noqa: TC003 -from typing import ClassVar, Self, TypeAlias +from typing import Self from clive.__private.models.schemas import PreconfiguredBaseModel, Transaction from clive.__private.storage.migrations import v0 -class TransactionStorageModel(PreconfiguredBaseModel): - transaction_core: Transaction - transaction_file_path: Path | None = None - - class ProfileStorageModel(v0.ProfileStorageModel): _REVISION_NONCE = 1 transaction: TransactionStorageModel | None = None # type: ignore[assignment] # changed storage model - _TransactionStorageModel: ClassVar[TypeAlias] = TransactionStorageModel + class TransactionStorageModel(PreconfiguredBaseModel): + transaction_core: Transaction + transaction_file_path: Path | None = None @classmethod def upgrade(cls, old: v0.ProfileStorageModel) -> Self: # type: ignore[override] # should always take previous model old_transaction = old.transaction new_transaction = ( - TransactionStorageModel( + cls.TransactionStorageModel( transaction_core=Transaction( **old_transaction.transaction_core.dict(), ), diff --git a/clive/__private/storage/runtime_to_storage_converter.py b/clive/__private/storage/runtime_to_storage_converter.py index 899a172122..2a0d5fb483 100644 --- a/clive/__private/storage/runtime_to_storage_converter.py +++ b/clive/__private/storage/runtime_to_storage_converter.py @@ -45,16 +45,16 @@ class RuntimeToStorageConverter: profile = self._profile return profile.accounts.working.name if profile.accounts.has_working_account else None - def _tracked_accounts_to_model_container(self) -> list[ProfileStorageModel._TrackedAccountStorageModel]: + def _tracked_accounts_to_model_container(self) -> list[ProfileStorageModel.TrackedAccountStorageModel]: return [self._tracked_account_to_model(account) for account in self._profile.accounts.tracked] def _known_accounts_to_model_container(self) -> list[str]: return [account.name for account in self._profile.accounts.known] - def _key_aliases_to_model_container(self) -> list[ProfileStorageModel._KeyAliasStorageModel]: + def _key_aliases_to_model_container(self) -> list[ProfileStorageModel.KeyAliasStorageModel]: return [self._key_alias_to_model(key) for key in self._profile.keys] - def _transaction_to_model(self) -> ProfileStorageModel._TransactionStorageModel: + def _transaction_to_model(self) -> ProfileStorageModel.TransactionStorageModel: transaction_core = Transaction( operations=deepcopy(self._profile.operation_representations), ref_block_num=self._profile.transaction.ref_block_num, @@ -63,38 +63,38 @@ class RuntimeToStorageConverter: extensions=deepcopy(self._profile.transaction.extensions), signatures=deepcopy(self._profile.transaction.signatures), ) - return ProfileStorageModel._TransactionStorageModel( + return ProfileStorageModel.TransactionStorageModel( transaction_core=transaction_core, transaction_file_path=self._profile.transaction_file_path ) - def _tracked_account_to_model(self, account: TrackedAccount) -> ProfileStorageModel._TrackedAccountStorageModel: + def _tracked_account_to_model(self, account: TrackedAccount) -> ProfileStorageModel.TrackedAccountStorageModel: alarms = [self._alarm_to_model(alarm) for alarm in account._alarms.all_alarms if alarm.has_identifier] - return ProfileStorageModel._TrackedAccountStorageModel(name=account.name, alarms=alarms) + return ProfileStorageModel.TrackedAccountStorageModel(name=account.name, alarms=alarms) - def _alarm_to_model(self, alarm: AnyAlarm) -> ProfileStorageModel._AllAlarmStorageModel: + def _alarm_to_model(self, alarm: AnyAlarm) -> ProfileStorageModel.AllAlarmStorageModel: alarm_cls = self._get_alarm_storage_model_cls_by_name(alarm.get_name()) return alarm_cls( is_harmless=alarm.is_harmless, - identifier=self._alarm_identifier_to_model(alarm.identifier_ensure), + identifier=self._alarm_identifier_to_model(alarm.identifier_ensure), # type: ignore[arg-type] ) def _alarm_identifier_to_model( self, identifier: AlarmIdentifier - ) -> ProfileStorageModel._AllAlarmIdentifiersStorageModel: + ) -> ProfileStorageModel.AllAlarmIdentifiersStorageModel: if isinstance(identifier, DateTimeAlarmIdentifier): - return ProfileStorageModel._DateTimeAlarmIdentifierStorageModel(value=identifier.value) + return ProfileStorageModel.DateTimeAlarmIdentifierStorageModel(value=identifier.value) if isinstance(identifier, RecoveryAccountWarningListedAlarmIdentifier): - return ProfileStorageModel._RecoveryAccountWarningListedAlarmIdentifierStorageModel( + return ProfileStorageModel.RecoveryAccountWarningListedAlarmIdentifierStorageModel( recovery_account=identifier.recovery_account ) raise AlarmIdentifierRuntimeToStorageConversionError(f"Unknown alarm identifier type: {type(identifier)}") - def _key_alias_to_model(self, key: PublicKeyAliased) -> ProfileStorageModel._KeyAliasStorageModel: - return ProfileStorageModel._KeyAliasStorageModel(alias=key.alias, public_key=key.value) + def _key_alias_to_model(self, key: PublicKeyAliased) -> ProfileStorageModel.KeyAliasStorageModel: + return ProfileStorageModel.KeyAliasStorageModel(alias=key.alias, public_key=key.value) - def _get_alarm_storage_model_cls_by_name(self, name: str) -> type[ProfileStorageModel._AllAlarmStorageModel]: - all_alarm_storage_model_classes = get_args(ProfileStorageModel._AllAlarmStorageModel) - name_to_cls: dict[str, type[ProfileStorageModel._AllAlarmStorageModel]] = { + def _get_alarm_storage_model_cls_by_name(self, name: str) -> type[ProfileStorageModel.AllAlarmStorageModel]: + all_alarm_storage_model_classes = get_args(ProfileStorageModel.AllAlarmStorageModel) + name_to_cls: dict[str, type[ProfileStorageModel.AllAlarmStorageModel]] = { cls.get_name(): cls for cls in all_alarm_storage_model_classes } assert name in name_to_cls, f"Alarm class not found for name: {name}" diff --git a/clive/__private/storage/storage_to_runtime_converter.py b/clive/__private/storage/storage_to_runtime_converter.py index 28f2c70cd4..ae4f0de165 100644 --- a/clive/__private/storage/storage_to_runtime_converter.py +++ b/clive/__private/storage/storage_to_runtime_converter.py @@ -98,34 +98,34 @@ class StorageToRuntimeConverter: return transaction_storage_model.transaction_file_path return None - def _working_account_from_model(self, model: ProfileStorageModel._TrackedAccountStorageModel) -> WorkingAccount: + def _working_account_from_model(self, model: ProfileStorageModel.TrackedAccountStorageModel) -> WorkingAccount: return WorkingAccount(model.name, self._alarms_storage_from_model(model)) - def _watched_account_from_model(self, model: ProfileStorageModel._TrackedAccountStorageModel) -> WatchedAccount: + def _watched_account_from_model(self, model: ProfileStorageModel.TrackedAccountStorageModel) -> WatchedAccount: return WatchedAccount(model.name, self._alarms_storage_from_model(model)) - def _alarms_storage_from_model(self, model: ProfileStorageModel._TrackedAccountStorageModel) -> AlarmsStorage: + def _alarms_storage_from_model(self, model: ProfileStorageModel.TrackedAccountStorageModel) -> AlarmsStorage: alarms = [self._alarm_from_model(alarm) for alarm in model.alarms] return AlarmsStorage(alarms) def _known_account_from_model_representation(self, name: str) -> KnownAccount: return KnownAccount(name) - def _alarm_from_model(self, model: ProfileStorageModel._AllAlarmStorageModel) -> AnyAlarm: + def _alarm_from_model(self, model: ProfileStorageModel.AllAlarmStorageModel) -> AnyAlarm: alarm_cls = Alarm.get_alarm_class_by_name(model.get_name()) identifier = self._alarm_identifier_from_model(model.identifier) return alarm_cls(identifier=identifier, is_harmless=model.is_harmless) def _alarm_identifier_from_model( - self, model: ProfileStorageModel._AllAlarmIdentifiersStorageModel + self, model: ProfileStorageModel.AllAlarmIdentifiersStorageModel ) -> AllAlarmIdentifiers: - if isinstance(model, ProfileStorageModel._DateTimeAlarmIdentifierStorageModel): + if isinstance(model, ProfileStorageModel.DateTimeAlarmIdentifierStorageModel): return DateTimeAlarmIdentifier(value=model.value) - if isinstance(model, ProfileStorageModel._RecoveryAccountWarningListedAlarmIdentifierStorageModel): + if isinstance(model, ProfileStorageModel.RecoveryAccountWarningListedAlarmIdentifierStorageModel): return RecoveryAccountWarningListedAlarmIdentifier(recovery_account=model.recovery_account) raise AlarmIdentifierStorageToRuntimeConversionError( f"Unknown alarm identifier storage model type: {type(model)}" ) - def _key_alias_from_model(self, model: ProfileStorageModel._KeyAliasStorageModel) -> PublicKeyAliased: + def _key_alias_from_model(self, model: ProfileStorageModel.KeyAliasStorageModel) -> PublicKeyAliased: return PublicKeyAliased(value=model.public_key, alias=model.alias) diff --git a/tests/clive-local-tools/clive_local_tools/storage_migration/regenerate_prepared_profiles.py b/tests/clive-local-tools/clive_local_tools/storage_migration/regenerate_prepared_profiles.py index a0e3572a51..51d1d16ebc 100644 --- a/tests/clive-local-tools/clive_local_tools/storage_migration/regenerate_prepared_profiles.py +++ b/tests/clive-local-tools/clive_local_tools/storage_migration/regenerate_prepared_profiles.py @@ -18,9 +18,6 @@ from typing import TYPE_CHECKING, Final import test_tools as tt from beekeepy import AsyncBeekeeper -from clive.__private.core.alarms.specific_alarms.recovery_account_warning_listed import ( - RecoveryAccountWarningListedAlarmIdentifier, -) from clive.__private.core.commands.create_encryption_wallet import CreateEncryptionWallet from clive.__private.core.commands.create_user_wallet import CreateUserWallet from clive.__private.core.constants.setting_identifiers import DATA_PATH @@ -79,10 +76,10 @@ def create_model_from_scratch() -> ProfileStorageModel: return ProfileStorageModel( name=account_name, working_account=account_name, - tracked_accounts=[ProfileStorageModel._TrackedAccountStorageModel(name=account_name, alarms=[])], + tracked_accounts=[ProfileStorageModel.TrackedAccountStorageModel(name=account_name, alarms=[])], known_accounts=[account_name, ALT_WORKING_ACCOUNT2_DATA.account.name], key_aliases=[ - ProfileStorageModel._KeyAliasStorageModel( + ProfileStorageModel.KeyAliasStorageModel( alias=ALT_WORKING_ACCOUNT1_KEY_ALIAS, public_key=ACCOUNT_DATA.account.public_key, ) @@ -126,9 +123,9 @@ async def _main() -> None: async with prepare_encryption_service() as encryption_service: profile_model = create_model_from_scratch() profile_model.tracked_accounts[0].alarms = [ - ProfileStorageModel._RecoveryAccountWarningListedStorageModel( + ProfileStorageModel.RecoveryAccountWarningListedStorageModel( is_harmless=False, - identifier=RecoveryAccountWarningListedAlarmIdentifier( + identifier=ProfileStorageModel.RecoveryAccountWarningListedAlarmIdentifierStorageModel( recovery_account=ALT_WORKING_ACCOUNT2_DATA.account.name ), ) @@ -139,8 +136,8 @@ async def _main() -> None: with copy_profile_files_from_tmp_dir("with_operations"): async with prepare_encryption_service() as encryption_service: profile_model = create_model_from_scratch() - profile_model.transaction = ProfileStorageModel._TransactionStorageModel( - transaction_core=ProfileStorageModel._TransactionCoreStorageModel( + profile_model.transaction = ProfileStorageModel.TransactionStorageModel( + transaction_core=ProfileStorageModel.TransactionCoreStorageModel( operations=[convert_to_representation(OPERATION)] ), transaction_file_path=Path("example/path"), -- GitLab From 913c634e78b47b801fa7cf2037b2b7f912c205f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Mas=C5=82owski?= Date: Tue, 5 Aug 2025 14:44:56 +0200 Subject: [PATCH 32/35] Update REVISIONS in test_storage_revisions --- tests/unit/storage/test_storage_revision.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/storage/test_storage_revision.py b/tests/unit/storage/test_storage_revision.py index 9b0cb9de86..25f650f6af 100644 --- a/tests/unit/storage/test_storage_revision.py +++ b/tests/unit/storage/test_storage_revision.py @@ -5,7 +5,7 @@ from typing import Final from clive.__private.storage import ProfileStorageModel from clive.__private.storage.storage_history import StorageHistory -REVISIONS: Final[list[str]] = ["ffc97b51", "a721f943", "9c46df0c"] +REVISIONS: Final[list[str]] = ["d8fef2cd", "3b81a04e", "0fc1e8b3"] LATEST_REVISION: Final[str] = REVISIONS[-1] -- GitLab From c74fa43d6e17ecf807c48bf69630595e15cac52b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20=C5=BBebrak?= Date: Thu, 7 Aug 2025 10:58:28 +0200 Subject: [PATCH 33/35] Handle -1 might be already stored for ref_block_num and ref_block_prefix during migration Previously we had -1 as the default for ref_block_prefix and ref_block_num. But since schemas doesn't allow for negative numbers anymore, there might be an error when loading old data in case if someone has not updated TaPoS in his transaction stored on the disk. --- clive/__private/storage/migrations/base.py | 30 ++++++++++++++++++++++ clive/__private/storage/migrations/v0.py | 30 ++++++++++++++++++++++ clive/__private/storage/service.py | 2 +- 3 files changed, 61 insertions(+), 1 deletion(-) diff --git a/clive/__private/storage/migrations/base.py b/clive/__private/storage/migrations/base.py index 0b918dc03c..525897f1b5 100644 --- a/clive/__private/storage/migrations/base.py +++ b/clive/__private/storage/migrations/base.py @@ -1,5 +1,6 @@ from __future__ import annotations +import json from hashlib import sha256 from typing import Any, ClassVar, Self, get_type_hints @@ -45,6 +46,35 @@ class ProfileStorageBase(PreconfiguredBaseModel): def __hash__(self) -> int: return hash(self.json(order="deterministic")) + @classmethod + def create(cls, raw: str) -> Self: + """ + Create a new model instance from data stored on the disk. + + It handles any preprocessing before parsing it into the model. + + Args: + raw: A profile data stored on disk. + + Returns: + A new instance of the model class initialized with the provided data. + """ + data = cls._preprocess_data(json.loads(raw)) + return cls.parse_builtins(data) + + @classmethod + def _preprocess_data(cls, data: dict[str, Any]) -> dict[str, Any]: + """ + Override to preprocess data before parsing it into the model. + + Args: + data: A mapping containing the raw profile data that will be later transformed into the model. + + Returns: + Data that will be used to initialize the model instance. + """ + return data + @classmethod def upgrade(cls, old: ProfileStorageBase) -> Self: raise NotImplementedError diff --git a/clive/__private/storage/migrations/v0.py b/clive/__private/storage/migrations/v0.py index cca875836d..46e0ce3386 100644 --- a/clive/__private/storage/migrations/v0.py +++ b/clive/__private/storage/migrations/v0.py @@ -101,3 +101,33 @@ class ProfileStorageModel(ProfileStorageBase, kw_only=True): @classmethod def upgrade(cls, old: ProfileStorageBase) -> Self: raise NotImplementedError("Upgrade is not not possible for first revision.") + + @classmethod + def _preprocess_data(cls, data: dict[str, Any]) -> dict[str, Any]: + cls._ensure_non_negative_tapos_fields(data) + return data + + @staticmethod + def _ensure_non_negative_tapos_fields(data: dict[str, Any]) -> None: + """ + Ensure that specific TAPoS fields in the transaction data are non-negative. + + This method modifies the data loaded from a disk to ensure that the specified TAPoS + fields, namely 'ref_block_num' and 'ref_block_prefix' within the transaction, are non-negative. + If the fields are absent or their values are negative, they are set to `0`. + + This method exists because in previous versions of storage we included `-1` + by default. + + Args: + data: The profile storage data stored on the disk. + """ + transaction: dict[str, Any] | None = data.get("transaction", {}) + if transaction is None: + # transaction can be also none, so we skip in that case + return + + transaction_core = transaction.get("transaction_core", {}) + for field_ in ("ref_block_num", "ref_block_prefix"): + value = transaction_core.get(field_, 0) + transaction_core[field_] = max(value, 0) diff --git a/clive/__private/storage/service.py b/clive/__private/storage/service.py index 84fd954679..fa1c0a3ab1 100644 --- a/clive/__private/storage/service.py +++ b/clive/__private/storage/service.py @@ -369,7 +369,7 @@ class PersistentStorageService: async def _parse_profile_model_from_file(self, profile_filepath: Path) -> ProfileStorageBase: raw = await self._read_profile_file_raw(profile_filepath) model_cls = self._model_cls_from_path(profile_filepath) - return model_cls.parse_raw(raw) + return model_cls.create(raw) async def _read_profile_file_raw(self, profile_filepath: Path) -> str: """ -- GitLab From 2c444cb5d767dd3961b69a66938909d1f7108ec3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20=C5=BBebrak?= Date: Thu, 7 Aug 2025 13:02:26 +0200 Subject: [PATCH 34/35] Create missing folders and init files during regenerate_prepared_profiles --- .../storage_migration/regenerate_prepared_profiles.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/clive-local-tools/clive_local_tools/storage_migration/regenerate_prepared_profiles.py b/tests/clive-local-tools/clive_local_tools/storage_migration/regenerate_prepared_profiles.py index 51d1d16ebc..14b61994fd 100644 --- a/tests/clive-local-tools/clive_local_tools/storage_migration/regenerate_prepared_profiles.py +++ b/tests/clive-local-tools/clive_local_tools/storage_migration/regenerate_prepared_profiles.py @@ -100,6 +100,7 @@ def save_encrypted_profile(encrypted: str) -> None: @contextmanager def copy_profile_files_from_tmp_dir(dst_dir_name: str) -> Generator[None, None]: + init_file_name = "__init__.py" dst_dir = Path(__file__).parent.absolute() / dst_dir_name with tempfile.TemporaryDirectory() as tmp_dir_name: @@ -109,8 +110,14 @@ def copy_profile_files_from_tmp_dir(dst_dir_name: str) -> Generator[None, None]: for path in Path(tmp_dir_name).rglob("*"): if path.suffix in {".wallet", ".profile"}: dst_path = dst_dir / path.relative_to(tmp_dir_name) + dst_last_dir = dst_path.parent + dst_last_dir.mkdir(parents=True, exist_ok=True) shutil.copy(path, dst_path) + all_directories = [dst_dir] + [path for path in dst_dir.rglob("*") if path.is_dir()] + for directory in all_directories: + (directory / init_file_name).touch(exist_ok=True) + async def _main() -> None: with copy_profile_files_from_tmp_dir("without_alarms_and_operations"): -- GitLab From a2c2188832cc16ca63bcc9927266695a25f26a32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20=C5=BBebrak?= Date: Thu, 7 Aug 2025 13:05:32 +0200 Subject: [PATCH 35/35] Add test_migrate_profile_with_transaction_containing_negative_tapos --- .../clive_local_tools/storage_migration/helpers.py | 6 ++++++ .../regenerate_prepared_profiles.py | 10 ++++++++++ .../__init__.py | 0 .../beekeeper/__init__.py | 0 .../beekeeper/mary.wallet | 1 + .../beekeeper/mary_encryption.wallet | 1 + .../data/__init__.py | 0 .../data/mary/__init__.py | 0 .../data/mary/v0.profile | 1 + tests/unit/storage/test_loading_and_migration.py | 14 ++++++++++++++ 10 files changed, 33 insertions(+) create mode 100644 tests/clive-local-tools/clive_local_tools/storage_migration/with_transaction_containing_negative_tapos/__init__.py create mode 100644 tests/clive-local-tools/clive_local_tools/storage_migration/with_transaction_containing_negative_tapos/beekeeper/__init__.py create mode 100644 tests/clive-local-tools/clive_local_tools/storage_migration/with_transaction_containing_negative_tapos/beekeeper/mary.wallet create mode 100644 tests/clive-local-tools/clive_local_tools/storage_migration/with_transaction_containing_negative_tapos/beekeeper/mary_encryption.wallet create mode 100644 tests/clive-local-tools/clive_local_tools/storage_migration/with_transaction_containing_negative_tapos/data/__init__.py create mode 100644 tests/clive-local-tools/clive_local_tools/storage_migration/with_transaction_containing_negative_tapos/data/mary/__init__.py create mode 100644 tests/clive-local-tools/clive_local_tools/storage_migration/with_transaction_containing_negative_tapos/data/mary/v0.profile diff --git a/tests/clive-local-tools/clive_local_tools/storage_migration/helpers.py b/tests/clive-local-tools/clive_local_tools/storage_migration/helpers.py index 92a3ccca17..932b51c06f 100644 --- a/tests/clive-local-tools/clive_local_tools/storage_migration/helpers.py +++ b/tests/clive-local-tools/clive_local_tools/storage_migration/helpers.py @@ -7,6 +7,7 @@ from clive_local_tools.storage_migration import ( blank_profile_files, with_alarms, with_operations, + with_transaction_containing_negative_tapos, without_alarms_and_operations, ) @@ -30,6 +31,11 @@ def copy_profile_with_operations(destination_dir: Path) -> None: _copy_recursively(package_path, destination_dir) +def copy_profile_with_transaction_containing_negative_tapos(destination_dir: Path) -> None: + package_path = Path(with_transaction_containing_negative_tapos.__file__).parent + _copy_recursively(package_path, destination_dir) + + def copy_profile_without_alarms_and_operations(destination_dir: Path) -> None: package_path = Path(without_alarms_and_operations.__file__).parent _copy_recursively(package_path, destination_dir) diff --git a/tests/clive-local-tools/clive_local_tools/storage_migration/regenerate_prepared_profiles.py b/tests/clive-local-tools/clive_local_tools/storage_migration/regenerate_prepared_profiles.py index 14b61994fd..2eaeb92f92 100644 --- a/tests/clive-local-tools/clive_local_tools/storage_migration/regenerate_prepared_profiles.py +++ b/tests/clive-local-tools/clive_local_tools/storage_migration/regenerate_prepared_profiles.py @@ -152,6 +152,16 @@ async def _main() -> None: encrypted = await encryption_service.encrypt(profile_model.json()) save_encrypted_profile(encrypted) + with copy_profile_files_from_tmp_dir("with_transaction_containing_negative_tapos"): + async with prepare_encryption_service() as encryption_service: + transaction_core = ProfileStorageModel.TransactionCoreStorageModel() + transaction_core.ref_block_num = -1 + transaction_core.ref_block_prefix = -1 + profile_model = create_model_from_scratch() + profile_model.transaction = ProfileStorageModel.TransactionStorageModel(transaction_core=transaction_core) + encrypted = await encryption_service.encrypt(profile_model.json()) + save_encrypted_profile(encrypted) + def main() -> None: asyncio.run(_main()) diff --git a/tests/clive-local-tools/clive_local_tools/storage_migration/with_transaction_containing_negative_tapos/__init__.py b/tests/clive-local-tools/clive_local_tools/storage_migration/with_transaction_containing_negative_tapos/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/clive-local-tools/clive_local_tools/storage_migration/with_transaction_containing_negative_tapos/beekeeper/__init__.py b/tests/clive-local-tools/clive_local_tools/storage_migration/with_transaction_containing_negative_tapos/beekeeper/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/clive-local-tools/clive_local_tools/storage_migration/with_transaction_containing_negative_tapos/beekeeper/mary.wallet b/tests/clive-local-tools/clive_local_tools/storage_migration/with_transaction_containing_negative_tapos/beekeeper/mary.wallet new file mode 100644 index 0000000000..584944b48d --- /dev/null +++ b/tests/clive-local-tools/clive_local_tools/storage_migration/with_transaction_containing_negative_tapos/beekeeper/mary.wallet @@ -0,0 +1 @@ +"9a68ec4751ade30194e349360ad813dd621d8be888b01b288b9dd090e24eaec720b54dcf2c993e8d5a0de823b495a3359d2236f9b0bbf098f027d74650bae26c35da6be2086fc438b1780686f0a3a3ca" \ No newline at end of file diff --git a/tests/clive-local-tools/clive_local_tools/storage_migration/with_transaction_containing_negative_tapos/beekeeper/mary_encryption.wallet b/tests/clive-local-tools/clive_local_tools/storage_migration/with_transaction_containing_negative_tapos/beekeeper/mary_encryption.wallet new file mode 100644 index 0000000000..5f60bebe77 --- /dev/null +++ b/tests/clive-local-tools/clive_local_tools/storage_migration/with_transaction_containing_negative_tapos/beekeeper/mary_encryption.wallet @@ -0,0 +1 @@ +"9a68ec4751ade30194e349360ad813dd621d8be888b01b288b9dd090e24eaec720b54dcf2c993e8d5a0de823b495a3359d2236f9b0bbf098f027d74650bae26c7cec114e4339b6c038df19c0be255b983e527d80b324c00d514037b99a21e92d251e3b26824894a1a15e020b79749809072a65e1cb4f74f2c84c21478ccf6b7ad7fdc73c59402ea8f5ac3c6c029f836d" \ No newline at end of file diff --git a/tests/clive-local-tools/clive_local_tools/storage_migration/with_transaction_containing_negative_tapos/data/__init__.py b/tests/clive-local-tools/clive_local_tools/storage_migration/with_transaction_containing_negative_tapos/data/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/clive-local-tools/clive_local_tools/storage_migration/with_transaction_containing_negative_tapos/data/mary/__init__.py b/tests/clive-local-tools/clive_local_tools/storage_migration/with_transaction_containing_negative_tapos/data/mary/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/clive-local-tools/clive_local_tools/storage_migration/with_transaction_containing_negative_tapos/data/mary/v0.profile b/tests/clive-local-tools/clive_local_tools/storage_migration/with_transaction_containing_negative_tapos/data/mary/v0.profile new file mode 100644 index 0000000000..885c87c1d4 --- /dev/null +++ b/tests/clive-local-tools/clive_local_tools/storage_migration/with_transaction_containing_negative_tapos/data/mary/v0.profile @@ -0,0 +1 @@ +1111111196gsM1k4TnKKYaXvQfGBDHRMRf1Jo6tXE9faxsLj4rfrdkAJQ3aRcd6dnVYXnenquj8AJjAiZEJVuhrvVAcsBnAx57HF8eLeuFBBwgmBpe5oXnuZdwfavx1DDtmUfEFKe9dpoPdkyS88e18a6Efu3yJPsFMDiHqFAfRfLK8au4EbarnKWfQ4C2YLXmg14pe2SU7ZzoqFbUPQHSCgBQFBCShdyWNiLvyr7vPVYa1gWuNELLtij99XKtfctitx7X7sWCub5xGPeh7bAsHMduKUJ5UGnKg8Hh4h4hsmgrdM8PyMknkAdSuiQAsCpSWfKDYGJNJBiNZaNN7P8ztWqUuCkjg2MZogVyMuoTtzeV1a1595eWkTWfZ8LJrF7izegZ43Pe7pfCXf5Y8txcuUE9sXY1jZ5bPWXXaHgEdhW3KjawpQZxtrQCUCkEr2vQtKzhA5eXVyBh3AqCFzyE3eXnj3dLRSJjPbnw5HjnyrXYW4RsGAvUH68Ev8FSwAcEMso4MS8cB2JRZyFbMqvTYhQembhbbBHuPZwna63g5KLteZGoZu59DLZKdv5QkWYSHfAVUueLuNNd7SdghQdhoPiRrZyMZ1HnNmojek5C3aujEHGV29 \ No newline at end of file diff --git a/tests/unit/storage/test_loading_and_migration.py b/tests/unit/storage/test_loading_and_migration.py index e64b337c3a..82eb09a340 100644 --- a/tests/unit/storage/test_loading_and_migration.py +++ b/tests/unit/storage/test_loading_and_migration.py @@ -14,6 +14,7 @@ from clive_local_tools.storage_migration import ( copy_profile_with_operations, copy_profile_without_alarms_and_operations, ) +from clive_local_tools.storage_migration.helpers import copy_profile_with_transaction_containing_negative_tapos from clive_local_tools.testnet_block_log import ALT_WORKING_ACCOUNT1_NAME, ALT_WORKING_ACCOUNT2_NAME PROFILE_NAME: Final[str] = ALT_WORKING_ACCOUNT1_NAME @@ -65,3 +66,16 @@ async def test_migrate_profile_with_operations() -> None: assert profile.operations, "Operations should be loaded from older profile version" operation = profile.operations[0] assert operation == OPERATION, f"Operation should be `{OPERATION}`, but got `{operation}`" + + +async def test_migrate_profile_with_transaction_containing_negative_tapos() -> None: + # ARRANGE + copy_profile_with_transaction_containing_negative_tapos(safe_settings.data_path) + + async with ProfileChecker.from_password(PROFILE_NAME, PROFILE_PASSWORD) as profile_checker: + # ACT + profile = await profile_checker.profile + + # ASSERT + assert profile.transaction.ref_block_num == 0, "ref_block_num should be 0 after migration" + assert profile.transaction.ref_block_prefix == 0, "ref_block_prefix should be 0 after migration" -- GitLab