From 4cb530809b72f0ca53ee24d87dc49a9e371767ef Mon Sep 17 00:00:00 2001 From: Paul Tomasula <31142705+ptomasula@users.noreply.github.com> Date: Wed, 20 Apr 2022 16:18:02 -0400 Subject: [PATCH 1/4] Migrate initial models and installer setup --- .gitignore | 5 + pyproject.toml | 3 + setup.cfg | 19 ++ src/odm2datamodels/__init__.py | 1 + src/odm2datamodels/base.py | 234 ++++++++++++++++++ src/odm2datamodels/exceptions.py | 5 + src/odm2datamodels/models/__init__.py | 13 + src/odm2datamodels/models/annotations.py | 45 ++++ src/odm2datamodels/models/auth.py | 24 ++ src/odm2datamodels/models/core.py | 51 ++++ src/odm2datamodels/models/cv.py | 87 +++++++ src/odm2datamodels/models/dataquality.py | 18 ++ src/odm2datamodels/models/equipment.py | 39 +++ .../models/extensionproperties.py | 24 ++ .../models/externalidentifiers.py | 30 +++ src/odm2datamodels/models/labanalyses.py | 12 + src/odm2datamodels/models/provenance.py | 30 +++ src/odm2datamodels/models/results.py | 57 +++++ src/odm2datamodels/models/samplingfeatures.py | 27 ++ src/odm2datamodels/models/simulation.py | 15 ++ 20 files changed, 739 insertions(+) create mode 100644 .gitignore create mode 100644 pyproject.toml create mode 100644 setup.cfg create mode 100644 src/odm2datamodels/__init__.py create mode 100644 src/odm2datamodels/base.py create mode 100644 src/odm2datamodels/exceptions.py create mode 100644 src/odm2datamodels/models/__init__.py create mode 100644 src/odm2datamodels/models/annotations.py create mode 100644 src/odm2datamodels/models/auth.py create mode 100644 src/odm2datamodels/models/core.py create mode 100644 src/odm2datamodels/models/cv.py create mode 100644 src/odm2datamodels/models/dataquality.py create mode 100644 src/odm2datamodels/models/equipment.py create mode 100644 src/odm2datamodels/models/extensionproperties.py create mode 100644 src/odm2datamodels/models/externalidentifiers.py create mode 100644 src/odm2datamodels/models/labanalyses.py create mode 100644 src/odm2datamodels/models/provenance.py create mode 100644 src/odm2datamodels/models/results.py create mode 100644 src/odm2datamodels/models/samplingfeatures.py create mode 100644 src/odm2datamodels/models/simulation.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..93bab39 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +#build files +dist/* +*.egg-info + +*.pyc diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..fa7093a --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["setuptools>=42"] +build-backend = "setuptools.build_meta" \ No newline at end of file diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..e72d12d --- /dev/null +++ b/setup.cfg @@ -0,0 +1,19 @@ +[metadata] +name = odm2datamodels +description = "Collection of object-relational mapping (ORM) data models for ODM2" +long_description = file: README.md +long_description_content = text/markdown +version = 0.0.1 +author = "ODM2 Team" +author_email = "ptomasula@limno.com" +url = https://github.com/ODM2/ODM2DataModels +project_urls = + bugtracker = https://github.com/ODM2/ODM2DataModels/issueshttps://github.com/pypa/sampleproject/issues +keywords='Observations Data Model ODM2' +package_dir = + = src +packages = find: +python_requires = >=3.8 + +[options.packages.find] +where = src \ No newline at end of file diff --git a/src/odm2datamodels/__init__.py b/src/odm2datamodels/__init__.py new file mode 100644 index 0000000..324f7cc --- /dev/null +++ b/src/odm2datamodels/__init__.py @@ -0,0 +1 @@ +from .base import ODM2DataModels as ODM2DataModels diff --git a/src/odm2datamodels/base.py b/src/odm2datamodels/base.py new file mode 100644 index 0000000..d3f0dd9 --- /dev/null +++ b/src/odm2datamodels/base.py @@ -0,0 +1,234 @@ +import sqlalchemy +from sqlalchemy.sql.expression import Select +from sqlalchemy.orm import Query +from sqlalchemy.ext.automap import automap_base +from sqlalchemy.ext.declarative import declared_attr, declarative_base +import geoalchemy2 + +import pickle +from enum import Enum +from typing import Dict, Union, Any, Type +import warnings + +import pandas as pd + +from .exceptions import ObjectNotFound + +from .models import annotations +from .models import auth +from .models import core +from .models import cv +from .models import dataquality +from .models import equipment +from .models import extensionproperties +from .models import externalidentifiers +from .models import labanalyses +from .models import provenance +from .models import results +from .models import samplingfeatures +from .models import simulation + + +class OutputFormats(Enum): + JSON ='JSON' + DATAFRAME = 'DATAFRAME' + DICT = 'DICT' + +class Base(): + + @declared_attr + def __tablename__(self) -> str: + cls_name = str(self.__name__) + return cls_name.lower() + + @classmethod + def from_dict(cls, attributes_dict:Dict) -> object: + """Alternative constructor that uses dictionary to populate attributes""" + instance = cls.__new__(cls) + instance.__init__() + for key, value in attributes_dict.items(): + if hasattr(instance, key): + if value == '': value = None + setattr(instance, key, value) + return instance + + def to_dict(self) -> Dict[str,Any]: + """Converts attributes into a dictionary""" + columns = self.__table__.columns.keys() + output_dict = {} + for column in columns: + output_dict[column] = getattr(self,column) + return output_dict + + def update_from_dict(self, attributes_dict:Dict[str, any]) -> None: + """Updates instance attributes based on provided dictionary""" + for key, value in attributes_dict.items(): + if hasattr(self, key): + if value == '': value = None + setattr(self, key, value) + + @classmethod + def get_pkey_name(cls) -> Union[str,None]: + """ Returns the primary key field name for a given model""" + columns = cls.__table__.columns + for column in columns: + if column.primary_key: return column.name + return None + +class ODM2Engine: + + def __init__(self, session_maker:sqlalchemy.orm.sessionmaker) -> None: + self.session_maker = session_maker + + def read_query(self, + query: Union[Query, Select], + output_format:OutputFormats=OutputFormats.JSON, + orient:str='records') -> Union[str, pd.DataFrame]: + with self.session_maker() as session: + if isinstance(query, Select): + df = pd.read_sql(query, session.bind) + else: + df = pd.read_sql(query.statement, session.bind) + + if output_format == OutputFormats.JSON: + return df.to_json(orient=orient) + elif output_format == OutputFormats.DATAFRAME: + return df + elif output_format == OutputFormats.DICT: + return df.to_dict() + raise TypeError("Unknown output format") + + def insert_query(self) -> None: + """Placeholder for bulk insert""" + #accept dataframe & model + #use pandas to_sql method to perform insert + #if except return false or maybe raise error + #else return true + raise NotImplementedError + + def create_object(self, obj:object) -> Union[int, str]: + pkey_name = obj.get_pkey_name() + setattr(obj, pkey_name, None) + + with self.session_maker() as session: + session.add(obj) + session.commit() + pkey_value = getattr(obj, pkey_name) + return pkey_value + + def read_object(self, model:Type[Base], pkey:Union[int, str], + output_format: OutputFormats=OutputFormats.DICT, + orient:str='records') -> Dict[str, Any]: + + with self.session_maker() as session: + obj = session.get(model, pkey) + pkey_name = model.get_pkey_name() + if obj is None: raise ObjectNotFound(f"No '{model.__name__}' object found with {pkey_name} = {pkey}") + session.commit() + + obj_dict = obj.to_dict() + if output_format == OutputFormats.DICT: + return obj_dict + + else: + # convert to series if only one row + keys = list(obj_dict.keys()) + if not isinstance(obj_dict[keys[0]], list): + for key in keys: + new_value = [obj_dict[key]] + obj_dict[key] = new_value + + obj_df = pd.DataFrame.from_dict(obj_dict) + if output_format == OutputFormats.DATAFRAME: + return obj_df + elif output_format == OutputFormats.JSON: + return obj_df.to_json(orient=orient) + raise TypeError("Unknown output format") + + + def update_object(self, model:Type[Base], pkey:Union[int,str], data:Dict[str, Any]) -> None: + if not isinstance(data, dict): + data = data.dict() + pkey_name = model.get_pkey_name() + if pkey_name in data: + data.pop(pkey_name) + with self.session_maker() as session: + obj = session.get(model, pkey) + if obj is None: raise ObjectNotFound(f"No '{model.__name__}' object found with {pkey_name} = {pkey}") + obj.update_from_dict(data) + session.commit() + + def delete_object(self, model:Type[Base], pkey:Union[int, str]) -> None: + with self.session_maker() as session: + obj = session.get(model, pkey) + pkey_name = model.get_pkey_name() + if obj is None: raise ObjectNotFound(f"No '{model.__name__}' object found with {pkey_name} = {pkey}") + session.delete(obj) + session.commit() + +class Models: + + def __init__(self, base_model) -> None: + self._base_model = base_model + self._process_schema(annotations) + self._process_schema(auth) + self._process_schema(core) + self._process_schema(cv) + self._process_schema(dataquality) + self._process_schema(equipment) + self._process_schema(extensionproperties) + self._process_schema(externalidentifiers) + self._process_schema(labanalyses) + self._process_schema(provenance) + self._process_schema(results) + self._process_schema(samplingfeatures) + self._process_schema(simulation) + + def _process_schema(self, schema:str) -> None: + classes = [c for c in dir(schema) if not c.startswith('__')] + base = tuple([self._base_model]) + for class_name in classes: + model = getattr(schema, class_name) + model_attribs = self._trim_dunders(dict(model.__dict__.copy())) + extended_model = type(class_name, base, model_attribs) + setattr(self, class_name, extended_model) + + def _trim_dunders(self, dictionary:Dict[str, Any]) -> Dict[str, Any]: + return { k:v for k, v in dictionary.items() if not k.startswith('__') } + +class ODM2DataModels(): + + def __init__(self, engine:sqlalchemy.engine, schema:str='odm2', cache_path:str=None) -> None: + + self._schema = schema + self._cache_path = cache_path + + self._engine = engine + self._session = sqlalchemy.orm.sessionmaker(self._engine) + self._cached= False + self.odm2_engine: ODM2Engine = ODM2Engine(self._session) + + self._model_base = self._prepare_model_base() + self.models = Models(self._model_base) + if not self._cached: + self._prepare_automap_models() + + def _prepare_model_base(self): + try: + with open(self._cache_path, 'rb') as file: + metadata = pickle.load(file=file) + self._cached = True + return declarative_base(cls=Base, bind=self._engine, metadata=metadata) + except FileNotFoundError: + metadata = sqlalchemy.MetaData(schema=self._schema) + self._cached = False + return automap_base(cls=Base, metadata=metadata) + + def _prepare_automap_models(self): + self._model_base.prepare(self._engine) + if not self._cache_path: return + try: + with open(self._cache_path, 'wb') as file: + pickle.dump(self._model_base.metadata, file) + except FileNotFoundError: + warnings.warn('Unable to cache models which may lead to degraded performance.', RuntimeWarning) \ No newline at end of file diff --git a/src/odm2datamodels/exceptions.py b/src/odm2datamodels/exceptions.py new file mode 100644 index 0000000..9cbd8d8 --- /dev/null +++ b/src/odm2datamodels/exceptions.py @@ -0,0 +1,5 @@ +class ObjectNotFound(Exception): + + def __init__(self, message:str) -> None: + self.message = message + super().__init__() \ No newline at end of file diff --git a/src/odm2datamodels/models/__init__.py b/src/odm2datamodels/models/__init__.py new file mode 100644 index 0000000..5d119aa --- /dev/null +++ b/src/odm2datamodels/models/__init__.py @@ -0,0 +1,13 @@ +from . import annotations +from . import auth +from . import core +from . import cv +from . import dataquality +from . import equipment +from . import extensionproperties +from . import externalidentifiers +from . import labanalyses +from . import provenance +from . import results +from . import samplingfeatures +from . import simulation \ No newline at end of file diff --git a/src/odm2datamodels/models/annotations.py b/src/odm2datamodels/models/annotations.py new file mode 100644 index 0000000..4b01507 --- /dev/null +++ b/src/odm2datamodels/models/annotations.py @@ -0,0 +1,45 @@ +"""Data models corresponding to the tables under the ODM2Annotations schema + Reference: http://odm2.github.io/ODM2/schemas/ODM2_Current/schemas/ODM2Annotations.html +""" + +class ActionAnnotations(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2Annotations_ActionAnnotations.html""" + +class Annotations(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2Annotations_Annotations.html""" + +class CategoricalResultValueAnnotations(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2Annotations_CategoricalResultValueAnnotations.html""" + +class EquipmentAnnotations(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2Annotations_EquipmentAnnotations.html""" + +class MethodAnnotations(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2Annotations_MethodAnnotations.html""" + +class PointCoverageResultValueAnnotations(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2Annotations_PointCoverageEesultValueAnnotations.html""" + +class ProfileResultValueAnnotations(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2Annotations_ProfileResultValueAnnotations.html""" + +class ResultAnnotations(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2Annotations_ResultAnnotations.html""" + +class SamplingFeatureAnnotations(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2Annotations_SamplingFeatureAnnotations.html""" + +class SectionResultValueAnnotations(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2Annotations_SectionResultValueAnnotations.html""" + +class SpectraResultValueAnnotations(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2Annotations_SpectraResultValueAnnotations.html""" + +class TimeSeriesResultValueAnnotations(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2Annotations_TimeSeriesResultValueAnnotations.html""" + +class TrajectoryResultValueAnnotations(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2Annotations_TrajectoryResultValueAnnotations.html""" + +class TransectResultValueAnnotations(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2Annotations_TransectResultValueAnnotations.html""" \ No newline at end of file diff --git a/src/odm2datamodels/models/auth.py b/src/odm2datamodels/models/auth.py new file mode 100644 index 0000000..af85dad --- /dev/null +++ b/src/odm2datamodels/models/auth.py @@ -0,0 +1,24 @@ +""" +""" + +class Accounts(): + """""" + +#PRT - even though this is a CV table it is for account auth so we might move this to the auth module. +class CV_Permission(): + """""" + +class OrganizationsPermissions(): + """""" + +class OrganizationsSamplingFeatures(): + """""" + +class ResultsPermissions(): + """""" + +class Roles(): + """""" + +class SamplingFeaturesPermissions(): + """""" diff --git a/src/odm2datamodels/models/core.py b/src/odm2datamodels/models/core.py new file mode 100644 index 0000000..1e4f7c7 --- /dev/null +++ b/src/odm2datamodels/models/core.py @@ -0,0 +1,51 @@ +"""Data models corresponding to the tables under the ODM2Core schema + Reference: http://odm2.github.io/ODM2/schemas/ODM2_Current/schemas/ODM2Core.html +""" + +class ActionBy(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2Core_ActionBy.html""" + +class Actions(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2Core_Actions.html""" + +class Affiliations(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2Core_Affiliations.html""" + +class Datasets(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2Core_Datasets.html""" + +class DatasetsResults(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2Core_DatasetsResults.html""" + +class FeatureActions(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2Core_FeatureActions.html""" + +class Methods(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2Core_Methods.html""" + +class Organizations(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2Core_Organizations.html""" + +class People(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2Core_People.html""" + +class ProcessingLevels(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2Core_ProcessingLevels.html""" + +class RelatedActions(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2Core_RelatedActions.html""" + +class Results(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2Core_Results.html""" + +class SamplingFeatures(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2Core_SamplingFeatures.html""" + +class TaxonomicClassifiers(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2Core_TaxonomicClassifiers.html""" + +class CV_Units(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2Core_Units.html""" + +class Variables(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2Core_Variables.html""" \ No newline at end of file diff --git a/src/odm2datamodels/models/cv.py b/src/odm2datamodels/models/cv.py new file mode 100644 index 0000000..3947aed --- /dev/null +++ b/src/odm2datamodels/models/cv.py @@ -0,0 +1,87 @@ +"""Data models corresponding to the tables under the ODM2CV schema + Reference: http://odm2.github.io/ODM2/schemas/ODM2_Current/schemas/ODM2CV.html +""" + +class CV_ActionType(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2CV_CV_ActionType.html""" + +class CV_AggregationStatistic(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2CV_CV_AggregationStatistic.html""" + +class CV_AnnotationType(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2CV_CV_AnnotationType.html""" + +class CV_CensorCode(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2CV_CV_CensorCode.html""" + +class CV_DataQualityType(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2CV_CV_DataQualityType.html""" + +class CV_DatasetType(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2CV_CV_DatasetType.html""" + +class CV_DirectiveType(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2CV_CV_DirectiveType.html""" + +class CV_ElevationDatum(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2CV_CV_ElevationDatum.html""" + +class CV_EquipmentType(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2CV_CV_EquipmentType.html""" + +class CV_FeaturesOfInterestType(): + """""" + +class CV_LBNLICPMSMapper(): + """""" + +class CV_Medium(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2CV_CV_Medium.html""" + +class CV_MethodType(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2CV_CV_MethodType.html""" + +class CV_OrganizationType(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2CV_CV_OrganizationType.html""" + +class CV_PropertyDataType(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2CV_CV_PropertyDataType.html""" + +class CV_QualityCode(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2CV_CV_QualityCode.html""" + +class CV_QuantityKind(): + """""" + +class CV_RelationshipType(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2CV_CV_RelationshipType.html""" + +class CV_ResultType(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2CV_CV_ResultType.html""" + +class CV_SamplingFeatureGeoType(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2CV_CV_SamplingFeatureGeoType.html""" + +class CV_SamplingFeatureType(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2CV_CV_SamplingFeatureType.html""" + +class CV_SpatialOffsetType(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2CV_CV_SpatialOffsetType.html""" + +class CV_SpecimenCollection(): + """""" + +class CV_SpecimenType(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2CV_CV_SpecimenType.html""" + +class CV_Status(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2CV_CV_Status.html""" + +class CV_TaxonomicClassifierDomain(): + """""" + +class CV_Units(): + """""" + +class CV_VariableDomain(): + """""" diff --git a/src/odm2datamodels/models/dataquality.py b/src/odm2datamodels/models/dataquality.py new file mode 100644 index 0000000..d29bb3e --- /dev/null +++ b/src/odm2datamodels/models/dataquality.py @@ -0,0 +1,18 @@ +"""Data models corresponding to the tables under the ODM2DataQuality schema + Reference: http://odm2.github.io/ODM2/schemas/ODM2_Current/schemas/ODM2DataQuality.html +""" + +class DataQuality(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2DataQuality_DataQuality.html""" + +class ReferenceMaterials(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2DataQuality_ReferenceMaterials.html""" + +class ReferenceMaterialValues(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2DataQuality_ReferenceMaterialValues.html""" + +class ResultNormalizationValues(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2DataQuality_ResultNormalizationValues.html""" + +class ResultsDataQuality(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2DataQuality_ResultsDataQuality.html""" \ No newline at end of file diff --git a/src/odm2datamodels/models/equipment.py b/src/odm2datamodels/models/equipment.py new file mode 100644 index 0000000..89fafa4 --- /dev/null +++ b/src/odm2datamodels/models/equipment.py @@ -0,0 +1,39 @@ +"""Data models corresponding to the tables under the ODM2Equipment schema + Reference: http://odm2.github.io/ODM2/schemas/ODM2_Current/schemas/ODM2Equipment.html +""" + +class CalibrationActions(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2Equipment_CalibrationActions.html""" + +class CalibrationReferenceEquipment(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2Equipment_CalibrationReferenceEquipment.html""" + +class CalibrationStandards(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2Equipment_CalibrationStandards.html""" + +class DataloggerFileColumns(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2Equipment_DataloggerFileColumns.html""" + +class DataLoggerFiles(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2Equipment_DataLoggerFiles.html""" + +class DataLoggerProgramFiles(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2Equipment_DataloggerProgramFiles.html""" + +class Equipment(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2Equipment_Equipment.html""" + +class EquipmentModels(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2Equipment_EquipmentModels.html""" + +class EquipmentUsed(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2Equipment_EquipmentUsed.html""" + +class InstrumentOutputQuantityKind(): + """""" + +class MaintenanceActions(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2Equipment_MaintenanceActions.html""" + +class RelatedEquipment(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2Equipment_RelatedEquipment.html""" \ No newline at end of file diff --git a/src/odm2datamodels/models/extensionproperties.py b/src/odm2datamodels/models/extensionproperties.py new file mode 100644 index 0000000..30dfe26 --- /dev/null +++ b/src/odm2datamodels/models/extensionproperties.py @@ -0,0 +1,24 @@ +"""Data models corresponding to the tables under the ODM2ExtensionProperties schema + Reference: http://odm2.github.io/ODM2/schemas/ODM2_Current/schemas/ODM2ExtensionProperties.html +""" + +class ActionExtensionPropertyValues(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2ExtensionProperties_ActionExtensionPropertyValues.html""" + +class CitationExtensionPropertyValues(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2ExtensionProperties_CitationExtensionPropertyValues.html""" + +class ExtensionProperties(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2ExtensionProperties_ExtensionProperties.html""" + +class MethodExtensionPropertyValues(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2ExtensionProperties_MethodExtensionPropertyValues.html""" + +class ResultExtensionPropertyValues(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2ExtensionProperties_ResultExtensionPropertyValues.html""" + +class SamplingFeatureExtensionPropertyValues(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2ExtensionProperties_SamplingFeatureExtensionPropertyValues.html""" + +class VariableExtensionPropertyValues(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2ExtensionProperties_VariableExtensionPropertyValues.html""" diff --git a/src/odm2datamodels/models/externalidentifiers.py b/src/odm2datamodels/models/externalidentifiers.py new file mode 100644 index 0000000..96a17db --- /dev/null +++ b/src/odm2datamodels/models/externalidentifiers.py @@ -0,0 +1,30 @@ +"""Data models corresponding to the tables under the ODM2Core schema + Reference: http://odm2.github.io/ODM2/schemas/ODM2_Current/schemas/ODM2ExternalIdentifiers.html +""" + +class CitationExternalIdentifiers(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2ExternalIdentifiers_CitationExternalIdentifiers.html""" + +class ExternalIdentifierSystems(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2ExternalIdentifiers_ExternalIdentifierSystems.html""" + +class MethodExternalIdentifiers(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2ExternalIdentifiers_MethodExternalIdentifiers.html""" + +class PersonExternalIdentifiers(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2ExternalIdentifiers_PersonExternalIdentifiers.html""" + +class ReferenceMaterialExternalIdentifiers(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2ExternalIdentifiers_ReferenceMaterialExternalIdentifiers.html""" + +class SamplingFeatureExternalIdentifiers(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2ExternalIdentifiers_SamplingFeatureExternalIdentifiers.html""" + +class SpatialReferenceExternalIdentifiers(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2ExternalIdentifiers_SpatialReferenceExternalIdentifiers.html""" + +class TaxonomicClassifierExternalIdentifiers(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2ExternalIdentifiers_TaxonomicClassifierExternalIdentifiers.html""" + +class VariableExternalIdentifiers(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2ExternalIdentifiers_VariableExternalIdentifiers.html""" \ No newline at end of file diff --git a/src/odm2datamodels/models/labanalyses.py b/src/odm2datamodels/models/labanalyses.py new file mode 100644 index 0000000..f07f16c --- /dev/null +++ b/src/odm2datamodels/models/labanalyses.py @@ -0,0 +1,12 @@ +"""Data models corresponding to the tables under the ODM2LabAnalyses schema + Reference: http://odm2.github.io/ODM2/schemas/ODM2_Current/schemas/ODM2LabAnalyses.html +""" + +class ActionDirectives(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2LabAnalyses_ActionDirectives.html""" + +class Directives(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2LabAnalyses_Directives.html""" + +class SpecimenBatchPositions(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2LabAnalyses_SpecimenBatchPostions.html""" \ No newline at end of file diff --git a/src/odm2datamodels/models/provenance.py b/src/odm2datamodels/models/provenance.py new file mode 100644 index 0000000..d3af104 --- /dev/null +++ b/src/odm2datamodels/models/provenance.py @@ -0,0 +1,30 @@ +"""Data models corresponding to the tables under the ODM2Provenance schema + Reference: http://odm2.github.io/ODM2/schemas/ODM2_Current/schemas/ODM2Provenance.html +""" + +class AuthorLists(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2Provenance_AuthorLists.html""" + +class Citations(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2Provenance_Citations.html""" + +class DataSetCitations(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2Provenance_DatasetCitations.html""" + +class DerivationEquations(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2Provenance_DerivationEquations.html""" + +class MethodCitations(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2Provenance_MethodCitations.html""" + +class RelatedAnnotations(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2Provenance_RelatedAnnotations.html""" + +class RelatedDatasets(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2Provenance_RelatedDatasets.html""" + +class RelatedResults(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2Provenance_RelatedResults.html""" + +class ResultDerivationEquations(): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2Provenance_ResultDerivationEquations.html""" \ No newline at end of file diff --git a/src/odm2datamodels/models/results.py b/src/odm2datamodels/models/results.py new file mode 100644 index 0000000..f87e004 --- /dev/null +++ b/src/odm2datamodels/models/results.py @@ -0,0 +1,57 @@ +"""Data models corresponding to the tables under the ODM2Results schema + Reference: http://odm2.github.io/ODM2/schemas/ODM2_Current/schemas/ODM2Results.html +""" + +class CategoricalResults (): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2Results_CategoricalResults.html""" + +class CategoricalResultValues (): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2Results_CategoricalResultValues.html""" + +class MeasurementResults (): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2Results_MeasurementResults.html""" + +class MeasurementResultValues (): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2Results_MeasurementResultValues.html""" + +class PointCoverageResults (): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2Results_PointCoverageResults.html""" + +class PointCoverageResultValues (): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2Results_PointCoverageResultValues.html""" + +class ProfileResults (): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2Results_ProfileResults.html""" + +class ProfileResultValues (): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2Results_ProfileResultValues.html""" + +class SectionResults (): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2Results_SectionResults.html""" + +class SectionResultValues (): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2Results_SectionResultValues.html""" + +class SpectraResults (): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2Results_SpectraResults.html""" + +class SpectraResultValues (): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2Results_SpectraResultValues.html""" + +class TimeSeriesResults (): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2Results_TimeSeriesResults.html""" + +class TimeSeriesResultValues (): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2Results_TimeSeriesResultValues.html""" + +class TrajectoryResults (): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2Results_TrajectoryResults.html""" + +class TrajectoryResultValues (): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2Results_TrajectoryResultValues.html""" + +class TransectResults (): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2Results_TransectResults.html""" + +class TransectResultValues (): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2Results_TransectResultValues.html""" \ No newline at end of file diff --git a/src/odm2datamodels/models/samplingfeatures.py b/src/odm2datamodels/models/samplingfeatures.py new file mode 100644 index 0000000..67b6a12 --- /dev/null +++ b/src/odm2datamodels/models/samplingfeatures.py @@ -0,0 +1,27 @@ +"""Data models corresponding to the tables under the ODM2SamplingFeatures schema + Reference: http://odm2.github.io/ODM2/schemas/ODM2_Current/schemas/ODM2SamplingFeatures.html +""" + +class FeaturesOfInterest(): + """""" + +class RelatedFeatures (): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2SamplingFeatures_RelatedFeatures.html""" + +class SampledFeatures(): + """""" + +class SpatialOffsets (): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2SamplingFeatures_SpatialOffsets.html""" + +class SpatialReferences (): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2SamplingFeatures_SpatialReferences.html""" + +class SpecimenCollections (): + """""" + +class Specimens (): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2SamplingFeatures_Specimens.html""" + +class SpecimenTaxonomicClassifiers (): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2SamplingFeatures_SpecimenTaxonomicClassifiers.html""" \ No newline at end of file diff --git a/src/odm2datamodels/models/simulation.py b/src/odm2datamodels/models/simulation.py new file mode 100644 index 0000000..75097b2 --- /dev/null +++ b/src/odm2datamodels/models/simulation.py @@ -0,0 +1,15 @@ +"""Data models corresponding to the tables under the ODM2Simulation schema + Reference: http://odm2.github.io/ODM2/schemas/ODM2_Current/schemas/ODM2Simulation.html +""" + +class ModelAffiliations (): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2Simulation_ModelAffiliations.html""" + +class Models (): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2Simulation_Models.html""" + +class RelatedModels (): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2Simulation_RelatedModels.html""" + +class Simulations (): + """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2Simulation_Simulations.html""" \ No newline at end of file From fa92e69dcc623d79b13a73b7120b3fdda71f35bc Mon Sep 17 00:00:00 2001 From: Paul Tomasula <31142705+ptomasula@users.noreply.github.com> Date: Wed, 20 Apr 2022 16:53:15 -0400 Subject: [PATCH 2/4] Add draft readme --- README.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/README.md b/README.md index e69de29..07326d7 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,21 @@ +# ODM2DataModels + +## What is this? +odm2datamodels is a Python package that provides a set of object-relational mapping (ORM) data models for the [Observations Data Model Version 2.1](http://www.odm2.org/). This data models are built of the [SQLAlchemy](https://www.sqlalchemy.org/) and provide a convenient way of interfacing with an ODM2.1 database. + +## Core Features +The primary is the `ODM2DataModels` class, which once instantiated, provides access to the set of ORM ODM2.1 data models and an instance of an ODM2Engine which provide utility function to perform basic Create, Read, Update, Delete operations as well are read execution of custom SQLQueries constructed using a SQLAlchemy [Select object](https://docs.sqlalchemy.org/en/14/orm/queryguide.html#select-statements) or [Query Object](https://docs.sqlalchemy.org/en/14/orm/query.html#sqlalchemy.orm.Query) + +## How to install? +Presently the build files are only available on our [github repository](https://github.com/ODM2/ODM2DataModels) + +Though we are aiming to release to the [Python Package Index (PyPI)](https://pypi.org/) and [Conda](https://docs.conda.io/en/latest/) in the near future. + +## Testing and Database Dialect Support +### Testing Method +Presently very limited testing has been conducted and has primarily been through an implementation of a REST API with limited coverage of selected data models. Further expanding and automating testing is an area for future updates. +### Database Dialect Support +These data models have only been validated for a PostgreSQL database running a deployment of the ODM2.1 schema. + + + From f3af1397cb79f17d6c0bbcaf0903c5481f70f9b1 Mon Sep 17 00:00:00 2001 From: Paul Tomasula <31142705+ptomasula@users.noreply.github.com> Date: Wed, 20 Apr 2022 17:48:25 -0400 Subject: [PATCH 3/4] Add in dependencies --- setup.cfg | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index e72d12d..f57c0b2 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,4 @@ + [metadata] name = odm2datamodels description = "Collection of object-relational mapping (ORM) data models for ODM2" @@ -5,7 +6,7 @@ long_description = file: README.md long_description_content = text/markdown version = 0.0.1 author = "ODM2 Team" -author_email = "ptomasula@limno.com" +author_email = "" url = https://github.com/ODM2/ODM2DataModels project_urls = bugtracker = https://github.com/ODM2/ODM2DataModels/issueshttps://github.com/pypa/sampleproject/issues @@ -13,6 +14,11 @@ keywords='Observations Data Model ODM2' package_dir = = src packages = find: + +install_requires = + sqlalchemy>=1.4.32 + pandas>=1.4 + geoalchemy2>=0.6.3 python_requires = >=3.8 [options.packages.find] From 73d7bfdffb3794d65af46497b28efac79ac97dec Mon Sep 17 00:00:00 2001 From: Paul Tomasula <31142705+ptomasula@users.noreply.github.com> Date: Thu, 21 Apr 2022 13:09:57 -0400 Subject: [PATCH 4/4] Resolve dependencies not installing --- setup.cfg | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/setup.cfg b/setup.cfg index f57c0b2..015f3d2 100644 --- a/setup.cfg +++ b/setup.cfg @@ -11,15 +11,17 @@ url = https://github.com/ODM2/ODM2DataModels project_urls = bugtracker = https://github.com/ODM2/ODM2DataModels/issueshttps://github.com/pypa/sampleproject/issues keywords='Observations Data Model ODM2' + +[options] +packages = find: package_dir = = src -packages = find: +python_requires = >=3.8 install_requires = sqlalchemy>=1.4.32 pandas>=1.4 geoalchemy2>=0.6.3 -python_requires = >=3.8 [options.packages.find] where = src \ No newline at end of file