Skip to content

Commit b550903

Browse files
committed
Fixed some URLs in docstrings. Converted the Creator class to represent names as a single value (supported in SBML Core 3 Level 2 using vCard4) (based on comments by cdiener in opencobra#1237). Creator names given as a separate given and family name are converted to a single name value and stored as such. That means that reading given+family names (from SBML files) is still supported, but they will always be concatenated and written as a single value when saving that model. Tests were modified to reflect this change. Separate given+family name support was also removed from the JSON v2 schema, whilst organisation name was added as optional field.
1 parent 7a12d2c commit b550903

File tree

9 files changed

+253
-82
lines changed

9 files changed

+253
-82
lines changed

documentation_builder/metadata.ipynb

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,7 @@
8989
"history = History(\n",
9090
" creators=[\n",
9191
" Creator(\n",
92-
" first_name=\"Matthias\",\n",
93-
" last_name=\"Koenig\",\n",
92+
" name=\"Matthias Koenig\",\n",
9493
" organization_name=\"HU\",\n",
9594
" email=\"[email protected]\",\n",
9695
" ),\n",
@@ -126,8 +125,7 @@
126125
"outputs": [],
127126
"source": [
128127
"new_creator = Creator(\n",
129-
" first_name=\"Andreas\",\n",
130-
" last_name=\"Draeger\",\n",
128+
" name=\"Andreas Draeger\",\n",
131129
" organization_name=\"University of Tübingen\",\n",
132130
" email=\"[email protected]\",\n",
133131
" )\n",
@@ -339,8 +337,7 @@
339337
"history = History(\n",
340338
" creators=[\n",
341339
" Creator(\n",
342-
" first_name=\"Matthias\",\n",
343-
" last_name=\"Koenig\",\n",
340+
" name=\"Matthias Koenig\",\n",
344341
" organization_name=\"HU\",\n",
345342
" email=\"[email protected]\",\n",
346343
" ),\n",
@@ -376,8 +373,7 @@
376373
},
377374
"source": [
378375
"new_creator = Creator(\n",
379-
" first_name=\"Andreas\",\n",
380-
" last_name=\"Draeger\",\n",
376+
" name=\"Andreas Draeger\",\n",
381377
" organization_name=\"University of Tübingen\",\n",
382378
" email=\"[email protected]\",\n",
383379
" )\n",

src/cobra/core/metadata/cvterm.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@
2121
class Qualifier(Enum):
2222
"""The possible qualifiers inside a CVTerm.
2323
24-
The qualifiers and their detailed description are present in
25-
https://co.mbine.org/standards/qualifiers.
24+
The qualifiers and their detailed description are present in:
25+
https://co.mbine.org/author/biomodels.net-qualifiers/
2626
2727
Qualifiers are divided into two groups
2828
bqb These kinds of qualifiers define the relationship between a biological
@@ -57,7 +57,7 @@ class CVTerm:
5757
"""CVTerm class, representing controlled vocabulary.
5858
5959
Controlled Vocabulary (CVTerm) can be defined as a curated and controlled
60-
relationship, described by Qualifier (see above) - the relationship between an
60+
relationship, described by a Qualifier (see above) - the relationship between an
6161
object and annotation must be part of the Qualifier class. These relationships
6262
are based in biochemical or biological relationships. The qualifiers/relationships
6363
are divided into bqbiol/bqb (biological qualification) and bqmodel/bqm (model
@@ -68,9 +68,10 @@ class CVTerm:
6868
"bqm_is" The modeling object encoded by the SBML component is the subject of
6969
the referenced resource. This might be used, e.g., to link the model
7070
to an entry in a model database.
71-
See https://co.mbine.org/standards/qualifiers
72-
For a definition of all qualifiers, see SBML Level 3, Version 2 Core, p 104
73-
(http://co.mbine.org/specifications/sbml.level-3.version-2.core.release-2.pdf)
71+
See `Biomodels Qualifiers
72+
<https://co.mbine.org/author/biomodels.net-qualifiers/>`_
73+
For a definition of all qualifiers, see `SBML Level 3, Version 2 Core, p 104
74+
<https://identifiers.org/combine.specifications:sbml.level-3.version-2.core.release-2>`_
7475
7576
The annotation will have one or more URI, which are encapsulated in
7677
ExternalResources class (see below).

src/cobra/core/metadata/history.py

Lines changed: 158 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
import re
99
from datetime import datetime
10-
from typing import Dict, Iterable, List, NamedTuple, Optional, Union
10+
from typing import Any, Dict, Iterable, List, Optional, Union
1111

1212

1313
STRTIME_FORMAT = "%Y-%m-%dT%H:%M:%S%z"
@@ -316,25 +316,144 @@ def __repr__(self):
316316
)
317317

318318

319-
class Creator(NamedTuple):
319+
class Creator:
320320
"""Metadata for person who created an object.
321321
322322
Parameters
323323
----------
324-
given_name: str
325-
Optional. Default None.
326-
family_name: str
324+
name: str
327325
Optional. Default None.
328326
email: str
329327
Optional. Default None.
330328
organisation: str
331329
Optional. Default None.
332330
"""
333331

334-
given_name: Optional[str] = None
335-
family_name: Optional[str] = None
336-
email: Optional[str] = None
337-
organisation: Optional[str] = None
332+
def __init__(
333+
self,
334+
name: Optional[str] = None,
335+
email: Optional[str] = None,
336+
organisation: Optional[str] = None,
337+
given_name: Optional[str] = None,
338+
family_name: Optional[str] = None,
339+
):
340+
"""Create an Creator metadata object.
341+
342+
The creator has optional name, email and organisation properties.
343+
Separate given and family name values will be combined to a single
344+
value for name. (This prevents confusion due to different cultural
345+
conventions, see for example:
346+
https://uxmovement.com/forms/why-your-form-only-needs-one-name-field/)
347+
348+
Parameters
349+
----------
350+
name: str
351+
Full name of the creator. Optional, default None.
352+
email: str
353+
Email address of the creator. Optional, default None.
354+
organisation: str
355+
Name of the organisation of the creator, or the organisation that
356+
created the model. Optional, default None.
357+
358+
"""
359+
self._name: Optional[str] = None
360+
self._email: Optional[str] = None
361+
self._organisation: Optional[str] = None
362+
363+
self.name = Creator._fix_name(
364+
name=name, given_name=given_name, family_name=family_name
365+
)
366+
self.email = email
367+
self.organisation = organisation
368+
369+
@staticmethod
370+
def _fix_name(
371+
name: Optional[str] = None,
372+
given_name: Optional[str] = None,
373+
family_name: Optional[str] = None,
374+
):
375+
if name is not None and name != family_name:
376+
if given_name is not None or family_name is not None:
377+
raise ValueError(
378+
"""Too many name values were provided. Either a name or a
379+
given and/or family name should be provided."""
380+
)
381+
return name
382+
if given_name is not None and family_name is not None:
383+
# This probably does not convert all names correctly.
384+
# Names should however preferentially be represented as a single value.
385+
return f"{given_name} {family_name}"
386+
elif given_name is not None:
387+
return given_name
388+
else:
389+
# This also covers the case wher all values are None
390+
return family_name
391+
392+
@property
393+
def name(self) -> Optional[str]:
394+
"""Get the model creator name.
395+
396+
Returns
397+
-------
398+
str
399+
Creator name.
400+
"""
401+
return self._name
402+
403+
@name.setter
404+
def name(self, value: Optional[str]) -> None:
405+
"""Set the name of the model creator.
406+
407+
Parameters
408+
----------
409+
value: str
410+
Name of the creator.
411+
"""
412+
self._name = value
413+
414+
@property
415+
def email(self) -> Optional[str]:
416+
"""Get the email address of the model creator.
417+
418+
Returns
419+
-------
420+
str
421+
Email address.
422+
"""
423+
return self._email
424+
425+
@email.setter
426+
def email(self, value: Optional[str]) -> None:
427+
"""Set the email of the model creator.
428+
429+
Parameters
430+
----------
431+
value: str
432+
Email address of the creator.
433+
"""
434+
self._email = value
435+
436+
@property
437+
def organisation(self) -> Optional[str]:
438+
"""Get the organisation of the model creator.
439+
440+
Returns
441+
-------
442+
str
443+
Organisation.
444+
"""
445+
return self._organisation
446+
447+
@organisation.setter
448+
def organisation(self, value: Optional[str]) -> None:
449+
"""Set the organisation of the model creator.
450+
451+
Parameters
452+
----------
453+
value: str
454+
Organisation of the model creator.
455+
"""
456+
self._organisation = value
338457

339458
@staticmethod
340459
def from_data(data: Union[Dict, "Creator"]) -> "Creator":
@@ -358,15 +477,24 @@ def from_data(data: Union[Dict, "Creator"]) -> "Creator":
358477
else:
359478
raise TypeError(f"Invalid format for Creator: {data}")
360479

480+
def _asdict(self) -> Dict:
481+
d = {}
482+
if self.name is not None:
483+
d["name"] = self.name
484+
if self.email is not None:
485+
d["email"] = self.email
486+
if self.organisation is not None:
487+
d["organisation"] = self.organisation
488+
return d
489+
361490
def to_dict(self) -> Dict:
362491
"""Convert Creator to dictionary.
363492
364493
Returns
365494
-------
366495
dict in this format
367496
{
368-
"given_name": str,
369-
"family_name": str,
497+
"name": str,
370498
"email": str,
371499
"organisation": str,
372500
}
@@ -392,6 +520,24 @@ def __repr__(self):
392520
"""
393521
return (
394522
f"{self.__class__.__module__}.{self.__class__.__qualname__}"
395-
f"('{self.given_name}', '{self.family_name}', '{self.email}', "
523+
f"('{self.name}', '{self.email}', "
396524
f"'{self.organisation}')"
397525
)
526+
527+
def __eq__(self, other: Any) -> bool:
528+
"""Determine whether the Creator is equal to another Creator object.
529+
530+
Parameters
531+
----------
532+
other: Creator
533+
Creator object to compare to.
534+
"""
535+
if not isinstance(other, __class__):
536+
return False
537+
if self.name != other.name:
538+
return False
539+
if self.email != other.email:
540+
return False
541+
if self.organisation != other.organisation:
542+
return False
543+
return True

src/cobra/core/metadata/keyvaluepairs.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
"""Class to deal with Key-Value pairs.
22
3-
Key-Value pairs are described in SBML FBC3. For the FBC3 standard, see
4-
https://github.com/bgoli/sbml-fbc-spec/blob/main/sf_svn/spec/main.pdf
3+
Key-Value pairs are described in the SBML FBC3 proposal. For the latest
4+
version of the FBC3 proposal, see Release Candidate 1:
5+
https://github.com/sbmlteam/sbml-specifications/blob/develop/sbml-level-3/version-1/fbc/spec/sbml-fbc-version-3-release-1.pdf
56
"""
67

8+
# TODO: Update docstring with final release, when available.
79
import uuid
810
from collections import UserDict
911
from dataclasses import asdict, dataclass

0 commit comments

Comments
 (0)