Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for instances with v4 namespace #26

Merged
merged 9 commits into from
Oct 28, 2024
5 changes: 2 additions & 3 deletions .github/workflows/update.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@ name: Update openMINDS_MATLAB
on:
# Triggers the workflow on push or pull request events for the "main" branch
push:
branches: [ "main" ]
branches-ignore: ["pipeline"]
paths-ignore:
- 'README.md'
- 'dev/README.md'
- '*README.md'
- '.github/workflows/**'
- 'docs/reports/**'
pull_request:
Expand Down
16 changes: 14 additions & 2 deletions code/+openminds/getSchemaVersion.m
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
function versionStr = getSchemaVersion()
function versionNum = getSchemaVersion(outputType)

arguments
% outputType - char or VersionNumber. char is default to keep
% backwards compatibility. Todo: Deprecate char option
outputType (1,1) string ...
{mustBeMember(outputType, ["char", "VersionNumber"])} = "char"
end

schemaFolder = fullfile(openminds.internal.rootpath, 'schemas/');
pathSplit = strsplit(path, pathsep);
Expand All @@ -9,5 +16,10 @@
warning('Multiple schema versions are on path');
end

versionStr = strrep(pathSplit{matchedIdx(1)}, schemaFolder, '');
versionNum = strrep(pathSplit{matchedIdx(1)}, schemaFolder, '');

if outputType == "VersionNumber"
versionNum = openminds.internal.utility.VersionNumber(versionNum);
versionNum.Format = "vX.Y";
end
end
20 changes: 1 addition & 19 deletions code/internal/+openminds/+abstract/ControlledTerm.m
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ function deserializeFromName(obj, instanceName)

schemaName = getSchemaName(class(obj));

if obj.isSemanticName(instanceName)
if openminds.utility.isSemanticInstanceName(instanceName)
[~, instanceName] = openminds.utility.parseAtID(instanceName);
end

Expand Down Expand Up @@ -126,22 +126,4 @@ function deserializeFromName(obj, instanceName)
obj.id = obj.at_id;
end
end

methods (Static)
function tf = isSemanticName(name)
% Todo: Move to utility package

URI = matlab.net.URI(name);

isValidUrl = sprintf("%s://%s", URI.Scheme, URI.Host) == ...
openminds.internal.constants.url.OpenMindsBaseURL;

URIPath = URI.Path;
URIPath(URIPath=="")=[];

isInstanceUrl = URIPath(1) == "instances";

tf = isValidUrl && isInstanceUrl;
end
end
end
36 changes: 33 additions & 3 deletions code/internal/+openminds/+constant/BaseURI.m
Original file line number Diff line number Diff line change
@@ -1,13 +1,43 @@
function baseURI = BaseURI(version)

% BaseURI Get the base URI for the specified OpenMINDS schema version
%
% baseURI = openminds.constant.BaseURI(version) returns the base URI as a
% string that corresponds to the specified OpenMINDS schema version. This
% function dynamically selects the URI based on the version input. If no
% version is specified, the version number for the active openMINDS version
% is selected.
%
% Input:
% version - (optional) An instance of openminds.internal.utility.VersionNumber
% specifying the schema version. If no version is provided,
% the function automatically retrieves the current schema
% version using openminds.getSchemaVersion.
%
% Output:
% baseURI - A string containing the base URI corresponding to the
% specified or default schema version.
%
% Conditions:
% - Versions <= 3 return "https://openminds.ebrains.eu"
% - Versions >= 4 return "https://openminds.om-i.org"
%
% Example:
% baseURI = openminds.constant.BaseURI(3); % Returns "https://openminds.ebrains.eu"
% baseURI = openminds.constant.BaseURI(4); % Returns "https://openminds.om-i.org"
%
% See also: openminds.getSchemaVersion

arguments
version (1,1) openminds.internal.utility.VersionNumber ...
{openminds.mustBeValidVersion(version)} = "3.0" % Todo: change to latest
{openminds.mustBeValidVersion(version)} = missing
end

if ismissing(version)
version = openminds.getSchemaVersion("VersionNumber");
end

if version <= 3
baseURI = "https://openminds.ebrains.eu";

elseif version >= 4
baseURI = "https://openminds.om-i.org";
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@
if contains(type, 'openminds.controlledterms')
allInstanceNames = eval(sprintf('%s.CONTROLLED_INSTANCES', type));

if openminds.utility.isSemanticName(instanceName)
if openminds.utility.isSemanticInstanceName(instanceName)
S = openminds.utility.parseAtID(instanceName);
instanceName = S.Name;
end
Expand Down

This file was deleted.

19 changes: 18 additions & 1 deletion code/internal/+openminds/+internal/+utility/VersionNumber.m
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

properties (SetAccess = protected, Hidden)
IsLatest (1,1) logical = false
IsMissing (1,1) logical = false
end

properties (Access = private, Dependent)
Expand Down Expand Up @@ -56,6 +57,9 @@

elseif isnumeric(iVersion)
obj(i).setVersion(iVersion);

elseif ismissing(iVersion)
obj(i).IsMissing = true; %#ok<AGROW>
end
end

Expand Down Expand Up @@ -128,16 +132,29 @@
verNums = obj(i).getNumbersForFormat();
if obj(i).IsLatest
str(i) = "latest";
elseif obj(i).IsMissing
str(i) = "missing";

Check warning on line 136 in code/internal/+openminds/+internal/+utility/VersionNumber.m

View check run for this annotation

Codecov / codecov/patch

code/internal/+openminds/+internal/+utility/VersionNumber.m#L136

Added line #L136 was not covered by tests
else
str(i) = string( sprintf(obj(i).FormatPattern, verNums{:}) );
end
end
end


function c = char(obj)
if numel(obj) == 1
c = char( string(obj) );

Check warning on line 145 in code/internal/+openminds/+internal/+utility/VersionNumber.m

View check run for this annotation

Codecov / codecov/patch

code/internal/+openminds/+internal/+utility/VersionNumber.m#L144-L145

Added lines #L144 - L145 were not covered by tests
else
c = cellstr( string(obj) );

Check warning on line 147 in code/internal/+openminds/+internal/+utility/VersionNumber.m

View check run for this annotation

Codecov / codecov/patch

code/internal/+openminds/+internal/+utility/VersionNumber.m#L147

Added line #L147 was not covered by tests
end
end
%
function verNum = double(obj)
verNum = [obj.Major, obj.Minor, obj.Patch, obj.Build];
end

function tf = ismissing(obj)
tf = obj.IsMissing;
end

% Bumping version methods
function bumpMajor(obj)
Expand Down
44 changes: 30 additions & 14 deletions code/internal/+openminds/+internal/getControlledInstance.m
Original file line number Diff line number Diff line change
@@ -1,34 +1,49 @@
function data = getControlledInstance(instanceName, schemaName, modelName)
function data = getControlledInstance(instanceName, schemaName, modelName, versionNumber, options)

arguments
instanceName (1,1) string
schemaName (1,1) string
modelName (1,1) string = "controlledTerms"
versionNumber (1,1) openminds.internal.utility.VersionNumber ...
{openminds.mustBeValidVersion(versionNumber)} = missing
options.FileSource (1,1) string ...
{mustBeMember(options.FileSource, ["local", "github"])} = "local"
end

if ismissing(versionNumber)
versionNumber = openminds.getSchemaVersion("VersionNumber");
end
versionNumber = string(versionNumber);

% Make type name lowercase unless it is an abbreviated typename like
% e.g. UBERONParcellation
if ~strcmp( upper(schemaName{1}(1:2)), schemaName{1}(1:2))
schemaName{1}(1) = lower(schemaName{1}(1));
end

try
data = getOfflineInstance(instanceName, schemaName, modelName);
catch
data = getOnlineInstance(instanceName, schemaName, modelName);
if options.FileSource == "local"
try
data = getOfflineInstance(instanceName, schemaName, modelName, versionNumber);
catch
data = getOnlineInstance(instanceName, schemaName, modelName, versionNumber);
end
else
data = getOnlineInstance(instanceName, schemaName, modelName, versionNumber);
end
end

function data = getOnlineInstance(instanceName, schemaName, modelName)
function data = getOnlineInstance(instanceName, schemaName, modelName, versionNumber)

filePath = getOnlineFilepath(instanceName, schemaName, modelName);
filePath = getOnlineFilepath(instanceName, schemaName, modelName, versionNumber);
jsonStr = webread(filePath);
data = openminds.internal.utility.json.decode(jsonStr);

% Save instance locally
filePath = getOfflineFilepath(instanceName, schemaName, modelName);
filePath = getOfflineFilepath(instanceName, schemaName, modelName, versionNumber);
openminds.internal.utility.filewrite(filePath, jsonStr)
end

function data = getOfflineInstance(instanceName, schemaName, modelName)
function data = getOfflineInstance(instanceName, schemaName, modelName, versionNumber)

%import openminds.internal.listControlledInstances

Expand All @@ -37,7 +52,7 @@
%assert(size(instanceTable, 1) == 1, 'Expected a single match for instance "%s", but %d was found.', instanceName, size(instanceTable, 1))
%jsonStr = fileread(instanceTable.Filepath);

filePath = getOfflineFilepath(instanceName, schemaName, modelName);
filePath = getOfflineFilepath(instanceName, schemaName, modelName, versionNumber);

if ~isfile(filePath)
error('File does not exist')
Expand All @@ -48,19 +63,20 @@
data = openminds.internal.utility.json.decode(jsonStr);
end

function pathStr = getOnlineFilepath(instanceName, schemaName, modelName)
function pathStr = getOnlineFilepath(instanceName, schemaName, modelName, versionNumber)
import openminds.internal.constants.Github
import openminds.internal.utility.string.uriJoin

fileParts = getRelativeInstanceFileParts(instanceName, schemaName, modelName);
relativePath = uriJoin(["main", "instances", "latest", fileParts]);
relativePath = uriJoin(["main", "instances", versionNumber, fileParts]);
pathStr = Github.getRawFileUrl("instances", relativePath);
end

function pathStr = getOfflineFilepath(instanceName, schemaName, modelName)
function pathStr = getOfflineFilepath(instanceName, schemaName, modelName, versionNumber)
rootPath = openminds.internal.PathConstants.LocalInstanceFolder;
fileParts = getRelativeInstanceFileParts(instanceName, schemaName, modelName);
pathStr = fullfile(rootPath, "latest", fileParts{:});

pathStr = fullfile(rootPath, versionNumber, fileParts{:});
end

function fileParts = getRelativeInstanceFileParts(instanceName, schemaName, modelName)
Expand Down
40 changes: 40 additions & 0 deletions code/internal/+openminds/+utility/isSemanticInstanceName.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
function tf = isSemanticInstanceName(name)
% isSemanticInstanceName Check if a name represents a semantic instance URI
%
% tf = openminds.utility.isSemanticInstanceName(name) returns true if the
% input `name` is a valid semantic instance URI, and false otherwise. This
% function verifies if `name` meets the conditions to be identified as a
% semantic instance by:
%
% 1. Parsing the URI scheme and host, then checking if they match
% the expected base URI for semantic instances.
% 2. Verifying that the path within the URI starts with "instances",
% indicating a semantic instance.
%
% Input:
% name - A string containing the URI to be validated as a semantic
% instance.
%
% Output:
% tf - A logical value (true or false) indicating whether `name` is a
% valid semantic instance URI.
%
% Example:
% instanceURI = "https://openminds.om-i.org/instances/ageCategory/adolescent"
% tf = openminds.utility.isSemanticInstanceName(instanceURI);
%
% See also: matlab.net.URI, openminds.constant.BaseURI


URI = matlab.net.URI(name);

isValidUrl = sprintf("%s://%s", URI.Scheme, URI.Host) == ...
openminds.constant.BaseURI;

URIPath = URI.Path;
URIPath(URIPath=="")=[];

isInstanceUrl = URIPath(1) == "instances";

tf = isValidUrl && isInstanceUrl;
end
14 changes: 0 additions & 14 deletions code/internal/+openminds/+utility/isSemanticName.m

This file was deleted.

2 changes: 1 addition & 1 deletion code/internal/+openminds/+utility/isSemanticSchemaName.m
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
function tf = isSemanticSchemaName(fullSchemaName)
%isSemanticName Check if name is a semantic name
%isSemanticSchemaName Check if name is a semantic name
tf = startsWith(fullSchemaName, openminds.constant.BaseURI);
end
3 changes: 3 additions & 0 deletions code/internal/+openminds/mustBeValidVersion.m
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ function mustBeValidVersion(version)
version (1,1) openminds.internal.utility.VersionNumber
end

% Allow missing version specification
if ismissing(version); return; end

validVersions = openminds.internal.listValidVersions();
version.Format = 'vX.Y';
version.validateVersion(version, validVersions{:})
Expand Down
3 changes: 2 additions & 1 deletion code/setup.m
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ function setup(options)
% Add the code folder to the search path
codeFolder = fileparts( mfilename('fullpath') );
addpath(codeFolder)

addpath(fullfile(codeFolder, 'internal'))

% Running startup will properly add openMINDS_MATLAB to the search path
openminds.startup(options.Version)

Expand Down
2 changes: 1 addition & 1 deletion dev/testToolbox.m
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ function testToolbox(options)
finalize = onCleanup(@()(path(oldpath)));

% Use the openMINDS_MATLAB startup function to correctly configure the path
run( fullfile(codeFolder, 'startup.m') )
run( fullfile(codeFolder, 'setup.m') )

outputDirectory = fullfile(rootDir, "docs", "reports", options.ReportSubdirectory);
if ~isfolder(outputDirectory)
Expand Down
32 changes: 32 additions & 0 deletions dev/tests/unitTests/ControlledInstanceTest.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
classdef ControlledInstanceTest < matlab.unittest.TestCase

properties (TestParameter)
instanceSpecification = { {'adult', "AgeCategory", "controlledTerms"} }
versionNumber = {3, "latest"}
end

methods (TestClassSetup)
function setupClass(testCase)
testCase.applyFixture(matlab.unittest.fixtures.WorkingFolderFixture)
end
end

methods (Test)
function testGetControlledInstanceLocal(testCase, instanceSpecification, versionNumber)
jsonStr = openminds.internal.getControlledInstance(...
instanceSpecification{:}, versionNumber, "FileSource", "local");

expectedIdUriPrefix = sprintf("%s/instances", openminds.constant.BaseURI(versionNumber));
testCase.assertTrue(contains(jsonStr.at_id, expectedIdUriPrefix));

end

function testGetControlledInstanceRemote(testCase, instanceSpecification, versionNumber)
jsonStr = openminds.internal.getControlledInstance(...
instanceSpecification{:}, versionNumber, "FileSource", "github");

expectedIdUriPrefix = sprintf("%s/instances", openminds.constant.BaseURI(versionNumber));
testCase.assertTrue(contains(jsonStr.at_id, expectedIdUriPrefix));
end
end
end
Loading
Loading