diff --git a/abis_mapping/base/mapper.py b/abis_mapping/base/mapper.py index 45789939..6bbde7b9 100644 --- a/abis_mapping/base/mapper.py +++ b/abis_mapping/base/mapper.py @@ -13,12 +13,11 @@ import rdflib # Local -from . import types -from abis_mapping.types import spatial -from abis_mapping.types import temporal -from abis_mapping.types import schema +from . import types as base_types +from abis_mapping import types from abis_mapping import utils + # Typing from typing import Any, Iterator, Optional, final @@ -43,7 +42,7 @@ class ABISMapper(abc.ABC): @abc.abstractmethod def apply_validation( self, - data: types.ReadableType, + data: base_types.ReadableType, **kwargs: Any, ) -> frictionless.Report: """Applies Frictionless Validation to Raw Data to Generate Report. @@ -59,7 +58,7 @@ def apply_validation( @abc.abstractmethod def apply_mapping( self, - data: types.ReadableType, + data: base_types.ReadableType, dataset_iri: Optional[rdflib.URIRef] = None, base_iri: Optional[rdflib.Namespace] = None, **kwargs: Any, @@ -91,14 +90,14 @@ def add_default_dataset( graph.add((uri, a, utils.namespaces.TERN.RDFDataset)) graph.add((uri, rdflib.DCTERMS.title, rdflib.Literal(self.DATASET_DEFAULT_NAME))) graph.add((uri, rdflib.DCTERMS.description, rdflib.Literal(self.DATASET_DEFAULT_DESCRIPTION))) - graph.add((uri, rdflib.DCTERMS.issued, temporal.Date.today().to_rdf_literal())) + graph.add((uri, rdflib.DCTERMS.issued, types.temporal.Date.today().to_rdf_literal())) def add_geometry_supplied_as( self, subj: rdflib.graph.Node, pred: rdflib.graph.Node, obj: rdflib.graph.Node, - geom: spatial.Geometry, + geom: types.spatial.Geometry, graph: rdflib.Graph, ) -> None: """Add geometry supplied as originally to the graph. @@ -177,7 +176,7 @@ def extract_extra_fields( @classmethod def extra_fields_schema( cls, - data: frictionless.Row | types.ReadableType, + data: frictionless.Row | base_types.ReadableType, full_schema: bool = False ) -> frictionless.Schema: """Creates a schema with all extra fields found in data. @@ -306,11 +305,27 @@ def schema( # Read Schema and validate s_dict = json.loads(schema_file.read_text()) - s_class = schema.Schema.model_validate(s_dict, strict=True) + s_class = types.schema.Schema.model_validate(s_dict, strict=True) # Dump pydantic class to return dict return s_class.model_dump(exclude_none=discard_optional) + @final + @classmethod + @property + def fields(cls) -> dict[str, types.schema.Field]: + """Indexed dictionary of all fields' metadata. + + Returns: + dict[str, types.schema.Field]: Dictionary of all fields + with name as key.. + """ + # Get schema + schema = types.schema.Schema.model_validate(cls.schema()) + + # Return dictionary of fields + return {f.name: f for f in schema.fields} + @final @classmethod @functools.lru_cache diff --git a/abis_mapping/templates/incidental_occurrence_data/mapping.py b/abis_mapping/templates/incidental_occurrence_data/mapping.py index 6e51fa09..b9450d26 100644 --- a/abis_mapping/templates/incidental_occurrence_data/mapping.py +++ b/abis_mapping/templates/incidental_occurrence_data/mapping.py @@ -860,12 +860,11 @@ def add_observation_scientific_name( # that this row has a specimen, otherwise it is Field Sample foi = sample_specimen if has_specimen(row) else sample_field - # Retrieve Vocab or Create on the Fly - vocab = vocabs.identification_method.IDENTIFICATION_METHOD.get( - graph=graph, - value=row["identificationMethod"], - source=dataset, - ) + # Retrieve registered vocab + vocab = self.fields["identificationMethod"].get_vocab() + + # Retrieve term or Create on the Fly + term = vocab(graph=graph, source=dataset).get(row["identificationMethod"]) # Add to Graph graph.add((uri, a, utils.namespaces.TERN.Observation)) @@ -879,7 +878,7 @@ def add_observation_scientific_name( graph.add((uri, rdflib.TIME.hasTime, temporal_entity)) graph.add((temporal_entity, a, rdflib.TIME.Instant)) graph.add((temporal_entity, date_identified.rdf_in_xsd, date_identified.to_rdf_literal())) - graph.add((uri, rdflib.SOSA.usedProcedure, vocab)) + graph.add((uri, rdflib.SOSA.usedProcedure, term)) # Check for identifiedBy if row["identifiedBy"]: @@ -949,12 +948,11 @@ def add_observation_verbatim_id( # that this row has a specimen, otherwise it is Field Sample foi = sample_specimen if has_specimen(row) else sample_field - # Retrieve Vocab or Create on the Fly - vocab = vocabs.identification_method.IDENTIFICATION_METHOD.get( - graph=graph, - value=row["identificationMethod"], - source=dataset, - ) + # Retrieve vocab + vocab = self.fields["identificationMethod"].get_vocab() + + # Retrieve term or Create on the Fly + term = vocab(graph=graph, source=dataset).get(row["identificationMethod"]) # Add to Graph graph.add((uri, a, utils.namespaces.TERN.Observation)) @@ -968,7 +966,7 @@ def add_observation_verbatim_id( graph.add((uri, rdflib.TIME.hasTime, temporal_entity)) graph.add((temporal_entity, a, rdflib.TIME.Instant)) graph.add((temporal_entity, date_identified.rdf_in_xsd, date_identified.to_rdf_literal())) - graph.add((uri, rdflib.SOSA.usedProcedure, vocab)) + graph.add((uri, rdflib.SOSA.usedProcedure, term)) # Check for identifiedBy if row["identifiedBy"]: @@ -1025,12 +1023,11 @@ def add_sampling_field( datum=row["geodeticDatum"] ) - # Retrieve Vocab or Create on the Fly - vocab = vocabs.sampling_protocol.SAMPLING_PROTOCOL.get( - graph=graph, - value=row["samplingProtocol"], - source=dataset, - ) + # Retrieve vocab + vocab = self.fields["samplingProtocol"].get_vocab() + + # Retrieve term or create on the fly + term = vocab(graph=graph, source=dataset).get(row["samplingProtocol"]) # Add to Graph graph.add((uri, a, utils.namespaces.TERN.Sampling)) @@ -1059,7 +1056,7 @@ def add_sampling_field( graph.add((uri, rdflib.TIME.hasTime, temporal_entity)) graph.add((temporal_entity, a, rdflib.TIME.Instant)) graph.add((temporal_entity, event_date.rdf_in_xsd, event_date.to_rdf_literal())) - graph.add((uri, rdflib.SOSA.usedProcedure, vocab)) + graph.add((uri, rdflib.SOSA.usedProcedure, term)) # Check for recordID if row["recordID"]: @@ -1154,18 +1151,17 @@ def add_id_qualifier_value( if not row["identificationQualifier"]: return + # Retrieve vocab for field + vocab = self.fields["identificationQualifier"].get_vocab() + # Retrieve Vocab or Create on the Fly - vocab = vocabs.identification_qualifier.IDENTIFICATION_QUALIFIER.get( - graph=graph, - value=row["identificationQualifier"], - source=dataset, - ) + term = vocab(graph=graph, source=dataset).get(row["identificationQualifier"]) # Identification Qualifier Value graph.add((uri, a, utils.namespaces.TERN.IRI)) graph.add((uri, a, utils.namespaces.TERN.Value)) graph.add((uri, rdflib.RDFS.label, rdflib.Literal("identificationQualifier"))) - graph.add((uri, rdflib.RDF.value, vocab)) + graph.add((uri, rdflib.RDF.value, term)) def add_id_remarks_attribute( self, @@ -1394,12 +1390,12 @@ def add_sample_field( with this node graph (rdflib.Graph): Graph to add to """ - # Retrieve Vocab or Create on the Fly - vocab = vocabs.kingdom.KINGDOM_OCCURRENCE.get( - graph=graph, - value=row["kingdom"], - source=dataset, - ) + + # Retrieve vocab for field + vocab = self.fields["kingdom"].get_vocab("KINGDOM_OCCURRENCE") + + # Retrieve term or Create on the Fly + term = vocab(graph=graph, source=dataset).get(row["kingdom"]) # Add to Graph graph.add((uri, a, utils.namespaces.TERN.FeatureOfInterest)) @@ -1408,7 +1404,7 @@ def add_sample_field( graph.add((uri, rdflib.RDFS.comment, rdflib.Literal("field-sample"))) graph.add((uri, rdflib.SOSA.isResultOf, sampling_field)) graph.add((uri, rdflib.SOSA.isSampleOf, feature_of_interest)) - graph.add((uri, utils.namespaces.TERN.featureType, vocab)) + graph.add((uri, utils.namespaces.TERN.featureType, term)) # Check for institutionCode if row["institutionCode"]: @@ -1552,12 +1548,11 @@ def add_sample_specimen( if not has_specimen(row): return - # Retrieve Vocab or Create on the Fly - vocab = vocabs.kingdom.KINGDOM_SPECIMEN.get( - graph=graph, - value=row["kingdom"], - source=dataset, - ) + # Retrieve vocab for field + vocab = self.fields["kingdom"].get_vocab("KINGDOM_SPECIMEN") + + # Retrieve term or Create on the Fly + term = vocab(graph=graph, source=dataset).get(row["kingdom"]) # Add to Graph graph.add((uri, a, utils.namespaces.TERN.FeatureOfInterest)) @@ -1566,7 +1561,7 @@ def add_sample_specimen( graph.add((uri, rdflib.RDFS.comment, rdflib.Literal("specimen-sample"))) graph.add((uri, rdflib.SOSA.isResultOf, sampling_specimen)) graph.add((uri, rdflib.SOSA.isSampleOf, sample_field)) - graph.add((uri, utils.namespaces.TERN.featureType, vocab)) + graph.add((uri, utils.namespaces.TERN.featureType, term)) # Check for catalogNumber if row["catalogNumber"]: @@ -1718,18 +1713,17 @@ def add_kingdom_value( dataset (rdflib.URIRef): Dataset this belongs to graph (rdflib.Graph): Graph to add to """ - # Retrieve Vocab or Create on the Fly - vocab = vocabs.kingdom.KINGDOM.get( - graph=graph, - value=row["kingdom"], - source=dataset, - ) + # Retrieve vocab for field + vocab = self.fields["kingdom"].get_vocab("KINGDOM") + + # Retrieve term or Create on the Fly + term = vocab(graph=graph, source=dataset).get(row["kingdom"]) # Kingdom Value graph.add((uri, a, utils.namespaces.TERN.IRI)) graph.add((uri, a, utils.namespaces.TERN.Value)) graph.add((uri, rdflib.RDFS.label, rdflib.Literal(f"kingdom = {row['kingdom']}"))) - graph.add((uri, rdflib.RDF.value, vocab)) + graph.add((uri, rdflib.RDF.value, term)) def add_taxon_rank_attribute( self, @@ -1779,18 +1773,17 @@ def add_taxon_rank_value( if not row["taxonRank"]: return - # Retrieve Vocab or Create on the Fly - vocab = vocabs.taxon_rank.TAXON_RANK.get( - graph=graph, - value=row["taxonRank"], - source=dataset, - ) + # Retrieve vocab for field + vocab = self.fields["taxonRank"].get_vocab() + + # Retrieve term or Create on the Fly + term = vocab(graph=graph, source=dataset).get(row["taxonRank"]) # Taxon Rank Value graph.add((uri, a, utils.namespaces.TERN.IRI)) graph.add((uri, a, utils.namespaces.TERN.Value)) graph.add((uri, rdflib.RDFS.label, rdflib.Literal(f"taxon rank = {row['taxonRank']}"))) - graph.add((uri, rdflib.RDF.value, vocab)) + graph.add((uri, rdflib.RDF.value, term)) def add_individual_count_observation( self, @@ -2060,18 +2053,17 @@ def add_basis_value( if not row["basisOfRecord"]: return - # Retrieve Vocab or Create on the Fly - vocab = vocabs.basis_of_record.BASIS_OF_RECORD.get( - graph=graph, - value=row["basisOfRecord"], - source=dataset, - ) + # Retrieve vocab for field + vocab = self.fields["basisOfRecord"].get_vocab() + + # Retrieve term or Create on the Fly + term = vocab(graph=graph, source=dataset).get(row["basisOfRecord"]) # Basis of Record Value graph.add((uri, a, utils.namespaces.TERN.IRI)) graph.add((uri, a, utils.namespaces.TERN.Value)) graph.add((uri, rdflib.RDFS.label, rdflib.Literal("basisOfRecord"))) - graph.add((uri, rdflib.RDF.value, vocab)) + graph.add((uri, rdflib.RDF.value, term)) def add_owner_institution_provider( self, @@ -2189,18 +2181,17 @@ def add_occurrence_status_value( if not row["occurrenceStatus"]: return - # Retrieve Vocab or Create on the Fly - vocab = vocabs.occurrence_status.OCCURRENCE_STATUS.get( - graph=graph, - value=row["occurrenceStatus"], - source=dataset, - ) + # Retrieve vocab from field + vocab = self.fields["occurrenceStatus"].get_vocab() + + # Retrieve term or Create on the Fly + term = vocab(graph=graph, source=dataset).get(row["occurrenceStatus"]) # Occurrence Status Value graph.add((uri, a, utils.namespaces.TERN.IRI)) graph.add((uri, a, utils.namespaces.TERN.Value)) graph.add((uri, rdflib.RDFS.label, rdflib.Literal(f"occurrenceStatus = {row['occurrenceStatus']}"))) - graph.add((uri, rdflib.RDF.value, vocab)) + graph.add((uri, rdflib.RDF.value, term)) def add_preparations_attribute( self, @@ -2250,18 +2241,17 @@ def add_preparations_value( if not row["preparations"]: return - # Retrieve Vocab or Create on the Fly - vocab = vocabs.preparations.PREPARATIONS.get( - graph=graph, - value=row["preparations"], - source=dataset, - ) + # Retrieve vocab from field + vocab = self.fields["preparations"].get_vocab() + + # Retrieve term or Create on the Fly + term = vocab(graph=graph, source=dataset).get(row["preparations"]) # Preparations Value graph.add((uri, a, utils.namespaces.TERN.IRI)) graph.add((uri, a, utils.namespaces.TERN.Value)) graph.add((uri, rdflib.RDFS.label, rdflib.Literal("preparations"))) - graph.add((uri, rdflib.RDF.value, vocab)) + graph.add((uri, rdflib.RDF.value, term)) def add_establishment_means_observation( self, @@ -2343,18 +2333,17 @@ def add_establishment_means_value( if not row["establishmentMeans"]: return - # Retrieve Vocab or Create on the Fly - vocab = vocabs.establishment_means.ESTABLISHMENT_MEANS.get( - graph=graph, - value=row["establishmentMeans"], - source=dataset, - ) + # Retrieve vocab from field + vocab = self.fields["establishmentMeans"].get_vocab() + + # Retrieve term or Create on the Fly + term = vocab(graph=graph, source=dataset).get(row["establishmentMeans"]) # Establishment Means Value graph.add((uri, a, utils.namespaces.TERN.IRI)) graph.add((uri, a, utils.namespaces.TERN.Value)) graph.add((uri, rdflib.RDFS.label, rdflib.Literal("establishmentMeans-value"))) - graph.add((uri, rdflib.RDF.value, vocab)) + graph.add((uri, rdflib.RDF.value, term)) def add_life_stage_observation( self, @@ -2444,18 +2433,17 @@ def add_life_stage_value( if not row["lifeStage"]: return + # Retrieve vocab from field + vocab = self.fields["lifeStage"].get_vocab() + # Retrieve Vocab or Create on the Fly - vocab = vocabs.life_stage.LIFE_STAGE.get( - graph=graph, - value=row["lifeStage"], - source=dataset, - ) + term = vocab(graph=graph, source=dataset).get(row["lifeStage"]) # Life Stage Value graph.add((uri, a, utils.namespaces.TERN.IRI)) graph.add((uri, a, utils.namespaces.TERN.Value)) graph.add((uri, rdflib.RDFS.label, rdflib.Literal("lifeStage-value"))) - graph.add((uri, rdflib.RDF.value, vocab)) + graph.add((uri, rdflib.RDF.value, term)) def add_sex_observation( self, @@ -2544,18 +2532,17 @@ def add_sex_value( if not row["sex"]: return + # Retrieve vocab for field + vocab = self.fields["sex"].get_vocab() + # Retrieve Vocab or Create on the Fly - vocab = vocabs.sex.SEX.get( - graph=graph, - value=row["sex"], - source=dataset, - ) + term = vocab(graph=graph, source=dataset).get(row["sex"]) # Sex Value graph.add((uri, a, utils.namespaces.TERN.IRI)) graph.add((uri, a, utils.namespaces.TERN.Value)) graph.add((uri, rdflib.RDFS.label, rdflib.Literal("sex-value"))) - graph.add((uri, rdflib.RDF.value, vocab)) + graph.add((uri, rdflib.RDF.value, term)) def add_reproductive_condition_observation( self, @@ -2645,18 +2632,17 @@ def add_reproductive_condition_value( if not row["reproductiveCondition"]: return - # Retrieve Vocab or Create on the Fly - vocab = vocabs.reproductive_condition.REPRODUCTIVE_CONDITION.get( - graph=graph, - value=row["reproductiveCondition"], - source=dataset, - ) + # Retrieve vocab for field + vocab = self.fields["reproductiveCondition"].get_vocab() + + # Retrieve term or Create on the Fly + term = vocab(graph=graph, source=dataset).get(row["reproductiveCondition"]) # Reproductive Condition Value graph.add((uri, a, utils.namespaces.TERN.IRI)) graph.add((uri, a, utils.namespaces.TERN.Value)) graph.add((uri, rdflib.RDFS.label, rdflib.Literal("reproductiveCondition-value"))) - graph.add((uri, rdflib.RDF.value, vocab)) + graph.add((uri, rdflib.RDF.value, term)) def add_accepted_name_usage_observation( self, @@ -2771,12 +2757,11 @@ def add_sampling_sequencing( datum=row["geodeticDatum"], ) + # Retrieve vocab for field + vocab = self.fields["sequencingMethod"].get_vocab() + # Retrieve Vocab or Create on the Fly - vocab = vocabs.sequencing_method.SEQUENCING_METHOD.get( - graph=graph, - value=row["sequencingMethod"], - source=dataset, - ) + term = vocab(graph=graph, source=dataset).get(row["sequencingMethod"]) # Add to Graph graph.add((uri, a, utils.namespaces.TERN.Sampling)) @@ -2788,7 +2773,7 @@ def add_sampling_sequencing( graph.add((uri, rdflib.TIME.hasTime, temporal_entity)) graph.add((temporal_entity, a, rdflib.TIME.Instant)) graph.add((temporal_entity, event_date.rdf_in_xsd, event_date.to_rdf_literal())) - graph.add((uri, rdflib.SOSA.usedProcedure, vocab)) + graph.add((uri, rdflib.SOSA.usedProcedure, term)) # Add geometry geometry_node = rdflib.BNode() @@ -2934,13 +2919,11 @@ def add_threat_status_observation( or row["preparedDate"] or row["eventDate"] ) + # Retrieve vocab for field + vocab = self.fields["threatStatusCheckProtocol"].get_vocab() - # Retrieve Vocab or Create on the Fly - vocab = vocabs.check_protocol.CHECK_PROTOCOL.get( - graph=graph, - value=row["threatStatusCheckProtocol"], - source=dataset, - ) + # Retrieve term or Create on the Fly + term = vocab(graph=graph, source=dataset).get(row["threatStatusCheckProtocol"]) # Threat Status Observation graph.add((uri, a, utils.namespaces.TERN.Observation)) @@ -2951,7 +2934,7 @@ def add_threat_status_observation( graph.add((uri, rdflib.SOSA.hasSimpleResult, rdflib.Literal(row["threatStatus"]))) graph.add((uri, rdflib.SOSA.observedProperty, CONCEPT_CONSERVATION_STATUS)) graph.add((uri, rdflib.PROV.wasInfluencedBy, jurisdiction_attribute)) - graph.add((uri, rdflib.SOSA.usedProcedure, vocab)) + graph.add((uri, rdflib.SOSA.usedProcedure, term)) temporal_entity = rdflib.BNode() graph.add((uri, rdflib.TIME.hasTime, temporal_entity)) graph.add((temporal_entity, a, rdflib.TIME.Instant)) @@ -3003,18 +2986,17 @@ def add_threat_status_value( # Combine conservationJurisdiction and threatStatus value = f"{row['conservationJurisdiction']}/{row['threatStatus']}" - # Retrieve Vocab or Create on the Fly - vocab = vocabs.threat_status.THREAT_STATUS.get( - graph=graph, - value=value, - source=dataset, - ) + # Retrieve vocab for field + vocab = self.fields["threatStatus"].get_vocab() + + # Retrieve term or Create on the Fly + term = vocab(graph=graph, source=dataset).get(value) # Threat Status Value graph.add((uri, a, utils.namespaces.TERN.IRI)) graph.add((uri, a, utils.namespaces.TERN.Value)) graph.add((uri, rdflib.RDFS.label, rdflib.Literal(f"Conservation status = {row['threatStatus']}"))) - graph.add((uri, rdflib.RDF.value, vocab)) + graph.add((uri, rdflib.RDF.value, term)) def add_conservation_jurisdiction_attribute( self, @@ -3062,8 +3044,11 @@ def add_conservation_jurisdiction_value( if not row["conservationJurisdiction"]: return - # Retrieve Vocab or Create on the Fly - vocab = vocabs.conservation_jurisdiction.CONSERVATION_JURISDICTION.get(row["conservationJurisdiction"]) + # Retrieve vocab for field + vocab = self.fields["conservationJurisdiction"].get_vocab() + + # Retrieve term or Create on the Fly + term = vocab(graph=graph).get(row["conservationJurisdiction"]) # Construct Label label = f"Conservation Jurisdiction = {row['conservationJurisdiction']}" @@ -3072,7 +3057,7 @@ def add_conservation_jurisdiction_value( graph.add((uri, a, utils.namespaces.TERN.IRI)) graph.add((uri, a, utils.namespaces.TERN.Value)) graph.add((uri, rdflib.RDFS.label, rdflib.Literal(label))) - graph.add((uri, rdflib.RDF.value, vocab)) + graph.add((uri, rdflib.RDF.value, term)) # Helper Functions diff --git a/abis_mapping/templates/incidental_occurrence_data/schema.json b/abis_mapping/templates/incidental_occurrence_data/schema.json index a93fafe2..54c13913 100644 --- a/abis_mapping/templates/incidental_occurrence_data/schema.json +++ b/abis_mapping/templates/incidental_occurrence_data/schema.json @@ -114,7 +114,10 @@ "format": "default", "constraints": { "required": false - } + }, + "vocabularies": [ + "SAMPLING_PROTOCOL" + ] }, { "name": "basisOfRecord", @@ -125,7 +128,10 @@ "format": "default", "constraints": { "required": false - } + }, + "vocabularies": [ + "BASIS_OF_RECORD" + ] }, { "name": "recordedBy", @@ -158,7 +164,10 @@ "format": "default", "constraints": { "required": false - } + }, + "vocabularies": [ + "OCCURRENCE_STATUS" + ] }, { "name": "habitat", @@ -180,7 +189,10 @@ "format": "default", "constraints": { "required": false - } + }, + "vocabularies": [ + "ESTABLISHMENT_MEANS" + ] }, { "name": "organismRemarks", @@ -213,7 +225,10 @@ "format": "default", "constraints": { "required": false - } + }, + "vocabularies": [ + "LIFE_STAGE" + ] }, { "name": "sex", @@ -224,7 +239,10 @@ "format": "default", "constraints": { "required": false - } + }, + "vocabularies": [ + "SEX" + ] }, { "name": "reproductiveCondition", @@ -235,7 +253,10 @@ "format": "default", "constraints": { "required": false - } + }, + "vocabularies": [ + "REPRODUCTIVE_CONDITION" + ] }, { "name": "occurrenceID", @@ -312,7 +333,10 @@ "format": "default", "constraints": { "required": false - } + }, + "vocabularies": [ + "PREPARATIONS" + ] }, { "name": "preparedDate", @@ -345,7 +369,10 @@ "format": "default", "constraints": { "required": false - } + }, + "vocabularies": [ + "SEQUENCING_METHOD" + ] }, { "name": "verbatimIdentification", @@ -389,7 +416,10 @@ "format": "default", "constraints": { "required": false - } + }, + "vocabularies": [ + "IDENTIFICATION_METHOD" + ] }, { "name": "scientificName", @@ -411,7 +441,10 @@ "format": "default", "constraints": { "required": false - } + }, + "vocabularies": [ + "IDENTIFICATION_QUALIFIER" + ] }, { "name": "identificationRemarks", @@ -444,7 +477,12 @@ "format": "default", "constraints": { "required": true - } + }, + "vocabularies": [ + "KINGDOM", + "KINGDOM_OCCURRENCE", + "KINGDOM_SPECIMEN" + ] }, { "name": "taxonRank", @@ -455,7 +493,10 @@ "format": "default", "constraints": { "required": false - } + }, + "vocabularies": [ + "TAXON_RANK" + ] }, { "name": "threatStatus", @@ -466,7 +507,10 @@ "format": "default", "constraints": { "required": false - } + }, + "vocabularies": [ + "THREAT_STATUS" + ] }, { "name": "conservationJurisdiction", @@ -506,7 +550,10 @@ "WESTERN AUSTRALIA", "Western Australia" ] - } + }, + "vocabularies": [ + "CONSERVATION_JURISDICTION" + ] }, { "name": "threatStatusCheckProtocol", @@ -517,7 +564,10 @@ "format": "default", "constraints": { "required": false - } + }, + "vocabularies": [ + "CHECK_PROTOCOL" + ] }, { "name": "threatStatusDateDetermined", @@ -542,4 +592,4 @@ } } ] -} \ No newline at end of file +} diff --git a/abis_mapping/templates/incidental_occurrence_data_v2/mapping.py b/abis_mapping/templates/incidental_occurrence_data_v2/mapping.py index b3d05242..ed12dff8 100644 --- a/abis_mapping/templates/incidental_occurrence_data_v2/mapping.py +++ b/abis_mapping/templates/incidental_occurrence_data_v2/mapping.py @@ -949,12 +949,11 @@ def add_observation_scientific_name( # that this row has a specimen, otherwise it is Field Sample foi = sample_specimen if has_specimen(row) else sample_field - # Retrieve Vocab or Create on the Fly - vocab = vocabs.identification_method.IDENTIFICATION_METHOD.get( - graph=graph, - value=row["identificationMethod"], - source=dataset, - ) + # Retrieve vocab for field + vocab = self.fields["identificationMethod"].get_vocab() + + # Retrieve term or Create on the Fly + term = vocab(graph=graph, source=dataset).get(row["identificationMethod"]) # Add to Graph graph.add((uri, a, utils.namespaces.TERN.Observation)) @@ -968,7 +967,7 @@ def add_observation_scientific_name( graph.add((uri, rdflib.TIME.hasTime, temporal_entity)) graph.add((temporal_entity, a, rdflib.TIME.Instant)) graph.add((temporal_entity, date_identified.rdf_in_xsd, date_identified.to_rdf_literal())) - graph.add((uri, rdflib.SOSA.usedProcedure, vocab)) + graph.add((uri, rdflib.SOSA.usedProcedure, term)) # Check for identifiedBy if row["identifiedBy"]: @@ -1032,12 +1031,11 @@ def add_observation_verbatim_id( # that this row has a specimen, otherwise it is Field Sample foi = sample_specimen if has_specimen(row) else sample_field - # Retrieve Vocab or Create on the Fly - vocab = vocabs.identification_method.IDENTIFICATION_METHOD.get( - graph=graph, - value=row["identificationMethod"], - source=dataset, - ) + # Retrieve vocab for field + vocab = self.fields["identificationMethod"].get_vocab() + + # Retrieve term or Create on the Fly + term = vocab(graph=graph, source=dataset).get(row["identificationMethod"]) # Add to Graph graph.add((uri, a, utils.namespaces.TERN.Observation)) @@ -1051,7 +1049,7 @@ def add_observation_verbatim_id( graph.add((uri, rdflib.TIME.hasTime, temporal_entity)) graph.add((temporal_entity, a, rdflib.TIME.Instant)) graph.add((temporal_entity, date_identified.rdf_in_xsd, date_identified.to_rdf_literal())) - graph.add((uri, rdflib.SOSA.usedProcedure, vocab)) + graph.add((uri, rdflib.SOSA.usedProcedure, term)) # Check for identifiedBy if row["identifiedBy"]: @@ -1178,12 +1176,11 @@ def add_sampling_field( datum=row["geodeticDatum"] ) - # Retrieve Vocab or Create on the Fly - vocab = vocabs.sampling_protocol.SAMPLING_PROTOCOL.get( - graph=graph, - value=row["samplingProtocol"], - source=dataset, - ) + # Retrieve vocab for field + vocab = self.fields["samplingProtocol"].get_vocab() + + # Retrieve term or Create on the Fly + term = vocab(graph=graph, source=dataset).get(row["samplingProtocol"]) # Add to Graph graph.add((uri, a, utils.namespaces.TERN.Sampling)) @@ -1212,7 +1209,7 @@ def add_sampling_field( graph.add((uri, rdflib.TIME.hasTime, temporal_entity)) graph.add((temporal_entity, a, rdflib.TIME.Instant)) graph.add((temporal_entity, event_date.rdf_in_xsd, event_date.to_rdf_literal())) - graph.add((uri, rdflib.SOSA.usedProcedure, vocab)) + graph.add((uri, rdflib.SOSA.usedProcedure, term)) # Add Identifier graph.add( @@ -1343,18 +1340,17 @@ def add_id_qualifier_value( if not row["identificationQualifier"]: return - # Retrieve Vocab or Create on the Fly - vocab = vocabs.identification_qualifier.IDENTIFICATION_QUALIFIER.get( - graph=graph, - value=row["identificationQualifier"], - source=dataset, - ) + # Retrieve vocab for field + vocab = self.fields["identificationQualifier"].get_vocab() + + # Retrieve term or Create on the Fly + term = vocab(graph=graph, source=dataset).get(row["identificationQualifier"]) # Identification Qualifier Value graph.add((uri, a, utils.namespaces.TERN.IRI)) graph.add((uri, a, utils.namespaces.TERN.Value)) graph.add((uri, rdflib.RDFS.label, rdflib.Literal("identificationQualifier"))) - graph.add((uri, rdflib.RDF.value, vocab)) + graph.add((uri, rdflib.RDF.value, term)) def add_id_remarks_attribute( self, @@ -1673,12 +1669,11 @@ def add_sample_field( with record number literal. graph (rdflib.Graph): Graph to add to """ - # Retrieve Vocab or Create on the Fly - vocab = vocabs.kingdom.KINGDOM_OCCURRENCE.get( - graph=graph, - value=row["kingdom"], - source=dataset, - ) + # Retrieve vocab for field + vocab = self.fields["kingdom"].get_vocab("KINGDOM_OCCURRENCE") + + # Retrieve term or Create on the Fly + term = vocab(graph=graph, source=dataset).get(row["kingdom"]) # Add to Graph graph.add((uri, a, utils.namespaces.TERN.FeatureOfInterest)) @@ -1687,7 +1682,7 @@ def add_sample_field( graph.add((uri, rdflib.RDFS.comment, rdflib.Literal("field-sample"))) graph.add((uri, rdflib.SOSA.isResultOf, sampling_field)) graph.add((uri, rdflib.SOSA.isSampleOf, feature_of_interest)) - graph.add((uri, utils.namespaces.TERN.featureType, vocab)) + graph.add((uri, utils.namespaces.TERN.featureType, term)) # Check for recordNumber if row["recordNumber"]: @@ -1780,12 +1775,11 @@ def add_sample_specimen( if not has_specimen(row): return + # Retrieve vocab for field + vocab = self.fields["kingdom"].get_vocab("KINGDOM_SPECIMEN") + # Retrieve Vocab or Create on the Fly - vocab = vocabs.kingdom.KINGDOM_SPECIMEN.get( - graph=graph, - value=row["kingdom"], - source=dataset, - ) + term = vocab(graph=graph, source=dataset).get(row["kingdom"]) # Add to Graph graph.add((uri, a, utils.namespaces.TERN.FeatureOfInterest)) @@ -1794,7 +1788,7 @@ def add_sample_specimen( graph.add((uri, rdflib.RDFS.comment, rdflib.Literal("specimen-sample"))) graph.add((uri, rdflib.SOSA.isResultOf, sampling_specimen)) graph.add((uri, rdflib.SOSA.isSampleOf, sample_field)) - graph.add((uri, utils.namespaces.TERN.featureType, vocab)) + graph.add((uri, utils.namespaces.TERN.featureType, term)) # Check for catalogNumber if row["catalogNumber"]: @@ -1908,18 +1902,17 @@ def add_kingdom_value( dataset (rdflib.URIRef): Dataset this belongs to graph (rdflib.Graph): Graph to add to """ - # Retrieve Vocab or Create on the Fly - vocab = vocabs.kingdom.KINGDOM.get( - graph=graph, - value=row["kingdom"], - source=dataset, - ) + # Retrieve vocab for field + vocab = self.fields["kingdom"].get_vocab("KINGDOM") + + # Retrieve term or Create on the Fly + term = vocab(graph=graph, source=dataset).get(row["kingdom"]) # Kingdom Value graph.add((uri, a, utils.namespaces.TERN.IRI)) graph.add((uri, a, utils.namespaces.TERN.Value)) graph.add((uri, rdflib.RDFS.label, rdflib.Literal(f"kingdom = {row['kingdom']}"))) - graph.add((uri, rdflib.RDF.value, vocab)) + graph.add((uri, rdflib.RDF.value, term)) def add_taxon_rank_attribute( self, @@ -1968,19 +1961,17 @@ def add_taxon_rank_value( # Check Existence if not row["taxonRank"]: return + # Retrieve vocab for field + vocab = self.fields["taxonRank"].get_vocab() - # Retrieve Vocab or Create on the Fly - vocab = vocabs.taxon_rank.TAXON_RANK.get( - graph=graph, - value=row["taxonRank"], - source=dataset, - ) + # Retrieve term or Create on the Fly + term = vocab(graph=graph, source=dataset).get(row["taxonRank"]) # Taxon Rank Value graph.add((uri, a, utils.namespaces.TERN.IRI)) graph.add((uri, a, utils.namespaces.TERN.Value)) graph.add((uri, rdflib.RDFS.label, rdflib.Literal(f"taxon rank = {row['taxonRank']}"))) - graph.add((uri, rdflib.RDF.value, vocab)) + graph.add((uri, rdflib.RDF.value, term)) def add_individual_count_observation( self, @@ -2234,18 +2225,17 @@ def add_basis_value( if not row["basisOfRecord"]: return - # Retrieve Vocab or Create on the Fly - vocab = vocabs.basis_of_record.BASIS_OF_RECORD.get( - graph=graph, - value=row["basisOfRecord"], - source=dataset, - ) + # Retrieve vocab for field + vocab = self.fields["basisOfRecord"].get_vocab() + + # Retrieve term or Create on the Fly + term = vocab(graph=graph, source=dataset).get(row["basisOfRecord"]) # Basis of Record Value graph.add((uri, a, utils.namespaces.TERN.IRI)) graph.add((uri, a, utils.namespaces.TERN.Value)) graph.add((uri, rdflib.RDFS.label, rdflib.Literal("basisOfRecord"))) - graph.add((uri, rdflib.RDF.value, vocab)) + graph.add((uri, rdflib.RDF.value, term)) def add_owner_institution_provider( self, @@ -2355,18 +2345,17 @@ def add_occurrence_status_value( if not row["occurrenceStatus"]: return - # Retrieve Vocab or Create on the Fly - vocab = vocabs.occurrence_status.OCCURRENCE_STATUS.get( - graph=graph, - value=row["occurrenceStatus"], - source=dataset, - ) + # Retrieve vocab for field + vocab = self.fields["occurrenceStatus"].get_vocab() + + # Retrieve term or Create on the Fly + term = vocab(graph=graph, source=dataset).get(row["occurrenceStatus"]) # Occurrence Status Value graph.add((uri, a, utils.namespaces.TERN.IRI)) graph.add((uri, a, utils.namespaces.TERN.Value)) graph.add((uri, rdflib.RDFS.label, rdflib.Literal(f"occurrenceStatus = {row['occurrenceStatus']}"))) - graph.add((uri, rdflib.RDF.value, vocab)) + graph.add((uri, rdflib.RDF.value, term)) def add_preparations_attribute( self, @@ -2416,18 +2405,17 @@ def add_preparations_value( if not row["preparations"]: return - # Retrieve Vocab or Create on the Fly - vocab = vocabs.preparations.PREPARATIONS.get( - graph=graph, - value=row["preparations"], - source=dataset, - ) + # Retrieve vocab for field + vocab = self.fields["preparations"].get_vocab() + + # Retrieve term or Create on the Fly + term = vocab(graph=graph, source=dataset).get(row["preparations"]) # Preparations Value graph.add((uri, a, utils.namespaces.TERN.IRI)) graph.add((uri, a, utils.namespaces.TERN.Value)) graph.add((uri, rdflib.RDFS.label, rdflib.Literal("preparations"))) - graph.add((uri, rdflib.RDF.value, vocab)) + graph.add((uri, rdflib.RDF.value, term)) def add_establishment_means_observation( self, @@ -2501,18 +2489,17 @@ def add_establishment_means_value( if not row["establishmentMeans"]: return - # Retrieve Vocab or Create on the Fly - vocab = vocabs.establishment_means.ESTABLISHMENT_MEANS.get( - graph=graph, - value=row["establishmentMeans"], - source=dataset, - ) + # Retrieve vocab for field + vocab = self.fields["establishmentMeans"].get_vocab() + + # Retrieve term or Create on the Fly + term = vocab(graph=graph, source=dataset).get(row["establishmentMeans"]) # Establishment Means Value graph.add((uri, a, utils.namespaces.TERN.IRI)) graph.add((uri, a, utils.namespaces.TERN.Value)) graph.add((uri, rdflib.RDFS.label, rdflib.Literal("establishmentMeans-value"))) - graph.add((uri, rdflib.RDF.value, vocab)) + graph.add((uri, rdflib.RDF.value, term)) def add_life_stage_observation( self, @@ -2594,18 +2581,17 @@ def add_life_stage_value( if not row["lifeStage"]: return - # Retrieve Vocab or Create on the Fly - vocab = vocabs.life_stage.LIFE_STAGE.get( - graph=graph, - value=row["lifeStage"], - source=dataset, - ) + # Retrieve vocab for field + vocab = self.fields["lifeStage"].get_vocab() + + # Retrieve term or Create on the Fly + term = vocab(graph=graph, source=dataset).get(row["lifeStage"]) # Life Stage Value graph.add((uri, a, utils.namespaces.TERN.IRI)) graph.add((uri, a, utils.namespaces.TERN.Value)) graph.add((uri, rdflib.RDFS.label, rdflib.Literal("lifeStage-value"))) - graph.add((uri, rdflib.RDF.value, vocab)) + graph.add((uri, rdflib.RDF.value, term)) def add_sex_observation( self, @@ -2686,18 +2672,17 @@ def add_sex_value( if not row["sex"]: return - # Retrieve Vocab or Create on the Fly - vocab = vocabs.sex.SEX.get( - graph=graph, - value=row["sex"], - source=dataset, - ) + # Retrieve vocab for field + vocab = self.fields["sex"].get_vocab() + + # Retrieve term or Create on the Fly + term = vocab(graph=graph, source=dataset).get(row["sex"]) # Sex Value graph.add((uri, a, utils.namespaces.TERN.IRI)) graph.add((uri, a, utils.namespaces.TERN.Value)) graph.add((uri, rdflib.RDFS.label, rdflib.Literal("sex-value"))) - graph.add((uri, rdflib.RDF.value, vocab)) + graph.add((uri, rdflib.RDF.value, term)) def add_reproductive_condition_observation( self, @@ -2779,18 +2764,17 @@ def add_reproductive_condition_value( if not row["reproductiveCondition"]: return - # Retrieve Vocab or Create on the Fly - vocab = vocabs.reproductive_condition.REPRODUCTIVE_CONDITION.get( - graph=graph, - value=row["reproductiveCondition"], - source=dataset, - ) + # Retrieve vocab for field + vocab = self.fields["reproductiveCondition"].get_vocab() + + # Retrieve term or Create on the Fly + term = vocab(graph=graph, source=dataset).get(row["reproductiveCondition"]) # Reproductive Condition Value graph.add((uri, a, utils.namespaces.TERN.IRI)) graph.add((uri, a, utils.namespaces.TERN.Value)) graph.add((uri, rdflib.RDFS.label, rdflib.Literal("reproductiveCondition-value"))) - graph.add((uri, rdflib.RDF.value, vocab)) + graph.add((uri, rdflib.RDF.value, term)) def add_accepted_name_usage_observation( self, @@ -2901,12 +2885,11 @@ def add_sampling_sequencing( datum=row["geodeticDatum"], ) - # Retrieve Vocab or Create on the Fly - vocab = vocabs.sequencing_method.SEQUENCING_METHOD.get( - graph=graph, - value=row["sequencingMethod"], - source=dataset, - ) + # Retrieve vocab for field + vocab = self.fields["sequencingMethod"].get_vocab() + + # Retrieve term or Create on the Fly + term = vocab(graph=graph, source=dataset).get(row["sequencingMethod"]) # Add to Graph graph.add((uri, a, utils.namespaces.TERN.Sampling)) @@ -2918,7 +2901,7 @@ def add_sampling_sequencing( graph.add((uri, rdflib.TIME.hasTime, temporal_entity)) graph.add((temporal_entity, a, rdflib.TIME.Instant)) graph.add((temporal_entity, event_date.rdf_in_xsd, event_date.to_rdf_literal())) - graph.add((uri, rdflib.SOSA.usedProcedure, vocab)) + graph.add((uri, rdflib.SOSA.usedProcedure, term)) # Add geometry geometry_node = rdflib.BNode() @@ -3057,12 +3040,11 @@ def add_threat_status_observation( or row["eventDate"] ) - # Retrieve Vocab or Create on the Fly - vocab = vocabs.check_protocol.CHECK_PROTOCOL.get( - graph=graph, - value=row["threatStatusCheckProtocol"], - source=dataset, - ) + # Retrieve vocab for field + vocab = self.fields["threatStatusCheckProtocol"].get_vocab() + + # Retrieve term or Create on the Fly + term = vocab(graph=graph, source=dataset).get(row["threatStatusCheckProtocol"]) # Threat Status Observation graph.add((uri, a, utils.namespaces.TERN.Observation)) @@ -3073,7 +3055,7 @@ def add_threat_status_observation( graph.add((uri, rdflib.SOSA.hasSimpleResult, rdflib.Literal(row["threatStatus"]))) graph.add((uri, rdflib.SOSA.observedProperty, CONCEPT_CONSERVATION_STATUS)) graph.add((uri, rdflib.PROV.wasInfluencedBy, jurisdiction_attribute)) - graph.add((uri, rdflib.SOSA.usedProcedure, vocab)) + graph.add((uri, rdflib.SOSA.usedProcedure, term)) temporal_entity = rdflib.BNode() graph.add((uri, rdflib.TIME.hasTime, temporal_entity)) graph.add((temporal_entity, a, rdflib.TIME.Instant)) @@ -3119,18 +3101,17 @@ def add_threat_status_value( # Combine conservationJurisdiction and threatStatus value = f"{row['conservationJurisdiction']}/{row['threatStatus']}" - # Retrieve Vocab or Create on the Fly - vocab = vocabs.threat_status.THREAT_STATUS.get( - graph=graph, - value=value, - source=dataset, - ) + # Retrieve vocab for field + vocab = self.fields["threatStatus"].get_vocab() + + # Retrieve term or Create on the Fly + term = vocab(graph=graph, source=dataset).get(value) # Threat Status Value graph.add((uri, a, utils.namespaces.TERN.IRI)) graph.add((uri, a, utils.namespaces.TERN.Value)) graph.add((uri, rdflib.RDFS.label, rdflib.Literal(f"Conservation status = {row['threatStatus']}"))) - graph.add((uri, rdflib.RDF.value, vocab)) + graph.add((uri, rdflib.RDF.value, term)) def add_conservation_jurisdiction_attribute( self, @@ -3178,8 +3159,11 @@ def add_conservation_jurisdiction_value( if not row["conservationJurisdiction"]: return - # Retrieve Vocab or Create on the Fly - vocab = vocabs.conservation_jurisdiction.CONSERVATION_JURISDICTION.get(row["conservationJurisdiction"]) + # Retrieve vocab for field + vocab = self.fields["conservationJurisdiction"].get_vocab() + + # Retrieve term or Create on the Fly + term = vocab(graph=graph).get(row["conservationJurisdiction"]) # Construct Label label = f"Conservation Jurisdiction = {row['conservationJurisdiction']}" @@ -3188,7 +3172,7 @@ def add_conservation_jurisdiction_value( graph.add((uri, a, utils.namespaces.TERN.IRI)) graph.add((uri, a, utils.namespaces.TERN.Value)) graph.add((uri, rdflib.RDFS.label, rdflib.Literal(label))) - graph.add((uri, rdflib.RDF.value, vocab)) + graph.add((uri, rdflib.RDF.value, term)) # Helper Functions diff --git a/abis_mapping/templates/survey_metadata/mapping.py b/abis_mapping/templates/survey_metadata/mapping.py index fc273c1e..7dd35cb0 100644 --- a/abis_mapping/templates/survey_metadata/mapping.py +++ b/abis_mapping/templates/survey_metadata/mapping.py @@ -13,7 +13,6 @@ from abis_mapping import plugins from abis_mapping import types from abis_mapping import utils -from abis_mapping import vocabs # Typing from typing import Optional, Iterator, Any @@ -805,13 +804,12 @@ def add_survey_type_value( # Add label graph.add((uri, rdflib.RDFS.label, rdflib.Literal("surveyType"))) + # Retrieve vocab for field + vocab = self.fields["surveyType"].get_vocab() + # Add value - vocab = vocabs.survey_type.SURVEY_TYPE.get( - graph=graph, - value=survey_type, - source=dataset, - ) - graph.add((uri, rdflib.RDF.value, vocab)) + term = vocab(graph=graph, source=dataset).get(survey_type) + graph.add((uri, rdflib.RDF.value, term)) def add_sampling_effort_attribute( self, @@ -881,14 +879,13 @@ def add_sampling_effort_value( # Add label graph.add((uri, rdflib.RDFS.label, rdflib.Literal("samplingEffort"))) + # Retrieve vocab for field + vocab = self.fields["samplingEffortUnit"].get_vocab() + # Add value - unit_vocab = vocabs.sampling_effort_unit.SAMPLING_EFFORT_UNIT.get( - graph=graph, - value=effort_unit, - source=dataset, - ) + term = vocab(graph=graph, source=dataset).get(effort_unit) graph.add((uri, rdflib.RDF.value, rdflib.Literal(effort_value))) - graph.add((uri, utils.namespaces.TERN.unit, unit_vocab)) + graph.add((uri, utils.namespaces.TERN.unit, term)) def add_target_habitat_attribute( self, @@ -942,13 +939,12 @@ def add_target_habitat_value( # Add label graph.add((uri, rdflib.RDFS.label, rdflib.Literal("targetHabitatScope"))) + # Retrieve vocab for field + vocab = self.fields["targetHabitatScope"].get_vocab() + # Add value - vocab = vocabs.target_habitat_scope.TARGET_HABITAT_SCOPE.get( - graph=graph, - value=raw_value, - source=dataset, - ) - graph.add((uri, rdflib.RDF.value, vocab)) + term = vocab(graph=graph, source=dataset).get(raw_value) + graph.add((uri, rdflib.RDF.value, term)) def add_target_taxonomic_attribute( self, @@ -1003,13 +999,12 @@ def add_target_taxonomic_value( # Add label graph.add((uri, rdflib.RDFS.label, rdflib.Literal("targetTaxonomicScope"))) + # Retrieve vocab for field + vocab = self.fields["targetTaxonomicScope"].get_vocab() + # Add value - vocab = vocabs.target_taxonomic_scope.TARGET_TAXONOMIC_SCOPE.get( - graph=graph, - value=raw_value, - source=dataset, - ) - graph.add((uri, rdflib.RDF.value, vocab)) + term = vocab(graph=graph, source=dataset).get(raw_value) + graph.add((uri, rdflib.RDF.value, term)) base.mapper.ABISMapper.register_mapper(SurveyMetadataMapper) diff --git a/abis_mapping/templates/survey_occurrence_data/mapping.py b/abis_mapping/templates/survey_occurrence_data/mapping.py index 553c5434..4edc1609 100644 --- a/abis_mapping/templates/survey_occurrence_data/mapping.py +++ b/abis_mapping/templates/survey_occurrence_data/mapping.py @@ -1090,12 +1090,11 @@ def add_observation_scientific_name( # that this row has a specimen, otherwise it is Field Sample foi = sample_specimen if has_specimen(row) else sample_field + # Retrieve vocab for field + vocab = self.fields["identificationMethod"].get_vocab() + # Retrieve Vocab or Create on the Fly - vocab = vocabs.identification_method.IDENTIFICATION_METHOD.get( - graph=graph, - value=row["identificationMethod"], - source=dataset, - ) + term = vocab(graph=graph, source=dataset).get(row["identificationMethod"]) # Add to Graph graph.add((uri, a, utils.namespaces.TERN.Observation)) @@ -1109,7 +1108,7 @@ def add_observation_scientific_name( graph.add((uri, rdflib.TIME.hasTime, temporal_entity)) graph.add((temporal_entity, a, rdflib.TIME.Instant)) graph.add((temporal_entity, date_identified.rdf_in_xsd, date_identified.to_rdf_literal())) - graph.add((uri, rdflib.SOSA.usedProcedure, vocab)) + graph.add((uri, rdflib.SOSA.usedProcedure, term)) # Check for identifiedBy if row["identifiedBy"]: @@ -1173,12 +1172,11 @@ def add_observation_verbatim_id( # that this row has a specimen, otherwise it is Field Sample foi = sample_specimen if has_specimen(row) else sample_field - # Retrieve Vocab or Create on the Fly - vocab = vocabs.identification_method.IDENTIFICATION_METHOD.get( - graph=graph, - value=row["identificationMethod"], - source=dataset, - ) + # Retrieve vocab for field + vocab = self.fields["identificationMethod"].get_vocab() + + # Retrieve term or Create on the Fly + term = vocab(graph=graph, source=dataset).get(row["identificationMethod"]) # Add to Graph graph.add((uri, a, utils.namespaces.TERN.Observation)) @@ -1192,7 +1190,7 @@ def add_observation_verbatim_id( graph.add((uri, rdflib.TIME.hasTime, temporal_entity)) graph.add((temporal_entity, a, rdflib.TIME.Instant)) graph.add((temporal_entity, date_identified.rdf_in_xsd, date_identified.to_rdf_literal())) - graph.add((uri, rdflib.SOSA.usedProcedure, vocab)) + graph.add((uri, rdflib.SOSA.usedProcedure, term)) # Check for identifiedBy if row["identifiedBy"]: @@ -1339,12 +1337,11 @@ def add_sampling_field( # Add survey graph.add((uri, rdflib.SDO.isPartOf, DEFAULT_SURVEY)) # TODO -> Cross reference - # Retrieve Vocab or Create on the Fly - vocab = vocabs.sampling_protocol.SAMPLING_PROTOCOL.get( - graph=graph, - value=row["samplingProtocol"], - source=dataset, - ) + # Retrieve vocab for field + vocab = self.fields["samplingProtocol"].get_vocab() + + # Retrieve term or Create on the Fly + term = vocab(graph=graph, source=dataset).get(row["samplingProtocol"]) # Add to Graph graph.add((uri, a, utils.namespaces.TERN.Sampling)) @@ -1354,7 +1351,7 @@ def add_sampling_field( graph.add((uri, rdflib.TIME.hasTime, temporal_entity)) graph.add((temporal_entity, a, rdflib.TIME.Instant)) graph.add((temporal_entity, event_date.rdf_in_xsd, event_date.to_rdf_literal())) - graph.add((uri, rdflib.SOSA.usedProcedure, vocab)) + graph.add((uri, rdflib.SOSA.usedProcedure, term)) # Add geometry geometry_node = rdflib.BNode() @@ -1504,18 +1501,17 @@ def add_id_qualifier_value( if not row["identificationQualifier"]: return - # Retrieve Vocab or Create on the Fly - vocab = vocabs.identification_qualifier.IDENTIFICATION_QUALIFIER.get( - graph=graph, - value=row["identificationQualifier"], - source=dataset, - ) + # Retrieve vocab for field + vocab = self.fields["identificationQualifier"].get_vocab() + + # Retrieve term or Create on the Fly + term = vocab(graph=graph, source=dataset).get(row["identificationQualifier"]) # Identification Qualifier Value graph.add((uri, a, utils.namespaces.TERN.IRI)) graph.add((uri, a, utils.namespaces.TERN.Value)) graph.add((uri, rdflib.RDFS.label, rdflib.Literal("identificationQualifier"))) - graph.add((uri, rdflib.RDF.value, vocab)) + graph.add((uri, rdflib.RDF.value, term)) def add_id_remarks_attribute( self, @@ -1856,12 +1852,11 @@ def add_sample_field( provided else None. graph (rdflib.Graph): Graph to add to """ - # Retrieve Vocab or Create on the Fly - vocab = vocabs.kingdom.KINGDOM_OCCURRENCE.get( - graph=graph, - value=row["kingdom"], - source=dataset, - ) + # Retrieve vocab for field (multiple exists for kingdom) + vocab = self.fields["kingdom"].get_vocab("KINGDOM_OCCURRENCE") + + # Retrieve term or Create on the Fly + term = vocab(graph=graph, source=dataset).get(row["kingdom"]) # Add to Graph graph.add((uri, a, utils.namespaces.TERN.FeatureOfInterest)) @@ -1869,7 +1864,7 @@ def add_sample_field( graph.add((uri, rdflib.VOID.inDataset, dataset)) graph.add((uri, rdflib.RDFS.comment, rdflib.Literal("field-sample"))) graph.add((uri, rdflib.SOSA.isResultOf, sampling_field)) - graph.add((uri, utils.namespaces.TERN.featureType, vocab)) + graph.add((uri, utils.namespaces.TERN.featureType, term)) if site is not None: graph.add((uri, rdflib.SOSA.isSampleOf, site)) @@ -1971,12 +1966,11 @@ def add_sample_specimen( if not has_specimen(row): return - # Retrieve Vocab or Create on the Fly - vocab = vocabs.kingdom.KINGDOM_SPECIMEN.get( - graph=graph, - value=row["kingdom"], - source=dataset, - ) + # Retrieve vocab for field (multiple exists for kingdom) + vocab = self.fields["kingdom"].get_vocab("KINGDOM_SPECIMEN") + + # Retrieve term or Create on the Fly + term = vocab(graph=graph, source=dataset).get(row["kingdom"]) # Add to Graph graph.add((uri, a, utils.namespaces.TERN.FeatureOfInterest)) @@ -1985,7 +1979,7 @@ def add_sample_specimen( graph.add((uri, rdflib.RDFS.comment, rdflib.Literal("specimen-sample"))) graph.add((uri, rdflib.SOSA.isResultOf, sampling_specimen)) graph.add((uri, rdflib.SOSA.isSampleOf, sample_field)) - graph.add((uri, utils.namespaces.TERN.featureType, vocab)) + graph.add((uri, utils.namespaces.TERN.featureType, term)) # Check for catalogNumber if row["catalogNumber"]: @@ -2099,18 +2093,17 @@ def add_kingdom_value( dataset (rdflib.URIRef): Dataset this belongs to graph (rdflib.Graph): Graph to add to """ - # Retrieve Vocab or Create on the Fly - vocab = vocabs.kingdom.KINGDOM.get( - graph=graph, - value=row["kingdom"], - source=dataset, - ) + # Retrieve vocab for field (multiple exist for kingdom) + vocab = self.fields["kingdom"].get_vocab("KINGDOM") + + # Retrieve term or Create on the Fly + term = vocab(graph=graph, source=dataset).get(row["kingdom"]) # Kingdom Value graph.add((uri, a, utils.namespaces.TERN.IRI)) graph.add((uri, a, utils.namespaces.TERN.Value)) graph.add((uri, rdflib.RDFS.label, rdflib.Literal(f"kingdom = {row['kingdom']}"))) - graph.add((uri, rdflib.RDF.value, vocab)) + graph.add((uri, rdflib.RDF.value, term)) def add_taxon_rank_attribute( self, @@ -2160,18 +2153,17 @@ def add_taxon_rank_value( if not row["taxonRank"]: return - # Retrieve Vocab or Create on the Fly - vocab = vocabs.taxon_rank.TAXON_RANK.get( - graph=graph, - value=row["taxonRank"], - source=dataset, - ) + # Retrieve vocab for field + vocab = self.fields["taxonRank"].get_vocab() + + # Retrieve term or Create on the Fly + term = vocab(graph=graph, source=dataset).get(row["taxonRank"]) # Taxon Rank Value graph.add((uri, a, utils.namespaces.TERN.IRI)) graph.add((uri, a, utils.namespaces.TERN.Value)) graph.add((uri, rdflib.RDFS.label, rdflib.Literal(f"taxon rank = {row['taxonRank']}"))) - graph.add((uri, rdflib.RDF.value, vocab)) + graph.add((uri, rdflib.RDF.value, term)) def add_individual_count_observation( self, @@ -2425,18 +2417,17 @@ def add_basis_value( if not row["basisOfRecord"]: return - # Retrieve Vocab or Create on the Fly - vocab = vocabs.basis_of_record.BASIS_OF_RECORD.get( - graph=graph, - value=row["basisOfRecord"], - source=dataset, - ) + # Retrieve vocab for field + vocab = self.fields["basisOfRecord"].get_vocab() + + # Retrieve term or Create on the Fly + term = vocab(graph=graph, source=dataset).get(row["basisOfRecord"]) # Basis of Record Value graph.add((uri, a, utils.namespaces.TERN.IRI)) graph.add((uri, a, utils.namespaces.TERN.Value)) graph.add((uri, rdflib.RDFS.label, rdflib.Literal("basisOfRecord"))) - graph.add((uri, rdflib.RDF.value, vocab)) + graph.add((uri, rdflib.RDF.value, term)) def add_owner_institution_provider( self, @@ -2546,18 +2537,17 @@ def add_occurrence_status_value( if not row["occurrenceStatus"]: return - # Retrieve Vocab or Create on the Fly - vocab = vocabs.occurrence_status.OCCURRENCE_STATUS.get( - graph=graph, - value=row["occurrenceStatus"], - source=dataset, - ) + # Retrieve vocab for field + vocab = self.fields["occurrenceStatus"].get_vocab() + + # Retrieve term or Create on the Fly + term = vocab(graph=graph, source=dataset).get(row["occurrenceStatus"]) # Occurrence Status Value graph.add((uri, a, utils.namespaces.TERN.IRI)) graph.add((uri, a, utils.namespaces.TERN.Value)) graph.add((uri, rdflib.RDFS.label, rdflib.Literal(f"occurrenceStatus = {row['occurrenceStatus']}"))) - graph.add((uri, rdflib.RDF.value, vocab)) + graph.add((uri, rdflib.RDF.value, term)) def add_preparations_attribute( self, @@ -2607,18 +2597,17 @@ def add_preparations_value( if not row["preparations"]: return - # Retrieve Vocab or Create on the Fly - vocab = vocabs.preparations.PREPARATIONS.get( - graph=graph, - value=row["preparations"], - source=dataset, - ) + # Retrieve vocab for field + vocab = self.fields["preparations"].get_vocab() + + # Retrieve term or Create on the Fly + term = vocab(graph=graph, source=dataset).get(row["preparations"]) # Preparations Value graph.add((uri, a, utils.namespaces.TERN.IRI)) graph.add((uri, a, utils.namespaces.TERN.Value)) graph.add((uri, rdflib.RDFS.label, rdflib.Literal("preparations"))) - graph.add((uri, rdflib.RDF.value, vocab)) + graph.add((uri, rdflib.RDF.value, term)) def add_establishment_means_observation( self, @@ -2692,18 +2681,17 @@ def add_establishment_means_value( if not row["establishmentMeans"]: return - # Retrieve Vocab or Create on the Fly - vocab = vocabs.establishment_means.ESTABLISHMENT_MEANS.get( - graph=graph, - value=row["establishmentMeans"], - source=dataset, - ) + # Retrieve vocab for field + vocab = self.fields["establishmentMeans"].get_vocab() + + # Retrieve term or Create on the Fly + term = vocab(graph=graph, source=dataset).get(row["establishmentMeans"]) # Establishment Means Value graph.add((uri, a, utils.namespaces.TERN.IRI)) graph.add((uri, a, utils.namespaces.TERN.Value)) graph.add((uri, rdflib.RDFS.label, rdflib.Literal("establishmentMeans-value"))) - graph.add((uri, rdflib.RDF.value, vocab)) + graph.add((uri, rdflib.RDF.value, term)) def add_life_stage_observation( self, @@ -2785,18 +2773,17 @@ def add_life_stage_value( if not row["lifeStage"]: return - # Retrieve Vocab or Create on the Fly - vocab = vocabs.life_stage.LIFE_STAGE.get( - graph=graph, - value=row["lifeStage"], - source=dataset, - ) + # Retrieve vocab for field + vocab = self.fields["lifeStage"].get_vocab() + + # Retrieve term or Create on the Fly + term = vocab(graph=graph, source=dataset).get(row["lifeStage"]) # Life Stage Value graph.add((uri, a, utils.namespaces.TERN.IRI)) graph.add((uri, a, utils.namespaces.TERN.Value)) graph.add((uri, rdflib.RDFS.label, rdflib.Literal("lifeStage-value"))) - graph.add((uri, rdflib.RDF.value, vocab)) + graph.add((uri, rdflib.RDF.value, term)) def add_sex_observation( self, @@ -2877,18 +2864,17 @@ def add_sex_value( if not row["sex"]: return - # Retrieve Vocab or Create on the Fly - vocab = vocabs.sex.SEX.get( - graph=graph, - value=row["sex"], - source=dataset, - ) + # Retrieve vocab for field + vocab = self.fields["sex"].get_vocab() + + # Retrieve term or Create on the Fly + term = vocab(graph=graph, source=dataset).get(row["sex"]) # Sex Value graph.add((uri, a, utils.namespaces.TERN.IRI)) graph.add((uri, a, utils.namespaces.TERN.Value)) graph.add((uri, rdflib.RDFS.label, rdflib.Literal("sex-value"))) - graph.add((uri, rdflib.RDF.value, vocab)) + graph.add((uri, rdflib.RDF.value, term)) def add_reproductive_condition_observation( self, @@ -2970,18 +2956,17 @@ def add_reproductive_condition_value( if not row["reproductiveCondition"]: return - # Retrieve Vocab or Create on the Fly - vocab = vocabs.reproductive_condition.REPRODUCTIVE_CONDITION.get( - graph=graph, - value=row["reproductiveCondition"], - source=dataset, - ) + # Retrieve vocab for field + vocab = self.fields["reproductiveCondition"].get_vocab() + + # Retrieve term or Create on the Fly + term = vocab(graph=graph, source=dataset).get(row["reproductiveCondition"]) # Reproductive Condition Value graph.add((uri, a, utils.namespaces.TERN.IRI)) graph.add((uri, a, utils.namespaces.TERN.Value)) graph.add((uri, rdflib.RDFS.label, rdflib.Literal("reproductiveCondition-value"))) - graph.add((uri, rdflib.RDF.value, vocab)) + graph.add((uri, rdflib.RDF.value, term)) def add_accepted_name_usage_observation( self, @@ -3112,12 +3097,11 @@ def add_sampling_sequencing( # but if it does then node will be ommitted from graph. return - # Retrieve Vocab or Create on the Fly - vocab = vocabs.sequencing_method.SEQUENCING_METHOD.get( - graph=graph, - value=row["sequencingMethod"], - source=dataset, - ) + # Retrieve vocab for field + vocab = self.fields["sequencingMethod"].get_vocab() + + # Retrieve term or Create on the Fly + term = vocab(graph=graph, source=dataset).get(row["sequencingMethod"]) # Add to Graph graph.add((uri, a, utils.namespaces.TERN.Sampling)) @@ -3129,7 +3113,7 @@ def add_sampling_sequencing( graph.add((uri, rdflib.TIME.hasTime, temporal_entity)) graph.add((temporal_entity, a, rdflib.TIME.Instant)) graph.add((temporal_entity, event_date.rdf_in_xsd, event_date.to_rdf_literal())) - graph.add((uri, rdflib.SOSA.usedProcedure, vocab)) + graph.add((uri, rdflib.SOSA.usedProcedure, term)) # Add geometry geometry_node = rdflib.BNode() @@ -3267,12 +3251,11 @@ def add_threat_status_observation( or row["eventDate"] ) - # Retrieve Vocab or Create on the Fly - vocab = vocabs.check_protocol.CHECK_PROTOCOL.get( - graph=graph, - value=row["threatStatusCheckProtocol"], - source=dataset, - ) + # Retrieve vocab for field + vocab = self.fields["threatStatusCheckProtocol"].get_vocab() + + # Retrieve term or Create on the Fly + term = vocab(graph=graph, source=dataset).get(row["threatStatusCheckProtocol"]) # Threat Status Observation graph.add((uri, a, utils.namespaces.TERN.Observation)) @@ -3283,7 +3266,7 @@ def add_threat_status_observation( graph.add((uri, rdflib.SOSA.hasSimpleResult, rdflib.Literal(row["threatStatus"]))) graph.add((uri, rdflib.SOSA.observedProperty, CONCEPT_CONSERVATION_STATUS)) graph.add((uri, rdflib.PROV.wasInfluencedBy, jurisdiction_attribute)) - graph.add((uri, rdflib.SOSA.usedProcedure, vocab)) + graph.add((uri, rdflib.SOSA.usedProcedure, term)) temporal_entity = rdflib.BNode() graph.add((uri, rdflib.TIME.hasTime, temporal_entity)) graph.add((temporal_entity, a, rdflib.TIME.Instant)) @@ -3329,18 +3312,17 @@ def add_threat_status_value( # Combine conservationJurisdiction and threatStatus value = f"{row['conservationJurisdiction']}/{row['threatStatus']}" - # Retrieve Vocab or Create on the Fly - vocab = vocabs.threat_status.THREAT_STATUS.get( - graph=graph, - value=value, - source=dataset, - ) + # Retrieve vocab for field + vocab = self.fields["threatStatus"].get_vocab() + + # Retrieve term or Create on the Fly + term = vocab(graph=graph, source=dataset).get(value) # Threat Status Value graph.add((uri, a, utils.namespaces.TERN.IRI)) graph.add((uri, a, utils.namespaces.TERN.Value)) graph.add((uri, rdflib.RDFS.label, rdflib.Literal(f"Conservation status = {row['threatStatus']}"))) - graph.add((uri, rdflib.RDF.value, vocab)) + graph.add((uri, rdflib.RDF.value, term)) def add_conservation_jurisdiction_attribute( self, @@ -3388,8 +3370,11 @@ def add_conservation_jurisdiction_value( if not row["conservationJurisdiction"]: return - # Retrieve Vocab or Create on the Fly - vocab = vocabs.conservation_jurisdiction.CONSERVATION_JURISDICTION.get(row["conservationJurisdiction"]) + # Retrieve vocab for field + vocab = self.fields["conservationJurisdiction"].get_vocab() + + # Retrieve term + term = vocab(graph=graph).get(row["conservationJurisdiction"]) # Construct Label label = f"Conservation Jurisdiction = {row['conservationJurisdiction']}" @@ -3398,7 +3383,7 @@ def add_conservation_jurisdiction_value( graph.add((uri, a, utils.namespaces.TERN.IRI)) graph.add((uri, a, utils.namespaces.TERN.Value)) graph.add((uri, rdflib.RDFS.label, rdflib.Literal(label))) - graph.add((uri, rdflib.RDF.value, vocab)) + graph.add((uri, rdflib.RDF.value, term)) def add_organism_quantity_observation( self, @@ -3483,19 +3468,18 @@ def add_organism_quantity_value( if not (organism_qty and organism_qty_type): return - # Get vocab or create on the fly - vocab = vocabs.organism_quantity_type.ORGANISM_QUANTITY_TYPE.get( - graph=graph, - value=organism_qty_type, - source=dataset, - ) + # Retrieve vocab for field + vocab = self.fields["organismQuantityType"].get_vocab() + + # Get term or create on the fly + term = vocab(graph=graph, source=dataset).get(organism_qty_type) # Add to graph graph.add((organism_qty_observation, rdflib.SOSA.hasResult, uri)) graph.add((uri, a, utils.namespaces.TERN.Value)) graph.add((uri, a, utils.namespaces.TERN.Float)) graph.add((uri, rdflib.RDFS.label, rdflib.Literal("organism-count"))) - graph.add((uri, utils.namespaces.TERN.unit, vocab)) + graph.add((uri, utils.namespaces.TERN.unit, term)) graph.add((uri, rdflib.RDF.value, rdflib.Literal(organism_qty, datatype=rdflib.XSD.float))) def add_site( diff --git a/abis_mapping/templates/survey_occurrence_data/schema.json b/abis_mapping/templates/survey_occurrence_data/schema.json index c6964adc..371ba4d0 100644 --- a/abis_mapping/templates/survey_occurrence_data/schema.json +++ b/abis_mapping/templates/survey_occurrence_data/schema.json @@ -600,7 +600,10 @@ "WESTERN AUSTRALIA", "Western Australia" ] - } + }, + "vocabularies": [ + "CONSERVATION_JURISDICTION" + ] }, { "name": "threatStatusCheckProtocol", diff --git a/abis_mapping/templates/survey_site_data/mapping.py b/abis_mapping/templates/survey_site_data/mapping.py index 0c20ec1e..7e6f2aa3 100644 --- a/abis_mapping/templates/survey_site_data/mapping.py +++ b/abis_mapping/templates/survey_site_data/mapping.py @@ -501,29 +501,31 @@ def add_site( (relationship_to_related_site := row["relationshipToRelatedSite"]) and (related_site := row["relatedSiteID"]) ): - # Retrieve vocab - relationship_to_related_site_vocab = vocabs.relationship_to_related_site.RELATIONSHIP_TO_RELATED_SITE.get( - value=relationship_to_related_site, - ) + # Retrieve vocab for field + relationship_to_related_site_vocab = self.fields["relationshipToRelatedSite"].get_vocab() + + # Retrieve term + relationship_to_related_site_term = relationship_to_related_site_vocab( + graph=graph + ).get(relationship_to_related_site) # Assign triple based on related site string if (related_site_literal := utils.rdf.uri_or_string_literal(related_site)).datatype == rdflib.XSD.string: - graph.add((uri, relationship_to_related_site_vocab, related_site_literal)) + graph.add((uri, relationship_to_related_site_term, related_site_literal)) else: - graph.add((uri, relationship_to_related_site_vocab, rdflib.URIRef(related_site))) + graph.add((uri, relationship_to_related_site_term, rdflib.URIRef(related_site))) # Add site tern featuretype graph.add((uri, utils.namespaces.TERN.featureType, vocabs.site_type.SITE.iri)) - # Retrieve vocab or create on the fly - site_type_vocab = vocabs.site_type.SITE_TYPE.get( - graph=graph, - value=site_type, - source=dataset, - ) + # Retrieve vocab for field + site_type_vocab = self.fields["siteType"].get_vocab() + + # Retrieve term or create on the fly + site_type_term = site_type_vocab(graph=graph, source=dataset).get(site_type) # Add to site type graph - graph.add((uri, rdflib.DCTERMS.type, site_type_vocab)) + graph.add((uri, rdflib.DCTERMS.type, site_type_term)) # Add site name if available if site_name: @@ -643,13 +645,12 @@ def add_habitat_value( # Add label graph.add((uri, rdflib.RDFS.label, rdflib.Literal("Site habitat"))) - # Add flexible vocab - vocab = vocabs.target_habitat_scope.TARGET_HABITAT_SCOPE.get( - graph=graph, - value=raw, - source=dataset, - ) - graph.add((uri, rdflib.RDF.value, vocab)) + # Retrieve vocab for field + vocab = self.fields["habitat"].get_vocab() + + # Add flexible vocab term + term = vocab(graph=graph, source=dataset).get(raw) + graph.add((uri, rdflib.RDF.value, term)) def add_data_generalizations_attribute( self, diff --git a/abis_mapping/templates/survey_site_data/validators/shaping.py b/abis_mapping/templates/survey_site_data/validators/shaping.py index bd512a0f..1416534f 100644 --- a/abis_mapping/templates/survey_site_data/validators/shaping.py +++ b/abis_mapping/templates/survey_site_data/validators/shaping.py @@ -193,7 +193,7 @@ def add_site_shape(g: rdflib.Graph, dataset_shape: rdflib.term.Node) -> rdflib.t dcterms_type_opts = rdflib.BNode() g.add((dcterms_type_prop, SH.path, rdflib.DCTERMS.type)) site_type_vocab_values: list[rdflib.term.Node] = [ - v.iri for v in vocabs.site_type.SITE_TYPE.terms if v is not None + v.iri for v in vocabs.site_type.SiteType.terms if v is not None ] rdflib.collection.Collection(g, terms_list, site_type_vocab_values) g.add((terms_list_shape, SH["in"], terms_list)) diff --git a/abis_mapping/types/schema.py b/abis_mapping/types/schema.py index 72ed5868..41232799 100644 --- a/abis_mapping/types/schema.py +++ b/abis_mapping/types/schema.py @@ -1,10 +1,14 @@ """Describes the models to define a schema.""" + # Third-party import pydantic +# Local +from abis_mapping import utils + # Typing -from typing import Any +from typing import Any, Type class Constraints(pydantic.BaseModel): @@ -29,6 +33,51 @@ class Field(pydantic.BaseModel): # Allow extra fields to be captured mainly to catch errors in json model_config = pydantic.ConfigDict(extra="allow") + @pydantic.field_validator("vocabularies", mode="after") + @classmethod + def check_vocabularies(cls, values: list[str]) -> list[str]: + """Custom validation of the vocabularies field. + + Args: + values (list[str]): The provided vocabularies initial value. + + Returns: + list[str]: The validated vocabulary ids. + + Raises: + KeyError: A provided vocabulary id does not exist. + """ + # Check each provided name to see if it exists in the registry + for name in values: + if utils.vocabs.get_vocab(name) is None: + raise ValueError(f"Vocabulary id {name} does not exist.") + + # Return list + return values + + def get_vocab(self, name: str | None = None) -> Type[utils.vocabs.Vocabulary]: + """Retrieves the vocab for the field. + + Args: + name (str | None, optional): The name of the vocab to retrieve. Will + return first vocab if not provided. + + Returns: + Type[utils.vocabs.Vocabulary]: Returns vocabulary for the field. + + Raises: + ValueError: If name is not within the vocabularies field. + """ + # Check if name exists + if name is not None and name not in self.vocabularies: + raise ValueError(f"Vocabulary '{name}' is not defined for field {self.name}.") + + # Retrieve + if name is not None: + return utils.vocabs.get_vocab(name) + + return utils.vocabs.get_vocab(self.vocabularies[0]) + class Schema(pydantic.BaseModel): """Model for overall schema object of a schema definition.""" diff --git a/abis_mapping/types/spatial.py b/abis_mapping/types/spatial.py index 9a06c569..56b5ad50 100644 --- a/abis_mapping/types/spatial.py +++ b/abis_mapping/types/spatial.py @@ -13,7 +13,7 @@ # Local from abis_mapping import settings from abis_mapping.utils import namespaces -from abis_mapping import vocabs +from abis_mapping import utils # Typing from typing import NamedTuple @@ -88,7 +88,12 @@ def original_datum_uri(self) -> rdflib.URIRef | None: Returns: rdflib.URIRef: Uri corresponding to original datum if known, else None. """ - return vocabs.geodetic_datum.GEODETIC_DATUM.get(self.original_datum_name) + # Retrieve vocab class + if (vocab := utils.vocabs.get_vocab("GEODETIC_DATUM")) is not None: + # Init with dummy graph and return corresponding URI + return vocab(graph=rdflib.Graph()).get(self.original_datum_name) + else: + return None @property def _transformed_geometry(self) -> shapely.Geometry: @@ -109,7 +114,13 @@ def transformer_datum_uri(self) -> rdflib.URIRef | None: Returns: rdflib.URIRef: Uri corresponding to transformer datum if known, else None. """ - return vocabs.geodetic_datum.GEODETIC_DATUM.get(settings.DEFAULT_TARGET_CRS) + # Retrieve vocab class + if (vocab := utils.vocabs.get_vocab("GEODETIC_DATUM")) is not None: + # Init with dummy graph and return corresponding uri + return vocab(graph=rdflib.Graph()).get(settings.DEFAULT_TARGET_CRS) + + # If vocab doesn't exist + return None @classmethod def from_geosparql_wkt_literal(cls, literal: rdflib.Literal | str) -> "Geometry": diff --git a/abis_mapping/utils/vocabs.py b/abis_mapping/utils/vocabs.py index ce60920d..4aae8dbc 100644 --- a/abis_mapping/utils/vocabs.py +++ b/abis_mapping/utils/vocabs.py @@ -11,7 +11,7 @@ from abis_mapping.utils import strings # Typing -from typing import Optional, Iterable, final, Final +from typing import Optional, Iterable, final, Final, Type # Constants @@ -79,40 +79,63 @@ def alternative_labels(self) -> Iterable[str]: class Vocabulary(abc.ABC): - """Base Vocabulary class.""" + """Base Vocabulary class. + + Attributes: + id_registry (dict[str, Vocabulary]): Dictionary to hold all vocabs for + mapping by their id. + vocab_id (str): ID to assign vocabulary. + terms (Iterable[Term]): Terms to add to the vocabulary. + publish (bool, optional): Whether to publish vocabulary + in documentation. Defaults to True. + """ # Dictionary to hold all vocabs for mapping by their id. - id_registry: dict[str, "Vocabulary"] = {} + id_registry: dict[str, Type["Vocabulary"]] = {} + + # Attributes assigned per vocabulary + vocab_id: str + terms: Iterable[Term] + publish: bool = True def __init__( self, - vocab_id: str, - terms: Iterable[Term], - publish: bool = True, + graph: rdflib.Graph, + source: Optional[rdflib.URIRef] = None, ): """Vocabulary constructor. Args: - vocab_id (str): ID to assign vocabulary. - publish (bool, optional): Whether to publish vocabulary - in documentation. Defaults to True. + graph (rdflib.Graph): Graph to reference + within vocabulary + source (Optional[rdflib.URIRef]): Optional source URI to attribute + a new vocabulary term to. """ - # Assign object internal variables - self.vocab_id: Final[str] = vocab_id - self.publish: Final[bool] = publish - - # Set Instance Variables - self.terms: Final[tuple[Term, ...]] = tuple(terms) + # Assign instance variables + self.graph = graph + self.source: Optional[rdflib.URIRef] = source # Generate Dictionary Mapping from Terms self._mapping: dict[str | None, rdflib.URIRef | None] = {} for term in self.terms: self._mapping.update(**term.to_mapping()) + @abc.abstractmethod + def get(self, value: str | None) -> rdflib.URIRef: + """Retrieve na IRI from the vocabulary. + + Args: + value (Optional[str]): Possible raw string value to search for in + vocabulary. + + Returns: + rdflib.URIRef: Matching vocabulary IRI. + """ + @final @classmethod def register( cls, - vocab: "Vocabulary", + vocab: Type["Vocabulary"], ) -> None: """Register a Vocabulary within the centralise vocabulary id registry. @@ -131,7 +154,7 @@ def register( class RestrictedVocabulary(Vocabulary): """Restricted Vocabulary""" - def get(self, value: str) -> rdflib.URIRef: + def get(self, value: str | None) -> rdflib.URIRef: """Retrieves an IRI from the Vocabulary. Args: @@ -144,6 +167,11 @@ def get(self, value: str) -> rdflib.URIRef: VocabularyError: Raised if supplied value does not match an existing vocabulary term. """ + # Check for Value + if not value: + # Raise Error + raise VocabularyError("Value not supplied for vocabulary with no default") + # Sanitise Value sanitised_value = strings.sanitise(value) @@ -157,46 +185,45 @@ def get(self, value: str) -> rdflib.URIRef: class FlexibleVocabulary(Vocabulary): - """Flexible Vocabulary""" + """Flexible Vocabulary. + + Attributes: + definition (rdflib.Literal): Definition to use when creating a new + vocabulary term 'on the fly'. + base_ns (rdflib.URIRef): Base IRI namespace to use when creating a new + vocabulary term 'on the fly'. + scheme (rdflib.URIRef): Scheme IRI to use when creating a new + vocabulary term 'on the fly'. + broader (Optional[rdflib.URIRef]): Optional broader IRI to use when + creating a new vocabulary term 'on the fly'. + default (Optional[Term]): Optional default term to fall back on if + a value is not supplied. + """ + # Declare attributes applicable to a flexible vocab + definition: rdflib.Literal + base: rdflib.URIRef + scheme: rdflib.URIRef + broader: Optional[rdflib.URIRef] + default: Optional[Term] def __init__( self, - vocab_id: str, - definition: rdflib.Literal, - base: rdflib.URIRef, - scheme: rdflib.URIRef, - broader: Optional[rdflib.URIRef], - default: Optional[Term], - terms: Iterable[Term], - publish: bool = True, - ) -> None: - """Initialises a Flexible Vocabulary. + graph: rdflib.Graph, + source: Optional[rdflib.URIRef] = None, + ): + """Flexible Vocabulary constructor. Args: - vocab_id (str): ID to assign vocabulary. - definition (rdflib.Literal): Definition to use when creating a new - vocabulary term 'on the fly'. - base (rdflib.URIRef): Base IRI namespace to use when creating a new - vocabulary term 'on the fly'. - scheme (rdflib.URIRef): Scheme IRI to use when creating a new - vocabulary term 'on the fly'. - broader (Optional[rdflib.URIRef]): Optional broader IRI to use when - creating a new vocabulary term 'on the fly'. - default (Optional[Term]): Optional default term to fall back on if - a value is not supplied. - terms (Iterable[Term]): Terms for the vocabulary. - publish (bool, optional): Whether to publish vocabulary - within documentation. Defaults to True. + graph (rdflib.Graph): Graph to reference + within vocabulary + source (Optional[rdflib.URIRef]): Optional source URI to attribute + a new vocabulary term to. """ # Call parent constructor - super().__init__(vocab_id, terms, publish) + super().__init__(graph=graph, source=source) # Set Instance Variables - self.definition = definition - self.base = rdflib.Namespace(base) # Cast to a Namespace - self.scheme = scheme - self.broader = broader - self.default = default + self.base_ns = rdflib.Namespace(self.base) # Cast to a Namespace # Add Default mapping if Applicable if self.default: @@ -204,9 +231,7 @@ def __init__( def get( self, - graph: rdflib.Graph, value: Optional[str], - source: Optional[rdflib.URIRef] = None, ) -> rdflib.URIRef: """Retrieves an IRI from the Vocabulary. @@ -218,11 +243,8 @@ def get( 'on the fly'. Args: - graph (rdflib.Graph): Graph to create a new vocabulary term in. value (Optional[str]): Possible raw string value to search for in vocabulary. - source (Optional[rdflib.URIRef]): Optional source URI to attribute - a new vocabulary term to. Returns: rdflib.URIRef: Default, matching or created vocabulary IRI. @@ -245,26 +267,26 @@ def get( raise VocabularyError("Value not supplied for vocabulary with no default") # Create our Own Concept IRI - iri = rdf.uri(value, namespace=self.base) + iri = rdf.uri(value, namespace=self.base_ns) # Add to Graph - graph.add((iri, a, rdflib.SKOS.Concept)) - graph.add((iri, rdflib.SKOS.definition, self.definition)) - graph.add((iri, rdflib.SKOS.inScheme, self.scheme)) - graph.add((iri, rdflib.SKOS.prefLabel, rdflib.Literal(value))) + self.graph.add((iri, a, rdflib.SKOS.Concept)) + self.graph.add((iri, rdflib.SKOS.definition, self.definition)) + self.graph.add((iri, rdflib.SKOS.inScheme, self.scheme)) + self.graph.add((iri, rdflib.SKOS.prefLabel, rdflib.Literal(value))) # Check for Broader IRI if self.broader: # Add Broader - graph.add((iri, rdflib.SKOS.broader, self.broader)) + self.graph.add((iri, rdflib.SKOS.broader, self.broader)) # Check for Source URI - if source: + if self.source: # Construct Source URI Literal - uri = rdflib.Literal(source, datatype=rdflib.XSD.anyURI) + uri = rdflib.Literal(self.source, datatype=rdflib.XSD.anyURI) # Add Source - graph.add((iri, rdflib.DCTERMS.source, uri)) + self.graph.add((iri, rdflib.DCTERMS.source, uri)) # Return return iri @@ -274,13 +296,21 @@ class VocabularyError(Exception): """Error Raised in Vocabulary Handling""" -def get_vocab(key: str) -> Vocabulary | None: +def get_vocab(key: str) -> Type[Vocabulary]: """Retrieves vocab object for given key. Args: key (str): Key to retrieve vocab for. Returns: - Vocabulary | None: Corresponding vocabulary for given key or None. + Type[Vocabulary] | None: Corresponding vocabulary class + for given key or None. + + Raises: + ValueError: If supplied key doesn't exist within the registry. """ - return Vocabulary.id_registry.get(key) + try: + return Vocabulary.id_registry[key] + except KeyError: + # Transform to ValueError, to assist with validation libraries. + raise ValueError(f"Key {key} not found in registry.") diff --git a/abis_mapping/vocabs/basis_of_record.py b/abis_mapping/vocabs/basis_of_record.py index c4709c0b..d930e858 100644 --- a/abis_mapping/vocabs/basis_of_record.py +++ b/abis_mapping/vocabs/basis_of_record.py @@ -53,14 +53,14 @@ # Vocabulary -BASIS_OF_RECORD = utils.vocabs.FlexibleVocabulary( - vocab_id="BASIS_OF_RECORD", - definition=rdflib.Literal("A type of basisOfRecord."), - base=utils.rdf.uri("bdr-cv/attribute/basisOfRecord/"), - scheme=rdflib.URIRef("http://linked.data.gov.au/def/tern-cv/dd085299-ae86-4371-ae15-61dfa432f924"), - broader=utils.rdf.uri("bdr-cv/attribute/basisOfRecord", utils.namespaces.EXAMPLE), # TODO -> Need real URI - default=None, # No default, omitted if not provided - terms=( +class BasisOfRecord(utils.vocabs.FlexibleVocabulary): + vocab_id = "BASIS_OF_RECORD" + definition = rdflib.Literal("A type of basisOfRecord.") + base = utils.rdf.uri("bdr-cv/attribute/basisOfRecord/") + scheme = rdflib.URIRef("http://linked.data.gov.au/def/tern-cv/dd085299-ae86-4371-ae15-61dfa432f924") + broader = utils.rdf.uri("bdr-cv/attribute/basisOfRecord", utils.namespaces.EXAMPLE) # TODO -> Need real URI + default = None # No default, omitted if not provided + terms = ( HUMAN_OBSERVATION, OCCURRENCE, PRESERVED_SPECIMEN, @@ -69,7 +69,7 @@ MACHINE_OBSERVATION, MATERIAL_SAMPLE, ) -) + # Register vocabulary -utils.vocabs.Vocabulary.register(BASIS_OF_RECORD) +utils.vocabs.Vocabulary.register(BasisOfRecord) diff --git a/abis_mapping/vocabs/check_protocol.py b/abis_mapping/vocabs/check_protocol.py index ad1d8811..79261c06 100644 --- a/abis_mapping/vocabs/check_protocol.py +++ b/abis_mapping/vocabs/check_protocol.py @@ -15,17 +15,18 @@ description="Unspecified", ) + # Vocabulary -CHECK_PROTOCOL = utils.vocabs.FlexibleVocabulary( - vocab_id="CHECK_PROTOCOL", - definition=rdflib.Literal("A type of threatStatusCheckProtocol."), - base=utils.rdf.uri("bdr-cv/methods/threatStatusCheckProtocol/"), - scheme=rdflib.URIRef("http://linked.data.gov.au/def/tern-cv/2fd44aca-168f-4177-b393-0688ce38102c"), - broader=utils.rdf.uri("bdr-cv/methods/threatStatusCheckProtocol", utils.namespaces.EXAMPLE), # TODO -> Need real URI # noqa: E501 - default=UNSPECIFIED, - terms=(UNSPECIFIED, ), - publish=False, -) +class CheckProtocol(utils.vocabs.FlexibleVocabulary): + vocab_id = "CHECK_PROTOCOL" + definition = rdflib.Literal("A type of threatStatusCheckProtocol.") + base = utils.rdf.uri("bdr-cv/methods/threatStatusCheckProtocol/") + scheme = rdflib.URIRef("http://linked.data.gov.au/def/tern-cv/2fd44aca-168f-4177-b393-0688ce38102c") + broader = utils.rdf.uri("bdr-cv/methods/threatStatusCheckProtocol", utils.namespaces.EXAMPLE) # TODO -> Need real URI # noqa: E501 + default = UNSPECIFIED + terms = (UNSPECIFIED, ) + publish = False + # Register -utils.vocabs.Vocabulary.register(CHECK_PROTOCOL) +utils.vocabs.Vocabulary.register(CheckProtocol) diff --git a/abis_mapping/vocabs/conservation_jurisdiction.py b/abis_mapping/vocabs/conservation_jurisdiction.py index 598f178d..cb255303 100644 --- a/abis_mapping/vocabs/conservation_jurisdiction.py +++ b/abis_mapping/vocabs/conservation_jurisdiction.py @@ -55,12 +55,13 @@ description="Western Australia", ) + # Vocabulary -CONSERVATION_JURISDICTION = utils.vocabs.RestrictedVocabulary( - vocab_id="CONSERVATION_JURISDICTION", - terms=(ACT, EPBC, NSW, NT, QLD, SA, TAS, VIC, WA), - publish=False, -) +class ConservationJurisdiction(utils.vocabs.RestrictedVocabulary): + vocab_id = "CONSERVATION_JURISDICTION" + terms = (ACT, EPBC, NSW, NT, QLD, SA, TAS, VIC, WA) + publish = False + # Register -utils.vocabs.Vocabulary.register(CONSERVATION_JURISDICTION) +utils.vocabs.Vocabulary.register(ConservationJurisdiction) diff --git a/abis_mapping/vocabs/establishment_means.py b/abis_mapping/vocabs/establishment_means.py index 9026c79d..b70c09a9 100644 --- a/abis_mapping/vocabs/establishment_means.py +++ b/abis_mapping/vocabs/establishment_means.py @@ -46,16 +46,17 @@ description="The temporary occurrence of a taxon far outside its natural or migratory range.", ) + # Vocabulary -ESTABLISHMENT_MEANS = utils.vocabs.FlexibleVocabulary( - vocab_id="ESTABLISHMENT_MEANS", - definition=rdflib.Literal("A type of establishmentMeans."), - base=utils.rdf.uri("bdr-cv/parameter/establishmentMeans/"), - scheme=rdflib.URIRef("http://linked.data.gov.au/def/tern-cv/5699eca7-9ef0-47a6-bcfb-9306e0e2b85e"), - broader=utils.rdf.uri("bdr-cv/parameter/establishmentMeans", utils.namespaces.EXAMPLE), # TODO -> Need real URI - default=None, # No default, omitted if not provided - terms=(INTRODUCED, INTRODUCED_ASSISTED_COLONISATION, NATIVE, NATIVE_REINTRODUCED, UNCERTAIN, VAGRANT), -) +class EstablishmentMeans(utils.vocabs.FlexibleVocabulary): + vocab_id = "ESTABLISHMENT_MEANS" + definition = rdflib.Literal("A type of establishmentMeans.") + base = utils.rdf.uri("bdr-cv/parameter/establishmentMeans/") + scheme = rdflib.URIRef("http://linked.data.gov.au/def/tern-cv/5699eca7-9ef0-47a6-bcfb-9306e0e2b85e") + broader = utils.rdf.uri("bdr-cv/parameter/establishmentMeans", utils.namespaces.EXAMPLE) # TODO -> Need real URI + default = None # No default, omitted if not provided + terms = (INTRODUCED, INTRODUCED_ASSISTED_COLONISATION, NATIVE, NATIVE_REINTRODUCED, UNCERTAIN, VAGRANT) + # Register -utils.vocabs.Vocabulary.register(ESTABLISHMENT_MEANS) +utils.vocabs.Vocabulary.register(EstablishmentMeans) diff --git a/abis_mapping/vocabs/geodetic_datum.py b/abis_mapping/vocabs/geodetic_datum.py index b76b94df..2570af6e 100644 --- a/abis_mapping/vocabs/geodetic_datum.py +++ b/abis_mapping/vocabs/geodetic_datum.py @@ -35,11 +35,12 @@ description="World Geodetic System 1984, used in GPS", ) + # Vocabulary -GEODETIC_DATUM = utils.vocabs.RestrictedVocabulary( - vocab_id="GEODETIC_DATUM", - terms=(AGD66, AGD84, GDA2020, GDA94, WGS84), -) +class GeodeticDatum(utils.vocabs.RestrictedVocabulary): + vocab_id = "GEODETIC_DATUM" + terms = (AGD66, AGD84, GDA2020, GDA94, WGS84) + # Register -utils.vocabs.Vocabulary.register(GEODETIC_DATUM) +utils.vocabs.Vocabulary.register(GeodeticDatum) diff --git a/abis_mapping/vocabs/identification_method.py b/abis_mapping/vocabs/identification_method.py index a6f6800d..1818d40d 100644 --- a/abis_mapping/vocabs/identification_method.py +++ b/abis_mapping/vocabs/identification_method.py @@ -15,17 +15,18 @@ description="Undefined", ) + # Vocabulary -IDENTIFICATION_METHOD = utils.vocabs.FlexibleVocabulary( - vocab_id="IDENTIFICATION_METHOD", - definition=rdflib.Literal("A type of identificationMethod."), - base=utils.rdf.uri("bdr-cv/methods/identificationMethod/"), - scheme=rdflib.URIRef("http://linked.data.gov.au/def/tern-cv/2fd44aca-168f-4177-b393-0688ce38102c"), - broader=utils.rdf.uri("bdr-cv/methods/identificationMethod", utils.namespaces.EXAMPLE), # TODO -> Need real URI - default=UNDEFINED, - terms=(), # No baseline vocabulary values - publish=False, -) +class IdentificationMethod(utils.vocabs.FlexibleVocabulary): + vocab_id = "IDENTIFICATION_METHOD" + definition = rdflib.Literal("A type of identificationMethod.") + base = utils.rdf.uri("bdr-cv/methods/identificationMethod/") + scheme = rdflib.URIRef("http://linked.data.gov.au/def/tern-cv/2fd44aca-168f-4177-b393-0688ce38102c") + broader = utils.rdf.uri("bdr-cv/methods/identificationMethod", utils.namespaces.EXAMPLE) # TODO -> Need real URI + default = UNDEFINED + terms = () # No baseline vocabulary values + publish = False + # Register -utils.vocabs.Vocabulary.register(IDENTIFICATION_METHOD) +utils.vocabs.Vocabulary.register(IdentificationMethod) diff --git a/abis_mapping/vocabs/identification_qualifier.py b/abis_mapping/vocabs/identification_qualifier.py index 803eaffa..59d23814 100644 --- a/abis_mapping/vocabs/identification_qualifier.py +++ b/abis_mapping/vocabs/identification_qualifier.py @@ -158,15 +158,16 @@ ), ) + # Vocabulary -IDENTIFICATION_QUALIFIER = utils.vocabs.FlexibleVocabulary( - vocab_id="IDENTIFICATION_QUALIFIER", - definition=rdflib.Literal("A type of identificationQualifier."), - base=utils.rdf.uri("bdr-cv/attribute/identificationQualifier/"), - scheme=rdflib.URIRef("http://linked.data.gov.au/def/tern-cv/dd085299-ae86-4371-ae15-61dfa432f924"), - broader=utils.rdf.uri("bdr-cv/attribute/identificationQualifier", utils.namespaces.EXAMPLE), # TODO -> Need real URI # noqa: E501 - default=None, # No default, ommitted if not provided - terms=( +class IdentificationQualifier(utils.vocabs.FlexibleVocabulary): + vocab_id = "IDENTIFICATION_QUALIFIER" + definition = rdflib.Literal("A type of identificationQualifier.") + base = utils.rdf.uri("bdr-cv/attribute/identificationQualifier/") + scheme = rdflib.URIRef("http://linked.data.gov.au/def/tern-cv/dd085299-ae86-4371-ae15-61dfa432f924") + broader = utils.rdf.uri("bdr-cv/attribute/identificationQualifier", utils.namespaces.EXAMPLE) # TODO -> Need real URI # noqa: E501 + default = None # No default, ommitted if not provided + terms = ( ANIMALIA_CETERA, CONFER, EX_GREGE, @@ -182,8 +183,8 @@ SPECIES_PROXIMA, STETIT, SUBSPECIES, - ), -) + ) + # Register -utils.vocabs.Vocabulary.register(IDENTIFICATION_QUALIFIER) +utils.vocabs.Vocabulary.register(IdentificationQualifier) diff --git a/abis_mapping/vocabs/kingdom.py b/abis_mapping/vocabs/kingdom.py index b6ab2468..b3dfccde 100644 --- a/abis_mapping/vocabs/kingdom.py +++ b/abis_mapping/vocabs/kingdom.py @@ -58,38 +58,41 @@ description="Kingdom (taxonRank: Regnum) Fungi specimen", ) + # Vocabularies -KINGDOM = utils.vocabs.FlexibleVocabulary( - vocab_id="KINGDOM", - definition=rdflib.Literal("A type of kingdom."), - base=utils.rdf.uri("bdr-cv/attribute/kingdom/"), - scheme=rdflib.URIRef("http://linked.data.gov.au/def/tern-cv/dd085299-ae86-4371-ae15-61dfa432f924"), - broader=utils.rdf.uri("bdr-cv/attribute/kingdom", utils.namespaces.EXAMPLE), # TODO -> Need real URI - default=None, # No default, kingdom is required in the CSV - terms=(ANIMALIA, PLANTAE, FUNGI), -) -KINGDOM_OCCURRENCE = utils.vocabs.FlexibleVocabulary( - vocab_id="KINGDOM_OCCURRENCE", - definition=rdflib.Literal("The existence of the organism sampled at a particular place at a particular time."), - base=utils.rdf.uri("bdr-cv/featureType/occurrence/kingdom/"), - scheme=rdflib.URIRef("http://linked.data.gov.au/def/tern-cv/68af3d25-c801-4089-afff-cf701e2bd61d"), - broader=None, # No broader, top level concept - default=None, # No default, kingdom is required in the CSV - terms=(ANIMALIA_OCCURRENCE, PLANTAE_OCCURRENCE, FUNGI_OCCURRENCE), - publish=False, -) -KINGDOM_SPECIMEN = utils.vocabs.FlexibleVocabulary( - vocab_id="KINGDOM_SPECIMEN", - definition=rdflib.Literal("An organism specimen."), - base=utils.rdf.uri("bdr-cv/featureType/specimen/kingdom/"), - scheme=rdflib.URIRef("http://linked.data.gov.au/def/tern-cv/68af3d25-c801-4089-afff-cf701e2bd61d"), - broader=None, # No broader, top level concept - default=None, # No default, kingdom is required in the CSV - terms=(ANIMALIA_SPECIMEN, PLANTAE_SPECIMEN, FUNGI_SPECIMEN), - publish=False, -) +class Kingdom(utils.vocabs.FlexibleVocabulary): + vocab_id = "KINGDOM" + definition = rdflib.Literal("A type of kingdom.") + base = utils.rdf.uri("bdr-cv/attribute/kingdom/") + scheme = rdflib.URIRef("http://linked.data.gov.au/def/tern-cv/dd085299-ae86-4371-ae15-61dfa432f924") + broader = utils.rdf.uri("bdr-cv/attribute/kingdom", utils.namespaces.EXAMPLE) # TODO -> Need real URI + default = None # No default, kingdom is required in the CSV + terms = (ANIMALIA, PLANTAE, FUNGI) + + +class KingdomOccurrence(utils.vocabs.FlexibleVocabulary): + vocab_id = "KINGDOM_OCCURRENCE" + definition = rdflib.Literal("The existence of the organism sampled at a particular place at a particular time.") + base = utils.rdf.uri("bdr-cv/featureType/occurrence/kingdom/") + scheme = rdflib.URIRef("http://linked.data.gov.au/def/tern-cv/68af3d25-c801-4089-afff-cf701e2bd61d") + broader = None # No broader, top level concept + default = None # No default, kingdom is required in the CSV + terms = (ANIMALIA_OCCURRENCE, PLANTAE_OCCURRENCE, FUNGI_OCCURRENCE) + publish = False + + +class KingdomSpecimen(utils.vocabs.FlexibleVocabulary): + vocab_id = "KINGDOM_SPECIMEN" + definition = rdflib.Literal("An organism specimen.") + base = utils.rdf.uri("bdr-cv/featureType/specimen/kingdom/") + scheme = rdflib.URIRef("http://linked.data.gov.au/def/tern-cv/68af3d25-c801-4089-afff-cf701e2bd61d") + broader = None # No broader, top level concept + default = None # No default, kingdom is required in the CSV + terms = (ANIMALIA_SPECIMEN, PLANTAE_SPECIMEN, FUNGI_SPECIMEN) + publish = False + # Register -utils.vocabs.Vocabulary.register(KINGDOM) -utils.vocabs.Vocabulary.register(KINGDOM_OCCURRENCE) -utils.vocabs.Vocabulary.register(KINGDOM_SPECIMEN) +utils.vocabs.Vocabulary.register(Kingdom) +utils.vocabs.Vocabulary.register(KingdomOccurrence) +utils.vocabs.Vocabulary.register(KingdomSpecimen) diff --git a/abis_mapping/vocabs/life_stage.py b/abis_mapping/vocabs/life_stage.py index 5010e7eb..8cae0d11 100644 --- a/abis_mapping/vocabs/life_stage.py +++ b/abis_mapping/vocabs/life_stage.py @@ -182,15 +182,16 @@ ), ) + # Vocabulary -LIFE_STAGE = utils.vocabs.FlexibleVocabulary( - vocab_id="LIFE_STAGE", - definition=rdflib.Literal("A type of lifeStage."), - base=utils.rdf.uri("bdr-cv/parameter/lifeStage/"), - scheme=rdflib.URIRef("http://linked.data.gov.au/def/tern-cv/5699eca7-9ef0-47a6-bcfb-9306e0e2b85e"), - broader=utils.rdf.uri("bdr-cv/parameter/lifeStage", utils.namespaces.EXAMPLE), # TODO -> Need real URI - default=None, # No default, ommitted if not provided - terms=( +class LifeStage(utils.vocabs.FlexibleVocabulary): + vocab_id = "LIFE_STAGE" + definition = rdflib.Literal("A type of lifeStage.") + base = utils.rdf.uri("bdr-cv/parameter/lifeStage/") + scheme = rdflib.URIRef("http://linked.data.gov.au/def/tern-cv/5699eca7-9ef0-47a6-bcfb-9306e0e2b85e") + broader = utils.rdf.uri("bdr-cv/parameter/lifeStage", utils.namespaces.EXAMPLE) # TODO -> Need real URI + default = None # No default, omitted if not provided + terms = ( ADULT, EMBRYO, GAMETE, @@ -201,8 +202,8 @@ SPORE, SPOROPHYTE, ZYGOTE, - ), -) + ) + # Register -utils.vocabs.Vocabulary.register(LIFE_STAGE) +utils.vocabs.Vocabulary.register(LifeStage) diff --git a/abis_mapping/vocabs/occurrence_status.py b/abis_mapping/vocabs/occurrence_status.py index bb302529..a06f09f6 100644 --- a/abis_mapping/vocabs/occurrence_status.py +++ b/abis_mapping/vocabs/occurrence_status.py @@ -20,16 +20,17 @@ description="The occurrence was not present at the location and time of the observation." ) + # Vocabulary -OCCURRENCE_STATUS = utils.vocabs.FlexibleVocabulary( - vocab_id="OCCURRENCE_STATUS", - definition=rdflib.Literal("A type of occurrenceStatus."), - base=utils.rdf.uri("bdr-cv/parameter/occurrenceStatus/"), - scheme=rdflib.URIRef("http://linked.data.gov.au/def/tern-cv/5699eca7-9ef0-47a6-bcfb-9306e0e2b85e"), - broader=utils.rdf.uri("bdr-cv/parameter/occurrenceStatus", utils.namespaces.EXAMPLE), # TODO -> Need real URI - default=None, # No default, ommitted if not provided - terms=(PRESENT, ABSENT) -) +class OccurrenceStatus(utils.vocabs.FlexibleVocabulary): + vocab_id = "OCCURRENCE_STATUS" + definition = rdflib.Literal("A type of occurrenceStatus.") + base = utils.rdf.uri("bdr-cv/parameter/occurrenceStatus/") + scheme = rdflib.URIRef("http://linked.data.gov.au/def/tern-cv/5699eca7-9ef0-47a6-bcfb-9306e0e2b85e") + broader = utils.rdf.uri("bdr-cv/parameter/occurrenceStatus", utils.namespaces.EXAMPLE) # TODO -> Need real URI + default = None # No default, ommitted if not provided + terms = (PRESENT, ABSENT) + # Register -utils.vocabs.Vocabulary.register(OCCURRENCE_STATUS) +utils.vocabs.Vocabulary.register(OccurrenceStatus) diff --git a/abis_mapping/vocabs/organism_quantity_type.py b/abis_mapping/vocabs/organism_quantity_type.py index 84589487..9dceb494 100644 --- a/abis_mapping/vocabs/organism_quantity_type.py +++ b/abis_mapping/vocabs/organism_quantity_type.py @@ -114,14 +114,14 @@ # Vocab -ORGANISM_QUANTITY_TYPE = utils.vocabs.FlexibleVocabulary( - vocab_id="ORGANISM_QUANTITY_TYPE", - definition=rdflib.Literal("A type of organism quantity."), - base=utils.rdf.uri("bdr-cv/concept/organismQuantityType"), - scheme=rdflib.URIRef("http://rs.gbif.org/vocabulary/gbif/quantityType"), - broader=utils.rdf.uri("bdr-cv/concept/organismQuantityType", utils.namespaces.EXAMPLE), - default=None, # No default, ommitted if not provided. - terms=( +class OrganismQuantityType(utils.vocabs.FlexibleVocabulary): + vocab_id = "ORGANISM_QUANTITY_TYPE" + definition = rdflib.Literal("A type of organism quantity.") + base = utils.rdf.uri("bdr-cv/concept/organismQuantityType") + scheme = rdflib.URIRef("http://rs.gbif.org/vocabulary/gbif/quantityType") + broader = utils.rdf.uri("bdr-cv/concept/organismQuantityType", utils.namespaces.EXAMPLE) + default = None # No default, ommitted if not provided. + terms = ( PERCENTAGE_OF_SPECIES, PERCENTAGE_OF_BIOVOLUME, PERCENTAGE_OF_BIOMASS, @@ -134,8 +134,8 @@ BIOMASS_KG, BIOVOLUME_CUBIC_MICRONS, BIOVOLUME_ML, - ), -) + ) + # Register -utils.vocabs.Vocabulary.register(ORGANISM_QUANTITY_TYPE) +utils.vocabs.Vocabulary.register(OrganismQuantityType) diff --git a/abis_mapping/vocabs/preparations.py b/abis_mapping/vocabs/preparations.py index d5942e2a..c0de1762 100644 --- a/abis_mapping/vocabs/preparations.py +++ b/abis_mapping/vocabs/preparations.py @@ -80,15 +80,16 @@ description="Refrigerated", ) + # Vocabulary -PREPARATIONS = utils.vocabs.FlexibleVocabulary( - vocab_id="PREPARATIONS", - definition=rdflib.Literal("A type of preparations."), - base=utils.rdf.uri("bdr-cv/attribute/preparations/"), - scheme=rdflib.URIRef("http://linked.data.gov.au/def/tern-cv/dd085299-ae86-4371-ae15-61dfa432f924"), - broader=utils.rdf.uri("bdr-cv/attribute/preparations", utils.namespaces.EXAMPLE), # TODO -> Need real URI - default=None, # No default, ommitted if not provided - terms=( +class Preparations(utils.vocabs.FlexibleVocabulary): + vocab_id = "PREPARATIONS" + definition = rdflib.Literal("A type of preparations.") + base = utils.rdf.uri("bdr-cv/attribute/preparations/") + scheme = rdflib.URIRef("http://linked.data.gov.au/def/tern-cv/dd085299-ae86-4371-ae15-61dfa432f924") + broader = utils.rdf.uri("bdr-cv/attribute/preparations", utils.namespaces.EXAMPLE) # TODO -> Need real URI + default = None # No default, ommitted if not provided + terms = ( ALCOHOL, DEEPFROZEN, DRIED, @@ -103,8 +104,8 @@ OTHER, PINNED, REFRIGERATED, - ), -) + ) + # Register -utils.vocabs.Vocabulary.register(PREPARATIONS) +utils.vocabs.Vocabulary.register(Preparations) diff --git a/abis_mapping/vocabs/relationship_to_related_site.py b/abis_mapping/vocabs/relationship_to_related_site.py index 01ac2fc3..8ff1c7b6 100644 --- a/abis_mapping/vocabs/relationship_to_related_site.py +++ b/abis_mapping/vocabs/relationship_to_related_site.py @@ -19,11 +19,12 @@ description="When a site is a subset of another site." ) + # Vocabulary -RELATIONSHIP_TO_RELATED_SITE = utils.vocabs.RestrictedVocabulary( - vocab_id="RELATIONSHIP_TO_RELATED_SITE", - terms=(SAME_AS, PART_OF), -) +class RelationshipToRelatedSite(utils.vocabs.RestrictedVocabulary): + vocab_id = "RELATIONSHIP_TO_RELATED_SITE" + terms = (SAME_AS, PART_OF) + # Register -utils.vocabs.Vocabulary.register(RELATIONSHIP_TO_RELATED_SITE) +utils.vocabs.Vocabulary.register(RelationshipToRelatedSite) diff --git a/abis_mapping/vocabs/reproductive_condition.py b/abis_mapping/vocabs/reproductive_condition.py index 01e66823..42b84376 100644 --- a/abis_mapping/vocabs/reproductive_condition.py +++ b/abis_mapping/vocabs/reproductive_condition.py @@ -9,16 +9,16 @@ # Vocabulary -REPRODUCTIVE_CONDITION = utils.vocabs.FlexibleVocabulary( - vocab_id="REPRODUCTIVE_CONDITION", - definition=rdflib.Literal("A type of reproductiveCondition."), - base=utils.rdf.uri("bdr-cv/parameter/reproductiveCondition/"), - scheme=rdflib.URIRef("http://linked.data.gov.au/def/tern-cv/5699eca7-9ef0-47a6-bcfb-9306e0e2b85e"), - broader=utils.rdf.uri("bdr-cv/parameter/reproductiveCondition", utils.namespaces.EXAMPLE), # TODO -> Need real URI - default=None, # No default, ommitted if not provided - terms=(), # No baseline vocabulary values - publish=False, -) +class ReproductiveCondition(utils.vocabs.FlexibleVocabulary): + vocab_id = "REPRODUCTIVE_CONDITION" + definition = rdflib.Literal("A type of reproductiveCondition.") + base = utils.rdf.uri("bdr-cv/parameter/reproductiveCondition/") + scheme = rdflib.URIRef("http://linked.data.gov.au/def/tern-cv/5699eca7-9ef0-47a6-bcfb-9306e0e2b85e") + broader = utils.rdf.uri("bdr-cv/parameter/reproductiveCondition", utils.namespaces.EXAMPLE) # TODO -> Need real URI + default = None # No default, ommitted if not provided + terms = () # No baseline vocabulary values + publish = False + # Register -utils.vocabs.Vocabulary.register(REPRODUCTIVE_CONDITION) +utils.vocabs.Vocabulary.register(ReproductiveCondition) diff --git a/abis_mapping/vocabs/sampling_effort_unit.py b/abis_mapping/vocabs/sampling_effort_unit.py index 7f81cd2e..46e58f9e 100644 --- a/abis_mapping/vocabs/sampling_effort_unit.py +++ b/abis_mapping/vocabs/sampling_effort_unit.py @@ -7,16 +7,16 @@ from abis_mapping import utils -SAMPLING_EFFORT_UNIT = utils.vocabs.FlexibleVocabulary( - vocab_id="SAMPLING_EFFORT_UNIT", - definition=rdflib.Literal("A type of samplingEffortUnit"), - base=utils.rdf.uri("bdr-cv/attribute/samplingEffortUnit/"), - scheme=rdflib.URIRef("http://linked.data.gov.au/def/tern-cv/dd085299-ae86-4371-ae15-61dfa432f924"), - broader=utils.rdf.uri("bdr-cv/concept/samplingEffortUnit", utils.namespaces.EXAMPLE), # TODO -> Need real URI - default=None, - terms=(), - publish=False, -) +class SamplingEffortUnit(utils.vocabs.FlexibleVocabulary): + vocab_id = "SAMPLING_EFFORT_UNIT" + definition = rdflib.Literal("A type of samplingEffortUnit") + base = utils.rdf.uri("bdr-cv/attribute/samplingEffortUnit/") + scheme = rdflib.URIRef("http://linked.data.gov.au/def/tern-cv/dd085299-ae86-4371-ae15-61dfa432f924") + broader = utils.rdf.uri("bdr-cv/concept/samplingEffortUnit", utils.namespaces.EXAMPLE) # TODO -> Need real URI + default = None + terms = () + publish = False + # Register -utils.vocabs.Vocabulary.register(SAMPLING_EFFORT_UNIT) +utils.vocabs.Vocabulary.register(SamplingEffortUnit) diff --git a/abis_mapping/vocabs/sampling_protocol.py b/abis_mapping/vocabs/sampling_protocol.py index 0f7dfb3b..e5ece05e 100644 --- a/abis_mapping/vocabs/sampling_protocol.py +++ b/abis_mapping/vocabs/sampling_protocol.py @@ -20,16 +20,17 @@ description="No protocol provided.", ) + # Vocabulary -SAMPLING_PROTOCOL = utils.vocabs.FlexibleVocabulary( - vocab_id="SAMPLING_PROTOCOL", - definition=rdflib.Literal("A type of samplingProtocol."), - base=utils.rdf.uri("bdr-cv/methods/samplingProtocol/"), - scheme=rdflib.URIRef("http://linked.data.gov.au/def/tern-cv/2fd44aca-168f-4177-b393-0688ce38102c"), - broader=utils.rdf.uri("bdr-cv/methods/samplingProtocol", utils.namespaces.EXAMPLE), # TODO -> Need real URI - default=UNSPECIFIED, - terms=(HUMAN_OBSERVATION, UNSPECIFIED), -) +class SamplingProtocol(utils.vocabs.FlexibleVocabulary): + vocab_id = "SAMPLING_PROTOCOL" + definition = rdflib.Literal("A type of samplingProtocol.") + base = utils.rdf.uri("bdr-cv/methods/samplingProtocol/") + scheme = rdflib.URIRef("http://linked.data.gov.au/def/tern-cv/2fd44aca-168f-4177-b393-0688ce38102c") + broader = utils.rdf.uri("bdr-cv/methods/samplingProtocol", utils.namespaces.EXAMPLE) # TODO -> Need real URI + default = UNSPECIFIED + terms = (HUMAN_OBSERVATION, UNSPECIFIED) + # Register -utils.vocabs.Vocabulary.register(SAMPLING_PROTOCOL) +utils.vocabs.Vocabulary.register(SamplingProtocol) diff --git a/abis_mapping/vocabs/sequencing_method.py b/abis_mapping/vocabs/sequencing_method.py index fd095b80..1d637ccc 100644 --- a/abis_mapping/vocabs/sequencing_method.py +++ b/abis_mapping/vocabs/sequencing_method.py @@ -15,17 +15,18 @@ description="Undefined", ) + # Vocabulary -SEQUENCING_METHOD = utils.vocabs.FlexibleVocabulary( - vocab_id="SEQUENCING_METHOD", - definition=rdflib.Literal("A type of sequencingMethod."), - base=utils.rdf.uri("bdr-cv/methods/sequencingMethod/"), - scheme=rdflib.URIRef("http://linked.data.gov.au/def/tern-cv/2fd44aca-168f-4177-b393-0688ce38102c"), - broader=utils.rdf.uri("bdr-cv/methods/sequencingMethod", utils.namespaces.EXAMPLE), # TODO -> Need real URI - default=UNDEFINED, - terms=(), # No baseline vocabulary values - publish=False, -) +class SequencingMethod(utils.vocabs.FlexibleVocabulary): + vocab_id = "SEQUENCING_METHOD" + definition = rdflib.Literal("A type of sequencingMethod.") + base = utils.rdf.uri("bdr-cv/methods/sequencingMethod/") + scheme = rdflib.URIRef("http://linked.data.gov.au/def/tern-cv/2fd44aca-168f-4177-b393-0688ce38102c") + broader = utils.rdf.uri("bdr-cv/methods/sequencingMethod", utils.namespaces.EXAMPLE) # TODO -> Need real URI + default = UNDEFINED + terms = () # No baseline vocabulary values + publish = False + # Register -utils.vocabs.Vocabulary.register(SEQUENCING_METHOD) +utils.vocabs.Vocabulary.register(SequencingMethod) diff --git a/abis_mapping/vocabs/sex.py b/abis_mapping/vocabs/sex.py index 5ee8c86b..127bae4f 100644 --- a/abis_mapping/vocabs/sex.py +++ b/abis_mapping/vocabs/sex.py @@ -40,16 +40,17 @@ description="If the sex of an organism can't be determined for some reason.", ) + # Vocabulary -SEX = utils.vocabs.FlexibleVocabulary( - vocab_id="SEX", - definition=rdflib.Literal("A type of sex."), - base=utils.rdf.uri("bdr-cv/parameter/sex/"), - scheme=rdflib.URIRef("http://linked.data.gov.au/def/tern-cv/5699eca7-9ef0-47a6-bcfb-9306e0e2b85e"), - broader=utils.rdf.uri("bdr-cv/parameter/sex", utils.namespaces.EXAMPLE), # TODO -> Need real URI - default=None, # No default, ommitted if not provided - terms=(FEMALE, HERMAPHRODITE, MALE, UNDETERMINED), -) +class Sex(utils.vocabs.FlexibleVocabulary): + vocab_id = "SEX" + definition = rdflib.Literal("A type of sex.") + base = utils.rdf.uri("bdr-cv/parameter/sex/") + scheme = rdflib.URIRef("http://linked.data.gov.au/def/tern-cv/5699eca7-9ef0-47a6-bcfb-9306e0e2b85e") + broader = utils.rdf.uri("bdr-cv/parameter/sex", utils.namespaces.EXAMPLE) # TODO -> Need real URI + default = None # No default, ommitted if not provided + terms = (FEMALE, HERMAPHRODITE, MALE, UNDETERMINED) + # Register -utils.vocabs.Vocabulary.register(SEX) +utils.vocabs.Vocabulary.register(Sex) diff --git a/abis_mapping/vocabs/site_type.py b/abis_mapping/vocabs/site_type.py index 7bf89dff..237c4b50 100644 --- a/abis_mapping/vocabs/site_type.py +++ b/abis_mapping/vocabs/site_type.py @@ -39,22 +39,23 @@ description="A line along which biotic and abiotic characteristics are sampled", ) + # Vocabulary -SITE_TYPE = utils.vocabs.FlexibleVocabulary( - vocab_id="SITE_TYPE", - definition=rdflib.Literal("A type of site."), - base=utils.rdf.uri("bdr-cv/concept/siteType/"), - scheme=rdflib.URIRef("http://linked.data.gov.au/def/tern-cv/74aa68d3-28fd-468d-8ff5-7e791d9f7159"), - broader=utils.rdf.uri("bdr-cv/concept/siteType", utils.namespaces.EXAMPLE), # TODO -> Need real URI - default=SITE, - terms=( +class SiteType(utils.vocabs.FlexibleVocabulary): + vocab_id = "SITE_TYPE" + definition = rdflib.Literal("A type of site.") + base = utils.rdf.uri("bdr-cv/concept/siteType/") + scheme = rdflib.URIRef("http://linked.data.gov.au/def/tern-cv/74aa68d3-28fd-468d-8ff5-7e791d9f7159") + broader = utils.rdf.uri("bdr-cv/concept/siteType", utils.namespaces.EXAMPLE) # TODO -> Need real URI + default = SITE + terms = ( SITE, PARENT_SITE, PLOT, QUADRAT, TRANSECT, - ), -) + ) + # Register -utils.vocabs.Vocabulary.register(SITE_TYPE) +utils.vocabs.Vocabulary.register(SiteType) diff --git a/abis_mapping/vocabs/survey_type.py b/abis_mapping/vocabs/survey_type.py index 3c022c5b..32120ea9 100644 --- a/abis_mapping/vocabs/survey_type.py +++ b/abis_mapping/vocabs/survey_type.py @@ -20,15 +20,16 @@ ), ) -SURVEY_TYPE = utils.vocabs.FlexibleVocabulary( - vocab_id="SURVEY_TYPE", - definition=rdflib.Literal("A type of surveyType"), - base=utils.rdf.uri("bdr-cv/attribute/surveyType/"), - scheme=rdflib.URIRef("http://linked.data.gov.au/def/tern-cv/dd085299-ae86-4371-ae15-61dfa432f924"), - broader=utils.rdf.uri("bdr-cv/concept/surveyType", utils.namespaces.EXAMPLE), # TODO -> Need real URI - default=None, - terms=(WET_PITFALL_TRAPPING,), -) + +class SurveyType(utils.vocabs.FlexibleVocabulary): + vocab_id = "SURVEY_TYPE" + definition = rdflib.Literal("A type of surveyType") + base = utils.rdf.uri("bdr-cv/attribute/surveyType/") + scheme = rdflib.URIRef("http://linked.data.gov.au/def/tern-cv/dd085299-ae86-4371-ae15-61dfa432f924") + broader = utils.rdf.uri("bdr-cv/concept/surveyType", utils.namespaces.EXAMPLE) # TODO -> Need real URI + default = None + terms = (WET_PITFALL_TRAPPING,) + # Register -utils.vocabs.Vocabulary.register(SURVEY_TYPE) +utils.vocabs.Vocabulary.register(SurveyType) diff --git a/abis_mapping/vocabs/target_habitat_scope.py b/abis_mapping/vocabs/target_habitat_scope.py index bdfe8bb0..bbacecb4 100644 --- a/abis_mapping/vocabs/target_habitat_scope.py +++ b/abis_mapping/vocabs/target_habitat_scope.py @@ -1044,14 +1044,15 @@ ), ) -TARGET_HABITAT_SCOPE = utils.vocabs.FlexibleVocabulary( - vocab_id="TARGET_HABITAT_SCOPE", - definition=rdflib.Literal("A type of targetHabitatScope"), - base=utils.rdf.uri("bdr-cv/attribute/targetHabitatScope/"), - scheme=rdflib.URIRef("http://linked.data.gov.au/def/tern-cv/dd085299-ae86-4371-ae15-61dfa432f924"), - broader=rdflib.URIRef("https://linked.data.gov.au/def/nrm/c19a0098-1f3f-4bc2-b84d-fdb6d4e24d6f"), - default=None, - terms=( + +class TargetHabitatScope(utils.vocabs.FlexibleVocabulary): + vocab_id = "TARGET_HABITAT_SCOPE" + definition = rdflib.Literal("A type of targetHabitatScope") + base = utils.rdf.uri("bdr-cv/attribute/targetHabitatScope/") + scheme = rdflib.URIRef("http://linked.data.gov.au/def/tern-cv/dd085299-ae86-4371-ae15-61dfa432f924") + broader = rdflib.URIRef("https://linked.data.gov.au/def/nrm/c19a0098-1f3f-4bc2-b84d-fdb6d4e24d6f") + default = None + terms = ( BEACH, BILLABONG_OR_SWAMP, CAVE, @@ -1168,8 +1169,8 @@ URBAN, VINELAND, WOODLAND, - ), -) + ) + # Register -utils.vocabs.Vocabulary.register(TARGET_HABITAT_SCOPE) +utils.vocabs.Vocabulary.register(TargetHabitatScope) diff --git a/abis_mapping/vocabs/target_taxonomic_scope.py b/abis_mapping/vocabs/target_taxonomic_scope.py index d4e37cfe..9d26fec4 100644 --- a/abis_mapping/vocabs/target_taxonomic_scope.py +++ b/abis_mapping/vocabs/target_taxonomic_scope.py @@ -79,14 +79,15 @@ ), ) -TARGET_TAXONOMIC_SCOPE = utils.vocabs.FlexibleVocabulary( - vocab_id="TARGET_TAXONOMIC_SCOPE", - definition=rdflib.Literal("A type of targetTaxonomicScope"), - base=utils.rdf.uri("bdr-cv/attribute/targetTaxonomicScope/"), - scheme=rdflib.URIRef("http://linked.data.gov.au/def/tern-cv/dd085299-ae86-4371-ae15-61dfa432f924"), - broader=rdflib.URIRef("https://linked.data.gov.au/def/nrm/7ea12fed-6b87-4c20-9ab4-600b32ce15ec"), - default=None, - terms=( + +class TargetTaxonomicScope(utils.vocabs.FlexibleVocabulary): + vocab_id = "TARGET_TAXONOMIC_SCOPE" + definition = rdflib.Literal("A type of targetTaxonomicScope") + base = utils.rdf.uri("bdr-cv/attribute/targetTaxonomicScope/") + scheme = rdflib.URIRef("http://linked.data.gov.au/def/tern-cv/dd085299-ae86-4371-ae15-61dfa432f924") + broader = rdflib.URIRef("https://linked.data.gov.au/def/nrm/7ea12fed-6b87-4c20-9ab4-600b32ce15ec") + default = None + terms = ( AMPHIBIAN, BIRD, INVERTEBRATE, @@ -94,8 +95,8 @@ NON_VASCULAR_PLANT, REPTILE, VASCULAR_PLANT, - ), -) + ) + # Register -utils.vocabs.Vocabulary.register(TARGET_TAXONOMIC_SCOPE) +utils.vocabs.Vocabulary.register(TargetTaxonomicScope) diff --git a/abis_mapping/vocabs/taxon_rank.py b/abis_mapping/vocabs/taxon_rank.py index a2ae80cd..22324b44 100644 --- a/abis_mapping/vocabs/taxon_rank.py +++ b/abis_mapping/vocabs/taxon_rank.py @@ -202,15 +202,16 @@ description="Variety", ) + # Vocabulary -TAXON_RANK = utils.vocabs.FlexibleVocabulary( - vocab_id="TAXON_RANK", - definition=rdflib.Literal("A type of taxonRank."), - base=utils.rdf.uri("bdr-cv/attribute/taxonRank/"), - scheme=rdflib.URIRef("http://linked.data.gov.au/def/tern-cv/dd085299-ae86-4371-ae15-61dfa432f924"), - broader=utils.rdf.uri("bdr-cv/attribute/taxonRank", utils.namespaces.EXAMPLE), # TODO -> Need real URI - default=None, # No default, ommitted if not provided - terms=( +class TaxonRank(utils.vocabs.FlexibleVocabulary): + vocab_id = "TAXON_RANK" + definition = rdflib.Literal("A type of taxonRank.") + base = utils.rdf.uri("bdr-cv/attribute/taxonRank/") + scheme = rdflib.URIRef("http://linked.data.gov.au/def/tern-cv/dd085299-ae86-4371-ae15-61dfa432f924") + broader = utils.rdf.uri("bdr-cv/attribute/taxonRank", utils.namespaces.EXAMPLE) # TODO -> Need real URI + default = None # No default, ommitted if not provided + terms = ( CLASS, CULTIVAR, CULTIVARGROUP, @@ -246,7 +247,7 @@ UNRANKED, VARIETY, ) -) + # Register -utils.vocabs.Vocabulary.register(TAXON_RANK) +utils.vocabs.Vocabulary.register(TaxonRank) diff --git a/abis_mapping/vocabs/threat_status.py b/abis_mapping/vocabs/threat_status.py index 43984e24..c1857777 100644 --- a/abis_mapping/vocabs/threat_status.py +++ b/abis_mapping/vocabs/threat_status.py @@ -742,15 +742,16 @@ description="Western Australia, vulnerable species.", ) + # Vocabulary -THREAT_STATUS = utils.vocabs.FlexibleVocabulary( - vocab_id="THREAT_STATUS", - definition=rdflib.Literal("A type of threatStatus."), - base=utils.rdf.uri("bdr-cv/parameter/threatStatus/"), - scheme=rdflib.URIRef("http://linked.data.gov.au/def/tern-cv/5699eca7-9ef0-47a6-bcfb-9306e0e2b85e"), - broader=utils.rdf.uri("bdr-cv/parameter/threatStatus", utils.namespaces.EXAMPLE), # TODO -> Need real URI - default=None, # No default, ommitted if not provided - terms=( +class ThreatStatus(utils.vocabs.FlexibleVocabulary): + vocab_id = "THREAT_STATUS" + definition = rdflib.Literal("A type of threatStatus.") + base = utils.rdf.uri("bdr-cv/parameter/threatStatus/") + scheme = rdflib.URIRef("http://linked.data.gov.au/def/tern-cv/5699eca7-9ef0-47a6-bcfb-9306e0e2b85e") + broader = utils.rdf.uri("bdr-cv/parameter/threatStatus", utils.namespaces.EXAMPLE) # TODO -> Need real URI + default = None # No default, ommitted if not provided + terms = ( ACT_CRITICALLY_ENDANGERED, ACT_ENDANGERED, ACT_EXTINCT_IN_THE_WILD, @@ -824,9 +825,9 @@ WA_SPECIALLY_PROTECTED_SPECIES, WA_T, WA_VU, - ), - publish=False, -) + ) + publish = False + # Register -utils.vocabs.Vocabulary.register(THREAT_STATUS) +utils.vocabs.Vocabulary.register(ThreatStatus) diff --git a/docs/instructions.py b/docs/instructions.py index 81952588..cebad62f 100644 --- a/docs/instructions.py +++ b/docs/instructions.py @@ -51,6 +51,10 @@ def get_source( except KeyError: raise ValueError(f"Template '{self.mapper_id}' not found.") + # Check mapper returned + if mapper is None: + raise TypeError(f"Template '{self.mapper_id}' not defined; got NoneType") + # Create path path = mapper().root_dir() / "templates" / template @@ -95,7 +99,7 @@ def build_instructions(mapper_id: str) -> str: "threat_status": tables.threat_status.ThreatStatusTabler(mapper_id).generate_table(as_markdown=True), }, "values": { - "geodetic_datum_count": len(vocabs.geodetic_datum.GEODETIC_DATUM.terms), + "geodetic_datum_count": len(vocabs.geodetic_datum.GeodeticDatum.terms), }, } diff --git a/docs/tables/base.py b/docs/tables/base.py index da38d521..1b3dc6ce 100644 --- a/docs/tables/base.py +++ b/docs/tables/base.py @@ -3,13 +3,12 @@ # Standard import abc import csv -import re # Local from abis_mapping import base # Typing -from typing import IO, Final, Any +from typing import IO, Final, Any, Mapping, Iterable class BaseTabler(abc.ABC): @@ -66,7 +65,7 @@ class MarkdownDialect(csv.excel): escapechar = '\\' lineterminator = '\n' quoting = csv.QUOTE_NONE - quotechar = None + quotechar = None # type: ignore[assignment] # Register the dialect with the csv module @@ -115,21 +114,20 @@ def __init__( fieldnames.append("__end__") # Assign attributes - self.dialect: Final[csv.Dialect] = csv.get_dialect("markdown") self.alignment: Final[list[str] | None] = ["l", *alignment, "l"] if alignment is not None else None # Call parent constructor - super().__init__(f, fieldnames, dialect="markdown", *args, **kwargs) + super().__init__(f, fieldnames, dialect="markdown", *args, **kwargs) # type: ignore[arg-type, misc] @property - def first_field(self) -> str: + def first_field(self) -> Any: """Returns name of first field.""" - return self.fieldnames[0] + return self.fieldnames[0] # type: ignore[index] @property - def last_field(self) -> str: + def last_field(self) -> Any: """Returns name of last field.""" - return self.fieldnames[-1] + return self.fieldnames[-1] # type: ignore[index] def writeheader(self) -> Any: """Writes the first row of the markdown table. @@ -158,23 +156,23 @@ def writeheader(self) -> Any: header_break = dict(zip(self.fieldnames, divider)) return self.writerow(header_break) - def writerow(self, rowdict: dict[str, Any]) -> Any: + def writerow(self, rowdict: Mapping[Any, Any]) -> Any: """Writes a row of the markdown table. Args: - rowdict (dict[str, Any]): Dictionary to convert. + rowdict (Mapping[Any, Any]): Dictionary to convert. Returns: Any: Value returned from underlying file write method. """ # Add the first and last blank values to allow beginning and trailing pipes - rowdict[self.first_field] = None - rowdict[self.last_field] = None + rowdict[self.first_field] = None # type: ignore[index] + rowdict[self.last_field] = None # type: ignore[index] # Clean cells for fieldname in self.fieldnames: if isinstance(rowdict[fieldname], str): - rowdict[fieldname] = self._clean_cell(rowdict[fieldname]) + rowdict[fieldname] = self._clean_cell(rowdict[fieldname]) # type: ignore[index] # Call parent writerow and return return super().writerow(rowdict) @@ -191,7 +189,7 @@ def _clean_cell(self, contents: str) -> str: # Perform replacement return contents.replace('\n', "
") - def writerows(self, rowdicts: list[dict[str, Any]]) -> None: + def writerows(self, rowdicts: Iterable[Mapping[Any, Any]]) -> None: """Writes rows of the markdown table. Raises: diff --git a/docs/tables/fields.py b/docs/tables/fields.py index 46dfe823..45e0cb41 100644 --- a/docs/tables/fields.py +++ b/docs/tables/fields.py @@ -55,9 +55,16 @@ def generate_table( # Create a memory io and dictionary to csv writer output = io.StringIO() - header = [hdr.serialization_alias or hdr.title for hdr in FieldTableRow.model_fields.values()] + raw_hdr = (hdr.serialization_alias or hdr.title for hdr in FieldTableRow.model_fields.values()) + header = [hdr for hdr in raw_hdr if hdr is not None] + if as_markdown: - writer = tables.base.MarkdownDictWriter(output, fieldnames=header, alignment=["l", "l", "c", "c", "l"]) + # MarkdownDictWriter is a subclass of DictWriter hence the type hint. + writer: csv.DictWriter = tables.base.MarkdownDictWriter( + f=output, + fieldnames=header, + alignment=["l", "l", "c", "c", "l"], + ) else: writer = csv.DictWriter(output, fieldnames=header) @@ -169,9 +176,10 @@ def determine_checklist( # Get validate method mock mocked_validate: mock.Mock = mocked_resource.return_value.validate - # Retrieve checklist + # Retrieve checklist and return if mocked_validate.called: - return mocked_validate.call_args.kwargs.get("checklist") + checklist: frictionless.Checklist = mocked_validate.call_args.kwargs.get("checklist") + return checklist # Else return None diff --git a/docs/tables/threat_status.py b/docs/tables/threat_status.py index 5c628006..a4626d02 100644 --- a/docs/tables/threat_status.py +++ b/docs/tables/threat_status.py @@ -41,7 +41,7 @@ def __init__( """ super().__init__(template_id) fields = self.mapper.schema()['fields'] - if len([fld for fld in fields if fld.get("vocabularies") and "THREAT_STATUS" in fld.get("vocabularies")])== 0: + if not [fld for fld in fields if fld.get("vocabularies") and "THREAT_STATUS" in fld.get("vocabularies")]: raise ValueError(f"No THREAT_STATUS vocabularies found for template {template_id}") def generate_table( @@ -62,18 +62,24 @@ def generate_table( output = io.StringIO() # Get header list - header = [hdr.serialization_alias for hdr in ThreatStatusRow.model_fields.values()] + raw_hdr = (hdr.serialization_alias or hdr.title for hdr in ThreatStatusRow.model_fields.values()) + header = [hdr for hdr in raw_hdr if hdr is not None] # Create writer if as_markdown: - writer = tables.base.MarkdownDictWriter(output, fieldnames=header, alignment=["c", "l", "l"]) + # MarkdownDictWriter is a subclass of DictWriter hence the type hint + writer: csv.DictWriter = tables.base.MarkdownDictWriter( + f=output, + fieldnames=header, + alignment=["c", "l", "l"], + ) else: writer = csv.DictWriter(output, fieldnames=header) writer.writeheader() # Iterate through vocab's terms - for term in sorted(utils.vocabs.get_vocab("THREAT_STATUS").terms, key=lambda x: x.preferred_label): + for term in sorted(utils.vocabs.get_vocab("THREAT_STATUS").terms, key=lambda x: x.preferred_label): # type: ignore[arg-type, return-value] # noqa: E501 # Generate row row = self.generate_row(term) # Write to output @@ -98,9 +104,17 @@ def generate_row( Return; ThreatStatConsJurTableRow: Table row. + + Raises: + ValueError: If there is no preferred label for the supplied Term.' """ - # Split out preferred label - splt_preferred = threat_stat_cons_jur_term.preferred_label.split('/') + # Check preferred label + if (preferred_label := threat_stat_cons_jur_term.preferred_label) is not None: + # Split out preferred label + splt_preferred = preferred_label.split('/') + else: + # Raise + raise ValueError(f"No preferred label for {threat_stat_cons_jur_term}") # Split threat status alt labels threat_stat_alt: list[str] = [lbl.split('/')[1] for lbl in threat_stat_cons_jur_term.alternative_labels] diff --git a/docs/tables/vocabs.py b/docs/tables/vocabs.py index 53817daa..18956290 100644 --- a/docs/tables/vocabs.py +++ b/docs/tables/vocabs.py @@ -1,3 +1,5 @@ +"""Tool for extracting vocabularies from the mappers.""" + # Standard import argparse import csv @@ -13,7 +15,7 @@ from abis_mapping import utils # Typing -from typing import Iterator, IO +from typing import Iterator, IO, Type class VocabTableRow(pydantic.BaseModel): @@ -49,9 +51,14 @@ def generate_table( # Create a memory io and dictionary to csv writer output = io.StringIO() - header = [hdr.serialization_alias or hdr.title for hdr in VocabTableRow.model_fields.values()] + # Get serialization title or fall back to given title for each field. + raw_hdr = (hdr.serialization_alias or hdr.title for hdr in VocabTableRow.model_fields.values()) + # Assert all values as not None + header = [hdr for hdr in raw_hdr if hdr is not None] + if as_markdown: - writer = tables.base.MarkdownDictWriter(output, fieldnames=header) + # MarkdownDictWriter is a child of DictWriter hence typehint + writer: csv.DictWriter = tables.base.MarkdownDictWriter(output, fieldnames=header) else: writer = csv.DictWriter(output, fieldnames=header) @@ -81,7 +88,7 @@ def generate_table( def generate_vocab_rows( self, field: types.schema.Field, - vocab: utils.vocabs.Vocabulary, + vocab: Type[utils.vocabs.Vocabulary], as_markdown: bool = False, ) -> Iterator[VocabTableRow]: """Generates a set of rows based on vocabulary. @@ -95,7 +102,7 @@ def generate_vocab_rows( VocabTableRow: Vocabulary table rows. """ # Sort terms and turn into a generator - terms = (t for t in sorted(vocab.terms, key=lambda x: x.preferred_label)) + terms = (t for t in sorted(vocab.terms, key=lambda x: x.preferred_label)) # type: ignore[arg-type, return-value] # noqa: E501 # If markdown then the first row must contain an anchor if as_markdown and (term := next(terms, None)) is not None: @@ -161,4 +168,3 @@ def generate_row( finally: # Close output file args.output_dest.close() - diff --git a/scripts/lint.sh b/scripts/lint.sh index 69b9aea5..438f2c1e 100755 --- a/scripts/lint.sh +++ b/scripts/lint.sh @@ -1,3 +1,3 @@ #!/usr/bin/env bash set -x -flake8 tests abis_mapping \ No newline at end of file +flake8 tests abis_mapping docs diff --git a/scripts/typecheck.sh b/scripts/typecheck.sh index 6d757619..a9894edb 100755 --- a/scripts/typecheck.sh +++ b/scripts/typecheck.sh @@ -1,3 +1,3 @@ #!/usr/bin/env bash set -x -mypy tests abis_mapping \ No newline at end of file +mypy tests abis_mapping docs diff --git a/tests/base/test_mapper.py b/tests/base/test_mapper.py index 93d534eb..22e99aea 100644 --- a/tests/base/test_mapper.py +++ b/tests/base/test_mapper.py @@ -2,9 +2,10 @@ # Standard -import json import csv import io +import json +import unittest.mock # Third-party import frictionless @@ -16,7 +17,9 @@ from abis_mapping import utils # Typing -from typing import Any +from typing import Any, Optional, Iterator + +from abis_mapping.base import types as base_types def data_to_csv(data: list[dict[str, Any]]) -> bytes: @@ -321,3 +324,73 @@ def test_extra_fields_middle(mocker: pytest_mock.MockerFixture) -> None: assert not report.valid error_codes = [code for codes in report.flatten(['type']) for code in codes] assert error_codes == ["incorrect-label"] + + +def test_fields( + mocker: pytest_mock.MockerFixture, + mocked_vocab: unittest.mock.MagicMock, +) -> None: + """Tests the fields method. + + Args: + mocker (pytest_mock.MockerFixture): Pytest mocker fixture + mocked_vocab (unittest.mock.MagicMock): Patched get_vocab and resulting mock. + """ + # Patch schema method + descriptor = { + "fields": [ + { + "name": "fieldA", + "title": "Title A", + "description": "Description A", + "example": "Example A", + "type": "typeA", + "format": "formatA", + "constraints": { + "required": False, + }, + "vocabularies": [ + "vocabularyA" + ] + }, + { + "name": "fieldB", + "title": "Title B", + "description": "Description B", + "example": "Example B", + "type": "typeB", + "format": "formatB", + "constraints": { + "required": False, + }, + "vocabularies": [ + "vocabularyB" + ] + } + ] + } + mocker.patch.object(base.mapper.ABISMapper, "schema", return_value=descriptor) + + # Create mapper + class TestMapper(base.mapper.ABISMapper): + + def apply_mapping( + self, + data: base_types.ReadableType, + dataset_iri: Optional[rdflib.URIRef] = None, + base_iri: Optional[rdflib.Namespace] = None, + **kwargs: Any + ) -> Iterator[rdflib.Graph]: + pass + + def apply_validation( + self, + data: base_types.ReadableType, + **kwargs: Any + ) -> frictionless.Report: + pass + + mapper = TestMapper() + + # Assert + assert list(mapper.fields.keys()) == ["fieldA", "fieldB"] diff --git a/tests/conftest.py b/tests/conftest.py index 4990cd43..65a00ca2 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2,15 +2,47 @@ # Standard import json +import unittest.mock # Third-Party +import pytest +import pytest_mock import rdflib import rdflib.compare +# Local +from abis_mapping import utils + # Typing from typing import Union +@pytest.fixture +def mocked_vocab(mocker: pytest_mock.MockerFixture) -> unittest.mock.MagicMock: + """Provides a mocked term fixture. + + Args: + mocker (pytest_mock.MockerFixture): Pytest mocker fixture. + + Returns: + unittest.mock.MagicMock: Mocked term fixture. + """ + # Patch get_vocab + mocked_vocab = mocker.patch("abis_mapping.utils.vocabs.get_vocab") + + # Patch terms + mocked_vocab.return_value.terms = ( + utils.vocabs.Term( + labels=("SOME LABEL", "ANOTHER LABEL", ), + iri=rdflib.URIRef("https://example.org/some-term"), + description="Some description.", + ), + ) + + # Return + return mocked_vocab + + def compare_graphs( graph1: Union[rdflib.Graph, str], graph2: Union[rdflib.Graph, str], diff --git a/tests/docs/tables/conftest.py b/tests/docs/tables/conftest.py deleted file mode 100644 index d22e2964..00000000 --- a/tests/docs/tables/conftest.py +++ /dev/null @@ -1,38 +0,0 @@ -"""Provides fixtures and helpers for the unit tests.""" - -# Standard -import unittest.mock - -# Third-party -import pytest -import pytest_mock -import rdflib - -# Local -from abis_mapping import utils - - -@pytest.fixture -def mocked_vocab(mocker: pytest_mock.MockerFixture) -> unittest.mock.MagicMock: - """Provides a mocked term fixture. - - Args: - mocker (pytest_mock.MockerFixture): Pytest mocker fixture. - - Returns: - unittest.mock.MagicMock: Mocked term fixture. - """ - # Patch get_vocab - mocked_vocab = mocker.patch("abis_mapping.utils.vocabs.get_vocab") - - # Patch terms - mocked_vocab.return_value.terms = ( - utils.vocabs.Term( - labels=("SOME LABEL", "ANOTHER LABEL", ), - iri=rdflib.URIRef("https://example.org/some-term"), - description="Some description.", - ), - ) - - # Return - return mocked_vocab diff --git a/tests/docs/tables/test_fields.py b/tests/docs/tables/test_fields.py index a94a254d..1563976b 100644 --- a/tests/docs/tables/test_fields.py +++ b/tests/docs/tables/test_fields.py @@ -130,11 +130,15 @@ def test_determine_checklist() -> None: assert len(checklist.checks) == 6 -def test_generate_table(mocked_mapper: unittest.mock.MagicMock) -> None: +def test_generate_table( + mocked_mapper: unittest.mock.MagicMock, + mocked_vocab: unittest.mock.MagicMock, +) -> None: """Tests generate_table method. Args: - mocked_mapper (pytest_mock.mocker.mock.MagicMock): Mocked mapper fixture. + mocked_mapper (unittest.mock.MagicMock): Mocked mapper fixture. + mocked_vocab (unittest.mock.MagicMock): Mocked vocab fixture. """ # Create an in memory io dest = io.StringIO() @@ -148,17 +152,23 @@ def test_generate_table(mocked_mapper: unittest.mock.MagicMock) -> None: ) # Assert + mocked_vocab.assert_called() + mocked_mapper.assert_called() assert dest.getvalue() == ( 'Field Name,Description,Mandatory / Optional,Datatype Format,Examples\r\n' 'someName,Some description,Mandatory,String,SOME EXAMPLE\r\n\n' ) -def test_generate_table_markdown(mocked_mapper: unittest.mock.MagicMock) -> None: +def test_generate_table_markdown( + mocked_mapper: unittest.mock.MagicMock, + mocked_vocab: unittest.mock.MagicMock, +) -> None: """Tests generate_table method with markdown format. Args: mocked_mapper (unittest.mock.MagicMock): Mocked mapper fixture. + mocked_vocab (unittest.mock.MagicMock): Mocked vocab fixture. """ # Create a tabler tabler = tables.fields.FieldTabler("some_id") @@ -167,6 +177,8 @@ def test_generate_table_markdown(mocked_mapper: unittest.mock.MagicMock) -> None actual = tabler.generate_table(as_markdown=True) # Assert + mocked_mapper.assert_called() + mocked_vocab.assert_called() assert actual == ( '|Field Name|Description|Mandatory / Optional|Datatype Format|Examples|\n' '|:---|:---|:---:|:---:|:---|\n' diff --git a/tests/types/test_schema.py b/tests/types/test_schema.py new file mode 100644 index 00000000..aa90191f --- /dev/null +++ b/tests/types/test_schema.py @@ -0,0 +1,106 @@ +"""Provides unit tests for the schema module.""" + +# Third-party +import pydantic +import pytest + +# Local +from abis_mapping import types + +# Typing +from typing import Any + + +class TestField: + @pytest.fixture + def field_d(self) -> dict[str, Any]: + """Returns a field definition. + + Returns: + dict[str, Any]: The field definition. + """ + + return { + "name": "fieldA", + "title": "titleA", + "description": "descriptionA", + "example": "exampleA", + "type": "typeA", + "format": None, + "constraints": { + "required": False, + }, + "vocabularies": [ + "SEX" + ], + } + + def test_check_vocabularies(self, field_d: dict[str, Any]) -> None: + """Tests the check vocabularies method with valid vocab. + + Args: + field_d (dict[str, Any]): The field definition. + """ + # Perform validation + field = types.schema.Field.model_validate(field_d) + + # Assert + assert field.vocabularies == ["SEX"] + + def test_check_vocabularies_invalid_vocab(self, field_d: dict[str, Any]) -> None: + """Tests the custom vocabularies validator method with invalid input. + + Args: + field_d (dict[str, Any]): Field definition fixture + """ + # Modify the field fixture to have invalid vocab + field_d["vocabularies"] = ["AFAKEVOCAB123"] + + with pytest.raises(pydantic.ValidationError): + # Create field + types.schema.Field.model_validate(field_d) + + def test_get_vocab(self, field_d: dict[str, Any]) -> None: + """Tests the get vocab method. + + Args: + field_d dict[str, Any]: The field definition fixture. + """ + # Create field + field = types.schema.Field.model_validate(field_d) + + # Invoke + vocab = field.get_vocab() + + # Assert + assert vocab.vocab_id == "SEX" + + def test_get_vocab_invalid_id(self, field_d: dict[str, Any]) -> None: + """Test the get_vocab method with invalid id. + + Args: + field_d (dict[str, Any]): The field definition fixture. + """ + # Create field + field = types.schema.Field.model_validate(field_d) + + with pytest.raises(ValueError): + # Invoke + field.get_vocab("AFAKEVOCAB123") + + def test_get_vocab_no_vocabs(self, field_d: dict[str, Any]) -> None: + """Test the get_vocab method with no vocabs on field. + + Args: + field_d (dict[str, Any]): The field definition fixture. + """ + # Modify field d + field_d["vocabularies"] = [] + + # Create field + field = types.schema.Field.model_validate(field_d) + + # Should raise index error + with pytest.raises(IndexError): + # Invoke + field.get_vocab() diff --git a/tests/types/test_spatial.py b/tests/types/test_spatial.py index 5e5f535d..cc7d8eec 100644 --- a/tests/types/test_spatial.py +++ b/tests/types/test_spatial.py @@ -130,9 +130,12 @@ def test_geometry_transformer_datum_uri() -> None: raw="POINT(0 0)", datum="AGD66", ) + # Retrieve geodetic datum vocab + vocab = utils.vocabs.get_vocab("GEODETIC_DATUM") # Assert default datum - assert geometry.transformer_datum_uri == vocabs.geodetic_datum.GEODETIC_DATUM.get(settings.DEFAULT_TARGET_CRS) + assert vocab is not None + assert geometry.transformer_datum_uri == vocab(graph=rdflib.Graph()).get(settings.DEFAULT_TARGET_CRS) @pytest.mark.parametrize( @@ -201,7 +204,7 @@ def test_geometry_to_rdf_literal() -> None: ( "POINT (571666.4475041276 5539109.815175673)", "EPSG:26917", - f"<{vocabs.geodetic_datum.GEODETIC_DATUM.get(settings.DEFAULT_TARGET_CRS)}> POINT (-80 50)", + f"<{vocabs.geodetic_datum.GeodeticDatum(rdflib.Graph()).get(settings.DEFAULT_TARGET_CRS)}> POINT (-80 50)", ), ] diff --git a/tests/utils/test_vocabs.py b/tests/utils/test_vocabs.py index 482c41cc..9a2cce8d 100644 --- a/tests/utils/test_vocabs.py +++ b/tests/utils/test_vocabs.py @@ -41,9 +41,9 @@ def test_vocabs_term() -> None: def test_vocabs_restricted_vocab() -> None: """Tests the RestrictedVocab Class""" # Create Vocab - vocab = abis_mapping.utils.vocabs.RestrictedVocabulary( - vocab_id="TEST_RESTRICT", - terms=( + class Vocab(abis_mapping.utils.vocabs.RestrictedVocabulary): + vocab_id = "TEST_RESTRICT" + terms = ( abis_mapping.utils.vocabs.Term( labels=("A",), iri=rdflib.URIRef("A"), @@ -54,8 +54,8 @@ def test_vocabs_restricted_vocab() -> None: iri=rdflib.URIRef("B"), description="B", ), - ), - ) + ) + vocab = Vocab(graph=rdflib.Graph()) # Assert Existing Values assert vocab.get("a") == rdflib.URIRef("A") @@ -71,14 +71,14 @@ def test_vocabs_restricted_vocab() -> None: def test_vocabs_flexible_vocab() -> None: """Tests the FlexibleVocab Class""" # Create Vocab - vocab = abis_mapping.utils.vocabs.FlexibleVocabulary( - vocab_id="TEST_FLEX", - definition=rdflib.Literal("definition"), - base=rdflib.URIRef("base/"), - scheme=rdflib.URIRef("scheme"), - broader=rdflib.URIRef("broader"), - default=None, - terms=( + class Vocab(abis_mapping.utils.vocabs.FlexibleVocabulary): + vocab_id = "TEST_FLEX" + definition = rdflib.Literal("definition") + base = rdflib.URIRef("base/") + scheme = rdflib.URIRef("scheme") + broader = rdflib.URIRef("broader") + default = None + terms = ( abis_mapping.utils.vocabs.Term( labels=("A",), iri=rdflib.URIRef("A"), @@ -89,20 +89,22 @@ def test_vocabs_flexible_vocab() -> None: iri=rdflib.URIRef("B"), description="B", ), - ), - ) - - # Create Graph + ) + # Create graph graph = rdflib.Graph() + # Initialize vocab + vocab = Vocab(graph=graph) + # Assert Existing Values - assert vocab.get(graph, "a") == rdflib.URIRef("A") - assert vocab.get(graph, "A") == rdflib.URIRef("A") - assert vocab.get(graph, "b") == rdflib.URIRef("B") - assert vocab.get(graph, "B") == rdflib.URIRef("B") + assert vocab.get("a") == rdflib.URIRef("A") + assert vocab.get("A") == rdflib.URIRef("A") + assert vocab.get("b") == rdflib.URIRef("B") + assert vocab.get("B") == rdflib.URIRef("B") # Assert New Values - assert vocab.get(graph, "C", rdflib.URIRef("D")) == rdflib.URIRef("base/C") + vocab.source = rdflib.URIRef("D") + assert vocab.get("C") == rdflib.URIRef("base/C") assert graph.serialize(format="ttl").strip() == textwrap.dedent( """ @prefix dcterms: . @@ -120,7 +122,7 @@ def test_vocabs_flexible_vocab() -> None: # Assert Invalid Values with pytest.raises(abis_mapping.utils.vocabs.VocabularyError): - vocab.get(graph, None) # No Default + vocab.get(None) # No Default def test_vocab_register_id() -> None: @@ -130,12 +132,16 @@ def test_vocab_register_id() -> None: def test_get_vocab() -> None: """Tests get_vocab function.""" - # Retrieve vocabs + # Retrieve vocab v1 = abis_mapping.utils.vocabs.get_vocab("SEX") - v2 = abis_mapping.utils.vocabs.get_vocab("NOT_A_VOCAB") # Assert exists - assert isinstance(v1, abis_mapping.utils.vocabs.FlexibleVocabulary) + assert v1 is not None + assert issubclass(v1, abis_mapping.utils.vocabs.FlexibleVocabulary) + - # Assert None - assert v2 is None +def test_get_vocab_invalid() -> None: + """Tests get_vocab function with an invalid vocab.""" + # Should raise key error + with pytest.raises(ValueError): + abis_mapping.utils.vocabs.get_vocab("NOT_A_VOCAB") diff --git a/tests/vocabs/test_organism_quantity_type.py b/tests/vocabs/test_organism_quantity_type.py index 1aac8cb1..227adbb4 100644 --- a/tests/vocabs/test_organism_quantity_type.py +++ b/tests/vocabs/test_organism_quantity_type.py @@ -1,7 +1,7 @@ """Tests the organism quantity type vocab functionality.""" # Local -from abis_mapping import vocabs +from abis_mapping import utils # Third-party import rdflib @@ -13,7 +13,9 @@ def test_get_percentage_biomass() -> None: graph = rdflib.Graph() # Get vocab - iri = vocabs.organism_quantity_type.ORGANISM_QUANTITY_TYPE.get(graph, "% of biomass") + vocab = utils.vocabs.get_vocab("ORGANISM_QUANTITY_TYPE") + assert vocab is not None + iri = vocab(graph=graph).get("% of biomass") # Assert assert iri == rdflib.URIRef("http://rs.gbif.org/vocabulary/gbif/quantityType/percentageOfBiomass")