Fix circular reference recursion in JSON serialization

Dataclass models have parent back-references (TrackLayout→Circuit→
Locality→Country) causing infinite recursion in dataclasses.asdict().
Add to_dict() methods that exclude parent refs, use them in API endpoints.
This commit is contained in:
2026-05-08 03:01:42 +02:00
parent 40d92983ed
commit 83311a6079
5 changed files with 34 additions and 5 deletions
+5 -5
View File
@@ -67,7 +67,7 @@ async def root():
async def get_countries(): async def get_countries():
"""Get list of all available countries with their slugs""" """Get list of all available countries with their slugs"""
return { return {
"countries": circuit_service.get_countries_list() "countries": [c.to_dict() for c in circuit_service.get_countries_list()]
} }
@app.get("/circuits/{country_slug}") @app.get("/circuits/{country_slug}")
@@ -76,7 +76,7 @@ async def get_cities(country_slug: str):
cities = circuit_service.get_localities_list(country_slug) cities = circuit_service.get_localities_list(country_slug)
if not cities: if not cities:
raise HTTPException(status_code=404, detail="Country not found") raise HTTPException(status_code=404, detail="Country not found")
return {"cities": cities} return {"cities": [c.to_dict() for c in cities]}
@app.get("/circuits/{country_slug}/{city_slug}") @app.get("/circuits/{country_slug}/{city_slug}")
async def get_circuits(country_slug: str, city_slug: str): async def get_circuits(country_slug: str, city_slug: str):
@@ -84,7 +84,7 @@ async def get_circuits(country_slug: str, city_slug: str):
circuits = circuit_service.get_circuits_list(country_slug, city_slug) circuits = circuit_service.get_circuits_list(country_slug, city_slug)
if not circuits: if not circuits:
raise HTTPException(status_code=404, detail="City not found") raise HTTPException(status_code=404, detail="City not found")
return {"circuits": circuits} return {"circuits": [c.to_dict() for c in circuits]}
@app.get("/circuits/{country_slug}/{city_slug}/{circuit_slug}") @app.get("/circuits/{country_slug}/{city_slug}/{circuit_slug}")
async def get_circuit_details(country_slug: str, city_slug: str, circuit_slug: str): async def get_circuit_details(country_slug: str, city_slug: str, circuit_slug: str):
@@ -92,7 +92,7 @@ async def get_circuit_details(country_slug: str, city_slug: str, circuit_slug: s
circuit_details = circuit_service.get_circuit_details(country_slug, city_slug, circuit_slug) circuit_details = circuit_service.get_circuit_details(country_slug, city_slug, circuit_slug)
if not circuit_details: if not circuit_details:
raise HTTPException(status_code=404, detail="Circuit not found") raise HTTPException(status_code=404, detail="Circuit not found")
return circuit_details return circuit_details.to_dict()
@app.get("/circuits/{country_slug}/{city_slug}/{circuit_slug}/layout/{layout_slug}") @app.get("/circuits/{country_slug}/{city_slug}/{circuit_slug}/layout/{layout_slug}")
async def get_layout_details(country_slug: str, city_slug: str, circuit_slug: str, layout_slug: str): async def get_layout_details(country_slug: str, city_slug: str, circuit_slug: str, layout_slug: str):
@@ -100,7 +100,7 @@ async def get_layout_details(country_slug: str, city_slug: str, circuit_slug: st
layout_details = circuit_service.get_layout_details(country_slug, city_slug, circuit_slug, layout_slug) layout_details = circuit_service.get_layout_details(country_slug, city_slug, circuit_slug, layout_slug)
if not layout_details: if not layout_details:
raise HTTPException(status_code=404, detail="Layout not found") raise HTTPException(status_code=404, detail="Layout not found")
return layout_details return layout_details.to_dict()
@app.get("/tracks/{country_slug}/{city_slug}/{circuit_slug}/year/{year}/{image_format}") @app.get("/tracks/{country_slug}/{city_slug}/{circuit_slug}/year/{year}/{image_format}")
async def get_track_by_year( async def get_track_by_year(
+8
View File
@@ -19,6 +19,14 @@ class Circuit:
locality: 'Locality' locality: 'Locality'
layouts: dict[str, TrackLayout] layouts: dict[str, TrackLayout]
def to_dict(self) -> dict:
return {
"slug": self.slug,
"name": self.name,
"urls": {"wikipedia": self.urls.wikipedia, "fandom": self.urls.fandom},
"layouts": {k: v.to_dict() for k, v in self.layouts.items()},
}
@classmethod @classmethod
def from_dict(cls, locality: 'Locality', slug: str, data: dict) -> 'Circuit': def from_dict(cls, locality: 'Locality', slug: str, data: dict) -> 'Circuit':
circuit = cls( circuit = cls(
+7
View File
@@ -7,6 +7,13 @@ class Country:
name: str name: str
localities: dict[str, Locality] localities: dict[str, Locality]
def to_dict(self) -> dict:
return {
"slug": self.slug,
"name": self.name,
"localities": {k: v.to_dict() for k, v in self.localities.items()},
}
@classmethod @classmethod
def from_dict(cls, slug: str, data: dict): def from_dict(cls, slug: str, data: dict):
country = cls( country = cls(
+7
View File
@@ -12,6 +12,13 @@ class Locality:
country: 'Country' country: 'Country'
circuits: dict[str, Circuit] circuits: dict[str, Circuit]
def to_dict(self) -> dict:
return {
"slug": self.slug,
"name": self.name,
"circuits": {k: v.to_dict() for k, v in self.circuits.items()},
}
@classmethod @classmethod
def from_dict(cls, country: 'Country', slug: str, data: dict): def from_dict(cls, country: 'Country', slug: str, data: dict):
locality = cls( locality = cls(
+7
View File
@@ -27,6 +27,13 @@ class TrackLayout:
circuit=circuit circuit=circuit
) )
def to_dict(self) -> dict:
return {
"slug": self.slug,
"description": self.description,
"imageUrl": self.imageUrl,
}
@property @property
def coordinates(self) -> list[tuple[float, float]]: def coordinates(self) -> list[tuple[float, float]]:
return self._geo_data.features[0].geometry.coordinates return self._geo_data.features[0].geometry.coordinates