1+ # pylint: disable=redefined-outer-name
2+ # pylint: disable=unused-argument
3+ # pylint: disable=unused-variable
4+ # pylint: disable=too-many-arguments
5+ # pylint: disable=too-many-statements
6+
17import re
2- from typing import Annotated , Any , Iterator , Literal
8+ from collections .abc import Iterator
9+ from typing import Annotated , Any , Literal
310
411import pytest
512import respx
613from faker import Faker
714from httpx import AsyncClient
8- from pydantic import BaseModel , Field , HttpUrl , ValidationError
15+ from pydantic import BaseModel , BeforeValidator , Field , HttpUrl , ValidationError
916from servicelib .aiohttp import status
1017
1118
@@ -19,12 +26,13 @@ def mock_itis_vip_downloadables_api(faker: Faker) -> Iterator[respx.MockRouter]:
1926 "ID" : faker .random_int (min = 70 , max = 90 ),
2027 "Description" : faker .sentence (),
2128 "Thumbnail" : faker .image_url (),
22- "Features" : f"{{name: { faker .name ()} Right Hand, version: V{ faker .pyint ()} .0, sex: { faker .gender ()} , age: { faker .age ()} years,date: { faker .date ()} , ethnicity: Caucasian, functionality: Posable}}" ,
23- "DOI" : None ,
29+ # NOTE: this is manually added in the server side so be more robust to errors
30+ "Features" : f"{{name: { faker .name ()} Right Hand, version: V{ faker .pyint ()} .0, sex: Male, age: 8 years,date: { faker .date ()} , ethnicity: Caucasian, functionality: Posable}}" ,
31+ "DOI" : faker .bothify (text = "10.####/ViP#####-##-#" ),
2432 "LicenseKey" : faker .bothify (text = "MODEL_????_V#" ),
25- "LicenseVersion" : "V1 .0" ,
26- "Protection" : "Code" ,
27- "AvailableFromURL" : None ,
33+ "LicenseVersion" : faker . bothify ( text = "V# .0") ,
34+ "Protection" : faker . random_element ( elements = [ "Code" , "PayPal" ]) ,
35+ "AvailableFromURL" : faker . random_element ( elements = [ None , faker . url ()]) ,
2836 }
2937 for _ in range (8 )
3038 ],
@@ -37,17 +45,22 @@ def mock_itis_vip_downloadables_api(faker: Faker) -> Iterator[respx.MockRouter]:
3745 yield mock
3846
3947
40- def descriptor_to_dict (descriptor : str ) -> dict [str , Any ]:
48+ def _feature_descriptor_to_dict (descriptor : str ) -> dict [str , Any ]:
49+ # NOTE: this is manually added in the server side so be more robust to errors
4150 pattern = r"(\w+): ([^,]+)"
42- matches = re .findall (pattern , descriptor )
43- return { key : value for key , value in matches }
51+ matches = re .findall (pattern , descriptor . strip ( "{}" ) )
52+ return dict ( matches )
4453
4554
4655class AvailableDownload (BaseModel ):
4756 id : Annotated [int , Field (alias = "ID" )]
4857 description : Annotated [str , Field (alias = "Description" )]
4958 thumbnail : Annotated [str , Field (alias = "Thumbnail" )]
50- features : Annotated [dict [str , Any ], Field (alias = "Features" )]
59+ features : Annotated [
60+ dict [str , Any ],
61+ BeforeValidator (_feature_descriptor_to_dict ),
62+ Field (alias = "Features" ),
63+ ]
5164 doi : Annotated [str , Field (alias = "DOI" )]
5265 license_key : Annotated [str | None , Field (alias = "LicenseKey" )]
5366 license_version : Annotated [str | None , Field (alias = "LicenseVersion" )]
@@ -62,7 +75,7 @@ class ResponseData(BaseModel):
6275 ]
6376
6477
65- async def test_computational_pantom_api (
78+ async def test_fetch_itis_vip_api (
6679 mock_itis_vip_downloadables_api : respx .MockRouter ,
6780):
6881 async with AsyncClient (base_url = "http://testserver" ) as client :
@@ -76,10 +89,10 @@ async def test_computational_pantom_api(
7689 pytest .fail (f"Response validation failed: { e } " )
7790
7891 assert validated_data .msg == 0
79- assert len (validated_data .availableDownloads ) == 8
92+ assert len (validated_data .available_downloads ) == 8
8093
8194 assert (
82- validated_data .availableDownloads [0 ].Features ["functionality" ] == "Posable"
95+ validated_data .available_downloads [0 ].features ["functionality" ] == "Posable"
8396 )
8497
85- print (validated_data .model_dump_json ())
98+ print (validated_data .model_dump_json (indent = 1 ))
0 commit comments