pynmrstar 3.3.5__cp310-cp310-musllinux_1_2_x86_64.whl
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.
Potentially problematic release.
This version of pynmrstar might be problematic. Click here for more details.
- cnmrstar.cpython-310-x86_64-linux-gnu.so +0 -0
- pynmrstar/__init__.py +55 -0
- pynmrstar/_internal.py +292 -0
- pynmrstar/definitions.py +32 -0
- pynmrstar/entry.py +970 -0
- pynmrstar/exceptions.py +43 -0
- pynmrstar/loop.py +1197 -0
- pynmrstar/parser.py +287 -0
- pynmrstar/reference_files/comments.str +538 -0
- pynmrstar/reference_files/data_types.csv +24 -0
- pynmrstar/reference_files/schema.csv +6726 -0
- pynmrstar/saveframe.py +1015 -0
- pynmrstar/schema.py +367 -0
- pynmrstar/utils.py +134 -0
- pynmrstar-3.3.5.dist-info/LICENSE +21 -0
- pynmrstar-3.3.5.dist-info/METADATA +59 -0
- pynmrstar-3.3.5.dist-info/RECORD +19 -0
- pynmrstar-3.3.5.dist-info/WHEEL +5 -0
- pynmrstar-3.3.5.dist-info/top_level.txt +2 -0
pynmrstar/schema.py
ADDED
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
import decimal
|
|
2
|
+
import logging
|
|
3
|
+
import os
|
|
4
|
+
import re
|
|
5
|
+
from csv import DictReader
|
|
6
|
+
from datetime import date
|
|
7
|
+
from functools import lru_cache
|
|
8
|
+
from io import StringIO
|
|
9
|
+
from typing import Union, List, Optional, Any, Dict, IO
|
|
10
|
+
|
|
11
|
+
from pynmrstar import definitions, utils
|
|
12
|
+
from pynmrstar._internal import _interpret_file
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger('pynmrstar')
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Schema(object):
|
|
18
|
+
"""A BMRB schema. Used to validate NMR-STAR files. Unless you need to
|
|
19
|
+
choose a specific schema version, PyNMR-STAR will automatically load
|
|
20
|
+
a schema for you. If you do need a specific schema version, you can
|
|
21
|
+
create an object of this class and then pass it to the methods
|
|
22
|
+
which allow the specification of a schema. """
|
|
23
|
+
|
|
24
|
+
def __init__(self, schema_file: Union[str, IO] = None) -> None:
|
|
25
|
+
"""Initialize a BMRB schema. With no arguments the most
|
|
26
|
+
up-to-date schema will be fetched from the BMRB FTP site.
|
|
27
|
+
Otherwise pass a URL or a file to load a schema from using the
|
|
28
|
+
schema_file keyword argument."""
|
|
29
|
+
|
|
30
|
+
self.headers: List[str] = []
|
|
31
|
+
self.schema: Dict[str, Dict[str, str]] = {}
|
|
32
|
+
self.schema_order: List[str] = []
|
|
33
|
+
self.category_order: List[str] = []
|
|
34
|
+
self.version: str = "unknown"
|
|
35
|
+
self.data_types: Dict[str, str] = {}
|
|
36
|
+
|
|
37
|
+
# Try loading from the internet first
|
|
38
|
+
if schema_file is None:
|
|
39
|
+
schema_file = definitions.SCHEMA_URL
|
|
40
|
+
self.schema_file = schema_file
|
|
41
|
+
|
|
42
|
+
# Get whatever schema they specified, wrap in StringIO and pass that to the csv reader
|
|
43
|
+
schema_stream = _interpret_file(schema_file)
|
|
44
|
+
fix_newlines = StringIO('\n'.join(schema_stream.read().splitlines()))
|
|
45
|
+
|
|
46
|
+
csv_reader_instance = DictReader(fix_newlines)
|
|
47
|
+
self.headers = csv_reader_instance.fieldnames
|
|
48
|
+
|
|
49
|
+
# Skip the header descriptions and header index values and anything
|
|
50
|
+
# else before the real data starts
|
|
51
|
+
tmp_line = next(csv_reader_instance)
|
|
52
|
+
try:
|
|
53
|
+
while tmp_line['Dictionary sequence'] != "TBL_BEGIN":
|
|
54
|
+
tmp_line = next(csv_reader_instance)
|
|
55
|
+
except IndexError:
|
|
56
|
+
raise ValueError(f"Could not parse a schema from the specified URL: {schema_file}")
|
|
57
|
+
self.version = tmp_line['ADIT category view type']
|
|
58
|
+
|
|
59
|
+
for line in csv_reader_instance:
|
|
60
|
+
|
|
61
|
+
if line['Dictionary sequence'] == "TBL_END":
|
|
62
|
+
break
|
|
63
|
+
|
|
64
|
+
single_tag_data: Dict[str, any] = dict(line)
|
|
65
|
+
|
|
66
|
+
# Convert nulls
|
|
67
|
+
if single_tag_data['Nullable'] == "NOT NULL":
|
|
68
|
+
single_tag_data['Nullable'] = False
|
|
69
|
+
else:
|
|
70
|
+
single_tag_data['Nullable'] = True
|
|
71
|
+
if '' in single_tag_data:
|
|
72
|
+
del single_tag_data['']
|
|
73
|
+
|
|
74
|
+
self.schema[single_tag_data['Tag'].lower()] = single_tag_data
|
|
75
|
+
self.schema_order.append(single_tag_data['Tag'])
|
|
76
|
+
formatted = utils.format_category(single_tag_data['Tag'])
|
|
77
|
+
if formatted not in self.category_order:
|
|
78
|
+
self.category_order.append(formatted)
|
|
79
|
+
|
|
80
|
+
try:
|
|
81
|
+
# Read in the data types
|
|
82
|
+
types_file = _interpret_file(os.path.join(os.path.dirname(os.path.realpath(__file__)),
|
|
83
|
+
"reference_files/data_types.csv"))
|
|
84
|
+
except IOError:
|
|
85
|
+
# Load the data types from Github if we can't find them locally
|
|
86
|
+
try:
|
|
87
|
+
types_file = _interpret_file(definitions.TYPES_URL)
|
|
88
|
+
except Exception:
|
|
89
|
+
raise ValueError("Could not load the data type definition file from disk or the internet!")
|
|
90
|
+
|
|
91
|
+
csv_reader_instance = DictReader(types_file, fieldnames=['type_name', 'type_definition'])
|
|
92
|
+
for item in csv_reader_instance:
|
|
93
|
+
self.data_types[item['type_name']] = f"^{item['type_definition']}$"
|
|
94
|
+
|
|
95
|
+
def __repr__(self) -> str:
|
|
96
|
+
"""Return how we can be initialized."""
|
|
97
|
+
|
|
98
|
+
return f"pynmrstar.Schema(schema_file='{self.schema_file}') version {self.version}"
|
|
99
|
+
|
|
100
|
+
def __str__(self) -> str:
|
|
101
|
+
"""Print the schema that we are adhering to."""
|
|
102
|
+
|
|
103
|
+
return self.string_representation()
|
|
104
|
+
|
|
105
|
+
def add_tag(self, tag: str, tag_type: str, null_allowed: bool, sf_category: str, loop_flag: bool,
|
|
106
|
+
after: str = None):
|
|
107
|
+
""" Adds the specified tag to the tag dictionary. You must provide:
|
|
108
|
+
|
|
109
|
+
1) The full tag as such:
|
|
110
|
+
"_Entry_interview.Sf_category"
|
|
111
|
+
2) The tag type which is one of the following:
|
|
112
|
+
"INTEGER"
|
|
113
|
+
"FLOAT"
|
|
114
|
+
"CHAR(len)"
|
|
115
|
+
"VARCHAR(len)"
|
|
116
|
+
"TEXT"
|
|
117
|
+
"DATETIME year to day"
|
|
118
|
+
3) A python True/False that indicates whether null values are allowed.
|
|
119
|
+
4) The sf_category of the parent saveframe.
|
|
120
|
+
5) A True/False value which indicates if this tag is a loop tag.
|
|
121
|
+
6) Optional: The tag to order this tag behind when normalizing
|
|
122
|
+
saveframes."""
|
|
123
|
+
|
|
124
|
+
# Add the underscore preceding the tag
|
|
125
|
+
if tag[0] != "_":
|
|
126
|
+
tag = "_" + tag
|
|
127
|
+
|
|
128
|
+
# See if the tag is already in the schema
|
|
129
|
+
if tag.lower() in self.schema:
|
|
130
|
+
raise ValueError("Cannot add a tag to the schema that is already in"
|
|
131
|
+
f" the schema: {tag}")
|
|
132
|
+
|
|
133
|
+
# Check the tag type
|
|
134
|
+
tag_type = tag_type.upper()
|
|
135
|
+
if tag_type not in ["INTEGER", "FLOAT", "TEXT", "DATETIME year to day"]:
|
|
136
|
+
if tag_type.startswith("CHAR(") or tag_type.startswith("VARCHAR("):
|
|
137
|
+
# This will allow things through that have extra junk on the end, but in general it is
|
|
138
|
+
# okay to be forgiving as long as we can guess what they mean.
|
|
139
|
+
length = tag_type[tag_type.index("(") + 1:tag_type.index(")")]
|
|
140
|
+
# Check the length for non-numbers and 0
|
|
141
|
+
try:
|
|
142
|
+
1 / int(length)
|
|
143
|
+
except (ValueError, ZeroDivisionError):
|
|
144
|
+
raise ValueError(f"Illegal length specified in tag type: {length}")
|
|
145
|
+
|
|
146
|
+
# Cut off anything that might be at the end
|
|
147
|
+
tag_type = tag_type[0:tag_type.index(")") + 1]
|
|
148
|
+
else:
|
|
149
|
+
raise ValueError("The tag type you provided is not valid. Please use a type as specified in the help "
|
|
150
|
+
"for this method.")
|
|
151
|
+
|
|
152
|
+
# Check the null allowed
|
|
153
|
+
if str(null_allowed).lower() == "false":
|
|
154
|
+
null_allowed = False
|
|
155
|
+
if str(null_allowed).lower() == "true":
|
|
156
|
+
null_allowed = True
|
|
157
|
+
if not (null_allowed is True or null_allowed is False):
|
|
158
|
+
raise ValueError("Please specify whether null is allowed with True/False")
|
|
159
|
+
|
|
160
|
+
# Check the category
|
|
161
|
+
if not sf_category:
|
|
162
|
+
raise ValueError("Please provide the sf_category of the parent saveframe.")
|
|
163
|
+
|
|
164
|
+
# Check the loop flag
|
|
165
|
+
if loop_flag is not True and loop_flag:
|
|
166
|
+
raise ValueError("Invalid loop_flag. Please specify True or False.")
|
|
167
|
+
|
|
168
|
+
# Conditionally check the tag to insert after
|
|
169
|
+
new_tag_pos = len(self.schema_order)
|
|
170
|
+
if after is not None:
|
|
171
|
+
try:
|
|
172
|
+
# See if the tag with caps exists in the order
|
|
173
|
+
new_tag_pos = self.schema_order.index(after) + 1
|
|
174
|
+
except ValueError:
|
|
175
|
+
try:
|
|
176
|
+
# See if the tag in lowercase exists in the order
|
|
177
|
+
new_tag_pos = [x.lower() for x in
|
|
178
|
+
self.schema_order].index(after.lower()) + 1
|
|
179
|
+
except ValueError:
|
|
180
|
+
raise ValueError("The tag you specified to insert this tag after does not exist in the schema.")
|
|
181
|
+
else:
|
|
182
|
+
# Determine a sensible place to put the new tag
|
|
183
|
+
search = utils.format_category(tag.lower())
|
|
184
|
+
for pos, stag in enumerate([x.lower() for x in self.schema_order]):
|
|
185
|
+
if stag.startswith(search):
|
|
186
|
+
new_tag_pos = pos + 1
|
|
187
|
+
|
|
188
|
+
# Add the new tag to the tag order and tag list
|
|
189
|
+
self.schema_order.insert(new_tag_pos, tag)
|
|
190
|
+
self.category_order.insert(new_tag_pos, "_" + utils.format_tag(tag))
|
|
191
|
+
|
|
192
|
+
# Calculate up the 'Dictionary Sequence' based on the tag position
|
|
193
|
+
new_tag_pos = (new_tag_pos - 1) * 10
|
|
194
|
+
|
|
195
|
+
def _test_pos(position, schema) -> int:
|
|
196
|
+
for item in schema.schema.values():
|
|
197
|
+
if float(item["Dictionary sequence"]) == position:
|
|
198
|
+
return _test_pos(position + 1, schema)
|
|
199
|
+
return position
|
|
200
|
+
|
|
201
|
+
new_tag_pos = _test_pos(new_tag_pos, self)
|
|
202
|
+
|
|
203
|
+
self.schema[tag.lower()] = {"Data Type": tag_type, "Loopflag": loop_flag,
|
|
204
|
+
"Nullable": null_allowed, "public": "Y",
|
|
205
|
+
"SFCategory": sf_category, "Tag": tag,
|
|
206
|
+
"Dictionary sequence": new_tag_pos}
|
|
207
|
+
|
|
208
|
+
@lru_cache(maxsize=1024, typed=True)
|
|
209
|
+
def convert_tag(self, tag: str, value: Any) -> \
|
|
210
|
+
Optional[Union[str, int, decimal.Decimal, date]]:
|
|
211
|
+
""" Converts the provided tag from string to the appropriate
|
|
212
|
+
type as specified in this schema."""
|
|
213
|
+
|
|
214
|
+
# If we don't know what the tag is, just return it
|
|
215
|
+
if tag.lower() not in self.schema:
|
|
216
|
+
if tag != '_internal.use':
|
|
217
|
+
logger.warning(f"Couldn't convert tag data type because it is not in the dictionary: {tag}")
|
|
218
|
+
return value
|
|
219
|
+
|
|
220
|
+
full_tag = self.schema[tag.lower()]
|
|
221
|
+
|
|
222
|
+
# Get the type
|
|
223
|
+
value_type, null_allowed = full_tag["Data Type"], full_tag["Nullable"]
|
|
224
|
+
|
|
225
|
+
# Check for null
|
|
226
|
+
if value in definitions.NULL_VALUES:
|
|
227
|
+
return None
|
|
228
|
+
|
|
229
|
+
# Keep strings strings
|
|
230
|
+
if "CHAR" in value_type or "VARCHAR" in value_type or "TEXT" in value_type:
|
|
231
|
+
return value
|
|
232
|
+
|
|
233
|
+
# Convert ints
|
|
234
|
+
if "INTEGER" in value_type:
|
|
235
|
+
try:
|
|
236
|
+
return int(value)
|
|
237
|
+
except (ValueError, TypeError):
|
|
238
|
+
raise ValueError("Could not parse the file because a value that should be an INTEGER is not. Either "
|
|
239
|
+
f"do not specify convert_data_types or fix the file. Tag: '{tag}'.")
|
|
240
|
+
|
|
241
|
+
# Convert floats
|
|
242
|
+
if "FLOAT" in value_type:
|
|
243
|
+
try:
|
|
244
|
+
# If we used int() we would lose the precision
|
|
245
|
+
return decimal.Decimal(value)
|
|
246
|
+
except (decimal.InvalidOperation, TypeError):
|
|
247
|
+
raise ValueError("Could not parse the file because a value that should be a FLOAT is not. Either "
|
|
248
|
+
f"do not specify convert_data_types or fix the file. Tag: '{tag}'.")
|
|
249
|
+
|
|
250
|
+
if "DATETIME year to day" in value_type:
|
|
251
|
+
try:
|
|
252
|
+
year, month, day = [int(x) for x in value.split("-")]
|
|
253
|
+
return date(year, month, day)
|
|
254
|
+
except (ValueError, TypeError):
|
|
255
|
+
raise ValueError("Could not parse the file because a value that should be a DATETIME is not. Please "
|
|
256
|
+
f"do not specify convert_data_types or fix the file. Tag: '{tag}'.")
|
|
257
|
+
|
|
258
|
+
# We don't know the data type, so just keep it a string
|
|
259
|
+
return value
|
|
260
|
+
|
|
261
|
+
def string_representation(self, search: bool = None) -> str:
|
|
262
|
+
""" Prints all the tags in the schema if search is not specified
|
|
263
|
+
and prints the tags that contain the search string if it is."""
|
|
264
|
+
|
|
265
|
+
# Get the longest lengths
|
|
266
|
+
lengths = [max([len(utils.format_tag(x)) for x in self.schema_order])]
|
|
267
|
+
|
|
268
|
+
values = []
|
|
269
|
+
for key in self.schema.keys():
|
|
270
|
+
sc = self.schema[key]
|
|
271
|
+
values.append((sc["Data Type"], sc["Nullable"], sc["SFCategory"], sc["Tag"]))
|
|
272
|
+
|
|
273
|
+
for y in range(0, len(values[0])):
|
|
274
|
+
lengths.append(max([len(str(x[y])) for x in values]))
|
|
275
|
+
|
|
276
|
+
text = f"""BMRB schema from: '{self.schema_file}' version '{self.version}'
|
|
277
|
+
{''}
|
|
278
|
+
{'Tag_Prefix':<{lengths[0]}} {'Tag':<{lengths[1] - 6}} {'Type':<{lengths[2]}} {'Null_Allowed':<{lengths[3]}} {'SF_Category'}
|
|
279
|
+
"""
|
|
280
|
+
last_tag = ""
|
|
281
|
+
|
|
282
|
+
for tag in self.schema_order:
|
|
283
|
+
# Skip to the next tag if there is a search and it fails
|
|
284
|
+
if search and search not in tag:
|
|
285
|
+
continue
|
|
286
|
+
st = self.schema.get(tag.lower(), None)
|
|
287
|
+
tag_cat = utils.format_category(tag)
|
|
288
|
+
if st:
|
|
289
|
+
if tag_cat != last_tag:
|
|
290
|
+
last_tag = tag_cat
|
|
291
|
+
text += "\n%-30s\n" % tag_cat
|
|
292
|
+
|
|
293
|
+
text += " %-*s %-*s %-*s %-*s\n" % (lengths[0], utils.format_tag(tag),
|
|
294
|
+
lengths[1], st["Data Type"],
|
|
295
|
+
lengths[2], st["Nullable"],
|
|
296
|
+
lengths[3], st["SFCategory"])
|
|
297
|
+
|
|
298
|
+
return text
|
|
299
|
+
|
|
300
|
+
def val_type(self, tag: str, value: Any, category: str = None):
|
|
301
|
+
""" Validates that a tag matches the type it should have
|
|
302
|
+
according to this schema."""
|
|
303
|
+
|
|
304
|
+
if tag.lower() not in self.schema:
|
|
305
|
+
return [f"Tag '{tag}' not found in schema."]
|
|
306
|
+
|
|
307
|
+
# We will skip type checks for None's
|
|
308
|
+
is_none = value is None
|
|
309
|
+
|
|
310
|
+
# Allow manual specification of conversions for booleans, Nones, etc.
|
|
311
|
+
if value in definitions.STR_CONVERSION_DICT:
|
|
312
|
+
if any(isinstance(value, type(x)) for x in definitions.STR_CONVERSION_DICT):
|
|
313
|
+
value = definitions.STR_CONVERSION_DICT[value]
|
|
314
|
+
|
|
315
|
+
# Value should always be string
|
|
316
|
+
if not isinstance(value, str):
|
|
317
|
+
value = str(value)
|
|
318
|
+
|
|
319
|
+
# Check that it isn't a string None
|
|
320
|
+
if value in definitions.NULL_VALUES:
|
|
321
|
+
is_none = True
|
|
322
|
+
|
|
323
|
+
# Make local copies of the fields we care about
|
|
324
|
+
full_tag = self.schema[tag.lower()]
|
|
325
|
+
bmrb_type = full_tag["BMRB data type"]
|
|
326
|
+
val_type = full_tag["Data Type"]
|
|
327
|
+
null_allowed = full_tag["Nullable"]
|
|
328
|
+
allowed_category = full_tag["SFCategory"]
|
|
329
|
+
capitalized_tag = full_tag["Tag"]
|
|
330
|
+
|
|
331
|
+
if category is not None:
|
|
332
|
+
if category != allowed_category:
|
|
333
|
+
return [f"The tag '{capitalized_tag}' in category '{category}' should be in category "
|
|
334
|
+
f"'{allowed_category}'."]
|
|
335
|
+
|
|
336
|
+
if is_none:
|
|
337
|
+
if not null_allowed:
|
|
338
|
+
return [f"Value cannot be NULL but is: {capitalized_tag}':'{value}'."]
|
|
339
|
+
return []
|
|
340
|
+
else:
|
|
341
|
+
# Don't run these checks on unassigned tags
|
|
342
|
+
if "CHAR" in val_type:
|
|
343
|
+
length = int(val_type[val_type.index("(") + 1:val_type.index(")")])
|
|
344
|
+
if len(str(value)) > length:
|
|
345
|
+
return [f"Length of '{len(value)}' is too long for '{val_type}': '{capitalized_tag}':'{value}'."]
|
|
346
|
+
|
|
347
|
+
# Check that the value matches the regular expression for the type
|
|
348
|
+
if not re.match(self.data_types[bmrb_type], str(value)):
|
|
349
|
+
return [f"Value does not match specification: '{capitalized_tag}':'{value}'.\n"
|
|
350
|
+
f" Type specified: {bmrb_type}\n"
|
|
351
|
+
f" Regular expression for type: '{self.data_types[bmrb_type]}'"]
|
|
352
|
+
|
|
353
|
+
# Check the tag capitalization
|
|
354
|
+
if tag != capitalized_tag:
|
|
355
|
+
return [f"The tag '{tag}' is improperly capitalized but otherwise valid. Should be '{capitalized_tag}'."]
|
|
356
|
+
return []
|
|
357
|
+
|
|
358
|
+
def tag_key(self, x) -> int:
|
|
359
|
+
""" Helper function to figure out how to sort the tags."""
|
|
360
|
+
|
|
361
|
+
try:
|
|
362
|
+
return self.schema_order.index(x)
|
|
363
|
+
except ValueError:
|
|
364
|
+
# Generate an arbitrary sort order for tags that aren't in the
|
|
365
|
+
# schema but make sure that they always come after tags in the
|
|
366
|
+
# schema
|
|
367
|
+
return len(self.schema_order) + abs(hash(x))
|
pynmrstar/utils.py
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
""" This file contains various helper functions."""
|
|
4
|
+
import functools
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
from typing import Iterable, Any, Dict
|
|
8
|
+
from urllib.error import HTTPError, URLError
|
|
9
|
+
|
|
10
|
+
from pynmrstar import definitions, cnmrstar, entry as entry_mod
|
|
11
|
+
from pynmrstar._internal import _interpret_file
|
|
12
|
+
from pynmrstar.schema import Schema
|
|
13
|
+
|
|
14
|
+
# Set this to allow import * from pynmrstar to work sensibly
|
|
15
|
+
__all__ = ['diff', 'format_category', 'format_tag', 'get_schema', 'iter_entries', 'quote_value', 'validate']
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def diff(entry1: 'entry_mod.Entry', entry2: 'entry_mod.Entry') -> None:
|
|
19
|
+
"""Prints the differences between two entries. Non-equal entries
|
|
20
|
+
will always be detected, but specific differences detected depends
|
|
21
|
+
on the order of entries."""
|
|
22
|
+
|
|
23
|
+
diffs = entry1.compare(entry2)
|
|
24
|
+
if len(diffs) == 0:
|
|
25
|
+
print("Identical entries.")
|
|
26
|
+
for difference in diffs:
|
|
27
|
+
print(difference)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@functools.lru_cache(maxsize=1024)
|
|
31
|
+
def format_category(tag: str) -> str:
|
|
32
|
+
"""Adds a '_' to the front of a tag (if not present) and strips out
|
|
33
|
+
anything after a '.'"""
|
|
34
|
+
|
|
35
|
+
if tag:
|
|
36
|
+
if not tag.startswith("_"):
|
|
37
|
+
tag = "_" + tag
|
|
38
|
+
if "." in tag:
|
|
39
|
+
tag = tag[:tag.index(".")]
|
|
40
|
+
return tag
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@functools.lru_cache(maxsize=1024)
|
|
44
|
+
def format_tag(tag: str) -> str:
|
|
45
|
+
"""Strips anything before the '.'"""
|
|
46
|
+
|
|
47
|
+
if '.' in tag:
|
|
48
|
+
return tag[tag.index('.') + 1:]
|
|
49
|
+
return tag
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@functools.lru_cache(maxsize=1024)
|
|
53
|
+
def format_tag_lc(tag: str) -> str:
|
|
54
|
+
"""Strips anything before the '.' and makes the tag lowercase. """
|
|
55
|
+
|
|
56
|
+
return format_tag(tag.lower())
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
# noinspection PyDefaultArgument
|
|
60
|
+
def get_schema(passed_schema: 'Schema' = None, _cached_schema: Dict[str, Schema] = {}) -> 'Schema':
|
|
61
|
+
"""If passed a schema (not None) it returns it. If passed none,
|
|
62
|
+
it checks if the default schema has been initialized. If not
|
|
63
|
+
initialized, it initializes it. Then it returns the default schema."""
|
|
64
|
+
|
|
65
|
+
if passed_schema:
|
|
66
|
+
return passed_schema
|
|
67
|
+
|
|
68
|
+
if not _cached_schema:
|
|
69
|
+
|
|
70
|
+
# Try to load the local file first
|
|
71
|
+
try:
|
|
72
|
+
schema_file = os.path.join(os.path.dirname(os.path.realpath(__file__)))
|
|
73
|
+
schema_file = os.path.join(schema_file, "reference_files/schema.csv")
|
|
74
|
+
_cached_schema['schema'] = Schema(schema_file=schema_file)
|
|
75
|
+
except IOError:
|
|
76
|
+
# Try to load from the internet
|
|
77
|
+
try:
|
|
78
|
+
_cached_schema['schema'] = Schema()
|
|
79
|
+
except (HTTPError, URLError):
|
|
80
|
+
raise ValueError("Could not load a BMRB schema from the internet or from the local repository.")
|
|
81
|
+
|
|
82
|
+
return _cached_schema['schema']
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def iter_entries(metabolomics: bool = False) -> Iterable['entry_mod.Entry']:
|
|
86
|
+
""" Returns a generator that will yield an Entry object for every
|
|
87
|
+
macromolecule entry in the current BMRB database. Perfect for performing
|
|
88
|
+
an operation across the entire BMRB database. Set `metabolomics=True`
|
|
89
|
+
in order to get all the entries in the metabolomics database."""
|
|
90
|
+
|
|
91
|
+
api_url = f"{definitions.API_URL}/list_entries?database=macromolecules"
|
|
92
|
+
if metabolomics:
|
|
93
|
+
api_url = f"{definitions.API_URL}/list_entries?database=metabolomics"
|
|
94
|
+
|
|
95
|
+
for entry in json.loads(_interpret_file(api_url).read()):
|
|
96
|
+
yield entry_mod.Entry.from_database(entry)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@functools.lru_cache(maxsize=65536, typed=True)
|
|
100
|
+
def quote_value(value: Any) -> str:
|
|
101
|
+
"""Automatically quotes the value in the appropriate way. Don't
|
|
102
|
+
quote values you send to this method or they will show up in
|
|
103
|
+
another set of quotes as part of the actual data. E.g.:
|
|
104
|
+
|
|
105
|
+
quote_value('"e. coli"') returns '\'"e. coli"\''
|
|
106
|
+
|
|
107
|
+
while
|
|
108
|
+
|
|
109
|
+
quote_value("e. coli") returns "'e. coli'"
|
|
110
|
+
|
|
111
|
+
This will automatically be called on all values when you use a str()
|
|
112
|
+
method (so don't call it before inserting values into tags or loops).
|
|
113
|
+
|
|
114
|
+
Be mindful of the value of STR_CONVERSION_DICT as it will effect the
|
|
115
|
+
way the value is converted to a string.
|
|
116
|
+
|
|
117
|
+
"""
|
|
118
|
+
|
|
119
|
+
# Allow manual specification of conversions for booleans, Nones, etc.
|
|
120
|
+
if value in definitions.STR_CONVERSION_DICT:
|
|
121
|
+
if any(isinstance(value, type(x)) for x in definitions.STR_CONVERSION_DICT):
|
|
122
|
+
value = definitions.STR_CONVERSION_DICT[value]
|
|
123
|
+
|
|
124
|
+
return cnmrstar.quote_value(value)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def validate(entry_to_validate: 'entry_mod.Entry', schema: 'Schema' = None) -> None:
|
|
128
|
+
"""Prints a validation report of an object."""
|
|
129
|
+
|
|
130
|
+
validation = entry_to_validate.validate(schema=schema)
|
|
131
|
+
if len(validation) == 0:
|
|
132
|
+
print("No problems found during validation.")
|
|
133
|
+
for pos, err in enumerate(validation):
|
|
134
|
+
print(f"{pos + 1}: {err}")
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) [year] [fullname]
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: pynmrstar
|
|
3
|
+
Version: 3.3.5
|
|
4
|
+
Summary: PyNMR-STAR provides tools for reading, writing, modifying, and interacting with NMR-STAR files. Maintained by the BMRB.
|
|
5
|
+
Home-page: https://github.com/uwbmrb/PyNMRSTAR
|
|
6
|
+
Author: Jon Wedell
|
|
7
|
+
Author-email: wedell@uchc.edu
|
|
8
|
+
License: MIT
|
|
9
|
+
Keywords: bmrb,parser,nmr,nmrstar,biomagresbank,biological magnetic resonance bank
|
|
10
|
+
Classifier: Development Status :: 6 - Mature
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Intended Audience :: Developers
|
|
20
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
21
|
+
Classifier: Natural Language :: English
|
|
22
|
+
Classifier: Operating System :: MacOS
|
|
23
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
24
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
25
|
+
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
|
26
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
27
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
28
|
+
Requires-Python: >=3.7
|
|
29
|
+
Description-Content-Type: text/x-rst
|
|
30
|
+
License-File: LICENSE
|
|
31
|
+
Requires-Dist: requests<=3,>=2.21.0
|
|
32
|
+
|
|
33
|
+
Welcome to PyNMR-STAR!
|
|
34
|
+
======================================
|
|
35
|
+
|
|
36
|
+
A Python module for reading, writing, and manipulating NMR-STAR files.
|
|
37
|
+
|
|
38
|
+
|License| |Wheel| |PythonVersions|
|
|
39
|
+
|
|
40
|
+
Overview
|
|
41
|
+
--------
|
|
42
|
+
|
|
43
|
+
This library was developed by the BMRB to give the Python-using NMR
|
|
44
|
+
community tools to work with the NMR-STAR data format. It is used
|
|
45
|
+
internally and is actively maintained. The library is thoroughly
|
|
46
|
+
documented such that calling ``help(object_or_method)`` from an
|
|
47
|
+
interactive python session will print the documentation for the object
|
|
48
|
+
or method. An even more convenient way to learn how to use this library
|
|
49
|
+
is to read the documentation at `read the docs <https://pynmrstar.readthedocs.org>`__.
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
.. |PythonVersions| image:: https://img.shields.io/pypi/pyversions/pynmrstar.svg
|
|
53
|
+
:target: https://pypi.org/project/PyNMRSTAR
|
|
54
|
+
|
|
55
|
+
.. |License| image:: https://img.shields.io/pypi/l/pynmrstar.svg
|
|
56
|
+
:target: https://pypi.org/project/PyNMRSTAR
|
|
57
|
+
|
|
58
|
+
.. |Wheel| image:: https://img.shields.io/pypi/wheel/pynmrstar.svg
|
|
59
|
+
:target: https://pypi.org/project/PyNMRSTAR
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
cnmrstar.cpython-310-x86_64-linux-gnu.so,sha256=KBAgrS8hqSKCQF-2dsH9FYgilg_cG9C6H2xNUiVubp0,60792
|
|
2
|
+
pynmrstar/loop.py,sha256=46vAeXMtKaSovLzFhhiFQCz1nsNqOJPSVpkTTa9tbtg,51892
|
|
3
|
+
pynmrstar/exceptions.py,sha256=xJlGbHB_QXMiNWuU_6livAQBMljBuCba50WA_K9pIbc,1449
|
|
4
|
+
pynmrstar/schema.py,sha256=tJsVHkVQR8glvchFBzlRvGDpx2HxS76eucAjR_aKVog,15779
|
|
5
|
+
pynmrstar/_internal.py,sha256=GNWZPAKI7swRfqndVKamFyATNdNa_3nhocTvzT-fM4Y,12068
|
|
6
|
+
pynmrstar/parser.py,sha256=cYnRBkQAdXJU_HY6WCcWMY0Rq2weDyr1hxiWOoQbNfg,15546
|
|
7
|
+
pynmrstar/definitions.py,sha256=H4X8POb0CsA9lv8tiLupH0wBzKPdyy5OZCe0A1OqZiU,1510
|
|
8
|
+
pynmrstar/__init__.py,sha256=z9uQkhmqipDrLHpEk81w7y7HEgepEcKyK7ZWr8YavJA,1967
|
|
9
|
+
pynmrstar/utils.py,sha256=rd7JkVM9B7gMiveO0RhH6YtoJTB0CUiggsPtSVzmvl8,4662
|
|
10
|
+
pynmrstar/entry.py,sha256=BcwVtSdfcDj4NWzmWvSbwilG9whxTk6CyZUbR15mLBk,46240
|
|
11
|
+
pynmrstar/saveframe.py,sha256=5p-_NWi2nEju2FtJG5zm1aSdJrSZi_5lkCMIpTHWXYI,44133
|
|
12
|
+
pynmrstar/reference_files/comments.str,sha256=vOGWBs5XU3ddNyROkVOhaPwGb8kx5stoChgo6ICBMio,9942
|
|
13
|
+
pynmrstar/reference_files/schema.csv,sha256=BfAJyjfDQaZakdEL5V97dTSDxwgxQZCIp1DeKEhT65w,3694703
|
|
14
|
+
pynmrstar/reference_files/data_types.csv,sha256=awbw34Icg16-6epkQLGRM5dEvthcq3hvkgbt6rlZh0I,3432
|
|
15
|
+
pynmrstar-3.3.5.dist-info/METADATA,sha256=DDi4dJhZ0O9p6mWUX2bd72UtKVfKySfqa1uSJEOQRN8,2438
|
|
16
|
+
pynmrstar-3.3.5.dist-info/RECORD,,
|
|
17
|
+
pynmrstar-3.3.5.dist-info/top_level.txt,sha256=e5QP9re453LfgZ_mZNGHa7E5HFAXiRoNBPkF1hnn7JQ,19
|
|
18
|
+
pynmrstar-3.3.5.dist-info/WHEEL,sha256=C_u37Gjsvm-axxahYbHrVJK_acyekKqTTso2RCL4LvU,112
|
|
19
|
+
pynmrstar-3.3.5.dist-info/LICENSE,sha256=pAZXnNE2dxxwXFIduGyn1gpvPefJtUYOYZOi3yeGG94,1068
|