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.
Files changed (52) hide show
  1. {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/PKG-INFO +1 -1
  2. {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/pyproject.toml +1 -2
  3. mindbridge_api_python_client-1.4.11/src/mindbridgeapi/account_group_item.py +31 -0
  4. mindbridge_api_python_client-1.4.11/src/mindbridgeapi/account_grouping_item.py +49 -0
  5. {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/src/mindbridgeapi/account_groupings.py +38 -22
  6. mindbridge_api_python_client-1.4.11/src/mindbridgeapi/account_groups.py +65 -0
  7. {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/src/mindbridgeapi/analyses.py +6 -10
  8. {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/src/mindbridgeapi/analysis_sources.py +3 -3
  9. mindbridge_api_python_client-1.4.11/src/mindbridgeapi/async_result_item.py +70 -0
  10. {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/src/mindbridgeapi/async_results.py +56 -39
  11. {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/src/mindbridgeapi/base_set.py +4 -1
  12. {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/src/mindbridgeapi/data_tables.py +75 -61
  13. 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
  14. mindbridge_api_python_client-1.4.11/src/mindbridgeapi/engagement_account_groupings.py +92 -0
  15. {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/src/mindbridgeapi/engagement_item.py +15 -0
  16. {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/src/mindbridgeapi/engagements.py +21 -15
  17. mindbridge_api_python_client-1.4.11/src/mindbridgeapi/file_result_item.py +25 -0
  18. mindbridge_api_python_client-1.4.11/src/mindbridgeapi/file_results.py +47 -0
  19. {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/src/mindbridgeapi/generated_pydantic_model/model.py +21 -0
  20. {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/src/mindbridgeapi/server.py +11 -0
  21. {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/src/mindbridgeapi/tasks.py +4 -1
  22. {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/LICENSE.txt +0 -0
  23. {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/README.md +0 -0
  24. {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/src/mindbridgeapi/__init__.py +0 -0
  25. {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/src/mindbridgeapi/accounting_period.py +0 -0
  26. {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/src/mindbridgeapi/analysis_item.py +0 -0
  27. {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/src/mindbridgeapi/analysis_period.py +0 -0
  28. {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/src/mindbridgeapi/analysis_source_item.py +0 -0
  29. {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/src/mindbridgeapi/analysis_source_type_item.py +0 -0
  30. {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/src/mindbridgeapi/analysis_source_types.py +0 -0
  31. {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/src/mindbridgeapi/analysis_type_item.py +0 -0
  32. {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/src/mindbridgeapi/analysis_types.py +0 -0
  33. {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/src/mindbridgeapi/chunked_file_item.py +0 -0
  34. {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/src/mindbridgeapi/chunked_file_part_item.py +0 -0
  35. {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/src/mindbridgeapi/chunked_files.py +0 -0
  36. {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/src/mindbridgeapi/column_mapping.py +0 -0
  37. {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/src/mindbridgeapi/common_validators.py +0 -0
  38. {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/src/mindbridgeapi/enumerations/analysis_source_type.py +0 -0
  39. {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/src/mindbridgeapi/enumerations/analysis_type.py +0 -0
  40. {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/src/mindbridgeapi/enumerations/deprecated_enum.py +0 -0
  41. {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/src/mindbridgeapi/enumerations/system_library.py +0 -0
  42. {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/src/mindbridgeapi/exceptions.py +0 -0
  43. {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/src/mindbridgeapi/file_manager.py +0 -0
  44. {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/src/mindbridgeapi/file_manager_item.py +0 -0
  45. {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/src/mindbridgeapi/libraries.py +0 -0
  46. {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/src/mindbridgeapi/library_item.py +0 -0
  47. {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/src/mindbridgeapi/organization_item.py +0 -0
  48. {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/src/mindbridgeapi/organizations.py +0 -0
  49. {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/src/mindbridgeapi/task_item.py +0 -0
  50. {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/src/mindbridgeapi/transaction_id_selection.py +0 -0
  51. {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/src/mindbridgeapi/users.py +0 -0
  52. {mindbridge_api_python_client-1.4.10 → mindbridge_api_python_client-1.4.11}/src/mindbridgeapi/virtual_column.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: mindbridge-api-python-client
3
- Version: 1.4.10
3
+ Version: 1.4.11
4
4
  Summary: Interact with the MindBridge API
5
5
  Home-page: https://www.mindbridge.ai
6
6
  License: Proprietary
@@ -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.10"
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
- return AccountGroupingItem.model_validate(resp_dict)
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
- yield AccountGroupingItem.model_validate(resp_dict)
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) -> ApiAsyncResult:
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 ApiAsyncResult.model_validate(resp_dict)
71
+ return AsyncResultItem.model_validate(resp_dict)
69
72
 
70
73
  def wait_for_export(
71
- self, async_result: ApiAsyncResult, max_wait_minutes: int = 5
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: ApiAsyncResult, output_file_path: "Path"
98
+ self, async_result: AsyncResultItem, output_file_path: "Path"
96
99
  ) -> "Path":
97
- # Get the current status
98
- resp_dict = super()._get_by_id(
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
- url = f"{self.server.base_url}/file-results/{async_result.entity_id}/export"
103
+ async_result = self.server.async_results.get_by_id(async_result.id)
104
104
 
105
- if async_result.type != AsyncResultType.ACCOUNT_GROUPING_EXPORT:
106
- raise ItemError(f"{async_result.type=}")
105
+ file_result_id = async_result._get_file_result_id(
106
+ expected_type=AsyncResultType.ACCOUNT_GROUPING_EXPORT
107
+ )
107
108
 
108
- if async_result.status != AsyncResultStatus.COMPLETE:
109
- raise ItemError(f"{async_result.status=}")
109
+ file_result = self.server.file_results.get_by_id(file_result_id)
110
110
 
111
- return super()._download(url=url, output_path=output_file_path)
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
- return AccountGroupingItem.model_validate(resp_dict)
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
- return AccountGroupingItem.model_validate(resp_dict)
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
- return AccountGroupingItem.model_validate(resp_dict)
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.async_results import AsyncResults
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.async_result_set.get(
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.async_result_set._wait_for_async_results(
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.async_result_set.get(
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.async_result_set._wait_for_async_result(
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 = ApiAsyncResult.model_validate(resp_dict)
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 = ApiAsyncResult.model_validate(resp_dict)
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 = ApiAsyncResult.model_validate(resp_dict)
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 {number_of_async_results} "
125
+ f"{elapsed_time:.1f} seconds. Loop {i=} and {len(async_results)} "
95
126
  "to check"
96
127
  )
97
128
 
98
- if number_of_async_results <= 0:
129
+ if not async_results:
99
130
  break
100
131
 
101
- if number_of_async_results == 1:
102
- new_async_results = [self.get_by_id(sorted_async_results_ids[0])]
103
- else:
104
- query = {"id": {"$in": sorted_async_results_ids}}
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
- async_results = []
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
- if sleep_seconds > 0:
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
 
@@ -173,4 +173,7 @@ class BaseSet:
173
173
  except (UnicodeDecodeError, json.JSONDecodeError):
174
174
  pass
175
175
 
176
- raise ValidationError(http_error_msg)
176
+ if resp.status < 500:
177
+ raise ValidationError(http_error_msg)
178
+
179
+ raise UnexpectedServerError(http_error_msg)