hadro 0.0.4__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.
hadro-0.0.4/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 Justin Joyce
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.
hadro-0.0.4/NOTICES ADDED
@@ -0,0 +1,5 @@
1
+ HadroDB is based on [CaskDB](https://github.com/avinassh/py-caskdb) which is based on [Bitcask](https://riak.com/assets/bitcask-intro.pdf).
2
+
3
+ [ormsgpack](https://github.com/aviramha/ormsgpack)
4
+
5
+ [CityHash](https://github.com/escherba/python-cityhash)
hadro-0.0.4/PKG-INFO ADDED
@@ -0,0 +1,42 @@
1
+ Metadata-Version: 2.1
2
+ Name: hadro
3
+ Version: 0.0.4
4
+ Summary: Storage Engine
5
+ Home-page: https://github.com/mabel-dev/hadrodb/
6
+ Author: @joocer
7
+ Author-email: justin.joyce@joocer.com
8
+ Maintainer: @joocer
9
+ Description-Content-Type: text/markdown
10
+ License-File: LICENSE
11
+ License-File: NOTICES
12
+
13
+ <div align="center">
14
+
15
+ ![HadroDB](https://raw.githubusercontent.com/mabel-dev/hadrodb/main/hadro.png)
16
+
17
+ HadroDB is a database storage engine for [Opteryx](https://opteryx.dev).
18
+
19
+ [![PyPI Latest Release](https://img.shields.io/pypi/v/hadrodb.svg)](https://pypi.org/project/hadrodb/)
20
+ [![codecov](https://codecov.io/gh/mabel-dev/hadrodb/branch/main/graph/badge.svg?token=nl9JwOVdPs)](https://codecov.io/gh/mabel-dev/hadrodb)
21
+ [![Downloads](https://static.pepy.tech/badge/hadrodb)](https://pepy.tech/project/hadrodb)
22
+
23
+ </div>
24
+
25
+ ## License
26
+
27
+ [![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/mabel-dev/hadrodb/blob/main/LICENSE)
28
+ [![Notices](https://img.shields.io/badge/-Notices-darkgreen.svg)](https://github.com/mabel-dev/hadrodb/blob/main/NOTICES)
29
+
30
+ ## Status
31
+
32
+ [![Status](https://img.shields.io/badge/Status-alpha-orange)](https://github.com/mabel-dev/opteryx)
33
+
34
+ HadroDB is in alpha. Alpha means different things to different people, to us, being alpha means:
35
+
36
+ - Interfaces may be significantly changed
37
+ - The data file format is unstable
38
+ - Expected functionality is missing
39
+ - Things that worked yesterday, don't work today
40
+ - The results of the system may be unreliable
41
+
42
+ As such, we really don't recommend using HadroDB anywhere where your data matters.
hadro-0.0.4/README.md ADDED
@@ -0,0 +1,30 @@
1
+ <div align="center">
2
+
3
+ ![HadroDB](https://raw.githubusercontent.com/mabel-dev/hadrodb/main/hadro.png)
4
+
5
+ HadroDB is a database storage engine for [Opteryx](https://opteryx.dev).
6
+
7
+ [![PyPI Latest Release](https://img.shields.io/pypi/v/hadrodb.svg)](https://pypi.org/project/hadrodb/)
8
+ [![codecov](https://codecov.io/gh/mabel-dev/hadrodb/branch/main/graph/badge.svg?token=nl9JwOVdPs)](https://codecov.io/gh/mabel-dev/hadrodb)
9
+ [![Downloads](https://static.pepy.tech/badge/hadrodb)](https://pepy.tech/project/hadrodb)
10
+
11
+ </div>
12
+
13
+ ## License
14
+
15
+ [![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/mabel-dev/hadrodb/blob/main/LICENSE)
16
+ [![Notices](https://img.shields.io/badge/-Notices-darkgreen.svg)](https://github.com/mabel-dev/hadrodb/blob/main/NOTICES)
17
+
18
+ ## Status
19
+
20
+ [![Status](https://img.shields.io/badge/Status-alpha-orange)](https://github.com/mabel-dev/opteryx)
21
+
22
+ HadroDB is in alpha. Alpha means different things to different people, to us, being alpha means:
23
+
24
+ - Interfaces may be significantly changed
25
+ - The data file format is unstable
26
+ - Expected functionality is missing
27
+ - Things that worked yesterday, don't work today
28
+ - The results of the system may be unreliable
29
+
30
+ As such, we really don't recommend using HadroDB anywhere where your data matters.
@@ -0,0 +1,3 @@
1
+ from hadro.engine import HadroDB
2
+ from hadro.version import __author__
3
+ from hadro.version import __version__
@@ -0,0 +1,24 @@
1
+ from enum import Enum
2
+ from enum import auto
3
+
4
+
5
+ class ConsistencyMode(int, Enum):
6
+ """
7
+ Aggressive will ensure each write is flushed before moving to the next, Relaxed
8
+ allows the OS to handle write flushes.
9
+
10
+ Aggressive has significant impact on write performance, but significantly reduces
11
+ the chance of data loss due to system crashes.
12
+
13
+ Relaxed is faster (10x), but if the system crashes, your data may be in an
14
+ inconsistent state.
15
+
16
+ Aggressive is the default (safe default), but there are some scenarios where a
17
+ relaxed approach to write consistency is okay or acceptable.
18
+ """
19
+
20
+ AGGRESSIVE = auto()
21
+ RELAXED = auto()
22
+
23
+
24
+ WRITE_CONSISTENCY: ConsistencyMode = ConsistencyMode.RELAXED
@@ -0,0 +1,197 @@
1
+ """
2
+
3
+ Typical usage example:
4
+
5
+ disk: DiskStorage = DiskStore(file_name="books.db")
6
+ disk.set(key="othello", value="shakespeare")
7
+ author: str = disk.get("othello")
8
+ # it also supports dictionary style API too:
9
+ disk["hamlet"] = "shakespeare"
10
+ """
11
+ import io
12
+ import os.path
13
+ import struct
14
+ import typing
15
+ from collections import namedtuple
16
+
17
+ from hadro.config import WRITE_CONSISTENCY
18
+ from hadro.config import ConsistencyMode
19
+ from orso import logging
20
+ from orso.row import Row
21
+
22
+ logging.set_log_name("MESOS")
23
+ logger = logging.get_logger()
24
+ logger.setLevel(5)
25
+
26
+ DELETED_FLAG: int = 1
27
+
28
+ RecordHeader = namedtuple("RecordHeader", ["flags", "size"])
29
+
30
+
31
+ # DiskStorage is a Log-Structured Hash Table as described in the BitCask paper. We
32
+ # keep appending the data to a file, like a log. DiskStorage maintains an in-memory
33
+ # hash table called KeyDir, which keeps the row's location on the disk.
34
+ #
35
+ # The idea is simple yet brilliant:
36
+ # - Write the record to the disk
37
+ # - Update the internal hash table to point to that byte offset
38
+ # - Whenever we get a read request, check the internal hash table for the address,
39
+ # fetch that and return
40
+ #
41
+ # KeyDir does not store values, only their locations.
42
+ #
43
+ # The above approach solves a lot of problems:
44
+ # - Writes are insanely fast since you are just appending to the file
45
+ # - Reads are insanely fast since you do only one disk seek. In B-Tree backed
46
+ # storage, there could be 2-3 disk seeks
47
+ #
48
+ # However, there are drawbacks too:
49
+ # - We need to maintain an in-memory hash table KeyDir. A database with a large
50
+ # number of keys would require more RAM
51
+ # - Since we need to build the KeyDir at initialisation, it will affect the startup
52
+ # time too
53
+ # - Deleted keys need to be purged from the file to reduce the file size
54
+ #
55
+ # Read the paper for more details: https://riak.com/assets/bitcask-intro.pdf
56
+
57
+
58
+ class HadroDB:
59
+ """
60
+ Implements the KV store on the disk
61
+
62
+ Args:
63
+ file_name (str): name of the file where all the data will be written. Just
64
+ passing the file name will save the data in the current directory. You may
65
+ pass the full file location too.
66
+
67
+ Attributes:
68
+ file_name (str): name of the file where all the data will be written. Just
69
+ passing the file name will save the data in the current directory. You may
70
+ pass the full file location too.
71
+ file (typing.BinaryIO): file object pointing the file_name
72
+ write_position (int): current cursor position in the file where the data can be
73
+ written
74
+ key_dir (dict[str, KeyEntry]): is a map of key and KeyEntry being the value.
75
+ KeyEntry contains the position of the byte offset in the file where the
76
+ value exists. key_dir map acts as in-memory index to fetch the values
77
+ quickly from the disk
78
+ """
79
+
80
+ def __init__(self, collection: typing.Union[str, None] = None):
81
+ logger.warning("HadroDB is experimental and not recommended for use.")
82
+ self.collection: str = collection
83
+ self.file_name: str = collection + "/00000000.data"
84
+ self._schema_file: str = collection + "/00000000.schema"
85
+ self.write_position: int = 0
86
+ self.key_dir: dict[bytes, Row] = {}
87
+
88
+ if collection is None:
89
+ raise ValueError("HadroDB requires a collection name")
90
+ # if the collection exists, it must be a folder, not a file
91
+ if os.path.exists(collection):
92
+ if not os.path.isdir(collection):
93
+ raise ValueError("Collection must be a folder")
94
+ # if the file exists already, then we will load the key_dir
95
+ # self._init_key_dir()
96
+ else:
97
+ os.makedirs(collection, exist_ok=True)
98
+
99
+ # if os.path.exists(self._schema_file):
100
+ # load the schema
101
+
102
+ # we open the file in `a+b` mode:
103
+ # a - says the writes are append only. `a+` means we want append and read
104
+ # b - says that we are operating the file in binary mode (as opposed to the
105
+ # default string mode)
106
+ self.file: typing.BinaryIO = open(self.file_name, "a+b")
107
+ self.fileno = self.file.fileno()
108
+
109
+ schema = {
110
+ "id": {"type": "SMALLINT", "nullable": False},
111
+ "planetId": {"type": "SMALLINT", "nullable": False},
112
+ "name": {"type": "VARCHAR", "nullable": False},
113
+ "gm": {"type": "FLOAT", "nullable": False},
114
+ "radius": {"type": "FLOAT", "nullable": False},
115
+ "density": {"type": "FLOAT", "nullable": True},
116
+ "magnitude": {"type": "FLOAT", "nullable": True},
117
+ "albedo": {"type": "FLOAT", "nullable": True},
118
+ }
119
+
120
+ self.rows = Row.create_class(schema)
121
+
122
+ def append(self, record) -> None:
123
+ if isinstance(record, dict):
124
+ _record = record.values()
125
+ else:
126
+ _record = record
127
+
128
+ record = self.rows(_record)
129
+ # test it matches the schema
130
+
131
+ bytes_to_write = record.to_bytes()
132
+ self._write(bytes_to_write)
133
+
134
+ # update indices index
135
+ #
136
+
137
+ self.write_position += len(bytes_to_write)
138
+
139
+ def scan(self, columns=None, predicates=None):
140
+ block_size: int = 8 * 1024 * 1024 # read 1Mb at a time
141
+ self.file.seek(0, 0)
142
+
143
+ # TODO: read file header
144
+
145
+ buffer = io.BufferedReader(self.file, block_size) # type: ignore
146
+
147
+ header_bytes = buffer.read(5)
148
+ flags, size = struct.unpack(">BI", header_bytes)
149
+ block_start = 5 # start of the current block
150
+
151
+ while size > 0:
152
+ if block_start + size > block_size:
153
+ # The current record spans multiple blocks, so read the rest of it in the next block
154
+ remaining_size = size - (block_size - block_start)
155
+ data_bytes = bytearray(
156
+ buffer.read(block_size - block_start)
157
+ ) # read the remaining bytes in the current block
158
+ while remaining_size > 0:
159
+ # Read the remaining bytes in subsequent blocks
160
+ block_bytes = buffer.read(min(remaining_size, block_size))
161
+ data_bytes += block_bytes
162
+ remaining_size -= len(block_bytes)
163
+ block_start = len(block_bytes)
164
+ else:
165
+ # The current record fits in the current block, so just read it
166
+ data_bytes = bytearray(buffer.read(size))
167
+ block_start += size
168
+
169
+ if flags & DELETED_FLAG == 0:
170
+ yield self.rows.from_bytes(data_bytes)
171
+
172
+ # Read the size of the next record
173
+ header_bytes = buffer.read(5)
174
+ if len(header_bytes) == 0:
175
+ break
176
+ flags, size = struct.unpack(">BI", header_bytes)
177
+ block_start += 5 # add the size of the size field to the start of the next block
178
+
179
+ def _write(self, data: bytes) -> None:
180
+ # saving stuff to a file reliably is hard!
181
+ # if you would like to explore and learn more, then
182
+ # start from here: https://danluu.com/file-consistency/
183
+ # and read this too: https://lwn.net/Articles/457667/
184
+ os.write(self.fileno, data)
185
+
186
+ if WRITE_CONSISTENCY == ConsistencyMode.AGGRESSIVE:
187
+ # calling fsync after every write is important, this assures that our writes
188
+ # are actually persisted to the disk
189
+ os.fsync(self.fileno)
190
+
191
+ def close(self) -> None:
192
+ # before we close the file, we need to safely write the contents in the buffers
193
+ # to the disk. Check documentation of DiskStorage._write() to understand
194
+ # following the operations
195
+ self.file.flush()
196
+ os.fsync(self.fileno)
197
+ self.file.close()
@@ -0,0 +1,79 @@
1
+ class Field:
2
+ def __init__(self, name, ftype, nullable=True):
3
+ self.name = name
4
+ self.ftype = ftype
5
+ self.nullable = nullable
6
+
7
+
8
+ class Index:
9
+ def __init__(self, name, fields):
10
+ self.name = name
11
+ self.fields = fields
12
+
13
+
14
+ class TableSchema:
15
+ def __init__(self, fields=None, primary_key=None, indexes=None):
16
+ self._fields = {}
17
+ self.primary_key = primary_key
18
+ self.indexes = indexes or []
19
+
20
+ if fields is not None:
21
+ for name, field in fields.items():
22
+ self.add_field(name, field["type"], field.get("nullable", True))
23
+
24
+ def add_field(self, name_or_field, field_type=None, nullable=True):
25
+ if isinstance(name_or_field, Field):
26
+ name = name_or_field.name
27
+ field = name_or_field
28
+ else:
29
+ name = name_or_field
30
+ field = Field(name, field_type, nullable)
31
+ self._fields[name] = field
32
+
33
+ def remove_field(self, name):
34
+ if name in self._fields:
35
+ del self._fields[name]
36
+
37
+ def field(self, i):
38
+ if isinstance(i, int):
39
+ return self._fields[list(self._fields.keys())[i]]
40
+ else:
41
+ return self._fields[i]
42
+
43
+ @property
44
+ def num_fields(self):
45
+ return len(self._fields)
46
+
47
+ @property
48
+ def names(self):
49
+ return list(self._fields.keys())
50
+
51
+ @property
52
+ def types(self):
53
+ return [field["type"] for field in self._fields.values()]
54
+
55
+ def to_dict(self):
56
+ return {
57
+ "fields": {
58
+ name: {"type": field["type"], "nullable": field["nullable"]}
59
+ for name, field in self._fields.items()
60
+ },
61
+ "primaryKey": self.primary_key,
62
+ "indexes": [index.to_dict() for index in self.indexes],
63
+ }
64
+
65
+ def validate(self, data):
66
+ if not isinstance(data, dict):
67
+ return False
68
+
69
+ # Check that all required fields are present
70
+ for field_name, field in self._fields.items():
71
+ if not field["nullable"] and field_name not in data:
72
+ return False
73
+
74
+ # Check that all fields have the correct type
75
+ for field_name, field in self._fields.items():
76
+ if field_name in data and not isinstance(data[field_name], field["type"]):
77
+ return False
78
+
79
+ return True
@@ -0,0 +1,2 @@
1
+ __version__ = "0.0.4"
2
+ __author__ = "@joocer"
@@ -0,0 +1,42 @@
1
+ Metadata-Version: 2.1
2
+ Name: hadro
3
+ Version: 0.0.4
4
+ Summary: Storage Engine
5
+ Home-page: https://github.com/mabel-dev/hadrodb/
6
+ Author: @joocer
7
+ Author-email: justin.joyce@joocer.com
8
+ Maintainer: @joocer
9
+ Description-Content-Type: text/markdown
10
+ License-File: LICENSE
11
+ License-File: NOTICES
12
+
13
+ <div align="center">
14
+
15
+ ![HadroDB](https://raw.githubusercontent.com/mabel-dev/hadrodb/main/hadro.png)
16
+
17
+ HadroDB is a database storage engine for [Opteryx](https://opteryx.dev).
18
+
19
+ [![PyPI Latest Release](https://img.shields.io/pypi/v/hadrodb.svg)](https://pypi.org/project/hadrodb/)
20
+ [![codecov](https://codecov.io/gh/mabel-dev/hadrodb/branch/main/graph/badge.svg?token=nl9JwOVdPs)](https://codecov.io/gh/mabel-dev/hadrodb)
21
+ [![Downloads](https://static.pepy.tech/badge/hadrodb)](https://pepy.tech/project/hadrodb)
22
+
23
+ </div>
24
+
25
+ ## License
26
+
27
+ [![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/mabel-dev/hadrodb/blob/main/LICENSE)
28
+ [![Notices](https://img.shields.io/badge/-Notices-darkgreen.svg)](https://github.com/mabel-dev/hadrodb/blob/main/NOTICES)
29
+
30
+ ## Status
31
+
32
+ [![Status](https://img.shields.io/badge/Status-alpha-orange)](https://github.com/mabel-dev/opteryx)
33
+
34
+ HadroDB is in alpha. Alpha means different things to different people, to us, being alpha means:
35
+
36
+ - Interfaces may be significantly changed
37
+ - The data file format is unstable
38
+ - Expected functionality is missing
39
+ - Things that worked yesterday, don't work today
40
+ - The results of the system may be unreliable
41
+
42
+ As such, we really don't recommend using HadroDB anywhere where your data matters.
@@ -0,0 +1,16 @@
1
+ LICENSE
2
+ NOTICES
3
+ README.md
4
+ pyproject.toml
5
+ setup.py
6
+ hadro/__init__.py
7
+ hadro/config.py
8
+ hadro/engine.py
9
+ hadro/schema.py
10
+ hadro/version.py
11
+ hadro.egg-info/PKG-INFO
12
+ hadro.egg-info/SOURCES.txt
13
+ hadro.egg-info/dependency_links.txt
14
+ hadro.egg-info/requires.txt
15
+ hadro.egg-info/top_level.txt
16
+ tests/test_interface.py
@@ -0,0 +1,2 @@
1
+ ormsgpack
2
+ orso
@@ -0,0 +1 @@
1
+ hadro
@@ -0,0 +1,11 @@
1
+ [tool.black]
2
+ line-length = 100
3
+ target-version = ['py310']
4
+
5
+ [tool.isort]
6
+ profile = "black"
7
+ src_paths = ["hadrodb"]
8
+ line_length = 100
9
+ multi_line_output = 9
10
+ force_single_line = true
11
+ float_to_top = true
hadro-0.0.4/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
hadro-0.0.4/setup.py ADDED
@@ -0,0 +1,37 @@
1
+ from setuptools import find_packages
2
+ from setuptools import setup
3
+
4
+ LIBRARY = "hadro"
5
+
6
+
7
+ __version__ = "notset"
8
+ with open(f"{LIBRARY}/version.py", mode="r") as v:
9
+ vers = v.read()
10
+ exec(vers) # nosec
11
+
12
+ with open("README.md", mode="r", encoding="UTF8") as rm:
13
+ long_description = rm.read()
14
+
15
+ try:
16
+ with open("requirements.txt", "r") as f:
17
+ required = f.read().splitlines()
18
+ except:
19
+ with open(f"{LIBRARY}.egg-info/requires.txt", "r") as f:
20
+ required = f.read().splitlines()
21
+
22
+
23
+ setup_config = {
24
+ "name": LIBRARY,
25
+ "version": __version__,
26
+ "description": "Storage Engine",
27
+ "long_description": long_description,
28
+ "long_description_content_type": "text/markdown",
29
+ "maintainer": "@joocer",
30
+ "author": "@joocer",
31
+ "author_email": "justin.joyce@joocer.com",
32
+ "packages": find_packages(include=[LIBRARY, f"{LIBRARY}.*"]),
33
+ "url": "https://github.com/mabel-dev/hadrodb/",
34
+ "install_requires": required,
35
+ }
36
+
37
+ setup(**setup_config)
@@ -0,0 +1,90 @@
1
+ import os
2
+ import shutil
3
+ import sys
4
+ import time
5
+
6
+ sys.path.insert(1, os.path.join(sys.path[0], ".."))
7
+ sys.path.insert(1, os.path.join(sys.path[0], "../.."))
8
+
9
+ from hadro import HadroDB # isort: skip
10
+
11
+
12
+ TEST_COLLECTION = "test_123"
13
+
14
+
15
+ def create_doc(docs):
16
+ docid = str(time.monotonic_ns())
17
+ doc = {"document": docid}
18
+ docs[docid] = doc
19
+ return docid, doc
20
+
21
+
22
+ def test_interface():
23
+ # initialize the dataset
24
+ if os.path.exists(TEST_COLLECTION): # pragma: no cover
25
+ shutil.rmtree(TEST_COLLECTION, ignore_errors=True)
26
+
27
+ # create a copy of the docs for us to compare against
28
+ comparision_set_of_docs = {}
29
+
30
+ # OPEN COLLECTION
31
+ hadro = HadroDB(TEST_COLLECTION)
32
+
33
+ # SET DOCUMENT IN COLLECTION
34
+ # subscript syntax
35
+ docid, doc = create_doc(comparision_set_of_docs)
36
+ hadro.append(doc)
37
+
38
+ docid, doc = create_doc(comparision_set_of_docs)
39
+ # set syntax
40
+ hadro.append(doc)
41
+
42
+ # add syntax
43
+ docid = hadro.append(doc)
44
+ comparision_set_of_docs[docid] = doc
45
+
46
+ # GET DOCUMENT FROM COLLECTION
47
+ # subscript, single doc syntax
48
+ # doc = hadro[docid]
49
+ # assert doc == comparision_set_of_docs[docid]
50
+
51
+ # subscript, list syntax
52
+ # list_of_docs = hadro[comparision_set_of_docs.keys()]
53
+ # assert len(list_of_docs) == len(comparision_set_of_docs)
54
+
55
+ # get syntax
56
+ # doc = hadro.get(docid)
57
+ # assert doc == comparision_set_of_docs[docid]
58
+
59
+ # GET COLLECTION SIZE
60
+ # assert len(hadro) == len(comparision_set_of_docs)
61
+
62
+ # GET KEYS IN COLLECTION
63
+ # keys = hadro.keys()
64
+ # assert len(keys) == len(comparision_set_of_docs)
65
+ # for key in keys:
66
+ # assert hadro[key] == comparision_set_of_docs[key]
67
+
68
+ """
69
+ # DELETE DOCUMENT FROM COLLECTION
70
+ del hadro[id]
71
+ hadro.del(id[, id...])
72
+
73
+ # CONTAINMENT
74
+ id in hadro
75
+ hadro.contains(id[, id ...])
76
+
77
+ # FILTER COLLECTION
78
+ filtered_collection = hadro.where(predicate)
79
+
80
+
81
+ """
82
+
83
+ hadro.close()
84
+
85
+ # tidy up
86
+ if os.path.exists(TEST_COLLECTION): # pragma: no cover
87
+ shutil.rmtree(TEST_COLLECTION, ignore_errors=True)
88
+
89
+
90
+ test_interface()