kosh 3.0__tar.gz → 3.2.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.

Potentially problematic release.


This version of kosh might be problematic. Click here for more details.

Files changed (94) hide show
  1. kosh-3.2.0/PKG-INFO +70 -0
  2. {kosh-3.0 → kosh-3.2.0}/README.md +1 -4
  3. {kosh-3.0 → kosh-3.2.0}/kosh/__init__.py +4 -4
  4. {kosh-3.0 → kosh-3.2.0}/kosh/core_sina.py +171 -107
  5. kosh-3.2.0/kosh/current_version.py +1 -0
  6. {kosh-3.0 → kosh-3.2.0}/kosh/dataset.py +386 -126
  7. kosh-3.2.0/kosh/ensemble.py +424 -0
  8. {kosh-3.0 → kosh-3.2.0}/kosh/exec_graphs/core.py +9 -5
  9. {kosh-3.0/scripts → kosh-3.2.0/kosh}/kosh_command.py +112 -58
  10. {kosh-3.0 → kosh-3.2.0}/kosh/loaders/UltraLoader.py +30 -16
  11. {kosh-3.0 → kosh-3.2.0}/kosh/loaders/__init__.py +2 -1
  12. {kosh-3.0 → kosh-3.2.0}/kosh/loaders/core.py +28 -14
  13. {kosh-3.0 → kosh-3.2.0}/kosh/loaders/npy.py +2 -1
  14. kosh-3.2.0/kosh/loaders/pandas_loader.py +118 -0
  15. {kosh-3.0 → kosh-3.2.0}/kosh/loaders/pgm.py +1 -1
  16. {kosh-3.0 → kosh-3.2.0}/kosh/loaders/pil.py +1 -1
  17. {kosh-3.0 → kosh-3.2.0}/kosh/loaders/sidre.py +1 -1
  18. kosh-3.2.0/kosh/lock_strategies.py +266 -0
  19. {kosh-3.0 → kosh-3.2.0}/kosh/operators/__init__.py +1 -0
  20. {kosh-3.0 → kosh-3.2.0}/kosh/operators/core.py +4 -4
  21. kosh-3.2.0/kosh/operators/koshClustering.py +756 -0
  22. kosh-3.2.0/kosh/operators/koshMaths.py +116 -0
  23. {kosh-3.0 → kosh-3.2.0}/kosh/sampling_methods/cluster_sampling/Clustering.py +731 -217
  24. kosh-3.2.0/kosh/sampling_methods/cluster_sampling/__init__.py +4 -0
  25. {kosh-3.0 → kosh-3.2.0}/kosh/schema.py +16 -4
  26. {kosh-3.0 → kosh-3.2.0}/kosh/store.py +889 -299
  27. kosh-3.2.0/kosh/threadsafe.py +258 -0
  28. kosh-3.2.0/kosh/threadsafe_decorators.py +96 -0
  29. {kosh-3.0 → kosh-3.2.0}/kosh/transformers/core.py +3 -3
  30. {kosh-3.0 → kosh-3.2.0}/kosh/transformers/npy.py +5 -1
  31. {kosh-3.0 → kosh-3.2.0}/kosh/transformers/sidre.py +1 -1
  32. {kosh-3.0 → kosh-3.2.0}/kosh/transformers/skl.py +1 -1
  33. {kosh-3.0 → kosh-3.2.0}/kosh/transformers/utils.py +1 -1
  34. {kosh-3.0 → kosh-3.2.0}/kosh/utils.py +173 -32
  35. kosh-3.2.0/kosh.egg-info/PKG-INFO +70 -0
  36. kosh-3.2.0/kosh.egg-info/SOURCES.txt +50 -0
  37. {kosh-3.0 → kosh-3.2.0}/kosh.egg-info/requires.txt +2 -1
  38. {kosh-3.0 → kosh-3.2.0}/scripts/kosh +112 -58
  39. {kosh-3.0 → kosh-3.2.0}/scripts/sbang +1 -1
  40. {kosh-3.0 → kosh-3.2.0}/setup.py +22 -17
  41. kosh-3.0/PKG-INFO +0 -75
  42. kosh-3.0/kosh/current_version.py +0 -1
  43. kosh-3.0/kosh/ensemble.py +0 -264
  44. kosh-3.0/kosh/operators/koshClustering.py +0 -458
  45. kosh-3.0/kosh/sampling_methods/cluster_sampling/__init__.py +0 -1
  46. kosh-3.0/kosh.egg-info/PKG-INFO +0 -75
  47. kosh-3.0/kosh.egg-info/SOURCES.txt +0 -80
  48. kosh-3.0/tests/test_kosh_broken_loader.py +0 -73
  49. kosh-3.0/tests/test_kosh_clustering.py +0 -358
  50. kosh-3.0/tests/test_kosh_clustering_raw.py +0 -287
  51. kosh-3.0/tests/test_kosh_command_line.py +0 -282
  52. kosh-3.0/tests/test_kosh_context_manager.py +0 -53
  53. kosh-3.0/tests/test_kosh_curves.py +0 -194
  54. kosh-3.0/tests/test_kosh_datasets.py +0 -556
  55. kosh-3.0/tests/test_kosh_describe_feature.py +0 -117
  56. kosh-3.0/tests/test_kosh_ensembles.py +0 -274
  57. kosh-3.0/tests/test_kosh_fix_fastsha.py +0 -43
  58. kosh-3.0/tests/test_kosh_flake8.py +0 -43
  59. kosh-3.0/tests/test_kosh_getitem_back_propagate.py +0 -272
  60. kosh-3.0/tests/test_kosh_import_export_datasets.py +0 -308
  61. kosh-3.0/tests/test_kosh_load_sina.py +0 -31
  62. kosh-3.0/tests/test_kosh_loaders.py +0 -520
  63. kosh-3.0/tests/test_kosh_loaders_multiple_features_name.py +0 -61
  64. kosh-3.0/tests/test_kosh_mv.py +0 -401
  65. kosh-3.0/tests/test_kosh_operators.py +0 -171
  66. kosh-3.0/tests/test_kosh_operators_decorators.py +0 -110
  67. kosh-3.0/tests/test_kosh_reassociate.py +0 -235
  68. kosh-3.0/tests/test_kosh_rm.py +0 -137
  69. kosh-3.0/tests/test_kosh_schema.py +0 -137
  70. kosh-3.0/tests/test_kosh_script_wrapper.py +0 -344
  71. kosh-3.0/tests/test_kosh_search.py +0 -62
  72. kosh-3.0/tests/test_kosh_search_speed.py +0 -46
  73. kosh-3.0/tests/test_kosh_sina_curves_as_associated.py +0 -104
  74. kosh-3.0/tests/test_kosh_sina_files_section_as_associated.py +0 -187
  75. kosh-3.0/tests/test_kosh_store.py +0 -373
  76. kosh-3.0/tests/test_kosh_store_custom_loaders.py +0 -70
  77. kosh-3.0/tests/test_kosh_sync.py +0 -220
  78. kosh-3.0/tests/test_kosh_transformers.py +0 -241
  79. kosh-3.0/tests/test_kosh_transformers_decorators.py +0 -93
  80. kosh-3.0/tests/test_kosh_transformers_parent.py +0 -64
  81. kosh-3.0/tests/test_kosh_users_and_groups.py +0 -49
  82. kosh-3.0/tests/test_kosh_verbose_list_features.py +0 -108
  83. {kosh-3.0 → kosh-3.2.0}/LICENSE +0 -0
  84. {kosh-3.0 → kosh-3.2.0}/NOTICE +0 -0
  85. {kosh-3.0 → kosh-3.2.0}/kosh/exec_graphs/__init__.py +0 -0
  86. {kosh-3.0 → kosh-3.2.0}/kosh/loaders/hdf5.py +0 -0
  87. {kosh-3.0 → kosh-3.2.0}/kosh/loaders/jsons.py +0 -0
  88. {kosh-3.0 → kosh-3.2.0}/kosh/sampling_methods/__init__.py +0 -0
  89. {kosh-3.0 → kosh-3.2.0}/kosh/transformers/__init__.py +0 -0
  90. {kosh-3.0 → kosh-3.2.0}/kosh/wrapper.py +0 -0
  91. {kosh-3.0 → kosh-3.2.0}/kosh.egg-info/dependency_links.txt +0 -0
  92. {kosh-3.0 → kosh-3.2.0}/kosh.egg-info/not-zip-safe +0 -0
  93. {kosh-3.0 → kosh-3.2.0}/kosh.egg-info/top_level.txt +0 -0
  94. {kosh-3.0 → kosh-3.2.0}/setup.cfg +0 -0
kosh-3.2.0/PKG-INFO ADDED
@@ -0,0 +1,70 @@
1
+ Metadata-Version: 2.1
2
+ Name: kosh
3
+ Version: 3.2.0
4
+ Summary: Manages Data Store with External Bulk Data
5
+ Home-page: https://github.com/LLNL/Kosh
6
+ Author: Charles Doutriaux, Renee Olson
7
+ Author-email: doutriaux1@llnl.gov
8
+ License: MIT
9
+ Description: ![Kosh Logo](share/icons/png/Kosh_Logo_Blue.png)
10
+ # Overview
11
+
12
+ Kosh allows codes to store, query, share data via an easy-to-use Python API. Kosh lies on top of Sina and as a result can use any database backend supported by Sina.
13
+
14
+ In addition Kosh aims to make data access and sharing as simple as possible.
15
+
16
+ Via "loaders", "transformers" and "operators" Kosh can access and process data in a consistent fashion, decoupled from data format and location.
17
+
18
+ Kosh is a Hindi word that means *treasury*, which is derived from *Kosha*, a Sanskrit word that means container in either a direct or metaphorical sense. A fairly good translation would be *repository*.
19
+
20
+ # Getting Started
21
+
22
+
23
+ To get the latest public version:
24
+
25
+ ```bash
26
+ pip install kosh
27
+ ```
28
+
29
+ To get the latest stable, from a cloned repo simply run:
30
+
31
+ ```bash
32
+ pip install .
33
+ ```
34
+
35
+ Alternatively add the path to this repo to your `PYTHONPATH` environment variable, or in your code with:
36
+
37
+ ```python
38
+ import sys
39
+ sys.path.append(path_to_kosh_repo)
40
+ ```
41
+
42
+ For more details look into the [installation doc](INSTALL.md)
43
+
44
+ # First steps
45
+
46
+ See [this file](docs/source/users/index.md)
47
+
48
+ # Getting Involved
49
+ Kosh is user-oriented, and users' questions, comments, and contributions help guide its evolution. We welcome involvement and feedbacks.
50
+
51
+ # Contact Info
52
+ You can reach our team at kosh-support@llnl.gov.
53
+ Kosh main developer can be reached at: doutriaux1@llnl.gov
54
+
55
+ # Contributing
56
+ Contributions should be submitted as a pull request pointing to the develop branch, and must pass Kosh's CI process; to run the same checks locally follow the instructions in the tests directory.
57
+
58
+ Contributions must be made under the same license as Kosh (see the bottom of this file).
59
+
60
+ # Release and License
61
+ Kosh is distributed under the terms of the MIT license; new contributions must be made under this license.
62
+
63
+ SPDX-License-Identifier: MIT
64
+
65
+ ``LLNL-CODE-814755``
66
+ Platform: UNKNOWN
67
+ Classifier: Programming Language :: Python
68
+ Classifier: License :: OSI Approved :: MIT License
69
+ Classifier: Operating System :: OS Independent
70
+ Description-Content-Type: text/markdown
@@ -45,10 +45,7 @@ You can reach our team at kosh-support@llnl.gov.
45
45
  Kosh main developer can be reached at: doutriaux1@llnl.gov
46
46
 
47
47
  # Contributing
48
- Contributions should be submitted as a pull request pointing to the develop branch, and must pass Kosh's CI process; to run the same checks locally, use:
49
- ```
50
- pytest tests/test_kosh*py
51
- ```
48
+ Contributions should be submitted as a pull request pointing to the develop branch, and must pass Kosh's CI process; to run the same checks locally follow the instructions in the tests directory.
52
49
 
53
50
  Contributions must be made under the same license as Kosh (see the bottom of this file).
54
51
 
@@ -1,3 +1,4 @@
1
+ import importlib.metadata
1
2
  from .current_version import current_version
2
3
  import os
3
4
  # import .sampling_methods # noqa
@@ -6,18 +7,17 @@ from .loaders import KoshLoader, KoshSinaLoader # noqa
6
7
  from .utils import create_new_db, walk_dictionary_keys, version # noqa
7
8
  from .schema import KoshSchema # noqa
8
9
  from .operators import KoshOperator # noqa
9
- import pkg_resources # noqa
10
10
  from .store import KoshStore, connect # noqa
11
11
  from .dataset import KoshDataset # noqa
12
12
  from .transformers import typed_transformer, numpy_transformer, typed_transformer_with_format # noqa
13
13
  from .transformers import KoshTransformer # noqa
14
14
  from .operators import KoshOperator # noqa
15
15
  from .operators import typed_operator_with_kwargs, typed_operator, numpy_operator # noqa
16
+ from .kosh_command import KoshCmd # noqa
16
17
 
17
18
  try:
18
- d = pkg_resources.get_distribution("kosh")
19
- __version__ = d.version
20
- metadata = list(d._get_metadata(d.PKG_INFO))
19
+ metadata = importlib.metadata.metadata("kosh")
20
+ __version__ = metadata["Version"]
21
21
  __sha__ = None
22
22
  for meta in metadata:
23
23
  if "Summary:" in meta:
@@ -1,25 +1,32 @@
1
1
  import uuid
2
2
  import warnings
3
3
  import time
4
+ import reprlib
4
5
  from .schema import KoshSchema
5
- from sina.model import Record
6
- from sina import get_version
7
- import pickle
6
+ from .utils import KoshPickler
7
+ from . import lock_strategies
8
8
 
9
9
 
10
- sina_version = float(".".join(get_version().split(".")[:2]))
10
+ kosh_pickler = KoshPickler()
11
+
12
+
13
+ def __getattr__(name):
14
+ if name == "sina_version":
15
+ from sina import get_version
16
+ return float(".".join(get_version().split(".")[:2]))
11
17
 
12
18
 
13
19
  class KoshSinaObject(object):
14
20
  """KoshSinaObject Base class for sina objects
15
21
  """
16
-
22
+ @lock_strategies.lock_method
17
23
  def get_record(self):
18
24
  return self.__store__.get_record(self.id)
19
25
 
20
26
  def __init__(self, Id, store, kosh_type,
21
27
  record_handler, protected=[], metadata={}, schema=None,
22
28
  record=None):
29
+ from sina.model import Record
23
30
  """__init__ sina object base class
24
31
 
25
32
  :param Id: id to use for unique identification, if None is passed set for you via uui4()
@@ -37,56 +44,59 @@ class KoshSinaObject(object):
37
44
  :param record: sina record to prevent looking it up again and again in sina
38
45
  :type record: Record
39
46
  """
40
- self.__dict__["__store__"] = store
41
- self.__dict__["__schema__"] = schema
42
- self.__dict__["__record_handler__"] = record_handler
43
- self.__dict__["__protected__"] = [
44
- "id", "__type__", "__protected__",
45
- "__record_handler__", "__store__", "id", "__schema__"] + protected
46
- self.__dict__["__type__"] = kosh_type
47
- if Id is None:
48
- Id = uuid.uuid4().hex
49
- record = Record(id=Id, type=kosh_type)
50
- if store.__sync__:
51
- store.lock()
52
- store.__record_handler__.insert(record)
53
- store.unlock()
54
- else:
55
- record["user_defined"]["last_update_from_db"] = time.time()
56
- self.__store__.__sync__dict__[Id] = record
57
- self.__dict__["id"] = Id
58
- else:
59
- self.__dict__["id"] = Id
60
- if record is None:
61
- try:
62
- record = self.get_record()
63
- except BaseException: # record exists nowhere
64
- record = Record(id=Id, type=kosh_type)
65
- if store.__sync__:
66
- store.lock()
67
- store.__record_handler__.insert(record)
68
- store.unlock()
69
- else:
70
- self.__store__.__sync__dict__[Id] = record
71
- record["user_defined"]["last_update_from_db"] = time.time()
47
+ with store.lock_strategy:
48
+ self.__dict__["__store__"] = store
49
+ self.__dict__["__schema__"] = schema
50
+ self.__dict__["__record_handler__"] = record_handler
51
+ self.__dict__["__protected__"] = [
52
+ "id", "__type__", "__protected__",
53
+ "__record_handler__", "__store__", "id", "__schema__"] + protected
54
+ self.__dict__["__type__"] = kosh_type
55
+ self.__dict__["lock_strategy"] = store.lock_strategy
56
+ if Id is None:
57
+ Id = uuid.uuid4().hex
58
+ record = Record(id=Id, type=kosh_type, user_defined={'kosh_information': {}})
59
+ if store.__sync__:
60
+ store.lock()
61
+ store.__record_handler__.insert(record)
62
+ store.unlock()
63
+ else:
64
+ record["user_defined"]['kosh_information']["last_update_from_db"] = time.time()
65
+ self.__store__.__sync__dict__[Id] = record
66
+ self.__dict__["id"] = Id
72
67
  else:
73
- deleted_items = False
74
- for att in self.__dict__["__protected__"]:
75
- if att in record["data"]:
76
- del record["data"][att]
77
- deleted_items = True
78
- if deleted_items:
79
- if store.__sync__:
80
- self._update_record(record)
81
- else:
82
- self.__store__.__sync__dict__[Id] = record
83
-
84
- metadata_copy = metadata.copy()
85
- for key in metadata:
86
- if att in self.__dict__["__protected__"]:
87
- del metadata_copy[key]
88
- self.update(metadata_copy)
89
-
68
+ self.__dict__["id"] = Id
69
+ if record is None:
70
+ try:
71
+ record = self.get_record()
72
+ except BaseException: # record exists nowhere
73
+ record = Record(id=Id, type=kosh_type, user_defined={'kosh_information': {}})
74
+ if store.__sync__:
75
+ store.lock()
76
+ store.__record_handler__.insert(record)
77
+ store.unlock()
78
+ else:
79
+ self.__store__.__sync__dict__[Id] = record
80
+ record["user_defined"]['kosh_information']["last_update_from_db"] = time.time()
81
+ else:
82
+ deleted_items = False
83
+ for att in self.__dict__["__protected__"]:
84
+ if att in record["data"]:
85
+ del record["data"][att]
86
+ deleted_items = True
87
+ if deleted_items:
88
+ if store.__sync__:
89
+ self._update_record(record)
90
+ else:
91
+ self.__store__.__sync__dict__[Id] = record
92
+
93
+ metadata_copy = metadata.copy()
94
+ for key in metadata:
95
+ if key in self.__dict__["__protected__"]:
96
+ del metadata_copy[key]
97
+ self.update(metadata_copy)
98
+
99
+ @lock_strategies.lock_method
90
100
  def __getattr__(self, name):
91
101
  """__getattr__ get an attribute
92
102
 
@@ -105,6 +115,12 @@ class KoshSinaObject(object):
105
115
  rels = self.get_sina_store().relationships.find(
106
116
  None, "is a member of ensemble", self.id)
107
117
  return [str(x.subject_id) for x in rels]
118
+ if name == "__features__":
119
+ record = self.get_record()
120
+ try:
121
+ return KoshPickler().loads(record["user_defined"]["__features__"])
122
+ except Exception:
123
+ return {None: {}}
108
124
  if name == "_associated_data_":
109
125
  from kosh.dataset import KoshDataset
110
126
  record = self.get_record()
@@ -138,13 +154,19 @@ class KoshSinaObject(object):
138
154
  elif name == "schema":
139
155
  if self.__dict__[
140
156
  "__schema__"] is None and "schema" in record["data"]:
141
- schema = pickle.loads(
142
- record["data"]["schema"]["value"].encode("latin1"))
157
+ schema = kosh_pickler.loads(record["data"]["schema"]["value"])
143
158
  self.__dict__["__schema__"] = schema
144
159
  return self.__dict__["__schema__"]
160
+ elif name in ['alias_feature', 'loader_kwargs']:
161
+ if name in record["data"]:
162
+ return kosh_pickler.loads(record["data"][name]["value"])
163
+ else:
164
+ return {}
145
165
  if name not in record["data"]:
146
166
  if name == "mime_type":
147
167
  return record["type"]
168
+ elif name == "uri":
169
+ return ""
148
170
  else:
149
171
  raise AttributeError(
150
172
  "Object {} does not have {} attribute".format(self.id,
@@ -154,18 +176,20 @@ class KoshSinaObject(object):
154
176
  # old records have user id let's fix this
155
177
  if value in self.__store__.__record_handler__.find_with_type(
156
178
  self.__store__._users_type, ids_only=True):
157
- value = self.__store__.get_record(
158
- value)["data"]["username"]["value"]
179
+ value = self.__store__.get_record(value)["data"]["username"]["value"]
159
180
  return value
160
181
 
182
+ @lock_strategies.lock_method
161
183
  def get_sina_store(self):
162
184
  """Returns the sina store object"""
163
185
  return self.__store__.get_sina_store()
164
186
 
187
+ @lock_strategies.lock_method
165
188
  def get_sina_records(self):
166
189
  """Returns sina store's records"""
167
190
  return self.__record_handler__
168
191
 
192
+ @lock_strategies.lock_method
169
193
  def update(self, attributes):
170
194
  """update many attributes at once to limit db writes
171
195
  :param: attributes: dictionary with attributes to update
@@ -184,6 +208,7 @@ class KoshSinaObject(object):
184
208
  update_db = False
185
209
  rec = self.___setattr___(name, value, rec, update_db=update_db)
186
210
 
211
+ @lock_strategies.lock_method
187
212
  def __setattr__(self, name, value):
188
213
  """set an attribute
189
214
  We are calling the ___setattr___
@@ -191,6 +216,7 @@ class KoshSinaObject(object):
191
216
  """
192
217
  self.___setattr___(name, value)
193
218
 
219
+ @lock_strategies.lock_method
194
220
  def ___setattr___(self, name, value, record=None,
195
221
  update_db=True, force=False):
196
222
  """__setattr__ set an attribute on an object
@@ -213,6 +239,14 @@ class KoshSinaObject(object):
213
239
  if name == "schema":
214
240
  assert isinstance(value, KoshSchema)
215
241
  value.validate(self)
242
+ elif name == "__features__":
243
+ value = kosh_pickler.dumps(value)
244
+ record["user_defined"]["__features__"] = value
245
+ elif name in ['alias_feature', 'loader_kwargs']:
246
+ if isinstance(value, dict):
247
+ value = kosh_pickler.dumps(value)
248
+ else: # Pre-pickled at dataset level
249
+ value = value
216
250
  elif self.schema is not None:
217
251
  self.schema.validate_attribute(name, value)
218
252
 
@@ -225,36 +259,45 @@ class KoshSinaObject(object):
225
259
  self.id, self.__store__._ensemble_predicate, None)
226
260
  for relationship in relationships:
227
261
  ensemble = self.__store__.open(relationship.object_id)
228
- if name in ensemble.list_attributes() and name not in ensemble.__dict__["__ok_duplicates__"]:
229
- raise KeyError(
230
- "The attribute {} is controlled by ensemble: {} and cannot be set here".format(
231
- name, relationship.object_id))
262
+ ens_tags = self.list_ensemble_tags(ensemble.id, dictionary=True, obscure=False)
263
+ inherit_attributes = ens_tags.get(f"{ensemble.id}_ENSEMBLE_TAG_INHERIT_ATTRIBUTES", True)
264
+ if inherit_attributes:
265
+ if name in ensemble.list_attributes() and name not in ensemble.__dict__["__ok_duplicates__"]:
266
+ if value != getattr(ensemble, name):
267
+ raise KeyError(
268
+ "The attribute {} is controlled by ensemble: {} and cannot be set here".format(
269
+ name, relationship.object_id))
270
+ else:
271
+ warnings.warn(
272
+ "The attribute {} is controlled by ensemble: {}"
273
+ ". You should NOT set this attribute at the dataset level"
274
+ ". Values match so we will accept it here".format(
275
+ name, relationship.object_id), UserWarning)
232
276
 
233
277
  # For Ensembles we need to set it on all members
234
278
  from kosh.ensemble import KoshEnsemble
235
279
  if isinstance(self, KoshEnsemble):
236
280
  # First we make a pass to collect all other ensembles datasets are
237
281
  # part of
238
- other_ensembles = set()
239
282
  for dataset in self.get_members():
240
- for e in dataset.get_ensembles():
241
- other_ensembles.add(e)
242
- for ensemble in other_ensembles:
243
- if ensemble.id == self.id:
244
- continue
245
- for att in ensemble.list_attributes():
246
- if att in self.__dict__["__ok_duplicates__"]:
247
- continue
248
- if att == name:
249
- raise NameError("A member of this ensemble belongs to ensemble {} "
250
- "which already controls attribute {}".format(ensemble.id, att))
251
- for dataset in self.get_members():
252
- dataset.___setattr___(
253
- name=name,
254
- value=value,
255
- record=None,
256
- update_db=update_db,
257
- force=True)
283
+ for ensemble in dataset.get_ensembles():
284
+ ens_tags = dataset.list_ensemble_tags(ensemble.id, dictionary=True, obscure=False)
285
+ inherit_attributes = ens_tags.get(f"{ensemble.id}_ENSEMBLE_TAG_INHERIT_ATTRIBUTES", True)
286
+ if inherit_attributes:
287
+ if ensemble.id != self.id:
288
+ for att in ensemble.list_attributes():
289
+ if att in self.__dict__["__ok_duplicates__"]:
290
+ continue
291
+ if att == name:
292
+ raise NameError("A member of this ensemble belongs to ensemble {} "
293
+ "which already controls attribute {}".format(ensemble.id, att))
294
+
295
+ dataset.___setattr___(
296
+ name=name,
297
+ value=value,
298
+ record=None,
299
+ update_db=update_db,
300
+ force=True)
258
301
 
259
302
  # Did it change on db since we last read it?
260
303
  last_modif_att = "{name}_last_modified".format(name=name)
@@ -265,7 +308,7 @@ class KoshSinaObject(object):
265
308
  last = time.time()
266
309
  try:
267
310
  # Time we last read its value
268
- last_db = record["user_defined"][last_modif_att]
311
+ last_db = record["user_defined"]['kosh_information'][last_modif_att]
269
312
  except KeyError:
270
313
  last_db = last
271
314
  # last time attribute was modified in db
@@ -281,15 +324,16 @@ class KoshSinaObject(object):
281
324
  if "{name}_last_modified".format(name=name) not in self.__protected__:
282
325
  self.__dict__["__protected__"] += [last_modif_att, ]
283
326
  self.__dict__[last_modif_att] = now
284
- record["user_defined"][last_modif_att] = now
327
+ record["user_defined"]['kosh_information'][last_modif_att] = now
285
328
  if name == "schema":
286
329
  self.__dict__["__schema__"] = value
287
- value = pickle.dumps(value).decode("latin1")
330
+ value = kosh_pickler.dumps(value)
288
331
  record["data"][name] = {"value": value}
289
332
  if update_db and self.__store__.__sync__:
290
333
  self._update_record(record)
291
334
  return record
292
335
 
336
+ @lock_strategies.lock_method
293
337
  def _update_record(self, record, store=None):
294
338
  """Updates a record in the sina store
295
339
  :param record: The record to update
@@ -302,23 +346,14 @@ class KoshSinaObject(object):
302
346
  id_ = record.id
303
347
  rels = store.relationships.find(id_, None, None)
304
348
  rels += store.relationships.find(None, None, id_)
305
- try:
306
- # if rec exists let's get it
307
- old_record = store.records.get(id_)
308
- except Exception:
309
- old_record = None # new record
310
- store.records.delete(id_)
311
- try:
349
+ if not self.__store__.__sync__:
350
+ store.records.delete(id_)
312
351
  store.records.insert(record)
313
- except Exception as err:
314
- # Let's put back in place the old record
315
- if old_record is not None:
316
- store.records.insert(old_record)
317
- raise err
318
-
319
- store.relationships.insert(rels)
352
+ store.relationships.insert(rels)
353
+ store.records.update(record)
320
354
  self.__store__.unlock()
321
355
 
356
+ @lock_strategies.lock_method
322
357
  def __delattr__(self, name):
323
358
  """__delattr__ deletes an attribute
324
359
 
@@ -330,7 +365,7 @@ class KoshSinaObject(object):
330
365
  record = self.get_record()
331
366
  last_modif_att = "{name}_last_modified".format(name=name)
332
367
  now = time.time()
333
- record["user_defined"][last_modif_att] = now
368
+ record["user_defined"]['kosh_information'][last_modif_att] = now
334
369
  # We need to remember we touched it otherwise
335
370
  # if we create it again the db will look
336
371
  # out of sync.
@@ -339,36 +374,53 @@ class KoshSinaObject(object):
339
374
  if self.__store__.__sync__:
340
375
  self._update_record(record)
341
376
 
377
+ @lock_strategies.lock_method
342
378
  def sync(self):
343
379
  """sync this object with database"""
344
380
  self.__store__.sync([self.id, ])
345
381
 
346
- def list_attributes(self, dictionary=False):
382
+ @lock_strategies.lock_method
383
+ def list_attributes(self, dictionary=False, ensemble_id=None):
347
384
  __doc__ = self.listattributes.__doc__.replace("listattributes", "list_attributes") # noqa
348
- return self.listattributes(dictionary=dictionary)
385
+ return self.listattributes(dictionary=dictionary, ensemble_id=ensemble_id)
349
386
 
350
- def listattributes(self, dictionary=False):
387
+ @lock_strategies.lock_method
388
+ def listattributes(self, dictionary=False, ensemble_id=None):
351
389
  """listattributes list all non protected attributes
352
390
 
353
- :parm dictionary: return a dictionary of value/pair rather than just attributes names
391
+ :param dictionary: return a dictionary of value/pair rather than just attributes names
354
392
  :type dictionary: bool
393
+ :param ensemble_id: Provide ensemble ID(s) to return ensemble tags
394
+ :type ensemble_id: str, lst
355
395
 
356
396
  :return: list of attributes set on object
357
397
  :rtype: list
358
398
  """
359
399
  record = self.get_record()
360
400
  attributes = list(record["data"].keys()) + ['id', ]
401
+
402
+ no_tags = [a for a in attributes if "_ENSEMBLE_TAG_" not in a] # Remove ensemble tags
403
+ ens_tags = []
404
+ if ensemble_id is not None:
405
+ if isinstance(ensemble_id, str):
406
+ ensemble_id = [ensemble_id]
407
+ for ens_id in ensemble_id:
408
+ ens_tags += sorted([a for a in attributes if f"{ens_id}_ENSEMBLE_TAG_" in a])
409
+ attributes = sorted(no_tags)
410
+
361
411
  for att in self.__protected__:
362
412
  if att in attributes and att != "id":
363
413
  attributes.remove(att)
414
+ attributes += ens_tags
364
415
  if dictionary:
365
416
  out = {}
366
417
  for att in attributes:
367
418
  out[att] = getattr(self, att)
368
419
  return out
369
420
  else:
370
- return sorted(attributes)
421
+ return attributes
371
422
 
423
+ @lock_strategies.lock_method
372
424
  def __getattributes__(self):
373
425
  """__getattributes__ return dictionary with pairs of attribute/value
374
426
 
@@ -378,27 +430,39 @@ class KoshSinaObject(object):
378
430
  record = self.get_record()
379
431
  attributes = {}
380
432
  for a in record["data"]:
433
+ if a in ['alias_feature', 'loader_kwargs']:
434
+ continue
381
435
  attributes[a] = record["data"][a]["value"]
382
436
  if a == "creator":
383
437
  # old records have user id let's fix this
384
438
  if attributes[a] in self.__store__.__record_handler__.find_with_type(
385
439
  self.__store__._users_type, ids_only=True):
386
- attributes[a] = self.__store__.get_record(
387
- attributes[a])["data"]["username"]["value"]
440
+ attributes[a] = self.__store__.get_record(attributes[a])["data"]["username"]["value"]
388
441
  return attributes
389
442
 
443
+ @lock_strategies.lock_method
390
444
  def __str__(self):
391
445
  """String for printing"""
446
+ if self.__dict__["__store__"].verbose_attributes:
447
+ def reprtool(item):
448
+ return item
449
+ else:
450
+ def reprtool(item):
451
+ if isinstance(item, str):
452
+ return reprlib.repr(item)[1:-1]
453
+ else:
454
+ return reprlib.repr(item)
392
455
  st = "Id: {}".format(self.id)
393
456
  for att in sorted(self.listattributes()):
394
457
  if att != 'id':
395
- st += "\n\t{}: {}".format(att, getattr(self, att))
458
+ st += "\n\t{}: {}".format(att, reprtool(getattr(self, att)))
396
459
  return st
397
460
 
398
461
 
399
462
  class KoshSinaFile(KoshSinaObject):
400
463
  """KoshSinaFile file representation in Kosh via Sina"""
401
464
 
465
+ @lock_strategies.lock_method
402
466
  def open(self, *args, **kargs):
403
467
  """open opens the file
404
468
  :return: handle to file in open mode
@@ -0,0 +1 @@
1
+ current_version = "3.2.0"