entitysdk 0.6.2__tar.gz → 0.7.1__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.
- {entitysdk-0.6.2/src/entitysdk.egg-info → entitysdk-0.7.1}/PKG-INFO +1 -1
- {entitysdk-0.6.2 → entitysdk-0.7.1}/pyproject.toml +1 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/src/entitysdk/client.py +59 -30
- {entitysdk-0.6.2 → entitysdk-0.7.1}/src/entitysdk/core.py +1 -1
- entitysdk-0.7.1/src/entitysdk/dependencies/__init__.py +1 -0
- entitysdk-0.7.1/src/entitysdk/dependencies/entity.py +18 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/src/entitysdk/downloaders/emodel.py +1 -1
- {entitysdk-0.6.2 → entitysdk-0.7.1}/src/entitysdk/downloaders/ion_channel_model.py +1 -1
- {entitysdk-0.6.2 → entitysdk-0.7.1}/src/entitysdk/downloaders/morphology.py +1 -1
- entitysdk-0.7.1/src/entitysdk/downloaders/simulation.py +78 -0
- entitysdk-0.7.1/src/entitysdk/downloaders/simulation_result.py +61 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/src/entitysdk/exception.py +8 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/src/entitysdk/models/asset.py +5 -2
- entitysdk-0.7.1/src/entitysdk/schemas/asset.py +20 -0
- entitysdk-0.7.1/src/entitysdk/staging/__init__.py +7 -0
- entitysdk-0.7.1/src/entitysdk/staging/circuit.py +44 -0
- entitysdk-0.7.1/src/entitysdk/staging/simulation.py +142 -0
- entitysdk-0.7.1/src/entitysdk/staging/simulation_result.py +72 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/src/entitysdk/types.py +6 -0
- entitysdk-0.7.1/src/entitysdk/utils/io.py +16 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1/src/entitysdk.egg-info}/PKG-INFO +1 -1
- {entitysdk-0.6.2 → entitysdk-0.7.1}/src/entitysdk.egg-info/SOURCES.txt +24 -0
- entitysdk-0.7.1/tests/unit/dependencies/test_entity.py +27 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/tests/unit/downloaders/test_emodel.py +1 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/tests/unit/downloaders/test_ion_channel_model.py +1 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/tests/unit/downloaders/test_memodel.py +3 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/tests/unit/downloaders/test_morphology.py +1 -0
- entitysdk-0.7.1/tests/unit/models/data/.gitignore +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/tests/unit/models/data/electrical_cell_recording.json +1 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/tests/unit/models/data/ion_channel_model.json +2 -1
- {entitysdk-0.6.2 → entitysdk-0.7.1}/tests/unit/models/data/reconstruction_morphology.json +2 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/tests/unit/models/test_asset.py +4 -2
- entitysdk-0.7.1/tests/unit/staging/__init__.py +0 -0
- entitysdk-0.7.1/tests/unit/staging/conftest.py +281 -0
- entitysdk-0.7.1/tests/unit/staging/data/SomaVoltRec 1.h5 +0 -0
- entitysdk-0.7.1/tests/unit/staging/data/SomaVoltRec 2.h5 +0 -0
- entitysdk-0.7.1/tests/unit/staging/data/circuit/circuit_config.json +36 -0
- entitysdk-0.7.1/tests/unit/staging/data/circuit/edges.h5 +0 -0
- entitysdk-0.7.1/tests/unit/staging/data/circuit/nodes.h5 +0 -0
- entitysdk-0.7.1/tests/unit/staging/data/node_sets.json +67 -0
- entitysdk-0.7.1/tests/unit/staging/data/simulation_config.json +128 -0
- entitysdk-0.7.1/tests/unit/staging/data/spike_replays.h5 +0 -0
- entitysdk-0.7.1/tests/unit/staging/data/spikes.h5 +0 -0
- entitysdk-0.7.1/tests/unit/staging/test_simulation.py +98 -0
- entitysdk-0.7.1/tests/unit/staging/test_simulation_result.py +106 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/tests/unit/test_client.py +79 -26
- {entitysdk-0.6.2 → entitysdk-0.7.1}/tests/unit/utils/test_asset.py +8 -0
- entitysdk-0.6.2/src/entitysdk/schemas/asset.py +0 -13
- {entitysdk-0.6.2 → entitysdk-0.7.1}/.github/workflows/sdist.yml +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/.github/workflows/tox.yml +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/.gitignore +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/CHANGELOG.rst +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/CONTRIBUTING.md +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/LICENSE.txt +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/README.md +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/examples/01_searching.ipynb +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/examples/02_morphology.ipynb +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/examples/03_circuit.ipynb +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/examples/04_simulation_campaign.ipynb +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/examples/utils.py +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/setup.cfg +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/src/entitysdk/__init__.py +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/src/entitysdk/common.py +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/src/entitysdk/config.py +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/src/entitysdk/downloaders/__init__.py +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/src/entitysdk/downloaders/memodel.py +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/src/entitysdk/mixin.py +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/src/entitysdk/models/__init__.py +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/src/entitysdk/models/activity.py +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/src/entitysdk/models/agent.py +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/src/entitysdk/models/base.py +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/src/entitysdk/models/brain_location.py +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/src/entitysdk/models/brain_region.py +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/src/entitysdk/models/brain_region_hierarchy.py +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/src/entitysdk/models/circuit.py +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/src/entitysdk/models/classification.py +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/src/entitysdk/models/contribution.py +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/src/entitysdk/models/core.py +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/src/entitysdk/models/electrical_cell_recording.py +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/src/entitysdk/models/emodel.py +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/src/entitysdk/models/entity.py +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/src/entitysdk/models/etype.py +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/src/entitysdk/models/ion_channel_model.py +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/src/entitysdk/models/license.py +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/src/entitysdk/models/memodel.py +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/src/entitysdk/models/memodelcalibrationresult.py +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/src/entitysdk/models/morphology.py +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/src/entitysdk/models/mtype.py +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/src/entitysdk/models/response.py +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/src/entitysdk/models/scientific_artifact.py +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/src/entitysdk/models/simulation.py +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/src/entitysdk/models/simulation_campaign.py +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/src/entitysdk/models/simulation_execution.py +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/src/entitysdk/models/simulation_generation.py +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/src/entitysdk/models/simulation_result.py +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/src/entitysdk/models/single_neuron_simulation.py +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/src/entitysdk/models/single_neuron_synaptome_simulation.py +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/src/entitysdk/models/subject.py +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/src/entitysdk/models/synaptome.py +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/src/entitysdk/models/taxonomy.py +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/src/entitysdk/models/validation_result.py +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/src/entitysdk/result.py +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/src/entitysdk/route.py +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/src/entitysdk/schemas/__init__.py +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/src/entitysdk/schemas/base.py +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/src/entitysdk/schemas/memodel.py +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/src/entitysdk/serdes.py +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/src/entitysdk/token_manager.py +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/src/entitysdk/util.py +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/src/entitysdk/utils/__init__.py +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/src/entitysdk/utils/asset.py +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/src/entitysdk/utils/filesystem.py +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/src/entitysdk.egg-info/dependency_links.txt +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/src/entitysdk.egg-info/requires.txt +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/src/entitysdk.egg-info/top_level.txt +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/tests/__init__.py +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/tests/integration/__init__.py +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/tests/integration/conftest.py +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/tests/integration/test_searching.py +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/tests/unit/__init__.py +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/tests/unit/conftest.py +0 -0
- {entitysdk-0.6.2/tests/unit/models → entitysdk-0.7.1/tests/unit/dependencies}/__init__.py +0 -0
- /entitysdk-0.6.2/tests/unit/models/data/.gitignore → /entitysdk-0.7.1/tests/unit/models/__init__.py +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/tests/unit/models/data/circuit.json +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/tests/unit/models/data/memodel_calibration_result.json +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/tests/unit/models/data/simulation_campaign.json +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/tests/unit/models/data/validation_result.json +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/tests/unit/models/test_agent.py +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/tests/unit/models/test_brain_region.py +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/tests/unit/models/test_circuit.py +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/tests/unit/models/test_contribution.py +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/tests/unit/models/test_electrical_cell_recording.py +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/tests/unit/models/test_init.py +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/tests/unit/models/test_ion_channel_model.py +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/tests/unit/models/test_memodel_calibration_result.py +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/tests/unit/models/test_morphology.py +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/tests/unit/models/test_simulation_campaign.py +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/tests/unit/models/test_validation_result.py +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/tests/unit/test_base.py +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/tests/unit/test_common.py +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/tests/unit/test_config.py +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/tests/unit/test_result.py +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/tests/unit/test_route.py +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/tests/unit/test_serdes.py +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/tests/unit/test_token_manager.py +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/tests/unit/test_util.py +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/tests/unit/util.py +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/tests/unit/utils/test_filesystem.py +0 -0
- {entitysdk-0.6.2 → entitysdk-0.7.1}/tox.ini +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: entitysdk
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.7.1
|
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>
|
@@ -1,9 +1,10 @@
|
|
1
1
|
"""Identifiable SDK client."""
|
2
2
|
|
3
|
+
import concurrent.futures
|
3
4
|
import io
|
4
5
|
import os
|
5
6
|
from pathlib import Path
|
6
|
-
from typing import Any, cast
|
7
|
+
from typing import Any, TypeVar, cast
|
7
8
|
|
8
9
|
import httpx
|
9
10
|
|
@@ -14,7 +15,7 @@ from entitysdk.models.asset import Asset, DetailedFileList, LocalAssetMetadata
|
|
14
15
|
from entitysdk.models.core import Identifiable
|
15
16
|
from entitysdk.models.entity import Entity
|
16
17
|
from entitysdk.result import IteratorResult
|
17
|
-
from entitysdk.schemas.asset import
|
18
|
+
from entitysdk.schemas.asset import DownloadedAssetFile
|
18
19
|
from entitysdk.token_manager import TokenFromValue, TokenManager
|
19
20
|
from entitysdk.types import ID, DeploymentEnvironment, Token
|
20
21
|
from entitysdk.util import (
|
@@ -24,6 +25,8 @@ from entitysdk.util import (
|
|
24
25
|
)
|
25
26
|
from entitysdk.utils.asset import filter_assets
|
26
27
|
|
28
|
+
TEntity = TypeVar("TEntity", bound=Entity)
|
29
|
+
|
27
30
|
|
28
31
|
class Client:
|
29
32
|
"""Client for entitysdk."""
|
@@ -94,9 +97,9 @@ class Client:
|
|
94
97
|
self,
|
95
98
|
entity_id: ID,
|
96
99
|
*,
|
97
|
-
entity_type: type[
|
100
|
+
entity_type: type[TEntity],
|
98
101
|
project_context: ProjectContext | None = None,
|
99
|
-
) ->
|
102
|
+
) -> TEntity:
|
100
103
|
"""Get entity from resource id.
|
101
104
|
|
102
105
|
Args:
|
@@ -214,7 +217,7 @@ class Client:
|
|
214
217
|
file_content_type: str,
|
215
218
|
file_name: str | None = None,
|
216
219
|
file_metadata: dict | None = None,
|
217
|
-
asset_label: str
|
220
|
+
asset_label: str,
|
218
221
|
project_context: ProjectContext | None = None,
|
219
222
|
) -> Asset:
|
220
223
|
"""Upload asset to an existing entity's endpoint from a file path."""
|
@@ -250,7 +253,7 @@ class Client:
|
|
250
253
|
file_name: str,
|
251
254
|
file_content_type: str,
|
252
255
|
file_metadata: dict | None = None,
|
253
|
-
asset_label: str
|
256
|
+
asset_label: str,
|
254
257
|
project_context: ProjectContext | None = None,
|
255
258
|
) -> Asset:
|
256
259
|
"""Upload asset to an existing entity's endpoint from a file-like object."""
|
@@ -284,7 +287,7 @@ class Client:
|
|
284
287
|
name: str,
|
285
288
|
paths: dict[os.PathLike, os.PathLike],
|
286
289
|
metadata: dict | None = None,
|
287
|
-
label: str
|
290
|
+
label: str,
|
288
291
|
project_context: ProjectContext | None = None,
|
289
292
|
) -> Asset:
|
290
293
|
"""Attach directory to an entity from with a group of paths."""
|
@@ -347,8 +350,9 @@ class Client:
|
|
347
350
|
output_path: os.PathLike,
|
348
351
|
project_context: ProjectContext | None = None,
|
349
352
|
ignore_directory_name: bool = False,
|
353
|
+
max_concurrent: int = 1,
|
350
354
|
) -> list[Path]:
|
351
|
-
"""
|
355
|
+
"""Download directory of assets."""
|
352
356
|
output_path = Path(output_path)
|
353
357
|
|
354
358
|
if output_path.exists() and output_path.is_file():
|
@@ -357,34 +361,35 @@ class Client:
|
|
357
361
|
|
358
362
|
context = self._optional_user_context(override_context=project_context)
|
359
363
|
|
360
|
-
asset = None
|
364
|
+
asset = cast(Asset, asset_id) if isinstance(asset_id, Asset) else None
|
365
|
+
|
361
366
|
if not ignore_directory_name:
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
367
|
+
if asset is None:
|
368
|
+
asset_endpoint = route.get_assets_endpoint(
|
369
|
+
api_url=self.api_url,
|
370
|
+
entity_type=entity_type,
|
371
|
+
entity_id=cast(ID, entity_id),
|
372
|
+
asset_id=asset_id,
|
373
|
+
)
|
374
|
+
asset = core.get_entity(
|
375
|
+
asset_endpoint,
|
376
|
+
entity_type=Asset,
|
377
|
+
project_context=context,
|
378
|
+
http_client=self._http_client,
|
379
|
+
token=self._token_manager.get_token(),
|
380
|
+
)
|
375
381
|
|
376
382
|
output_path /= asset.path
|
377
383
|
|
378
384
|
contents = self.list_directory(
|
379
385
|
entity_id=entity_id,
|
380
386
|
entity_type=entity_type,
|
381
|
-
asset_id=asset_id,
|
387
|
+
asset_id=asset_id if isinstance(asset_id, ID) else asset.id,
|
382
388
|
project_context=project_context,
|
383
389
|
)
|
384
390
|
|
385
|
-
|
386
|
-
|
387
|
-
paths.append(
|
391
|
+
if max_concurrent == 1:
|
392
|
+
paths = [
|
388
393
|
self.download_file(
|
389
394
|
entity_id=entity_id,
|
390
395
|
entity_type=entity_type,
|
@@ -393,7 +398,24 @@ class Client:
|
|
393
398
|
asset_path=path,
|
394
399
|
project_context=context,
|
395
400
|
)
|
396
|
-
|
401
|
+
for path in contents.files
|
402
|
+
]
|
403
|
+
else:
|
404
|
+
with concurrent.futures.ThreadPoolExecutor(max_workers=max_concurrent) as executor:
|
405
|
+
futures = [
|
406
|
+
executor.submit(
|
407
|
+
self.download_file,
|
408
|
+
entity_id=entity_id,
|
409
|
+
entity_type=entity_type,
|
410
|
+
asset_id=asset if asset else asset_id,
|
411
|
+
output_path=output_path / path,
|
412
|
+
asset_path=path,
|
413
|
+
project_context=context,
|
414
|
+
)
|
415
|
+
for path in contents.files
|
416
|
+
]
|
417
|
+
result = concurrent.futures.wait(futures)
|
418
|
+
paths = [res.result() for res in result.done]
|
397
419
|
|
398
420
|
return paths
|
399
421
|
|
@@ -457,6 +479,7 @@ class Client:
|
|
457
479
|
Output file path.
|
458
480
|
"""
|
459
481
|
context = self._optional_user_context(override_context=project_context)
|
482
|
+
|
460
483
|
asset_endpoint = route.get_assets_endpoint(
|
461
484
|
api_url=self.api_url,
|
462
485
|
entity_type=entity_type,
|
@@ -498,6 +521,11 @@ class Client:
|
|
498
521
|
token=self._token_manager.get_token(),
|
499
522
|
)
|
500
523
|
|
524
|
+
@staticmethod
|
525
|
+
def select_assets(entity: Entity, selection: dict) -> IteratorResult:
|
526
|
+
"""Select assets from entity based on selection."""
|
527
|
+
return IteratorResult(filter_assets(entity.assets, selection))
|
528
|
+
|
501
529
|
def download_assets(
|
502
530
|
self,
|
503
531
|
entity_or_id: Entity | tuple[ID, type[Entity]],
|
@@ -520,9 +548,9 @@ class Client:
|
|
520
548
|
project_context=context,
|
521
549
|
)
|
522
550
|
|
523
|
-
return
|
551
|
+
return DownloadedAssetFile(
|
524
552
|
asset=asset,
|
525
|
-
|
553
|
+
path=path,
|
526
554
|
)
|
527
555
|
|
528
556
|
context = self._optional_user_context(override_context=project_context)
|
@@ -587,7 +615,7 @@ class Client:
|
|
587
615
|
|
588
616
|
Note: This operation is not atomic. Deletion can succeed and upload can fail.
|
589
617
|
"""
|
590
|
-
self.delete_asset(
|
618
|
+
deleted_asset = self.delete_asset(
|
591
619
|
entity_id=entity_id,
|
592
620
|
entity_type=entity_type,
|
593
621
|
asset_id=asset_id,
|
@@ -601,4 +629,5 @@ class Client:
|
|
601
629
|
file_name=file_name,
|
602
630
|
file_metadata=file_metadata,
|
603
631
|
project_context=project_context,
|
632
|
+
asset_label=deleted_asset.label,
|
604
633
|
)
|
@@ -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
|
@@ -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(
|
@@ -62,7 +65,7 @@ class Asset(Identifiable):
|
|
62
65
|
dict,
|
63
66
|
Field(description="Asset json metadata."),
|
64
67
|
] = {}
|
65
|
-
label: Annotated[str
|
68
|
+
label: Annotated[str, Field(description="Asset label.")]
|
66
69
|
|
67
70
|
|
68
71
|
class LocalAssetMetadata(BaseModel):
|
@@ -93,7 +96,7 @@ class LocalAssetMetadata(BaseModel):
|
|
93
96
|
description="The metadata of the asset.",
|
94
97
|
),
|
95
98
|
] = None
|
96
|
-
label: Annotated[str
|
99
|
+
label: Annotated[str, Field(description="Optional asset label.")]
|
97
100
|
|
98
101
|
|
99
102
|
class DetailedFile(BaseModel):
|
@@ -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,44 @@
|
|
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
|
+
"label": "sonata_circuit",
|
26
|
+
},
|
27
|
+
).one()
|
28
|
+
|
29
|
+
paths = client.download_directory(
|
30
|
+
entity_id=cast(ID, model.id),
|
31
|
+
entity_type=Circuit,
|
32
|
+
asset_id=asset,
|
33
|
+
output_path=output_dir,
|
34
|
+
ignore_directory_name=True,
|
35
|
+
)
|
36
|
+
|
37
|
+
L.debug("Downloaded circuit %s paths: %s", model.id, paths)
|
38
|
+
|
39
|
+
circuit_config_path = output_dir / "circuit_config.json"
|
40
|
+
assert circuit_config_path in paths
|
41
|
+
|
42
|
+
L.info("Circuit %s staged at %s", model.id, circuit_config_path)
|
43
|
+
|
44
|
+
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
|
+
}
|