pynmrstar 3.3.5__cp312-cp312-macosx_10_13_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.

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-312-darwin.so,sha256=ZYYrEf-cniAP2ML8TZJ_tQMW7ZQEbWcwfhg0YAq2z58,38160
2
+ pynmrstar/definitions.py,sha256=H4X8POb0CsA9lv8tiLupH0wBzKPdyy5OZCe0A1OqZiU,1510
3
+ pynmrstar/_internal.py,sha256=GNWZPAKI7swRfqndVKamFyATNdNa_3nhocTvzT-fM4Y,12068
4
+ pynmrstar/__init__.py,sha256=z9uQkhmqipDrLHpEk81w7y7HEgepEcKyK7ZWr8YavJA,1967
5
+ pynmrstar/parser.py,sha256=cYnRBkQAdXJU_HY6WCcWMY0Rq2weDyr1hxiWOoQbNfg,15546
6
+ pynmrstar/utils.py,sha256=rd7JkVM9B7gMiveO0RhH6YtoJTB0CUiggsPtSVzmvl8,4662
7
+ pynmrstar/entry.py,sha256=BcwVtSdfcDj4NWzmWvSbwilG9whxTk6CyZUbR15mLBk,46240
8
+ pynmrstar/loop.py,sha256=46vAeXMtKaSovLzFhhiFQCz1nsNqOJPSVpkTTa9tbtg,51892
9
+ pynmrstar/saveframe.py,sha256=5p-_NWi2nEju2FtJG5zm1aSdJrSZi_5lkCMIpTHWXYI,44133
10
+ pynmrstar/exceptions.py,sha256=xJlGbHB_QXMiNWuU_6livAQBMljBuCba50WA_K9pIbc,1449
11
+ pynmrstar/schema.py,sha256=tJsVHkVQR8glvchFBzlRvGDpx2HxS76eucAjR_aKVog,15779
12
+ pynmrstar/reference_files/schema.csv,sha256=BfAJyjfDQaZakdEL5V97dTSDxwgxQZCIp1DeKEhT65w,3694703
13
+ pynmrstar/reference_files/comments.str,sha256=vOGWBs5XU3ddNyROkVOhaPwGb8kx5stoChgo6ICBMio,9942
14
+ pynmrstar/reference_files/data_types.csv,sha256=awbw34Icg16-6epkQLGRM5dEvthcq3hvkgbt6rlZh0I,3432
15
+ pynmrstar-3.3.5.dist-info/RECORD,,
16
+ pynmrstar-3.3.5.dist-info/LICENSE,sha256=pAZXnNE2dxxwXFIduGyn1gpvPefJtUYOYZOi3yeGG94,1068
17
+ pynmrstar-3.3.5.dist-info/WHEEL,sha256=kD-LhIT6lUKGukNhSkqq4OnwDJg6bUoeCVUytlUwUF8,111
18
+ pynmrstar-3.3.5.dist-info/top_level.txt,sha256=e5QP9re453LfgZ_mZNGHa7E5HFAXiRoNBPkF1hnn7JQ,19
19
+ pynmrstar-3.3.5.dist-info/METADATA,sha256=DDi4dJhZ0O9p6mWUX2bd72UtKVfKySfqa1uSJEOQRN8,2438
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (75.6.0)
3
+ Root-Is-Purelib: false
4
+ Tag: cp312-cp312-macosx_10_13_x86_64
5
+
@@ -0,0 +1,2 @@
1
+ cnmrstar
2
+ pynmrstar