From ec57cb95dc04c7827e0cfb44bd2f3689887cca2c Mon Sep 17 00:00:00 2001 From: DaveyLupes Date: Wed, 30 Apr 2025 15:04:00 +0200 Subject: [PATCH 1/3] feat: add listings endpoint with pagination, filters, and tests --- core/serializers.py | 25 ++++ core/tests/factories.py | 26 ++++ ...ted_tokens..py => test_accepted_tokens.py} | 0 core/tests/test_listings.py | 69 ++++++++++ core/urls.py | 5 + core/views.py | 32 ++++- poetry.lock | 130 +++++++++++++++--- pyproject.toml | 1 + pytest.ini | 4 + 9 files changed, 273 insertions(+), 19 deletions(-) rename core/tests/{test_accepted_tokens..py => test_accepted_tokens.py} (100%) create mode 100644 core/tests/test_listings.py create mode 100644 pytest.ini diff --git a/core/serializers.py b/core/serializers.py index cf1dcf8..13ccf9a 100644 --- a/core/serializers.py +++ b/core/serializers.py @@ -3,6 +3,7 @@ from rest_framework import generics, serializers from . import exceptions, models +from .models import Listing from .service import CoreService @@ -234,3 +235,27 @@ def validate(self, attrs): def save(self): # delete the offer CoreService.cancel_offer(self.validated_data["offer_id"]) + + +class ListingSerializer(serializers.ModelSerializer): + listing_id = serializers.UUIDField(source="id", read_only=True) + collateral_contract = serializers.CharField( + source="nft_contract_address", read_only=True + ) + collateral_id = serializers.IntegerField(source="nft_token_id", read_only=True) + borrower_address = serializers.CharField(source="user.public_key", read_only=True) + loan_amount = serializers.IntegerField(source="borrow_amount", read_only=True) + loan_duration = serializers.IntegerField(source="duration", read_only=True) + + class Meta: + model = Listing + fields = [ + "listing_id", + "collateral_contract", + "collateral_id", + "borrower_address", + "loan_amount", + "loan_duration", + "created_at", + "status", + ] diff --git a/core/tests/factories.py b/core/tests/factories.py index 97f631b..d233545 100644 --- a/core/tests/factories.py +++ b/core/tests/factories.py @@ -25,3 +25,29 @@ class Meta: @factory.lazy_attribute def contract_address(self): return "0x" + "".join(random.choices("0123456789abcdef", k=60)) + + token_decimal = factory.Faker("pyint", min_value=6, max_value=18) + + +class UserFactory(factory.django.DjangoModelFactory): + class Meta: + model = models.User + + public_key = factory.LazyFunction( + lambda: "0x" + "".join(random.choices("0123456789abcdef", k=60)) + ) + + +class ListingFactory(factory.django.DjangoModelFactory): + class Meta: + model = models.Listing + + user = factory.SubFactory("core.factories.UserFactory") + nft_contract_address = factory.LazyFunction( + lambda: "0x" + "".join(random.choices("0123456789abcdef", k=60)) + ) + nft_token_id = factory.Sequence(lambda n: n) + borrow_amount = factory.Faker("pyint", min_value=100, max_value=10000) + repayment_amount = factory.Faker("pyint", min_value=110, max_value=11000) + duration = factory.Faker("pyint", min_value=1, max_value=365) + status = models.ListingStatus.OPEN diff --git a/core/tests/test_accepted_tokens..py b/core/tests/test_accepted_tokens.py similarity index 100% rename from core/tests/test_accepted_tokens..py rename to core/tests/test_accepted_tokens.py diff --git a/core/tests/test_listings.py b/core/tests/test_listings.py new file mode 100644 index 0000000..e27d577 --- /dev/null +++ b/core/tests/test_listings.py @@ -0,0 +1,69 @@ +import pytest +from django.urls import reverse +from rest_framework.test import APIClient + +from core.models import ListingStatus +from core.tests import factories + + +@pytest.mark.django_db +def test_list_active_listings(): + client = APIClient() + url = reverse("listing-list") + + user = factories.UserFactory() + + active_listing = factories.ListingFactory(user=user, status=ListingStatus.OPEN) + factories.ListingFactory(user=user, status=ListingStatus.CLOSED) + + response = client.get(url) + assert response.status_code == 200 + + data = response.json() + results = data["results"] + assert len(results) == 1 + + listing = results[0] + assert listing["listing_id"] == str(active_listing.id) + assert listing["collateral_contract"] == active_listing.nft_contract_address + assert listing["collateral_id"] == active_listing.nft_token_id + assert listing["borrower_address"] == active_listing.user.public_key + assert listing["loan_amount"] == active_listing.borrow_amount + assert listing["loan_duration"] == active_listing.duration + assert listing["status"] == active_listing.status + + +@pytest.mark.django_db +def test_listings_filter_by_collateral_contract(): + client = APIClient() + url = reverse("listing-list") + + user = factories.UserFactory() + factories.ListingFactory( + user=user, nft_contract_address="0xFilterThis", status=ListingStatus.OPEN + ) + + response = client.get(url, {"collateral_contract": "0xFilterThis"}) + assert response.status_code == 200 + + data = response.json() + results = data["results"] + assert len(results) == 1 + assert results[0]["collateral_contract"] == "0xFilterThis" + + +@pytest.mark.django_db +def test_listings_filter_by_borrower_address(): + client = APIClient() + url = reverse("listing-list") + + user = factories.UserFactory(public_key="0xBorrowerFilter") + factories.ListingFactory(user=user, status=ListingStatus.OPEN) + + response = client.get(url, {"borrower_address": "0xBorrowerFilter"}) + assert response.status_code == 200 + + data = response.json() + results = data["results"] + assert len(results) == 1 + assert results[0]["borrower_address"] == "0xBorrowerFilter" diff --git a/core/urls.py b/core/urls.py index 7642312..91e6f69 100644 --- a/core/urls.py +++ b/core/urls.py @@ -18,6 +18,11 @@ views.SignInAPIView.as_view(), name="signin", ), + path( + "listings/", + views.ListingListAPIView.as_view(), + name="listing-list", + ), path("offer/create/", views.OfferCreateAPIView.as_view(), name="create-offer"), path("offer/cancel/", views.OfferCancelAPIView.as_view(), name="cancel-offer"), ] diff --git a/core/views.py b/core/views.py index fd3602e..33e8330 100644 --- a/core/views.py +++ b/core/views.py @@ -1,10 +1,13 @@ # Create your views here. -from rest_framework import status +from rest_framework import filters, status from rest_framework.generics import GenericAPIView, ListAPIView +from rest_framework.pagination import PageNumberPagination from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from . import models, serializers +from .models import Listing, ListingStatus +from .serializers import ListingSerializer class AcceptedNFTListAPIView(ListAPIView): @@ -50,3 +53,30 @@ def post(self, request): serializer.is_valid(raise_exception=True) serializer.save() return Response(status=status.HTTP_204_NO_CONTENT) + + +class ListingPagination(PageNumberPagination): + page_size = 10 + page_size_query_param = "page_size" + max_page_size = 100 + + +class ListingListAPIView(ListAPIView): + serializer_class = ListingSerializer + pagination_class = ListingPagination + filter_backends = [filters.SearchFilter] + search_fields = ["nft_contract_address", "user__public_key"] + + def get_queryset(self): + queryset = Listing.objects.filter(status=ListingStatus.OPEN).order_by( + "-created_at" + ) + collateral_contract = self.request.query_params.get("collateral_contract") + borrower_address = self.request.query_params.get("borrower_address") + + if collateral_contract: + queryset = queryset.filter(nft_contract_address__iexact=collateral_contract) + if borrower_address: + queryset = queryset.filter(user__public_key__iexact=borrower_address) + + return queryset diff --git a/poetry.lock b/poetry.lock index be7b8a4..c895b93 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.1.2 and should not be changed by hand. [[package]] name = "aiohappyeyeballs" @@ -6,6 +6,7 @@ version = "2.6.1" description = "Happy Eyeballs for asyncio" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, @@ -17,6 +18,7 @@ version = "3.11.13" description = "Async http client/server framework (asyncio)" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "aiohttp-3.11.13-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a4fe27dbbeec445e6e1291e61d61eb212ee9fed6e47998b27de71d70d3e8777d"}, {file = "aiohttp-3.11.13-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9e64ca2dbea28807f8484c13f684a2f761e69ba2640ec49dacd342763cc265ef"}, @@ -111,7 +113,7 @@ propcache = ">=0.2.0" yarl = ">=1.17.0,<2.0" [package.extras] -speedups = ["Brotli", "aiodns (>=3.2.0)", "brotlicffi"] +speedups = ["Brotli ; platform_python_implementation == \"CPython\"", "aiodns (>=3.2.0) ; sys_platform == \"linux\" or sys_platform == \"darwin\"", "brotlicffi ; platform_python_implementation != \"CPython\""] [[package]] name = "aiosignal" @@ -119,6 +121,7 @@ version = "1.3.2" description = "aiosignal: a list of registered asynchronous callbacks" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "aiosignal-1.3.2-py2.py3-none-any.whl", hash = "sha256:45cde58e409a301715980c2b01d0c28bdde3770d8290b5eb2173759d9acb31a5"}, {file = "aiosignal-1.3.2.tar.gz", hash = "sha256:a8c255c66fafb1e499c9351d0bf32ff2d8a0321595ebac3b93713656d2436f54"}, @@ -133,6 +136,7 @@ version = "3.8.1" description = "ASGI specs, helper code, and adapters" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "asgiref-3.8.1-py3-none-any.whl", hash = "sha256:3e1e3ecc849832fe52ccf2cb6686b7a55f82bb1d6aee72a58826471390335e47"}, {file = "asgiref-3.8.1.tar.gz", hash = "sha256:c343bd80a0bec947a9860adb4c432ffa7db769836c64238fc34bdc3fec84d590"}, @@ -147,18 +151,19 @@ version = "25.3.0" description = "Classes Without Boilerplate" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3"}, {file = "attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b"}, ] [package.extras] -benchmark = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -cov = ["cloudpickle", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit-uv", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +benchmark = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +cov = ["cloudpickle ; platform_python_implementation == \"CPython\"", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +dev = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pre-commit-uv", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier"] -tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"] +tests = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +tests-mypy = ["mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\""] [[package]] name = "black" @@ -166,6 +171,7 @@ version = "23.12.1" description = "The uncompromising code formatter." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "black-23.12.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0aaf6041986767a5e0ce663c7a2f0e9eaf21e6ff87a5f95cbf3675bfd4c41d2"}, {file = "black-23.12.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c88b3711d12905b74206227109272673edce0cb29f27e1385f33b0163c414bba"}, @@ -200,7 +206,7 @@ platformdirs = ">=2" [package.extras] colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] +d = ["aiohttp (>=3.7.4) ; sys_platform != \"win32\" or implementation_name != \"pypy\"", "aiohttp (>=3.7.4,!=3.9.0) ; sys_platform == \"win32\" and implementation_name == \"pypy\""] jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] uvloop = ["uvloop (>=0.15.2)"] @@ -210,6 +216,7 @@ version = "2.0.1" description = "A decorator for caching properties in classes." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "cached_property-2.0.1-py3-none-any.whl", hash = "sha256:f617d70ab1100b7bcf6e42228f9ddcb78c676ffa167278d9f730d1c2fba69ccb"}, {file = "cached_property-2.0.1.tar.gz", hash = "sha256:484d617105e3ee0e4f1f58725e72a8ef9e93deee462222dbd51cd91230897641"}, @@ -221,6 +228,7 @@ version = "3.4.0" description = "Validate configuration and produce human readable error messages." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, @@ -232,6 +240,7 @@ version = "8.1.8" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, @@ -246,6 +255,8 @@ version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["dev"] +markers = "platform_system == \"Windows\" or sys_platform == \"win32\"" files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, @@ -257,6 +268,7 @@ version = "7.6.12" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "coverage-7.6.12-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:704c8c8c6ce6569286ae9622e534b4f5b9759b6f2cd643f1c1a61f666d534fe8"}, {file = "coverage-7.6.12-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ad7525bf0241e5502168ae9c643a2f6c219fa0a283001cee4cf23a9b7da75879"}, @@ -324,7 +336,7 @@ files = [ ] [package.extras] -toml = ["tomli"] +toml = ["tomli ; python_full_version <= \"3.11.0a6\""] [[package]] name = "crypto-cpp-py" @@ -332,6 +344,7 @@ version = "1.4.5" description = "This is a packaged crypto-cpp program" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "crypto_cpp_py-1.4.5-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:31f41a942fb6e90d7adc90deb8a9f6d0d1ad73fa6d4aff4fa8ae85185efb8a4e"}, {file = "crypto_cpp_py-1.4.5-cp310-cp310-macosx_13_0_universal2.whl", hash = "sha256:39f248661fa9cd152f18d4f499ba03638cd0fd28d911c141f84ecd9f15a3f5b4"}, @@ -415,6 +428,8 @@ version = "1.0.1" description = "Cython implementation of Toolz: High performance functional utilities" optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "implementation_name == \"cpython\"" files = [ {file = "cytoolz-1.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cec9af61f71fc3853eb5dca3d42eb07d1f48a4599fa502cbe92adde85f74b042"}, {file = "cytoolz-1.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:140bbd649dbda01e91add7642149a5987a7c3ccc251f2263de894b89f50b6608"}, @@ -530,6 +545,7 @@ version = "0.3.9" description = "Distribution utilities" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87"}, {file = "distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403"}, @@ -541,6 +557,7 @@ version = "2.3.0" description = "Use Database URLs in your Django Application." optional = false python-versions = "*" +groups = ["main"] files = [ {file = "dj_database_url-2.3.0-py3-none-any.whl", hash = "sha256:bb0d414ba0ac5cd62773ec7f86f8cc378a9dbb00a80884c2fc08cc570452521e"}, {file = "dj_database_url-2.3.0.tar.gz", hash = "sha256:ae52e8e634186b57e5a45e445da5dc407a819c2ceed8a53d1fac004cc5288787"}, @@ -556,6 +573,7 @@ version = "4.2" description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "Django-4.2-py3-none-any.whl", hash = "sha256:ad33ed68db9398f5dfb33282704925bce044bef4261cd4fb59e4e7f9ae505a78"}, {file = "Django-4.2.tar.gz", hash = "sha256:c36e2ab12824e2ac36afa8b2515a70c53c7742f0d6eaefa7311ec379558db997"}, @@ -576,6 +594,7 @@ version = "4.7.0" description = "django-cors-headers is a Django application for handling the server headers required for Cross-Origin Resource Sharing (CORS)." optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "django_cors_headers-4.7.0-py3-none-any.whl", hash = "sha256:f1c125dcd58479fe7a67fe2499c16ee38b81b397463cf025f0e2c42937421070"}, {file = "django_cors_headers-4.7.0.tar.gz", hash = "sha256:6fdf31bf9c6d6448ba09ef57157db2268d515d94fc5c89a0a1028e1fc03ee52b"}, @@ -591,6 +610,7 @@ version = "5.0.2" description = "Authentication for django rest framework" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "django_rest_knox-5.0.2-py3-none-any.whl", hash = "sha256:694da5d0ad6eb3edbfd7cdc8d69c089fc074e6b0e548e00ff2750bf2fdfadb6f"}, {file = "django_rest_knox-5.0.2.tar.gz", hash = "sha256:f283622bcf5d28a6a0203845c065d06c7432efa54399ae32070c61ac03af2d6f"}, @@ -606,6 +626,7 @@ version = "3.15.2" description = "Web APIs for Django, made easy." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "djangorestframework-3.15.2-py3-none-any.whl", hash = "sha256:2b8871b062ba1aefc2de01f773875441a961fefbf79f5eed1e32b2f096944b20"}, {file = "djangorestframework-3.15.2.tar.gz", hash = "sha256:36fe88cd2d6c6bec23dca9804bab2ba5517a8bb9d8f47ebc68981b56840107ad"}, @@ -620,6 +641,7 @@ version = "5.5.0" description = "A minimal JSON Web Token authentication plugin for Django REST Framework" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "djangorestframework_simplejwt-5.5.0-py3-none-any.whl", hash = "sha256:4ef6b38af20cdde4a4a51d1fd8e063cbbabb7b45f149cc885d38d905c5a62edb"}, {file = "djangorestframework_simplejwt-5.5.0.tar.gz", hash = "sha256:474a1b737067e6462b3609627a392d13a4da8a08b1f0574104ac6d7b1406f90e"}, @@ -644,6 +666,7 @@ version = "0.18.0" description = "ECDSA cryptographic signature library (pure python)" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +groups = ["main"] files = [ {file = "ecdsa-0.18.0-py2.py3-none-any.whl", hash = "sha256:80600258e7ed2f16b9aa1d7c295bd70194109ad5a30fdee0eaeefef1d4c559dd"}, {file = "ecdsa-0.18.0.tar.gz", hash = "sha256:190348041559e21b22a1d65cee485282ca11a6f81d503fddb84d5017e9ed1e49"}, @@ -662,6 +685,7 @@ version = "0.7.1" description = "eth-hash: The Ethereum hashing function, keccak256, sometimes (erroneously) called sha3" optional = false python-versions = "<4,>=3.8" +groups = ["main"] files = [ {file = "eth_hash-0.7.1-py3-none-any.whl", hash = "sha256:0fb1add2adf99ef28883fd6228eb447ef519ea72933535ad1a0b28c6f65f868a"}, {file = "eth_hash-0.7.1.tar.gz", hash = "sha256:d2411a403a0b0a62e8247b4117932d900ffb4c8c64b15f92620547ca5ce46be5"}, @@ -671,7 +695,7 @@ files = [ dev = ["build (>=0.9.0)", "bump_my_version (>=0.19.0)", "ipython", "mypy (==1.10.0)", "pre-commit (>=3.4.0)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)", "sphinx (>=6.0.0)", "sphinx-autobuild (>=2021.3.14)", "sphinx_rtd_theme (>=1.0.0)", "towncrier (>=24,<25)", "tox (>=4.0.0)", "twine", "wheel"] docs = ["sphinx (>=6.0.0)", "sphinx-autobuild (>=2021.3.14)", "sphinx_rtd_theme (>=1.0.0)", "towncrier (>=24,<25)"] pycryptodome = ["pycryptodome (>=3.6.6,<4)"] -pysha3 = ["pysha3 (>=1.0.0,<2.0.0)", "safe-pysha3 (>=1.0.0)"] +pysha3 = ["pysha3 (>=1.0.0,<2.0.0) ; python_version < \"3.9\"", "safe-pysha3 (>=1.0.0) ; python_version >= \"3.9\""] test = ["pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] [[package]] @@ -680,6 +704,7 @@ version = "0.9.1" description = "eth-keyfile: A library for handling the encrypted keyfiles used to store ethereum private keys" optional = false python-versions = "<4,>=3.8" +groups = ["main"] files = [ {file = "eth_keyfile-0.9.1-py3-none-any.whl", hash = "sha256:9789c3b4fa0bb6e2616cdc2bdd71b8755b42947d78ef1e900a0149480fabb5c2"}, {file = "eth_keyfile-0.9.1.tar.gz", hash = "sha256:c7a8bc6af4527d1ab2eb1d1b949d59925252e17663eaf90087da121327b51df6"}, @@ -702,6 +727,7 @@ version = "0.6.1" description = "eth-keys: Common API for Ethereum key operations" optional = false python-versions = "<4,>=3.8" +groups = ["main"] files = [ {file = "eth_keys-0.6.1-py3-none-any.whl", hash = "sha256:7deae4cd56e862e099ec58b78176232b931c4ea5ecded2f50c7b1ccbc10c24cf"}, {file = "eth_keys-0.6.1.tar.gz", hash = "sha256:a43e263cbcabfd62fa769168efc6c27b1f5603040e4de22bb84d12567e4fd962"}, @@ -723,6 +749,7 @@ version = "5.2.0" description = "eth-typing: Common type annotations for ethereum python packages" optional = false python-versions = "<4,>=3.8" +groups = ["main"] files = [ {file = "eth_typing-5.2.0-py3-none-any.whl", hash = "sha256:e1f424e97990fc3c6a1c05a7b0968caed4e20e9c99a4d5f4db3df418e25ddc80"}, {file = "eth_typing-5.2.0.tar.gz", hash = "sha256:28685f7e2270ea0d209b75bdef76d8ecef27703e1a16399f6929820d05071c28"}, @@ -742,6 +769,7 @@ version = "5.2.0" description = "eth-utils: Common utility functions for python code that interacts with Ethereum" optional = false python-versions = "<4,>=3.8" +groups = ["main"] files = [ {file = "eth_utils-5.2.0-py3-none-any.whl", hash = "sha256:4d43eeb6720e89a042ad5b28d4b2111630ae764f444b85cbafb708d7f076da10"}, {file = "eth_utils-5.2.0.tar.gz", hash = "sha256:17e474eb654df6e18f20797b22c6caabb77415a996b3ba0f3cc8df3437463134"}, @@ -764,6 +792,7 @@ version = "3.3.0" description = "A versatile test fixtures replacement based on thoughtbot's factory_bot for Ruby." optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "factory_boy-3.3.0-py2.py3-none-any.whl", hash = "sha256:a2cdbdb63228177aa4f1c52f4b6d83fab2b8623bf602c7dedd7eb83c0f69c04c"}, {file = "factory_boy-3.3.0.tar.gz", hash = "sha256:bc76d97d1a65bbd9842a6d722882098eb549ec8ee1081f9fb2e8ff29f0c300f1"}, @@ -782,6 +811,7 @@ version = "37.1.0" description = "Faker is a Python package that generates fake data for you." optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "faker-37.1.0-py3-none-any.whl", hash = "sha256:dc2f730be71cb770e9c715b13374d80dbcee879675121ab51f9683d262ae9a1c"}, {file = "faker-37.1.0.tar.gz", hash = "sha256:ad9dc66a3b84888b837ca729e85299a96b58fdaef0323ed0baace93c9614af06"}, @@ -796,6 +826,7 @@ version = "3.18.0" description = "A platform independent file lock." optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de"}, {file = "filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2"}, @@ -804,7 +835,7 @@ files = [ [package.extras] docs = ["furo (>=2024.8.6)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"] testing = ["covdefaults (>=2.3)", "coverage (>=7.6.10)", "diff-cover (>=9.2.1)", "pytest (>=8.3.4)", "pytest-asyncio (>=0.25.2)", "pytest-cov (>=6)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.28.1)"] -typing = ["typing-extensions (>=4.12.2)"] +typing = ["typing-extensions (>=4.12.2) ; python_version < \"3.11\""] [[package]] name = "frozenlist" @@ -812,6 +843,7 @@ version = "1.5.0" description = "A list-like structure which implements collections.abc.MutableSequence" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "frozenlist-1.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5b6a66c18b5b9dd261ca98dffcb826a525334b2f29e7caa54e182255c5f6a65a"}, {file = "frozenlist-1.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d1b3eb7b05ea246510b43a7e53ed1653e55c2121019a97e60cad7efb881a97bb"}, @@ -913,6 +945,7 @@ version = "2.6.9" description = "File identification library for Python" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "identify-2.6.9-py2.py3-none-any.whl", hash = "sha256:c98b4322da415a8e5a70ff6e51fbc2d2932c015532d77e9f8537b4ba7813b150"}, {file = "identify-2.6.9.tar.gz", hash = "sha256:d40dfe3142a1421d8518e3d3985ef5ac42890683e32306ad614a29490abeb6bf"}, @@ -927,6 +960,7 @@ version = "3.10" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, @@ -941,6 +975,7 @@ version = "2.0.0" description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, @@ -952,6 +987,7 @@ version = "5.13.2" description = "A Python utility / library to sort Python imports." optional = false python-versions = ">=3.8.0" +groups = ["dev"] files = [ {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, @@ -966,6 +1002,7 @@ version = "1.2.2" description = "a modern parsing library" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "lark-1.2.2-py3-none-any.whl", hash = "sha256:c2276486b02f0f1b90be155f2c8ba4a8e194d42775786db622faccd652d8e80c"}, {file = "lark-1.2.2.tar.gz", hash = "sha256:ca807d0162cd16cef15a8feecb862d7319e7a09bdb13aef927968e45040fed80"}, @@ -983,6 +1020,7 @@ version = "3.26.1" description = "A lightweight library for converting complex datatypes to and from native Python datatypes." optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "marshmallow-3.26.1-py3-none-any.whl", hash = "sha256:3350409f20a70a7e4e11a27661187b77cdcaeb20abca41c1454fe33636bea09c"}, {file = "marshmallow-3.26.1.tar.gz", hash = "sha256:e6d8affb6cb61d39d26402096dc0aee12d5a26d490a121f118d2e81dc0719dc6"}, @@ -1002,6 +1040,7 @@ version = "8.7.1" description = "Python library to convert dataclasses into marshmallow schemas." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "marshmallow_dataclass-8.7.1-py3-none-any.whl", hash = "sha256:405cbaaad9cea56b3de2f85eff32a9880e3bf849f652e7f6de7395e4b1ddc072"}, {file = "marshmallow_dataclass-8.7.1.tar.gz", hash = "sha256:4fb80e1bf7b31ce1b192aa87ffadee2cedb3f6f37bb0042f8500b07e6fad59c4"}, @@ -1013,10 +1052,10 @@ typeguard = ">=4.0,<5" typing-inspect = ">=0.9.0" [package.extras] -dev = ["pre-commit (>=2.17,<3.0)", "pytest (>=5.4)", "pytest-mypy-plugins (>=1.2.0)", "sphinx"] +dev = ["pre-commit (>=2.17,<3.0)", "pytest (>=5.4)", "pytest-mypy-plugins (>=1.2.0) ; implementation_name != \"pypy\"", "sphinx"] docs = ["sphinx"] lint = ["pre-commit (>=2.17,<3.0)"] -tests = ["pytest (>=5.4)", "pytest-mypy-plugins (>=1.2.0)"] +tests = ["pytest (>=5.4)", "pytest-mypy-plugins (>=1.2.0) ; implementation_name != \"pypy\""] [[package]] name = "marshmallow-oneofschema" @@ -1024,6 +1063,7 @@ version = "3.1.1" description = "marshmallow multiplexing schema" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "marshmallow_oneofschema-3.1.1-py3-none-any.whl", hash = "sha256:ff4cb2a488785ee8edd521a765682c2c80c78b9dc48894124531bdfa1ec9303b"}, {file = "marshmallow_oneofschema-3.1.1.tar.gz", hash = "sha256:68b4a57d0281a04ac25d4eb7a4c5865a57090a0a8fd30fd6362c8e833ac6a6d9"}, @@ -1042,6 +1082,7 @@ version = "1.3.0" description = "Python library for arbitrary-precision floating-point arithmetic" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c"}, {file = "mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f"}, @@ -1050,7 +1091,7 @@ files = [ [package.extras] develop = ["codecov", "pycodestyle", "pytest (>=4.6)", "pytest-cov", "wheel"] docs = ["sphinx"] -gmpy = ["gmpy2 (>=2.1.0a4)"] +gmpy = ["gmpy2 (>=2.1.0a4) ; platform_python_implementation != \"PyPy\""] tests = ["pytest (>=4.6)"] [[package]] @@ -1059,6 +1100,7 @@ version = "6.1.0" description = "multidict implementation" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "multidict-6.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3380252550e372e8511d49481bd836264c009adb826b23fefcc5dd3c69692f60"}, {file = "multidict-6.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:99f826cbf970077383d7de805c0681799491cb939c25450b9b5b3ced03ca99f1"}, @@ -1160,6 +1202,7 @@ 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"] 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"}, @@ -1171,6 +1214,7 @@ version = "1.9.1" description = "Node.js virtual environment builder" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["main"] files = [ {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, @@ -1182,6 +1226,7 @@ version = "24.2" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, @@ -1193,6 +1238,7 @@ version = "0.12.1" description = "Utility library for gitignore style pattern matching of file paths." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, @@ -1204,6 +1250,7 @@ version = "4.3.6" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, @@ -1220,6 +1267,7 @@ version = "1.5.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, @@ -1235,6 +1283,7 @@ version = "0.1.5" description = "Python implementation of Poseidon hash" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "poseidon_py-0.1.5-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:3a8b4cc09d28439764956c47a00e49aa36c23421f724b0ffbd2c62550325240e"}, {file = "poseidon_py-0.1.5-cp310-cp310-macosx_13_0_universal2.whl", hash = "sha256:a7e611fe04d200eb782a0e7b6f8ee8e34da531a527f96685ff5f12117014fca8"}, @@ -1310,6 +1359,7 @@ version = "4.1.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "pre_commit-4.1.0-py2.py3-none-any.whl", hash = "sha256:d29e7cb346295bcc1cc75fc3e92e343495e3ea0196c9ec6ba53f49f10ab6ae7b"}, {file = "pre_commit-4.1.0.tar.gz", hash = "sha256:ae3f018575a588e30dfddfab9a05448bfbd6b73d78709617b5a2b853549716d4"}, @@ -1328,6 +1378,7 @@ version = "0.3.0" description = "Accelerated property cache" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "propcache-0.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:efa44f64c37cc30c9f05932c740a8b40ce359f51882c70883cc95feac842da4d"}, {file = "propcache-0.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2383a17385d9800b6eb5855c2f05ee550f803878f344f58b6e194de08b96352c"}, @@ -1435,6 +1486,7 @@ version = "2.9.10" description = "psycopg2 - Python-PostgreSQL Database Adapter" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "psycopg2-binary-2.9.10.tar.gz", hash = "sha256:4b3df0e6990aa98acda57d983942eff13d824135fe2250e6522edaa782a06de2"}, {file = "psycopg2_binary-2.9.10-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:0ea8e3d0ae83564f2fc554955d327fa081d065c8ca5cc6d2abb643e2c9c1200f"}, @@ -1512,6 +1564,7 @@ version = "7.0.1" description = "py-ecc: Elliptic curve crypto in python including secp256k1, alt_bn128, and bls12_381" optional = false python-versions = "<4,>=3.8" +groups = ["main"] files = [ {file = "py_ecc-7.0.1-py3-none-any.whl", hash = "sha256:84a8b4d436163c83c65345a68e32f921ef6e64374a36f8e561f0455b4b08f5f2"}, {file = "py_ecc-7.0.1.tar.gz", hash = "sha256:557461f42e57294d734305a30faf6b8903421651871e9cdeff8d8e67c6796c70"}, @@ -1533,6 +1586,7 @@ version = "3.21.0" description = "Cryptographic library for Python" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +groups = ["main"] files = [ {file = "pycryptodome-3.21.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:dad9bf36eda068e89059d1f07408e397856be9511d7113ea4b586642a429a4fd"}, {file = "pycryptodome-3.21.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:a1752eca64c60852f38bb29e2c86fca30d7672c024128ef5d70cc15868fa10f4"}, @@ -1574,6 +1628,7 @@ version = "2.9.0" description = "JSON Web Token implementation in Python" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "PyJWT-2.9.0-py3-none-any.whl", hash = "sha256:3b02fb0f44517787776cf48f2ae25d8e14f300e6d7545a4315cee571a415e850"}, {file = "pyjwt-2.9.0.tar.gz", hash = "sha256:7e1e5b56cc735432a7369cbfa0efe50fa113ebecdc04ae6922deba8b84582d0c"}, @@ -1591,6 +1646,7 @@ version = "7.4.4" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, @@ -1611,6 +1667,7 @@ version = "4.1.0" description = "Pytest plugin for measuring coverage." optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6"}, {file = "pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a"}, @@ -1623,12 +1680,32 @@ pytest = ">=4.6" [package.extras] testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] +[[package]] +name = "pytest-django" +version = "4.11.1" +description = "A Django plugin for pytest." +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "pytest_django-4.11.1-py3-none-any.whl", hash = "sha256:1b63773f648aa3d8541000c26929c1ea63934be1cfa674c76436966d73fe6a10"}, + {file = "pytest_django-4.11.1.tar.gz", hash = "sha256:a949141a1ee103cb0e7a20f1451d355f83f5e4a5d07bdd4dcfdd1fd0ff227991"}, +] + +[package.dependencies] +pytest = ">=7.0.0" + +[package.extras] +docs = ["sphinx", "sphinx_rtd_theme"] +testing = ["Django", "django-configurations (>=2.0)"] + [[package]] name = "python-dotenv" version = "1.0.1" description = "Read key-value pairs from a .env file and set them as environment variables" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"}, {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}, @@ -1643,6 +1720,8 @@ version = "306" description = "Python for Window Extensions" optional = false python-versions = "*" +groups = ["main"] +markers = "os_name == \"nt\"" files = [ {file = "pywin32-306-cp310-cp310-win32.whl", hash = "sha256:06d3420a5155ba65f0b72f2699b5bacf3109f36acbe8923765c22938a69dfc8d"}, {file = "pywin32-306-cp310-cp310-win_amd64.whl", hash = "sha256:84f4471dbca1887ea3803d8848a1616429ac94a4a8d05f4bc9c5dcfd42ca99c8"}, @@ -1666,6 +1745,7 @@ version = "6.0.2" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, @@ -1728,6 +1808,7 @@ version = "0.11.0" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "ruff-0.11.0-py3-none-linux_armv6l.whl", hash = "sha256:dc67e32bc3b29557513eb7eeabb23efdb25753684b913bebb8a0c62495095acb"}, {file = "ruff-0.11.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:38c23fd9bdec4eb437b4c1e3595905a0a8edfccd63a790f818b28c78fe345639"}, @@ -1755,6 +1836,7 @@ version = "1.17.0" description = "Python 2 and 3 compatibility utilities" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["main"] files = [ {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, @@ -1766,6 +1848,7 @@ version = "0.5.3" description = "A non-validating SQL parser." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "sqlparse-0.5.3-py3-none-any.whl", hash = "sha256:cf2196ed3418f3ba5de6af7e82c694a9fbdbfecccdfc72e281548517081f16ca"}, {file = "sqlparse-0.5.3.tar.gz", hash = "sha256:09f67787f56a0b16ecdbde1bfc7f5d9c3371ca683cfeaa8e6ff60b4807ec9272"}, @@ -1781,6 +1864,7 @@ version = "0.26.2" description = "A python SDK for Starknet" optional = false python-versions = "<3.13,>=3.9" +groups = ["main"] files = [ {file = "starknet_py-0.26.2.tar.gz", hash = "sha256:afad0eab1d019a57bbcff7b3d0a6f5f5adea49650fa3ad49f2fabf5cfdd81ab1"}, ] @@ -1809,6 +1893,7 @@ version = "1.12.1" description = "Computer algebra system (CAS) in Python" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "sympy-1.12.1-py3-none-any.whl", hash = "sha256:9b2cbc7f1a640289430e13d2a56f02f867a1da0190f2f99d8968c2f74da0e515"}, {file = "sympy-1.12.1.tar.gz", hash = "sha256:2877b03f998cd8c08f07cd0de5b767119cd3ef40d09f41c30d722f6686b0fb88"}, @@ -1823,6 +1908,8 @@ version = "1.0.0" description = "List processing tools and functional utilities" optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "implementation_name == \"cpython\" or implementation_name == \"pypy\"" files = [ {file = "toolz-1.0.0-py3-none-any.whl", hash = "sha256:292c8f1c4e7516bf9086f8850935c799a874039c8bcf959d47b600e4c44a6236"}, {file = "toolz-1.0.0.tar.gz", hash = "sha256:2c86e3d9a04798ac556793bced838816296a2f085017664e4995cb40a1047a02"}, @@ -1834,6 +1921,7 @@ version = "4.4.2" description = "Run-time type checker for Python" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "typeguard-4.4.2-py3-none-any.whl", hash = "sha256:77a78f11f09777aeae7fa08585f33b5f4ef0e7335af40005b0c422ed398ff48c"}, {file = "typeguard-4.4.2.tar.gz", hash = "sha256:a6f1065813e32ef365bc3b3f503af8a96f9dd4e0033a02c28c4a4983de8c6c49"}, @@ -1844,7 +1932,7 @@ typing_extensions = ">=4.10.0" [package.extras] doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme (>=1.3.0)"] -test = ["coverage[toml] (>=7)", "mypy (>=1.2.0)", "pytest (>=7)"] +test = ["coverage[toml] (>=7)", "mypy (>=1.2.0) ; platform_python_implementation != \"PyPy\"", "pytest (>=7)"] [[package]] name = "typing-extensions" @@ -1852,6 +1940,7 @@ version = "4.12.2" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, @@ -1863,6 +1952,7 @@ version = "0.9.0" description = "Runtime inspection utilities for typing module." optional = false python-versions = "*" +groups = ["main"] files = [ {file = "typing_inspect-0.9.0-py3-none-any.whl", hash = "sha256:9ee6fc59062311ef8547596ab6b955e1b8aa46242d854bfc78f4f6b0eff35f9f"}, {file = "typing_inspect-0.9.0.tar.gz", hash = "sha256:b23fc42ff6f6ef6954e4852c1fb512cdd18dbea03134f91f856a95ccc9461f78"}, @@ -1878,6 +1968,7 @@ version = "2025.1" description = "Provider of IANA time zone data" optional = false python-versions = ">=2" +groups = ["main"] files = [ {file = "tzdata-2025.1-py2.py3-none-any.whl", hash = "sha256:7e127113816800496f027041c570f50bcd464a020098a3b6b199517772303639"}, {file = "tzdata-2025.1.tar.gz", hash = "sha256:24894909e88cdb28bd1636c6887801df64cb485bd593f2fd83ef29075a81d694"}, @@ -1889,6 +1980,7 @@ version = "20.29.3" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "virtualenv-20.29.3-py3-none-any.whl", hash = "sha256:3e3d00f5807e83b234dfb6122bf37cfadf4be216c53a49ac059d02414f819170"}, {file = "virtualenv-20.29.3.tar.gz", hash = "sha256:95e39403fcf3940ac45bc717597dba16110b74506131845d9b687d5e73d947ac"}, @@ -1901,7 +1993,7 @@ platformdirs = ">=3.9.1,<5" [package.extras] docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] -test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] +test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8) ; platform_python_implementation == \"PyPy\" or platform_python_implementation == \"CPython\" and sys_platform == \"win32\" and python_version >= \"3.13\"", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10) ; platform_python_implementation == \"CPython\""] [[package]] name = "websockets" @@ -1909,6 +2001,7 @@ version = "15.0.1" description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "websockets-15.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d63efaa0cd96cf0c5fe4d581521d9fa87744540d4bc999ae6e08595a1014b45b"}, {file = "websockets-15.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac60e3b188ec7574cb761b08d50fcedf9d77f1530352db4eef1707fe9dee7205"}, @@ -1987,6 +2080,7 @@ version = "1.18.3" description = "Yet another URL library" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "yarl-1.18.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7df647e8edd71f000a5208fe6ff8c382a1de8edfbccdbbfe649d263de07d8c34"}, {file = "yarl-1.18.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c69697d3adff5aa4f874b19c0e4ed65180ceed6318ec856ebc423aa5850d84f7"}, @@ -2078,6 +2172,6 @@ multidict = ">=4.0" propcache = ">=0.2.0" [metadata] -lock-version = "2.0" +lock-version = "2.1" python-versions = ">=3.12,<3.13" -content-hash = "004fbac7b3ed626a8c2826ae5684f94dc4b2795c9e82d4178a5cef794f393f26" +content-hash = "f5cf49805c487579de935a5ad469b8133523d4d5dccb3d7e73f188867bf3ee26" diff --git a/pyproject.toml b/pyproject.toml index 647e50e..99f3173 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,6 +26,7 @@ isort = "^5.12.0" pytest = "^7.4.0" pytest-cov = "^4.1.0" ruff = "^0.11.0" +pytest-django = "^4.11.1" [tool.black] line-length = 88 diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..42365c8 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,4 @@ +[pytest] +DJANGO_SETTINGS_MODULE = trajectfi.settings +python_files = tests.py test_*.py *_tests.py +addopts = --reuse-db From 8a939304e800a2ce02c4b0c4b870e93d6fe07aa0 Mon Sep 17 00:00:00 2001 From: DaveyLupes Date: Wed, 30 Apr 2025 15:44:02 +0200 Subject: [PATCH 2/3] test: add pagination structure test for listings endpoint --- core/tests/test_listings.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/core/tests/test_listings.py b/core/tests/test_listings.py index e27d577..dd35397 100644 --- a/core/tests/test_listings.py +++ b/core/tests/test_listings.py @@ -67,3 +67,24 @@ def test_listings_filter_by_borrower_address(): results = data["results"] assert len(results) == 1 assert results[0]["borrower_address"] == "0xBorrowerFilter" + + +@pytest.mark.django_db +def test_listings_pagination_structure(): + client = APIClient() + url = reverse("listing-list") + + user = factories.UserFactory() + for _ in range(5): + factories.ListingFactory(user=user, status=ListingStatus.OPEN) + + response = client.get(url) + assert response.status_code == 200 + + data = response.json() + + assert "results" in data + assert isinstance(data["results"], list) + assert "count" in data + assert "next" in data + assert "previous" in data From c979bb049c8bc5baf99e943e1725d53a702132f4 Mon Sep 17 00:00:00 2001 From: DaveyLupes Date: Wed, 30 Apr 2025 18:40:02 +0200 Subject: [PATCH 3/3] refactor: update ListingSerializer to use model field names as per review --- core/serializers.py | 23 +++++++---------------- core/tests/test_listings.py | 19 ++++++++----------- 2 files changed, 15 insertions(+), 27 deletions(-) diff --git a/core/serializers.py b/core/serializers.py index 13ccf9a..33a6b8c 100644 --- a/core/serializers.py +++ b/core/serializers.py @@ -171,7 +171,7 @@ def validate(self, attrs): # Principal and Repayment Validation if attrs["principal"] > attrs["repayment_amount"]: raise serializers.ValidationError( - {"detail": "Principal should be less tha Repayment amount"} + {"detail": "Principal should be less than Repayment amount"} ) # validate offer token @@ -238,24 +238,15 @@ def save(self): class ListingSerializer(serializers.ModelSerializer): - listing_id = serializers.UUIDField(source="id", read_only=True) - collateral_contract = serializers.CharField( - source="nft_contract_address", read_only=True - ) - collateral_id = serializers.IntegerField(source="nft_token_id", read_only=True) - borrower_address = serializers.CharField(source="user.public_key", read_only=True) - loan_amount = serializers.IntegerField(source="borrow_amount", read_only=True) - loan_duration = serializers.IntegerField(source="duration", read_only=True) - class Meta: model = Listing fields = [ - "listing_id", - "collateral_contract", - "collateral_id", - "borrower_address", - "loan_amount", - "loan_duration", + "id", + "nft_contract_address", + "nft_token_id", + "user", + "borrow_amount", + "duration", "created_at", "status", ] diff --git a/core/tests/test_listings.py b/core/tests/test_listings.py index dd35397..a7bd5f1 100644 --- a/core/tests/test_listings.py +++ b/core/tests/test_listings.py @@ -12,7 +12,6 @@ def test_list_active_listings(): url = reverse("listing-list") user = factories.UserFactory() - active_listing = factories.ListingFactory(user=user, status=ListingStatus.OPEN) factories.ListingFactory(user=user, status=ListingStatus.CLOSED) @@ -24,12 +23,12 @@ def test_list_active_listings(): assert len(results) == 1 listing = results[0] - assert listing["listing_id"] == str(active_listing.id) - assert listing["collateral_contract"] == active_listing.nft_contract_address - assert listing["collateral_id"] == active_listing.nft_token_id - assert listing["borrower_address"] == active_listing.user.public_key - assert listing["loan_amount"] == active_listing.borrow_amount - assert listing["loan_duration"] == active_listing.duration + assert listing["id"] == str(active_listing.id) + assert listing["nft_contract_address"] == active_listing.nft_contract_address + assert listing["nft_token_id"] == active_listing.nft_token_id + assert listing["user"] == str(active_listing.user.id) + assert listing["borrow_amount"] == active_listing.borrow_amount + assert listing["duration"] == active_listing.duration assert listing["status"] == active_listing.status @@ -49,7 +48,7 @@ def test_listings_filter_by_collateral_contract(): data = response.json() results = data["results"] assert len(results) == 1 - assert results[0]["collateral_contract"] == "0xFilterThis" + assert results[0]["nft_contract_address"] == "0xFilterThis" @pytest.mark.django_db @@ -58,7 +57,6 @@ def test_listings_filter_by_borrower_address(): url = reverse("listing-list") user = factories.UserFactory(public_key="0xBorrowerFilter") - factories.ListingFactory(user=user, status=ListingStatus.OPEN) response = client.get(url, {"borrower_address": "0xBorrowerFilter"}) assert response.status_code == 200 @@ -66,7 +64,7 @@ def test_listings_filter_by_borrower_address(): data = response.json() results = data["results"] assert len(results) == 1 - assert results[0]["borrower_address"] == "0xBorrowerFilter" + assert results[0]["user"] == str(user.id) @pytest.mark.django_db @@ -82,7 +80,6 @@ def test_listings_pagination_structure(): assert response.status_code == 200 data = response.json() - assert "results" in data assert isinstance(data["results"], list) assert "count" in data