From ee96ab4063b72b85a2ac486ad8a6129e9058ca2e Mon Sep 17 00:00:00 2001 From: pkdash Date: Wed, 3 Apr 2024 13:19:36 -0400 Subject: [PATCH 01/20] [#105] schemas for netcdf and raster datasets --- api/main.py | 14 +- api/models/catalog.py | 28 +- api/models/schema.py | 189 +++- api/models/schemas/schema.json | 1650 ++++++++++++++++---------------- api/models/user.py | 1 + 5 files changed, 1019 insertions(+), 863 deletions(-) diff --git a/api/main.py b/api/main.py index 1bbef28..1cac2ad 100644 --- a/api/main.py +++ b/api/main.py @@ -14,7 +14,13 @@ from starlette.responses import PlainTextResponse from api.config import get_settings -from api.models.catalog import DatasetMetadataDOC +from api.models.catalog import ( + CoreMetadataDOC, + HSResourceMetadataDOC, + GenericDatasetMetadataDOC, + NetCDFMetadataDOC, + RasterMetadataDOC, +) from api.models.user import Submission, User from api.routes.catalog import router as catalog_router from api.routes.discovery import router as discovery_router @@ -48,7 +54,11 @@ async def startup_db_client(): settings = get_settings() app.mongodb_client = AsyncIOMotorClient(settings.db_connection_string) app.mongodb = app.mongodb_client[settings.database_name] - await init_beanie(database=app.mongodb, document_models=[DatasetMetadataDOC, User, Submission]) + await init_beanie( + database=app.mongodb, + document_models=[CoreMetadataDOC, HSResourceMetadataDOC, GenericDatasetMetadataDOC, NetCDFMetadataDOC, + RasterMetadataDOC, User, Submission] + ) @app.on_event("shutdown") diff --git a/api/models/catalog.py b/api/models/catalog.py index 3b289bc..ad19819 100644 --- a/api/models/catalog.py +++ b/api/models/catalog.py @@ -3,7 +3,13 @@ from beanie import Document from api.models.user import Submission -from .schema import CoreMetadata, DatasetMetadata +from .schema import ( + CoreMetadata, + GenericDatasetMetadata, + HSResourceMetadata, + HSNetCDFMetadata, + HSRasterMetadata, +) class CoreMetadataDOC(Document, CoreMetadata): @@ -31,5 +37,23 @@ def as_submission(self) -> Submission: ) -class DatasetMetadataDOC(CoreMetadataDOC, DatasetMetadata): +class HSResourceMetadataDOC(CoreMetadataDOC, HSResourceMetadata): + repository_identifier: str = None + + def as_submission(self) -> Submission: + submission = super().as_submission() + submission.repository = "HydroShare" + submission.repository_identifier = self.repository_identifier + return submission + + +class GenericDatasetMetadataDOC(CoreMetadataDOC, GenericDatasetMetadata): + repository_identifier: str = None + + +class NetCDFMetadataDOC(CoreMetadataDOC, HSNetCDFMetadata): + repository_identifier: str = None + + +class RasterMetadataDOC(CoreMetadataDOC, HSRasterMetadata): repository_identifier: str = None diff --git a/api/models/schema.py b/api/models/schema.py index 8305b9c..9d9dda8 100644 --- a/api/models/schema.py +++ b/api/models/schema.py @@ -319,7 +319,7 @@ class PropertyValueBase(SchemaBaseModel): type: str = Field( alias="@type", default="PropertyValue", - const="PropertyValue", + const=True, description="A property-value pair.", ) propertyID: Optional[str] = Field( @@ -401,7 +401,7 @@ class MediaObject(SchemaBaseModel): ) name: str = Field(description="The name of the media object (file).") sha256: Optional[str] = Field(title="SHA-256", description="The SHA-256 hash of the media object.") - isPartOf: Optional[List[MediaObjectPartOf]] = Field( + isPartOf: Optional[MediaObjectPartOf] = Field( title="Is part of", description="Link to or citation for a related metadata document that this media object is a part of", ) @@ -456,9 +456,10 @@ class CoreMetadata(SchemaBaseModel): name: str = Field(title="Name or title", description="A text string with a descriptive name or title for the resource." ) - description: str = Field(title="Description or abstract", - description="A text string containing a description/abstract for the resource." - ) + description: Optional[str] = Field( + title="Description or abstract", + description="A text string containing a description/abstract for the resource.", + ) url: HttpUrl = Field( title="URL", description="A URL for the landing page that describes the resource and where the content " @@ -467,22 +468,26 @@ class CoreMetadata(SchemaBaseModel): ) identifier: Optional[List[IdentifierStr]] = Field( title="Identifiers", - description="Any kind of identifier for the resource. Identifiers may be DOIs or unique strings " + description="Any kind of identifier for the resource/dataset. Identifiers may be DOIs or unique strings " "assigned by a repository. Multiple identifiers can be entered. Where identifiers can be " "encoded as URLs, enter URLs here." ) - creator: List[Union[Creator, Organization]] = Field(description="Person or Organization that created the resource.") - dateCreated: datetime = Field(title="Date created", description="The date on which the resource was created.") - keywords: List[str] = Field( + creator: Optional[List[Union[Creator, Organization]]] = Field( + description="Person or Organization that created the resource." + ) + dateCreated: Optional[datetime] = Field( + title="Date created", description="The date on which the resource was created." + ) + keywords: Optional[List[str]] = Field( min_items=1, description="Keywords or tags used to describe the dataset, delimited by commas." ) - license: License = Field( + license: Optional[License] = Field( description="A license document that applies to the resource." ) - provider: Union[Organization, Provider] = Field( + provider: Optional[Union[Organization, Provider]] = Field( description="The repository, service provider, organization, person, or service performer that provides" - " access to the resource." + " access to the resource." ) publisher: Optional[PublisherOrganization] = Field( title="Publisher", @@ -518,17 +523,6 @@ class CoreMetadata(SchemaBaseModel): description="A Grant or monetary assistance that directly or indirectly provided funding or sponsorship " "for creation of the resource.", ) - temporalCoverage: Optional[TemporalCoverage] = Field( - title="Temporal coverage", - description="The time period that applies to all of the content within the resource.", - ) - spatialCoverage: Optional[Place] = Field( - description="The spatialCoverage of a CreativeWork indicates the place(s) which are the focus of the content. " - "It is a sub property of contentLocation intended primarily for more technical and " - "detailed materials. For example with a Dataset, it indicates areas that the dataset " - "describes: a dataset of New York weather would have spatialCoverage which was the " - "place: the state of New York.", - ) hasPart: Optional[List[HasPart]] = Field( title="Has part", description="Link to or citation for a related resource that is part of this resource." @@ -543,18 +537,157 @@ class CoreMetadata(SchemaBaseModel): description="A media object that encodes this CreativeWork. This property is a synonym for encoding.", ) citation: Optional[List[str]] = Field(title="Citation", description="A bibliographic citation for the resource.") + additionalProperty: Optional[List[PropertyValue]] = Field( + title="Additional properties", + default=[], + description="Additional properties of the dataset/resource." + ) -class DatasetMetadata(CoreMetadata): +class HSResourceMetadata(CoreMetadata): + # modeled after hydroshare resource metadata + # used in cataloging a HydroShare resource + description: str = Field( + title="Description or abstract", + description="A text string containing a description/abstract for the resource.", + ) + url: HttpUrl = Field( + title="URL", + description="A URL for the landing page that describes the resource and where the content " + "of the resource can be accessed. If there is no landing page," + " provide the URL of the content." + ) + identifier: List[IdentifierStr] = Field( + title="Identifiers", + description="Any kind of identifier for the resource. Identifiers may be DOIs or unique strings " + "assigned by a repository. Multiple identifiers can be entered. Where identifiers can be " + "encoded as URLs, enter URLs here." + ) + creator: List[Union[Creator, Organization]] = Field(description="Person or Organization that created the resource.") + dateCreated: datetime = Field( + title="Date created", description="The date on which the resource was created." + ) + keywords: List[str] = Field( + min_items=1, + description="Keywords or tags used to describe the dataset, delimited by commas.", + ) + license: License = Field( + description="A license document that applies to the resource." + ) + provider: Union[Organization, Provider] = Field( + description="The repository, service provider, organization, person, or service performer that provides" + " access to the resource." + ) + inLanguage: Union[LanguageEnum, InLanguageStr] = Field( + title="Language", + description="The language of the content of the resource." + ) + dateModified: datetime = Field( + title="Date modified", + description="The date on which the resource was most recently modified or updated." + ) + temporalCoverage: Optional[TemporalCoverage] = Field( + title="Temporal coverage", + description="The time period that applies to all of the content within the resource.", + ) + spatialCoverage: Optional[Place] = Field( + description="The spatialCoverage of a CreativeWork indicates the place(s) which are the focus of the content. " + "It is a sub property of contentLocation intended primarily for more technical and " + "detailed materials. For example with a Dataset, it indicates areas that the dataset " + "describes: a dataset of New York weather would have spatialCoverage which was the " + "place: the state of New York.", + ) + + +class GenericDatasetMetadata(CoreMetadata): + # A generic dataset schema - primarily used for manual metadata entry for cataloging a dataset + # It should not be used for fetched/extracted metadata from external sources - subtypes specifically defined + # should be used for that + type: str = Field( + alias="@type", + title="Submission type", + default="Dataset", + const=True, + description="Type of submission", + ) + additionalType: Optional[str] = Field( + title="Additional type of submission", + description="An additional type for the dataset." + ) + temporalCoverage: Optional[TemporalCoverage] = Field( + title="Temporal coverage", + description="The time period that applies to the dataset.", + ) + spatialCoverage: Optional[Place] = Field( + description="The spatialCoverage of a CreativeWork indicates the place(s) which are the focus of the content. " + "It is a sub property of contentLocation intended primarily for more technical and " + "detailed materials. For example with a Dataset, it indicates areas that the dataset " + "describes: a dataset of New York weather would have spatialCoverage which was the " + "place: the state of New York.", + ) variableMeasured: Optional[List[Union[str, PropertyValue]]] = Field( title="Variables measured", description="Measured variables." ) - additionalProperty: Optional[List[PropertyValue]] = Field( - title="Additional properties", - default=[], - description="Additional properties of the dataset." + associatedMedia: List[MediaObject] = Field( + title="Dataset content", + description="A media object that encodes this CreativeWork. This property is a synonym for encoding.", ) sourceOrganization: Optional[SourceOrganization] = Field( title="Source organization", description="The organization that provided the data for this dataset." ) + + +class HSNetCDFMetadata(GenericDatasetMetadata): + # modeled after hydroshare netcdf aggregation metadata + # used for registering a netcdf dataset in the catalog - netcdf metadata is saved as a catalog record + + additionalType: str = Field( + title="Specific additional type of submission", + default="NetCDF", + const=True, + description="Specific additional type of submission", + ) + temporalCoverage: TemporalCoverage = Field( + title="Temporal coverage", + description="The time period that applies to the dataset.", + readOnly=True, + ) + spatialCoverage: Place = Field( + description="The spatialCoverage of a CreativeWork indicates the place(s) which are the focus of the content. " + "It is a sub property of contentLocation intended primarily for more technical and " + "detailed materials. For example with a Dataset, it indicates areas that the dataset " + "describes: a dataset of New York weather would have spatialCoverage which was the " + "place: the state of New York.", + readOnly=True, + ) + variableMeasured: List[Union[str, PropertyValue]] = Field( + title="Variables measured", + description="Measured variables.", + readOnly=True, + ) + + +class HSRasterMetadata(GenericDatasetMetadata): + # modeled after hydroshare raster aggregation metadata + # used for registering a raster dataset in the catalog - raster metadata is saved as a catalog record + + additionalType: str = Field( + title="Specific additional type of submission", + default="Geo Raster", + const=True, + description="Specific additional type of submission", + ) + temporalCoverage: Optional[TemporalCoverage] = Field( + title="Temporal coverage", + description="The time period that applies to the dataset.", + readOnly=True, + ) + spatialCoverage: Place = Field( + description="The spatialCoverage of a CreativeWork indicates the place(s) which are the focus of the content. " + "It is a sub property of contentLocation intended primarily for more technical and " + "detailed materials. For example with a Dataset, it indicates areas that the dataset " + "describes: a dataset of New York weather would have spatialCoverage which was the " + "place: the state of New York.", + readOnly=True, + ) diff --git a/api/models/schemas/schema.json b/api/models/schemas/schema.json index 779792b..40874f0 100644 --- a/api/models/schemas/schema.json +++ b/api/models/schemas/schema.json @@ -1,5 +1,5 @@ { - "title": "DatasetMetadata", + "title": "GenericDatasetMetadata", "type": "object", "properties": { "@context": { @@ -16,13 +16,9 @@ }, "@type": { "title": "Submission type", - "description": "Submission type can include various forms of content, such as datasets, software source code, digital documents, etc.", + "description": "Type of submission", "default": "Dataset", - "enum": [ - "Dataset", - "Notebook", - "Software Source Code" - ], + "const": "Dataset", "type": "string" }, "name": { @@ -48,7 +44,7 @@ }, "identifier": { "title": "Identifiers", - "description": "Any kind of identifier for the resource. Identifiers may be DOIs or unique strings assigned by a repository. Multiple identifiers can be entered. Where identifiers can be encoded as URLs, enter URLs here.", + "description": "Any kind of identifier for the resource/dataset. Identifiers may be DOIs or unique strings assigned by a repository. Multiple identifiers can be entered. Where identifiers can be encoded as URLs, enter URLs here.", "type": "array", "items": { "type": "string", @@ -631,145 +627,514 @@ ] } }, - "temporalCoverage": { - "title": "TemporalCoverage", - "description": "The time period that applies to all of the content within the resource.", - "type": "object", - "properties": { - "startDate": { - "title": "Start date", - "description": "A date/time object containing the instant corresponding to the commencement of the time interval (ISO8601 formatted date - YYYY-MM-DDTHH:MM).", - "type": "string", - "format": "date-time" + "hasPart": { + "title": "Has part", + "description": "Link to or citation for a related resource that is part of this resource.", + "type": "array", + "items": { + "title": "HasPart", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "Submission type can include various forms of content, such as datasets, software source code, digital documents, etc.", + "default": "CreativeWork", + "type": "string" + }, + "name": { + "title": "Name or title", + "description": "Submission's name or title", + "type": "string" + }, + "url": { + "title": "URL", + "description": "The URL address to the data resource.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "description": { + "title": "Description", + "description": "Information about a related resource that is part of this resource.", + "type": "string" + } }, - "endDate": { - "title": "End date", - "description": "A date/time object containing the instant corresponding to the termination of the time interval (ISO8601 formatted date - YYYY-MM-DDTHH:MM). If the ending date is left off, that means the temporal coverage is ongoing.", - "type": "string", - "format": "date-time" - } - }, - "required": [ - "startDate" - ] + "required": [ + "name" + ] + } }, - "spatialCoverage": { - "title": "Place", - "description": "The spatialCoverage of a CreativeWork indicates the place(s) which are the focus of the content. It is a sub property of contentLocation intended primarily for more technical and detailed materials. For example with a Dataset, it indicates areas that the dataset describes: a dataset of New York weather would have spatialCoverage which was the place: the state of New York.", - "type": "object", - "properties": { - "@type": { - "title": "@Type", - "description": "Represents the focus area of the record's content.", - "default": "Place", - "type": "string" - }, - "name": { - "title": "Name", - "description": "Name of the place.", - "type": "string" - }, - "geo": { - "title": "Geo", - "description": "Specifies the geographic coordinates of the place in the form of a point location, line, or area coverage extent.", - "anyOf": [ - { - "title": "GeoCoordinates", - "type": "object", - "properties": { - "@type": { - "title": "@Type", - "description": "Geographic coordinates that represent a specific location on the Earth's surface. GeoCoordinates typically consists of two components: latitude and longitude.", - "default": "GeoCoordinates", - "type": "string" - }, - "latitude": { - "title": "Latitude", - "description": "Represents the angular distance of a location north or south of the equator, measured in degrees and ranges from -90 to +90 degrees.", - "type": "number" - }, - "longitude": { - "title": "Longitude", - "description": "Represents the angular distance of a location east or west of the Prime Meridian, measured in degrees and ranges from -180 to +180 degrees.", - "type": "number" - } - }, - "required": [ - "latitude", - "longitude" - ] - }, - { - "title": "GeoShape", - "type": "object", - "properties": { - "@type": { - "title": "@Type", - "description": "A structured representation that describes the coordinates of a geographic feature.", - "default": "GeoShape", - "type": "string" - }, - "box": { - "title": "Box", - "description": "A box is a rectangular region defined by a pair of coordinates representing the southwest and northeast corners of the box.", - "type": "string" - } - }, - "required": [ - "box" - ] + "isPartOf": { + "title": "Is part of", + "description": "Link to or citation for a related resource that this resource is a part of - e.g., a related collection.", + "type": "array", + "items": { + "title": "IsPartOf", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "Submission type can include various forms of content, such as datasets, software source code, digital documents, etc.", + "default": "CreativeWork", + "type": "string" + }, + "name": { + "title": "Name or title", + "description": "Submission's name or title", + "type": "string" + }, + "url": { + "title": "URL", + "description": "The URL address to the data resource.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" } - ] + }, + "description": { + "title": "Description", + "description": "Information about a related resource that this resource is a part of - e.g., a related collection.", + "type": "string" + } }, - "additionalProperty": { - "title": "Additional properties", - "description": "Additional properties of the place.", - "default": [], - "type": "array", - "items": { - "title": "PropertyValue", + "required": [ + "name" + ] + } + }, + "associatedMedia": { + "title": "Dataset content", + "description": "A media object that encodes this CreativeWork. This property is a synonym for encoding.", + "type": "array", + "items": { + "title": "MediaObject", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "An item that encodes the record.", + "default": "MediaObject", + "type": "string" + }, + "contentUrl": { + "title": "Content URL", + "description": "The direct URL link to access or download the actual content of the media object.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "encodingFormat": { + "title": "Encoding format", + "description": "Represents the specific file format in which the media is encoded.", + "type": "string" + }, + "contentSize": { + "title": "Content size", + "description": "Represents the file size, expressed in bytes, kilobytes, megabytes, or another unit of measurement.", + "type": "string" + }, + "name": { + "title": "Name", + "description": "The name of the media object (file).", + "type": "string" + }, + "sha256": { + "title": "SHA-256", + "description": "The SHA-256 hash of the media object.", + "type": "string" + }, + "isPartOf": { + "title": "MediaObjectPartOf", + "description": "Link to or citation for a related metadata document that this media object is a part of", "type": "object", "properties": { "@type": { "title": "@Type", - "description": "A property-value pair.", - "default": "PropertyValue", - "const": "PropertyValue", - "type": "string" - }, - "propertyID": { - "title": "Property ID", - "description": "The ID of the property.", + "description": "Submission type can include various forms of content, such as datasets, software source code, digital documents, etc.", + "default": "CreativeWork", "type": "string" }, "name": { - "title": "Name", - "description": "The name of the property.", + "title": "Name or title", + "description": "Submission's name or title", "type": "string" }, - "value": { - "title": "Value", - "description": "The value of the property.", - "anyOf": [ - { - "type": "string" - }, - { - "title": "PropertyValue", - "type": "object", - "properties": { - "@type": { - "title": "@Type", - "description": "A property-value pair.", - "default": "PropertyValue", - "const": "PropertyValue", - "type": "string" - }, - "propertyID": { - "title": "Property ID", - "description": "The ID of the property.", - "type": "string" - }, + "url": { + "title": "URL", + "description": "The URL address to the related metadata document.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "description": { + "title": "Description", + "description": "Information about a related metadata document.", + "type": "string" + } + }, + "required": [ + "name" + ] + } + }, + "required": [ + "contentUrl", + "contentSize", + "name" + ] + } + }, + "citation": { + "title": "Citation", + "description": "A bibliographic citation for the resource.", + "type": "array", + "items": { + "type": "string" + } + }, + "additionalProperty": { + "title": "Additional properties", + "description": "Additional properties of the dataset/resource.", + "default": [], + "type": "array", + "items": { + "title": "PropertyValue", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A property-value pair.", + "default": "PropertyValue", + "const": "PropertyValue", + "type": "string" + }, + "propertyID": { + "title": "Property ID", + "description": "The ID of the property.", + "type": "string" + }, + "name": { + "title": "Name", + "description": "The name of the property.", + "type": "string" + }, + "value": { + "title": "Value", + "description": "The value of the property.", + "anyOf": [ + { + "type": "string" + }, + { + "title": "PropertyValue", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A property-value pair.", + "default": "PropertyValue", + "const": "PropertyValue", + "type": "string" + }, + "propertyID": { + "title": "Property ID", + "description": "The ID of the property.", + "type": "string" + }, + "name": { + "title": "Name", + "description": "The name of the property.", + "type": "string" + }, + "value": { + "title": "Value", + "description": "The value of the property.", + "type": "string" + }, + "unitCode": { + "title": "Measurement unit", + "description": "The unit of measurement for the value.", + "type": "string" + }, + "description": { + "title": "Description", + "description": "A description of the property.", + "type": "string" + }, + "minValue": { + "title": "Minimum value", + "description": "The minimum allowed value for the property.", + "type": "number" + }, + "maxValue": { + "title": "Maximum value", + "description": "The maximum allowed value for the property.", + "type": "number" + }, + "measurementTechnique": { + "title": "Measurement technique", + "description": "A technique or technology used in a measurement.", + "type": "string" + } + }, + "required": [ + "name", + "value" + ] + }, + { + "type": "array", + "items": { + "title": "PropertyValue", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A property-value pair.", + "default": "PropertyValue", + "const": "PropertyValue", + "type": "string" + }, + "propertyID": { + "title": "Property ID", + "description": "The ID of the property.", + "type": "string" + }, + "name": { + "title": "Name", + "description": "The name of the property.", + "type": "string" + }, + "value": { + "title": "Value", + "description": "The value of the property.", + "type": "string" + }, + "unitCode": { + "title": "Measurement unit", + "description": "The unit of measurement for the value.", + "type": "string" + }, + "description": { + "title": "Description", + "description": "A description of the property.", + "type": "string" + }, + "minValue": { + "title": "Minimum value", + "description": "The minimum allowed value for the property.", + "type": "number" + }, + "maxValue": { + "title": "Maximum value", + "description": "The maximum allowed value for the property.", + "type": "number" + }, + "measurementTechnique": { + "title": "Measurement technique", + "description": "A technique or technology used in a measurement.", + "type": "string" + } + }, + "required": [ + "name", + "value" + ] + } + } + ] + }, + "unitCode": { + "title": "Measurement unit", + "description": "The unit of measurement for the value.", + "type": "string" + }, + "description": { + "title": "Description", + "description": "A description of the property.", + "type": "string" + }, + "minValue": { + "title": "Minimum value", + "description": "The minimum allowed value for the property.", + "type": "number" + }, + "maxValue": { + "title": "Maximum value", + "description": "The maximum allowed value for the property.", + "type": "number" + }, + "measurementTechnique": { + "title": "Measurement technique", + "description": "A technique or technology used in a measurement.", + "type": "string" + } + }, + "required": [ + "name", + "value" + ] + } + }, + "additionalType": { + "title": "Additional type of submission", + "description": "An additional type for the dataset.", + "type": "string" + }, + "temporalCoverage": { + "title": "TemporalCoverage", + "description": "The time period that applies to the dataset.", + "type": "object", + "properties": { + "startDate": { + "title": "Start date", + "description": "A date/time object containing the instant corresponding to the commencement of the time interval (ISO8601 formatted date - YYYY-MM-DDTHH:MM).", + "type": "string", + "format": "date-time" + }, + "endDate": { + "title": "End date", + "description": "A date/time object containing the instant corresponding to the termination of the time interval (ISO8601 formatted date - YYYY-MM-DDTHH:MM). If the ending date is left off, that means the temporal coverage is ongoing.", + "type": "string", + "format": "date-time" + } + }, + "required": [ + "startDate" + ] + }, + "spatialCoverage": { + "title": "Place", + "description": "The spatialCoverage of a CreativeWork indicates the place(s) which are the focus of the content. It is a sub property of contentLocation intended primarily for more technical and detailed materials. For example with a Dataset, it indicates areas that the dataset describes: a dataset of New York weather would have spatialCoverage which was the place: the state of New York.", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "Represents the focus area of the record's content.", + "default": "Place", + "type": "string" + }, + "name": { + "title": "Name", + "description": "Name of the place.", + "type": "string" + }, + "geo": { + "title": "Geo", + "description": "Specifies the geographic coordinates of the place in the form of a point location, line, or area coverage extent.", + "anyOf": [ + { + "title": "GeoCoordinates", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "Geographic coordinates that represent a specific location on the Earth's surface. GeoCoordinates typically consists of two components: latitude and longitude.", + "default": "GeoCoordinates", + "type": "string" + }, + "latitude": { + "title": "Latitude", + "description": "Represents the angular distance of a location north or south of the equator, measured in degrees and ranges from -90 to +90 degrees.", + "type": "number" + }, + "longitude": { + "title": "Longitude", + "description": "Represents the angular distance of a location east or west of the Prime Meridian, measured in degrees and ranges from -180 to +180 degrees.", + "type": "number" + } + }, + "required": [ + "latitude", + "longitude" + ] + }, + { + "title": "GeoShape", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A structured representation that describes the coordinates of a geographic feature.", + "default": "GeoShape", + "type": "string" + }, + "box": { + "title": "Box", + "description": "A box is a rectangular region defined by a pair of coordinates representing the southwest and northeast corners of the box.", + "type": "string" + } + }, + "required": [ + "box" + ] + } + ] + }, + "additionalProperty": { + "title": "Additional properties", + "description": "Additional properties of the place.", + "default": [], + "type": "array", + "items": { + "title": "PropertyValue", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A property-value pair.", + "default": "PropertyValue", + "const": "PropertyValue", + "type": "string" + }, + "propertyID": { + "title": "Property ID", + "description": "The ID of the property.", + "type": "string" + }, + "name": { + "title": "Name", + "description": "The name of the property.", + "type": "string" + }, + "value": { + "title": "Value", + "description": "The value of the property.", + "anyOf": [ + { + "type": "string" + }, + { + "title": "PropertyValue", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A property-value pair.", + "default": "PropertyValue", + "const": "PropertyValue", + "type": "string" + }, + "propertyID": { + "title": "Property ID", + "description": "The ID of the property.", + "type": "string" + }, "name": { "title": "Name", "description": "The name of the property.", @@ -830,265 +1195,81 @@ "type": "string" }, "name": { - "title": "Name", - "description": "The name of the property.", - "type": "string" - }, - "value": { - "title": "Value", - "description": "The value of the property.", - "type": "string" - }, - "unitCode": { - "title": "Measurement unit", - "description": "The unit of measurement for the value.", - "type": "string" - }, - "description": { - "title": "Description", - "description": "A description of the property.", - "type": "string" - }, - "minValue": { - "title": "Minimum value", - "description": "The minimum allowed value for the property.", - "type": "number" - }, - "maxValue": { - "title": "Maximum value", - "description": "The maximum allowed value for the property.", - "type": "number" - }, - "measurementTechnique": { - "title": "Measurement technique", - "description": "A technique or technology used in a measurement.", - "type": "string" - } - }, - "required": [ - "name", - "value" - ] - } - } - ] - }, - "unitCode": { - "title": "Measurement unit", - "description": "The unit of measurement for the value.", - "type": "string" - }, - "description": { - "title": "Description", - "description": "A description of the property.", - "type": "string" - }, - "minValue": { - "title": "Minimum value", - "description": "The minimum allowed value for the property.", - "type": "number" - }, - "maxValue": { - "title": "Maximum value", - "description": "The maximum allowed value for the property.", - "type": "number" - }, - "measurementTechnique": { - "title": "Measurement technique", - "description": "A technique or technology used in a measurement.", - "type": "string" - } - }, - "required": [ - "name", - "value" - ] - } - } - } - }, - "hasPart": { - "title": "Has part", - "description": "Link to or citation for a related resource that is part of this resource.", - "type": "array", - "items": { - "title": "HasPart", - "type": "object", - "properties": { - "@type": { - "title": "@Type", - "description": "Submission type can include various forms of content, such as datasets, software source code, digital documents, etc.", - "default": "CreativeWork", - "type": "string" - }, - "name": { - "title": "Name or title", - "description": "Submission's name or title", - "type": "string" - }, - "url": { - "title": "URL", - "description": "The URL address to the data resource.", - "minLength": 1, - "maxLength": 2083, - "type": "string", - "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", - "errorMessage": { - "pattern": "must match format \"url\"" - } - }, - "description": { - "title": "Description", - "description": "Information about a related resource that is part of this resource.", - "type": "string" - } - }, - "required": [ - "name" - ] - } - }, - "isPartOf": { - "title": "Is part of", - "description": "Link to or citation for a related resource that this resource is a part of - e.g., a related collection.", - "type": "array", - "items": { - "title": "IsPartOf", - "type": "object", - "properties": { - "@type": { - "title": "@Type", - "description": "Submission type can include various forms of content, such as datasets, software source code, digital documents, etc.", - "default": "CreativeWork", - "type": "string" - }, - "name": { - "title": "Name or title", - "description": "Submission's name or title", - "type": "string" - }, - "url": { - "title": "URL", - "description": "The URL address to the data resource.", - "minLength": 1, - "maxLength": 2083, - "type": "string", - "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", - "errorMessage": { - "pattern": "must match format \"url\"" - } - }, - "description": { - "title": "Description", - "description": "Information about a related resource that this resource is a part of - e.g., a related collection.", - "type": "string" - } - }, - "required": [ - "name" - ] - } - }, - "associatedMedia": { - "title": "Resource content", - "description": "A media object that encodes this CreativeWork. This property is a synonym for encoding.", - "type": "array", - "items": { - "title": "MediaObject", - "type": "object", - "properties": { - "@type": { - "title": "@Type", - "description": "An item that encodes the record.", - "default": "MediaObject", - "type": "string" - }, - "contentUrl": { - "title": "Content URL", - "description": "The direct URL link to access or download the actual content of the media object.", - "minLength": 1, - "maxLength": 2083, - "type": "string", - "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", - "errorMessage": { - "pattern": "must match format \"url\"" - } - }, - "encodingFormat": { - "title": "Encoding format", - "description": "Represents the specific file format in which the media is encoded.", - "type": "string" - }, - "contentSize": { - "title": "Content size", - "description": "Represents the file size, expressed in bytes, kilobytes, megabytes, or another unit of measurement.", - "type": "string" - }, - "name": { - "title": "Name", - "description": "The name of the media object (file).", - "type": "string" - }, - "sha256": { - "title": "SHA-256", - "description": "The SHA-256 hash of the media object.", - "type": "string" - }, - "isPartOf": { - "title": "Is part of", - "description": "Link to or citation for a related metadata document that this media object is a part of", - "type": "array", - "items": { - "title": "MediaObjectPartOf", - "type": "object", - "properties": { - "@type": { - "title": "@Type", - "description": "Submission type can include various forms of content, such as datasets, software source code, digital documents, etc.", - "default": "CreativeWork", - "type": "string" - }, - "name": { - "title": "Name or title", - "description": "Submission's name or title", - "type": "string" - }, - "url": { - "title": "URL", - "description": "The URL address to the related metadata document.", - "minLength": 1, - "maxLength": 2083, - "type": "string", - "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", - "errorMessage": { - "pattern": "must match format \"url\"" + "title": "Name", + "description": "The name of the property.", + "type": "string" + }, + "value": { + "title": "Value", + "description": "The value of the property.", + "type": "string" + }, + "unitCode": { + "title": "Measurement unit", + "description": "The unit of measurement for the value.", + "type": "string" + }, + "description": { + "title": "Description", + "description": "A description of the property.", + "type": "string" + }, + "minValue": { + "title": "Minimum value", + "description": "The minimum allowed value for the property.", + "type": "number" + }, + "maxValue": { + "title": "Maximum value", + "description": "The maximum allowed value for the property.", + "type": "number" + }, + "measurementTechnique": { + "title": "Measurement technique", + "description": "A technique or technology used in a measurement.", + "type": "string" + } + }, + "required": [ + "name", + "value" + ] + } } - }, - "description": { - "title": "Description", - "description": "Information about a related metadata document.", - "type": "string" - } + ] }, - "required": [ - "name" - ] - } + "unitCode": { + "title": "Measurement unit", + "description": "The unit of measurement for the value.", + "type": "string" + }, + "description": { + "title": "Description", + "description": "A description of the property.", + "type": "string" + }, + "minValue": { + "title": "Minimum value", + "description": "The minimum allowed value for the property.", + "type": "number" + }, + "maxValue": { + "title": "Maximum value", + "description": "The maximum allowed value for the property.", + "type": "number" + }, + "measurementTechnique": { + "title": "Measurement technique", + "description": "A technique or technology used in a measurement.", + "type": "string" + } + }, + "required": [ + "name", + "value" + ] } - }, - "required": [ - "contentUrl", - "contentSize", - "name" - ] - } - }, - "citation": { - "title": "Citation", - "description": "A bibliographic citation for the resource.", - "type": "array", - "items": { - "type": "string" + } } }, "variableMeasured": { @@ -1268,200 +1449,16 @@ "type": "number" }, "measurementTechnique": { - "title": "Measurement technique", - "description": "A technique or technology used in a measurement.", - "type": "string" - } - }, - "required": [ - "name", - "value" - ] - } - ] - } - }, - "additionalProperty": { - "title": "Additional properties", - "description": "Additional properties of the dataset.", - "default": [], - "type": "array", - "items": { - "title": "PropertyValue", - "type": "object", - "properties": { - "@type": { - "title": "@Type", - "description": "A property-value pair.", - "default": "PropertyValue", - "const": "PropertyValue", - "type": "string" - }, - "propertyID": { - "title": "Property ID", - "description": "The ID of the property.", - "type": "string" - }, - "name": { - "title": "Name", - "description": "The name of the property.", - "type": "string" - }, - "value": { - "title": "Value", - "description": "The value of the property.", - "anyOf": [ - { - "type": "string" - }, - { - "title": "PropertyValue", - "type": "object", - "properties": { - "@type": { - "title": "@Type", - "description": "A property-value pair.", - "default": "PropertyValue", - "const": "PropertyValue", - "type": "string" - }, - "propertyID": { - "title": "Property ID", - "description": "The ID of the property.", - "type": "string" - }, - "name": { - "title": "Name", - "description": "The name of the property.", - "type": "string" - }, - "value": { - "title": "Value", - "description": "The value of the property.", - "type": "string" - }, - "unitCode": { - "title": "Measurement unit", - "description": "The unit of measurement for the value.", - "type": "string" - }, - "description": { - "title": "Description", - "description": "A description of the property.", - "type": "string" - }, - "minValue": { - "title": "Minimum value", - "description": "The minimum allowed value for the property.", - "type": "number" - }, - "maxValue": { - "title": "Maximum value", - "description": "The maximum allowed value for the property.", - "type": "number" - }, - "measurementTechnique": { - "title": "Measurement technique", - "description": "A technique or technology used in a measurement.", - "type": "string" - } - }, - "required": [ - "name", - "value" - ] - }, - { - "type": "array", - "items": { - "title": "PropertyValue", - "type": "object", - "properties": { - "@type": { - "title": "@Type", - "description": "A property-value pair.", - "default": "PropertyValue", - "const": "PropertyValue", - "type": "string" - }, - "propertyID": { - "title": "Property ID", - "description": "The ID of the property.", - "type": "string" - }, - "name": { - "title": "Name", - "description": "The name of the property.", - "type": "string" - }, - "value": { - "title": "Value", - "description": "The value of the property.", - "type": "string" - }, - "unitCode": { - "title": "Measurement unit", - "description": "The unit of measurement for the value.", - "type": "string" - }, - "description": { - "title": "Description", - "description": "A description of the property.", - "type": "string" - }, - "minValue": { - "title": "Minimum value", - "description": "The minimum allowed value for the property.", - "type": "number" - }, - "maxValue": { - "title": "Maximum value", - "description": "The maximum allowed value for the property.", - "type": "number" - }, - "measurementTechnique": { - "title": "Measurement technique", - "description": "A technique or technology used in a measurement.", - "type": "string" - } - }, - "required": [ - "name", - "value" - ] - } + "title": "Measurement technique", + "description": "A technique or technology used in a measurement.", + "type": "string" } + }, + "required": [ + "name", + "value" ] - }, - "unitCode": { - "title": "Measurement unit", - "description": "The unit of measurement for the value.", - "type": "string" - }, - "description": { - "title": "Description", - "description": "A description of the property.", - "type": "string" - }, - "minValue": { - "title": "Minimum value", - "description": "The minimum allowed value for the property.", - "type": "number" - }, - "maxValue": { - "title": "Maximum value", - "description": "The maximum allowed value for the property.", - "type": "number" - }, - "measurementTechnique": { - "title": "Measurement technique", - "description": "A technique or technology used in a measurement.", - "type": "string" } - }, - "required": [ - "name", - "value" ] } }, @@ -1505,13 +1502,8 @@ }, "required": [ "name", - "description", "url", - "creator", - "dateCreated", - "keywords", - "license", - "provider" + "associatedMedia" ], "definitions": { "Affiliation": { @@ -2014,71 +2006,197 @@ "name" ] }, - "TemporalCoverage": { - "title": "TemporalCoverage", + "HasPart": { + "title": "HasPart", "type": "object", "properties": { - "startDate": { - "title": "Start date", - "description": "A date/time object containing the instant corresponding to the commencement of the time interval (ISO8601 formatted date - YYYY-MM-DDTHH:MM).", + "@type": { + "title": "@Type", + "description": "Submission type can include various forms of content, such as datasets, software source code, digital documents, etc.", + "default": "CreativeWork", + "type": "string" + }, + "name": { + "title": "Name or title", + "description": "Submission's name or title", + "type": "string" + }, + "url": { + "title": "URL", + "description": "The URL address to the data resource.", + "minLength": 1, + "maxLength": 2083, "type": "string", - "format": "date-time" + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } }, - "endDate": { - "title": "End date", - "description": "A date/time object containing the instant corresponding to the termination of the time interval (ISO8601 formatted date - YYYY-MM-DDTHH:MM). If the ending date is left off, that means the temporal coverage is ongoing.", + "description": { + "title": "Description", + "description": "Information about a related resource that is part of this resource.", + "type": "string" + } + }, + "required": [ + "name" + ] + }, + "IsPartOf": { + "title": "IsPartOf", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "Submission type can include various forms of content, such as datasets, software source code, digital documents, etc.", + "default": "CreativeWork", + "type": "string" + }, + "name": { + "title": "Name or title", + "description": "Submission's name or title", + "type": "string" + }, + "url": { + "title": "URL", + "description": "The URL address to the data resource.", + "minLength": 1, + "maxLength": 2083, "type": "string", - "format": "date-time" + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "description": { + "title": "Description", + "description": "Information about a related resource that this resource is a part of - e.g., a related collection.", + "type": "string" } }, "required": [ - "startDate" + "name" ] }, - "GeoCoordinates": { - "title": "GeoCoordinates", + "MediaObjectPartOf": { + "title": "MediaObjectPartOf", "type": "object", "properties": { "@type": { "title": "@Type", - "description": "Geographic coordinates that represent a specific location on the Earth's surface. GeoCoordinates typically consists of two components: latitude and longitude.", - "default": "GeoCoordinates", + "description": "Submission type can include various forms of content, such as datasets, software source code, digital documents, etc.", + "default": "CreativeWork", "type": "string" }, - "latitude": { - "title": "Latitude", - "description": "Represents the angular distance of a location north or south of the equator, measured in degrees and ranges from -90 to +90 degrees.", - "type": "number" + "name": { + "title": "Name or title", + "description": "Submission's name or title", + "type": "string" }, - "longitude": { - "title": "Longitude", - "description": "Represents the angular distance of a location east or west of the Prime Meridian, measured in degrees and ranges from -180 to +180 degrees.", - "type": "number" + "url": { + "title": "URL", + "description": "The URL address to the related metadata document.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "description": { + "title": "Description", + "description": "Information about a related metadata document.", + "type": "string" } }, "required": [ - "latitude", - "longitude" + "name" ] }, - "GeoShape": { - "title": "GeoShape", + "MediaObject": { + "title": "MediaObject", "type": "object", "properties": { "@type": { "title": "@Type", - "description": "A structured representation that describes the coordinates of a geographic feature.", - "default": "GeoShape", + "description": "An item that encodes the record.", + "default": "MediaObject", "type": "string" }, - "box": { - "title": "Box", - "description": "A box is a rectangular region defined by a pair of coordinates representing the southwest and northeast corners of the box.", + "contentUrl": { + "title": "Content URL", + "description": "The direct URL link to access or download the actual content of the media object.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "encodingFormat": { + "title": "Encoding format", + "description": "Represents the specific file format in which the media is encoded.", + "type": "string" + }, + "contentSize": { + "title": "Content size", + "description": "Represents the file size, expressed in bytes, kilobytes, megabytes, or another unit of measurement.", + "type": "string" + }, + "name": { + "title": "Name", + "description": "The name of the media object (file).", + "type": "string" + }, + "sha256": { + "title": "SHA-256", + "description": "The SHA-256 hash of the media object.", "type": "string" + }, + "isPartOf": { + "title": "MediaObjectPartOf", + "description": "Link to or citation for a related metadata document that this media object is a part of", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "Submission type can include various forms of content, such as datasets, software source code, digital documents, etc.", + "default": "CreativeWork", + "type": "string" + }, + "name": { + "title": "Name or title", + "description": "Submission's name or title", + "type": "string" + }, + "url": { + "title": "URL", + "description": "The URL address to the related metadata document.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "description": { + "title": "Description", + "description": "Information about a related metadata document.", + "type": "string" + } + }, + "required": [ + "name" + ] } }, "required": [ - "box" + "contentUrl", + "contentSize", + "name" ] }, "PropertyValueBase": { @@ -2290,30 +2408,97 @@ "description": "The unit of measurement for the value.", "type": "string" }, - "description": { - "title": "Description", - "description": "A description of the property.", + "description": { + "title": "Description", + "description": "A description of the property.", + "type": "string" + }, + "minValue": { + "title": "Minimum value", + "description": "The minimum allowed value for the property.", + "type": "number" + }, + "maxValue": { + "title": "Maximum value", + "description": "The maximum allowed value for the property.", + "type": "number" + }, + "measurementTechnique": { + "title": "Measurement technique", + "description": "A technique or technology used in a measurement.", + "type": "string" + } + }, + "required": [ + "name", + "value" + ] + }, + "TemporalCoverage": { + "title": "TemporalCoverage", + "type": "object", + "properties": { + "startDate": { + "title": "Start date", + "description": "A date/time object containing the instant corresponding to the commencement of the time interval (ISO8601 formatted date - YYYY-MM-DDTHH:MM).", + "type": "string", + "format": "date-time" + }, + "endDate": { + "title": "End date", + "description": "A date/time object containing the instant corresponding to the termination of the time interval (ISO8601 formatted date - YYYY-MM-DDTHH:MM). If the ending date is left off, that means the temporal coverage is ongoing.", + "type": "string", + "format": "date-time" + } + }, + "required": [ + "startDate" + ] + }, + "GeoCoordinates": { + "title": "GeoCoordinates", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "Geographic coordinates that represent a specific location on the Earth's surface. GeoCoordinates typically consists of two components: latitude and longitude.", + "default": "GeoCoordinates", "type": "string" }, - "minValue": { - "title": "Minimum value", - "description": "The minimum allowed value for the property.", + "latitude": { + "title": "Latitude", + "description": "Represents the angular distance of a location north or south of the equator, measured in degrees and ranges from -90 to +90 degrees.", "type": "number" }, - "maxValue": { - "title": "Maximum value", - "description": "The maximum allowed value for the property.", + "longitude": { + "title": "Longitude", + "description": "Represents the angular distance of a location east or west of the Prime Meridian, measured in degrees and ranges from -180 to +180 degrees.", "type": "number" + } + }, + "required": [ + "latitude", + "longitude" + ] + }, + "GeoShape": { + "title": "GeoShape", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A structured representation that describes the coordinates of a geographic feature.", + "default": "GeoShape", + "type": "string" }, - "measurementTechnique": { - "title": "Measurement technique", - "description": "A technique or technology used in a measurement.", + "box": { + "title": "Box", + "description": "A box is a rectangular region defined by a pair of coordinates representing the southwest and northeast corners of the box.", "type": "string" } }, "required": [ - "name", - "value" + "box" ] }, "Place": { @@ -2569,203 +2754,6 @@ } } }, - "HasPart": { - "title": "HasPart", - "type": "object", - "properties": { - "@type": { - "title": "@Type", - "description": "Submission type can include various forms of content, such as datasets, software source code, digital documents, etc.", - "default": "CreativeWork", - "type": "string" - }, - "name": { - "title": "Name or title", - "description": "Submission's name or title", - "type": "string" - }, - "url": { - "title": "URL", - "description": "The URL address to the data resource.", - "minLength": 1, - "maxLength": 2083, - "type": "string", - "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", - "errorMessage": { - "pattern": "must match format \"url\"" - } - }, - "description": { - "title": "Description", - "description": "Information about a related resource that is part of this resource.", - "type": "string" - } - }, - "required": [ - "name" - ] - }, - "IsPartOf": { - "title": "IsPartOf", - "type": "object", - "properties": { - "@type": { - "title": "@Type", - "description": "Submission type can include various forms of content, such as datasets, software source code, digital documents, etc.", - "default": "CreativeWork", - "type": "string" - }, - "name": { - "title": "Name or title", - "description": "Submission's name or title", - "type": "string" - }, - "url": { - "title": "URL", - "description": "The URL address to the data resource.", - "minLength": 1, - "maxLength": 2083, - "type": "string", - "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", - "errorMessage": { - "pattern": "must match format \"url\"" - } - }, - "description": { - "title": "Description", - "description": "Information about a related resource that this resource is a part of - e.g., a related collection.", - "type": "string" - } - }, - "required": [ - "name" - ] - }, - "MediaObjectPartOf": { - "title": "MediaObjectPartOf", - "type": "object", - "properties": { - "@type": { - "title": "@Type", - "description": "Submission type can include various forms of content, such as datasets, software source code, digital documents, etc.", - "default": "CreativeWork", - "type": "string" - }, - "name": { - "title": "Name or title", - "description": "Submission's name or title", - "type": "string" - }, - "url": { - "title": "URL", - "description": "The URL address to the related metadata document.", - "minLength": 1, - "maxLength": 2083, - "type": "string", - "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", - "errorMessage": { - "pattern": "must match format \"url\"" - } - }, - "description": { - "title": "Description", - "description": "Information about a related metadata document.", - "type": "string" - } - }, - "required": [ - "name" - ] - }, - "MediaObject": { - "title": "MediaObject", - "type": "object", - "properties": { - "@type": { - "title": "@Type", - "description": "An item that encodes the record.", - "default": "MediaObject", - "type": "string" - }, - "contentUrl": { - "title": "Content URL", - "description": "The direct URL link to access or download the actual content of the media object.", - "minLength": 1, - "maxLength": 2083, - "type": "string", - "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", - "errorMessage": { - "pattern": "must match format \"url\"" - } - }, - "encodingFormat": { - "title": "Encoding format", - "description": "Represents the specific file format in which the media is encoded.", - "type": "string" - }, - "contentSize": { - "title": "Content size", - "description": "Represents the file size, expressed in bytes, kilobytes, megabytes, or another unit of measurement.", - "type": "string" - }, - "name": { - "title": "Name", - "description": "The name of the media object (file).", - "type": "string" - }, - "sha256": { - "title": "SHA-256", - "description": "The SHA-256 hash of the media object.", - "type": "string" - }, - "isPartOf": { - "title": "Is part of", - "description": "Link to or citation for a related metadata document that this media object is a part of", - "type": "array", - "items": { - "title": "MediaObjectPartOf", - "type": "object", - "properties": { - "@type": { - "title": "@Type", - "description": "Submission type can include various forms of content, such as datasets, software source code, digital documents, etc.", - "default": "CreativeWork", - "type": "string" - }, - "name": { - "title": "Name or title", - "description": "Submission's name or title", - "type": "string" - }, - "url": { - "title": "URL", - "description": "The URL address to the related metadata document.", - "minLength": 1, - "maxLength": 2083, - "type": "string", - "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", - "errorMessage": { - "pattern": "must match format \"url\"" - } - }, - "description": { - "title": "Description", - "description": "Information about a related metadata document.", - "type": "string" - } - }, - "required": [ - "name" - ] - } - } - }, - "required": [ - "contentUrl", - "contentSize", - "name" - ] - }, "SourceOrganization": { "title": "SourceOrganization", "type": "object", diff --git a/api/models/user.py b/api/models/user.py index 4c3a254..bccb183 100644 --- a/api/models/user.py +++ b/api/models/user.py @@ -12,6 +12,7 @@ class Submission(Document): title: str = None authors: List[str] = [] + # id of the metadata record that was submitted identifier: PydanticObjectId submitted: datetime = datetime.utcnow() url: HttpUrl = None From 2a075311cde2d5c21aab44aa6ffba579b0fe9a0a Mon Sep 17 00:00:00 2001 From: pkdash Date: Wed, 3 Apr 2024 13:26:47 -0400 Subject: [PATCH 02/20] [#105] update to schema adapters to support specific schema types --- api/adapters/base.py | 8 ++++---- api/adapters/hydroshare.py | 8 ++++---- api/adapters/s3.py | 38 ++++++++++++++++++++++++++++---------- api/adapters/utils.py | 4 ++-- 4 files changed, 38 insertions(+), 20 deletions(-) diff --git a/api/adapters/base.py b/api/adapters/base.py index dbe6eab..360e881 100644 --- a/api/adapters/base.py +++ b/api/adapters/base.py @@ -1,7 +1,7 @@ import abc - +from typing import Type from api.config import Settings, get_settings -from api.models.catalog import DatasetMetadataDOC +from api.models.catalog import CoreMetadataDOC from api.models.user import Submission @@ -19,13 +19,13 @@ class AbstractRepositoryMetadataAdapter(abc.ABC): @staticmethod @abc.abstractmethod - def to_catalog_record(metadata: dict) -> DatasetMetadataDOC: + def to_catalog_record(metadata: dict) -> Type[CoreMetadataDOC]: """Converts repository metadata to a catalog dataset record""" ... @staticmethod @abc.abstractmethod - def to_repository_record(catalog_record: DatasetMetadataDOC): + def to_repository_record(catalog_record: Type[CoreMetadataDOC]): """Converts dataset catalog dataset record to repository metadata""" ... diff --git a/api/adapters/hydroshare.py b/api/adapters/hydroshare.py index 6baec90..cfe2874 100644 --- a/api/adapters/hydroshare.py +++ b/api/adapters/hydroshare.py @@ -7,7 +7,7 @@ from api.adapters.utils import RepositoryType, register_adapter from api.exceptions import RepositoryException from api.models import schema -from api.models.catalog import DatasetMetadataDOC +from api.models.catalog import HSResourceMetadataDOC from api.models.user import Submission @@ -195,13 +195,13 @@ class HydroshareMetadataAdapter(AbstractRepositoryMetadataAdapter): repo_api_handler = _HydroshareRequestHandler() @staticmethod - def to_catalog_record(metadata: dict) -> DatasetMetadataDOC: + def to_catalog_record(metadata: dict) -> HSResourceMetadataDOC: """Converts hydroshare resource metadata to a catalog dataset record""" hs_metadata_model = _HydroshareResourceMetadata(**metadata) return hs_metadata_model.to_catalog_dataset() @staticmethod - def to_repository_record(catalog_record: DatasetMetadataDOC): + def to_repository_record(catalog_record: HSResourceMetadataDOC): """Converts dataset catalog record to hydroshare resource metadata""" raise NotImplementedError @@ -294,7 +294,7 @@ def to_dataset_provider(): return provider def to_catalog_dataset(self): - dataset = DatasetMetadataDOC.construct() + dataset = HSResourceMetadataDOC.construct() dataset.provider = self.to_dataset_provider() dataset.name = self.title dataset.description = self.abstract diff --git a/api/adapters/s3.py b/api/adapters/s3.py index 43f1b91..2caef32 100644 --- a/api/adapters/s3.py +++ b/api/adapters/s3.py @@ -1,28 +1,45 @@ -import boto3 import json + +import boto3 +from botocore.exceptions import ClientError as S3ClientError from botocore.client import Config from botocore import UNSIGNED from api.adapters.base import AbstractRepositoryMetadataAdapter, AbstractRepositoryRequestHandler from api.adapters.utils import RepositoryType, register_adapter -from api.models.catalog import DatasetMetadataDOC +from api.models.catalog import NetCDFMetadataDOC from api.models.user import Submission class _S3RequestHandler(AbstractRepositoryRequestHandler): - + def get_metadata(self, record_id: str): endpoint_url = record_id.split("+")[0] bucket_name = record_id.split("+")[1] file_key = record_id.split("+")[2] + # TODO: Should we be expecting the path for the data file and then compute the metadata file path from that? + # Or should we be expecting the metadata file path directly? May be we should get path for both + # data file and metadata file. If have the path for the data file we can check that the file + # exists and then retrieve the metadata file and catalog the metadata. s3 = boto3.client('s3', config=Config(signature_version=UNSIGNED), endpoint_url=endpoint_url) - - response = s3.get_object(Bucket=bucket_name, Key=file_key) + try: + response = s3.get_object(Bucket=bucket_name, Key=file_key) + except S3ClientError as ex: + if ex.response['Error']['Code'] == 'NoSuchKey': + raise FileNotFoundError(f"Expected metadata file not found in S3 bucket: {bucket_name}/{file_key}") + else: + raise ex + json_content = response['Body'].read().decode('utf-8') + # parse the JSON content + try: + data = json.loads(json_content) + except json.JSONDecodeError as ex: + raise ValueError(f"Invalid JSON content in S3 file ({file_key}). Error: {str(ex)}") - # Parse the JSON content - data = json.loads(json_content) + # remove additionalType field - this will be set by the schema model + data.pop('additionalType', None) return data @@ -30,12 +47,13 @@ def get_metadata(self, record_id: str): class S3MetadataAdapter(AbstractRepositoryMetadataAdapter): repo_api_handler = _S3RequestHandler() + # TODO: Need to support multiple metadata types - NetCDF, Raster, GenericDataset @staticmethod - def to_catalog_record(metadata: dict) -> DatasetMetadataDOC: - return DatasetMetadataDOC(**metadata) + def to_catalog_record(metadata: dict) -> NetCDFMetadataDOC: + return NetCDFMetadataDOC(**metadata) @staticmethod - def to_repository_record(catalog_record: DatasetMetadataDOC): + def to_repository_record(catalog_record: NetCDFMetadataDOC): """Converts dataset catalog record to hydroshare resource metadata""" raise NotImplementedError diff --git a/api/adapters/utils.py b/api/adapters/utils.py index 22473df..ee2f7bc 100644 --- a/api/adapters/utils.py +++ b/api/adapters/utils.py @@ -1,5 +1,5 @@ from enum import Enum -from typing import Type, Union +from typing import Type, Union, Dict from api.adapters.base import AbstractRepositoryMetadataAdapter @@ -9,7 +9,7 @@ class RepositoryType(str, Enum): S3 = 'S3' -_adapter_registry = {} +_adapter_registry: Dict[RepositoryType, Type[AbstractRepositoryMetadataAdapter]] = {} def register_adapter(repository_type: RepositoryType, adapter_class: Type[AbstractRepositoryMetadataAdapter]) -> None: From 0ddf19c2982c799b3ca9694d52bc198b0a5098f2 Mon Sep 17 00:00:00 2001 From: pkdash Date: Wed, 3 Apr 2024 13:29:37 -0400 Subject: [PATCH 03/20] [#105] update to api endpoints to support metadata of different schemas --- api/routes/catalog.py | 258 ++++++++++++++++++++++++++++++++---------- 1 file changed, 199 insertions(+), 59 deletions(-) diff --git a/api/routes/catalog.py b/api/routes/catalog.py index 476dfc5..5278fb6 100644 --- a/api/routes/catalog.py +++ b/api/routes/catalog.py @@ -1,25 +1,53 @@ -from typing import Annotated, List +from typing import Annotated, List, Type from beanie import PydanticObjectId, WriteRules from fastapi import APIRouter, Depends, HTTPException, status +from pydantic import BaseModel from api.adapters.utils import get_adapter_by_type, RepositoryType from api.authentication.user import get_current_user -from api.models.catalog import DatasetMetadataDOC +from api.models.catalog import ( + CoreMetadataDOC, + GenericDatasetMetadataDOC, + HSResourceMetadataDOC, + NetCDFMetadataDOC, +) from api.models.user import Submission, User -from pydantic import BaseModel + router = APIRouter() +# TODO: create endpoints to register netcdf and raster datasets - these endpoints will take a path to the +# metadata json file and will create a new metadata record in the catalog. Use S3 (minIO) as the metadata file path + + +class S3Path(BaseModel): + # this model is used to parse the request body for registering a dataset from S3 + path: str + bucket: str + endpoint_url: str = 'https://api.minio.cuahsi.io' + -def inject_repository_identifier(submission: Submission, document: DatasetMetadataDOC): +def inject_repository_identifier( + submission: Submission, + document: Type[CoreMetadataDOC], +): if submission.repository_identifier: document.repository_identifier = submission.repository_identifier return document -@router.post("/dataset/", response_model=DatasetMetadataDOC, status_code=status.HTTP_201_CREATED) -async def create_dataset(document: DatasetMetadataDOC, user: Annotated[User, Depends(get_current_user)]): +@router.post( + "/dataset/generic", + response_model=GenericDatasetMetadataDOC, + summary="Create a new dataset metadata record in catalog with user provided metadata", + description="Validates the user provided metadata and creates a new dataset metadata record in catalog", + status_code=status.HTTP_201_CREATED, +) +async def create_generic_dataset( + document: GenericDatasetMetadataDOC, + user: Annotated[User, Depends(get_current_user)], +): await document.insert() submission = document.as_submission() user.submissions.append(submission) @@ -27,44 +55,90 @@ async def create_dataset(document: DatasetMetadataDOC, user: Annotated[User, Dep return document -@router.get("/dataset/{submission_id}", response_model=DatasetMetadataDOC, response_model_exclude_none=True) -async def get_dataset(submission_id: PydanticObjectId): - submission: Submission = await Submission.find_one(Submission.identifier == submission_id) +@router.get( + "/dataset/generic/{submission_id}", + response_model=GenericDatasetMetadataDOC, + summary="Get a generic dataset metadata record", + description="Retrieves a generic dataset metadata record by submission identifier", + response_model_exclude_none=True, +) +async def get_generic_dataset(submission_id: PydanticObjectId): + submission: Submission = await Submission.find_one( + Submission.identifier == submission_id + ) if submission is None: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Dataset metadata record was not found") + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Metadata record was not found", + ) - document: DatasetMetadataDOC = await DatasetMetadataDOC.get(submission.identifier) + document: GenericDatasetMetadataDOC = await GenericDatasetMetadataDOC.get(submission.identifier) if document is None: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Dataset metadata record was not found") + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Metadata record was not found", + ) document = inject_repository_identifier(submission, document) return document -@router.get("/dataset/", response_model=List[DatasetMetadataDOC], response_model_exclude_none=True) -async def get_datasets(user: Annotated[User, Depends(get_current_user)]): - documents = [inject_repository_identifier(submission, await DatasetMetadataDOC.get(submission.identifier)) for - submission in user.submissions] - return documents +@router.get( + "/dataset/hs-resource/{submission_id}", + response_model=HSResourceMetadataDOC, + response_model_exclude_none=True, + summary="Get a HydroShare resource metadata record", + description="Retrieves a HydroShare resource metadata record by submission identifier", +) +async def get_hydroshare_dataset(submission_id: PydanticObjectId): + submission: Submission = await Submission.find_one( + Submission.identifier == submission_id + ) + if submission is None: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Metadata record was not found", + ) + + document: HSResourceMetadataDOC = await HSResourceMetadataDOC.get(submission.identifier) + if document is None: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Metadata record was not found", + ) + + document = inject_repository_identifier(submission, document) + return document -@router.put("/dataset/{submission_id}", response_model=DatasetMetadataDOC) +@router.put("/dataset/generic/{submission_id}", + response_model=GenericDatasetMetadataDOC, + summary="Update an existing generic dataset metadata record in catalog with user provided metadata", + description="Validates the user provided metadata and updates an existing dataset metadata " + "record in catalog", + ) async def update_dataset( submission_id: PydanticObjectId, - updated_document: DatasetMetadataDOC, + updated_document: GenericDatasetMetadataDOC, user: Annotated[User, Depends(get_current_user)], ): submission: Submission = user.submission(submission_id) if submission is None: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Dataset metadata record was not found") + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Dataset metadata record was not found", + ) - dataset: DatasetMetadataDOC = await DatasetMetadataDOC.get(submission.identifier) + dataset: GenericDatasetMetadataDOC = await GenericDatasetMetadataDOC.get(submission.identifier) if dataset is None: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Dataset metadata record was not found") + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Dataset metadata record was not found", + ) updated_document.id = dataset.id await updated_document.replace() - dataset: DatasetMetadataDOC = await DatasetMetadataDOC.get(submission_id) + dataset: GenericDatasetMetadataDOC = await GenericDatasetMetadataDOC.get(submission_id) updated_submission: Submission = dataset.as_submission() updated_submission.id = submission.id updated_submission.repository_identifier = submission.repository_identifier @@ -75,14 +149,26 @@ async def update_dataset( return dataset -@router.delete("/dataset/{submission_id}", response_model=dict) -async def delete_dataset(submission_id: PydanticObjectId, user: Annotated[User, Depends(get_current_user)]): +@router.delete("/dataset/{submission_id}", + response_model=dict, + summary="Delete a metadata record in catalog", + description="Deletes a metadata record in catalog along with the submission record", + ) +async def delete_dataset( + submission_id: PydanticObjectId, user: Annotated[User, Depends(get_current_user)] +): submission: Submission = user.submission(submission_id) if submission is None: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Dataset metadata record was not found") - dataset: DatasetMetadataDOC = await DatasetMetadataDOC.get(submission.identifier) + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Dataset metadata record was not found", + ) + dataset = await CoreMetadataDOC.get(submission.identifier, with_children=True) if dataset is None: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Dataset metadata record was not found") + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Dataset metadata record was not found", + ) user.submissions.remove(submission) await user.save(link_rule=WriteRules.WRITE) await submission.delete() @@ -90,77 +176,125 @@ async def delete_dataset(submission_id: PydanticObjectId, user: Annotated[User, return {"deleted_dataset_id": submission_id} -@router.get("/submission/", response_model=List[Submission]) +@router.get("/submission/", + response_model=List[Submission], + response_model_exclude_none=True, + summary="Get all submission records for the authenticated user", + description="Retrieves all submission records for the authenticated user", + ) async def get_submissions(user: Annotated[User, Depends(get_current_user)]): return user.submissions -@router.get("/repository/hydroshare/{identifier}", response_model=DatasetMetadataDOC) -async def register_hydroshare_resource_metadata(identifier: str, user: Annotated[User, Depends(get_current_user)]): +@router.get("/repository/hydroshare/{identifier}", + response_model=HSResourceMetadataDOC, + summary="Register HydroShare resource metadata record in the catalog", + description="Retrieves the metadata for the resource from HydroShare repository and creates a new " + "metadata record in the catalog", + ) +async def register_hydroshare_resource_metadata( + identifier: str, user: Annotated[User, Depends(get_current_user)] +): # check that the user has not already registered this resource - submission: Submission = user.submission_by_repository(repo_type=RepositoryType.HYDROSHARE, identifier=identifier) + submission: Submission = user.submission_by_repository( + repo_type=RepositoryType.HYDROSHARE, identifier=identifier + ) if submission is not None: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="This resource has already been submitted by this user", ) - dataset: DatasetMetadataDOC = await _save_to_db(repository_type=RepositoryType.HYDROSHARE, - identifier=identifier, user=user) + dataset = await _save_to_db( + repository_type=RepositoryType.HYDROSHARE, identifier=identifier, user=user + ) return dataset -@router.put("/repository/hydroshare/{identifier}", response_model=DatasetMetadataDOC) -async def refresh_dataset_from_hydroshare(identifier: str, user: Annotated[User, Depends(get_current_user)]): - submission: Submission = user.submission_by_repository(repo_type=RepositoryType.HYDROSHARE, identifier=identifier) +@router.put("/repository/hydroshare/{identifier}", + response_model=HSResourceMetadataDOC, + summary="Refresh HydroShare resource metadata record in the catalog", + description="Retrieves the metadata for the resource from HydroShare repository and updates the existing " + "metadata record in the catalog", + ) +async def refresh_dataset_from_hydroshare( + identifier: str, user: Annotated[User, Depends(get_current_user)] +): + submission: Submission = user.submission_by_repository( + repo_type=RepositoryType.HYDROSHARE, identifier=identifier + ) if submission is None: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Dataset metadata record was not found") + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Dataset metadata record was not found", + ) - dataset: DatasetMetadataDOC = await DatasetMetadataDOC.get(submission.identifier) + dataset = await HSResourceMetadataDOC.get(submission.identifier) if dataset is None: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Dataset metadata record was not found") + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Dataset metadata record was not found", + ) - dataset = await _save_to_db(repository_type=RepositoryType.HYDROSHARE, identifier=identifier, - user=user, submission=submission) + dataset = await _save_to_db( + repository_type=RepositoryType.HYDROSHARE, + identifier=identifier, + user=user, + submission=submission, + ) return dataset -class S3Path(BaseModel): - path: str - bucket: str - endpoint_url: str = 'https://api.minio.cuahsi.io' - - -@router.put("/repository/s3", response_model=DatasetMetadataDOC) -async def register_s3_dataset(request_model: S3Path, user: Annotated[User, Depends(get_current_user)]): +@router.put("/repository/s3/netcdf", + response_model=NetCDFMetadataDOC, + summary="Register a S3 NetCDF dataset metadata record in the catalog", + description="Retrieves the metadata for the NetCDF dataset from S3 repository and creates a new metadata " + "record in the catalog", + status_code=status.HTTP_201_CREATED + ) +async def register_s3_netcdf_dataset(request_model: S3Path, user: Annotated[User, Depends(get_current_user)]): path = request_model.path bucket = request_model.bucket endpoint_url = request_model.endpoint_url identifier = f"{endpoint_url}+{bucket}+{path}" submission: Submission = user.submission_by_repository(repo_type=RepositoryType.S3, identifier=identifier) - dataset = await _save_to_db(repository_type=RepositoryType.S3, identifier=identifier, user=user, submission=submission) + dataset = await _save_to_db(repository_type=RepositoryType.S3, identifier=identifier, user=user, + submission=submission) return dataset -async def _save_to_db(repository_type: RepositoryType, identifier: str, user: User, submission: Submission = None): +async def _save_to_db( + repository_type: RepositoryType, + identifier: str, + user: User, + submission: Submission = None, +): adapter = get_adapter_by_type(repository_type=repository_type) # fetch metadata from repository as catalog dataset - repo_dataset: DatasetMetadataDOC = await _get_repo_meta_as_catalog_record(adapter=adapter, identifier=identifier) + repo_dataset = await _get_repo_meta_as_catalog_record( + adapter=adapter, identifier=identifier + ) if submission is None: # new registration await repo_dataset.insert() submission = repo_dataset.as_submission() - submission = adapter.update_submission(submission=submission, repo_record_id=identifier) + submission = adapter.update_submission( + submission=submission, repo_record_id=identifier + ) user.submissions.append(submission) await user.save(link_rule=WriteRules.WRITE) dataset = repo_dataset else: # update existing registration - dataset: DatasetMetadataDOC = await DatasetMetadataDOC.get(submission.identifier) + dataset = await CoreMetadataDOC.get(submission.identifier, with_children=True) repo_dataset.id = dataset.id await repo_dataset.replace() - updated_dataset: DatasetMetadataDOC = await DatasetMetadataDOC.get(submission.identifier) + updated_dataset = await CoreMetadataDOC.get( + submission.identifier, with_children=True + ) updated_submission: Submission = updated_dataset.as_submission() - updated_submission = adapter.update_submission(submission=updated_submission, repo_record_id=identifier) + updated_submission = adapter.update_submission( + submission=updated_submission, repo_record_id=identifier + ) updated_submission.id = submission.id updated_submission.submitted = submission.submitted await updated_submission.replace() @@ -172,6 +306,12 @@ async def _save_to_db(repository_type: RepositoryType, identifier: str, user: Us async def _get_repo_meta_as_catalog_record(adapter, identifier: str): - metadata = await adapter.get_metadata(identifier) - catalog_dataset: DatasetMetadataDOC = adapter.to_catalog_record(metadata) + try: + metadata = await adapter.get_metadata(identifier) + except Exception as ex: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=f"{str(ex)}", + ) + catalog_dataset: Type[CoreMetadataDOC] = adapter.to_catalog_record(metadata) return catalog_dataset From 4bb3f215575de31d9809b61c0b419b5aa09aeb03 Mon Sep 17 00:00:00 2001 From: pkdash Date: Wed, 3 Apr 2024 13:31:08 -0400 Subject: [PATCH 04/20] [#105] generating schema json using generic dataset schema for now --- api/models/management/generate_schema.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/api/models/management/generate_schema.py b/api/models/management/generate_schema.py index e6cdb91..cb40540 100644 --- a/api/models/management/generate_schema.py +++ b/api/models/management/generate_schema.py @@ -3,12 +3,14 @@ import typer -from api.models.schema import DatasetMetadata +from api.models.schema import GenericDatasetMetadata + +# TODO: Need to generate schemas for all models and each needs to be written to a separate file def main(output_name: str = "api/models/schemas/schema.json"): - schema = DatasetMetadata.schema() - json_schema = DatasetMetadata.schema_json()#indent=2) + schema = GenericDatasetMetadata.schema() + json_schema = GenericDatasetMetadata.schema_json()#indent=2) # Have to run it a few times for the definitions to get updated before inserted into another model while "#/definitions/" in json_schema: for definition in schema["definitions"]: From aa8ff27d3978ce65ca73c7251b41f64684ab2100 Mon Sep 17 00:00:00 2001 From: pkdash Date: Wed, 3 Apr 2024 13:32:47 -0400 Subject: [PATCH 05/20] [#105] updating scheduler to handle metadata catalog records of different schema types --- triggers/scheduler.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/triggers/scheduler.py b/triggers/scheduler.py index c32634f..bed5a3a 100644 --- a/triggers/scheduler.py +++ b/triggers/scheduler.py @@ -8,7 +8,7 @@ from api.adapters.utils import RepositoryType, get_adapter_by_type from api.config import get_settings -from api.models.catalog import DatasetMetadataDOC +from api.models.catalog import CoreMetadataDOC from api.models.user import Submission app = Rocketry(config={"task_execution": "async"}) @@ -42,11 +42,11 @@ async def retrieve_repository_record(submission: Submission): async def do_daily(): settings = get_settings() db = AsyncIOMotorClient(settings.db_connection_string)[settings.database_name] - await init_beanie(database=db, document_models=[Submission, DatasetMetadataDOC]) + await init_beanie(database=db, document_models=[Submission, CoreMetadataDOC]) async for submission in Submission.find(Submission.repository != None): try: - dataset = await DatasetMetadataDOC.get(submission.identifier) + dataset = await CoreMetadataDOC.get(submission.identifier, with_children=True) if dataset is None: logger.warning(f"No catalog record was found for submission: {submission.identifier}") continue @@ -58,7 +58,7 @@ async def do_daily(): await updated_dataset.replace() # update submission record - dataset = await DatasetMetadataDOC.get(submission.identifier) + dataset = await CoreMetadataDOC.get(submission.identifier, with_children=True) updated_submission = dataset.as_submission() updated_submission.id = submission.id updated_submission.submitted = submission.submitted From 4b28d2546ab7f747d414e1ef96b20b41865b9384 Mon Sep 17 00:00:00 2001 From: pkdash Date: Wed, 3 Apr 2024 13:37:26 -0400 Subject: [PATCH 06/20] [#105] updating tests and new tests for netcdf and raster metadata schemas --- tests/conftest.py | 45 ++- tests/data/core_metadata.json | 5 - tests/data/dataset_metadata.json | 1 + tests/data/netcdf_metadata.json | 195 +++++++++++ tests/data/raster_metadata.json | 294 ++++++++++++++++ tests/test_core_schema.py | 466 +++++++++++++++----------- tests/test_dataset_routes.py | 89 +++-- tests/test_dataset_schema.py | 28 +- tests/test_hydroshare_meta_adapter.py | 14 +- tests/test_netcdf_schema.py | 24 ++ tests/test_raster_schema.py | 22 ++ tests/utils.py | 13 +- 12 files changed, 938 insertions(+), 258 deletions(-) create mode 100644 tests/data/netcdf_metadata.json create mode 100644 tests/data/raster_metadata.json create mode 100644 tests/test_netcdf_schema.py create mode 100644 tests/test_raster_schema.py diff --git a/tests/conftest.py b/tests/conftest.py index ee1662c..ce646fc 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -10,7 +10,13 @@ from api.main import app from api.authentication.user import get_current_user from api.models.catalog import CoreMetadataDOC, Submission -from api.models.schema import CoreMetadata, DatasetMetadata +from api.models.schema import ( + CoreMetadata, + GenericDatasetMetadata, + HSResourceMetadata, + HSNetCDFMetadata, + HSRasterMetadata, +) from api.models.user import User from api.procedures.user import create_or_update_user @@ -34,7 +40,9 @@ async def client_test(): if not get_settings().testing: raise Exception("App is not in testing mode") async with LifespanManager(app): - async with AsyncClient(app=app, base_url="http://test", follow_redirects=True) as ac: + async with AsyncClient( + app=app, base_url="http://test", follow_redirects=True + ) as ac: ac.app = app yield ac @@ -66,7 +74,7 @@ async def test_user_access_token(): @pytest_asyncio.fixture -async def core_data(change_test_dir): +async def core_metadata(change_test_dir): with open("data/core_metadata.json", "r") as f: return json.loads(f.read()) @@ -77,14 +85,41 @@ async def dataset_data(change_test_dir): return json.loads(f.read()) +@pytest_asyncio.fixture +async def netcdf_metadata(change_test_dir): + with open("data/netcdf_metadata.json", "r") as f: + return json.loads(f.read()) + + +@pytest_asyncio.fixture +async def raster_metadata(change_test_dir): + with open("data/raster_metadata.json", "r") as f: + return json.loads(f.read()) + + +@pytest_asyncio.fixture +async def hs_resource_model(): + return HSResourceMetadata + + @pytest_asyncio.fixture async def core_model(): return CoreMetadata @pytest_asyncio.fixture -async def dataset_model(): - return DatasetMetadata +async def generic_dataset_model(): + return GenericDatasetMetadata + + +@pytest_asyncio.fixture +async def netcdf_metadata_model(): + return HSNetCDFMetadata + + +@pytest_asyncio.fixture +async def raster_metadata_model(): + return HSRasterMetadata @pytest_asyncio.fixture diff --git a/tests/data/core_metadata.json b/tests/data/core_metadata.json index 99fffd9..33f3d06 100644 --- a/tests/data/core_metadata.json +++ b/tests/data/core_metadata.json @@ -107,11 +107,6 @@ "contentSize": "0.17 MB", "name": "USGS gage locations within the Harvey-affected areas in Texas", "sha256": "830f4b50e78e8a8fb0f7eee7369171dacbcaa43cc2c4deb59cef8e4fd2f641c5", - "additionalProperty": [], - "variableMeasured": null, - "spatialCoverage": null, - "temporalCoverage": null, - "sourceOrganization": null, "isPartOf": null } ], diff --git a/tests/data/dataset_metadata.json b/tests/data/dataset_metadata.json index 2e0e4cd..c5dd395 100644 --- a/tests/data/dataset_metadata.json +++ b/tests/data/dataset_metadata.json @@ -5,6 +5,7 @@ "description": "This is a test dataset metadata json file to test catalog api", "url": "https://hydroshare.org/resource/e2fdf99cbb0c4275b32afd3c16ae6863", "identifier": ["https://www.hydroshare.org/resource/6625bdbde41c45c2b906f32be7ea70f0/"], + "additionalType": "Generic", "creator": [ { "@type": "Person", diff --git a/tests/data/netcdf_metadata.json b/tests/data/netcdf_metadata.json new file mode 100644 index 0000000..c2bb465 --- /dev/null +++ b/tests/data/netcdf_metadata.json @@ -0,0 +1,195 @@ +{ + "context": "https://schema.org", + "type": "Dataset", + "additionalType": "Multidimensional Dataset", + "name": "Snow water equivalent estimation at TWDEF site from Oct 2009 to June 2010", + "description": "This netCDF data is the simulation output from Utah Energy Balance (UEB) model.It includes the simulation result of snow water equivalent during the period Oct. 2009 to June 2010 for TWDEF site in Utah.", + "url": "https://www.hydroshare.org/resource/e2319a79641047bebaee534cfc250e2a/data/contents/netcdf_valid.nc", + "creator": [ + { + "type": "Person", + "email": "jamy@gmail.com", + "identifier": null, + "affiliation": null, + "name": "Jamy" + } + ], + "keywords": [ + "Snow water equivalent" + ], + "associatedMedia": [ + { + "type": "MediaObject", + "contentUrl": "https://www.hydroshare.org/resource/e2319a79641047bebaee534cfc250e2a/data/contents/netcdf_valid.nc", + "encodingFormat": "application/x-netcdf", + "contentSize": "4869.898 KB", + "name": "netcdf_valid.nc", + "sha256": "a3229e683732e4188077d33480f0321c7d97be2248f50ac2a6c1c3928b8c6f21" + } + ], + "spatialCoverage": { + "type": "Place", + "name": null, + "geo": { + "type": "GeoShape", + "box": "41.867126409000086 -111.5059403684569 41.86390807452128 -111.51138807956225" + }, + "additionalProperty": [ + { + "type": "PropertyValue", + "propertyID": null, + "unitCode": null, + "description": null, + "minValue": null, + "maxValue": null, + "measurementTechnique": null, + "name": "projection", + "value": "WGS 84 EPSG:4326" + }, + { + "type": "PropertyValue", + "propertyID": null, + "unitCode": null, + "description": null, + "minValue": null, + "maxValue": null, + "measurementTechnique": null, + "name": "units", + "value": "Decimal degrees" + } + ] + }, + "temporalCoverage": { + "endDate": "2010-05-30T23:00:00", + "startDate": "2009-10-01T00:00:00" + }, + "additionalProperty": [], + "variableMeasured": [ + { + "type": "PropertyValue", + "propertyID": null, + "unitCode": "m", + "description": "Snow water equivalent", + "minValue": null, + "maxValue": null, + "measurementTechnique": "model simulation of UEB model", + "name": "SWE", + "value": [ + { + "type": "PropertyValue", + "propertyID": null, + "unitCode": "Float", + "description": null, + "minValue": null, + "maxValue": null, + "measurementTechnique": null, + "name": "shape", + "value": "y,x,time" + }, + { + "type": "PropertyValue", + "propertyID": null, + "unitCode": null, + "description": null, + "minValue": null, + "maxValue": null, + "measurementTechnique": null, + "name": "missingValue", + "value": "-9999" + } + ] + }, + { + "type": "PropertyValue", + "propertyID": null, + "unitCode": "Unknown", + "description": null, + "minValue": null, + "maxValue": null, + "measurementTechnique": null, + "name": "transverse_mercator", + "value": [ + { + "type": "PropertyValue", + "propertyID": null, + "unitCode": "Unknown", + "description": null, + "minValue": null, + "maxValue": null, + "measurementTechnique": null, + "name": "shape", + "value": "Not defined" + } + ] + }, + { + "type": "PropertyValue", + "propertyID": null, + "unitCode": "hours since 2009-10-1 0:0:00 UTC", + "description": "time", + "minValue": null, + "maxValue": null, + "measurementTechnique": null, + "name": "time", + "value": [ + { + "type": "PropertyValue", + "propertyID": null, + "unitCode": "Float", + "description": null, + "minValue": null, + "maxValue": null, + "measurementTechnique": null, + "name": "shape", + "value": "time" + } + ] + }, + { + "type": "PropertyValue", + "propertyID": null, + "unitCode": "Meter", + "description": "y coordinate of projection", + "minValue": null, + "maxValue": null, + "measurementTechnique": null, + "name": "y", + "value": [ + { + "type": "PropertyValue", + "propertyID": null, + "unitCode": "Float", + "description": null, + "minValue": null, + "maxValue": null, + "measurementTechnique": null, + "name": "shape", + "value": "y" + } + ] + }, + { + "type": "PropertyValue", + "propertyID": null, + "unitCode": "Meter", + "description": "x coordinate of projection", + "minValue": null, + "maxValue": null, + "measurementTechnique": null, + "name": "x", + "value": [ + { + "type": "PropertyValue", + "propertyID": null, + "unitCode": "Float", + "description": null, + "minValue": null, + "maxValue": null, + "measurementTechnique": null, + "name": "shape", + "value": "x" + } + ] + } + ] +} \ No newline at end of file diff --git a/tests/data/raster_metadata.json b/tests/data/raster_metadata.json new file mode 100644 index 0000000..28ae0b7 --- /dev/null +++ b/tests/data/raster_metadata.json @@ -0,0 +1,294 @@ +{ + "context": "https://schema.org", + "type": "Dataset", + "additionalType": "Geo Raster", + "name": "Logan", + "description": null, + "creator": null, + "keywords": null, + "url": "https://www.hydroshare.org/resource/e2319a79641047bebaee534cfc250e2a/data/contents/logan.vrt", + "associatedMedia": [ + { + "type": "MediaObject", + "contentUrl": "https://www.hydroshare.org/resource/e2319a79641047bebaee534cfc250e2a/data/contents/logan1.tif", + "encodingFormat": "image/tiff", + "contentSize": "100.749 KB", + "name": "logan1.tif", + "sha256": "830f4b50e78e8a8fb0f7eee7369171dacbcaa43cc2c4deb59cef8e4fd2f641c5" + }, + { + "type": "MediaObject", + "contentUrl": "https://www.hydroshare.org/resource/e2319a79641047bebaee534cfc250e2a/data/contents/logan2.tif", + "encodingFormat": "image/tiff", + "contentSize": "98.943 KB", + "name": "logan2.tif", + "sha256": "b5ad48ae6428c23c63ceaa93838eb2b3a688a3ecfbf10f01fcbc25f0d2d16f46" + }, + { + "type": "MediaObject", + "contentUrl": "https://www.hydroshare.org/resource/e2319a79641047bebaee534cfc250e2a/data/contents/logan.vrt", + "encodingFormat": null, + "contentSize": "2.175 KB", + "name": "logan.vrt", + "sha256": "94a844d49defcafef815258d1d2bc558b464d9454de7e178b25d5377eca789b4" + } + ], + "spatialCoverage": { + "type": "Place", + "name": null, + "geo": { + "type": "GeoShape", + "box": "42.0500287857716 -111.57737502643897 41.98745777903126 -111.65768822411246" + }, + "additionalProperty": [ + { + "type": "PropertyValue", + "propertyID": null, + "unitCode": null, + "description": null, + "minValue": null, + "maxValue": null, + "measurementTechnique": null, + "name": "projection", + "value": "WGS 84 EPSG:4326" + }, + { + "type": "PropertyValue", + "propertyID": null, + "unitCode": null, + "description": null, + "minValue": null, + "maxValue": null, + "measurementTechnique": null, + "name": "units", + "value": "Decimal degrees" + }, + { + "type": "PropertyValue", + "propertyID": null, + "unitCode": "meter", + "description": null, + "minValue": null, + "maxValue": null, + "measurementTechnique": null, + "name": "spatialReference", + "value": [ + { + "type": "PropertyValue", + "propertyID": null, + "unitCode": null, + "description": null, + "minValue": null, + "maxValue": null, + "measurementTechnique": null, + "name": "datum", + "value": "North_American_Datum_1983" + }, + { + "type": "PropertyValue", + "propertyID": null, + "unitCode": null, + "description": null, + "minValue": null, + "maxValue": null, + "measurementTechnique": null, + "name": "projection_string", + "value": "PROJCS[\"NAD83 / UTM zone 12N\",GEOGCS[\"NAD83\",DATUM[\"North_American_Datum_1983\",SPHEROID[\"GRS 1980\",6378137,298.257222101,AUTHORITY[\"EPSG\",\"7019\"]],AUTHORITY[\"EPSG\",\"6269\"]],PRIMEM[\"Greenwich\",0],UNIT[\"degree\",0.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]],AUTHORITY[\"EPSG\",\"4269\"]],PROJECTION[\"Transverse_Mercator\"],PARAMETER[\"latitude_of_origin\",0],PARAMETER[\"central_meridian\",-111],PARAMETER[\"scale_factor\",0.9996],PARAMETER[\"false_easting\",500000],PARAMETER[\"false_northing\",0],UNIT[\"metre\",1,AUTHORITY[\"EPSG\",\"9001\"]],AXIS[\"Easting\",EAST],AXIS[\"Northing\",NORTH],AUTHORITY[\"EPSG\",\"26912\"]]" + }, + { + "type": "PropertyValue", + "propertyID": null, + "unitCode": null, + "description": null, + "minValue": null, + "maxValue": null, + "measurementTechnique": null, + "name": "projection", + "value": "NAD83 / UTM zone 12N" + }, + { + "type": "PropertyValue", + "propertyID": null, + "unitCode": null, + "description": null, + "minValue": null, + "maxValue": null, + "measurementTechnique": null, + "name": "eastlimit", + "value": 452174.01909127034 + }, + { + "type": "PropertyValue", + "propertyID": null, + "unitCode": null, + "description": null, + "minValue": null, + "maxValue": null, + "measurementTechnique": null, + "name": "northlimit", + "value": 4655492.446916306 + }, + { + "type": "PropertyValue", + "propertyID": null, + "unitCode": null, + "description": null, + "minValue": null, + "maxValue": null, + "measurementTechnique": null, + "name": "westlimit", + "value": 445574.01909127034 + }, + { + "type": "PropertyValue", + "propertyID": null, + "unitCode": null, + "description": null, + "minValue": null, + "maxValue": null, + "measurementTechnique": null, + "name": "southlimit", + "value": 4648592.446916306 + } + ] + } + ] + }, + "temporalCoverage": null, + "additionalProperty": [ + { + "type": "PropertyValue", + "propertyID": null, + "unitCode": null, + "description": null, + "minValue": null, + "maxValue": null, + "measurementTechnique": null, + "name": "cellInformation", + "value": [ + { + "type": "PropertyValue", + "propertyID": null, + "unitCode": null, + "description": null, + "minValue": null, + "maxValue": null, + "measurementTechnique": null, + "name": "name", + "value": "logan.vrt" + }, + { + "type": "PropertyValue", + "propertyID": null, + "unitCode": null, + "description": null, + "minValue": null, + "maxValue": null, + "measurementTechnique": null, + "name": "cellDataType", + "value": "Float32" + }, + { + "type": "PropertyValue", + "propertyID": null, + "unitCode": null, + "description": null, + "minValue": null, + "maxValue": null, + "measurementTechnique": null, + "name": "cellSizeXValue", + "value": 30.0 + }, + { + "type": "PropertyValue", + "propertyID": null, + "unitCode": null, + "description": null, + "minValue": null, + "maxValue": null, + "measurementTechnique": null, + "name": "cellSizeYValue", + "value": 30.0 + }, + { + "type": "PropertyValue", + "propertyID": null, + "unitCode": null, + "description": null, + "minValue": null, + "maxValue": null, + "measurementTechnique": null, + "name": "columns", + "value": 220 + }, + { + "type": "PropertyValue", + "propertyID": null, + "unitCode": null, + "description": null, + "minValue": null, + "maxValue": null, + "measurementTechnique": null, + "name": "rows", + "value": 230 + } + ] + }, + { + "type": "PropertyValue", + "propertyID": null, + "unitCode": null, + "description": null, + "minValue": 2274.958984375, + "maxValue": 2880.007080078125, + "measurementTechnique": null, + "name": "bandInformation", + "value": [ + { + "type": "PropertyValue", + "propertyID": null, + "unitCode": null, + "description": null, + "minValue": null, + "maxValue": null, + "measurementTechnique": null, + "name": "name", + "value": "Band_1" + }, + { + "type": "PropertyValue", + "propertyID": null, + "unitCode": null, + "description": null, + "minValue": null, + "maxValue": null, + "measurementTechnique": null, + "name": "noDataValue", + "value": -3.4028234663852886e+38 + }, + { + "type": "PropertyValue", + "propertyID": null, + "unitCode": null, + "description": null, + "minValue": null, + "maxValue": null, + "measurementTechnique": null, + "name": "variableName", + "value": "" + }, + { + "type": "PropertyValue", + "propertyID": null, + "unitCode": null, + "description": null, + "minValue": null, + "maxValue": null, + "measurementTechnique": null, + "name": "variableUnit", + "value": "" + } + ] + } + ] +} \ No newline at end of file diff --git a/tests/test_core_schema.py b/tests/test_core_schema.py index 5be8669..bc07eed 100644 --- a/tests/test_core_schema.py +++ b/tests/test_core_schema.py @@ -6,34 +6,35 @@ @pytest.mark.asyncio -async def test_core_schema(core_data, core_model): +async def test_core_schema(core_metadata, generic_dataset_model): """Test that a core metadata pydantic model can be created from core metadata json. Purpose of the test is to validate core metadata schema as defined by the pydantic model. Note: This test does nat add a record to the database. """ - core_data = core_data - core_model = core_model + core_metadata = core_metadata + generic_dataset_model = generic_dataset_model # validate the data model - core_model_instance = await utils.validate_data_model(core_data, core_model) + generic_model_instance = await utils.validate_data_model(core_metadata, generic_dataset_model) - assert core_model_instance.name == "Test Dataset" + assert generic_model_instance.name == "Test Dataset" @pytest.mark.parametrize('multiple_creators', [True, False]) @pytest.mark.parametrize('creator_type', ["person", "organization"]) @pytest.mark.asyncio -async def test_core_schema_creator_cardinality(core_data, core_model, multiple_creators, creator_type): +async def test_core_schema_creator_cardinality(core_metadata, generic_dataset_model, multiple_creators, creator_type): """Test that a core metadata pydantic model can be created from core metadata json. Purpose of the test is to validate core metadata schema as defined by the pydantic model where we can have one or more creators. Note: This test does nat add a record to the database. """ - core_data = core_data - core_model = core_model - core_data.pop("creator") + core_metadata = core_metadata + # core metadata model for dataset + generic_dataset_model = generic_dataset_model + core_metadata.pop("creator") if multiple_creators: if creator_type == "person": - core_data["creator"] = [ + core_metadata["creator"] = [ {"@type": "Person", "name": "John Doe", "email": "john.doe@gmail.com"}, { "@type": "Person", @@ -48,7 +49,7 @@ async def test_core_schema_creator_cardinality(core_data, core_model, multiple_c } ] else: - core_data["creator"] = [ + core_metadata["creator"] = [ { "@type": "Organization", "name": "National Centers for Environmental Information", @@ -63,11 +64,11 @@ async def test_core_schema_creator_cardinality(core_data, core_model, multiple_c ] else: if creator_type == "person": - core_data["creator"] = [ + core_metadata["creator"] = [ {"@type": "Person", "name": "John Doe", "email": "john.doe@gmail.com"} ] else: - core_data["creator"] = [ + core_metadata["creator"] = [ { "@type": "Organization", "name": "National Centers for Environmental Information", @@ -76,7 +77,7 @@ async def test_core_schema_creator_cardinality(core_data, core_model, multiple_c ] # validate the data model - core_model_instance = await utils.validate_data_model(core_data, core_model) + core_model_instance = await utils.validate_data_model(core_metadata, generic_dataset_model) if multiple_creators: if creator_type == "person": @@ -135,18 +136,18 @@ async def test_core_schema_creator_cardinality(core_data, core_model, multiple_c ] ) @pytest.mark.asyncio -async def test_core_schema_creator_person_optional_attributes(core_data, core_model, data_format): +async def test_core_schema_creator_person_optional_attributes(core_metadata, generic_dataset_model, data_format): """Test that a core metadata pydantic model can be created from core metadata json. Purpose of the test is to validate core metadata schema as defined by the pydantic model where we are testing email and identifier attributes are optional. Note: This test does nat add a record to the database. """ - core_data = core_data - core_model = core_model - core_data.pop("creator") - core_data["creator"] = [data_format] + core_metadata = core_metadata + core_model = generic_dataset_model + core_metadata.pop("creator") + core_metadata["creator"] = [data_format] # validate the data model - core_model_instance = await utils.validate_data_model(core_data, core_model) + core_model_instance = await utils.validate_data_model(core_metadata, core_model) assert core_model_instance.creator[0].type == "Person" assert core_model_instance.creator[0].name == "John Doe" @@ -198,18 +199,18 @@ async def test_core_schema_creator_person_optional_attributes(core_data, core_mo ] ) @pytest.mark.asyncio -async def test_core_schema_creator_affiliation_optional_attributes(core_data, core_model, data_format): +async def test_core_schema_creator_affiliation_optional_attributes(core_metadata, generic_dataset_model, data_format): """Test that a core metadata pydantic model can be created from core metadata json. Purpose of the test is to validate core metadata schema as defined by the pydantic model where we are testing creator affiliation optional attributes. Note: This test does nat add a record to the database. """ - core_data = core_data - core_model = core_model - core_data.pop("creator") - core_data["creator"] = [data_format] + core_metadata = core_metadata + generic_dataset_model = generic_dataset_model + core_metadata.pop("creator") + core_metadata["creator"] = [data_format] # validate the data model - core_model_instance = await utils.validate_data_model(core_data, core_model) + core_model_instance = await utils.validate_data_model(core_metadata, generic_dataset_model) assert core_model_instance.creator[0].type == "Person" assert core_model_instance.creator[0].name == "John Doe" @@ -248,18 +249,18 @@ async def test_core_schema_creator_affiliation_optional_attributes(core_data, co ] ) @pytest.mark.asyncio -async def test_core_schema_creator_organization_optional_attributes(core_data, core_model, data_format): +async def test_core_schema_creator_organization_optional_attributes(core_metadata, generic_dataset_model, data_format): """Test that a core metadata pydantic model can be created from core metadata json. Purpose of the test is to validate core metadata schema as defined by the pydantic model where we are testing optional attributes of the organization object. Note: This test does nat add a record to the database. """ - core_data = core_data - core_model = core_model - core_data.pop("creator") - core_data["creator"] = [data_format] + core_metadata = core_metadata + generic_dataset_model = generic_dataset_model + core_metadata.pop("creator") + core_metadata["creator"] = [data_format] # validate the data model - core_model_instance = await utils.validate_data_model(core_data, core_model) + core_model_instance = await utils.validate_data_model(core_metadata, generic_dataset_model) assert core_model_instance.creator[0].type == "Organization" assert core_model_instance.creator[0].name == "National Centers for Environmental Information" if "url" in data_format: @@ -268,18 +269,16 @@ async def test_core_schema_creator_organization_optional_attributes(core_data, c assert core_model_instance.creator[0].address == "1167 Massachusetts Ave Suites 418 & 419, Arlington, MA 02476" -@pytest.mark.parametrize('multiple_media', [True, False, None]) +@pytest.mark.parametrize('multiple_media', [True, False]) @pytest.mark.asyncio -async def test_core_schema_associated_media_cardinality(core_data, core_model, multiple_media): +async def test_core_schema_associated_media_cardinality(core_metadata, generic_dataset_model, multiple_media): """Test that a core metadata pydantic model can be created from core metadata json. Purpose of the test is to validate core metadata schema as defined by the pydantic model where we are testing one or more associated media objects can be created. Note: This test does nat add a record to the database. """ - core_data = core_data - core_model = core_model - if multiple_media is None: - core_data.pop("associatedMedia", None) - if multiple_media and multiple_media is not None: + core_metadata = core_metadata + generic_dataset_model = generic_dataset_model + if multiple_media: associated_media = [ { "@type": "MediaObject", @@ -299,9 +298,9 @@ async def test_core_schema_associated_media_cardinality(core_data, core_model, m }, ] - core_data["associatedMedia"] = associated_media + core_metadata["associatedMedia"] = associated_media - elif multiple_media is not None: + else: associated_media = [ { "@type": "MediaObject", @@ -312,14 +311,12 @@ async def test_core_schema_associated_media_cardinality(core_data, core_model, m "name": "USGS gage locations within the Harvey-affected areas in Texas", } ] - core_data["associatedMedia"] = associated_media + core_metadata["associatedMedia"] = associated_media # validate the data model - core_model_instance = await utils.validate_data_model(core_data, core_model) + core_model_instance = await utils.validate_data_model(core_metadata, generic_dataset_model) - if multiple_media is None: - assert core_model_instance.associatedMedia is None - if multiple_media and multiple_media is not None: + if multiple_media: assert len(core_model_instance.associatedMedia) == 2 assert core_model_instance.associatedMedia[0].type == associated_media[0]["@type"] assert core_model_instance.associatedMedia[1].type == associated_media[1]["@type"] @@ -333,7 +330,7 @@ async def test_core_schema_associated_media_cardinality(core_data, core_model, m assert core_model_instance.associatedMedia[1].contentUrl == associated_media[1]["contentUrl"] assert core_model_instance.associatedMedia[0].sha256 == associated_media[0]["sha256"] assert core_model_instance.associatedMedia[1].sha256 == associated_media[1]["sha256"] - elif multiple_media is not None: + else: assert core_model_instance.associatedMedia[0].type == associated_media[0]["@type"] assert core_model_instance.associatedMedia[0].name == associated_media[0]["name"] assert core_model_instance.associatedMedia[0].contentSize == associated_media[0]["contentSize"] @@ -361,7 +358,7 @@ async def test_core_schema_associated_media_cardinality(core_data, core_model, m ) @pytest.mark.asyncio async def test_core_schema_associated_media_content_size( - core_data, core_model, content_size_format + core_metadata, generic_dataset_model, content_size_format ): """Test that a core metadata pydantic model can be created from core metadata json. Purpose of the test is to validate core metadata schema as defined by the pydantic model where we are testing @@ -369,10 +366,10 @@ async def test_core_schema_associated_media_content_size( Note: This test does nat add a record to the database. """ - core_data = core_data - core_model = core_model + core_metadata = core_metadata + generic_dataset_model = generic_dataset_model - core_data["associatedMedia"] = [ + core_metadata["associatedMedia"] = [ { "@type": "MediaObject", "contentUrl": "https://www.hydroshare.org/resource/51d1539bf6e94b15ac33f7631228118c/data/contents/USGS_Harvey_gages_TxLaMsAr.csv", @@ -382,34 +379,74 @@ async def test_core_schema_associated_media_content_size( "name": "USGS gage locations within the Harvey-affected areas in Texas", } ] - # validate the data model - core_model_instance = await utils.validate_data_model(core_data, core_model) + core_model_instance = await utils.validate_data_model(core_metadata, generic_dataset_model) assert core_model_instance.associatedMedia[0].contentSize == content_size_format +@pytest.mark.parametrize('include_is_part_of', [True, False]) +@pytest.mark.asyncio +async def test_core_schema_associated_media_is_part_of_optional( + core_metadata, generic_dataset_model, include_is_part_of +): + """Test that a core metadata pydantic model can be created from core metadata json. + Purpose of the test is to validate core metadata schema as defined by the pydantic model where we are testing + that the isPartOf attribute of the associatedMedia is optional. + Note: This test does nat add a record to the database. + """ + + core_metadata = core_metadata + generic_dataset_model = generic_dataset_model + + core_metadata["associatedMedia"] = [ + { + "@type": "MediaObject", + "contentUrl": "https://www.hydroshare.org/resource/51d1539bf6e94b15ac33f7631228118c/data/contents/USGS_Harvey_gages_TxLaMsAr.csv", + "encodingFormat": "text/csv", + "contentSize": "10.0 GB", + "sha256": "2fba6f2ebac562dac6a57acf0fdc5fdfabc9654b3c910aa6ef69cf4385997e19", + "name": "USGS gage locations within the Harvey-affected areas in Texas", + } + ] + if include_is_part_of: + core_metadata["associatedMedia"][0]["isPartOf"] = { + "@type": "CreativeWork", + "name": "USGS_Harvey_gages_TxLaMsAr.csv.json", + "url": "https://www.hydroshare.org/resource/51d1539bf6e94b15ac33f7631228118c/data/contents/USGS_Harvey_gages_TxLaMsAr.csv.json", + } + # validate the data model + core_model_instance = await utils.validate_data_model(core_metadata, generic_dataset_model) + # assert core_model_instance.associatedMedia[0].contentSize == content_size_format + if include_is_part_of: + assert core_model_instance.associatedMedia[0].isPartOf.type == "CreativeWork" + assert core_model_instance.associatedMedia[0].isPartOf.name == "USGS_Harvey_gages_TxLaMsAr.csv.json" + assert core_model_instance.associatedMedia[0].isPartOf.url == "https://www.hydroshare.org/resource/51d1539bf6e94b15ac33f7631228118c/data/contents/USGS_Harvey_gages_TxLaMsAr.csv.json" + else: + assert core_model_instance.associatedMedia[0].isPartOf is None + + @pytest.mark.parametrize("include_coverage", [True, False]) @pytest.mark.asyncio -async def test_core_schema_temporal_coverage_optional(core_data, core_model, include_coverage): +async def test_core_schema_temporal_coverage_optional(core_metadata, generic_dataset_model, include_coverage): """Test that a core metadata pydantic model can be created from core metadata json. Purpose of the test is to validate core metadata schema as defined by the pydantic model where we are testing temporal coverage can be optional. Note: This test does nat add a record to the database. """ - core_data = core_data - core_model = core_model + core_metadata = core_metadata + generic_dataset_model = generic_dataset_model coverage_value = { "startDate": "2007-03-01T13:00:00", "endDate": "2008-05-11T15:30:00", } - core_data.pop("temporalCoverage", None) + core_metadata.pop("temporalCoverage", None) if not include_coverage: - core_data.pop("temporalCoverage", None) + core_metadata.pop("temporalCoverage", None) else: - core_data["temporalCoverage"] = coverage_value + core_metadata["temporalCoverage"] = coverage_value # validate the data model - core_model_instance = await utils.validate_data_model(core_data, core_model) + core_model_instance = await utils.validate_data_model(core_metadata, generic_dataset_model) if not include_coverage: assert core_model_instance.temporalCoverage is None else: @@ -425,18 +462,18 @@ async def test_core_schema_temporal_coverage_optional(core_data, core_model, inc ], ) @pytest.mark.asyncio -async def test_core_schema_temporal_coverage_format(core_data, core_model, data_format): +async def test_core_schema_temporal_coverage_format(core_metadata, generic_dataset_model, data_format): """Test that a core metadata pydantic model can be created from core metadata json. Purpose of the test is to validate core metadata schema as defined by the pydantic model where we are testing that endDate is optional for temporal coverage. Note: This test does nat add a record to the database. """ - core_data = core_data - core_model = core_model - core_data["temporalCoverage"] = data_format + core_metadata = core_metadata + generic_dataset_model = generic_dataset_model + core_metadata["temporalCoverage"] = data_format # validate the data model - core_model_instance = await utils.validate_data_model(core_data, core_model) + core_model_instance = await utils.validate_data_model(core_metadata, generic_dataset_model) assert core_model_instance.temporalCoverage.startDate == datetime.datetime(2007, 3, 1, 13, 0, 0) if "endDate" in data_format: assert core_model_instance.temporalCoverage.endDate == datetime.datetime(2008, 5, 11, 15, 30, 0) @@ -446,14 +483,14 @@ async def test_core_schema_temporal_coverage_format(core_data, core_model, data_ @pytest.mark.parametrize('include_coverage', [True, False]) @pytest.mark.asyncio -async def test_core_schema_spatial_coverage_optional(core_data, core_model, include_coverage): +async def test_core_schema_spatial_coverage_optional(core_metadata, generic_dataset_model, include_coverage): """Test that a core metadata pydantic model can be created from core metadata json. Purpose of the test is to validate core metadata schema as defined by the pydantic model where we are testing spatial coverage can be optional. Note: This test does nat add a record to the database. """ - core_data = core_data - core_model = core_model + core_metadata = core_metadata + core_model = generic_dataset_model coverage_value = { "@type": "Place", "name": "CUAHSI Office", @@ -465,12 +502,12 @@ async def test_core_schema_spatial_coverage_optional(core_data, core_model, incl } if not include_coverage: - core_data.pop("spatialCoverage", None) + core_metadata.pop("spatialCoverage", None) else: - core_data["spatialCoverage"] = coverage_value + core_metadata["spatialCoverage"] = coverage_value # validate the data model - core_model_instance = await utils.validate_data_model(core_data, core_model) + core_model_instance = await utils.validate_data_model(core_metadata, generic_dataset_model) if not include_coverage: assert core_model_instance.spatialCoverage is None else: @@ -551,17 +588,17 @@ async def test_core_schema_spatial_coverage_optional(core_data, core_model, incl ], ) @pytest.mark.asyncio -async def test_core_schema_spatial_coverage_value_type(core_data, core_model, data_format): +async def test_core_schema_spatial_coverage_value_type(core_metadata, generic_dataset_model, data_format): """Test that a core metadata pydantic model can be created from core metadata json. Purpose of the test is to validate core metadata schema as defined by the pydantic model where we are testing valid values for spatial coverage with optional additionalProperty attribute. Note: This test does not add a record to the database. """ - core_data = core_data - core_model = core_model - core_data["spatialCoverage"] = data_format + core_metadata = core_metadata + generic_dataset_model = generic_dataset_model + core_metadata["spatialCoverage"] = data_format # validate the data model - core_model_instance = await utils.validate_data_model(core_data, core_model) + core_model_instance = await utils.validate_data_model(core_metadata, generic_dataset_model) assert core_model_instance.spatialCoverage.type == "Place" if "name" in data_format: @@ -620,26 +657,27 @@ async def test_core_schema_spatial_coverage_value_type(core_data, core_model, da @pytest.mark.parametrize('include_creative_works', [True, False]) @pytest.mark.asyncio -async def test_create_dataset_creative_works_status_optional(core_data, core_model, include_creative_works): +async def test_create_dataset_creative_works_status_optional(core_metadata, generic_dataset_model, + include_creative_works): """Test that a core metadata pydantic model can be created from core metadata json. Purpose of the test is to validate core metadata schema as defined by the pydantic model where we are testing creativeWorkStatus can be optional. Note: This test does nat add a record to the database. """ - core_data = core_data - core_model = core_model + core_metadata = core_metadata + generic_dataset_model = generic_dataset_model if not include_creative_works: - core_data.pop("creativeWorkStatus", None) + core_metadata.pop("creativeWorkStatus", None) else: - core_data["creativeWorkStatus"] = { + core_metadata["creativeWorkStatus"] = { "@type": "DefinedTerm", "name": "Draft", "description": "This is a draft dataset" } # validate the data model - core_model_instance = await utils.validate_data_model(core_data, core_model) + core_model_instance = await utils.validate_data_model(core_metadata, generic_dataset_model) if not include_creative_works: assert core_model_instance.creativeWorkStatus is None else: @@ -650,23 +688,23 @@ async def test_create_dataset_creative_works_status_optional(core_data, core_mod @pytest.mark.parametrize('include_multiple', [True, False]) @pytest.mark.asyncio -async def test_core_schema_keywords_cardinality(core_data, core_model, include_multiple): +async def test_core_schema_keywords_cardinality(core_metadata, generic_dataset_model, include_multiple): """Test that a core metadata pydantic model can be created from core metadata json. Purpose of the test is to validate core metadata schema as defined by the pydantic model where we are testing that one or more keywords can be added. Note: This test does nat add a record to the database. """ - core_data = core_data - core_model = core_model - core_data.pop("keywords", None) + core_metadata = core_metadata + generic_dataset_model = generic_dataset_model + core_metadata.pop("keywords", None) if include_multiple: - core_data["keywords"] = ["Leaf wetness", "Core"] + core_metadata["keywords"] = ["Leaf wetness", "Core"] else: - core_data["keywords"] = ["Leaf wetness"] + core_metadata["keywords"] = ["Leaf wetness"] # validate the data model - core_model_instance = await utils.validate_data_model(core_data, core_model) + core_model_instance = await utils.validate_data_model(core_metadata, generic_dataset_model) if include_multiple: assert len(core_model_instance.keywords) == 2 assert core_model_instance.keywords[0] == "Leaf wetness" @@ -676,6 +714,31 @@ async def test_core_schema_keywords_cardinality(core_data, core_model, include_m assert core_model_instance.keywords[0] == "Leaf wetness" +@pytest.mark.parametrize('include_keywords', [True, False]) +@pytest.mark.asyncio +async def test_core_schema_keywords_optional(core_metadata, generic_dataset_model, include_keywords): + """Test that a core metadata pydantic model can be created from core metadata json. + Purpose of the test is to validate core metadata schema as defined by the pydantic model where we are testing + that the value for the keywords attribute is optional. + Note: This test does nat add a record to the database. + """ + + core_metadata = core_metadata + generic_dataset_model = generic_dataset_model + core_metadata.pop("keywords", None) + if include_keywords: + core_metadata["keywords"] = ["Leaf wetness", "Core"] + + # validate the data model + core_model_instance = await utils.validate_data_model(core_metadata, generic_dataset_model) + if include_keywords: + assert len(core_model_instance.keywords) == 2 + assert core_model_instance.keywords[0] == "Leaf wetness" + assert core_model_instance.keywords[1] == "Core" + else: + assert core_model_instance.keywords is None + + @pytest.mark.skip(reason="Not sure if we need to support URL format for license.") @pytest.mark.parametrize( "data_format", @@ -690,18 +753,18 @@ async def test_core_schema_keywords_cardinality(core_data, core_model, include_m ] ) @pytest.mark.asyncio -async def test_core_schema_license_value_type(core_data, core_model, data_format): +async def test_core_schema_license_value_type(core_metadata, generic_dataset_model, data_format): """Test that a core metadata pydantic model can be created from core metadata json. Purpose of the test is to validate core metadata schema as defined by the pydantic model where we are testing valid value types for license property. Note: This test does nat add a record to the database. """ - core_data = core_data - core_model = core_model - core_data["license"] = data_format + core_metadata = core_metadata + generic_dataset_model = generic_dataset_model + core_metadata["license"] = data_format # validate the data model - core_model_instance = await utils.validate_data_model(core_data, core_model) + core_model_instance = await utils.validate_data_model(core_metadata, generic_dataset_model) if isinstance(data_format, str): assert core_model_instance.license == data_format else: @@ -737,18 +800,18 @@ async def test_core_schema_license_value_type(core_data, core_model, data_format ] ) @pytest.mark.asyncio -async def test_core_schema_license_optional_attributes(core_data, core_model, data_format): +async def test_core_schema_license_optional_attributes(core_metadata, generic_dataset_model, data_format): """Test that a core metadata pydantic model can be created from core metadata json. Purpose of the test is to validate core metadata schema as defined by the pydantic model where we are testing license of type CreativeWork optional attributes. Note: This test does nat add a record to the database. """ - core_data = core_data - core_model = core_model - core_data["license"] = data_format + core_metadata = core_metadata + generic_dataset_model = generic_dataset_model + core_metadata["license"] = data_format # validate the data model - core_model_instance = await utils.validate_data_model(core_data, core_model) + core_model_instance = await utils.validate_data_model(core_metadata, generic_dataset_model) assert core_model_instance.license.type == data_format["@type"] assert core_model_instance.license.name == data_format["name"] @@ -760,15 +823,15 @@ async def test_core_schema_license_optional_attributes(core_data, core_model, da @pytest.mark.parametrize('is_multiple', [True, False, None]) @pytest.mark.asyncio -async def test_core_schema_has_part_of_cardinality(core_data, core_model, is_multiple): +async def test_core_schema_has_part_of_cardinality(core_metadata, generic_dataset_model, is_multiple): """Test that a core metadata pydantic model can be created from core metadata json. Purpose of the test is to validate core metadata schema as defined by the pydantic model where we are testing that the hasPartOf property is optional and one or more values can be added for this property. Note: This test does nat add a record to the database. """ - core_data = core_data - core_model = core_model - core_data.pop("hasPart", None) + core_metadata = core_metadata + generic_dataset_model = generic_dataset_model + core_metadata.pop("hasPart", None) has_parts = [] if is_multiple and is_multiple is not None: @@ -786,7 +849,7 @@ async def test_core_schema_has_part_of_cardinality(core_data, core_model, is_mul "url": "https://www.hydroshare.org/resource/582060f00f6b443bb26e896426d9f62b/" } ] - core_data["hasPart"] = has_parts + core_metadata["hasPart"] = has_parts elif is_multiple is not None: has_parts = [ { @@ -796,10 +859,10 @@ async def test_core_schema_has_part_of_cardinality(core_data, core_model, is_mul "url": "https://www.hydroshare.org/resource/582060f00f6b443bb26e896426d9f62a/" } ] - core_data["hasPart"] = has_parts + core_metadata["hasPart"] = has_parts # validate the data model - core_model_instance = await utils.validate_data_model(core_data, core_model) + core_model_instance = await utils.validate_data_model(core_metadata, generic_dataset_model) if is_multiple and is_multiple is not None: assert len(core_model_instance.hasPart) == 2 assert core_model_instance.hasPart[0].type == has_parts[0]["@type"] @@ -846,19 +909,19 @@ async def test_core_schema_has_part_of_cardinality(core_data, core_model, is_mul ] ) @pytest.mark.asyncio -async def test_core_schema_has_part_optional_attributes(core_data, core_model, data_format): +async def test_core_schema_has_part_optional_attributes(core_metadata, generic_dataset_model, data_format): """Test that a core metadata pydantic model can be created from core metadata json. Purpose of the test is to validate core metadata schema as defined by the pydantic model where we are testing the optional attributes of hasPart property. Note: This test does nat add a record to the database. """ - core_data = core_data - core_model = core_model - core_data.pop("hasPart", None) - core_data["hasPart"] = [data_format] + core_metadata = core_metadata + generic_dataset_model = generic_dataset_model + core_metadata.pop("hasPart", None) + core_metadata["hasPart"] = [data_format] # validate the data model - core_model_instance = await utils.validate_data_model(core_data, core_model) + core_model_instance = await utils.validate_data_model(core_metadata, generic_dataset_model) assert core_model_instance.hasPart[0].type == data_format["@type"] assert core_model_instance.hasPart[0].name == data_format["name"] if "description" in data_format: @@ -869,15 +932,15 @@ async def test_core_schema_has_part_optional_attributes(core_data, core_model, d @pytest.mark.parametrize('is_multiple', [True, False, None]) @pytest.mark.asyncio -async def test_core_schema_is_part_of_cardinality(core_data, core_model, is_multiple): +async def test_core_schema_is_part_of_cardinality(core_metadata, generic_dataset_model, is_multiple): """Test that a core metadata pydantic model can be created from core metadata json. Purpose of the test is to validate core metadata schema as defined by the pydantic model where we are testing that the isPartOf property is optional and one or more values can be added for this property. Note: This test does nat add a record to the database. """ - core_data = core_data - core_model = core_model - core_data.pop("isPartOf", None) + core_metadata = core_metadata + generic_dataset_model = generic_dataset_model + core_metadata.pop("isPartOf", None) is_part_of = [] if is_multiple and is_multiple is not None: is_part_of = [ @@ -894,7 +957,7 @@ async def test_core_schema_is_part_of_cardinality(core_data, core_model, is_mult "url": "https://www.hydroshare.org/resource/582060f00f6b443bb26e896426d9f62b/" } ] - core_data["isPartOf"] = is_part_of + core_metadata["isPartOf"] = is_part_of elif is_multiple is not None: is_part_of = [ { @@ -904,10 +967,10 @@ async def test_core_schema_is_part_of_cardinality(core_data, core_model, is_mult "url": "https://www.hydroshare.org/resource/582060f00f6b443bb26e896426d9f62a/" } ] - core_data["isPartOf"] = is_part_of + core_metadata["isPartOf"] = is_part_of # validate the data model - core_model_instance = await utils.validate_data_model(core_data, core_model) + core_model_instance = await utils.validate_data_model(core_metadata, generic_dataset_model) if is_multiple and is_multiple is not None: assert len(core_model_instance.isPartOf) == 2 assert core_model_instance.isPartOf[0].type == is_part_of[0]["@type"] @@ -954,19 +1017,19 @@ async def test_core_schema_is_part_of_cardinality(core_data, core_model, is_mult ] ) @pytest.mark.asyncio -async def test_core_schema_is_part_of_optional_attributes(core_data, core_model, data_format): +async def test_core_schema_is_part_of_optional_attributes(core_metadata, generic_dataset_model, data_format): """Test that a core metadata pydantic model can be created from core metadata json. Purpose of the test is to validate core metadata schema as defined by the pydantic model where we are testing the optional attributes of the isPartOf property. Note: This test does nat add a record to the database. """ - core_data = core_data - core_model = core_model - core_data.pop("isPartOf", None) - core_data["isPartOf"] = [data_format] + core_metadata = core_metadata + generic_dataset_model = generic_dataset_model + core_metadata.pop("isPartOf", None) + core_metadata["isPartOf"] = [data_format] # validate the data model - core_model_instance = await utils.validate_data_model(core_data, core_model) + core_model_instance = await utils.validate_data_model(core_metadata, generic_dataset_model) assert core_model_instance.isPartOf[0].type == data_format["@type"] assert core_model_instance.isPartOf[0].name == data_format["name"] if "description" in data_format: @@ -977,31 +1040,31 @@ async def test_core_schema_is_part_of_optional_attributes(core_data, core_model, @pytest.mark.parametrize('dt_type', ["datetime", None]) @pytest.mark.asyncio -async def test_core_schema_date_value_type(core_data, core_model, dt_type): +async def test_core_schema_date_value_type(core_metadata, generic_dataset_model, dt_type): """Test that a core metadata pydantic model can be created from core metadata json. Purpose of the test is to validate core metadata schema as defined by the pydantic model where we are testing allowed value types for the date type attributes (dateCreated, dateModified, and datePublished). Also testing that dateModified and datePublished are optional. Note: This test does nat add a record to the database. """ - core_data = core_data - core_model = core_model + core_metadata = core_metadata + generic_dataset_model = generic_dataset_model # TODO: test 'date' type after knowing whether we need to support both date and datetime if dt_type == "date": - core_data["dateCreated"] = "2020-01-01" - core_data["dateModified"] = "2020-02-01" - core_data["datePublished"] = "2020-05-01" + core_metadata["dateCreated"] = "2020-01-01" + core_metadata["dateModified"] = "2020-02-01" + core_metadata["datePublished"] = "2020-05-01" elif dt_type == "datetime": - core_data["dateCreated"] = "2020-01-01T10:00:05" - core_data["dateModified"] = "2020-02-01T11:20:30" - core_data["datePublished"] = "2020-05-01T08:00:45" + core_metadata["dateCreated"] = "2020-01-01T10:00:05" + core_metadata["dateModified"] = "2020-02-01T11:20:30" + core_metadata["datePublished"] = "2020-05-01T08:00:45" else: - core_data["dateCreated"] = "2020-01-01T10:00:05" - core_data.pop("dateModified", None) - core_data.pop("datePublished", None) + core_metadata["dateCreated"] = "2020-01-01T10:00:05" + core_metadata.pop("dateModified", None) + core_metadata.pop("datePublished", None) # validate the data model - core_model_instance = await utils.validate_data_model(core_data, core_model) + core_model_instance = await utils.validate_data_model(core_metadata, generic_dataset_model) if dt_type == "date": assert core_model_instance.dateCreated == datetime.date(2020, 1, 1) assert core_model_instance.dateModified == datetime.date(2020, 2, 1) @@ -1018,30 +1081,30 @@ async def test_core_schema_date_value_type(core_data, core_model, dt_type): @pytest.mark.parametrize('provider_type', ["person", "organization"]) @pytest.mark.asyncio -async def test_core_schema_provider_value_type(core_data, core_model, provider_type): +async def test_core_schema_provider_value_type(core_metadata, generic_dataset_model, provider_type): """Test that a core metadata pydantic model can be created from core metadata json. Purpose of the test is to validate core metadata schema as defined by the pydantic model where we are testing allowed value types for the provider. Note: This test does nat add a record to the database. """ - core_data = core_data - core_model = core_model - core_data.pop("provider", None) + core_metadata = core_metadata + generic_dataset_model = generic_dataset_model + core_metadata.pop("provider", None) if provider_type == "person": - core_data["provider"] = { + core_metadata["provider"] = { "@type": "Person", "name": "John Doe", "email": "jdoe@gmail.com" } else: - core_data["provider"] = { + core_metadata["provider"] = { "@type": "Organization", "name": "HydroShare", "url": "https://hydroshare.org" } # validate the data model - core_model_instance = await utils.validate_data_model(core_data, core_model) + core_model_instance = await utils.validate_data_model(core_metadata, generic_dataset_model) provider = core_model_instance.provider if provider_type == "person": assert provider.type == "Person" @@ -1055,18 +1118,18 @@ async def test_core_schema_provider_value_type(core_data, core_model, provider_t @pytest.mark.parametrize('multiple_values', [True, False, None]) @pytest.mark.asyncio -async def test_core_schema_subject_of_cardinality(core_data, core_model, multiple_values): +async def test_core_schema_subject_of_cardinality(core_metadata, generic_dataset_model, multiple_values): """Test that a core metadata pydantic model can be created from core metadata json. Purpose of the test is to validate core metadata schema as defined by the pydantic model where we are testing that the subjectOf property is optional and one or more values can be added for this property. Note: This test does nat add a record to the database. """ - core_data = core_data - core_model = core_model + core_metadata = core_metadata + generic_dataset_model = generic_dataset_model if multiple_values is None: - core_data.pop("subjectOf", None) + core_metadata.pop("subjectOf", None) elif multiple_values: - core_data["subjectOf"] = [ + core_metadata["subjectOf"] = [ { "@type": "CreativeWork", "name": "Test subject of - 1", @@ -1080,7 +1143,7 @@ async def test_core_schema_subject_of_cardinality(core_data, core_model, multipl }, ] else: - core_data["subjectOf"] = [ + core_metadata["subjectOf"] = [ { "@type": "CreativeWork", "name": "Test subject of" @@ -1088,7 +1151,7 @@ async def test_core_schema_subject_of_cardinality(core_data, core_model, multipl ] # validate the data model - core_model_instance = await utils.validate_data_model(core_data, core_model) + core_model_instance = await utils.validate_data_model(core_metadata, generic_dataset_model) if multiple_values and multiple_values is not None: assert len(core_model_instance.subjectOf) == 2 assert core_model_instance.subjectOf[0].type == "CreativeWork" @@ -1112,21 +1175,21 @@ async def test_core_schema_subject_of_cardinality(core_data, core_model, multipl @pytest.mark.parametrize('include_version', [True, False]) @pytest.mark.asyncio -async def test_core_schema_version_cardinality(core_data, core_model, include_version): +async def test_core_schema_version_cardinality(core_metadata, generic_dataset_model, include_version): """Test that a core metadata pydantic model can be created from core metadata json. Purpose of the test is to validate core metadata schema as defined by the pydantic model where we are testing that the version property is optional. Note: This test does nat add a record to the database. """ - core_data = core_data - core_model = core_model + core_metadata = core_metadata + generic_dataset_model = generic_dataset_model if include_version: - core_data["version"] = "v1.0" + core_metadata["version"] = "v1.0" else: - core_data.pop("version", None) + core_metadata.pop("version", None) # validate the data model - core_model_instance = await utils.validate_data_model(core_data, core_model) + core_model_instance = await utils.validate_data_model(core_metadata, generic_dataset_model) if include_version: assert core_model_instance.version == "v1.0" else: @@ -1135,21 +1198,21 @@ async def test_core_schema_version_cardinality(core_data, core_model, include_ve @pytest.mark.parametrize('include_language', [True, False]) @pytest.mark.asyncio -async def test_core_schema_language_cardinality(core_data, core_model, include_language): +async def test_core_schema_language_cardinality(core_metadata, generic_dataset_model, include_language): """Test that a core metadata pydantic model can be created from core metadata json. Purpose of the test is to validate core metadata schema as defined by the pydantic model where we are testing that the inLanguage property is optional. Note: This test does nat add a record to the database. """ - core_data = core_data - core_model = core_model + core_metadata = core_metadata + generic_dataset_model = generic_dataset_model if include_language: - core_data["inLanguage"] = "en-US" + core_metadata["inLanguage"] = "en-US" else: - core_data.pop("inLanguage", None) + core_metadata.pop("inLanguage", None) # validate the data model - core_model_instance = await utils.validate_data_model(core_data, core_model) + core_model_instance = await utils.validate_data_model(core_metadata, generic_dataset_model) if include_language: assert core_model_instance.inLanguage == "en-US" else: @@ -1158,17 +1221,17 @@ async def test_core_schema_language_cardinality(core_data, core_model, include_l @pytest.mark.parametrize("multiple_funding", [True, False, None]) @pytest.mark.asyncio -async def test_core_schema_funding_cardinality(core_data, core_model, multiple_funding): +async def test_core_schema_funding_cardinality(core_metadata, generic_dataset_model, multiple_funding): """Test that a core metadata pydantic model can be created from core metadata json. Purpose of the test is to validate core metadata schema as defined by the pydantic model where we are testing that the funding property is optional and one or more values can be added to this property. Note: This test does nat add a record to the database. """ - core_data = core_data - core_model = core_model + core_metadata = core_metadata + generic_dataset_model = generic_dataset_model if multiple_funding and multiple_funding is not None: - core_data["funding"] = [ + core_metadata["funding"] = [ { "@type": "MonetaryGrant", "name": "HDR Institute: Geospatial Understanding through an Integrative Discovery Environment - 1", @@ -1181,7 +1244,7 @@ async def test_core_schema_funding_cardinality(core_data, core_model, multiple_f } ] elif multiple_funding is not None: - core_data["funding"] = [ + core_metadata["funding"] = [ { "@type": "MonetaryGrant", "name": "HDR Institute: Geospatial Understanding through an Integrative Discovery Environment", @@ -1190,10 +1253,10 @@ async def test_core_schema_funding_cardinality(core_data, core_model, multiple_f } ] else: - core_data.pop("funding", None) + core_metadata.pop("funding", None) # validate the data model - core_model_instance = await utils.validate_data_model(core_data, core_model) + core_model_instance = await utils.validate_data_model(core_metadata, generic_dataset_model) if multiple_funding and multiple_funding is not None: assert core_model_instance.funding[0].type == "MonetaryGrant" assert ( @@ -1231,17 +1294,17 @@ async def test_core_schema_funding_cardinality(core_data, core_model, multiple_f @pytest.mark.parametrize('include_funder', [True, False]) @pytest.mark.asyncio -async def test_core_schema_funding_funder_optional(core_data, core_model, include_funder): +async def test_core_schema_funding_funder_optional(core_metadata, generic_dataset_model, include_funder): """Test that a core metadata pydantic model can be created from core metadata json. Purpose of the test is to validate core metadata schema as defined by the pydantic model where we are testing value for the funder attribute of the funding property is optional. Note: This test does nat add a record to the database. """ - core_data = core_data - core_model = core_model - core_data.pop("funding", None) + core_metadata = core_metadata + generic_dataset_model = generic_dataset_model + core_metadata.pop("funding", None) if include_funder: - core_data["funding"] = [ + core_metadata["funding"] = [ { "@type": "MonetaryGrant", "name": "HDR Institute: Geospatial Understanding through an Integrative Discovery Environment", @@ -1276,7 +1339,7 @@ async def test_core_schema_funding_funder_optional(core_data, core_model, includ } ] else: - core_data["funding"] = [ + core_metadata["funding"] = [ { "@type": "MonetaryGrant", "name": "HDR Institute: Geospatial Understanding through an Integrative Discovery Environment", @@ -1286,7 +1349,7 @@ async def test_core_schema_funding_funder_optional(core_data, core_model, includ ] # validate the data model - core_model_instance = await utils.validate_data_model(core_data, core_model) + core_model_instance = await utils.validate_data_model(core_metadata, generic_dataset_model) if include_funder: assert core_model_instance.funding[0].funder.type == "Organization" @@ -1303,20 +1366,20 @@ async def test_core_schema_funding_funder_optional(core_data, core_model, includ @pytest.mark.parametrize('include_citation', [True, False]) @pytest.mark.asyncio -async def test_core_schema_citation_optional(core_data, core_model, include_citation): +async def test_core_schema_citation_optional(core_metadata, generic_dataset_model, include_citation): """Test that a core metadata pydantic model can be created from core metadata json. Purpose of the test is to validate core metadata schema as defined by the pydantic model where we are testing citation is optional for the funding property. Note: This test does nat add a record to the database. """ - core_data = core_data - core_model = core_model - core_data.pop("citation", None) + core_metadata = core_metadata + generic_dataset_model = generic_dataset_model + core_metadata.pop("citation", None) if include_citation: - core_data["citation"] = ["Test citation"] + core_metadata["citation"] = ["Test citation"] # validate the data model - core_model_instance = await utils.validate_data_model(core_data, core_model) + core_model_instance = await utils.validate_data_model(core_metadata, generic_dataset_model) if include_citation: assert core_model_instance.citation == ["Test citation"] else: @@ -1325,17 +1388,17 @@ async def test_core_schema_citation_optional(core_data, core_model, include_cita @pytest.mark.parametrize('include_publisher', [True, False]) @pytest.mark.asyncio -async def test_core_schema_publisher_optional(core_data, core_model, include_publisher): +async def test_core_schema_publisher_optional(core_metadata, generic_dataset_model, include_publisher): """Test that a core metadata pydantic model can be created from core metadata json. Purpose of the test is to validate core metadata schema as defined by the pydantic model where we are testing - publisher is optional for the funding property. + publisher is optional. Note: This test does nat add a record to the database. """ - core_data = core_data - core_model = core_model - core_data.pop("publisher", None) + core_metadata = core_metadata + generic_dataset_model = generic_dataset_model + core_metadata.pop("publisher", None) if include_publisher: - core_data["publisher"] = { + core_metadata["publisher"] = { "@type": "Organization", "name": "HydroShare", "url": "https://hydroshare.org", @@ -1343,7 +1406,7 @@ async def test_core_schema_publisher_optional(core_data, core_model, include_pub } # validate the data model - core_model_instance = await utils.validate_data_model(core_data, core_model) + core_model_instance = await utils.validate_data_model(core_metadata, generic_dataset_model) if include_publisher: assert core_model_instance.publisher.type == "Organization" assert core_model_instance.publisher.name == "HydroShare" @@ -1351,3 +1414,24 @@ async def test_core_schema_publisher_optional(core_data, core_model, include_pub assert core_model_instance.publisher.address == "1167 Massachusetts Ave Suites 418 & 419, Arlington, MA 02476" else: assert core_model_instance.publisher is None + +@pytest.mark.parametrize('include_additional_type', [True, False]) +@pytest.mark.asyncio +async def test_core_schema_additional_type_optional(core_metadata, generic_dataset_model, include_additional_type): + """Test that a core metadata pydantic model can be created from core metadata json. + Purpose of the test is to validate core metadata schema as defined by the pydantic model where we are testing + additionalType is optional. + Note: This test does nat add a record to the database. + """ + core_metadata = core_metadata + generic_dataset_model = generic_dataset_model + core_metadata.pop("additionalType", None) + if include_additional_type: + core_metadata["additionalType"] = "NetCDF" + + # validate the data model + core_model_instance = await utils.validate_data_model(core_metadata, generic_dataset_model) + if include_additional_type: + assert core_model_instance.additionalType == "NetCDF" + else: + assert core_model_instance.additionalType is None diff --git a/tests/test_dataset_routes.py b/tests/test_dataset_routes.py index 426fd1c..49c6214 100644 --- a/tests/test_dataset_routes.py +++ b/tests/test_dataset_routes.py @@ -11,7 +11,7 @@ async def test_create_dataset(client_test, dataset_data, test_user_access_token) """Testing the dataset routes for post and get""" # add a dataset record to the db - response = await client_test.post("api/catalog/dataset", json=dataset_data) + response = await client_test.post("api/catalog/dataset/generic", json=dataset_data) assert response.status_code == 201 response_data = response.json() record_id = response_data.pop('_id') @@ -49,7 +49,7 @@ async def test_create_dataset(client_test, dataset_data, test_user_access_token) assert user.submission(submission_id).repository is None assert user.submission(submission_id).repository_identifier is None # retrieve the record from the db - response = await client_test.get(f"api/catalog/dataset/{record_id}") + response = await client_test.get(f"api/catalog/dataset/generic/{record_id}") assert response.status_code == 200 @@ -67,7 +67,7 @@ async def test_create_refresh_dataset_from_hydroshare(client_test, test_user_acc # retrieve the record from the db record_id = hs_dataset.get('_id') - response = await client_test.get(f"api/catalog/dataset/{record_id}") + response = await client_test.get(f"api/catalog/dataset/hs-resource/{record_id}") assert response.status_code == 200 # refresh the hydroshare metadata record @@ -83,7 +83,7 @@ async def test_update_dataset(client_test, dataset_data): """Testing the dataset put route for updating dataset record""" # add a dataset record to the db - response = await client_test.post("api/catalog/dataset", json=dataset_data) + response = await client_test.post("api/catalog/dataset/generic", json=dataset_data) assert response.status_code == 201 response_data = response.json() record_id = response_data.get('_id') @@ -96,7 +96,7 @@ async def test_update_dataset(client_test, dataset_data): # update the dataset temporal coverage dataset_data["temporalCoverage"] = {"startDate": "2020-01-01T10:00:20", "endDate": "2020-11-29T00:30:00"} - response = await client_test.put(f"api/catalog/dataset/{record_id}", json=dataset_data) + response = await client_test.put(f"api/catalog/dataset/generic/{record_id}", json=dataset_data) assert response.status_code == 200 response_data = response.json() response_data.pop('_id') @@ -117,8 +117,8 @@ async def test_update_dataset(client_test, dataset_data): async def test_delete_dataset(client_test, dataset_data, test_user_access_token): """Testing the dataset delete route for deleting a dataset record""" - # add a dataset record to the db - response = await client_test.post("api/catalog/dataset", json=dataset_data) + # add a generic dataset record to the db + response = await client_test.post("api/catalog/dataset/generic", json=dataset_data) assert response.status_code == 201 response_data = response.json() record_id = response_data.get('_id') @@ -135,26 +135,26 @@ async def test_delete_dataset(client_test, dataset_data, test_user_access_token) assert submission_response.status_code == 200 -@pytest.mark.parametrize("multiple", [True, False]) -@pytest.mark.asyncio -async def test_get_datasets(client_test, dataset_data, multiple): - """Testing the get all datasets for a given user""" - - # add a dataset record to the db - dataset_response = await client_test.post("api/catalog/dataset", json=dataset_data) - assert dataset_response.status_code == 201 - if multiple: - # add another dataset record to the db - dataset_response = await client_test.post("api/catalog/dataset", json=dataset_data) - assert dataset_response.status_code == 201 - - dataset_response = await client_test.get("api/catalog/dataset") - assert dataset_response.status_code == 200 - dataset_response_data = dataset_response.json() - if multiple: - assert len(dataset_response_data) == 2 - else: - assert len(dataset_response_data) == 1 +# @pytest.mark.parametrize("multiple", [True, False]) +# @pytest.mark.asyncio +# async def test_get_datasets(client_test, dataset_data, multiple): +# """Testing the get all datasets for a given user""" +# +# # add a dataset record to the db +# dataset_response = await client_test.post("api/catalog/dataset", json=dataset_data) +# assert dataset_response.status_code == 201 +# if multiple: +# # add another dataset record to the db +# dataset_response = await client_test.post("api/catalog/dataset", json=dataset_data) +# assert dataset_response.status_code == 201 +# +# dataset_response = await client_test.get("api/catalog/dataset") +# assert dataset_response.status_code == 200 +# dataset_response_data = dataset_response.json() +# if multiple: +# assert len(dataset_response_data) == 2 +# else: +# assert len(dataset_response_data) == 1 @pytest.mark.asyncio @@ -163,14 +163,16 @@ async def test_get_datasets_exclude_none(client_test, dataset_data): dataset_data["version"] = None # add a dataset record to the db - dataset_response = await client_test.post("api/catalog/dataset", json=dataset_data) + dataset_response = await client_test.post("api/catalog/dataset/generic", json=dataset_data) assert dataset_response.status_code == 201 + response_data = dataset_response.json() + record_id = response_data.get('_id') - dataset_response = await client_test.get("api/catalog/dataset") + dataset_response = await client_test.get(f"api/catalog/dataset/generic/{record_id}") assert dataset_response.status_code == 200 dataset_response_data = dataset_response.json() - assert "version" not in dataset_response_data[0] - for a_property in dataset_response_data[0]["additionalProperty"]: + assert "version" not in dataset_response_data + for a_property in dataset_response_data["additionalProperty"]: assert "description" not in a_property assert "minValue" not in a_property assert "maxValue" not in a_property @@ -179,18 +181,39 @@ async def test_get_datasets_exclude_none(client_test, dataset_data): assert "measurementTechnique" not in a_property +@pytest.mark.asyncio +async def test_register_s3_netcdf_dataset(client_test): + """Testing registering metadata for a netcdf dataset stored on s3""" + + # set the path to the netcdf file on s3 + path = "catalogapi/.hs/netcdf/netcdf_valid.nc.json" + s3_path = { + "path": "catalogapi/.hs/netcdf/netcdf_valid.nc.json", + "bucket": "pkdash", + "endpoint_url": "https://api.minio.cuahsi.io/" + } + + dataset_response = await client_test.put("api/catalog/repository/s3/netcdf", json=s3_path) + # print response content + print(dataset_response.content) + + assert dataset_response.status_code == 201 + response_data = dataset_response.json() + assert response_data['additionalType'] == 'NetCDF' + + @pytest.mark.parametrize("multiple", [True, False]) @pytest.mark.asyncio async def test_get_submissions(client_test, dataset_data, multiple): """Testing the get submissions route""" # add a dataset record to the db - this will also add a submission record - dataset_response = await client_test.post("api/catalog/dataset", json=dataset_data) + dataset_response = await client_test.post("api/catalog/dataset/generic", json=dataset_data) assert dataset_response.status_code == 201 dataset_response_data = dataset_response.json() if multiple: # add another dataset record to the db - this will also add a submission record - dataset_response = await client_test.post("api/catalog/dataset", json=dataset_data) + dataset_response = await client_test.post("api/catalog/dataset/generic", json=dataset_data) assert dataset_response.status_code == 201 dataset_response_data = [dataset_response_data, dataset_response.json()] diff --git a/tests/test_dataset_schema.py b/tests/test_dataset_schema.py index 015d0b9..2b10b1c 100644 --- a/tests/test_dataset_schema.py +++ b/tests/test_dataset_schema.py @@ -6,7 +6,7 @@ @pytest.mark.parametrize("set_additional_property", [True, False]) @pytest.mark.asyncio async def test_dataset_schema_additional_property( - dataset_data, dataset_model, set_additional_property + dataset_data, generic_dataset_model, set_additional_property ): """Test that a dataset metadata pydantic model can be created from dataset metadata json. Purpose of the test is to validate dataset metadata schema as defined by the pydantic model where we are testing @@ -15,7 +15,7 @@ async def test_dataset_schema_additional_property( """ dataset_data = dataset_data - dataset_model = dataset_model + dataset_model = generic_dataset_model additional_property = [ { "@type": "PropertyValue", @@ -61,7 +61,7 @@ async def test_dataset_schema_additional_property( @pytest.mark.parametrize("set_source_organization", [True, False]) @pytest.mark.asyncio async def test_dataset_schema_source_organization( - dataset_data, dataset_model, set_source_organization + dataset_data, generic_dataset_model, set_source_organization ): """Test that a dataset metadata pydantic model can be created from dataset metadata json. Purpose of the test is to validate dataset metadata schema as defined by the pydantic model where we are testing @@ -70,7 +70,7 @@ async def test_dataset_schema_source_organization( """ dataset_data = dataset_data - dataset_model = dataset_model + dataset_model = generic_dataset_model source_organization = { "@type": "Organization", "name": "National Hydrography Dataset", @@ -95,7 +95,7 @@ async def test_dataset_schema_source_organization( @pytest.mark.parametrize("multiple_variable", [True, False, None]) @pytest.mark.asyncio async def test_dataset_schema_variable_cardinality( - dataset_data, dataset_model, multiple_variable + dataset_data, generic_dataset_model, multiple_variable ): """Test that a dataset pydantic model can be created from dataset json data. Purpose of the test is to validate dataset pydantic model where the variableMeasured property can @@ -103,7 +103,7 @@ async def test_dataset_schema_variable_cardinality( Note: This test does nat add a record to the database. """ dataset_data = dataset_data - dataset_model = dataset_model + dataset_model = generic_dataset_model if multiple_variable and multiple_variable is not None: dataset_data["variableMeasured"] = [ @@ -172,7 +172,7 @@ async def test_dataset_schema_variable_cardinality( ) @pytest.mark.asyncio async def test_dataset_schema_variable_value_type( - dataset_data, dataset_model, data_format + dataset_data, generic_dataset_model, data_format ): """Test that a dataset pydantic model can be created from dataset json data. Purpose of the test is to validate dataset pydantic model where we are testing allowed value types for @@ -180,7 +180,7 @@ async def test_dataset_schema_variable_value_type( Note: This test does nat add a record to the database. """ dataset_data = dataset_data - dataset_model = dataset_model + dataset_model = generic_dataset_model dataset_data["variableMeasured"] = data_format # validate the data model dataset_instance = await utils.validate_data_model(dataset_data, dataset_model) @@ -203,7 +203,7 @@ async def test_dataset_schema_variable_value_type( @pytest.mark.parametrize("multiple_distribution", [True, False]) @pytest.mark.asyncio async def test_dataset_schema_distribution_cardinality( - dataset_data, dataset_model, multiple_distribution + dataset_data, generic_dataset_model, multiple_distribution ): """Test that a dataset pydantic model can be created from dataset json data. Purpose of the test is to validate dataset pydantic model where we are testing the distribution property can have @@ -211,7 +211,7 @@ async def test_dataset_schema_distribution_cardinality( Note: This test does nat add a record to the database. """ dataset_data = dataset_data - dataset_model = dataset_model + dataset_model = generic_dataset_model if multiple_distribution: dataset_data["distribution"] = [ { @@ -310,7 +310,7 @@ async def test_dataset_schema_distribution_cardinality( ) @pytest.mark.asyncio async def test_dataset_schema_distribution_value_type( - dataset_data, dataset_model, data_format + dataset_data, generic_dataset_model, data_format ): """Test that a dataset pydantic model can be created from dataset json data. Purpose of the test is to validate dataset pydantic model where we are testing allowed value types @@ -318,7 +318,7 @@ async def test_dataset_schema_distribution_value_type( Note: This test does nat add a record to the database. """ dataset_data = dataset_data - dataset_model = dataset_model + dataset_model = generic_dataset_model dataset_data["distribution"] = data_format # validate the data model dataset_instance = await utils.validate_data_model(dataset_data, dataset_model) @@ -349,7 +349,7 @@ async def test_dataset_schema_distribution_value_type( @pytest.mark.parametrize("multiple_data_catalog", [True, False]) @pytest.mark.asyncio async def test_dataset_schema_data_catalog_cardinality( - dataset_data, dataset_model, multiple_data_catalog + dataset_data, generic_dataset_model, multiple_data_catalog ): """Test that a dataset pydantic model can be created from dataset json data. Purpose of the test is to validate dataset pydantic model where the includedInDataCatalog property can @@ -357,7 +357,7 @@ async def test_dataset_schema_data_catalog_cardinality( Note: This test does nat add a record to the database. """ dataset_data = dataset_data - dataset_model = dataset_model + dataset_model = generic_dataset_model if multiple_data_catalog: dataset_data["includedInDataCatalog"] = [ { diff --git a/tests/test_hydroshare_meta_adapter.py b/tests/test_hydroshare_meta_adapter.py index 6865198..e9ca7ca 100644 --- a/tests/test_hydroshare_meta_adapter.py +++ b/tests/test_hydroshare_meta_adapter.py @@ -3,12 +3,12 @@ from pydantic import ValidationError from api.adapters.utils import RepositoryType, get_adapter_by_type -from api.models.catalog import DatasetMetadataDOC +from api.models.catalog import HSResourceMetadataDOC @pytest.mark.parametrize('coverage_type', ["box", "point"]) @pytest.mark.asyncio -async def test_hydroshare_resource_meta_adapter(hydroshare_resource_metadata, coverage_type, dataset_model): +async def test_hydroshare_resource_meta_adapter(hydroshare_resource_metadata, coverage_type, hs_resource_model): """Test the HydroshareMetaAdapter for Composite Resource""" adapter = get_adapter_by_type(RepositoryType.HYDROSHARE) @@ -20,11 +20,11 @@ async def test_hydroshare_resource_meta_adapter(hydroshare_resource_metadata, co dataset = adapter.to_catalog_record(hydroshare_resource_metadata) try: - dataset_model(**dataset.dict()) + hs_resource_model(**dataset.dict()) except ValidationError as err: pytest.fail(f"Catalog dataset schema model validation failed: {str(err)}") - assert isinstance(dataset, DatasetMetadataDOC) + assert isinstance(dataset, HSResourceMetadataDOC) assert dataset.name == "Testing IGUIDE Metadata Adapter for Hydroshare Resource" assert dataset.description == "This is a test resource - abstract" assert dataset.url == "http://www.hydroshare.org/resource/1ee81318135c40f587d9a3e5d689daf5" @@ -101,17 +101,17 @@ async def test_hydroshare_resource_meta_adapter(hydroshare_resource_metadata, co @pytest.mark.asyncio -async def test_hydroshare_collection_meta_adapter(hydroshare_collection_metadata, dataset_model): +async def test_hydroshare_collection_meta_adapter(hydroshare_collection_metadata, hs_resource_model): """Test the HydroshareMetaAdapter for Collection Resource""" adapter = get_adapter_by_type(RepositoryType.HYDROSHARE) dataset = adapter.to_catalog_record(hydroshare_collection_metadata) try: - dataset_model(**dataset.dict()) + hs_resource_model(**dataset.dict()) except ValidationError as err: pytest.fail(f"Catalog dataset schema model validation failed: {str(err)}") - assert isinstance(dataset, DatasetMetadataDOC) + assert isinstance(dataset, HSResourceMetadataDOC) assert dataset.isPartOf == [] assert len(dataset.hasPart) == 3 assert dataset.hasPart[0].description == "Tarboton, D. (2019). Created from iRODS by copy from create resource page, HydroShare" diff --git a/tests/test_netcdf_schema.py b/tests/test_netcdf_schema.py new file mode 100644 index 0000000..b52d6aa --- /dev/null +++ b/tests/test_netcdf_schema.py @@ -0,0 +1,24 @@ +import pytest + +from tests import utils + + +@pytest.mark.asyncio +async def test_netcdf_schema(netcdf_metadata, netcdf_metadata_model): + """Test that a netcdf metadata pydantic model can be created from netcdf metadata json. + Purpose of the test is to validate netcdf metadata schema as defined by the pydantic model. Note: This test does nat + add a record to the database. + """ + netcdf_metadata = netcdf_metadata + netcdf_metadata_model = netcdf_metadata_model + # remove additionalType field + netcdf_metadata.pop("additionalType") + # validate the data model + name = "Snow water equivalent estimation at TWDEF site from Oct 2009 to June 2010" + description = ("This netCDF data is the simulation output from Utah Energy Balance (UEB) model.It includes " + "the simulation result of snow water equivalent during the period Oct. 2009 to June 2010 " + "for TWDEF site in Utah.") + netcdf_model_instance = await utils.validate_data_model(netcdf_metadata, netcdf_metadata_model, name, description) + + assert netcdf_model_instance.name == name + assert netcdf_model_instance.additionalType == "NetCDF" diff --git a/tests/test_raster_schema.py b/tests/test_raster_schema.py new file mode 100644 index 0000000..9e927d4 --- /dev/null +++ b/tests/test_raster_schema.py @@ -0,0 +1,22 @@ +import pytest + +from tests import utils + + +@pytest.mark.asyncio +async def test_raster_schema(raster_metadata, raster_metadata_model): + """Test that a raster metadata pydantic model can be created from netcdf metadata json. + Purpose of the test is to validate raster metadata schema as defined by the pydantic model. Note: This test does nat + add a record to the database. + """ + raster_metadata = raster_metadata + raster_metadata_model = raster_metadata_model + # remove additionalType field + raster_metadata.pop("additionalType") + # validate the data model + name = "Logan" + description = None + raster_model_instance = await utils.validate_data_model(raster_metadata, raster_metadata_model, name, description) + + assert raster_model_instance.name == name + assert raster_model_instance.additionalType == "Geo Raster" diff --git a/tests/utils.py b/tests/utils.py index 569c3d5..18286b7 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -2,14 +2,21 @@ from pydantic import ValidationError -async def validate_data_model(data, model_class): +async def validate_data_model( + data, + model_class, + name="Test Dataset", + description="This is a test dataset metadata json file to test catalog api" +): """Helper function to validate pydantic schema data models.""" try: model_instance = model_class(**data) except ValidationError as e: pytest.fail(f"Schema model validation failed: {str(e)}") + # checking some core metadata - assert model_instance.name == "Test Dataset" - assert model_instance.description == "This is a test dataset metadata json file to test catalog api" + assert model_instance.name == name + assert model_instance.description == description + return model_instance From db02e85696b01a429b8d80c64d89118705a0416d Mon Sep 17 00:00:00 2001 From: pkdash Date: Wed, 3 Apr 2024 13:40:44 -0400 Subject: [PATCH 07/20] [#105] removing commented code --- tests/test_dataset_routes.py | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/tests/test_dataset_routes.py b/tests/test_dataset_routes.py index 49c6214..22e03f2 100644 --- a/tests/test_dataset_routes.py +++ b/tests/test_dataset_routes.py @@ -135,28 +135,6 @@ async def test_delete_dataset(client_test, dataset_data, test_user_access_token) assert submission_response.status_code == 200 -# @pytest.mark.parametrize("multiple", [True, False]) -# @pytest.mark.asyncio -# async def test_get_datasets(client_test, dataset_data, multiple): -# """Testing the get all datasets for a given user""" -# -# # add a dataset record to the db -# dataset_response = await client_test.post("api/catalog/dataset", json=dataset_data) -# assert dataset_response.status_code == 201 -# if multiple: -# # add another dataset record to the db -# dataset_response = await client_test.post("api/catalog/dataset", json=dataset_data) -# assert dataset_response.status_code == 201 -# -# dataset_response = await client_test.get("api/catalog/dataset") -# assert dataset_response.status_code == 200 -# dataset_response_data = dataset_response.json() -# if multiple: -# assert len(dataset_response_data) == 2 -# else: -# assert len(dataset_response_data) == 1 - - @pytest.mark.asyncio async def test_get_datasets_exclude_none(client_test, dataset_data): """Testing exclude none is applied to dataset response model""" @@ -186,7 +164,6 @@ async def test_register_s3_netcdf_dataset(client_test): """Testing registering metadata for a netcdf dataset stored on s3""" # set the path to the netcdf file on s3 - path = "catalogapi/.hs/netcdf/netcdf_valid.nc.json" s3_path = { "path": "catalogapi/.hs/netcdf/netcdf_valid.nc.json", "bucket": "pkdash", @@ -194,9 +171,6 @@ async def test_register_s3_netcdf_dataset(client_test): } dataset_response = await client_test.put("api/catalog/repository/s3/netcdf", json=s3_path) - # print response content - print(dataset_response.content) - assert dataset_response.status_code == 201 response_data = dataset_response.json() assert response_data['additionalType'] == 'NetCDF' From 9ca3e1deacf0a8f5c9f5fa8135f5b9e87f3375eb Mon Sep 17 00:00:00 2001 From: pkdash Date: Wed, 3 Apr 2024 22:55:46 -0400 Subject: [PATCH 08/20] [#105] async pytest fixtures to yield --- tests/conftest.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index ce646fc..5b183b0 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -76,56 +76,56 @@ async def test_user_access_token(): @pytest_asyncio.fixture async def core_metadata(change_test_dir): with open("data/core_metadata.json", "r") as f: - return json.loads(f.read()) + yield json.loads(f.read()) @pytest_asyncio.fixture async def dataset_data(change_test_dir): with open("data/dataset_metadata.json", "r") as f: - return json.loads(f.read()) + yield json.loads(f.read()) @pytest_asyncio.fixture async def netcdf_metadata(change_test_dir): with open("data/netcdf_metadata.json", "r") as f: - return json.loads(f.read()) + yield json.loads(f.read()) @pytest_asyncio.fixture async def raster_metadata(change_test_dir): with open("data/raster_metadata.json", "r") as f: - return json.loads(f.read()) + yield json.loads(f.read()) @pytest_asyncio.fixture async def hs_resource_model(): - return HSResourceMetadata + yield HSResourceMetadata @pytest_asyncio.fixture async def core_model(): - return CoreMetadata + yield CoreMetadata @pytest_asyncio.fixture async def generic_dataset_model(): - return GenericDatasetMetadata + yield GenericDatasetMetadata @pytest_asyncio.fixture async def netcdf_metadata_model(): - return HSNetCDFMetadata + yield HSNetCDFMetadata @pytest_asyncio.fixture async def raster_metadata_model(): - return HSRasterMetadata + yield HSRasterMetadata @pytest_asyncio.fixture async def hydroshare_resource_metadata(change_test_dir): with open("data/hydroshare_resource_meta.json", "r") as f: - return json.loads(f.read()) + yield json.loads(f.read()) @pytest_asyncio.fixture @@ -149,4 +149,4 @@ async def hydroshare_collection_metadata(hydroshare_resource_metadata): {"type": "This resource is described by", "value": "another resource"}, ] collection_meta["relations"] = relations - return collection_meta + yield collection_meta From de7e2084d249ec4a6cd4a22da52204b4679eb1fe Mon Sep 17 00:00:00 2001 From: pkdash Date: Wed, 3 Apr 2024 22:56:57 -0400 Subject: [PATCH 09/20] [#105] cleaning of testing code --- tests/test_core_schema.py | 103 +++++++++-------------------------- tests/test_dataset_schema.py | 32 ++++------- tests/test_netcdf_schema.py | 3 +- tests/test_raster_schema.py | 3 +- 4 files changed, 39 insertions(+), 102 deletions(-) diff --git a/tests/test_core_schema.py b/tests/test_core_schema.py index bc07eed..2b5ded2 100644 --- a/tests/test_core_schema.py +++ b/tests/test_core_schema.py @@ -11,8 +11,6 @@ async def test_core_schema(core_metadata, generic_dataset_model): Purpose of the test is to validate core metadata schema as defined by the pydantic model. Note: This test does nat add a record to the database. """ - core_metadata = core_metadata - generic_dataset_model = generic_dataset_model # validate the data model generic_model_instance = await utils.validate_data_model(core_metadata, generic_dataset_model) @@ -28,9 +26,7 @@ async def test_core_schema_creator_cardinality(core_metadata, generic_dataset_mo Purpose of the test is to validate core metadata schema as defined by the pydantic model where we can have one or more creators. Note: This test does nat add a record to the database. """ - core_metadata = core_metadata - # core metadata model for dataset - generic_dataset_model = generic_dataset_model + core_metadata.pop("creator") if multiple_creators: if creator_type == "person": @@ -141,13 +137,12 @@ async def test_core_schema_creator_person_optional_attributes(core_metadata, gen Purpose of the test is to validate core metadata schema as defined by the pydantic model where we are testing email and identifier attributes are optional. Note: This test does nat add a record to the database. """ - core_metadata = core_metadata - core_model = generic_dataset_model + core_metadata.pop("creator") core_metadata["creator"] = [data_format] # validate the data model - core_model_instance = await utils.validate_data_model(core_metadata, core_model) + core_model_instance = await utils.validate_data_model(core_metadata, generic_dataset_model) assert core_model_instance.creator[0].type == "Person" assert core_model_instance.creator[0].name == "John Doe" @@ -204,8 +199,7 @@ async def test_core_schema_creator_affiliation_optional_attributes(core_metadata Purpose of the test is to validate core metadata schema as defined by the pydantic model where we are testing creator affiliation optional attributes. Note: This test does nat add a record to the database. """ - core_metadata = core_metadata - generic_dataset_model = generic_dataset_model + core_metadata.pop("creator") core_metadata["creator"] = [data_format] @@ -254,8 +248,7 @@ async def test_core_schema_creator_organization_optional_attributes(core_metadat Purpose of the test is to validate core metadata schema as defined by the pydantic model where we are testing optional attributes of the organization object. Note: This test does nat add a record to the database. """ - core_metadata = core_metadata - generic_dataset_model = generic_dataset_model + core_metadata.pop("creator") core_metadata["creator"] = [data_format] @@ -276,8 +269,7 @@ async def test_core_schema_associated_media_cardinality(core_metadata, generic_d Purpose of the test is to validate core metadata schema as defined by the pydantic model where we are testing one or more associated media objects can be created. Note: This test does nat add a record to the database. """ - core_metadata = core_metadata - generic_dataset_model = generic_dataset_model + if multiple_media: associated_media = [ { @@ -366,9 +358,6 @@ async def test_core_schema_associated_media_content_size( Note: This test does nat add a record to the database. """ - core_metadata = core_metadata - generic_dataset_model = generic_dataset_model - core_metadata["associatedMedia"] = [ { "@type": "MediaObject", @@ -395,9 +384,6 @@ async def test_core_schema_associated_media_is_part_of_optional( Note: This test does nat add a record to the database. """ - core_metadata = core_metadata - generic_dataset_model = generic_dataset_model - core_metadata["associatedMedia"] = [ { "@type": "MediaObject", @@ -433,8 +419,7 @@ async def test_core_schema_temporal_coverage_optional(core_metadata, generic_dat temporal coverage can be optional. Note: This test does nat add a record to the database. """ - core_metadata = core_metadata - generic_dataset_model = generic_dataset_model + coverage_value = { "startDate": "2007-03-01T13:00:00", "endDate": "2008-05-11T15:30:00", @@ -468,8 +453,7 @@ async def test_core_schema_temporal_coverage_format(core_metadata, generic_datas that endDate is optional for temporal coverage. Note: This test does nat add a record to the database. """ - core_metadata = core_metadata - generic_dataset_model = generic_dataset_model + core_metadata["temporalCoverage"] = data_format # validate the data model @@ -489,8 +473,7 @@ async def test_core_schema_spatial_coverage_optional(core_metadata, generic_data spatial coverage can be optional. Note: This test does nat add a record to the database. """ - core_metadata = core_metadata - core_model = generic_dataset_model + coverage_value = { "@type": "Place", "name": "CUAHSI Office", @@ -594,8 +577,7 @@ async def test_core_schema_spatial_coverage_value_type(core_metadata, generic_da valid values for spatial coverage with optional additionalProperty attribute. Note: This test does not add a record to the database. """ - core_metadata = core_metadata - generic_dataset_model = generic_dataset_model + core_metadata["spatialCoverage"] = data_format # validate the data model core_model_instance = await utils.validate_data_model(core_metadata, generic_dataset_model) @@ -665,8 +647,6 @@ async def test_create_dataset_creative_works_status_optional(core_metadata, gene Note: This test does nat add a record to the database. """ - core_metadata = core_metadata - generic_dataset_model = generic_dataset_model if not include_creative_works: core_metadata.pop("creativeWorkStatus", None) else: @@ -695,8 +675,6 @@ async def test_core_schema_keywords_cardinality(core_metadata, generic_dataset_m Note: This test does nat add a record to the database. """ - core_metadata = core_metadata - generic_dataset_model = generic_dataset_model core_metadata.pop("keywords", None) if include_multiple: core_metadata["keywords"] = ["Leaf wetness", "Core"] @@ -723,8 +701,6 @@ async def test_core_schema_keywords_optional(core_metadata, generic_dataset_mode Note: This test does nat add a record to the database. """ - core_metadata = core_metadata - generic_dataset_model = generic_dataset_model core_metadata.pop("keywords", None) if include_keywords: core_metadata["keywords"] = ["Leaf wetness", "Core"] @@ -760,8 +736,6 @@ async def test_core_schema_license_value_type(core_metadata, generic_dataset_mod Note: This test does nat add a record to the database. """ - core_metadata = core_metadata - generic_dataset_model = generic_dataset_model core_metadata["license"] = data_format # validate the data model core_model_instance = await utils.validate_data_model(core_metadata, generic_dataset_model) @@ -807,8 +781,6 @@ async def test_core_schema_license_optional_attributes(core_metadata, generic_da Note: This test does nat add a record to the database. """ - core_metadata = core_metadata - generic_dataset_model = generic_dataset_model core_metadata["license"] = data_format # validate the data model core_model_instance = await utils.validate_data_model(core_metadata, generic_dataset_model) @@ -829,10 +801,8 @@ async def test_core_schema_has_part_of_cardinality(core_metadata, generic_datase that the hasPartOf property is optional and one or more values can be added for this property. Note: This test does nat add a record to the database. """ - core_metadata = core_metadata - generic_dataset_model = generic_dataset_model - core_metadata.pop("hasPart", None) + core_metadata.pop("hasPart", None) has_parts = [] if is_multiple and is_multiple is not None: has_parts = [ @@ -915,8 +885,7 @@ async def test_core_schema_has_part_optional_attributes(core_metadata, generic_d the optional attributes of hasPart property. Note: This test does nat add a record to the database. """ - core_metadata = core_metadata - generic_dataset_model = generic_dataset_model + core_metadata.pop("hasPart", None) core_metadata["hasPart"] = [data_format] @@ -938,8 +907,7 @@ async def test_core_schema_is_part_of_cardinality(core_metadata, generic_dataset that the isPartOf property is optional and one or more values can be added for this property. Note: This test does nat add a record to the database. """ - core_metadata = core_metadata - generic_dataset_model = generic_dataset_model + core_metadata.pop("isPartOf", None) is_part_of = [] if is_multiple and is_multiple is not None: @@ -1023,8 +991,7 @@ async def test_core_schema_is_part_of_optional_attributes(core_metadata, generic the optional attributes of the isPartOf property. Note: This test does nat add a record to the database. """ - core_metadata = core_metadata - generic_dataset_model = generic_dataset_model + core_metadata.pop("isPartOf", None) core_metadata["isPartOf"] = [data_format] @@ -1047,8 +1014,7 @@ async def test_core_schema_date_value_type(core_metadata, generic_dataset_model, Also testing that dateModified and datePublished are optional. Note: This test does nat add a record to the database. """ - core_metadata = core_metadata - generic_dataset_model = generic_dataset_model + # TODO: test 'date' type after knowing whether we need to support both date and datetime if dt_type == "date": core_metadata["dateCreated"] = "2020-01-01" @@ -1087,8 +1053,7 @@ async def test_core_schema_provider_value_type(core_metadata, generic_dataset_mo allowed value types for the provider. Note: This test does nat add a record to the database. """ - core_metadata = core_metadata - generic_dataset_model = generic_dataset_model + core_metadata.pop("provider", None) if provider_type == "person": core_metadata["provider"] = { @@ -1124,8 +1089,7 @@ async def test_core_schema_subject_of_cardinality(core_metadata, generic_dataset that the subjectOf property is optional and one or more values can be added for this property. Note: This test does nat add a record to the database. """ - core_metadata = core_metadata - generic_dataset_model = generic_dataset_model + if multiple_values is None: core_metadata.pop("subjectOf", None) elif multiple_values: @@ -1181,8 +1145,7 @@ async def test_core_schema_version_cardinality(core_metadata, generic_dataset_mo that the version property is optional. Note: This test does nat add a record to the database. """ - core_metadata = core_metadata - generic_dataset_model = generic_dataset_model + if include_version: core_metadata["version"] = "v1.0" else: @@ -1204,8 +1167,7 @@ async def test_core_schema_language_cardinality(core_metadata, generic_dataset_m that the inLanguage property is optional. Note: This test does nat add a record to the database. """ - core_metadata = core_metadata - generic_dataset_model = generic_dataset_model + if include_language: core_metadata["inLanguage"] = "en-US" else: @@ -1227,8 +1189,6 @@ async def test_core_schema_funding_cardinality(core_metadata, generic_dataset_mo that the funding property is optional and one or more values can be added to this property. Note: This test does nat add a record to the database. """ - core_metadata = core_metadata - generic_dataset_model = generic_dataset_model if multiple_funding and multiple_funding is not None: core_metadata["funding"] = [ @@ -1264,19 +1224,14 @@ async def test_core_schema_funding_cardinality(core_metadata, generic_dataset_mo == "HDR Institute: Geospatial Understanding through an Integrative Discovery Environment - 1" ) assert core_model_instance.funding[0].identifier == "https://nsf.gov/awardsearch/showAward?AWD_ID=2118329" - # assert core_model_instance.funding[0].funder.type == "Organization" - # assert core_model_instance.funding[0].funder.name == "National Science Foundation" - # assert core_model_instance.funding[0].funder.url[0] == "https://ror.org/021nxhr62" - # assert core_model_instance.funding[0].funder.identifier[1] == "https://doi.org/10.13039/100000001" + assert core_model_instance.funding[1].type == "MonetaryGrant" assert ( core_model_instance.funding[1].name == "HDR Institute: Geospatial Understanding through an Integrative Discovery Environment - 2" ) assert core_model_instance.funding[1].description == "Test grant description" - # assert core_model_instance.funding[1].funder.type == "Person" - # assert core_model_instance.funding[1].funder.name == "John Doe" - # assert core_model_instance.funding[1].funder.email == "johnd@gmail.com" + elif multiple_funding is not None: assert core_model_instance.funding[0].type == "MonetaryGrant" assert ( @@ -1285,9 +1240,6 @@ async def test_core_schema_funding_cardinality(core_metadata, generic_dataset_mo ) assert core_model_instance.funding[0].identifier == "https://nsf.gov/awardsearch/showAward?AWD_ID=2118329" assert core_model_instance.funding[0].description == "Test grant description" - # assert core_model_instance.funding[0].funder.type == "Person" - # assert core_model_instance.funding[0].funder.name == "John Doe" - # assert core_model_instance.funding[0].funder.email == "johnd@gmail.com" else: assert core_model_instance.funding is None @@ -1300,8 +1252,7 @@ async def test_core_schema_funding_funder_optional(core_metadata, generic_datase value for the funder attribute of the funding property is optional. Note: This test does nat add a record to the database. """ - core_metadata = core_metadata - generic_dataset_model = generic_dataset_model + core_metadata.pop("funding", None) if include_funder: core_metadata["funding"] = [ @@ -1372,8 +1323,7 @@ async def test_core_schema_citation_optional(core_metadata, generic_dataset_mode citation is optional for the funding property. Note: This test does nat add a record to the database. """ - core_metadata = core_metadata - generic_dataset_model = generic_dataset_model + core_metadata.pop("citation", None) if include_citation: core_metadata["citation"] = ["Test citation"] @@ -1394,8 +1344,7 @@ async def test_core_schema_publisher_optional(core_metadata, generic_dataset_mod publisher is optional. Note: This test does nat add a record to the database. """ - core_metadata = core_metadata - generic_dataset_model = generic_dataset_model + core_metadata.pop("publisher", None) if include_publisher: core_metadata["publisher"] = { @@ -1415,6 +1364,7 @@ async def test_core_schema_publisher_optional(core_metadata, generic_dataset_mod else: assert core_model_instance.publisher is None + @pytest.mark.parametrize('include_additional_type', [True, False]) @pytest.mark.asyncio async def test_core_schema_additional_type_optional(core_metadata, generic_dataset_model, include_additional_type): @@ -1423,8 +1373,7 @@ async def test_core_schema_additional_type_optional(core_metadata, generic_datas additionalType is optional. Note: This test does nat add a record to the database. """ - core_metadata = core_metadata - generic_dataset_model = generic_dataset_model + core_metadata.pop("additionalType", None) if include_additional_type: core_metadata["additionalType"] = "NetCDF" diff --git a/tests/test_dataset_schema.py b/tests/test_dataset_schema.py index 2b10b1c..7062c0f 100644 --- a/tests/test_dataset_schema.py +++ b/tests/test_dataset_schema.py @@ -14,8 +14,6 @@ async def test_dataset_schema_additional_property( Note: This test does not add a record to the database. """ - dataset_data = dataset_data - dataset_model = generic_dataset_model additional_property = [ { "@type": "PropertyValue", @@ -39,7 +37,7 @@ async def test_dataset_schema_additional_property( dataset_data.pop("additionalProperty", None) # validate the data model - dataset_model_instance = await utils.validate_data_model(dataset_data, dataset_model) + dataset_model_instance = await utils.validate_data_model(dataset_data, generic_dataset_model) if set_additional_property: assert len(dataset_model_instance.additionalProperty) == 2 assert dataset_model_instance.additionalProperty[0].name == additional_property[0]["name"] @@ -69,8 +67,6 @@ async def test_dataset_schema_source_organization( Note: This test does not add a record to the database. """ - dataset_data = dataset_data - dataset_model = generic_dataset_model source_organization = { "@type": "Organization", "name": "National Hydrography Dataset", @@ -83,7 +79,7 @@ async def test_dataset_schema_source_organization( dataset_data.pop("sourceOrganization", None) # validate the data model - dataset_instance = await utils.validate_data_model(dataset_data, dataset_model) + dataset_instance = await utils.validate_data_model(dataset_data, generic_dataset_model) if set_source_organization: assert dataset_instance.sourceOrganization.type == source_organization["@type"] assert dataset_instance.sourceOrganization.name == source_organization["name"] @@ -102,8 +98,6 @@ async def test_dataset_schema_variable_cardinality( have 0 or more values. Note: This test does nat add a record to the database. """ - dataset_data = dataset_data - dataset_model = generic_dataset_model if multiple_variable and multiple_variable is not None: dataset_data["variableMeasured"] = [ @@ -133,7 +127,7 @@ async def test_dataset_schema_variable_cardinality( dataset_data["variableMeasured"] = [] # validate the dataset model - dataset_instance = await utils.validate_data_model(dataset_data, dataset_model) + dataset_instance = await utils.validate_data_model(dataset_data, generic_dataset_model) # checking dataset specific metadata if multiple_variable and multiple_variable is not None: assert len(dataset_instance.variableMeasured) == 2 @@ -179,11 +173,10 @@ async def test_dataset_schema_variable_value_type( the variableMeasured property. Note: This test does nat add a record to the database. """ - dataset_data = dataset_data - dataset_model = generic_dataset_model + dataset_data["variableMeasured"] = data_format # validate the data model - dataset_instance = await utils.validate_data_model(dataset_data, dataset_model) + dataset_instance = await utils.validate_data_model(dataset_data, generic_dataset_model) # checking dataset specific metadata if isinstance(data_format[0], dict): assert dataset_instance.variableMeasured[0].type == "PropertyValue" @@ -210,8 +203,7 @@ async def test_dataset_schema_distribution_cardinality( one or more values. Note: This test does nat add a record to the database. """ - dataset_data = dataset_data - dataset_model = generic_dataset_model + if multiple_distribution: dataset_data["distribution"] = [ { @@ -238,7 +230,7 @@ async def test_dataset_schema_distribution_cardinality( "encodingFormat": "text/csv", } - dataset_instance = await utils.validate_data_model(dataset_data, dataset_model) + dataset_instance = await utils.validate_data_model(dataset_data, generic_dataset_model) # checking dataset specific metadata if multiple_distribution: assert len(dataset_instance.distribution) == 2 @@ -317,11 +309,10 @@ async def test_dataset_schema_distribution_value_type( for distribution property. Note: This test does nat add a record to the database. """ - dataset_data = dataset_data - dataset_model = generic_dataset_model + dataset_data["distribution"] = data_format # validate the data model - dataset_instance = await utils.validate_data_model(dataset_data, dataset_model) + dataset_instance = await utils.validate_data_model(dataset_data, generic_dataset_model) # checking dataset specific metadata assert dataset_instance.distribution.type == "DataDownload" assert dataset_instance.distribution.name == "Fiber_opticdist.zip" @@ -356,8 +347,7 @@ async def test_dataset_schema_data_catalog_cardinality( have one or more values. Note: This test does nat add a record to the database. """ - dataset_data = dataset_data - dataset_model = generic_dataset_model + if multiple_data_catalog: dataset_data["includedInDataCatalog"] = [ { @@ -401,7 +391,7 @@ async def test_dataset_schema_data_catalog_cardinality( } ] # validate the dataset model - dataset_instance = await utils.validate_data_model(dataset_data, dataset_model) + dataset_instance = await utils.validate_data_model(dataset_data, generic_dataset_model) # checking dataset specific metadata if multiple_data_catalog: assert len(dataset_instance.includedInDataCatalog) == 2 diff --git a/tests/test_netcdf_schema.py b/tests/test_netcdf_schema.py index b52d6aa..08ffdc6 100644 --- a/tests/test_netcdf_schema.py +++ b/tests/test_netcdf_schema.py @@ -9,8 +9,7 @@ async def test_netcdf_schema(netcdf_metadata, netcdf_metadata_model): Purpose of the test is to validate netcdf metadata schema as defined by the pydantic model. Note: This test does nat add a record to the database. """ - netcdf_metadata = netcdf_metadata - netcdf_metadata_model = netcdf_metadata_model + # remove additionalType field netcdf_metadata.pop("additionalType") # validate the data model diff --git a/tests/test_raster_schema.py b/tests/test_raster_schema.py index 9e927d4..7c05035 100644 --- a/tests/test_raster_schema.py +++ b/tests/test_raster_schema.py @@ -9,8 +9,7 @@ async def test_raster_schema(raster_metadata, raster_metadata_model): Purpose of the test is to validate raster metadata schema as defined by the pydantic model. Note: This test does nat add a record to the database. """ - raster_metadata = raster_metadata - raster_metadata_model = raster_metadata_model + # remove additionalType field raster_metadata.pop("additionalType") # validate the data model From 041b22d9dd9a3df0f1271e3aac90efc57fd38eb4 Mon Sep 17 00:00:00 2001 From: pkdash Date: Thu, 4 Apr 2024 10:32:27 -0400 Subject: [PATCH 10/20] [#105] using generic schema type for type hints to handle multiple schema types --- api/adapters/base.py | 10 +++---- api/adapters/hydroshare.py | 4 +-- api/adapters/s3.py | 41 ++++++++++++++++----------- api/models/catalog.py | 6 ++++ api/routes/catalog.py | 19 ++++++++----- tests/test_hydroshare_meta_adapter.py | 4 +-- 6 files changed, 52 insertions(+), 32 deletions(-) diff --git a/api/adapters/base.py b/api/adapters/base.py index 360e881..6343451 100644 --- a/api/adapters/base.py +++ b/api/adapters/base.py @@ -1,7 +1,7 @@ import abc from typing import Type from api.config import Settings, get_settings -from api.models.catalog import CoreMetadataDOC +from api.models.catalog import T from api.models.user import Submission @@ -9,7 +9,7 @@ class AbstractRepositoryRequestHandler(abc.ABC): settings: Settings = get_settings() @abc.abstractmethod - def get_metadata(self, record_id: str): + def get_metadata(self, record_id: str) -> dict: """Returns the metadata for the specified record from a repository""" ... @@ -19,13 +19,13 @@ class AbstractRepositoryMetadataAdapter(abc.ABC): @staticmethod @abc.abstractmethod - def to_catalog_record(metadata: dict) -> Type[CoreMetadataDOC]: + def to_catalog_record(metadata: dict, meta_model_type: Type[T]) -> T: """Converts repository metadata to a catalog dataset record""" ... @staticmethod @abc.abstractmethod - def to_repository_record(catalog_record: Type[CoreMetadataDOC]): + def to_repository_record(catalog_record: T): """Converts dataset catalog dataset record to repository metadata""" ... @@ -35,7 +35,7 @@ def update_submission(submission: Submission, repo_record_id: str) -> Submission """Sets additional repository specific metadata to submission record""" ... - async def get_metadata(self, record_id: str): + async def get_metadata(self, record_id: str) -> dict: """Returns the metadata for the specified record from a repository""" return self.repo_api_handler.get_metadata(record_id) diff --git a/api/adapters/hydroshare.py b/api/adapters/hydroshare.py index cfe2874..11d9b37 100644 --- a/api/adapters/hydroshare.py +++ b/api/adapters/hydroshare.py @@ -164,7 +164,7 @@ def to_dataset_license(self): class _HydroshareRequestHandler(AbstractRepositoryRequestHandler): - def get_metadata(self, record_id: str): + def get_metadata(self, record_id: str) -> dict: hs_meta_url = self.settings.hydroshare_meta_read_url % record_id hs_file_url = self.settings.hydroshare_file_read_url % record_id @@ -195,7 +195,7 @@ class HydroshareMetadataAdapter(AbstractRepositoryMetadataAdapter): repo_api_handler = _HydroshareRequestHandler() @staticmethod - def to_catalog_record(metadata: dict) -> HSResourceMetadataDOC: + def to_catalog_record(metadata: dict, meta_model_type: HSResourceMetadataDOC) -> HSResourceMetadataDOC: """Converts hydroshare resource metadata to a catalog dataset record""" hs_metadata_model = _HydroshareResourceMetadata(**metadata) return hs_metadata_model.to_catalog_dataset() diff --git a/api/adapters/s3.py b/api/adapters/s3.py index 2caef32..3c7a5bd 100644 --- a/api/adapters/s3.py +++ b/api/adapters/s3.py @@ -1,45 +1,55 @@ import json +from typing import Type import boto3 from botocore.exceptions import ClientError as S3ClientError from botocore.client import Config from botocore import UNSIGNED -from api.adapters.base import AbstractRepositoryMetadataAdapter, AbstractRepositoryRequestHandler +from api.adapters.base import ( + AbstractRepositoryMetadataAdapter, + AbstractRepositoryRequestHandler, +) from api.adapters.utils import RepositoryType, register_adapter -from api.models.catalog import NetCDFMetadataDOC +from api.models.catalog import T from api.models.user import Submission class _S3RequestHandler(AbstractRepositoryRequestHandler): - - def get_metadata(self, record_id: str): + def get_metadata(self, record_id: str) -> dict: endpoint_url = record_id.split("+")[0] bucket_name = record_id.split("+")[1] file_key = record_id.split("+")[2] + # TODO: Should we be expecting the path for the data file and then compute the metadata file path from that? # Or should we be expecting the metadata file path directly? May be we should get path for both - # data file and metadata file. If have the path for the data file we can check that the file + # data file and metadata file. If have the path for the data file we can check that the data file # exists and then retrieve the metadata file and catalog the metadata. - s3 = boto3.client('s3', config=Config(signature_version=UNSIGNED), endpoint_url=endpoint_url) + s3 = boto3.client( + "s3", config=Config(signature_version=UNSIGNED), endpoint_url=endpoint_url + ) try: response = s3.get_object(Bucket=bucket_name, Key=file_key) except S3ClientError as ex: - if ex.response['Error']['Code'] == 'NoSuchKey': - raise FileNotFoundError(f"Expected metadata file not found in S3 bucket: {bucket_name}/{file_key}") + if ex.response["Error"]["Code"] == "NoSuchKey": + raise FileNotFoundError( + f"Expected metadata file not found in S3 bucket: {bucket_name}/{file_key}" + ) else: raise ex - json_content = response['Body'].read().decode('utf-8') + json_content = response["Body"].read().decode("utf-8") # parse the JSON content try: data = json.loads(json_content) except json.JSONDecodeError as ex: - raise ValueError(f"Invalid JSON content in S3 file ({file_key}). Error: {str(ex)}") + raise ValueError( + f"Invalid JSON content in S3 file ({file_key}). Error: {str(ex)}" + ) # remove additionalType field - this will be set by the schema model - data.pop('additionalType', None) + data.pop("additionalType", None) return data @@ -47,14 +57,13 @@ def get_metadata(self, record_id: str): class S3MetadataAdapter(AbstractRepositoryMetadataAdapter): repo_api_handler = _S3RequestHandler() - # TODO: Need to support multiple metadata types - NetCDF, Raster, GenericDataset @staticmethod - def to_catalog_record(metadata: dict) -> NetCDFMetadataDOC: - return NetCDFMetadataDOC(**metadata) + def to_catalog_record(metadata: dict, meta_model_type: Type[T]) -> T: + return meta_model_type(**metadata) @staticmethod - def to_repository_record(catalog_record: NetCDFMetadataDOC): - """Converts dataset catalog record to hydroshare resource metadata""" + def to_repository_record(catalog_record: T): + """Converts dataset catalog record to repository resource/dataset metadata""" raise NotImplementedError @staticmethod diff --git a/api/models/catalog.py b/api/models/catalog.py index ad19819..24695d9 100644 --- a/api/models/catalog.py +++ b/api/models/catalog.py @@ -1,4 +1,5 @@ import datetime +from typing import TypeVar from beanie import Document @@ -57,3 +58,8 @@ class NetCDFMetadataDOC(CoreMetadataDOC, HSNetCDFMetadata): class RasterMetadataDOC(CoreMetadataDOC, HSRasterMetadata): repository_identifier: str = None + + +# T is a type variable that can be used for type hinting for any schema model that inherits from CoreMetadataDOC + +T = TypeVar("T", bound=CoreMetadataDOC) diff --git a/api/routes/catalog.py b/api/routes/catalog.py index 5278fb6..d6d1905 100644 --- a/api/routes/catalog.py +++ b/api/routes/catalog.py @@ -7,6 +7,7 @@ from api.adapters.utils import get_adapter_by_type, RepositoryType from api.authentication.user import get_current_user from api.models.catalog import ( + T, CoreMetadataDOC, GenericDatasetMetadataDOC, HSResourceMetadataDOC, @@ -30,8 +31,8 @@ class S3Path(BaseModel): def inject_repository_identifier( submission: Submission, - document: Type[CoreMetadataDOC], -): + document: T, +) -> T: if submission.repository_identifier: document.repository_identifier = submission.repository_identifier return document @@ -205,7 +206,8 @@ async def register_hydroshare_resource_metadata( detail="This resource has already been submitted by this user", ) dataset = await _save_to_db( - repository_type=RepositoryType.HYDROSHARE, identifier=identifier, user=user + repository_type=RepositoryType.HYDROSHARE, identifier=identifier, + meta_model_type=HSResourceMetadataDOC, user=user ) return dataset @@ -239,6 +241,7 @@ async def refresh_dataset_from_hydroshare( repository_type=RepositoryType.HYDROSHARE, identifier=identifier, user=user, + meta_model_type=HSResourceMetadataDOC, submission=submission, ) return dataset @@ -258,6 +261,7 @@ async def register_s3_netcdf_dataset(request_model: S3Path, user: Annotated[User identifier = f"{endpoint_url}+{bucket}+{path}" submission: Submission = user.submission_by_repository(repo_type=RepositoryType.S3, identifier=identifier) dataset = await _save_to_db(repository_type=RepositoryType.S3, identifier=identifier, user=user, + meta_model_type=NetCDFMetadataDOC, submission=submission) return dataset @@ -266,12 +270,13 @@ async def _save_to_db( repository_type: RepositoryType, identifier: str, user: User, + meta_model_type: Type[T], submission: Submission = None, -): +) -> T: adapter = get_adapter_by_type(repository_type=repository_type) # fetch metadata from repository as catalog dataset repo_dataset = await _get_repo_meta_as_catalog_record( - adapter=adapter, identifier=identifier + adapter=adapter, identifier=identifier, meta_model_type=meta_model_type ) if submission is None: # new registration @@ -305,7 +310,7 @@ async def _save_to_db( return dataset -async def _get_repo_meta_as_catalog_record(adapter, identifier: str): +async def _get_repo_meta_as_catalog_record(adapter, identifier: str, meta_model_type: Type[T]) -> T: try: metadata = await adapter.get_metadata(identifier) except Exception as ex: @@ -313,5 +318,5 @@ async def _get_repo_meta_as_catalog_record(adapter, identifier: str): status_code=status.HTTP_400_BAD_REQUEST, detail=f"{str(ex)}", ) - catalog_dataset: Type[CoreMetadataDOC] = adapter.to_catalog_record(metadata) + catalog_dataset: T = adapter.to_catalog_record(metadata, meta_model_type=meta_model_type) return catalog_dataset diff --git a/tests/test_hydroshare_meta_adapter.py b/tests/test_hydroshare_meta_adapter.py index e9ca7ca..8568658 100644 --- a/tests/test_hydroshare_meta_adapter.py +++ b/tests/test_hydroshare_meta_adapter.py @@ -18,7 +18,7 @@ async def test_hydroshare_resource_meta_adapter(hydroshare_resource_metadata, co "units": "Decimal degrees", "projection": "Unknown"} - dataset = adapter.to_catalog_record(hydroshare_resource_metadata) + dataset = adapter.to_catalog_record(hydroshare_resource_metadata, type(hs_resource_model)) try: hs_resource_model(**dataset.dict()) except ValidationError as err: @@ -105,7 +105,7 @@ async def test_hydroshare_collection_meta_adapter(hydroshare_collection_metadata """Test the HydroshareMetaAdapter for Collection Resource""" adapter = get_adapter_by_type(RepositoryType.HYDROSHARE) - dataset = adapter.to_catalog_record(hydroshare_collection_metadata) + dataset = adapter.to_catalog_record(hydroshare_collection_metadata, type(hs_resource_model)) try: hs_resource_model(**dataset.dict()) except ValidationError as err: From d8ea4d4d296f8e452b69b5204be4eb6a8070c0e5 Mon Sep 17 00:00:00 2001 From: pkdash Date: Thu, 4 Apr 2024 11:00:59 -0400 Subject: [PATCH 11/20] [#105] using a test bucket for testing --- tests/test_dataset_routes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_dataset_routes.py b/tests/test_dataset_routes.py index 22e03f2..72a73fa 100644 --- a/tests/test_dataset_routes.py +++ b/tests/test_dataset_routes.py @@ -165,8 +165,8 @@ async def test_register_s3_netcdf_dataset(client_test): # set the path to the netcdf file on s3 s3_path = { - "path": "catalogapi/.hs/netcdf/netcdf_valid.nc.json", - "bucket": "pkdash", + "path": "data/.hs/netcdf/netcdf_valid.nc.json", + "bucket": "catalog-api-test", "endpoint_url": "https://api.minio.cuahsi.io/" } From 677575bd3a88b4c109001279bb161e3856150e7c Mon Sep 17 00:00:00 2001 From: pkdash Date: Thu, 4 Apr 2024 23:16:53 -0400 Subject: [PATCH 12/20] [#105] code changes to handle metadata registration from AWS S3 --- api/adapters/s3.py | 4 +++- api/routes/catalog.py | 13 ++++++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/api/adapters/s3.py b/api/adapters/s3.py index 3c7a5bd..ef36ca2 100644 --- a/api/adapters/s3.py +++ b/api/adapters/s3.py @@ -26,6 +26,9 @@ def get_metadata(self, record_id: str) -> dict: # data file and metadata file. If have the path for the data file we can check that the data file # exists and then retrieve the metadata file and catalog the metadata. + # check if the endpoint URL is an AWS S3 URL + if endpoint_url.endswith("amazonaws.com"): + endpoint_url = None s3 = boto3.client( "s3", config=Config(signature_version=UNSIGNED), endpoint_url=endpoint_url ) @@ -50,7 +53,6 @@ def get_metadata(self, record_id: str) -> dict: # remove additionalType field - this will be set by the schema model data.pop("additionalType", None) - return data diff --git a/api/routes/catalog.py b/api/routes/catalog.py index d6d1905..2d92eb2 100644 --- a/api/routes/catalog.py +++ b/api/routes/catalog.py @@ -258,8 +258,13 @@ async def register_s3_netcdf_dataset(request_model: S3Path, user: Annotated[User path = request_model.path bucket = request_model.bucket endpoint_url = request_model.endpoint_url - identifier = f"{endpoint_url}+{bucket}+{path}" + endpoint_url = endpoint_url.rstrip("/") + if endpoint_url.endswith("amazonaws.com"): + identifier = f"{endpoint_url}/{path}" + else: + identifier = f"{endpoint_url}/{bucket}/{path}" submission: Submission = user.submission_by_repository(repo_type=RepositoryType.S3, identifier=identifier) + identifier = f"{endpoint_url}+{bucket}+{path}" dataset = await _save_to_db(repository_type=RepositoryType.S3, identifier=identifier, user=user, meta_model_type=NetCDFMetadataDOC, submission=submission) @@ -278,6 +283,12 @@ async def _save_to_db( repo_dataset = await _get_repo_meta_as_catalog_record( adapter=adapter, identifier=identifier, meta_model_type=meta_model_type ) + if repository_type == RepositoryType.S3: + s3_endpoint_url, bucket, path = identifier.split("+") + if s3_endpoint_url.endswith("amazonaws.com"): + identifier = f"{s3_endpoint_url}/{path}" + else: + identifier = f"{s3_endpoint_url}/{bucket}/{path}" if submission is None: # new registration await repo_dataset.insert() From e7342a8dc1836a2cf2224c4d5853da573f01c9d1 Mon Sep 17 00:00:00 2001 From: pkdash Date: Thu, 4 Apr 2024 23:19:52 -0400 Subject: [PATCH 13/20] [#105] test for registering metadata from AWS S3 --- tests/test_dataset_routes.py | 56 ++++++++++++++++++++++++++---------- 1 file changed, 41 insertions(+), 15 deletions(-) diff --git a/tests/test_dataset_routes.py b/tests/test_dataset_routes.py index 72a73fa..46f5081 100644 --- a/tests/test_dataset_routes.py +++ b/tests/test_dataset_routes.py @@ -14,7 +14,7 @@ async def test_create_dataset(client_test, dataset_data, test_user_access_token) response = await client_test.post("api/catalog/dataset/generic", json=dataset_data) assert response.status_code == 201 response_data = response.json() - record_id = response_data.pop('_id') + record_id = response_data.pop("_id") # adjust the temporal coverage dates for comparison if dataset_data["temporalCoverage"]["startDate"].endswith("Z"): @@ -62,11 +62,11 @@ async def test_create_refresh_dataset_from_hydroshare(client_test, test_user_acc response = await client_test.get(f"api/catalog/repository/hydroshare/{hs_published_res_id}") assert response.status_code == 200 hs_dataset = response.json() - assert hs_dataset['repository_identifier'] == hs_published_res_id + assert hs_dataset["repository_identifier"] == hs_published_res_id await _check_hs_submission(hs_dataset, test_user_access_token, hs_published_res_id) # retrieve the record from the db - record_id = hs_dataset.get('_id') + record_id = hs_dataset.get("_id") response = await client_test.get(f"api/catalog/dataset/hs-resource/{record_id}") assert response.status_code == 200 @@ -86,17 +86,22 @@ async def test_update_dataset(client_test, dataset_data): response = await client_test.post("api/catalog/dataset/generic", json=dataset_data) assert response.status_code == 201 response_data = response.json() - record_id = response_data.get('_id') + record_id = response_data.get("_id") # update the dataset name - dataset_data['name'] = 'Updated title' + dataset_data["name"] = "Updated title" # remove citation - dataset_data['citation'] = [] + dataset_data["citation"] = [] # remove publisher - dataset_data['publisher'] = None + dataset_data["publisher"] = None # update the dataset temporal coverage - dataset_data["temporalCoverage"] = {"startDate": "2020-01-01T10:00:20", "endDate": "2020-11-29T00:30:00"} - response = await client_test.put(f"api/catalog/dataset/generic/{record_id}", json=dataset_data) + dataset_data["temporalCoverage"] = { + "startDate": "2020-01-01T10:00:20", + "endDate": "2020-11-29T00:30:00", + } + response = await client_test.put( + f"api/catalog/dataset/generic/{record_id}", json=dataset_data + ) assert response.status_code == 200 response_data = response.json() response_data.pop('_id') @@ -121,7 +126,7 @@ async def test_delete_dataset(client_test, dataset_data, test_user_access_token) response = await client_test.post("api/catalog/dataset/generic", json=dataset_data) assert response.status_code == 201 response_data = response.json() - record_id = response_data.get('_id') + record_id = response_data.get("_id") # delete the dataset record response = await client_test.delete(f"api/catalog/dataset/{record_id}") assert response.status_code == 200 @@ -144,7 +149,7 @@ async def test_get_datasets_exclude_none(client_test, dataset_data): dataset_response = await client_test.post("api/catalog/dataset/generic", json=dataset_data) assert dataset_response.status_code == 201 response_data = dataset_response.json() - record_id = response_data.get('_id') + record_id = response_data.get("_id") dataset_response = await client_test.get(f"api/catalog/dataset/generic/{record_id}") assert dataset_response.status_code == 200 @@ -161,19 +166,40 @@ async def test_get_datasets_exclude_none(client_test, dataset_data): @pytest.mark.asyncio async def test_register_s3_netcdf_dataset(client_test): - """Testing registering metadata for a netcdf dataset stored on s3""" + """Testing registering metadata for a netcdf dataset stored on minIO""" - # set the path to the netcdf file on s3 + # set the path to the netcdf file on s3 (minIO) s3_path = { "path": "data/.hs/netcdf/netcdf_valid.nc.json", "bucket": "catalog-api-test", - "endpoint_url": "https://api.minio.cuahsi.io/" + "endpoint_url": "https://api.minio.cuahsi.io/", } dataset_response = await client_test.put("api/catalog/repository/s3/netcdf", json=s3_path) assert dataset_response.status_code == 201 response_data = dataset_response.json() - assert response_data['additionalType'] == 'NetCDF' + assert response_data["additionalType"] == "NetCDF" + assert response_data["repository_identifier"] == f"{s3_path['endpoint_url']}{s3_path['bucket']}/{s3_path['path']}" + + +@pytest.mark.asyncio +async def test_register_aws_s3_netcdf_dataset(client_test): + """Testing registering metadata for a netcdf dataset stored on AWS s3""" + + # set the path to the netcdf file on amazon s3 + s3_path = { + "path": "data/.hs/netcdf/netcdf_valid.nc.json", + "bucket": "iguide-catalog", + "endpoint_url": "https://iguide-catalog.s3.us-west-2.amazonaws.com/", + } + + dataset_response = await client_test.put( + "api/catalog/repository/s3/netcdf", json=s3_path + ) + assert dataset_response.status_code == 201 + response_data = dataset_response.json() + assert response_data["additionalType"] == "NetCDF" + assert response_data["repository_identifier"] == f"{s3_path['endpoint_url']}{s3_path['path']}" @pytest.mark.parametrize("multiple", [True, False]) From c75cc0d9a24f669d6b9e26d6c4c81d8cec4d38e1 Mon Sep 17 00:00:00 2001 From: pkdash Date: Fri, 5 Apr 2024 14:53:01 -0400 Subject: [PATCH 14/20] [#105] generating schema.json file for each of the schema types supported in the catalog --- api/models/management/generate_schema.py | 85 +- api/models/schemas/generic/__init__.py | 0 api/models/schemas/generic/schema.json | 2794 +++++++++++++++++++ api/models/schemas/hs_resource/__init__.py | 0 api/models/schemas/hs_resource/schema.json | 2538 ++++++++++++++++++ api/models/schemas/netcdf/__init__.py | 0 api/models/schemas/netcdf/schema.json | 2802 ++++++++++++++++++++ api/models/schemas/raster/__init__.py | 0 api/models/schemas/raster/schema.json | 2799 +++++++++++++++++++ 9 files changed, 10992 insertions(+), 26 deletions(-) create mode 100644 api/models/schemas/generic/__init__.py create mode 100644 api/models/schemas/generic/schema.json create mode 100644 api/models/schemas/hs_resource/__init__.py create mode 100644 api/models/schemas/hs_resource/schema.json create mode 100644 api/models/schemas/netcdf/__init__.py create mode 100644 api/models/schemas/netcdf/schema.json create mode 100644 api/models/schemas/raster/__init__.py create mode 100644 api/models/schemas/raster/schema.json diff --git a/api/models/management/generate_schema.py b/api/models/management/generate_schema.py index cb40540..bad9cd1 100644 --- a/api/models/management/generate_schema.py +++ b/api/models/management/generate_schema.py @@ -3,32 +3,65 @@ import typer -from api.models.schema import GenericDatasetMetadata - -# TODO: Need to generate schemas for all models and each needs to be written to a separate file - - -def main(output_name: str = "api/models/schemas/schema.json"): - schema = GenericDatasetMetadata.schema() - json_schema = GenericDatasetMetadata.schema_json()#indent=2) - # Have to run it a few times for the definitions to get updated before inserted into another model - while "#/definitions/" in json_schema: - for definition in schema["definitions"]: - class_definition = schema["definitions"][definition] - # replace allOf with a single definition - json_schema = json_schema.replace( - f'"allOf": [{{"$ref": "#/definitions/{definition}"}}]', - json.dumps(class_definition)[1:-1] - ) - #replace definition directly - json_schema = json_schema.replace( - f'"$ref": "#/definitions/{definition}"', - json.dumps(class_definition)[1:-1] - ) - embedded_schema = json.loads(json_schema) - current_directory = absolute_directory(output_name) - with open(current_directory, "w") as f: - f.write(json.dumps(embedded_schema, indent=2)) +from api.models.schema import ( + GenericDatasetMetadata, + HSNetCDFMetadata, + HSRasterMetadata, + HSResourceMetadata, +) + + +def main(): + def generate_schema_json(schema_model, folder_name): + base_directory = "api/models/schemas" + schema_file_path = os.path.join(base_directory, folder_name, "schema.json") + schema = schema_model.schema() + json_schema = schema_model.schema_json() + + # Have to run it a few times for the definitions to get updated before inserted into another model + while "#/definitions/" in json_schema: + for definition in schema["definitions"]: + class_definition = schema["definitions"][definition] + # replace allOf with a single definition + json_schema = json_schema.replace( + f'"allOf": [{{"$ref": "#/definitions/{definition}"}}]', + json.dumps(class_definition)[1:-1] + ) + #replace definition directly + json_schema = json_schema.replace( + f'"$ref": "#/definitions/{definition}"', + json.dumps(class_definition)[1:-1] + ) + embedded_schema = json.loads(json_schema) + current_directory = absolute_directory(schema_file_path) + with open(current_directory, "w") as f: + f.write(json.dumps(embedded_schema, indent=2)) + + schemas = get_schemas() + for schema_item in schemas: + generate_schema_json(schema_model=schema_item["model"], folder_name=schema_item["folder_name"]) + + +def get_schemas(): + schemas = [ + { + "model": GenericDatasetMetadata, + "folder_name": "generic", + }, + { + "model": HSResourceMetadata, + "folder_name": "hs_resource", + }, + { + "model": HSNetCDFMetadata, + "folder_name": "netcdf", + }, + { + "model": HSRasterMetadata, + "folder_name": "raster", + }, + ] + return schemas def absolute_directory(output_name): diff --git a/api/models/schemas/generic/__init__.py b/api/models/schemas/generic/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/api/models/schemas/generic/schema.json b/api/models/schemas/generic/schema.json new file mode 100644 index 0000000..40874f0 --- /dev/null +++ b/api/models/schemas/generic/schema.json @@ -0,0 +1,2794 @@ +{ + "title": "GenericDatasetMetadata", + "type": "object", + "properties": { + "@context": { + "title": "@Context", + "description": "Specifies the vocabulary employed for understanding the structured data markup.", + "default": "https://schema.org", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "@type": { + "title": "Submission type", + "description": "Type of submission", + "default": "Dataset", + "const": "Dataset", + "type": "string" + }, + "name": { + "title": "Name or title", + "description": "A text string with a descriptive name or title for the resource.", + "type": "string" + }, + "description": { + "title": "Description or abstract", + "description": "A text string containing a description/abstract for the resource.", + "type": "string" + }, + "url": { + "title": "URL", + "description": "A URL for the landing page that describes the resource and where the content of the resource can be accessed. If there is no landing page, provide the URL of the content.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "identifier": { + "title": "Identifiers", + "description": "Any kind of identifier for the resource/dataset. Identifiers may be DOIs or unique strings assigned by a repository. Multiple identifiers can be entered. Where identifiers can be encoded as URLs, enter URLs here.", + "type": "array", + "items": { + "type": "string", + "title": "Identifier" + } + }, + "creator": { + "title": "Creator", + "description": "Person or Organization that created the resource.", + "type": "array", + "items": { + "anyOf": [ + { + "title": "Creator", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A person.", + "default": "Person", + "const": "Person", + "type": "string" + }, + "name": { + "title": "Name", + "description": "A string containing the full name of the person. Personal name format: Family Name, Given Name.", + "type": "string" + }, + "email": { + "title": "Email", + "description": "A string containing an email address for the creator.", + "type": "string", + "format": "email" + }, + "identifier": { + "title": "Identifier", + "description": "ORCID identifier for creator.", + "pattern": "\\b\\d{4}-\\d{4}-\\d{4}-\\d{3}[0-9X]\\b", + "options": { + "placeholder": "e.g. '0000-0001-2345-6789'" + }, + "errorMessage": { + "pattern": "must match the ORCID pattern. e.g. '0000-0001-2345-6789'" + }, + "type": "string" + }, + "affiliation": { + "title": "Affiliation", + "description": "The affiliation of the creator with the organization.", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "Organization", + "const": "Organization", + "type": "string" + }, + "name": { + "title": "Name", + "description": "Name of the organization the creator is affiliated with.", + "type": "string" + }, + "url": { + "title": "URL", + "description": "A URL to the homepage for the organization.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "address": { + "title": "Address", + "description": "Full address for the organization - e.g., \u201c8200 Old Main Hill, Logan, UT 84322-8200\u201d.", + "type": "string" + } + }, + "required": [ + "name" + ] + } + }, + "required": [ + "name" + ] + }, + { + "title": "Organization", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "Organization", + "const": "Organization", + "type": "string" + }, + "name": { + "title": "Name", + "description": "Name of the provider organization or repository.", + "type": "string" + }, + "url": { + "title": "URL", + "description": "A URL to the homepage for the organization.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "address": { + "title": "Address", + "description": "Full address for the organization - e.g., \u201c8200 Old Main Hill, Logan, UT 84322-8200\u201d.", + "type": "string" + } + }, + "required": [ + "name" + ] + } + ] + } + }, + "dateCreated": { + "title": "Date created", + "description": "The date on which the resource was created.", + "type": "string", + "format": "date-time" + }, + "keywords": { + "title": "Keywords", + "description": "Keywords or tags used to describe the dataset, delimited by commas.", + "minItems": 1, + "type": "array", + "items": { + "type": "string" + } + }, + "license": { + "title": "License", + "description": "A license document that applies to the resource.", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "Submission type can include various forms of content, such as datasets, software source code, digital documents, etc.", + "default": "CreativeWork", + "type": "string" + }, + "name": { + "title": "Name", + "description": "A text string indicating the name of the license under which the resource is shared.", + "type": "string" + }, + "url": { + "title": "URL", + "description": "A URL for a web page that describes the license.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "description": { + "title": "Description", + "description": "A text string describing the license or containing the text of the license itself.", + "type": "string" + } + }, + "required": [ + "name" + ] + }, + "provider": { + "title": "Provider", + "description": "The repository, service provider, organization, person, or service performer that provides access to the resource.", + "anyOf": [ + { + "title": "Organization", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "Organization", + "const": "Organization", + "type": "string" + }, + "name": { + "title": "Name", + "description": "Name of the provider organization or repository.", + "type": "string" + }, + "url": { + "title": "URL", + "description": "A URL to the homepage for the organization.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "address": { + "title": "Address", + "description": "Full address for the organization - e.g., \u201c8200 Old Main Hill, Logan, UT 84322-8200\u201d.", + "type": "string" + } + }, + "required": [ + "name" + ] + }, + { + "title": "Provider", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A person.", + "default": "Person", + "const": "Person", + "type": "string" + }, + "name": { + "title": "Name", + "description": "A string containing the full name of the person. Personal name format: Family Name, Given Name.", + "type": "string" + }, + "email": { + "title": "Email", + "description": "A string containing an email address for the provider.", + "type": "string", + "format": "email" + }, + "identifier": { + "title": "Identifier", + "description": "ORCID identifier for the person.", + "pattern": "\\b\\d{4}-\\d{4}-\\d{4}-\\d{3}[0-9X]\\b", + "options": { + "placeholder": "e.g. '0000-0001-2345-6789'" + }, + "errorMessage": { + "pattern": "must match the ORCID pattern. e.g. '0000-0001-2345-6789'" + }, + "type": "string" + }, + "affiliation": { + "title": "Affiliation", + "description": "The affiliation of the creator with the organization.", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "Organization", + "const": "Organization", + "type": "string" + }, + "name": { + "title": "Name", + "description": "Name of the organization the creator is affiliated with.", + "type": "string" + }, + "url": { + "title": "URL", + "description": "A URL to the homepage for the organization.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "address": { + "title": "Address", + "description": "Full address for the organization - e.g., \u201c8200 Old Main Hill, Logan, UT 84322-8200\u201d.", + "type": "string" + } + }, + "required": [ + "name" + ] + } + }, + "required": [ + "name" + ] + } + ] + }, + "publisher": { + "title": "PublisherOrganization", + "description": "Where the resource is permanently published, indicated the repository, service provider, or organization that published the resource - e.g., CUAHSI HydroShare. This may be the same as Provider.", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "Organization", + "const": "Organization", + "type": "string" + }, + "name": { + "title": "Name", + "description": "Name of the publishing organization.", + "type": "string" + }, + "url": { + "title": "URL", + "description": "A URL to the homepage for the publisher organization or repository.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "address": { + "title": "Address", + "description": "Full address for the organization - e.g., \u201c8200 Old Main Hill, Logan, UT 84322-8200\u201d.", + "type": "string" + } + }, + "required": [ + "name" + ] + }, + "datePublished": { + "title": "Date published", + "description": "Date of first publication for the resource.", + "type": "string", + "format": "date-time" + }, + "subjectOf": { + "title": "Subject of", + "description": "Link to or citation for a related resource that is about or describes this resource - e.g., a journal paper that describes this resource or a related metadata document describing the resource.", + "type": "array", + "items": { + "title": "SubjectOf", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "Submission type can include various forms of content, such as datasets, software source code, digital documents, etc.", + "default": "CreativeWork", + "type": "string" + }, + "name": { + "title": "Name or title", + "description": "Submission's name or title", + "type": "string" + }, + "url": { + "title": "URL", + "description": "The URL address that serves as a reference to access additional details related to the record. It is important to note that this type of metadata solely pertains to the record itself and may not necessarily be an integral component of the record, unlike the HasPart metadata.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "description": { + "title": "Description", + "description": "Information about a related resource that is about or describes this resource - e.g., a related metadata document describing the resource.", + "type": "string" + } + }, + "required": [ + "name" + ] + } + }, + "version": { + "title": "Version", + "description": "A text string indicating the version of the resource.", + "type": "string" + }, + "inLanguage": { + "title": "Language", + "description": "The language of the content of the resource.", + "anyOf": [ + { + "title": "Language", + "description": "", + "enum": [ + "eng", + "esp" + ], + "type": "string" + }, + { + "type": "string", + "title": "Other", + "description": "Please specify another language." + } + ] + }, + "creativeWorkStatus": { + "title": "Resource status", + "description": "The status of this resource in terms of its stage in a lifecycle. Example terms include Incomplete, Draft, Published, and Obsolete.", + "anyOf": [ + { + "title": "Draft", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "DefinedTerm", + "type": "string" + }, + "name": { + "title": "Name", + "default": "Draft", + "type": "string" + }, + "description": { + "title": "Description", + "description": "The description of the item being defined.", + "default": "The resource is in draft state and should not be considered final. Content and metadata may change", + "readOnly": true, + "type": "string" + } + } + }, + { + "title": "Incomplete", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "DefinedTerm", + "type": "string" + }, + "name": { + "title": "Name", + "default": "Incomplete", + "type": "string" + }, + "description": { + "title": "Description", + "description": "The description of the item being defined.", + "default": "Data collection is ongoing or the resource is not completed", + "readOnly": true, + "type": "string" + } + } + }, + { + "title": "Obsolete", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "DefinedTerm", + "type": "string" + }, + "name": { + "title": "Name", + "default": "Obsolete", + "type": "string" + }, + "description": { + "title": "Description", + "description": "The description of the item being defined.", + "default": "The resource has been replaced by a newer version, or the resource is no longer considered applicable", + "readOnly": true, + "type": "string" + } + } + }, + { + "title": "Published", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "DefinedTerm", + "type": "string" + }, + "name": { + "title": "Name", + "default": "Published", + "type": "string" + }, + "description": { + "title": "Description", + "description": "The description of the item being defined.", + "default": "The resource has been permanently published and should be considered final and complete", + "readOnly": true, + "type": "string" + } + } + } + ] + }, + "dateModified": { + "title": "Date modified", + "description": "The date on which the resource was most recently modified or updated.", + "type": "string", + "format": "date-time" + }, + "funding": { + "title": "Funding", + "description": "A Grant or monetary assistance that directly or indirectly provided funding or sponsorship for creation of the resource.", + "type": "array", + "items": { + "title": "Grant", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "This metadata represents details about a grant or financial assistance provided to an individual(s) or organization(s) for supporting the work related to the record.", + "default": "MonetaryGrant", + "type": "string" + }, + "name": { + "title": "Name or title", + "description": "A text string indicating the name or title of the grant or financial assistance.", + "type": "string" + }, + "description": { + "title": "Description", + "description": "A text string describing the grant or financial assistance.", + "type": "string" + }, + "identifier": { + "title": "Funding identifier", + "description": "Grant award number or other identifier.", + "type": "string" + }, + "funder": { + "title": "Funding Organization", + "description": "The organization that provided the funding or sponsorship.", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "Organization", + "const": "Organization", + "type": "string" + }, + "name": { + "title": "Name", + "description": "Name of the organization.", + "type": "string" + }, + "url": { + "title": "URL", + "description": "A URL to the homepage for the organization.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "address": { + "title": "Address", + "description": "Full address for the organization - e.g., \u201c8200 Old Main Hill, Logan, UT 84322-8200\u201d.", + "type": "string" + } + }, + "required": [ + "name" + ] + } + }, + "required": [ + "name" + ] + } + }, + "hasPart": { + "title": "Has part", + "description": "Link to or citation for a related resource that is part of this resource.", + "type": "array", + "items": { + "title": "HasPart", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "Submission type can include various forms of content, such as datasets, software source code, digital documents, etc.", + "default": "CreativeWork", + "type": "string" + }, + "name": { + "title": "Name or title", + "description": "Submission's name or title", + "type": "string" + }, + "url": { + "title": "URL", + "description": "The URL address to the data resource.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "description": { + "title": "Description", + "description": "Information about a related resource that is part of this resource.", + "type": "string" + } + }, + "required": [ + "name" + ] + } + }, + "isPartOf": { + "title": "Is part of", + "description": "Link to or citation for a related resource that this resource is a part of - e.g., a related collection.", + "type": "array", + "items": { + "title": "IsPartOf", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "Submission type can include various forms of content, such as datasets, software source code, digital documents, etc.", + "default": "CreativeWork", + "type": "string" + }, + "name": { + "title": "Name or title", + "description": "Submission's name or title", + "type": "string" + }, + "url": { + "title": "URL", + "description": "The URL address to the data resource.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "description": { + "title": "Description", + "description": "Information about a related resource that this resource is a part of - e.g., a related collection.", + "type": "string" + } + }, + "required": [ + "name" + ] + } + }, + "associatedMedia": { + "title": "Dataset content", + "description": "A media object that encodes this CreativeWork. This property is a synonym for encoding.", + "type": "array", + "items": { + "title": "MediaObject", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "An item that encodes the record.", + "default": "MediaObject", + "type": "string" + }, + "contentUrl": { + "title": "Content URL", + "description": "The direct URL link to access or download the actual content of the media object.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "encodingFormat": { + "title": "Encoding format", + "description": "Represents the specific file format in which the media is encoded.", + "type": "string" + }, + "contentSize": { + "title": "Content size", + "description": "Represents the file size, expressed in bytes, kilobytes, megabytes, or another unit of measurement.", + "type": "string" + }, + "name": { + "title": "Name", + "description": "The name of the media object (file).", + "type": "string" + }, + "sha256": { + "title": "SHA-256", + "description": "The SHA-256 hash of the media object.", + "type": "string" + }, + "isPartOf": { + "title": "MediaObjectPartOf", + "description": "Link to or citation for a related metadata document that this media object is a part of", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "Submission type can include various forms of content, such as datasets, software source code, digital documents, etc.", + "default": "CreativeWork", + "type": "string" + }, + "name": { + "title": "Name or title", + "description": "Submission's name or title", + "type": "string" + }, + "url": { + "title": "URL", + "description": "The URL address to the related metadata document.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "description": { + "title": "Description", + "description": "Information about a related metadata document.", + "type": "string" + } + }, + "required": [ + "name" + ] + } + }, + "required": [ + "contentUrl", + "contentSize", + "name" + ] + } + }, + "citation": { + "title": "Citation", + "description": "A bibliographic citation for the resource.", + "type": "array", + "items": { + "type": "string" + } + }, + "additionalProperty": { + "title": "Additional properties", + "description": "Additional properties of the dataset/resource.", + "default": [], + "type": "array", + "items": { + "title": "PropertyValue", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A property-value pair.", + "default": "PropertyValue", + "const": "PropertyValue", + "type": "string" + }, + "propertyID": { + "title": "Property ID", + "description": "The ID of the property.", + "type": "string" + }, + "name": { + "title": "Name", + "description": "The name of the property.", + "type": "string" + }, + "value": { + "title": "Value", + "description": "The value of the property.", + "anyOf": [ + { + "type": "string" + }, + { + "title": "PropertyValue", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A property-value pair.", + "default": "PropertyValue", + "const": "PropertyValue", + "type": "string" + }, + "propertyID": { + "title": "Property ID", + "description": "The ID of the property.", + "type": "string" + }, + "name": { + "title": "Name", + "description": "The name of the property.", + "type": "string" + }, + "value": { + "title": "Value", + "description": "The value of the property.", + "type": "string" + }, + "unitCode": { + "title": "Measurement unit", + "description": "The unit of measurement for the value.", + "type": "string" + }, + "description": { + "title": "Description", + "description": "A description of the property.", + "type": "string" + }, + "minValue": { + "title": "Minimum value", + "description": "The minimum allowed value for the property.", + "type": "number" + }, + "maxValue": { + "title": "Maximum value", + "description": "The maximum allowed value for the property.", + "type": "number" + }, + "measurementTechnique": { + "title": "Measurement technique", + "description": "A technique or technology used in a measurement.", + "type": "string" + } + }, + "required": [ + "name", + "value" + ] + }, + { + "type": "array", + "items": { + "title": "PropertyValue", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A property-value pair.", + "default": "PropertyValue", + "const": "PropertyValue", + "type": "string" + }, + "propertyID": { + "title": "Property ID", + "description": "The ID of the property.", + "type": "string" + }, + "name": { + "title": "Name", + "description": "The name of the property.", + "type": "string" + }, + "value": { + "title": "Value", + "description": "The value of the property.", + "type": "string" + }, + "unitCode": { + "title": "Measurement unit", + "description": "The unit of measurement for the value.", + "type": "string" + }, + "description": { + "title": "Description", + "description": "A description of the property.", + "type": "string" + }, + "minValue": { + "title": "Minimum value", + "description": "The minimum allowed value for the property.", + "type": "number" + }, + "maxValue": { + "title": "Maximum value", + "description": "The maximum allowed value for the property.", + "type": "number" + }, + "measurementTechnique": { + "title": "Measurement technique", + "description": "A technique or technology used in a measurement.", + "type": "string" + } + }, + "required": [ + "name", + "value" + ] + } + } + ] + }, + "unitCode": { + "title": "Measurement unit", + "description": "The unit of measurement for the value.", + "type": "string" + }, + "description": { + "title": "Description", + "description": "A description of the property.", + "type": "string" + }, + "minValue": { + "title": "Minimum value", + "description": "The minimum allowed value for the property.", + "type": "number" + }, + "maxValue": { + "title": "Maximum value", + "description": "The maximum allowed value for the property.", + "type": "number" + }, + "measurementTechnique": { + "title": "Measurement technique", + "description": "A technique or technology used in a measurement.", + "type": "string" + } + }, + "required": [ + "name", + "value" + ] + } + }, + "additionalType": { + "title": "Additional type of submission", + "description": "An additional type for the dataset.", + "type": "string" + }, + "temporalCoverage": { + "title": "TemporalCoverage", + "description": "The time period that applies to the dataset.", + "type": "object", + "properties": { + "startDate": { + "title": "Start date", + "description": "A date/time object containing the instant corresponding to the commencement of the time interval (ISO8601 formatted date - YYYY-MM-DDTHH:MM).", + "type": "string", + "format": "date-time" + }, + "endDate": { + "title": "End date", + "description": "A date/time object containing the instant corresponding to the termination of the time interval (ISO8601 formatted date - YYYY-MM-DDTHH:MM). If the ending date is left off, that means the temporal coverage is ongoing.", + "type": "string", + "format": "date-time" + } + }, + "required": [ + "startDate" + ] + }, + "spatialCoverage": { + "title": "Place", + "description": "The spatialCoverage of a CreativeWork indicates the place(s) which are the focus of the content. It is a sub property of contentLocation intended primarily for more technical and detailed materials. For example with a Dataset, it indicates areas that the dataset describes: a dataset of New York weather would have spatialCoverage which was the place: the state of New York.", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "Represents the focus area of the record's content.", + "default": "Place", + "type": "string" + }, + "name": { + "title": "Name", + "description": "Name of the place.", + "type": "string" + }, + "geo": { + "title": "Geo", + "description": "Specifies the geographic coordinates of the place in the form of a point location, line, or area coverage extent.", + "anyOf": [ + { + "title": "GeoCoordinates", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "Geographic coordinates that represent a specific location on the Earth's surface. GeoCoordinates typically consists of two components: latitude and longitude.", + "default": "GeoCoordinates", + "type": "string" + }, + "latitude": { + "title": "Latitude", + "description": "Represents the angular distance of a location north or south of the equator, measured in degrees and ranges from -90 to +90 degrees.", + "type": "number" + }, + "longitude": { + "title": "Longitude", + "description": "Represents the angular distance of a location east or west of the Prime Meridian, measured in degrees and ranges from -180 to +180 degrees.", + "type": "number" + } + }, + "required": [ + "latitude", + "longitude" + ] + }, + { + "title": "GeoShape", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A structured representation that describes the coordinates of a geographic feature.", + "default": "GeoShape", + "type": "string" + }, + "box": { + "title": "Box", + "description": "A box is a rectangular region defined by a pair of coordinates representing the southwest and northeast corners of the box.", + "type": "string" + } + }, + "required": [ + "box" + ] + } + ] + }, + "additionalProperty": { + "title": "Additional properties", + "description": "Additional properties of the place.", + "default": [], + "type": "array", + "items": { + "title": "PropertyValue", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A property-value pair.", + "default": "PropertyValue", + "const": "PropertyValue", + "type": "string" + }, + "propertyID": { + "title": "Property ID", + "description": "The ID of the property.", + "type": "string" + }, + "name": { + "title": "Name", + "description": "The name of the property.", + "type": "string" + }, + "value": { + "title": "Value", + "description": "The value of the property.", + "anyOf": [ + { + "type": "string" + }, + { + "title": "PropertyValue", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A property-value pair.", + "default": "PropertyValue", + "const": "PropertyValue", + "type": "string" + }, + "propertyID": { + "title": "Property ID", + "description": "The ID of the property.", + "type": "string" + }, + "name": { + "title": "Name", + "description": "The name of the property.", + "type": "string" + }, + "value": { + "title": "Value", + "description": "The value of the property.", + "type": "string" + }, + "unitCode": { + "title": "Measurement unit", + "description": "The unit of measurement for the value.", + "type": "string" + }, + "description": { + "title": "Description", + "description": "A description of the property.", + "type": "string" + }, + "minValue": { + "title": "Minimum value", + "description": "The minimum allowed value for the property.", + "type": "number" + }, + "maxValue": { + "title": "Maximum value", + "description": "The maximum allowed value for the property.", + "type": "number" + }, + "measurementTechnique": { + "title": "Measurement technique", + "description": "A technique or technology used in a measurement.", + "type": "string" + } + }, + "required": [ + "name", + "value" + ] + }, + { + "type": "array", + "items": { + "title": "PropertyValue", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A property-value pair.", + "default": "PropertyValue", + "const": "PropertyValue", + "type": "string" + }, + "propertyID": { + "title": "Property ID", + "description": "The ID of the property.", + "type": "string" + }, + "name": { + "title": "Name", + "description": "The name of the property.", + "type": "string" + }, + "value": { + "title": "Value", + "description": "The value of the property.", + "type": "string" + }, + "unitCode": { + "title": "Measurement unit", + "description": "The unit of measurement for the value.", + "type": "string" + }, + "description": { + "title": "Description", + "description": "A description of the property.", + "type": "string" + }, + "minValue": { + "title": "Minimum value", + "description": "The minimum allowed value for the property.", + "type": "number" + }, + "maxValue": { + "title": "Maximum value", + "description": "The maximum allowed value for the property.", + "type": "number" + }, + "measurementTechnique": { + "title": "Measurement technique", + "description": "A technique or technology used in a measurement.", + "type": "string" + } + }, + "required": [ + "name", + "value" + ] + } + } + ] + }, + "unitCode": { + "title": "Measurement unit", + "description": "The unit of measurement for the value.", + "type": "string" + }, + "description": { + "title": "Description", + "description": "A description of the property.", + "type": "string" + }, + "minValue": { + "title": "Minimum value", + "description": "The minimum allowed value for the property.", + "type": "number" + }, + "maxValue": { + "title": "Maximum value", + "description": "The maximum allowed value for the property.", + "type": "number" + }, + "measurementTechnique": { + "title": "Measurement technique", + "description": "A technique or technology used in a measurement.", + "type": "string" + } + }, + "required": [ + "name", + "value" + ] + } + } + } + }, + "variableMeasured": { + "title": "Variables measured", + "description": "Measured variables.", + "type": "array", + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "title": "PropertyValue", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A property-value pair.", + "default": "PropertyValue", + "const": "PropertyValue", + "type": "string" + }, + "propertyID": { + "title": "Property ID", + "description": "The ID of the property.", + "type": "string" + }, + "name": { + "title": "Name", + "description": "The name of the property.", + "type": "string" + }, + "value": { + "title": "Value", + "description": "The value of the property.", + "anyOf": [ + { + "type": "string" + }, + { + "title": "PropertyValue", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A property-value pair.", + "default": "PropertyValue", + "const": "PropertyValue", + "type": "string" + }, + "propertyID": { + "title": "Property ID", + "description": "The ID of the property.", + "type": "string" + }, + "name": { + "title": "Name", + "description": "The name of the property.", + "type": "string" + }, + "value": { + "title": "Value", + "description": "The value of the property.", + "type": "string" + }, + "unitCode": { + "title": "Measurement unit", + "description": "The unit of measurement for the value.", + "type": "string" + }, + "description": { + "title": "Description", + "description": "A description of the property.", + "type": "string" + }, + "minValue": { + "title": "Minimum value", + "description": "The minimum allowed value for the property.", + "type": "number" + }, + "maxValue": { + "title": "Maximum value", + "description": "The maximum allowed value for the property.", + "type": "number" + }, + "measurementTechnique": { + "title": "Measurement technique", + "description": "A technique or technology used in a measurement.", + "type": "string" + } + }, + "required": [ + "name", + "value" + ] + }, + { + "type": "array", + "items": { + "title": "PropertyValue", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A property-value pair.", + "default": "PropertyValue", + "const": "PropertyValue", + "type": "string" + }, + "propertyID": { + "title": "Property ID", + "description": "The ID of the property.", + "type": "string" + }, + "name": { + "title": "Name", + "description": "The name of the property.", + "type": "string" + }, + "value": { + "title": "Value", + "description": "The value of the property.", + "type": "string" + }, + "unitCode": { + "title": "Measurement unit", + "description": "The unit of measurement for the value.", + "type": "string" + }, + "description": { + "title": "Description", + "description": "A description of the property.", + "type": "string" + }, + "minValue": { + "title": "Minimum value", + "description": "The minimum allowed value for the property.", + "type": "number" + }, + "maxValue": { + "title": "Maximum value", + "description": "The maximum allowed value for the property.", + "type": "number" + }, + "measurementTechnique": { + "title": "Measurement technique", + "description": "A technique or technology used in a measurement.", + "type": "string" + } + }, + "required": [ + "name", + "value" + ] + } + } + ] + }, + "unitCode": { + "title": "Measurement unit", + "description": "The unit of measurement for the value.", + "type": "string" + }, + "description": { + "title": "Description", + "description": "A description of the property.", + "type": "string" + }, + "minValue": { + "title": "Minimum value", + "description": "The minimum allowed value for the property.", + "type": "number" + }, + "maxValue": { + "title": "Maximum value", + "description": "The maximum allowed value for the property.", + "type": "number" + }, + "measurementTechnique": { + "title": "Measurement technique", + "description": "A technique or technology used in a measurement.", + "type": "string" + } + }, + "required": [ + "name", + "value" + ] + } + ] + } + }, + "sourceOrganization": { + "title": "SourceOrganization", + "description": "The organization that provided the data for this dataset.", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "Organization", + "const": "Organization", + "type": "string" + }, + "name": { + "title": "Name", + "description": "Name of the organization that created the data.", + "type": "string" + }, + "url": { + "title": "URL", + "description": "A URL to the homepage for the organization.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "address": { + "title": "Address", + "description": "Full address for the organization - e.g., \u201c8200 Old Main Hill, Logan, UT 84322-8200\u201d.", + "type": "string" + } + }, + "required": [ + "name" + ] + } + }, + "required": [ + "name", + "url", + "associatedMedia" + ], + "definitions": { + "Affiliation": { + "title": "Affiliation", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "Organization", + "const": "Organization", + "type": "string" + }, + "name": { + "title": "Name", + "description": "Name of the organization the creator is affiliated with.", + "type": "string" + }, + "url": { + "title": "URL", + "description": "A URL to the homepage for the organization.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "address": { + "title": "Address", + "description": "Full address for the organization - e.g., \u201c8200 Old Main Hill, Logan, UT 84322-8200\u201d.", + "type": "string" + } + }, + "required": [ + "name" + ] + }, + "Creator": { + "title": "Creator", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A person.", + "default": "Person", + "const": "Person", + "type": "string" + }, + "name": { + "title": "Name", + "description": "A string containing the full name of the person. Personal name format: Family Name, Given Name.", + "type": "string" + }, + "email": { + "title": "Email", + "description": "A string containing an email address for the creator.", + "type": "string", + "format": "email" + }, + "identifier": { + "title": "Identifier", + "description": "ORCID identifier for creator.", + "pattern": "\\b\\d{4}-\\d{4}-\\d{4}-\\d{3}[0-9X]\\b", + "options": { + "placeholder": "e.g. '0000-0001-2345-6789'" + }, + "errorMessage": { + "pattern": "must match the ORCID pattern. e.g. '0000-0001-2345-6789'" + }, + "type": "string" + }, + "affiliation": { + "title": "Affiliation", + "description": "The affiliation of the creator with the organization.", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "Organization", + "const": "Organization", + "type": "string" + }, + "name": { + "title": "Name", + "description": "Name of the organization the creator is affiliated with.", + "type": "string" + }, + "url": { + "title": "URL", + "description": "A URL to the homepage for the organization.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "address": { + "title": "Address", + "description": "Full address for the organization - e.g., \u201c8200 Old Main Hill, Logan, UT 84322-8200\u201d.", + "type": "string" + } + }, + "required": [ + "name" + ] + } + }, + "required": [ + "name" + ] + }, + "Organization": { + "title": "Organization", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "Organization", + "const": "Organization", + "type": "string" + }, + "name": { + "title": "Name", + "description": "Name of the provider organization or repository.", + "type": "string" + }, + "url": { + "title": "URL", + "description": "A URL to the homepage for the organization.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "address": { + "title": "Address", + "description": "Full address for the organization - e.g., \u201c8200 Old Main Hill, Logan, UT 84322-8200\u201d.", + "type": "string" + } + }, + "required": [ + "name" + ] + }, + "License": { + "title": "License", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "Submission type can include various forms of content, such as datasets, software source code, digital documents, etc.", + "default": "CreativeWork", + "type": "string" + }, + "name": { + "title": "Name", + "description": "A text string indicating the name of the license under which the resource is shared.", + "type": "string" + }, + "url": { + "title": "URL", + "description": "A URL for a web page that describes the license.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "description": { + "title": "Description", + "description": "A text string describing the license or containing the text of the license itself.", + "type": "string" + } + }, + "required": [ + "name" + ] + }, + "Provider": { + "title": "Provider", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A person.", + "default": "Person", + "const": "Person", + "type": "string" + }, + "name": { + "title": "Name", + "description": "A string containing the full name of the person. Personal name format: Family Name, Given Name.", + "type": "string" + }, + "email": { + "title": "Email", + "description": "A string containing an email address for the provider.", + "type": "string", + "format": "email" + }, + "identifier": { + "title": "Identifier", + "description": "ORCID identifier for the person.", + "pattern": "\\b\\d{4}-\\d{4}-\\d{4}-\\d{3}[0-9X]\\b", + "options": { + "placeholder": "e.g. '0000-0001-2345-6789'" + }, + "errorMessage": { + "pattern": "must match the ORCID pattern. e.g. '0000-0001-2345-6789'" + }, + "type": "string" + }, + "affiliation": { + "title": "Affiliation", + "description": "The affiliation of the creator with the organization.", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "Organization", + "const": "Organization", + "type": "string" + }, + "name": { + "title": "Name", + "description": "Name of the organization the creator is affiliated with.", + "type": "string" + }, + "url": { + "title": "URL", + "description": "A URL to the homepage for the organization.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "address": { + "title": "Address", + "description": "Full address for the organization - e.g., \u201c8200 Old Main Hill, Logan, UT 84322-8200\u201d.", + "type": "string" + } + }, + "required": [ + "name" + ] + } + }, + "required": [ + "name" + ] + }, + "PublisherOrganization": { + "title": "PublisherOrganization", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "Organization", + "const": "Organization", + "type": "string" + }, + "name": { + "title": "Name", + "description": "Name of the publishing organization.", + "type": "string" + }, + "url": { + "title": "URL", + "description": "A URL to the homepage for the publisher organization or repository.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "address": { + "title": "Address", + "description": "Full address for the organization - e.g., \u201c8200 Old Main Hill, Logan, UT 84322-8200\u201d.", + "type": "string" + } + }, + "required": [ + "name" + ] + }, + "SubjectOf": { + "title": "SubjectOf", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "Submission type can include various forms of content, such as datasets, software source code, digital documents, etc.", + "default": "CreativeWork", + "type": "string" + }, + "name": { + "title": "Name or title", + "description": "Submission's name or title", + "type": "string" + }, + "url": { + "title": "URL", + "description": "The URL address that serves as a reference to access additional details related to the record. It is important to note that this type of metadata solely pertains to the record itself and may not necessarily be an integral component of the record, unlike the HasPart metadata.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "description": { + "title": "Description", + "description": "Information about a related resource that is about or describes this resource - e.g., a related metadata document describing the resource.", + "type": "string" + } + }, + "required": [ + "name" + ] + }, + "LanguageEnum": { + "title": "Language", + "description": "", + "enum": [ + "eng", + "esp" + ], + "type": "string" + }, + "Draft": { + "title": "Draft", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "DefinedTerm", + "type": "string" + }, + "name": { + "title": "Name", + "default": "Draft", + "type": "string" + }, + "description": { + "title": "Description", + "description": "The description of the item being defined.", + "default": "The resource is in draft state and should not be considered final. Content and metadata may change", + "readOnly": true, + "type": "string" + } + } + }, + "Incomplete": { + "title": "Incomplete", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "DefinedTerm", + "type": "string" + }, + "name": { + "title": "Name", + "default": "Incomplete", + "type": "string" + }, + "description": { + "title": "Description", + "description": "The description of the item being defined.", + "default": "Data collection is ongoing or the resource is not completed", + "readOnly": true, + "type": "string" + } + } + }, + "Obsolete": { + "title": "Obsolete", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "DefinedTerm", + "type": "string" + }, + "name": { + "title": "Name", + "default": "Obsolete", + "type": "string" + }, + "description": { + "title": "Description", + "description": "The description of the item being defined.", + "default": "The resource has been replaced by a newer version, or the resource is no longer considered applicable", + "readOnly": true, + "type": "string" + } + } + }, + "Published": { + "title": "Published", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "DefinedTerm", + "type": "string" + }, + "name": { + "title": "Name", + "default": "Published", + "type": "string" + }, + "description": { + "title": "Description", + "description": "The description of the item being defined.", + "default": "The resource has been permanently published and should be considered final and complete", + "readOnly": true, + "type": "string" + } + } + }, + "Grant": { + "title": "Grant", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "This metadata represents details about a grant or financial assistance provided to an individual(s) or organization(s) for supporting the work related to the record.", + "default": "MonetaryGrant", + "type": "string" + }, + "name": { + "title": "Name or title", + "description": "A text string indicating the name or title of the grant or financial assistance.", + "type": "string" + }, + "description": { + "title": "Description", + "description": "A text string describing the grant or financial assistance.", + "type": "string" + }, + "identifier": { + "title": "Funding identifier", + "description": "Grant award number or other identifier.", + "type": "string" + }, + "funder": { + "title": "Funding Organization", + "description": "The organization that provided the funding or sponsorship.", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "Organization", + "const": "Organization", + "type": "string" + }, + "name": { + "title": "Name", + "description": "Name of the organization.", + "type": "string" + }, + "url": { + "title": "URL", + "description": "A URL to the homepage for the organization.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "address": { + "title": "Address", + "description": "Full address for the organization - e.g., \u201c8200 Old Main Hill, Logan, UT 84322-8200\u201d.", + "type": "string" + } + }, + "required": [ + "name" + ] + } + }, + "required": [ + "name" + ] + }, + "HasPart": { + "title": "HasPart", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "Submission type can include various forms of content, such as datasets, software source code, digital documents, etc.", + "default": "CreativeWork", + "type": "string" + }, + "name": { + "title": "Name or title", + "description": "Submission's name or title", + "type": "string" + }, + "url": { + "title": "URL", + "description": "The URL address to the data resource.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "description": { + "title": "Description", + "description": "Information about a related resource that is part of this resource.", + "type": "string" + } + }, + "required": [ + "name" + ] + }, + "IsPartOf": { + "title": "IsPartOf", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "Submission type can include various forms of content, such as datasets, software source code, digital documents, etc.", + "default": "CreativeWork", + "type": "string" + }, + "name": { + "title": "Name or title", + "description": "Submission's name or title", + "type": "string" + }, + "url": { + "title": "URL", + "description": "The URL address to the data resource.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "description": { + "title": "Description", + "description": "Information about a related resource that this resource is a part of - e.g., a related collection.", + "type": "string" + } + }, + "required": [ + "name" + ] + }, + "MediaObjectPartOf": { + "title": "MediaObjectPartOf", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "Submission type can include various forms of content, such as datasets, software source code, digital documents, etc.", + "default": "CreativeWork", + "type": "string" + }, + "name": { + "title": "Name or title", + "description": "Submission's name or title", + "type": "string" + }, + "url": { + "title": "URL", + "description": "The URL address to the related metadata document.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "description": { + "title": "Description", + "description": "Information about a related metadata document.", + "type": "string" + } + }, + "required": [ + "name" + ] + }, + "MediaObject": { + "title": "MediaObject", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "An item that encodes the record.", + "default": "MediaObject", + "type": "string" + }, + "contentUrl": { + "title": "Content URL", + "description": "The direct URL link to access or download the actual content of the media object.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "encodingFormat": { + "title": "Encoding format", + "description": "Represents the specific file format in which the media is encoded.", + "type": "string" + }, + "contentSize": { + "title": "Content size", + "description": "Represents the file size, expressed in bytes, kilobytes, megabytes, or another unit of measurement.", + "type": "string" + }, + "name": { + "title": "Name", + "description": "The name of the media object (file).", + "type": "string" + }, + "sha256": { + "title": "SHA-256", + "description": "The SHA-256 hash of the media object.", + "type": "string" + }, + "isPartOf": { + "title": "MediaObjectPartOf", + "description": "Link to or citation for a related metadata document that this media object is a part of", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "Submission type can include various forms of content, such as datasets, software source code, digital documents, etc.", + "default": "CreativeWork", + "type": "string" + }, + "name": { + "title": "Name or title", + "description": "Submission's name or title", + "type": "string" + }, + "url": { + "title": "URL", + "description": "The URL address to the related metadata document.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "description": { + "title": "Description", + "description": "Information about a related metadata document.", + "type": "string" + } + }, + "required": [ + "name" + ] + } + }, + "required": [ + "contentUrl", + "contentSize", + "name" + ] + }, + "PropertyValueBase": { + "title": "PropertyValue", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A property-value pair.", + "default": "PropertyValue", + "const": "PropertyValue", + "type": "string" + }, + "propertyID": { + "title": "Property ID", + "description": "The ID of the property.", + "type": "string" + }, + "name": { + "title": "Name", + "description": "The name of the property.", + "type": "string" + }, + "value": { + "title": "Value", + "description": "The value of the property.", + "type": "string" + }, + "unitCode": { + "title": "Measurement unit", + "description": "The unit of measurement for the value.", + "type": "string" + }, + "description": { + "title": "Description", + "description": "A description of the property.", + "type": "string" + }, + "minValue": { + "title": "Minimum value", + "description": "The minimum allowed value for the property.", + "type": "number" + }, + "maxValue": { + "title": "Maximum value", + "description": "The maximum allowed value for the property.", + "type": "number" + }, + "measurementTechnique": { + "title": "Measurement technique", + "description": "A technique or technology used in a measurement.", + "type": "string" + } + }, + "required": [ + "name", + "value" + ] + }, + "PropertyValue": { + "title": "PropertyValue", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A property-value pair.", + "default": "PropertyValue", + "const": "PropertyValue", + "type": "string" + }, + "propertyID": { + "title": "Property ID", + "description": "The ID of the property.", + "type": "string" + }, + "name": { + "title": "Name", + "description": "The name of the property.", + "type": "string" + }, + "value": { + "title": "Value", + "description": "The value of the property.", + "anyOf": [ + { + "type": "string" + }, + { + "title": "PropertyValue", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A property-value pair.", + "default": "PropertyValue", + "const": "PropertyValue", + "type": "string" + }, + "propertyID": { + "title": "Property ID", + "description": "The ID of the property.", + "type": "string" + }, + "name": { + "title": "Name", + "description": "The name of the property.", + "type": "string" + }, + "value": { + "title": "Value", + "description": "The value of the property.", + "type": "string" + }, + "unitCode": { + "title": "Measurement unit", + "description": "The unit of measurement for the value.", + "type": "string" + }, + "description": { + "title": "Description", + "description": "A description of the property.", + "type": "string" + }, + "minValue": { + "title": "Minimum value", + "description": "The minimum allowed value for the property.", + "type": "number" + }, + "maxValue": { + "title": "Maximum value", + "description": "The maximum allowed value for the property.", + "type": "number" + }, + "measurementTechnique": { + "title": "Measurement technique", + "description": "A technique or technology used in a measurement.", + "type": "string" + } + }, + "required": [ + "name", + "value" + ] + }, + { + "type": "array", + "items": { + "title": "PropertyValue", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A property-value pair.", + "default": "PropertyValue", + "const": "PropertyValue", + "type": "string" + }, + "propertyID": { + "title": "Property ID", + "description": "The ID of the property.", + "type": "string" + }, + "name": { + "title": "Name", + "description": "The name of the property.", + "type": "string" + }, + "value": { + "title": "Value", + "description": "The value of the property.", + "type": "string" + }, + "unitCode": { + "title": "Measurement unit", + "description": "The unit of measurement for the value.", + "type": "string" + }, + "description": { + "title": "Description", + "description": "A description of the property.", + "type": "string" + }, + "minValue": { + "title": "Minimum value", + "description": "The minimum allowed value for the property.", + "type": "number" + }, + "maxValue": { + "title": "Maximum value", + "description": "The maximum allowed value for the property.", + "type": "number" + }, + "measurementTechnique": { + "title": "Measurement technique", + "description": "A technique or technology used in a measurement.", + "type": "string" + } + }, + "required": [ + "name", + "value" + ] + } + } + ] + }, + "unitCode": { + "title": "Measurement unit", + "description": "The unit of measurement for the value.", + "type": "string" + }, + "description": { + "title": "Description", + "description": "A description of the property.", + "type": "string" + }, + "minValue": { + "title": "Minimum value", + "description": "The minimum allowed value for the property.", + "type": "number" + }, + "maxValue": { + "title": "Maximum value", + "description": "The maximum allowed value for the property.", + "type": "number" + }, + "measurementTechnique": { + "title": "Measurement technique", + "description": "A technique or technology used in a measurement.", + "type": "string" + } + }, + "required": [ + "name", + "value" + ] + }, + "TemporalCoverage": { + "title": "TemporalCoverage", + "type": "object", + "properties": { + "startDate": { + "title": "Start date", + "description": "A date/time object containing the instant corresponding to the commencement of the time interval (ISO8601 formatted date - YYYY-MM-DDTHH:MM).", + "type": "string", + "format": "date-time" + }, + "endDate": { + "title": "End date", + "description": "A date/time object containing the instant corresponding to the termination of the time interval (ISO8601 formatted date - YYYY-MM-DDTHH:MM). If the ending date is left off, that means the temporal coverage is ongoing.", + "type": "string", + "format": "date-time" + } + }, + "required": [ + "startDate" + ] + }, + "GeoCoordinates": { + "title": "GeoCoordinates", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "Geographic coordinates that represent a specific location on the Earth's surface. GeoCoordinates typically consists of two components: latitude and longitude.", + "default": "GeoCoordinates", + "type": "string" + }, + "latitude": { + "title": "Latitude", + "description": "Represents the angular distance of a location north or south of the equator, measured in degrees and ranges from -90 to +90 degrees.", + "type": "number" + }, + "longitude": { + "title": "Longitude", + "description": "Represents the angular distance of a location east or west of the Prime Meridian, measured in degrees and ranges from -180 to +180 degrees.", + "type": "number" + } + }, + "required": [ + "latitude", + "longitude" + ] + }, + "GeoShape": { + "title": "GeoShape", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A structured representation that describes the coordinates of a geographic feature.", + "default": "GeoShape", + "type": "string" + }, + "box": { + "title": "Box", + "description": "A box is a rectangular region defined by a pair of coordinates representing the southwest and northeast corners of the box.", + "type": "string" + } + }, + "required": [ + "box" + ] + }, + "Place": { + "title": "Place", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "Represents the focus area of the record's content.", + "default": "Place", + "type": "string" + }, + "name": { + "title": "Name", + "description": "Name of the place.", + "type": "string" + }, + "geo": { + "title": "Geo", + "description": "Specifies the geographic coordinates of the place in the form of a point location, line, or area coverage extent.", + "anyOf": [ + { + "title": "GeoCoordinates", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "Geographic coordinates that represent a specific location on the Earth's surface. GeoCoordinates typically consists of two components: latitude and longitude.", + "default": "GeoCoordinates", + "type": "string" + }, + "latitude": { + "title": "Latitude", + "description": "Represents the angular distance of a location north or south of the equator, measured in degrees and ranges from -90 to +90 degrees.", + "type": "number" + }, + "longitude": { + "title": "Longitude", + "description": "Represents the angular distance of a location east or west of the Prime Meridian, measured in degrees and ranges from -180 to +180 degrees.", + "type": "number" + } + }, + "required": [ + "latitude", + "longitude" + ] + }, + { + "title": "GeoShape", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A structured representation that describes the coordinates of a geographic feature.", + "default": "GeoShape", + "type": "string" + }, + "box": { + "title": "Box", + "description": "A box is a rectangular region defined by a pair of coordinates representing the southwest and northeast corners of the box.", + "type": "string" + } + }, + "required": [ + "box" + ] + } + ] + }, + "additionalProperty": { + "title": "Additional properties", + "description": "Additional properties of the place.", + "default": [], + "type": "array", + "items": { + "title": "PropertyValue", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A property-value pair.", + "default": "PropertyValue", + "const": "PropertyValue", + "type": "string" + }, + "propertyID": { + "title": "Property ID", + "description": "The ID of the property.", + "type": "string" + }, + "name": { + "title": "Name", + "description": "The name of the property.", + "type": "string" + }, + "value": { + "title": "Value", + "description": "The value of the property.", + "anyOf": [ + { + "type": "string" + }, + { + "title": "PropertyValue", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A property-value pair.", + "default": "PropertyValue", + "const": "PropertyValue", + "type": "string" + }, + "propertyID": { + "title": "Property ID", + "description": "The ID of the property.", + "type": "string" + }, + "name": { + "title": "Name", + "description": "The name of the property.", + "type": "string" + }, + "value": { + "title": "Value", + "description": "The value of the property.", + "type": "string" + }, + "unitCode": { + "title": "Measurement unit", + "description": "The unit of measurement for the value.", + "type": "string" + }, + "description": { + "title": "Description", + "description": "A description of the property.", + "type": "string" + }, + "minValue": { + "title": "Minimum value", + "description": "The minimum allowed value for the property.", + "type": "number" + }, + "maxValue": { + "title": "Maximum value", + "description": "The maximum allowed value for the property.", + "type": "number" + }, + "measurementTechnique": { + "title": "Measurement technique", + "description": "A technique or technology used in a measurement.", + "type": "string" + } + }, + "required": [ + "name", + "value" + ] + }, + { + "type": "array", + "items": { + "title": "PropertyValue", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A property-value pair.", + "default": "PropertyValue", + "const": "PropertyValue", + "type": "string" + }, + "propertyID": { + "title": "Property ID", + "description": "The ID of the property.", + "type": "string" + }, + "name": { + "title": "Name", + "description": "The name of the property.", + "type": "string" + }, + "value": { + "title": "Value", + "description": "The value of the property.", + "type": "string" + }, + "unitCode": { + "title": "Measurement unit", + "description": "The unit of measurement for the value.", + "type": "string" + }, + "description": { + "title": "Description", + "description": "A description of the property.", + "type": "string" + }, + "minValue": { + "title": "Minimum value", + "description": "The minimum allowed value for the property.", + "type": "number" + }, + "maxValue": { + "title": "Maximum value", + "description": "The maximum allowed value for the property.", + "type": "number" + }, + "measurementTechnique": { + "title": "Measurement technique", + "description": "A technique or technology used in a measurement.", + "type": "string" + } + }, + "required": [ + "name", + "value" + ] + } + } + ] + }, + "unitCode": { + "title": "Measurement unit", + "description": "The unit of measurement for the value.", + "type": "string" + }, + "description": { + "title": "Description", + "description": "A description of the property.", + "type": "string" + }, + "minValue": { + "title": "Minimum value", + "description": "The minimum allowed value for the property.", + "type": "number" + }, + "maxValue": { + "title": "Maximum value", + "description": "The maximum allowed value for the property.", + "type": "number" + }, + "measurementTechnique": { + "title": "Measurement technique", + "description": "A technique or technology used in a measurement.", + "type": "string" + } + }, + "required": [ + "name", + "value" + ] + } + } + } + }, + "SourceOrganization": { + "title": "SourceOrganization", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "Organization", + "const": "Organization", + "type": "string" + }, + "name": { + "title": "Name", + "description": "Name of the organization that created the data.", + "type": "string" + }, + "url": { + "title": "URL", + "description": "A URL to the homepage for the organization.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "address": { + "title": "Address", + "description": "Full address for the organization - e.g., \u201c8200 Old Main Hill, Logan, UT 84322-8200\u201d.", + "type": "string" + } + }, + "required": [ + "name" + ] + } + } +} \ No newline at end of file diff --git a/api/models/schemas/hs_resource/__init__.py b/api/models/schemas/hs_resource/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/api/models/schemas/hs_resource/schema.json b/api/models/schemas/hs_resource/schema.json new file mode 100644 index 0000000..cf70b7c --- /dev/null +++ b/api/models/schemas/hs_resource/schema.json @@ -0,0 +1,2538 @@ +{ + "title": "HSResourceMetadata", + "type": "object", + "properties": { + "@context": { + "title": "@Context", + "description": "Specifies the vocabulary employed for understanding the structured data markup.", + "default": "https://schema.org", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "@type": { + "title": "Submission type", + "description": "Submission type can include various forms of content, such as datasets, software source code, digital documents, etc.", + "default": "Dataset", + "enum": [ + "Dataset", + "Notebook", + "Software Source Code" + ], + "type": "string" + }, + "name": { + "title": "Name or title", + "description": "A text string with a descriptive name or title for the resource.", + "type": "string" + }, + "description": { + "title": "Description or abstract", + "description": "A text string containing a description/abstract for the resource.", + "type": "string" + }, + "url": { + "title": "URL", + "description": "A URL for the landing page that describes the resource and where the content of the resource can be accessed. If there is no landing page, provide the URL of the content.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "identifier": { + "title": "Identifiers", + "description": "Any kind of identifier for the resource. Identifiers may be DOIs or unique strings assigned by a repository. Multiple identifiers can be entered. Where identifiers can be encoded as URLs, enter URLs here.", + "type": "array", + "items": { + "type": "string", + "title": "Identifier" + } + }, + "creator": { + "title": "Creator", + "description": "Person or Organization that created the resource.", + "type": "array", + "items": { + "anyOf": [ + { + "title": "Creator", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A person.", + "default": "Person", + "const": "Person", + "type": "string" + }, + "name": { + "title": "Name", + "description": "A string containing the full name of the person. Personal name format: Family Name, Given Name.", + "type": "string" + }, + "email": { + "title": "Email", + "description": "A string containing an email address for the creator.", + "type": "string", + "format": "email" + }, + "identifier": { + "title": "Identifier", + "description": "ORCID identifier for creator.", + "pattern": "\\b\\d{4}-\\d{4}-\\d{4}-\\d{3}[0-9X]\\b", + "options": { + "placeholder": "e.g. '0000-0001-2345-6789'" + }, + "errorMessage": { + "pattern": "must match the ORCID pattern. e.g. '0000-0001-2345-6789'" + }, + "type": "string" + }, + "affiliation": { + "title": "Affiliation", + "description": "The affiliation of the creator with the organization.", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "Organization", + "const": "Organization", + "type": "string" + }, + "name": { + "title": "Name", + "description": "Name of the organization the creator is affiliated with.", + "type": "string" + }, + "url": { + "title": "URL", + "description": "A URL to the homepage for the organization.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "address": { + "title": "Address", + "description": "Full address for the organization - e.g., \u201c8200 Old Main Hill, Logan, UT 84322-8200\u201d.", + "type": "string" + } + }, + "required": [ + "name" + ] + } + }, + "required": [ + "name" + ] + }, + { + "title": "Organization", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "Organization", + "const": "Organization", + "type": "string" + }, + "name": { + "title": "Name", + "description": "Name of the provider organization or repository.", + "type": "string" + }, + "url": { + "title": "URL", + "description": "A URL to the homepage for the organization.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "address": { + "title": "Address", + "description": "Full address for the organization - e.g., \u201c8200 Old Main Hill, Logan, UT 84322-8200\u201d.", + "type": "string" + } + }, + "required": [ + "name" + ] + } + ] + } + }, + "dateCreated": { + "title": "Date created", + "description": "The date on which the resource was created.", + "type": "string", + "format": "date-time" + }, + "keywords": { + "title": "Keywords", + "description": "Keywords or tags used to describe the dataset, delimited by commas.", + "minItems": 1, + "type": "array", + "items": { + "type": "string" + } + }, + "license": { + "title": "License", + "description": "A license document that applies to the resource.", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "Submission type can include various forms of content, such as datasets, software source code, digital documents, etc.", + "default": "CreativeWork", + "type": "string" + }, + "name": { + "title": "Name", + "description": "A text string indicating the name of the license under which the resource is shared.", + "type": "string" + }, + "url": { + "title": "URL", + "description": "A URL for a web page that describes the license.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "description": { + "title": "Description", + "description": "A text string describing the license or containing the text of the license itself.", + "type": "string" + } + }, + "required": [ + "name" + ] + }, + "provider": { + "title": "Provider", + "description": "The repository, service provider, organization, person, or service performer that provides access to the resource.", + "anyOf": [ + { + "title": "Organization", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "Organization", + "const": "Organization", + "type": "string" + }, + "name": { + "title": "Name", + "description": "Name of the provider organization or repository.", + "type": "string" + }, + "url": { + "title": "URL", + "description": "A URL to the homepage for the organization.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "address": { + "title": "Address", + "description": "Full address for the organization - e.g., \u201c8200 Old Main Hill, Logan, UT 84322-8200\u201d.", + "type": "string" + } + }, + "required": [ + "name" + ] + }, + { + "title": "Provider", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A person.", + "default": "Person", + "const": "Person", + "type": "string" + }, + "name": { + "title": "Name", + "description": "A string containing the full name of the person. Personal name format: Family Name, Given Name.", + "type": "string" + }, + "email": { + "title": "Email", + "description": "A string containing an email address for the provider.", + "type": "string", + "format": "email" + }, + "identifier": { + "title": "Identifier", + "description": "ORCID identifier for the person.", + "pattern": "\\b\\d{4}-\\d{4}-\\d{4}-\\d{3}[0-9X]\\b", + "options": { + "placeholder": "e.g. '0000-0001-2345-6789'" + }, + "errorMessage": { + "pattern": "must match the ORCID pattern. e.g. '0000-0001-2345-6789'" + }, + "type": "string" + }, + "affiliation": { + "title": "Affiliation", + "description": "The affiliation of the creator with the organization.", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "Organization", + "const": "Organization", + "type": "string" + }, + "name": { + "title": "Name", + "description": "Name of the organization the creator is affiliated with.", + "type": "string" + }, + "url": { + "title": "URL", + "description": "A URL to the homepage for the organization.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "address": { + "title": "Address", + "description": "Full address for the organization - e.g., \u201c8200 Old Main Hill, Logan, UT 84322-8200\u201d.", + "type": "string" + } + }, + "required": [ + "name" + ] + } + }, + "required": [ + "name" + ] + } + ] + }, + "publisher": { + "title": "PublisherOrganization", + "description": "Where the resource is permanently published, indicated the repository, service provider, or organization that published the resource - e.g., CUAHSI HydroShare. This may be the same as Provider.", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "Organization", + "const": "Organization", + "type": "string" + }, + "name": { + "title": "Name", + "description": "Name of the publishing organization.", + "type": "string" + }, + "url": { + "title": "URL", + "description": "A URL to the homepage for the publisher organization or repository.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "address": { + "title": "Address", + "description": "Full address for the organization - e.g., \u201c8200 Old Main Hill, Logan, UT 84322-8200\u201d.", + "type": "string" + } + }, + "required": [ + "name" + ] + }, + "datePublished": { + "title": "Date published", + "description": "Date of first publication for the resource.", + "type": "string", + "format": "date-time" + }, + "subjectOf": { + "title": "Subject of", + "description": "Link to or citation for a related resource that is about or describes this resource - e.g., a journal paper that describes this resource or a related metadata document describing the resource.", + "type": "array", + "items": { + "title": "SubjectOf", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "Submission type can include various forms of content, such as datasets, software source code, digital documents, etc.", + "default": "CreativeWork", + "type": "string" + }, + "name": { + "title": "Name or title", + "description": "Submission's name or title", + "type": "string" + }, + "url": { + "title": "URL", + "description": "The URL address that serves as a reference to access additional details related to the record. It is important to note that this type of metadata solely pertains to the record itself and may not necessarily be an integral component of the record, unlike the HasPart metadata.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "description": { + "title": "Description", + "description": "Information about a related resource that is about or describes this resource - e.g., a related metadata document describing the resource.", + "type": "string" + } + }, + "required": [ + "name" + ] + } + }, + "version": { + "title": "Version", + "description": "A text string indicating the version of the resource.", + "type": "string" + }, + "inLanguage": { + "title": "Language", + "description": "The language of the content of the resource.", + "anyOf": [ + { + "title": "Language", + "description": "", + "enum": [ + "eng", + "esp" + ], + "type": "string" + }, + { + "type": "string", + "title": "Other", + "description": "Please specify another language." + } + ] + }, + "creativeWorkStatus": { + "title": "Resource status", + "description": "The status of this resource in terms of its stage in a lifecycle. Example terms include Incomplete, Draft, Published, and Obsolete.", + "anyOf": [ + { + "title": "Draft", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "DefinedTerm", + "type": "string" + }, + "name": { + "title": "Name", + "default": "Draft", + "type": "string" + }, + "description": { + "title": "Description", + "description": "The description of the item being defined.", + "default": "The resource is in draft state and should not be considered final. Content and metadata may change", + "readOnly": true, + "type": "string" + } + } + }, + { + "title": "Incomplete", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "DefinedTerm", + "type": "string" + }, + "name": { + "title": "Name", + "default": "Incomplete", + "type": "string" + }, + "description": { + "title": "Description", + "description": "The description of the item being defined.", + "default": "Data collection is ongoing or the resource is not completed", + "readOnly": true, + "type": "string" + } + } + }, + { + "title": "Obsolete", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "DefinedTerm", + "type": "string" + }, + "name": { + "title": "Name", + "default": "Obsolete", + "type": "string" + }, + "description": { + "title": "Description", + "description": "The description of the item being defined.", + "default": "The resource has been replaced by a newer version, or the resource is no longer considered applicable", + "readOnly": true, + "type": "string" + } + } + }, + { + "title": "Published", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "DefinedTerm", + "type": "string" + }, + "name": { + "title": "Name", + "default": "Published", + "type": "string" + }, + "description": { + "title": "Description", + "description": "The description of the item being defined.", + "default": "The resource has been permanently published and should be considered final and complete", + "readOnly": true, + "type": "string" + } + } + } + ] + }, + "dateModified": { + "title": "Date modified", + "description": "The date on which the resource was most recently modified or updated.", + "type": "string", + "format": "date-time" + }, + "funding": { + "title": "Funding", + "description": "A Grant or monetary assistance that directly or indirectly provided funding or sponsorship for creation of the resource.", + "type": "array", + "items": { + "title": "Grant", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "This metadata represents details about a grant or financial assistance provided to an individual(s) or organization(s) for supporting the work related to the record.", + "default": "MonetaryGrant", + "type": "string" + }, + "name": { + "title": "Name or title", + "description": "A text string indicating the name or title of the grant or financial assistance.", + "type": "string" + }, + "description": { + "title": "Description", + "description": "A text string describing the grant or financial assistance.", + "type": "string" + }, + "identifier": { + "title": "Funding identifier", + "description": "Grant award number or other identifier.", + "type": "string" + }, + "funder": { + "title": "Funding Organization", + "description": "The organization that provided the funding or sponsorship.", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "Organization", + "const": "Organization", + "type": "string" + }, + "name": { + "title": "Name", + "description": "Name of the organization.", + "type": "string" + }, + "url": { + "title": "URL", + "description": "A URL to the homepage for the organization.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "address": { + "title": "Address", + "description": "Full address for the organization - e.g., \u201c8200 Old Main Hill, Logan, UT 84322-8200\u201d.", + "type": "string" + } + }, + "required": [ + "name" + ] + } + }, + "required": [ + "name" + ] + } + }, + "hasPart": { + "title": "Has part", + "description": "Link to or citation for a related resource that is part of this resource.", + "type": "array", + "items": { + "title": "HasPart", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "Submission type can include various forms of content, such as datasets, software source code, digital documents, etc.", + "default": "CreativeWork", + "type": "string" + }, + "name": { + "title": "Name or title", + "description": "Submission's name or title", + "type": "string" + }, + "url": { + "title": "URL", + "description": "The URL address to the data resource.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "description": { + "title": "Description", + "description": "Information about a related resource that is part of this resource.", + "type": "string" + } + }, + "required": [ + "name" + ] + } + }, + "isPartOf": { + "title": "Is part of", + "description": "Link to or citation for a related resource that this resource is a part of - e.g., a related collection.", + "type": "array", + "items": { + "title": "IsPartOf", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "Submission type can include various forms of content, such as datasets, software source code, digital documents, etc.", + "default": "CreativeWork", + "type": "string" + }, + "name": { + "title": "Name or title", + "description": "Submission's name or title", + "type": "string" + }, + "url": { + "title": "URL", + "description": "The URL address to the data resource.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "description": { + "title": "Description", + "description": "Information about a related resource that this resource is a part of - e.g., a related collection.", + "type": "string" + } + }, + "required": [ + "name" + ] + } + }, + "associatedMedia": { + "title": "Resource content", + "description": "A media object that encodes this CreativeWork. This property is a synonym for encoding.", + "type": "array", + "items": { + "title": "MediaObject", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "An item that encodes the record.", + "default": "MediaObject", + "type": "string" + }, + "contentUrl": { + "title": "Content URL", + "description": "The direct URL link to access or download the actual content of the media object.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "encodingFormat": { + "title": "Encoding format", + "description": "Represents the specific file format in which the media is encoded.", + "type": "string" + }, + "contentSize": { + "title": "Content size", + "description": "Represents the file size, expressed in bytes, kilobytes, megabytes, or another unit of measurement.", + "type": "string" + }, + "name": { + "title": "Name", + "description": "The name of the media object (file).", + "type": "string" + }, + "sha256": { + "title": "SHA-256", + "description": "The SHA-256 hash of the media object.", + "type": "string" + }, + "isPartOf": { + "title": "MediaObjectPartOf", + "description": "Link to or citation for a related metadata document that this media object is a part of", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "Submission type can include various forms of content, such as datasets, software source code, digital documents, etc.", + "default": "CreativeWork", + "type": "string" + }, + "name": { + "title": "Name or title", + "description": "Submission's name or title", + "type": "string" + }, + "url": { + "title": "URL", + "description": "The URL address to the related metadata document.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "description": { + "title": "Description", + "description": "Information about a related metadata document.", + "type": "string" + } + }, + "required": [ + "name" + ] + } + }, + "required": [ + "contentUrl", + "contentSize", + "name" + ] + } + }, + "citation": { + "title": "Citation", + "description": "A bibliographic citation for the resource.", + "type": "array", + "items": { + "type": "string" + } + }, + "additionalProperty": { + "title": "Additional properties", + "description": "Additional properties of the dataset/resource.", + "default": [], + "type": "array", + "items": { + "title": "PropertyValue", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A property-value pair.", + "default": "PropertyValue", + "const": "PropertyValue", + "type": "string" + }, + "propertyID": { + "title": "Property ID", + "description": "The ID of the property.", + "type": "string" + }, + "name": { + "title": "Name", + "description": "The name of the property.", + "type": "string" + }, + "value": { + "title": "Value", + "description": "The value of the property.", + "anyOf": [ + { + "type": "string" + }, + { + "title": "PropertyValue", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A property-value pair.", + "default": "PropertyValue", + "const": "PropertyValue", + "type": "string" + }, + "propertyID": { + "title": "Property ID", + "description": "The ID of the property.", + "type": "string" + }, + "name": { + "title": "Name", + "description": "The name of the property.", + "type": "string" + }, + "value": { + "title": "Value", + "description": "The value of the property.", + "type": "string" + }, + "unitCode": { + "title": "Measurement unit", + "description": "The unit of measurement for the value.", + "type": "string" + }, + "description": { + "title": "Description", + "description": "A description of the property.", + "type": "string" + }, + "minValue": { + "title": "Minimum value", + "description": "The minimum allowed value for the property.", + "type": "number" + }, + "maxValue": { + "title": "Maximum value", + "description": "The maximum allowed value for the property.", + "type": "number" + }, + "measurementTechnique": { + "title": "Measurement technique", + "description": "A technique or technology used in a measurement.", + "type": "string" + } + }, + "required": [ + "name", + "value" + ] + }, + { + "type": "array", + "items": { + "title": "PropertyValue", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A property-value pair.", + "default": "PropertyValue", + "const": "PropertyValue", + "type": "string" + }, + "propertyID": { + "title": "Property ID", + "description": "The ID of the property.", + "type": "string" + }, + "name": { + "title": "Name", + "description": "The name of the property.", + "type": "string" + }, + "value": { + "title": "Value", + "description": "The value of the property.", + "type": "string" + }, + "unitCode": { + "title": "Measurement unit", + "description": "The unit of measurement for the value.", + "type": "string" + }, + "description": { + "title": "Description", + "description": "A description of the property.", + "type": "string" + }, + "minValue": { + "title": "Minimum value", + "description": "The minimum allowed value for the property.", + "type": "number" + }, + "maxValue": { + "title": "Maximum value", + "description": "The maximum allowed value for the property.", + "type": "number" + }, + "measurementTechnique": { + "title": "Measurement technique", + "description": "A technique or technology used in a measurement.", + "type": "string" + } + }, + "required": [ + "name", + "value" + ] + } + } + ] + }, + "unitCode": { + "title": "Measurement unit", + "description": "The unit of measurement for the value.", + "type": "string" + }, + "description": { + "title": "Description", + "description": "A description of the property.", + "type": "string" + }, + "minValue": { + "title": "Minimum value", + "description": "The minimum allowed value for the property.", + "type": "number" + }, + "maxValue": { + "title": "Maximum value", + "description": "The maximum allowed value for the property.", + "type": "number" + }, + "measurementTechnique": { + "title": "Measurement technique", + "description": "A technique or technology used in a measurement.", + "type": "string" + } + }, + "required": [ + "name", + "value" + ] + } + }, + "temporalCoverage": { + "title": "TemporalCoverage", + "description": "The time period that applies to all of the content within the resource.", + "type": "object", + "properties": { + "startDate": { + "title": "Start date", + "description": "A date/time object containing the instant corresponding to the commencement of the time interval (ISO8601 formatted date - YYYY-MM-DDTHH:MM).", + "type": "string", + "format": "date-time" + }, + "endDate": { + "title": "End date", + "description": "A date/time object containing the instant corresponding to the termination of the time interval (ISO8601 formatted date - YYYY-MM-DDTHH:MM). If the ending date is left off, that means the temporal coverage is ongoing.", + "type": "string", + "format": "date-time" + } + }, + "required": [ + "startDate" + ] + }, + "spatialCoverage": { + "title": "Place", + "description": "The spatialCoverage of a CreativeWork indicates the place(s) which are the focus of the content. It is a sub property of contentLocation intended primarily for more technical and detailed materials. For example with a Dataset, it indicates areas that the dataset describes: a dataset of New York weather would have spatialCoverage which was the place: the state of New York.", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "Represents the focus area of the record's content.", + "default": "Place", + "type": "string" + }, + "name": { + "title": "Name", + "description": "Name of the place.", + "type": "string" + }, + "geo": { + "title": "Geo", + "description": "Specifies the geographic coordinates of the place in the form of a point location, line, or area coverage extent.", + "anyOf": [ + { + "title": "GeoCoordinates", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "Geographic coordinates that represent a specific location on the Earth's surface. GeoCoordinates typically consists of two components: latitude and longitude.", + "default": "GeoCoordinates", + "type": "string" + }, + "latitude": { + "title": "Latitude", + "description": "Represents the angular distance of a location north or south of the equator, measured in degrees and ranges from -90 to +90 degrees.", + "type": "number" + }, + "longitude": { + "title": "Longitude", + "description": "Represents the angular distance of a location east or west of the Prime Meridian, measured in degrees and ranges from -180 to +180 degrees.", + "type": "number" + } + }, + "required": [ + "latitude", + "longitude" + ] + }, + { + "title": "GeoShape", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A structured representation that describes the coordinates of a geographic feature.", + "default": "GeoShape", + "type": "string" + }, + "box": { + "title": "Box", + "description": "A box is a rectangular region defined by a pair of coordinates representing the southwest and northeast corners of the box.", + "type": "string" + } + }, + "required": [ + "box" + ] + } + ] + }, + "additionalProperty": { + "title": "Additional properties", + "description": "Additional properties of the place.", + "default": [], + "type": "array", + "items": { + "title": "PropertyValue", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A property-value pair.", + "default": "PropertyValue", + "const": "PropertyValue", + "type": "string" + }, + "propertyID": { + "title": "Property ID", + "description": "The ID of the property.", + "type": "string" + }, + "name": { + "title": "Name", + "description": "The name of the property.", + "type": "string" + }, + "value": { + "title": "Value", + "description": "The value of the property.", + "anyOf": [ + { + "type": "string" + }, + { + "title": "PropertyValue", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A property-value pair.", + "default": "PropertyValue", + "const": "PropertyValue", + "type": "string" + }, + "propertyID": { + "title": "Property ID", + "description": "The ID of the property.", + "type": "string" + }, + "name": { + "title": "Name", + "description": "The name of the property.", + "type": "string" + }, + "value": { + "title": "Value", + "description": "The value of the property.", + "type": "string" + }, + "unitCode": { + "title": "Measurement unit", + "description": "The unit of measurement for the value.", + "type": "string" + }, + "description": { + "title": "Description", + "description": "A description of the property.", + "type": "string" + }, + "minValue": { + "title": "Minimum value", + "description": "The minimum allowed value for the property.", + "type": "number" + }, + "maxValue": { + "title": "Maximum value", + "description": "The maximum allowed value for the property.", + "type": "number" + }, + "measurementTechnique": { + "title": "Measurement technique", + "description": "A technique or technology used in a measurement.", + "type": "string" + } + }, + "required": [ + "name", + "value" + ] + }, + { + "type": "array", + "items": { + "title": "PropertyValue", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A property-value pair.", + "default": "PropertyValue", + "const": "PropertyValue", + "type": "string" + }, + "propertyID": { + "title": "Property ID", + "description": "The ID of the property.", + "type": "string" + }, + "name": { + "title": "Name", + "description": "The name of the property.", + "type": "string" + }, + "value": { + "title": "Value", + "description": "The value of the property.", + "type": "string" + }, + "unitCode": { + "title": "Measurement unit", + "description": "The unit of measurement for the value.", + "type": "string" + }, + "description": { + "title": "Description", + "description": "A description of the property.", + "type": "string" + }, + "minValue": { + "title": "Minimum value", + "description": "The minimum allowed value for the property.", + "type": "number" + }, + "maxValue": { + "title": "Maximum value", + "description": "The maximum allowed value for the property.", + "type": "number" + }, + "measurementTechnique": { + "title": "Measurement technique", + "description": "A technique or technology used in a measurement.", + "type": "string" + } + }, + "required": [ + "name", + "value" + ] + } + } + ] + }, + "unitCode": { + "title": "Measurement unit", + "description": "The unit of measurement for the value.", + "type": "string" + }, + "description": { + "title": "Description", + "description": "A description of the property.", + "type": "string" + }, + "minValue": { + "title": "Minimum value", + "description": "The minimum allowed value for the property.", + "type": "number" + }, + "maxValue": { + "title": "Maximum value", + "description": "The maximum allowed value for the property.", + "type": "number" + }, + "measurementTechnique": { + "title": "Measurement technique", + "description": "A technique or technology used in a measurement.", + "type": "string" + } + }, + "required": [ + "name", + "value" + ] + } + } + } + } + }, + "required": [ + "name", + "description", + "url", + "identifier", + "creator", + "dateCreated", + "keywords", + "license", + "provider", + "inLanguage", + "dateModified" + ], + "definitions": { + "Affiliation": { + "title": "Affiliation", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "Organization", + "const": "Organization", + "type": "string" + }, + "name": { + "title": "Name", + "description": "Name of the organization the creator is affiliated with.", + "type": "string" + }, + "url": { + "title": "URL", + "description": "A URL to the homepage for the organization.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "address": { + "title": "Address", + "description": "Full address for the organization - e.g., \u201c8200 Old Main Hill, Logan, UT 84322-8200\u201d.", + "type": "string" + } + }, + "required": [ + "name" + ] + }, + "Creator": { + "title": "Creator", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A person.", + "default": "Person", + "const": "Person", + "type": "string" + }, + "name": { + "title": "Name", + "description": "A string containing the full name of the person. Personal name format: Family Name, Given Name.", + "type": "string" + }, + "email": { + "title": "Email", + "description": "A string containing an email address for the creator.", + "type": "string", + "format": "email" + }, + "identifier": { + "title": "Identifier", + "description": "ORCID identifier for creator.", + "pattern": "\\b\\d{4}-\\d{4}-\\d{4}-\\d{3}[0-9X]\\b", + "options": { + "placeholder": "e.g. '0000-0001-2345-6789'" + }, + "errorMessage": { + "pattern": "must match the ORCID pattern. e.g. '0000-0001-2345-6789'" + }, + "type": "string" + }, + "affiliation": { + "title": "Affiliation", + "description": "The affiliation of the creator with the organization.", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "Organization", + "const": "Organization", + "type": "string" + }, + "name": { + "title": "Name", + "description": "Name of the organization the creator is affiliated with.", + "type": "string" + }, + "url": { + "title": "URL", + "description": "A URL to the homepage for the organization.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "address": { + "title": "Address", + "description": "Full address for the organization - e.g., \u201c8200 Old Main Hill, Logan, UT 84322-8200\u201d.", + "type": "string" + } + }, + "required": [ + "name" + ] + } + }, + "required": [ + "name" + ] + }, + "Organization": { + "title": "Organization", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "Organization", + "const": "Organization", + "type": "string" + }, + "name": { + "title": "Name", + "description": "Name of the provider organization or repository.", + "type": "string" + }, + "url": { + "title": "URL", + "description": "A URL to the homepage for the organization.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "address": { + "title": "Address", + "description": "Full address for the organization - e.g., \u201c8200 Old Main Hill, Logan, UT 84322-8200\u201d.", + "type": "string" + } + }, + "required": [ + "name" + ] + }, + "License": { + "title": "License", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "Submission type can include various forms of content, such as datasets, software source code, digital documents, etc.", + "default": "CreativeWork", + "type": "string" + }, + "name": { + "title": "Name", + "description": "A text string indicating the name of the license under which the resource is shared.", + "type": "string" + }, + "url": { + "title": "URL", + "description": "A URL for a web page that describes the license.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "description": { + "title": "Description", + "description": "A text string describing the license or containing the text of the license itself.", + "type": "string" + } + }, + "required": [ + "name" + ] + }, + "Provider": { + "title": "Provider", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A person.", + "default": "Person", + "const": "Person", + "type": "string" + }, + "name": { + "title": "Name", + "description": "A string containing the full name of the person. Personal name format: Family Name, Given Name.", + "type": "string" + }, + "email": { + "title": "Email", + "description": "A string containing an email address for the provider.", + "type": "string", + "format": "email" + }, + "identifier": { + "title": "Identifier", + "description": "ORCID identifier for the person.", + "pattern": "\\b\\d{4}-\\d{4}-\\d{4}-\\d{3}[0-9X]\\b", + "options": { + "placeholder": "e.g. '0000-0001-2345-6789'" + }, + "errorMessage": { + "pattern": "must match the ORCID pattern. e.g. '0000-0001-2345-6789'" + }, + "type": "string" + }, + "affiliation": { + "title": "Affiliation", + "description": "The affiliation of the creator with the organization.", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "Organization", + "const": "Organization", + "type": "string" + }, + "name": { + "title": "Name", + "description": "Name of the organization the creator is affiliated with.", + "type": "string" + }, + "url": { + "title": "URL", + "description": "A URL to the homepage for the organization.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "address": { + "title": "Address", + "description": "Full address for the organization - e.g., \u201c8200 Old Main Hill, Logan, UT 84322-8200\u201d.", + "type": "string" + } + }, + "required": [ + "name" + ] + } + }, + "required": [ + "name" + ] + }, + "PublisherOrganization": { + "title": "PublisherOrganization", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "Organization", + "const": "Organization", + "type": "string" + }, + "name": { + "title": "Name", + "description": "Name of the publishing organization.", + "type": "string" + }, + "url": { + "title": "URL", + "description": "A URL to the homepage for the publisher organization or repository.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "address": { + "title": "Address", + "description": "Full address for the organization - e.g., \u201c8200 Old Main Hill, Logan, UT 84322-8200\u201d.", + "type": "string" + } + }, + "required": [ + "name" + ] + }, + "SubjectOf": { + "title": "SubjectOf", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "Submission type can include various forms of content, such as datasets, software source code, digital documents, etc.", + "default": "CreativeWork", + "type": "string" + }, + "name": { + "title": "Name or title", + "description": "Submission's name or title", + "type": "string" + }, + "url": { + "title": "URL", + "description": "The URL address that serves as a reference to access additional details related to the record. It is important to note that this type of metadata solely pertains to the record itself and may not necessarily be an integral component of the record, unlike the HasPart metadata.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "description": { + "title": "Description", + "description": "Information about a related resource that is about or describes this resource - e.g., a related metadata document describing the resource.", + "type": "string" + } + }, + "required": [ + "name" + ] + }, + "LanguageEnum": { + "title": "Language", + "description": "", + "enum": [ + "eng", + "esp" + ], + "type": "string" + }, + "Draft": { + "title": "Draft", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "DefinedTerm", + "type": "string" + }, + "name": { + "title": "Name", + "default": "Draft", + "type": "string" + }, + "description": { + "title": "Description", + "description": "The description of the item being defined.", + "default": "The resource is in draft state and should not be considered final. Content and metadata may change", + "readOnly": true, + "type": "string" + } + } + }, + "Incomplete": { + "title": "Incomplete", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "DefinedTerm", + "type": "string" + }, + "name": { + "title": "Name", + "default": "Incomplete", + "type": "string" + }, + "description": { + "title": "Description", + "description": "The description of the item being defined.", + "default": "Data collection is ongoing or the resource is not completed", + "readOnly": true, + "type": "string" + } + } + }, + "Obsolete": { + "title": "Obsolete", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "DefinedTerm", + "type": "string" + }, + "name": { + "title": "Name", + "default": "Obsolete", + "type": "string" + }, + "description": { + "title": "Description", + "description": "The description of the item being defined.", + "default": "The resource has been replaced by a newer version, or the resource is no longer considered applicable", + "readOnly": true, + "type": "string" + } + } + }, + "Published": { + "title": "Published", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "DefinedTerm", + "type": "string" + }, + "name": { + "title": "Name", + "default": "Published", + "type": "string" + }, + "description": { + "title": "Description", + "description": "The description of the item being defined.", + "default": "The resource has been permanently published and should be considered final and complete", + "readOnly": true, + "type": "string" + } + } + }, + "Grant": { + "title": "Grant", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "This metadata represents details about a grant or financial assistance provided to an individual(s) or organization(s) for supporting the work related to the record.", + "default": "MonetaryGrant", + "type": "string" + }, + "name": { + "title": "Name or title", + "description": "A text string indicating the name or title of the grant or financial assistance.", + "type": "string" + }, + "description": { + "title": "Description", + "description": "A text string describing the grant or financial assistance.", + "type": "string" + }, + "identifier": { + "title": "Funding identifier", + "description": "Grant award number or other identifier.", + "type": "string" + }, + "funder": { + "title": "Funding Organization", + "description": "The organization that provided the funding or sponsorship.", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "Organization", + "const": "Organization", + "type": "string" + }, + "name": { + "title": "Name", + "description": "Name of the organization.", + "type": "string" + }, + "url": { + "title": "URL", + "description": "A URL to the homepage for the organization.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "address": { + "title": "Address", + "description": "Full address for the organization - e.g., \u201c8200 Old Main Hill, Logan, UT 84322-8200\u201d.", + "type": "string" + } + }, + "required": [ + "name" + ] + } + }, + "required": [ + "name" + ] + }, + "HasPart": { + "title": "HasPart", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "Submission type can include various forms of content, such as datasets, software source code, digital documents, etc.", + "default": "CreativeWork", + "type": "string" + }, + "name": { + "title": "Name or title", + "description": "Submission's name or title", + "type": "string" + }, + "url": { + "title": "URL", + "description": "The URL address to the data resource.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "description": { + "title": "Description", + "description": "Information about a related resource that is part of this resource.", + "type": "string" + } + }, + "required": [ + "name" + ] + }, + "IsPartOf": { + "title": "IsPartOf", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "Submission type can include various forms of content, such as datasets, software source code, digital documents, etc.", + "default": "CreativeWork", + "type": "string" + }, + "name": { + "title": "Name or title", + "description": "Submission's name or title", + "type": "string" + }, + "url": { + "title": "URL", + "description": "The URL address to the data resource.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "description": { + "title": "Description", + "description": "Information about a related resource that this resource is a part of - e.g., a related collection.", + "type": "string" + } + }, + "required": [ + "name" + ] + }, + "MediaObjectPartOf": { + "title": "MediaObjectPartOf", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "Submission type can include various forms of content, such as datasets, software source code, digital documents, etc.", + "default": "CreativeWork", + "type": "string" + }, + "name": { + "title": "Name or title", + "description": "Submission's name or title", + "type": "string" + }, + "url": { + "title": "URL", + "description": "The URL address to the related metadata document.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "description": { + "title": "Description", + "description": "Information about a related metadata document.", + "type": "string" + } + }, + "required": [ + "name" + ] + }, + "MediaObject": { + "title": "MediaObject", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "An item that encodes the record.", + "default": "MediaObject", + "type": "string" + }, + "contentUrl": { + "title": "Content URL", + "description": "The direct URL link to access or download the actual content of the media object.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "encodingFormat": { + "title": "Encoding format", + "description": "Represents the specific file format in which the media is encoded.", + "type": "string" + }, + "contentSize": { + "title": "Content size", + "description": "Represents the file size, expressed in bytes, kilobytes, megabytes, or another unit of measurement.", + "type": "string" + }, + "name": { + "title": "Name", + "description": "The name of the media object (file).", + "type": "string" + }, + "sha256": { + "title": "SHA-256", + "description": "The SHA-256 hash of the media object.", + "type": "string" + }, + "isPartOf": { + "title": "MediaObjectPartOf", + "description": "Link to or citation for a related metadata document that this media object is a part of", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "Submission type can include various forms of content, such as datasets, software source code, digital documents, etc.", + "default": "CreativeWork", + "type": "string" + }, + "name": { + "title": "Name or title", + "description": "Submission's name or title", + "type": "string" + }, + "url": { + "title": "URL", + "description": "The URL address to the related metadata document.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "description": { + "title": "Description", + "description": "Information about a related metadata document.", + "type": "string" + } + }, + "required": [ + "name" + ] + } + }, + "required": [ + "contentUrl", + "contentSize", + "name" + ] + }, + "PropertyValueBase": { + "title": "PropertyValue", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A property-value pair.", + "default": "PropertyValue", + "const": "PropertyValue", + "type": "string" + }, + "propertyID": { + "title": "Property ID", + "description": "The ID of the property.", + "type": "string" + }, + "name": { + "title": "Name", + "description": "The name of the property.", + "type": "string" + }, + "value": { + "title": "Value", + "description": "The value of the property.", + "type": "string" + }, + "unitCode": { + "title": "Measurement unit", + "description": "The unit of measurement for the value.", + "type": "string" + }, + "description": { + "title": "Description", + "description": "A description of the property.", + "type": "string" + }, + "minValue": { + "title": "Minimum value", + "description": "The minimum allowed value for the property.", + "type": "number" + }, + "maxValue": { + "title": "Maximum value", + "description": "The maximum allowed value for the property.", + "type": "number" + }, + "measurementTechnique": { + "title": "Measurement technique", + "description": "A technique or technology used in a measurement.", + "type": "string" + } + }, + "required": [ + "name", + "value" + ] + }, + "PropertyValue": { + "title": "PropertyValue", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A property-value pair.", + "default": "PropertyValue", + "const": "PropertyValue", + "type": "string" + }, + "propertyID": { + "title": "Property ID", + "description": "The ID of the property.", + "type": "string" + }, + "name": { + "title": "Name", + "description": "The name of the property.", + "type": "string" + }, + "value": { + "title": "Value", + "description": "The value of the property.", + "anyOf": [ + { + "type": "string" + }, + { + "title": "PropertyValue", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A property-value pair.", + "default": "PropertyValue", + "const": "PropertyValue", + "type": "string" + }, + "propertyID": { + "title": "Property ID", + "description": "The ID of the property.", + "type": "string" + }, + "name": { + "title": "Name", + "description": "The name of the property.", + "type": "string" + }, + "value": { + "title": "Value", + "description": "The value of the property.", + "type": "string" + }, + "unitCode": { + "title": "Measurement unit", + "description": "The unit of measurement for the value.", + "type": "string" + }, + "description": { + "title": "Description", + "description": "A description of the property.", + "type": "string" + }, + "minValue": { + "title": "Minimum value", + "description": "The minimum allowed value for the property.", + "type": "number" + }, + "maxValue": { + "title": "Maximum value", + "description": "The maximum allowed value for the property.", + "type": "number" + }, + "measurementTechnique": { + "title": "Measurement technique", + "description": "A technique or technology used in a measurement.", + "type": "string" + } + }, + "required": [ + "name", + "value" + ] + }, + { + "type": "array", + "items": { + "title": "PropertyValue", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A property-value pair.", + "default": "PropertyValue", + "const": "PropertyValue", + "type": "string" + }, + "propertyID": { + "title": "Property ID", + "description": "The ID of the property.", + "type": "string" + }, + "name": { + "title": "Name", + "description": "The name of the property.", + "type": "string" + }, + "value": { + "title": "Value", + "description": "The value of the property.", + "type": "string" + }, + "unitCode": { + "title": "Measurement unit", + "description": "The unit of measurement for the value.", + "type": "string" + }, + "description": { + "title": "Description", + "description": "A description of the property.", + "type": "string" + }, + "minValue": { + "title": "Minimum value", + "description": "The minimum allowed value for the property.", + "type": "number" + }, + "maxValue": { + "title": "Maximum value", + "description": "The maximum allowed value for the property.", + "type": "number" + }, + "measurementTechnique": { + "title": "Measurement technique", + "description": "A technique or technology used in a measurement.", + "type": "string" + } + }, + "required": [ + "name", + "value" + ] + } + } + ] + }, + "unitCode": { + "title": "Measurement unit", + "description": "The unit of measurement for the value.", + "type": "string" + }, + "description": { + "title": "Description", + "description": "A description of the property.", + "type": "string" + }, + "minValue": { + "title": "Minimum value", + "description": "The minimum allowed value for the property.", + "type": "number" + }, + "maxValue": { + "title": "Maximum value", + "description": "The maximum allowed value for the property.", + "type": "number" + }, + "measurementTechnique": { + "title": "Measurement technique", + "description": "A technique or technology used in a measurement.", + "type": "string" + } + }, + "required": [ + "name", + "value" + ] + }, + "TemporalCoverage": { + "title": "TemporalCoverage", + "type": "object", + "properties": { + "startDate": { + "title": "Start date", + "description": "A date/time object containing the instant corresponding to the commencement of the time interval (ISO8601 formatted date - YYYY-MM-DDTHH:MM).", + "type": "string", + "format": "date-time" + }, + "endDate": { + "title": "End date", + "description": "A date/time object containing the instant corresponding to the termination of the time interval (ISO8601 formatted date - YYYY-MM-DDTHH:MM). If the ending date is left off, that means the temporal coverage is ongoing.", + "type": "string", + "format": "date-time" + } + }, + "required": [ + "startDate" + ] + }, + "GeoCoordinates": { + "title": "GeoCoordinates", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "Geographic coordinates that represent a specific location on the Earth's surface. GeoCoordinates typically consists of two components: latitude and longitude.", + "default": "GeoCoordinates", + "type": "string" + }, + "latitude": { + "title": "Latitude", + "description": "Represents the angular distance of a location north or south of the equator, measured in degrees and ranges from -90 to +90 degrees.", + "type": "number" + }, + "longitude": { + "title": "Longitude", + "description": "Represents the angular distance of a location east or west of the Prime Meridian, measured in degrees and ranges from -180 to +180 degrees.", + "type": "number" + } + }, + "required": [ + "latitude", + "longitude" + ] + }, + "GeoShape": { + "title": "GeoShape", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A structured representation that describes the coordinates of a geographic feature.", + "default": "GeoShape", + "type": "string" + }, + "box": { + "title": "Box", + "description": "A box is a rectangular region defined by a pair of coordinates representing the southwest and northeast corners of the box.", + "type": "string" + } + }, + "required": [ + "box" + ] + }, + "Place": { + "title": "Place", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "Represents the focus area of the record's content.", + "default": "Place", + "type": "string" + }, + "name": { + "title": "Name", + "description": "Name of the place.", + "type": "string" + }, + "geo": { + "title": "Geo", + "description": "Specifies the geographic coordinates of the place in the form of a point location, line, or area coverage extent.", + "anyOf": [ + { + "title": "GeoCoordinates", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "Geographic coordinates that represent a specific location on the Earth's surface. GeoCoordinates typically consists of two components: latitude and longitude.", + "default": "GeoCoordinates", + "type": "string" + }, + "latitude": { + "title": "Latitude", + "description": "Represents the angular distance of a location north or south of the equator, measured in degrees and ranges from -90 to +90 degrees.", + "type": "number" + }, + "longitude": { + "title": "Longitude", + "description": "Represents the angular distance of a location east or west of the Prime Meridian, measured in degrees and ranges from -180 to +180 degrees.", + "type": "number" + } + }, + "required": [ + "latitude", + "longitude" + ] + }, + { + "title": "GeoShape", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A structured representation that describes the coordinates of a geographic feature.", + "default": "GeoShape", + "type": "string" + }, + "box": { + "title": "Box", + "description": "A box is a rectangular region defined by a pair of coordinates representing the southwest and northeast corners of the box.", + "type": "string" + } + }, + "required": [ + "box" + ] + } + ] + }, + "additionalProperty": { + "title": "Additional properties", + "description": "Additional properties of the place.", + "default": [], + "type": "array", + "items": { + "title": "PropertyValue", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A property-value pair.", + "default": "PropertyValue", + "const": "PropertyValue", + "type": "string" + }, + "propertyID": { + "title": "Property ID", + "description": "The ID of the property.", + "type": "string" + }, + "name": { + "title": "Name", + "description": "The name of the property.", + "type": "string" + }, + "value": { + "title": "Value", + "description": "The value of the property.", + "anyOf": [ + { + "type": "string" + }, + { + "title": "PropertyValue", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A property-value pair.", + "default": "PropertyValue", + "const": "PropertyValue", + "type": "string" + }, + "propertyID": { + "title": "Property ID", + "description": "The ID of the property.", + "type": "string" + }, + "name": { + "title": "Name", + "description": "The name of the property.", + "type": "string" + }, + "value": { + "title": "Value", + "description": "The value of the property.", + "type": "string" + }, + "unitCode": { + "title": "Measurement unit", + "description": "The unit of measurement for the value.", + "type": "string" + }, + "description": { + "title": "Description", + "description": "A description of the property.", + "type": "string" + }, + "minValue": { + "title": "Minimum value", + "description": "The minimum allowed value for the property.", + "type": "number" + }, + "maxValue": { + "title": "Maximum value", + "description": "The maximum allowed value for the property.", + "type": "number" + }, + "measurementTechnique": { + "title": "Measurement technique", + "description": "A technique or technology used in a measurement.", + "type": "string" + } + }, + "required": [ + "name", + "value" + ] + }, + { + "type": "array", + "items": { + "title": "PropertyValue", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A property-value pair.", + "default": "PropertyValue", + "const": "PropertyValue", + "type": "string" + }, + "propertyID": { + "title": "Property ID", + "description": "The ID of the property.", + "type": "string" + }, + "name": { + "title": "Name", + "description": "The name of the property.", + "type": "string" + }, + "value": { + "title": "Value", + "description": "The value of the property.", + "type": "string" + }, + "unitCode": { + "title": "Measurement unit", + "description": "The unit of measurement for the value.", + "type": "string" + }, + "description": { + "title": "Description", + "description": "A description of the property.", + "type": "string" + }, + "minValue": { + "title": "Minimum value", + "description": "The minimum allowed value for the property.", + "type": "number" + }, + "maxValue": { + "title": "Maximum value", + "description": "The maximum allowed value for the property.", + "type": "number" + }, + "measurementTechnique": { + "title": "Measurement technique", + "description": "A technique or technology used in a measurement.", + "type": "string" + } + }, + "required": [ + "name", + "value" + ] + } + } + ] + }, + "unitCode": { + "title": "Measurement unit", + "description": "The unit of measurement for the value.", + "type": "string" + }, + "description": { + "title": "Description", + "description": "A description of the property.", + "type": "string" + }, + "minValue": { + "title": "Minimum value", + "description": "The minimum allowed value for the property.", + "type": "number" + }, + "maxValue": { + "title": "Maximum value", + "description": "The maximum allowed value for the property.", + "type": "number" + }, + "measurementTechnique": { + "title": "Measurement technique", + "description": "A technique or technology used in a measurement.", + "type": "string" + } + }, + "required": [ + "name", + "value" + ] + } + } + } + } + } +} \ No newline at end of file diff --git a/api/models/schemas/netcdf/__init__.py b/api/models/schemas/netcdf/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/api/models/schemas/netcdf/schema.json b/api/models/schemas/netcdf/schema.json new file mode 100644 index 0000000..9d9a0e4 --- /dev/null +++ b/api/models/schemas/netcdf/schema.json @@ -0,0 +1,2802 @@ +{ + "title": "HSNetCDFMetadata", + "type": "object", + "properties": { + "@context": { + "title": "@Context", + "description": "Specifies the vocabulary employed for understanding the structured data markup.", + "default": "https://schema.org", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "@type": { + "title": "Submission type", + "description": "Type of submission", + "default": "Dataset", + "const": "Dataset", + "type": "string" + }, + "name": { + "title": "Name or title", + "description": "A text string with a descriptive name or title for the resource.", + "type": "string" + }, + "description": { + "title": "Description or abstract", + "description": "A text string containing a description/abstract for the resource.", + "type": "string" + }, + "url": { + "title": "URL", + "description": "A URL for the landing page that describes the resource and where the content of the resource can be accessed. If there is no landing page, provide the URL of the content.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "identifier": { + "title": "Identifiers", + "description": "Any kind of identifier for the resource/dataset. Identifiers may be DOIs or unique strings assigned by a repository. Multiple identifiers can be entered. Where identifiers can be encoded as URLs, enter URLs here.", + "type": "array", + "items": { + "type": "string", + "title": "Identifier" + } + }, + "creator": { + "title": "Creator", + "description": "Person or Organization that created the resource.", + "type": "array", + "items": { + "anyOf": [ + { + "title": "Creator", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A person.", + "default": "Person", + "const": "Person", + "type": "string" + }, + "name": { + "title": "Name", + "description": "A string containing the full name of the person. Personal name format: Family Name, Given Name.", + "type": "string" + }, + "email": { + "title": "Email", + "description": "A string containing an email address for the creator.", + "type": "string", + "format": "email" + }, + "identifier": { + "title": "Identifier", + "description": "ORCID identifier for creator.", + "pattern": "\\b\\d{4}-\\d{4}-\\d{4}-\\d{3}[0-9X]\\b", + "options": { + "placeholder": "e.g. '0000-0001-2345-6789'" + }, + "errorMessage": { + "pattern": "must match the ORCID pattern. e.g. '0000-0001-2345-6789'" + }, + "type": "string" + }, + "affiliation": { + "title": "Affiliation", + "description": "The affiliation of the creator with the organization.", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "Organization", + "const": "Organization", + "type": "string" + }, + "name": { + "title": "Name", + "description": "Name of the organization the creator is affiliated with.", + "type": "string" + }, + "url": { + "title": "URL", + "description": "A URL to the homepage for the organization.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "address": { + "title": "Address", + "description": "Full address for the organization - e.g., \u201c8200 Old Main Hill, Logan, UT 84322-8200\u201d.", + "type": "string" + } + }, + "required": [ + "name" + ] + } + }, + "required": [ + "name" + ] + }, + { + "title": "Organization", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "Organization", + "const": "Organization", + "type": "string" + }, + "name": { + "title": "Name", + "description": "Name of the provider organization or repository.", + "type": "string" + }, + "url": { + "title": "URL", + "description": "A URL to the homepage for the organization.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "address": { + "title": "Address", + "description": "Full address for the organization - e.g., \u201c8200 Old Main Hill, Logan, UT 84322-8200\u201d.", + "type": "string" + } + }, + "required": [ + "name" + ] + } + ] + } + }, + "dateCreated": { + "title": "Date created", + "description": "The date on which the resource was created.", + "type": "string", + "format": "date-time" + }, + "keywords": { + "title": "Keywords", + "description": "Keywords or tags used to describe the dataset, delimited by commas.", + "minItems": 1, + "type": "array", + "items": { + "type": "string" + } + }, + "license": { + "title": "License", + "description": "A license document that applies to the resource.", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "Submission type can include various forms of content, such as datasets, software source code, digital documents, etc.", + "default": "CreativeWork", + "type": "string" + }, + "name": { + "title": "Name", + "description": "A text string indicating the name of the license under which the resource is shared.", + "type": "string" + }, + "url": { + "title": "URL", + "description": "A URL for a web page that describes the license.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "description": { + "title": "Description", + "description": "A text string describing the license or containing the text of the license itself.", + "type": "string" + } + }, + "required": [ + "name" + ] + }, + "provider": { + "title": "Provider", + "description": "The repository, service provider, organization, person, or service performer that provides access to the resource.", + "anyOf": [ + { + "title": "Organization", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "Organization", + "const": "Organization", + "type": "string" + }, + "name": { + "title": "Name", + "description": "Name of the provider organization or repository.", + "type": "string" + }, + "url": { + "title": "URL", + "description": "A URL to the homepage for the organization.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "address": { + "title": "Address", + "description": "Full address for the organization - e.g., \u201c8200 Old Main Hill, Logan, UT 84322-8200\u201d.", + "type": "string" + } + }, + "required": [ + "name" + ] + }, + { + "title": "Provider", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A person.", + "default": "Person", + "const": "Person", + "type": "string" + }, + "name": { + "title": "Name", + "description": "A string containing the full name of the person. Personal name format: Family Name, Given Name.", + "type": "string" + }, + "email": { + "title": "Email", + "description": "A string containing an email address for the provider.", + "type": "string", + "format": "email" + }, + "identifier": { + "title": "Identifier", + "description": "ORCID identifier for the person.", + "pattern": "\\b\\d{4}-\\d{4}-\\d{4}-\\d{3}[0-9X]\\b", + "options": { + "placeholder": "e.g. '0000-0001-2345-6789'" + }, + "errorMessage": { + "pattern": "must match the ORCID pattern. e.g. '0000-0001-2345-6789'" + }, + "type": "string" + }, + "affiliation": { + "title": "Affiliation", + "description": "The affiliation of the creator with the organization.", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "Organization", + "const": "Organization", + "type": "string" + }, + "name": { + "title": "Name", + "description": "Name of the organization the creator is affiliated with.", + "type": "string" + }, + "url": { + "title": "URL", + "description": "A URL to the homepage for the organization.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "address": { + "title": "Address", + "description": "Full address for the organization - e.g., \u201c8200 Old Main Hill, Logan, UT 84322-8200\u201d.", + "type": "string" + } + }, + "required": [ + "name" + ] + } + }, + "required": [ + "name" + ] + } + ] + }, + "publisher": { + "title": "PublisherOrganization", + "description": "Where the resource is permanently published, indicated the repository, service provider, or organization that published the resource - e.g., CUAHSI HydroShare. This may be the same as Provider.", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "Organization", + "const": "Organization", + "type": "string" + }, + "name": { + "title": "Name", + "description": "Name of the publishing organization.", + "type": "string" + }, + "url": { + "title": "URL", + "description": "A URL to the homepage for the publisher organization or repository.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "address": { + "title": "Address", + "description": "Full address for the organization - e.g., \u201c8200 Old Main Hill, Logan, UT 84322-8200\u201d.", + "type": "string" + } + }, + "required": [ + "name" + ] + }, + "datePublished": { + "title": "Date published", + "description": "Date of first publication for the resource.", + "type": "string", + "format": "date-time" + }, + "subjectOf": { + "title": "Subject of", + "description": "Link to or citation for a related resource that is about or describes this resource - e.g., a journal paper that describes this resource or a related metadata document describing the resource.", + "type": "array", + "items": { + "title": "SubjectOf", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "Submission type can include various forms of content, such as datasets, software source code, digital documents, etc.", + "default": "CreativeWork", + "type": "string" + }, + "name": { + "title": "Name or title", + "description": "Submission's name or title", + "type": "string" + }, + "url": { + "title": "URL", + "description": "The URL address that serves as a reference to access additional details related to the record. It is important to note that this type of metadata solely pertains to the record itself and may not necessarily be an integral component of the record, unlike the HasPart metadata.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "description": { + "title": "Description", + "description": "Information about a related resource that is about or describes this resource - e.g., a related metadata document describing the resource.", + "type": "string" + } + }, + "required": [ + "name" + ] + } + }, + "version": { + "title": "Version", + "description": "A text string indicating the version of the resource.", + "type": "string" + }, + "inLanguage": { + "title": "Language", + "description": "The language of the content of the resource.", + "anyOf": [ + { + "title": "Language", + "description": "", + "enum": [ + "eng", + "esp" + ], + "type": "string" + }, + { + "type": "string", + "title": "Other", + "description": "Please specify another language." + } + ] + }, + "creativeWorkStatus": { + "title": "Resource status", + "description": "The status of this resource in terms of its stage in a lifecycle. Example terms include Incomplete, Draft, Published, and Obsolete.", + "anyOf": [ + { + "title": "Draft", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "DefinedTerm", + "type": "string" + }, + "name": { + "title": "Name", + "default": "Draft", + "type": "string" + }, + "description": { + "title": "Description", + "description": "The description of the item being defined.", + "default": "The resource is in draft state and should not be considered final. Content and metadata may change", + "readOnly": true, + "type": "string" + } + } + }, + { + "title": "Incomplete", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "DefinedTerm", + "type": "string" + }, + "name": { + "title": "Name", + "default": "Incomplete", + "type": "string" + }, + "description": { + "title": "Description", + "description": "The description of the item being defined.", + "default": "Data collection is ongoing or the resource is not completed", + "readOnly": true, + "type": "string" + } + } + }, + { + "title": "Obsolete", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "DefinedTerm", + "type": "string" + }, + "name": { + "title": "Name", + "default": "Obsolete", + "type": "string" + }, + "description": { + "title": "Description", + "description": "The description of the item being defined.", + "default": "The resource has been replaced by a newer version, or the resource is no longer considered applicable", + "readOnly": true, + "type": "string" + } + } + }, + { + "title": "Published", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "DefinedTerm", + "type": "string" + }, + "name": { + "title": "Name", + "default": "Published", + "type": "string" + }, + "description": { + "title": "Description", + "description": "The description of the item being defined.", + "default": "The resource has been permanently published and should be considered final and complete", + "readOnly": true, + "type": "string" + } + } + } + ] + }, + "dateModified": { + "title": "Date modified", + "description": "The date on which the resource was most recently modified or updated.", + "type": "string", + "format": "date-time" + }, + "funding": { + "title": "Funding", + "description": "A Grant or monetary assistance that directly or indirectly provided funding or sponsorship for creation of the resource.", + "type": "array", + "items": { + "title": "Grant", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "This metadata represents details about a grant or financial assistance provided to an individual(s) or organization(s) for supporting the work related to the record.", + "default": "MonetaryGrant", + "type": "string" + }, + "name": { + "title": "Name or title", + "description": "A text string indicating the name or title of the grant or financial assistance.", + "type": "string" + }, + "description": { + "title": "Description", + "description": "A text string describing the grant or financial assistance.", + "type": "string" + }, + "identifier": { + "title": "Funding identifier", + "description": "Grant award number or other identifier.", + "type": "string" + }, + "funder": { + "title": "Funding Organization", + "description": "The organization that provided the funding or sponsorship.", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "Organization", + "const": "Organization", + "type": "string" + }, + "name": { + "title": "Name", + "description": "Name of the organization.", + "type": "string" + }, + "url": { + "title": "URL", + "description": "A URL to the homepage for the organization.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "address": { + "title": "Address", + "description": "Full address for the organization - e.g., \u201c8200 Old Main Hill, Logan, UT 84322-8200\u201d.", + "type": "string" + } + }, + "required": [ + "name" + ] + } + }, + "required": [ + "name" + ] + } + }, + "hasPart": { + "title": "Has part", + "description": "Link to or citation for a related resource that is part of this resource.", + "type": "array", + "items": { + "title": "HasPart", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "Submission type can include various forms of content, such as datasets, software source code, digital documents, etc.", + "default": "CreativeWork", + "type": "string" + }, + "name": { + "title": "Name or title", + "description": "Submission's name or title", + "type": "string" + }, + "url": { + "title": "URL", + "description": "The URL address to the data resource.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "description": { + "title": "Description", + "description": "Information about a related resource that is part of this resource.", + "type": "string" + } + }, + "required": [ + "name" + ] + } + }, + "isPartOf": { + "title": "Is part of", + "description": "Link to or citation for a related resource that this resource is a part of - e.g., a related collection.", + "type": "array", + "items": { + "title": "IsPartOf", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "Submission type can include various forms of content, such as datasets, software source code, digital documents, etc.", + "default": "CreativeWork", + "type": "string" + }, + "name": { + "title": "Name or title", + "description": "Submission's name or title", + "type": "string" + }, + "url": { + "title": "URL", + "description": "The URL address to the data resource.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "description": { + "title": "Description", + "description": "Information about a related resource that this resource is a part of - e.g., a related collection.", + "type": "string" + } + }, + "required": [ + "name" + ] + } + }, + "associatedMedia": { + "title": "Dataset content", + "description": "A media object that encodes this CreativeWork. This property is a synonym for encoding.", + "type": "array", + "items": { + "title": "MediaObject", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "An item that encodes the record.", + "default": "MediaObject", + "type": "string" + }, + "contentUrl": { + "title": "Content URL", + "description": "The direct URL link to access or download the actual content of the media object.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "encodingFormat": { + "title": "Encoding format", + "description": "Represents the specific file format in which the media is encoded.", + "type": "string" + }, + "contentSize": { + "title": "Content size", + "description": "Represents the file size, expressed in bytes, kilobytes, megabytes, or another unit of measurement.", + "type": "string" + }, + "name": { + "title": "Name", + "description": "The name of the media object (file).", + "type": "string" + }, + "sha256": { + "title": "SHA-256", + "description": "The SHA-256 hash of the media object.", + "type": "string" + }, + "isPartOf": { + "title": "MediaObjectPartOf", + "description": "Link to or citation for a related metadata document that this media object is a part of", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "Submission type can include various forms of content, such as datasets, software source code, digital documents, etc.", + "default": "CreativeWork", + "type": "string" + }, + "name": { + "title": "Name or title", + "description": "Submission's name or title", + "type": "string" + }, + "url": { + "title": "URL", + "description": "The URL address to the related metadata document.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "description": { + "title": "Description", + "description": "Information about a related metadata document.", + "type": "string" + } + }, + "required": [ + "name" + ] + } + }, + "required": [ + "contentUrl", + "contentSize", + "name" + ] + } + }, + "citation": { + "title": "Citation", + "description": "A bibliographic citation for the resource.", + "type": "array", + "items": { + "type": "string" + } + }, + "additionalProperty": { + "title": "Additional properties", + "description": "Additional properties of the dataset/resource.", + "default": [], + "type": "array", + "items": { + "title": "PropertyValue", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A property-value pair.", + "default": "PropertyValue", + "const": "PropertyValue", + "type": "string" + }, + "propertyID": { + "title": "Property ID", + "description": "The ID of the property.", + "type": "string" + }, + "name": { + "title": "Name", + "description": "The name of the property.", + "type": "string" + }, + "value": { + "title": "Value", + "description": "The value of the property.", + "anyOf": [ + { + "type": "string" + }, + { + "title": "PropertyValue", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A property-value pair.", + "default": "PropertyValue", + "const": "PropertyValue", + "type": "string" + }, + "propertyID": { + "title": "Property ID", + "description": "The ID of the property.", + "type": "string" + }, + "name": { + "title": "Name", + "description": "The name of the property.", + "type": "string" + }, + "value": { + "title": "Value", + "description": "The value of the property.", + "type": "string" + }, + "unitCode": { + "title": "Measurement unit", + "description": "The unit of measurement for the value.", + "type": "string" + }, + "description": { + "title": "Description", + "description": "A description of the property.", + "type": "string" + }, + "minValue": { + "title": "Minimum value", + "description": "The minimum allowed value for the property.", + "type": "number" + }, + "maxValue": { + "title": "Maximum value", + "description": "The maximum allowed value for the property.", + "type": "number" + }, + "measurementTechnique": { + "title": "Measurement technique", + "description": "A technique or technology used in a measurement.", + "type": "string" + } + }, + "required": [ + "name", + "value" + ] + }, + { + "type": "array", + "items": { + "title": "PropertyValue", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A property-value pair.", + "default": "PropertyValue", + "const": "PropertyValue", + "type": "string" + }, + "propertyID": { + "title": "Property ID", + "description": "The ID of the property.", + "type": "string" + }, + "name": { + "title": "Name", + "description": "The name of the property.", + "type": "string" + }, + "value": { + "title": "Value", + "description": "The value of the property.", + "type": "string" + }, + "unitCode": { + "title": "Measurement unit", + "description": "The unit of measurement for the value.", + "type": "string" + }, + "description": { + "title": "Description", + "description": "A description of the property.", + "type": "string" + }, + "minValue": { + "title": "Minimum value", + "description": "The minimum allowed value for the property.", + "type": "number" + }, + "maxValue": { + "title": "Maximum value", + "description": "The maximum allowed value for the property.", + "type": "number" + }, + "measurementTechnique": { + "title": "Measurement technique", + "description": "A technique or technology used in a measurement.", + "type": "string" + } + }, + "required": [ + "name", + "value" + ] + } + } + ] + }, + "unitCode": { + "title": "Measurement unit", + "description": "The unit of measurement for the value.", + "type": "string" + }, + "description": { + "title": "Description", + "description": "A description of the property.", + "type": "string" + }, + "minValue": { + "title": "Minimum value", + "description": "The minimum allowed value for the property.", + "type": "number" + }, + "maxValue": { + "title": "Maximum value", + "description": "The maximum allowed value for the property.", + "type": "number" + }, + "measurementTechnique": { + "title": "Measurement technique", + "description": "A technique or technology used in a measurement.", + "type": "string" + } + }, + "required": [ + "name", + "value" + ] + } + }, + "additionalType": { + "title": "Specific additional type of submission", + "description": "Specific additional type of submission", + "default": "NetCDF", + "const": "NetCDF", + "type": "string" + }, + "temporalCoverage": { + "title": "TemporalCoverage", + "description": "The time period that applies to the dataset.", + "readOnly": true, + "type": "object", + "properties": { + "startDate": { + "title": "Start date", + "description": "A date/time object containing the instant corresponding to the commencement of the time interval (ISO8601 formatted date - YYYY-MM-DDTHH:MM).", + "type": "string", + "format": "date-time" + }, + "endDate": { + "title": "End date", + "description": "A date/time object containing the instant corresponding to the termination of the time interval (ISO8601 formatted date - YYYY-MM-DDTHH:MM). If the ending date is left off, that means the temporal coverage is ongoing.", + "type": "string", + "format": "date-time" + } + }, + "required": [ + "startDate" + ] + }, + "spatialCoverage": { + "title": "Place", + "description": "The spatialCoverage of a CreativeWork indicates the place(s) which are the focus of the content. It is a sub property of contentLocation intended primarily for more technical and detailed materials. For example with a Dataset, it indicates areas that the dataset describes: a dataset of New York weather would have spatialCoverage which was the place: the state of New York.", + "readOnly": true, + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "Represents the focus area of the record's content.", + "default": "Place", + "type": "string" + }, + "name": { + "title": "Name", + "description": "Name of the place.", + "type": "string" + }, + "geo": { + "title": "Geo", + "description": "Specifies the geographic coordinates of the place in the form of a point location, line, or area coverage extent.", + "anyOf": [ + { + "title": "GeoCoordinates", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "Geographic coordinates that represent a specific location on the Earth's surface. GeoCoordinates typically consists of two components: latitude and longitude.", + "default": "GeoCoordinates", + "type": "string" + }, + "latitude": { + "title": "Latitude", + "description": "Represents the angular distance of a location north or south of the equator, measured in degrees and ranges from -90 to +90 degrees.", + "type": "number" + }, + "longitude": { + "title": "Longitude", + "description": "Represents the angular distance of a location east or west of the Prime Meridian, measured in degrees and ranges from -180 to +180 degrees.", + "type": "number" + } + }, + "required": [ + "latitude", + "longitude" + ] + }, + { + "title": "GeoShape", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A structured representation that describes the coordinates of a geographic feature.", + "default": "GeoShape", + "type": "string" + }, + "box": { + "title": "Box", + "description": "A box is a rectangular region defined by a pair of coordinates representing the southwest and northeast corners of the box.", + "type": "string" + } + }, + "required": [ + "box" + ] + } + ] + }, + "additionalProperty": { + "title": "Additional properties", + "description": "Additional properties of the place.", + "default": [], + "type": "array", + "items": { + "title": "PropertyValue", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A property-value pair.", + "default": "PropertyValue", + "const": "PropertyValue", + "type": "string" + }, + "propertyID": { + "title": "Property ID", + "description": "The ID of the property.", + "type": "string" + }, + "name": { + "title": "Name", + "description": "The name of the property.", + "type": "string" + }, + "value": { + "title": "Value", + "description": "The value of the property.", + "anyOf": [ + { + "type": "string" + }, + { + "title": "PropertyValue", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A property-value pair.", + "default": "PropertyValue", + "const": "PropertyValue", + "type": "string" + }, + "propertyID": { + "title": "Property ID", + "description": "The ID of the property.", + "type": "string" + }, + "name": { + "title": "Name", + "description": "The name of the property.", + "type": "string" + }, + "value": { + "title": "Value", + "description": "The value of the property.", + "type": "string" + }, + "unitCode": { + "title": "Measurement unit", + "description": "The unit of measurement for the value.", + "type": "string" + }, + "description": { + "title": "Description", + "description": "A description of the property.", + "type": "string" + }, + "minValue": { + "title": "Minimum value", + "description": "The minimum allowed value for the property.", + "type": "number" + }, + "maxValue": { + "title": "Maximum value", + "description": "The maximum allowed value for the property.", + "type": "number" + }, + "measurementTechnique": { + "title": "Measurement technique", + "description": "A technique or technology used in a measurement.", + "type": "string" + } + }, + "required": [ + "name", + "value" + ] + }, + { + "type": "array", + "items": { + "title": "PropertyValue", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A property-value pair.", + "default": "PropertyValue", + "const": "PropertyValue", + "type": "string" + }, + "propertyID": { + "title": "Property ID", + "description": "The ID of the property.", + "type": "string" + }, + "name": { + "title": "Name", + "description": "The name of the property.", + "type": "string" + }, + "value": { + "title": "Value", + "description": "The value of the property.", + "type": "string" + }, + "unitCode": { + "title": "Measurement unit", + "description": "The unit of measurement for the value.", + "type": "string" + }, + "description": { + "title": "Description", + "description": "A description of the property.", + "type": "string" + }, + "minValue": { + "title": "Minimum value", + "description": "The minimum allowed value for the property.", + "type": "number" + }, + "maxValue": { + "title": "Maximum value", + "description": "The maximum allowed value for the property.", + "type": "number" + }, + "measurementTechnique": { + "title": "Measurement technique", + "description": "A technique or technology used in a measurement.", + "type": "string" + } + }, + "required": [ + "name", + "value" + ] + } + } + ] + }, + "unitCode": { + "title": "Measurement unit", + "description": "The unit of measurement for the value.", + "type": "string" + }, + "description": { + "title": "Description", + "description": "A description of the property.", + "type": "string" + }, + "minValue": { + "title": "Minimum value", + "description": "The minimum allowed value for the property.", + "type": "number" + }, + "maxValue": { + "title": "Maximum value", + "description": "The maximum allowed value for the property.", + "type": "number" + }, + "measurementTechnique": { + "title": "Measurement technique", + "description": "A technique or technology used in a measurement.", + "type": "string" + } + }, + "required": [ + "name", + "value" + ] + } + } + } + }, + "variableMeasured": { + "title": "Variables measured", + "description": "Measured variables.", + "readOnly": true, + "type": "array", + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "title": "PropertyValue", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A property-value pair.", + "default": "PropertyValue", + "const": "PropertyValue", + "type": "string" + }, + "propertyID": { + "title": "Property ID", + "description": "The ID of the property.", + "type": "string" + }, + "name": { + "title": "Name", + "description": "The name of the property.", + "type": "string" + }, + "value": { + "title": "Value", + "description": "The value of the property.", + "anyOf": [ + { + "type": "string" + }, + { + "title": "PropertyValue", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A property-value pair.", + "default": "PropertyValue", + "const": "PropertyValue", + "type": "string" + }, + "propertyID": { + "title": "Property ID", + "description": "The ID of the property.", + "type": "string" + }, + "name": { + "title": "Name", + "description": "The name of the property.", + "type": "string" + }, + "value": { + "title": "Value", + "description": "The value of the property.", + "type": "string" + }, + "unitCode": { + "title": "Measurement unit", + "description": "The unit of measurement for the value.", + "type": "string" + }, + "description": { + "title": "Description", + "description": "A description of the property.", + "type": "string" + }, + "minValue": { + "title": "Minimum value", + "description": "The minimum allowed value for the property.", + "type": "number" + }, + "maxValue": { + "title": "Maximum value", + "description": "The maximum allowed value for the property.", + "type": "number" + }, + "measurementTechnique": { + "title": "Measurement technique", + "description": "A technique or technology used in a measurement.", + "type": "string" + } + }, + "required": [ + "name", + "value" + ] + }, + { + "type": "array", + "items": { + "title": "PropertyValue", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A property-value pair.", + "default": "PropertyValue", + "const": "PropertyValue", + "type": "string" + }, + "propertyID": { + "title": "Property ID", + "description": "The ID of the property.", + "type": "string" + }, + "name": { + "title": "Name", + "description": "The name of the property.", + "type": "string" + }, + "value": { + "title": "Value", + "description": "The value of the property.", + "type": "string" + }, + "unitCode": { + "title": "Measurement unit", + "description": "The unit of measurement for the value.", + "type": "string" + }, + "description": { + "title": "Description", + "description": "A description of the property.", + "type": "string" + }, + "minValue": { + "title": "Minimum value", + "description": "The minimum allowed value for the property.", + "type": "number" + }, + "maxValue": { + "title": "Maximum value", + "description": "The maximum allowed value for the property.", + "type": "number" + }, + "measurementTechnique": { + "title": "Measurement technique", + "description": "A technique or technology used in a measurement.", + "type": "string" + } + }, + "required": [ + "name", + "value" + ] + } + } + ] + }, + "unitCode": { + "title": "Measurement unit", + "description": "The unit of measurement for the value.", + "type": "string" + }, + "description": { + "title": "Description", + "description": "A description of the property.", + "type": "string" + }, + "minValue": { + "title": "Minimum value", + "description": "The minimum allowed value for the property.", + "type": "number" + }, + "maxValue": { + "title": "Maximum value", + "description": "The maximum allowed value for the property.", + "type": "number" + }, + "measurementTechnique": { + "title": "Measurement technique", + "description": "A technique or technology used in a measurement.", + "type": "string" + } + }, + "required": [ + "name", + "value" + ] + } + ] + } + }, + "sourceOrganization": { + "title": "SourceOrganization", + "description": "The organization that provided the data for this dataset.", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "Organization", + "const": "Organization", + "type": "string" + }, + "name": { + "title": "Name", + "description": "Name of the organization that created the data.", + "type": "string" + }, + "url": { + "title": "URL", + "description": "A URL to the homepage for the organization.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "address": { + "title": "Address", + "description": "Full address for the organization - e.g., \u201c8200 Old Main Hill, Logan, UT 84322-8200\u201d.", + "type": "string" + } + }, + "required": [ + "name" + ] + } + }, + "required": [ + "name", + "url", + "associatedMedia", + "temporalCoverage", + "spatialCoverage", + "variableMeasured" + ], + "definitions": { + "Affiliation": { + "title": "Affiliation", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "Organization", + "const": "Organization", + "type": "string" + }, + "name": { + "title": "Name", + "description": "Name of the organization the creator is affiliated with.", + "type": "string" + }, + "url": { + "title": "URL", + "description": "A URL to the homepage for the organization.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "address": { + "title": "Address", + "description": "Full address for the organization - e.g., \u201c8200 Old Main Hill, Logan, UT 84322-8200\u201d.", + "type": "string" + } + }, + "required": [ + "name" + ] + }, + "Creator": { + "title": "Creator", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A person.", + "default": "Person", + "const": "Person", + "type": "string" + }, + "name": { + "title": "Name", + "description": "A string containing the full name of the person. Personal name format: Family Name, Given Name.", + "type": "string" + }, + "email": { + "title": "Email", + "description": "A string containing an email address for the creator.", + "type": "string", + "format": "email" + }, + "identifier": { + "title": "Identifier", + "description": "ORCID identifier for creator.", + "pattern": "\\b\\d{4}-\\d{4}-\\d{4}-\\d{3}[0-9X]\\b", + "options": { + "placeholder": "e.g. '0000-0001-2345-6789'" + }, + "errorMessage": { + "pattern": "must match the ORCID pattern. e.g. '0000-0001-2345-6789'" + }, + "type": "string" + }, + "affiliation": { + "title": "Affiliation", + "description": "The affiliation of the creator with the organization.", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "Organization", + "const": "Organization", + "type": "string" + }, + "name": { + "title": "Name", + "description": "Name of the organization the creator is affiliated with.", + "type": "string" + }, + "url": { + "title": "URL", + "description": "A URL to the homepage for the organization.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "address": { + "title": "Address", + "description": "Full address for the organization - e.g., \u201c8200 Old Main Hill, Logan, UT 84322-8200\u201d.", + "type": "string" + } + }, + "required": [ + "name" + ] + } + }, + "required": [ + "name" + ] + }, + "Organization": { + "title": "Organization", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "Organization", + "const": "Organization", + "type": "string" + }, + "name": { + "title": "Name", + "description": "Name of the provider organization or repository.", + "type": "string" + }, + "url": { + "title": "URL", + "description": "A URL to the homepage for the organization.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "address": { + "title": "Address", + "description": "Full address for the organization - e.g., \u201c8200 Old Main Hill, Logan, UT 84322-8200\u201d.", + "type": "string" + } + }, + "required": [ + "name" + ] + }, + "License": { + "title": "License", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "Submission type can include various forms of content, such as datasets, software source code, digital documents, etc.", + "default": "CreativeWork", + "type": "string" + }, + "name": { + "title": "Name", + "description": "A text string indicating the name of the license under which the resource is shared.", + "type": "string" + }, + "url": { + "title": "URL", + "description": "A URL for a web page that describes the license.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "description": { + "title": "Description", + "description": "A text string describing the license or containing the text of the license itself.", + "type": "string" + } + }, + "required": [ + "name" + ] + }, + "Provider": { + "title": "Provider", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A person.", + "default": "Person", + "const": "Person", + "type": "string" + }, + "name": { + "title": "Name", + "description": "A string containing the full name of the person. Personal name format: Family Name, Given Name.", + "type": "string" + }, + "email": { + "title": "Email", + "description": "A string containing an email address for the provider.", + "type": "string", + "format": "email" + }, + "identifier": { + "title": "Identifier", + "description": "ORCID identifier for the person.", + "pattern": "\\b\\d{4}-\\d{4}-\\d{4}-\\d{3}[0-9X]\\b", + "options": { + "placeholder": "e.g. '0000-0001-2345-6789'" + }, + "errorMessage": { + "pattern": "must match the ORCID pattern. e.g. '0000-0001-2345-6789'" + }, + "type": "string" + }, + "affiliation": { + "title": "Affiliation", + "description": "The affiliation of the creator with the organization.", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "Organization", + "const": "Organization", + "type": "string" + }, + "name": { + "title": "Name", + "description": "Name of the organization the creator is affiliated with.", + "type": "string" + }, + "url": { + "title": "URL", + "description": "A URL to the homepage for the organization.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "address": { + "title": "Address", + "description": "Full address for the organization - e.g., \u201c8200 Old Main Hill, Logan, UT 84322-8200\u201d.", + "type": "string" + } + }, + "required": [ + "name" + ] + } + }, + "required": [ + "name" + ] + }, + "PublisherOrganization": { + "title": "PublisherOrganization", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "Organization", + "const": "Organization", + "type": "string" + }, + "name": { + "title": "Name", + "description": "Name of the publishing organization.", + "type": "string" + }, + "url": { + "title": "URL", + "description": "A URL to the homepage for the publisher organization or repository.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "address": { + "title": "Address", + "description": "Full address for the organization - e.g., \u201c8200 Old Main Hill, Logan, UT 84322-8200\u201d.", + "type": "string" + } + }, + "required": [ + "name" + ] + }, + "SubjectOf": { + "title": "SubjectOf", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "Submission type can include various forms of content, such as datasets, software source code, digital documents, etc.", + "default": "CreativeWork", + "type": "string" + }, + "name": { + "title": "Name or title", + "description": "Submission's name or title", + "type": "string" + }, + "url": { + "title": "URL", + "description": "The URL address that serves as a reference to access additional details related to the record. It is important to note that this type of metadata solely pertains to the record itself and may not necessarily be an integral component of the record, unlike the HasPart metadata.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "description": { + "title": "Description", + "description": "Information about a related resource that is about or describes this resource - e.g., a related metadata document describing the resource.", + "type": "string" + } + }, + "required": [ + "name" + ] + }, + "LanguageEnum": { + "title": "Language", + "description": "", + "enum": [ + "eng", + "esp" + ], + "type": "string" + }, + "Draft": { + "title": "Draft", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "DefinedTerm", + "type": "string" + }, + "name": { + "title": "Name", + "default": "Draft", + "type": "string" + }, + "description": { + "title": "Description", + "description": "The description of the item being defined.", + "default": "The resource is in draft state and should not be considered final. Content and metadata may change", + "readOnly": true, + "type": "string" + } + } + }, + "Incomplete": { + "title": "Incomplete", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "DefinedTerm", + "type": "string" + }, + "name": { + "title": "Name", + "default": "Incomplete", + "type": "string" + }, + "description": { + "title": "Description", + "description": "The description of the item being defined.", + "default": "Data collection is ongoing or the resource is not completed", + "readOnly": true, + "type": "string" + } + } + }, + "Obsolete": { + "title": "Obsolete", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "DefinedTerm", + "type": "string" + }, + "name": { + "title": "Name", + "default": "Obsolete", + "type": "string" + }, + "description": { + "title": "Description", + "description": "The description of the item being defined.", + "default": "The resource has been replaced by a newer version, or the resource is no longer considered applicable", + "readOnly": true, + "type": "string" + } + } + }, + "Published": { + "title": "Published", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "DefinedTerm", + "type": "string" + }, + "name": { + "title": "Name", + "default": "Published", + "type": "string" + }, + "description": { + "title": "Description", + "description": "The description of the item being defined.", + "default": "The resource has been permanently published and should be considered final and complete", + "readOnly": true, + "type": "string" + } + } + }, + "Grant": { + "title": "Grant", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "This metadata represents details about a grant or financial assistance provided to an individual(s) or organization(s) for supporting the work related to the record.", + "default": "MonetaryGrant", + "type": "string" + }, + "name": { + "title": "Name or title", + "description": "A text string indicating the name or title of the grant or financial assistance.", + "type": "string" + }, + "description": { + "title": "Description", + "description": "A text string describing the grant or financial assistance.", + "type": "string" + }, + "identifier": { + "title": "Funding identifier", + "description": "Grant award number or other identifier.", + "type": "string" + }, + "funder": { + "title": "Funding Organization", + "description": "The organization that provided the funding or sponsorship.", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "Organization", + "const": "Organization", + "type": "string" + }, + "name": { + "title": "Name", + "description": "Name of the organization.", + "type": "string" + }, + "url": { + "title": "URL", + "description": "A URL to the homepage for the organization.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "address": { + "title": "Address", + "description": "Full address for the organization - e.g., \u201c8200 Old Main Hill, Logan, UT 84322-8200\u201d.", + "type": "string" + } + }, + "required": [ + "name" + ] + } + }, + "required": [ + "name" + ] + }, + "HasPart": { + "title": "HasPart", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "Submission type can include various forms of content, such as datasets, software source code, digital documents, etc.", + "default": "CreativeWork", + "type": "string" + }, + "name": { + "title": "Name or title", + "description": "Submission's name or title", + "type": "string" + }, + "url": { + "title": "URL", + "description": "The URL address to the data resource.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "description": { + "title": "Description", + "description": "Information about a related resource that is part of this resource.", + "type": "string" + } + }, + "required": [ + "name" + ] + }, + "IsPartOf": { + "title": "IsPartOf", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "Submission type can include various forms of content, such as datasets, software source code, digital documents, etc.", + "default": "CreativeWork", + "type": "string" + }, + "name": { + "title": "Name or title", + "description": "Submission's name or title", + "type": "string" + }, + "url": { + "title": "URL", + "description": "The URL address to the data resource.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "description": { + "title": "Description", + "description": "Information about a related resource that this resource is a part of - e.g., a related collection.", + "type": "string" + } + }, + "required": [ + "name" + ] + }, + "MediaObjectPartOf": { + "title": "MediaObjectPartOf", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "Submission type can include various forms of content, such as datasets, software source code, digital documents, etc.", + "default": "CreativeWork", + "type": "string" + }, + "name": { + "title": "Name or title", + "description": "Submission's name or title", + "type": "string" + }, + "url": { + "title": "URL", + "description": "The URL address to the related metadata document.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "description": { + "title": "Description", + "description": "Information about a related metadata document.", + "type": "string" + } + }, + "required": [ + "name" + ] + }, + "MediaObject": { + "title": "MediaObject", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "An item that encodes the record.", + "default": "MediaObject", + "type": "string" + }, + "contentUrl": { + "title": "Content URL", + "description": "The direct URL link to access or download the actual content of the media object.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "encodingFormat": { + "title": "Encoding format", + "description": "Represents the specific file format in which the media is encoded.", + "type": "string" + }, + "contentSize": { + "title": "Content size", + "description": "Represents the file size, expressed in bytes, kilobytes, megabytes, or another unit of measurement.", + "type": "string" + }, + "name": { + "title": "Name", + "description": "The name of the media object (file).", + "type": "string" + }, + "sha256": { + "title": "SHA-256", + "description": "The SHA-256 hash of the media object.", + "type": "string" + }, + "isPartOf": { + "title": "MediaObjectPartOf", + "description": "Link to or citation for a related metadata document that this media object is a part of", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "Submission type can include various forms of content, such as datasets, software source code, digital documents, etc.", + "default": "CreativeWork", + "type": "string" + }, + "name": { + "title": "Name or title", + "description": "Submission's name or title", + "type": "string" + }, + "url": { + "title": "URL", + "description": "The URL address to the related metadata document.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "description": { + "title": "Description", + "description": "Information about a related metadata document.", + "type": "string" + } + }, + "required": [ + "name" + ] + } + }, + "required": [ + "contentUrl", + "contentSize", + "name" + ] + }, + "PropertyValueBase": { + "title": "PropertyValue", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A property-value pair.", + "default": "PropertyValue", + "const": "PropertyValue", + "type": "string" + }, + "propertyID": { + "title": "Property ID", + "description": "The ID of the property.", + "type": "string" + }, + "name": { + "title": "Name", + "description": "The name of the property.", + "type": "string" + }, + "value": { + "title": "Value", + "description": "The value of the property.", + "type": "string" + }, + "unitCode": { + "title": "Measurement unit", + "description": "The unit of measurement for the value.", + "type": "string" + }, + "description": { + "title": "Description", + "description": "A description of the property.", + "type": "string" + }, + "minValue": { + "title": "Minimum value", + "description": "The minimum allowed value for the property.", + "type": "number" + }, + "maxValue": { + "title": "Maximum value", + "description": "The maximum allowed value for the property.", + "type": "number" + }, + "measurementTechnique": { + "title": "Measurement technique", + "description": "A technique or technology used in a measurement.", + "type": "string" + } + }, + "required": [ + "name", + "value" + ] + }, + "PropertyValue": { + "title": "PropertyValue", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A property-value pair.", + "default": "PropertyValue", + "const": "PropertyValue", + "type": "string" + }, + "propertyID": { + "title": "Property ID", + "description": "The ID of the property.", + "type": "string" + }, + "name": { + "title": "Name", + "description": "The name of the property.", + "type": "string" + }, + "value": { + "title": "Value", + "description": "The value of the property.", + "anyOf": [ + { + "type": "string" + }, + { + "title": "PropertyValue", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A property-value pair.", + "default": "PropertyValue", + "const": "PropertyValue", + "type": "string" + }, + "propertyID": { + "title": "Property ID", + "description": "The ID of the property.", + "type": "string" + }, + "name": { + "title": "Name", + "description": "The name of the property.", + "type": "string" + }, + "value": { + "title": "Value", + "description": "The value of the property.", + "type": "string" + }, + "unitCode": { + "title": "Measurement unit", + "description": "The unit of measurement for the value.", + "type": "string" + }, + "description": { + "title": "Description", + "description": "A description of the property.", + "type": "string" + }, + "minValue": { + "title": "Minimum value", + "description": "The minimum allowed value for the property.", + "type": "number" + }, + "maxValue": { + "title": "Maximum value", + "description": "The maximum allowed value for the property.", + "type": "number" + }, + "measurementTechnique": { + "title": "Measurement technique", + "description": "A technique or technology used in a measurement.", + "type": "string" + } + }, + "required": [ + "name", + "value" + ] + }, + { + "type": "array", + "items": { + "title": "PropertyValue", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A property-value pair.", + "default": "PropertyValue", + "const": "PropertyValue", + "type": "string" + }, + "propertyID": { + "title": "Property ID", + "description": "The ID of the property.", + "type": "string" + }, + "name": { + "title": "Name", + "description": "The name of the property.", + "type": "string" + }, + "value": { + "title": "Value", + "description": "The value of the property.", + "type": "string" + }, + "unitCode": { + "title": "Measurement unit", + "description": "The unit of measurement for the value.", + "type": "string" + }, + "description": { + "title": "Description", + "description": "A description of the property.", + "type": "string" + }, + "minValue": { + "title": "Minimum value", + "description": "The minimum allowed value for the property.", + "type": "number" + }, + "maxValue": { + "title": "Maximum value", + "description": "The maximum allowed value for the property.", + "type": "number" + }, + "measurementTechnique": { + "title": "Measurement technique", + "description": "A technique or technology used in a measurement.", + "type": "string" + } + }, + "required": [ + "name", + "value" + ] + } + } + ] + }, + "unitCode": { + "title": "Measurement unit", + "description": "The unit of measurement for the value.", + "type": "string" + }, + "description": { + "title": "Description", + "description": "A description of the property.", + "type": "string" + }, + "minValue": { + "title": "Minimum value", + "description": "The minimum allowed value for the property.", + "type": "number" + }, + "maxValue": { + "title": "Maximum value", + "description": "The maximum allowed value for the property.", + "type": "number" + }, + "measurementTechnique": { + "title": "Measurement technique", + "description": "A technique or technology used in a measurement.", + "type": "string" + } + }, + "required": [ + "name", + "value" + ] + }, + "TemporalCoverage": { + "title": "TemporalCoverage", + "type": "object", + "properties": { + "startDate": { + "title": "Start date", + "description": "A date/time object containing the instant corresponding to the commencement of the time interval (ISO8601 formatted date - YYYY-MM-DDTHH:MM).", + "type": "string", + "format": "date-time" + }, + "endDate": { + "title": "End date", + "description": "A date/time object containing the instant corresponding to the termination of the time interval (ISO8601 formatted date - YYYY-MM-DDTHH:MM). If the ending date is left off, that means the temporal coverage is ongoing.", + "type": "string", + "format": "date-time" + } + }, + "required": [ + "startDate" + ] + }, + "GeoCoordinates": { + "title": "GeoCoordinates", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "Geographic coordinates that represent a specific location on the Earth's surface. GeoCoordinates typically consists of two components: latitude and longitude.", + "default": "GeoCoordinates", + "type": "string" + }, + "latitude": { + "title": "Latitude", + "description": "Represents the angular distance of a location north or south of the equator, measured in degrees and ranges from -90 to +90 degrees.", + "type": "number" + }, + "longitude": { + "title": "Longitude", + "description": "Represents the angular distance of a location east or west of the Prime Meridian, measured in degrees and ranges from -180 to +180 degrees.", + "type": "number" + } + }, + "required": [ + "latitude", + "longitude" + ] + }, + "GeoShape": { + "title": "GeoShape", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A structured representation that describes the coordinates of a geographic feature.", + "default": "GeoShape", + "type": "string" + }, + "box": { + "title": "Box", + "description": "A box is a rectangular region defined by a pair of coordinates representing the southwest and northeast corners of the box.", + "type": "string" + } + }, + "required": [ + "box" + ] + }, + "Place": { + "title": "Place", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "Represents the focus area of the record's content.", + "default": "Place", + "type": "string" + }, + "name": { + "title": "Name", + "description": "Name of the place.", + "type": "string" + }, + "geo": { + "title": "Geo", + "description": "Specifies the geographic coordinates of the place in the form of a point location, line, or area coverage extent.", + "anyOf": [ + { + "title": "GeoCoordinates", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "Geographic coordinates that represent a specific location on the Earth's surface. GeoCoordinates typically consists of two components: latitude and longitude.", + "default": "GeoCoordinates", + "type": "string" + }, + "latitude": { + "title": "Latitude", + "description": "Represents the angular distance of a location north or south of the equator, measured in degrees and ranges from -90 to +90 degrees.", + "type": "number" + }, + "longitude": { + "title": "Longitude", + "description": "Represents the angular distance of a location east or west of the Prime Meridian, measured in degrees and ranges from -180 to +180 degrees.", + "type": "number" + } + }, + "required": [ + "latitude", + "longitude" + ] + }, + { + "title": "GeoShape", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A structured representation that describes the coordinates of a geographic feature.", + "default": "GeoShape", + "type": "string" + }, + "box": { + "title": "Box", + "description": "A box is a rectangular region defined by a pair of coordinates representing the southwest and northeast corners of the box.", + "type": "string" + } + }, + "required": [ + "box" + ] + } + ] + }, + "additionalProperty": { + "title": "Additional properties", + "description": "Additional properties of the place.", + "default": [], + "type": "array", + "items": { + "title": "PropertyValue", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A property-value pair.", + "default": "PropertyValue", + "const": "PropertyValue", + "type": "string" + }, + "propertyID": { + "title": "Property ID", + "description": "The ID of the property.", + "type": "string" + }, + "name": { + "title": "Name", + "description": "The name of the property.", + "type": "string" + }, + "value": { + "title": "Value", + "description": "The value of the property.", + "anyOf": [ + { + "type": "string" + }, + { + "title": "PropertyValue", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A property-value pair.", + "default": "PropertyValue", + "const": "PropertyValue", + "type": "string" + }, + "propertyID": { + "title": "Property ID", + "description": "The ID of the property.", + "type": "string" + }, + "name": { + "title": "Name", + "description": "The name of the property.", + "type": "string" + }, + "value": { + "title": "Value", + "description": "The value of the property.", + "type": "string" + }, + "unitCode": { + "title": "Measurement unit", + "description": "The unit of measurement for the value.", + "type": "string" + }, + "description": { + "title": "Description", + "description": "A description of the property.", + "type": "string" + }, + "minValue": { + "title": "Minimum value", + "description": "The minimum allowed value for the property.", + "type": "number" + }, + "maxValue": { + "title": "Maximum value", + "description": "The maximum allowed value for the property.", + "type": "number" + }, + "measurementTechnique": { + "title": "Measurement technique", + "description": "A technique or technology used in a measurement.", + "type": "string" + } + }, + "required": [ + "name", + "value" + ] + }, + { + "type": "array", + "items": { + "title": "PropertyValue", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A property-value pair.", + "default": "PropertyValue", + "const": "PropertyValue", + "type": "string" + }, + "propertyID": { + "title": "Property ID", + "description": "The ID of the property.", + "type": "string" + }, + "name": { + "title": "Name", + "description": "The name of the property.", + "type": "string" + }, + "value": { + "title": "Value", + "description": "The value of the property.", + "type": "string" + }, + "unitCode": { + "title": "Measurement unit", + "description": "The unit of measurement for the value.", + "type": "string" + }, + "description": { + "title": "Description", + "description": "A description of the property.", + "type": "string" + }, + "minValue": { + "title": "Minimum value", + "description": "The minimum allowed value for the property.", + "type": "number" + }, + "maxValue": { + "title": "Maximum value", + "description": "The maximum allowed value for the property.", + "type": "number" + }, + "measurementTechnique": { + "title": "Measurement technique", + "description": "A technique or technology used in a measurement.", + "type": "string" + } + }, + "required": [ + "name", + "value" + ] + } + } + ] + }, + "unitCode": { + "title": "Measurement unit", + "description": "The unit of measurement for the value.", + "type": "string" + }, + "description": { + "title": "Description", + "description": "A description of the property.", + "type": "string" + }, + "minValue": { + "title": "Minimum value", + "description": "The minimum allowed value for the property.", + "type": "number" + }, + "maxValue": { + "title": "Maximum value", + "description": "The maximum allowed value for the property.", + "type": "number" + }, + "measurementTechnique": { + "title": "Measurement technique", + "description": "A technique or technology used in a measurement.", + "type": "string" + } + }, + "required": [ + "name", + "value" + ] + } + } + } + }, + "SourceOrganization": { + "title": "SourceOrganization", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "Organization", + "const": "Organization", + "type": "string" + }, + "name": { + "title": "Name", + "description": "Name of the organization that created the data.", + "type": "string" + }, + "url": { + "title": "URL", + "description": "A URL to the homepage for the organization.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "address": { + "title": "Address", + "description": "Full address for the organization - e.g., \u201c8200 Old Main Hill, Logan, UT 84322-8200\u201d.", + "type": "string" + } + }, + "required": [ + "name" + ] + } + } +} \ No newline at end of file diff --git a/api/models/schemas/raster/__init__.py b/api/models/schemas/raster/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/api/models/schemas/raster/schema.json b/api/models/schemas/raster/schema.json new file mode 100644 index 0000000..27f1bb6 --- /dev/null +++ b/api/models/schemas/raster/schema.json @@ -0,0 +1,2799 @@ +{ + "title": "HSRasterMetadata", + "type": "object", + "properties": { + "@context": { + "title": "@Context", + "description": "Specifies the vocabulary employed for understanding the structured data markup.", + "default": "https://schema.org", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "@type": { + "title": "Submission type", + "description": "Type of submission", + "default": "Dataset", + "const": "Dataset", + "type": "string" + }, + "name": { + "title": "Name or title", + "description": "A text string with a descriptive name or title for the resource.", + "type": "string" + }, + "description": { + "title": "Description or abstract", + "description": "A text string containing a description/abstract for the resource.", + "type": "string" + }, + "url": { + "title": "URL", + "description": "A URL for the landing page that describes the resource and where the content of the resource can be accessed. If there is no landing page, provide the URL of the content.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "identifier": { + "title": "Identifiers", + "description": "Any kind of identifier for the resource/dataset. Identifiers may be DOIs or unique strings assigned by a repository. Multiple identifiers can be entered. Where identifiers can be encoded as URLs, enter URLs here.", + "type": "array", + "items": { + "type": "string", + "title": "Identifier" + } + }, + "creator": { + "title": "Creator", + "description": "Person or Organization that created the resource.", + "type": "array", + "items": { + "anyOf": [ + { + "title": "Creator", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A person.", + "default": "Person", + "const": "Person", + "type": "string" + }, + "name": { + "title": "Name", + "description": "A string containing the full name of the person. Personal name format: Family Name, Given Name.", + "type": "string" + }, + "email": { + "title": "Email", + "description": "A string containing an email address for the creator.", + "type": "string", + "format": "email" + }, + "identifier": { + "title": "Identifier", + "description": "ORCID identifier for creator.", + "pattern": "\\b\\d{4}-\\d{4}-\\d{4}-\\d{3}[0-9X]\\b", + "options": { + "placeholder": "e.g. '0000-0001-2345-6789'" + }, + "errorMessage": { + "pattern": "must match the ORCID pattern. e.g. '0000-0001-2345-6789'" + }, + "type": "string" + }, + "affiliation": { + "title": "Affiliation", + "description": "The affiliation of the creator with the organization.", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "Organization", + "const": "Organization", + "type": "string" + }, + "name": { + "title": "Name", + "description": "Name of the organization the creator is affiliated with.", + "type": "string" + }, + "url": { + "title": "URL", + "description": "A URL to the homepage for the organization.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "address": { + "title": "Address", + "description": "Full address for the organization - e.g., \u201c8200 Old Main Hill, Logan, UT 84322-8200\u201d.", + "type": "string" + } + }, + "required": [ + "name" + ] + } + }, + "required": [ + "name" + ] + }, + { + "title": "Organization", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "Organization", + "const": "Organization", + "type": "string" + }, + "name": { + "title": "Name", + "description": "Name of the provider organization or repository.", + "type": "string" + }, + "url": { + "title": "URL", + "description": "A URL to the homepage for the organization.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "address": { + "title": "Address", + "description": "Full address for the organization - e.g., \u201c8200 Old Main Hill, Logan, UT 84322-8200\u201d.", + "type": "string" + } + }, + "required": [ + "name" + ] + } + ] + } + }, + "dateCreated": { + "title": "Date created", + "description": "The date on which the resource was created.", + "type": "string", + "format": "date-time" + }, + "keywords": { + "title": "Keywords", + "description": "Keywords or tags used to describe the dataset, delimited by commas.", + "minItems": 1, + "type": "array", + "items": { + "type": "string" + } + }, + "license": { + "title": "License", + "description": "A license document that applies to the resource.", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "Submission type can include various forms of content, such as datasets, software source code, digital documents, etc.", + "default": "CreativeWork", + "type": "string" + }, + "name": { + "title": "Name", + "description": "A text string indicating the name of the license under which the resource is shared.", + "type": "string" + }, + "url": { + "title": "URL", + "description": "A URL for a web page that describes the license.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "description": { + "title": "Description", + "description": "A text string describing the license or containing the text of the license itself.", + "type": "string" + } + }, + "required": [ + "name" + ] + }, + "provider": { + "title": "Provider", + "description": "The repository, service provider, organization, person, or service performer that provides access to the resource.", + "anyOf": [ + { + "title": "Organization", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "Organization", + "const": "Organization", + "type": "string" + }, + "name": { + "title": "Name", + "description": "Name of the provider organization or repository.", + "type": "string" + }, + "url": { + "title": "URL", + "description": "A URL to the homepage for the organization.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "address": { + "title": "Address", + "description": "Full address for the organization - e.g., \u201c8200 Old Main Hill, Logan, UT 84322-8200\u201d.", + "type": "string" + } + }, + "required": [ + "name" + ] + }, + { + "title": "Provider", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A person.", + "default": "Person", + "const": "Person", + "type": "string" + }, + "name": { + "title": "Name", + "description": "A string containing the full name of the person. Personal name format: Family Name, Given Name.", + "type": "string" + }, + "email": { + "title": "Email", + "description": "A string containing an email address for the provider.", + "type": "string", + "format": "email" + }, + "identifier": { + "title": "Identifier", + "description": "ORCID identifier for the person.", + "pattern": "\\b\\d{4}-\\d{4}-\\d{4}-\\d{3}[0-9X]\\b", + "options": { + "placeholder": "e.g. '0000-0001-2345-6789'" + }, + "errorMessage": { + "pattern": "must match the ORCID pattern. e.g. '0000-0001-2345-6789'" + }, + "type": "string" + }, + "affiliation": { + "title": "Affiliation", + "description": "The affiliation of the creator with the organization.", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "Organization", + "const": "Organization", + "type": "string" + }, + "name": { + "title": "Name", + "description": "Name of the organization the creator is affiliated with.", + "type": "string" + }, + "url": { + "title": "URL", + "description": "A URL to the homepage for the organization.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "address": { + "title": "Address", + "description": "Full address for the organization - e.g., \u201c8200 Old Main Hill, Logan, UT 84322-8200\u201d.", + "type": "string" + } + }, + "required": [ + "name" + ] + } + }, + "required": [ + "name" + ] + } + ] + }, + "publisher": { + "title": "PublisherOrganization", + "description": "Where the resource is permanently published, indicated the repository, service provider, or organization that published the resource - e.g., CUAHSI HydroShare. This may be the same as Provider.", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "Organization", + "const": "Organization", + "type": "string" + }, + "name": { + "title": "Name", + "description": "Name of the publishing organization.", + "type": "string" + }, + "url": { + "title": "URL", + "description": "A URL to the homepage for the publisher organization or repository.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "address": { + "title": "Address", + "description": "Full address for the organization - e.g., \u201c8200 Old Main Hill, Logan, UT 84322-8200\u201d.", + "type": "string" + } + }, + "required": [ + "name" + ] + }, + "datePublished": { + "title": "Date published", + "description": "Date of first publication for the resource.", + "type": "string", + "format": "date-time" + }, + "subjectOf": { + "title": "Subject of", + "description": "Link to or citation for a related resource that is about or describes this resource - e.g., a journal paper that describes this resource or a related metadata document describing the resource.", + "type": "array", + "items": { + "title": "SubjectOf", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "Submission type can include various forms of content, such as datasets, software source code, digital documents, etc.", + "default": "CreativeWork", + "type": "string" + }, + "name": { + "title": "Name or title", + "description": "Submission's name or title", + "type": "string" + }, + "url": { + "title": "URL", + "description": "The URL address that serves as a reference to access additional details related to the record. It is important to note that this type of metadata solely pertains to the record itself and may not necessarily be an integral component of the record, unlike the HasPart metadata.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "description": { + "title": "Description", + "description": "Information about a related resource that is about or describes this resource - e.g., a related metadata document describing the resource.", + "type": "string" + } + }, + "required": [ + "name" + ] + } + }, + "version": { + "title": "Version", + "description": "A text string indicating the version of the resource.", + "type": "string" + }, + "inLanguage": { + "title": "Language", + "description": "The language of the content of the resource.", + "anyOf": [ + { + "title": "Language", + "description": "", + "enum": [ + "eng", + "esp" + ], + "type": "string" + }, + { + "type": "string", + "title": "Other", + "description": "Please specify another language." + } + ] + }, + "creativeWorkStatus": { + "title": "Resource status", + "description": "The status of this resource in terms of its stage in a lifecycle. Example terms include Incomplete, Draft, Published, and Obsolete.", + "anyOf": [ + { + "title": "Draft", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "DefinedTerm", + "type": "string" + }, + "name": { + "title": "Name", + "default": "Draft", + "type": "string" + }, + "description": { + "title": "Description", + "description": "The description of the item being defined.", + "default": "The resource is in draft state and should not be considered final. Content and metadata may change", + "readOnly": true, + "type": "string" + } + } + }, + { + "title": "Incomplete", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "DefinedTerm", + "type": "string" + }, + "name": { + "title": "Name", + "default": "Incomplete", + "type": "string" + }, + "description": { + "title": "Description", + "description": "The description of the item being defined.", + "default": "Data collection is ongoing or the resource is not completed", + "readOnly": true, + "type": "string" + } + } + }, + { + "title": "Obsolete", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "DefinedTerm", + "type": "string" + }, + "name": { + "title": "Name", + "default": "Obsolete", + "type": "string" + }, + "description": { + "title": "Description", + "description": "The description of the item being defined.", + "default": "The resource has been replaced by a newer version, or the resource is no longer considered applicable", + "readOnly": true, + "type": "string" + } + } + }, + { + "title": "Published", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "DefinedTerm", + "type": "string" + }, + "name": { + "title": "Name", + "default": "Published", + "type": "string" + }, + "description": { + "title": "Description", + "description": "The description of the item being defined.", + "default": "The resource has been permanently published and should be considered final and complete", + "readOnly": true, + "type": "string" + } + } + } + ] + }, + "dateModified": { + "title": "Date modified", + "description": "The date on which the resource was most recently modified or updated.", + "type": "string", + "format": "date-time" + }, + "funding": { + "title": "Funding", + "description": "A Grant or monetary assistance that directly or indirectly provided funding or sponsorship for creation of the resource.", + "type": "array", + "items": { + "title": "Grant", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "This metadata represents details about a grant or financial assistance provided to an individual(s) or organization(s) for supporting the work related to the record.", + "default": "MonetaryGrant", + "type": "string" + }, + "name": { + "title": "Name or title", + "description": "A text string indicating the name or title of the grant or financial assistance.", + "type": "string" + }, + "description": { + "title": "Description", + "description": "A text string describing the grant or financial assistance.", + "type": "string" + }, + "identifier": { + "title": "Funding identifier", + "description": "Grant award number or other identifier.", + "type": "string" + }, + "funder": { + "title": "Funding Organization", + "description": "The organization that provided the funding or sponsorship.", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "Organization", + "const": "Organization", + "type": "string" + }, + "name": { + "title": "Name", + "description": "Name of the organization.", + "type": "string" + }, + "url": { + "title": "URL", + "description": "A URL to the homepage for the organization.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "address": { + "title": "Address", + "description": "Full address for the organization - e.g., \u201c8200 Old Main Hill, Logan, UT 84322-8200\u201d.", + "type": "string" + } + }, + "required": [ + "name" + ] + } + }, + "required": [ + "name" + ] + } + }, + "hasPart": { + "title": "Has part", + "description": "Link to or citation for a related resource that is part of this resource.", + "type": "array", + "items": { + "title": "HasPart", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "Submission type can include various forms of content, such as datasets, software source code, digital documents, etc.", + "default": "CreativeWork", + "type": "string" + }, + "name": { + "title": "Name or title", + "description": "Submission's name or title", + "type": "string" + }, + "url": { + "title": "URL", + "description": "The URL address to the data resource.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "description": { + "title": "Description", + "description": "Information about a related resource that is part of this resource.", + "type": "string" + } + }, + "required": [ + "name" + ] + } + }, + "isPartOf": { + "title": "Is part of", + "description": "Link to or citation for a related resource that this resource is a part of - e.g., a related collection.", + "type": "array", + "items": { + "title": "IsPartOf", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "Submission type can include various forms of content, such as datasets, software source code, digital documents, etc.", + "default": "CreativeWork", + "type": "string" + }, + "name": { + "title": "Name or title", + "description": "Submission's name or title", + "type": "string" + }, + "url": { + "title": "URL", + "description": "The URL address to the data resource.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "description": { + "title": "Description", + "description": "Information about a related resource that this resource is a part of - e.g., a related collection.", + "type": "string" + } + }, + "required": [ + "name" + ] + } + }, + "associatedMedia": { + "title": "Dataset content", + "description": "A media object that encodes this CreativeWork. This property is a synonym for encoding.", + "type": "array", + "items": { + "title": "MediaObject", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "An item that encodes the record.", + "default": "MediaObject", + "type": "string" + }, + "contentUrl": { + "title": "Content URL", + "description": "The direct URL link to access or download the actual content of the media object.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "encodingFormat": { + "title": "Encoding format", + "description": "Represents the specific file format in which the media is encoded.", + "type": "string" + }, + "contentSize": { + "title": "Content size", + "description": "Represents the file size, expressed in bytes, kilobytes, megabytes, or another unit of measurement.", + "type": "string" + }, + "name": { + "title": "Name", + "description": "The name of the media object (file).", + "type": "string" + }, + "sha256": { + "title": "SHA-256", + "description": "The SHA-256 hash of the media object.", + "type": "string" + }, + "isPartOf": { + "title": "MediaObjectPartOf", + "description": "Link to or citation for a related metadata document that this media object is a part of", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "Submission type can include various forms of content, such as datasets, software source code, digital documents, etc.", + "default": "CreativeWork", + "type": "string" + }, + "name": { + "title": "Name or title", + "description": "Submission's name or title", + "type": "string" + }, + "url": { + "title": "URL", + "description": "The URL address to the related metadata document.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "description": { + "title": "Description", + "description": "Information about a related metadata document.", + "type": "string" + } + }, + "required": [ + "name" + ] + } + }, + "required": [ + "contentUrl", + "contentSize", + "name" + ] + } + }, + "citation": { + "title": "Citation", + "description": "A bibliographic citation for the resource.", + "type": "array", + "items": { + "type": "string" + } + }, + "additionalProperty": { + "title": "Additional properties", + "description": "Additional properties of the dataset/resource.", + "default": [], + "type": "array", + "items": { + "title": "PropertyValue", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A property-value pair.", + "default": "PropertyValue", + "const": "PropertyValue", + "type": "string" + }, + "propertyID": { + "title": "Property ID", + "description": "The ID of the property.", + "type": "string" + }, + "name": { + "title": "Name", + "description": "The name of the property.", + "type": "string" + }, + "value": { + "title": "Value", + "description": "The value of the property.", + "anyOf": [ + { + "type": "string" + }, + { + "title": "PropertyValue", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A property-value pair.", + "default": "PropertyValue", + "const": "PropertyValue", + "type": "string" + }, + "propertyID": { + "title": "Property ID", + "description": "The ID of the property.", + "type": "string" + }, + "name": { + "title": "Name", + "description": "The name of the property.", + "type": "string" + }, + "value": { + "title": "Value", + "description": "The value of the property.", + "type": "string" + }, + "unitCode": { + "title": "Measurement unit", + "description": "The unit of measurement for the value.", + "type": "string" + }, + "description": { + "title": "Description", + "description": "A description of the property.", + "type": "string" + }, + "minValue": { + "title": "Minimum value", + "description": "The minimum allowed value for the property.", + "type": "number" + }, + "maxValue": { + "title": "Maximum value", + "description": "The maximum allowed value for the property.", + "type": "number" + }, + "measurementTechnique": { + "title": "Measurement technique", + "description": "A technique or technology used in a measurement.", + "type": "string" + } + }, + "required": [ + "name", + "value" + ] + }, + { + "type": "array", + "items": { + "title": "PropertyValue", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A property-value pair.", + "default": "PropertyValue", + "const": "PropertyValue", + "type": "string" + }, + "propertyID": { + "title": "Property ID", + "description": "The ID of the property.", + "type": "string" + }, + "name": { + "title": "Name", + "description": "The name of the property.", + "type": "string" + }, + "value": { + "title": "Value", + "description": "The value of the property.", + "type": "string" + }, + "unitCode": { + "title": "Measurement unit", + "description": "The unit of measurement for the value.", + "type": "string" + }, + "description": { + "title": "Description", + "description": "A description of the property.", + "type": "string" + }, + "minValue": { + "title": "Minimum value", + "description": "The minimum allowed value for the property.", + "type": "number" + }, + "maxValue": { + "title": "Maximum value", + "description": "The maximum allowed value for the property.", + "type": "number" + }, + "measurementTechnique": { + "title": "Measurement technique", + "description": "A technique or technology used in a measurement.", + "type": "string" + } + }, + "required": [ + "name", + "value" + ] + } + } + ] + }, + "unitCode": { + "title": "Measurement unit", + "description": "The unit of measurement for the value.", + "type": "string" + }, + "description": { + "title": "Description", + "description": "A description of the property.", + "type": "string" + }, + "minValue": { + "title": "Minimum value", + "description": "The minimum allowed value for the property.", + "type": "number" + }, + "maxValue": { + "title": "Maximum value", + "description": "The maximum allowed value for the property.", + "type": "number" + }, + "measurementTechnique": { + "title": "Measurement technique", + "description": "A technique or technology used in a measurement.", + "type": "string" + } + }, + "required": [ + "name", + "value" + ] + } + }, + "additionalType": { + "title": "Specific additional type of submission", + "description": "Specific additional type of submission", + "default": "Geo Raster", + "const": "Geo Raster", + "type": "string" + }, + "temporalCoverage": { + "title": "TemporalCoverage", + "description": "The time period that applies to the dataset.", + "readOnly": true, + "type": "object", + "properties": { + "startDate": { + "title": "Start date", + "description": "A date/time object containing the instant corresponding to the commencement of the time interval (ISO8601 formatted date - YYYY-MM-DDTHH:MM).", + "type": "string", + "format": "date-time" + }, + "endDate": { + "title": "End date", + "description": "A date/time object containing the instant corresponding to the termination of the time interval (ISO8601 formatted date - YYYY-MM-DDTHH:MM). If the ending date is left off, that means the temporal coverage is ongoing.", + "type": "string", + "format": "date-time" + } + }, + "required": [ + "startDate" + ] + }, + "spatialCoverage": { + "title": "Place", + "description": "The spatialCoverage of a CreativeWork indicates the place(s) which are the focus of the content. It is a sub property of contentLocation intended primarily for more technical and detailed materials. For example with a Dataset, it indicates areas that the dataset describes: a dataset of New York weather would have spatialCoverage which was the place: the state of New York.", + "readOnly": true, + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "Represents the focus area of the record's content.", + "default": "Place", + "type": "string" + }, + "name": { + "title": "Name", + "description": "Name of the place.", + "type": "string" + }, + "geo": { + "title": "Geo", + "description": "Specifies the geographic coordinates of the place in the form of a point location, line, or area coverage extent.", + "anyOf": [ + { + "title": "GeoCoordinates", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "Geographic coordinates that represent a specific location on the Earth's surface. GeoCoordinates typically consists of two components: latitude and longitude.", + "default": "GeoCoordinates", + "type": "string" + }, + "latitude": { + "title": "Latitude", + "description": "Represents the angular distance of a location north or south of the equator, measured in degrees and ranges from -90 to +90 degrees.", + "type": "number" + }, + "longitude": { + "title": "Longitude", + "description": "Represents the angular distance of a location east or west of the Prime Meridian, measured in degrees and ranges from -180 to +180 degrees.", + "type": "number" + } + }, + "required": [ + "latitude", + "longitude" + ] + }, + { + "title": "GeoShape", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A structured representation that describes the coordinates of a geographic feature.", + "default": "GeoShape", + "type": "string" + }, + "box": { + "title": "Box", + "description": "A box is a rectangular region defined by a pair of coordinates representing the southwest and northeast corners of the box.", + "type": "string" + } + }, + "required": [ + "box" + ] + } + ] + }, + "additionalProperty": { + "title": "Additional properties", + "description": "Additional properties of the place.", + "default": [], + "type": "array", + "items": { + "title": "PropertyValue", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A property-value pair.", + "default": "PropertyValue", + "const": "PropertyValue", + "type": "string" + }, + "propertyID": { + "title": "Property ID", + "description": "The ID of the property.", + "type": "string" + }, + "name": { + "title": "Name", + "description": "The name of the property.", + "type": "string" + }, + "value": { + "title": "Value", + "description": "The value of the property.", + "anyOf": [ + { + "type": "string" + }, + { + "title": "PropertyValue", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A property-value pair.", + "default": "PropertyValue", + "const": "PropertyValue", + "type": "string" + }, + "propertyID": { + "title": "Property ID", + "description": "The ID of the property.", + "type": "string" + }, + "name": { + "title": "Name", + "description": "The name of the property.", + "type": "string" + }, + "value": { + "title": "Value", + "description": "The value of the property.", + "type": "string" + }, + "unitCode": { + "title": "Measurement unit", + "description": "The unit of measurement for the value.", + "type": "string" + }, + "description": { + "title": "Description", + "description": "A description of the property.", + "type": "string" + }, + "minValue": { + "title": "Minimum value", + "description": "The minimum allowed value for the property.", + "type": "number" + }, + "maxValue": { + "title": "Maximum value", + "description": "The maximum allowed value for the property.", + "type": "number" + }, + "measurementTechnique": { + "title": "Measurement technique", + "description": "A technique or technology used in a measurement.", + "type": "string" + } + }, + "required": [ + "name", + "value" + ] + }, + { + "type": "array", + "items": { + "title": "PropertyValue", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A property-value pair.", + "default": "PropertyValue", + "const": "PropertyValue", + "type": "string" + }, + "propertyID": { + "title": "Property ID", + "description": "The ID of the property.", + "type": "string" + }, + "name": { + "title": "Name", + "description": "The name of the property.", + "type": "string" + }, + "value": { + "title": "Value", + "description": "The value of the property.", + "type": "string" + }, + "unitCode": { + "title": "Measurement unit", + "description": "The unit of measurement for the value.", + "type": "string" + }, + "description": { + "title": "Description", + "description": "A description of the property.", + "type": "string" + }, + "minValue": { + "title": "Minimum value", + "description": "The minimum allowed value for the property.", + "type": "number" + }, + "maxValue": { + "title": "Maximum value", + "description": "The maximum allowed value for the property.", + "type": "number" + }, + "measurementTechnique": { + "title": "Measurement technique", + "description": "A technique or technology used in a measurement.", + "type": "string" + } + }, + "required": [ + "name", + "value" + ] + } + } + ] + }, + "unitCode": { + "title": "Measurement unit", + "description": "The unit of measurement for the value.", + "type": "string" + }, + "description": { + "title": "Description", + "description": "A description of the property.", + "type": "string" + }, + "minValue": { + "title": "Minimum value", + "description": "The minimum allowed value for the property.", + "type": "number" + }, + "maxValue": { + "title": "Maximum value", + "description": "The maximum allowed value for the property.", + "type": "number" + }, + "measurementTechnique": { + "title": "Measurement technique", + "description": "A technique or technology used in a measurement.", + "type": "string" + } + }, + "required": [ + "name", + "value" + ] + } + } + } + }, + "variableMeasured": { + "title": "Variables measured", + "description": "Measured variables.", + "type": "array", + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "title": "PropertyValue", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A property-value pair.", + "default": "PropertyValue", + "const": "PropertyValue", + "type": "string" + }, + "propertyID": { + "title": "Property ID", + "description": "The ID of the property.", + "type": "string" + }, + "name": { + "title": "Name", + "description": "The name of the property.", + "type": "string" + }, + "value": { + "title": "Value", + "description": "The value of the property.", + "anyOf": [ + { + "type": "string" + }, + { + "title": "PropertyValue", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A property-value pair.", + "default": "PropertyValue", + "const": "PropertyValue", + "type": "string" + }, + "propertyID": { + "title": "Property ID", + "description": "The ID of the property.", + "type": "string" + }, + "name": { + "title": "Name", + "description": "The name of the property.", + "type": "string" + }, + "value": { + "title": "Value", + "description": "The value of the property.", + "type": "string" + }, + "unitCode": { + "title": "Measurement unit", + "description": "The unit of measurement for the value.", + "type": "string" + }, + "description": { + "title": "Description", + "description": "A description of the property.", + "type": "string" + }, + "minValue": { + "title": "Minimum value", + "description": "The minimum allowed value for the property.", + "type": "number" + }, + "maxValue": { + "title": "Maximum value", + "description": "The maximum allowed value for the property.", + "type": "number" + }, + "measurementTechnique": { + "title": "Measurement technique", + "description": "A technique or technology used in a measurement.", + "type": "string" + } + }, + "required": [ + "name", + "value" + ] + }, + { + "type": "array", + "items": { + "title": "PropertyValue", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A property-value pair.", + "default": "PropertyValue", + "const": "PropertyValue", + "type": "string" + }, + "propertyID": { + "title": "Property ID", + "description": "The ID of the property.", + "type": "string" + }, + "name": { + "title": "Name", + "description": "The name of the property.", + "type": "string" + }, + "value": { + "title": "Value", + "description": "The value of the property.", + "type": "string" + }, + "unitCode": { + "title": "Measurement unit", + "description": "The unit of measurement for the value.", + "type": "string" + }, + "description": { + "title": "Description", + "description": "A description of the property.", + "type": "string" + }, + "minValue": { + "title": "Minimum value", + "description": "The minimum allowed value for the property.", + "type": "number" + }, + "maxValue": { + "title": "Maximum value", + "description": "The maximum allowed value for the property.", + "type": "number" + }, + "measurementTechnique": { + "title": "Measurement technique", + "description": "A technique or technology used in a measurement.", + "type": "string" + } + }, + "required": [ + "name", + "value" + ] + } + } + ] + }, + "unitCode": { + "title": "Measurement unit", + "description": "The unit of measurement for the value.", + "type": "string" + }, + "description": { + "title": "Description", + "description": "A description of the property.", + "type": "string" + }, + "minValue": { + "title": "Minimum value", + "description": "The minimum allowed value for the property.", + "type": "number" + }, + "maxValue": { + "title": "Maximum value", + "description": "The maximum allowed value for the property.", + "type": "number" + }, + "measurementTechnique": { + "title": "Measurement technique", + "description": "A technique or technology used in a measurement.", + "type": "string" + } + }, + "required": [ + "name", + "value" + ] + } + ] + } + }, + "sourceOrganization": { + "title": "SourceOrganization", + "description": "The organization that provided the data for this dataset.", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "Organization", + "const": "Organization", + "type": "string" + }, + "name": { + "title": "Name", + "description": "Name of the organization that created the data.", + "type": "string" + }, + "url": { + "title": "URL", + "description": "A URL to the homepage for the organization.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "address": { + "title": "Address", + "description": "Full address for the organization - e.g., \u201c8200 Old Main Hill, Logan, UT 84322-8200\u201d.", + "type": "string" + } + }, + "required": [ + "name" + ] + } + }, + "required": [ + "name", + "url", + "associatedMedia", + "spatialCoverage" + ], + "definitions": { + "Affiliation": { + "title": "Affiliation", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "Organization", + "const": "Organization", + "type": "string" + }, + "name": { + "title": "Name", + "description": "Name of the organization the creator is affiliated with.", + "type": "string" + }, + "url": { + "title": "URL", + "description": "A URL to the homepage for the organization.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "address": { + "title": "Address", + "description": "Full address for the organization - e.g., \u201c8200 Old Main Hill, Logan, UT 84322-8200\u201d.", + "type": "string" + } + }, + "required": [ + "name" + ] + }, + "Creator": { + "title": "Creator", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A person.", + "default": "Person", + "const": "Person", + "type": "string" + }, + "name": { + "title": "Name", + "description": "A string containing the full name of the person. Personal name format: Family Name, Given Name.", + "type": "string" + }, + "email": { + "title": "Email", + "description": "A string containing an email address for the creator.", + "type": "string", + "format": "email" + }, + "identifier": { + "title": "Identifier", + "description": "ORCID identifier for creator.", + "pattern": "\\b\\d{4}-\\d{4}-\\d{4}-\\d{3}[0-9X]\\b", + "options": { + "placeholder": "e.g. '0000-0001-2345-6789'" + }, + "errorMessage": { + "pattern": "must match the ORCID pattern. e.g. '0000-0001-2345-6789'" + }, + "type": "string" + }, + "affiliation": { + "title": "Affiliation", + "description": "The affiliation of the creator with the organization.", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "Organization", + "const": "Organization", + "type": "string" + }, + "name": { + "title": "Name", + "description": "Name of the organization the creator is affiliated with.", + "type": "string" + }, + "url": { + "title": "URL", + "description": "A URL to the homepage for the organization.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "address": { + "title": "Address", + "description": "Full address for the organization - e.g., \u201c8200 Old Main Hill, Logan, UT 84322-8200\u201d.", + "type": "string" + } + }, + "required": [ + "name" + ] + } + }, + "required": [ + "name" + ] + }, + "Organization": { + "title": "Organization", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "Organization", + "const": "Organization", + "type": "string" + }, + "name": { + "title": "Name", + "description": "Name of the provider organization or repository.", + "type": "string" + }, + "url": { + "title": "URL", + "description": "A URL to the homepage for the organization.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "address": { + "title": "Address", + "description": "Full address for the organization - e.g., \u201c8200 Old Main Hill, Logan, UT 84322-8200\u201d.", + "type": "string" + } + }, + "required": [ + "name" + ] + }, + "License": { + "title": "License", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "Submission type can include various forms of content, such as datasets, software source code, digital documents, etc.", + "default": "CreativeWork", + "type": "string" + }, + "name": { + "title": "Name", + "description": "A text string indicating the name of the license under which the resource is shared.", + "type": "string" + }, + "url": { + "title": "URL", + "description": "A URL for a web page that describes the license.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "description": { + "title": "Description", + "description": "A text string describing the license or containing the text of the license itself.", + "type": "string" + } + }, + "required": [ + "name" + ] + }, + "Provider": { + "title": "Provider", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A person.", + "default": "Person", + "const": "Person", + "type": "string" + }, + "name": { + "title": "Name", + "description": "A string containing the full name of the person. Personal name format: Family Name, Given Name.", + "type": "string" + }, + "email": { + "title": "Email", + "description": "A string containing an email address for the provider.", + "type": "string", + "format": "email" + }, + "identifier": { + "title": "Identifier", + "description": "ORCID identifier for the person.", + "pattern": "\\b\\d{4}-\\d{4}-\\d{4}-\\d{3}[0-9X]\\b", + "options": { + "placeholder": "e.g. '0000-0001-2345-6789'" + }, + "errorMessage": { + "pattern": "must match the ORCID pattern. e.g. '0000-0001-2345-6789'" + }, + "type": "string" + }, + "affiliation": { + "title": "Affiliation", + "description": "The affiliation of the creator with the organization.", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "Organization", + "const": "Organization", + "type": "string" + }, + "name": { + "title": "Name", + "description": "Name of the organization the creator is affiliated with.", + "type": "string" + }, + "url": { + "title": "URL", + "description": "A URL to the homepage for the organization.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "address": { + "title": "Address", + "description": "Full address for the organization - e.g., \u201c8200 Old Main Hill, Logan, UT 84322-8200\u201d.", + "type": "string" + } + }, + "required": [ + "name" + ] + } + }, + "required": [ + "name" + ] + }, + "PublisherOrganization": { + "title": "PublisherOrganization", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "Organization", + "const": "Organization", + "type": "string" + }, + "name": { + "title": "Name", + "description": "Name of the publishing organization.", + "type": "string" + }, + "url": { + "title": "URL", + "description": "A URL to the homepage for the publisher organization or repository.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "address": { + "title": "Address", + "description": "Full address for the organization - e.g., \u201c8200 Old Main Hill, Logan, UT 84322-8200\u201d.", + "type": "string" + } + }, + "required": [ + "name" + ] + }, + "SubjectOf": { + "title": "SubjectOf", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "Submission type can include various forms of content, such as datasets, software source code, digital documents, etc.", + "default": "CreativeWork", + "type": "string" + }, + "name": { + "title": "Name or title", + "description": "Submission's name or title", + "type": "string" + }, + "url": { + "title": "URL", + "description": "The URL address that serves as a reference to access additional details related to the record. It is important to note that this type of metadata solely pertains to the record itself and may not necessarily be an integral component of the record, unlike the HasPart metadata.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "description": { + "title": "Description", + "description": "Information about a related resource that is about or describes this resource - e.g., a related metadata document describing the resource.", + "type": "string" + } + }, + "required": [ + "name" + ] + }, + "LanguageEnum": { + "title": "Language", + "description": "", + "enum": [ + "eng", + "esp" + ], + "type": "string" + }, + "Draft": { + "title": "Draft", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "DefinedTerm", + "type": "string" + }, + "name": { + "title": "Name", + "default": "Draft", + "type": "string" + }, + "description": { + "title": "Description", + "description": "The description of the item being defined.", + "default": "The resource is in draft state and should not be considered final. Content and metadata may change", + "readOnly": true, + "type": "string" + } + } + }, + "Incomplete": { + "title": "Incomplete", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "DefinedTerm", + "type": "string" + }, + "name": { + "title": "Name", + "default": "Incomplete", + "type": "string" + }, + "description": { + "title": "Description", + "description": "The description of the item being defined.", + "default": "Data collection is ongoing or the resource is not completed", + "readOnly": true, + "type": "string" + } + } + }, + "Obsolete": { + "title": "Obsolete", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "DefinedTerm", + "type": "string" + }, + "name": { + "title": "Name", + "default": "Obsolete", + "type": "string" + }, + "description": { + "title": "Description", + "description": "The description of the item being defined.", + "default": "The resource has been replaced by a newer version, or the resource is no longer considered applicable", + "readOnly": true, + "type": "string" + } + } + }, + "Published": { + "title": "Published", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "DefinedTerm", + "type": "string" + }, + "name": { + "title": "Name", + "default": "Published", + "type": "string" + }, + "description": { + "title": "Description", + "description": "The description of the item being defined.", + "default": "The resource has been permanently published and should be considered final and complete", + "readOnly": true, + "type": "string" + } + } + }, + "Grant": { + "title": "Grant", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "This metadata represents details about a grant or financial assistance provided to an individual(s) or organization(s) for supporting the work related to the record.", + "default": "MonetaryGrant", + "type": "string" + }, + "name": { + "title": "Name or title", + "description": "A text string indicating the name or title of the grant or financial assistance.", + "type": "string" + }, + "description": { + "title": "Description", + "description": "A text string describing the grant or financial assistance.", + "type": "string" + }, + "identifier": { + "title": "Funding identifier", + "description": "Grant award number or other identifier.", + "type": "string" + }, + "funder": { + "title": "Funding Organization", + "description": "The organization that provided the funding or sponsorship.", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "Organization", + "const": "Organization", + "type": "string" + }, + "name": { + "title": "Name", + "description": "Name of the organization.", + "type": "string" + }, + "url": { + "title": "URL", + "description": "A URL to the homepage for the organization.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "address": { + "title": "Address", + "description": "Full address for the organization - e.g., \u201c8200 Old Main Hill, Logan, UT 84322-8200\u201d.", + "type": "string" + } + }, + "required": [ + "name" + ] + } + }, + "required": [ + "name" + ] + }, + "HasPart": { + "title": "HasPart", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "Submission type can include various forms of content, such as datasets, software source code, digital documents, etc.", + "default": "CreativeWork", + "type": "string" + }, + "name": { + "title": "Name or title", + "description": "Submission's name or title", + "type": "string" + }, + "url": { + "title": "URL", + "description": "The URL address to the data resource.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "description": { + "title": "Description", + "description": "Information about a related resource that is part of this resource.", + "type": "string" + } + }, + "required": [ + "name" + ] + }, + "IsPartOf": { + "title": "IsPartOf", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "Submission type can include various forms of content, such as datasets, software source code, digital documents, etc.", + "default": "CreativeWork", + "type": "string" + }, + "name": { + "title": "Name or title", + "description": "Submission's name or title", + "type": "string" + }, + "url": { + "title": "URL", + "description": "The URL address to the data resource.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "description": { + "title": "Description", + "description": "Information about a related resource that this resource is a part of - e.g., a related collection.", + "type": "string" + } + }, + "required": [ + "name" + ] + }, + "MediaObjectPartOf": { + "title": "MediaObjectPartOf", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "Submission type can include various forms of content, such as datasets, software source code, digital documents, etc.", + "default": "CreativeWork", + "type": "string" + }, + "name": { + "title": "Name or title", + "description": "Submission's name or title", + "type": "string" + }, + "url": { + "title": "URL", + "description": "The URL address to the related metadata document.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "description": { + "title": "Description", + "description": "Information about a related metadata document.", + "type": "string" + } + }, + "required": [ + "name" + ] + }, + "MediaObject": { + "title": "MediaObject", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "An item that encodes the record.", + "default": "MediaObject", + "type": "string" + }, + "contentUrl": { + "title": "Content URL", + "description": "The direct URL link to access or download the actual content of the media object.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "encodingFormat": { + "title": "Encoding format", + "description": "Represents the specific file format in which the media is encoded.", + "type": "string" + }, + "contentSize": { + "title": "Content size", + "description": "Represents the file size, expressed in bytes, kilobytes, megabytes, or another unit of measurement.", + "type": "string" + }, + "name": { + "title": "Name", + "description": "The name of the media object (file).", + "type": "string" + }, + "sha256": { + "title": "SHA-256", + "description": "The SHA-256 hash of the media object.", + "type": "string" + }, + "isPartOf": { + "title": "MediaObjectPartOf", + "description": "Link to or citation for a related metadata document that this media object is a part of", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "Submission type can include various forms of content, such as datasets, software source code, digital documents, etc.", + "default": "CreativeWork", + "type": "string" + }, + "name": { + "title": "Name or title", + "description": "Submission's name or title", + "type": "string" + }, + "url": { + "title": "URL", + "description": "The URL address to the related metadata document.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "description": { + "title": "Description", + "description": "Information about a related metadata document.", + "type": "string" + } + }, + "required": [ + "name" + ] + } + }, + "required": [ + "contentUrl", + "contentSize", + "name" + ] + }, + "PropertyValueBase": { + "title": "PropertyValue", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A property-value pair.", + "default": "PropertyValue", + "const": "PropertyValue", + "type": "string" + }, + "propertyID": { + "title": "Property ID", + "description": "The ID of the property.", + "type": "string" + }, + "name": { + "title": "Name", + "description": "The name of the property.", + "type": "string" + }, + "value": { + "title": "Value", + "description": "The value of the property.", + "type": "string" + }, + "unitCode": { + "title": "Measurement unit", + "description": "The unit of measurement for the value.", + "type": "string" + }, + "description": { + "title": "Description", + "description": "A description of the property.", + "type": "string" + }, + "minValue": { + "title": "Minimum value", + "description": "The minimum allowed value for the property.", + "type": "number" + }, + "maxValue": { + "title": "Maximum value", + "description": "The maximum allowed value for the property.", + "type": "number" + }, + "measurementTechnique": { + "title": "Measurement technique", + "description": "A technique or technology used in a measurement.", + "type": "string" + } + }, + "required": [ + "name", + "value" + ] + }, + "PropertyValue": { + "title": "PropertyValue", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A property-value pair.", + "default": "PropertyValue", + "const": "PropertyValue", + "type": "string" + }, + "propertyID": { + "title": "Property ID", + "description": "The ID of the property.", + "type": "string" + }, + "name": { + "title": "Name", + "description": "The name of the property.", + "type": "string" + }, + "value": { + "title": "Value", + "description": "The value of the property.", + "anyOf": [ + { + "type": "string" + }, + { + "title": "PropertyValue", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A property-value pair.", + "default": "PropertyValue", + "const": "PropertyValue", + "type": "string" + }, + "propertyID": { + "title": "Property ID", + "description": "The ID of the property.", + "type": "string" + }, + "name": { + "title": "Name", + "description": "The name of the property.", + "type": "string" + }, + "value": { + "title": "Value", + "description": "The value of the property.", + "type": "string" + }, + "unitCode": { + "title": "Measurement unit", + "description": "The unit of measurement for the value.", + "type": "string" + }, + "description": { + "title": "Description", + "description": "A description of the property.", + "type": "string" + }, + "minValue": { + "title": "Minimum value", + "description": "The minimum allowed value for the property.", + "type": "number" + }, + "maxValue": { + "title": "Maximum value", + "description": "The maximum allowed value for the property.", + "type": "number" + }, + "measurementTechnique": { + "title": "Measurement technique", + "description": "A technique or technology used in a measurement.", + "type": "string" + } + }, + "required": [ + "name", + "value" + ] + }, + { + "type": "array", + "items": { + "title": "PropertyValue", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A property-value pair.", + "default": "PropertyValue", + "const": "PropertyValue", + "type": "string" + }, + "propertyID": { + "title": "Property ID", + "description": "The ID of the property.", + "type": "string" + }, + "name": { + "title": "Name", + "description": "The name of the property.", + "type": "string" + }, + "value": { + "title": "Value", + "description": "The value of the property.", + "type": "string" + }, + "unitCode": { + "title": "Measurement unit", + "description": "The unit of measurement for the value.", + "type": "string" + }, + "description": { + "title": "Description", + "description": "A description of the property.", + "type": "string" + }, + "minValue": { + "title": "Minimum value", + "description": "The minimum allowed value for the property.", + "type": "number" + }, + "maxValue": { + "title": "Maximum value", + "description": "The maximum allowed value for the property.", + "type": "number" + }, + "measurementTechnique": { + "title": "Measurement technique", + "description": "A technique or technology used in a measurement.", + "type": "string" + } + }, + "required": [ + "name", + "value" + ] + } + } + ] + }, + "unitCode": { + "title": "Measurement unit", + "description": "The unit of measurement for the value.", + "type": "string" + }, + "description": { + "title": "Description", + "description": "A description of the property.", + "type": "string" + }, + "minValue": { + "title": "Minimum value", + "description": "The minimum allowed value for the property.", + "type": "number" + }, + "maxValue": { + "title": "Maximum value", + "description": "The maximum allowed value for the property.", + "type": "number" + }, + "measurementTechnique": { + "title": "Measurement technique", + "description": "A technique or technology used in a measurement.", + "type": "string" + } + }, + "required": [ + "name", + "value" + ] + }, + "TemporalCoverage": { + "title": "TemporalCoverage", + "type": "object", + "properties": { + "startDate": { + "title": "Start date", + "description": "A date/time object containing the instant corresponding to the commencement of the time interval (ISO8601 formatted date - YYYY-MM-DDTHH:MM).", + "type": "string", + "format": "date-time" + }, + "endDate": { + "title": "End date", + "description": "A date/time object containing the instant corresponding to the termination of the time interval (ISO8601 formatted date - YYYY-MM-DDTHH:MM). If the ending date is left off, that means the temporal coverage is ongoing.", + "type": "string", + "format": "date-time" + } + }, + "required": [ + "startDate" + ] + }, + "GeoCoordinates": { + "title": "GeoCoordinates", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "Geographic coordinates that represent a specific location on the Earth's surface. GeoCoordinates typically consists of two components: latitude and longitude.", + "default": "GeoCoordinates", + "type": "string" + }, + "latitude": { + "title": "Latitude", + "description": "Represents the angular distance of a location north or south of the equator, measured in degrees and ranges from -90 to +90 degrees.", + "type": "number" + }, + "longitude": { + "title": "Longitude", + "description": "Represents the angular distance of a location east or west of the Prime Meridian, measured in degrees and ranges from -180 to +180 degrees.", + "type": "number" + } + }, + "required": [ + "latitude", + "longitude" + ] + }, + "GeoShape": { + "title": "GeoShape", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A structured representation that describes the coordinates of a geographic feature.", + "default": "GeoShape", + "type": "string" + }, + "box": { + "title": "Box", + "description": "A box is a rectangular region defined by a pair of coordinates representing the southwest and northeast corners of the box.", + "type": "string" + } + }, + "required": [ + "box" + ] + }, + "Place": { + "title": "Place", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "Represents the focus area of the record's content.", + "default": "Place", + "type": "string" + }, + "name": { + "title": "Name", + "description": "Name of the place.", + "type": "string" + }, + "geo": { + "title": "Geo", + "description": "Specifies the geographic coordinates of the place in the form of a point location, line, or area coverage extent.", + "anyOf": [ + { + "title": "GeoCoordinates", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "Geographic coordinates that represent a specific location on the Earth's surface. GeoCoordinates typically consists of two components: latitude and longitude.", + "default": "GeoCoordinates", + "type": "string" + }, + "latitude": { + "title": "Latitude", + "description": "Represents the angular distance of a location north or south of the equator, measured in degrees and ranges from -90 to +90 degrees.", + "type": "number" + }, + "longitude": { + "title": "Longitude", + "description": "Represents the angular distance of a location east or west of the Prime Meridian, measured in degrees and ranges from -180 to +180 degrees.", + "type": "number" + } + }, + "required": [ + "latitude", + "longitude" + ] + }, + { + "title": "GeoShape", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A structured representation that describes the coordinates of a geographic feature.", + "default": "GeoShape", + "type": "string" + }, + "box": { + "title": "Box", + "description": "A box is a rectangular region defined by a pair of coordinates representing the southwest and northeast corners of the box.", + "type": "string" + } + }, + "required": [ + "box" + ] + } + ] + }, + "additionalProperty": { + "title": "Additional properties", + "description": "Additional properties of the place.", + "default": [], + "type": "array", + "items": { + "title": "PropertyValue", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A property-value pair.", + "default": "PropertyValue", + "const": "PropertyValue", + "type": "string" + }, + "propertyID": { + "title": "Property ID", + "description": "The ID of the property.", + "type": "string" + }, + "name": { + "title": "Name", + "description": "The name of the property.", + "type": "string" + }, + "value": { + "title": "Value", + "description": "The value of the property.", + "anyOf": [ + { + "type": "string" + }, + { + "title": "PropertyValue", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A property-value pair.", + "default": "PropertyValue", + "const": "PropertyValue", + "type": "string" + }, + "propertyID": { + "title": "Property ID", + "description": "The ID of the property.", + "type": "string" + }, + "name": { + "title": "Name", + "description": "The name of the property.", + "type": "string" + }, + "value": { + "title": "Value", + "description": "The value of the property.", + "type": "string" + }, + "unitCode": { + "title": "Measurement unit", + "description": "The unit of measurement for the value.", + "type": "string" + }, + "description": { + "title": "Description", + "description": "A description of the property.", + "type": "string" + }, + "minValue": { + "title": "Minimum value", + "description": "The minimum allowed value for the property.", + "type": "number" + }, + "maxValue": { + "title": "Maximum value", + "description": "The maximum allowed value for the property.", + "type": "number" + }, + "measurementTechnique": { + "title": "Measurement technique", + "description": "A technique or technology used in a measurement.", + "type": "string" + } + }, + "required": [ + "name", + "value" + ] + }, + { + "type": "array", + "items": { + "title": "PropertyValue", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "description": "A property-value pair.", + "default": "PropertyValue", + "const": "PropertyValue", + "type": "string" + }, + "propertyID": { + "title": "Property ID", + "description": "The ID of the property.", + "type": "string" + }, + "name": { + "title": "Name", + "description": "The name of the property.", + "type": "string" + }, + "value": { + "title": "Value", + "description": "The value of the property.", + "type": "string" + }, + "unitCode": { + "title": "Measurement unit", + "description": "The unit of measurement for the value.", + "type": "string" + }, + "description": { + "title": "Description", + "description": "A description of the property.", + "type": "string" + }, + "minValue": { + "title": "Minimum value", + "description": "The minimum allowed value for the property.", + "type": "number" + }, + "maxValue": { + "title": "Maximum value", + "description": "The maximum allowed value for the property.", + "type": "number" + }, + "measurementTechnique": { + "title": "Measurement technique", + "description": "A technique or technology used in a measurement.", + "type": "string" + } + }, + "required": [ + "name", + "value" + ] + } + } + ] + }, + "unitCode": { + "title": "Measurement unit", + "description": "The unit of measurement for the value.", + "type": "string" + }, + "description": { + "title": "Description", + "description": "A description of the property.", + "type": "string" + }, + "minValue": { + "title": "Minimum value", + "description": "The minimum allowed value for the property.", + "type": "number" + }, + "maxValue": { + "title": "Maximum value", + "description": "The maximum allowed value for the property.", + "type": "number" + }, + "measurementTechnique": { + "title": "Measurement technique", + "description": "A technique or technology used in a measurement.", + "type": "string" + } + }, + "required": [ + "name", + "value" + ] + } + } + } + }, + "SourceOrganization": { + "title": "SourceOrganization", + "type": "object", + "properties": { + "@type": { + "title": "@Type", + "default": "Organization", + "const": "Organization", + "type": "string" + }, + "name": { + "title": "Name", + "description": "Name of the organization that created the data.", + "type": "string" + }, + "url": { + "title": "URL", + "description": "A URL to the homepage for the organization.", + "minLength": 1, + "maxLength": 2083, + "type": "string", + "pattern": "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$", + "errorMessage": { + "pattern": "must match format \"url\"" + } + }, + "address": { + "title": "Address", + "description": "Full address for the organization - e.g., \u201c8200 Old Main Hill, Logan, UT 84322-8200\u201d.", + "type": "string" + } + }, + "required": [ + "name" + ] + } + } +} \ No newline at end of file From 9b9a20cf8ab77f6e0a28b367750c87553c431c9a Mon Sep 17 00:00:00 2001 From: pkdash Date: Tue, 9 Apr 2024 16:48:51 -0400 Subject: [PATCH 15/20] [#105] using RepositoryException for s3 fetch object operation errors --- api/adapters/s3.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/api/adapters/s3.py b/api/adapters/s3.py index ef36ca2..acb55e7 100644 --- a/api/adapters/s3.py +++ b/api/adapters/s3.py @@ -1,16 +1,18 @@ import json +from http import HTTPStatus from typing import Type import boto3 -from botocore.exceptions import ClientError as S3ClientError -from botocore.client import Config from botocore import UNSIGNED +from botocore.client import Config +from botocore.exceptions import ClientError as S3ClientError from api.adapters.base import ( AbstractRepositoryMetadataAdapter, AbstractRepositoryRequestHandler, ) from api.adapters.utils import RepositoryType, register_adapter +from api.exceptions import RepositoryException from api.models.catalog import T from api.models.user import Submission @@ -36,20 +38,21 @@ def get_metadata(self, record_id: str) -> dict: response = s3.get_object(Bucket=bucket_name, Key=file_key) except S3ClientError as ex: if ex.response["Error"]["Code"] == "NoSuchKey": - raise FileNotFoundError( - f"Expected metadata file not found in S3 bucket: {bucket_name}/{file_key}" + raise RepositoryException( + detail=f"Specified metadata file was not found in S3: {bucket_name}/{file_key}", + status_code=HTTPStatus.NOT_FOUND ) else: - raise ex + err_msg = f"Error accessing S3 file({bucket_name}/{file_key}): {str(ex)}" + raise RepositoryException(detail=err_msg, status_code=HTTPStatus.BAD_REQUEST) json_content = response["Body"].read().decode("utf-8") # parse the JSON content try: data = json.loads(json_content) except json.JSONDecodeError as ex: - raise ValueError( - f"Invalid JSON content in S3 file ({file_key}). Error: {str(ex)}" - ) + err_msg = f"Invalid JSON content in S3 file ({file_key}). Error: {str(ex)}" + raise RepositoryException(detail=err_msg, status_code=HTTPStatus.BAD_REQUEST) # remove additionalType field - this will be set by the schema model data.pop("additionalType", None) From 0c1142d0e057871f0e9c5f1fe640139fc52ce0fd Mon Sep 17 00:00:00 2001 From: pkdash Date: Tue, 9 Apr 2024 16:51:48 -0400 Subject: [PATCH 16/20] [#105] refactoring catalog route --- api/adapters/utils.py | 7 ++++ api/routes/catalog.py | 79 ++++++++++++++++--------------------------- 2 files changed, 37 insertions(+), 49 deletions(-) diff --git a/api/adapters/utils.py b/api/adapters/utils.py index ee2f7bc..482f9cd 100644 --- a/api/adapters/utils.py +++ b/api/adapters/utils.py @@ -21,3 +21,10 @@ def get_adapter_by_type(repository_type: RepositoryType) -> Union[AbstractReposi if adapter_cls: return adapter_cls() return None + + +def get_s3_object_url_path(endpoint_url: str, file_path: str, bucket: str) -> str: + endpoint_url = endpoint_url.rstrip("/") + if endpoint_url.endswith("amazonaws.com"): + return f"{endpoint_url}/{file_path}" + return f"{endpoint_url}/{bucket}/{file_path}" diff --git a/api/routes/catalog.py b/api/routes/catalog.py index 2d92eb2..44fa4b6 100644 --- a/api/routes/catalog.py +++ b/api/routes/catalog.py @@ -4,7 +4,7 @@ from fastapi import APIRouter, Depends, HTTPException, status from pydantic import BaseModel -from api.adapters.utils import get_adapter_by_type, RepositoryType +from api.adapters.utils import get_adapter_by_type, RepositoryType, get_s3_object_url_path from api.authentication.user import get_current_user from api.models.catalog import ( T, @@ -64,23 +64,9 @@ async def create_generic_dataset( response_model_exclude_none=True, ) async def get_generic_dataset(submission_id: PydanticObjectId): - submission: Submission = await Submission.find_one( - Submission.identifier == submission_id + document: GenericDatasetMetadataDOC = await _get_metadata_doc( + submission_id, GenericDatasetMetadataDOC ) - if submission is None: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="Metadata record was not found", - ) - - document: GenericDatasetMetadataDOC = await GenericDatasetMetadataDOC.get(submission.identifier) - if document is None: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="Metadata record was not found", - ) - - document = inject_repository_identifier(submission, document) return document @@ -92,23 +78,9 @@ async def get_generic_dataset(submission_id: PydanticObjectId): description="Retrieves a HydroShare resource metadata record by submission identifier", ) async def get_hydroshare_dataset(submission_id: PydanticObjectId): - submission: Submission = await Submission.find_one( - Submission.identifier == submission_id + document: HSResourceMetadataDOC = await _get_metadata_doc( + submission_id, HSResourceMetadataDOC ) - if submission is None: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="Metadata record was not found", - ) - - document: HSResourceMetadataDOC = await HSResourceMetadataDOC.get(submission.identifier) - if document is None: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="Metadata record was not found", - ) - - document = inject_repository_identifier(submission, document) return document @@ -152,7 +124,7 @@ async def update_dataset( @router.delete("/dataset/{submission_id}", response_model=dict, - summary="Delete a metadata record in catalog", + summary="Delete a metadata record from the catalog", description="Deletes a metadata record in catalog along with the submission record", ) async def delete_dataset( @@ -259,10 +231,7 @@ async def register_s3_netcdf_dataset(request_model: S3Path, user: Annotated[User bucket = request_model.bucket endpoint_url = request_model.endpoint_url endpoint_url = endpoint_url.rstrip("/") - if endpoint_url.endswith("amazonaws.com"): - identifier = f"{endpoint_url}/{path}" - else: - identifier = f"{endpoint_url}/{bucket}/{path}" + identifier = get_s3_object_url_path(endpoint_url, path, bucket) submission: Submission = user.submission_by_repository(repo_type=RepositoryType.S3, identifier=identifier) identifier = f"{endpoint_url}+{bucket}+{path}" dataset = await _save_to_db(repository_type=RepositoryType.S3, identifier=identifier, user=user, @@ -285,10 +254,7 @@ async def _save_to_db( ) if repository_type == RepositoryType.S3: s3_endpoint_url, bucket, path = identifier.split("+") - if s3_endpoint_url.endswith("amazonaws.com"): - identifier = f"{s3_endpoint_url}/{path}" - else: - identifier = f"{s3_endpoint_url}/{bucket}/{path}" + identifier = get_s3_object_url_path(s3_endpoint_url, path, bucket) if submission is None: # new registration await repo_dataset.insert() @@ -322,12 +288,27 @@ async def _save_to_db( async def _get_repo_meta_as_catalog_record(adapter, identifier: str, meta_model_type: Type[T]) -> T: - try: - metadata = await adapter.get_metadata(identifier) - except Exception as ex: - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail=f"{str(ex)}", - ) + metadata = await adapter.get_metadata(identifier) catalog_dataset: T = adapter.to_catalog_record(metadata, meta_model_type=meta_model_type) return catalog_dataset + + +async def _get_metadata_doc(submission_id: PydanticObjectId, meta_model_type: Type[T]) -> T: + submission: Submission = await Submission.find_one( + Submission.identifier == submission_id + ) + if submission is None: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Metadata record was not found", + ) + + document: meta_model_type = await meta_model_type.get(submission.identifier) + if document is None: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Metadata record was not found", + ) + + document = inject_repository_identifier(submission, document) + return document From 138aa8fe8503e235de12f0d77c214323447a4a56 Mon Sep 17 00:00:00 2001 From: pkdash Date: Tue, 9 Apr 2024 16:54:07 -0400 Subject: [PATCH 17/20] [#105] adding endpoint for registering s3 generic dataset --- api/routes/catalog.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/api/routes/catalog.py b/api/routes/catalog.py index 44fa4b6..7eef568 100644 --- a/api/routes/catalog.py +++ b/api/routes/catalog.py @@ -219,6 +219,27 @@ async def refresh_dataset_from_hydroshare( return dataset +@router.put("/repository/s3/generic", + response_model=GenericDatasetMetadataDOC, + summary="Register a S3 generic dataset metadata record in the catalog", + description="Retrieves the metadata for the generic dataset from S3 repository and creates a new metadata " + "record in the catalog", + status_code=status.HTTP_201_CREATED + ) +async def register_s3_generic_dataset(request_model: S3Path, user: Annotated[User, Depends(get_current_user)]): + path = request_model.path + bucket = request_model.bucket + endpoint_url = request_model.endpoint_url + endpoint_url = endpoint_url.rstrip("/") + identifier = get_s3_object_url_path(endpoint_url, path, bucket) + submission: Submission = user.submission_by_repository(repo_type=RepositoryType.S3, identifier=identifier) + identifier = f"{endpoint_url}+{bucket}+{path}" + dataset = await _save_to_db(repository_type=RepositoryType.S3, identifier=identifier, user=user, + meta_model_type=GenericDatasetMetadataDOC, + submission=submission) + return dataset + + @router.put("/repository/s3/netcdf", response_model=NetCDFMetadataDOC, summary="Register a S3 NetCDF dataset metadata record in the catalog", From 86cda5692184d899758aa41f7f393b4e1e04aa20 Mon Sep 17 00:00:00 2001 From: pkdash Date: Tue, 9 Apr 2024 16:55:16 -0400 Subject: [PATCH 18/20] [#105] endpoint for retrieving netcdf metadata catalog record --- api/routes/catalog.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/api/routes/catalog.py b/api/routes/catalog.py index 7eef568..c027717 100644 --- a/api/routes/catalog.py +++ b/api/routes/catalog.py @@ -70,6 +70,20 @@ async def get_generic_dataset(submission_id: PydanticObjectId): return document +@router.get( + "/dataset/netcdf/{submission_id}", + response_model=NetCDFMetadataDOC, + summary="Get a netcdf dataset metadata record", + description="Retrieves a netcdf dataset metadata record by submission identifier", + response_model_exclude_none=True, +) +async def get_generic_dataset(submission_id: PydanticObjectId): + document: NetCDFMetadataDOC = await _get_metadata_doc( + submission_id, NetCDFMetadataDOC + ) + return document + + @router.get( "/dataset/hs-resource/{submission_id}", response_model=HSResourceMetadataDOC, From 1429d52e8b231a725d653fad5a3b6c29d9ff91ff Mon Sep 17 00:00:00 2001 From: pkdash Date: Tue, 9 Apr 2024 16:56:42 -0400 Subject: [PATCH 19/20] [#105] test for registering s3 generic dataset --- tests/test_dataset_routes.py | 50 +++++++++++++++++++++++++++++++----- 1 file changed, 43 insertions(+), 7 deletions(-) diff --git a/tests/test_dataset_routes.py b/tests/test_dataset_routes.py index 46f5081..df14d2b 100644 --- a/tests/test_dataset_routes.py +++ b/tests/test_dataset_routes.py @@ -165,7 +165,32 @@ async def test_get_datasets_exclude_none(client_test, dataset_data): @pytest.mark.asyncio -async def test_register_s3_netcdf_dataset(client_test): +async def test_register_minio_s3_generic_dataset(client_test): + """Testing registering metadata for a generic dataset stored on minIO s3""" + + # set the path to the generic metadata file on minIO s3 + s3_path = { + "path": "data/.hs/dataset_metadata.json", + "bucket": "catalog-api-test", + "endpoint_url": "https://api.minio.cuahsi.io/", + } + + dataset_response = await client_test.put( + "api/catalog/repository/s3/generic", json=s3_path + ) + assert dataset_response.status_code == 201 + ds_metadata = dataset_response.json() + expected_repository_identifier = f"{s3_path['endpoint_url']}{s3_path['bucket']}/{s3_path['path']}" + assert ds_metadata["repository_identifier"] == expected_repository_identifier + + # retrieve the record from the db + record_id = ds_metadata.get('_id') + response = await client_test.get(f"api/catalog/dataset/generic/{record_id}") + assert response.status_code == 200 + + +@pytest.mark.asyncio +async def test_register_minio_s3_netcdf_dataset(client_test): """Testing registering metadata for a netcdf dataset stored on minIO""" # set the path to the netcdf file on s3 (minIO) @@ -177,9 +202,15 @@ async def test_register_s3_netcdf_dataset(client_test): dataset_response = await client_test.put("api/catalog/repository/s3/netcdf", json=s3_path) assert dataset_response.status_code == 201 - response_data = dataset_response.json() - assert response_data["additionalType"] == "NetCDF" - assert response_data["repository_identifier"] == f"{s3_path['endpoint_url']}{s3_path['bucket']}/{s3_path['path']}" + ds_metadata = dataset_response.json() + assert ds_metadata["additionalType"] == "NetCDF" + expected_repository_identifier = f"{s3_path['endpoint_url']}{s3_path['bucket']}/{s3_path['path']}" + assert ds_metadata["repository_identifier"] == expected_repository_identifier + + # retrieve the record from the db + record_id = ds_metadata.get('_id') + response = await client_test.get(f"api/catalog/dataset/netcdf/{record_id}") + assert response.status_code == 200 @pytest.mark.asyncio @@ -197,9 +228,14 @@ async def test_register_aws_s3_netcdf_dataset(client_test): "api/catalog/repository/s3/netcdf", json=s3_path ) assert dataset_response.status_code == 201 - response_data = dataset_response.json() - assert response_data["additionalType"] == "NetCDF" - assert response_data["repository_identifier"] == f"{s3_path['endpoint_url']}{s3_path['path']}" + ds_metadata = dataset_response.json() + assert ds_metadata["additionalType"] == "NetCDF" + assert ds_metadata["repository_identifier"] == f"{s3_path['endpoint_url']}{s3_path['path']}" + + # retrieve the record from the db + record_id = ds_metadata.get('_id') + response = await client_test.get(f"api/catalog/dataset/netcdf/{record_id}") + assert response.status_code == 200 @pytest.mark.parametrize("multiple", [True, False]) From bb5eaa3ee1406cff835ef4e436e03b57281fb326 Mon Sep 17 00:00:00 2001 From: pkdash Date: Thu, 6 Jun 2024 12:28:55 -0400 Subject: [PATCH 20/20] [#105] fixing the aws s3 endpoint url paths in tests --- tests/test_dataset_routes.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/tests/test_dataset_routes.py b/tests/test_dataset_routes.py index e1b4e9f..7759f64 100644 --- a/tests/test_dataset_routes.py +++ b/tests/test_dataset_routes.py @@ -74,7 +74,7 @@ async def test_create_dataset_s3(client_test, dataset_data, test_user_access_tok s3_path = { "path": "data/.hs/dataset_metadata.json", "bucket": "iguide-catalog", - "endpoint_url": "https://iguide-catalog.s3.us-west-2.amazonaws.com/", + "endpoint_url": "https://s3.us-west-2.amazonaws.com/", } payload = { @@ -124,7 +124,7 @@ async def test_update_dataset_s3(client_test, dataset_data, test_user_access_tok s3_path = { "path": "data/.hs/dataset_metadata.json", "bucket": "iguide-catalog", - "endpoint_url": "https://iguide-catalog.s3.us-west-2.amazonaws.com/", + "endpoint_url": "https://s3.us-west-2.amazonaws.com/", } payload = { @@ -160,7 +160,7 @@ async def test_update_dataset_s3(client_test, dataset_data, test_user_access_tok s3_path = { "path": "data/.hs/dataset_metadata-updated.json", "bucket": "iguide-catalog-updated", - "endpoint_url": "https://iguide-catalog-updated.s3.us-west-2.amazonaws.com/", + "endpoint_url": "https://s3.us-west-2.amazonaws.com/", } payload = { @@ -329,7 +329,7 @@ async def test_get_datasets_different_submission_types(client_test, dataset_data s3_path = { "path": "data/.hs/dataset_metadata.json", "bucket": "iguide-catalog", - "endpoint_url": "https://iguide-catalog.s3.us-west-2.amazonaws.com/", + "endpoint_url": "https://s3.us-west-2.amazonaws.com/", } payload = { @@ -438,7 +438,6 @@ async def test_register_minio_s3_netcdf_dataset(client_test): assert response.status_code == 200 -@pytest.mark.skip("This test is failing due to AWS bucket access denied error - need to fix") @pytest.mark.asyncio async def test_register_aws_s3_netcdf_dataset(client_test): """Testing registering metadata route (POST: api/catalog/repository/s3/netcdf) for a netcdf dataset on AWS s3""" @@ -447,7 +446,7 @@ async def test_register_aws_s3_netcdf_dataset(client_test): s3_path = { "path": "data/.hs/netcdf/netcdf_valid.nc.json", "bucket": "iguide-catalog", - "endpoint_url": "https://iguide-catalog.s3.us-west-2.amazonaws.com/", + "endpoint_url": "https://s3.us-west-2.amazonaws.com/", } dataset_response = await client_test.post( @@ -515,10 +514,12 @@ async def test_get_submissions_2(client_test, dataset_data): dataset_response = await client_test.post("api/catalog/dataset/generic", json=dataset_data) assert dataset_response.status_code == 201 dataset_response_data = dataset_response.json() + + # add a dataset record to the db simulation S3 submission s3_path = { "path": "data/.hs/dataset_metadata.json", "bucket": "iguide-catalog", - "endpoint_url": "https://iguide-catalog.s3.us-west-2.amazonaws.com/", + "endpoint_url": "https://s3.us-west-2.amazonaws.com/", } payload = {