Skip to content

Commit 4a0a728

Browse files
update to v1.5.0
1 parent 03a4c46 commit 4a0a728

File tree

13 files changed

+175
-41
lines changed

13 files changed

+175
-41
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
Log of changes in the versions
44

5+
## v1.5.0.0
6+
- `Collection` is added to allow constructing transformations that use terms, that are not of type `Qualification`
7+
- min `ontolutils` version is v0.15.0
8+
59
## v1.4.0.0
610

711
- update to newer version of the ontology

CITATION.cff

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ authors:
44
- family-names: "Probst"
55
given-names: "Matthias"
66
orcid: "https://orcid.org/0000-0001-8729-0482"
7-
title: "ssnolib (1.4.0.0)"
8-
version: 1.4.0.0
9-
doi: 10.5281/zenodo.15034611
10-
date-released: 2025-03-16
7+
title: "ssnolib (1.5.0.0)"
8+
version: 1.5.0.0
9+
doi: 10.5281/zenodo.15072194
10+
date-released: 2025-03-23
1111
url: "https://github.com/matthiasprobst/ssnolib"

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@
33
![Tests](https://github.com/matthiasprobst/SSNOlib/actions/workflows/tests.yml/badge.svg)
44
![DOCS](https://codecov.io/gh/matthiasprobst/SSNOlib/branch/main/graph/badge.svg)
55
![pyvers](https://img.shields.io/badge/python-3.8%20%7C%203.9%20%7C%203.10%20%7C%203.11%20%7C%203.12-blue)
6-
![ssno](https://img.shields.io/badge/ssno-1.4.0-orange)
6+
![ssno](https://img.shields.io/badge/ssno-1.5.0-orange)
77

8-
A Python library to work with the [SSNO ontology](https://matthiasprobst.github.io/ssno/1.4.0). It provides Python classes
8+
A Python library to work with the [SSNO ontology](https://matthiasprobst.github.io/ssno/1.5.0). It provides Python classes
99
for the ontology classes and facilitates the creation of JSON-LD files. JSON-LD files are both human- and machine-readable
1010
and most importantly machine-actionable. The library can be integrated in you data (conversion) pipelines.
1111

12-
> **_NOTE:_** The version of the library corresponds to the version of the ontology it supports. Hence, 1.4.0.1 refers
13-
> to the ontology version 1.4.0 and the last part (.1) is the patch version of this library.
12+
> **_NOTE:_** The version of the library corresponds to the version of the ontology it supports. Hence, 1.5.0.1 refers
13+
> to the ontology version 1.5.0 and the last part (.1) is the patch version of this library.
1414
1515
## Quickstart
1616

codemeta.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"license": "https://spdx.org/licenses/MIT",
55
"codeRepository": "git+https://github.com/matthiasprobst/ssnolib",
66
"name": "ssnolib",
7-
"version": "1.4.0.0",
7+
"version": "1.5.0.0",
88
"description": "Python library for working with the SSNO ontology. ",
99
"applicationCategory": "Engineering",
1010
"programmingLanguage": [

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
rdflib>=7.0.0
2-
ontolutils>0.14.1,<1.0.0
2+
ontolutils>=0.15.0,<1.0.0
33
pydantic[email]>=2.7.4
44
appdirs>=1.4.4
55
python-dateutil>=2.9.0

setup.cfg

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[metadata]
22
name = ssnolib
3-
version = 1.4.0.0
3+
version = 1.5.0.0
44
author = Matthias Probst
55
author_email = matthias.probst@kit.edu
66
description = SSNOlib is a Python library for working with the Standard Name Ontology (SSNO).
@@ -28,7 +28,7 @@ install_requires =
2828
pydantic[email]>=2.7.4
2929
python-dateutil>=2.9.0
3030
requests>=2.32.3
31-
ontolutils>0.14.1,<1.0.0
31+
ontolutils>=0.15.0,<1.0.0
3232

3333
[options.package_data]
3434
ssnolib =

ssnolib/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from .prov import Person, Organization
44
from .prov.attribution import Attribution
55
from .ssno import StandardNameTable, Qualification, VectorQualification, Transformation, Character, AgentRole, \
6-
VectorStandardName, StandardName, ScalarStandardName
6+
VectorStandardName, StandardName, ScalarStandardName, DomainConceptSet
77
from .utils import get_cache_dir
88
from . import schema
99
from .ssno.standard_name_table import parse_table

ssnolib/namespace.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ class SSNO(DefinedNamespace):
66
# uri = "https://matthiasprobst.github.io/ssno#"
77
# Generated with ssnolib
88
Character: URIRef # ['Charakter']
9+
DomainConceptSet: URIRef # ['Domain Concept Set']
910
Qualification: URIRef # ['Qualification']
1011
ScalarStandardName: URIRef # ['Scalar Standard Name']
1112
StandardName: URIRef # ['Standard Name']
@@ -20,6 +21,7 @@ class SSNO(DefinedNamespace):
2021
before: URIRef # ['before']
2122
dataset: URIRef # ['dataset']
2223
hasCharacter: URIRef # ['has character']
24+
hasDomainConceptSet: URIRef # ['has domain concept set']
2325
hasModifier: URIRef # ['has modifier']
2426
hasStandardName: URIRef # ['has standard name']
2527
hasValidValues: URIRef # ['has valid values']
@@ -45,6 +47,7 @@ class SSNO(DefinedNamespace):
4547

4648

4749
setattr(SSNO, "Charakter", SSNO.Character)
50+
setattr(SSNO, "Domain_Concept_Set", SSNO.DomainConceptSet)
4851
setattr(SSNO, "Qualification", SSNO.Qualification)
4952
setattr(SSNO, "Scalar_Standard_Name", SSNO.ScalarStandardName)
5053
setattr(SSNO, "Standard_Name", SSNO.StandardName)
@@ -59,6 +62,7 @@ class SSNO(DefinedNamespace):
5962
setattr(SSNO, "before", SSNO.before)
6063
setattr(SSNO, "dataset", SSNO.dataset)
6164
setattr(SSNO, "has_character", SSNO.hasCharacter)
65+
setattr(SSNO, "has_domain_concept_set", SSNO.hasDomainConceptSet)
6266
setattr(SSNO, "has_modifier", SSNO.hasModifier)
6367
setattr(SSNO, "has_standard_name", SSNO.hasStandardName)
6468
setattr(SSNO, "has_valid_values", SSNO.hasValidValues)

ssnolib/ssno/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
from .standard_name import StandardName, ScalarStandardName, VectorStandardName
22
from .standard_name_table import StandardNameTable, Qualification, VectorQualification, Transformation, Character, \
3-
parse_table, AgentRole, StandardNameModification
3+
parse_table, AgentRole, StandardNameModification, DomainConceptSet
44

55
__all__ = ('StandardNameTable',
66
'Qualification',
77
'VectorQualification',
88
'ScalarStandardName',
99
'VectorStandardName',
1010
'Transformation',
11+
'DomainConceptSet',
1112
'StandardName',
1213
'Character',
1314
'AgentRole',

ssnolib/ssno/standard_name_table.py

Lines changed: 88 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@
66
from dataclasses import make_dataclass
77
from datetime import datetime
88
from typing import List, Union, Dict, Optional, Tuple
9+
910
import rdflib
1011
from ontolutils import namespaces, urirefs, Thing, as_id
1112
from ontolutils.namespacelib.m4i import M4I
1213
from pydantic import field_validator, Field, HttpUrl, ValidationError, model_validator
1314
from rdflib import URIRef
15+
1416
from ssnolib import config
1517
from ssnolib.dcat import Distribution, Dataset
1618
from ssnolib.m4i import TextVariable
@@ -20,7 +22,6 @@
2022
from ssnolib.skos import Concept
2123
from ssnolib.sparql_utils import build_simple_sparql_query, WHERE
2224
from ssnolib.utils import parse_and_exclude_none, download_file
23-
2425
from . import plugins
2526
from .standard_name import StandardName, VectorStandardName, ScalarStandardName
2627
from .unit_utils import _parse_unit, reverse_qudt_lookup, _format_unit
@@ -123,6 +124,20 @@ def __str__(self) -> str:
123124
return f'{self.__class__.__name__}("{self.name}")'
124125

125126

127+
@namespaces(ssno="https://matthiasprobst.github.io/ssno#",
128+
schema="https://schema.org/",
129+
dcterms="http://purl.org/dc/terms/")
130+
@urirefs(DomainConceptSet='ssno:DomainConceptSet',
131+
hasValidValues='ssno:hasValidValues',
132+
name='schema:name',
133+
description='dcterms:description')
134+
class DomainConceptSet(Concept):
135+
"""Implementation of ssno:Collection"""
136+
name: str # schema:name
137+
description: str # dcterms:description
138+
hasValidValues: Optional[List[Union[str, Dict, TextVariable]]] = None # ssno:hasValidValues
139+
140+
126141
@namespaces(ssno="https://matthiasprobst.github.io/ssno#")
127142
@urirefs(Qualification='ssno:Qualification',
128143
before='ssno:before',
@@ -200,7 +215,7 @@ class Character(Concept):
200215
"""Implementation of ssno:Transformation"""
201216

202217
character: str # ssno:character
203-
associatedWith: Union[str, HttpUrl, Qualification] # ssno:associatedWith
218+
associatedWith: Union[str, HttpUrl, Qualification, DomainConceptSet] # ssno:associatedWith
204219

205220
@field_validator('associatedWith', mode='before')
206221
@classmethod
@@ -251,6 +266,7 @@ def _hasCharacter(cls, hasCharacter):
251266
qualifiedAttribution='prov:qualifiedAttribution',
252267
standardNames='ssno:standardNames',
253268
hasModifier='ssno:hasModifier',
269+
hasDomainConceptSet='ssno:hasDomainConceptSet',
254270
subject='dcterms:subject',
255271
keywords='schema:keywords',
256272
relation='dcterms:relation',
@@ -297,6 +313,7 @@ class StandardNameTable(Concept):
297313
hasModifier: Optional[
298314
List[Union[Qualification, VectorQualification, Transformation]]
299315
] = Field(default=None, alias="has_modifier") # ssno:hasModifier
316+
hasDomainConceptSet: Optional[List[DomainConceptSet]] = Field(default=None, alias="has_domain_concept_set")
300317
subject: Optional[Union[str, HttpUrl]] = Field(default=None)
301318
keywords: Optional[Union[str, List[str]]] = Field(default=None)
302319
relation: Optional[Union[Thing, List[Thing]]] = Field(default=None)
@@ -1260,7 +1277,7 @@ def parse_table(source=None, data=None, fmt: Optional[str] = None):
12601277
# jetzt qualifications holen:
12611278
has_modifier = []
12621279
for _type in ("ssno:Qualification", "ssno:VectorQualification"):
1263-
sparql = build_simple_sparql_query(
1280+
sparql_modifiers = build_simple_sparql_query(
12641281
prefixes=prefixes,
12651282
wheres=[
12661283
WHERE(snt_id, "ssno:hasModifier", "?modifierID"),
@@ -1272,7 +1289,7 @@ def parse_table(source=None, data=None, fmt: Optional[str] = None):
12721289
WHERE("?modifierID", "dcterms:description", "?description", is_optional=True)
12731290
]
12741291
)
1275-
for res in sparql.query(g):
1292+
for res in sparql_modifiers.query(g):
12761293
modifierID = _parse_id(res['modifierID'])
12771294
# now look for the valid values:
12781295
sparql_valid_values = build_simple_sparql_query(
@@ -1334,7 +1351,6 @@ def parse_table(source=None, data=None, fmt: Optional[str] = None):
13341351
]
13351352
)
13361353

1337-
13381354
for res in sparql_transformation.query(g):
13391355
hasCharacter = []
13401356
modifierID = _parse_id(res['modifierID'])
@@ -1350,14 +1366,58 @@ def parse_table(source=None, data=None, fmt: Optional[str] = None):
13501366
)
13511367
for character in sparql_hasCharacter.query(g):
13521368
hasCharacter.append(
1353-
Character(id=character["hasCharacterID"], character=character["character"].value, associatedWith=character["associatedWith"].value))
1369+
Character(id=character["hasCharacterID"], character=character["character"].value,
1370+
associatedWith=character["associatedWith"].value))
13541371
has_modifier.append(
13551372
Transformation(name=res['name'].value, description=res['description'], altersUnit=res['altersUnit'],
13561373
hasCharacter=hasCharacter)
13571374
)
13581375
if has_modifier:
13591376
snt.hasModifier = has_modifier
13601377

1378+
# domain concept sets holen:
1379+
domain_concept_sets = []
1380+
sparql_domain_concept_sets = build_simple_sparql_query(
1381+
prefixes=prefixes,
1382+
wheres=[
1383+
WHERE(snt_id, "ssno:hasDomainConceptSet", "?domainConceptSetID"),
1384+
WHERE("?domainConceptSetID", "a", "ssno:DomainConceptSet"),
1385+
WHERE("?domainConceptSetID", "schema:name", "?name"),
1386+
WHERE("?domainConceptSetID", "dcterms:description", "?description", is_optional=True),
1387+
]
1388+
)
1389+
1390+
for res in sparql_domain_concept_sets.query(g):
1391+
domain_concept_set_id = _parse_id(res['domainConceptSetID'])
1392+
# now look for the valid values:
1393+
sparql_valid_values = build_simple_sparql_query(
1394+
prefixes=prefixes,
1395+
wheres=[
1396+
WHERE(domain_concept_set_id, "ssno:hasValidValues", "?hasValidValuesID"),
1397+
WHERE("?hasValidValuesID", "a", "m4i:TextVariable"),
1398+
WHERE("?hasValidValuesID", "m4i:hasStringValue", "?hasStringValue"),
1399+
WHERE("?hasValidValuesID", "m4i:hasVariableDescription", "?hasVariableDescription",
1400+
is_optional=True),
1401+
]
1402+
)
1403+
hasValidValues = []
1404+
for valid_values in sparql_valid_values.query(g):
1405+
validvalues_dict = dict(hasStringValue=valid_values['hasStringValue'],
1406+
hasVariableDescription=valid_values['hasVariableDescription'])
1407+
hasValidValues.append(
1408+
TextVariable(**{k: v.value.strip() for k, v in validvalues_dict.items() if v}))
1409+
1410+
has_domain_concept_set_dict = dict(name=res['name'].value, description=res['description'].value)
1411+
domain_concept_sets.append(
1412+
DomainConceptSet(
1413+
id=domain_concept_set_id,
1414+
hasValidValues=hasValidValues,
1415+
**{k: v for k, v in has_domain_concept_set_dict.items() if v}
1416+
)
1417+
)
1418+
1419+
snt.hasDomainConceptSet = domain_concept_sets
1420+
13611421
standard_names = []
13621422

13631423
sparql_get_vector_standard_names = build_simple_sparql_query(
@@ -1471,8 +1531,16 @@ def get_regex_from_transformation(transformation: Transformation) -> str:
14711531

14721532
def check_if_standard_name_can_be_build_with_transformation(standard_name: str, snt: StandardNameTable) -> Tuple[
14731533
List[StandardName], Union[Transformation, None]]:
1474-
transformations = [t for t in snt.hasModifier if isinstance(t, Transformation)]
1475-
qualifications = {t.id: t for t in snt.hasModifier if isinstance(t, Qualification)}
1534+
if snt.hasModifier is None:
1535+
transformations = {}
1536+
qualifications = {}
1537+
else:
1538+
transformations = [t for t in snt.hasModifier if isinstance(t, Transformation)]
1539+
qualifications = {t.id: t for t in snt.hasModifier if isinstance(t, Qualification)}
1540+
if snt.hasDomainConceptSet is not None:
1541+
domain_concept_sets = {t.id: t for t in snt.hasDomainConceptSet if isinstance(t, DomainConceptSet)}
1542+
else:
1543+
domain_concept_sets = {}
14761544
for transformation in transformations:
14771545
pattern = get_regex_from_transformation(transformation)
14781546
match = re.fullmatch(f"^{pattern}$", standard_name)
@@ -1488,13 +1556,18 @@ def check_if_standard_name_can_be_build_with_transformation(standard_name: str,
14881556
found_ns = snt.get_standard_name(term)
14891557
if found_ns:
14901558
matching_standard_names.append(found_ns)
1491-
else: # must be qualificaiton
1492-
# search in qualifications
1493-
found_q = qualifications.get(char.associatedWith)
1494-
if found_q:
1495-
if term in [v.hasStringValue for v in found_q.hasValidValues]:
1496-
found_valid_value = [v for v in found_q.hasValidValues if v.hasStringValue == term][0]
1497-
matching_standard_names.append(found_valid_value)
1559+
elif str(char.associatedWith) in domain_concept_sets:
1560+
found_dcs = domain_concept_sets.get(char.associatedWith)
1561+
if term in [v.hasStringValue for v in found_dcs.hasValidValues]:
1562+
found_valid_value = [v for v in found_dcs.hasValidValues if v.hasStringValue == term][0]
1563+
matching_standard_names.append(found_valid_value)
1564+
elif str(char.associatedWith) in qualifications:
1565+
found_q = domain_concept_sets.get(char.associatedWith)
1566+
if term in [v.hasStringValue for v in found_q.hasValidValues]:
1567+
found_valid_value = [v for v in found_q.hasValidValues if v.hasStringValue == term][0]
1568+
matching_standard_names.append(found_valid_value)
1569+
else: # must be qualification or domain concept set
1570+
raise ValueError(f"Unknown associatedWith value {char.associatedWith}")
14981571
if len(matching_standard_names) == len(terms):
14991572
return matching_standard_names, transformation
15001573
# matching_standard_names = [snt.get_standard_name(t) for t in terms]

0 commit comments

Comments
 (0)