ansys / grantami-recordlists

A Python wrapper for GRANTA MI RecordLists API
https://recordlists.grantami.docs.pyansys.com/
MIT License
3 stars 0 forks source link

Calling `RecordLIstsApiClient.search_for_lists()` with `include_items=True` gives 400 response #209

Closed CalebMDMI closed 6 months ago

CalebMDMI commented 6 months ago

🔍 Before submitting the issue

🐞 Description of the bug

When calling RecordListsApiClient.search_for_lists(criterion, include_items=True), I got the following exception message:

Traceback (most recent call last):
    search_results = PyGranta_API_client.search_for_lists(criterion, include_items=True)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\CalebWilson\Code\Testing\record_lists\env\Lib\site-packages\ansys\grantami\recordlists\_connection.py", line 141, in search_for_lists
    search_info = self.list_management_api.api_v1_lists_search_post(
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\CalebWilson\Code\Testing\record_lists\env\Lib\site-packages\ansys\grantami\serverapi_openapi\api\list_management_api.py", line 1086, in api_v1_lists_search_post
    data = self._api_v1_lists_search_post_with_http_info(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\CalebWilson\Code\Testing\record_lists\env\Lib\site-packages\ansys\grantami\serverapi_openapi\api\list_management_api.py", line 1147, in _api_v1_lists_search_post_with_http_info
    return self.api_client.call_api(
           ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\CalebWilson\Code\Testing\record_lists\env\Lib\site-packages\ansys\openapi\common\_api_client.py", line 456, in call_api
    return self.__call_api(
           ^^^^^^^^^^^^^^^^
  File "C:\Users\CalebWilson\Code\Testing\record_lists\env\Lib\site-packages\ansys\openapi\common\_api_client.py", line 177, in __call_api
    response_data = self.request(
                    ^^^^^^^^^^^^^
  File "C:\Users\CalebWilson\Code\Testing\record_lists\env\Lib\site-packages\ansys\openapi\common\_api_client.py", line 544, in request
    return handle_response(
           ^^^^^^^^^^^^^^^^
  File "C:\Users\CalebWilson\Code\Testing\record_lists\env\Lib\site-packages\ansys\openapi\common\_util.py", line 360, in handle_response
    raise ApiException.from_response(response)
ansys.openapi.common._exceptions.ApiException: ApiException(400, 'Bad Request')
HTTP response headers: {'Cache-Control': 'private', 'Content-Type': 'application/json; charset=utf-8', 'Server': 'Microsoft-IIS/10.0', 'Persistent-Auth': 'true', 'X-Frame-Options': 'SAMEORIGIN', 'X-Content-Type-Options': 'nosniff', 'Strict-Transport-Security': 'max-age=31536000', 'Date': 'Thu, 04 Apr 2024 14:24:49 GMT', 'Content-Length': '74'}
HTTP response body: {"code":400,"message":"An item with the same key has already been added."}

This generated the following server log:

2024-04-04 10:17:08,054 [413] ERROR Granta.Server.Api.Filters.ExceptionHandlingAttribute - An exception occurred
System.ArgumentException: An item with the same key has already been added.
   at System.ThrowHelper.ThrowArgumentException(ExceptionResource resource)
   at System.Collections.Generic.Dictionary`2.Insert(TKey key, TValue value, Boolean add)
   at System.Linq.Enumerable.ToDictionary[TSource,TKey,TElement](IEnumerable`1 source, Func`2 keySelector, Func`2 elementSelector, IEqualityComparer`1 comparer)
   at Granta.MI.Components.Services.RecordListsService.<DecorateRecordListsInternal>d__23.MoveNext()
   at Granta.MI.Components.Services.RecordListsService.<Search>d__21.MoveNext()
   at System.Linq.Enumerable.WhereSelectEnumerableIterator`2.MoveNext()
   at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
   at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
   at Granta.Server.Api.Controllers.Lists.ListManagementController.Search(RecordListSearchRequest searchRequest)
   at lambda_method(Closure , Object , Object[] )
   at Microsoft.AspNetCore.Mvc.Internal.ActionMethodExecutor.SyncObjectResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.<InvokeActionMethodAsync>d__12.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.<InvokeNextActionFilterAsync>d__10.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Rethrow(ActionExecutedContext context)
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.<InvokeInnerFilterAsync>d__13.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.<InvokeNextExceptionFilterAsync>d__24.MoveNext()

📝 Steps to reproduce

from GRANTA_MIScriptingToolkit import granta
from ansys.grantami.recordlists import Connection, SearchCriterion
from getpass import getpass

client = getpass("Client: ")
service_layer_url = f"http://mi-{client.lower()}/mi_servicelayer"

session = granta.connect(service_layer_url, autologon=True)
db = session.get_db(f"{client} Metals Database")
table = db.get_table("Metals")

connection = Connection(service_layer_url)
connection = connection.with_autologon()
PyGranta_API_client = connection.connect()
criterion = SearchCriterion(contains_records_in_tables=[table.guid])
search_results = PyGranta_API_client.search_for_lists(criterion, include_items=True)

(Client name confidential)

💻 Which operating system are you using?

Windows

📀 Which ANSYS version are you using?

🐍 Which Python version are you using?

3.12

📦 Installed packages

-grantami-recordlists==1.1.0
ansys-grantami-serverapi-openapi==2.0.0
ansys-openapi-common==1.5.1
certifi==2024.2.2
cffi==1.16.0
charset-normalizer==3.3.2
cryptography==42.0.5
granta-miscriptingtoolkit==3.2.164
idna==3.6
jaraco.classes==3.4.0
keyring==23.13.1
more-itertools==10.2.0
pycparser==2.22
pyparsing==3.1.2
pypiwin32==223
pyspnego==0.10.2
python-dateutil==2.9.0.post0
pywin32==306
pywin32-ctypes==0.2.2
requests==2.31.0
requests-negotiate-sspi==0.5.2
requests-ntlm==1.2.0
six==1.16.0
sspilib==0.1.0
urllib3==2.2.1
Andy-Grigg commented 6 months ago

Thanks @CalebMDMI . This definitely looks like a server-side issue. I have raised this internally and I'll get back to you when I know more.

Andy-Grigg commented 6 months ago

@CalebMDMI This has been successfully reproduced in the Granta MI server. Once we have a build available with this fix I'll update our test machine and create a Pull Request that introduces a test to verify the fix.

Andy-Grigg commented 6 months ago

This issue has been fixed in Granta MI, and the associated PR adds some additional integration tests that validate this behavior.

This issue also manifested in multiple copies of the list being returned with include_items=False, typically the behavior seemed to be that a separate list result was returned for every item in the list that matched the criterion. This issue has also been resolved with the same fix in Granta MI, and now only a single list object is returned.

As a workaround, I think the best that can be done is to just manually iterate over every returned list result and only keep ones with unique list identifiers.