mindbridge-api-python-client 1.4.10__tar.gz → 1.4.11__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.
- {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/PKG-INFO +1 -1
- {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/pyproject.toml +1 -2
- mindbridge_api_python_client-1.4.11/src/mindbridgeapi/account_group_item.py +31 -0
- mindbridge_api_python_client-1.4.11/src/mindbridgeapi/account_grouping_item.py +49 -0
- {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/src/mindbridgeapi/account_groupings.py +38 -22
- mindbridge_api_python_client-1.4.11/src/mindbridgeapi/account_groups.py +65 -0
- {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/src/mindbridgeapi/analyses.py +6 -10
- {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/src/mindbridgeapi/analysis_sources.py +3 -3
- mindbridge_api_python_client-1.4.11/src/mindbridgeapi/async_result_item.py +70 -0
- {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/src/mindbridgeapi/async_results.py +56 -39
- {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/src/mindbridgeapi/base_set.py +4 -1
- {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/src/mindbridgeapi/data_tables.py +75 -61
- mindbridge_api_python_client-1.4.10/src/mindbridgeapi/account_grouping_item.py → mindbridge_api_python_client-1.4.11/src/mindbridgeapi/engagement_account_grouping_item.py +2 -10
- mindbridge_api_python_client-1.4.11/src/mindbridgeapi/engagement_account_groupings.py +92 -0
- {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/src/mindbridgeapi/engagement_item.py +15 -0
- {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/src/mindbridgeapi/engagements.py +21 -15
- mindbridge_api_python_client-1.4.11/src/mindbridgeapi/file_result_item.py +25 -0
- mindbridge_api_python_client-1.4.11/src/mindbridgeapi/file_results.py +47 -0
- {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/src/mindbridgeapi/generated_pydantic_model/model.py +21 -0
- {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/src/mindbridgeapi/server.py +11 -0
- {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/src/mindbridgeapi/tasks.py +4 -1
- {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/LICENSE.txt +0 -0
- {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/README.md +0 -0
- {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/src/mindbridgeapi/__init__.py +0 -0
- {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/src/mindbridgeapi/accounting_period.py +0 -0
- {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/src/mindbridgeapi/analysis_item.py +0 -0
- {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/src/mindbridgeapi/analysis_period.py +0 -0
- {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/src/mindbridgeapi/analysis_source_item.py +0 -0
- {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/src/mindbridgeapi/analysis_source_type_item.py +0 -0
- {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/src/mindbridgeapi/analysis_source_types.py +0 -0
- {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/src/mindbridgeapi/analysis_type_item.py +0 -0
- {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/src/mindbridgeapi/analysis_types.py +0 -0
- {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/src/mindbridgeapi/chunked_file_item.py +0 -0
- {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/src/mindbridgeapi/chunked_file_part_item.py +0 -0
- {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/src/mindbridgeapi/chunked_files.py +0 -0
- {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/src/mindbridgeapi/column_mapping.py +0 -0
- {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/src/mindbridgeapi/common_validators.py +0 -0
- {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/src/mindbridgeapi/enumerations/analysis_source_type.py +0 -0
- {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/src/mindbridgeapi/enumerations/analysis_type.py +0 -0
- {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/src/mindbridgeapi/enumerations/deprecated_enum.py +0 -0
- {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/src/mindbridgeapi/enumerations/system_library.py +0 -0
- {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/src/mindbridgeapi/exceptions.py +0 -0
- {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/src/mindbridgeapi/file_manager.py +0 -0
- {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/src/mindbridgeapi/file_manager_item.py +0 -0
- {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/src/mindbridgeapi/libraries.py +0 -0
- {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/src/mindbridgeapi/library_item.py +0 -0
- {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/src/mindbridgeapi/organization_item.py +0 -0
- {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/src/mindbridgeapi/organizations.py +0 -0
- {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/src/mindbridgeapi/task_item.py +0 -0
- {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/src/mindbridgeapi/transaction_id_selection.py +0 -0
- {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/src/mindbridgeapi/users.py +0 -0
- {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/src/mindbridgeapi/virtual_column.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
|
|
|
4
4
|
|
|
5
5
|
[tool.poetry]
|
|
6
6
|
name = "mindbridge-api-python-client"
|
|
7
|
-
version = "1.4.
|
|
7
|
+
version = "1.4.11"
|
|
8
8
|
description = "Interact with the MindBridge API"
|
|
9
9
|
license = "Proprietary"
|
|
10
10
|
authors = [
|
|
@@ -63,7 +63,6 @@ flake8-clean-block = "^0.1.2"
|
|
|
63
63
|
flake8-comprehensions = "^3.14.0"
|
|
64
64
|
flake8-encodings = "^0.5.1"
|
|
65
65
|
flake8-implicit-str-concat = "^0.4.0"
|
|
66
|
-
flake8-newspaper-style = "^0.4.3"
|
|
67
66
|
flake8-pytest-style = "^2.0.0"
|
|
68
67
|
flake8-return = "^1.2.0"
|
|
69
68
|
flake8-simplify = "^0.21.0"
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright MindBridge Analytics Inc. all rights reserved.
|
|
3
|
+
#
|
|
4
|
+
# This material is confidential and may not be copied, distributed,
|
|
5
|
+
# reversed engineered, decompiled or otherwise disseminated without
|
|
6
|
+
# the prior written consent of MindBridge Analytics Inc.
|
|
7
|
+
#
|
|
8
|
+
|
|
9
|
+
from typing import Any, Dict
|
|
10
|
+
from pydantic import ConfigDict, model_validator
|
|
11
|
+
from mindbridgeapi.common_validators import _warning_if_extra_fields
|
|
12
|
+
from mindbridgeapi.generated_pydantic_model.model import (
|
|
13
|
+
ApiAccountGroupRead,
|
|
14
|
+
ApiAccountGroupUpdate,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class AccountGroupItem(ApiAccountGroupRead):
|
|
19
|
+
model_config = ConfigDict(
|
|
20
|
+
extra="allow",
|
|
21
|
+
validate_assignment=True,
|
|
22
|
+
validate_default=True,
|
|
23
|
+
validate_return=True,
|
|
24
|
+
)
|
|
25
|
+
_ = model_validator(mode="after")(_warning_if_extra_fields)
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
def update_json(self) -> Dict[str, Any]:
|
|
29
|
+
in_class_dict = self.model_dump()
|
|
30
|
+
ag_update = ApiAccountGroupUpdate.model_validate(in_class_dict)
|
|
31
|
+
return ag_update.model_dump(mode="json", by_alias=True, exclude_none=True)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright MindBridge Analytics Inc. all rights reserved.
|
|
3
|
+
#
|
|
4
|
+
# This material is confidential and may not be copied, distributed,
|
|
5
|
+
# reversed engineered, decompiled or otherwise disseminated without
|
|
6
|
+
# the prior written consent of MindBridge Analytics Inc.
|
|
7
|
+
#
|
|
8
|
+
|
|
9
|
+
from typing import Any, Dict, Generator
|
|
10
|
+
from pydantic import ConfigDict, Field, field_validator, model_validator
|
|
11
|
+
from mindbridgeapi.account_group_item import AccountGroupItem
|
|
12
|
+
from mindbridgeapi.common_validators import (
|
|
13
|
+
_convert_userinfo_to_useritem,
|
|
14
|
+
_warning_if_extra_fields,
|
|
15
|
+
)
|
|
16
|
+
from mindbridgeapi.generated_pydantic_model.model import (
|
|
17
|
+
ApiAccountGroupingRead,
|
|
18
|
+
ApiAccountGroupingUpdate,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _empty_account_groups() -> Generator[AccountGroupItem, None, None]:
|
|
23
|
+
"""Empty generator function
|
|
24
|
+
|
|
25
|
+
This returns an empty generator function, it's use is to ensure account_groups is
|
|
26
|
+
not None for the AccountGroupingItem class
|
|
27
|
+
"""
|
|
28
|
+
yield from ()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class AccountGroupingItem(ApiAccountGroupingRead):
|
|
32
|
+
account_groups: Generator[AccountGroupItem, None, None] = Field(
|
|
33
|
+
default_factory=_empty_account_groups, exclude=True
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
model_config = ConfigDict(
|
|
37
|
+
extra="allow",
|
|
38
|
+
validate_assignment=True,
|
|
39
|
+
validate_default=True,
|
|
40
|
+
validate_return=True,
|
|
41
|
+
)
|
|
42
|
+
_a = model_validator(mode="after")(_warning_if_extra_fields)
|
|
43
|
+
_b = field_validator("*")(_convert_userinfo_to_useritem)
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def update_json(self) -> Dict[str, Any]:
|
|
47
|
+
in_class_dict = self.model_dump()
|
|
48
|
+
ag_update = ApiAccountGroupingUpdate.model_validate(in_class_dict)
|
|
49
|
+
return ag_update.model_dump(mode="json", by_alias=True, exclude_none=True)
|
|
@@ -11,18 +11,17 @@ from functools import cached_property
|
|
|
11
11
|
import logging
|
|
12
12
|
from typing import TYPE_CHECKING, Any, Dict, Generator, Optional, Union
|
|
13
13
|
from mindbridgeapi.account_grouping_item import AccountGroupingItem
|
|
14
|
+
from mindbridgeapi.async_result_item import AsyncResultItem
|
|
14
15
|
from mindbridgeapi.async_results import AsyncResults
|
|
15
16
|
from mindbridgeapi.base_set import BaseSet
|
|
16
17
|
from mindbridgeapi.exceptions import ItemError, ItemNotFoundError
|
|
17
18
|
from mindbridgeapi.generated_pydantic_model.model import (
|
|
18
|
-
ApiAsyncResult,
|
|
19
19
|
ApiImportAccountGroupingParamsCreateOnly,
|
|
20
20
|
ApiImportAccountGroupingParamsUpdate,
|
|
21
21
|
)
|
|
22
22
|
from mindbridgeapi.generated_pydantic_model.model import (
|
|
23
23
|
Type7 as AccountGroupingFileType,
|
|
24
24
|
)
|
|
25
|
-
from mindbridgeapi.generated_pydantic_model.model import Status2 as AsyncResultStatus
|
|
26
25
|
from mindbridgeapi.generated_pydantic_model.model import Type1 as AsyncResultType
|
|
27
26
|
|
|
28
27
|
if TYPE_CHECKING:
|
|
@@ -46,7 +45,9 @@ class AccountGroupings(BaseSet):
|
|
|
46
45
|
url = f"{self.base_url}/{id}"
|
|
47
46
|
resp_dict = super()._get_by_id(url=url)
|
|
48
47
|
|
|
49
|
-
|
|
48
|
+
account_grouping = AccountGroupingItem.model_validate(resp_dict)
|
|
49
|
+
self.restart_account_groups(account_grouping)
|
|
50
|
+
return account_grouping
|
|
50
51
|
|
|
51
52
|
def get(
|
|
52
53
|
self, json: Optional[Dict[str, Any]] = None
|
|
@@ -56,19 +57,21 @@ class AccountGroupings(BaseSet):
|
|
|
56
57
|
|
|
57
58
|
url = f"{self.base_url}/query"
|
|
58
59
|
for resp_dict in super()._get(url=url, json=json):
|
|
59
|
-
|
|
60
|
+
account_grouping = AccountGroupingItem.model_validate(resp_dict)
|
|
61
|
+
self.restart_account_groups(account_grouping)
|
|
62
|
+
yield account_grouping
|
|
60
63
|
|
|
61
|
-
def export(self, input_item: AccountGroupingItem) ->
|
|
64
|
+
def export(self, input_item: AccountGroupingItem) -> AsyncResultItem:
|
|
62
65
|
if getattr(input_item, "id", None) is None:
|
|
63
66
|
raise ItemNotFoundError
|
|
64
67
|
|
|
65
68
|
url = f"{self.base_url}/{input_item.id}/export"
|
|
66
69
|
resp_dict = super()._create(url=url)
|
|
67
70
|
|
|
68
|
-
return
|
|
71
|
+
return AsyncResultItem.model_validate(resp_dict)
|
|
69
72
|
|
|
70
73
|
def wait_for_export(
|
|
71
|
-
self, async_result:
|
|
74
|
+
self, async_result: AsyncResultItem, max_wait_minutes: int = 5
|
|
72
75
|
) -> None:
|
|
73
76
|
"""Wait for the async result for the data table export to complete
|
|
74
77
|
|
|
@@ -92,23 +95,22 @@ class AccountGroupings(BaseSet):
|
|
|
92
95
|
)
|
|
93
96
|
|
|
94
97
|
def download(
|
|
95
|
-
self, async_result:
|
|
98
|
+
self, async_result: AsyncResultItem, output_file_path: "Path"
|
|
96
99
|
) -> "Path":
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
url=f"{self.server.base_url}/async-results/{async_result.id}"
|
|
100
|
-
)
|
|
101
|
-
async_result = ApiAsyncResult.model_validate(resp_dict)
|
|
100
|
+
if async_result.id is None:
|
|
101
|
+
raise ItemNotFoundError
|
|
102
102
|
|
|
103
|
-
|
|
103
|
+
async_result = self.server.async_results.get_by_id(async_result.id)
|
|
104
104
|
|
|
105
|
-
|
|
106
|
-
|
|
105
|
+
file_result_id = async_result._get_file_result_id(
|
|
106
|
+
expected_type=AsyncResultType.ACCOUNT_GROUPING_EXPORT
|
|
107
|
+
)
|
|
107
108
|
|
|
108
|
-
|
|
109
|
-
raise ItemError(f"{async_result.status=}")
|
|
109
|
+
file_result = self.server.file_results.get_by_id(file_result_id)
|
|
110
110
|
|
|
111
|
-
return
|
|
111
|
+
return self.server.file_results.export(
|
|
112
|
+
file_result=file_result, output_file_path=output_file_path
|
|
113
|
+
)
|
|
112
114
|
|
|
113
115
|
def upload(
|
|
114
116
|
self,
|
|
@@ -125,7 +127,9 @@ class AccountGroupings(BaseSet):
|
|
|
125
127
|
json = ag_params.model_dump(mode="json", by_alias=True, exclude_none=True)
|
|
126
128
|
resp_dict = super()._create(url=url, json=json)
|
|
127
129
|
|
|
128
|
-
|
|
130
|
+
account_grouping = AccountGroupingItem.model_validate(resp_dict)
|
|
131
|
+
self.restart_account_groups(account_grouping)
|
|
132
|
+
return account_grouping
|
|
129
133
|
|
|
130
134
|
def update(self, account_grouping: AccountGroupingItem) -> AccountGroupingItem:
|
|
131
135
|
if getattr(account_grouping, "id", None) is None:
|
|
@@ -134,7 +138,9 @@ class AccountGroupings(BaseSet):
|
|
|
134
138
|
url = f"{self.base_url}/{account_grouping.id}"
|
|
135
139
|
resp_dict = super()._update(url=url, json=account_grouping.update_json)
|
|
136
140
|
|
|
137
|
-
|
|
141
|
+
account_grouping = AccountGroupingItem.model_validate(resp_dict)
|
|
142
|
+
self.restart_account_groups(account_grouping)
|
|
143
|
+
return account_grouping
|
|
138
144
|
|
|
139
145
|
def delete(self, account_grouping: AccountGroupingItem) -> None:
|
|
140
146
|
if getattr(account_grouping, "id", None) is None:
|
|
@@ -157,4 +163,14 @@ class AccountGroupings(BaseSet):
|
|
|
157
163
|
json = ag_params.model_dump(mode="json", by_alias=True, exclude_none=True)
|
|
158
164
|
resp_dict = super()._create(url=url, json=json)
|
|
159
165
|
|
|
160
|
-
|
|
166
|
+
account_grouping = AccountGroupingItem.model_validate(resp_dict)
|
|
167
|
+
self.restart_account_groups(account_grouping)
|
|
168
|
+
return account_grouping
|
|
169
|
+
|
|
170
|
+
def restart_account_groups(self, account_grouping: AccountGroupingItem) -> None:
|
|
171
|
+
if getattr(account_grouping, "id", None) is None:
|
|
172
|
+
raise ItemNotFoundError
|
|
173
|
+
|
|
174
|
+
account_grouping.account_groups = self.server.account_groups.get(
|
|
175
|
+
json={"accountGroupingId": {"$eq": account_grouping.id}}
|
|
176
|
+
)
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright MindBridge Analytics Inc. all rights reserved.
|
|
3
|
+
#
|
|
4
|
+
# This material is confidential and may not be copied, distributed,
|
|
5
|
+
# reversed engineered, decompiled or otherwise disseminated without
|
|
6
|
+
# the prior written consent of MindBridge Analytics Inc.
|
|
7
|
+
#
|
|
8
|
+
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from functools import cached_property
|
|
11
|
+
import logging
|
|
12
|
+
from typing import Any, Dict, Generator, Optional
|
|
13
|
+
from mindbridgeapi.account_group_item import AccountGroupItem
|
|
14
|
+
from mindbridgeapi.base_set import BaseSet
|
|
15
|
+
from mindbridgeapi.exceptions import ItemNotFoundError, ParameterError
|
|
16
|
+
from mindbridgeapi.generated_pydantic_model.model import MindBridgeQueryTerm
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class AccountGroups(BaseSet):
|
|
23
|
+
@cached_property
|
|
24
|
+
def base_url(self) -> str:
|
|
25
|
+
return f"{self.server.base_url}/account-groups"
|
|
26
|
+
|
|
27
|
+
def get_by_id(self, id: str) -> AccountGroupItem:
|
|
28
|
+
url = f"{self.base_url}/{id}"
|
|
29
|
+
resp_dict = super()._get_by_id(url=url)
|
|
30
|
+
|
|
31
|
+
return AccountGroupItem.model_validate(resp_dict)
|
|
32
|
+
|
|
33
|
+
def get(
|
|
34
|
+
self, json: Optional[Dict[str, Any]] = None
|
|
35
|
+
) -> Generator[AccountGroupItem, None, None]:
|
|
36
|
+
if json is None:
|
|
37
|
+
json = {}
|
|
38
|
+
|
|
39
|
+
mindbridgequeryterm = MindBridgeQueryTerm.model_validate(json)
|
|
40
|
+
json_str = mindbridgequeryterm.model_dump_json(
|
|
41
|
+
by_alias=True, exclude_none=True, warnings=False
|
|
42
|
+
)
|
|
43
|
+
logger.info(f"Query (get) is: {json_str}")
|
|
44
|
+
|
|
45
|
+
if "accountGroupingId" not in json_str:
|
|
46
|
+
raise ParameterError(
|
|
47
|
+
parameter_name="json",
|
|
48
|
+
details=(
|
|
49
|
+
"At least one valid accountGroupingId term must be provided when "
|
|
50
|
+
"querying this entity."
|
|
51
|
+
),
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
url = f"{self.base_url}/query"
|
|
55
|
+
for resp_dict in super()._get(url=url, json=json):
|
|
56
|
+
yield AccountGroupItem.model_validate(resp_dict)
|
|
57
|
+
|
|
58
|
+
def update(self, account_group: AccountGroupItem) -> AccountGroupItem:
|
|
59
|
+
if getattr(account_group, "id", None) is None:
|
|
60
|
+
raise ItemNotFoundError
|
|
61
|
+
|
|
62
|
+
url = f"{self.base_url}/{account_group.id}"
|
|
63
|
+
resp_dict = super()._update(url=url, json=account_group.update_json)
|
|
64
|
+
|
|
65
|
+
return AccountGroupItem.model_validate(resp_dict)
|
|
@@ -14,7 +14,7 @@ from typing import TYPE_CHECKING, Any, Dict, Generator, Optional
|
|
|
14
14
|
import warnings
|
|
15
15
|
from mindbridgeapi.analysis_item import AnalysisItem
|
|
16
16
|
from mindbridgeapi.analysis_sources import AnalysisSources
|
|
17
|
-
from mindbridgeapi.
|
|
17
|
+
from mindbridgeapi.async_result_item import AsyncResultItem
|
|
18
18
|
from mindbridgeapi.base_set import BaseSet
|
|
19
19
|
from mindbridgeapi.data_tables import DataTables
|
|
20
20
|
from mindbridgeapi.exceptions import (
|
|
@@ -27,7 +27,6 @@ from mindbridgeapi.exceptions import (
|
|
|
27
27
|
)
|
|
28
28
|
from mindbridgeapi.generated_pydantic_model.model import (
|
|
29
29
|
ApiAnalysisStatusRead,
|
|
30
|
-
ApiAsyncResult,
|
|
31
30
|
ApiEngagementRollForwardRequest,
|
|
32
31
|
EntityType,
|
|
33
32
|
)
|
|
@@ -42,9 +41,6 @@ logger = logging.getLogger(__name__)
|
|
|
42
41
|
|
|
43
42
|
@dataclass
|
|
44
43
|
class Analyses(BaseSet):
|
|
45
|
-
def __post_init__(self) -> None:
|
|
46
|
-
self.async_result_set = AsyncResults(server=self.server)
|
|
47
|
-
|
|
48
44
|
@cached_property
|
|
49
45
|
def base_url(self) -> str:
|
|
50
46
|
return f"{self.server.base_url}/analyses"
|
|
@@ -133,7 +129,7 @@ class Analyses(BaseSet):
|
|
|
133
129
|
# Get the list of async_result ids
|
|
134
130
|
async_results_to_check = []
|
|
135
131
|
for analysis_source in analysis.analysis_sources:
|
|
136
|
-
async_results = self.
|
|
132
|
+
async_results = self.server.async_results.get(
|
|
137
133
|
json={
|
|
138
134
|
"$and": [
|
|
139
135
|
{"entityId": {"$eq": analysis_source.id}},
|
|
@@ -166,7 +162,7 @@ class Analyses(BaseSet):
|
|
|
166
162
|
"""
|
|
167
163
|
raise ItemError("Analysis has no analysis sources.") # noqa: TRY003
|
|
168
164
|
|
|
169
|
-
self.
|
|
165
|
+
self.server.async_results._wait_for_async_results(
|
|
170
166
|
async_results=async_results_to_check,
|
|
171
167
|
max_wait_minutes=max_wait_minutes,
|
|
172
168
|
init_interval_sec=11,
|
|
@@ -261,7 +257,7 @@ class Analyses(BaseSet):
|
|
|
261
257
|
if analysis.id is None:
|
|
262
258
|
raise ItemNotFoundError
|
|
263
259
|
|
|
264
|
-
async_results = self.
|
|
260
|
+
async_results = self.server.async_results.get(
|
|
265
261
|
json={
|
|
266
262
|
"$and": [
|
|
267
263
|
{"entityId": {"$eq": analysis.id}},
|
|
@@ -281,7 +277,7 @@ class Analyses(BaseSet):
|
|
|
281
277
|
async_results_list, key=lambda x: getattr(x, "last_modified_date", date.min)
|
|
282
278
|
)
|
|
283
279
|
|
|
284
|
-
self.
|
|
280
|
+
self.server.async_results._wait_for_async_result(
|
|
285
281
|
async_result=async_result,
|
|
286
282
|
max_wait_minutes=max_wait_minutes,
|
|
287
283
|
init_interval_sec=76,
|
|
@@ -321,7 +317,7 @@ class Analyses(BaseSet):
|
|
|
321
317
|
mode="json", by_alias=True, exclude_none=True
|
|
322
318
|
)
|
|
323
319
|
resp_dict = super()._create(url=url, json=roll_forward_request_json)
|
|
324
|
-
async_result =
|
|
320
|
+
async_result = AsyncResultItem.model_validate(resp_dict)
|
|
325
321
|
if async_result.entity_id is None:
|
|
326
322
|
raise UnexpectedServerError(details="async_result.entity_id was None")
|
|
327
323
|
|
|
@@ -11,6 +11,7 @@ from functools import cached_property
|
|
|
11
11
|
import logging
|
|
12
12
|
from typing import Any, Dict, Generator, Optional
|
|
13
13
|
from mindbridgeapi.analysis_source_item import AnalysisSourceItem
|
|
14
|
+
from mindbridgeapi.async_result_item import AsyncResultItem
|
|
14
15
|
from mindbridgeapi.base_set import BaseSet
|
|
15
16
|
from mindbridgeapi.exceptions import (
|
|
16
17
|
ItemAlreadyExistsError,
|
|
@@ -24,7 +25,6 @@ from mindbridgeapi.generated_pydantic_model.model import (
|
|
|
24
25
|
from mindbridgeapi.generated_pydantic_model.model import (
|
|
25
26
|
PeriodType as AnalysisEffectiveDateMetricsPeriod,
|
|
26
27
|
)
|
|
27
|
-
from mindbridgeapi.generated_pydantic_model.model import ApiAsyncResult
|
|
28
28
|
from mindbridgeapi.generated_pydantic_model.model import Type1 as AsyncResultType
|
|
29
29
|
|
|
30
30
|
logger = logging.getLogger(__name__)
|
|
@@ -42,7 +42,7 @@ class AnalysisSources(BaseSet):
|
|
|
42
42
|
|
|
43
43
|
url = self.base_url
|
|
44
44
|
resp_dict = super()._create(url=url, json=item.create_json)
|
|
45
|
-
async_result =
|
|
45
|
+
async_result = AsyncResultItem.model_validate(resp_dict)
|
|
46
46
|
|
|
47
47
|
if async_result.type != AsyncResultType.ANALYSIS_SOURCE_INGESTION:
|
|
48
48
|
raise UnexpectedServerError(f"Async Result Type: {async_result.type}")
|
|
@@ -109,7 +109,7 @@ class AnalysisSources(BaseSet):
|
|
|
109
109
|
url = f"{self.base_url}/{item.id}"
|
|
110
110
|
resp_dict = super()._update(url=url, json=item.update_json)
|
|
111
111
|
|
|
112
|
-
async_result =
|
|
112
|
+
async_result = AsyncResultItem.model_validate(resp_dict)
|
|
113
113
|
|
|
114
114
|
if async_result.type != AsyncResultType.ANALYSIS_SOURCE_INGESTION:
|
|
115
115
|
raise UnexpectedServerError(f"Async Result Type: {async_result.type}")
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright MindBridge Analytics Inc. all rights reserved.
|
|
3
|
+
#
|
|
4
|
+
# This material is confidential and may not be copied, distributed,
|
|
5
|
+
# reversed engineered, decompiled or otherwise disseminated without
|
|
6
|
+
# the prior written consent of MindBridge Analytics Inc.
|
|
7
|
+
#
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
from pydantic import ConfigDict, field_validator, model_validator
|
|
11
|
+
from mindbridgeapi.common_validators import (
|
|
12
|
+
_convert_userinfo_to_useritem,
|
|
13
|
+
_warning_if_extra_fields,
|
|
14
|
+
)
|
|
15
|
+
from mindbridgeapi.exceptions import ItemError, ItemNotFoundError, ValidationError
|
|
16
|
+
from mindbridgeapi.generated_pydantic_model.model import ApiAsyncResult
|
|
17
|
+
from mindbridgeapi.generated_pydantic_model.model import Status2 as AsyncResultStatus
|
|
18
|
+
from mindbridgeapi.generated_pydantic_model.model import Type1 as AsyncResultType
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class AsyncResultItem(ApiAsyncResult):
|
|
24
|
+
model_config = ConfigDict(
|
|
25
|
+
extra="allow",
|
|
26
|
+
validate_assignment=True,
|
|
27
|
+
validate_default=True,
|
|
28
|
+
validate_return=True,
|
|
29
|
+
)
|
|
30
|
+
_a = model_validator(mode="after")(_warning_if_extra_fields)
|
|
31
|
+
_b = field_validator("*")(_convert_userinfo_to_useritem)
|
|
32
|
+
|
|
33
|
+
def _check_if_completed(self) -> bool:
|
|
34
|
+
"""Checks if the AsyncResultItem is completed
|
|
35
|
+
|
|
36
|
+
Returns True if COMPLETE, False if IN_PROGRESS and raises and error otherwise.
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
bool: True if COMPLETE, False if IN_PROGRESS
|
|
40
|
+
|
|
41
|
+
Raises:
|
|
42
|
+
ValidationError: If the async_result resulted in an error state
|
|
43
|
+
"""
|
|
44
|
+
async_result_str = (
|
|
45
|
+
f"Async Result {self.id} for"
|
|
46
|
+
f" {self.entity_type} {self.entity_id} resulted in"
|
|
47
|
+
f" {self.status}"
|
|
48
|
+
)
|
|
49
|
+
logger.info(async_result_str)
|
|
50
|
+
|
|
51
|
+
if self.status == AsyncResultStatus.IN_PROGRESS:
|
|
52
|
+
return False
|
|
53
|
+
|
|
54
|
+
if self.status == AsyncResultStatus.COMPLETE:
|
|
55
|
+
return True
|
|
56
|
+
|
|
57
|
+
# Must be AsyncResultStatus.ERROR
|
|
58
|
+
raise ValidationError(f"{async_result_str} with message {self.error}.")
|
|
59
|
+
|
|
60
|
+
def _get_file_result_id(self, expected_type: AsyncResultType) -> str:
|
|
61
|
+
if self.type != expected_type:
|
|
62
|
+
raise ItemError(f"{self.type=}")
|
|
63
|
+
|
|
64
|
+
if self.status != AsyncResultStatus.COMPLETE:
|
|
65
|
+
raise ItemError(f"{self.status=}")
|
|
66
|
+
|
|
67
|
+
if self.entity_id is None:
|
|
68
|
+
raise ItemNotFoundError
|
|
69
|
+
|
|
70
|
+
return self.entity_id
|
|
@@ -11,15 +11,13 @@ from functools import cached_property
|
|
|
11
11
|
import logging
|
|
12
12
|
import time
|
|
13
13
|
from typing import Any, Dict, Generator, List, Optional
|
|
14
|
+
from mindbridgeapi.async_result_item import AsyncResultItem
|
|
14
15
|
from mindbridgeapi.base_set import BaseSet
|
|
15
16
|
from mindbridgeapi.exceptions import (
|
|
16
17
|
ItemNotFoundError,
|
|
17
18
|
UnexpectedServerError,
|
|
18
19
|
ValidationError,
|
|
19
20
|
)
|
|
20
|
-
from mindbridgeapi.generated_pydantic_model.model import (
|
|
21
|
-
ApiAsyncResult as AsyncResultItem,
|
|
22
|
-
)
|
|
23
21
|
from mindbridgeapi.generated_pydantic_model.model import Status2 as AsyncResultStatus
|
|
24
22
|
|
|
25
23
|
logger = logging.getLogger(__name__)
|
|
@@ -52,6 +50,45 @@ class AsyncResults(BaseSet):
|
|
|
52
50
|
init_interval_sec=init_interval_sec,
|
|
53
51
|
)
|
|
54
52
|
|
|
53
|
+
@staticmethod
|
|
54
|
+
def _wait_for_async_results_refresh_sorted_ids(
|
|
55
|
+
async_results: List[AsyncResultItem],
|
|
56
|
+
) -> List[str]:
|
|
57
|
+
async_result_ids = []
|
|
58
|
+
for async_result in async_results:
|
|
59
|
+
if async_result.id is None:
|
|
60
|
+
raise ItemNotFoundError
|
|
61
|
+
|
|
62
|
+
async_result_ids.append(async_result.id)
|
|
63
|
+
|
|
64
|
+
return sorted(async_result_ids)
|
|
65
|
+
|
|
66
|
+
def _wait_for_async_results_refresh(
|
|
67
|
+
self, async_results: List[AsyncResultItem]
|
|
68
|
+
) -> List[AsyncResultItem]:
|
|
69
|
+
if not async_results:
|
|
70
|
+
return []
|
|
71
|
+
|
|
72
|
+
sorted_async_result_ids = self._wait_for_async_results_refresh_sorted_ids(
|
|
73
|
+
async_results=async_results
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
new_async_results = list(
|
|
77
|
+
self.get(json={"id": {"$in": sorted_async_result_ids}})
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
sorted_new_async_result_ids = self._wait_for_async_results_refresh_sorted_ids(
|
|
81
|
+
async_results=new_async_results
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
if sorted_async_result_ids != sorted_new_async_result_ids:
|
|
85
|
+
raise UnexpectedServerError(
|
|
86
|
+
f"AsyncResults received didn't match: {sorted_async_result_ids=}, "
|
|
87
|
+
f"{sorted_new_async_result_ids=}"
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
return new_async_results
|
|
91
|
+
|
|
55
92
|
def _wait_for_async_results(
|
|
56
93
|
self,
|
|
57
94
|
async_results: List[AsyncResultItem],
|
|
@@ -82,66 +119,46 @@ class AsyncResults(BaseSet):
|
|
|
82
119
|
while (time.monotonic() - start_time) < max_wait_seconds:
|
|
83
120
|
loop_start_time = time.monotonic()
|
|
84
121
|
elapsed_time = loop_start_time - start_time
|
|
85
|
-
sorted_async_results_ids = sorted(
|
|
86
|
-
[x.id for x in async_results if x.id is not None]
|
|
87
|
-
)
|
|
88
|
-
number_of_async_results = len(sorted_async_results_ids)
|
|
89
|
-
if number_of_async_results != len(async_results):
|
|
90
|
-
raise ItemNotFoundError
|
|
91
122
|
|
|
92
123
|
logger.info(
|
|
93
124
|
"Starting a AsyncResult iteration. It has been: "
|
|
94
|
-
f"{elapsed_time:.1f} seconds. Loop {i=} and {
|
|
125
|
+
f"{elapsed_time:.1f} seconds. Loop {i=} and {len(async_results)} "
|
|
95
126
|
"to check"
|
|
96
127
|
)
|
|
97
128
|
|
|
98
|
-
if
|
|
129
|
+
if not async_results:
|
|
99
130
|
break
|
|
100
131
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
new_async_results = list(self.get(json=query))
|
|
106
|
-
|
|
107
|
-
if (
|
|
108
|
-
sorted([x.id for x in new_async_results if x.id is not None])
|
|
109
|
-
!= sorted_async_results_ids
|
|
110
|
-
):
|
|
111
|
-
raise UnexpectedServerError( # noqa: TRY003
|
|
112
|
-
"AsyncResults received didn't match requested"
|
|
132
|
+
new_async_results = [
|
|
133
|
+
async_result
|
|
134
|
+
for async_result in self._wait_for_async_results_refresh(
|
|
135
|
+
async_results=async_results
|
|
113
136
|
)
|
|
137
|
+
if not async_result._check_if_completed()
|
|
138
|
+
]
|
|
114
139
|
|
|
115
|
-
|
|
116
|
-
for async_result in new_async_results:
|
|
117
|
-
if async_result is None:
|
|
118
|
-
raise UnexpectedServerError("AsyncResult was None") # noqa: TRY003
|
|
119
|
-
|
|
120
|
-
if not self._check_if_async_result_is_completed(async_result):
|
|
121
|
-
async_results.append(async_result)
|
|
122
|
-
|
|
123
|
-
if len(async_results) == 0:
|
|
140
|
+
if not new_async_results:
|
|
124
141
|
break
|
|
125
142
|
|
|
126
|
-
sleep_seconds = interval_sec - (time.monotonic() - loop_start_time)
|
|
143
|
+
sleep_seconds = max(interval_sec - (time.monotonic() - loop_start_time), 0)
|
|
127
144
|
logger.info(
|
|
128
145
|
f"Waiting for about {sleep_seconds} seconds as some of the async "
|
|
129
146
|
"results are not complete yet."
|
|
130
147
|
)
|
|
131
|
-
|
|
132
|
-
time.sleep(sleep_seconds)
|
|
148
|
+
time.sleep(sleep_seconds)
|
|
133
149
|
|
|
134
150
|
elapsed_time = time.monotonic() - start_time
|
|
135
|
-
logger.info(
|
|
136
|
-
"Finished a AsyncResult iteration. It has been:"
|
|
137
|
-
f" {elapsed_time:.1f} seconds"
|
|
138
|
-
)
|
|
139
151
|
|
|
140
152
|
if interval_sec < max_interval_sec:
|
|
141
153
|
interval_sec = min(init_interval_sec * 2**i, max_interval_sec)
|
|
142
154
|
|
|
143
155
|
i = i + 1
|
|
144
156
|
|
|
157
|
+
logger.info(
|
|
158
|
+
"Finished a AsyncResults iteration. It has been:"
|
|
159
|
+
f" {elapsed_time:.1f} seconds"
|
|
160
|
+
)
|
|
161
|
+
|
|
145
162
|
else:
|
|
146
163
|
raise TimeoutError(f"Waited too long: {max_wait_minutes} minutes")
|
|
147
164
|
|