diff --git a/rip_api/api/__init__.py b/rip_api/api/__init__.py index 5372022..f1f4897 100644 --- a/rip_api/api/__init__.py +++ b/rip_api/api/__init__.py @@ -77,6 +77,7 @@ class ListLawsIncludeOptions(Enum): tags=["Laws"], summary="List all laws", response_model=api_schemas.LawsResponse, + response_model_exclude_unset=True, ) def list_laws( include: Optional[ListLawsIncludeOptions] = None, @@ -117,6 +118,7 @@ class GetLawIncludeOptions(Enum): tags=["Laws"], summary="Get a single law", response_model=api_schemas.LawResponse, + response_model_exclude_unset=True, ) def get_law( slug: str = Path(..., description="URL-safe lowercased abbreviation of the law."), @@ -162,10 +164,6 @@ def get_law( parent['children'].append(item) ``` """ - schema_class = api_schemas.LawAllFields - if include == GetLawIncludeOptions.contents: - schema_class = api_schemas.LawAllFieldsWithContents - with db.session_scope() as session: law = db.find_law_by_slug(session, slug) if not law: @@ -173,7 +171,11 @@ def get_law( status_code=404, title="Resource not found", detail="Could not find a law for this slug." ) - return {"data": schema_class.from_orm_model(law)} + law_data = api_schemas.LawAllFields.from_orm_model( + law, + include_contents=(include == GetLawIncludeOptions.contents) + ) + return api_schemas.LawResponse(data=law_data) @v1.get( diff --git a/rip_api/api_schemas.py b/rip_api/api_schemas.py index 4d0a6ef..b29c0e0 100644 --- a/rip_api/api_schemas.py +++ b/rip_api/api_schemas.py @@ -17,6 +17,7 @@ class ContentItemBasicFields(BaseModel): def _attrs_dict_from_item(cls, item): return { "id": item.doknr, + "type": humps.camelize(item.item_type), "url": urls.get_article(item.law.slug, item.doknr), "name": item.name, "title": item.title, @@ -101,6 +102,7 @@ class LawBasicFields(BaseModel): def _attrs_dict_from_law(cls, law): return dict( id=law.doknr, + type="law", url=urls.get_law(law.slug), abbreviation=law.abbreviation, slug=law.slug, @@ -115,48 +117,6 @@ def from_orm_model(cls, law): return cls(**cls._attrs_dict_from_law(law)) -class LawAllFields(LawBasicFields): - extraAbbreviations: List[str] - publicationInfo: List[create_model( - 'PublicationInfoItem', # noqa - reference=(str, ...), - periodical=(str, ...) - )] - statusInfo: List[create_model( - 'StatusInfoItem', # noqa - comment=(str, ...), - category=(str, ...) - )] - notes: create_model( - 'TextContent', # noqa - body=(Optional[str], ...), - footnotes=(Optional[str], ...), - documentaryFootnotes=(Optional[str], ...) - ) - attachments: dict - - @classmethod - def _attrs_dict_from_law(cls, law): - attrs = super()._attrs_dict_from_law(law) - - attrs["extraAbbreviations"] = law.extra_abbreviations - attrs["publicationInfo"] = law.publication_info - attrs["statusInfo"] = law.status_info - - attrs["notes"] = { - "body": law.notes_body, - "footnotes": law.notes_footnotes, - "documentaryFootnotes": law.notes_documentary_footnotes - } - - attrs["attachments"] = { - name: f"{PUBLIC_ASSET_ROOT}/gesetze_im_internet/{law.gii_slug}/{name}" - for name in law.attachment_names - } - - return attrs - - class ContentItemBasicFieldsWithLaw(ContentItemBasicFields): law: LawBasicFields @@ -212,21 +172,59 @@ class HeadingArticleAllFields(ContentItemWithBodyAndFootnotes): type: str = "headingArticle" -class LawAllFieldsWithContents(LawAllFields): - # Ordering in the Union matters, cf. LawResponse. - contents: List[Union[ArticleAllFields, HeadingAllFields, HeadingArticleAllFields]] +class LawAllFields(LawBasicFields): + extraAbbreviations: List[str] + publicationInfo: List[create_model( + 'PublicationInfoItem', # noqa + reference=(str, ...), + periodical=(str, ...) + )] + statusInfo: List[create_model( + 'StatusInfoItem', # noqa + comment=(str, ...), + category=(str, ...) + )] + notes: create_model( + 'TextContent', # noqa + body=(Optional[str], ...), + footnotes=(Optional[str], ...), + documentaryFootnotes=(Optional[str], ...) + ) + attachments: dict + # Order in the Union matters: FastAPI tries one after the other and only skips types if there's a validation error. + contents: Optional[List[Union[ArticleAllFields, HeadingAllFields, HeadingArticleAllFields]]] @classmethod - def _attrs_dict_from_law(cls, law): + def _attrs_dict_from_law(cls, law, include_contents=False): attrs = super()._attrs_dict_from_law(law) - attrs["contents"] = [ContentItemAllFields.from_orm_model(ci) for ci in law.contents] + + attrs["extraAbbreviations"] = law.extra_abbreviations + attrs["publicationInfo"] = law.publication_info + attrs["statusInfo"] = law.status_info + + attrs["notes"] = { + "body": law.notes_body, + "footnotes": law.notes_footnotes, + "documentaryFootnotes": law.notes_documentary_footnotes + } + + attrs["attachments"] = { + name: f"{PUBLIC_ASSET_ROOT}/gesetze_im_internet/{law.gii_slug}/{name}" + for name in law.attachment_names + } + + if include_contents: + attrs["contents"] = [ContentItemAllFields.from_orm_model(ci) for ci in law.contents] + return attrs + @classmethod + def from_orm_model(cls, law, include_contents=False): + return cls(**cls._attrs_dict_from_law(law, include_contents=include_contents)) + class LawResponse(BaseModel): - # LawWithContents must come first in the Union: FastAPI tries them in order and only skips - # types if there's a validation error. - data: Union[LawAllFieldsWithContents, LawAllFields] + data: LawAllFields @classmethod def from_orm_model(cls, law): @@ -251,10 +249,12 @@ class LawsResponse(BaseModel): class ContentItemResponse(BaseModel): + # Order in the Union matters: FastAPI tries one after the other and only skips types if there's a validation error. data: Union[LawAllFields, ArticleAllFields, HeadingArticleAllFields] class SearchResultsResponse(BaseModel): + # Order in the Union matters: FastAPI tries one after the other and only skips types if there's a validation error. data: List[Union[ LawBasicFields, ArticleBasicFieldsWithLaw, ArticleBasicFields, diff --git a/rip_api/gesetze_im_internet/__init__.py b/rip_api/gesetze_im_internet/__init__.py index cc23ae5..e15e983 100644 --- a/rip_api/gesetze_im_internet/__init__.py +++ b/rip_api/gesetze_im_internet/__init__.py @@ -154,7 +154,7 @@ def write_all_law_json_files(session, dir_path): all_laws = [] for law in db.all_laws(session): - law_api_model = api_schemas.LawAllFieldsWithContents.from_orm_model(law) + law_api_model = api_schemas.LawAllFields.from_orm_model(law, include_contents=True) single_law_response = api_schemas.LawResponse(data=law_api_model) _write_file(f"{laws_path}/{law.slug}.json", single_law_response.json(indent=2)) @@ -165,7 +165,7 @@ def write_all_law_json_files(session, dir_path): def write_law_json_file(session, law, dir_path): filepath = f"{dir_path}/{law.slug}.json" - law_schema = api_schemas.LawAllFieldsWithContents.from_orm_model(law) + law_schema = api_schemas.LawAllFields.from_orm_model(law, include_contents=True) response = api_schemas.LawResponse(data=law_schema) _write_file(filepath, response.json(indent=2)) diff --git a/tests/test_json_generation_e2e.py b/tests/test_json_generation_e2e.py index bf120f7..4f2804d 100644 --- a/tests/test_json_generation_e2e.py +++ b/tests/test_json_generation_e2e.py @@ -22,7 +22,7 @@ def test_examples(slug): with db.session_scope() as session: law = db.find_law_by_slug(session, slug) - parsed = json.loads(api_schemas.LawAllFieldsWithContents.from_orm_model(law).json(indent=2)) + parsed = json.loads(api_schemas.LawAllFields.from_orm_model(law, include_contents=True).json(indent=2)) expected = load_example_json(slug)["data"] assert parsed == expected