entitysdk 0.2.5__tar.gz → 0.3.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.
- {entitysdk-0.2.5/src/entitysdk.egg-info → entitysdk-0.3.0}/PKG-INFO +1 -1
- {entitysdk-0.2.5 → entitysdk-0.3.0}/src/entitysdk/client.py +29 -14
- {entitysdk-0.2.5 → entitysdk-0.3.0}/src/entitysdk/core.py +5 -2
- {entitysdk-0.2.5 → entitysdk-0.3.0}/src/entitysdk/models/__init__.py +4 -0
- {entitysdk-0.2.5 → entitysdk-0.3.0}/src/entitysdk/models/asset.py +2 -2
- {entitysdk-0.2.5 → entitysdk-0.3.0}/src/entitysdk/models/entity.py +2 -2
- {entitysdk-0.2.5 → entitysdk-0.3.0}/src/entitysdk/models/memodel.py +7 -0
- entitysdk-0.3.0/src/entitysdk/models/memodelcalibrationresult.py +42 -0
- entitysdk-0.3.0/src/entitysdk/models/validation_result.py +34 -0
- {entitysdk-0.2.5 → entitysdk-0.3.0}/src/entitysdk/route.py +6 -4
- {entitysdk-0.2.5 → entitysdk-0.3.0}/src/entitysdk/util.py +13 -0
- {entitysdk-0.2.5 → entitysdk-0.3.0/src/entitysdk.egg-info}/PKG-INFO +1 -1
- {entitysdk-0.2.5 → entitysdk-0.3.0}/src/entitysdk.egg-info/SOURCES.txt +7 -1
- {entitysdk-0.2.5 → entitysdk-0.3.0}/tests/integration/test_searching.py +4 -0
- {entitysdk-0.2.5 → entitysdk-0.3.0}/tests/unit/models/data/electrical_cell_recording.json +6 -3
- entitysdk-0.3.0/tests/unit/models/data/memodel_calibration_result.json +8 -0
- entitysdk-0.3.0/tests/unit/models/data/validation_result.json +7 -0
- entitysdk-0.3.0/tests/unit/models/test_memodel_calibration_result.py +42 -0
- entitysdk-0.3.0/tests/unit/models/test_validation_result.py +42 -0
- {entitysdk-0.2.5 → entitysdk-0.3.0}/tests/unit/test_client.py +142 -1
- {entitysdk-0.2.5 → entitysdk-0.3.0}/tests/unit/test_util.py +16 -0
- {entitysdk-0.2.5 → entitysdk-0.3.0}/.github/workflows/sdist.yml +0 -0
- {entitysdk-0.2.5 → entitysdk-0.3.0}/.github/workflows/tox.yml +0 -0
- {entitysdk-0.2.5 → entitysdk-0.3.0}/.gitignore +0 -0
- {entitysdk-0.2.5 → entitysdk-0.3.0}/CHANGELOG.rst +0 -0
- {entitysdk-0.2.5 → entitysdk-0.3.0}/CONTRIBUTING.md +0 -0
- {entitysdk-0.2.5 → entitysdk-0.3.0}/LICENSE.txt +0 -0
- {entitysdk-0.2.5 → entitysdk-0.3.0}/README.md +0 -0
- {entitysdk-0.2.5 → entitysdk-0.3.0}/examples/01_searching.ipynb +0 -0
- {entitysdk-0.2.5 → entitysdk-0.3.0}/examples/02_morphology.ipynb +0 -0
- {entitysdk-0.2.5 → entitysdk-0.3.0}/examples/03_contribution.ipynb +0 -0
- {entitysdk-0.2.5 → entitysdk-0.3.0}/pyproject.toml +0 -0
- {entitysdk-0.2.5 → entitysdk-0.3.0}/setup.cfg +0 -0
- {entitysdk-0.2.5 → entitysdk-0.3.0}/src/entitysdk/__init__.py +0 -0
- {entitysdk-0.2.5 → entitysdk-0.3.0}/src/entitysdk/common.py +0 -0
- {entitysdk-0.2.5 → entitysdk-0.3.0}/src/entitysdk/config.py +0 -0
- {entitysdk-0.2.5 → entitysdk-0.3.0}/src/entitysdk/exception.py +0 -0
- {entitysdk-0.2.5 → entitysdk-0.3.0}/src/entitysdk/mixin.py +0 -0
- {entitysdk-0.2.5 → entitysdk-0.3.0}/src/entitysdk/models/agent.py +0 -0
- {entitysdk-0.2.5 → entitysdk-0.3.0}/src/entitysdk/models/base.py +0 -0
- {entitysdk-0.2.5 → entitysdk-0.3.0}/src/entitysdk/models/brain_location.py +0 -0
- {entitysdk-0.2.5 → entitysdk-0.3.0}/src/entitysdk/models/brain_region.py +0 -0
- {entitysdk-0.2.5 → entitysdk-0.3.0}/src/entitysdk/models/contribution.py +0 -0
- {entitysdk-0.2.5 → entitysdk-0.3.0}/src/entitysdk/models/core.py +0 -0
- {entitysdk-0.2.5 → entitysdk-0.3.0}/src/entitysdk/models/electrical_cell_recording.py +0 -0
- {entitysdk-0.2.5 → entitysdk-0.3.0}/src/entitysdk/models/emodel.py +0 -0
- {entitysdk-0.2.5 → entitysdk-0.3.0}/src/entitysdk/models/etype.py +0 -0
- {entitysdk-0.2.5 → entitysdk-0.3.0}/src/entitysdk/models/ion_channel_model.py +0 -0
- {entitysdk-0.2.5 → entitysdk-0.3.0}/src/entitysdk/models/license.py +0 -0
- {entitysdk-0.2.5 → entitysdk-0.3.0}/src/entitysdk/models/morphology.py +0 -0
- {entitysdk-0.2.5 → entitysdk-0.3.0}/src/entitysdk/models/mtype.py +0 -0
- {entitysdk-0.2.5 → entitysdk-0.3.0}/src/entitysdk/models/response.py +0 -0
- {entitysdk-0.2.5 → entitysdk-0.3.0}/src/entitysdk/models/subject.py +0 -0
- {entitysdk-0.2.5 → entitysdk-0.3.0}/src/entitysdk/models/taxonomy.py +0 -0
- {entitysdk-0.2.5 → entitysdk-0.3.0}/src/entitysdk/result.py +0 -0
- {entitysdk-0.2.5 → entitysdk-0.3.0}/src/entitysdk/serdes.py +0 -0
- {entitysdk-0.2.5 → entitysdk-0.3.0}/src/entitysdk/token_manager.py +0 -0
- {entitysdk-0.2.5 → entitysdk-0.3.0}/src/entitysdk/types.py +0 -0
- {entitysdk-0.2.5 → entitysdk-0.3.0}/src/entitysdk.egg-info/dependency_links.txt +0 -0
- {entitysdk-0.2.5 → entitysdk-0.3.0}/src/entitysdk.egg-info/requires.txt +0 -0
- {entitysdk-0.2.5 → entitysdk-0.3.0}/src/entitysdk.egg-info/top_level.txt +0 -0
- {entitysdk-0.2.5 → entitysdk-0.3.0}/tests/__init__.py +0 -0
- {entitysdk-0.2.5 → entitysdk-0.3.0}/tests/integration/__init__.py +0 -0
- {entitysdk-0.2.5 → entitysdk-0.3.0}/tests/integration/conftest.py +0 -0
- {entitysdk-0.2.5 → entitysdk-0.3.0}/tests/unit/__init__.py +0 -0
- {entitysdk-0.2.5 → entitysdk-0.3.0}/tests/unit/conftest.py +0 -0
- {entitysdk-0.2.5 → entitysdk-0.3.0}/tests/unit/models/__init__.py +0 -0
- {entitysdk-0.2.5 → entitysdk-0.3.0}/tests/unit/models/data/.gitignore +0 -0
- {entitysdk-0.2.5 → entitysdk-0.3.0}/tests/unit/models/data/ion_channel_model.json +0 -0
- {entitysdk-0.2.5 → entitysdk-0.3.0}/tests/unit/models/data/reconstruction_morphology.json +0 -0
- {entitysdk-0.2.5 → entitysdk-0.3.0}/tests/unit/models/test_agent.py +0 -0
- {entitysdk-0.2.5 → entitysdk-0.3.0}/tests/unit/models/test_asset.py +0 -0
- {entitysdk-0.2.5 → entitysdk-0.3.0}/tests/unit/models/test_brain_region.py +0 -0
- {entitysdk-0.2.5 → entitysdk-0.3.0}/tests/unit/models/test_contribution.py +0 -0
- {entitysdk-0.2.5 → entitysdk-0.3.0}/tests/unit/models/test_electrical_cell_recording.py +0 -0
- {entitysdk-0.2.5 → entitysdk-0.3.0}/tests/unit/models/test_init.py +0 -0
- {entitysdk-0.2.5 → entitysdk-0.3.0}/tests/unit/models/test_ion_channel_model.py +0 -0
- {entitysdk-0.2.5 → entitysdk-0.3.0}/tests/unit/models/test_morphology.py +0 -0
- {entitysdk-0.2.5 → entitysdk-0.3.0}/tests/unit/test_base.py +0 -0
- {entitysdk-0.2.5 → entitysdk-0.3.0}/tests/unit/test_common.py +0 -0
- {entitysdk-0.2.5 → entitysdk-0.3.0}/tests/unit/test_config.py +0 -0
- {entitysdk-0.2.5 → entitysdk-0.3.0}/tests/unit/test_result.py +0 -0
- {entitysdk-0.2.5 → entitysdk-0.3.0}/tests/unit/test_route.py +0 -0
- {entitysdk-0.2.5 → entitysdk-0.3.0}/tests/unit/test_serdes.py +0 -0
- {entitysdk-0.2.5 → entitysdk-0.3.0}/tests/unit/test_token_manager.py +0 -0
- {entitysdk-0.2.5 → entitysdk-0.3.0}/tests/unit/util.py +0 -0
- {entitysdk-0.2.5 → entitysdk-0.3.0}/tox.ini +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: entitysdk
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.3.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>
|
@@ -14,7 +14,11 @@ from entitysdk.models.core import Identifiable
|
|
14
14
|
from entitysdk.result import IteratorResult
|
15
15
|
from entitysdk.token_manager import TokenManager
|
16
16
|
from entitysdk.types import ID, DeploymentEnvironment
|
17
|
-
from entitysdk.util import
|
17
|
+
from entitysdk.util import (
|
18
|
+
build_api_url,
|
19
|
+
create_intermediate_directories,
|
20
|
+
validate_filename_extension_consistency,
|
21
|
+
)
|
18
22
|
|
19
23
|
|
20
24
|
class Client:
|
@@ -314,7 +318,7 @@ class Client:
|
|
314
318
|
*,
|
315
319
|
entity_id: ID,
|
316
320
|
entity_type: type[Identifiable],
|
317
|
-
asset_id:
|
321
|
+
asset_id: ID,
|
318
322
|
project_context: ProjectContext | None = None,
|
319
323
|
token: str | None = None,
|
320
324
|
) -> bytes:
|
@@ -353,7 +357,7 @@ class Client:
|
|
353
357
|
*,
|
354
358
|
entity_id: ID,
|
355
359
|
entity_type: type[Identifiable],
|
356
|
-
asset_id:
|
360
|
+
asset_id: ID,
|
357
361
|
output_path: os.PathLike,
|
358
362
|
project_context: ProjectContext | None = None,
|
359
363
|
token: str | None = None,
|
@@ -364,26 +368,37 @@ class Client:
|
|
364
368
|
entity_id: Id of the entity.
|
365
369
|
entity_type: Type of the entity.
|
366
370
|
asset_id: Id of the asset.
|
367
|
-
output_path:
|
371
|
+
output_path: Either be a file path to write the file to or an output directory.
|
368
372
|
project_context: Optional project context.
|
369
373
|
token: Authorization access token.
|
370
374
|
"""
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
asset_id=asset_id,
|
377
|
-
)
|
378
|
-
+ "/download"
|
375
|
+
asset_endpoint = route.get_assets_endpoint(
|
376
|
+
api_url=self.api_url,
|
377
|
+
entity_type=entity_type,
|
378
|
+
entity_id=entity_id,
|
379
|
+
asset_id=asset_id,
|
379
380
|
)
|
380
381
|
token = self._get_token(override_token=token)
|
381
382
|
context = self._optional_user_context(override_context=project_context)
|
383
|
+
asset = core.get_entity(
|
384
|
+
asset_endpoint,
|
385
|
+
entity_type=Asset,
|
386
|
+
token=token,
|
387
|
+
project_context=context,
|
388
|
+
http_client=self._http_client,
|
389
|
+
)
|
390
|
+
path: Path = Path(output_path)
|
391
|
+
path = (
|
392
|
+
path / asset.path
|
393
|
+
if path.is_dir()
|
394
|
+
else validate_filename_extension_consistency(path, Path(asset.path).suffix)
|
395
|
+
)
|
396
|
+
create_intermediate_directories(path)
|
382
397
|
return core.download_asset_file(
|
383
|
-
url=
|
398
|
+
url=f"{asset_endpoint}/download",
|
384
399
|
token=token,
|
385
400
|
project_context=context,
|
386
|
-
output_path=
|
401
|
+
output_path=path,
|
387
402
|
http_client=self._http_client,
|
388
403
|
)
|
389
404
|
|
@@ -3,6 +3,7 @@
|
|
3
3
|
import io
|
4
4
|
from collections.abc import Iterator
|
5
5
|
from pathlib import Path
|
6
|
+
from typing import TypeVar
|
6
7
|
|
7
8
|
import httpx
|
8
9
|
|
@@ -13,6 +14,8 @@ from entitysdk.models.core import Identifiable
|
|
13
14
|
from entitysdk.result import IteratorResult
|
14
15
|
from entitysdk.util import make_db_api_request, stream_paginated_request
|
15
16
|
|
17
|
+
TIdentifiable = TypeVar("TIdentifiable", bound=Identifiable)
|
18
|
+
|
16
19
|
|
17
20
|
def search_entities(
|
18
21
|
url: str,
|
@@ -55,11 +58,11 @@ def search_entities(
|
|
55
58
|
def get_entity(
|
56
59
|
url: str,
|
57
60
|
*,
|
58
|
-
entity_type: type[
|
61
|
+
entity_type: type[TIdentifiable],
|
59
62
|
project_context: ProjectContext | None = None,
|
60
63
|
token: str,
|
61
64
|
http_client: httpx.Client | None = None,
|
62
|
-
) ->
|
65
|
+
) -> TIdentifiable:
|
63
66
|
"""Instantiate entity with model ``entity_type`` from resource id."""
|
64
67
|
response = make_db_api_request(
|
65
68
|
url=url,
|
@@ -10,9 +10,11 @@ from entitysdk.models.emodel import EModel
|
|
10
10
|
from entitysdk.models.ion_channel_model import IonChannelModel, NeuronBlock, UseIon
|
11
11
|
from entitysdk.models.license import License
|
12
12
|
from entitysdk.models.memodel import MEModel
|
13
|
+
from entitysdk.models.memodelcalibrationresult import MEModelCalibrationResult
|
13
14
|
from entitysdk.models.morphology import ReconstructionMorphology
|
14
15
|
from entitysdk.models.mtype import MTypeClass
|
15
16
|
from entitysdk.models.taxonomy import Species, Strain, Taxonomy
|
17
|
+
from entitysdk.models.validation_result import ValidationResult
|
16
18
|
|
17
19
|
__all__ = [
|
18
20
|
"Asset",
|
@@ -24,6 +26,7 @@ __all__ = [
|
|
24
26
|
"IonChannelModel",
|
25
27
|
"License",
|
26
28
|
"MEModel",
|
29
|
+
"MEModelCalibrationResult",
|
27
30
|
"MTypeClass",
|
28
31
|
"NeuronBlock",
|
29
32
|
"Organization",
|
@@ -34,4 +37,5 @@ __all__ = [
|
|
34
37
|
"Strain",
|
35
38
|
"Taxonomy",
|
36
39
|
"UseIon",
|
40
|
+
"ValidationResult",
|
37
41
|
]
|
@@ -13,13 +13,13 @@ class Asset(Identifiable):
|
|
13
13
|
path: Annotated[
|
14
14
|
str,
|
15
15
|
Field(
|
16
|
-
description="The path of the asset.",
|
16
|
+
description="The relative path of the asset.",
|
17
17
|
),
|
18
18
|
]
|
19
19
|
full_path: Annotated[
|
20
20
|
str,
|
21
21
|
Field(
|
22
|
-
description="The full path of the asset.",
|
22
|
+
description="The full s3 path of the asset.",
|
23
23
|
),
|
24
24
|
]
|
25
25
|
is_directory: Annotated[
|
@@ -46,12 +46,12 @@ class Entity(Identifiable, HasAssets):
|
|
46
46
|
),
|
47
47
|
] = None
|
48
48
|
authorized_public: Annotated[
|
49
|
-
bool
|
49
|
+
bool,
|
50
50
|
Field(
|
51
51
|
examples=[True, False],
|
52
52
|
description="Whether the resource is authorized to be public.",
|
53
53
|
),
|
54
|
-
] =
|
54
|
+
] = False
|
55
55
|
authorized_project_id: Annotated[
|
56
56
|
ID | None,
|
57
57
|
Field(
|
@@ -8,6 +8,7 @@ from entitysdk.models.contribution import Contribution
|
|
8
8
|
from entitysdk.models.emodel import EModel
|
9
9
|
from entitysdk.models.entity import Entity
|
10
10
|
from entitysdk.models.etype import ETypeClass
|
11
|
+
from entitysdk.models.memodelcalibrationresult import MEModelCalibrationResult
|
11
12
|
from entitysdk.models.morphology import (
|
12
13
|
BrainRegion,
|
13
14
|
License,
|
@@ -93,4 +94,10 @@ class MEModel(Entity):
|
|
93
94
|
description="The mtype classes of the memodel.",
|
94
95
|
),
|
95
96
|
] = None
|
97
|
+
calibration_result: Annotated[
|
98
|
+
MEModelCalibrationResult | None,
|
99
|
+
Field(
|
100
|
+
description="The calibration result of the memodel.",
|
101
|
+
),
|
102
|
+
] = None
|
96
103
|
legacy_id: list[str] | None = None
|
@@ -0,0 +1,42 @@
|
|
1
|
+
"""ME-Model Calibration Result."""
|
2
|
+
|
3
|
+
from typing import Annotated
|
4
|
+
|
5
|
+
from pydantic import Field
|
6
|
+
|
7
|
+
from entitysdk.models.entity import Entity
|
8
|
+
from entitysdk.types import ID
|
9
|
+
|
10
|
+
|
11
|
+
class MEModelCalibrationResult(Entity):
|
12
|
+
"""ME-Model calibration result."""
|
13
|
+
|
14
|
+
holding_current: Annotated[
|
15
|
+
float,
|
16
|
+
Field(
|
17
|
+
description="The holding current to apply to the simulatable neuron, in nA.",
|
18
|
+
example=-0.016,
|
19
|
+
),
|
20
|
+
]
|
21
|
+
threshold_current: Annotated[
|
22
|
+
float,
|
23
|
+
Field(
|
24
|
+
description="The minimal amount of current needed to make "
|
25
|
+
"the simulatable neuron spike, in nA.",
|
26
|
+
example=0.1,
|
27
|
+
),
|
28
|
+
]
|
29
|
+
rin: Annotated[
|
30
|
+
float | None,
|
31
|
+
Field(
|
32
|
+
description="The input resistance of the simulatable neuron, in MOhm.",
|
33
|
+
example=0.1,
|
34
|
+
),
|
35
|
+
] = None
|
36
|
+
calibrated_entity_id: Annotated[
|
37
|
+
ID,
|
38
|
+
Field(
|
39
|
+
description="ID of the calibrated entity.",
|
40
|
+
example="85663316-a7ff-4107-9eb9-236de8868c5c",
|
41
|
+
),
|
42
|
+
]
|
@@ -0,0 +1,34 @@
|
|
1
|
+
"""Validation result."""
|
2
|
+
|
3
|
+
from typing import Annotated
|
4
|
+
|
5
|
+
from pydantic import Field
|
6
|
+
|
7
|
+
from entitysdk.models.entity import Entity
|
8
|
+
from entitysdk.types import ID
|
9
|
+
|
10
|
+
|
11
|
+
class ValidationResult(Entity):
|
12
|
+
"""Validation result."""
|
13
|
+
|
14
|
+
passed: Annotated[
|
15
|
+
bool,
|
16
|
+
Field(
|
17
|
+
description="True if the validation passed, False otherwise.",
|
18
|
+
example=True,
|
19
|
+
),
|
20
|
+
]
|
21
|
+
name: Annotated[
|
22
|
+
str,
|
23
|
+
Field(
|
24
|
+
description="Name of the validation.",
|
25
|
+
example="Neuron spiking validation",
|
26
|
+
),
|
27
|
+
]
|
28
|
+
validated_entity_id: Annotated[
|
29
|
+
ID,
|
30
|
+
Field(
|
31
|
+
description="ID of the validated entity.",
|
32
|
+
example="85663316-a7ff-4107-9eb9-236de8868c5c",
|
33
|
+
),
|
34
|
+
]
|
@@ -11,8 +11,13 @@ _ROUTES = {
|
|
11
11
|
"BrainRegion": "brain-region",
|
12
12
|
"Contribution": "contribution",
|
13
13
|
"ElectricalCellRecording": "electrical-cell-recording",
|
14
|
+
"EModel": "emodel",
|
14
15
|
"Entity": "entity",
|
16
|
+
"Ion": "ion",
|
17
|
+
"IonChannelModel": "ion-channel-model",
|
15
18
|
"License": "license",
|
19
|
+
"MEModel": "memodel",
|
20
|
+
"MEModelCalibrationResult": "memodel-calibration-result",
|
16
21
|
"MTypeClass": "mtype",
|
17
22
|
"Organization": "organization",
|
18
23
|
"Person": "person",
|
@@ -21,10 +26,7 @@ _ROUTES = {
|
|
21
26
|
"Species": "species",
|
22
27
|
"Strain": "strain",
|
23
28
|
"Taxonomy": "taxonomy",
|
24
|
-
"
|
25
|
-
"Ion": "ion",
|
26
|
-
"EModel": "emodel",
|
27
|
-
"MEModel": "memodel",
|
29
|
+
"ValidationResult": "validation-result",
|
28
30
|
}
|
29
31
|
|
30
32
|
|
@@ -3,6 +3,7 @@
|
|
3
3
|
import sys
|
4
4
|
from collections.abc import Iterator
|
5
5
|
from json import dumps
|
6
|
+
from pathlib import Path
|
6
7
|
|
7
8
|
import httpx
|
8
9
|
|
@@ -135,3 +136,15 @@ def build_api_url(environment: DeploymentEnvironment) -> str:
|
|
135
136
|
DeploymentEnvironment.staging: settings.staging_api_url,
|
136
137
|
DeploymentEnvironment.production: settings.production_api_url,
|
137
138
|
}[environment]
|
139
|
+
|
140
|
+
|
141
|
+
def validate_filename_extension_consistency(path: Path, expected_extension: str) -> Path:
|
142
|
+
"""Validate file path extension against expected extension."""
|
143
|
+
if path.suffix.lower() == expected_extension.lower():
|
144
|
+
return path
|
145
|
+
raise EntitySDKError(f"File path {path} does not have expected extension {expected_extension}.")
|
146
|
+
|
147
|
+
|
148
|
+
def create_intermediate_directories(path: Path) -> None:
|
149
|
+
"""Create intermediate directories in a path."""
|
150
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: entitysdk
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.3.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>
|
@@ -43,11 +43,13 @@ src/entitysdk/models/etype.py
|
|
43
43
|
src/entitysdk/models/ion_channel_model.py
|
44
44
|
src/entitysdk/models/license.py
|
45
45
|
src/entitysdk/models/memodel.py
|
46
|
+
src/entitysdk/models/memodelcalibrationresult.py
|
46
47
|
src/entitysdk/models/morphology.py
|
47
48
|
src/entitysdk/models/mtype.py
|
48
49
|
src/entitysdk/models/response.py
|
49
50
|
src/entitysdk/models/subject.py
|
50
51
|
src/entitysdk/models/taxonomy.py
|
52
|
+
src/entitysdk/models/validation_result.py
|
51
53
|
tests/__init__.py
|
52
54
|
tests/integration/__init__.py
|
53
55
|
tests/integration/conftest.py
|
@@ -72,8 +74,12 @@ tests/unit/models/test_contribution.py
|
|
72
74
|
tests/unit/models/test_electrical_cell_recording.py
|
73
75
|
tests/unit/models/test_init.py
|
74
76
|
tests/unit/models/test_ion_channel_model.py
|
77
|
+
tests/unit/models/test_memodel_calibration_result.py
|
75
78
|
tests/unit/models/test_morphology.py
|
79
|
+
tests/unit/models/test_validation_result.py
|
76
80
|
tests/unit/models/data/.gitignore
|
77
81
|
tests/unit/models/data/electrical_cell_recording.json
|
78
82
|
tests/unit/models/data/ion_channel_model.json
|
79
|
-
tests/unit/models/data/
|
83
|
+
tests/unit/models/data/memodel_calibration_result.json
|
84
|
+
tests/unit/models/data/reconstruction_morphology.json
|
85
|
+
tests/unit/models/data/validation_result.json
|
@@ -7,6 +7,7 @@ from entitysdk.models import (
|
|
7
7
|
IonChannelModel,
|
8
8
|
License,
|
9
9
|
MEModel,
|
10
|
+
MEModelCalibrationResult,
|
10
11
|
MTypeClass,
|
11
12
|
Organization,
|
12
13
|
Person,
|
@@ -14,6 +15,7 @@ from entitysdk.models import (
|
|
14
15
|
Role,
|
15
16
|
Species,
|
16
17
|
Strain,
|
18
|
+
ValidationResult,
|
17
19
|
)
|
18
20
|
|
19
21
|
|
@@ -33,6 +35,8 @@ from entitysdk.models import (
|
|
33
35
|
EModel,
|
34
36
|
MEModel,
|
35
37
|
ElectricalCellRecording,
|
38
|
+
ValidationResult,
|
39
|
+
MEModelCalibrationResult,
|
36
40
|
],
|
37
41
|
)
|
38
42
|
def test_is_searchable(entity_type, client):
|
@@ -97,7 +97,8 @@
|
|
97
97
|
"name": "GenericStep",
|
98
98
|
"description": "",
|
99
99
|
"injection_type": "current_clamp",
|
100
|
-
"shape": "step"
|
100
|
+
"shape": "step",
|
101
|
+
"authorized_public": false
|
101
102
|
},
|
102
103
|
{
|
103
104
|
"type": "electrical_recording_stimulus",
|
@@ -107,7 +108,8 @@
|
|
107
108
|
"name": "GenericStep",
|
108
109
|
"description": "",
|
109
110
|
"injection_type": "current_clamp",
|
110
|
-
"shape": "step"
|
111
|
+
"shape": "step",
|
112
|
+
"authorized_public": false
|
111
113
|
},
|
112
114
|
{
|
113
115
|
"type": "electrical_recording_stimulus",
|
@@ -117,7 +119,8 @@
|
|
117
119
|
"name": "GenericStep",
|
118
120
|
"description": "",
|
119
121
|
"injection_type": "current_clamp",
|
120
|
-
"shape": "step"
|
122
|
+
"shape": "step",
|
123
|
+
"authorized_public": false
|
121
124
|
}
|
122
125
|
]
|
123
126
|
}
|
@@ -0,0 +1,42 @@
|
|
1
|
+
import json
|
2
|
+
from pathlib import Path
|
3
|
+
|
4
|
+
import pytest
|
5
|
+
|
6
|
+
from entitysdk.models import MEModelCalibrationResult
|
7
|
+
|
8
|
+
from ..util import MOCK_UUID
|
9
|
+
|
10
|
+
DATA_DIR = Path(__file__).parent / "data"
|
11
|
+
|
12
|
+
Model = MEModelCalibrationResult
|
13
|
+
|
14
|
+
|
15
|
+
@pytest.fixture
|
16
|
+
def json_data():
|
17
|
+
return json.loads(Path(DATA_DIR / "memodel_calibration_result.json").read_bytes())
|
18
|
+
|
19
|
+
|
20
|
+
@pytest.fixture
|
21
|
+
def model(json_data):
|
22
|
+
return Model.model_validate(json_data)
|
23
|
+
|
24
|
+
|
25
|
+
def test_read(client, httpx_mock, auth_token, json_data):
|
26
|
+
httpx_mock.add_response(method="GET", json=json_data)
|
27
|
+
entity = client.get_entity(
|
28
|
+
entity_id=MOCK_UUID,
|
29
|
+
entity_type=Model,
|
30
|
+
token=auth_token,
|
31
|
+
with_assets=False,
|
32
|
+
)
|
33
|
+
assert entity.model_dump(mode="json", exclude_none=True) == json_data
|
34
|
+
|
35
|
+
|
36
|
+
def test_register(client, httpx_mock, auth_token, model, json_data):
|
37
|
+
httpx_mock.add_response(
|
38
|
+
method="POST", json=model.model_dump(mode="json") | {"id": str(MOCK_UUID)}
|
39
|
+
)
|
40
|
+
registered = client.register_entity(entity=model, token=auth_token)
|
41
|
+
expected_json = json_data | {"id": str(MOCK_UUID)}
|
42
|
+
assert registered.model_dump(mode="json", exclude_none=True) == expected_json
|
@@ -0,0 +1,42 @@
|
|
1
|
+
import json
|
2
|
+
from pathlib import Path
|
3
|
+
|
4
|
+
import pytest
|
5
|
+
|
6
|
+
from entitysdk.models import ValidationResult
|
7
|
+
|
8
|
+
from ..util import MOCK_UUID
|
9
|
+
|
10
|
+
DATA_DIR = Path(__file__).parent / "data"
|
11
|
+
|
12
|
+
Model = ValidationResult
|
13
|
+
|
14
|
+
|
15
|
+
@pytest.fixture
|
16
|
+
def json_data():
|
17
|
+
return json.loads(Path(DATA_DIR / "validation_result.json").read_bytes())
|
18
|
+
|
19
|
+
|
20
|
+
@pytest.fixture
|
21
|
+
def model(json_data):
|
22
|
+
return Model.model_validate(json_data)
|
23
|
+
|
24
|
+
|
25
|
+
def test_read(client, httpx_mock, auth_token, json_data):
|
26
|
+
httpx_mock.add_response(method="GET", json=json_data)
|
27
|
+
entity = client.get_entity(
|
28
|
+
entity_id=MOCK_UUID,
|
29
|
+
entity_type=Model,
|
30
|
+
token=auth_token,
|
31
|
+
with_assets=False,
|
32
|
+
)
|
33
|
+
assert entity.model_dump(mode="json", exclude_none=True) == json_data
|
34
|
+
|
35
|
+
|
36
|
+
def test_register(client, httpx_mock, auth_token, model, json_data):
|
37
|
+
httpx_mock.add_response(
|
38
|
+
method="POST", json=model.model_dump(mode="json") | {"id": str(MOCK_UUID)}
|
39
|
+
)
|
40
|
+
registered = client.register_entity(entity=model, token=auth_token)
|
41
|
+
expected_json = json_data | {"id": str(MOCK_UUID)}
|
42
|
+
assert registered.model_dump(mode="json", exclude_none=True) == expected_json
|
@@ -1,6 +1,7 @@
|
|
1
1
|
import io
|
2
2
|
import re
|
3
3
|
import uuid
|
4
|
+
from pathlib import Path
|
4
5
|
from unittest.mock import patch
|
5
6
|
|
6
7
|
import pytest
|
@@ -237,7 +238,7 @@ def test_client_download_content(
|
|
237
238
|
assert res == b"foo"
|
238
239
|
|
239
240
|
|
240
|
-
def
|
241
|
+
def test_client_download_file__output_file(
|
241
242
|
tmp_path,
|
242
243
|
client,
|
243
244
|
httpx_mock,
|
@@ -249,6 +250,12 @@ def test_client_download_file(
|
|
249
250
|
entity_id = uuid.uuid4()
|
250
251
|
asset_id = uuid.uuid4()
|
251
252
|
|
253
|
+
httpx_mock.add_response(
|
254
|
+
method="GET",
|
255
|
+
url=f"{api_url}/entity/{entity_id}/assets/{asset_id}",
|
256
|
+
match_headers=request_headers,
|
257
|
+
json=_mock_asset_response(asset_id) | {"path": "foo.h5"},
|
258
|
+
)
|
252
259
|
httpx_mock.add_response(
|
253
260
|
method="GET",
|
254
261
|
url=f"{api_url}/entity/{entity_id}/assets/{asset_id}/download",
|
@@ -268,6 +275,140 @@ def test_client_download_file(
|
|
268
275
|
assert output_path.read_bytes() == b"foo"
|
269
276
|
|
270
277
|
|
278
|
+
def test_client_download_file__output_file__inconsistent_ext(
|
279
|
+
tmp_path,
|
280
|
+
client,
|
281
|
+
httpx_mock,
|
282
|
+
api_url,
|
283
|
+
project_context,
|
284
|
+
auth_token,
|
285
|
+
request_headers,
|
286
|
+
):
|
287
|
+
"""User must provide a path extension that is consitent with the asset path."""
|
288
|
+
|
289
|
+
entity_id = uuid.uuid4()
|
290
|
+
asset_id = uuid.uuid4()
|
291
|
+
|
292
|
+
httpx_mock.add_response(
|
293
|
+
method="GET",
|
294
|
+
url=f"{api_url}/entity/{entity_id}/assets/{asset_id}",
|
295
|
+
match_headers=request_headers,
|
296
|
+
json=_mock_asset_response(asset_id) | {"path": "foo.swc"},
|
297
|
+
)
|
298
|
+
output_path = tmp_path / "foo.h5"
|
299
|
+
|
300
|
+
with pytest.raises(
|
301
|
+
EntitySDKError, match=f"File path {output_path} does not have expected extension .swc."
|
302
|
+
):
|
303
|
+
client.download_file(
|
304
|
+
entity_id=entity_id,
|
305
|
+
entity_type=Entity,
|
306
|
+
asset_id=asset_id,
|
307
|
+
output_path=output_path,
|
308
|
+
token=auth_token,
|
309
|
+
)
|
310
|
+
|
311
|
+
|
312
|
+
def test_client_download_file__output_file__user_subdirectory_path(
|
313
|
+
tmp_path,
|
314
|
+
client,
|
315
|
+
httpx_mock,
|
316
|
+
api_url,
|
317
|
+
project_context,
|
318
|
+
auth_token,
|
319
|
+
request_headers,
|
320
|
+
):
|
321
|
+
"""User provides a nested output path that overrides the asset path."""
|
322
|
+
|
323
|
+
entity_id = uuid.uuid4()
|
324
|
+
asset_id = uuid.uuid4()
|
325
|
+
|
326
|
+
httpx_mock.add_response(
|
327
|
+
method="GET",
|
328
|
+
url=f"{api_url}/entity/{entity_id}/assets/{asset_id}",
|
329
|
+
match_headers=request_headers,
|
330
|
+
json=_mock_asset_response(asset_id) | {"path": "foo.h5"},
|
331
|
+
)
|
332
|
+
|
333
|
+
httpx_mock.add_response(
|
334
|
+
method="GET",
|
335
|
+
url=f"{api_url}/entity/{entity_id}/assets/{asset_id}/download",
|
336
|
+
match_headers=request_headers,
|
337
|
+
content=b"foo",
|
338
|
+
)
|
339
|
+
|
340
|
+
output_path = tmp_path / "foo" / "bar" / "bar.h5"
|
341
|
+
|
342
|
+
client.download_file(
|
343
|
+
entity_id=entity_id,
|
344
|
+
entity_type=Entity,
|
345
|
+
asset_id=asset_id,
|
346
|
+
output_path=output_path,
|
347
|
+
token=auth_token,
|
348
|
+
)
|
349
|
+
assert output_path.read_bytes() == b"foo"
|
350
|
+
|
351
|
+
|
352
|
+
def test_client_download_file__asset_subdirectory_paths(
|
353
|
+
tmp_path,
|
354
|
+
client,
|
355
|
+
httpx_mock,
|
356
|
+
api_url,
|
357
|
+
project_context,
|
358
|
+
auth_token,
|
359
|
+
request_headers,
|
360
|
+
):
|
361
|
+
"""User provides directory, relative file paths from assets are written to it."""
|
362
|
+
|
363
|
+
entity_id = uuid.uuid4()
|
364
|
+
asset1_id = uuid.uuid4()
|
365
|
+
asset2_id = uuid.uuid4()
|
366
|
+
|
367
|
+
httpx_mock.add_response(
|
368
|
+
method="GET",
|
369
|
+
url=f"{api_url}/entity/{entity_id}/assets/{asset1_id}",
|
370
|
+
match_headers=request_headers,
|
371
|
+
json=_mock_asset_response(asset1_id) | {"path": "foo/bar/foo.h5"},
|
372
|
+
)
|
373
|
+
httpx_mock.add_response(
|
374
|
+
method="GET",
|
375
|
+
url=f"{api_url}/entity/{entity_id}/assets/{asset1_id}/download",
|
376
|
+
match_headers=request_headers,
|
377
|
+
content=b"foo",
|
378
|
+
)
|
379
|
+
httpx_mock.add_response(
|
380
|
+
method="GET",
|
381
|
+
url=f"{api_url}/entity/{entity_id}/assets/{asset2_id}",
|
382
|
+
match_headers=request_headers,
|
383
|
+
json=_mock_asset_response(asset2_id) | {"path": "foo/bar/bar.swc"},
|
384
|
+
)
|
385
|
+
httpx_mock.add_response(
|
386
|
+
method="GET",
|
387
|
+
url=f"{api_url}/entity/{entity_id}/assets/{asset2_id}/download",
|
388
|
+
match_headers=request_headers,
|
389
|
+
content=b"bar",
|
390
|
+
)
|
391
|
+
output_path = tmp_path
|
392
|
+
|
393
|
+
client.download_file(
|
394
|
+
entity_id=entity_id,
|
395
|
+
entity_type=Entity,
|
396
|
+
asset_id=asset1_id,
|
397
|
+
output_path=output_path,
|
398
|
+
token=auth_token,
|
399
|
+
)
|
400
|
+
client.download_file(
|
401
|
+
entity_id=entity_id,
|
402
|
+
entity_type=Entity,
|
403
|
+
asset_id=asset2_id,
|
404
|
+
output_path=output_path,
|
405
|
+
token=auth_token,
|
406
|
+
)
|
407
|
+
|
408
|
+
assert Path(output_path, "foo/bar/foo.h5").read_bytes() == b"foo"
|
409
|
+
assert Path(output_path, "foo/bar/bar.swc").read_bytes() == b"bar"
|
410
|
+
|
411
|
+
|
271
412
|
@patch("entitysdk.route.get_route_name")
|
272
413
|
def test_client_get(
|
273
414
|
mock_route,
|
@@ -430,3 +430,19 @@ def test_stream_paginated_request_with_unexpected_page_size(
|
|
430
430
|
match="Unexpected response: payload.pagination.page_size=2 but it should be 123",
|
431
431
|
):
|
432
432
|
next(it)
|
433
|
+
|
434
|
+
|
435
|
+
def test_validate_filename_extension_consistency(tmp_path):
|
436
|
+
assert test_module.validate_filename_extension_consistency(tmp_path / "foo.txt", ".txt")
|
437
|
+
assert test_module.validate_filename_extension_consistency(tmp_path / "foo.txt", ".TXT")
|
438
|
+
|
439
|
+
|
440
|
+
def test_create_intermediate_directories(tmp_path):
|
441
|
+
path = tmp_path / "foo" / "bar" / "foo.txt"
|
442
|
+
|
443
|
+
assert not path.parent.is_dir()
|
444
|
+
|
445
|
+
test_module.create_intermediate_directories(path)
|
446
|
+
|
447
|
+
assert path.parent.is_dir()
|
448
|
+
assert path.parent.parent.is_dir()
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|