Skip to content

Commit

Permalink
merging develop into issue-497-fix
Browse files Browse the repository at this point in the history
  • Loading branch information
proccaserra committed Oct 23, 2023
2 parents 42b5d4b + b2ecfb0 commit 4f41ecd
Show file tree
Hide file tree
Showing 32 changed files with 174 additions and 87 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/buildandtestpython.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ jobs:
python-version: [3.8, 3.9, '3.10', '3.11']

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Download Test Data
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/pythonpublish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v2
uses: actions/setup-python@v4
with:
python-version: '3.x'
- name: Install dependencies
Expand Down
24 changes: 15 additions & 9 deletions isatools/isatab/dump/write.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,9 @@ def flatten(current_list):
log.warning(s_graph.nodes())

sample_in_path_count = 0
protocol_in_path_count = 0
longest_path = _longest_path_and_attrs(paths, s_graph.indexes)

for node_index in longest_path:
node = s_graph.indexes[node_index]
if isinstance(node, Source):
Expand All @@ -76,8 +77,9 @@ def flatten(current_list):
map(lambda x: get_comment_column(
olabel, x), node.comments))
elif isinstance(node, Process):
olabel = "Protocol REF.{}".format(node.executes_protocol.name)
olabel = "Protocol REF.{}".format(protocol_in_path_count)
columns.append(olabel)
protocol_in_path_count += 1
if node.executes_protocol.name not in protnames.keys():
protnames[node.executes_protocol.name] = protrefcount
protrefcount += 1
Expand All @@ -104,6 +106,7 @@ def flatten(current_list):
columns += flatten(map(lambda x: get_fv_columns(olabel, x),
node.factor_values))


omap = get_object_column_map(columns, columns)
# load into dictionary
df_dict = dict(map(lambda k: (k, []), flatten(omap)))
Expand All @@ -113,6 +116,7 @@ def flatten(current_list):
df_dict[k].extend([""])

sample_in_path_count = 0
protocol_in_path_count = 0
for node_index in path_:
node = s_graph.indexes[node_index]
if isinstance(node, Source):
Expand All @@ -129,8 +133,8 @@ def flatten(current_list):
df_dict[colabel][-1] = co.value

elif isinstance(node, Process):
olabel = "Protocol REF.{}".format(
node.executes_protocol.name)
olabel = "Protocol REF.{}".format(protocol_in_path_count)
protocol_in_path_count += 1
df_dict[olabel][-1] = node.executes_protocol.name
for pv in node.parameter_values:
pvlabel = "{0}.Parameter Value[{1}]".format(
Expand Down Expand Up @@ -263,6 +267,8 @@ def flatten(current_list):
if _longest_path_and_attrs(paths, a_graph.indexes) is None:
raise IOError(
"Could not find any valid end-to-end paths in assay graph")

protocol_in_path_count = 0
for node_index in _longest_path_and_attrs(paths, a_graph.indexes):
node = a_graph.indexes[node_index]
if isinstance(node, Sample):
Expand All @@ -279,9 +285,9 @@ def flatten(current_list):
node.factor_values))

elif isinstance(node, Process):
olabel = "Protocol REF.{}".format(
node.executes_protocol.name)
olabel = "Protocol REF.{}".format(protocol_in_path_count)
columns.append(olabel)
protocol_in_path_count += 1
if node.executes_protocol.name not in protnames.keys():
protnames[node.executes_protocol.name] = protrefcount
protrefcount += 1
Expand Down Expand Up @@ -338,12 +344,12 @@ def pbar(x):
for k in df_dict.keys(): # add a row per path
df_dict[k].extend([""])

protocol_in_path_count = 0
for node_index in path_:
node = a_graph.indexes[node_index]
if isinstance(node, Process):
olabel = "Protocol REF.{}".format(
node.executes_protocol.name
)
olabel = "Protocol REF.{}".format(protocol_in_path_count)
protocol_in_path_count += 1
df_dict[olabel][-1] = node.executes_protocol.name
if node.executes_protocol.protocol_type:
oname_label = get_column_header(
Expand Down
76 changes: 63 additions & 13 deletions isatools/model/context.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
from __future__ import annotations

from os import path
from re import sub
from abc import ABCMeta
import requests
from json import loads

from isatools.model.identifiable import Identifiable

Expand All @@ -12,32 +16,53 @@
EXCEPTIONS = {
'OntologySource': 'OntologySourceReference',
'Characteristic': 'MaterialAttributeValueNumber',
'StudyFactor': 'Factor'
'StudyFactor': 'Factor',
'DataFile': "Data",
'RawDataFile': "RawData"
}


def get_name(name):
def get_name(name: str) -> str:
""" Get the name of the class to include in the context name.
:param name: the name of the class.
:return: the name of the class to include in the context name.
"""
if name in EXCEPTIONS:
return EXCEPTIONS[name]
return name


def camelcase2snakecase(camelcase: str) -> str:
""" Convert a camelcase string to snakecase.
:param camelcase: the camelcase string to convert
:return: the snakecase string
"""
return sub(r'(?<!^)(?=[A-Z])', '_', camelcase).lower()


def gen_id(classname: str) -> str:
""" Generate an identifier based on the class name
:param classname: the name of the class
:return: the identifier
"""
from uuid import uuid4
prefix = '#' + camelcase2snakecase(classname) + '/'
return prefix + str(uuid4())


class ContextPath:
"""
A class to manage the context of the JSON-LD serialization. This should not be used directly. Use the `context`
object and `set_context()` function instead.
"""

def __init__(self):
def __init__(self) -> None:
""" Initialize the context path. """
self.__context = 'obo'
self.all_in_one = True
self.local = True
self.include_contexts = False
self.contexts = {}
self.get_context()
self.prepend_url = None

Expand All @@ -46,14 +71,17 @@ def context(self) -> str:
return self.__context

@context.setter

def context(self, val: str) -> None:
allowed_context = ['obo', 'sdo', 'wd', 'sio']
if val not in allowed_context:
raise ValueError('Context name must be one in %s but got %s' % (allowed_context, val))
self.__context = val

def get_context(self, classname: str = 'allinone'):
def get_context(self, classname: str = 'allinone') -> str | dict:
""" Get the context needed to serialize ISA to JSON-LD. Will either return a URL to the context of resolve the
context and include it in the instance.
:param classname: the name of the class to get the context for.
"""
classname = get_name(classname)
classname = camelcase2snakecase(classname)
name = self.__context
Expand All @@ -62,12 +90,25 @@ def get_context(self, classname: str = 'allinone'):
if self.all_in_one:
filename = 'isa_allinone_%s_context.jsonld' % name

return path.join(path_source, name, filename) if self.local else path_source + filename

def __repr__(self):
context_path = path.join(path_source, filename) if self.local else path_source + filename
return context_path if not self.include_contexts else self.load_context(context_path)

def load_context(self, context_path: str) -> dict:
"""
Load the context from the given path or URL. If the context is already loaded, return it.
:param context_path: the path or URL to the context.
"""
if context_path in self.contexts:
return self.contexts[context_path]
if self.local:
with open(context_path, 'r') as f:
return loads(f.read())
return requests.get(context_path).json()

def __repr__(self) -> str:
return self.__context

def __str__(self):
def __str__(self) -> str:
return self.__context


Expand Down Expand Up @@ -96,7 +137,9 @@ def set_context(


class LDSerializable(metaclass=ABCMeta):
def __init__(self):
""" A mixin used by ISA objects to provide utility methods for JSON-LD serialization. """

def __init__(self) -> None:
self.context = context

def gen_id(self) -> str:
Expand All @@ -107,17 +150,24 @@ def gen_id(self) -> str:
return self.id if self.id.startswith('http') else prepend + self.id
return prepend + gen_id(self.__class__.__name__)

def get_context(self):
def get_context(self) -> str | dict:
""" Get the context for the object. """
return self.context.get_context(classname=self.__class__.__name__)

def get_ld_attributes(self):
def get_ld_attributes(self) -> dict:
""" Generate and return the LD attributes for the object. """
return {
'@type': get_name(self.__class__.__name__).replace('Number', ''),
'@context': self.get_context(),
'@id': self.gen_id()
}

def update_isa_object(self, isa_object, ld=False):
def update_isa_object(self, isa_object, ld=False) -> object:
""" Update the ISA object with the LD attributes if necessary. Needs to be called
after serialization the object.
:param isa_object: the ISA object to update.
:param ld: if True, update the object with the LD attributes, else return the object before injection
"""
if not ld:
return isa_object
isa_object.update(self.get_ld_attributes())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
"description": "JSON Schema describing an ISA model Assay object",
"type": "object",
"properties": {
"@id": { "type": "string", "format": "uri" },
"@context": { "type": "string", "format": "uri"},
"@id": { "type": "string", "format": "uri-reference" },
"@context": { "type": "string", "format": "uri-reference"},
"@type" : { "type": "string", "enum": ["Assay"] },
"filename" : { "type" : "string" },
"measurementType" : {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
"description": "JSON-schema representing an ISA model Comment object",
"type": "object",
"properties": {
"@id": { "type": "string", "format": "uri" },
"@context": { "type": "string", "format": "uri"},
"@id": { "type": "string", "format": "uri-reference" },
"@context": { "type": "string", "format": "uri-reference"},
"@type" : { "type": "string", "enum": ["Comment"] },
"name": {
"type": "string"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
"description": "JSON-schema representing an ISA model Data object",
"type": "object",
"properties": {
"@id": { "type": "string", "format": "uri" },
"@context": { "type": "string", "format": "uri"},
"@id": { "type": "string", "format": "uri-reference" },
"@context": { "type": "string", "format": "uri-reference"},
"@type" : { "type": "string", "enum": ["Data"] },
"name": {
"type": "string"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
"description": "JSON-schema representing an ISA model Factor object",
"type": "object",
"properties": {
"@id": { "type": "string", "format": "uri" },
"@context": { "type": "string", "format": "uri"},
"@id": { "type": "string", "format": "uri-reference" },
"@context": { "type": "string", "format": "uri-reference"},
"@type" : { "type": "string", "enum": ["Factor"] },
"factorName": {
"type": "string"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
"description": "JSON-schema representing an ISA model Factor Value object",
"type": "object",
"properties": {
"@id": { "type": "string", "format": "uri" },
"@context": { "type": "string", "format": "uri"},
"@type" : { "type": "string", "enum": ["FactorValue"] },
"@id": { "type": "string", "format": "uri-reference" },
"@context": { "type": "string", "format": "uri-reference"},
"@type" : { "type": "string", "enum": ["FactorValue"] },
"category" : {
"$ref": "factor_schema.json#"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
"description" : "JSON-schema representing an ISA mode Investigation object",
"type" : "object",
"properties" : {
"@id": { "type": "string", "format": "uri" },
"@context": { "type": "string", "format": "uri"},
"@id": { "type": "string", "format": "uri-reference" },
"@context": { "type": "string", "format": "uri-reference"},
"@type" : { "type": "string", "enum": ["Investigation"] },
"filename": { "type" : "string"},
"identifier" : { "type" : "string" },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
"description" : "JSON-schema representing a characteristics category (what appears between the brackets in Characteristics[]) in the ISA model",
"type" : "object",
"properties" : {
"@id": { "type": "string", "format": "uri" },
"@context": { "type": "string", "format": "uri"},
"@id": { "type": "string", "format": "uri-reference" },
"@context": { "type": "string", "format": "uri-reference"},
"@type" : { "type": "string", "enum": ["MaterialAttribute"] },
"characteristicType": {
"$ref": "ontology_annotation_schema.json#"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
"description" : "JSON-schema representing an ISA model material attribute (or characteristic) value object",
"type" : "object",
"properties" : {
"@id": { "type": "string", "format": "uri" },
"@context": { "type": "string", "format": "uri"},
"@id": { "type": "string", "format": "uri-reference" },
"@context": { "type": "string", "format": "uri-reference"},
"@type" : { "type": "string", "enum": ["MaterialAttributeValue"] },
"category" : {
"$ref": "material_attribute_schema.json#"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
"description" : "JSON-schema representing an ISA model material object, which is not a source or a sample (as they have specific schemas) - this will correspond to 'Extract Name', 'Labeled Extract Name'",
"type" : "object",
"properties" : {
"@id": { "type": "string", "format": "uri" },
"@context": { "type": "string", "format": "uri"},
"@id": { "type": "string", "format": "uri-reference" },
"@context": { "type": "string", "format": "uri-reference"},
"@type" : { "type": "string", "enum": ["Material"] },
"name" : { "type" : "string" },
"type": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
"description" : "JSON-schema representing an ISA model Ontology Reference or annotation (for fields that are required to be ontology annotations)",
"type" : "object",
"properties" : {
"@id": { "type": "string", "format": "uri" },
"@context": { "type": "string", "format": "uri"},
"@id": { "type": "string", "format": "uri-reference" },
"@context": { "type": "string", "format": "uri-reference"},
"@type" : { "type": "string", "enum": ["OntologyAnnotation"] },
"annotationValue": {
"anyOf": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
"description" : "JSON-schema representing an ISA model ontology reference object",
"type" : "object",
"properties" : {
"@id": { "type": "string", "format": "uri" },
"@context": { "type": "string", "format": "uri"},
"@id": { "type": "string", "format": "uri-reference"},
"@context": { "type": "string", "format": "uri-reference"},
"@type" : { "type": "string", "enum": ["OntologySourceReference"] },
"comments": {
"type": "array",
Expand Down
Loading

0 comments on commit 4f41ecd

Please sign in to comment.