diff --git a/iiif_prezi3/skeleton.py b/iiif_prezi3/skeleton.py index ccc860c6..07a98cc1 100644 --- a/iiif_prezi3/skeleton.py +++ b/iiif_prezi3/skeleton.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: iiif_3_0.json -# timestamp: 2023-02-17T13:09:25+00:00 +# timestamp: 2023-03-14T00:28:59+00:00 from __future__ import annotations @@ -55,8 +55,12 @@ class ViewingDirection(Base): ] +class Context(Base): + __root__: Union[str, List[AnyUrl]] + + class Id(Base): - __root__: AnyUrl = Field(..., title='Id must be presesnt and must be a URI') + __root__: AnyUrl = Field(..., title='Id must be present and must be a URI') class LngString(Base): @@ -94,13 +98,33 @@ class Format(Base): __root__: constr(regex=r'^[a-z][a-z]*/.*$') -class AnnoSelectorItem(Base): - type: str - t: Optional[Duration] = None +class SelectorItem(Base): + type: constr(regex=r'^PointSelector$') = 'PointSelector' + t: Duration + + +class SelectorItem1(Base): + type: constr(regex=r'^FragmentSelector$') = 'FragmentSelector' + conformsTo: AnyUrl = 'http://www.w3.org/TR/media-frags/' + value: Any + +class SelectorItem2(Base): + type: constr(regex=r'^SvgSelector$') = 'SvgSelector' + value: Any -class AnnoSelector(Base): - __root__: Union[AnyUrl, AnnoSelectorItem] + +class SelectorItem3(Base): + type: constr(regex=r'^ImageApiSelector$') = 'ImageApiSelector' + region: Optional[Any] = None + size: Optional[Any] = None + rotation: Optional[Any] = None + quality: Optional[Any] = None + format: Optional[Any] = None + + +class Selector(Base): + __root__: Union[AnyUrl, SelectorItem, SelectorItem1, SelectorItem2, SelectorItem3] class NavPlace(Base): @@ -109,7 +133,7 @@ class NavPlace(Base): features: Optional[List[Dict[str, Any]]] = None -class ResourceItem1(Base): +class ResourceItem(Base): id: Optional[Id] = None type: constr(regex=r'^TextualBody$') = 'TextualBody' value: str @@ -142,8 +166,8 @@ class SpecificResource(Base): type: constr(regex=r'^SpecificResource$') = 'SpecificResource' format: Optional[Format] = None accessibility: Optional[str] = None - source: Id - selector: Union[AnnoSelector, List[AnnoSelector]] + source: Union[Id, Class] + selector: Optional[Union[Selector, List[Selector]]] = None class HomepageItem(Class): @@ -163,10 +187,6 @@ class PartOf(Base): __root__: List[Class] -class Item(Class): - type: constr(regex=r'^Canvas$') = 'Canvas' - - class SeeAlso(Base): __root__: External @@ -179,18 +199,15 @@ class Model(Base): __root__: Union[Manifest, Collection, AnnotationPage] -class AnnotationPage(Base): - class Config: - extra = Extra.forbid - - context: Optional[Any] = Field(None, alias='@context') +class AnnotationPage(Class): + context: Optional[Context] = Field(None, alias='@context') id: Id type: constr(regex=r'^AnnotationPage$') = 'AnnotationPage' - rendering: Optional[External] = None label: Optional[LngString] = None service: Optional[Service] = None + rendering: Optional[External] = None thumbnail: Optional[List[Resource]] = None - items: Optional[List[Annotation]] = None + items: List[Annotation] class Collection(Class): @@ -217,11 +234,11 @@ class Collection(Class): behavior: Optional[Behavior] = None partOf: Optional[PartOf] = None items: Optional[List[Union[ManifestRef, CollectionRef, Collection]]] = None - annotations: Optional[List[AnnotationPage]] = None + annotations: Optional[AnnotationsLink] = None class Manifest(Class): - context: Optional[Union[List[AnyUrl], str]] = Field(None, alias='@context') + context: Optional[Context] = Field(None, alias='@context') id: Id label: LngString type: constr(regex=r'^Manifest') = 'Manifest' @@ -246,7 +263,7 @@ class Manifest(Class): partOf: Optional[PartOf] = None items: Optional[List[Canvas]] = None structures: Optional[List[Range]] = None - annotations: Optional[List[AnnotationPage]] = None + annotations: Optional[AnnotationsLink] = None class AccompanyingCanvas(Class): @@ -269,11 +286,7 @@ class AccompanyingCanvas(Class): behavior: Optional[Behavior] = None partOf: Optional[PartOf] = None items: List[AnnotationPage] - annotations: Optional[List[AnnotationPage]] = None - - -class BodyItem(Choice): - items: List[Resource] + annotations: Optional[AnnotationsLink] = None class Annotation(Class): @@ -281,8 +294,8 @@ class Annotation(Class): service: Optional[Service] = None rendering: Optional[External] = None thumbnail: Optional[List[Resource]] = None - motivation: Optional[Union[str, List[str]]] = None - body: Optional[Union[Resource, BodyItem, List[Dict[str, Any]]]] = None + motivation: Optional[Union[List[str], str]] = None + body: Optional[Union[Choice, Resource, List[Resource]]] = None target: Union[AnnoTarget, List[AnnoTarget]] @@ -308,7 +321,7 @@ class Canvas(Class): behavior: Optional[Behavior] = None partOf: Optional[PartOf] = None items: List[AnnotationPage] - annotations: Optional[List[AnnotationPage]] = None + annotations: Optional[AnnotationsLink] = None class PlaceholderCanvas(Class): @@ -331,7 +344,7 @@ class PlaceholderCanvas(Class): behavior: Optional[Behavior] = None partOf: Optional[PartOf] = None items: List[AnnotationPage] - annotations: Optional[List[AnnotationPage]] = None + annotations: Optional[AnnotationsLink] = None class ProviderItem(Class): @@ -352,12 +365,12 @@ class Range(Class): service: Optional[Service] = None placeholderCanvas: Optional[PlaceholderCanvas] = None accompanyingCanvas: Optional[AccompanyingCanvas] = None - annotations: Optional[List[AnnotationPage]] = None + annotations: Optional[AnnotationsLink] = None thumbnail: Optional[List[Resource]] = None - items: List[Union[SpecificResource, Item, Range, CanvasRef, RangeRef]] + items: List[Union[SpecificResource, Range, Canvas, CanvasRef]] -class ResourceItem(Base): +class ResourceItem1(Base): id: Id type: str height: Optional[Dimension] = None @@ -369,44 +382,57 @@ class ResourceItem(Base): format: Optional[Format] = None label: Optional[LngString] = None thumbnail: Optional[List[Resource]] = None - annotations: Optional[List[AnnotationPage]] = None + annotations: Optional[AnnotationsLink] = None class Resource(Base): - __root__: Union[ResourceItem, ResourceItem1] + __root__: Union[ResourceItem, SpecificResource, ResourceItem1] = Field( + ..., title='ContentResource' + ) -class ServiceItem(Class): +class ServiceItem(Base): + id: Id = Field(..., alias='@id') + type: str = Field(..., alias='@type') profile: Optional[str] = None service: Optional[Service] = None -class ServiceItem1(Base): - id: Id = Field(..., alias='@id') - type: str = Field(..., alias='@type') +class ServiceItem1(Class): profile: Optional[str] = None service: Optional[Service] = None class Service(Base): - __root__: List[Union[ServiceItem, ServiceItem1]] + __root__: List[Union[ServiceItem, ServiceItem1]] = Field( + ..., + description='must be an array and have an id and type or @id and @type', + title='Service', + ) + + +class AnnotationsLink(Base): + __root__: List[Union[AnnotationPage, AnnotationPageRef]] class AnnotationCollection(Class): type: constr(regex=r'^AnnotationCollection$') = 'AnnotationCollection' rendering: Optional[External] = None partOf: Optional[PartOf] = None - next: Optional[AnnotationPage] = None - first: Optional[AnnotationPage] = None - last: Optional[AnnotationPage] = None + next: Optional[AnnotationPageRef] = None + first: Optional[AnnotationPageRef] = None + last: Optional[AnnotationPageRef] = None service: Optional[Service] = None thumbnail: Optional[List[Resource]] = None items: Optional[List[Annotation]] = None class Reference(Base): + class Config: + extra = Extra.allow + id: Id - label: LngString + label: Optional[LngString] = None type: constr( regex=r'^Manifest$|^AnnotationPage$|^Collection$|^AnnotationCollection$|^Canvas$|^Range$' ) @@ -415,6 +441,7 @@ class Reference(Base): class CollectionRef(Reference): type: Optional[constr(regex=r'^Collection$')] = None + label: LngString class ManifestRef(Reference): @@ -426,10 +453,7 @@ class Config: class CanvasRef(Reference): type: Optional[constr(regex=r'^Canvas$')] = None - - -class RangeRef(Reference): - type: Optional[constr(regex=r'^Range$')] = None + label: Optional[LngString] = None Model.update_forward_refs() @@ -437,12 +461,13 @@ class RangeRef(Reference): Collection.update_forward_refs() Manifest.update_forward_refs() AccompanyingCanvas.update_forward_refs() -BodyItem.update_forward_refs() Annotation.update_forward_refs() Canvas.update_forward_refs() PlaceholderCanvas.update_forward_refs() ProviderItem.update_forward_refs() Range.update_forward_refs() -ResourceItem.update_forward_refs() +ResourceItem1.update_forward_refs() ServiceItem.update_forward_refs() ServiceItem1.update_forward_refs() +AnnotationsLink.update_forward_refs() +AnnotationCollection.update_forward_refs() diff --git a/utils/regenerate_skeleton.py b/utils/regenerate_skeleton.py index 4e0c498f..53c4af0f 100644 --- a/utils/regenerate_skeleton.py +++ b/utils/regenerate_skeleton.py @@ -1,12 +1,70 @@ import os import shlex import subprocess +import json import requests from modify_skeleton import modify_skeleton -SCHEMA_LOCATION = "https://raw.githubusercontent.com/IIIF/presentation-validator/main/schema/iiif_3_0.json" -DATAMODEL_COMMAND = "datamodel-codegen --input iiif_3_0.json --input-file-type jsonschema --use-default --remove-special-field-name-prefix --strict-nullable --base-class .base.Base --output ../iiif_prezi3/skeleton.py" +SCHEMA_LOCATION = "https://raw.githubusercontent.com/IIIF/presentation-validator/issue-154/schema/iiif_3_0.json" +SCHEMA_FILENAME = "iiif_3_0.json" +DATAMODEL_COMMAND = "datamodel-codegen --input {} --input-file-type jsonschema --use-default --remove-special-field-name-prefix --strict-nullable --base-class .base.Base --output ../iiif_prezi3/skeleton.py".format(SCHEMA_FILENAME) + +def downgradeSchema(schema, filename): + runSubstitute(schema) + + #print(json.dumps(schema, indent=4)) + with open(SCHEMA_FILENAME, "w") as out: + out.write(json.dumps(schema, indent=4)) + +def runSubstitute(schema): + if isinstance(schema, list): + for item in schema: + runSubstitute(item) + + if isinstance(schema,dict): + if 'allOf' in schema and "if" in schema['allOf'][0]: + # We have an all of if statement + oneOf = [] + for statement in schema['allOf']: + oneOf.append(statement["then"]) + del schema['allOf'] + schema['oneOf'] = oneOf + + + for key in schema: + if isinstance(schema[key],dict) and 'if' in schema[key]: + if 'else' in schema[key]: + schema[key] = { 'oneOf': [ + schema[key]['then'], + schema[key]['else'] + ]} + # Handle nested if statements + while True: + containsIf = False + for index,item in enumerate(schema[key]['oneOf']): + if 'if' in item: + # Nested if is in the else block + containsIf = True + # Add the nested if/then part + schema[key]['oneOf'].append(item['then']) + if 'else' in item: + # Add the nested else part + schema[key]['oneOf'].append(item['else']) + # Delete the item in the list that contained the if/then/else as constituent parts + # are now part of the list of oneOfs + del schema[key]['oneOf'][index] + break + + if not containsIf: + break + else: + schema[key] = schema[key]['then'] + + if isinstance(schema[key],dict) or isinstance(schema[key],list): + #print ('recursive on ' + key) + #print (schema[key]) + runSubstitute(schema[key]) if __name__ == "__main__": print("== Prezi3 Skeleton Regenerator ==") @@ -15,15 +73,16 @@ if safety.lower() != "y": exit() - print("Downloading latest JSON Schema...") + print("Downloading latest JSON Schema..." + SCHEMA_LOCATION) js = requests.get(SCHEMA_LOCATION) - if js.status_code == 200: - with open("iiif_3_0.json", "wb") as out: - out.write(js.content) - else: + if js.status_code != 200: print(f"Error retrieving JSON Schema - Status {js.status_code}") exit() + downgradeSchema(json.loads(js.content),SCHEMA_FILENAME) + #with open('test.json') as tst_file: + # downgradeSchema(json.loads(tst_file.read()),SCHEMA_FILENAME) + #exit(-1) print("Generating Skeleton file...") subprocess.run(shlex.split(DATAMODEL_COMMAND), check=True) @@ -31,6 +90,6 @@ modify_skeleton() print("Cleaning up Schema file...") - os.remove("iiif_3_0.json") + os.remove(SCHEMA_FILENAME) print("Done!")