entitysdk 0.6.2__tar.gz → 0.7.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.
Files changed (149) hide show
  1. {entitysdk-0.6.2/src/entitysdk.egg-info → entitysdk-0.7.0}/PKG-INFO +1 -1
  2. {entitysdk-0.6.2 → entitysdk-0.7.0}/src/entitysdk/client.py +31 -21
  3. entitysdk-0.7.0/src/entitysdk/dependencies/__init__.py +1 -0
  4. entitysdk-0.7.0/src/entitysdk/dependencies/entity.py +18 -0
  5. {entitysdk-0.6.2 → entitysdk-0.7.0}/src/entitysdk/downloaders/emodel.py +1 -1
  6. {entitysdk-0.6.2 → entitysdk-0.7.0}/src/entitysdk/downloaders/ion_channel_model.py +1 -1
  7. {entitysdk-0.6.2 → entitysdk-0.7.0}/src/entitysdk/downloaders/morphology.py +1 -1
  8. entitysdk-0.7.0/src/entitysdk/downloaders/simulation.py +78 -0
  9. entitysdk-0.7.0/src/entitysdk/downloaders/simulation_result.py +61 -0
  10. {entitysdk-0.6.2 → entitysdk-0.7.0}/src/entitysdk/exception.py +8 -0
  11. {entitysdk-0.6.2 → entitysdk-0.7.0}/src/entitysdk/models/asset.py +3 -0
  12. entitysdk-0.7.0/src/entitysdk/schemas/asset.py +20 -0
  13. entitysdk-0.7.0/src/entitysdk/staging/__init__.py +7 -0
  14. entitysdk-0.7.0/src/entitysdk/staging/circuit.py +43 -0
  15. entitysdk-0.7.0/src/entitysdk/staging/simulation.py +142 -0
  16. entitysdk-0.7.0/src/entitysdk/staging/simulation_result.py +72 -0
  17. {entitysdk-0.6.2 → entitysdk-0.7.0}/src/entitysdk/types.py +6 -0
  18. entitysdk-0.7.0/src/entitysdk/utils/io.py +16 -0
  19. {entitysdk-0.6.2 → entitysdk-0.7.0/src/entitysdk.egg-info}/PKG-INFO +1 -1
  20. {entitysdk-0.6.2 → entitysdk-0.7.0}/src/entitysdk.egg-info/SOURCES.txt +24 -0
  21. entitysdk-0.7.0/tests/unit/dependencies/test_entity.py +27 -0
  22. entitysdk-0.7.0/tests/unit/models/data/.gitignore +0 -0
  23. entitysdk-0.7.0/tests/unit/staging/__init__.py +0 -0
  24. entitysdk-0.7.0/tests/unit/staging/conftest.py +281 -0
  25. entitysdk-0.7.0/tests/unit/staging/data/SomaVoltRec 1.h5 +0 -0
  26. entitysdk-0.7.0/tests/unit/staging/data/SomaVoltRec 2.h5 +0 -0
  27. entitysdk-0.7.0/tests/unit/staging/data/circuit/circuit_config.json +36 -0
  28. entitysdk-0.7.0/tests/unit/staging/data/circuit/edges.h5 +0 -0
  29. entitysdk-0.7.0/tests/unit/staging/data/circuit/nodes.h5 +0 -0
  30. entitysdk-0.7.0/tests/unit/staging/data/node_sets.json +67 -0
  31. entitysdk-0.7.0/tests/unit/staging/data/simulation_config.json +128 -0
  32. entitysdk-0.7.0/tests/unit/staging/data/spike_replays.h5 +0 -0
  33. entitysdk-0.7.0/tests/unit/staging/data/spikes.h5 +0 -0
  34. entitysdk-0.7.0/tests/unit/staging/test_simulation.py +98 -0
  35. entitysdk-0.7.0/tests/unit/staging/test_simulation_result.py +106 -0
  36. {entitysdk-0.6.2 → entitysdk-0.7.0}/tests/unit/test_client.py +54 -2
  37. {entitysdk-0.6.2 → entitysdk-0.7.0}/tests/unit/utils/test_asset.py +5 -0
  38. entitysdk-0.6.2/src/entitysdk/schemas/asset.py +0 -13
  39. {entitysdk-0.6.2 → entitysdk-0.7.0}/.github/workflows/sdist.yml +0 -0
  40. {entitysdk-0.6.2 → entitysdk-0.7.0}/.github/workflows/tox.yml +0 -0
  41. {entitysdk-0.6.2 → entitysdk-0.7.0}/.gitignore +0 -0
  42. {entitysdk-0.6.2 → entitysdk-0.7.0}/CHANGELOG.rst +0 -0
  43. {entitysdk-0.6.2 → entitysdk-0.7.0}/CONTRIBUTING.md +0 -0
  44. {entitysdk-0.6.2 → entitysdk-0.7.0}/LICENSE.txt +0 -0
  45. {entitysdk-0.6.2 → entitysdk-0.7.0}/README.md +0 -0
  46. {entitysdk-0.6.2 → entitysdk-0.7.0}/examples/01_searching.ipynb +0 -0
  47. {entitysdk-0.6.2 → entitysdk-0.7.0}/examples/02_morphology.ipynb +0 -0
  48. {entitysdk-0.6.2 → entitysdk-0.7.0}/examples/03_circuit.ipynb +0 -0
  49. {entitysdk-0.6.2 → entitysdk-0.7.0}/examples/04_simulation_campaign.ipynb +0 -0
  50. {entitysdk-0.6.2 → entitysdk-0.7.0}/examples/utils.py +0 -0
  51. {entitysdk-0.6.2 → entitysdk-0.7.0}/pyproject.toml +0 -0
  52. {entitysdk-0.6.2 → entitysdk-0.7.0}/setup.cfg +0 -0
  53. {entitysdk-0.6.2 → entitysdk-0.7.0}/src/entitysdk/__init__.py +0 -0
  54. {entitysdk-0.6.2 → entitysdk-0.7.0}/src/entitysdk/common.py +0 -0
  55. {entitysdk-0.6.2 → entitysdk-0.7.0}/src/entitysdk/config.py +0 -0
  56. {entitysdk-0.6.2 → entitysdk-0.7.0}/src/entitysdk/core.py +0 -0
  57. {entitysdk-0.6.2 → entitysdk-0.7.0}/src/entitysdk/downloaders/__init__.py +0 -0
  58. {entitysdk-0.6.2 → entitysdk-0.7.0}/src/entitysdk/downloaders/memodel.py +0 -0
  59. {entitysdk-0.6.2 → entitysdk-0.7.0}/src/entitysdk/mixin.py +0 -0
  60. {entitysdk-0.6.2 → entitysdk-0.7.0}/src/entitysdk/models/__init__.py +0 -0
  61. {entitysdk-0.6.2 → entitysdk-0.7.0}/src/entitysdk/models/activity.py +0 -0
  62. {entitysdk-0.6.2 → entitysdk-0.7.0}/src/entitysdk/models/agent.py +0 -0
  63. {entitysdk-0.6.2 → entitysdk-0.7.0}/src/entitysdk/models/base.py +0 -0
  64. {entitysdk-0.6.2 → entitysdk-0.7.0}/src/entitysdk/models/brain_location.py +0 -0
  65. {entitysdk-0.6.2 → entitysdk-0.7.0}/src/entitysdk/models/brain_region.py +0 -0
  66. {entitysdk-0.6.2 → entitysdk-0.7.0}/src/entitysdk/models/brain_region_hierarchy.py +0 -0
  67. {entitysdk-0.6.2 → entitysdk-0.7.0}/src/entitysdk/models/circuit.py +0 -0
  68. {entitysdk-0.6.2 → entitysdk-0.7.0}/src/entitysdk/models/classification.py +0 -0
  69. {entitysdk-0.6.2 → entitysdk-0.7.0}/src/entitysdk/models/contribution.py +0 -0
  70. {entitysdk-0.6.2 → entitysdk-0.7.0}/src/entitysdk/models/core.py +0 -0
  71. {entitysdk-0.6.2 → entitysdk-0.7.0}/src/entitysdk/models/electrical_cell_recording.py +0 -0
  72. {entitysdk-0.6.2 → entitysdk-0.7.0}/src/entitysdk/models/emodel.py +0 -0
  73. {entitysdk-0.6.2 → entitysdk-0.7.0}/src/entitysdk/models/entity.py +0 -0
  74. {entitysdk-0.6.2 → entitysdk-0.7.0}/src/entitysdk/models/etype.py +0 -0
  75. {entitysdk-0.6.2 → entitysdk-0.7.0}/src/entitysdk/models/ion_channel_model.py +0 -0
  76. {entitysdk-0.6.2 → entitysdk-0.7.0}/src/entitysdk/models/license.py +0 -0
  77. {entitysdk-0.6.2 → entitysdk-0.7.0}/src/entitysdk/models/memodel.py +0 -0
  78. {entitysdk-0.6.2 → entitysdk-0.7.0}/src/entitysdk/models/memodelcalibrationresult.py +0 -0
  79. {entitysdk-0.6.2 → entitysdk-0.7.0}/src/entitysdk/models/morphology.py +0 -0
  80. {entitysdk-0.6.2 → entitysdk-0.7.0}/src/entitysdk/models/mtype.py +0 -0
  81. {entitysdk-0.6.2 → entitysdk-0.7.0}/src/entitysdk/models/response.py +0 -0
  82. {entitysdk-0.6.2 → entitysdk-0.7.0}/src/entitysdk/models/scientific_artifact.py +0 -0
  83. {entitysdk-0.6.2 → entitysdk-0.7.0}/src/entitysdk/models/simulation.py +0 -0
  84. {entitysdk-0.6.2 → entitysdk-0.7.0}/src/entitysdk/models/simulation_campaign.py +0 -0
  85. {entitysdk-0.6.2 → entitysdk-0.7.0}/src/entitysdk/models/simulation_execution.py +0 -0
  86. {entitysdk-0.6.2 → entitysdk-0.7.0}/src/entitysdk/models/simulation_generation.py +0 -0
  87. {entitysdk-0.6.2 → entitysdk-0.7.0}/src/entitysdk/models/simulation_result.py +0 -0
  88. {entitysdk-0.6.2 → entitysdk-0.7.0}/src/entitysdk/models/single_neuron_simulation.py +0 -0
  89. {entitysdk-0.6.2 → entitysdk-0.7.0}/src/entitysdk/models/single_neuron_synaptome_simulation.py +0 -0
  90. {entitysdk-0.6.2 → entitysdk-0.7.0}/src/entitysdk/models/subject.py +0 -0
  91. {entitysdk-0.6.2 → entitysdk-0.7.0}/src/entitysdk/models/synaptome.py +0 -0
  92. {entitysdk-0.6.2 → entitysdk-0.7.0}/src/entitysdk/models/taxonomy.py +0 -0
  93. {entitysdk-0.6.2 → entitysdk-0.7.0}/src/entitysdk/models/validation_result.py +0 -0
  94. {entitysdk-0.6.2 → entitysdk-0.7.0}/src/entitysdk/result.py +0 -0
  95. {entitysdk-0.6.2 → entitysdk-0.7.0}/src/entitysdk/route.py +0 -0
  96. {entitysdk-0.6.2 → entitysdk-0.7.0}/src/entitysdk/schemas/__init__.py +0 -0
  97. {entitysdk-0.6.2 → entitysdk-0.7.0}/src/entitysdk/schemas/base.py +0 -0
  98. {entitysdk-0.6.2 → entitysdk-0.7.0}/src/entitysdk/schemas/memodel.py +0 -0
  99. {entitysdk-0.6.2 → entitysdk-0.7.0}/src/entitysdk/serdes.py +0 -0
  100. {entitysdk-0.6.2 → entitysdk-0.7.0}/src/entitysdk/token_manager.py +0 -0
  101. {entitysdk-0.6.2 → entitysdk-0.7.0}/src/entitysdk/util.py +0 -0
  102. {entitysdk-0.6.2 → entitysdk-0.7.0}/src/entitysdk/utils/__init__.py +0 -0
  103. {entitysdk-0.6.2 → entitysdk-0.7.0}/src/entitysdk/utils/asset.py +0 -0
  104. {entitysdk-0.6.2 → entitysdk-0.7.0}/src/entitysdk/utils/filesystem.py +0 -0
  105. {entitysdk-0.6.2 → entitysdk-0.7.0}/src/entitysdk.egg-info/dependency_links.txt +0 -0
  106. {entitysdk-0.6.2 → entitysdk-0.7.0}/src/entitysdk.egg-info/requires.txt +0 -0
  107. {entitysdk-0.6.2 → entitysdk-0.7.0}/src/entitysdk.egg-info/top_level.txt +0 -0
  108. {entitysdk-0.6.2 → entitysdk-0.7.0}/tests/__init__.py +0 -0
  109. {entitysdk-0.6.2 → entitysdk-0.7.0}/tests/integration/__init__.py +0 -0
  110. {entitysdk-0.6.2 → entitysdk-0.7.0}/tests/integration/conftest.py +0 -0
  111. {entitysdk-0.6.2 → entitysdk-0.7.0}/tests/integration/test_searching.py +0 -0
  112. {entitysdk-0.6.2 → entitysdk-0.7.0}/tests/unit/__init__.py +0 -0
  113. {entitysdk-0.6.2 → entitysdk-0.7.0}/tests/unit/conftest.py +0 -0
  114. {entitysdk-0.6.2/tests/unit/models → entitysdk-0.7.0/tests/unit/dependencies}/__init__.py +0 -0
  115. {entitysdk-0.6.2 → entitysdk-0.7.0}/tests/unit/downloaders/test_emodel.py +0 -0
  116. {entitysdk-0.6.2 → entitysdk-0.7.0}/tests/unit/downloaders/test_ion_channel_model.py +0 -0
  117. {entitysdk-0.6.2 → entitysdk-0.7.0}/tests/unit/downloaders/test_memodel.py +0 -0
  118. {entitysdk-0.6.2 → entitysdk-0.7.0}/tests/unit/downloaders/test_morphology.py +0 -0
  119. /entitysdk-0.6.2/tests/unit/models/data/.gitignore → /entitysdk-0.7.0/tests/unit/models/__init__.py +0 -0
  120. {entitysdk-0.6.2 → entitysdk-0.7.0}/tests/unit/models/data/circuit.json +0 -0
  121. {entitysdk-0.6.2 → entitysdk-0.7.0}/tests/unit/models/data/electrical_cell_recording.json +0 -0
  122. {entitysdk-0.6.2 → entitysdk-0.7.0}/tests/unit/models/data/ion_channel_model.json +0 -0
  123. {entitysdk-0.6.2 → entitysdk-0.7.0}/tests/unit/models/data/memodel_calibration_result.json +0 -0
  124. {entitysdk-0.6.2 → entitysdk-0.7.0}/tests/unit/models/data/reconstruction_morphology.json +0 -0
  125. {entitysdk-0.6.2 → entitysdk-0.7.0}/tests/unit/models/data/simulation_campaign.json +0 -0
  126. {entitysdk-0.6.2 → entitysdk-0.7.0}/tests/unit/models/data/validation_result.json +0 -0
  127. {entitysdk-0.6.2 → entitysdk-0.7.0}/tests/unit/models/test_agent.py +0 -0
  128. {entitysdk-0.6.2 → entitysdk-0.7.0}/tests/unit/models/test_asset.py +0 -0
  129. {entitysdk-0.6.2 → entitysdk-0.7.0}/tests/unit/models/test_brain_region.py +0 -0
  130. {entitysdk-0.6.2 → entitysdk-0.7.0}/tests/unit/models/test_circuit.py +0 -0
  131. {entitysdk-0.6.2 → entitysdk-0.7.0}/tests/unit/models/test_contribution.py +0 -0
  132. {entitysdk-0.6.2 → entitysdk-0.7.0}/tests/unit/models/test_electrical_cell_recording.py +0 -0
  133. {entitysdk-0.6.2 → entitysdk-0.7.0}/tests/unit/models/test_init.py +0 -0
  134. {entitysdk-0.6.2 → entitysdk-0.7.0}/tests/unit/models/test_ion_channel_model.py +0 -0
  135. {entitysdk-0.6.2 → entitysdk-0.7.0}/tests/unit/models/test_memodel_calibration_result.py +0 -0
  136. {entitysdk-0.6.2 → entitysdk-0.7.0}/tests/unit/models/test_morphology.py +0 -0
  137. {entitysdk-0.6.2 → entitysdk-0.7.0}/tests/unit/models/test_simulation_campaign.py +0 -0
  138. {entitysdk-0.6.2 → entitysdk-0.7.0}/tests/unit/models/test_validation_result.py +0 -0
  139. {entitysdk-0.6.2 → entitysdk-0.7.0}/tests/unit/test_base.py +0 -0
  140. {entitysdk-0.6.2 → entitysdk-0.7.0}/tests/unit/test_common.py +0 -0
  141. {entitysdk-0.6.2 → entitysdk-0.7.0}/tests/unit/test_config.py +0 -0
  142. {entitysdk-0.6.2 → entitysdk-0.7.0}/tests/unit/test_result.py +0 -0
  143. {entitysdk-0.6.2 → entitysdk-0.7.0}/tests/unit/test_route.py +0 -0
  144. {entitysdk-0.6.2 → entitysdk-0.7.0}/tests/unit/test_serdes.py +0 -0
  145. {entitysdk-0.6.2 → entitysdk-0.7.0}/tests/unit/test_token_manager.py +0 -0
  146. {entitysdk-0.6.2 → entitysdk-0.7.0}/tests/unit/test_util.py +0 -0
  147. {entitysdk-0.6.2 → entitysdk-0.7.0}/tests/unit/util.py +0 -0
  148. {entitysdk-0.6.2 → entitysdk-0.7.0}/tests/unit/utils/test_filesystem.py +0 -0
  149. {entitysdk-0.6.2 → entitysdk-0.7.0}/tox.ini +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: entitysdk
3
- Version: 0.6.2
3
+ Version: 0.7.0
4
4
  Summary: Python library for interacting with the entitycore service
5
5
  Author-email: Open Brain Institute <info@openbraininstitute.org>
6
6
  Maintainer-email: Open Brain Institute <info@openbraininstitute.org>
@@ -3,7 +3,7 @@
3
3
  import io
4
4
  import os
5
5
  from pathlib import Path
6
- from typing import Any, cast
6
+ from typing import Any, TypeVar, cast
7
7
 
8
8
  import httpx
9
9
 
@@ -14,7 +14,7 @@ from entitysdk.models.asset import Asset, DetailedFileList, LocalAssetMetadata
14
14
  from entitysdk.models.core import Identifiable
15
15
  from entitysdk.models.entity import Entity
16
16
  from entitysdk.result import IteratorResult
17
- from entitysdk.schemas.asset import DownloadedAsset
17
+ from entitysdk.schemas.asset import DownloadedAssetFile
18
18
  from entitysdk.token_manager import TokenFromValue, TokenManager
19
19
  from entitysdk.types import ID, DeploymentEnvironment, Token
20
20
  from entitysdk.util import (
@@ -24,6 +24,8 @@ from entitysdk.util import (
24
24
  )
25
25
  from entitysdk.utils.asset import filter_assets
26
26
 
27
+ TEntity = TypeVar("TEntity", bound=Entity)
28
+
27
29
 
28
30
  class Client:
29
31
  """Client for entitysdk."""
@@ -94,9 +96,9 @@ class Client:
94
96
  self,
95
97
  entity_id: ID,
96
98
  *,
97
- entity_type: type[Identifiable],
99
+ entity_type: type[TEntity],
98
100
  project_context: ProjectContext | None = None,
99
- ) -> Identifiable:
101
+ ) -> TEntity:
100
102
  """Get entity from resource id.
101
103
 
102
104
  Args:
@@ -357,28 +359,30 @@ class Client:
357
359
 
358
360
  context = self._optional_user_context(override_context=project_context)
359
361
 
360
- asset = None
362
+ asset = cast(Asset, asset_id) if isinstance(asset_id, Asset) else None
363
+
361
364
  if not ignore_directory_name:
362
- asset_endpoint = route.get_assets_endpoint(
363
- api_url=self.api_url,
364
- entity_type=entity_type,
365
- entity_id=entity_id,
366
- asset_id=asset_id,
367
- )
368
- asset = core.get_entity(
369
- asset_endpoint,
370
- entity_type=Asset,
371
- project_context=context,
372
- http_client=self._http_client,
373
- token=self._token_manager.get_token(),
374
- )
365
+ if asset is None:
366
+ asset_endpoint = route.get_assets_endpoint(
367
+ api_url=self.api_url,
368
+ entity_type=entity_type,
369
+ entity_id=cast(ID, entity_id),
370
+ asset_id=asset_id,
371
+ )
372
+ asset = core.get_entity(
373
+ asset_endpoint,
374
+ entity_type=Asset,
375
+ project_context=context,
376
+ http_client=self._http_client,
377
+ token=self._token_manager.get_token(),
378
+ )
375
379
 
376
380
  output_path /= asset.path
377
381
 
378
382
  contents = self.list_directory(
379
383
  entity_id=entity_id,
380
384
  entity_type=entity_type,
381
- asset_id=asset_id,
385
+ asset_id=asset_id if isinstance(asset_id, ID) else asset.id,
382
386
  project_context=project_context,
383
387
  )
384
388
 
@@ -457,6 +461,7 @@ class Client:
457
461
  Output file path.
458
462
  """
459
463
  context = self._optional_user_context(override_context=project_context)
464
+
460
465
  asset_endpoint = route.get_assets_endpoint(
461
466
  api_url=self.api_url,
462
467
  entity_type=entity_type,
@@ -498,6 +503,11 @@ class Client:
498
503
  token=self._token_manager.get_token(),
499
504
  )
500
505
 
506
+ @staticmethod
507
+ def select_assets(entity: Entity, selection: dict) -> IteratorResult:
508
+ """Select assets from entity based on selection."""
509
+ return IteratorResult(filter_assets(entity.assets, selection))
510
+
501
511
  def download_assets(
502
512
  self,
503
513
  entity_or_id: Entity | tuple[ID, type[Entity]],
@@ -520,9 +530,9 @@ class Client:
520
530
  project_context=context,
521
531
  )
522
532
 
523
- return DownloadedAsset(
533
+ return DownloadedAssetFile(
524
534
  asset=asset,
525
- output_path=path,
535
+ path=path,
526
536
  )
527
537
 
528
538
  context = self._optional_user_context(override_context=project_context)
@@ -0,0 +1 @@
1
+ """Dependencies."""
@@ -0,0 +1,18 @@
1
+ """Entity dependencies."""
2
+
3
+ from entitysdk.exception import DependencyError
4
+ from entitysdk.models.entity import Entity
5
+
6
+
7
+ def ensure_has_id(model: Entity) -> Entity:
8
+ """Ensure entity has id."""
9
+ if model.id is None:
10
+ raise DependencyError(f"Model has no id: {repr(model)}")
11
+ return model
12
+
13
+
14
+ def ensure_has_assets(model: Entity) -> Entity:
15
+ """Ensure entity has assets."""
16
+ if not model.assets:
17
+ raise DependencyError(f"Model has no assets: {repr(model)}")
18
+ return model
@@ -26,4 +26,4 @@ def download_hoc(
26
26
  output_path=output_dir,
27
27
  ).one()
28
28
 
29
- return asset.output_path
29
+ return asset.path
@@ -26,4 +26,4 @@ def download_ion_channel_mechanism(
26
26
  output_path=output_dir,
27
27
  ).one()
28
28
 
29
- return asset.output_path
29
+ return asset.path
@@ -33,4 +33,4 @@ def download_morphology(
33
33
  output_path=output_dir,
34
34
  ).one()
35
35
 
36
- return asset.output_path
36
+ return asset.path
@@ -0,0 +1,78 @@
1
+ """Downloading functions for Simulation."""
2
+
3
+ import json
4
+ import logging
5
+ from pathlib import Path
6
+ from typing import cast
7
+
8
+ from entitysdk.client import Client
9
+ from entitysdk.dependencies.entity import ensure_has_assets, ensure_has_id
10
+ from entitysdk.models import Simulation
11
+ from entitysdk.types import ID
12
+
13
+ L = logging.getLogger(__name__)
14
+
15
+
16
+ def download_simulation_config_content(client: Client, *, model: Simulation) -> dict:
17
+ """Download the the simulation config json into a dictionary."""
18
+ ensure_has_id(model)
19
+ ensure_has_assets(model)
20
+
21
+ asset = client.select_assets(
22
+ model,
23
+ selection={"label": "sonata_simulation_config"},
24
+ ).one()
25
+
26
+ json_content: bytes = client.download_content(
27
+ entity_id=cast(ID, model.id),
28
+ entity_type=Simulation,
29
+ asset_id=asset.id,
30
+ )
31
+
32
+ return json.loads(json_content)
33
+
34
+
35
+ def download_node_sets_file(client: Client, *, model: Simulation, output_path: Path) -> Path:
36
+ """Download the node sets file from simulation's assets."""
37
+ ensure_has_id(model)
38
+ ensure_has_assets(model)
39
+
40
+ asset = client.select_assets(
41
+ model,
42
+ selection={"label": "custom_node_sets"},
43
+ ).one()
44
+
45
+ path = client.download_file(
46
+ entity_id=cast(ID, model.id),
47
+ entity_type=Simulation,
48
+ asset_id=asset,
49
+ output_path=output_path,
50
+ )
51
+
52
+ L.info("Node sets file downloaded at %s", path)
53
+
54
+ return path
55
+
56
+
57
+ def download_spike_replay_files(
58
+ client: Client, *, model: Simulation, output_dir: Path
59
+ ) -> list[Path]:
60
+ """Download the spike replay files from simualtion's assets."""
61
+ ensure_has_id(model)
62
+ ensure_has_assets(model)
63
+
64
+ assets = client.select_assets(model, selection={"label": "replay_spikes"}).all()
65
+
66
+ spike_files: list[Path] = [
67
+ client.download_file(
68
+ entity_id=cast(ID, model.id),
69
+ entity_type=Simulation,
70
+ asset_id=asset,
71
+ output_path=output_dir / asset.path,
72
+ )
73
+ for asset in assets
74
+ ]
75
+
76
+ L.info("Downloaded %d spike replay files: %s", len(spike_files), spike_files)
77
+
78
+ return spike_files
@@ -0,0 +1,61 @@
1
+ """Downloading functions for SimulationResult."""
2
+
3
+ import logging
4
+ from pathlib import Path
5
+ from typing import cast
6
+
7
+ from entitysdk.client import Client
8
+ from entitysdk.dependencies.entity import ensure_has_assets, ensure_has_id
9
+ from entitysdk.models import SimulationResult
10
+ from entitysdk.types import ID
11
+
12
+ L = logging.getLogger(__name__)
13
+
14
+
15
+ def download_spike_report_file(
16
+ client: Client, *, model: SimulationResult, output_path: Path
17
+ ) -> Path:
18
+ """Download spike report file from SimulationResult entity."""
19
+ ensure_has_id(model)
20
+ ensure_has_assets(model)
21
+
22
+ asset = client.select_assets(
23
+ model,
24
+ selection={"label": "spike_report"},
25
+ ).one()
26
+
27
+ path = client.download_file(
28
+ entity_id=cast(ID, model.id),
29
+ entity_type=SimulationResult,
30
+ asset_id=asset,
31
+ output_path=output_path / asset.path if output_path.is_dir() else output_path,
32
+ )
33
+ L.info("Spike report file downloaded at %s", path)
34
+ return path
35
+
36
+
37
+ def download_voltage_report_files(
38
+ client: Client, *, model: SimulationResult, output_dir: Path
39
+ ) -> list[Path]:
40
+ """Download voltage report files from SimulationResult entity."""
41
+ ensure_has_id(model)
42
+ ensure_has_assets(model)
43
+
44
+ assets = client.select_assets(
45
+ model,
46
+ selection={"label": "voltage_report"},
47
+ ).all()
48
+
49
+ files: list[Path] = [
50
+ client.download_file(
51
+ entity_id=cast(ID, model.id),
52
+ entity_type=SimulationResult,
53
+ asset_id=asset,
54
+ output_path=output_dir / asset.path,
55
+ )
56
+ for asset in assets
57
+ ]
58
+
59
+ L.info("Downloaded voltage report files: %s", files)
60
+
61
+ return files
@@ -11,3 +11,11 @@ class RouteNotFoundError(EntitySDKError):
11
11
 
12
12
  class IteratorResultError(EntitySDKError):
13
13
  """Raised when the result of an iterator is not as expected."""
14
+
15
+
16
+ class DependencyError(EntitySDKError):
17
+ """Raised when a dependency check fails."""
18
+
19
+
20
+ class StagingError(EntitySDKError):
21
+ """Raised when a staging operation has failed."""
@@ -8,11 +8,14 @@ from pydantic import ConfigDict, Field
8
8
 
9
9
  from entitysdk.models.base import BaseModel
10
10
  from entitysdk.models.core import Identifiable
11
+ from entitysdk.types import ID
11
12
 
12
13
 
13
14
  class Asset(Identifiable):
14
15
  """Asset."""
15
16
 
17
+ id: ID
18
+
16
19
  path: Annotated[
17
20
  str,
18
21
  Field(
@@ -0,0 +1,20 @@
1
+ """Asset related schemas."""
2
+
3
+ from pathlib import Path
4
+
5
+ from entitysdk.models.asset import Asset
6
+ from entitysdk.schemas.base import Schema
7
+
8
+
9
+ class DownloadedAssetFile(Schema):
10
+ """Downloaded asset file."""
11
+
12
+ asset: Asset
13
+ path: Path
14
+
15
+
16
+ class DownloadedAssetContent(Schema):
17
+ """Downloaded asset content."""
18
+
19
+ asset: Asset
20
+ content: bytes
@@ -0,0 +1,7 @@
1
+ """Staging functions."""
2
+
3
+ from entitysdk.staging.circuit import stage_circuit
4
+ from entitysdk.staging.simulation import stage_simulation
5
+ from entitysdk.staging.simulation_result import stage_simulation_result
6
+
7
+ __all__ = ["stage_circuit", "stage_simulation", "stage_simulation_result"]
@@ -0,0 +1,43 @@
1
+ """Staging functions for Circuit."""
2
+
3
+ import logging
4
+ from pathlib import Path
5
+ from typing import cast
6
+
7
+ from entitysdk.client import Client
8
+ from entitysdk.dependencies.entity import ensure_has_assets, ensure_has_id
9
+ from entitysdk.models import Circuit
10
+ from entitysdk.types import ID
11
+
12
+ L = logging.getLogger(__name__)
13
+
14
+
15
+ def stage_circuit(client: Client, *, model: Circuit, output_dir: Path) -> Path:
16
+ """Stage a Circuit directory into output_dir."""
17
+ ensure_has_id(model)
18
+ ensure_has_assets(model)
19
+
20
+ asset = client.select_assets(
21
+ model,
22
+ selection={
23
+ "content_type": "application/vnd.directory",
24
+ "is_directory": True,
25
+ },
26
+ ).one()
27
+
28
+ paths = client.download_directory(
29
+ entity_id=cast(ID, model.id),
30
+ entity_type=Circuit,
31
+ asset_id=asset,
32
+ output_path=output_dir,
33
+ ignore_directory_name=True,
34
+ )
35
+
36
+ L.debug("Downloaded circuit %s paths: %s", model.id, paths)
37
+
38
+ circuit_config_path = output_dir / "circuit_config.json"
39
+ assert circuit_config_path in paths
40
+
41
+ L.info("Circuit %s staged at %s", model.id, circuit_config_path)
42
+
43
+ return circuit_config_path
@@ -0,0 +1,142 @@
1
+ """Staging functions for Simulation."""
2
+
3
+ import logging
4
+ from copy import deepcopy
5
+ from pathlib import Path
6
+
7
+ from entitysdk.client import Client
8
+ from entitysdk.downloaders.simulation import (
9
+ download_node_sets_file,
10
+ download_simulation_config_content,
11
+ download_spike_replay_files,
12
+ )
13
+ from entitysdk.exception import StagingError
14
+ from entitysdk.models import Circuit, Simulation
15
+ from entitysdk.staging.circuit import stage_circuit
16
+ from entitysdk.types import StrOrPath
17
+ from entitysdk.utils.filesystem import create_dir
18
+ from entitysdk.utils.io import write_json
19
+
20
+ L = logging.getLogger(__name__)
21
+
22
+ DEFAULT_NODE_SETS_FILENAME = "node_sets.json"
23
+ DEFAULT_SIMULATION_CONFIG_FILENAME = "simulation_config.json"
24
+ DEFAULT_CIRCUIT_DIR = "circuit"
25
+
26
+
27
+ def stage_simulation(
28
+ client: Client,
29
+ *,
30
+ model: Simulation,
31
+ output_dir: StrOrPath,
32
+ circuit_config_path: Path | None = None,
33
+ override_results_dir: Path | None = None,
34
+ ) -> Path:
35
+ """Stage a simulation entity into output_dir.
36
+
37
+ Args:
38
+ client: The client to use to stage the simulation.
39
+ model: The simulation entity to stage.
40
+ output_dir: The directory to stage the simulation into.
41
+ circuit_config_path: The path to the circuit config file.
42
+ If not provided, the circuit will be staged from metadata.
43
+ override_results_dir: Directory to update the simulation config section to point to.
44
+
45
+ Returns:
46
+ The path to the staged simulation config file.
47
+ """
48
+ output_dir = create_dir(output_dir).resolve()
49
+
50
+ simulation_config: dict = download_simulation_config_content(client, model=model)
51
+ node_sets_file: Path = download_node_sets_file(
52
+ client,
53
+ model=model,
54
+ output_path=output_dir / DEFAULT_NODE_SETS_FILENAME,
55
+ )
56
+ spike_paths: list[Path] = download_spike_replay_files(
57
+ client,
58
+ model=model,
59
+ output_dir=output_dir,
60
+ )
61
+ if circuit_config_path is None:
62
+ L.info(
63
+ "Circuit config path was not provided. Circuit is going to be staged from metadata. "
64
+ "Circuit id to be staged: %s"
65
+ )
66
+ circuit_config_path = stage_circuit(
67
+ client,
68
+ model=client.get_entity(
69
+ entity_id=model.entity_id,
70
+ entity_type=Circuit,
71
+ ),
72
+ output_dir=create_dir(output_dir / DEFAULT_CIRCUIT_DIR),
73
+ )
74
+
75
+ transformed_simulation_config: dict = _transform_simulation_config(
76
+ simulation_config=simulation_config,
77
+ circuit_config_path=circuit_config_path,
78
+ node_sets_path=node_sets_file,
79
+ spike_paths=spike_paths,
80
+ output_dir=output_dir,
81
+ override_results_dir=override_results_dir,
82
+ )
83
+
84
+ output_simulation_config_file = output_dir / DEFAULT_SIMULATION_CONFIG_FILENAME
85
+
86
+ write_json(
87
+ data=transformed_simulation_config,
88
+ path=output_simulation_config_file,
89
+ )
90
+
91
+ L.info("Staged Simulation %s at %s", model.id, output_dir)
92
+
93
+ return output_simulation_config_file
94
+
95
+
96
+ def _transform_simulation_config(
97
+ simulation_config: dict,
98
+ circuit_config_path: Path,
99
+ node_sets_path: Path,
100
+ spike_paths: list[Path],
101
+ output_dir: Path,
102
+ override_results_dir: Path | None,
103
+ ) -> dict:
104
+ return simulation_config | {
105
+ "network": str(circuit_config_path),
106
+ "node_sets_file": str(node_sets_path.relative_to(output_dir)),
107
+ "inputs": _transform_inputs(simulation_config["inputs"], spike_paths),
108
+ "output": _transform_output(simulation_config["output"], override_results_dir),
109
+ }
110
+
111
+
112
+ def _transform_inputs(inputs: dict, spike_paths: list[Path]) -> dict:
113
+ expected_spike_filenames = {p.name for p in spike_paths}
114
+
115
+ transformed_inputs = deepcopy(inputs)
116
+ for values in transformed_inputs.values():
117
+ if values["input_type"] == "spikes":
118
+ path = Path(values["spike_file"]).name
119
+
120
+ if path not in expected_spike_filenames:
121
+ raise StagingError(
122
+ f"Spike file name in config is not present in spike asset file names.\n"
123
+ f"Config file name: {path}\n"
124
+ f"Asset file names: {expected_spike_filenames}"
125
+ )
126
+
127
+ values["spike_file"] = str(path)
128
+ L.debug("Spike file %s -> %s", values["spike_file"], path)
129
+
130
+ return transformed_inputs
131
+
132
+
133
+ def _transform_output(output: dict, override_results_dir: StrOrPath | None) -> dict:
134
+ if override_results_dir is None:
135
+ return output
136
+
137
+ path = Path(override_results_dir)
138
+
139
+ return {
140
+ "output_dir": str(path),
141
+ "spikes_file": str(path / "spikes.h5"),
142
+ }
@@ -0,0 +1,72 @@
1
+ """Staging functions for SimulationResult."""
2
+
3
+ import logging
4
+ from pathlib import Path
5
+
6
+ from entitysdk.client import Client
7
+ from entitysdk.downloaders.simulation_result import (
8
+ download_spike_report_file,
9
+ download_voltage_report_files,
10
+ )
11
+ from entitysdk.models import Simulation, SimulationResult
12
+ from entitysdk.staging.simulation import stage_simulation
13
+ from entitysdk.types import StrOrPath
14
+ from entitysdk.utils.filesystem import create_dir
15
+ from entitysdk.utils.io import load_json
16
+
17
+ L = logging.getLogger(__name__)
18
+
19
+
20
+ DEFAULT_REPORTS_DIR_NAME = "output"
21
+ DEFAULT_SPIKE_FILE_NAME = "output/spikes.h5"
22
+ DEFAULT_SIMULATION_CONFIG_FILENAME = "simulation_config.json"
23
+
24
+
25
+ def stage_simulation_result(
26
+ client: Client,
27
+ *,
28
+ model: SimulationResult,
29
+ output_dir: StrOrPath,
30
+ simulation_config_file: StrOrPath | None = None,
31
+ ) -> Path:
32
+ """Stage a SimulationResult entity."""
33
+ output_dir: Path = create_dir(output_dir)
34
+
35
+ if simulation_config_file is None:
36
+ L.info(
37
+ "Simulation will be staged from simulation result's simulation_id %s",
38
+ model.simulation_id,
39
+ )
40
+ simulation_config_file = stage_simulation(
41
+ client,
42
+ model=client.get_entity(entity_id=model.simulation_id, entity_type=Simulation),
43
+ output_dir=output_dir,
44
+ )
45
+ else:
46
+ L.info(
47
+ "External simulation config provided at %s. Outputs will be staged relative to it.",
48
+ simulation_config_file,
49
+ )
50
+
51
+ config: dict = load_json(simulation_config_file)
52
+ reports_dir, spikes_file = _get_output_paths(config, Path(simulation_config_file).parent)
53
+ create_dir(reports_dir)
54
+
55
+ download_spike_report_file(
56
+ client,
57
+ model=model,
58
+ output_path=spikes_file,
59
+ )
60
+ download_voltage_report_files(
61
+ client,
62
+ model=model,
63
+ output_dir=reports_dir,
64
+ )
65
+
66
+ return Path(simulation_config_file)
67
+
68
+
69
+ def _get_output_paths(config: dict, output_dir: Path) -> tuple[Path, Path]:
70
+ reports_dir = output_dir / config["output"]["output_dir"]
71
+ spikes_file = output_dir / config["output"]["spikes_file"]
72
+ return reports_dir, spikes_file
@@ -123,3 +123,9 @@ class SimulationExecutionStatus(StrEnum):
123
123
  running = auto()
124
124
  done = auto()
125
125
  error = auto()
126
+
127
+
128
+ class ContentType(StrEnum):
129
+ """Content types."""
130
+
131
+ json = "application/json"
@@ -0,0 +1,16 @@
1
+ """IO utilities."""
2
+
3
+ import json
4
+ from pathlib import Path
5
+
6
+ from entitysdk.types import StrOrPath
7
+
8
+
9
+ def write_json(data: dict, path: StrOrPath, **json_kwargs) -> None:
10
+ """Write dictionary to file as JSON."""
11
+ Path(path).write_text(json.dumps(data, **json_kwargs))
12
+
13
+
14
+ def load_json(path: StrOrPath) -> dict:
15
+ """Load JSON file to dict."""
16
+ return json.loads(Path(path).read_bytes())
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: entitysdk
3
- Version: 0.6.2
3
+ Version: 0.7.0
4
4
  Summary: Python library for interacting with the entitycore service
5
5
  Author-email: Open Brain Institute <info@openbraininstitute.org>
6
6
  Maintainer-email: Open Brain Institute <info@openbraininstitute.org>