From ef1f292df6baf5a1889c0683adbd9bcfe406bf9c Mon Sep 17 00:00:00 2001 From: Uxio Fuentefria Date: Mon, 5 Oct 2026 09:45:12 +0000 Subject: [PATCH 1/2] Bump sqlmodel to 0.0.47, sqladmin to 0.32.0 and alembic to 1.20.0 - sqlmodel 0.0.42 -> 0.0.47 (sqlalchemy 2.0.52 -> 2.0.54) - sqladmin 0.31.1 -> 0.32.0 - alembic 1.19.2 -> 1.20.0 sqlmodel 0.0.47 types the model `__init__` from the fields. Fix the new mypy errors: - Cast `eth_typing.ABI` values to `list[dict]` when creating `Abi`. - Remove unused `type: ignore` comments in `TimeStampedSQLModel`. - Remove the `name` argument from the `Project` test, the model has no such field. --- app/datasources/db/models.py | 4 +- app/services/abis.py | 5 +- app/tests/datasources/db/test_models.py | 6 +-- app/tests/routers/test_data_decoder.py | 10 +++- app/tests/services/test_data_decoder.py | 58 +++++++++++++++----- pyproject.toml | 6 +-- uv.lock | 70 ++++++++++++++----------- 7 files changed, 102 insertions(+), 57 deletions(-) diff --git a/app/datasources/db/models.py b/app/datasources/db/models.py index 23e0a6f..ea5dc32 100644 --- a/app/datasources/db/models.py +++ b/app/datasources/db/models.py @@ -58,14 +58,14 @@ class TimeStampedSQLModel(SQLModel): """ - created: datetime.datetime = Field( # type: ignore[call-overload] + created: datetime.datetime = Field( default_factory=lambda: datetime.datetime.now(datetime.UTC), nullable=False, sa_type=DateTime(timezone=True), index=True, ) - modified: datetime.datetime = Field( # type: ignore[call-overload] + modified: datetime.datetime = Field( default_factory=lambda: datetime.datetime.now(datetime.UTC), nullable=False, sa_type=DateTime(timezone=True), diff --git a/app/services/abis.py b/app/services/abis.py index 306568d..48e2e0c 100644 --- a/app/services/abis.py +++ b/app/services/abis.py @@ -75,10 +75,11 @@ async def _store_abis_in_database( abi_source: AbiSource, ) -> None: for abi_json in abi_jsons: - abi = await Abi.get_abi(cast(list[dict], abi_json)) + abi_dicts = cast(list[dict], abi_json) + abi = await Abi.get_abi(abi_dicts) if abi is None: await Abi( - abi_json=abi_json, source_id=abi_source.id, relevance=relevance + abi_json=abi_dicts, source_id=abi_source.id, relevance=relevance ).create() async def load_local_abis_in_database(self) -> None: diff --git a/app/tests/datasources/db/test_models.py b/app/tests/datasources/db/test_models.py index 5121ca9..747cf6f 100644 --- a/app/tests/datasources/db/test_models.py +++ b/app/tests/datasources/db/test_models.py @@ -98,9 +98,7 @@ async def test_contract_get_abi_by_contract_address(self): @db_session_context async def test_project(self): - project = Project( - name="Safe", description="A Test Project", logo_file="logo.jpg" - ) + project = Project(description="A Test Project", logo_file="logo.jpg") await project.create() result = await project.get_all() self.assertEqual(result[0], project) @@ -440,7 +438,7 @@ async def test_abi_json_not_null_constraint(self): source = AbiSource(name="null_test_source", url="") await source.create() - abi = Abi(abi_json=None, source_id=source.id) + abi = Abi(abi_json=None, source_id=source.id) # type: ignore[arg-type] with self.assertRaises(IntegrityError): await abi.create() await db_session.rollback() diff --git a/app/tests/routers/test_data_decoder.py b/app/tests/routers/test_data_decoder.py index 958fac8..34ff6f7 100644 --- a/app/tests/routers/test_data_decoder.py +++ b/app/tests/routers/test_data_decoder.py @@ -169,7 +169,11 @@ async def test_view_data_decoder_with_chain_id(self): source = AbiSource(name="local", url="") await source.create() - abi = Abi(abi_json=example_abi, relevance=101, source_id=source.id) + abi = Abi( + abi_json=cast(list[dict], example_abi), + relevance=101, + source_id=source.id, + ) await abi.create() contract = Contract( address=HexBytes(contract_address), @@ -180,7 +184,9 @@ async def test_view_data_decoder_with_chain_id(self): await contract.create() swapped_abi = Abi( - abi_json=example_swapped_abi, relevance=100, source_id=source.id + abi_json=cast(list[dict], example_swapped_abi), + relevance=100, + source_id=source.id, ) await swapped_abi.create() contract = Contract( diff --git a/app/tests/services/test_data_decoder.py b/app/tests/services/test_data_decoder.py index f7826cd..6904134 100644 --- a/app/tests/services/test_data_decoder.py +++ b/app/tests/services/test_data_decoder.py @@ -60,10 +60,26 @@ async def _store_safe_contract_abi(): # Add Safe Contract Abi and decode it for abi in ( - Abi(abi_json=erc20_contract.abi, relevance=150, source_id=source.id), - Abi(abi_json=safe_v1_1_1_contract.abi, relevance=100, source_id=source.id), - Abi(abi_json=safe_v1_4_1_contract.abi, relevance=100, source_id=source.id), - Abi(abi_json=multisend_contract.abi, relevance=100, source_id=source.id), + Abi( + abi_json=cast(list[dict], erc20_contract.abi), + relevance=150, + source_id=source.id, + ), + Abi( + abi_json=cast(list[dict], safe_v1_1_1_contract.abi), + relevance=100, + source_id=source.id, + ), + Abi( + abi_json=cast(list[dict], safe_v1_4_1_contract.abi), + relevance=100, + source_id=source.id, + ), + Abi( + abi_json=cast(list[dict], multisend_contract.abi), + relevance=100, + source_id=source.id, + ), Abi(abi_json=gnosis_protocol_abi, relevance=50, source_id=source.id), Abi( abi_json=fleet_factory_deterministic_abi, @@ -103,7 +119,9 @@ async def test_init_skips_an_abi_without_selectors(self): await self._store_safe_contract_abi() source = AbiSource(name="broken", url="") await source.create() - broken_abi = Abi(abi_json=malformed_abi, relevance=200, source_id=source.id) + broken_abi = Abi( + abi_json=cast(list[dict], malformed_abi), relevance=200, source_id=source.id + ) await broken_abi.create() decoder_service = DataDecoderService() @@ -511,7 +529,9 @@ async def test_db_tx_decoder(self): source = AbiSource(name="local", url="") await source.create() # Test load a new DbTxDecoder - abi = Abi(abi_json=example_abi, relevance=100, source_id=source.id) + abi = Abi( + abi_json=cast(list[dict], example_abi), relevance=100, source_id=source.id + ) await abi.create() decoder_service = DataDecoderService() await decoder_service.init() @@ -519,7 +539,11 @@ async def test_db_tx_decoder(self): self.assertEqual(fn_name, "buyDroid") self.assertEqual(arguments, {"droidId": "4", "numberOfDroids": "10"}) - abi = Abi(abi_json=example_swapped_abi, relevance=100, source_id=source.id) + abi = Abi( + abi_json=cast(list[dict], example_swapped_abi), + relevance=100, + source_id=source.id, + ) await abi.create() contract = Contract(address=b"c", abi=abi, name="SwappedContract", chain_id=1) await contract.create() @@ -546,11 +570,15 @@ async def test_db_tx_decoder_multichain(self): # Both ABIs generate the same function selector, but with differently ordered parameter names, so # decoding will be different - abi = Abi(abi_json=example_abi, relevance=1, source_id=source.id) + abi = Abi( + abi_json=cast(list[dict], example_abi), relevance=1, source_id=source.id + ) await abi.create() abi_reversed = Abi( - abi_json=example_swapped_abi, relevance=100, source_id=source.id + abi_json=cast(list[dict], example_swapped_abi), + relevance=100, + source_id=source.id, ) await abi_reversed.create() @@ -742,7 +770,9 @@ async def test_load_new_abis(self): # Add a new ABI source = AbiSource(name="local", url="") await source.create() - abi = Abi(abi_json=example_abi, relevance=1, source_id=source.id) + abi = Abi( + abi_json=cast(list[dict], example_abi), relevance=1, source_id=source.id + ) await abi.create() len_previous_selectors = len(decoder_service.fn_selectors_with_abis) self.assertEqual(await decoder_service.load_new_abis(), 1) @@ -762,7 +792,9 @@ async def _decoder_with_one_abi() -> tuple[DataDecoderService, AbiSource, Abi]: source = AbiSource(name="local", url="") await source.create() - abi = Abi(abi_json=example_abi, relevance=1, source_id=source.id) + abi = Abi( + abi_json=cast(list[dict], example_abi), relevance=1, source_id=source.id + ) await abi.create() assert await decoder_service.load_new_abis() == 1 return decoder_service, source, abi @@ -800,7 +832,9 @@ async def _drop_connection(last_id: int): async def test_load_new_abis_skips_a_malformed_abi(self): decoder_service, source, _ = await self._decoder_with_one_abi() - bad_abi = Abi(abi_json=malformed_abi, relevance=1, source_id=source.id) + bad_abi = Abi( + abi_json=cast(list[dict], malformed_abi), relevance=1, source_id=source.id + ) await bad_abi.create() good_abi = Abi(abi_json=comptroller_abi, relevance=1, source_id=source.id) await good_abi.create() diff --git a/pyproject.toml b/pyproject.toml index 3ceb3a4..395dfaa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ dynamic = ["version"] requires-python = ">=3.13" dependencies = [ "aio-pika==10.0.1", - "alembic==1.19.2", + "alembic==1.20.0", "async-lru==2.3.0", "asyncpg==0.31.0", "fastapi-camelcase==2.0.0", @@ -14,8 +14,8 @@ dependencies = [ "redis[hiredis]==8.1.0", "safe-eth-py==7.26.1", "setuptools>=84.0.0,<85", - "sqladmin[full]==0.31.1", - "sqlmodel==0.0.42", + "sqladmin[full]==0.32.0", + "sqlmodel==0.0.47", "taskiq==0.12.6", "taskiq-redis==1.2.3", "uvicorn-worker==0.4.0", diff --git a/uv.lock b/uv.lock index 56dbcbb..8d2df5a 100644 --- a/uv.lock +++ b/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 3 +revision = 5 requires-python = ">=3.13" resolution-markers = [ "python_full_version >= '3.15'", @@ -143,16 +143,16 @@ wheels = [ [[package]] name = "alembic" -version = "1.19.2" +version = "1.20.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "mako" }, { name = "sqlalchemy" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/34/10/181eecdd552217d0342492bd6f3b8a96e973083379aace3d3402830ddc03/alembic-1.19.2.tar.gz", hash = "sha256:297950a8a91f6770eb82bfbce9bea55c728b90a5386c6e81430191a319d138b0", size = 2082643, upload-time = "2026-09-04T17:10:11.212Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ed/aa/02910bdb8e2f1444f6654d5b296cd827d126f82209050ee7b1000f92ac4b/alembic-1.20.0.tar.gz", hash = "sha256:db505480647bc60386c5369402f4a57a506b7539c9e9ef5e270d45cbbe4939bf", size = 2093272, upload-time = "2026-09-11T19:09:11.126Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9c/cb/9014784dcb0585977ae23b6f43331d0a33c51ac0d692506d12b4f5ee9f3b/alembic-1.19.2-py3-none-any.whl", hash = "sha256:32d553dcd577e6fe5c3c63e91468526d35e4dcecafe865d7db4e9b328fa93cb2", size = 267399, upload-time = "2026-09-04T17:10:12.796Z" }, + { url = "https://files.pythonhosted.org/packages/3f/27/78a89b55b0904d222183164e079b4ca56208e94eff1d35ad1f1ad5be9b06/alembic-1.20.0-py3-none-any.whl", hash = "sha256:77eb101048d95f982c0353e9233404889dcd7a6fc244c107836c0e2fc9cf7d9d", size = 268719, upload-time = "2026-09-11T19:09:12.88Z" }, ] [[package]] @@ -2467,7 +2467,7 @@ dev = [ [package.metadata] requires-dist = [ { name = "aio-pika", specifier = "==10.0.1" }, - { name = "alembic", specifier = "==1.19.2" }, + { name = "alembic", specifier = "==1.20.0" }, { name = "async-lru", specifier = "==2.3.0" }, { name = "asyncpg", specifier = "==0.31.0" }, { name = "fastapi", extras = ["standard-no-fastapi-cloud-cli"], specifier = "==0.141.1" }, @@ -2477,8 +2477,8 @@ requires-dist = [ { name = "redis", extras = ["hiredis"], specifier = "==8.1.0" }, { name = "safe-eth-py", specifier = "==7.26.1" }, { name = "setuptools", specifier = ">=84.0.0,<85" }, - { name = "sqladmin", extras = ["full"], specifier = "==0.31.1" }, - { name = "sqlmodel", specifier = "==0.0.42" }, + { name = "sqladmin", extras = ["full"], specifier = "==0.32.0" }, + { name = "sqlmodel", specifier = "==0.0.47" }, { name = "taskiq", specifier = "==0.12.6" }, { name = "taskiq-redis", specifier = "==1.2.3" }, { name = "uvicorn-worker", specifier = "==0.4.0" }, @@ -2563,7 +2563,7 @@ wheels = [ [[package]] name = "sqladmin" -version = "0.31.1" +version = "0.32.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jinja2" }, @@ -2572,9 +2572,9 @@ dependencies = [ { name = "starlette" }, { name = "wtforms" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6f/74/277739921c1f8d44a9a40fda6efd18046b5f277c4ba544e36c324bfc9ca1/sqladmin-0.31.1.tar.gz", hash = "sha256:27f39dd851645ac3f059cf98e91372898e485c8c63a9e60eb287af2dcc571e3f", size = 1012836, upload-time = "2026-09-01T06:53:12.786Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a7/ea/ba6e016faf975531fd9911e3ae11093b86894d176c86b9a0ba3b8177fa4a/sqladmin-0.32.0.tar.gz", hash = "sha256:4433dd89f6442856efa8f1ed1acf4b1b8ba67abe15ca54f9364c043ed301c823", size = 1029593, upload-time = "2026-09-20T23:18:00.088Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/13/c7/b0e1edf56b637117527891ec5cbed0ca27b32893ed872f9913d8d9ceb9fb/sqladmin-0.31.1-py3-none-any.whl", hash = "sha256:1b74d40d0585268d71bd7653e9d99442bbc3e6e3112a670bd53e29d225be7659", size = 1045322, upload-time = "2026-09-01T06:53:11.474Z" }, + { url = "https://files.pythonhosted.org/packages/a3/89/df5319e0d786a53929f9eb44711d759fd3b7fa56b5c95cbb056250b99a4b/sqladmin-0.32.0-py3-none-any.whl", hash = "sha256:4f5cff4ab8e5349a3dcad17f3a4d46a7867b54b92ac45e5626fdb96e465c127f", size = 1071265, upload-time = "2026-09-20T23:17:58.685Z" }, ] [package.optional-dependencies] @@ -2585,44 +2585,50 @@ full = [ [[package]] name = "sqlalchemy" -version = "2.0.52" +version = "2.0.54" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "greenlet", marker = "platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64'" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3b/21/77b4c147963073040dc3c3a5cb7a8c3001a1893c0209432cb77f9df836aa/sqlalchemy-2.0.52.tar.gz", hash = "sha256:5e2d46356ac2ccb7d268ab6c2319ac6a2b42f1b8d5fd8bd3d46855cd82abee97", size = 9945637, upload-time = "2026-08-11T19:07:09.829Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7f/18/e30c6fe1eca1bf34a39fbdd6066121cc9974c850faf6f349eac563697a26/sqlalchemy-2.0.52-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2eb3c6a64b1bfe6704777cfd504e7b8ad093a5f3e03ce67663a5e6742f294e43", size = 2167724, upload-time = "2026-08-11T20:58:12.679Z" }, - { url = "https://files.pythonhosted.org/packages/d0/56/2e17d161a4f7ecc1c2ffb93e607b4e1898bb551b451b283235acb8f6ce47/sqlalchemy-2.0.52-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:923bb183c1dc64fdf7b717965e3d59938ec4f8b8710b419a21ce403e5da9a9e1", size = 3321189, upload-time = "2026-08-11T21:02:41.932Z" }, - { url = "https://files.pythonhosted.org/packages/cf/b8/8490916e893f3f8d74dc9cc54c078619364999dee37047a188e73abbc852/sqlalchemy-2.0.52-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:651d6d8782e80679e6151707c7b490834d46ada526328895abf567f25e63d29c", size = 3338185, upload-time = "2026-08-11T21:17:02.597Z" }, - { url = "https://files.pythonhosted.org/packages/8b/f7/752cc8ee453da222829b3f5c4613614bf750d97429363b70414fa10478e4/sqlalchemy-2.0.52-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b08cddb8989775e3c88799d86704bdfc3ee6e9846118201aa5997f16f27e3a15", size = 3271698, upload-time = "2026-08-11T21:02:43.963Z" }, - { url = "https://files.pythonhosted.org/packages/51/e6/074ade0c07b9e4c8e8bca46820320ed94df9702afdb6f2af06623068d2e6/sqlalchemy-2.0.52-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ab66fa9618269390d4dfa222f2f2f88f7bc4bf5da13905131b818217db7e8057", size = 3308936, upload-time = "2026-08-11T21:17:04.172Z" }, - { url = "https://files.pythonhosted.org/packages/66/07/557c0d04716705599227945ac14e0a17ad0338e899f37d8c2ddff4dcc663/sqlalchemy-2.0.52-cp313-cp313-win32.whl", hash = "sha256:c63bda077685c85ca513286547a531ba57e7a68cf0a7ed3bafcc2bbd18896f4d", size = 2127308, upload-time = "2026-08-11T21:14:53.879Z" }, - { url = "https://files.pythonhosted.org/packages/96/4e/226eda27654318ce525d043025221f689abef883da2c7126f9065121618c/sqlalchemy-2.0.52-cp313-cp313-win_amd64.whl", hash = "sha256:9876b09b9f1ce7398b0ffece585c0a911244c53191187341f6bcae640e133751", size = 2153876, upload-time = "2026-08-11T21:14:55.527Z" }, - { url = "https://files.pythonhosted.org/packages/d5/f5/71cb30af58c9b80a4e1fac0b73bb48f86d497a774a6a2eb6d2f1e657bb73/sqlalchemy-2.0.52-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:410d52be41d17f1a236d19520fbe776257dc16516ed06bd16d433311842aefd9", size = 2169537, upload-time = "2026-08-11T20:58:13.855Z" }, - { url = "https://files.pythonhosted.org/packages/4c/93/d07ebd645d1b07b6b5ed63450a70f063a346a7e0f2c8810daf2e532400cb/sqlalchemy-2.0.52-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dfe9ce533dbe4d0a2ae1486546619bd30b76bcd670539a44d910361376175f5e", size = 3319606, upload-time = "2026-08-11T21:02:45.829Z" }, - { url = "https://files.pythonhosted.org/packages/ae/5c/290c84c7c2566ecd3b65baaae0fddec9bc33b033b398a06123bb86fbfc6e/sqlalchemy-2.0.52-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:812bae5138bfc0aa46fb0686da0fc7f581f68e2bbb05bc24c3713bebaedd1437", size = 3323642, upload-time = "2026-08-11T21:17:05.675Z" }, - { url = "https://files.pythonhosted.org/packages/13/f5/2cc160590ca49173359557880b92a0572293ccb899e8f6cedf150c5a3ddf/sqlalchemy-2.0.52-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:50bff43b632a56fbf5ed9afdd76307e1512b62051bcd5afb341ae67205bbb6c8", size = 3268125, upload-time = "2026-08-11T21:02:47.649Z" }, - { url = "https://files.pythonhosted.org/packages/35/f3/ea8933fc9f7d1353e9c2ff9965eae687c4cef181120574591ed2fa0633e1/sqlalchemy-2.0.52-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:49565daf5af554f538e23aef1fc81a95a4e49658f152285e45c02f5fc44f04cd", size = 3289516, upload-time = "2026-08-11T21:17:07.267Z" }, - { url = "https://files.pythonhosted.org/packages/45/67/05cf86541c1e1716fca1e4a996954a439cd74501707cda607fb7cb02ef50/sqlalchemy-2.0.52-cp314-cp314-win32.whl", hash = "sha256:ab9da41e61b9979b910499d633b241df20c51ee5037e5405b11c2faac3cbe1a2", size = 2130249, upload-time = "2026-08-11T21:14:57.273Z" }, - { url = "https://files.pythonhosted.org/packages/96/d7/8ac6ffa1e36169e762ef65bd835046abb2251b1bc17f8f6708e14ed8d31f/sqlalchemy-2.0.52-cp314-cp314-win_amd64.whl", hash = "sha256:a593db51b3bae75db17a5738ad5f992244b3a03863f83c28117ee482c6a3f76d", size = 2156718, upload-time = "2026-08-11T21:14:58.667Z" }, - { url = "https://files.pythonhosted.org/packages/dc/4b/e01a737eef378e734cc6394a82248a6ce13b167dfa36c731075ce9fc9c64/sqlalchemy-2.0.52-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1e61d08bdf4ee2f41024569e3400de7d6734ba498144766b11260936ccfa582", size = 2190344, upload-time = "2026-08-11T19:53:21.393Z" }, - { url = "https://files.pythonhosted.org/packages/b3/3f/3582293d1e185e71d19d7c731c3e2ee20ba21981c4a1115c0806c1f62120/sqlalchemy-2.0.52-py3-none-any.whl", hash = "sha256:3b81b8363a919ce53453591cdb93702e6bd54ade6c4fa2f468fc053baee5ed89", size = 1950700, upload-time = "2026-08-11T20:47:21.603Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/29/9c/271aa905cf2964f841371a97f3e63ab692bf51b4423d0491e67bc7f64037/sqlalchemy-2.0.54.tar.gz", hash = "sha256:baa8521e8ee9f24e75dfc7aaabc08020e551ef0d48d7c3e3536f5cddf277586b", size = 9969559, upload-time = "2026-09-15T21:06:57.337Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/aa/1c/021ccd7a838425f52e67c02edc541e8d53be8a27a103e684e14b18aac17b/sqlalchemy-2.0.54-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b374e3bc91e246a942592a98ba6a23be76fff21358b00546ac8c0ebc0fd0e00b", size = 2184259, upload-time = "2026-09-15T22:28:59.153Z" }, + { url = "https://files.pythonhosted.org/packages/74/8f/f95de908a4af7ac3a6925cdcb837a93274f608925a0fbc786df38811bccf/sqlalchemy-2.0.54-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:31d5458672a6f72db2c087f4a5098b3c8503ea0254186ff29205d63afa9401a4", size = 3398792, upload-time = "2026-09-15T22:29:35.218Z" }, + { url = "https://files.pythonhosted.org/packages/84/26/bd327a1a6be223e438c98ac8faf299307d83797225a81f3b209607a9fd98/sqlalchemy-2.0.54-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cad78d04254967bdbcccbed5e631d88fe4868530946ab0929aa45e9032849518", size = 3417562, upload-time = "2026-09-15T22:40:32.637Z" }, + { url = "https://files.pythonhosted.org/packages/4b/b1/f13a8fe8e8e167d8e65484b91b827897b8a5ca1efd7ba6e87a5f63412fed/sqlalchemy-2.0.54-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:48611087a75d26d798003645c688c7d3cfc26b89dbe4a2c568d6b378d330deae", size = 3349138, upload-time = "2026-09-15T22:29:36.729Z" }, + { url = "https://files.pythonhosted.org/packages/b1/b7/5fd58f03281a74c57f02f509433e1be58a79d9f5b0e14020e665e0dcb614/sqlalchemy-2.0.54-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d6adf80277372a89910a0f3ccfe960b846d279dc55b366dd5c5ec07f41c84758", size = 3382943, upload-time = "2026-09-15T22:40:34.587Z" }, + { url = "https://files.pythonhosted.org/packages/4b/73/e29fa88dfc809857a55e6523911d345d00cab4adf87032c21ade8874223c/sqlalchemy-2.0.54-cp313-cp313-win32.whl", hash = "sha256:264460333ed0b177cbb1956355d0ee4e0cab83fb415c934ce12a25db2e7be39c", size = 2138931, upload-time = "2026-09-15T22:42:51.801Z" }, + { url = "https://files.pythonhosted.org/packages/89/9d/885e7491836f3dad368c74f0d5b93551d37b0ec66c9e8753240ee988308f/sqlalchemy-2.0.54-cp313-cp313-win_amd64.whl", hash = "sha256:cf89e92bf0d4204a6afcc17af27b9271ed9c7e34e17d6f80c085d431ea4a1747", size = 2166496, upload-time = "2026-09-15T22:42:53.603Z" }, + { url = "https://files.pythonhosted.org/packages/ab/c0/4a6503c9d22d6d00a5631082ab1484222ecf7d573db791e0f53161bf7745/sqlalchemy-2.0.54-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:abd6b21bc58e91c1932eb5d6d7f1bd44a551dfec7b6a7f517c3638ccd67233a0", size = 2185899, upload-time = "2026-09-15T22:29:00.581Z" }, + { url = "https://files.pythonhosted.org/packages/12/28/f4424f618bd1f373761a32a821d53ce2c257350e9894bae9b968cb03d8fd/sqlalchemy-2.0.54-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5417322b3c025dd82918725d3bf09ec105fac95efc195722b8b06e1d9c381139", size = 3394763, upload-time = "2026-09-15T22:29:38.131Z" }, + { url = "https://files.pythonhosted.org/packages/59/d2/7f0c77f8e042cb5f28275fea29c3080b4ac6fd4b3fdd7f59ff1ef3e28c11/sqlalchemy-2.0.54-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f84099e4b04a5c2d44500a2a8302eee5af4bc6fee63e8c6e9cf6786e747280e", size = 3402800, upload-time = "2026-09-15T22:40:36.509Z" }, + { url = "https://files.pythonhosted.org/packages/af/32/3eaa930bcf71d17a72587081d2706a5fb97ab3f11a7e0fb838f581f7cff1/sqlalchemy-2.0.54-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a0956dc754d3884da7fe60097110ec7a8a105d26afa2f0844468f4b1598c6912", size = 3341469, upload-time = "2026-09-15T22:29:39.682Z" }, + { url = "https://files.pythonhosted.org/packages/eb/cc/cddb6cbd4408e5c55b3bf722be26b9d3b54d42509f901b7ecc15debd3d1f/sqlalchemy-2.0.54-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:87ba8834318b0d8dc94fc6f405d071b5c08be32a6c3fd68107fd6952ee949615", size = 3373232, upload-time = "2026-09-15T22:40:39.181Z" }, + { url = "https://files.pythonhosted.org/packages/34/2f/9c2aa5efc642b7f3b985d13565cd1a5e78856e079ef3022796fea5180498/sqlalchemy-2.0.54-cp314-cp314-win32.whl", hash = "sha256:842540e4382472f23c79589995752648d14696a8200d0807ed8c5c59c92ade44", size = 2142018, upload-time = "2026-09-15T22:42:55.118Z" }, + { url = "https://files.pythonhosted.org/packages/e2/0b/3594f1f51769feb3022d686135dc5d8682a12345ed15ae61d0c0ca42cbee/sqlalchemy-2.0.54-cp314-cp314-win_amd64.whl", hash = "sha256:f4e8f955d13af83fb4e35c3472e5377ee22d3445eada1e5e48199588edb69835", size = 2169220, upload-time = "2026-09-15T22:42:56.727Z" }, + { url = "https://files.pythonhosted.org/packages/c3/a5/c211a9a7af83222509519407e16a4db760c6df3d03be69ebc5414d465321/sqlalchemy-2.0.54-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ca05f4e7852cf48083b0cf157e4f9504b7068780422a50fa82f45353b8c5e14a", size = 2208458, upload-time = "2026-09-15T22:30:05.718Z" }, + { url = "https://files.pythonhosted.org/packages/cb/2e/490ad7b3731116cb48ba170f7722eaa99a89707193e54389ca84b7ad55af/sqlalchemy-2.0.54-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:18a8b6417cbb7b735cf91c2b59453c2a554cefa0a8d7bd15aa35740739410d77", size = 3660585, upload-time = "2026-09-15T22:36:06.649Z" }, + { url = "https://files.pythonhosted.org/packages/eb/25/15dfe6814847eeda773bd58ab6cf42a94b0176e5cf25a578fc1165777160/sqlalchemy-2.0.54-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4e55a0b96a1577a1e108c91ccdeeb9cd92768f28ce206597311c3bf6d6423abd", size = 3624442, upload-time = "2026-09-15T22:36:38.377Z" }, + { url = "https://files.pythonhosted.org/packages/aa/19/724d0a6a2fb2a86ff2d6008e581c258f722d9b7d8e52adc7b79085febdd4/sqlalchemy-2.0.54-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:69cab115c40fd02c5a22c68e4ee630fa6ef9a1650f1de944419aab1f7096fc4f", size = 3562972, upload-time = "2026-09-15T22:36:08.581Z" }, + { url = "https://files.pythonhosted.org/packages/49/bb/9df1bd81c2f2d000cf5e7a1b1a9b331468a3aab939ad983355e701fa42b2/sqlalchemy-2.0.54-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e08397c6c42f53b2488acde9108b8bfefd52d7afd1bf2f03d2ffcab7a204aceb", size = 3576479, upload-time = "2026-09-15T22:36:40.272Z" }, + { url = "https://files.pythonhosted.org/packages/df/c0/b5775465d3b89061d7c46057c31c56ff8fb6c509550b2b0c6570ffc248b3/sqlalchemy-2.0.54-cp314-cp314t-win32.whl", hash = "sha256:b9086b8ad48280ef6a7ba68262d5e44f7db1c4cb1973e8cdae8a9f467ae66f51", size = 2174794, upload-time = "2026-09-15T22:31:45.925Z" }, + { url = "https://files.pythonhosted.org/packages/77/f8/296c2e46b4ccd3f29b00b954ef2f195dde32f98e352ed21de1d292cedc0d/sqlalchemy-2.0.54-cp314-cp314t-win_amd64.whl", hash = "sha256:b67c1744e453af833667fc1b84de07adb4a64f3536ef52a8ec5ac2b941d43970", size = 2211942, upload-time = "2026-09-15T22:31:47.368Z" }, + { url = "https://files.pythonhosted.org/packages/24/a1/bd5e3e99bc9c8863b51ac5b9b03008a7f2da8c6b59695992f5c654e1265b/sqlalchemy-2.0.54-py3-none-any.whl", hash = "sha256:7e33a631ab1474f8fe6b910bd1a07b7b8009c4c78cdd3fb18001b03e3bc2e1d2", size = 1958015, upload-time = "2026-09-15T22:24:22.95Z" }, ] [[package]] name = "sqlmodel" -version = "0.0.42" +version = "0.0.47" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pydantic" }, { name = "sqlalchemy" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ab/50/27188d8cbccabf9968d74d4095765399dbc4f9fcae95e928fda69aa44a21/sqlmodel-0.0.42.tar.gz", hash = "sha256:9ecec2b6aa4c2aa58da39d2572e7bef4373419dcd6ec52973c07de7be2b1c516", size = 91737, upload-time = "2026-08-28T19:42:01.611Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4f/60/aa4cb68f03fa6bd1ca9e9a9ca89c3bc6c67f50981d0abcd9d6364609516d/sqlmodel-0.0.47.tar.gz", hash = "sha256:3606430ea6db7f00c8ae2157458b9766644ea3ad365c601e26a86cf23f5910d5", size = 98136, upload-time = "2026-09-23T17:31:07.921Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/67/37/d7d6b12b9066005c237de3d52b599298a2693ac423bff0e6b97a90b90bf8/sqlmodel-0.0.42-py3-none-any.whl", hash = "sha256:a732dab1a40e5cbd6ac250cf2b12e8d9b1bf1a47edf27c34d612f359e8875c63", size = 29991, upload-time = "2026-08-28T19:42:00.568Z" }, + { url = "https://files.pythonhosted.org/packages/f7/b3/988928029b0e68de7ac587c2e1ccd379aa18e7c0aa7cc426628145a032fe/sqlmodel-0.0.47-py3-none-any.whl", hash = "sha256:165294386c3e4b25fa02f151ca1abb0cbf5fdb76f6093a38aca33fdc30dfd485", size = 31314, upload-time = "2026-09-23T17:31:06.8Z" }, ] [[package]] From 05866568b394268f4ebe63c461f9210d155a07f1 Mon Sep 17 00:00:00 2001 From: Uxio Fuentefria Date: Mon, 5 Oct 2026 09:51:19 +0000 Subject: [PATCH 2/2] Type Abi.abi_json with Mapping instead of casting `list[dict] | dict` does not accept the `eth_typing` ABI TypedDicts, so every caller needed a cast. `Sequence[Mapping[str, Any]] | Mapping[str, Any]` accepts both plain dicts and the ABI TypedDicts. The DB column is set by `sa_column`, so it does not change. --- app/datasources/db/models.py | 16 ++++--- app/services/abis.py | 6 +-- app/tests/routers/test_data_decoder.py | 10 +---- app/tests/services/test_data_decoder.py | 58 +++++-------------------- 4 files changed, 25 insertions(+), 65 deletions(-) diff --git a/app/datasources/db/models.py b/app/datasources/db/models.py index ea5dc32..d09a12a 100644 --- a/app/datasources/db/models.py +++ b/app/datasources/db/models.py @@ -1,8 +1,8 @@ # SPDX-License-Identifier: FSL-1.1-MIT import datetime import json -from collections.abc import AsyncIterator -from typing import Self, cast +from collections.abc import AsyncIterator, Mapping, Sequence +from typing import Any, Self, cast from eth_typing import ABI from sqlalchemy import ( @@ -32,6 +32,10 @@ from .database import db_session +# JSON stored in ``Abi.abi_json``. ``Mapping`` accepts both plain dicts and the +# ``eth_typing`` ABI TypedDicts. +type AbiJson = Sequence[Mapping[str, Any]] | Mapping[str, Any] + class SqlQueryBase: @classmethod @@ -123,9 +127,7 @@ class Abi(SqlQueryBase, TimeStampedSQLModel, table=True): ), ) relevance: int | None = Field(nullable=False, default=0, index=True) - abi_json: list[dict] | dict = Field( - sa_column=Column(JSON(none_as_null=True), nullable=False) - ) + abi_json: AbiJson = Field(sa_column=Column(JSON(none_as_null=True), nullable=False)) source_id: int | None = Field( nullable=False, default=None, foreign_key="abisource.id" ) @@ -183,7 +185,7 @@ async def create(self): @classmethod async def get_abi( cls, - abi_json: list[dict] | dict, + abi_json: AbiJson, ): """ Checks if an Abi with the given 'abi_json' exists by matching the @@ -207,7 +209,7 @@ async def get_abi( @classmethod async def get_or_create_abi( cls, - abi_json: list[dict] | dict, + abi_json: AbiJson, source_id: int | None, relevance: int | None = 0, ) -> tuple["Abi", bool]: diff --git a/app/services/abis.py b/app/services/abis.py index 48e2e0c..8b0ccf1 100644 --- a/app/services/abis.py +++ b/app/services/abis.py @@ -1,5 +1,4 @@ # SPDX-License-Identifier: FSL-1.1-MIT -from typing import cast from eth_typing import ABI from safe_eth.eth.contracts import ( @@ -75,11 +74,10 @@ async def _store_abis_in_database( abi_source: AbiSource, ) -> None: for abi_json in abi_jsons: - abi_dicts = cast(list[dict], abi_json) - abi = await Abi.get_abi(abi_dicts) + abi = await Abi.get_abi(abi_json) if abi is None: await Abi( - abi_json=abi_dicts, source_id=abi_source.id, relevance=relevance + abi_json=abi_json, source_id=abi_source.id, relevance=relevance ).create() async def load_local_abis_in_database(self) -> None: diff --git a/app/tests/routers/test_data_decoder.py b/app/tests/routers/test_data_decoder.py index 34ff6f7..958fac8 100644 --- a/app/tests/routers/test_data_decoder.py +++ b/app/tests/routers/test_data_decoder.py @@ -169,11 +169,7 @@ async def test_view_data_decoder_with_chain_id(self): source = AbiSource(name="local", url="") await source.create() - abi = Abi( - abi_json=cast(list[dict], example_abi), - relevance=101, - source_id=source.id, - ) + abi = Abi(abi_json=example_abi, relevance=101, source_id=source.id) await abi.create() contract = Contract( address=HexBytes(contract_address), @@ -184,9 +180,7 @@ async def test_view_data_decoder_with_chain_id(self): await contract.create() swapped_abi = Abi( - abi_json=cast(list[dict], example_swapped_abi), - relevance=100, - source_id=source.id, + abi_json=example_swapped_abi, relevance=100, source_id=source.id ) await swapped_abi.create() contract = Contract( diff --git a/app/tests/services/test_data_decoder.py b/app/tests/services/test_data_decoder.py index 6904134..f7826cd 100644 --- a/app/tests/services/test_data_decoder.py +++ b/app/tests/services/test_data_decoder.py @@ -60,26 +60,10 @@ async def _store_safe_contract_abi(): # Add Safe Contract Abi and decode it for abi in ( - Abi( - abi_json=cast(list[dict], erc20_contract.abi), - relevance=150, - source_id=source.id, - ), - Abi( - abi_json=cast(list[dict], safe_v1_1_1_contract.abi), - relevance=100, - source_id=source.id, - ), - Abi( - abi_json=cast(list[dict], safe_v1_4_1_contract.abi), - relevance=100, - source_id=source.id, - ), - Abi( - abi_json=cast(list[dict], multisend_contract.abi), - relevance=100, - source_id=source.id, - ), + Abi(abi_json=erc20_contract.abi, relevance=150, source_id=source.id), + Abi(abi_json=safe_v1_1_1_contract.abi, relevance=100, source_id=source.id), + Abi(abi_json=safe_v1_4_1_contract.abi, relevance=100, source_id=source.id), + Abi(abi_json=multisend_contract.abi, relevance=100, source_id=source.id), Abi(abi_json=gnosis_protocol_abi, relevance=50, source_id=source.id), Abi( abi_json=fleet_factory_deterministic_abi, @@ -119,9 +103,7 @@ async def test_init_skips_an_abi_without_selectors(self): await self._store_safe_contract_abi() source = AbiSource(name="broken", url="") await source.create() - broken_abi = Abi( - abi_json=cast(list[dict], malformed_abi), relevance=200, source_id=source.id - ) + broken_abi = Abi(abi_json=malformed_abi, relevance=200, source_id=source.id) await broken_abi.create() decoder_service = DataDecoderService() @@ -529,9 +511,7 @@ async def test_db_tx_decoder(self): source = AbiSource(name="local", url="") await source.create() # Test load a new DbTxDecoder - abi = Abi( - abi_json=cast(list[dict], example_abi), relevance=100, source_id=source.id - ) + abi = Abi(abi_json=example_abi, relevance=100, source_id=source.id) await abi.create() decoder_service = DataDecoderService() await decoder_service.init() @@ -539,11 +519,7 @@ async def test_db_tx_decoder(self): self.assertEqual(fn_name, "buyDroid") self.assertEqual(arguments, {"droidId": "4", "numberOfDroids": "10"}) - abi = Abi( - abi_json=cast(list[dict], example_swapped_abi), - relevance=100, - source_id=source.id, - ) + abi = Abi(abi_json=example_swapped_abi, relevance=100, source_id=source.id) await abi.create() contract = Contract(address=b"c", abi=abi, name="SwappedContract", chain_id=1) await contract.create() @@ -570,15 +546,11 @@ async def test_db_tx_decoder_multichain(self): # Both ABIs generate the same function selector, but with differently ordered parameter names, so # decoding will be different - abi = Abi( - abi_json=cast(list[dict], example_abi), relevance=1, source_id=source.id - ) + abi = Abi(abi_json=example_abi, relevance=1, source_id=source.id) await abi.create() abi_reversed = Abi( - abi_json=cast(list[dict], example_swapped_abi), - relevance=100, - source_id=source.id, + abi_json=example_swapped_abi, relevance=100, source_id=source.id ) await abi_reversed.create() @@ -770,9 +742,7 @@ async def test_load_new_abis(self): # Add a new ABI source = AbiSource(name="local", url="") await source.create() - abi = Abi( - abi_json=cast(list[dict], example_abi), relevance=1, source_id=source.id - ) + abi = Abi(abi_json=example_abi, relevance=1, source_id=source.id) await abi.create() len_previous_selectors = len(decoder_service.fn_selectors_with_abis) self.assertEqual(await decoder_service.load_new_abis(), 1) @@ -792,9 +762,7 @@ async def _decoder_with_one_abi() -> tuple[DataDecoderService, AbiSource, Abi]: source = AbiSource(name="local", url="") await source.create() - abi = Abi( - abi_json=cast(list[dict], example_abi), relevance=1, source_id=source.id - ) + abi = Abi(abi_json=example_abi, relevance=1, source_id=source.id) await abi.create() assert await decoder_service.load_new_abis() == 1 return decoder_service, source, abi @@ -832,9 +800,7 @@ async def _drop_connection(last_id: int): async def test_load_new_abis_skips_a_malformed_abi(self): decoder_service, source, _ = await self._decoder_with_one_abi() - bad_abi = Abi( - abi_json=cast(list[dict], malformed_abi), relevance=1, source_id=source.id - ) + bad_abi = Abi(abi_json=malformed_abi, relevance=1, source_id=source.id) await bad_abi.create() good_abi = Abi(abi_json=comptroller_abi, relevance=1, source_id=source.id) await good_abi.create()