entitysdk 0.1.2__tar.gz → 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. {entitysdk-0.1.2/src/entitysdk.egg-info → entitysdk-0.2.0}/PKG-INFO +1 -1
  2. {entitysdk-0.1.2 → entitysdk-0.2.0}/src/entitysdk/client.py +32 -3
  3. entitysdk-0.2.0/src/entitysdk/common.py +45 -0
  4. entitysdk-0.2.0/src/entitysdk/config.py +37 -0
  5. {entitysdk-0.1.2 → entitysdk-0.2.0}/src/entitysdk/models/morphology.py +0 -7
  6. entitysdk-0.2.0/src/entitysdk/typedef.py +13 -0
  7. {entitysdk-0.1.2 → entitysdk-0.2.0}/src/entitysdk/util.py +9 -0
  8. {entitysdk-0.1.2 → entitysdk-0.2.0/src/entitysdk.egg-info}/PKG-INFO +1 -1
  9. {entitysdk-0.1.2 → entitysdk-0.2.0}/src/entitysdk.egg-info/SOURCES.txt +1 -0
  10. {entitysdk-0.1.2 → entitysdk-0.2.0}/tests/unit/models/data/reconstruction_morphology.json +0 -1
  11. {entitysdk-0.1.2 → entitysdk-0.2.0}/tests/unit/test_client.py +30 -0
  12. entitysdk-0.2.0/tests/unit/test_common.py +89 -0
  13. entitysdk-0.1.2/src/entitysdk/common.py +0 -12
  14. entitysdk-0.1.2/src/entitysdk/config.py +0 -21
  15. entitysdk-0.1.2/src/entitysdk/typedef.py +0 -5
  16. {entitysdk-0.1.2 → entitysdk-0.2.0}/.github/workflows/sdist.yml +0 -0
  17. {entitysdk-0.1.2 → entitysdk-0.2.0}/.github/workflows/tox.yml +0 -0
  18. {entitysdk-0.1.2 → entitysdk-0.2.0}/.gitignore +0 -0
  19. {entitysdk-0.1.2 → entitysdk-0.2.0}/CHANGELOG.rst +0 -0
  20. {entitysdk-0.1.2 → entitysdk-0.2.0}/CONTRIBUTING.md +0 -0
  21. {entitysdk-0.1.2 → entitysdk-0.2.0}/LICENSE.txt +0 -0
  22. {entitysdk-0.1.2 → entitysdk-0.2.0}/README.md +0 -0
  23. {entitysdk-0.1.2 → entitysdk-0.2.0}/examples/contribution.ipynb +0 -0
  24. {entitysdk-0.1.2 → entitysdk-0.2.0}/examples/morphology.ipynb +0 -0
  25. {entitysdk-0.1.2 → entitysdk-0.2.0}/examples/searching.ipynb +0 -0
  26. {entitysdk-0.1.2 → entitysdk-0.2.0}/pyproject.toml +0 -0
  27. {entitysdk-0.1.2 → entitysdk-0.2.0}/setup.cfg +0 -0
  28. {entitysdk-0.1.2 → entitysdk-0.2.0}/src/entitysdk/__init__.py +0 -0
  29. {entitysdk-0.1.2 → entitysdk-0.2.0}/src/entitysdk/core.py +0 -0
  30. {entitysdk-0.1.2 → entitysdk-0.2.0}/src/entitysdk/exception.py +0 -0
  31. {entitysdk-0.1.2 → entitysdk-0.2.0}/src/entitysdk/mixin.py +0 -0
  32. {entitysdk-0.1.2 → entitysdk-0.2.0}/src/entitysdk/models/__init__.py +0 -0
  33. {entitysdk-0.1.2 → entitysdk-0.2.0}/src/entitysdk/models/agent.py +0 -0
  34. {entitysdk-0.1.2 → entitysdk-0.2.0}/src/entitysdk/models/asset.py +0 -0
  35. {entitysdk-0.1.2 → entitysdk-0.2.0}/src/entitysdk/models/base.py +0 -0
  36. {entitysdk-0.1.2 → entitysdk-0.2.0}/src/entitysdk/models/contribution.py +0 -0
  37. {entitysdk-0.1.2 → entitysdk-0.2.0}/src/entitysdk/models/core.py +0 -0
  38. {entitysdk-0.1.2 → entitysdk-0.2.0}/src/entitysdk/models/entity.py +0 -0
  39. {entitysdk-0.1.2 → entitysdk-0.2.0}/src/entitysdk/models/mtype.py +0 -0
  40. {entitysdk-0.1.2 → entitysdk-0.2.0}/src/entitysdk/models/response.py +0 -0
  41. {entitysdk-0.1.2 → entitysdk-0.2.0}/src/entitysdk/result.py +0 -0
  42. {entitysdk-0.1.2 → entitysdk-0.2.0}/src/entitysdk/route.py +0 -0
  43. {entitysdk-0.1.2 → entitysdk-0.2.0}/src/entitysdk/serdes.py +0 -0
  44. {entitysdk-0.1.2 → entitysdk-0.2.0}/src/entitysdk/token_manager.py +0 -0
  45. {entitysdk-0.1.2 → entitysdk-0.2.0}/src/entitysdk.egg-info/dependency_links.txt +0 -0
  46. {entitysdk-0.1.2 → entitysdk-0.2.0}/src/entitysdk.egg-info/requires.txt +0 -0
  47. {entitysdk-0.1.2 → entitysdk-0.2.0}/src/entitysdk.egg-info/top_level.txt +0 -0
  48. {entitysdk-0.1.2 → entitysdk-0.2.0}/tests/__init__.py +0 -0
  49. {entitysdk-0.1.2 → entitysdk-0.2.0}/tests/integration/__init__.py +0 -0
  50. {entitysdk-0.1.2 → entitysdk-0.2.0}/tests/integration/conftest.py +0 -0
  51. {entitysdk-0.1.2 → entitysdk-0.2.0}/tests/integration/test_searching.py +0 -0
  52. {entitysdk-0.1.2 → entitysdk-0.2.0}/tests/unit/__init__.py +0 -0
  53. {entitysdk-0.1.2 → entitysdk-0.2.0}/tests/unit/conftest.py +0 -0
  54. {entitysdk-0.1.2 → entitysdk-0.2.0}/tests/unit/models/__init__.py +0 -0
  55. {entitysdk-0.1.2 → entitysdk-0.2.0}/tests/unit/models/data/.gitignore +0 -0
  56. {entitysdk-0.1.2 → entitysdk-0.2.0}/tests/unit/models/test_agent.py +0 -0
  57. {entitysdk-0.1.2 → entitysdk-0.2.0}/tests/unit/models/test_asset.py +0 -0
  58. {entitysdk-0.1.2 → entitysdk-0.2.0}/tests/unit/models/test_contribution.py +0 -0
  59. {entitysdk-0.1.2 → entitysdk-0.2.0}/tests/unit/models/test_morphology.py +0 -0
  60. {entitysdk-0.1.2 → entitysdk-0.2.0}/tests/unit/test_base.py +0 -0
  61. {entitysdk-0.1.2 → entitysdk-0.2.0}/tests/unit/test_config.py +0 -0
  62. {entitysdk-0.1.2 → entitysdk-0.2.0}/tests/unit/test_result.py +0 -0
  63. {entitysdk-0.1.2 → entitysdk-0.2.0}/tests/unit/test_route.py +0 -0
  64. {entitysdk-0.1.2 → entitysdk-0.2.0}/tests/unit/test_serdes.py +0 -0
  65. {entitysdk-0.1.2 → entitysdk-0.2.0}/tests/unit/test_token_manager.py +0 -0
  66. {entitysdk-0.1.2 → entitysdk-0.2.0}/tests/unit/test_util.py +0 -0
  67. {entitysdk-0.1.2 → entitysdk-0.2.0}/tests/unit/util.py +0 -0
  68. {entitysdk-0.1.2 → entitysdk-0.2.0}/tox.ini +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: entitysdk
3
- Version: 0.1.2
3
+ Version: 0.2.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>
@@ -13,7 +13,8 @@ from entitysdk.models.asset import Asset, LocalAssetMetadata
13
13
  from entitysdk.models.core import Identifiable
14
14
  from entitysdk.result import IteratorResult
15
15
  from entitysdk.token_manager import TokenManager
16
- from entitysdk.typedef import ID
16
+ from entitysdk.typedef import ID, DeploymentEnvironment
17
+ from entitysdk.util import build_api_url
17
18
 
18
19
 
19
20
  class Client:
@@ -21,10 +22,11 @@ class Client:
21
22
 
22
23
  def __init__(
23
24
  self,
24
- api_url: str,
25
+ api_url: str | None = None,
25
26
  project_context: ProjectContext | None = None,
26
27
  http_client: httpx.Client | None = None,
27
28
  token_manager: TokenManager | None = None,
29
+ environment: DeploymentEnvironment | str | None = None,
28
30
  ) -> None:
29
31
  """Initialize client.
30
32
 
@@ -33,12 +35,39 @@ class Client:
33
35
  project_context: Project context.
34
36
  http_client: Optional HTTP client to use.
35
37
  token_manager: Optional token manager to use.
38
+ environment: Deployment environent.
36
39
  """
37
- self.api_url = api_url.rstrip("/")
40
+ try:
41
+ environment = DeploymentEnvironment(environment) if environment else None
42
+ except ValueError:
43
+ raise EntitySDKError(
44
+ f"'{environment}' is not a valid DeploymentEnvironment. "
45
+ f"Choose one of: {[str(env) for env in DeploymentEnvironment]}"
46
+ ) from None
47
+
48
+ self.api_url = self._handle_api_url(
49
+ api_url=api_url,
50
+ environment=environment,
51
+ )
38
52
  self.project_context = project_context
39
53
  self._http_client = http_client or httpx.Client()
40
54
  self._token_manager = token_manager
41
55
 
56
+ @staticmethod
57
+ def _handle_api_url(api_url: str | None, environment: DeploymentEnvironment | None) -> str:
58
+ """Return or create api url."""
59
+ match (api_url, environment):
60
+ case (str(), None):
61
+ return api_url
62
+ case (None, DeploymentEnvironment()):
63
+ return build_api_url(environment=environment)
64
+ case (None, None):
65
+ raise EntitySDKError("Neither api_url nor environment have been defined.")
66
+ case (str(), DeploymentEnvironment()):
67
+ raise EntitySDKError("Either the api_url or environment must be defined, not both.")
68
+ case _:
69
+ raise EntitySDKError("Either api_url or environment is of the wrong type.")
70
+
42
71
  def _get_token(self, override_token: str | None = None) -> str:
43
72
  """Get a token either from an override or from the token manager.
44
73
 
@@ -0,0 +1,45 @@
1
+ """Common stuff."""
2
+
3
+ import re
4
+ from typing import Self
5
+ from uuid import UUID
6
+
7
+ from pydantic import BaseModel
8
+
9
+ from entitysdk.exception import EntitySDKError
10
+ from entitysdk.typedef import DeploymentEnvironment
11
+
12
+ UUID_RE = "[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}"
13
+
14
+
15
+ VLAB_URL_PATTERN = re.compile(
16
+ r"^https:\/\/(?P<env>staging|www)\.openbraininstitute\.org"
17
+ rf"\/app\/virtual-lab\/lab\/(?P<vlab>{UUID_RE})"
18
+ rf"\/project\/(?P<proj>{UUID_RE})(?:\/.*)?$"
19
+ )
20
+
21
+
22
+ class ProjectContext(BaseModel):
23
+ """Project context."""
24
+
25
+ project_id: UUID
26
+ virtual_lab_id: UUID
27
+ environment: DeploymentEnvironment | None = None
28
+
29
+ @classmethod
30
+ def from_vlab_url(cls, url: str) -> Self:
31
+ """Construct a ProjectContext from a virtual lab url."""
32
+ result = VLAB_URL_PATTERN.match(url)
33
+
34
+ if not result:
35
+ raise EntitySDKError(f"Badly formed vlab url: {url}")
36
+
37
+ env = {
38
+ "www": DeploymentEnvironment.production,
39
+ "staging": DeploymentEnvironment.staging,
40
+ }[result.group("env")]
41
+
42
+ vlab_id = UUID(result.group("vlab"))
43
+ proj_id = UUID(result.group("proj"))
44
+
45
+ return cls(project_id=proj_id, virtual_lab_id=vlab_id, environment=env)
@@ -0,0 +1,37 @@
1
+ """Configuration for this library."""
2
+
3
+ from typing import Annotated
4
+
5
+ from pydantic import Field
6
+ from pydantic_settings import BaseSettings
7
+
8
+
9
+ class Settings(BaseSettings):
10
+ """Constants for this library."""
11
+
12
+ page_size: Annotated[
13
+ int | None,
14
+ Field(
15
+ alias="ENTITYSDK_PAGE_SIZE",
16
+ description="Default pagination page size, or None to use server default.",
17
+ ),
18
+ ] = None
19
+
20
+ staging_api_url: Annotated[
21
+ str,
22
+ Field(
23
+ alias="ENTITYSDK_STAGING_API_URL",
24
+ description="Default staging entitycore API url.",
25
+ ),
26
+ ] = "https://staging.openbraininstitute.org/api/entitycore"
27
+
28
+ production_api_url: Annotated[
29
+ str,
30
+ Field(
31
+ alias="ENTITYSDK_PRODUCTION_API_URL",
32
+ description="Default production entitycore API url.",
33
+ ),
34
+ ] = "https://www.openbraininstitute.org/api/entitycore"
35
+
36
+
37
+ settings = Settings()
@@ -190,13 +190,6 @@ class ReconstructionMorphology(HasAssets, Entity):
190
190
  description="The description of the morphology.",
191
191
  ),
192
192
  ]
193
- pref_label: Annotated[
194
- str | None,
195
- Field(
196
- examples=["layer 5 Pyramidal Cell"],
197
- description="The preferred label of the morphology.",
198
- ),
199
- ] = None
200
193
  species: Annotated[
201
194
  Species,
202
195
  Field(
@@ -0,0 +1,13 @@
1
+ """Type definitions."""
2
+
3
+ import uuid
4
+ from enum import StrEnum
5
+
6
+ ID = uuid.UUID
7
+
8
+
9
+ class DeploymentEnvironment(StrEnum):
10
+ """Deployment environment."""
11
+
12
+ staging = "staging"
13
+ production = "production"
@@ -10,6 +10,7 @@ from entitysdk.common import ProjectContext
10
10
  from entitysdk.config import settings
11
11
  from entitysdk.exception import EntitySDKError
12
12
  from entitysdk.models.response import ListResponse
13
+ from entitysdk.typedef import DeploymentEnvironment
13
14
 
14
15
 
15
16
  def make_db_api_request(
@@ -126,3 +127,11 @@ def stream_paginated_request(
126
127
  if number_of_items >= limit:
127
128
  return
128
129
  page += 1
130
+
131
+
132
+ def build_api_url(environment: DeploymentEnvironment) -> str:
133
+ """Return API url for the respective deployment environment."""
134
+ return {
135
+ DeploymentEnvironment.staging: settings.staging_api_url,
136
+ DeploymentEnvironment.production: settings.production_api_url,
137
+ }[environment]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: entitysdk
3
- Version: 0.1.2
3
+ Version: 0.2.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>
@@ -46,6 +46,7 @@ tests/unit/__init__.py
46
46
  tests/unit/conftest.py
47
47
  tests/unit/test_base.py
48
48
  tests/unit/test_client.py
49
+ tests/unit/test_common.py
49
50
  tests/unit/test_config.py
50
51
  tests/unit/test_result.py
51
52
  tests/unit/test_route.py
@@ -99,7 +99,6 @@
99
99
  "children": []
100
100
  },
101
101
  "description": "Neuronal morphology of excitatory neuron cell 'mpg150211_A_idA' in Field CA1, pyramidal layer of cell type SR_PC. A neuron that releases excitatory neurotransmitters. This neuron doesn't have axon, has a basal dendrite and apical dendrite.",
102
- "pref_label": null,
103
102
  "species": {
104
103
  "id": "0895fa61-fa6d-4674-9014-7300f9edf8da",
105
104
  "update_date": "2025-03-29T16:48:36.175090Z",
@@ -1,13 +1,43 @@
1
1
  import io
2
+ import re
2
3
  import uuid
3
4
  from unittest.mock import patch
4
5
 
5
6
  import pytest
6
7
 
7
8
  from entitysdk.client import Client
9
+ from entitysdk.config import settings
8
10
  from entitysdk.exception import EntitySDKError
9
11
  from entitysdk.mixin import HasAssets
10
12
  from entitysdk.models.entity import Entity
13
+ from entitysdk.typedef import DeploymentEnvironment
14
+
15
+
16
+ def test_client_api_url():
17
+ client = Client(api_url="foo")
18
+ assert client.api_url == "foo"
19
+
20
+ client = Client(api_url=None, environment="staging")
21
+ assert client.api_url == settings.staging_api_url
22
+
23
+ client = Client(api_url=None, environment="production")
24
+ assert client.api_url == settings.production_api_url
25
+
26
+ with pytest.raises(
27
+ EntitySDKError, match="Either the api_url or environment must be defined, not both."
28
+ ):
29
+ Client(api_url="foo", environment="staging")
30
+
31
+ with pytest.raises(EntitySDKError, match="Neither api_url nor environment have been defined."):
32
+ Client()
33
+
34
+ with pytest.raises(EntitySDKError, match="Either api_url or environment is of the wrong type."):
35
+ Client(api_url=int)
36
+
37
+ str_envs = [str(env) for env in DeploymentEnvironment]
38
+ expected = f"'foo' is not a valid DeploymentEnvironment. Choose one of: {str_envs}"
39
+ with pytest.raises(EntitySDKError, match=re.escape(expected)):
40
+ Client(environment="foo")
11
41
 
12
42
 
13
43
  def test_client_get_token():
@@ -0,0 +1,89 @@
1
+ import pytest
2
+
3
+ from entitysdk import common as test_module
4
+ from entitysdk.exception import EntitySDKError
5
+ from entitysdk.typedef import DeploymentEnvironment
6
+
7
+ VLAB_ID = "ff888f05-f314-4702-8a92-b86f754270bb"
8
+ PROJ_ID = "f373e771-3a2f-4f45-ab59-0955efd7b1f4"
9
+
10
+
11
+ @pytest.mark.parametrize(
12
+ ("url", "expected"),
13
+ [
14
+ (
15
+ f"https://staging.openbraininstitute.org/app/virtual-lab/lab/{VLAB_ID}/project/{PROJ_ID}/home",
16
+ test_module.ProjectContext(
17
+ virtual_lab_id=VLAB_ID,
18
+ project_id=PROJ_ID,
19
+ environment=DeploymentEnvironment.staging,
20
+ ),
21
+ ),
22
+ (
23
+ f"https://staging.openbraininstitute.org/app/virtual-lab/lab/{VLAB_ID}/project/{PROJ_ID}",
24
+ test_module.ProjectContext(
25
+ virtual_lab_id=VLAB_ID,
26
+ project_id=PROJ_ID,
27
+ environment=DeploymentEnvironment.staging,
28
+ ),
29
+ ),
30
+ (
31
+ f"https://www.openbraininstitute.org/app/virtual-lab/lab/{VLAB_ID}/project/{PROJ_ID}/home",
32
+ test_module.ProjectContext(
33
+ virtual_lab_id=VLAB_ID,
34
+ project_id=PROJ_ID,
35
+ environment=DeploymentEnvironment.production,
36
+ ),
37
+ ),
38
+ (
39
+ f"https://www.openbraininstitute.org/app/virtual-lab/lab/{VLAB_ID}/project/{PROJ_ID}",
40
+ test_module.ProjectContext(
41
+ virtual_lab_id=VLAB_ID,
42
+ project_id=PROJ_ID,
43
+ environment=DeploymentEnvironment.production,
44
+ ),
45
+ ),
46
+ ],
47
+ )
48
+ def test_project_context__from_vlab_url(url, expected):
49
+ res = test_module.ProjectContext.from_vlab_url(url)
50
+ assert res == expected
51
+
52
+
53
+ @pytest.mark.parametrize(
54
+ ("url", "expected_error", "expected_msg"),
55
+ [
56
+ ("asdf", EntitySDKError, "Badly formed vlab url"),
57
+ ("https://", EntitySDKError, "Badly formed vlab url"),
58
+ ("https://openbraininstitute.org", EntitySDKError, "Badly formed vlab url"),
59
+ ("https://staging.openbraininstitute.org", EntitySDKError, "Badly formed vlab url"),
60
+ (
61
+ "https://staging.openbraininstitute.org/app/virtual-lab/lab/foo",
62
+ EntitySDKError,
63
+ "Badly formed vlab url",
64
+ ),
65
+ (
66
+ "https://staging.openbraininstitute.org/app/virtual-lab/lab/foo/project/bar",
67
+ EntitySDKError,
68
+ "Badly formed vlab url",
69
+ ),
70
+ (
71
+ f"https://dev.openbraininstitute.org/app/virtual-lab/lab/{VLAB_ID}/project/{PROJ_ID}",
72
+ EntitySDKError,
73
+ "Badly formed vlab url",
74
+ ),
75
+ (
76
+ f"https://dev.openbraininstitute.org/app/virtual-lab/lab/foo/project/{PROJ_ID}",
77
+ EntitySDKError,
78
+ "Badly formed vlab url",
79
+ ),
80
+ (
81
+ f"https://dev.openbraininstitute.org/app/virtual-lab/lab/{VLAB_ID}/project/bar",
82
+ EntitySDKError,
83
+ "Badly formed vlab url",
84
+ ),
85
+ ],
86
+ )
87
+ def test_project_context__from_vlab_url__raises(url, expected_error, expected_msg):
88
+ with pytest.raises(expected_error, match=expected_msg):
89
+ test_module.ProjectContext.from_vlab_url(url)
@@ -1,12 +0,0 @@
1
- """Common stuff."""
2
-
3
- from dataclasses import dataclass
4
- from uuid import UUID
5
-
6
-
7
- @dataclass
8
- class ProjectContext:
9
- """Project context."""
10
-
11
- project_id: UUID
12
- virtual_lab_id: UUID
@@ -1,21 +0,0 @@
1
- """Configuration for this library."""
2
-
3
- from typing import Annotated
4
-
5
- from pydantic import Field
6
- from pydantic_settings import BaseSettings
7
-
8
-
9
- class Settings(BaseSettings):
10
- """Constants for this library."""
11
-
12
- page_size: Annotated[
13
- int | None,
14
- Field(
15
- alias="ENTITYSDK_PAGE_SIZE",
16
- description="Default pagination page size, or None to use server default.",
17
- ),
18
- ] = None
19
-
20
-
21
- settings = Settings()
@@ -1,5 +0,0 @@
1
- """Type definitions."""
2
-
3
- import uuid
4
-
5
- ID = uuid.UUID
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