Skip to content

Commit

Permalink
Merge branch 'dev' of github.com:AAFC-BICoE/dina-py into dev
Browse files Browse the repository at this point in the history
  • Loading branch information
brandonandre committed Dec 16, 2024
2 parents 0f6d70e + c5d0722 commit 78ffaf0
Show file tree
Hide file tree
Showing 7 changed files with 274 additions and 2 deletions.
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ Python access to the DINA API
- Storage Unit
- Form Template
- Split Configuration
- Metagenomics Batch
- Metagenomics Batch Item
- Molecular Analysis Result
- Molecular Analysis Run
- Molecular Analysis Run Item
- Project
#### DINA APIs
- Currently supported APIs:
- Agent API
Expand All @@ -26,12 +32,18 @@ Python access to the DINA API
- SplitConfiguration
- MaterialSample
- Organism
- Project
- Object Store API
- Any Object Store API endpoint using ObjectStoreApi's CRUD methods
- SeqDB API
- PCR Batch
- PCR Batch Item
- SEQ Reaction
- Metagenomics Batch
- Metagenomics Batch Item
- Molecular Analysis Result
- Molecular Analysis Run
- Molecular Analysis Run Item
- Export API
- Any Export API endpoint using DinaExportApi's CRUD methods

Expand Down Expand Up @@ -84,6 +96,12 @@ Python access to the DINA API
- Person
- Split Configuration
- Storage Unit Usage
- Metagenomics Batch
- Metagenomics Batch Item
- Molecular Analysis Result
- Molecular Analysis Run
- Molecular Analysis Run Item
- Project

## Installation Instructions
### Local Install
Expand Down
9 changes: 9 additions & 0 deletions dinapy/apis/collectionapi/project_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
"""Class that extracts common functionality for Project entity"""

from .collectionapi import CollectionModuleApi

class ProjectAPI(CollectionModuleApi):

def __init__(self, config_path: str = None, base_url: str = None) -> None:
super().__init__(config_path, base_url)
self.base_url += "collecting-event"
103 changes: 103 additions & 0 deletions dinapy/entities/Project.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
class ProjectDTO:
def __init__(self, id = None, type = 'project', attributes = None, relationships = 'undefined'):
self.id = id
self.type = type
self.attributes = attributes
self.relationships = relationships

def get_id(self):
return self.id

def get_type(self):
return self.type

class ProjectDTOBuilder:
def __init__(self):
self._id = None
self._type = 'project'
self._attributes = None
self._relationships = None

def attributes(self, attributes):
self._attributes = attributes
return self

def relationships(self, relationships):
self._relationships = relationships
return self

def build(self):
return ProjectDTO(self._id, self._type, self._attributes, self._relationships)

class ProjectAttributesDTO:
def __init__(self, createdOn = 'undefined', createdBy = 'undefined', group = 'undefined', name = 'undefined', startDate = 'undefined', endDate = 'undefined', status = 'undefined', multilingualDescription = 'undefined', extensionValues = 'undefined'):
self.createdOn = createdOn
self.createdBy = createdBy
self.group = group
self.name = name
self.startDate = startDate
self.endDate = endDate
self.status = status
self.multilingualDescription = multilingualDescription
self.extensionValues = extensionValues

class ProjectAttributesDTOBuilder:
def __init__(self):
self._createdOn = 'undefined'
self._createdBy = 'undefined'
self._group = 'undefined'
self._name = 'undefined'
self._startDate = 'undefined'
self._endDate = 'undefined'
self._status = 'undefined'
self._multilingualDescription = 'undefined'
self._extensionValues = 'undefined'

def createdOn(self, createdOn):
self._createdOn = createdOn
return self

def createdBy(self, createdBy):
self._createdBy = createdBy
return self

def group(self, group):
self._group = group
return self

def name(self, name):
self._name = name
return self

def startDate(self, startDate):
self._startDate = startDate
return self

def endDate(self, endDate):
self._endDate = endDate
return self

def status(self, status):
self._status = status
return self

def multilingualDescription(self, multilingualDescription):
self._multilingualDescription = multilingualDescription
return self

def extensionValues(self, extensionValues):
self._extensionValues = extensionValues
return self

def build(self):
return ProjectAttributesDTO(
self._createdOn,
self._createdBy,
self._group,
self._name,
self._startDate,
self._endDate,
self._status,
self._multilingualDescription,
self._extensionValues
)
2 changes: 1 addition & 1 deletion dinapy/schemas/molecular_analysis_result_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

class Attachment(BaseSchema):
class Meta:
type_ = 'attacment'
type_ = 'attachment'

class MolecularAnalysisResultSchema(Schema):
id = fields.Str(load_only=True)
Expand Down
61 changes: 61 additions & 0 deletions dinapy/schemas/project_schema.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# This file holds schemas for serializing and deserializing Project entities
# using the JSON API format. It utilizes the marshmallow_jsonapi library.
from marshmallow import post_load
from marshmallow_jsonapi import Schema, fields
from dinapy.schemas.materialsampleschema import *
from dinapy.entities.Project import ProjectDTO
from .customFields import SkipUndefinedField
from .BaseSchema import *

class Attachment(BaseSchema):
class Meta:
type_ = 'attachment'

class ProjectSchema(Schema):
id = fields.Str(load_only=True)
createdBy = SkipUndefinedField(fields.Str,attribute="attributes.createdBy")
createdOn = SkipUndefinedField(fields.DateTime,load_only=True,attribute="attributes.createdOn")
group = SkipUndefinedField(fields.Str,attribute="attributes.group")
name = SkipUndefinedField(fields.Str,required=True,attribute="attributes.name")
startDate = SkipUndefinedField(fields.Str,allow_none=True,attribute="attributes.startDate")
endDate = SkipUndefinedField(fields.Str,allow_none=True,attribute="attributes.endDate")
status = SkipUndefinedField(fields.Str,allow_none=True,attribute="attributes.status")
multilingualDescription = SkipUndefinedField(fields.Dict,allow_none=True,attribute="attributes.multilingualDescription")
extensionValues = SkipUndefinedField(fields.Dict,allow_none=True,attribute="attributes.extensionValues")

attachment = create_relationship("project","attachment")

meta = fields.DocumentMeta()

class Meta:
type_ = "project"

@post_dump
def remove_skipped_fields(self, data, many, **kwargs):
# Remove fields with the special marker value
return {key: value for key, value in data.items() if value is not SkipUndefinedField(fields.Field).SKIP_MARKER}

@post_dump
def remove_meta(self, data, many, **kwargs):
if 'meta' in data:
del(data['meta'])
return data

@post_load
def set_none_to_undefined(self, data, **kwargs):
for attr in data.attributes:
if data.attributes[attr] is None:
data.attributes[attr] = 'undefined'
return data

@post_load
def object_deserialization(self, data, **kwargs):
if 'meta' in data:
del data['meta']
return ProjectDTO(**data)

def dump(self, obj, many=None, *args, **kwargs):
data = super().dump(obj, many=many, *args, **kwargs)
if 'meta' in data:
del data['meta']
return data
4 changes: 3 additions & 1 deletion examples/external-resource-import-demo/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,6 @@ Copy the .txt file containing the External URLs to be uploaded into the current
Then modify (as needed) and run the **external_resource_importer.py** script.

An output file called **external_url_uuids.txt** should have been created containing the generated UUIDs for each External Resource.
A similar file called material_sample_uuids.txt should also appear for the created Material Samples.
A similar file called **material_sample_uuids.txt** should also appear for the created Material Samples.

Optionally, you can also link all creted Material Samples using **link_samples_to_project.py** which reads **material_sample_uuids.txt** and links them to a Project in DINA through a given Project UUID.
79 changes: 79 additions & 0 deletions tests/project_schema_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# This file contains tests relating to ProjectAPI.
# Currently only contains tests for the ProjectSchema (serialization and deserialization tests).
# API mock call tests should be added.

import unittest
from marshmallow.exceptions import ValidationError

import sys
import os

# Add the root directory of the project to the Python path
project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, project_root)

# Import modules from the dinaapi package
from dinapy.schemas.project_schema import ProjectSchema
from dinapy.entities.Project import *

VALID_PROJECT_DATA = {
"data": {
"id": "01939377-b15d-75f6-b405-54d9e719a509",
"type": "project",
"attributes": {
"createdOn": "2024-12-04T20:58:35.237491Z",
"createdBy": "dina-admin",
"group": "aafc",
"name": "test project",
"startDate": "2024-05-01",
"endDate": "2025-12-04",
"status": "Open",
"multilingualDescription": {
"descriptions": [
{
"lang": "en",
"desc": "test decription"
}
]
},
"extensionValues": {}
},
"relationships": {}
}
}

class ProjectSchemaTest(unittest.TestCase):
def test_deserialize_project(self):
schema = ProjectSchema()
try:
result = schema.load(VALID_PROJECT_DATA)
print(result.__dict__)
self.assertIsInstance(result, ProjectDTO)
except ValidationError as e:
self.fail(f"Validation failed with error: {e.messages}")

def test_serialize_project(self):
schema = ProjectSchema()
project_attributes = ProjectAttributesDTOBuilder(
).name("test project").group("aafc").build()
project = ProjectDTOBuilder().attributes(project_attributes).build()

try:
serialized_project = schema.dump(project)
expected = {
"data": {
"type": "project",
"attributes": {
"name": "test project",
"group": "aafc"
}
}
}
print(serialized_project)
self.assertIsInstance(serialized_project, dict)
self.assertDictEqual(serialized_project, expected)
except ValidationError as e:
self.fail(f"Validation failed with error: {e.messages}")

if __name__ == "__main__":
unittest.main()

0 comments on commit 78ffaf0

Please sign in to comment.