Skip to content

Commit 79f4663

Browse files
authored
Merge pull request #12 from ModECI/development
Latest from development with bson support
2 parents c52dbb8 + 6a88a64 commit 79f4663

File tree

9 files changed

+116
-7
lines changed

9 files changed

+116
-7
lines changed

.github/workflows/ci.yml

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,24 @@ on:
77
branches: [ main, development, experimental ]
88

99
jobs:
10+
11+
pre-commit:
12+
name: Format
13+
runs-on: ubuntu-latest
14+
steps:
15+
- uses: actions/checkout@v1
16+
- uses: actions/setup-python@v2
17+
- uses: pre-commit/[email protected]
18+
with:
19+
extra_args: --hook-stage manual --all-files
20+
1021
build:
1122

1223
runs-on: ubuntu-latest
1324
strategy:
1425
fail-fast: false
1526
matrix:
16-
python-version: [ 3.7 , 3.9 ]
27+
python-version: ["3.7", "3.8", "3.9", "3.10"]
1728

1829
steps:
1930
- uses: actions/checkout@v2
@@ -60,7 +71,7 @@ jobs:
6071

6172

6273
- name: Install MDF
63-
if: ${{ matrix.python-version != '2.7' }}
74+
if: ${{ matrix.python-version != '3.10' }}
6475
run: |
6576
6677
git clone --branch development https://github.com/ModECI/MDF.git

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,6 @@ repos:
4646
# files: src
4747

4848
- repo: https://github.com/psf/black
49-
rev: 20.8b1
49+
rev: 22.3.0
5050
hooks:
5151
- id: black

examples/document.bson

222 Bytes
Binary file not shown.

examples/document.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ class Document(Base):
6868

6969
doc.to_json_file("document.json")
7070
doc.to_yaml_file("document.yaml")
71+
doc.to_bson_file("document.bson")
7172

7273
doc_md = doc.generate_documentation(format="markdown")
7374

@@ -85,10 +86,14 @@ class Document(Base):
8586

8687
import json
8788
import yaml
89+
import bson
8890

8991
with open("document.specification.json", "w") as d:
9092
d.write(json.dumps(doc_dict, indent=4))
9193

9294
print("Generating specification in YAML")
9395
with open("document.specification.yaml", "w") as d:
9496
d.write(yaml.dump(doc_dict, indent=4, sort_keys=False))
97+
98+
with open("document.specification.bson", "wb") as d:
99+
d.write(bson.encode(doc_dict))

setup.cfg

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,16 @@ classifiers =
1616
License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)
1717
Natural Language :: English
1818
Operating System :: OS Independent
19-
Programming Language :: Python :: 3.6
19+
Programming Language :: Python :: 3.7
2020
Topic :: Scientific/Engineering
2121
Intended Audience :: Science/Research
2222
Programming Language :: Python
2323
Programming Language :: Python :: 3
2424
Programming Language :: Python :: 3 :: Only
25-
Programming Language :: Python :: 3.6
2625
Programming Language :: Python :: 3.7
2726
Programming Language :: Python :: 3.8
2827
Programming Language :: Python :: 3.9
28+
Programming Language :: Python :: 3.10
2929
Topic :: Scientific/Engineering
3030
Topic :: Software Development
3131
Typing :: Typed
@@ -34,6 +34,7 @@ classifiers =
3434
packages = find:
3535
install_requires =
3636
pyyaml
37+
pymongo
3738
numpy
3839
tabulate
3940
attrs

src/modelspec/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
__version__ = "0.2.3"
1+
__version__ = "0.2.4"
22

33
from .base_types import Base, define, has, field, fields, optional, instance_of, in_
44

src/modelspec/base_types.py

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import json
22
import yaml
3+
import bson
34
import sys
45

56
import numpy as np
@@ -23,6 +24,7 @@
2324

2425
from cattr.gen import make_dict_unstructure_fn, override
2526
from cattr.preconf.pyyaml import make_converter as make_yaml_converter
27+
from cattr.preconf.bson import make_converter as make_bson_converter
2628

2729
from tabulate import tabulate
2830

@@ -72,7 +74,7 @@ def print_v(text: str):
7274
# A setup an additional converter to apply when we are serializing using PyYAML.
7375
# This handles things like tuples as lists.
7476
yaml_converter = make_yaml_converter()
75-
77+
bson_converter = make_bson_converter()
7678

7779
# A simple converter that handles only value expressions.
7880
value_expr_converter = cattr.Converter()
@@ -104,6 +106,12 @@ def to_json(self) -> str:
104106
"""
105107
return json.dumps(self.to_dict(), indent=4)
106108

109+
def to_bson(self) -> str:
110+
"""
111+
Convert the Base object to a BSON string representation.
112+
"""
113+
return bson.encode(self.to_dict())
114+
107115
@classmethod
108116
def from_dict(cls, d: Dict[str, Any]) -> "Base":
109117
"""Instantiate an Base object from a dictionary"""
@@ -130,6 +138,11 @@ def from_json(cls, json_str: str) -> "Base":
130138
"""Instantiate an modelspec object from a JSON string"""
131139
return cls.from_dict(json.loads(json_str))
132140

141+
@classmethod
142+
def from_bson(cls, bson_str: str) -> "Base":
143+
"""Instantiate an modelspec object from a BSON string"""
144+
return cls.from_dict(bson.decode(bson_str))
145+
133146
def to_json_file(
134147
self, filename: Optional[str] = None, include_metadata: bool = True
135148
) -> str:
@@ -155,6 +168,28 @@ def to_json_file(
155168

156169
return filename
157170

171+
def to_bson_file(self, filename: str, include_metadata: bool = True) -> str:
172+
"""Convert modelspec format to bson format
173+
174+
Args:
175+
filename: File in modelspec format (Filename extension: .bson )
176+
include_metadata: Contains contact information, citations, acknowledgements, pointers to sample data,
177+
benchmark results, and environments in which the specified model was originally implemented
178+
Returns:
179+
The name of the generated bson file
180+
"""
181+
182+
if filename is None:
183+
filename = f"{self.id}.bson"
184+
185+
with open(filename, "wb") as outfile:
186+
bson_data = bson.encode(
187+
bson_converter.unstructure(self.to_dict()),
188+
)
189+
outfile.write(bson_data)
190+
191+
return filename
192+
158193
def to_yaml(self, include_metadata: bool = True) -> str:
159194
"""
160195
Convert the Base object to a YAML dictionary representation.
@@ -211,6 +246,8 @@ def from_file(cls, filename: str) -> "Base":
211246
return cls.from_yaml_file(filename)
212247
elif filename.endswith(".json"):
213248
return cls.from_json_file(filename)
249+
elif filename.endswith(".bson"):
250+
return cls.from_bson_file(filename)
214251
else:
215252
raise ValueError(
216253
f"Cannot auto-detect modelspec serialization format from filename ({filename}). The filename "
@@ -232,6 +269,22 @@ def from_json_file(cls, filename: str) -> "Base":
232269
d = json.load(infile)
233270
return cls.from_dict(d)
234271

272+
@classmethod
273+
def from_bson_file(cls, filename: str) -> "Base":
274+
"""
275+
Create a :class:`.Base` from its BSON representation stored in a file.
276+
277+
Args:
278+
filename: The file from which to load the BSON data.
279+
280+
Returns:
281+
An modelspec :class:`.Base` for this BSON
282+
"""
283+
with open(filename, "rb") as infile:
284+
data_encoded = infile.read()
285+
d = bson.decode(data_encoded)
286+
return cls.from_dict(d)
287+
235288
@classmethod
236289
def from_yaml_file(cls, filename: str) -> "Base":
237290
"""

src/modelspec/utils.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import sys
22
import json
3+
import bson
34
import yaml
45
import os
56
import math
@@ -32,6 +33,17 @@ def load_yaml(filename):
3233
return data
3334

3435

36+
def load_bson(filename):
37+
"""
38+
Load a generic BSON file
39+
"""
40+
with open(filename, "rb") as infile:
41+
data_encoded = infile.read()
42+
data = bson.decode(data_encoded)
43+
44+
return data
45+
46+
3547
def save_to_json_file(info_dict, filename, indent=4):
3648

3749
strj = json.dumps(info_dict, indent=indent)

tests/test_base.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,3 +312,30 @@ class Node(Base):
312312

313313
model = Node(id="a", metadata={"b": np.array([0])})
314314
model.to_json()
315+
316+
317+
def test_bson_array(tmp_path):
318+
import numpy as np
319+
320+
test_filename = str(tmp_path / "test_array.bson")
321+
322+
@modelspec.define(eq=False)
323+
class ArrayTestModel(Base):
324+
array: Optional[ValueExprType] = field()
325+
list_of_lists: List[List[int]] = field(default=None)
326+
ragged_list: List[List[int]] = field(default=None)
327+
328+
model = ArrayTestModel(
329+
array=np.arange(27).reshape((3, 3, 3)),
330+
list_of_lists=[[1, 2], [3, 4]],
331+
ragged_list=[[1, 2], [1]],
332+
)
333+
334+
model.to_bson_file(test_filename)
335+
336+
# Load it back in
337+
m2 = model.from_bson_file(test_filename)
338+
# Check we get the same values back
339+
np.testing.assert_array_equal(model.array, m2.array)
340+
assert model.list_of_lists == m2.list_of_lists
341+
assert model.ragged_list == m2.ragged_list

0 commit comments

Comments
 (0)