pymzqc 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pymzqc-1.0.0/LICENSE +24 -0
- pymzqc-1.0.0/PKG-INFO +91 -0
- pymzqc-1.0.0/README.md +64 -0
- pymzqc-1.0.0/mzqc/MZQCFile.py +485 -0
- pymzqc-1.0.0/mzqc/SemanticCheck.py +784 -0
- pymzqc-1.0.0/mzqc/SyntaxCheck.py +85 -0
- pymzqc-1.0.0/mzqc/__init__.py +0 -0
- pymzqc-1.0.0/mzqcaccessories/__init__.py +0 -0
- pymzqc-1.0.0/mzqcaccessories/filehandling/__init__.py +0 -0
- pymzqc-1.0.0/mzqcaccessories/filehandling/mzqc_fileinfo.py +71 -0
- pymzqc-1.0.0/mzqcaccessories/filehandling/mzqc_filemerger.py +127 -0
- pymzqc-1.0.0/mzqcaccessories/filehandling/mzqc_fixdescriptions.py +58 -0
- pymzqc-1.0.0/mzqcaccessories/offlinevalidator/__init__.py +0 -0
- pymzqc-1.0.0/mzqcaccessories/offlinevalidator/mzqc_offline_validator.py +70 -0
- pymzqc-1.0.0/pymzqc.egg-info/PKG-INFO +91 -0
- pymzqc-1.0.0/pymzqc.egg-info/SOURCES.txt +25 -0
- pymzqc-1.0.0/pymzqc.egg-info/dependency_links.txt +1 -0
- pymzqc-1.0.0/pymzqc.egg-info/entry_points.txt +5 -0
- pymzqc-1.0.0/pymzqc.egg-info/requires.txt +8 -0
- pymzqc-1.0.0/pymzqc.egg-info/top_level.txt +2 -0
- pymzqc-1.0.0/setup.cfg +4 -0
- pymzqc-1.0.0/setup.py +42 -0
- pymzqc-1.0.0/tests/test_MZQCFile.py +150 -0
- pymzqc-1.0.0/tests/test_SemanticCheck.py +183 -0
- pymzqc-1.0.0/tests/test_SyntaxCheck.py +63 -0
- pymzqc-1.0.0/tests/test_Versioning.py +74 -0
- pymzqc-1.0.0/tests/test_rw_circle.py +26 -0
pymzqc-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
Copyright (c) 2019-22, Mathias Walzer
|
|
2
|
+
All rights reserved.
|
|
3
|
+
|
|
4
|
+
Redistribution and use in source and binary forms, with or without
|
|
5
|
+
modification, are permitted provided that the following conditions are met:
|
|
6
|
+
* Redistributions of source code must retain the above copyright
|
|
7
|
+
notice, this list of conditions and the following disclaimer.
|
|
8
|
+
* Redistributions in binary form must reproduce the above copyright
|
|
9
|
+
notice, this list of conditions and the following disclaimer in the
|
|
10
|
+
documentation and/or other materials provided with the distribution.
|
|
11
|
+
* Neither the name of EBI nor HUPO-PSI nor MS-Quality-Hub nor the
|
|
12
|
+
names of its contributors may be used to endorse or promote products
|
|
13
|
+
derived from this software without specific prior written permission.
|
|
14
|
+
|
|
15
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
16
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
17
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
18
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
19
|
+
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
20
|
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
21
|
+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
22
|
+
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
23
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
24
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
pymzqc-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
|
+
Name: pymzqc
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Python library for the PSI-mzQC quality control file format.
|
|
5
|
+
Home-page: https://github.com/MS-Quality-hub/pymzqc
|
|
6
|
+
Author: Mathias Walzer
|
|
7
|
+
Author-email: walzer@ebi.ack.uk
|
|
8
|
+
Requires-Python: >=3.8
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Requires-Dist: jsonschema>=3.2.0
|
|
12
|
+
Requires-Dist: strict-rfc3339
|
|
13
|
+
Requires-Dist: rfc3339-validator
|
|
14
|
+
Requires-Dist: numpy
|
|
15
|
+
Requires-Dist: pandas>=1.1.5
|
|
16
|
+
Requires-Dist: pronto
|
|
17
|
+
Requires-Dist: requests>=2.27.1
|
|
18
|
+
Requires-Dist: click
|
|
19
|
+
Dynamic: author
|
|
20
|
+
Dynamic: author-email
|
|
21
|
+
Dynamic: description
|
|
22
|
+
Dynamic: description-content-type
|
|
23
|
+
Dynamic: home-page
|
|
24
|
+
Dynamic: requires-dist
|
|
25
|
+
Dynamic: requires-python
|
|
26
|
+
Dynamic: summary
|
|
27
|
+
|
|
28
|
+
# MZQC python library
|
|
29
|
+
[](https://github.com/MS-Quality-hub/pymzqc/actions/workflows/unit_tests.yml)
|
|
30
|
+
[](https://pymzqc.readthedocs.io/en/latest/?badge=latest)
|
|
31
|
+
[](https://quay.io/repository/mwalzer/pymzqc?tab=tags)
|
|
32
|
+
[](https://pypi.com/project/pymzqc)
|
|
33
|
+
[](https://colab.research.google.com/github/MS-Quality-hub/pymzqc/blob/main/jupyter/mzqc_in_5/write_in_5_minutes.ipynb)
|
|
34
|
+
|
|
35
|
+
A python library to create and use mzQC files. Specifically, the library facilitates access to
|
|
36
|
+
mzQC files in form of a **directly usable object representation of mzQC** and offers additional
|
|
37
|
+
functionality to:
|
|
38
|
+
* serialise
|
|
39
|
+
* deserialise
|
|
40
|
+
* check syntax
|
|
41
|
+
* check semantics
|
|
42
|
+
* file-info
|
|
43
|
+
* experimental file-merging
|
|
44
|
+
|
|
45
|
+
**The library follows the formats versioning** (which is 'v(Major).(Minor).(Patch)').
|
|
46
|
+
|
|
47
|
+
This library implements python modules for (de-)serialisation and validity checks of the [PSI fileformat](http://www.psidev.info/groups/quality-control) [**mzQC**](https://hupo-psi.github.io/mzQC/).
|
|
48
|
+
Find the specification document, examples, and and further documentation there.
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
## Install
|
|
52
|
+
|
|
53
|
+
### Latest Release
|
|
54
|
+
Most people will want to install the latest release version of pymzqc. Please install pymzqc via [**pypi**](https://pypi.org/project/pymzqc/):
|
|
55
|
+
```
|
|
56
|
+
pip install pymzqc
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### From Git
|
|
60
|
+
If you want a development version, use for example :
|
|
61
|
+
```
|
|
62
|
+
pip install -U git+https://github.com/MS-Quality-hub/pymzqc.git
|
|
63
|
+
```
|
|
64
|
+
You can select **a development branch of your choice** by changing the command after the `.git`, see [the manual build instructions](BUILD.md).
|
|
65
|
+
|
|
66
|
+
### Containers
|
|
67
|
+
However, we recommend using the ready-built [**containers**](https://quay.io/repository/mwalzer/pymzqc?tab=tags) to check out the latest updates.
|
|
68
|
+
|
|
69
|
+
## Online docs
|
|
70
|
+
To get a nice and simple overview of how pymzqc works, visit [here](https://pymzqc.readthedocs.io/en/latest/examples.html).
|
|
71
|
+
If you've successfully installed the library and want to **jump right in and work on the library**, we suggest a peek at the [codestructure](https://pymzqc.readthedocs.io/en/latest/codestructure.html).
|
|
72
|
+
|
|
73
|
+
If you however just want to get your toes wet, or use it as-is, have a look at the interactive guides (below).
|
|
74
|
+
|
|
75
|
+
### Interactive pymzqc
|
|
76
|
+
Have a go with our [interactive python notebooks](jupyter/README.md) to explore what is possible.
|
|
77
|
+
|
|
78
|
+
## Development
|
|
79
|
+
Contributions are welcome! (Just fork, develop, and open PR.)
|
|
80
|
+
|
|
81
|
+
Please note that most member attributes of the MZQCFile submodule classes and many functional elements do not conform to PEP8.
|
|
82
|
+
The element names of the mzQC JSON-schema need to be preserved in order to create a successful and automated JSON<=>pymzqc object mapping.
|
|
83
|
+
Accordingly, other elements such as functions in all pymzqc modules will keep the JSON-schema names in their naming for consistency.
|
|
84
|
+
|
|
85
|
+
### Repository structure
|
|
86
|
+
The python package's code is located in the `mzqc` folder, continuous testing code in `tests`, the documentation in `doc`. The libray-**use** container descriptions are in `containers`, if you want a container for library-**development**, you can use the container description within `.devcontainer`, more development presets can be found in `.vscode`.
|
|
87
|
+
The `jupyter` and `accessories` folders are subprojects making use of the library.
|
|
88
|
+
See their README in the respective sub-folders.
|
|
89
|
+
|
|
90
|
+
### Documentation
|
|
91
|
+
The code documentation style convention is of the type `Sphinx/numpy`.
|
pymzqc-1.0.0/README.md
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# MZQC python library
|
|
2
|
+
[](https://github.com/MS-Quality-hub/pymzqc/actions/workflows/unit_tests.yml)
|
|
3
|
+
[](https://pymzqc.readthedocs.io/en/latest/?badge=latest)
|
|
4
|
+
[](https://quay.io/repository/mwalzer/pymzqc?tab=tags)
|
|
5
|
+
[](https://pypi.com/project/pymzqc)
|
|
6
|
+
[](https://colab.research.google.com/github/MS-Quality-hub/pymzqc/blob/main/jupyter/mzqc_in_5/write_in_5_minutes.ipynb)
|
|
7
|
+
|
|
8
|
+
A python library to create and use mzQC files. Specifically, the library facilitates access to
|
|
9
|
+
mzQC files in form of a **directly usable object representation of mzQC** and offers additional
|
|
10
|
+
functionality to:
|
|
11
|
+
* serialise
|
|
12
|
+
* deserialise
|
|
13
|
+
* check syntax
|
|
14
|
+
* check semantics
|
|
15
|
+
* file-info
|
|
16
|
+
* experimental file-merging
|
|
17
|
+
|
|
18
|
+
**The library follows the formats versioning** (which is 'v(Major).(Minor).(Patch)').
|
|
19
|
+
|
|
20
|
+
This library implements python modules for (de-)serialisation and validity checks of the [PSI fileformat](http://www.psidev.info/groups/quality-control) [**mzQC**](https://hupo-psi.github.io/mzQC/).
|
|
21
|
+
Find the specification document, examples, and and further documentation there.
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
## Install
|
|
25
|
+
|
|
26
|
+
### Latest Release
|
|
27
|
+
Most people will want to install the latest release version of pymzqc. Please install pymzqc via [**pypi**](https://pypi.org/project/pymzqc/):
|
|
28
|
+
```
|
|
29
|
+
pip install pymzqc
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### From Git
|
|
33
|
+
If you want a development version, use for example :
|
|
34
|
+
```
|
|
35
|
+
pip install -U git+https://github.com/MS-Quality-hub/pymzqc.git
|
|
36
|
+
```
|
|
37
|
+
You can select **a development branch of your choice** by changing the command after the `.git`, see [the manual build instructions](BUILD.md).
|
|
38
|
+
|
|
39
|
+
### Containers
|
|
40
|
+
However, we recommend using the ready-built [**containers**](https://quay.io/repository/mwalzer/pymzqc?tab=tags) to check out the latest updates.
|
|
41
|
+
|
|
42
|
+
## Online docs
|
|
43
|
+
To get a nice and simple overview of how pymzqc works, visit [here](https://pymzqc.readthedocs.io/en/latest/examples.html).
|
|
44
|
+
If you've successfully installed the library and want to **jump right in and work on the library**, we suggest a peek at the [codestructure](https://pymzqc.readthedocs.io/en/latest/codestructure.html).
|
|
45
|
+
|
|
46
|
+
If you however just want to get your toes wet, or use it as-is, have a look at the interactive guides (below).
|
|
47
|
+
|
|
48
|
+
### Interactive pymzqc
|
|
49
|
+
Have a go with our [interactive python notebooks](jupyter/README.md) to explore what is possible.
|
|
50
|
+
|
|
51
|
+
## Development
|
|
52
|
+
Contributions are welcome! (Just fork, develop, and open PR.)
|
|
53
|
+
|
|
54
|
+
Please note that most member attributes of the MZQCFile submodule classes and many functional elements do not conform to PEP8.
|
|
55
|
+
The element names of the mzQC JSON-schema need to be preserved in order to create a successful and automated JSON<=>pymzqc object mapping.
|
|
56
|
+
Accordingly, other elements such as functions in all pymzqc modules will keep the JSON-schema names in their naming for consistency.
|
|
57
|
+
|
|
58
|
+
### Repository structure
|
|
59
|
+
The python package's code is located in the `mzqc` folder, continuous testing code in `tests`, the documentation in `doc`. The libray-**use** container descriptions are in `containers`, if you want a container for library-**development**, you can use the container description within `.devcontainer`, more development presets can be found in `.vscode`.
|
|
60
|
+
The `jupyter` and `accessories` folders are subprojects making use of the library.
|
|
61
|
+
See their README in the respective sub-folders.
|
|
62
|
+
|
|
63
|
+
### Documentation
|
|
64
|
+
The code documentation style convention is of the type `Sphinx/numpy`.
|
|
@@ -0,0 +1,485 @@
|
|
|
1
|
+
__author__ = 'walzer'
|
|
2
|
+
import json
|
|
3
|
+
import re
|
|
4
|
+
import logging
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from typing import List,Dict,Union,Any
|
|
7
|
+
import numpy as np
|
|
8
|
+
import pandas as pd
|
|
9
|
+
|
|
10
|
+
#int
|
|
11
|
+
#str
|
|
12
|
+
#float
|
|
13
|
+
FloatVector = List[float]
|
|
14
|
+
IntVector = List[int]
|
|
15
|
+
StringVector = List[str]
|
|
16
|
+
FloatMatrix = List[FloatVector]
|
|
17
|
+
IntMatrix = List[IntVector]
|
|
18
|
+
StringMatrix = List[StringVector]
|
|
19
|
+
#Table = Dict[str,Union(FloatVector,IntVector,StringVector)]
|
|
20
|
+
Table = Dict[str,List]
|
|
21
|
+
|
|
22
|
+
class JsonSerialisable(object):
|
|
23
|
+
"""
|
|
24
|
+
JsonSerialisable Main structure template for mzQC objects
|
|
25
|
+
|
|
26
|
+
Sets the foundation for a mzQC object to be readily (de-)serialisable with standard
|
|
27
|
+
python json handling code. Facilitates reading and writing of complex objects.
|
|
28
|
+
|
|
29
|
+
"""
|
|
30
|
+
mappings: Dict[str, Any] = dict()
|
|
31
|
+
|
|
32
|
+
@staticmethod
|
|
33
|
+
def time_helper(da:str) -> datetime:
|
|
34
|
+
"""
|
|
35
|
+
time_helper Helper method for ISO8601 string of various length consumption
|
|
36
|
+
|
|
37
|
+
Used on JSON datetime object string representation will handle length and return
|
|
38
|
+
python datetime objects. JSON-schema actually follows
|
|
39
|
+
https://www.rfc-editor.org/rfc/rfc3339.html#section-5.6 which is a little more
|
|
40
|
+
stringent subset of ISO8601.
|
|
41
|
+
|
|
42
|
+
Parameters
|
|
43
|
+
----------
|
|
44
|
+
da : str
|
|
45
|
+
JSON datetime object string representation
|
|
46
|
+
|
|
47
|
+
Returns
|
|
48
|
+
-------
|
|
49
|
+
datetime
|
|
50
|
+
Python datetime object including the same amount detail provided
|
|
51
|
+
"""
|
|
52
|
+
try:
|
|
53
|
+
dt = pd.to_datetime(da)
|
|
54
|
+
except Exception as exc:
|
|
55
|
+
raise ValueError(f"Unknown string format: {da}") from exc
|
|
56
|
+
return dt
|
|
57
|
+
|
|
58
|
+
@classmethod
|
|
59
|
+
def class_mapper(classself, d):
|
|
60
|
+
"""
|
|
61
|
+
class_mapper Maps incoming objects to their respective definition
|
|
62
|
+
|
|
63
|
+
Allows every registered object to 'know' its type map incuding recursing into
|
|
64
|
+
its attributes. Can be used as object_hook in the json load process.
|
|
65
|
+
|
|
66
|
+
Parameters
|
|
67
|
+
----------
|
|
68
|
+
classself : self
|
|
69
|
+
The objects class self
|
|
70
|
+
d : dict
|
|
71
|
+
The dictionary mapping attributes
|
|
72
|
+
|
|
73
|
+
Returns
|
|
74
|
+
-------
|
|
75
|
+
class object
|
|
76
|
+
Returns an object of the 'outer-most' class
|
|
77
|
+
|
|
78
|
+
Raises
|
|
79
|
+
------
|
|
80
|
+
ValueError
|
|
81
|
+
If expected date strings are invalid.
|
|
82
|
+
"""
|
|
83
|
+
maxcls: Any = None
|
|
84
|
+
exmax: int = 0
|
|
85
|
+
for keys, cls in classself.mappings.items():
|
|
86
|
+
if keys.issuperset(d.keys()):
|
|
87
|
+
nx = len(set(d.keys()).intersection(set(keys)))
|
|
88
|
+
if nx > exmax:
|
|
89
|
+
maxcls = cls
|
|
90
|
+
exmax = nx
|
|
91
|
+
|
|
92
|
+
if maxcls is not None:
|
|
93
|
+
return maxcls(**d)
|
|
94
|
+
else:
|
|
95
|
+
if {'creationDate': None}.keys() == d.keys():
|
|
96
|
+
try:
|
|
97
|
+
return JsonSerialisable.time_helper(d['creationDate'])
|
|
98
|
+
except ValueError as exc:
|
|
99
|
+
raise ValueError(f"It appears the creationDate of your file is not of ISO 8601 format including time to the second: {d['creationDate']}") from exc
|
|
100
|
+
else:
|
|
101
|
+
# raise ValueError('Unable to find a matching class for object: {d} (keys: {k})' .format(d=d,k=d.keys()))
|
|
102
|
+
return d
|
|
103
|
+
|
|
104
|
+
@classmethod
|
|
105
|
+
def complex_handler(classself, obj):
|
|
106
|
+
"""
|
|
107
|
+
complex_handler Handles the in-depth serialisations necessary
|
|
108
|
+
|
|
109
|
+
Facilitates the correct serialisation for each type of object (within
|
|
110
|
+
the registered mzQC JsonSerialisable context) through possible
|
|
111
|
+
serialisation specialisation by way of object type. If objects behave
|
|
112
|
+
like dictionaries, but are complex classes (like all pymzqc obj),
|
|
113
|
+
the dict needs to be returned as classical dict in order to serialise.
|
|
114
|
+
|
|
115
|
+
Parameters
|
|
116
|
+
----------
|
|
117
|
+
classself : self
|
|
118
|
+
The objects class self
|
|
119
|
+
obj : object
|
|
120
|
+
The object to be deserialised
|
|
121
|
+
|
|
122
|
+
Returns
|
|
123
|
+
-------
|
|
124
|
+
obj
|
|
125
|
+
The correct object deconstruction into its deserialisable bits
|
|
126
|
+
|
|
127
|
+
Raises
|
|
128
|
+
------
|
|
129
|
+
TypeError
|
|
130
|
+
In case a given object cannot be serialised with the given set of functionalities.
|
|
131
|
+
"""
|
|
132
|
+
if isinstance(obj, datetime):
|
|
133
|
+
logging.debug("serialisation specialisation dates: "+str(obj))
|
|
134
|
+
if obj.tzinfo:
|
|
135
|
+
return obj.isoformat().replace('+00:00','Z')
|
|
136
|
+
else: #assume local time is UTC, the standard requires RFC3339 after all (see specification)
|
|
137
|
+
return obj.isoformat()+('Z')
|
|
138
|
+
|
|
139
|
+
if 'numpy' in str(type(obj)):
|
|
140
|
+
logging.debug("serialisation specialisation np.dtypes: "+str(obj))
|
|
141
|
+
if isinstance(obj,np.ndarray):
|
|
142
|
+
return obj.tolist()
|
|
143
|
+
return obj.item()
|
|
144
|
+
|
|
145
|
+
# needs to be last
|
|
146
|
+
if hasattr(obj, '__dict__'):
|
|
147
|
+
return {k:v for k,v in obj.__dict__.items() if v is not None and v != ""}
|
|
148
|
+
|
|
149
|
+
raise TypeError(f"Object of type {type(obj)} with value {repr(obj)} is not JSON (de)serializable.")
|
|
150
|
+
|
|
151
|
+
@classmethod
|
|
152
|
+
def register(classself, cls):
|
|
153
|
+
"""
|
|
154
|
+
register The method for class registration in the class mapping process
|
|
155
|
+
|
|
156
|
+
Each registered class gets mapped.
|
|
157
|
+
|
|
158
|
+
Parameters
|
|
159
|
+
----------
|
|
160
|
+
classself : self
|
|
161
|
+
the objects class self
|
|
162
|
+
cls : object
|
|
163
|
+
the class type
|
|
164
|
+
|
|
165
|
+
Returns
|
|
166
|
+
-------
|
|
167
|
+
cls
|
|
168
|
+
the class type
|
|
169
|
+
"""
|
|
170
|
+
classself.mappings[frozenset(tuple([attr for attr, val in cls().__dict__.items()]))] = cls
|
|
171
|
+
return cls
|
|
172
|
+
|
|
173
|
+
@classmethod
|
|
174
|
+
def to_json(classself, obj, readability=0, complete=True):
|
|
175
|
+
"""
|
|
176
|
+
to_json main method for serialisation
|
|
177
|
+
|
|
178
|
+
Parameters
|
|
179
|
+
----------
|
|
180
|
+
classself : self
|
|
181
|
+
The objects class self
|
|
182
|
+
obj : object
|
|
183
|
+
The object to be serialised
|
|
184
|
+
readability : int, optional
|
|
185
|
+
The indentation level, by default 0 (=no indentation,
|
|
186
|
+
1=minor indentation on MZQC objects, >1 heavy indentation for max.
|
|
187
|
+
human readability)
|
|
188
|
+
complete: bool, optional
|
|
189
|
+
Flag to indicate if the object is to be left without the
|
|
190
|
+
enclosing `mzQC` key or if the JSON is to be amended to full
|
|
191
|
+
schema compliance (default).
|
|
192
|
+
|
|
193
|
+
Returns
|
|
194
|
+
-------
|
|
195
|
+
str
|
|
196
|
+
The serialisation result
|
|
197
|
+
"""
|
|
198
|
+
if readability==0:
|
|
199
|
+
ret = json.dumps(obj.__dict__ if isinstance(obj, MzQcFile) else
|
|
200
|
+
obj, default=classself.complex_handler)
|
|
201
|
+
elif readability == 1:
|
|
202
|
+
ret = json.dumps(obj.__dict__ if isinstance(obj, MzQcFile) else
|
|
203
|
+
obj, default=classself.complex_handler,
|
|
204
|
+
indent=2, cls=MzqcJSONEncoder)
|
|
205
|
+
else:
|
|
206
|
+
ret = json.dumps(obj.__dict__ if isinstance(obj, MzQcFile) else
|
|
207
|
+
obj, default=classself.complex_handler, indent=4)
|
|
208
|
+
#remove empty run/setQualities and other optinal and empty elements, return with mzqc root,
|
|
209
|
+
ret = re.sub(r'(\"setQualities\"\:\s+\[\s*\][,]*)|(\"runQualities\"\:\s+\[\s*\][,]*)|([,]*\s+\"fileProperties\"\:\s+\[\s*\][,]*)', "", ret)
|
|
210
|
+
ret = re.sub(r'(\s*\"contactName\"\:\s+"",)|(\s*\"contactAddress\"\:\s+"",)|(\s*\"description\"\:\s+"",)', "", ret)
|
|
211
|
+
ret = f"{{\"mzQC\": \n{ret} \n}}" if complete else ret
|
|
212
|
+
return ret
|
|
213
|
+
|
|
214
|
+
@classmethod
|
|
215
|
+
def from_json(classself, json_str, complete=False):
|
|
216
|
+
"""
|
|
217
|
+
from_json main method for deserialisation
|
|
218
|
+
|
|
219
|
+
Accounts for neccessary object rectification due to same-attribute
|
|
220
|
+
class footprints. N.B.: for this to work the class init variables must
|
|
221
|
+
be same name as the corresponding member attributes (self).
|
|
222
|
+
|
|
223
|
+
Parameters
|
|
224
|
+
----------
|
|
225
|
+
classself : self
|
|
226
|
+
The objects class self
|
|
227
|
+
json_str : str
|
|
228
|
+
The JSON string to be deserialised
|
|
229
|
+
complete : bool, optional
|
|
230
|
+
Flag to indicate if the whole JSON is to be returned deserialised,
|
|
231
|
+
or just the `mzQC` entry (default).
|
|
232
|
+
|
|
233
|
+
Returns
|
|
234
|
+
-------
|
|
235
|
+
MzQcFile object
|
|
236
|
+
The deserialised JSON string
|
|
237
|
+
"""
|
|
238
|
+
if isinstance(json_str, str):
|
|
239
|
+
j = json.loads(json_str, object_hook=classself.class_mapper)
|
|
240
|
+
else: # assume it is a IO wrapper
|
|
241
|
+
j = json.load(json_str, object_hook=classself.class_mapper)
|
|
242
|
+
if not(complete) and 'mzQC' in j.keys():
|
|
243
|
+
j = j['mzQC']
|
|
244
|
+
return rectify(j)
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def rectify(obj):
|
|
248
|
+
"""
|
|
249
|
+
rectify Rectifies objects according to their position in the local hierarchy
|
|
250
|
+
|
|
251
|
+
Carries out the neccessary object rectification due to same-attribute class footprints.
|
|
252
|
+
Rectification depends on the object position in the local object hierarchy.
|
|
253
|
+
|
|
254
|
+
Parameters
|
|
255
|
+
----------
|
|
256
|
+
obj : object
|
|
257
|
+
The object to be rectified
|
|
258
|
+
|
|
259
|
+
Returns
|
|
260
|
+
-------
|
|
261
|
+
object
|
|
262
|
+
The rectified object
|
|
263
|
+
"""
|
|
264
|
+
static_list_typemap = {'runQualities': RunQuality, 'setQualities': SetQuality,
|
|
265
|
+
'controlledVocabularies': ControlledVocabulary,
|
|
266
|
+
'qualityMetrics': QualityMetric,
|
|
267
|
+
'inputFiles': InputFile,
|
|
268
|
+
'analysisSoftware': AnalysisSoftware,
|
|
269
|
+
'fileProperties': CvParameter}
|
|
270
|
+
static_singlet_typemap = {'fileFormat': CvParameter, 'metadata': MetaDataParameters}
|
|
271
|
+
if hasattr(obj, '__dict__'):
|
|
272
|
+
for k,v in obj.__dict__.items():
|
|
273
|
+
if k in static_list_typemap.keys():
|
|
274
|
+
v[:] = [rectify((static_list_typemap[k])(**i.__dict__ if hasattr(i, '__dict__') else i)) for i in v]
|
|
275
|
+
elif k in static_singlet_typemap.keys():
|
|
276
|
+
k = rectify((static_singlet_typemap[k])(**v.__dict__ if hasattr(v, '__dict__') else v))
|
|
277
|
+
else:
|
|
278
|
+
rectify(v)
|
|
279
|
+
elif isinstance(obj, dict):
|
|
280
|
+
for k,v in obj.items():
|
|
281
|
+
rectify(v)
|
|
282
|
+
return obj
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
class MzqcJSONEncoder(json.JSONEncoder):
|
|
286
|
+
"""
|
|
287
|
+
MzqcJSONEncoder The encoder used to facilitate indented encoding
|
|
288
|
+
|
|
289
|
+
Handles the string encoding and formatting of the serialised objects.
|
|
290
|
+
"""
|
|
291
|
+
def iterencode(self, o, _one_shot=False):
|
|
292
|
+
indent_level = 0
|
|
293
|
+
value_scope = False
|
|
294
|
+
for s in super(MzqcJSONEncoder, self).iterencode(o, _one_shot=_one_shot):
|
|
295
|
+
if value_scope and indent_level == 0 and s.startswith('}'):
|
|
296
|
+
value_scope = False
|
|
297
|
+
elif s.startswith('"value"'):
|
|
298
|
+
value_scope = True
|
|
299
|
+
if 0 < indent_level:
|
|
300
|
+
s = s.replace('\n', '').rstrip().lstrip()
|
|
301
|
+
if s.startswith(','):
|
|
302
|
+
s = ',' + s[1:].lstrip()
|
|
303
|
+
if s.startswith('[') and value_scope:
|
|
304
|
+
indent_level += 1
|
|
305
|
+
if s.endswith(']') and value_scope:
|
|
306
|
+
indent_level -= 1
|
|
307
|
+
s = s.replace(']', '\n'+' '*self.indent*6+']').rstrip()
|
|
308
|
+
yield s
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
class JsonObject(object):
|
|
312
|
+
"""
|
|
313
|
+
JsonObject Proxy object for better integration of mzQC objects
|
|
314
|
+
|
|
315
|
+
Useful for testing and validity checks as __eq__ is overridden to compare all
|
|
316
|
+
attributes as well.
|
|
317
|
+
|
|
318
|
+
"""
|
|
319
|
+
def __eq__(self, other):
|
|
320
|
+
"""
|
|
321
|
+
__eq__ Overrides the default implementation
|
|
322
|
+
|
|
323
|
+
Compare all attributes as well.
|
|
324
|
+
|
|
325
|
+
Parameters
|
|
326
|
+
----------
|
|
327
|
+
other : object
|
|
328
|
+
|
|
329
|
+
Returns
|
|
330
|
+
-------
|
|
331
|
+
bool
|
|
332
|
+
False if the two objects are not of the same class or any of the attributes differ
|
|
333
|
+
"""
|
|
334
|
+
if isinstance(other, __class__):
|
|
335
|
+
# TODO find difference in keys and check whether they are None or "" in the other or vice versa
|
|
336
|
+
snn = [k for k,v in self.__dict__.items() if (not v is None and not v == "")]
|
|
337
|
+
onn = [k for k,v in other.__dict__.items() if (not v is None and not v == "")]
|
|
338
|
+
if set(snn) == set(onn):
|
|
339
|
+
return all([self.__getattribute__(attr) == other.__getattribute__(attr) for attr in self.__dict__.keys()])
|
|
340
|
+
return False
|
|
341
|
+
|
|
342
|
+
@JsonSerialisable.register
|
|
343
|
+
class ControlledVocabulary(JsonObject):
|
|
344
|
+
"""
|
|
345
|
+
ControlledVocabulary Object representation for mzQC schema type ControlledVocabulary
|
|
346
|
+
|
|
347
|
+
"""
|
|
348
|
+
def __init__(self, name: str="", uri: str="", version: str=""):
|
|
349
|
+
self.name = name # required
|
|
350
|
+
self.uri = uri # required
|
|
351
|
+
self.version = version # optional
|
|
352
|
+
|
|
353
|
+
@JsonSerialisable.register
|
|
354
|
+
class CvParameter(JsonObject):
|
|
355
|
+
"""
|
|
356
|
+
CvParameter Object representation for mzQC schema type CvParameter
|
|
357
|
+
|
|
358
|
+
"""
|
|
359
|
+
def __init__(self, accession: str="",
|
|
360
|
+
name: str="",
|
|
361
|
+
description: str="",
|
|
362
|
+
value: Union[int,str,float,IntVector,StringVector,FloatVector,IntMatrix,StringMatrix,FloatMatrix,Table, None]=None,
|
|
363
|
+
unit: str=""):
|
|
364
|
+
self.accession = accession # required "pattern": "^[A-Z]+:[0-9]{7}$"
|
|
365
|
+
self.name = name # required
|
|
366
|
+
self.description = description # optional, "pattern": "^[A-Z]+$"
|
|
367
|
+
self.value = value # optional
|
|
368
|
+
self.unit = unit # optional, IMO this should be accession only, not annother cvParam
|
|
369
|
+
|
|
370
|
+
@JsonSerialisable.register
|
|
371
|
+
class AnalysisSoftware(CvParameter):
|
|
372
|
+
"""
|
|
373
|
+
AnalysisSoftware Object representation for mzQC schema type AnalysisSoftware
|
|
374
|
+
|
|
375
|
+
"""
|
|
376
|
+
def __init__(self, accession: str="",
|
|
377
|
+
name: str="",
|
|
378
|
+
description: str="",
|
|
379
|
+
value: str="",
|
|
380
|
+
unit: str="",
|
|
381
|
+
version: str = "",
|
|
382
|
+
uri: str = ""):
|
|
383
|
+
super().__init__(accession, name, description, value, unit) # optional, this will set None to optional omitted arguments
|
|
384
|
+
self.version = version # required
|
|
385
|
+
self.uri = uri # required
|
|
386
|
+
|
|
387
|
+
@JsonSerialisable.register
|
|
388
|
+
class InputFile(JsonObject):
|
|
389
|
+
"""
|
|
390
|
+
InputFile Object representation for mzQC schema type InputFile
|
|
391
|
+
|
|
392
|
+
"""
|
|
393
|
+
def __init__(self, location: str = "",
|
|
394
|
+
name: str = "",
|
|
395
|
+
fileFormat: CvParameter = None,
|
|
396
|
+
fileProperties: List[CvParameter] = None):
|
|
397
|
+
self.location = location # required , uri
|
|
398
|
+
self.name = name # required , string (doubles as internal and external ref anchor?)
|
|
399
|
+
self.fileFormat = fileFormat # required , cvParam
|
|
400
|
+
self.fileProperties = [] if fileProperties is None else fileProperties # optional, cvParam, at least one item
|
|
401
|
+
|
|
402
|
+
@JsonSerialisable.register
|
|
403
|
+
class MetaDataParameters(JsonObject):
|
|
404
|
+
"""
|
|
405
|
+
MetaDataParameters Object representation for mzQC schema type MetaDataParameters
|
|
406
|
+
|
|
407
|
+
"""
|
|
408
|
+
def __init__(self,
|
|
409
|
+
# fileProvenance: str="",
|
|
410
|
+
# cv_params: List[CvParameter] = None ,
|
|
411
|
+
label: str = "",
|
|
412
|
+
inputFiles: List[InputFile] = None,
|
|
413
|
+
analysisSoftware: List[AnalysisSoftware]=None
|
|
414
|
+
):
|
|
415
|
+
# self.fileProvenance = fileProvenance # not in schema
|
|
416
|
+
# self.cv_params = [] if cv_params is None else cv_params # not in schema, IMO should be in there
|
|
417
|
+
self.label = label # optional
|
|
418
|
+
self.inputFiles = [] if inputFiles is None else inputFiles # required
|
|
419
|
+
self.analysisSoftware = [] if analysisSoftware is None else analysisSoftware # required
|
|
420
|
+
|
|
421
|
+
@JsonSerialisable.register
|
|
422
|
+
class QualityMetric(CvParameter):
|
|
423
|
+
"""
|
|
424
|
+
QualityMetric Object representation is passed for its more concrete derivatives
|
|
425
|
+
|
|
426
|
+
"""
|
|
427
|
+
pass
|
|
428
|
+
# def __init__(self, cvRef: str="",
|
|
429
|
+
# accession: str="",
|
|
430
|
+
# name: str="",
|
|
431
|
+
# description: str="",
|
|
432
|
+
# value: Union[int,str,float,IntVector,StringVector,FloatVector,IntMatrix,StringMatrix,FloatMatrix,Table, None]=None, # here we could clamp down on allowed value types
|
|
433
|
+
# unit: str=""):
|
|
434
|
+
# super().__init__(cvRef, accession, name, description, value, unit) # optional, this will set None to optional omitted arguments
|
|
435
|
+
# implementation: this is a different object class because we want to make
|
|
436
|
+
# semantical distinctions between pure metrics and generic CvParams
|
|
437
|
+
|
|
438
|
+
@JsonSerialisable.register
|
|
439
|
+
class BaseQuality(JsonObject):
|
|
440
|
+
"""
|
|
441
|
+
BaseQuality Object representation for mzQC schema type BaseQuality
|
|
442
|
+
|
|
443
|
+
"""
|
|
444
|
+
def __init__(self, metadata: MetaDataParameters=None,
|
|
445
|
+
qualityMetrics: List[QualityMetric]=None):
|
|
446
|
+
self.metadata = metadata # required
|
|
447
|
+
self.qualityMetrics = [] if qualityMetrics is None else qualityMetrics # required,
|
|
448
|
+
|
|
449
|
+
@JsonSerialisable.register
|
|
450
|
+
class RunQuality(BaseQuality):
|
|
451
|
+
"""
|
|
452
|
+
QualityMetric Object representation is passed for its more general basis
|
|
453
|
+
|
|
454
|
+
"""
|
|
455
|
+
pass
|
|
456
|
+
|
|
457
|
+
@JsonSerialisable.register
|
|
458
|
+
class SetQuality(BaseQuality):
|
|
459
|
+
"""
|
|
460
|
+
SetQuality Object representation is passed for its more general basis
|
|
461
|
+
|
|
462
|
+
"""
|
|
463
|
+
pass
|
|
464
|
+
|
|
465
|
+
@JsonSerialisable.register
|
|
466
|
+
class MzQcFile(JsonObject):
|
|
467
|
+
"""
|
|
468
|
+
MzQcFile Object representation for mzQC schema type MzQcFile
|
|
469
|
+
|
|
470
|
+
"""
|
|
471
|
+
def __init__(self, creationDate: Union[datetime,str] = datetime.now().replace(microsecond=0),
|
|
472
|
+
version: str = "1.0.0",
|
|
473
|
+
contactName: str = "", contactAddress: str = "", description: str = "",
|
|
474
|
+
runQualities: List[RunQuality]=None,
|
|
475
|
+
setQualities: List[SetQuality]=None,
|
|
476
|
+
controlledVocabularies: List[ControlledVocabulary]=None
|
|
477
|
+
):
|
|
478
|
+
self.creationDate = JsonSerialisable.time_helper(creationDate) if isinstance(creationDate, str) else creationDate # required
|
|
479
|
+
self.version = version # required
|
|
480
|
+
self.contactName = contactName # optional
|
|
481
|
+
self.contactAddress = contactAddress # optional
|
|
482
|
+
self.description = description # optional
|
|
483
|
+
self.runQualities = [] if runQualities is None else runQualities # either or set required
|
|
484
|
+
self.setQualities = [] if setQualities is None else setQualities # either or run required
|
|
485
|
+
self.controlledVocabularies = [] if controlledVocabularies is None else controlledVocabularies # required
|