From 7d6970e3d59ac6eb532216e9781dcb55eea26cc8 Mon Sep 17 00:00:00 2001 From: pkdash Date: Mon, 4 Sep 2023 16:32:49 -0400 Subject: [PATCH 01/20] [#44] initial upgrade with all tests passing --- hsmodels/schemas/__init__.py | 96 +++++++++------- hsmodels/schemas/aggregations.py | 78 +++++++------ hsmodels/schemas/base_models.py | 32 +++--- hsmodels/schemas/fields.py | 94 ++++++++-------- hsmodels/schemas/rdf/aggregations.py | 106 +++++++++--------- hsmodels/schemas/rdf/fields.py | 86 +++++++++++--- hsmodels/schemas/rdf/resource.py | 55 ++++----- hsmodels/schemas/rdf/root_validators.py | 2 +- hsmodels/schemas/rdf/validators.py | 4 +- hsmodels/schemas/resource.py | 50 ++++----- requirements.txt | 2 +- tests/data/json/modelprogram.json | 4 +- tests/data/json/resource.json | 14 +-- tests/data/metadata/fileset_meta.xml | 2 +- .../data/metadata/geographicfeature_meta.xml | 2 +- tests/data/metadata/geographicraster_meta.xml | 2 +- tests/data/metadata/modelprogram_meta.xml | 4 +- tests/data/metadata/multidimensional_meta.xml | 2 +- .../referencedtimeseries.refts_meta.xml | 2 +- tests/data/metadata/resourcemetadata.xml | 14 +-- tests/data/metadata/singlefile_meta.xml | 2 +- tests/test_defaults.py | 10 +- tests/test_metadata.py | 14 +-- tests/test_metadata_json.py | 8 +- tests/test_schemas.py | 20 ++-- tests/test_validation.py | 60 +++++----- 26 files changed, 431 insertions(+), 334 deletions(-) diff --git a/hsmodels/schemas/__init__.py b/hsmodels/schemas/__init__.py index f1b7c05..004d7eb 100644 --- a/hsmodels/schemas/__init__.py +++ b/hsmodels/schemas/__init__.py @@ -1,7 +1,8 @@ import inspect from enum import Enum -from pydantic import AnyUrl, BaseModel +from pydantic import BaseModel +from pydantic_core import Url from rdflib import Graph, Literal, URIRef from hsmodels.namespaces import DC, HSTERMS, ORE, RDF, RDFS1 @@ -71,7 +72,7 @@ def load_rdf(rdf_str, file_format='xml'): else: rdf_metadata = _parse(schema, g) if schema in user_schemas.keys(): - return user_schemas[schema](**rdf_metadata.dict()) + return user_schemas[schema](**rdf_metadata.model_dump(exclude_none=True)) return rdf_metadata raise Exception("Could not find schema for \n{}".format(rdf_str)) @@ -84,7 +85,7 @@ def parse_file(schema, file, file_format='xml', subject=None): def rdf_graph(schema): for rdf_schema, user_schema in user_schemas.items(): if isinstance(schema, user_schema): - return _rdf_graph(rdf_schema(**schema.dict(to_rdf=True)), Graph()) + return _rdf_graph(rdf_schema(**schema.model_dump(to_rdf=True)), Graph()) return _rdf_graph(schema, Graph()) @@ -93,23 +94,26 @@ def rdf_string(schema, rdf_format='pretty-xml'): def _rdf_fields(schema): - for f in schema.__fields__.values(): - if f.alias not in ['rdf_subject', 'rdf_type', 'label', 'dc_type']: - predicate = f.field_info.extra.get('rdf_predicate', None) + for fname, finfo in schema.model_fields.items(): + if fname not in ['rdf_subject', 'rdf_type', 'label', 'dc_type']: + predicate = None + if finfo.json_schema_extra: + predicate = finfo.json_schema_extra.get('rdf_predicate', None) + if not predicate: - config_field_info = schema.Config.fields.get(f.name, None) + config_field_info = schema.model_config['fields'].get(fname, None) if isinstance(config_field_info, dict): predicate = config_field_info.get('rdf_predicate', None) if not predicate: raise Exception( "Schema configuration error for {}, all fields must specify a rdf_predicate".format(schema) ) - yield f, predicate + yield finfo, fname, predicate def _rdf_graph(schema, graph=None): - for f, predicate in _rdf_fields(schema): - values = getattr(schema, f.name, None) + for f, fname, predicate in _rdf_fields(schema): + values = getattr(schema, fname, None) if values is not None: if not isinstance(values, list): # handle single values as a list to simplify @@ -122,8 +126,8 @@ def _rdf_graph(schema, graph=None): graph = _rdf_graph(value, graph) else: # primitive value - if isinstance(value, AnyUrl): - value = URIRef(value) + if isinstance(value, Url): + value = URIRef(str(value)) elif isinstance(value, TermEnum): value = URIRef(value.value) elif isinstance(value, Enum): @@ -146,18 +150,21 @@ def get_args(t): def _parse(schema, metadata_graph, subject=None): - def nested_class(field): - if field.sub_fields: - clazz = get_args(field.outer_type_)[0] - else: - clazz = field.outer_type_ - if inspect.isclass(clazz): - return issubclass(clazz, BaseModel) - return False + def get_nested_class(field): + origin = field.annotation + if origin and inspect.isclass(origin): + if issubclass(origin, BaseModel): # and origin.model_fields: + return origin + if get_args(origin): + clazz = get_args(origin)[0] + if inspect.isclass(clazz): + if issubclass(clazz, BaseModel): + return clazz + return None def class_rdf_type(schema): - if schema.__fields__['rdf_type']: - return schema.__fields__['rdf_type'].default + if schema.model_fields['rdf_type']: + return schema.model_fields['rdf_type'].default return None if not subject: @@ -168,34 +175,47 @@ def class_rdf_type(schema): subject = metadata_graph.value(predicate=RDF.type, object=target_class) if not subject: raise Exception("Could not find subject for predicate=RDF.type, object={}".format(target_class)) + kwargs = {} - for f, predicate in _rdf_fields(schema): + for f, name, predicate in _rdf_fields(schema): parsed = [] for value in metadata_graph.objects(subject=subject, predicate=predicate): - if nested_class(f): - if f.sub_fields: - # list - clazz = f.sub_fields[0].outer_type_ - else: - # single - clazz = f.outer_type_ - parsed_class = _parse(clazz, metadata_graph, value) + nested_clazz = get_nested_class(f) + if nested_clazz: + parsed_class = _parse(nested_clazz, metadata_graph, value) if parsed_class: parsed.append(parsed_class) - elif f.sub_fields: - parsed.append([]) else: - parsed_value = str(value.toPython()) - # primitive value + # not a nested class (primitive class and not a subclass of BaseModel) + + origin = f.annotation + origin_clazz = getattr(origin, '__origin__', None) + parsed_value = None + if origin_clazz is list: + clazz = origin.__args__[0] + if issubclass(clazz, BaseModel): + parsed_class = _parse(clazz, metadata_graph, value) + if parsed_class: + parsed.append(parsed_class) + else: + # primitive value + parsed_value = str(value.toPython()) + else: + # primitive value + parsed_value = str(value.toPython()) + if parsed_value: parsed.append(parsed_value) + if len(parsed) > 0: - if f.sub_fields: + origin = f.annotation + origin_clazz = getattr(origin, '__origin__', None) + if origin_clazz is list: # list - kwargs[f.name] = parsed + kwargs[name] = parsed else: # single - kwargs[f.name] = parsed[0] + kwargs[name] = parsed[0] if kwargs: instance = schema(**kwargs, rdf_subject=subject) return instance diff --git a/hsmodels/schemas/aggregations.py b/hsmodels/schemas/aggregations.py index 8588917..144c795 100644 --- a/hsmodels/schemas/aggregations.py +++ b/hsmodels/schemas/aggregations.py @@ -1,7 +1,7 @@ from datetime import date -from typing import Dict, List, Union +from typing import Dict, List, Optional, Union, Literal -from pydantic import AnyUrl, Field, root_validator, validator +from pydantic import AnyUrl, Field, model_validator, field_validator from hsmodels.schemas.base_models import BaseMetadata from hsmodels.schemas.enums import AggregationType @@ -59,24 +59,24 @@ class BaseAggregationMetadataIn(BaseMetadata): title="Extended metadata", description="A list of extended metadata elements expressed as key-value pairs", ) - spatial_coverage: Union[PointCoverage, BoxCoverage] = Field( + spatial_coverage: Optional[Union[PointCoverage, BoxCoverage]] = Field( default=None, title="Spatial coverage", description="An object containing the geospatial coverage for the aggregation expressed as either a bounding box or point", ) - period_coverage: PeriodCoverage = Field( + period_coverage: Optional[PeriodCoverage] = Field( default=None, title="Temporal coverage", description="An object containing the temporal coverage for a aggregation expressed as a date range", ) - _parse_additional_metadata = root_validator(pre=True, allow_reuse=True)(parse_additional_metadata) - _parse_coverages = root_validator(pre=True, allow_reuse=True)(split_coverages) + _parse_additional_metadata = model_validator(mode='before')(parse_additional_metadata) + _parse_coverages = model_validator(mode='before')(split_coverages) - _subjects_constraint = validator('subjects', allow_reuse=True)(subjects_constraint) - _language_constraint = validator('language', allow_reuse=True)(language_constraint) - _parse_spatial_coverage = validator("spatial_coverage", allow_reuse=True, pre=True)(parse_spatial_coverage) - _normalize_additional_metadata = root_validator(allow_reuse=True, pre=True)(normalize_additional_metadata) + _subjects_constraint = field_validator('subjects')(subjects_constraint) + _language_constraint = field_validator('language')(language_constraint) + _parse_spatial_coverage = field_validator("spatial_coverage", mode='before')(parse_spatial_coverage) + _normalize_additional_metadata = model_validator(mode='before')(normalize_additional_metadata) class GeographicRasterMetadataIn(BaseAggregationMetadataIn): @@ -106,14 +106,14 @@ class Config: title="Cell information", description="An object containing information about the raster grid cells" ) - _parse_spatial_reference = validator("spatial_reference", pre=True, allow_reuse=True)(parse_spatial_reference) + _parse_spatial_reference = field_validator("spatial_reference", mode='before')(parse_spatial_reference) class GeographicRasterMetadata(GeographicRasterMetadataIn): - _type = AggregationType.GeographicRasterAggregation + _type = Literal[AggregationType.GeographicRasterAggregation] type: AggregationType = Field( - const=True, + frozen=True, default=_type, title="Aggregation type", description="A string expressing the aggregation type from the list of HydroShare aggregation types", @@ -130,7 +130,7 @@ class GeographicRasterMetadata(GeographicRasterMetadataIn): description="An object containing information about the rights held in and over the aggregation and the license under which a aggregation is shared", ) - _parse_url = root_validator(pre=True, allow_reuse=True)(parse_url) + _parse_url = model_validator(mode='before')(parse_url) class GeographicFeatureMetadataIn(BaseAggregationMetadataIn): @@ -162,12 +162,12 @@ class Config: description="An object containing spatial reference information for the dataset", ) - _parse_spatial_reference = validator("spatial_reference", pre=True, allow_reuse=True)(parse_spatial_reference) + _parse_spatial_reference = field_validator("spatial_reference", mode='before')(parse_spatial_reference) class GeographicFeatureMetadata(GeographicFeatureMetadataIn): type: AggregationType = Field( - const=True, + frozen=True, default=AggregationType.GeographicFeatureAggregation, title="Aggregation type", description="A string expressing the aggregation type from the list of HydroShare aggregation types", @@ -184,7 +184,7 @@ class GeographicFeatureMetadata(GeographicFeatureMetadataIn): description="An object containing information about the rights held in and over the aggregation and the license under which a aggregation is shared", ) - _parse_url = root_validator(pre=True, allow_reuse=True)(parse_url) + _parse_url = model_validator(mode='before')(parse_url) class MultidimensionalMetadataIn(BaseAggregationMetadataIn): @@ -212,14 +212,14 @@ class Config: description="An object containing spatial reference information for the dataset", ) - _parse_spatial_reference = validator("spatial_reference", pre=True, allow_reuse=True)( + _parse_spatial_reference = field_validator("spatial_reference", mode='before')( parse_multidimensional_spatial_reference ) class MultidimensionalMetadata(MultidimensionalMetadataIn): type: AggregationType = Field( - const=True, + frozen=True, default=AggregationType.MultidimensionalAggregation, title="Aggregation type", description="A string expressing the aggregation type from the list of HydroShare aggregation types", @@ -236,7 +236,7 @@ class MultidimensionalMetadata(MultidimensionalMetadataIn): description="An object containing information about the rights held in and over the aggregation and the license under which a aggregation is shared", ) - _parse_url = root_validator(pre=True, allow_reuse=True)(parse_url) + _parse_url = model_validator(mode='before')(parse_url) class ReferencedTimeSeriesMetadataIn(BaseAggregationMetadataIn): @@ -256,7 +256,7 @@ class Config: class ReferencedTimeSeriesMetadata(ReferencedTimeSeriesMetadataIn): type: AggregationType = Field( - const=True, + frozen=True, default=AggregationType.ReferencedTimeSeriesAggregation, title="Aggregation type", description="A string expressing the aggregation type from the list of HydroShare aggregation types", @@ -273,7 +273,7 @@ class ReferencedTimeSeriesMetadata(ReferencedTimeSeriesMetadataIn): description="An object containing information about the rights held in and over the aggregation and the license under which a aggregation is shared", ) - _parse_url = root_validator(pre=True, allow_reuse=True)(parse_url) + _parse_url = model_validator(mode='before')(parse_url) class FileSetMetadataIn(BaseAggregationMetadataIn): @@ -293,7 +293,7 @@ class Config: class FileSetMetadata(FileSetMetadataIn): type: AggregationType = Field( - const=True, + frozen=True, default=AggregationType.FileSetAggregation, title="Aggregation type", description="A string expressing the aggregation type from the list of HydroShare aggregation types", @@ -310,7 +310,7 @@ class FileSetMetadata(FileSetMetadataIn): description="An object containing information about the rights held in and over the aggregation and the license under which a aggregation is shared", ) - _parse_url = root_validator(pre=True, allow_reuse=True)(parse_url) + _parse_url = model_validator(mode='before')(parse_url) class SingleFileMetadataIn(BaseAggregationMetadataIn): @@ -329,7 +329,7 @@ class Config: class SingleFileMetadata(SingleFileMetadataIn): type: AggregationType = Field( - const=True, + frozen=True, default=AggregationType.SingleFileAggregation, title="Aggregation type", description="A string expressing the aggregation type from the list of HydroShare aggregation types", @@ -346,7 +346,7 @@ class SingleFileMetadata(SingleFileMetadataIn): description="An object containing information about the rights held in and over the aggregation and the license under which a aggregation is shared", ) - _parse_url = root_validator(pre=True, allow_reuse=True)(parse_url) + _parse_url = model_validator(mode='before')(parse_url) class TimeSeriesMetadataIn(BaseAggregationMetadataIn): @@ -373,12 +373,12 @@ class Config: abstract: str = Field(default=None, title="Abstract", description="A string containing a summary of a aggregation") - _parse_abstract = root_validator(pre=True, allow_reuse=True)(parse_abstract) + _parse_abstract = model_validator(mode='before')(parse_abstract) class TimeSeriesMetadata(TimeSeriesMetadataIn): type: AggregationType = Field( - const=True, + frozen=True, default=AggregationType.TimeSeriesAggregation, title="Aggregation type", description="A string expressing the aggregation type from the list of HydroShare aggregation types", @@ -395,7 +395,7 @@ class TimeSeriesMetadata(TimeSeriesMetadataIn): description="An object containing information about the rights held in and over the aggregation and the license under which a aggregation is shared", ) - _parse_url = root_validator(pre=True, allow_reuse=True)(parse_url) + _parse_url = model_validator(mode='before')(parse_url) class ModelProgramMetadataIn(BaseAggregationMetadataIn): @@ -452,12 +452,22 @@ class Config: description='A url to the JSON metadata schema for the model program', ) - _parse_file_types = root_validator(pre=True, allow_reuse=True)(parse_file_types) + _parse_file_types = model_validator(mode='before')(parse_file_types) + + # @model_validator(mode='after') + # def url_to_string(self): + # if self.website is not None: + # self.website = str(self.website) + # if self.code_repository is not None: + # self.code_repository = str(self.code_repository) + # if self.program_schema_json is not None: + # self.program_schema_json = str(self.program_schema_json) + # return self class ModelProgramMetadata(ModelProgramMetadataIn): type: AggregationType = Field( - const=True, + frozen=True, default=AggregationType.ModelProgramAggregation, title="Aggregation type", description="A string expressing the aggregation type from the list of HydroShare aggregation types", @@ -474,7 +484,7 @@ class ModelProgramMetadata(ModelProgramMetadataIn): description="An object containing information about the rights held in and over the aggregation and the license under which a aggregation is shared", ) - _parse_url = root_validator(pre=True, allow_reuse=True)(parse_url) + _parse_url = model_validator(mode='before')(parse_url) class ModelInstanceMetadataIn(BaseAggregationMetadataIn): @@ -513,7 +523,7 @@ class Config: class ModelInstanceMetadata(ModelInstanceMetadataIn): type: AggregationType = Field( - const=True, + frozen=True, default=AggregationType.ModelInstanceAggregation, title="Aggregation type", description="A string expressing the aggregation type from the list of HydroShare aggregation types", @@ -530,4 +540,4 @@ class ModelInstanceMetadata(ModelInstanceMetadataIn): description="An object containing information about the rights held in and over the aggregation and the license under which a aggregation is shared", ) - _parse_url = root_validator(pre=True, allow_reuse=True)(parse_url) + _parse_url = model_validator(mode='before')(parse_url) diff --git a/hsmodels/schemas/base_models.py b/hsmodels/schemas/base_models.py index f83676c..c68cc05 100644 --- a/hsmodels/schemas/base_models.py +++ b/hsmodels/schemas/base_models.py @@ -1,22 +1,23 @@ from datetime import datetime -from typing import Any, Callable, Dict, Optional, Union +from typing import Any, Dict, Union from pydantic import BaseModel class BaseMetadata(BaseModel): - def dict( + def model_dump( self, *, include: Union['AbstractSetIntStr', 'MappingIntStrAny'] = None, exclude: Union['AbstractSetIntStr', 'MappingIntStrAny'] = None, by_alias: bool = False, - skip_defaults: bool = None, exclude_unset: bool = False, exclude_defaults: bool = False, exclude_none: bool = True, + round_trip: bool = False, + warnings: bool = False, to_rdf: bool = False, - ) -> 'DictStrAny': + ) -> dict[str, Any]: """ Generate a dictionary representation of the model, optionally specifying which fields to include or exclude. @@ -26,14 +27,15 @@ def dict( Override the default of exclude_none to True """ - d = super().dict( + d = super().model_dump( include=include, exclude=exclude, by_alias=by_alias, - skip_defaults=skip_defaults, exclude_unset=exclude_unset, exclude_defaults=exclude_defaults, exclude_none=exclude_none, + round_trip=round_trip, + warnings=warnings, ) if to_rdf and hasattr(self.Config, "schema_config"): @@ -44,18 +46,18 @@ def dict( d[field] = [{"key": key, "value": value} for key, value in field_value.items()] return d - def json( + def model_dump_json( self, *, + indent: Union[int, None] = None, include: Union['AbstractSetIntStr', 'MappingIntStrAny'] = None, exclude: Union['AbstractSetIntStr', 'MappingIntStrAny'] = None, by_alias: bool = False, - skip_defaults: bool = None, exclude_unset: bool = False, exclude_defaults: bool = False, exclude_none: bool = True, - encoder: Optional[Callable[[Any], Any]] = None, - **dumps_kwargs: Any, + round_trip: bool = False, + warnings: bool = False, ) -> str: """ Generate a JSON representation of the model, `include` and `exclude` arguments as per `dict()`. @@ -64,23 +66,23 @@ def json( Override the default of exclude_none to True """ - return super().json( + return super().model_dump_json( + indent=indent, include=include, exclude=exclude, by_alias=by_alias, - skip_defaults=skip_defaults, exclude_unset=exclude_unset, exclude_defaults=exclude_defaults, exclude_none=exclude_none, - encoder=encoder, - **dumps_kwargs, + round_trip=round_trip, + warnings=warnings, ) class Config: validate_assignment = True @staticmethod - def schema_extra(schema: Dict[str, Any], model) -> None: + def json_schema_extra(schema: Dict[str, Any], model) -> None: if hasattr(model.Config, "schema_config"): schema_config = model.Config.schema_config if "read_only" in schema_config: diff --git a/hsmodels/schemas/fields.py b/hsmodels/schemas/fields.py index b7150a3..a998b4f 100644 --- a/hsmodels/schemas/fields.py +++ b/hsmodels/schemas/fields.py @@ -1,7 +1,7 @@ from datetime import datetime -from typing import Dict +from typing import Dict, Literal, Optional -from pydantic import AnyUrl, EmailStr, Field, HttpUrl, root_validator, validator +from pydantic import AnyUrl, EmailStr, Field, HttpUrl, field_validator, model_validator from hsmodels.schemas import base_models from hsmodels.schemas.base_models import BaseMetadata @@ -25,7 +25,7 @@ class Config: description="String expressing the Full text citation, URL link for, or description of the related resource", ) - _parse_relation = root_validator(pre=True)(parse_relation) + _parse_relation = model_validator(mode='before')(parse_relation) class CellInformation(BaseMetadata): @@ -155,6 +155,12 @@ class Config: title="Homepage", description="An object containing the URL for website associated with the creator", ) + creator_order: int = Field( + default=None, + title="Creator order", + description="An integer to order creators", + allow_mutation=False, + ) hydroshare_user_id: int = Field( default=None, title="Hydroshare user id", @@ -166,9 +172,9 @@ class Config: title="Creator identifiers", description="A dictionary containing identifier types and URL links to alternative identifiers for the creator", ) - _description_validator = validator("hydroshare_user_id", pre=True)(validate_user_id) + _description_validator = field_validator("hydroshare_user_id", mode='before')(validate_user_id) - _split_identifiers = root_validator(pre=True, allow_reuse=True)(group_user_identifiers) + _split_identifiers = model_validator(mode='before')(group_user_identifiers) @classmethod def from_user(cls, user): @@ -223,7 +229,7 @@ class Config: description="A dictionary containing identifier types and URL links to alternative identiers for the contributor", ) - _split_identifiers = root_validator(pre=True, allow_reuse=True)(group_user_identifiers) + _split_identifiers = model_validator(mode='before')(group_user_identifiers) @classmethod def from_user(cls, user): @@ -274,37 +280,37 @@ class Config: title = 'Raster Band Metadata' name: str = Field(max_length=500, title="Name", description="A string containing the name of the raster band") - variable_name: str = Field( + variable_name: Optional[str] = Field( default=None, max_length=100, title="Variable name", description="A string containing the name of the variable represented by the raster band", ) - variable_unit: str = Field( + variable_unit: Optional[str] = Field( default=None, max_length=50, title="Variable unit", description="A string containing the units for the raster band variable", ) - no_data_value: str = Field( + no_data_value: Optional[str] = Field( default=None, title="Nodata value", description="A string containing the numeric nodata value for the raster band", ) - maximum_value: str = Field( + maximum_value: Optional[str] = Field( default=None, title="Maximum value", description="A string containing the maximum numeric value for the raster band", ) - comment: str = Field( + comment: Optional[str] = Field( default=None, title="Comment", description="A string containing a comment about the raster band" ) - method: str = Field( + method: Optional[str] = Field( default=None, title="Method", description="A string containing a description of the method used to create the raster band data", ) - minimum_value: str = Field( + minimum_value: Optional[str] = Field( default=None, title="Minimum value", description="A string containing the minimum numerica value for the raster dataset", @@ -532,7 +538,7 @@ class Config: method_link: AnyUrl = Field( default=None, title="Method link", - description="An object containg a URL that points to a website having a detailed description of the method", + description="An object containing a URL that points to a website having a detailed description of the method", ) @@ -671,7 +677,7 @@ class Config: title="UTC Offset", description="A floating point value that represents the time offset from UTC time in hours associated with the time series result value timestamps", ) - _parse_utc_offset = root_validator(pre=True, allow_reuse=True)(parse_utc_offset_value) + _parse_utc_offset = model_validator(mode='before')(parse_utc_offset_value) class BoxCoverage(base_models.BaseCoverage): @@ -685,12 +691,11 @@ class Config: schema_config = {'read_only': ['type']} - type: str = Field( + type: Literal['box'] = Field( default="box", - const=True, + frozen=True, title="Geographic coverage type", description="A string containing the type of geographic coverage", - allow_mutation=False, ) name: str = Field( default=None, @@ -731,12 +736,11 @@ class Config: description="A string containing the name of the projection used with any parameters required, such as ellipsoid parameters, datum, standard parallels and meridians, zone, etc.", ) - @root_validator - def compare_north_south(cls, values): - north, south = values["northlimit"], values["southlimit"] - if north < south: - raise ValueError(f"North latitude [{north}] must be greater than or equal to South latitude [{south}]") - return values + @model_validator(mode='after') + def compare_north_south(self): + if self.northlimit < self.southlimit: + raise ValueError(f"North latitude [{self.northlimit}] must be greater than or equal to South latitude [{self.southlimit}]") + return self class BoxSpatialReference(base_models.BaseCoverage): @@ -750,12 +754,11 @@ class Config: schema_config = {'read_only': ['type']} - type: str = Field( + type: Literal['box'] = Field( default="box", - const=True, + frozen=True, title="Spatial reference type", description="A string containing the type of spatial reference", - allow_mutation=False, ) name: str = Field( default=None, @@ -828,14 +831,13 @@ class Config: schema_config = {'read_only': ['type']} - type: str = Field( + type: Literal['point'] = Field( default="point", - const=True, + frozen=True, title="Geographic coverage type", description="A string containing the type of geographic coverage", - allow_mutation=False, ) - name: str = Field( + name: Optional[str] = Field( default=None, title="Name", description="A string containing a name for the place associated with the geographic coverage", @@ -866,12 +868,11 @@ class Config: schema_config = {'read_only': ['type']} - type: str = Field( + type: Literal['point'] = Field( default="point", - const=True, + frozen=True, title="Spatial reference type", description="A string containing the type of spatial reference", - allow_mutation=False, ) name: str = Field( default=None, @@ -931,21 +932,16 @@ class Config: description="A datetime object containing the instant corresponding to the termination of the time interval", ) - @root_validator - def start_before_end(cls, values): - start, end = None, None - if "start" in values: - start = values["start"] - if "end" in values: - end = values["end"] - if start and end: - if start > end: - raise ValueError(f"start date [{start}] is after end date [{end}]") - elif start and not end: - raise ValueError(f"An end date was not included with start date [{start}]") - elif end and not start: - raise ValueError(f"A start date was not included with end date [{end}]") - return values + @model_validator(mode='after') + def start_before_end(self): + if self.start and self.end: + if self.start > self.end: + raise ValueError(f"start date [{self.start}] is after end date [{self.end}]") + elif self.start and not self.end: + raise ValueError(f"An end date was not included with start date [{self.start}]") + elif self.end and not self.start: + raise ValueError(f"A start date was not included with end date [{self.end}]") + return self class ModelProgramFile(BaseMetadata): diff --git a/hsmodels/schemas/rdf/aggregations.py b/hsmodels/schemas/rdf/aggregations.py index a072dd6..273c571 100644 --- a/hsmodels/schemas/rdf/aggregations.py +++ b/hsmodels/schemas/rdf/aggregations.py @@ -1,7 +1,7 @@ -from datetime import date, datetime -from typing import List +from datetime import date +from typing import List, Literal -from pydantic import AnyUrl, Field, root_validator +from pydantic import AnyUrl, Field, model_validator from hsmodels.namespaces import DC, HSTERMS, RDF from hsmodels.schemas.rdf.fields import ( @@ -31,7 +31,7 @@ class BaseAggregationMetadataInRDF(RDFBaseModel): - _parse_rdf_subject = root_validator(pre=True, allow_reuse=True)(rdf_parse_rdf_subject) + _parse_rdf_subject = model_validator(mode='before')(rdf_parse_rdf_subject) title: str = Field(rdf_predicate=DC.title) subjects: List[str] = Field(rdf_predicate=DC.subject, default=[]) @@ -40,107 +40,112 @@ class BaseAggregationMetadataInRDF(RDFBaseModel): coverages: List[CoverageInRDF] = Field(rdf_predicate=DC.coverage, default=[]) rights: RightsInRDF = Field(rdf_predicate=DC.rights, default=[]) - _parse_coverages = root_validator(pre=True, allow_reuse=True)(parse_coverages) + _parse_coverages = model_validator(mode='before')(parse_coverages) - _parse_extended_metadata = root_validator(pre=True, allow_reuse=True)(parse_rdf_extended_metadata) + _parse_extended_metadata = model_validator(mode='before')(parse_rdf_extended_metadata) class GeographicRasterMetadataInRDF(BaseAggregationMetadataInRDF): - rdf_type: AnyUrl = Field(rdf_predicate=RDF.type, const=True, default=HSTERMS.GeographicRasterAggregation) - - label: str = Field( - const=True, + rdf_type: AnyUrl = Field(rdf_predicate=RDF.type, frozen=True, default=HSTERMS.GeographicRasterAggregation) + _label_literal = Literal["Geographic Raster Content: A geographic grid represented by a virtual raster tile (.vrt) file and one or more geotiff (.tif) files"] + label: _label_literal = Field( + frozen=True, default="Geographic Raster Content: A geographic grid represented by a virtual " "raster tile (.vrt) file and one or more geotiff (.tif) files", ) - dc_type: AnyUrl = Field(rdf_predicate=DC.type, default=HSTERMS.GeographicRasterAggregation, const=True) + dc_type: AnyUrl = Field(rdf_predicate=DC.type, default=HSTERMS.GeographicRasterAggregation, frozen=True) band_information: BandInformationInRDF = Field(rdf_predicate=HSTERMS.BandInformation) spatial_reference: SpatialReferenceInRDF = Field(rdf_predicate=HSTERMS.spatialReference, default=None) cell_information: CellInformationInRDF = Field(rdf_predicate=HSTERMS.CellInformation) - _parse_spatial_reference = root_validator(pre=True, allow_reuse=True)(parse_rdf_spatial_reference) + _parse_spatial_reference = model_validator(mode='before')(parse_rdf_spatial_reference) class GeographicFeatureMetadataInRDF(BaseAggregationMetadataInRDF): - rdf_type: AnyUrl = Field(rdf_predicate=RDF.type, const=True, default=HSTERMS.GeographicFeatureAggregation) + rdf_type: AnyUrl = Field(rdf_predicate=RDF.type, frozen=True, default=HSTERMS.GeographicFeatureAggregation) - label: str = Field( - const=True, default="Geographic Feature Content: The multiple files that are part of a " "geographic shapefile" + _label_literal = Literal["Geographic Feature Content: The multiple files that are part of a geographic shapefile"] + label: _label_literal = Field( + frozen=True, default="Geographic Feature Content: The multiple files that are part of a " "geographic shapefile" ) - dc_type: AnyUrl = Field(rdf_predicate=DC.type, default=HSTERMS.GeographicFeatureAggregation, const=True) + dc_type: AnyUrl = Field(rdf_predicate=DC.type, default=HSTERMS.GeographicFeatureAggregation, frozen=True) field_information: List[FieldInformationInRDF] = Field(rdf_predicate=HSTERMS.FieldInformation, default=[]) geometry_information: GeometryInformationInRDF = Field(rdf_predicate=HSTERMS.GeometryInformation) spatial_reference: SpatialReferenceInRDF = Field(rdf_predicate=HSTERMS.spatialReference, default=None) - _parse_spatial_reference = root_validator(pre=True, allow_reuse=True)(parse_rdf_spatial_reference) + _parse_spatial_reference = model_validator(mode='before')(parse_rdf_spatial_reference) class MultidimensionalMetadataInRDF(BaseAggregationMetadataInRDF): - rdf_type: AnyUrl = Field(rdf_predicate=RDF.type, const=True, default=HSTERMS.MultidimensionalAggregation) - - label: str = Field( - const=True, + rdf_type: AnyUrl = Field(rdf_predicate=RDF.type, frozen=True, default=HSTERMS.MultidimensionalAggregation) + _label_literal = Literal['Multidimensional Content: A multidimensional dataset represented by a NetCDF file (.nc) and text file giving its NetCDF header content'] + label: _label_literal = Field( + frozen=True, default="Multidimensional Content: A multidimensional dataset represented by a " "NetCDF file (.nc) and text file giving its NetCDF header content", ) - dc_type: AnyUrl = Field(rdf_predicate=DC.type, default=HSTERMS.MultidimensionalAggregation, const=True) + dc_type: AnyUrl = Field(rdf_predicate=DC.type, default=HSTERMS.MultidimensionalAggregation, frozen=True) variables: List[VariableInRDF] = Field(rdf_predicate=HSTERMS.Variable, default=[]) spatial_reference: MultidimensionalSpatialReferenceInRDF = Field( rdf_predicate=HSTERMS.spatialReference, default=None ) - _parse_spatial_reference = root_validator(pre=True, allow_reuse=True)(parse_rdf_multidimensional_spatial_reference) + _parse_spatial_reference = model_validator(mode='before')(parse_rdf_multidimensional_spatial_reference) class TimeSeriesMetadataInRDF(BaseAggregationMetadataInRDF): - rdf_type: AnyUrl = Field(rdf_predicate=RDF.type, const=True, default=HSTERMS.TimeSeriesAggregation) + rdf_type: AnyUrl = Field(rdf_predicate=RDF.type, frozen=True, default=HSTERMS.TimeSeriesAggregation) - label: str = Field( - const=True, + _label_literal = Literal["Time Series Content: One or more time series held in an ODM2 format SQLite file and optional source comma separated (.csv) files"] + label: _label_literal = Field( + frozen=True, default="Time Series Content: One or more time series held in an ODM2 format " "SQLite file and optional source comma separated (.csv) files", ) - dc_type: AnyUrl = Field(rdf_predicate=DC.type, default=HSTERMS.TimeSeriesAggregation, const=True) + dc_type: AnyUrl = Field(rdf_predicate=DC.type, default=HSTERMS.TimeSeriesAggregation, frozen=True) description: DescriptionInRDF = Field(rdf_predicate=DC.description, default_factory=DescriptionInRDF) time_series_results: List[TimeSeriesResultInRDF] = Field(rdf_predicate=HSTERMS.timeSeriesResult, default=[]) - _parse_description = root_validator(pre=True, allow_reuse=True)(rdf_parse_description) + _parse_description = model_validator(mode='before')(rdf_parse_description) class ReferencedTimeSeriesMetadataInRDF(BaseAggregationMetadataInRDF): - rdf_type: AnyUrl = Field(rdf_predicate=RDF.type, const=True, default=HSTERMS.ReferencedTimeSeriesAggregation) + rdf_type: AnyUrl = Field(rdf_predicate=RDF.type, frozen=True, default=HSTERMS.ReferencedTimeSeriesAggregation) - label: str = Field( - const=True, + _label_literal = Literal["Referenced Time Series Content: A reference to one or more time series served from HydroServers outside of HydroShare in WaterML format"] + label: _label_literal = Field( + frozen=True, default="Referenced Time Series Content: A reference to one or more time series " "served from HydroServers outside of HydroShare in WaterML format", ) - dc_type: AnyUrl = Field(rdf_predicate=DC.type, default=HSTERMS.ReferencedTimeSeriesAggregation, const=True) + dc_type: AnyUrl = Field(rdf_predicate=DC.type, default=HSTERMS.ReferencedTimeSeriesAggregation, frozen=True) class FileSetMetadataInRDF(BaseAggregationMetadataInRDF): - rdf_type: AnyUrl = Field(rdf_predicate=RDF.type, const=True, default=HSTERMS.FileSetAggregation) - - label: str = Field(const=True, default="File Set Content: One or more files with specific metadata") - dc_type: AnyUrl = Field(rdf_predicate=DC.type, default=HSTERMS.FileSetAggregation, const=True) + rdf_type: AnyUrl = Field(rdf_predicate=RDF.type, frozen=True, default=HSTERMS.FileSetAggregation) + _label_literal = Literal["File Set Content: One or more files with specific metadata"] + label: _label_literal = Field(frozen=True, default="File Set Content: One or more files with specific metadata") + dc_type: AnyUrl = Field(rdf_predicate=DC.type, default=HSTERMS.FileSetAggregation, frozen=True) class SingleFileMetadataInRDF(BaseAggregationMetadataInRDF): - rdf_type: AnyUrl = Field(rdf_predicate=RDF.type, const=True, default=HSTERMS.SingleFileAggregation) + rdf_type: AnyUrl = Field(rdf_predicate=RDF.type, frozen=True, default=HSTERMS.SingleFileAggregation) - label: str = Field(const=True, default="Single File Content: A single file with file specific metadata") - dc_type: AnyUrl = Field(rdf_predicate=DC.type, default=HSTERMS.SingleFileAggregation, const=True) + _label_literal = Literal["Single File Content: A single file with file specific metadata"] + label: _label_literal = Field(frozen=True, default="Single File Content: A single file with file specific metadata") + dc_type: AnyUrl = Field(rdf_predicate=DC.type, default=HSTERMS.SingleFileAggregation, frozen=True) class ModelProgramMetadataInRDF(BaseAggregationMetadataInRDF): - rdf_type: AnyUrl = Field(rdf_predicate=RDF.type, const=True, default=HSTERMS.ModelProgramAggregation) + rdf_type: AnyUrl = Field(rdf_predicate=RDF.type, frozen=True, default=HSTERMS.ModelProgramAggregation) - label: str = Field(const=True, default="Model Program Content: One or more files with specific metadata") - dc_type: AnyUrl = Field(rdf_predicate=DC.type, default=HSTERMS.ModelProgramAggregation, const=True) + _label_literal = Literal["Model Program Content: One or more files with specific metadata"] + label: _label_literal = Field(frozen=True, default="Model Program Content: One or more files with specific metadata") + dc_type: AnyUrl = Field(rdf_predicate=DC.type, default=HSTERMS.ModelProgramAggregation, frozen=True) name: str = Field(rdf_predicate=HSTERMS.modelProgramName, default=None) version: str = Field(rdf_predicate=HSTERMS.modelVersion, default=None) @@ -151,19 +156,20 @@ class ModelProgramMetadataInRDF(BaseAggregationMetadataInRDF): code_repository: AnyUrl = Field(rdf_predicate=HSTERMS.modelCodeRepository, default=None) program_schema_json: AnyUrl = Field(rdf_predicate=HSTERMS.modelProgramSchema, default=None) - release_notes: List[str] = Field(rdf_predicate=HSTERMS.modelReleaseNotes, default=[]) - documentation: List[str] = Field(rdf_predicate=HSTERMS.modelDocumentation, default=[]) - software: List[str] = Field(rdf_predicate=HSTERMS.modelSoftware, default=[]) - engine: List[str] = Field(rdf_predicate=HSTERMS.modelEngine, default=[]) + release_notes: List[AnyUrl] = Field(rdf_predicate=HSTERMS.modelReleaseNotes, default=[]) + documentation: List[AnyUrl] = Field(rdf_predicate=HSTERMS.modelDocumentation, default=[]) + software: List[AnyUrl] = Field(rdf_predicate=HSTERMS.modelSoftware, default=[]) + engine: List[AnyUrl] = Field(rdf_predicate=HSTERMS.modelEngine, default=[]) - _parse_file_types = root_validator(pre=True, allow_reuse=True)(rdf_parse_file_types) + _parse_file_types = model_validator(mode='before')(rdf_parse_file_types) class ModelInstanceMetadataInRDF(BaseAggregationMetadataInRDF): - rdf_type: AnyUrl = Field(rdf_predicate=RDF.type, const=True, default=HSTERMS.ModelInstanceAggregation) + rdf_type: AnyUrl = Field(rdf_predicate=RDF.type, frozen=True, default=HSTERMS.ModelInstanceAggregation) - label: str = Field(const=True, default="Model Instance Content: One or more files with specific metadata") - dc_type: AnyUrl = Field(rdf_predicate=DC.type, default=HSTERMS.ModelInstanceAggregation, const=True) + _label_literal = Literal["Model Instance Content: One or more files with specific metadata"] + label: _label_literal = Field(frozen=True, default="Model Instance Content: One or more files with specific metadata") + dc_type: AnyUrl = Field(rdf_predicate=DC.type, default=HSTERMS.ModelInstanceAggregation, frozen=True) includes_model_output: bool = Field(rdf_predicate=HSTERMS.includesModelOutput) executed_by: AnyUrl = Field(rdf_predicate=HSTERMS.executedByModelProgram, default=None) diff --git a/hsmodels/schemas/rdf/fields.py b/hsmodels/schemas/rdf/fields.py index 6b1ed9c..563868d 100644 --- a/hsmodels/schemas/rdf/fields.py +++ b/hsmodels/schemas/rdf/fields.py @@ -1,6 +1,12 @@ +from typing import Any, Callable, Optional + +from pydantic.json_schema import JsonSchemaValue +from typing_extensions import Annotated + from datetime import datetime -from pydantic import AnyUrl, BaseModel, EmailStr, Field, HttpUrl, PositiveInt, root_validator +from pydantic_core import core_schema +from pydantic import AnyUrl, BaseModel, EmailStr, Field, GetJsonSchemaHandler, HttpUrl, PositiveInt, model_validator from rdflib import BNode from rdflib.term import Identifier as RDFIdentifier @@ -26,8 +32,56 @@ from hsmodels.schemas.rdf.root_validators import parse_relation_rdf, rdf_parse_utc_offset, split_user_identifiers +class _RDFIdentifierTypePydanticAnnotation: + @classmethod + def __get_pydantic_core_schema__( + cls, + _source_type: Any, + _handler: Callable[[Any], core_schema.CoreSchema], + ) -> core_schema.CoreSchema: + """ + Reference: https://docs.pydantic.dev/latest/usage/types/custom/#handling-third-party-types + """ + + def validate_identifier(value: str) -> RDFIdentifier: + result = RDFIdentifier(value) + return result + + from_int_schema = core_schema.chain_schema( + [ + core_schema.str_schema(), + core_schema.no_info_plain_validator_function(validate_identifier), + ] + ) + + return core_schema.json_or_python_schema( + json_schema=from_int_schema, + python_schema=core_schema.union_schema( + [ + # check if it's an instance first before doing any further work + core_schema.is_instance_schema(RDFIdentifier), + from_int_schema, + ] + ), + serialization=core_schema.plain_serializer_function_ser_schema( + lambda instance: instance.toPython() + ), + ) + + @classmethod + def __get_pydantic_json_schema__( + cls, _core_schema: core_schema.CoreSchema, handler: GetJsonSchemaHandler + ) -> JsonSchemaValue: + # Use the same schema that would be used for RDFIdentifier + return handler(_core_schema) + + +def get_RDF_IdentifierType(field: Field): + return Annotated[RDFIdentifier, _RDFIdentifierTypePydanticAnnotation, field] + + class RDFBaseModel(BaseModel): - rdf_subject: RDFIdentifier = Field(default_factory=BNode) + rdf_subject: get_RDF_IdentifierType(Field(default_factory=BNode)) class DCTypeInRDF(RDFBaseModel): @@ -55,7 +109,7 @@ class RelationInRDF(RDFBaseModel): replaces: str = Field(rdf_predicate=DCTERMS.replaces, default=None) source: str = Field(rdf_predicate=DCTERMS.source, default=None) - _parse_relation = root_validator(pre=True)(parse_relation_rdf) + _parse_relation = model_validator(mode='before')(parse_relation_rdf) class DescriptionInRDF(RDFBaseModel): @@ -96,17 +150,17 @@ class Config: class CreatorInRDF(RDFBaseModel): creator_order: PositiveInt name: str = Field(default=None) - phone: str = Field(default=None) - address: str = Field(default=None) - organization: str = Field(default=None) - email: EmailStr = Field(default=None) - homepage: HttpUrl = Field(default=None) - hydroshare_user_id: int = Field(default=None) - ORCID: AnyUrl = Field(default=None) - google_scholar_id: AnyUrl = Field(default=None) - research_gate_id: AnyUrl = Field(default=None) - - _group_identifiers = root_validator(pre=True, allow_reuse=True)(split_user_identifiers) + phone: Optional[str] = Field(default=None) + address: Optional[str] = Field(default=None) + organization: Optional[str] = Field(default=None) + email: Optional[EmailStr] = Field(default=None) + homepage: Optional[HttpUrl] = Field(default=None) + hydroshare_user_id: Optional[int] = Field(default=None) + ORCID: Optional[AnyUrl] = Field(default=None) + google_scholar_id: Optional[AnyUrl] = Field(default=None) + research_gate_id: Optional[AnyUrl] = Field(default=None) + + _group_identifiers = model_validator(mode='before')(split_user_identifiers) class Config: fields = { @@ -136,7 +190,7 @@ class ContributorInRDF(RDFBaseModel): google_scholar_id: AnyUrl = Field(default=None) research_gate_id: AnyUrl = Field(default=None) - _group_identifiers = root_validator(pre=True, allow_reuse=True)(split_user_identifiers) + _group_identifiers = model_validator(mode='before')(split_user_identifiers) class Config: fields = { @@ -306,4 +360,4 @@ class Config: 'series_label': {"rdf_predicate": HSTERMS.SeriesLabel}, } - _parse_utc_offset = root_validator(pre=True)(rdf_parse_utc_offset) + _parse_utc_offset = model_validator(mode='before')(rdf_parse_utc_offset) diff --git a/hsmodels/schemas/rdf/resource.py b/hsmodels/schemas/rdf/resource.py index 7fbb9fa..8a36fc7 100644 --- a/hsmodels/schemas/rdf/resource.py +++ b/hsmodels/schemas/rdf/resource.py @@ -1,7 +1,7 @@ import uuid -from typing import List +from typing import List, Literal -from pydantic import AnyUrl, BaseModel, Field, root_validator, validator +from pydantic import AnyUrl, BaseModel, Field, field_validator, model_validator from rdflib.term import Identifier as RDFIdentifier from hsmodels.namespaces import CITOTERMS, DC, DCTERMS, HSRESOURCE, HSTERMS, ORE, RDF @@ -17,6 +17,7 @@ PublisherInRDF, RelationInRDF, RightsInRDF, + get_RDF_IdentifierType, ) from hsmodels.schemas.rdf.root_validators import ( parse_coverages, @@ -40,8 +41,8 @@ def hs_uid(): class FileMap(BaseModel): - rdf_subject: RDFIdentifier = Field(default_factory=hs_uid) - rdf_type: AnyUrl = Field(rdf_predicate=RDF.type, const=True, default=ORE.Aggregation) + rdf_subject: get_RDF_IdentifierType(Field(default_factory=hs_uid)) + rdf_type: AnyUrl = Field(rdf_predicate=RDF.type, frozen=True, default=ORE.Aggregation) is_documented_by: AnyUrl = Field(rdf_predicate=CITOTERMS.isDocumentedBy) files: List[AnyUrl] = Field(rdf_predicate=ORE.aggregates, default=[]) @@ -50,8 +51,8 @@ class FileMap(BaseModel): class ResourceMap(BaseModel): - rdf_subject: RDFIdentifier = Field(default_factory=hs_uid) - rdf_type: AnyUrl = Field(rdf_predicate=RDF.type, const=True, default=ORE.ResourceMap) + rdf_subject: get_RDF_IdentifierType(Field(default_factory=hs_uid)) + rdf_type: AnyUrl = Field(rdf_predicate=RDF.type, frozen=True, default=ORE.ResourceMap) describes: FileMap = Field(rdf_predicate=ORE.describes) identifier: str = Field(rdf_predicate=DC.identifier, default=None) @@ -60,14 +61,14 @@ class ResourceMap(BaseModel): class BaseResource(BaseModel): - rdf_subject: RDFIdentifier = Field(default_factory=hs_uid) + rdf_subject: get_RDF_IdentifierType(Field(default_factory=hs_uid, alias='rdf_subject')) title: str = Field(rdf_predicate=DC.title) description: DescriptionInRDF = Field(rdf_predicate=DC.description, default_factory=DescriptionInRDF) language: str = Field(rdf_predicate=DC.language, default='eng') subjects: List[str] = Field(rdf_predicate=DC.subject, default=[]) - dc_type: AnyUrl = Field(rdf_predicate=DC.type, default=HSTERMS.CompositeResource, const=True) - identifier: IdentifierInRDF = Field(rdf_predicate=DC.identifier, cont=True) + dc_type: AnyUrl = Field(rdf_predicate=DC.type, default=HSTERMS.CompositeResource, frozen=True) + identifier: IdentifierInRDF = Field(rdf_predicate=DC.identifier, frozen=True) creators: List[CreatorInRDF] = Field(rdf_predicate=DC.creator, default=[]) contributors: List[ContributorInRDF] = Field(rdf_predicate=DC.contributor, default=[]) @@ -80,30 +81,32 @@ class BaseResource(BaseModel): publisher: PublisherInRDF = Field(rdf_predicate=DC.publisher, default=None) citation: str = Field(rdf_predicate=DCTERMS.bibliographicCitation) - _parse_rdf_subject = root_validator(pre=True, allow_reuse=True)(rdf_parse_rdf_subject) + _parse_rdf_subject = model_validator(mode='before')(rdf_parse_rdf_subject) - _parse_coverages = root_validator(pre=True, allow_reuse=True)(parse_coverages) - _parse_extended_metadata = root_validator(pre=True, allow_reuse=True)(parse_rdf_extended_metadata) - _parse_rdf_dates = root_validator(pre=True, allow_reuse=True)(parse_rdf_dates) - _parse_description = root_validator(pre=True, allow_reuse=True)(rdf_parse_description) + _parse_coverages = model_validator(mode='before')(parse_coverages) + _parse_extended_metadata = model_validator(mode='before')(parse_rdf_extended_metadata) + _parse_rdf_dates = model_validator(mode='before')(parse_rdf_dates) + _parse_description = model_validator(mode='before')(rdf_parse_description) - _parse_identifier = validator("identifier", pre=True, allow_reuse=True)(rdf_parse_identifier) + _parse_identifier = field_validator("identifier", mode='before')(rdf_parse_identifier) - _language_constraint = validator('language', allow_reuse=True)(language_constraint) - _dates_constraint = validator('dates', allow_reuse=True)(dates_constraint) - _coverages_constraint = validator('coverages', allow_reuse=True)(coverages_constraint) - _coverages_spatial_constraint = validator('coverages', allow_reuse=True)(coverages_spatial_constraint) - _sort_creators = validator("creators", pre=True)(sort_creators) + _language_constraint = field_validator('language')(language_constraint) + _dates_constraint = field_validator('dates')(dates_constraint) + _coverages_constraint = field_validator('coverages')(coverages_constraint) + _coverages_spatial_constraint = field_validator('coverages')(coverages_spatial_constraint) + _sort_creators = field_validator("creators")(sort_creators) class ResourceMetadataInRDF(BaseResource): - dc_type: AnyUrl = Field(rdf_predicate=DC.type, default=HSTERMS.CompositeResource, const=True) - rdf_type: AnyUrl = Field(rdf_predicate=RDF.type, const=True, default=HSTERMS.CompositeResource) + dc_type: AnyUrl = Field(rdf_predicate=DC.type, default=HSTERMS.CompositeResource, frozen=True) + rdf_type: AnyUrl = Field(rdf_predicate=RDF.type, frozen=True, default=HSTERMS.CompositeResource) - label: str = Field(default="Composite Resource", const=True) + _label_literal = Literal["Composite Resource"] + label: _label_literal = Field(default="Composite Resource", frozen=True, alias='label') class CollectionMetadataInRDF(BaseResource): - dc_type: AnyUrl = Field(rdf_predicate=DC.type, default=HSTERMS.CollectionResource, const=True) - rdf_type: AnyUrl = Field(rdf_predicate=RDF.type, const=True, default=HSTERMS.CollectionResource) - label: str = Field(default="Collection Resource", const=True) + dc_type: AnyUrl = Field(rdf_predicate=DC.type, default=HSTERMS.CollectionResource, frozen=True) + rdf_type: AnyUrl = Field(rdf_predicate=RDF.type, frozen=True, default=HSTERMS.CollectionResource) + _label_literal = Literal["Collection Resource"] + label: _label_literal = Field(default="Collection Resource", frozen=True, alias='label') diff --git a/hsmodels/schemas/rdf/root_validators.py b/hsmodels/schemas/rdf/root_validators.py index 4e00a4c..4bd2e5c 100644 --- a/hsmodels/schemas/rdf/root_validators.py +++ b/hsmodels/schemas/rdf/root_validators.py @@ -66,7 +66,7 @@ def rdf_parse_utc_offset(cls, values): def rdf_parse_rdf_subject(cls, values): if "url" in values: - values["rdf_subject"] = URIRef(values["url"]) + values["rdf_subject"] = URIRef(str(values["url"])) del values["url"] return values diff --git a/hsmodels/schemas/rdf/validators.py b/hsmodels/schemas/rdf/validators.py index bdf7c0b..9fe4e20 100644 --- a/hsmodels/schemas/rdf/validators.py +++ b/hsmodels/schemas/rdf/validators.py @@ -1,3 +1,5 @@ +from pydantic_core import Url + from hsmodels.schemas.enums import CoverageType, DateType from hsmodels.schemas.languages_iso import languages @@ -13,7 +15,7 @@ def rdf_parse_extended_metadata(cls, value): def rdf_parse_identifier(cls, value): - if isinstance(value, str): + if isinstance(value, Url): return {"hydroshare_identifier": value} return value diff --git a/hsmodels/schemas/resource.py b/hsmodels/schemas/resource.py index 79ce149..17ad456 100644 --- a/hsmodels/schemas/resource.py +++ b/hsmodels/schemas/resource.py @@ -1,7 +1,7 @@ from datetime import datetime -from typing import Dict, List, Union +from typing import Dict, List, Optional, Union, Literal -from pydantic import AnyUrl, Field, root_validator, validator +from pydantic import AnyUrl, Field, field_validator, model_validator from hsmodels.schemas.base_models import BaseMetadata from hsmodels.schemas.fields import ( @@ -82,7 +82,7 @@ class Config: title="Funding agency information", description="A list of objects containing information about the funding agencies and awards associated with a resource", ) - spatial_coverage: Union[PointCoverage, BoxCoverage] = Field( + spatial_coverage: Optional[Union[PointCoverage, BoxCoverage]] = Field( default=None, title="Spatial coverage", description="An object containing information about the spatial topic of a resource, the spatial applicability of a resource, or jurisdiction under with a resource is relevant", @@ -101,73 +101,71 @@ class Config: default=None, title="Citation", description="A string containing the biblilographic citation for a resource" ) - _parse_coverages = root_validator(pre=True, allow_reuse=True)(split_coverages) - _parse_additional_metadata = root_validator(pre=True, allow_reuse=True)(parse_additional_metadata) - _parse_abstract = root_validator(pre=True)(parse_abstract) + _parse_coverages = model_validator(mode='before')(split_coverages) + _parse_additional_metadata = model_validator(mode='before')(parse_additional_metadata) + _parse_abstract = model_validator(mode='before')(parse_abstract) - _parse_spatial_coverage = validator("spatial_coverage", allow_reuse=True, pre=True)(parse_spatial_coverage) + _parse_spatial_coverage = field_validator("spatial_coverage", mode='before')(parse_spatial_coverage) - _normalize_additional_metadata = root_validator(allow_reuse=True, pre=True)(normalize_additional_metadata) + _normalize_additional_metadata = model_validator(mode='before')(normalize_additional_metadata) - _subjects_constraint = validator('subjects', allow_reuse=True)(subjects_constraint) - _language_constraint = validator('language', allow_reuse=True)(language_constraint) - _creators_constraint = validator('creators')(list_not_empty) + _subjects_constraint = field_validator('subjects')(subjects_constraint) + _language_constraint = field_validator('language')(language_constraint) + _creators_constraint = field_validator('creators')(list_not_empty) class BaseResourceMetadata(ResourceMetadataIn): - url: AnyUrl = Field(title="URL", description="An object containing the URL for a resource", allow_mutation=False) + url: AnyUrl = Field(title="URL", description="An object containing the URL for a resource", frozen=True) identifier: AnyUrl = Field( title="Identifier", description="An object containing the URL-encoded unique identifier for a resource", - allow_mutation=False, + frozen=True, ) created: datetime = Field( default_factory=datetime.now, title="Creation date", description="A datetime object containing the instant associated with when a resource was created", - allow_mutation=False, + frozen=True, ) modified: datetime = Field( default_factory=datetime.now, title="Modified date", description="A datetime object containing the instant associated with when a resource was last modified", - allow_mutation=False, + frozen=True, ) review_started: datetime = Field( default=None, title="Review started date", description="A datetime object containing the instant associated with when metadata review started on a resource", - allow_mutation=False, + frozen=True, ) published: datetime = Field( default=None, title="Published date", description="A datetime object containing the instant associated with when a resource was published", - allow_mutation=False, + frozen=True, ) - _parse_dates = root_validator(pre=True, allow_reuse=True)(split_dates) - _parse_url = root_validator(pre=True, allow_reuse=True)(parse_url) + _parse_dates = model_validator(mode='before')(split_dates) + _parse_url = model_validator(mode='before')(parse_url) - _parse_identifier = validator("identifier", pre=True)(parse_identifier) + _parse_identifier = field_validator("identifier", mode='before')(parse_identifier) class ResourceMetadata(BaseResourceMetadata): - type: str = Field( - const=True, + type: Literal['CompositeResource'] = Field( + frozen=True, default="CompositeResource", title="Resource Type", description="An object containing a URL that points to the HydroShare resource type selected from the hsterms namespace", - allow_mutation=False, ) class CollectionMetadata(BaseResourceMetadata): - type: str = Field( - const=True, + type: Literal['CollectionResource'] = Field( + frozen=True, default="CollectionResource", title="Resource Type", description="An object containing a URL that points to the HydroShare resource type selected from the hsterms namespace", - allow_mutation=False, ) diff --git a/requirements.txt b/requirements.txt index ec1d916..cdf0952 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ rdflib<6.0.0 -pydantic>=1.8.1,<2.0 +pydantic==2.* email-validator jsonschema2md black diff --git a/tests/data/json/modelprogram.json b/tests/data/json/modelprogram.json index 64c513f..459350d 100644 --- a/tests/data/json/modelprogram.json +++ b/tests/data/json/modelprogram.json @@ -19,8 +19,8 @@ "windows" ], "release_date": "2021-09-22", - "website": "https://www.hydroshare.org", - "code_repository": "https://www.github.com", + "website": "https://www.hydroshare.org/", + "code_repository": "https://www.github.com/", "file_types": [ { "type": "https://www.hydroshare.org/terms/modelReleaseNotes", diff --git a/tests/data/json/resource.json b/tests/data/json/resource.json index 0466907..9d51c32 100644 --- a/tests/data/json/resource.json +++ b/tests/data/json/resource.json @@ -39,7 +39,7 @@ "address": "MA, US", "organization": "CUAHSI", "email": "castronova.anthony@gmail.com", - "homepage": "http://anthonycastronova.com", + "homepage": "http://anthonycastronova.com/", "identifiers": { "GoogleScholarID": "https://scholar.google.com/citations?user=ScWTFoQAAAAJ&hl=en", "ResearchGateID": "https://www.researchgate.net/profile/Anthony_Castronova", @@ -52,7 +52,7 @@ "address": "Utah, US", "organization": "Utah State University", "email": "dtarb@usu.edu", - "homepage": "http://hydrology.usu.edu/dtarb", + "homepage": "http://hydrology.usu.edu/dtarb/", "identifiers": { "ORCID": "https://orcid.org/0000-0002-1998-3479" } @@ -65,11 +65,11 @@ }, { "type": "The content of this resource is part of", - "value": "https://sadf.com" + "value": "https://sadf.com/" }, { "type": "The content of this resource was created by a related App or software program", - "value": "https://www.google.com" + "value": "https://www.google.com/" } ], "additional_metadata": { @@ -79,20 +79,20 @@ }, "rights": { "statement": "my statement", - "url": "http://studio.bakajo.com" + "url": "http://studio.bakajo.com/" }, "awards": [ { "funding_agency_name": "agency1", "title": "t", "number": "n", - "funding_agency_url": "https://google.com" + "funding_agency_url": "https://google.com/" }, { "funding_agency_name": "agency2", "title": "T2", "number": "TN", - "funding_agency_url": "https://google.com" + "funding_agency_url": "https://google.com/" } ], "spatial_coverage": { diff --git a/tests/data/metadata/fileset_meta.xml b/tests/data/metadata/fileset_meta.xml index a0abdaf..ba16cd4 100644 --- a/tests/data/metadata/fileset_meta.xml +++ b/tests/data/metadata/fileset_meta.xml @@ -27,7 +27,7 @@ - + my statement diff --git a/tests/data/metadata/geographicfeature_meta.xml b/tests/data/metadata/geographicfeature_meta.xml index 9df2c69..554c960 100644 --- a/tests/data/metadata/geographicfeature_meta.xml +++ b/tests/data/metadata/geographicfeature_meta.xml @@ -39,7 +39,7 @@ my statement - + diff --git a/tests/data/metadata/geographicraster_meta.xml b/tests/data/metadata/geographicraster_meta.xml index b06d331..c900b72 100644 --- a/tests/data/metadata/geographicraster_meta.xml +++ b/tests/data/metadata/geographicraster_meta.xml @@ -10,7 +10,7 @@ my statement - + diff --git a/tests/data/metadata/modelprogram_meta.xml b/tests/data/metadata/modelprogram_meta.xml index 6ef631a..57c05be 100644 --- a/tests/data/metadata/modelprogram_meta.xml +++ b/tests/data/metadata/modelprogram_meta.xml @@ -8,7 +8,7 @@ linux setup againas - + @@ -29,7 +29,7 @@ This resource is shared under the Creative Commons Attribution CC BY. - + diff --git a/tests/data/metadata/multidimensional_meta.xml b/tests/data/metadata/multidimensional_meta.xml index 3f0e162..f9ef02f 100644 --- a/tests/data/metadata/multidimensional_meta.xml +++ b/tests/data/metadata/multidimensional_meta.xml @@ -89,7 +89,7 @@ my statement - + diff --git a/tests/data/metadata/referencedtimeseries.refts_meta.xml b/tests/data/metadata/referencedtimeseries.refts_meta.xml index 693eeeb..8642420 100644 --- a/tests/data/metadata/referencedtimeseries.refts_meta.xml +++ b/tests/data/metadata/referencedtimeseries.refts_meta.xml @@ -16,7 +16,7 @@ my statement - + Sites, Variable diff --git a/tests/data/metadata/resourcemetadata.xml b/tests/data/metadata/resourcemetadata.xml index c96af18..74c80cb 100644 --- a/tests/data/metadata/resourcemetadata.xml +++ b/tests/data/metadata/resourcemetadata.xml @@ -28,7 +28,7 @@ - + my statement @@ -43,7 +43,7 @@ t n agency1 - + Time Series @@ -54,7 +54,7 @@ - https://www.google.com + https://www.google.com/ @@ -86,11 +86,11 @@ MA, US CUAHSI tel:3399334127 - + Anthony Michael Castronova castronova.anthony@gmail.com - + 11 @@ -138,7 +138,7 @@ key 1 - + T2 agency2 TN @@ -181,7 +181,7 @@ Consortium of Universities for the Advancement of Hydrologic Science, Inc. (CUAHSI) - + diff --git a/tests/data/metadata/singlefile_meta.xml b/tests/data/metadata/singlefile_meta.xml index 13028f2..2f325a9 100644 --- a/tests/data/metadata/singlefile_meta.xml +++ b/tests/data/metadata/singlefile_meta.xml @@ -16,7 +16,7 @@ my statement - + key1 diff --git a/tests/test_defaults.py b/tests/test_defaults.py index 4b3c47d..8bed477 100644 --- a/tests/test_defaults.py +++ b/tests/test_defaults.py @@ -1,3 +1,5 @@ +from typing import get_origin + import pytest from hsmodels.schemas.aggregations import ( @@ -51,16 +53,12 @@ ] -def get_origin(t): - return getattr(t, '__origin__', None) - - @pytest.mark.parametrize("schema_list_count", schema_list_count) def test_lists_default_empty(schema_list_count): schema, number_of_lists = schema_list_count list_count = 0 - for f in schema.__fields__.values(): - origin = get_origin(f.outer_type_) + for f in schema.model_fields.values(): + origin = get_origin(f.annotation) if origin is list: assert f.default == [] list_count = list_count + 1 diff --git a/tests/test_metadata.py b/tests/test_metadata.py index b1267a8..6c67264 100644 --- a/tests/test_metadata.py +++ b/tests/test_metadata.py @@ -80,7 +80,7 @@ def test_resource_metadata(res_md): assert res_md.language == "eng" - assert res_md.identifier == "http://www.hydroshare.org/resource/84805fd615a04d63b4eada65644a1e20" + assert str(res_md.identifier) == "http://www.hydroshare.org/resource/84805fd615a04d63b4eada65644a1e20" assert len(res_md.additional_metadata) == 3 assert "key2" in res_md.additional_metadata @@ -109,19 +109,19 @@ def test_resource_metadata(res_md): assert contributor assert contributor.phone == "tel:4357973172" assert contributor.address == "Utah, US" - assert contributor.homepage == "http://hydrology.usu.edu/dtarb" + assert str(contributor.homepage) == "http://hydrology.usu.edu/dtarb" assert contributor.organization == "Utah State University" - assert contributor.identifiers[UserIdentifierType.ORCID] == "https://orcid.org/0000-0002-1998-3479" + assert str(contributor.identifiers[UserIdentifierType.ORCID]) == "https://orcid.org/0000-0002-1998-3479" assert contributor.name == "David Tarboton" assert len(res_md.relations) == 3 assert any(x for x in res_md.relations if x.value == "https://sadf.com" and x.type == RelationType.isPartOf) assert any( - x for x in res_md.relations if x.value == "https://www.google.com" and x.type == RelationType.isCreatedBy + x for x in res_md.relations if x.value == "https://www.google.com/" and x.type == RelationType.isCreatedBy ) assert res_md.rights.statement == "my statement" - assert res_md.rights.url == "http://studio.bakajo.com" + assert str(res_md.rights.url) == "http://studio.bakajo.com/" assert res_md.modified == datetime.fromisoformat("2020-11-13T19:40:57.276064+00:00") assert res_md.created == datetime.fromisoformat("2020-07-09T19:12:21.354703+00:00") @@ -133,7 +133,7 @@ def test_resource_metadata(res_md): assert award assert award.number == "n" assert award.funding_agency_name == "agency1" - assert award.funding_agency_url == "https://google.com" + assert str(award.funding_agency_url) == "https://google.com/" assert res_md.period_coverage == PeriodCoverage( start=datetime.fromisoformat("2020-07-10T00:00:00"), end=datetime.fromisoformat("2020-07-29T00:00:00") @@ -165,4 +165,4 @@ def test_resource_metadata(res_md): assert ( res_md.publisher.name == "Consortium of Universities for the Advancement of Hydrologic Science, Inc. (CUAHSI)" ) - assert res_md.publisher.url == "https://www.cuahsi.org" + assert str(res_md.publisher.url) == "https://www.cuahsi.org/" diff --git a/tests/test_metadata_json.py b/tests/test_metadata_json.py index 92ebf85..7409b43 100644 --- a/tests/test_metadata_json.py +++ b/tests/test_metadata_json.py @@ -34,10 +34,10 @@ def res_md(): def test_resource_additional_metadata_dictionary(res_md): assert res_md.additional_metadata == {"key1": "value1", "key2": "value2", "key_empty": ""} - res_md_in = ResourceMetadataIn(**res_md.dict()) + res_md_in = ResourceMetadataIn(**res_md.model_dump()) assert res_md_in.additional_metadata == {"key1": "value1", "key2": "value2", "key_empty": ""} - assert res_md_in.dict()["additional_metadata"] == {"key1": "value1", "key2": "value2", "key_empty": ""} + assert res_md_in.model_dump()["additional_metadata"] == {"key1": "value1", "key2": "value2", "key_empty": ""} metadata_json_input = [ @@ -61,8 +61,8 @@ def test_metadata_json_serialization(metadata_json_input): metadata_file = os.path.join('data', 'json', metadata_file) with open(metadata_file, 'r') as f: json_file_str = f.read() - md = in_schema.parse_raw(json_file_str) - from_schema = sorting(json.loads(md.json())) + md = in_schema.model_validate_json(json_file_str) + from_schema = sorting(json.loads(md.model_dump_json())) from_file = sorting(json.loads(json_file_str)) for i in range(1, len(from_file)): assert from_file[i] == from_schema[i] diff --git a/tests/test_schemas.py b/tests/test_schemas.py index 2a4e69f..221765e 100644 --- a/tests/test_schemas.py +++ b/tests/test_schemas.py @@ -1,5 +1,4 @@ import pytest -from pydantic.schema import schema from hsmodels.schemas import ( CollectionMetadata, @@ -47,13 +46,12 @@ @pytest.mark.parametrize("read_only_field", read_only_fields) def test_readonly(read_only_field): clazz, fields = read_only_field - s = schema([clazz])["definitions"][clazz.__name__] - - for prop in s["properties"]: - if prop in fields: - assert "readOnly" in s["properties"][prop] and s["properties"][prop]["readOnly"] is True + s = clazz.schema()["properties"] + for field in s: + if field in fields: + assert "readOnly" in s[field] and s[field]["readOnly"] is True else: - assert "readOnly" not in s["properties"][prop] + assert "readOnly" not in s[field] additional_metadata_fields = [ @@ -74,12 +72,12 @@ def test_readonly(read_only_field): @pytest.mark.parametrize("additional_metadata_field", additional_metadata_fields) def test_dictionary_field(additional_metadata_field): clazz, fields = additional_metadata_field - s = schema([clazz])["definitions"][clazz.__name__] + s = clazz.schema()["properties"] for field in fields: - assert 'additionalProperties' not in s["properties"][field] - assert 'default' not in s["properties"][field] - assert s["properties"][field]['items'] == { + assert 'additionalProperties' not in s[field] + assert 'default' not in s[field] + assert s[field]['items'] == { "type": "object", "title": "Key-Value", "description": "A key-value pair", diff --git a/tests/test_validation.py b/tests/test_validation.py index d6aadc7..bed13e9 100644 --- a/tests/test_validation.py +++ b/tests/test_validation.py @@ -39,7 +39,7 @@ def test_extended_metadata(): ExtendedMetadataInRDF() assert False, "ExtendedMetadata key/value are required" except ValueError as ve: - assert "field required" in str(ve) + assert "Field required" in str(ve) def test_dates(): @@ -76,7 +76,7 @@ def test_variables(): Variable() assert False, "Some Variable fields should be required" except ValidationError as ve: - assert "4 validation errors for Variable" in str(ve) + assert "4 validation errors for Multidimensional Variable" in str(ve) assert "name" in str(ve) assert "unit" in str(ve) assert "type" in str(ve) @@ -160,7 +160,7 @@ def test_box_constraints_north_south(): def test_invalid_email(): try: - creator = Creator(email="bad") + _ = Creator(email="bad") assert False, "Should have thrown error" except ValueError as e: assert "value is not a valid email address" in str(e) @@ -171,8 +171,9 @@ def test_creator_readonly(): try: creator.hydroshare_user_id = 6 assert False, "Should have thrown error" - except TypeError as e: - assert '"hydroshare_user_id" has allow_mutation set to False and cannot be assigned' in str(e) + except ValidationError as e: + assert 'hydroshare_user_id' in str(e) + assert 'Field is frozen' in str(e) def test_contributor_readonly(): @@ -180,56 +181,63 @@ def test_contributor_readonly(): try: contributor.hydroshare_user_id = 6 assert False, "Should have thrown error" - except TypeError as e: - assert '"hydroshare_user_id" has allow_mutation set to False and cannot be assigned' in str(e) + except ValidationError as e: + assert 'hydroshare_user_id' in str(e) + assert 'Field is frozen' in str(e) def test_resource_created_readonly(res_md): try: res_md.created = datetime.now() assert False, "Should have thrown error" - except TypeError as e: - assert '"created" has allow_mutation set to False and cannot be assigned' in str(e) + except ValidationError as e: + assert 'created' in str(e) + assert 'Field is frozen' in str(e) def test_resource_modified_readonly(res_md): try: res_md.modified = datetime.now() assert False, "Should have thrown error" - except TypeError as e: - assert '"modified" has allow_mutation set to False and cannot be assigned' in str(e) + except ValidationError as e: + assert 'modified' in str(e) + assert 'Field is frozen' in str(e) def test_resource_review_readonly(res_md): try: res_md.review_started = datetime.now() assert False, "Should have thrown error" - except TypeError as e: - assert '"review_started" has allow_mutation set to False and cannot be assigned' in str(e) + except ValidationError as e: + assert 'review_started' in str(e) + assert 'Field is frozen' in str(e) def test_resource_published_readonly(res_md): try: res_md.published = datetime.now() assert False, "Should have thrown error" - except TypeError as e: - assert '"published" has allow_mutation set to False and cannot be assigned' in str(e) + except ValidationError as e: + assert 'published' in str(e) + assert 'Field is frozen' in str(e) def test_resource_type_readonly(res_md): try: res_md.type = "http://www.hydroshare.org/" assert False, "Should have thrown error" - except TypeError as e: - assert '"type" has allow_mutation set to False and cannot be assigned' in str(e) + except ValidationError as e: + assert 'type' in str(e) + assert 'Field is frozen' in str(e) def test_resource_identifier_readonly(res_md): try: res_md.identifier = "identifier" assert False, "Should have thrown error" - except TypeError as e: - assert '"identifier" has allow_mutation set to False and cannot be assigned' in str(e) + except ValidationError as e: + assert 'identifier' in str(e) + assert 'Field is frozen' in str(e) metadata_files = [ @@ -250,8 +258,9 @@ def test_aggregation_url_readonly(change_test_dir, metadata_file): try: md.url = "changed" assert False, "Should have thrown error" - except TypeError as e: - assert '"url" has allow_mutation set to False and cannot be assigned' in str(e) + except ValidationError as e: + assert 'url' in str(e) + assert 'Field is frozen' in str(e) @pytest.mark.parametrize("metadata_file", metadata_files) @@ -261,8 +270,9 @@ def test_aggregation_type_readonly(change_test_dir, metadata_file): try: md.type = AggregationType.TimeSeriesAggregation assert False, "Should have thrown error" - except TypeError as e: - assert '"type" has allow_mutation set to False and cannot be assigned' in str(e) + except ValidationError as e: + assert 'type' in str(e) + assert 'Field is frozen' in str(e) def test_resource_metadata_from_form(): @@ -370,5 +380,5 @@ def test_subjects_aggregation(agg_md): def test_default_exclude_none(res_md): res_md.spatial_coverage = None - assert "spatial_coverage" not in res_md.dict() - assert "spatial_coverage" in res_md.dict(exclude_none=False) + assert "spatial_coverage" not in res_md.model_dump() + assert "spatial_coverage" in res_md.model_dump(exclude_none=False) From 2cfff5cb25240bca2793993a1fba3bac9b5c9157 Mon Sep 17 00:00:00 2001 From: pkdash Date: Tue, 5 Sep 2023 16:10:03 -0400 Subject: [PATCH 02/20] [#44] using python 3.9 in github workflow --- .github/workflows/python-package.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 47a7fd2..da81fdb 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.7] + python-version: [3.9] steps: - uses: actions/checkout@v2 From ee7cb0741f919f15fe9d112926a64c71b3eb1481 Mon Sep 17 00:00:00 2001 From: pkdash Date: Tue, 5 Sep 2023 16:37:07 -0400 Subject: [PATCH 03/20] [#44] using python 3.9 and pydantic 2 in setup --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 92807da..028159b 100644 --- a/setup.py +++ b/setup.py @@ -9,7 +9,7 @@ exclude=("tests",)), install_requires=[ 'rdflib<6.0.0', - 'pydantic>=1.8.1,<2.0', + 'pydantic==2.*', 'email-validator' ], url='https://github.com/hydroshare/hsmodels', @@ -17,7 +17,7 @@ author='Scott Black', author_email='sblack@cuahsi.org', description='Pydantic models for HydroShare metadata', - python_requires='>=3.6', + python_requires='>=3.9', long_description=README, long_description_content_type="text/markdown", classifiers=[ From 9caf03866639c773bb55b9885347613a2320e4ee Mon Sep 17 00:00:00 2001 From: pkdash Date: Wed, 6 Sep 2023 17:35:40 -0400 Subject: [PATCH 04/20] [#44] removing nested ifs --- hsmodels/schemas/__init__.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/hsmodels/schemas/__init__.py b/hsmodels/schemas/__init__.py index 004d7eb..0c3042c 100644 --- a/hsmodels/schemas/__init__.py +++ b/hsmodels/schemas/__init__.py @@ -152,14 +152,13 @@ def get_args(t): def _parse(schema, metadata_graph, subject=None): def get_nested_class(field): origin = field.annotation - if origin and inspect.isclass(origin): - if issubclass(origin, BaseModel): # and origin.model_fields: + if origin: + if inspect.isclass(origin) and issubclass(origin, BaseModel): return origin if get_args(origin): clazz = get_args(origin)[0] - if inspect.isclass(clazz): - if issubclass(clazz, BaseModel): - return clazz + if inspect.isclass(clazz) and issubclass(clazz, BaseModel): + return clazz return None def class_rdf_type(schema): From cb128322cabdf171193929628e4c521632ade9f0 Mon Sep 17 00:00:00 2001 From: pkdash Date: Wed, 6 Sep 2023 17:39:44 -0400 Subject: [PATCH 05/20] [#44] rolling back use of Optional typing for now --- hsmodels/schemas/aggregations.py | 43 +++++++++----------------------- hsmodels/schemas/fields.py | 26 +++++++++---------- hsmodels/schemas/rdf/fields.py | 20 +++++++-------- hsmodels/schemas/rdf/resource.py | 1 - hsmodels/schemas/resource.py | 6 ++--- 5 files changed, 38 insertions(+), 58 deletions(-) diff --git a/hsmodels/schemas/aggregations.py b/hsmodels/schemas/aggregations.py index 144c795..1f19496 100644 --- a/hsmodels/schemas/aggregations.py +++ b/hsmodels/schemas/aggregations.py @@ -1,5 +1,5 @@ from datetime import date -from typing import Dict, List, Optional, Union, Literal +from typing import Dict, List, Union, Literal from pydantic import AnyUrl, Field, model_validator, field_validator @@ -59,12 +59,12 @@ class BaseAggregationMetadataIn(BaseMetadata): title="Extended metadata", description="A list of extended metadata elements expressed as key-value pairs", ) - spatial_coverage: Optional[Union[PointCoverage, BoxCoverage]] = Field( + spatial_coverage: Union[PointCoverage, BoxCoverage] = Field( default=None, title="Spatial coverage", description="An object containing the geospatial coverage for the aggregation expressed as either a bounding box or point", ) - period_coverage: Optional[PeriodCoverage] = Field( + period_coverage: PeriodCoverage = Field( default=None, title="Temporal coverage", description="An object containing the temporal coverage for a aggregation expressed as a date range", @@ -117,11 +117,10 @@ class GeographicRasterMetadata(GeographicRasterMetadataIn): default=_type, title="Aggregation type", description="A string expressing the aggregation type from the list of HydroShare aggregation types", - allow_mutation=False, ) url: AnyUrl = Field( - title="Aggregation URL", description="An object containing the URL of the aggregation", allow_mutation=False + title="Aggregation URL", description="An object containing the URL of the aggregation", frozen=True ) rights: Rights = Field( @@ -171,11 +170,10 @@ class GeographicFeatureMetadata(GeographicFeatureMetadataIn): default=AggregationType.GeographicFeatureAggregation, title="Aggregation type", description="A string expressing the aggregation type from the list of HydroShare aggregation types", - allow_mutation=False, ) url: AnyUrl = Field( - title="Aggregation URL", description="An object containing the URL of the aggregation", allow_mutation=False + title="Aggregation URL", description="An object containing the URL of the aggregation", frozen=True ) rights: Rights = Field( @@ -223,11 +221,10 @@ class MultidimensionalMetadata(MultidimensionalMetadataIn): default=AggregationType.MultidimensionalAggregation, title="Aggregation type", description="A string expressing the aggregation type from the list of HydroShare aggregation types", - allow_mutation=False, ) url: AnyUrl = Field( - title="Aggregation URL", description="An object containing the URL of the aggregation", allow_mutation=False + title="Aggregation URL", description="An object containing the URL of the aggregation", frozen=True ) rights: Rights = Field( @@ -260,11 +257,10 @@ class ReferencedTimeSeriesMetadata(ReferencedTimeSeriesMetadataIn): default=AggregationType.ReferencedTimeSeriesAggregation, title="Aggregation type", description="A string expressing the aggregation type from the list of HydroShare aggregation types", - allow_mutation=False, ) url: AnyUrl = Field( - title="Aggregation URL", description="An object containing the URL of the aggregation", allow_mutation=False + title="Aggregation URL", description="An object containing the URL of the aggregation", frozen=True ) rights: Rights = Field( @@ -297,11 +293,10 @@ class FileSetMetadata(FileSetMetadataIn): default=AggregationType.FileSetAggregation, title="Aggregation type", description="A string expressing the aggregation type from the list of HydroShare aggregation types", - allow_mutation=False, ) url: AnyUrl = Field( - title="Aggregation URL", description="An object containing the URL of the aggregation", allow_mutation=False + title="Aggregation URL", description="An object containing the URL of the aggregation", frozen=True ) rights: Rights = Field( @@ -333,11 +328,10 @@ class SingleFileMetadata(SingleFileMetadataIn): default=AggregationType.SingleFileAggregation, title="Aggregation type", description="A string expressing the aggregation type from the list of HydroShare aggregation types", - allow_mutation=False, ) url: AnyUrl = Field( - title="Aggregation URL", description="An object containing the URL of the aggregation", allow_mutation=False + title="Aggregation URL", description="An object containing the URL of the aggregation", frozen=True ) rights: Rights = Field( @@ -382,11 +376,10 @@ class TimeSeriesMetadata(TimeSeriesMetadataIn): default=AggregationType.TimeSeriesAggregation, title="Aggregation type", description="A string expressing the aggregation type from the list of HydroShare aggregation types", - allow_mutation=False, ) url: AnyUrl = Field( - title="Aggregation URL", description="An object containing the URL of the aggregation", allow_mutation=False + title="Aggregation URL", description="An object containing the URL of the aggregation", frozen=True ) rights: Rights = Field( @@ -454,16 +447,6 @@ class Config: _parse_file_types = model_validator(mode='before')(parse_file_types) - # @model_validator(mode='after') - # def url_to_string(self): - # if self.website is not None: - # self.website = str(self.website) - # if self.code_repository is not None: - # self.code_repository = str(self.code_repository) - # if self.program_schema_json is not None: - # self.program_schema_json = str(self.program_schema_json) - # return self - class ModelProgramMetadata(ModelProgramMetadataIn): type: AggregationType = Field( @@ -471,11 +454,10 @@ class ModelProgramMetadata(ModelProgramMetadataIn): default=AggregationType.ModelProgramAggregation, title="Aggregation type", description="A string expressing the aggregation type from the list of HydroShare aggregation types", - allow_mutation=False, ) url: AnyUrl = Field( - title="Aggregation URL", description="An object containing the URL of the aggregation", allow_mutation=False + title="Aggregation URL", description="An object containing the URL of the aggregation", frozen=True ) rights: Rights = Field( @@ -527,11 +509,10 @@ class ModelInstanceMetadata(ModelInstanceMetadataIn): default=AggregationType.ModelInstanceAggregation, title="Aggregation type", description="A string expressing the aggregation type from the list of HydroShare aggregation types", - allow_mutation=False, ) url: AnyUrl = Field( - title="Aggregation URL", description="An object containing the URL of the aggregation", allow_mutation=False + title="Aggregation URL", description="An object containing the URL of the aggregation", frozen=True ) rights: Rights = Field( diff --git a/hsmodels/schemas/fields.py b/hsmodels/schemas/fields.py index a998b4f..b10fb81 100644 --- a/hsmodels/schemas/fields.py +++ b/hsmodels/schemas/fields.py @@ -1,5 +1,5 @@ from datetime import datetime -from typing import Dict, Literal, Optional +from typing import Dict, Literal from pydantic import AnyUrl, EmailStr, Field, HttpUrl, field_validator, model_validator @@ -159,13 +159,13 @@ class Config: default=None, title="Creator order", description="An integer to order creators", - allow_mutation=False, + frozen=True, ) hydroshare_user_id: int = Field( default=None, title="Hydroshare user id", description="An integer containing the Hydroshare user ID", - allow_mutation=False, + frozen=True, ) identifiers: Dict[UserIdentifierType, AnyUrl] = Field( default={}, @@ -221,7 +221,7 @@ class Config: default=None, title="Hyroshare user id", description="An integer containing the Hydroshare user ID", - allow_mutation=False, + frozen=True, ) identifiers: Dict[UserIdentifierType, AnyUrl] = Field( default={}, @@ -280,37 +280,37 @@ class Config: title = 'Raster Band Metadata' name: str = Field(max_length=500, title="Name", description="A string containing the name of the raster band") - variable_name: Optional[str] = Field( + variable_name: str = Field( default=None, max_length=100, title="Variable name", description="A string containing the name of the variable represented by the raster band", ) - variable_unit: Optional[str] = Field( + variable_unit: str = Field( default=None, max_length=50, title="Variable unit", description="A string containing the units for the raster band variable", ) - no_data_value: Optional[str] = Field( + no_data_value: str = Field( default=None, title="Nodata value", description="A string containing the numeric nodata value for the raster band", ) - maximum_value: Optional[str] = Field( + maximum_value: str = Field( default=None, title="Maximum value", description="A string containing the maximum numeric value for the raster band", ) - comment: Optional[str] = Field( + comment: str = Field( default=None, title="Comment", description="A string containing a comment about the raster band" ) - method: Optional[str] = Field( + method: str = Field( default=None, title="Method", description="A string containing a description of the method used to create the raster band data", ) - minimum_value: Optional[str] = Field( + minimum_value: str = Field( default=None, title="Minimum value", description="A string containing the minimum numerica value for the raster dataset", @@ -837,7 +837,7 @@ class Config: title="Geographic coverage type", description="A string containing the type of geographic coverage", ) - name: Optional[str] = Field( + name: str = Field( default=None, title="Name", description="A string containing a name for the place associated with the geographic coverage", @@ -956,5 +956,5 @@ class Config: title="Model program file type", description="The type of the file used by the model program" ) url: AnyUrl = Field( - title="Model program file url", description="The url of the file used by the model program", default=None + title="Model program file url", description="The url of the file used by the model program" ) diff --git a/hsmodels/schemas/rdf/fields.py b/hsmodels/schemas/rdf/fields.py index 563868d..1a3dd04 100644 --- a/hsmodels/schemas/rdf/fields.py +++ b/hsmodels/schemas/rdf/fields.py @@ -1,4 +1,4 @@ -from typing import Any, Callable, Optional +from typing import Any, Callable from pydantic.json_schema import JsonSchemaValue from typing_extensions import Annotated @@ -150,15 +150,15 @@ class Config: class CreatorInRDF(RDFBaseModel): creator_order: PositiveInt name: str = Field(default=None) - phone: Optional[str] = Field(default=None) - address: Optional[str] = Field(default=None) - organization: Optional[str] = Field(default=None) - email: Optional[EmailStr] = Field(default=None) - homepage: Optional[HttpUrl] = Field(default=None) - hydroshare_user_id: Optional[int] = Field(default=None) - ORCID: Optional[AnyUrl] = Field(default=None) - google_scholar_id: Optional[AnyUrl] = Field(default=None) - research_gate_id: Optional[AnyUrl] = Field(default=None) + phone: str = Field(default=None) + address: str = Field(default=None) + organization: str = Field(default=None) + email: EmailStr = Field(default=None) + homepage: HttpUrl = Field(default=None) + hydroshare_user_id: int = Field(default=None) + ORCID: AnyUrl = Field(default=None) + google_scholar_id: AnyUrl = Field(default=None) + research_gate_id: AnyUrl = Field(default=None) _group_identifiers = model_validator(mode='before')(split_user_identifiers) diff --git a/hsmodels/schemas/rdf/resource.py b/hsmodels/schemas/rdf/resource.py index 8a36fc7..9132492 100644 --- a/hsmodels/schemas/rdf/resource.py +++ b/hsmodels/schemas/rdf/resource.py @@ -2,7 +2,6 @@ from typing import List, Literal from pydantic import AnyUrl, BaseModel, Field, field_validator, model_validator -from rdflib.term import Identifier as RDFIdentifier from hsmodels.namespaces import CITOTERMS, DC, DCTERMS, HSRESOURCE, HSTERMS, ORE, RDF from hsmodels.schemas.rdf.fields import ( diff --git a/hsmodels/schemas/resource.py b/hsmodels/schemas/resource.py index 17ad456..85bdf47 100644 --- a/hsmodels/schemas/resource.py +++ b/hsmodels/schemas/resource.py @@ -1,5 +1,5 @@ from datetime import datetime -from typing import Dict, List, Optional, Union, Literal +from typing import Dict, List, Union, Literal from pydantic import AnyUrl, Field, field_validator, model_validator @@ -41,7 +41,7 @@ class Config: } title: str = Field( - max_length=300, default=None, title="Title", description="A string containing the name given to a resource" + max_length=300, title="Title", description="A string containing the name given to a resource" ) abstract: str = Field(default=None, title="Abstract", description="A string containing a summary of a resource") language: str = Field( @@ -82,7 +82,7 @@ class Config: title="Funding agency information", description="A list of objects containing information about the funding agencies and awards associated with a resource", ) - spatial_coverage: Optional[Union[PointCoverage, BoxCoverage]] = Field( + spatial_coverage: Union[PointCoverage, BoxCoverage] = Field( default=None, title="Spatial coverage", description="An object containing information about the spatial topic of a resource, the spatial applicability of a resource, or jurisdiction under with a resource is relevant", From cb01aa4cb7bb7066397c94ff089abaac7e2d64f3 Mon Sep 17 00:00:00 2001 From: pkdash Date: Wed, 6 Sep 2023 17:43:13 -0400 Subject: [PATCH 06/20] [#44] leaving some comments in tests related to use of None as data value --- tests/test_validation.py | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/tests/test_validation.py b/tests/test_validation.py index bed13e9..e10ce2f 100644 --- a/tests/test_validation.py +++ b/tests/test_validation.py @@ -327,7 +327,7 @@ def test_aggregation_metadata_from_form(): "units": "Decimal degrees", "projection": "WGS 84 EPSG:4326", }, - "period_coverage": None, + # "period_coverage": None, "rights": { "statement": "This resource is shared under the Creative Commons Attribution CC BY.", "url": "http://creativecommons.org/licenses/by/4.0/", @@ -335,12 +335,12 @@ def test_aggregation_metadata_from_form(): "type": "GeoRaster", "band_information": { "name": "Band_1", - "variable_name": None, - "variable_unit": None, + # "variable_name": None, + # "variable_unit": None, "no_data_value": "-3.40282346639e+38", "maximum_value": "2880.00708008", - "comment": None, - "method": None, + # "comment": None, + # "method": None, "minimum_value": "2274.95898438", }, "spatial_reference": { @@ -361,6 +361,11 @@ def test_aggregation_metadata_from_form(): "cell_size_y_value": 30, }, } + # TODO: In the above data, commented all fields set to None in order for the validation to pass for + # GeographicRasterMetadata. If we want the fields with None value to be passed as input, + # then the schema needs to be updated using Optional[]. Example: "period_coverage": Optional[PeriodCoverage] + # Then if we do this schema change for GeographicRasterMetadata, we need to do the same for all other schemas where + # the field default value is None. agg = GeographicRasterMetadata(**md) assert agg.spatial_reference.type == "box" assert agg.spatial_coverage.type == "box" @@ -379,6 +384,13 @@ def test_subjects_aggregation(agg_md): def test_default_exclude_none(res_md): - res_md.spatial_coverage = None - assert "spatial_coverage" not in res_md.model_dump() + # TODO: we can't do the following assignment unless we change the schema for spatial_coverage + # to Optional[Union[PointCoverage, BoxCoverage]] + # res_md.spatial_coverage = None + assert "spatial_coverage" in res_md.model_dump() + model_data = res_md.model_dump() + + # remove spatial_coverage from input data + model_data.pop("spatial_coverage") + res_md = type(res_md)(**model_data) assert "spatial_coverage" in res_md.model_dump(exclude_none=False) From 92da1017e95c8af3e36c189fc716dd670c15fe39 Mon Sep 17 00:00:00 2001 From: pkdash Date: Fri, 15 Sep 2023 17:51:00 -0400 Subject: [PATCH 07/20] [#44] bump up version --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 028159b..5cebaad 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ setup( name='hsmodels', - version='0.5.8', + version='0.5.9', packages=find_packages(include=['hsmodels', 'hsmodels.*', 'hsmodels.schemas.*', 'hsmodels.schemas.rdf.*'], exclude=("tests",)), install_requires=[ From 9ace95c0a68ec22c0e56ed25d0d34d507dcf6760 Mon Sep 17 00:00:00 2001 From: pkdash Date: Tue, 19 Sep 2023 11:03:18 -0400 Subject: [PATCH 08/20] [#44] making creator_order optional --- hsmodels/schemas/fields.py | 4 ++-- hsmodels/schemas/rdf/fields.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/hsmodels/schemas/fields.py b/hsmodels/schemas/fields.py index b10fb81..7b45d82 100644 --- a/hsmodels/schemas/fields.py +++ b/hsmodels/schemas/fields.py @@ -1,5 +1,5 @@ from datetime import datetime -from typing import Dict, Literal +from typing import Dict, Literal, Optional from pydantic import AnyUrl, EmailStr, Field, HttpUrl, field_validator, model_validator @@ -155,7 +155,7 @@ class Config: title="Homepage", description="An object containing the URL for website associated with the creator", ) - creator_order: int = Field( + creator_order: Optional[int] = Field( default=None, title="Creator order", description="An integer to order creators", diff --git a/hsmodels/schemas/rdf/fields.py b/hsmodels/schemas/rdf/fields.py index 1a3dd04..75d4efd 100644 --- a/hsmodels/schemas/rdf/fields.py +++ b/hsmodels/schemas/rdf/fields.py @@ -1,4 +1,4 @@ -from typing import Any, Callable +from typing import Any, Callable, Optional from pydantic.json_schema import JsonSchemaValue from typing_extensions import Annotated @@ -148,7 +148,7 @@ class Config: class CreatorInRDF(RDFBaseModel): - creator_order: PositiveInt + creator_order: Optional[PositiveInt] = Field(default=None) name: str = Field(default=None) phone: str = Field(default=None) address: str = Field(default=None) From e69dba5902f5b0bcf8b3972ab7c83c79ef9ddac4 Mon Sep 17 00:00:00 2001 From: pkdash Date: Tue, 19 Sep 2023 11:05:40 -0400 Subject: [PATCH 09/20] [#44] fixing default value for type field of raster aggregation model --- hsmodels/schemas/aggregations.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/hsmodels/schemas/aggregations.py b/hsmodels/schemas/aggregations.py index 1f19496..7c64058 100644 --- a/hsmodels/schemas/aggregations.py +++ b/hsmodels/schemas/aggregations.py @@ -1,5 +1,5 @@ from datetime import date -from typing import Dict, List, Union, Literal +from typing import Dict, List, Union from pydantic import AnyUrl, Field, model_validator, field_validator @@ -110,11 +110,9 @@ class Config: class GeographicRasterMetadata(GeographicRasterMetadataIn): - _type = Literal[AggregationType.GeographicRasterAggregation] - type: AggregationType = Field( frozen=True, - default=_type, + default=AggregationType.GeographicRasterAggregation, title="Aggregation type", description="A string expressing the aggregation type from the list of HydroShare aggregation types", ) From 33e48b8c854f8fe1d17794e8edc2f1cf0519db2c Mon Sep 17 00:00:00 2001 From: pkdash Date: Tue, 19 Sep 2023 11:07:28 -0400 Subject: [PATCH 10/20] [#44] assigning creator order value if missing --- hsmodels/schemas/rdf/validators.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/hsmodels/schemas/rdf/validators.py b/hsmodels/schemas/rdf/validators.py index 9fe4e20..63e5a79 100644 --- a/hsmodels/schemas/rdf/validators.py +++ b/hsmodels/schemas/rdf/validators.py @@ -75,4 +75,17 @@ def sort_creators(cls, creators): for index, creator in enumerate(creators): creator["creator_order"] = index + 1 return creators - return sorted(creators, key=lambda creator: creator.creator_order) + else: + # assign creator_order to creators that don't have it + creator_order_numbers = [c.creator_order for c in creators if c.creator_order is not None] + if creator_order_numbers: + if len(creator_order_numbers) != len(set(creator_order_numbers)): + raise ValueError("creator_order values must be unique") + max_order_number = max(creator_order_numbers) + else: + max_order_number = 0 + + creators_without_order = [c for c in creators if c.creator_order is None] + for index, creator in enumerate(creators_without_order): + creator.creator_order = max_order_number + index + 1 + return sorted(creators, key=lambda _creator: _creator.creator_order) From 04d4499e9e5b9c279ac6bfebcc2fb68c0aab65a3 Mon Sep 17 00:00:00 2001 From: pkdash Date: Tue, 19 Sep 2023 11:08:19 -0400 Subject: [PATCH 11/20] [#44] tests for creator order --- tests/test_metadata.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/test_metadata.py b/tests/test_metadata.py index 6c67264..a35b47a 100644 --- a/tests/test_metadata.py +++ b/tests/test_metadata.py @@ -87,6 +87,11 @@ def test_resource_metadata(res_md): assert res_md.additional_metadata["key2"] == "value2" assert len(res_md.creators) == 3 + for cr in res_md.creators: + assert cr.creator_order in (1, 2, 3) + creator_orders = [cr.creator_order for cr in res_md.creators] + assert len(creator_orders) == len(set(creator_orders)) + creator = res_md.creators[0] assert creator.organization == 'Utah State University' assert creator.email == 'jeff.horsburgh@usu.edu' From e11be2fd8f6d84cbc3faa81e74aabfd52a42608f Mon Sep 17 00:00:00 2001 From: pkdash Date: Wed, 27 Sep 2023 23:28:02 -0400 Subject: [PATCH 12/20] [#44] updating _rdf_fields() - no need to look for rdf predicate in model_config --- hsmodels/schemas/__init__.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/hsmodels/schemas/__init__.py b/hsmodels/schemas/__init__.py index 0c3042c..81619b5 100644 --- a/hsmodels/schemas/__init__.py +++ b/hsmodels/schemas/__init__.py @@ -99,11 +99,6 @@ def _rdf_fields(schema): predicate = None if finfo.json_schema_extra: predicate = finfo.json_schema_extra.get('rdf_predicate', None) - - if not predicate: - config_field_info = schema.model_config['fields'].get(fname, None) - if isinstance(config_field_info, dict): - predicate = config_field_info.get('rdf_predicate', None) if not predicate: raise Exception( "Schema configuration error for {}, all fields must specify a rdf_predicate".format(schema) From 95a142c5284f271b7518e6048ab32aa73381014a Mon Sep 17 00:00:00 2001 From: pkdash Date: Wed, 27 Sep 2023 23:39:47 -0400 Subject: [PATCH 13/20] [#44] moving rdf_predicate from inner Config class to Field class --- hsmodels/schemas/fields.py | 120 ++++++++++++++++---- hsmodels/schemas/rdf/fields.py | 193 +++++++-------------------------- 2 files changed, 134 insertions(+), 179 deletions(-) diff --git a/hsmodels/schemas/fields.py b/hsmodels/schemas/fields.py index 7b45d82..cf59fcd 100644 --- a/hsmodels/schemas/fields.py +++ b/hsmodels/schemas/fields.py @@ -3,6 +3,7 @@ from pydantic import AnyUrl, EmailStr, Field, HttpUrl, field_validator, model_validator +from hsmodels.namespaces import HSTERMS from hsmodels.schemas import base_models from hsmodels.schemas.base_models import BaseMetadata from hsmodels.schemas.enums import ModelProgramFileType, RelationType, UserIdentifierType, VariableType @@ -37,23 +38,30 @@ class Config: title = 'Raster Cell Metadata' # TODO: Is there such a thing as "name" for CellInformation? - name: str = Field(default=None, max_length=500, title="Name", description="Name of the cell information") - rows: int = Field(default=None, title="Rows", description="The integer number of rows in the raster dataset") + name: str = Field(default=None, max_length=500, title="Name", description="Name of the cell information", + rdf_predicate=HSTERMS.name) + rows: int = Field(default=None, title="Rows", + description="The integer number of rows in the raster dataset", + rdf_predicate=HSTERMS.rows) columns: int = Field( - default=None, title="Columns", description="The integer number of columns in the raster dataset" + default=None, title="Columns", description="The integer number of columns in the raster dataset", + rdf_predicate=HSTERMS.columns ) cell_size_x_value: float = Field( default=None, title="Cell size x value", description="The size of the raster grid cell in the x-direction expressed as a float", + rdf_predicate=HSTERMS.cellSizeXValue ) cell_data_type: str = Field( - default=None, max_length=50, title="Cell data type", description="The data type of the raster grid cell values" + default=None, max_length=50, title="Cell data type", description="The data type of the raster grid cell values", + rdf_predicate=HSTERMS.cellDataType ) cell_size_y_value: float = Field( default=None, title="Cell size y value", description="The size of the raster grid cell in the y-direction expressed as a float", + rdf_predicate=HSTERMS.cellSizeYValue ) @@ -66,11 +74,13 @@ class Config: title = 'Rights Metadata' statement: str = Field( - title="Statement", description="A string containing the text of the license or rights statement" + title="Statement", description="A string containing the text of the license or rights statement", + rdf_predicate=HSTERMS.rightsStatement ) url: AnyUrl = Field( title="URL", description="An object containing the URL pointing to a description of the license or rights statement", + rdf_predicate=HSTERMS.URL ) @classmethod @@ -256,18 +266,22 @@ class Config: title = 'Funding Agency Metadata' funding_agency_name: str = Field( - title="Agency name", description="A string containing the name of the funding agency or organization" + title="Agency name", description="A string containing the name of the funding agency or organization", + rdf_predicate=HSTERMS.fundingAgencyName ) title: str = Field( - default=None, title="Award title", description="A string containing the title of the project or award" + default=None, title="Award title", description="A string containing the title of the project or award", + rdf_predicate=HSTERMS.awardTitle ) number: str = Field( - default=None, title="Award number", description="A string containing the award number or other identifier" + default=None, title="Award number", description="A string containing the award number or other identifier", + rdf_predicate=HSTERMS.awardNumber ) funding_agency_url: AnyUrl = Field( default=None, title="Agency URL", description="An object containing a URL pointing to a website describing the funding award", + rdf_predicate=HSTERMS.fundingAgencyURL ) @@ -279,41 +293,50 @@ class BandInformation(BaseMetadata): class Config: title = 'Raster Band Metadata' - name: str = Field(max_length=500, title="Name", description="A string containing the name of the raster band") + name: str = Field(max_length=500, title="Name", description="A string containing the name of the raster band", + rdf_predicate=HSTERMS.name + ) variable_name: str = Field( default=None, max_length=100, title="Variable name", description="A string containing the name of the variable represented by the raster band", + rdf_predicate=HSTERMS.variableName ) variable_unit: str = Field( default=None, max_length=50, title="Variable unit", description="A string containing the units for the raster band variable", + rdf_predicate=HSTERMS.variableUnit ) no_data_value: str = Field( default=None, title="Nodata value", description="A string containing the numeric nodata value for the raster band", + rdf_predicate=HSTERMS.noDataValue ) maximum_value: str = Field( default=None, title="Maximum value", description="A string containing the maximum numeric value for the raster band", + rdf_predicate=HSTERMS.maximumValue ) comment: str = Field( - default=None, title="Comment", description="A string containing a comment about the raster band" + default=None, title="Comment", description="A string containing a comment about the raster band", + rdf_predicate=HSTERMS.comment ) method: str = Field( default=None, title="Method", description="A string containing a description of the method used to create the raster band data", + rdf_predicate=HSTERMS.method ) minimum_value: str = Field( default=None, title="Minimum value", description="A string containing the minimum numerica value for the raster dataset", + rdf_predicate=HSTERMS.minimumValue ) @@ -327,10 +350,12 @@ class Config: title = 'Geographic Feature Field Metadata' field_name: str = Field( - max_length=128, title="Field name", description="A string containing the name of the attribute table field" + max_length=128, title="Field name", description="A string containing the name of the attribute table field", + rdf_predicate=HSTERMS.fieldName ) field_type: str = Field( - max_length=128, title="Field type", description="A string containing the data type of the values in the field" + max_length=128, title="Field type", description="A string containing the data type of the values in the field", + rdf_predicate=HSTERMS.fieldType ) # TODO: What is the "field_type_code"? It's not displayed on the resource landing page, but it's encoded in the # aggregation metadata as an integer value. @@ -339,14 +364,17 @@ class Config: max_length=50, title="Field type code", description="A string value containing a code that indicates the field type", + rdf_predicate=HSTERMS.fieldTypeCode ) field_width: int = Field( - default=None, title="Field width", description="An integer value containing the width of the attribute field" + default=None, title="Field width", description="An integer value containing the width of the attribute field", + rdf_predicate=HSTERMS.fieldWidth ) field_precision: int = Field( default=None, title="Field precision", description="An integer value containing the precision of the attribute field", + rdf_predicate=HSTERMS.fieldPrecision ) @@ -362,11 +390,13 @@ class Config: default=0, title="Feature count", description="An integer containing the number of features in the geographic feature aggregation", + rdf_predicate=HSTERMS.featureCount ) geometry_type: str = Field( max_length=128, title="Geometry type", description="A string containing the type of features in the geographic feature aggregation", + rdf_predicate=HSTERMS.geometryType ) @@ -379,35 +409,43 @@ class Config: title = 'Multidimensional Variable Metadata' name: str = Field( - max_length=1000, title="Variable name", description="A string containing the name of the variable" + max_length=1000, title="Variable name", description="A string containing the name of the variable", + rdf_predicate=HSTERMS.name ) unit: str = Field( max_length=1000, title="Units", description="A string containing the units in which the values for the variable are expressed", + rdf_predicate=HSTERMS.unit ) - type: VariableType = Field(title="Type", description="The data type of the values for the variable") + type: VariableType = Field(title="Type", description="The data type of the values for the variable", + rdf_predicate=HSTERMS.type + ) shape: str = Field( max_length=1000, title="Shape", description="A string containing the shape of the variable expressed as a list of dimensions", + rdf_predicate=HSTERMS.shape ) descriptive_name: str = Field( default=None, max_length=1000, title="Descriptive name", description="A string containing a descriptive name for the variable", + rdf_predicate=HSTERMS.descriptive_name ) method: str = Field( default=None, title="Method", description="A string containing a description of the method used to create the values for the variable", + rdf_predicate=HSTERMS.method ) missing_value: str = Field( default=None, max_length=1000, title="Missing value", description="A string containing the value used to indicate missing values for the variable", + rdf_predicate=HSTERMS.missing_value ) @@ -420,10 +458,12 @@ class Config: title = 'Publisher Metadata' name: str = Field( - max_length=200, title="Publisher name", description="A string containing the name of the publisher" + max_length=200, title="Publisher name", description="A string containing the name of the publisher", + rdf_predicate=HSTERMS.publisherName ) url: AnyUrl = Field( - title="Publisher URL", description="An object containing a URL that points to the publisher website" + title="Publisher URL", description="An object containing a URL that points to the publisher website", + rdf_predicate=HSTERMS.publisherURL ) @@ -439,30 +479,37 @@ class Config: max_length=50, title="Variable code", description="A string containing a short but meaningful code that identifies a variable", + rdf_predicate=HSTERMS.VariableCode ) variable_name: str = Field( - max_length=100, title="Variable name", description="A string containing the name of the variable" + max_length=100, title="Variable name", description="A string containing the name of the variable", + rdf_predicate=HSTERMS.VariableName ) variable_type: str = Field( max_length=100, title="Variable type", description="A string containing the type of variable from the ODM2 VariableType controlled vocabulary", + rdf_predicate=HSTERMS.VariableType ) # TODO: The NoData value for a variable in an ODM2 database is not always an integer. # It could be a floating point value. We might want to change this to a string or a floating point value # It is an integer in the HydroShare database, so will have to be updated there as well if changed - no_data_value: int = Field(title="NoData value", description="The NoData value for the variable") + no_data_value: int = Field(title="NoData value", description="The NoData value for the variable", + rdf_predicate=HSTERMS.NoDataValue + ) variable_definition: str = Field( default=None, max_length=255, title="Variable definition", description="A string containing a detailed description of the variable", + rdf_predicate=HSTERMS.VariableDefinition ) speciation: str = Field( default=None, max_length=255, title="Speciation", - description="A string containing the speciation for the variable from the ODM2 Speciation controllec vocabulary", + description="A string containing the speciation for the variable from the ODM2 Speciation control vocabulary", + rdf_predicate=HSTERMS.Speciation ) @@ -478,36 +525,43 @@ class Config: max_length=200, title="Site code", description="A string containing a short but meaningful code identifying the site", + rdf_predicate=HSTERMS.SiteCode ) site_name: str = Field( - default=None, max_length=255, title="Site name", description="A string containing the name of the site" + default=None, max_length=255, title="Site name", description="A string containing the name of the site", + rdf_predicate=HSTERMS.SiteName ) elevation_m: float = Field( default=None, title="Elevation", description="A floating point number expressing the elevation of the site in meters", + rdf_predicate=HSTERMS.Elevation_m ) elevation_datum: str = Field( default=None, max_length=50, title="Elevation datum", description="A string expressing the elevation datum used from the ODM2 Elevation Datum controlled vocabulary", + rdf_predicate=HSTERMS.ElevationDatum ) site_type: str = Field( default=None, max_length=100, title="Site type", description="A string containing the type of site from the ODM2 Sampling Feature Type controlled vocabulary ", + rdf_predicate=HSTERMS.SiteType ) latitude: float = Field( default=None, title="Latitude", description="A floating point value expressing the latitude coordinate of the site", + rdf_predicate=HSTERMS.Latitude ) longitude: float = Field( default=None, title="Longitude", description="A floating point value expressing the longitude coordinate of the site", + rdf_predicate=HSTERMS.Longitude ) @@ -523,22 +577,28 @@ class Config: max_length=50, title="Method code", description="A string containing a short but meaningful code identifying the method", + rdf_predicate=HSTERMS.MethodCode ) method_name: str = Field( - max_length=200, title="Method name", description="A string containing the name of the method" + max_length=200, title="Method name", description="A string containing the name of the method", + rdf_predicate=HSTERMS.MethodName ) method_type: str = Field( max_length=200, title="Method type", description="A string containing the method type from the ODM2 Method Type controlled vocabulary", + rdf_predicate=HSTERMS.MethodType ) method_description: str = Field( - default=None, title="Method description", description="A string containing a detailed description of the method" + default=None, title="Method description", + description="A string containing a detailed description of the method", + rdf_predicate=HSTERMS.MethodDescription ) method_link: AnyUrl = Field( default=None, title="Method link", description="An object containing a URL that points to a website having a detailed description of the method", + rdf_predicate=HSTERMS.MethodLink ) @@ -555,17 +615,20 @@ class Config: max_length=50, title="Processing level code", description="A string containing a short but meaningful code identifying the processing level", + rdf_predicate=HSTERMS.ProcessingLevelCode ) definition: str = Field( default=None, max_length=200, title="Definition", description="A string containing a description of the processing level", + rdf_predicate=HSTERMS.Definition ) explanation: str = Field( default=None, title="Explanation", description="A string containing a more extensive explanation of the meaning of the processing level", + rdf_predicate=HSTERMS.Explanation ) @@ -581,16 +644,19 @@ class Config: max_length=255, title="Unit type", description="A string containing the type of unit from the ODM2 Units Type controlled vocabulary", + rdf_predicate=HSTERMS.UnitsType ) name: str = Field( max_length=255, title="Unit name", description="A string containing the name of the unit from the ODM2 units list", + rdf_predicate=HSTERMS.UnitsName ) abbreviation: str = Field( max_length=20, title="Unit abbreviation", description="A string containing an abbreviation for the unit from the ODM2 units list", + rdf_predicate=HSTERMS.UnitsAbbreviation ) @@ -606,6 +672,7 @@ class Config: default=0, title="UTC offset value", description="A floating point number containing the UTC time offset associated with the data values expressed in hours", + rdf_predicate=HSTERMS.value ) @@ -621,31 +688,37 @@ class Config: max_length=36, title="Series ID", description="A string containing a unique identifier for the time series result", + rdf_predicate=HSTERMS.timeSeriesResultUUID ) unit: Unit = Field( default=None, title="Units", description="An object containing the units in which the values of the time series are expressed", + ) status: str = Field( default=None, max_length=255, title="Status", description="A string containing the status of the time series result chosen from the ODM2 Status controlled vocabulary", + rdf_predicate=HSTERMS.Status ) sample_medium: str = Field( max_length=255, title="Sample medium", description="A string containing the sample medium in which the time series result was measured chosen from the ODM2 Medium controlled vocabulary", + rdf_predicate=HSTERMS.SampleMedium ) value_count: int = Field( title="Value count", description="An integer value containing the number of data values contained within the time series result", + rdf_predicate=HSTERMS.ValueCount ) aggregation_statistic: str = Field( max_length=255, title="Aggregation statistic", description="A string containing the aggregation statistic associated with the values of the time series result chosen from the ODM2 Aggregation Statistic controlled vocabulary", + rdf_predicate=HSTERMS.AggregationStatistic ) # TODO: Not sure what "series_label" is. It's not an ODM2 thing # in HydroShare it is generated with this format @@ -655,6 +728,7 @@ class Config: max_length=255, title="Series label", description="A string containing a label for the time series result", + rdf_predicate=HSTERMS.SeriesLabel ) site: TimeSeriesSite = Field( title="Site", diff --git a/hsmodels/schemas/rdf/fields.py b/hsmodels/schemas/rdf/fields.py index 75d4efd..5985f4e 100644 --- a/hsmodels/schemas/rdf/fields.py +++ b/hsmodels/schemas/rdf/fields.py @@ -6,7 +6,8 @@ from datetime import datetime from pydantic_core import core_schema -from pydantic import AnyUrl, BaseModel, EmailStr, Field, GetJsonSchemaHandler, HttpUrl, PositiveInt, model_validator +from pydantic import AnyUrl, BaseModel, EmailStr, Field, GetJsonSchemaHandler, HttpUrl, PositiveInt, \ + model_validator from rdflib import BNode from rdflib.term import Identifier as RDFIdentifier @@ -126,15 +127,7 @@ class ExtendedMetadataInRDF(RDFBaseModel): class CellInformationInRDF(CellInformation, RDFBaseModel): - class Config: - fields = { - 'name': {"rdf_predicate": HSTERMS.name}, - 'rows': {"rdf_predicate": HSTERMS.rows}, - 'columns': {"rdf_predicate": HSTERMS.columns}, - 'cell_size_x_value': {"rdf_predicate": HSTERMS.cellSizeXValue}, - 'cell_data_type': {"rdf_predicate": HSTERMS.cellDataType}, - 'cell_size_y_value': {"rdf_predicate": HSTERMS.cellSizeYValue}, - } + pass class DateInRDF(RDFBaseModel): @@ -143,92 +136,46 @@ class DateInRDF(RDFBaseModel): class RightsInRDF(Rights, RDFBaseModel): - class Config: - fields = {'statement': {"rdf_predicate": HSTERMS.rightsStatement}, 'url': {"rdf_predicate": HSTERMS.URL}} + pass class CreatorInRDF(RDFBaseModel): - creator_order: Optional[PositiveInt] = Field(default=None) - name: str = Field(default=None) - phone: str = Field(default=None) - address: str = Field(default=None) - organization: str = Field(default=None) - email: EmailStr = Field(default=None) - homepage: HttpUrl = Field(default=None) - hydroshare_user_id: int = Field(default=None) - ORCID: AnyUrl = Field(default=None) - google_scholar_id: AnyUrl = Field(default=None) - research_gate_id: AnyUrl = Field(default=None) + creator_order: Optional[PositiveInt] = Field(default=None, rdf_predicate=HSTERMS.creatorOrder) + name: str = Field(default=None, rdf_predicate=HSTERMS.name) + phone: str = Field(default=None, rdf_predicate=HSTERMS.phone) + address: str = Field(default=None, rdf_predicate=HSTERMS.address) + organization: str = Field(default=None, rdf_predicate=HSTERMS.organization) + email: EmailStr = Field(default=None, rdf_predicate=HSTERMS.email) + homepage: HttpUrl = Field(default=None, rdf_predicate=HSTERMS.homepage) + hydroshare_user_id: int = Field(default=None, rdf_predicate=HSTERMS.hydroshare_user_id) + ORCID: AnyUrl = Field(default=None, rdf_predicate=HSTERMS.ORCID) + google_scholar_id: AnyUrl = Field(default=None, rdf_predicate=HSTERMS.GoogleScholarID) + research_gate_id: AnyUrl = Field(default=None, rdf_predicate=HSTERMS.ResearchGateID) _group_identifiers = model_validator(mode='before')(split_user_identifiers) - class Config: - fields = { - 'name': {"rdf_predicate": HSTERMS.name}, - 'creator_order': {"rdf_predicate": HSTERMS.creatorOrder}, - 'google_scholar_id': {"rdf_predicate": HSTERMS.GoogleScholarID}, - 'research_gate_id': {"rdf_predicate": HSTERMS.ResearchGateID}, - 'phone': {"rdf_predicate": HSTERMS.phone}, - 'ORCID': {"rdf_predicate": HSTERMS.ORCID}, - 'address': {"rdf_predicate": HSTERMS.address}, - 'organization': {"rdf_predicate": HSTERMS.organization}, - 'email': {"rdf_predicate": HSTERMS.email}, - 'homepage': {"rdf_predicate": HSTERMS.homepage}, - 'hydroshare_user_id': {"rdf_predicate": HSTERMS.hydroshare_user_id}, - } - class ContributorInRDF(RDFBaseModel): - name: str = Field(default=None) - phone: str = Field(default=None) - address: str = Field(default=None) - organization: str = Field(default=None) - email: EmailStr = Field(default=None) - homepage: HttpUrl = Field(default=None) - hydroshare_user_id: int = Field(default=None) - ORCID: AnyUrl = Field(default=None) - google_scholar_id: AnyUrl = Field(default=None) - research_gate_id: AnyUrl = Field(default=None) + name: str = Field(default=None, rdf_predicate=HSTERMS.name) + phone: str = Field(default=None, rdf_predicate=HSTERMS.phone) + address: str = Field(default=None, rdf_predicate=HSTERMS.address) + organization: str = Field(default=None, rdf_predicate=HSTERMS.organization) + email: EmailStr = Field(default=None, rdf_predicate=HSTERMS.email) + homepage: HttpUrl = Field(default=None, rdf_predicate=HSTERMS.homepage) + hydroshare_user_id: int = Field(default=None, rdf_predicate=HSTERMS.hydroshare_user_id) + ORCID: AnyUrl = Field(default=None, rdf_predicate=HSTERMS.ORCID) + google_scholar_id: AnyUrl = Field(default=None, rdf_predicate=HSTERMS.GoogleScholarID) + research_gate_id: AnyUrl = Field(default=None, rdf_predicate=HSTERMS.ResearchGateID) _group_identifiers = model_validator(mode='before')(split_user_identifiers) - class Config: - fields = { - 'name': {"rdf_predicate": HSTERMS.name}, - 'phone': {"rdf_predicate": HSTERMS.phone}, - 'address': {"rdf_predicate": HSTERMS.address}, - 'organization': {"rdf_predicate": HSTERMS.organization}, - 'email': {"rdf_predicate": HSTERMS.email}, - 'homepage': {"rdf_predicate": HSTERMS.homepage}, - 'ORCID': {"rdf_predicate": HSTERMS.ORCID}, - 'google_scholar_id': {"rdf_predicate": HSTERMS.GoogleScholarID}, - 'research_gate_id': {"rdf_predicate": HSTERMS.ResearchGateID}, - 'hydroshare_user_id': {"rdf_predicate": HSTERMS.hydroshare_user_id}, - } - class AwardInfoInRDF(AwardInfo, RDFBaseModel): - class Config: - fields = { - 'funding_agency_name': {"rdf_predicate": HSTERMS.fundingAgencyName}, - 'title': {"rdf_predicate": HSTERMS.awardTitle}, - 'number': {"rdf_predicate": HSTERMS.awardNumber}, - 'funding_agency_url': {"rdf_predicate": HSTERMS.fundingAgencyURL}, - } + pass class BandInformationInRDF(BandInformation, RDFBaseModel): - class Config: - fields = { - 'name': {"rdf_predicate": HSTERMS.name}, - 'variable_name': {"rdf_predicate": HSTERMS.variableName}, - 'variable_unit': {"rdf_predicate": HSTERMS.variableUnit}, - 'no_data_value': {"rdf_predicate": HSTERMS.noDataValue}, - 'maximum_value': {"rdf_predicate": HSTERMS.maximumValue}, - 'comment': {"rdf_predicate": HSTERMS.comment}, - 'method': {"rdf_predicate": HSTERMS.method}, - 'minimum_value': {"rdf_predicate": HSTERMS.minimumValue}, - } + pass class CoverageInRDF(RDFBaseModel): @@ -247,99 +194,43 @@ class MultidimensionalSpatialReferenceInRDF(RDFBaseModel): class FieldInformationInRDF(FieldInformation, RDFBaseModel): - class Config: - fields = { - 'field_name': {"rdf_predicate": HSTERMS.fieldName}, - 'field_type': {"rdf_predicate": HSTERMS.fieldType}, - 'field_type_code': {"rdf_predicate": HSTERMS.fieldTypeCode}, - 'field_width': {"rdf_predicate": HSTERMS.fieldWidth}, - 'field_precision': {"rdf_predicate": HSTERMS.fieldPrecision}, - } + pass class GeometryInformationInRDF(GeometryInformation, RDFBaseModel): - class Config: - fields = { - 'feature_count': {"rdf_predicate": HSTERMS.featureCount}, - 'geometry_type': {"rdf_predicate": HSTERMS.geometryType}, - } + pass class VariableInRDF(Variable, RDFBaseModel): - class Config: - fields = { - 'name': {"rdf_predicate": HSTERMS.name}, - 'unit': {"rdf_predicate": HSTERMS.unit}, - 'type': {"rdf_predicate": HSTERMS.type}, - 'shape': {"rdf_predicate": HSTERMS.shape}, - 'descriptive_name': {"rdf_predicate": HSTERMS.descriptive_name}, - 'method': {"rdf_predicate": HSTERMS.method}, - 'missing_value': {"rdf_predicate": HSTERMS.missing_value}, - } + pass class PublisherInRDF(Publisher, RDFBaseModel): - class Config: - fields = {'name': {"rdf_predicate": HSTERMS.publisherName}, 'url': {"rdf_predicate": HSTERMS.publisherURL}} + pass class TimeSeriesVariableInRDF(TimeSeriesVariable, RDFBaseModel): - class Config: - fields = { - 'variable_code': {"rdf_predicate": HSTERMS.VariableCode}, - 'variable_name': {"rdf_predicate": HSTERMS.VariableName}, - 'variable_type': {"rdf_predicate": HSTERMS.VariableType}, - 'no_data_value': {"rdf_predicate": HSTERMS.NoDataValue}, - 'variable_definition': {"rdf_predicate": HSTERMS.VariableDefinition}, - 'speciation': {"rdf_predicate": HSTERMS.Speciation}, - } + pass class TimeSeriesSiteInRDF(TimeSeriesSite, RDFBaseModel): - class Config: - fields = { - 'site_code': {"rdf_predicate": HSTERMS.SiteCode}, - 'site_name': {"rdf_predicate": HSTERMS.SiteName}, - 'elevation_m': {"rdf_predicate": HSTERMS.Elevation_m}, - 'elevation_datum': {"rdf_predicate": HSTERMS.ElevationDatum}, - 'site_type': {"rdf_predicate": HSTERMS.SiteType}, - 'latitude': {"rdf_predicate": HSTERMS.Latitude}, - 'longitude': {"rdf_predicate": HSTERMS.Longitude}, - } + pass class TimeSeriesMethodInRDF(TimeSeriesMethod, RDFBaseModel): - class Config: - fields = { - 'method_code': {"rdf_predicate": HSTERMS.MethodCode}, - 'method_name': {"rdf_predicate": HSTERMS.MethodName}, - 'method_type': {"rdf_predicate": HSTERMS.MethodType}, - 'method_description': {"rdf_predicate": HSTERMS.MethodDescription}, - 'method_link': {"rdf_predicate": HSTERMS.MethodLink}, - } + pass class ProcessingLevelInRDF(ProcessingLevel, RDFBaseModel): - class Config: - fields = { - 'processing_level_code': {"rdf_predicate": HSTERMS.ProcessingLevelCode}, - 'definition': {"rdf_predicate": HSTERMS.Definition}, - 'explanation': {"rdf_predicate": HSTERMS.Explanation}, - } + pass class UnitInRDF(Unit, RDFBaseModel): - class Config: - fields = { - 'type': {"rdf_predicate": HSTERMS.UnitsType}, - 'name': {"rdf_predicate": HSTERMS.UnitsName}, - 'abbreviation': {"rdf_predicate": HSTERMS.UnitsAbbreviation}, - } + pass class UTCOffSetInRDF(UTCOffSet, RDFBaseModel): - class Config: - fields = {'value': {"rdf_predicate": HSTERMS.value}} + pass class TimeSeriesResultInRDF(TimeSeriesResult, RDFBaseModel): @@ -350,14 +241,4 @@ class TimeSeriesResultInRDF(TimeSeriesResult, RDFBaseModel): processing_level: ProcessingLevelInRDF = Field(rdf_predicate=HSTERMS.processingLevel) utc_offset: UTCOffSetInRDF = Field(rdf_predicate=HSTERMS.UTCOffSet, default=None) - class Config: - fields = { - 'series_id': {"rdf_predicate": HSTERMS.timeSeriesResultUUID}, - 'status': {"rdf_predicate": HSTERMS.Status}, - 'sample_medium': {"rdf_predicate": HSTERMS.SampleMedium}, - 'value_count': {"rdf_predicate": HSTERMS.ValueCount}, - 'aggregation_statistic': {"rdf_predicate": HSTERMS.AggregationStatistic}, - 'series_label': {"rdf_predicate": HSTERMS.SeriesLabel}, - } - _parse_utc_offset = model_validator(mode='before')(rdf_parse_utc_offset) From 089d9b87ff003b3e7ccc31d95a9e03aa528c9a00 Mon Sep 17 00:00:00 2001 From: pkdash Date: Fri, 29 Sep 2023 17:46:12 -0400 Subject: [PATCH 14/20] [#44] fixing deprecated function use in tests --- tests/test_schemas.py | 4 ++-- tests/test_validation.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_schemas.py b/tests/test_schemas.py index 221765e..7bbfcb7 100644 --- a/tests/test_schemas.py +++ b/tests/test_schemas.py @@ -46,7 +46,7 @@ @pytest.mark.parametrize("read_only_field", read_only_fields) def test_readonly(read_only_field): clazz, fields = read_only_field - s = clazz.schema()["properties"] + s = clazz.model_json_schema()["properties"] for field in s: if field in fields: assert "readOnly" in s[field] and s[field]["readOnly"] is True @@ -72,7 +72,7 @@ def test_readonly(read_only_field): @pytest.mark.parametrize("additional_metadata_field", additional_metadata_fields) def test_dictionary_field(additional_metadata_field): clazz, fields = additional_metadata_field - s = clazz.schema()["properties"] + s = clazz.model_json_schema()["properties"] for field in fields: assert 'additionalProperties' not in s[field] diff --git a/tests/test_validation.py b/tests/test_validation.py index e10ce2f..6e361b1 100644 --- a/tests/test_validation.py +++ b/tests/test_validation.py @@ -1,7 +1,7 @@ from datetime import datetime, timedelta import pytest -from pydantic.error_wrappers import ValidationError +from pydantic import ValidationError from hsmodels.namespaces import DCTERMS from hsmodels.schemas import GeographicRasterMetadata, load_rdf From af52482e9cb1845fe2554994389837f2554060ea Mon Sep 17 00:00:00 2001 From: pkdash Date: Fri, 29 Sep 2023 17:49:33 -0400 Subject: [PATCH 15/20] [#44] adding custom serialization to convert URIRef to AnyUrl type --- hsmodels/schemas/rdf/aggregations.py | 39 +++++++++++++++++++++++++++- hsmodels/schemas/rdf/resource.py | 13 +++++++--- hsmodels/schemas/root_validators.py | 8 ++++++ 3 files changed, 56 insertions(+), 4 deletions(-) diff --git a/hsmodels/schemas/rdf/aggregations.py b/hsmodels/schemas/rdf/aggregations.py index 273c571..2ee0a82 100644 --- a/hsmodels/schemas/rdf/aggregations.py +++ b/hsmodels/schemas/rdf/aggregations.py @@ -1,7 +1,8 @@ from datetime import date from typing import List, Literal -from pydantic import AnyUrl, Field, model_validator +from pydantic import AnyUrl, Field, field_serializer, model_validator +from rdflib import URIRef from hsmodels.namespaces import DC, HSTERMS, RDF from hsmodels.schemas.rdf.fields import ( @@ -61,6 +62,10 @@ class GeographicRasterMetadataInRDF(BaseAggregationMetadataInRDF): _parse_spatial_reference = model_validator(mode='before')(parse_rdf_spatial_reference) + @field_serializer('dc_type', 'rdf_type') + def serialize_url(self, _type: URIRef, _info): + return AnyUrl(_type) + class GeographicFeatureMetadataInRDF(BaseAggregationMetadataInRDF): rdf_type: AnyUrl = Field(rdf_predicate=RDF.type, frozen=True, default=HSTERMS.GeographicFeatureAggregation) @@ -77,6 +82,10 @@ class GeographicFeatureMetadataInRDF(BaseAggregationMetadataInRDF): _parse_spatial_reference = model_validator(mode='before')(parse_rdf_spatial_reference) + @field_serializer('dc_type', 'rdf_type') + def serialize_url(self, _type: URIRef, _info): + return AnyUrl(_type) + class MultidimensionalMetadataInRDF(BaseAggregationMetadataInRDF): rdf_type: AnyUrl = Field(rdf_predicate=RDF.type, frozen=True, default=HSTERMS.MultidimensionalAggregation) @@ -95,6 +104,10 @@ class MultidimensionalMetadataInRDF(BaseAggregationMetadataInRDF): _parse_spatial_reference = model_validator(mode='before')(parse_rdf_multidimensional_spatial_reference) + @field_serializer('dc_type', 'rdf_type') + def serialize_url(self, _type: URIRef, _info): + return AnyUrl(_type) + class TimeSeriesMetadataInRDF(BaseAggregationMetadataInRDF): rdf_type: AnyUrl = Field(rdf_predicate=RDF.type, frozen=True, default=HSTERMS.TimeSeriesAggregation) @@ -112,6 +125,10 @@ class TimeSeriesMetadataInRDF(BaseAggregationMetadataInRDF): _parse_description = model_validator(mode='before')(rdf_parse_description) + @field_serializer('dc_type', 'rdf_type') + def serialize_url(self, _type: URIRef, _info): + return AnyUrl(_type) + class ReferencedTimeSeriesMetadataInRDF(BaseAggregationMetadataInRDF): rdf_type: AnyUrl = Field(rdf_predicate=RDF.type, frozen=True, default=HSTERMS.ReferencedTimeSeriesAggregation) @@ -124,6 +141,10 @@ class ReferencedTimeSeriesMetadataInRDF(BaseAggregationMetadataInRDF): ) dc_type: AnyUrl = Field(rdf_predicate=DC.type, default=HSTERMS.ReferencedTimeSeriesAggregation, frozen=True) + @field_serializer('dc_type', 'rdf_type') + def serialize_url(self, _type: URIRef, _info): + return AnyUrl(_type) + class FileSetMetadataInRDF(BaseAggregationMetadataInRDF): rdf_type: AnyUrl = Field(rdf_predicate=RDF.type, frozen=True, default=HSTERMS.FileSetAggregation) @@ -131,6 +152,10 @@ class FileSetMetadataInRDF(BaseAggregationMetadataInRDF): label: _label_literal = Field(frozen=True, default="File Set Content: One or more files with specific metadata") dc_type: AnyUrl = Field(rdf_predicate=DC.type, default=HSTERMS.FileSetAggregation, frozen=True) + @field_serializer('dc_type', 'rdf_type') + def serialize_url(self, _type: URIRef, _info): + return AnyUrl(_type) + class SingleFileMetadataInRDF(BaseAggregationMetadataInRDF): rdf_type: AnyUrl = Field(rdf_predicate=RDF.type, frozen=True, default=HSTERMS.SingleFileAggregation) @@ -139,6 +164,10 @@ class SingleFileMetadataInRDF(BaseAggregationMetadataInRDF): label: _label_literal = Field(frozen=True, default="Single File Content: A single file with file specific metadata") dc_type: AnyUrl = Field(rdf_predicate=DC.type, default=HSTERMS.SingleFileAggregation, frozen=True) + @field_serializer('dc_type', 'rdf_type') + def serialize_url(self, _type: URIRef, _info): + return AnyUrl(_type) + class ModelProgramMetadataInRDF(BaseAggregationMetadataInRDF): rdf_type: AnyUrl = Field(rdf_predicate=RDF.type, frozen=True, default=HSTERMS.ModelProgramAggregation) @@ -163,6 +192,10 @@ class ModelProgramMetadataInRDF(BaseAggregationMetadataInRDF): _parse_file_types = model_validator(mode='before')(rdf_parse_file_types) + @field_serializer('dc_type', 'rdf_type') + def serialize_url(self, _type: URIRef, _info): + return AnyUrl(_type) + class ModelInstanceMetadataInRDF(BaseAggregationMetadataInRDF): rdf_type: AnyUrl = Field(rdf_predicate=RDF.type, frozen=True, default=HSTERMS.ModelInstanceAggregation) @@ -175,3 +208,7 @@ class ModelInstanceMetadataInRDF(BaseAggregationMetadataInRDF): executed_by: AnyUrl = Field(rdf_predicate=HSTERMS.executedByModelProgram, default=None) program_schema_json: AnyUrl = Field(rdf_predicate=HSTERMS.modelProgramSchema, default=None) program_schema_json_values: AnyUrl = Field(rdf_predicate=HSTERMS.modelProgramSchemaValues, default=None) + + @field_serializer('dc_type', 'rdf_type') + def serialize_url(self, _type: URIRef, _info): + return AnyUrl(_type) diff --git a/hsmodels/schemas/rdf/resource.py b/hsmodels/schemas/rdf/resource.py index 9132492..2ba743d 100644 --- a/hsmodels/schemas/rdf/resource.py +++ b/hsmodels/schemas/rdf/resource.py @@ -1,7 +1,8 @@ import uuid from typing import List, Literal -from pydantic import AnyUrl, BaseModel, Field, field_validator, model_validator +from pydantic import AnyUrl, BaseModel, Field, field_serializer, field_validator, model_validator +from rdflib import URIRef from hsmodels.namespaces import CITOTERMS, DC, DCTERMS, HSRESOURCE, HSTERMS, ORE, RDF from hsmodels.schemas.rdf.fields import ( @@ -81,14 +82,12 @@ class BaseResource(BaseModel): citation: str = Field(rdf_predicate=DCTERMS.bibliographicCitation) _parse_rdf_subject = model_validator(mode='before')(rdf_parse_rdf_subject) - _parse_coverages = model_validator(mode='before')(parse_coverages) _parse_extended_metadata = model_validator(mode='before')(parse_rdf_extended_metadata) _parse_rdf_dates = model_validator(mode='before')(parse_rdf_dates) _parse_description = model_validator(mode='before')(rdf_parse_description) _parse_identifier = field_validator("identifier", mode='before')(rdf_parse_identifier) - _language_constraint = field_validator('language')(language_constraint) _dates_constraint = field_validator('dates')(dates_constraint) _coverages_constraint = field_validator('coverages')(coverages_constraint) @@ -103,9 +102,17 @@ class ResourceMetadataInRDF(BaseResource): _label_literal = Literal["Composite Resource"] label: _label_literal = Field(default="Composite Resource", frozen=True, alias='label') + @field_serializer('dc_type', 'rdf_type') + def serialize_url(self, _type: URIRef, _info): + return AnyUrl(_type) + class CollectionMetadataInRDF(BaseResource): dc_type: AnyUrl = Field(rdf_predicate=DC.type, default=HSTERMS.CollectionResource, frozen=True) rdf_type: AnyUrl = Field(rdf_predicate=RDF.type, frozen=True, default=HSTERMS.CollectionResource) _label_literal = Literal["Collection Resource"] label: _label_literal = Field(default="Collection Resource", frozen=True, alias='label') + + @field_serializer('dc_type', 'rdf_type') + def serialize_url(self, _type: URIRef, _info): + return AnyUrl(_type) diff --git a/hsmodels/schemas/root_validators.py b/hsmodels/schemas/root_validators.py index 76ed44a..cd5e728 100644 --- a/hsmodels/schemas/root_validators.py +++ b/hsmodels/schemas/root_validators.py @@ -1,3 +1,6 @@ +from pydantic_core import Url +from rdflib import URIRef + from hsmodels.schemas.enums import CoverageType, DateType, ModelProgramFileType, RelationType, UserIdentifierType from hsmodels.utils import to_coverage_dict @@ -68,6 +71,11 @@ def parse_url(cls, values): if value: values["url"] = values["rdf_subject"] del values["rdf_subject"] + + for key, value in values.items(): + if isinstance(value, URIRef): + values[key] = Url(str(value)) + return values From ac20c41b57e844e3042013cf6c1b41ba65cc174b Mon Sep 17 00:00:00 2001 From: pkdash Date: Sat, 30 Sep 2023 23:06:40 -0400 Subject: [PATCH 16/20] [#44] removing Config class based configuration --- hsmodels/schemas/aggregations.py | 112 +++++++++++++++++-------------- hsmodels/schemas/base_models.py | 38 ++--------- hsmodels/schemas/fields.py | 98 +++++++++------------------ hsmodels/schemas/resource.py | 45 ++++++++++--- 4 files changed, 136 insertions(+), 157 deletions(-) diff --git a/hsmodels/schemas/aggregations.py b/hsmodels/schemas/aggregations.py index 7c64058..cbfe9d0 100644 --- a/hsmodels/schemas/aggregations.py +++ b/hsmodels/schemas/aggregations.py @@ -1,7 +1,9 @@ from datetime import date from typing import Dict, List, Union -from pydantic import AnyUrl, Field, model_validator, field_validator +from pydantic import AnyUrl, ConfigDict, Field, GetJsonSchemaHandler, model_validator, field_validator +from pydantic.json_schema import JsonSchemaValue +from pydantic_core import CoreSchema from hsmodels.schemas.base_models import BaseMetadata from hsmodels.schemas.enums import AggregationType @@ -56,8 +58,8 @@ class BaseAggregationMetadataIn(BaseMetadata): ) additional_metadata: Dict[str, str] = Field( default={}, - title="Extended metadata", - description="A list of extended metadata elements expressed as key-value pairs", + title="Additional metadata", + description="A dictionary of additional metadata elements expressed as key-value pairs", ) spatial_coverage: Union[PointCoverage, BoxCoverage] = Field( default=None, @@ -78,6 +80,26 @@ class BaseAggregationMetadataIn(BaseMetadata): _parse_spatial_coverage = field_validator("spatial_coverage", mode='before')(parse_spatial_coverage) _normalize_additional_metadata = model_validator(mode='before')(normalize_additional_metadata) + @classmethod + def __get_pydantic_json_schema__( + cls, core_schema: CoreSchema, handler: GetJsonSchemaHandler + ) -> JsonSchemaValue: + json_schema = handler(core_schema) + json_schema = handler.resolve_ref_schema(json_schema) + prop = json_schema['properties']['additional_metadata'] + prop.pop('default', None) + prop.pop('additionalProperties', None) + prop['type'] = 'array' + prop['items'] = { + "type": "object", + "title": "Key-Value", + "description": "A key-value pair", + "default": [], + "properties": {"key": {"type": "string"}, "value": {"type": "string"}}, + } + + return json_schema + class GeographicRasterMetadataIn(BaseAggregationMetadataIn): """ @@ -88,10 +110,7 @@ class GeographicRasterMetadataIn(BaseAggregationMetadataIn): may have multiple files and multiple bands and are stored in HydroShare as GeoTIFF files. """ - class Config: - title = 'Geographic Raster Aggregation Metadata' - - schema_config = {'read_only': ['type', 'url'], 'dictionary_field': ['additional_metadata']} + model_config = ConfigDict(title="Geographic Raster Aggregation Metadata") band_information: BandInformation = Field( title="Band information", @@ -115,10 +134,12 @@ class GeographicRasterMetadata(GeographicRasterMetadataIn): default=AggregationType.GeographicRasterAggregation, title="Aggregation type", description="A string expressing the aggregation type from the list of HydroShare aggregation types", + json_schema_extra={"readOnly": True}, ) url: AnyUrl = Field( - title="Aggregation URL", description="An object containing the URL of the aggregation", frozen=True + title="Aggregation URL", description="An object containing the URL of the aggregation", frozen=True, + json_schema_extra={"readOnly": True}, ) rights: Rights = Field( @@ -139,10 +160,7 @@ class GeographicFeatureMetadataIn(BaseAggregationMetadataIn): metadata have been added. """ - class Config: - title = 'Geographic Feature Aggregation Metadata' - - schema_config = {'read_only': ['type', 'url'], 'dictionary_field': ['additional_metadata']} + model_config = ConfigDict(title="Geographic Feature Aggregation Metadata") field_information: List[FieldInformation] = Field( default=[], @@ -168,10 +186,12 @@ class GeographicFeatureMetadata(GeographicFeatureMetadataIn): default=AggregationType.GeographicFeatureAggregation, title="Aggregation type", description="A string expressing the aggregation type from the list of HydroShare aggregation types", + json_schema_extra={"readOnly": True}, ) url: AnyUrl = Field( - title="Aggregation URL", description="An object containing the URL of the aggregation", frozen=True + title="Aggregation URL", description="An object containing the URL of the aggregation", frozen=True, + json_schema_extra={"readOnly": True}, ) rights: Rights = Field( @@ -187,15 +207,11 @@ class MultidimensionalMetadataIn(BaseAggregationMetadataIn): """ A class used to represent the metadata associated with a multidimensional space-time aggregation - An multidimensional aggregation consists of a Network Common Data Form (NetCDF) file that + A multidimensional aggregation consists of a Network Common Data Form (NetCDF) file that makes up a multidimensional space-time dataset to which aggregation-level metadata have been added. """ - - class Config: - title = 'Multidimensional Aggregation Metadata' - - schema_config = {'read_only': ['type', 'url'], 'dictionary_field': ['additional_metadata']} + model_config = ConfigDict(title="Multidimensional Aggregation Metadata") variables: List[Variable] = Field( default=[], @@ -219,10 +235,12 @@ class MultidimensionalMetadata(MultidimensionalMetadataIn): default=AggregationType.MultidimensionalAggregation, title="Aggregation type", description="A string expressing the aggregation type from the list of HydroShare aggregation types", + json_schema_extra={"readOnly": True}, ) url: AnyUrl = Field( - title="Aggregation URL", description="An object containing the URL of the aggregation", frozen=True + title="Aggregation URL", description="An object containing the URL of the aggregation", frozen=True, + json_schema_extra={"readOnly": True}, ) rights: Rights = Field( @@ -243,10 +261,7 @@ class ReferencedTimeSeriesMetadataIn(BaseAggregationMetadataIn): been added. """ - class Config: - title = 'Referenced Time Series Aggregation Metadata' - - schema_config = {'read_only': ['type', 'url'], 'dictionary_field': ['additional_metadata']} + model_config = ConfigDict(title="Referenced Time Series Aggregation Metadata") class ReferencedTimeSeriesMetadata(ReferencedTimeSeriesMetadataIn): @@ -255,10 +270,12 @@ class ReferencedTimeSeriesMetadata(ReferencedTimeSeriesMetadataIn): default=AggregationType.ReferencedTimeSeriesAggregation, title="Aggregation type", description="A string expressing the aggregation type from the list of HydroShare aggregation types", + json_schema_extra={"readOnly": True}, ) url: AnyUrl = Field( - title="Aggregation URL", description="An object containing the URL of the aggregation", frozen=True + title="Aggregation URL", description="An object containing the URL of the aggregation", frozen=True, + json_schema_extra={"readOnly": True}, ) rights: Rights = Field( @@ -279,10 +296,7 @@ class FileSetMetadataIn(BaseAggregationMetadataIn): added. There may be any number of files in the aggregation, and files may be of any type. """ - class Config: - title = 'File Set Aggregation Metadata' - - schema_config = {'read_only': ['type', 'url'], 'dictionary_field': ['additional_metadata']} + model_config = ConfigDict(title="File Set Aggregation Metadata") class FileSetMetadata(FileSetMetadataIn): @@ -291,10 +305,12 @@ class FileSetMetadata(FileSetMetadataIn): default=AggregationType.FileSetAggregation, title="Aggregation type", description="A string expressing the aggregation type from the list of HydroShare aggregation types", + json_schema_extra={"readOnly": True}, ) url: AnyUrl = Field( - title="Aggregation URL", description="An object containing the URL of the aggregation", frozen=True + title="Aggregation URL", description="An object containing the URL of the aggregation", frozen=True, + json_schema_extra={"readOnly": True}, ) rights: Rights = Field( @@ -314,10 +330,7 @@ class SingleFileMetadataIn(BaseAggregationMetadataIn): metadata have been added. """ - class Config: - title = 'Single File Aggregation Metadata' - - schema_config = {'read_only': ['type', 'url'], 'dictionary_field': ['additional_metadata']} + model_config = ConfigDict(title="Single File Aggregation Metadata") class SingleFileMetadata(SingleFileMetadataIn): @@ -326,10 +339,12 @@ class SingleFileMetadata(SingleFileMetadataIn): default=AggregationType.SingleFileAggregation, title="Aggregation type", description="A string expressing the aggregation type from the list of HydroShare aggregation types", + json_schema_extra={"readOnly": True}, ) url: AnyUrl = Field( - title="Aggregation URL", description="An object containing the URL of the aggregation", frozen=True + title="Aggregation URL", description="An object containing the URL of the aggregation", frozen=True, + json_schema_extra={"readOnly": True}, ) rights: Rights = Field( @@ -352,10 +367,7 @@ class TimeSeriesMetadataIn(BaseAggregationMetadataIn): ODM2 SQLite database files. """ - class Config: - title = 'Time Series Aggregation Metadata' - - schema_config = {'read_only': ['type', 'url'], 'dictionary_field': ['additional_metadata']} + model_config = ConfigDict(title="Time Series Aggregation Metadata") time_series_results: List[TimeSeriesResult] = Field( default=[], @@ -374,10 +386,12 @@ class TimeSeriesMetadata(TimeSeriesMetadataIn): default=AggregationType.TimeSeriesAggregation, title="Aggregation type", description="A string expressing the aggregation type from the list of HydroShare aggregation types", + json_schema_extra={"readOnly": True}, ) url: AnyUrl = Field( - title="Aggregation URL", description="An object containing the URL of the aggregation", frozen=True + title="Aggregation URL", description="An object containing the URL of the aggregation", frozen=True, + json_schema_extra={"readOnly": True}, ) rights: Rights = Field( @@ -394,10 +408,7 @@ class ModelProgramMetadataIn(BaseAggregationMetadataIn): A class used to represent the metadata associated with a model program aggregation """ - class Config: - title = 'Model Program Aggregation Metadata' - - schema_config = {'read_only': ['type', 'url'], 'dictionary_field': ['additional_metadata']} + model_config = ConfigDict(title="Model Program Aggregation Metadata") version: str = Field( default=None, title="Version", description="The software version or build number of the model", max_length=255 @@ -452,10 +463,12 @@ class ModelProgramMetadata(ModelProgramMetadataIn): default=AggregationType.ModelProgramAggregation, title="Aggregation type", description="A string expressing the aggregation type from the list of HydroShare aggregation types", + json_schema_extra={"readOnly": True}, ) url: AnyUrl = Field( - title="Aggregation URL", description="An object containing the URL of the aggregation", frozen=True + title="Aggregation URL", description="An object containing the URL of the aggregation", frozen=True, + json_schema_extra={"readOnly": True}, ) rights: Rights = Field( @@ -472,10 +485,7 @@ class ModelInstanceMetadataIn(BaseAggregationMetadataIn): A class used to represent the metadata associated with a model instance aggregation """ - class Config: - title = 'Model Instance Aggregation Metadata' - - schema_config = {'read_only': ['type', 'url'], 'dictionary_field': ['additional_metadata']} + model_config = ConfigDict(title="Model Instance Aggregation Metadata") includes_model_output: bool = Field( title="Includes Model Output", @@ -507,10 +517,12 @@ class ModelInstanceMetadata(ModelInstanceMetadataIn): default=AggregationType.ModelInstanceAggregation, title="Aggregation type", description="A string expressing the aggregation type from the list of HydroShare aggregation types", + json_schema_extra={"readOnly": True}, ) url: AnyUrl = Field( - title="Aggregation URL", description="An object containing the URL of the aggregation", frozen=True + title="Aggregation URL", description="An object containing the URL of the aggregation", frozen=True, + json_schema_extra={"readOnly": True}, ) rights: Rights = Field( diff --git a/hsmodels/schemas/base_models.py b/hsmodels/schemas/base_models.py index c68cc05..58bff3d 100644 --- a/hsmodels/schemas/base_models.py +++ b/hsmodels/schemas/base_models.py @@ -1,7 +1,7 @@ from datetime import datetime from typing import Any, Dict, Union -from pydantic import BaseModel +from pydantic import BaseModel, ConfigDict class BaseMetadata(BaseModel): @@ -37,13 +37,10 @@ def model_dump( round_trip=round_trip, warnings=warnings, ) + if to_rdf and "additional_metadata" in d: + additional_metadata = d["additional_metadata"] + d["additional_metadata"] = [{"key": key, "value": value} for key, value in additional_metadata.items()] - if to_rdf and hasattr(self.Config, "schema_config"): - schema_config = self.Config.schema_config - if "dictionary_field" in schema_config: - for field in schema_config["dictionary_field"]: - field_value = d[field] - d[field] = [{"key": key, "value": value} for key, value in field_value.items()] return d def model_dump_json( @@ -78,32 +75,7 @@ def model_dump_json( warnings=warnings, ) - class Config: - validate_assignment = True - - @staticmethod - def json_schema_extra(schema: Dict[str, Any], model) -> None: - if hasattr(model.Config, "schema_config"): - schema_config = model.Config.schema_config - if "read_only" in schema_config: - # set readOnly in json schema - for field in schema_config["read_only"]: - if field in schema['properties']: # ignore unknown properties for inheritance - schema['properties'][field]['readOnly'] = True - if "dictionary_field" in schema_config: - for field in schema_config["dictionary_field"]: - if field in schema['properties']: # ignore unknown properties for inheritance - prop = schema["properties"][field] - prop.pop('default', None) - prop.pop('additionalProperties', None) - prop['type'] = "array" - prop['items'] = { - "type": "object", - "title": "Key-Value", - "description": "A key-value pair", - "default": [], - "properties": {"key": {"type": "string"}, "value": {"type": "string"}}, - } + model_config = ConfigDict(validate_assignment=True) class BaseCoverage(BaseMetadata): diff --git a/hsmodels/schemas/fields.py b/hsmodels/schemas/fields.py index cf59fcd..30eebe0 100644 --- a/hsmodels/schemas/fields.py +++ b/hsmodels/schemas/fields.py @@ -1,7 +1,7 @@ from datetime import datetime from typing import Dict, Literal, Optional -from pydantic import AnyUrl, EmailStr, Field, HttpUrl, field_validator, model_validator +from pydantic import AnyUrl, ConfigDict, EmailStr, Field, HttpUrl, field_validator, model_validator from hsmodels.namespaces import HSTERMS from hsmodels.schemas import base_models @@ -16,8 +16,7 @@ class Relation(BaseMetadata): A class used to represent the metadata associated with a resource related to the resource being described """ - class Config: - title = 'Related Resource Metadata' + model_config = ConfigDict(title='Related Resource Metadata') type: RelationType = Field(title="Relation type", description="The type of relationship with the related resource") value: str = Field( @@ -34,8 +33,7 @@ class CellInformation(BaseMetadata): A class used to represent the metadata associated with raster grid cells in geographic raster aggregations """ - class Config: - title = 'Raster Cell Metadata' + model_config = ConfigDict(title='Raster Cell Metadata') # TODO: Is there such a thing as "name" for CellInformation? name: str = Field(default=None, max_length=500, title="Name", description="Name of the cell information", @@ -70,8 +68,7 @@ class Rights(BaseMetadata): A class used to represent the rights statement metadata associated with a resource """ - class Config: - title = 'Rights Metadata' + model_config = ConfigDict(title='Rights Metadata') statement: str = Field( title="Statement", description="A string containing the text of the license or rights statement", @@ -137,10 +134,7 @@ class Creator(BaseMetadata): A class used to represent the metadata associated with a creator of a resource """ - class Config: - title = 'Creator Metadata' - - schema_config = {'read_only': ['hydroshare_user_id']} + model_config = ConfigDict(title='Creator Metadata') name: str = Field( default=None, max_length=100, title="Name", description="A string containing the name of the creator" @@ -176,6 +170,7 @@ class Config: title="Hydroshare user id", description="An integer containing the Hydroshare user ID", frozen=True, + json_schema_extra={"readOnly": True}, ) identifiers: Dict[UserIdentifierType, AnyUrl] = Field( default={}, @@ -202,10 +197,7 @@ class Contributor(BaseMetadata): A class used to represent the metadata associated with a contributor to a resource """ - class Config: - title = 'Contributor Metadata' - - schema_config = {'read_only': ['hydroshare_user_id']} + model_config = ConfigDict(title='Contributor Metadata') name: str = Field(default=None, title="Name", description="A string containing the name of the contributor") phone: str = Field( @@ -232,6 +224,7 @@ class Config: title="Hyroshare user id", description="An integer containing the Hydroshare user ID", frozen=True, + json_schema_extra={"readOnly": True}, ) identifiers: Dict[UserIdentifierType, AnyUrl] = Field( default={}, @@ -262,8 +255,7 @@ class AwardInfo(BaseMetadata): A class used to represent the metadata associated with funding agency credits for a resource """ - class Config: - title = 'Funding Agency Metadata' + model_config = ConfigDict(title='Funding Agency Metadata') funding_agency_name: str = Field( title="Agency name", description="A string containing the name of the funding agency or organization", @@ -290,8 +282,7 @@ class BandInformation(BaseMetadata): A class used to represent the metadata associated with the raster bands of a geographic raster aggregation """ - class Config: - title = 'Raster Band Metadata' + model_config = ConfigDict(title='Raster Band Metadata') name: str = Field(max_length=500, title="Name", description="A string containing the name of the raster band", rdf_predicate=HSTERMS.name @@ -346,8 +337,7 @@ class FieldInformation(BaseMetadata): feature aggregation """ - class Config: - title = 'Geographic Feature Field Metadata' + model_config = ConfigDict(title='Geographic Feature Field Metadata') field_name: str = Field( max_length=128, title="Field name", description="A string containing the name of the attribute table field", @@ -383,8 +373,7 @@ class GeometryInformation(BaseMetadata): A class used to represent the metadata associated with the geometry of a geographic feature aggregation """ - class Config: - title = 'Geographic Feature Geometry Metadata' + model_config = ConfigDict(title='Geographic Feature Geometry Metadata') feature_count: int = Field( default=0, @@ -405,8 +394,7 @@ class Variable(BaseMetadata): A class used to represent the metadata associated with a variable contained within a multidimensional aggregation """ - class Config: - title = 'Multidimensional Variable Metadata' + model_config = ConfigDict(title='Multidimensional Variable Metadata') name: str = Field( max_length=1000, title="Variable name", description="A string containing the name of the variable", @@ -454,8 +442,7 @@ class Publisher(BaseMetadata): A class used to represent the metadata associated with the publisher of a resource """ - class Config: - title = 'Publisher Metadata' + model_config = ConfigDict(title='Publisher Metadata') name: str = Field( max_length=200, title="Publisher name", description="A string containing the name of the publisher", @@ -472,8 +459,7 @@ class TimeSeriesVariable(BaseMetadata): A class used to represent the metadata associated with a variable contained within a time series aggregation """ - class Config: - title = 'Time Series Variable Metadata' + model_config = ConfigDict(title='Time Series Variable Metadata') variable_code: str = Field( max_length=50, @@ -518,8 +504,7 @@ class TimeSeriesSite(BaseMetadata): A class used to represent the metadata associated with a site contained within a time series aggregation """ - class Config: - title = 'Time Series Site Metadata' + model_config = ConfigDict(title='Time Series Site Metadata') site_code: str = Field( max_length=200, @@ -570,8 +555,7 @@ class TimeSeriesMethod(BaseMetadata): A class used to represent the metadata associated with a method contained within a time series aggregation """ - class Config: - title = 'Time Series Method Metadata' + model_config = ConfigDict(title='Time Series Method Metadata') method_code: str = Field( max_length=50, @@ -608,8 +592,7 @@ class ProcessingLevel(BaseMetadata): aggregation """ - class Config: - title = 'Time Series Processing Level Metadata' + model_config = ConfigDict(title='Time Series Processing Level Metadata') processing_level_code: str = Field( max_length=50, @@ -637,8 +620,7 @@ class Unit(BaseMetadata): A class used to represent the metadata associated with a dimensional unit within a time series aggregation """ - class Config: - title = 'Time Series Units Metadata' + model_config = ConfigDict(title='Time Series Units Metadata') type: str = Field( max_length=255, @@ -665,8 +647,7 @@ class UTCOffSet(BaseMetadata): A class used to represent the metadata associated with a UTC time offset within a time series aggregation) """ - class Config: - title = 'Time Series UTC Offset Metadata' + model_config = ConfigDict(title='Time Series UTC Offset Metadata') value: float = Field( default=0, @@ -681,8 +662,7 @@ class TimeSeriesResult(BaseMetadata): A class used to represent the metadata associated with a time series result within a time series aggregation """ - class Config: - title = 'Time Series Result Metadata' + model_config = ConfigDict(title='Time Series Result Metadata') series_id: str = Field( max_length=36, @@ -760,16 +740,14 @@ class BoxCoverage(base_models.BaseCoverage): latitude-longitude bounding box """ - class Config: - title = 'Box Coverage Metadata' - - schema_config = {'read_only': ['type']} + model_config = ConfigDict(title='Box Coverage Metadata') type: Literal['box'] = Field( default="box", frozen=True, title="Geographic coverage type", description="A string containing the type of geographic coverage", + json_schema_extra={"readOnly": True}, ) name: str = Field( default=None, @@ -823,16 +801,14 @@ class BoxSpatialReference(base_models.BaseCoverage): feature or raster aggregation expressed as a bounding box """ - class Config: - title = 'Box Spatial Reference Metadata' - - schema_config = {'read_only': ['type']} + model_config = ConfigDict(title='Box Spatial Reference Metadata') type: Literal['box'] = Field( default="box", frozen=True, title="Spatial reference type", description="A string containing the type of spatial reference", + json_schema_extra={'readOnly': True}, ) name: str = Field( default=None, @@ -890,8 +866,7 @@ class MultidimensionalBoxSpatialReference(BoxSpatialReference): aggregation expressed as a bounding box """ - class Config: - title = 'Multidimensional Box Spatial Reference Metadata' + model_config = ConfigDict(title='Multidimensional Box Spatial Reference Metadata') class PointCoverage(base_models.BaseCoverage): @@ -900,16 +875,14 @@ class PointCoverage(base_models.BaseCoverage): point location """ - class Config: - title = 'Point Coverage Metadata' - - schema_config = {'read_only': ['type']} + model_config = ConfigDict(title='Point Coverage Metadata') type: Literal['point'] = Field( default="point", frozen=True, title="Geographic coverage type", description="A string containing the type of geographic coverage", + json_schema_extra={"readOnly": True}, ) name: str = Field( default=None, @@ -937,16 +910,14 @@ class PointSpatialReference(base_models.BaseCoverage): feature or raster aggregation expressed as a point """ - class Config: - title = 'Point Spatial Reference Metadata' - - schema_config = {'read_only': ['type']} + model_config = ConfigDict(title='Point Spatial Reference Metadata') type: Literal['point'] = Field( default="point", frozen=True, title="Spatial reference type", description="A string containing the type of spatial reference", + json_schema_extra={'readOnly': True}, ) name: str = Field( default=None, @@ -984,8 +955,7 @@ class MultidimensionalPointSpatialReference(PointSpatialReference): aggregation expressed as a point """ - class Config: - title = 'Multidimensional Point Spatial Reference Metadata' + model_config = ConfigDict(title='Multidimensional Point Spatial Reference Metadata') class PeriodCoverage(base_models.BaseCoverage): @@ -993,8 +963,7 @@ class PeriodCoverage(base_models.BaseCoverage): A class used to represent temporal coverage metadata for a resource or aggregation """ - class Config: - title = 'Period Coverage Metadata' + model_config = ConfigDict(title='Period Coverage Metadata') name: str = Field(default=None, title="Name", description="A string containing a name for the time interval") start: datetime = Field( @@ -1023,8 +992,7 @@ class ModelProgramFile(BaseMetadata): A class used to represent the metadata associated with a file used by a model program aggregation """ - class Config: - title = "Model program file metadata" + model_config = ConfigDict(title='Model Program File Metadata') type: ModelProgramFileType = Field( title="Model program file type", description="The type of the file used by the model program" diff --git a/hsmodels/schemas/resource.py b/hsmodels/schemas/resource.py index 85bdf47..852684f 100644 --- a/hsmodels/schemas/resource.py +++ b/hsmodels/schemas/resource.py @@ -1,7 +1,9 @@ from datetime import datetime from typing import Dict, List, Union, Literal -from pydantic import AnyUrl, Field, field_validator, model_validator +from pydantic import AnyUrl, ConfigDict, Field, GetJsonSchemaHandler, field_validator, model_validator +from pydantic.json_schema import JsonSchemaValue +from pydantic_core import CoreSchema from hsmodels.schemas.base_models import BaseMetadata from hsmodels.schemas.fields import ( @@ -32,13 +34,7 @@ class ResourceMetadataIn(BaseMetadata): A class used to represent the metadata for a resource """ - class Config: - title = 'Resource Metadata' - - schema_config = { - 'read_only': ['type', 'identifier', 'created', 'modified', 'review_started', 'published', 'url'], - 'dictionary_field': ['additional_metadata'], - } + model_config = ConfigDict(title="Resource Metadata") title: str = Field( max_length=300, title="Title", description="A string containing the name given to a resource" @@ -113,38 +109,67 @@ class Config: _language_constraint = field_validator('language')(language_constraint) _creators_constraint = field_validator('creators')(list_not_empty) + @classmethod + def __get_pydantic_json_schema__( + cls, core_schema: CoreSchema, handler: GetJsonSchemaHandler + ) -> JsonSchemaValue: + json_schema = handler(core_schema) + json_schema = handler.resolve_ref_schema(json_schema) + prop = json_schema['properties']['additional_metadata'] + prop.pop('default', None) + prop.pop('additionalProperties', None) + prop['type'] = 'array' + prop['items'] = { + "type": "object", + "title": "Key-Value", + "description": "A key-value pair", + "default": [], + "properties": {"key": {"type": "string"}, "value": {"type": "string"}}, + } + + return json_schema + class BaseResourceMetadata(ResourceMetadataIn): - url: AnyUrl = Field(title="URL", description="An object containing the URL for a resource", frozen=True) + url: AnyUrl = Field( + title="URL", + description="An object containing the URL for a resource", + frozen=True, json_schema_extra={"readOnly": True}, + ) identifier: AnyUrl = Field( title="Identifier", description="An object containing the URL-encoded unique identifier for a resource", frozen=True, + json_schema_extra={"readOnly": True}, ) created: datetime = Field( default_factory=datetime.now, title="Creation date", description="A datetime object containing the instant associated with when a resource was created", frozen=True, + json_schema_extra={"readOnly": True}, ) modified: datetime = Field( default_factory=datetime.now, title="Modified date", description="A datetime object containing the instant associated with when a resource was last modified", frozen=True, + json_schema_extra={"readOnly": True}, ) review_started: datetime = Field( default=None, title="Review started date", description="A datetime object containing the instant associated with when metadata review started on a resource", frozen=True, + json_schema_extra={"readOnly": True}, ) published: datetime = Field( default=None, title="Published date", description="A datetime object containing the instant associated with when a resource was published", frozen=True, + json_schema_extra={"readOnly": True}, ) _parse_dates = model_validator(mode='before')(split_dates) @@ -159,6 +184,7 @@ class ResourceMetadata(BaseResourceMetadata): default="CompositeResource", title="Resource Type", description="An object containing a URL that points to the HydroShare resource type selected from the hsterms namespace", + json_schema_extra={"readOnly": True}, ) @@ -168,4 +194,5 @@ class CollectionMetadata(BaseResourceMetadata): default="CollectionResource", title="Resource Type", description="An object containing a URL that points to the HydroShare resource type selected from the hsterms namespace", + json_schema_extra={"readOnly": True}, ) From 951c44bbd3548e9d4aebd4585062ba3c222dafc4 Mon Sep 17 00:00:00 2001 From: pkdash Date: Sat, 30 Sep 2023 23:56:32 -0400 Subject: [PATCH 17/20] [#44] setting rdf_predicate using json_schema_extra key in Field --- hsmodels/schemas/fields.py | 134 +++++++++++----------- hsmodels/schemas/rdf/aggregations.py | 162 ++++++++++++++++++--------- hsmodels/schemas/rdf/fields.py | 120 ++++++++++---------- hsmodels/schemas/rdf/resource.py | 74 +++++++----- 4 files changed, 282 insertions(+), 208 deletions(-) diff --git a/hsmodels/schemas/fields.py b/hsmodels/schemas/fields.py index 30eebe0..9890fc5 100644 --- a/hsmodels/schemas/fields.py +++ b/hsmodels/schemas/fields.py @@ -37,29 +37,29 @@ class CellInformation(BaseMetadata): # TODO: Is there such a thing as "name" for CellInformation? name: str = Field(default=None, max_length=500, title="Name", description="Name of the cell information", - rdf_predicate=HSTERMS.name) + json_schema_extra={"rdf_predicate": HSTERMS.name}) rows: int = Field(default=None, title="Rows", description="The integer number of rows in the raster dataset", - rdf_predicate=HSTERMS.rows) + json_schema_extra={"rdf_predicate": HSTERMS.rows}) columns: int = Field( default=None, title="Columns", description="The integer number of columns in the raster dataset", - rdf_predicate=HSTERMS.columns + json_schema_extra={"rdf_predicate": HSTERMS.columns} ) cell_size_x_value: float = Field( default=None, title="Cell size x value", description="The size of the raster grid cell in the x-direction expressed as a float", - rdf_predicate=HSTERMS.cellSizeXValue + json_schema_extra={"rdf_predicate": HSTERMS.cellSizeXValue} ) cell_data_type: str = Field( default=None, max_length=50, title="Cell data type", description="The data type of the raster grid cell values", - rdf_predicate=HSTERMS.cellDataType + json_schema_extra={"rdf_predicate": HSTERMS.cellDataType} ) cell_size_y_value: float = Field( default=None, title="Cell size y value", description="The size of the raster grid cell in the y-direction expressed as a float", - rdf_predicate=HSTERMS.cellSizeYValue + json_schema_extra={"rdf_predicate": HSTERMS.cellSizeYValue} ) @@ -72,12 +72,12 @@ class Rights(BaseMetadata): statement: str = Field( title="Statement", description="A string containing the text of the license or rights statement", - rdf_predicate=HSTERMS.rightsStatement + json_schema_extra={"rdf_predicate": HSTERMS.rightsStatement} ) url: AnyUrl = Field( title="URL", description="An object containing the URL pointing to a description of the license or rights statement", - rdf_predicate=HSTERMS.URL + json_schema_extra={"rdf_predicate": HSTERMS.URL} ) @classmethod @@ -259,21 +259,21 @@ class AwardInfo(BaseMetadata): funding_agency_name: str = Field( title="Agency name", description="A string containing the name of the funding agency or organization", - rdf_predicate=HSTERMS.fundingAgencyName + json_schema_extra={"rdf_predicate": HSTERMS.fundingAgencyName} ) title: str = Field( default=None, title="Award title", description="A string containing the title of the project or award", - rdf_predicate=HSTERMS.awardTitle + json_schema_extra={"rdf_predicate": HSTERMS.awardTitle} ) number: str = Field( default=None, title="Award number", description="A string containing the award number or other identifier", - rdf_predicate=HSTERMS.awardNumber + json_schema_extra={"rdf_predicate": HSTERMS.awardNumber} ) funding_agency_url: AnyUrl = Field( default=None, title="Agency URL", description="An object containing a URL pointing to a website describing the funding award", - rdf_predicate=HSTERMS.fundingAgencyURL + json_schema_extra={"rdf_predicate": HSTERMS.fundingAgencyURL} ) @@ -285,49 +285,49 @@ class BandInformation(BaseMetadata): model_config = ConfigDict(title='Raster Band Metadata') name: str = Field(max_length=500, title="Name", description="A string containing the name of the raster band", - rdf_predicate=HSTERMS.name + json_schema_extra={"rdf_predicate": HSTERMS.name} ) variable_name: str = Field( default=None, max_length=100, title="Variable name", description="A string containing the name of the variable represented by the raster band", - rdf_predicate=HSTERMS.variableName + json_schema_extra={"rdf_predicate": HSTERMS.variableName} ) variable_unit: str = Field( default=None, max_length=50, title="Variable unit", description="A string containing the units for the raster band variable", - rdf_predicate=HSTERMS.variableUnit + json_schema_extra={"rdf_predicate": HSTERMS.variableUnit} ) no_data_value: str = Field( default=None, title="Nodata value", description="A string containing the numeric nodata value for the raster band", - rdf_predicate=HSTERMS.noDataValue + json_schema_extra={"rdf_predicate": HSTERMS.noDataValue} ) maximum_value: str = Field( default=None, title="Maximum value", description="A string containing the maximum numeric value for the raster band", - rdf_predicate=HSTERMS.maximumValue + json_schema_extra={"rdf_predicate": HSTERMS.maximumValue} ) comment: str = Field( default=None, title="Comment", description="A string containing a comment about the raster band", - rdf_predicate=HSTERMS.comment + json_schema_extra={"rdf_predicate": HSTERMS.comment} ) method: str = Field( default=None, title="Method", description="A string containing a description of the method used to create the raster band data", - rdf_predicate=HSTERMS.method + json_schema_extra={"rdf_predicate": HSTERMS.method} ) minimum_value: str = Field( default=None, title="Minimum value", description="A string containing the minimum numerica value for the raster dataset", - rdf_predicate=HSTERMS.minimumValue + json_schema_extra={"rdf_predicate": HSTERMS.minimumValue} ) @@ -341,11 +341,11 @@ class FieldInformation(BaseMetadata): field_name: str = Field( max_length=128, title="Field name", description="A string containing the name of the attribute table field", - rdf_predicate=HSTERMS.fieldName + json_schema_extra={"rdf_predicate": HSTERMS.fieldName} ) field_type: str = Field( max_length=128, title="Field type", description="A string containing the data type of the values in the field", - rdf_predicate=HSTERMS.fieldType + json_schema_extra={"rdf_predicate": HSTERMS.fieldType} ) # TODO: What is the "field_type_code"? It's not displayed on the resource landing page, but it's encoded in the # aggregation metadata as an integer value. @@ -354,17 +354,17 @@ class FieldInformation(BaseMetadata): max_length=50, title="Field type code", description="A string value containing a code that indicates the field type", - rdf_predicate=HSTERMS.fieldTypeCode + json_schema_extra={"rdf_predicate": HSTERMS.fieldTypeCode} ) field_width: int = Field( default=None, title="Field width", description="An integer value containing the width of the attribute field", - rdf_predicate=HSTERMS.fieldWidth + json_schema_extra={"rdf_predicate": HSTERMS.fieldWidth} ) field_precision: int = Field( default=None, title="Field precision", description="An integer value containing the precision of the attribute field", - rdf_predicate=HSTERMS.fieldPrecision + json_schema_extra={"rdf_predicate": HSTERMS.fieldPrecision} ) @@ -379,13 +379,13 @@ class GeometryInformation(BaseMetadata): default=0, title="Feature count", description="An integer containing the number of features in the geographic feature aggregation", - rdf_predicate=HSTERMS.featureCount + json_schema_extra={"rdf_predicate": HSTERMS.featureCount} ) geometry_type: str = Field( max_length=128, title="Geometry type", description="A string containing the type of features in the geographic feature aggregation", - rdf_predicate=HSTERMS.geometryType + json_schema_extra={"rdf_predicate": HSTERMS.geometryType} ) @@ -398,42 +398,42 @@ class Variable(BaseMetadata): name: str = Field( max_length=1000, title="Variable name", description="A string containing the name of the variable", - rdf_predicate=HSTERMS.name + json_schema_extra={"rdf_predicate": HSTERMS.name} ) unit: str = Field( max_length=1000, title="Units", description="A string containing the units in which the values for the variable are expressed", - rdf_predicate=HSTERMS.unit + json_schema_extra={"rdf_predicate": HSTERMS.unit} ) type: VariableType = Field(title="Type", description="The data type of the values for the variable", - rdf_predicate=HSTERMS.type + json_schema_extra={"rdf_predicate": HSTERMS.type} ) shape: str = Field( max_length=1000, title="Shape", description="A string containing the shape of the variable expressed as a list of dimensions", - rdf_predicate=HSTERMS.shape + json_schema_extra={"rdf_predicate": HSTERMS.shape} ) descriptive_name: str = Field( default=None, max_length=1000, title="Descriptive name", description="A string containing a descriptive name for the variable", - rdf_predicate=HSTERMS.descriptive_name + json_schema_extra={"rdf_predicate": HSTERMS.descriptive_name} ) method: str = Field( default=None, title="Method", description="A string containing a description of the method used to create the values for the variable", - rdf_predicate=HSTERMS.method + json_schema_extra={"rdf_predicate": HSTERMS.method} ) missing_value: str = Field( default=None, max_length=1000, title="Missing value", description="A string containing the value used to indicate missing values for the variable", - rdf_predicate=HSTERMS.missing_value + json_schema_extra={"rdf_predicate": HSTERMS.missing_value} ) @@ -446,11 +446,11 @@ class Publisher(BaseMetadata): name: str = Field( max_length=200, title="Publisher name", description="A string containing the name of the publisher", - rdf_predicate=HSTERMS.publisherName + json_schema_extra={"rdf_predicate": HSTERMS.publisherName} ) url: AnyUrl = Field( title="Publisher URL", description="An object containing a URL that points to the publisher website", - rdf_predicate=HSTERMS.publisherURL + json_schema_extra={"rdf_predicate": HSTERMS.publisherURL} ) @@ -465,37 +465,37 @@ class TimeSeriesVariable(BaseMetadata): max_length=50, title="Variable code", description="A string containing a short but meaningful code that identifies a variable", - rdf_predicate=HSTERMS.VariableCode + json_schema_extra={"rdf_predicate": HSTERMS.VariableCode} ) variable_name: str = Field( max_length=100, title="Variable name", description="A string containing the name of the variable", - rdf_predicate=HSTERMS.VariableName + json_schema_extra={"rdf_predicate": HSTERMS.VariableName} ) variable_type: str = Field( max_length=100, title="Variable type", description="A string containing the type of variable from the ODM2 VariableType controlled vocabulary", - rdf_predicate=HSTERMS.VariableType + json_schema_extra={"rdf_predicate": HSTERMS.VariableType} ) # TODO: The NoData value for a variable in an ODM2 database is not always an integer. # It could be a floating point value. We might want to change this to a string or a floating point value # It is an integer in the HydroShare database, so will have to be updated there as well if changed no_data_value: int = Field(title="NoData value", description="The NoData value for the variable", - rdf_predicate=HSTERMS.NoDataValue + json_schema_extra={"rdf_predicate": HSTERMS.NoDataValue} ) variable_definition: str = Field( default=None, max_length=255, title="Variable definition", description="A string containing a detailed description of the variable", - rdf_predicate=HSTERMS.VariableDefinition + json_schema_extra={"rdf_predicate": HSTERMS.VariableDefinition} ) speciation: str = Field( default=None, max_length=255, title="Speciation", description="A string containing the speciation for the variable from the ODM2 Speciation control vocabulary", - rdf_predicate=HSTERMS.Speciation + json_schema_extra={"rdf_predicate": HSTERMS.Speciation} ) @@ -510,43 +510,43 @@ class TimeSeriesSite(BaseMetadata): max_length=200, title="Site code", description="A string containing a short but meaningful code identifying the site", - rdf_predicate=HSTERMS.SiteCode + json_schema_extra={"rdf_predicate": HSTERMS.SiteCode} ) site_name: str = Field( default=None, max_length=255, title="Site name", description="A string containing the name of the site", - rdf_predicate=HSTERMS.SiteName + json_schema_extra={"rdf_predicate": HSTERMS.SiteName} ) elevation_m: float = Field( default=None, title="Elevation", description="A floating point number expressing the elevation of the site in meters", - rdf_predicate=HSTERMS.Elevation_m + json_schema_extra={"rdf_predicate": HSTERMS.Elevation_m} ) elevation_datum: str = Field( default=None, max_length=50, title="Elevation datum", description="A string expressing the elevation datum used from the ODM2 Elevation Datum controlled vocabulary", - rdf_predicate=HSTERMS.ElevationDatum + json_schema_extra={"rdf_predicate": HSTERMS.ElevationDatum} ) site_type: str = Field( default=None, max_length=100, title="Site type", description="A string containing the type of site from the ODM2 Sampling Feature Type controlled vocabulary ", - rdf_predicate=HSTERMS.SiteType + json_schema_extra={"rdf_predicate": HSTERMS.SiteType} ) latitude: float = Field( default=None, title="Latitude", description="A floating point value expressing the latitude coordinate of the site", - rdf_predicate=HSTERMS.Latitude + json_schema_extra={"rdf_predicate": HSTERMS.Latitude} ) longitude: float = Field( default=None, title="Longitude", description="A floating point value expressing the longitude coordinate of the site", - rdf_predicate=HSTERMS.Longitude + json_schema_extra={"rdf_predicate": HSTERMS.Longitude} ) @@ -561,28 +561,28 @@ class TimeSeriesMethod(BaseMetadata): max_length=50, title="Method code", description="A string containing a short but meaningful code identifying the method", - rdf_predicate=HSTERMS.MethodCode + json_schema_extra={"rdf_predicate": HSTERMS.MethodCode} ) method_name: str = Field( max_length=200, title="Method name", description="A string containing the name of the method", - rdf_predicate=HSTERMS.MethodName + json_schema_extra={"rdf_predicate": HSTERMS.MethodName} ) method_type: str = Field( max_length=200, title="Method type", description="A string containing the method type from the ODM2 Method Type controlled vocabulary", - rdf_predicate=HSTERMS.MethodType + json_schema_extra={"rdf_predicate": HSTERMS.MethodType} ) method_description: str = Field( default=None, title="Method description", description="A string containing a detailed description of the method", - rdf_predicate=HSTERMS.MethodDescription + json_schema_extra={"rdf_predicate": HSTERMS.MethodDescription} ) method_link: AnyUrl = Field( default=None, title="Method link", description="An object containing a URL that points to a website having a detailed description of the method", - rdf_predicate=HSTERMS.MethodLink + json_schema_extra={"rdf_predicate": HSTERMS.MethodLink} ) @@ -598,20 +598,20 @@ class ProcessingLevel(BaseMetadata): max_length=50, title="Processing level code", description="A string containing a short but meaningful code identifying the processing level", - rdf_predicate=HSTERMS.ProcessingLevelCode + json_schema_extra={"rdf_predicate": HSTERMS.ProcessingLevelCode} ) definition: str = Field( default=None, max_length=200, title="Definition", description="A string containing a description of the processing level", - rdf_predicate=HSTERMS.Definition + json_schema_extra={"rdf_predicate": HSTERMS.Definition} ) explanation: str = Field( default=None, title="Explanation", description="A string containing a more extensive explanation of the meaning of the processing level", - rdf_predicate=HSTERMS.Explanation + json_schema_extra={"rdf_predicate": HSTERMS.Explanation} ) @@ -626,19 +626,19 @@ class Unit(BaseMetadata): max_length=255, title="Unit type", description="A string containing the type of unit from the ODM2 Units Type controlled vocabulary", - rdf_predicate=HSTERMS.UnitsType + json_schema_extra={"rdf_predicate": HSTERMS.UnitsType} ) name: str = Field( max_length=255, title="Unit name", description="A string containing the name of the unit from the ODM2 units list", - rdf_predicate=HSTERMS.UnitsName + json_schema_extra={"rdf_predicate": HSTERMS.UnitsName} ) abbreviation: str = Field( max_length=20, title="Unit abbreviation", description="A string containing an abbreviation for the unit from the ODM2 units list", - rdf_predicate=HSTERMS.UnitsAbbreviation + json_schema_extra={"rdf_predicate": HSTERMS.UnitsAbbreviation} ) @@ -653,7 +653,7 @@ class UTCOffSet(BaseMetadata): default=0, title="UTC offset value", description="A floating point number containing the UTC time offset associated with the data values expressed in hours", - rdf_predicate=HSTERMS.value + json_schema_extra={"rdf_predicate": HSTERMS.value} ) @@ -668,7 +668,7 @@ class TimeSeriesResult(BaseMetadata): max_length=36, title="Series ID", description="A string containing a unique identifier for the time series result", - rdf_predicate=HSTERMS.timeSeriesResultUUID + json_schema_extra={"rdf_predicate": HSTERMS.timeSeriesResultUUID} ) unit: Unit = Field( default=None, @@ -681,24 +681,24 @@ class TimeSeriesResult(BaseMetadata): max_length=255, title="Status", description="A string containing the status of the time series result chosen from the ODM2 Status controlled vocabulary", - rdf_predicate=HSTERMS.Status + json_schema_extra={"rdf_predicate": HSTERMS.Status} ) sample_medium: str = Field( max_length=255, title="Sample medium", description="A string containing the sample medium in which the time series result was measured chosen from the ODM2 Medium controlled vocabulary", - rdf_predicate=HSTERMS.SampleMedium + json_schema_extra={"rdf_predicate": HSTERMS.SampleMedium} ) value_count: int = Field( title="Value count", description="An integer value containing the number of data values contained within the time series result", - rdf_predicate=HSTERMS.ValueCount + json_schema_extra={"rdf_predicate": HSTERMS.ValueCount} ) aggregation_statistic: str = Field( max_length=255, title="Aggregation statistic", description="A string containing the aggregation statistic associated with the values of the time series result chosen from the ODM2 Aggregation Statistic controlled vocabulary", - rdf_predicate=HSTERMS.AggregationStatistic + json_schema_extra={"rdf_predicate": HSTERMS.AggregationStatistic} ) # TODO: Not sure what "series_label" is. It's not an ODM2 thing # in HydroShare it is generated with this format @@ -708,7 +708,7 @@ class TimeSeriesResult(BaseMetadata): max_length=255, title="Series label", description="A string containing a label for the time series result", - rdf_predicate=HSTERMS.SeriesLabel + json_schema_extra={"rdf_predicate": HSTERMS.SeriesLabel} ) site: TimeSeriesSite = Field( title="Site", diff --git a/hsmodels/schemas/rdf/aggregations.py b/hsmodels/schemas/rdf/aggregations.py index 2ee0a82..86048ed 100644 --- a/hsmodels/schemas/rdf/aggregations.py +++ b/hsmodels/schemas/rdf/aggregations.py @@ -34,12 +34,14 @@ class BaseAggregationMetadataInRDF(RDFBaseModel): _parse_rdf_subject = model_validator(mode='before')(rdf_parse_rdf_subject) - title: str = Field(rdf_predicate=DC.title) - subjects: List[str] = Field(rdf_predicate=DC.subject, default=[]) - language: str = Field(rdf_predicate=DC.language, default="eng") - extended_metadata: List[ExtendedMetadataInRDF] = Field(rdf_predicate=HSTERMS.extendedMetadata, default=[]) - coverages: List[CoverageInRDF] = Field(rdf_predicate=DC.coverage, default=[]) - rights: RightsInRDF = Field(rdf_predicate=DC.rights, default=[]) + title: str = Field(json_schema_extra={"rdf_predicate": DC.title}) + subjects: List[str] = Field(json_schema_extra={"rdf_predicate": DC.subject}, default=[]) + language: str = Field(json_schema_extra={"rdf_predicate": DC.language}, default="eng") + extended_metadata: List[ExtendedMetadataInRDF] = Field( + json_schema_extra={"rdf_predicate": HSTERMS.extendedMetadata}, default=[] + ) + coverages: List[CoverageInRDF] = Field(json_schema_extra={"rdf_predicate": DC.coverage}, default=[]) + rights: RightsInRDF = Field(json_schema_extra={"rdf_predicate": DC.rights}, default=[]) _parse_coverages = model_validator(mode='before')(parse_coverages) @@ -47,18 +49,22 @@ class BaseAggregationMetadataInRDF(RDFBaseModel): class GeographicRasterMetadataInRDF(BaseAggregationMetadataInRDF): - rdf_type: AnyUrl = Field(rdf_predicate=RDF.type, frozen=True, default=HSTERMS.GeographicRasterAggregation) + rdf_type: AnyUrl = Field(json_schema_extra={"rdf_predicate": RDF.type}, frozen=True, default=HSTERMS.GeographicRasterAggregation) _label_literal = Literal["Geographic Raster Content: A geographic grid represented by a virtual raster tile (.vrt) file and one or more geotiff (.tif) files"] label: _label_literal = Field( frozen=True, default="Geographic Raster Content: A geographic grid represented by a virtual " "raster tile (.vrt) file and one or more geotiff (.tif) files", ) - dc_type: AnyUrl = Field(rdf_predicate=DC.type, default=HSTERMS.GeographicRasterAggregation, frozen=True) + dc_type: AnyUrl = Field( + json_schema_extra={"rdf_predicate": DC.type}, default=HSTERMS.GeographicRasterAggregation, frozen=True + ) - band_information: BandInformationInRDF = Field(rdf_predicate=HSTERMS.BandInformation) - spatial_reference: SpatialReferenceInRDF = Field(rdf_predicate=HSTERMS.spatialReference, default=None) - cell_information: CellInformationInRDF = Field(rdf_predicate=HSTERMS.CellInformation) + band_information: BandInformationInRDF = Field(json_schema_extra={"rdf_predicate": HSTERMS.BandInformation}) + spatial_reference: SpatialReferenceInRDF = Field( + json_schema_extra={"rdf_predicate": HSTERMS.spatialReference}, default=None + ) + cell_information: CellInformationInRDF = Field(json_schema_extra={"rdf_predicate": HSTERMS.CellInformation}) _parse_spatial_reference = model_validator(mode='before')(parse_rdf_spatial_reference) @@ -68,17 +74,27 @@ def serialize_url(self, _type: URIRef, _info): class GeographicFeatureMetadataInRDF(BaseAggregationMetadataInRDF): - rdf_type: AnyUrl = Field(rdf_predicate=RDF.type, frozen=True, default=HSTERMS.GeographicFeatureAggregation) + rdf_type: AnyUrl = Field( + json_schema_extra={"rdf_predicate": RDF.type}, frozen=True, default=HSTERMS.GeographicFeatureAggregation + ) _label_literal = Literal["Geographic Feature Content: The multiple files that are part of a geographic shapefile"] label: _label_literal = Field( frozen=True, default="Geographic Feature Content: The multiple files that are part of a " "geographic shapefile" ) - dc_type: AnyUrl = Field(rdf_predicate=DC.type, default=HSTERMS.GeographicFeatureAggregation, frozen=True) + dc_type: AnyUrl = Field( + json_schema_extra={"rdf_predicate": DC.type}, default=HSTERMS.GeographicFeatureAggregation, frozen=True + ) - field_information: List[FieldInformationInRDF] = Field(rdf_predicate=HSTERMS.FieldInformation, default=[]) - geometry_information: GeometryInformationInRDF = Field(rdf_predicate=HSTERMS.GeometryInformation) - spatial_reference: SpatialReferenceInRDF = Field(rdf_predicate=HSTERMS.spatialReference, default=None) + field_information: List[FieldInformationInRDF] = Field( + json_schema_extra={"rdf_predicate": HSTERMS.FieldInformation}, default=[] + ) + geometry_information: GeometryInformationInRDF = Field( + json_schema_extra={"rdf_predicate": HSTERMS.GeometryInformation} + ) + spatial_reference: SpatialReferenceInRDF = Field( + json_schema_extra={"rdf_predicate": HSTERMS.spatialReference}, default=None + ) _parse_spatial_reference = model_validator(mode='before')(parse_rdf_spatial_reference) @@ -88,18 +104,22 @@ def serialize_url(self, _type: URIRef, _info): class MultidimensionalMetadataInRDF(BaseAggregationMetadataInRDF): - rdf_type: AnyUrl = Field(rdf_predicate=RDF.type, frozen=True, default=HSTERMS.MultidimensionalAggregation) + rdf_type: AnyUrl = Field( + json_schema_extra={"rdf_predicate": RDF.type}, frozen=True, default=HSTERMS.MultidimensionalAggregation + ) _label_literal = Literal['Multidimensional Content: A multidimensional dataset represented by a NetCDF file (.nc) and text file giving its NetCDF header content'] label: _label_literal = Field( frozen=True, default="Multidimensional Content: A multidimensional dataset represented by a " "NetCDF file (.nc) and text file giving its NetCDF header content", ) - dc_type: AnyUrl = Field(rdf_predicate=DC.type, default=HSTERMS.MultidimensionalAggregation, frozen=True) + dc_type: AnyUrl = Field( + json_schema_extra={"rdf_predicate": DC.type}, default=HSTERMS.MultidimensionalAggregation, frozen=True + ) - variables: List[VariableInRDF] = Field(rdf_predicate=HSTERMS.Variable, default=[]) + variables: List[VariableInRDF] = Field(json_schema_extra={"rdf_predicate": HSTERMS.Variable}, default=[]) spatial_reference: MultidimensionalSpatialReferenceInRDF = Field( - rdf_predicate=HSTERMS.spatialReference, default=None + json_schema_extra={"rdf_predicate": HSTERMS.spatialReference}, default=None ) _parse_spatial_reference = model_validator(mode='before')(parse_rdf_multidimensional_spatial_reference) @@ -110,7 +130,9 @@ def serialize_url(self, _type: URIRef, _info): class TimeSeriesMetadataInRDF(BaseAggregationMetadataInRDF): - rdf_type: AnyUrl = Field(rdf_predicate=RDF.type, frozen=True, default=HSTERMS.TimeSeriesAggregation) + rdf_type: AnyUrl = Field( + json_schema_extra={"rdf_predicate": RDF.type}, frozen=True, default=HSTERMS.TimeSeriesAggregation + ) _label_literal = Literal["Time Series Content: One or more time series held in an ODM2 format SQLite file and optional source comma separated (.csv) files"] label: _label_literal = Field( @@ -118,10 +140,16 @@ class TimeSeriesMetadataInRDF(BaseAggregationMetadataInRDF): default="Time Series Content: One or more time series held in an ODM2 format " "SQLite file and optional source comma separated (.csv) files", ) - dc_type: AnyUrl = Field(rdf_predicate=DC.type, default=HSTERMS.TimeSeriesAggregation, frozen=True) - description: DescriptionInRDF = Field(rdf_predicate=DC.description, default_factory=DescriptionInRDF) + dc_type: AnyUrl = Field( + json_schema_extra={"rdf_predicate": DC.type}, default=HSTERMS.TimeSeriesAggregation, frozen=True + ) + description: DescriptionInRDF = Field( + json_schema_extra={"rdf_predicate": DC.description}, default_factory=DescriptionInRDF + ) - time_series_results: List[TimeSeriesResultInRDF] = Field(rdf_predicate=HSTERMS.timeSeriesResult, default=[]) + time_series_results: List[TimeSeriesResultInRDF] = Field( + json_schema_extra={"rdf_predicate": HSTERMS.timeSeriesResult}, default=[] + ) _parse_description = model_validator(mode='before')(rdf_parse_description) @@ -131,7 +159,9 @@ def serialize_url(self, _type: URIRef, _info): class ReferencedTimeSeriesMetadataInRDF(BaseAggregationMetadataInRDF): - rdf_type: AnyUrl = Field(rdf_predicate=RDF.type, frozen=True, default=HSTERMS.ReferencedTimeSeriesAggregation) + rdf_type: AnyUrl = Field( + json_schema_extra={"rdf_predicate": RDF.type}, frozen=True, default=HSTERMS.ReferencedTimeSeriesAggregation + ) _label_literal = Literal["Referenced Time Series Content: A reference to one or more time series served from HydroServers outside of HydroShare in WaterML format"] label: _label_literal = Field( @@ -139,7 +169,9 @@ class ReferencedTimeSeriesMetadataInRDF(BaseAggregationMetadataInRDF): default="Referenced Time Series Content: A reference to one or more time series " "served from HydroServers outside of HydroShare in WaterML format", ) - dc_type: AnyUrl = Field(rdf_predicate=DC.type, default=HSTERMS.ReferencedTimeSeriesAggregation, frozen=True) + dc_type: AnyUrl = Field( + json_schema_extra={"rdf_predicate": DC.type}, default=HSTERMS.ReferencedTimeSeriesAggregation, frozen=True + ) @field_serializer('dc_type', 'rdf_type') def serialize_url(self, _type: URIRef, _info): @@ -147,10 +179,14 @@ def serialize_url(self, _type: URIRef, _info): class FileSetMetadataInRDF(BaseAggregationMetadataInRDF): - rdf_type: AnyUrl = Field(rdf_predicate=RDF.type, frozen=True, default=HSTERMS.FileSetAggregation) + rdf_type: AnyUrl = Field( + json_schema_extra={"rdf_predicate": RDF.type}, frozen=True, default=HSTERMS.FileSetAggregation + ) _label_literal = Literal["File Set Content: One or more files with specific metadata"] label: _label_literal = Field(frozen=True, default="File Set Content: One or more files with specific metadata") - dc_type: AnyUrl = Field(rdf_predicate=DC.type, default=HSTERMS.FileSetAggregation, frozen=True) + dc_type: AnyUrl = Field( + json_schema_extra={"rdf_predicate": DC.type}, default=HSTERMS.FileSetAggregation, frozen=True + ) @field_serializer('dc_type', 'rdf_type') def serialize_url(self, _type: URIRef, _info): @@ -158,11 +194,15 @@ def serialize_url(self, _type: URIRef, _info): class SingleFileMetadataInRDF(BaseAggregationMetadataInRDF): - rdf_type: AnyUrl = Field(rdf_predicate=RDF.type, frozen=True, default=HSTERMS.SingleFileAggregation) + rdf_type: AnyUrl = Field( + json_schema_extra={"rdf_predicate": RDF.type}, frozen=True, default=HSTERMS.SingleFileAggregation + ) _label_literal = Literal["Single File Content: A single file with file specific metadata"] label: _label_literal = Field(frozen=True, default="Single File Content: A single file with file specific metadata") - dc_type: AnyUrl = Field(rdf_predicate=DC.type, default=HSTERMS.SingleFileAggregation, frozen=True) + dc_type: AnyUrl = Field( + json_schema_extra={"rdf_predicate": DC.type}, default=HSTERMS.SingleFileAggregation, frozen=True + ) @field_serializer('dc_type', 'rdf_type') def serialize_url(self, _type: URIRef, _info): @@ -170,25 +210,37 @@ def serialize_url(self, _type: URIRef, _info): class ModelProgramMetadataInRDF(BaseAggregationMetadataInRDF): - rdf_type: AnyUrl = Field(rdf_predicate=RDF.type, frozen=True, default=HSTERMS.ModelProgramAggregation) + rdf_type: AnyUrl = Field( + json_schema_extra={"rdf_predicate": RDF.type}, frozen=True, default=HSTERMS.ModelProgramAggregation + ) _label_literal = Literal["Model Program Content: One or more files with specific metadata"] label: _label_literal = Field(frozen=True, default="Model Program Content: One or more files with specific metadata") - dc_type: AnyUrl = Field(rdf_predicate=DC.type, default=HSTERMS.ModelProgramAggregation, frozen=True) - - name: str = Field(rdf_predicate=HSTERMS.modelProgramName, default=None) - version: str = Field(rdf_predicate=HSTERMS.modelVersion, default=None) - programming_languages: List[str] = Field(rdf_predicate=HSTERMS.modelProgramLanguage, default=[]) - operating_systems: List[str] = Field(rdf_predicate=HSTERMS.modelOperatingSystem, default=[]) - release_date: date = Field(rdf_predicate=HSTERMS.modelReleaseDate, default=None) - website: AnyUrl = Field(rdf_predicate=HSTERMS.modelWebsite, default=None) - code_repository: AnyUrl = Field(rdf_predicate=HSTERMS.modelCodeRepository, default=None) - program_schema_json: AnyUrl = Field(rdf_predicate=HSTERMS.modelProgramSchema, default=None) - - release_notes: List[AnyUrl] = Field(rdf_predicate=HSTERMS.modelReleaseNotes, default=[]) - documentation: List[AnyUrl] = Field(rdf_predicate=HSTERMS.modelDocumentation, default=[]) - software: List[AnyUrl] = Field(rdf_predicate=HSTERMS.modelSoftware, default=[]) - engine: List[AnyUrl] = Field(rdf_predicate=HSTERMS.modelEngine, default=[]) + dc_type: AnyUrl = Field( + json_schema_extra={"rdf_predicate": DC.type}, default=HSTERMS.ModelProgramAggregation, frozen=True + ) + + name: str = Field(json_schema_extra={"rdf_predicate": HSTERMS.modelProgramName}, default=None) + version: str = Field(json_schema_extra={"rdf_predicate": HSTERMS.modelVersion}, default=None) + programming_languages: List[str] = Field( + json_schema_extra={"rdf_predicate": HSTERMS.modelProgramLanguage}, default=[] + ) + operating_systems: List[str] = Field( + json_schema_extra={"rdf_predicate": HSTERMS.modelOperatingSystem}, default=[] + ) + release_date: date = Field( + json_schema_extra={"rdf_predicate": HSTERMS.modelReleaseDate}, default=None + ) + website: AnyUrl = Field( + json_schema_extra={"rdf_predicate": HSTERMS.modelWebsite}, default=None + ) + code_repository: AnyUrl = Field(json_schema_extra={"rdf_predicate": HSTERMS.modelCodeRepository}, default=None) + program_schema_json: AnyUrl = Field(json_schema_extra={"rdf_predicate": HSTERMS.modelProgramSchema}, default=None) + + release_notes: List[AnyUrl] = Field(json_schema_extra={"rdf_predicate": HSTERMS.modelReleaseNotes}, default=[]) + documentation: List[AnyUrl] = Field(json_schema_extra={"rdf_predicate": HSTERMS.modelDocumentation}, default=[]) + software: List[AnyUrl] = Field(json_schema_extra={"rdf_predicate": HSTERMS.modelSoftware}, default=[]) + engine: List[AnyUrl] = Field(json_schema_extra={"rdf_predicate": HSTERMS.modelEngine}, default=[]) _parse_file_types = model_validator(mode='before')(rdf_parse_file_types) @@ -198,16 +250,22 @@ def serialize_url(self, _type: URIRef, _info): class ModelInstanceMetadataInRDF(BaseAggregationMetadataInRDF): - rdf_type: AnyUrl = Field(rdf_predicate=RDF.type, frozen=True, default=HSTERMS.ModelInstanceAggregation) + rdf_type: AnyUrl = Field( + json_schema_extra={"rdf_predicate": RDF.type}, frozen=True, default=HSTERMS.ModelInstanceAggregation + ) _label_literal = Literal["Model Instance Content: One or more files with specific metadata"] label: _label_literal = Field(frozen=True, default="Model Instance Content: One or more files with specific metadata") - dc_type: AnyUrl = Field(rdf_predicate=DC.type, default=HSTERMS.ModelInstanceAggregation, frozen=True) + dc_type: AnyUrl = Field( + json_schema_extra={"rdf_predicate": DC.type}, default=HSTERMS.ModelInstanceAggregation, frozen=True + ) - includes_model_output: bool = Field(rdf_predicate=HSTERMS.includesModelOutput) - executed_by: AnyUrl = Field(rdf_predicate=HSTERMS.executedByModelProgram, default=None) - program_schema_json: AnyUrl = Field(rdf_predicate=HSTERMS.modelProgramSchema, default=None) - program_schema_json_values: AnyUrl = Field(rdf_predicate=HSTERMS.modelProgramSchemaValues, default=None) + includes_model_output: bool = Field(json_schema_extra={"rdf_predicate": HSTERMS.includesModelOutput}) + executed_by: AnyUrl = Field(json_schema_extra={"rdf_predicate": HSTERMS.executedByModelProgram}, default=None) + program_schema_json: AnyUrl = Field(json_schema_extra={"rdf_predicate": HSTERMS.modelProgramSchema}, default=None) + program_schema_json_values: AnyUrl = Field( + json_schema_extra={"rdf_predicate": HSTERMS.modelProgramSchemaValues}, default=None + ) @field_serializer('dc_type', 'rdf_type') def serialize_url(self, _type: URIRef, _info): diff --git a/hsmodels/schemas/rdf/fields.py b/hsmodels/schemas/rdf/fields.py index 5985f4e..37c3869 100644 --- a/hsmodels/schemas/rdf/fields.py +++ b/hsmodels/schemas/rdf/fields.py @@ -86,44 +86,44 @@ class RDFBaseModel(BaseModel): class DCTypeInRDF(RDFBaseModel): - is_defined_by: AnyUrl = Field(rdf_predicate=RDFS.isDefinedBy) - label: str = Field(rdf_predicate=RDFS.label) + is_defined_by: AnyUrl = Field(json_schema_extra={"rdf_predicate": RDFS.isDefinedBy}) + label: str = Field(json_schema_extra={"rdf_predicate": RDFS.label}) class RelationInRDF(RDFBaseModel): - isExecutedBy: str = Field(rdf_predicate=HSTERMS.isExecutedBy, default=None) - isCreatedBy: str = Field(rdf_predicate=HSTERMS.isCreatedBy, default=None) - isDescribedBy: str = Field(rdf_predicate=HSTERMS.isDescribedBy, default=None) - isSimilarTo: str = Field(rdf_predicate=HSTERMS.isSimilarTo, default=None) - - isPartOf: str = Field(rdf_predicate=DCTERMS.isPartOf, default=None) - hasPart: str = Field(rdf_predicate=DCTERMS.hasPart, default=None) - isVersionOf: str = Field(rdf_predicate=DCTERMS.isVersionOf, default=None) - isReplacedBy: str = Field(rdf_predicate=DCTERMS.isReplacedBy, default=None) - conformsTo: str = Field(rdf_predicate=DCTERMS.conformsTo, default=None) - hasFormat: str = Field(rdf_predicate=DCTERMS.hasFormat, default=None) - isFormatOf: str = Field(rdf_predicate=DCTERMS.isFormatOf, default=None) - isRequiredBy: str = Field(rdf_predicate=DCTERMS.isRequiredBy, default=None) - requires: str = Field(rdf_predicate=DCTERMS.requires, default=None) - isReferencedBy: str = Field(rdf_predicate=DCTERMS.isReferencedBy, default=None) - references: str = Field(rdf_predicate=DCTERMS.references, default=None) - replaces: str = Field(rdf_predicate=DCTERMS.replaces, default=None) - source: str = Field(rdf_predicate=DCTERMS.source, default=None) + isExecutedBy: str = Field(json_schema_extra={"rdf_predicate": HSTERMS.isExecutedBy}, default=None) + isCreatedBy: str = Field(json_schema_extra={"rdf_predicate": HSTERMS.isCreatedBy}, default=None) + isDescribedBy: str = Field(json_schema_extra={"rdf_predicate": HSTERMS.isDescribedBy}, default=None) + isSimilarTo: str = Field(json_schema_extra={"rdf_predicate": HSTERMS.isSimilarTo}, default=None) + + isPartOf: str = Field(json_schema_extra={"rdf_predicate": DCTERMS.isPartOf}, default=None) + hasPart: str = Field(json_schema_extra={"rdf_predicate": DCTERMS.hasPart}, default=None) + isVersionOf: str = Field(json_schema_extra={"rdf_predicate": DCTERMS.isVersionOf}, default=None) + isReplacedBy: str = Field(json_schema_extra={"rdf_predicate": DCTERMS.isReplacedBy}, default=None) + conformsTo: str = Field(json_schema_extra={"rdf_predicate": DCTERMS.conformsTo}, default=None) + hasFormat: str = Field(json_schema_extra={"rdf_predicate": DCTERMS.hasFormat}, default=None) + isFormatOf: str = Field(json_schema_extra={"rdf_predicate": DCTERMS.isFormatOf}, default=None) + isRequiredBy: str = Field(json_schema_extra={"rdf_predicate": DCTERMS.isRequiredBy}, default=None) + requires: str = Field(json_schema_extra={"rdf_predicate": DCTERMS.requires}, default=None) + isReferencedBy: str = Field(json_schema_extra={"rdf_predicate": DCTERMS.isReferencedBy}, default=None) + references: str = Field(json_schema_extra={"rdf_predicate": DCTERMS.references}, default=None) + replaces: str = Field(json_schema_extra={"rdf_predicate": DCTERMS.replaces}, default=None) + source: str = Field(json_schema_extra={"rdf_predicate": DCTERMS.source}, default=None) _parse_relation = model_validator(mode='before')(parse_relation_rdf) class DescriptionInRDF(RDFBaseModel): - abstract: str = Field(rdf_predicate=DCTERMS.abstract, default=None) + abstract: str = Field(json_schema_extra={"rdf_predicate": DCTERMS.abstract}, default=None) class IdentifierInRDF(RDFBaseModel): - hydroshare_identifier: AnyUrl = Field(rdf_predicate=HSTERMS.hydroShareIdentifier) + hydroshare_identifier: AnyUrl = Field(json_schema_extra={"rdf_predicate": HSTERMS.hydroShareIdentifier}) class ExtendedMetadataInRDF(RDFBaseModel): - value: str = Field(rdf_predicate=HSTERMS.value, default="") - key: str = Field(rdf_predicate=HSTERMS.key) + value: str = Field(json_schema_extra={"rdf_predicate": HSTERMS.value}, default="") + key: str = Field(json_schema_extra={"rdf_predicate": HSTERMS.key}) class CellInformationInRDF(CellInformation, RDFBaseModel): @@ -131,8 +131,8 @@ class CellInformationInRDF(CellInformation, RDFBaseModel): class DateInRDF(RDFBaseModel): - type: DateType = Field(rdf_predicate=RDF.type) - value: datetime = Field(rdf_predicate=RDF.value) + type: DateType = Field(json_schema_extra={"rdf_predicate": RDF.type}) + value: datetime = Field(json_schema_extra={"rdf_predicate": RDF.value}) class RightsInRDF(Rights, RDFBaseModel): @@ -140,32 +140,34 @@ class RightsInRDF(Rights, RDFBaseModel): class CreatorInRDF(RDFBaseModel): - creator_order: Optional[PositiveInt] = Field(default=None, rdf_predicate=HSTERMS.creatorOrder) - name: str = Field(default=None, rdf_predicate=HSTERMS.name) - phone: str = Field(default=None, rdf_predicate=HSTERMS.phone) - address: str = Field(default=None, rdf_predicate=HSTERMS.address) - organization: str = Field(default=None, rdf_predicate=HSTERMS.organization) - email: EmailStr = Field(default=None, rdf_predicate=HSTERMS.email) - homepage: HttpUrl = Field(default=None, rdf_predicate=HSTERMS.homepage) - hydroshare_user_id: int = Field(default=None, rdf_predicate=HSTERMS.hydroshare_user_id) - ORCID: AnyUrl = Field(default=None, rdf_predicate=HSTERMS.ORCID) - google_scholar_id: AnyUrl = Field(default=None, rdf_predicate=HSTERMS.GoogleScholarID) - research_gate_id: AnyUrl = Field(default=None, rdf_predicate=HSTERMS.ResearchGateID) + creator_order: Optional[PositiveInt] = Field( + default=None, json_schema_extra={"rdf_predicate": HSTERMS.creatorOrder} + ) + name: str = Field(default=None, json_schema_extra={"rdf_predicate": HSTERMS.name}) + phone: str = Field(default=None, json_schema_extra={"rdf_predicate": HSTERMS.phone}) + address: str = Field(default=None, json_schema_extra={"rdf_predicate": HSTERMS.address}) + organization: str = Field(default=None, json_schema_extra={"rdf_predicate": HSTERMS.organization}) + email: EmailStr = Field(default=None, json_schema_extra={"rdf_predicate": HSTERMS.email}) + homepage: HttpUrl = Field(default=None, json_schema_extra={"rdf_predicate": HSTERMS.homepage}) + hydroshare_user_id: int = Field(default=None, json_schema_extra={"rdf_predicate": HSTERMS.hydroshare_user_id}) + ORCID: AnyUrl = Field(default=None, json_schema_extra={"rdf_predicate": HSTERMS.ORCID}) + google_scholar_id: AnyUrl = Field(default=None, json_schema_extra={"rdf_predicate": HSTERMS.GoogleScholarID}) + research_gate_id: AnyUrl = Field(default=None, json_schema_extra={"rdf_predicate": HSTERMS.ResearchGateID}) _group_identifiers = model_validator(mode='before')(split_user_identifiers) class ContributorInRDF(RDFBaseModel): - name: str = Field(default=None, rdf_predicate=HSTERMS.name) - phone: str = Field(default=None, rdf_predicate=HSTERMS.phone) - address: str = Field(default=None, rdf_predicate=HSTERMS.address) - organization: str = Field(default=None, rdf_predicate=HSTERMS.organization) - email: EmailStr = Field(default=None, rdf_predicate=HSTERMS.email) - homepage: HttpUrl = Field(default=None, rdf_predicate=HSTERMS.homepage) - hydroshare_user_id: int = Field(default=None, rdf_predicate=HSTERMS.hydroshare_user_id) - ORCID: AnyUrl = Field(default=None, rdf_predicate=HSTERMS.ORCID) - google_scholar_id: AnyUrl = Field(default=None, rdf_predicate=HSTERMS.GoogleScholarID) - research_gate_id: AnyUrl = Field(default=None, rdf_predicate=HSTERMS.ResearchGateID) + name: str = Field(default=None, json_schema_extra={"rdf_predicate": HSTERMS.name}) + phone: str = Field(default=None, json_schema_extra={"rdf_predicate": HSTERMS.phone}) + address: str = Field(default=None, json_schema_extra={"rdf_predicate": HSTERMS.address}) + organization: str = Field(default=None, json_schema_extra={"rdf_predicate": HSTERMS.organization}) + email: EmailStr = Field(default=None, json_schema_extra={"rdf_predicate": HSTERMS.email}) + homepage: HttpUrl = Field(default=None, json_schema_extra={"rdf_predicate": HSTERMS.homepage}) + hydroshare_user_id: int = Field(default=None, json_schema_extra={"rdf_predicate": HSTERMS.hydroshare_user_id}) + ORCID: AnyUrl = Field(default=None, json_schema_extra={"rdf_predicate": HSTERMS.ORCID}) + google_scholar_id: AnyUrl = Field(default=None, json_schema_extra={"rdf_predicate": HSTERMS.GoogleScholarID}) + research_gate_id: AnyUrl = Field(default=None, json_schema_extra={"rdf_predicate": HSTERMS.ResearchGateID}) _group_identifiers = model_validator(mode='before')(split_user_identifiers) @@ -179,18 +181,18 @@ class BandInformationInRDF(BandInformation, RDFBaseModel): class CoverageInRDF(RDFBaseModel): - type: CoverageType = Field(rdf_predicate=RDF.type) - value: str = Field(rdf_predicate=RDF.value) + type: CoverageType = Field(json_schema_extra={"rdf_predicate": RDF.type}) + value: str = Field(json_schema_extra={"rdf_predicate": RDF.value}) class SpatialReferenceInRDF(RDFBaseModel): - type: SpatialReferenceType = Field(rdf_predicate=RDF.type) - value: str = Field(rdf_predicate=RDF.value) + type: SpatialReferenceType = Field(json_schema_extra={"rdf_predicate": RDF.type}) + value: str = Field(json_schema_extra={"rdf_predicate": RDF.value}) class MultidimensionalSpatialReferenceInRDF(RDFBaseModel): - type: MultidimensionalSpatialReferenceType = Field(rdf_predicate=RDF.type) - value: str = Field(rdf_predicate=RDF.value) + type: MultidimensionalSpatialReferenceType = Field(json_schema_extra={"rdf_predicate": RDF.type}) + value: str = Field(json_schema_extra={"rdf_predicate": RDF.value}) class FieldInformationInRDF(FieldInformation, RDFBaseModel): @@ -234,11 +236,11 @@ class UTCOffSetInRDF(UTCOffSet, RDFBaseModel): class TimeSeriesResultInRDF(TimeSeriesResult, RDFBaseModel): - unit: UnitInRDF = Field(rdf_predicate=HSTERMS.unit, default=None) - site: TimeSeriesSiteInRDF = Field(rdf_predicate=HSTERMS.site) - variable: TimeSeriesVariableInRDF = Field(rdf_predicate=HSTERMS.variable) - method: TimeSeriesMethodInRDF = Field(rdf_predicate=HSTERMS.method) - processing_level: ProcessingLevelInRDF = Field(rdf_predicate=HSTERMS.processingLevel) - utc_offset: UTCOffSetInRDF = Field(rdf_predicate=HSTERMS.UTCOffSet, default=None) + unit: UnitInRDF = Field(json_schema_extra={"rdf_predicate": HSTERMS.unit}, default=None) + site: TimeSeriesSiteInRDF = Field(json_schema_extra={"rdf_predicate": HSTERMS.site}) + variable: TimeSeriesVariableInRDF = Field(json_schema_extra={"rdf_predicate": HSTERMS.variable}) + method: TimeSeriesMethodInRDF = Field(json_schema_extra={"rdf_predicate": HSTERMS.method}) + processing_level: ProcessingLevelInRDF = Field(json_schema_extra={"rdf_predicate": HSTERMS.processingLevel}) + utc_offset: UTCOffSetInRDF = Field(json_schema_extra={"rdf_predicate": HSTERMS.UTCOffSet}, default=None) _parse_utc_offset = model_validator(mode='before')(rdf_parse_utc_offset) diff --git a/hsmodels/schemas/rdf/resource.py b/hsmodels/schemas/rdf/resource.py index 2ba743d..b0917b9 100644 --- a/hsmodels/schemas/rdf/resource.py +++ b/hsmodels/schemas/rdf/resource.py @@ -42,44 +42,50 @@ def hs_uid(): class FileMap(BaseModel): rdf_subject: get_RDF_IdentifierType(Field(default_factory=hs_uid)) - rdf_type: AnyUrl = Field(rdf_predicate=RDF.type, frozen=True, default=ORE.Aggregation) + rdf_type: AnyUrl = Field(json_schema_extra={"rdf_predicate": RDF.type}, frozen=True, default=ORE.Aggregation) - is_documented_by: AnyUrl = Field(rdf_predicate=CITOTERMS.isDocumentedBy) - files: List[AnyUrl] = Field(rdf_predicate=ORE.aggregates, default=[]) - title: str = Field(rdf_predicate=DC.title) - is_described_by: AnyUrl = Field(rdf_predicate=ORE.isDescribedBy) + is_documented_by: AnyUrl = Field(json_schema_extra={"rdf_predicate": CITOTERMS.isDocumentedBy}) + files: List[AnyUrl] = Field(json_schema_extra={"rdf_predicate": ORE.aggregates}, default=[]) + title: str = Field(json_schema_extra={"rdf_predicate": DC.title}) + is_described_by: AnyUrl = Field(json_schema_extra={"rdf_predicate": ORE.isDescribedBy}) class ResourceMap(BaseModel): rdf_subject: get_RDF_IdentifierType(Field(default_factory=hs_uid)) - rdf_type: AnyUrl = Field(rdf_predicate=RDF.type, frozen=True, default=ORE.ResourceMap) + rdf_type: AnyUrl = Field(json_schema_extra={"rdf_predicate": RDF.type}, frozen=True, default=ORE.ResourceMap) - describes: FileMap = Field(rdf_predicate=ORE.describes) - identifier: str = Field(rdf_predicate=DC.identifier, default=None) + describes: FileMap = Field(json_schema_extra={"rdf_predicate": ORE.describes}) + identifier: str = Field(json_schema_extra={"rdf_predicate": DC.identifier}, default=None) # modified: datetime = Field(rdf_predicate=DCTERMS.modified) - creator: str = Field(rdf_predicate=DC.creator, default=None) + creator: str = Field(json_schema_extra={"rdf_predicate": DC.creator}, default=None) class BaseResource(BaseModel): rdf_subject: get_RDF_IdentifierType(Field(default_factory=hs_uid, alias='rdf_subject')) - title: str = Field(rdf_predicate=DC.title) - description: DescriptionInRDF = Field(rdf_predicate=DC.description, default_factory=DescriptionInRDF) - language: str = Field(rdf_predicate=DC.language, default='eng') - subjects: List[str] = Field(rdf_predicate=DC.subject, default=[]) - dc_type: AnyUrl = Field(rdf_predicate=DC.type, default=HSTERMS.CompositeResource, frozen=True) - identifier: IdentifierInRDF = Field(rdf_predicate=DC.identifier, frozen=True) - creators: List[CreatorInRDF] = Field(rdf_predicate=DC.creator, default=[]) - - contributors: List[ContributorInRDF] = Field(rdf_predicate=DC.contributor, default=[]) - relations: List[RelationInRDF] = Field(rdf_predicate=DC.relation, default=[]) - extended_metadata: List[ExtendedMetadataInRDF] = Field(rdf_predicate=HSTERMS.extendedMetadata, default=[]) - rights: RightsInRDF = Field(rdf_predicate=DC.rights, default=None) - dates: List[DateInRDF] = Field(rdf_predicate=DC.date, default=[]) - awards: List[AwardInfoInRDF] = Field(rdf_predicate=HSTERMS.awardInfo, default=[]) - coverages: List[CoverageInRDF] = Field(rdf_predicate=DC.coverage, default=[]) - publisher: PublisherInRDF = Field(rdf_predicate=DC.publisher, default=None) - citation: str = Field(rdf_predicate=DCTERMS.bibliographicCitation) + title: str = Field(json_schema_extra={"rdf_predicate": DC.title}) + description: DescriptionInRDF = Field( + json_schema_extra={"rdf_predicate": DC.description}, default_factory=DescriptionInRDF + ) + language: str = Field(json_schema_extra={"rdf_predicate": DC.language}, default='eng') + subjects: List[str] = Field(json_schema_extra={"rdf_predicate": DC.subject}, default=[]) + dc_type: AnyUrl = Field( + json_schema_extra={"rdf_predicate": DC.type}, default=HSTERMS.CompositeResource, frozen=True + ) + identifier: IdentifierInRDF = Field(json_schema_extra={"rdf_predicate": DC.identifier}, frozen=True) + creators: List[CreatorInRDF] = Field(json_schema_extra={"rdf_predicate": DC.creator}, default=[]) + + contributors: List[ContributorInRDF] = Field(json_schema_extra={"rdf_predicate": DC.contributor}, default=[]) + relations: List[RelationInRDF] = Field(json_schema_extra={"rdf_predicate": DC.relation}, default=[]) + extended_metadata: List[ExtendedMetadataInRDF] = Field( + json_schema_extra={"rdf_predicate": HSTERMS.extendedMetadata}, default=[] + ) + rights: RightsInRDF = Field(json_schema_extra={"rdf_predicate": DC.rights}, default=None) + dates: List[DateInRDF] = Field(json_schema_extra={"rdf_predicate": DC.date}, default=[]) + awards: List[AwardInfoInRDF] = Field(json_schema_extra={"rdf_predicate": HSTERMS.awardInfo}, default=[]) + coverages: List[CoverageInRDF] = Field(json_schema_extra={"rdf_predicate": DC.coverage}, default=[]) + publisher: PublisherInRDF = Field(json_schema_extra={"rdf_predicate": DC.publisher}, default=None) + citation: str = Field(json_schema_extra={"rdf_predicate": DCTERMS.bibliographicCitation}) _parse_rdf_subject = model_validator(mode='before')(rdf_parse_rdf_subject) _parse_coverages = model_validator(mode='before')(parse_coverages) @@ -96,8 +102,12 @@ class BaseResource(BaseModel): class ResourceMetadataInRDF(BaseResource): - dc_type: AnyUrl = Field(rdf_predicate=DC.type, default=HSTERMS.CompositeResource, frozen=True) - rdf_type: AnyUrl = Field(rdf_predicate=RDF.type, frozen=True, default=HSTERMS.CompositeResource) + dc_type: AnyUrl = Field( + json_schema_extra={"rdf_predicate": DC.type}, default=HSTERMS.CompositeResource, frozen=True + ) + rdf_type: AnyUrl = Field( + json_schema_extra={"rdf_predicate": RDF.type}, frozen=True, default=HSTERMS.CompositeResource + ) _label_literal = Literal["Composite Resource"] label: _label_literal = Field(default="Composite Resource", frozen=True, alias='label') @@ -108,8 +118,12 @@ def serialize_url(self, _type: URIRef, _info): class CollectionMetadataInRDF(BaseResource): - dc_type: AnyUrl = Field(rdf_predicate=DC.type, default=HSTERMS.CollectionResource, frozen=True) - rdf_type: AnyUrl = Field(rdf_predicate=RDF.type, frozen=True, default=HSTERMS.CollectionResource) + dc_type: AnyUrl = Field( + json_schema_extra={"rdf_predicate": DC.type}, default=HSTERMS.CollectionResource, frozen=True + ) + rdf_type: AnyUrl = Field( + json_schema_extra={"rdf_predicate": RDF.type}, frozen=True, default=HSTERMS.CollectionResource + ) _label_literal = Literal["Collection Resource"] label: _label_literal = Field(default="Collection Resource", frozen=True, alias='label') From c5e6eefceee32f74a6d91d530962eb5b19061576 Mon Sep 17 00:00:00 2001 From: pkdash Date: Mon, 2 Oct 2023 13:03:42 -0400 Subject: [PATCH 18/20] [#44] replacing deprecated dict() with model_dump() --- hsmodels/schemas/fields.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hsmodels/schemas/fields.py b/hsmodels/schemas/fields.py index 9890fc5..c7bbfc1 100644 --- a/hsmodels/schemas/fields.py +++ b/hsmodels/schemas/fields.py @@ -183,7 +183,7 @@ class Creator(BaseMetadata): @classmethod def from_user(cls, user): - user_dict = user.dict() + user_dict = user.model_dump() hydroshare_user_id = user.url.path.split("/")[-2] user_dict["hydroshare_user_id"] = hydroshare_user_id if user.website: @@ -241,7 +241,7 @@ def from_user(cls, user): :param user: a User :return: a Contributor """ - user_dict = user.dict() + user_dict = user.model_dump() hydroshare_user_id = user.url.path.split("/")[-2] user_dict["hydroshare_user_id"] = hydroshare_user_id if user.website: From b68df27a2a9651af111ffa4d893319dc83d83f54 Mon Sep 17 00:00:00 2001 From: pkdash Date: Thu, 25 Jan 2024 14:24:28 -0500 Subject: [PATCH 19/20] [#44] moving rdf_predicate to RDF model fields --- hsmodels/schemas/fields.py | 73 +----------------- hsmodels/schemas/rdf/fields.py | 130 +++++++++++++++++++++------------ 2 files changed, 85 insertions(+), 118 deletions(-) diff --git a/hsmodels/schemas/fields.py b/hsmodels/schemas/fields.py index c7bbfc1..b7731a3 100644 --- a/hsmodels/schemas/fields.py +++ b/hsmodels/schemas/fields.py @@ -3,7 +3,6 @@ from pydantic import AnyUrl, ConfigDict, EmailStr, Field, HttpUrl, field_validator, model_validator -from hsmodels.namespaces import HSTERMS from hsmodels.schemas import base_models from hsmodels.schemas.base_models import BaseMetadata from hsmodels.schemas.enums import ModelProgramFileType, RelationType, UserIdentifierType, VariableType @@ -36,30 +35,24 @@ class CellInformation(BaseMetadata): model_config = ConfigDict(title='Raster Cell Metadata') # TODO: Is there such a thing as "name" for CellInformation? - name: str = Field(default=None, max_length=500, title="Name", description="Name of the cell information", - json_schema_extra={"rdf_predicate": HSTERMS.name}) + name: str = Field(default=None, max_length=500, title="Name", description="Name of the cell information",) rows: int = Field(default=None, title="Rows", - description="The integer number of rows in the raster dataset", - json_schema_extra={"rdf_predicate": HSTERMS.rows}) + description="The integer number of rows in the raster dataset",) columns: int = Field( default=None, title="Columns", description="The integer number of columns in the raster dataset", - json_schema_extra={"rdf_predicate": HSTERMS.columns} ) cell_size_x_value: float = Field( default=None, title="Cell size x value", description="The size of the raster grid cell in the x-direction expressed as a float", - json_schema_extra={"rdf_predicate": HSTERMS.cellSizeXValue} ) cell_data_type: str = Field( default=None, max_length=50, title="Cell data type", description="The data type of the raster grid cell values", - json_schema_extra={"rdf_predicate": HSTERMS.cellDataType} ) cell_size_y_value: float = Field( default=None, title="Cell size y value", description="The size of the raster grid cell in the y-direction expressed as a float", - json_schema_extra={"rdf_predicate": HSTERMS.cellSizeYValue} ) @@ -72,12 +65,10 @@ class Rights(BaseMetadata): statement: str = Field( title="Statement", description="A string containing the text of the license or rights statement", - json_schema_extra={"rdf_predicate": HSTERMS.rightsStatement} ) url: AnyUrl = Field( title="URL", description="An object containing the URL pointing to a description of the license or rights statement", - json_schema_extra={"rdf_predicate": HSTERMS.URL} ) @classmethod @@ -259,21 +250,17 @@ class AwardInfo(BaseMetadata): funding_agency_name: str = Field( title="Agency name", description="A string containing the name of the funding agency or organization", - json_schema_extra={"rdf_predicate": HSTERMS.fundingAgencyName} ) title: str = Field( default=None, title="Award title", description="A string containing the title of the project or award", - json_schema_extra={"rdf_predicate": HSTERMS.awardTitle} ) number: str = Field( default=None, title="Award number", description="A string containing the award number or other identifier", - json_schema_extra={"rdf_predicate": HSTERMS.awardNumber} ) funding_agency_url: AnyUrl = Field( default=None, title="Agency URL", description="An object containing a URL pointing to a website describing the funding award", - json_schema_extra={"rdf_predicate": HSTERMS.fundingAgencyURL} ) @@ -285,49 +272,41 @@ class BandInformation(BaseMetadata): model_config = ConfigDict(title='Raster Band Metadata') name: str = Field(max_length=500, title="Name", description="A string containing the name of the raster band", - json_schema_extra={"rdf_predicate": HSTERMS.name} ) variable_name: str = Field( default=None, max_length=100, title="Variable name", description="A string containing the name of the variable represented by the raster band", - json_schema_extra={"rdf_predicate": HSTERMS.variableName} ) variable_unit: str = Field( default=None, max_length=50, title="Variable unit", description="A string containing the units for the raster band variable", - json_schema_extra={"rdf_predicate": HSTERMS.variableUnit} ) no_data_value: str = Field( default=None, title="Nodata value", description="A string containing the numeric nodata value for the raster band", - json_schema_extra={"rdf_predicate": HSTERMS.noDataValue} ) maximum_value: str = Field( default=None, title="Maximum value", description="A string containing the maximum numeric value for the raster band", - json_schema_extra={"rdf_predicate": HSTERMS.maximumValue} ) comment: str = Field( default=None, title="Comment", description="A string containing a comment about the raster band", - json_schema_extra={"rdf_predicate": HSTERMS.comment} ) method: str = Field( default=None, title="Method", description="A string containing a description of the method used to create the raster band data", - json_schema_extra={"rdf_predicate": HSTERMS.method} ) minimum_value: str = Field( default=None, title="Minimum value", description="A string containing the minimum numerica value for the raster dataset", - json_schema_extra={"rdf_predicate": HSTERMS.minimumValue} ) @@ -341,11 +320,9 @@ class FieldInformation(BaseMetadata): field_name: str = Field( max_length=128, title="Field name", description="A string containing the name of the attribute table field", - json_schema_extra={"rdf_predicate": HSTERMS.fieldName} ) field_type: str = Field( max_length=128, title="Field type", description="A string containing the data type of the values in the field", - json_schema_extra={"rdf_predicate": HSTERMS.fieldType} ) # TODO: What is the "field_type_code"? It's not displayed on the resource landing page, but it's encoded in the # aggregation metadata as an integer value. @@ -354,17 +331,14 @@ class FieldInformation(BaseMetadata): max_length=50, title="Field type code", description="A string value containing a code that indicates the field type", - json_schema_extra={"rdf_predicate": HSTERMS.fieldTypeCode} ) field_width: int = Field( default=None, title="Field width", description="An integer value containing the width of the attribute field", - json_schema_extra={"rdf_predicate": HSTERMS.fieldWidth} ) field_precision: int = Field( default=None, title="Field precision", description="An integer value containing the precision of the attribute field", - json_schema_extra={"rdf_predicate": HSTERMS.fieldPrecision} ) @@ -379,13 +353,11 @@ class GeometryInformation(BaseMetadata): default=0, title="Feature count", description="An integer containing the number of features in the geographic feature aggregation", - json_schema_extra={"rdf_predicate": HSTERMS.featureCount} ) geometry_type: str = Field( max_length=128, title="Geometry type", description="A string containing the type of features in the geographic feature aggregation", - json_schema_extra={"rdf_predicate": HSTERMS.geometryType} ) @@ -398,42 +370,35 @@ class Variable(BaseMetadata): name: str = Field( max_length=1000, title="Variable name", description="A string containing the name of the variable", - json_schema_extra={"rdf_predicate": HSTERMS.name} ) unit: str = Field( max_length=1000, title="Units", description="A string containing the units in which the values for the variable are expressed", - json_schema_extra={"rdf_predicate": HSTERMS.unit} ) type: VariableType = Field(title="Type", description="The data type of the values for the variable", - json_schema_extra={"rdf_predicate": HSTERMS.type} ) shape: str = Field( max_length=1000, title="Shape", description="A string containing the shape of the variable expressed as a list of dimensions", - json_schema_extra={"rdf_predicate": HSTERMS.shape} ) descriptive_name: str = Field( default=None, max_length=1000, title="Descriptive name", description="A string containing a descriptive name for the variable", - json_schema_extra={"rdf_predicate": HSTERMS.descriptive_name} ) method: str = Field( default=None, title="Method", description="A string containing a description of the method used to create the values for the variable", - json_schema_extra={"rdf_predicate": HSTERMS.method} ) missing_value: str = Field( default=None, max_length=1000, title="Missing value", description="A string containing the value used to indicate missing values for the variable", - json_schema_extra={"rdf_predicate": HSTERMS.missing_value} ) @@ -446,11 +411,9 @@ class Publisher(BaseMetadata): name: str = Field( max_length=200, title="Publisher name", description="A string containing the name of the publisher", - json_schema_extra={"rdf_predicate": HSTERMS.publisherName} ) url: AnyUrl = Field( title="Publisher URL", description="An object containing a URL that points to the publisher website", - json_schema_extra={"rdf_predicate": HSTERMS.publisherURL} ) @@ -465,37 +428,31 @@ class TimeSeriesVariable(BaseMetadata): max_length=50, title="Variable code", description="A string containing a short but meaningful code that identifies a variable", - json_schema_extra={"rdf_predicate": HSTERMS.VariableCode} ) variable_name: str = Field( max_length=100, title="Variable name", description="A string containing the name of the variable", - json_schema_extra={"rdf_predicate": HSTERMS.VariableName} ) variable_type: str = Field( max_length=100, title="Variable type", description="A string containing the type of variable from the ODM2 VariableType controlled vocabulary", - json_schema_extra={"rdf_predicate": HSTERMS.VariableType} ) # TODO: The NoData value for a variable in an ODM2 database is not always an integer. # It could be a floating point value. We might want to change this to a string or a floating point value # It is an integer in the HydroShare database, so will have to be updated there as well if changed no_data_value: int = Field(title="NoData value", description="The NoData value for the variable", - json_schema_extra={"rdf_predicate": HSTERMS.NoDataValue} ) variable_definition: str = Field( default=None, max_length=255, title="Variable definition", description="A string containing a detailed description of the variable", - json_schema_extra={"rdf_predicate": HSTERMS.VariableDefinition} ) speciation: str = Field( default=None, max_length=255, title="Speciation", description="A string containing the speciation for the variable from the ODM2 Speciation control vocabulary", - json_schema_extra={"rdf_predicate": HSTERMS.Speciation} ) @@ -510,43 +467,36 @@ class TimeSeriesSite(BaseMetadata): max_length=200, title="Site code", description="A string containing a short but meaningful code identifying the site", - json_schema_extra={"rdf_predicate": HSTERMS.SiteCode} ) site_name: str = Field( default=None, max_length=255, title="Site name", description="A string containing the name of the site", - json_schema_extra={"rdf_predicate": HSTERMS.SiteName} ) elevation_m: float = Field( default=None, title="Elevation", description="A floating point number expressing the elevation of the site in meters", - json_schema_extra={"rdf_predicate": HSTERMS.Elevation_m} ) elevation_datum: str = Field( default=None, max_length=50, title="Elevation datum", description="A string expressing the elevation datum used from the ODM2 Elevation Datum controlled vocabulary", - json_schema_extra={"rdf_predicate": HSTERMS.ElevationDatum} ) site_type: str = Field( default=None, max_length=100, title="Site type", description="A string containing the type of site from the ODM2 Sampling Feature Type controlled vocabulary ", - json_schema_extra={"rdf_predicate": HSTERMS.SiteType} ) latitude: float = Field( default=None, title="Latitude", description="A floating point value expressing the latitude coordinate of the site", - json_schema_extra={"rdf_predicate": HSTERMS.Latitude} ) longitude: float = Field( default=None, title="Longitude", description="A floating point value expressing the longitude coordinate of the site", - json_schema_extra={"rdf_predicate": HSTERMS.Longitude} ) @@ -561,28 +511,23 @@ class TimeSeriesMethod(BaseMetadata): max_length=50, title="Method code", description="A string containing a short but meaningful code identifying the method", - json_schema_extra={"rdf_predicate": HSTERMS.MethodCode} ) method_name: str = Field( max_length=200, title="Method name", description="A string containing the name of the method", - json_schema_extra={"rdf_predicate": HSTERMS.MethodName} ) method_type: str = Field( max_length=200, title="Method type", description="A string containing the method type from the ODM2 Method Type controlled vocabulary", - json_schema_extra={"rdf_predicate": HSTERMS.MethodType} ) method_description: str = Field( default=None, title="Method description", description="A string containing a detailed description of the method", - json_schema_extra={"rdf_predicate": HSTERMS.MethodDescription} ) method_link: AnyUrl = Field( default=None, title="Method link", description="An object containing a URL that points to a website having a detailed description of the method", - json_schema_extra={"rdf_predicate": HSTERMS.MethodLink} ) @@ -598,20 +543,17 @@ class ProcessingLevel(BaseMetadata): max_length=50, title="Processing level code", description="A string containing a short but meaningful code identifying the processing level", - json_schema_extra={"rdf_predicate": HSTERMS.ProcessingLevelCode} ) definition: str = Field( default=None, max_length=200, title="Definition", description="A string containing a description of the processing level", - json_schema_extra={"rdf_predicate": HSTERMS.Definition} ) explanation: str = Field( default=None, title="Explanation", description="A string containing a more extensive explanation of the meaning of the processing level", - json_schema_extra={"rdf_predicate": HSTERMS.Explanation} ) @@ -626,19 +568,16 @@ class Unit(BaseMetadata): max_length=255, title="Unit type", description="A string containing the type of unit from the ODM2 Units Type controlled vocabulary", - json_schema_extra={"rdf_predicate": HSTERMS.UnitsType} ) name: str = Field( max_length=255, title="Unit name", description="A string containing the name of the unit from the ODM2 units list", - json_schema_extra={"rdf_predicate": HSTERMS.UnitsName} ) abbreviation: str = Field( max_length=20, title="Unit abbreviation", description="A string containing an abbreviation for the unit from the ODM2 units list", - json_schema_extra={"rdf_predicate": HSTERMS.UnitsAbbreviation} ) @@ -653,7 +592,6 @@ class UTCOffSet(BaseMetadata): default=0, title="UTC offset value", description="A floating point number containing the UTC time offset associated with the data values expressed in hours", - json_schema_extra={"rdf_predicate": HSTERMS.value} ) @@ -668,37 +606,31 @@ class TimeSeriesResult(BaseMetadata): max_length=36, title="Series ID", description="A string containing a unique identifier for the time series result", - json_schema_extra={"rdf_predicate": HSTERMS.timeSeriesResultUUID} ) unit: Unit = Field( default=None, title="Units", description="An object containing the units in which the values of the time series are expressed", - ) status: str = Field( default=None, max_length=255, title="Status", description="A string containing the status of the time series result chosen from the ODM2 Status controlled vocabulary", - json_schema_extra={"rdf_predicate": HSTERMS.Status} ) sample_medium: str = Field( max_length=255, title="Sample medium", description="A string containing the sample medium in which the time series result was measured chosen from the ODM2 Medium controlled vocabulary", - json_schema_extra={"rdf_predicate": HSTERMS.SampleMedium} ) value_count: int = Field( title="Value count", description="An integer value containing the number of data values contained within the time series result", - json_schema_extra={"rdf_predicate": HSTERMS.ValueCount} ) aggregation_statistic: str = Field( max_length=255, title="Aggregation statistic", description="A string containing the aggregation statistic associated with the values of the time series result chosen from the ODM2 Aggregation Statistic controlled vocabulary", - json_schema_extra={"rdf_predicate": HSTERMS.AggregationStatistic} ) # TODO: Not sure what "series_label" is. It's not an ODM2 thing # in HydroShare it is generated with this format @@ -708,7 +640,6 @@ class TimeSeriesResult(BaseMetadata): max_length=255, title="Series label", description="A string containing a label for the time series result", - json_schema_extra={"rdf_predicate": HSTERMS.SeriesLabel} ) site: TimeSeriesSite = Field( title="Site", diff --git a/hsmodels/schemas/rdf/fields.py b/hsmodels/schemas/rdf/fields.py index 37c3869..4b9e8b2 100644 --- a/hsmodels/schemas/rdf/fields.py +++ b/hsmodels/schemas/rdf/fields.py @@ -13,23 +13,6 @@ from hsmodels.namespaces import DCTERMS, HSTERMS, RDF, RDFS from hsmodels.schemas.enums import CoverageType, DateType, MultidimensionalSpatialReferenceType, SpatialReferenceType -from hsmodels.schemas.fields import ( - AwardInfo, - BandInformation, - CellInformation, - FieldInformation, - GeometryInformation, - ProcessingLevel, - Publisher, - Rights, - TimeSeriesMethod, - TimeSeriesResult, - TimeSeriesSite, - TimeSeriesVariable, - Unit, - UTCOffSet, - Variable, -) from hsmodels.schemas.rdf.root_validators import parse_relation_rdf, rdf_parse_utc_offset, split_user_identifiers @@ -126,8 +109,13 @@ class ExtendedMetadataInRDF(RDFBaseModel): key: str = Field(json_schema_extra={"rdf_predicate": HSTERMS.key}) -class CellInformationInRDF(CellInformation, RDFBaseModel): - pass +class CellInformationInRDF(RDFBaseModel): + name: str = Field(default=None, json_schema_extra={"rdf_predicate": HSTERMS.name}) + rows: int = Field(default=None, json_schema_extra={"rdf_predicate": HSTERMS.rows}) + columns: int = Field(default=None, json_schema_extra={"rdf_predicate": HSTERMS.columns}) + cell_size_x_value: float = Field(default=None, json_schema_extra={"rdf_predicate": HSTERMS.cellSizeXValue}) + cell_size_y_value: float = Field(default=None, json_schema_extra={"rdf_predicate": HSTERMS.cellSizeYValue}) + cell_data_type: str = Field(default=None, json_schema_extra={"rdf_predicate": HSTERMS.cellDataType}) class DateInRDF(RDFBaseModel): @@ -135,8 +123,9 @@ class DateInRDF(RDFBaseModel): value: datetime = Field(json_schema_extra={"rdf_predicate": RDF.value}) -class RightsInRDF(Rights, RDFBaseModel): - pass +class RightsInRDF(RDFBaseModel): + statement: str = Field(json_schema_extra={"rdf_predicate": HSTERMS.rightsStatement}) + url: AnyUrl = Field(json_schema_extra={"rdf_predicate": HSTERMS.URL}) class CreatorInRDF(RDFBaseModel): @@ -172,12 +161,22 @@ class ContributorInRDF(RDFBaseModel): _group_identifiers = model_validator(mode='before')(split_user_identifiers) -class AwardInfoInRDF(AwardInfo, RDFBaseModel): - pass +class AwardInfoInRDF(RDFBaseModel): + funding_agency_name: str = Field(json_schema_extra={"rdf_predicate": HSTERMS.fundingAgencyName}) + title: str = Field(default=None, json_schema_extra={"rdf_predicate": HSTERMS.awardTitle}) + number: str = Field(default=None, json_schema_extra={"rdf_predicate": HSTERMS.awardNumber}) + funding_agency_url: AnyUrl = Field(default=None, json_schema_extra={"rdf_predicate": HSTERMS.fundingAgencyURL}) -class BandInformationInRDF(BandInformation, RDFBaseModel): - pass +class BandInformationInRDF(RDFBaseModel): + name: str = Field(json_schema_extra={"rdf_predicate": HSTERMS.name}) + variable_name: str = Field(default=None, json_schema_extra={"rdf_predicate": HSTERMS.variableName}) + variable_unit: str = Field(default=None, json_schema_extra={"rdf_predicate": HSTERMS.variableUnit}) + no_data_value: str = Field(default=None, json_schema_extra={"rdf_predicate": HSTERMS.noDataValue}) + maximum_value: str = Field(default=None, json_schema_extra={"rdf_predicate": HSTERMS.maximumValue}) + comment: str = Field(default=None, json_schema_extra={"rdf_predicate": HSTERMS.comment}) + method: str = Field(default=None, json_schema_extra={"rdf_predicate": HSTERMS.method}) + minimum_value: str = Field(default=None, json_schema_extra={"rdf_predicate": HSTERMS.minimumValue}) class CoverageInRDF(RDFBaseModel): @@ -195,48 +194,85 @@ class MultidimensionalSpatialReferenceInRDF(RDFBaseModel): value: str = Field(json_schema_extra={"rdf_predicate": RDF.value}) -class FieldInformationInRDF(FieldInformation, RDFBaseModel): - pass - +class FieldInformationInRDF(RDFBaseModel): + field_name: str = Field(json_schema_extra={"rdf_predicate": HSTERMS.fieldName}) + field_type: str = Field(json_schema_extra={"rdf_predicate": HSTERMS.fieldType}) + field_type_code: str = Field(default=None, json_schema_extra={"rdf_predicate": HSTERMS.fieldTypeCode}) + field_width: int = Field(default=None, json_schema_extra={"rdf_predicate": HSTERMS.fieldWidth}) + field_precision: int = Field(default=None, json_schema_extra={"rdf_predicate": HSTERMS.fieldPrecision}) -class GeometryInformationInRDF(GeometryInformation, RDFBaseModel): - pass +class GeometryInformationInRDF(RDFBaseModel): + feature_count: int = Field(default=0, json_schema_extra={"rdf_predicate": HSTERMS.featureCount}) + geometry_type: str = Field(json_schema_extra={"rdf_predicate": HSTERMS.geometryType}) -class VariableInRDF(Variable, RDFBaseModel): - pass +class VariableInRDF(RDFBaseModel): + name: str = Field(json_schema_extra={"rdf_predicate": HSTERMS.name}) + unit: str = Field(json_schema_extra={"rdf_predicate": HSTERMS.unit}) + type: str = Field(json_schema_extra={"rdf_predicate": HSTERMS.type}) + shape: str = Field(json_schema_extra={"rdf_predicate": HSTERMS.shape}) + descriptive_name: str = Field(default=None, json_schema_extra={"rdf_predicate": HSTERMS.descriptive_name}) + method: str = Field(default=None, json_schema_extra={"rdf_predicate": HSTERMS.method}) + missing_value: str = Field(default=None, json_schema_extra={"rdf_predicate": HSTERMS.missing_value}) -class PublisherInRDF(Publisher, RDFBaseModel): - pass +class PublisherInRDF(RDFBaseModel): + name: str = Field(json_schema_extra={"rdf_predicate": HSTERMS.publisherName}) + url: AnyUrl = Field(json_schema_extra={"rdf_predicate": HSTERMS.publisherURL}) -class TimeSeriesVariableInRDF(TimeSeriesVariable, RDFBaseModel): - pass +class TimeSeriesVariableInRDF(RDFBaseModel): + variable_code: str = Field(json_schema_extra={"rdf_predicate": HSTERMS.VariableCode}) + variable_name: str = Field(json_schema_extra={"rdf_predicate": HSTERMS.VariableName}) + variable_type: str = Field(json_schema_extra={"rdf_predicate": HSTERMS.VariableType}) + no_data_value: int = Field(json_schema_extra={"rdf_predicate": HSTERMS.NoDataValue}) + variable_definition: str = Field(default=None, json_schema_extra={"rdf_predicate": HSTERMS.VariableDefinition}) + speciation: str = Field(default=None, json_schema_extra={"rdf_predicate": HSTERMS.Speciation}) + -class TimeSeriesSiteInRDF(TimeSeriesSite, RDFBaseModel): - pass +class TimeSeriesSiteInRDF(RDFBaseModel): + site_code: str = Field(json_schema_extra={"rdf_predicate": HSTERMS.SiteCode}) + site_name: str = Field(default=None, json_schema_extra={"rdf_predicate": HSTERMS.SiteName}) + elevation_m: float = Field(default=None, json_schema_extra={"rdf_predicate": HSTERMS.Elevation_m}) + elevation_datum: str = Field(default=None, json_schema_extra={"rdf_predicate": HSTERMS.ElevationDatum}) + site_type: str = Field(default=None, json_schema_extra={"rdf_predicate": HSTERMS.SiteType}) + latitude: float = Field(default=None, json_schema_extra={"rdf_predicate": HSTERMS.Latitude}) + longitude: float = Field(default=None, json_schema_extra={"rdf_predicate": HSTERMS.Longitude}) -class TimeSeriesMethodInRDF(TimeSeriesMethod, RDFBaseModel): - pass +class TimeSeriesMethodInRDF(RDFBaseModel): + method_code: str = Field(json_schema_extra={"rdf_predicate": HSTERMS.MethodCode}) + method_name: str = Field(json_schema_extra={"rdf_predicate": HSTERMS.MethodName}) + method_type: str = Field(json_schema_extra={"rdf_predicate": HSTERMS.MethodType}) + method_description: str = Field(default=None, json_schema_extra={"rdf_predicate": HSTERMS.MethodDescription}) + method_link: AnyUrl = Field(default=None, json_schema_extra={"rdf_predicate": HSTERMS.MethodLink}) -class ProcessingLevelInRDF(ProcessingLevel, RDFBaseModel): - pass +class ProcessingLevelInRDF(RDFBaseModel): + processing_level_code: str = Field(json_schema_extra={"rdf_predicate": HSTERMS.ProcessingLevelCode}) + definition: str = Field(default=None, json_schema_extra={"rdf_predicate": HSTERMS.Definition}) + explanation: str = Field(default=None, json_schema_extra={"rdf_predicate": HSTERMS.Explanation}) -class UnitInRDF(Unit, RDFBaseModel): - pass +class UnitInRDF(RDFBaseModel): + type: str = Field(json_schema_extra={"rdf_predicate": HSTERMS.UnitsType}) + name: str = Field(json_schema_extra={"rdf_predicate": HSTERMS.UnitsName}) + abbreviation: str = Field(json_schema_extra={"rdf_predicate": HSTERMS.UnitsAbbreviation}) -class UTCOffSetInRDF(UTCOffSet, RDFBaseModel): - pass +class UTCOffSetInRDF(RDFBaseModel): + value: float = Field(json_schema_extra={"rdf_predicate": HSTERMS.value}) -class TimeSeriesResultInRDF(TimeSeriesResult, RDFBaseModel): +class TimeSeriesResultInRDF(RDFBaseModel): + series_id: str = Field(json_schema_extra={"rdf_predicate": HSTERMS.timeSeriesResultUUID}) unit: UnitInRDF = Field(json_schema_extra={"rdf_predicate": HSTERMS.unit}, default=None) + status: str = Field(json_schema_extra={"rdf_predicate": HSTERMS.Status}, default=None) + sample_medium: str = Field(json_schema_extra={"rdf_predicate": HSTERMS.SampleMedium}) + value_count: int = Field(json_schema_extra={"rdf_predicate": HSTERMS.ValueCount}) + aggregation_statistic: str = Field(json_schema_extra={"rdf_predicate": HSTERMS.AggregationStatistic}) + series_label: str = Field(json_schema_extra={"rdf_predicate": HSTERMS.SeriesLabel}, default=None) site: TimeSeriesSiteInRDF = Field(json_schema_extra={"rdf_predicate": HSTERMS.site}) variable: TimeSeriesVariableInRDF = Field(json_schema_extra={"rdf_predicate": HSTERMS.variable}) method: TimeSeriesMethodInRDF = Field(json_schema_extra={"rdf_predicate": HSTERMS.method}) From baad19a465e0cb522fc085715cc34231ea8b67c8 Mon Sep 17 00:00:00 2001 From: Scott Black Date: Thu, 15 Feb 2024 16:04:55 -0700 Subject: [PATCH 20/20] Update setup.py --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 5cebaad..e588049 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ setup( name='hsmodels', - version='0.5.9', + version='1.0.0', packages=find_packages(include=['hsmodels', 'hsmodels.*', 'hsmodels.schemas.*', 'hsmodels.schemas.rdf.*'], exclude=("tests",)), install_requires=[