cognite-neat 1.0.24__py3-none-any.whl → 1.0.26__py3-none-any.whl
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.
- cognite/neat/__init__.py +1 -1
- cognite/neat/_client/api.py +173 -3
- cognite/neat/_client/containers_api.py +29 -90
- cognite/neat/_client/data_model_api.py +46 -74
- cognite/neat/_client/filters.py +40 -0
- cognite/neat/_client/init/credentials.py +0 -10
- cognite/neat/_client/init/env_vars.py +34 -22
- cognite/neat/_client/init/interactive.py +133 -0
- cognite/neat/_client/init/main.py +25 -15
- cognite/neat/_client/spaces_api.py +32 -67
- cognite/neat/_client/statistics_api.py +9 -4
- cognite/neat/_client/views_api.py +39 -89
- cognite/neat/_data_model/models/dms/_base.py +3 -0
- cognite/neat/_session/_session.py +13 -1
- cognite/neat/_utils/http_client/_client.py +3 -1
- cognite/neat/_utils/http_client/_data_classes.py +3 -3
- cognite/neat/_utils/repo.py +6 -4
- cognite/neat/_version.py +1 -1
- {cognite_neat-1.0.24.dist-info → cognite_neat-1.0.26.dist-info}/METADATA +1 -1
- {cognite_neat-1.0.24.dist-info → cognite_neat-1.0.26.dist-info}/RECORD +21 -19
- {cognite_neat-1.0.24.dist-info → cognite_neat-1.0.26.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import importlib.util
|
|
2
|
+
from abc import ABC, abstractmethod
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from .env_vars import AVAILABLE_LOGIN_FLOWS, AVAILABLE_PROVIDERS, LoginFlow, Provider, create_env_file_content
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class InteractiveFlow(ABC):
|
|
10
|
+
def __init__(self, env_path: Path):
|
|
11
|
+
self.env_path = env_path
|
|
12
|
+
|
|
13
|
+
@abstractmethod
|
|
14
|
+
def run(self) -> None: ...
|
|
15
|
+
|
|
16
|
+
def create_env_file(self, provider: Provider, login_flow: LoginFlow) -> None:
|
|
17
|
+
env_content = create_env_file_content(provider, login_flow)
|
|
18
|
+
self.env_path.write_text(env_content, encoding="utf-8", newline="\n")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def get_interactive_flow(env_file_path: Path) -> InteractiveFlow:
|
|
22
|
+
try:
|
|
23
|
+
importlib.util.find_spec("IPython")
|
|
24
|
+
importlib.util.find_spec("ipywidgets")
|
|
25
|
+
if not _is_in_notebook():
|
|
26
|
+
return NoDependencyFlow(env_file_path)
|
|
27
|
+
return NotebookFlow(env_file_path)
|
|
28
|
+
except ImportError:
|
|
29
|
+
return NoDependencyFlow(env_file_path)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _is_in_notebook() -> bool:
|
|
33
|
+
try:
|
|
34
|
+
from IPython import get_ipython
|
|
35
|
+
|
|
36
|
+
if "IPKernelApp" not in get_ipython().config:
|
|
37
|
+
return False
|
|
38
|
+
except ImportError:
|
|
39
|
+
return False
|
|
40
|
+
except AttributeError:
|
|
41
|
+
return False
|
|
42
|
+
return True
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class NoDependencyFlow(InteractiveFlow):
|
|
46
|
+
def run(self) -> None:
|
|
47
|
+
if not self.should_create_env_file():
|
|
48
|
+
return None
|
|
49
|
+
provider = self.provider()
|
|
50
|
+
login_flow: LoginFlow
|
|
51
|
+
if provider != "cdf":
|
|
52
|
+
login_flow = self.login_flow()
|
|
53
|
+
else:
|
|
54
|
+
login_flow = "client_credentials"
|
|
55
|
+
self.create_env_file(provider, login_flow)
|
|
56
|
+
print(f"Created environment file at {self.env_path!r}.")
|
|
57
|
+
return None
|
|
58
|
+
|
|
59
|
+
def should_create_env_file(self) -> bool:
|
|
60
|
+
env_file_name = self.env_path.name
|
|
61
|
+
answer = input(
|
|
62
|
+
f"Would you like to create a new {env_file_name!r} file with the required environment variables? [y/N]: "
|
|
63
|
+
)
|
|
64
|
+
return answer.strip().lower() == "y"
|
|
65
|
+
|
|
66
|
+
@classmethod
|
|
67
|
+
def provider(cls) -> Provider:
|
|
68
|
+
index = cls._prompt_choice(AVAILABLE_PROVIDERS, "Select provider:")
|
|
69
|
+
return AVAILABLE_PROVIDERS[index]
|
|
70
|
+
|
|
71
|
+
@classmethod
|
|
72
|
+
def login_flow(cls) -> LoginFlow:
|
|
73
|
+
index = cls._prompt_choice(AVAILABLE_LOGIN_FLOWS, "Select login flow:")
|
|
74
|
+
return AVAILABLE_LOGIN_FLOWS[index]
|
|
75
|
+
|
|
76
|
+
@classmethod
|
|
77
|
+
def _prompt_choice(cls, options: tuple[str, ...], prompt: str) -> int:
|
|
78
|
+
for i, option in enumerate(options, start=1):
|
|
79
|
+
print(f"{i}. {option}")
|
|
80
|
+
question = f"{prompt} [1-{len(options)}]: "
|
|
81
|
+
while True:
|
|
82
|
+
answer = input(question)
|
|
83
|
+
if answer.isdigit():
|
|
84
|
+
index = int(answer)
|
|
85
|
+
if 1 <= index <= len(options):
|
|
86
|
+
return index - 1
|
|
87
|
+
print(f"Invalid input. Please enter a number between 1 and {len(options)}.")
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class NotebookFlow(InteractiveFlow):
|
|
91
|
+
def __init__(self, env_path: Path):
|
|
92
|
+
super().__init__(env_path)
|
|
93
|
+
import ipywidgets as widgets # type: ignore[import-untyped]
|
|
94
|
+
from IPython.display import display
|
|
95
|
+
|
|
96
|
+
self._widgets = widgets
|
|
97
|
+
self._display = display
|
|
98
|
+
|
|
99
|
+
def run(self) -> None:
|
|
100
|
+
dropdown_providers = self._widgets.Dropdown(
|
|
101
|
+
options=list(AVAILABLE_PROVIDERS),
|
|
102
|
+
value=AVAILABLE_PROVIDERS[0],
|
|
103
|
+
description="Provider:",
|
|
104
|
+
)
|
|
105
|
+
dropdown_login_flows = self._widgets.Dropdown(
|
|
106
|
+
options=list(AVAILABLE_LOGIN_FLOWS),
|
|
107
|
+
value=AVAILABLE_LOGIN_FLOWS[0],
|
|
108
|
+
description="Login Flow:",
|
|
109
|
+
)
|
|
110
|
+
confirm_button = self._widgets.Button(description="Create template .env file", button_style="primary")
|
|
111
|
+
dropdowns = self._widgets.HBox([dropdown_providers, dropdown_login_flows])
|
|
112
|
+
output = self._widgets.Output()
|
|
113
|
+
container = self._widgets.VBox([dropdowns, confirm_button, output])
|
|
114
|
+
|
|
115
|
+
self._display(container)
|
|
116
|
+
|
|
117
|
+
def on_confirm_clicked(b: Any) -> None:
|
|
118
|
+
with output:
|
|
119
|
+
provider = dropdown_providers.value
|
|
120
|
+
login_flow = dropdown_login_flows.value
|
|
121
|
+
if provider == "cdf" and login_flow != "client_credentials":
|
|
122
|
+
print(
|
|
123
|
+
"Warning: 'cdf' provider only supports 'client_credentials' login flow. Overriding selection."
|
|
124
|
+
)
|
|
125
|
+
login_flow = "client_credentials"
|
|
126
|
+
is_existing = self.env_path.exists()
|
|
127
|
+
self.create_env_file(provider, login_flow)
|
|
128
|
+
if is_existing:
|
|
129
|
+
print(f"Overwrote existing environment file at {self.env_path!r}.")
|
|
130
|
+
else:
|
|
131
|
+
print(f"Created environment file at {self.env_path!r}.")
|
|
132
|
+
|
|
133
|
+
confirm_button.on_click(on_confirm_clicked)
|
|
@@ -1,15 +1,19 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
1
3
|
from cognite.client import CogniteClient
|
|
2
4
|
from cognite.client.config import ClientConfig, global_config
|
|
3
5
|
|
|
4
6
|
from cognite.neat import _version
|
|
7
|
+
from cognite.neat._utils.repo import get_repo_root
|
|
5
8
|
|
|
6
9
|
from .credentials import get_credentials
|
|
7
|
-
from .env_vars import ClientEnvironmentVariables,
|
|
10
|
+
from .env_vars import ClientEnvironmentVariables, parse_env_file
|
|
11
|
+
from .interactive import get_interactive_flow
|
|
8
12
|
|
|
9
13
|
CLIENT_NAME = f"CogniteNeat:{_version.__version__}"
|
|
10
14
|
|
|
11
15
|
|
|
12
|
-
def get_cognite_client(env_file_name: str) -> CogniteClient:
|
|
16
|
+
def get_cognite_client(env_file_name: str) -> CogniteClient | None:
|
|
13
17
|
"""Get a CogniteClient using environment variables from a .env file."
|
|
14
18
|
|
|
15
19
|
Args:
|
|
@@ -20,24 +24,30 @@ def get_cognite_client(env_file_name: str) -> CogniteClient:
|
|
|
20
24
|
Returns:
|
|
21
25
|
CogniteClient: An instance of CogniteClient configured with the loaded environment variables.
|
|
22
26
|
"""
|
|
23
|
-
try:
|
|
24
|
-
return get_cognite_client_internal(env_file_name)
|
|
25
|
-
except Exception as e:
|
|
26
|
-
raise RuntimeError(f"Failed to create client ❌: {e!s}") from None
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
def get_cognite_client_internal(env_file_name: str) -> CogniteClient:
|
|
30
27
|
# This function raises exceptions on failure
|
|
31
28
|
if not env_file_name.endswith(".env"):
|
|
32
29
|
raise ValueError(f"env_file_name must end with '.env'. Got: {env_file_name!r}")
|
|
33
30
|
global_config.disable_pypi_version_check = True
|
|
34
31
|
global_config.silence_feature_preview_warnings = True
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
32
|
+
|
|
33
|
+
repo_root = get_repo_root()
|
|
34
|
+
if repo_root and (env_path := repo_root / env_file_name).exists():
|
|
35
|
+
print(f"Found {env_file_name} in repository root.")
|
|
36
|
+
elif (env_path := Path.cwd() / env_file_name).exists():
|
|
37
|
+
print(f"Found {env_file_name} in current working directory.")
|
|
38
|
+
|
|
39
|
+
if env_path.exists():
|
|
40
|
+
env_vars = parse_env_file(env_path)
|
|
41
|
+
client_config = create_client_config_from_env_vars(env_vars)
|
|
42
|
+
return CogniteClient(client_config)
|
|
43
|
+
print(f"Failed to find {env_file_name} in repository root or current working directory.")
|
|
44
|
+
|
|
45
|
+
env_folder = repo_root if repo_root is not None else Path.cwd()
|
|
46
|
+
new_env_path = env_folder / env_file_name
|
|
47
|
+
flow = get_interactive_flow(new_env_path)
|
|
48
|
+
flow.run()
|
|
49
|
+
print("Could not create CogniteClient because no environment file was found.")
|
|
50
|
+
return None
|
|
41
51
|
|
|
42
52
|
|
|
43
53
|
def create_client_config_from_env_vars(env_vars: ClientEnvironmentVariables) -> ClientConfig:
|
|
@@ -1,18 +1,37 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from
|
|
3
|
+
from collections.abc import Sequence
|
|
4
|
+
|
|
5
|
+
from cognite.neat._data_model.models.dms import SpaceRequest, SpaceResponse
|
|
4
6
|
from cognite.neat._data_model.models.dms._references import SpaceReference
|
|
5
|
-
from cognite.neat._utils.http_client import
|
|
6
|
-
from cognite.neat._utils.useful_types import PrimitiveType
|
|
7
|
+
from cognite.neat._utils.http_client import HTTPClient, SuccessResponse
|
|
7
8
|
|
|
8
|
-
from .api import NeatAPI
|
|
9
|
+
from .api import Endpoint, NeatAPI
|
|
10
|
+
from .config import NeatClientConfig
|
|
9
11
|
from .data_classes import PagedResponse
|
|
12
|
+
from .filters import DataModelingFilter
|
|
10
13
|
|
|
11
14
|
|
|
12
15
|
class SpacesAPI(NeatAPI):
|
|
13
|
-
|
|
16
|
+
def __init__(self, neat_config: NeatClientConfig, http_client: HTTPClient) -> None:
|
|
17
|
+
super().__init__(
|
|
18
|
+
neat_config,
|
|
19
|
+
http_client,
|
|
20
|
+
endpoint_map={
|
|
21
|
+
"apply": Endpoint("POST", "/models/spaces", item_limit=100),
|
|
22
|
+
"retrieve": Endpoint("POST", "/models/spaces/byids", item_limit=100),
|
|
23
|
+
"delete": Endpoint("POST", "/models/spaces/delete", item_limit=100),
|
|
24
|
+
"list": Endpoint("GET", "/models/spaces", item_limit=1000),
|
|
25
|
+
},
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
def _validate_page_response(self, response: SuccessResponse) -> PagedResponse[SpaceResponse]:
|
|
29
|
+
return PagedResponse[SpaceResponse].model_validate_json(response.body)
|
|
30
|
+
|
|
31
|
+
def _validate_id_response(self, response: SuccessResponse) -> list[SpaceReference]:
|
|
32
|
+
return PagedResponse[SpaceReference].model_validate_json(response.body).items
|
|
14
33
|
|
|
15
|
-
def apply(self, spaces:
|
|
34
|
+
def apply(self, spaces: Sequence[SpaceRequest]) -> list[SpaceResponse]:
|
|
16
35
|
"""Apply (create or update) spaces in CDF.
|
|
17
36
|
|
|
18
37
|
Args:
|
|
@@ -20,20 +39,7 @@ class SpacesAPI(NeatAPI):
|
|
|
20
39
|
Returns:
|
|
21
40
|
List of SpaceResponse objects.
|
|
22
41
|
"""
|
|
23
|
-
|
|
24
|
-
return []
|
|
25
|
-
if len(spaces) > 100:
|
|
26
|
-
raise ValueError("Cannot apply more than 100 spaces at once.")
|
|
27
|
-
result = self._http_client.request_with_retries(
|
|
28
|
-
ItemsRequest(
|
|
29
|
-
endpoint_url=self._config.create_api_url(self.ENDPOINT),
|
|
30
|
-
method="POST",
|
|
31
|
-
body=DataModelBody(items=spaces),
|
|
32
|
-
)
|
|
33
|
-
)
|
|
34
|
-
result.raise_for_status()
|
|
35
|
-
result = PagedResponse[SpaceResponse].model_validate_json(result.success_response.body)
|
|
36
|
-
return result.items
|
|
42
|
+
return self._request_item_response(spaces, "apply")
|
|
37
43
|
|
|
38
44
|
def retrieve(self, spaces: list[SpaceReference]) -> list[SpaceResponse]:
|
|
39
45
|
"""Retrieve spaces by their identifiers.
|
|
@@ -44,21 +50,7 @@ class SpacesAPI(NeatAPI):
|
|
|
44
50
|
Returns:
|
|
45
51
|
List of SpaceResponse objects.
|
|
46
52
|
"""
|
|
47
|
-
|
|
48
|
-
return []
|
|
49
|
-
if len(spaces) > 1000:
|
|
50
|
-
raise ValueError("Cannot retrieve more than 1000 spaces at once.")
|
|
51
|
-
|
|
52
|
-
result = self._http_client.request_with_retries(
|
|
53
|
-
ItemsRequest(
|
|
54
|
-
endpoint_url=self._config.create_api_url(f"{self.ENDPOINT}/byids"),
|
|
55
|
-
method="POST",
|
|
56
|
-
body=ItemIDBody(items=spaces),
|
|
57
|
-
)
|
|
58
|
-
)
|
|
59
|
-
result.raise_for_status()
|
|
60
|
-
result = PagedResponse[SpaceResponse].model_validate_json(result.success_response.body)
|
|
61
|
-
return result.items
|
|
53
|
+
return self._request_item_response(spaces, "retrieve")
|
|
62
54
|
|
|
63
55
|
def delete(self, spaces: list[SpaceReference]) -> list[SpaceReference]:
|
|
64
56
|
"""Delete spaces by their identifiers.
|
|
@@ -68,48 +60,21 @@ class SpacesAPI(NeatAPI):
|
|
|
68
60
|
Returns:
|
|
69
61
|
List of SpaceReference objects representing the deleted spaces.
|
|
70
62
|
"""
|
|
71
|
-
|
|
72
|
-
return []
|
|
73
|
-
if len(spaces) > 100:
|
|
74
|
-
raise ValueError("Cannot delete more than 100 spaces at once.")
|
|
75
|
-
result = self._http_client.request_with_retries(
|
|
76
|
-
ItemsRequest(
|
|
77
|
-
endpoint_url=self._config.create_api_url(f"{self.ENDPOINT}/delete"),
|
|
78
|
-
method="POST",
|
|
79
|
-
body=ItemIDBody(items=spaces),
|
|
80
|
-
)
|
|
81
|
-
)
|
|
82
|
-
result.raise_for_status()
|
|
83
|
-
result = PagedResponse[SpaceReference].model_validate_json(result.success_response.body)
|
|
84
|
-
return result.items
|
|
63
|
+
return self._request_id_response(spaces, "delete")
|
|
85
64
|
|
|
86
65
|
def list(
|
|
87
66
|
self,
|
|
88
67
|
include_global: bool = False,
|
|
89
|
-
limit: int = 10,
|
|
68
|
+
limit: int | None = 10,
|
|
90
69
|
) -> list[SpaceResponse]:
|
|
91
70
|
"""List spaces in CDF Project.
|
|
92
71
|
|
|
93
72
|
Args:
|
|
94
73
|
include_global: If True, include global spaces.
|
|
95
|
-
limit: Maximum number of spaces to return.
|
|
74
|
+
limit: Maximum number of spaces to return. If None, return all spaces.
|
|
96
75
|
|
|
97
76
|
Returns:
|
|
98
77
|
List of SpaceResponse objects.
|
|
99
78
|
"""
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
parameters: dict[str, PrimitiveType] = {
|
|
103
|
-
"includeGlobal": include_global,
|
|
104
|
-
"limit": limit,
|
|
105
|
-
}
|
|
106
|
-
result = self._http_client.request_with_retries(
|
|
107
|
-
ParametersRequest(
|
|
108
|
-
endpoint_url=self._config.create_api_url(self.ENDPOINT),
|
|
109
|
-
method="GET",
|
|
110
|
-
parameters=parameters,
|
|
111
|
-
)
|
|
112
|
-
)
|
|
113
|
-
result.raise_for_status()
|
|
114
|
-
result = PagedResponse[SpaceResponse].model_validate_json(result.success_response.body)
|
|
115
|
-
return result.items
|
|
79
|
+
filter = DataModelingFilter(include_global=include_global)
|
|
80
|
+
return self._list(limit=limit, params=filter.dump())
|
|
@@ -1,9 +1,14 @@
|
|
|
1
|
-
from cognite.neat.
|
|
2
|
-
from cognite.neat._client.data_classes import StatisticsResponse
|
|
3
|
-
from cognite.neat._utils.http_client import ParametersRequest
|
|
1
|
+
from cognite.neat._utils.http_client import HTTPClient, ParametersRequest
|
|
4
2
|
|
|
3
|
+
from .config import NeatClientConfig
|
|
4
|
+
from .data_classes import StatisticsResponse
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class StatisticsAPI:
|
|
8
|
+
def __init__(self, neat_config: NeatClientConfig, http_client: HTTPClient) -> None:
|
|
9
|
+
self._config = neat_config
|
|
10
|
+
self._http_client = http_client
|
|
5
11
|
|
|
6
|
-
class StatisticsAPI(NeatAPI):
|
|
7
12
|
def project(self) -> StatisticsResponse:
|
|
8
13
|
"""Retrieve project-wide usage data and limits.
|
|
9
14
|
|
|
@@ -2,46 +2,45 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from collections.abc import Sequence
|
|
4
4
|
|
|
5
|
-
from cognite.neat._data_model.models.dms import
|
|
6
|
-
from cognite.neat._utils.
|
|
7
|
-
from cognite.neat._utils.http_client import ItemIDBody, ItemsRequest, ParametersRequest
|
|
8
|
-
from cognite.neat._utils.useful_types import PrimitiveType
|
|
5
|
+
from cognite.neat._data_model.models.dms import ViewReference, ViewRequest, ViewResponse
|
|
6
|
+
from cognite.neat._utils.http_client import HTTPClient, SuccessResponse
|
|
9
7
|
|
|
10
|
-
from .api import NeatAPI
|
|
8
|
+
from .api import Endpoint, NeatAPI
|
|
9
|
+
from .config import NeatClientConfig
|
|
11
10
|
from .data_classes import PagedResponse
|
|
11
|
+
from .filters import ViewFilter
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
class ViewsAPI(NeatAPI):
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
def __init__(self, neat_config: NeatClientConfig, http_client: HTTPClient) -> None:
|
|
16
|
+
super().__init__(
|
|
17
|
+
neat_config,
|
|
18
|
+
http_client,
|
|
19
|
+
endpoint_map={
|
|
20
|
+
"apply": Endpoint("POST", "/models/views", item_limit=100),
|
|
21
|
+
"retrieve": Endpoint("POST", "/models/views/byids", item_limit=100),
|
|
22
|
+
"delete": Endpoint("POST", "/models/views/delete", item_limit=100),
|
|
23
|
+
"list": Endpoint("GET", "/models/views", item_limit=1000),
|
|
24
|
+
},
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
def _validate_page_response(self, response: SuccessResponse) -> PagedResponse[ViewResponse]:
|
|
28
|
+
return PagedResponse[ViewResponse].model_validate_json(response.body)
|
|
29
|
+
|
|
30
|
+
def _validate_id_response(self, response: SuccessResponse) -> list[ViewReference]:
|
|
31
|
+
return PagedResponse[ViewReference].model_validate_json(response.body).items
|
|
17
32
|
|
|
18
33
|
def apply(self, items: Sequence[ViewRequest]) -> list[ViewResponse]:
|
|
19
34
|
"""Create or update views in CDF Project.
|
|
35
|
+
|
|
20
36
|
Args:
|
|
21
37
|
items: List of ViewRequest objects to create or update.
|
|
22
38
|
Returns:
|
|
23
39
|
List of ViewResponse objects.
|
|
24
40
|
"""
|
|
25
|
-
|
|
26
|
-
return []
|
|
27
|
-
if len(items) > 100:
|
|
28
|
-
raise ValueError("Cannot apply more than 100 views at once.")
|
|
29
|
-
result = self._http_client.request_with_retries(
|
|
30
|
-
ItemsRequest(
|
|
31
|
-
endpoint_url=self._config.create_api_url(self.ENDPOINT),
|
|
32
|
-
method="POST",
|
|
33
|
-
body=DataModelBody(items=items),
|
|
34
|
-
)
|
|
35
|
-
)
|
|
36
|
-
result.raise_for_status()
|
|
37
|
-
result = PagedResponse[ViewResponse].model_validate_json(result.success_response.body)
|
|
38
|
-
return result.items
|
|
41
|
+
return self._request_item_response(items, "apply")
|
|
39
42
|
|
|
40
|
-
def retrieve(
|
|
41
|
-
self,
|
|
42
|
-
items: list[ViewReference],
|
|
43
|
-
include_inherited_properties: bool = True,
|
|
44
|
-
) -> list[ViewResponse]:
|
|
43
|
+
def retrieve(self, items: list[ViewReference], include_inherited_properties: bool = True) -> list[ViewResponse]:
|
|
45
44
|
"""Retrieve views by their identifiers.
|
|
46
45
|
|
|
47
46
|
Args:
|
|
@@ -51,42 +50,20 @@ class ViewsAPI(NeatAPI):
|
|
|
51
50
|
Returns:
|
|
52
51
|
List of ViewResponse objects.
|
|
53
52
|
"""
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
ItemsRequest(
|
|
58
|
-
endpoint_url=self._config.create_api_url(f"{self.ENDPOINT}/byids"),
|
|
59
|
-
method="POST",
|
|
60
|
-
body=ItemIDBody(items=chunk),
|
|
61
|
-
parameters={"includeInheritedProperties": include_inherited_properties},
|
|
62
|
-
)
|
|
63
|
-
)
|
|
64
|
-
batch.raise_for_status()
|
|
65
|
-
result = PagedResponse[ViewResponse].model_validate_json(batch.success_response.body)
|
|
66
|
-
results.extend(result.items)
|
|
67
|
-
return results
|
|
53
|
+
return self._request_item_response(
|
|
54
|
+
items, "retrieve", extra_body={"includeInheritedProperties": include_inherited_properties}
|
|
55
|
+
)
|
|
68
56
|
|
|
69
57
|
def delete(self, items: list[ViewReference]) -> list[ViewReference]:
|
|
70
58
|
"""Delete views by their identifiers.
|
|
71
59
|
|
|
72
60
|
Args:
|
|
73
61
|
items: List of (space, external_id, version) tuples identifying the views to delete.
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
List of ViewReference objects representing the deleted views.
|
|
74
65
|
"""
|
|
75
|
-
|
|
76
|
-
return []
|
|
77
|
-
if len(items) > 100:
|
|
78
|
-
raise ValueError("Cannot delete more than 100 views at once.")
|
|
79
|
-
|
|
80
|
-
result = self._http_client.request_with_retries(
|
|
81
|
-
ItemsRequest(
|
|
82
|
-
endpoint_url=self._config.create_api_url(f"{self.ENDPOINT}/delete"),
|
|
83
|
-
method="POST",
|
|
84
|
-
body=ItemIDBody(items=items),
|
|
85
|
-
)
|
|
86
|
-
)
|
|
87
|
-
result.raise_for_status()
|
|
88
|
-
result = PagedResponse[ViewReference].model_validate_json(result.success_response.body)
|
|
89
|
-
return result.items
|
|
66
|
+
return self._request_id_response(items, "delete")
|
|
90
67
|
|
|
91
68
|
def list(
|
|
92
69
|
self,
|
|
@@ -108,37 +85,10 @@ class ViewsAPI(NeatAPI):
|
|
|
108
85
|
Returns:
|
|
109
86
|
List of ViewResponse objects.
|
|
110
87
|
"""
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
"includeGlobal": include_global,
|
|
119
|
-
}
|
|
120
|
-
if space is not None:
|
|
121
|
-
parameters["space"] = space
|
|
122
|
-
cursor: str | None = None
|
|
123
|
-
view_responses: list[ViewResponse] = []
|
|
124
|
-
while True:
|
|
125
|
-
if cursor is not None:
|
|
126
|
-
parameters["cursor"] = cursor
|
|
127
|
-
if limit is None:
|
|
128
|
-
parameters["limit"] = self.LIST_REQUEST_LIMIT
|
|
129
|
-
else:
|
|
130
|
-
parameters["limit"] = min(self.LIST_REQUEST_LIMIT, limit - len(view_responses))
|
|
131
|
-
result = self._http_client.request_with_retries(
|
|
132
|
-
ParametersRequest(
|
|
133
|
-
endpoint_url=self._config.create_api_url(self.ENDPOINT),
|
|
134
|
-
method="GET",
|
|
135
|
-
parameters=parameters,
|
|
136
|
-
)
|
|
137
|
-
)
|
|
138
|
-
result.raise_for_status()
|
|
139
|
-
result = PagedResponse[ViewResponse].model_validate_json(result.success_response.body)
|
|
140
|
-
view_responses.extend(result.items)
|
|
141
|
-
cursor = result.next_cursor
|
|
142
|
-
if cursor is None or (limit is not None and len(view_responses) >= limit):
|
|
143
|
-
break
|
|
144
|
-
return view_responses
|
|
88
|
+
filter = ViewFilter(
|
|
89
|
+
space=space,
|
|
90
|
+
all_versions=all_versions,
|
|
91
|
+
include_inherited_properties=include_inherited_properties,
|
|
92
|
+
include_global=include_global,
|
|
93
|
+
)
|
|
94
|
+
return self._list(limit=limit, params=filter.dump())
|
|
@@ -22,6 +22,9 @@ class WriteableResource(Resource, Generic[T_Resource], ABC):
|
|
|
22
22
|
raise NotImplementedError()
|
|
23
23
|
|
|
24
24
|
|
|
25
|
+
T_Response = TypeVar("T_Response", bound=WriteableResource)
|
|
26
|
+
|
|
27
|
+
|
|
25
28
|
class APIResource(Generic[T_Reference], ABC):
|
|
26
29
|
"""Base class for all API data modeling resources."""
|
|
27
30
|
|
|
@@ -15,11 +15,19 @@ from ._physical import PhysicalDataModel
|
|
|
15
15
|
from ._result import Result
|
|
16
16
|
|
|
17
17
|
|
|
18
|
+
def _is_in_browser() -> bool:
|
|
19
|
+
try:
|
|
20
|
+
from pyodide.ffi import IN_BROWSER # type: ignore [import-not-found]
|
|
21
|
+
except ModuleNotFoundError:
|
|
22
|
+
return False
|
|
23
|
+
return IN_BROWSER
|
|
24
|
+
|
|
25
|
+
|
|
18
26
|
class NeatSession:
|
|
19
27
|
"""A session is an interface for neat operations."""
|
|
20
28
|
|
|
21
29
|
def __init__(
|
|
22
|
-
self, client: CogniteClient | ClientConfig, config: PredefinedProfile | NeatConfig = "legacy-additive"
|
|
30
|
+
self, client: CogniteClient | ClientConfig | None, config: PredefinedProfile | NeatConfig = "legacy-additive"
|
|
23
31
|
) -> None:
|
|
24
32
|
"""Initialize a Neat session.
|
|
25
33
|
|
|
@@ -30,6 +38,10 @@ class NeatSession:
|
|
|
30
38
|
Defaults to "legacy-additive". This means Neat will perform additive modeling
|
|
31
39
|
and apply only validations that were part of the legacy Neat version.
|
|
32
40
|
"""
|
|
41
|
+
if client is None and _is_in_browser():
|
|
42
|
+
client = CogniteClient()
|
|
43
|
+
elif client is None:
|
|
44
|
+
raise ValueError("A CogniteClient or ClientConfig must be provided to initialize a NeatSession.")
|
|
33
45
|
self._config = NeatConfig.create_predefined(config) if isinstance(config, str) else config
|
|
34
46
|
|
|
35
47
|
# Use configuration for physical data model
|
|
@@ -167,7 +167,9 @@ class HTTPClient:
|
|
|
167
167
|
if isinstance(item, BodyRequest):
|
|
168
168
|
data = item.data()
|
|
169
169
|
if not global_config.disable_gzip:
|
|
170
|
-
|
|
170
|
+
if isinstance(data, str):
|
|
171
|
+
data = data.encode("utf-8")
|
|
172
|
+
data = gzip.compress(data)
|
|
171
173
|
return self.session.request(
|
|
172
174
|
method=item.method,
|
|
173
175
|
url=item.endpoint_url,
|
|
@@ -118,14 +118,14 @@ class BodyRequest(ParametersRequest, ABC):
|
|
|
118
118
|
"""Base class for HTTP request messages with a body"""
|
|
119
119
|
|
|
120
120
|
@abstractmethod
|
|
121
|
-
def data(self) -> str:
|
|
121
|
+
def data(self) -> str | bytes:
|
|
122
122
|
raise NotImplementedError()
|
|
123
123
|
|
|
124
124
|
|
|
125
125
|
class SimpleBodyRequest(BodyRequest):
|
|
126
|
-
body: str
|
|
126
|
+
body: str | bytes
|
|
127
127
|
|
|
128
|
-
def data(self) -> str:
|
|
128
|
+
def data(self) -> str | bytes:
|
|
129
129
|
return self.body
|
|
130
130
|
|
|
131
131
|
|
cognite/neat/_utils/repo.py
CHANGED
|
@@ -2,7 +2,7 @@ import subprocess
|
|
|
2
2
|
from pathlib import Path
|
|
3
3
|
|
|
4
4
|
|
|
5
|
-
def get_repo_root() -> Path:
|
|
5
|
+
def get_repo_root() -> Path | None:
|
|
6
6
|
"""Get the root path of the git repository.
|
|
7
7
|
|
|
8
8
|
Raises:
|
|
@@ -11,9 +11,11 @@ def get_repo_root() -> Path:
|
|
|
11
11
|
"""
|
|
12
12
|
try:
|
|
13
13
|
result = subprocess.run("git rev-parse --show-toplevel".split(), stdout=subprocess.PIPE)
|
|
14
|
-
except FileNotFoundError
|
|
15
|
-
|
|
14
|
+
except FileNotFoundError:
|
|
15
|
+
# Git is not installed or not found in PATH
|
|
16
|
+
return None
|
|
16
17
|
output = result.stdout.decode().strip()
|
|
17
18
|
if not output:
|
|
18
|
-
|
|
19
|
+
# Not in a git repository
|
|
20
|
+
return None
|
|
19
21
|
return Path(output)
|
cognite/neat/_version.py
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
__version__ = "1.0.
|
|
1
|
+
__version__ = "1.0.26"
|
|
2
2
|
__engine__ = "^2.0.4"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cognite-neat
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.26
|
|
4
4
|
Summary: Knowledge graph transformation
|
|
5
5
|
Author: Nikola Vasiljevic, Anders Albert
|
|
6
6
|
Author-email: Nikola Vasiljevic <nikola.vasiljevic@cognite.com>, Anders Albert <anders.albert@cognite.com>
|